@juangcarmona/lighthouse 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/assets/command-aliases.json +11 -0
- package/assets/profiles/catalog.json +1019 -17
- package/assets/profiles/mobile-expo/README.md +19 -0
- package/assets/profiles/mobile-expo/SKILL.md +14 -0
- package/assets/profiles/mobile-expo/adoption.md +11 -0
- package/assets/profiles/mobile-expo/guardrails.md +14 -0
- package/assets/profiles/mobile-expo/testing.md +13 -0
- package/assets/profiles/mobile-maui/README.md +19 -0
- package/assets/profiles/mobile-maui/SKILL.md +14 -0
- package/assets/profiles/mobile-maui/adoption.md +11 -0
- package/assets/profiles/mobile-maui/guardrails.md +13 -0
- package/assets/profiles/mobile-maui/testing.md +13 -0
- package/assets/profiles/pwa-capability/README.md +19 -0
- package/assets/profiles/pwa-capability/SKILL.md +14 -0
- package/assets/profiles/pwa-capability/adoption.md +9 -0
- package/assets/profiles/pwa-capability/guardrails.md +13 -0
- package/assets/profiles/pwa-capability/offline.md +11 -0
- package/assets/profiles/pwa-capability/testing.md +15 -0
- package/assets/profiles/react-web-platform/README.md +24 -0
- package/assets/profiles/react-web-platform/SKILL.md +18 -0
- package/assets/profiles/react-web-platform/adoption.md +17 -0
- package/assets/profiles/react-web-platform/guardrails.md +17 -0
- package/assets/profiles/react-web-platform/structure.md +36 -0
- package/assets/profiles/react-web-platform/testing.md +15 -0
- package/assets/profiles/web-bff/README.md +22 -0
- package/assets/profiles/web-bff/SKILL.md +16 -0
- package/assets/profiles/web-bff/adoption.md +9 -0
- package/assets/profiles/web-bff/security.md +16 -0
- package/assets/profiles/web-bff/testing.md +14 -0
- package/assets/schemas/command-aliases.schema.json +35 -0
- package/assets/schemas/init-plan.schema.json +40 -0
- package/assets/schemas/profile-catalog.schema.json +286 -20
- package/assets/skills/init/SKILL.md +30 -0
- package/package.json +5 -3
- package/src/aliases.mjs +56 -0
- package/src/create-dotnet.mjs +5 -1
- package/src/init-plan.mjs +62 -0
- package/src/lighthouse.mjs +443 -14
- package/src/profiles.mjs +78 -5
|
@@ -4,59 +4,325 @@
|
|
|
4
4
|
"title": "Lighthouse profile catalog",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
|
-
"required": [
|
|
7
|
+
"required": [
|
|
8
|
+
"$schema",
|
|
9
|
+
"schemaVersion",
|
|
10
|
+
"profiles"
|
|
11
|
+
],
|
|
8
12
|
"properties": {
|
|
9
|
-
"$schema": {
|
|
10
|
-
|
|
13
|
+
"$schema": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
},
|
|
16
|
+
"schemaVersion": {
|
|
17
|
+
"const": 1
|
|
18
|
+
},
|
|
11
19
|
"profiles": {
|
|
12
20
|
"type": "array",
|
|
13
|
-
"items": {
|
|
21
|
+
"items": {
|
|
22
|
+
"$ref": "#/$defs/profile"
|
|
23
|
+
}
|
|
14
24
|
}
|
|
15
25
|
},
|
|
16
26
|
"$defs": {
|
|
17
27
|
"profile": {
|
|
18
28
|
"type": "object",
|
|
19
29
|
"additionalProperties": false,
|
|
20
|
-
"required": [
|
|
30
|
+
"required": [
|
|
31
|
+
"id",
|
|
32
|
+
"owner",
|
|
33
|
+
"purpose",
|
|
34
|
+
"appliesTo",
|
|
35
|
+
"surfaces",
|
|
36
|
+
"prerequisites",
|
|
37
|
+
"dependencies",
|
|
38
|
+
"incompatibleWith",
|
|
39
|
+
"libraries",
|
|
40
|
+
"structure",
|
|
41
|
+
"commands",
|
|
42
|
+
"guardrails",
|
|
43
|
+
"failureConsequence",
|
|
44
|
+
"repairHints",
|
|
45
|
+
"exceptions",
|
|
46
|
+
"combinations",
|
|
47
|
+
"consumerFixture",
|
|
48
|
+
"upgradeStrategy",
|
|
49
|
+
"verification"
|
|
50
|
+
],
|
|
21
51
|
"properties": {
|
|
22
|
-
"id": {
|
|
23
|
-
|
|
24
|
-
|
|
52
|
+
"id": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
|
55
|
+
},
|
|
56
|
+
"owner": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"minLength": 1
|
|
59
|
+
},
|
|
60
|
+
"purpose": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"minLength": 1
|
|
63
|
+
},
|
|
25
64
|
"appliesTo": {
|
|
26
65
|
"type": "object",
|
|
27
66
|
"additionalProperties": false,
|
|
28
|
-
"required": [
|
|
67
|
+
"required": [
|
|
68
|
+
"shapes",
|
|
69
|
+
"stacks"
|
|
70
|
+
],
|
|
29
71
|
"properties": {
|
|
30
|
-
"shapes": {
|
|
31
|
-
|
|
72
|
+
"shapes": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "string"
|
|
76
|
+
},
|
|
77
|
+
"minItems": 1,
|
|
78
|
+
"uniqueItems": true
|
|
79
|
+
},
|
|
80
|
+
"stacks": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"items": {
|
|
83
|
+
"type": "string"
|
|
84
|
+
},
|
|
85
|
+
"uniqueItems": true
|
|
86
|
+
}
|
|
32
87
|
}
|
|
33
88
|
},
|
|
34
|
-
"surfaces": {
|
|
89
|
+
"surfaces": {
|
|
90
|
+
"type": "array",
|
|
91
|
+
"items": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"minLength": 1
|
|
94
|
+
},
|
|
95
|
+
"minItems": 1,
|
|
96
|
+
"uniqueItems": true
|
|
97
|
+
},
|
|
35
98
|
"verification": {
|
|
36
99
|
"type": "object",
|
|
37
100
|
"additionalProperties": false,
|
|
38
|
-
"required": [
|
|
101
|
+
"required": [
|
|
102
|
+
"layers"
|
|
103
|
+
],
|
|
39
104
|
"properties": {
|
|
40
105
|
"layers": {
|
|
41
106
|
"type": "array",
|
|
42
|
-
"items": {
|
|
107
|
+
"items": {
|
|
108
|
+
"$ref": "#/$defs/layer"
|
|
109
|
+
},
|
|
43
110
|
"minItems": 1,
|
|
44
111
|
"uniqueItems": true
|
|
45
112
|
}
|
|
46
113
|
}
|
|
114
|
+
},
|
|
115
|
+
"prerequisites": {
|
|
116
|
+
"type": "array",
|
|
117
|
+
"items": {
|
|
118
|
+
"type": "string",
|
|
119
|
+
"minLength": 1
|
|
120
|
+
},
|
|
121
|
+
"uniqueItems": true
|
|
122
|
+
},
|
|
123
|
+
"dependencies": {
|
|
124
|
+
"type": "array",
|
|
125
|
+
"items": {
|
|
126
|
+
"$ref": "#/$defs/profileId"
|
|
127
|
+
},
|
|
128
|
+
"uniqueItems": true
|
|
129
|
+
},
|
|
130
|
+
"incompatibleWith": {
|
|
131
|
+
"type": "array",
|
|
132
|
+
"items": {
|
|
133
|
+
"$ref": "#/$defs/profileId"
|
|
134
|
+
},
|
|
135
|
+
"uniqueItems": true
|
|
136
|
+
},
|
|
137
|
+
"libraries": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"items": {
|
|
140
|
+
"type": "string",
|
|
141
|
+
"minLength": 1
|
|
142
|
+
},
|
|
143
|
+
"uniqueItems": true
|
|
144
|
+
},
|
|
145
|
+
"structure": {
|
|
146
|
+
"type": "array",
|
|
147
|
+
"items": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"minLength": 1
|
|
150
|
+
},
|
|
151
|
+
"uniqueItems": true
|
|
152
|
+
},
|
|
153
|
+
"commands": {
|
|
154
|
+
"type": "array",
|
|
155
|
+
"items": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"minLength": 1
|
|
158
|
+
},
|
|
159
|
+
"uniqueItems": true
|
|
160
|
+
},
|
|
161
|
+
"guardrails": {
|
|
162
|
+
"type": "array",
|
|
163
|
+
"items": {
|
|
164
|
+
"$ref": "#/$defs/guardrail"
|
|
165
|
+
},
|
|
166
|
+
"uniqueItems": true
|
|
167
|
+
},
|
|
168
|
+
"failureConsequence": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"minLength": 1
|
|
171
|
+
},
|
|
172
|
+
"repairHints": {
|
|
173
|
+
"type": "array",
|
|
174
|
+
"items": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"minLength": 1
|
|
177
|
+
},
|
|
178
|
+
"minItems": 1
|
|
179
|
+
},
|
|
180
|
+
"exceptions": {
|
|
181
|
+
"type": "array",
|
|
182
|
+
"items": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"minLength": 1
|
|
185
|
+
},
|
|
186
|
+
"minItems": 1
|
|
187
|
+
},
|
|
188
|
+
"combinations": {
|
|
189
|
+
"type": "array",
|
|
190
|
+
"items": {
|
|
191
|
+
"$ref": "#/$defs/combination"
|
|
192
|
+
},
|
|
193
|
+
"uniqueItems": true
|
|
194
|
+
},
|
|
195
|
+
"consumerFixture": {
|
|
196
|
+
"type": "string",
|
|
197
|
+
"minLength": 1
|
|
198
|
+
},
|
|
199
|
+
"upgradeStrategy": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"minLength": 1
|
|
47
202
|
}
|
|
48
203
|
}
|
|
49
204
|
},
|
|
50
205
|
"layer": {
|
|
51
206
|
"type": "object",
|
|
52
207
|
"additionalProperties": false,
|
|
53
|
-
"required": [
|
|
208
|
+
"required": [
|
|
209
|
+
"id",
|
|
210
|
+
"level",
|
|
211
|
+
"kind",
|
|
212
|
+
"verification"
|
|
213
|
+
],
|
|
54
214
|
"properties": {
|
|
55
|
-
"id": {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
215
|
+
"id": {
|
|
216
|
+
"enum": [
|
|
217
|
+
"static",
|
|
218
|
+
"unit",
|
|
219
|
+
"architecture",
|
|
220
|
+
"integration",
|
|
221
|
+
"functional",
|
|
222
|
+
"browser-e2e",
|
|
223
|
+
"operational"
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
"level": {
|
|
227
|
+
"enum": [
|
|
228
|
+
"required",
|
|
229
|
+
"recommended",
|
|
230
|
+
"optional"
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
"kind": {
|
|
234
|
+
"enum": [
|
|
235
|
+
"hard",
|
|
236
|
+
"soft"
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
"verification": {
|
|
240
|
+
"enum": [
|
|
241
|
+
"deterministic",
|
|
242
|
+
"semantic",
|
|
243
|
+
"human"
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
"command": {
|
|
247
|
+
"type": [
|
|
248
|
+
"string",
|
|
249
|
+
"null"
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
"profileId": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
|
257
|
+
},
|
|
258
|
+
"guardrail": {
|
|
259
|
+
"type": "object",
|
|
260
|
+
"additionalProperties": false,
|
|
261
|
+
"required": [
|
|
262
|
+
"id",
|
|
263
|
+
"level",
|
|
264
|
+
"kind",
|
|
265
|
+
"verification",
|
|
266
|
+
"command",
|
|
267
|
+
"description"
|
|
268
|
+
],
|
|
269
|
+
"properties": {
|
|
270
|
+
"id": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"minLength": 1
|
|
273
|
+
},
|
|
274
|
+
"level": {
|
|
275
|
+
"enum": [
|
|
276
|
+
"required",
|
|
277
|
+
"recommended",
|
|
278
|
+
"optional"
|
|
279
|
+
]
|
|
280
|
+
},
|
|
281
|
+
"kind": {
|
|
282
|
+
"enum": [
|
|
283
|
+
"hard",
|
|
284
|
+
"soft"
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
"verification": {
|
|
288
|
+
"enum": [
|
|
289
|
+
"deterministic",
|
|
290
|
+
"semantic",
|
|
291
|
+
"human"
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
"command": {
|
|
295
|
+
"type": [
|
|
296
|
+
"string",
|
|
297
|
+
"null"
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
"description": {
|
|
301
|
+
"type": "string",
|
|
302
|
+
"minLength": 1
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"combination": {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"additionalProperties": false,
|
|
309
|
+
"required": [
|
|
310
|
+
"with",
|
|
311
|
+
"purpose"
|
|
312
|
+
],
|
|
313
|
+
"properties": {
|
|
314
|
+
"with": {
|
|
315
|
+
"type": "array",
|
|
316
|
+
"items": {
|
|
317
|
+
"$ref": "#/$defs/profileId"
|
|
318
|
+
},
|
|
319
|
+
"minItems": 1,
|
|
320
|
+
"uniqueItems": true
|
|
321
|
+
},
|
|
322
|
+
"purpose": {
|
|
323
|
+
"type": "string",
|
|
324
|
+
"minLength": 1
|
|
325
|
+
}
|
|
60
326
|
}
|
|
61
327
|
}
|
|
62
328
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lighthouse-init
|
|
3
|
+
description: Turn a natural-language project request into a reviewable Lighthouse initialization plan.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lighthouse semantic initialization
|
|
7
|
+
|
|
8
|
+
Use this skill when an adopter describes a project in product terms and wants Lighthouse to prepare its foundation.
|
|
9
|
+
|
|
10
|
+
## Inputs
|
|
11
|
+
|
|
12
|
+
1. Read the installed profile catalog and the repository's current Lighthouse manifest when one exists.
|
|
13
|
+
2. Treat a reference project as optional. If the adopter names one, record its location and purpose; do not assume it exists or copy it.
|
|
14
|
+
3. Ask only the questions needed to resolve the application shape, stacks, profiles and material profile combinations. Keep unresolved questions in the plan instead of guessing.
|
|
15
|
+
|
|
16
|
+
## Plan
|
|
17
|
+
|
|
18
|
+
Write a JSON plan that validates against `assets/schemas/init-plan.schema.json` with `schemaVersion: 1`, `product`, `intent`, `application`, `profiles`, optional `reference` metadata and `openQuestions`.
|
|
19
|
+
|
|
20
|
+
Select profiles from the installed catalog. Do not invent a profile, make a stack mandatory, or turn a reference into a requirement. An absent reference is represented by an omitted `reference` field or `{"kind":"none"}`.
|
|
21
|
+
|
|
22
|
+
## Apply
|
|
23
|
+
|
|
24
|
+
Review the generated plan with the adopter, then run `lighthouse init --plan-file <path>` for a dry run. Use `--format json` for agent consumption. Apply only after the adopter explicitly authorizes `lighthouse init --plan-file <path> --apply`.
|
|
25
|
+
|
|
26
|
+
The CLI owns deterministic validation, profile compatibility, conflict detection and local writes. It does not inspect or copy the reference and it performs no remote writes. A plan with `openQuestions` is not ready to apply.
|
|
27
|
+
|
|
28
|
+
Use `lighthouse aliases --format json` to discover the installed `/lh:*` aliases and their canonical `/lighthouse:*` commands. The generated `.lighthouse/commands.json` is the host-readable projection; aliases reference the package-owned command or skill asset and never create a second implementation.
|
|
29
|
+
|
|
30
|
+
After applying, run `lighthouse verify` and report the created, preserved and conflicting paths. Keep product and architecture work in ProductShape and ArcShape's own routes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juangcarmona/lighthouse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lighthouse adoption and verification CLI",
|
|
6
6
|
"repository": {
|
|
@@ -20,13 +20,15 @@
|
|
|
20
20
|
"node": ">=22"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@arcshape/cli": "0.1.
|
|
23
|
+
"@arcshape/cli": "0.1.4",
|
|
24
24
|
"@juangcarmona/lighthouse-ui": "0.2.0",
|
|
25
25
|
"@prodshape/cli": "0.22.0",
|
|
26
|
-
"@juangcarmona/lighthouse-sdlc": "0.
|
|
26
|
+
"@juangcarmona/lighthouse-sdlc": "0.7.0"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"src/lighthouse.mjs",
|
|
30
|
+
"src/aliases.mjs",
|
|
31
|
+
"src/init-plan.mjs",
|
|
30
32
|
"src/guardrails.mjs",
|
|
31
33
|
"assets",
|
|
32
34
|
"README.md",
|
package/src/aliases.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import { constants, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
7
|
+
const contractPath = resolve(packageRoot, 'assets/command-aliases.json');
|
|
8
|
+
const packageName = '@juangcarmona/lighthouse';
|
|
9
|
+
const aliasPattern = /^\/lh:[a-z][a-z0-9-]*$/u;
|
|
10
|
+
const canonicalPattern = /^\/lighthouse:[a-z][a-z0-9-]*$/u;
|
|
11
|
+
const assetPattern = /^skills\/[a-z0-9-]+\/SKILL\.md$/u;
|
|
12
|
+
|
|
13
|
+
async function exists(path) {
|
|
14
|
+
try { await access(path, constants.F_OK); return true; } catch { return false; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readCommandAliases(path = contractPath) {
|
|
18
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateCommandAliases(contract) {
|
|
22
|
+
const errors = [];
|
|
23
|
+
if (!contract || typeof contract !== 'object' || Array.isArray(contract)) return [{ code: 'aliases.invalid', message: 'command alias contract must be an object' }];
|
|
24
|
+
if (contract.schemaVersion !== 1) errors.push({ code: 'aliases.schema-version', message: 'command alias schemaVersion must be 1' });
|
|
25
|
+
if (contract.contractVersion !== 1) errors.push({ code: 'aliases.contract-version', message: 'command alias contractVersion must be 1' });
|
|
26
|
+
if (contract.package?.name !== packageName) errors.push({ code: 'aliases.package', message: `command alias contract package must be ${packageName}` });
|
|
27
|
+
if (contract.canonicalNamespace !== '/lighthouse') errors.push({ code: 'aliases.canonical-namespace', message: 'canonical namespace must be /lighthouse' });
|
|
28
|
+
if (contract.aliasNamespace !== '/lh') errors.push({ code: 'aliases.alias-namespace', message: 'alias namespace must be /lh' });
|
|
29
|
+
if (!Array.isArray(contract.aliases) || contract.aliases.length === 0) return [...errors, { code: 'aliases.list', message: 'command alias contract must declare at least one alias' }];
|
|
30
|
+
const aliases = new Set();
|
|
31
|
+
const canonicals = new Set();
|
|
32
|
+
for (const [index, item] of contract.aliases.entries()) {
|
|
33
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
34
|
+
errors.push({ code: 'aliases.entry', message: `alias ${index} must be an object` });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (typeof item.alias !== 'string' || !aliasPattern.test(item.alias)) errors.push({ code: 'aliases.alias', message: `alias ${index} must use the /lh:<verb> form` });
|
|
38
|
+
if (typeof item.canonical !== 'string' || !canonicalPattern.test(item.canonical)) errors.push({ code: 'aliases.canonical', message: `alias ${index} must target a /lighthouse:<verb> command` });
|
|
39
|
+
if (typeof item.asset !== 'string' || !assetPattern.test(item.asset)) errors.push({ code: 'aliases.asset', message: `alias ${index} must reference a packaged skill asset` });
|
|
40
|
+
if (aliases.has(item.alias)) errors.push({ code: 'aliases.duplicate', message: `alias ${item.alias} is declared more than once` });
|
|
41
|
+
if (canonicals.has(item.canonical)) errors.push({ code: 'aliases.duplicate-target', message: `canonical command ${item.canonical} has more than one alias` });
|
|
42
|
+
aliases.add(item.alias);
|
|
43
|
+
canonicals.add(item.canonical);
|
|
44
|
+
}
|
|
45
|
+
return errors;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function verifyCommandAliases(contract = readCommandAliases()) {
|
|
49
|
+
const errors = validateCommandAliases(contract);
|
|
50
|
+
for (const item of contract.aliases ?? []) {
|
|
51
|
+
if (typeof item.asset === 'string' && !(await exists(resolve(packageRoot, 'assets', item.asset)))) errors.push({ code: 'aliases.missing-asset', message: `alias ${item.alias} references missing asset ${item.asset}` });
|
|
52
|
+
}
|
|
53
|
+
return errors;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { contractPath };
|
package/src/create-dotnet.mjs
CHANGED
|
@@ -74,19 +74,23 @@ try {
|
|
|
74
74
|
await writeFile(solutionFile, `<Solution>\n${projects.map((project) => ` ${project}`).join('\n')}\n</Solution>\n`);
|
|
75
75
|
|
|
76
76
|
await copy(resolve(packageRoot, 'src/lighthouse.mjs'), resolve(stage, 'scripts/lighthouse.mjs'));
|
|
77
|
+
await copy(resolve(packageRoot, 'src/aliases.mjs'), resolve(stage, 'scripts/aliases.mjs'));
|
|
78
|
+
await copy(resolve(packageRoot, 'src/init-plan.mjs'), resolve(stage, 'scripts/init-plan.mjs'));
|
|
77
79
|
await copy(resolve(packageRoot, 'src/guardrails.mjs'), resolve(stage, 'scripts/guardrails.mjs'));
|
|
78
80
|
await copy(resolve(packageRoot, 'src/profiles.mjs'), resolve(stage, 'scripts/profiles.mjs'));
|
|
79
81
|
await copy(resolve(packageRoot, 'src/verify-dotnet.mjs'), resolve(stage, 'scripts/verify-dotnet.mjs'));
|
|
80
82
|
await copy(resolve(packageRoot, 'assets/guardrails'), resolve(stage, 'assets/guardrails'));
|
|
81
83
|
await copy(resolve(packageRoot, 'assets/profiles'), resolve(stage, 'assets/profiles'));
|
|
82
84
|
await copy(resolve(packageRoot, 'assets/schemas'), resolve(stage, 'schemas'));
|
|
85
|
+
await copy(resolve(packageRoot, 'assets/command-aliases.json'), resolve(stage, 'assets/command-aliases.json'));
|
|
86
|
+
await copy(resolve(packageRoot, 'assets/skills/init'), resolve(stage, 'assets/skills/init'));
|
|
83
87
|
|
|
84
88
|
await writeFile(resolve(stage, 'lighthouse.json'), `${JSON.stringify({
|
|
85
89
|
$schema: './schemas/lighthouse-manifest.schema.json', schemaVersion: 1, baseline: '0.1.0',
|
|
86
90
|
product: name, application: { shape: 'backend', stacks: ['dotnet'] }, profiles: ['dotnet-backend'],
|
|
87
91
|
integrations: { productShape: null, arcShape: null },
|
|
88
92
|
packages: { sdlc: { name: '@juangcarmona/lighthouse-sdlc', version: sdlcContract.version } },
|
|
89
|
-
managed: ['LIGHTHOUSE.md', 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.lighthouse/sdlc.json', '.lighthouse/upgrade.json', 'docs/profiles/dotnet/README.md', 'docs/profiles/dotnet/adoption.md', 'docs/profiles/dotnet/architecture.md', 'docs/profiles/dotnet/guardrails.md', 'docs/profiles/dotnet/practices.md', 'docs/profiles/dotnet/testing-and-development.md', 'docs/profiles/dotnet/use-cases.md', '.agents/skills/lighthouse-dotnet/SKILL.md'],
|
|
93
|
+
managed: ['LIGHTHOUSE.md', 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.lighthouse/sdlc.json', '.agents/skills/lighthouse-init/SKILL.md', '.lighthouse/upgrade.json', '.lighthouse/commands.json', 'docs/profiles/dotnet/README.md', 'docs/profiles/dotnet/adoption.md', 'docs/profiles/dotnet/architecture.md', 'docs/profiles/dotnet/guardrails.md', 'docs/profiles/dotnet/practices.md', 'docs/profiles/dotnet/testing-and-development.md', 'docs/profiles/dotnet/use-cases.md', '.agents/skills/lighthouse-dotnet/SKILL.md'],
|
|
90
94
|
repository: {
|
|
91
95
|
layout: ['src', 'docs', 'infra', 'scripts'], publicSite: { enabled: true },
|
|
92
96
|
conventions: { commits: 'conventional', branchPattern: '^(feat|fix|docs|refactor|test|chore|build|ci|perf|revert)/[1-9]\\d*-[a-z0-9]+(?:-[a-z0-9]+)*$' },
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const productNamePattern = /^[A-Za-z0-9][A-Za-z0-9 ._-]{0,63}$/u;
|
|
2
|
+
const shapes = new Set(['unknown', 'frontend', 'backend', 'full-stack', 'service', 'library', 'tooling', 'cli', 'worker', 'mobile']);
|
|
3
|
+
const referenceKinds = new Set(['none', 'local', 'repository']);
|
|
4
|
+
|
|
5
|
+
function isObject(value) {
|
|
6
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function uniqueStrings(value, name, { required = false } = {}) {
|
|
10
|
+
if (!Array.isArray(value) || (required && value.length === 0) || value.some((item) => typeof item !== 'string' || item.length === 0)) {
|
|
11
|
+
return [`${name} must contain non-empty strings`];
|
|
12
|
+
}
|
|
13
|
+
return new Set(value).size === value.length ? [] : [`${name} must be unique`];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function referenceProblems(reference) {
|
|
17
|
+
if (reference === undefined || reference === null) return [];
|
|
18
|
+
if (!isObject(reference)) return ['reference must be null or an object'];
|
|
19
|
+
const problems = [];
|
|
20
|
+
if (!referenceKinds.has(reference.kind)) problems.push('reference.kind must be none, local or repository');
|
|
21
|
+
if (reference.kind !== 'none' && (typeof reference.location !== 'string' || reference.location.length === 0)) {
|
|
22
|
+
problems.push('reference.location is required for local and repository references');
|
|
23
|
+
}
|
|
24
|
+
if (reference.kind === 'none' && 'location' in reference) problems.push('reference.location is not allowed when reference.kind is none');
|
|
25
|
+
if ('purpose' in reference && (typeof reference.purpose !== 'string' || reference.purpose.length === 0)) problems.push('reference.purpose must be a non-empty string');
|
|
26
|
+
return problems;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function validateInitPlan(plan, supportedProfiles) {
|
|
30
|
+
if (!isObject(plan)) return ['plan must be an object'];
|
|
31
|
+
const problems = [];
|
|
32
|
+
const allowed = new Set(['$schema', 'schemaVersion', 'product', 'intent', 'application', 'profiles', 'reference', 'openQuestions']);
|
|
33
|
+
for (const key of Object.keys(plan)) if (!allowed.has(key)) problems.push(`unknown field ${key}`);
|
|
34
|
+
if (plan.schemaVersion !== 1) problems.push('schemaVersion must be 1');
|
|
35
|
+
if (typeof plan.product !== 'string' || !productNamePattern.test(plan.product)) problems.push('product must be a readable name of 1–64 characters');
|
|
36
|
+
if (typeof plan.intent !== 'string' || plan.intent.trim().length === 0) problems.push('intent must be a non-empty string');
|
|
37
|
+
if (!isObject(plan.application)) problems.push('application must be an object');
|
|
38
|
+
else {
|
|
39
|
+
if (!shapes.has(plan.application.shape)) problems.push(`application.shape must be one of ${[...shapes].join(', ')}`);
|
|
40
|
+
problems.push(...uniqueStrings(plan.application.stacks, 'application.stacks'));
|
|
41
|
+
}
|
|
42
|
+
problems.push(...uniqueStrings(plan.profiles, 'profiles'));
|
|
43
|
+
for (const profile of plan.profiles ?? []) {
|
|
44
|
+
if (!supportedProfiles.includes(profile)) problems.push(`unknown profile ${profile}; use ${supportedProfiles.join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
problems.push(...referenceProblems(plan.reference));
|
|
47
|
+
problems.push(...uniqueStrings(plan.openQuestions, 'openQuestions'));
|
|
48
|
+
return problems;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function hasUnresolvedQuestions(plan) {
|
|
52
|
+
return Array.isArray(plan.openQuestions) && plan.openQuestions.length > 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function manifestFromInitPlan(plan, createManifest) {
|
|
56
|
+
const manifest = createManifest(plan.product, plan.profiles);
|
|
57
|
+
manifest.application = {
|
|
58
|
+
shape: plan.application.shape,
|
|
59
|
+
stacks: [...plan.application.stacks],
|
|
60
|
+
};
|
|
61
|
+
return manifest;
|
|
62
|
+
}
|