@juangcarmona/lighthouse 0.8.0 → 0.9.1
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/package.json +4 -4
- package/src/create-dotnet.mjs +27 -1
- package/src/lighthouse.mjs +104 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juangcarmona/lighthouse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lighthouse adoption and verification CLI",
|
|
6
6
|
"repository": {
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"node": ">=22"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@arcshape/cli": "0.1.
|
|
23
|
+
"@arcshape/cli": "0.1.6",
|
|
24
24
|
"@juangcarmona/lighthouse-ui": "0.2.0",
|
|
25
|
-
"@prodshape/cli": "0.
|
|
26
|
-
"@juangcarmona/lighthouse-sdlc": "0.
|
|
25
|
+
"@prodshape/cli": "0.23.0",
|
|
26
|
+
"@juangcarmona/lighthouse-sdlc": "0.9.0"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"src/lighthouse.mjs",
|
package/src/create-dotnet.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { dirname, join, resolve } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { createRequire } from 'node:module';
|
|
@@ -9,6 +10,23 @@ const templateRoot = resolve(packageRoot, 'assets/dotnet-template');
|
|
|
9
10
|
const cliPackage = JSON.parse(await readFile(resolve(packageRoot, 'package.json'), 'utf8'));
|
|
10
11
|
const require = createRequire(import.meta.url);
|
|
11
12
|
const sdlcContract = JSON.parse(await readFile(require.resolve('@juangcarmona/lighthouse-sdlc/contract.json'), 'utf8'));
|
|
13
|
+
const sdlcPackageRoot = dirname(require.resolve('@juangcarmona/lighthouse-sdlc/contract.json'));
|
|
14
|
+
const sdlcWorkflowPaths = [
|
|
15
|
+
'.github/workflows/lighthouse-sdlc-router.yml',
|
|
16
|
+
'.github/workflows/lighthouse-sdlc-triage.md',
|
|
17
|
+
'.github/workflows/lighthouse-sdlc-triage.lock.yml',
|
|
18
|
+
'.github/workflows/lighthouse-sdlc-refine.md',
|
|
19
|
+
'.github/workflows/lighthouse-sdlc-refine.lock.yml',
|
|
20
|
+
'.github/workflows/lighthouse-sdlc-propose.md',
|
|
21
|
+
'.github/workflows/lighthouse-sdlc-propose.lock.yml',
|
|
22
|
+
'.github/workflows/lighthouse-sdlc-implement.md',
|
|
23
|
+
'.github/workflows/lighthouse-sdlc-implement.lock.yml',
|
|
24
|
+
'.github/workflows/lighthouse-sdlc-integrate.md',
|
|
25
|
+
'.github/workflows/lighthouse-sdlc-integrate.lock.yml',
|
|
26
|
+
'.github/workflows/lighthouse-sdlc-review.md',
|
|
27
|
+
'.github/workflows/lighthouse-sdlc-review.lock.yml',
|
|
28
|
+
'.github/workflows/shared/lighthouse-sdlc-stage.md',
|
|
29
|
+
];
|
|
12
30
|
const args = process.argv.slice(2);
|
|
13
31
|
const target = args[0] ? resolve(args[0]) : null;
|
|
14
32
|
const name = args.includes('--name') ? args[args.indexOf('--name') + 1] : null;
|
|
@@ -90,7 +108,7 @@ try {
|
|
|
90
108
|
product: name, application: { shape: 'backend', stacks: ['dotnet'] }, profiles: ['dotnet-backend'],
|
|
91
109
|
integrations: { productShape: null, arcShape: null },
|
|
92
110
|
packages: { sdlc: { name: '@juangcarmona/lighthouse-sdlc', version: sdlcContract.version } },
|
|
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'],
|
|
111
|
+
managed: ['LIGHTHOUSE.md', 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.lighthouse/sdlc.json', ...sdlcWorkflowPaths, '.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'],
|
|
94
112
|
repository: {
|
|
95
113
|
layout: ['src', 'docs', 'infra', 'scripts'], publicSite: { enabled: true },
|
|
96
114
|
conventions: { commits: 'conventional', branchPattern: '^(feat|fix|docs|refactor|test|chore|build|ci|perf|revert)/[1-9]\\d*-[a-z0-9]+(?:-[a-z0-9]+)*$' },
|
|
@@ -107,6 +125,14 @@ try {
|
|
|
107
125
|
|
|
108
126
|
const initialized = (await import('node:child_process')).spawnSync(process.execPath, [resolve(stage, 'scripts/lighthouse.mjs'), 'init'], { cwd: stage, encoding: 'utf8' });
|
|
109
127
|
if (initialized.status !== 0) throw new Error(initialized.stderr || 'Projection generation failed.');
|
|
128
|
+
await copy(resolve(sdlcPackageRoot, 'workflows'), resolve(stage, '.github/workflows'));
|
|
129
|
+
const upgradePath = resolve(stage, '.lighthouse/upgrade.json');
|
|
130
|
+
const upgrade = JSON.parse(await readFile(upgradePath, 'utf8'));
|
|
131
|
+
for (const path of sdlcWorkflowPaths) {
|
|
132
|
+
const content = await readFile(resolve(stage, path), 'utf8');
|
|
133
|
+
upgrade.files[path] = { hash: createHash('sha256').update(content).digest('hex'), scope: 'sdlc' };
|
|
134
|
+
}
|
|
135
|
+
await writeFile(upgradePath, `${JSON.stringify(upgrade, null, 2)}\n`);
|
|
110
136
|
if (existing) await rmdir(target);
|
|
111
137
|
await rename(stage, target);
|
|
112
138
|
process.stdout.write(`Created ${name} in ${target}. Run dotnet restore ${name}.slnx, then npm run verify.\n`);
|
package/src/lighthouse.mjs
CHANGED
|
@@ -13,6 +13,7 @@ let sdlcCapabilities;
|
|
|
13
13
|
let sdlcReconcile;
|
|
14
14
|
let sdlcGithub;
|
|
15
15
|
let sdlcAliases;
|
|
16
|
+
let sdlcEvidence;
|
|
16
17
|
try {
|
|
17
18
|
[sdlcCapabilities, sdlcReconcile, sdlcGithub] = await Promise.all([
|
|
18
19
|
import('@juangcarmona/lighthouse-sdlc/capabilities'),
|
|
@@ -21,6 +22,7 @@ try {
|
|
|
21
22
|
]);
|
|
22
23
|
} catch { /* Standalone generated starters can run without the optional SDLC package. */ }
|
|
23
24
|
try { sdlcAliases = await import('@juangcarmona/lighthouse-sdlc/aliases'); } catch { /* Older standalone installations may not publish aliases. */ }
|
|
25
|
+
try { sdlcEvidence = await import('@juangcarmona/lighthouse-sdlc/evidence'); } catch { /* Older standalone installations may not publish evidence. */ }
|
|
24
26
|
|
|
25
27
|
const root = process.cwd();
|
|
26
28
|
const packageRequire = createRequire(import.meta.url);
|
|
@@ -38,6 +40,22 @@ const upgradeStatePath = resolve(root, upgradeStateRelative);
|
|
|
38
40
|
const layout = ['src', 'docs', 'infra', 'scripts'];
|
|
39
41
|
const sdlcPackage = { name: '@juangcarmona/lighthouse-sdlc', version: installedPackageVersion('@juangcarmona/lighthouse-sdlc', 'contract.json') ?? '0.1.0' };
|
|
40
42
|
const uiPackage = { name: '@juangcarmona/lighthouse-ui', version: installedPackageVersion('@juangcarmona/lighthouse-ui', 'DESIGN.md') ?? '0.1.0' };
|
|
43
|
+
const sdlcWorkflowPaths = Object.freeze([
|
|
44
|
+
'.github/workflows/lighthouse-sdlc-router.yml',
|
|
45
|
+
'.github/workflows/lighthouse-sdlc-triage.md',
|
|
46
|
+
'.github/workflows/lighthouse-sdlc-triage.lock.yml',
|
|
47
|
+
'.github/workflows/lighthouse-sdlc-refine.md',
|
|
48
|
+
'.github/workflows/lighthouse-sdlc-refine.lock.yml',
|
|
49
|
+
'.github/workflows/lighthouse-sdlc-propose.md',
|
|
50
|
+
'.github/workflows/lighthouse-sdlc-propose.lock.yml',
|
|
51
|
+
'.github/workflows/lighthouse-sdlc-implement.md',
|
|
52
|
+
'.github/workflows/lighthouse-sdlc-implement.lock.yml',
|
|
53
|
+
'.github/workflows/lighthouse-sdlc-integrate.md',
|
|
54
|
+
'.github/workflows/lighthouse-sdlc-integrate.lock.yml',
|
|
55
|
+
'.github/workflows/lighthouse-sdlc-review.md',
|
|
56
|
+
'.github/workflows/lighthouse-sdlc-review.lock.yml',
|
|
57
|
+
'.github/workflows/shared/lighthouse-sdlc-stage.md',
|
|
58
|
+
]);
|
|
41
59
|
const managedPaths = [
|
|
42
60
|
projection,
|
|
43
61
|
'scripts/lighthouse-guard.mjs',
|
|
@@ -45,6 +63,7 @@ const managedPaths = [
|
|
|
45
63
|
'.husky/pre-push',
|
|
46
64
|
'.github/workflows/lighthouse.yml',
|
|
47
65
|
'.lighthouse/sdlc.json',
|
|
66
|
+
...sdlcWorkflowPaths,
|
|
48
67
|
'.agents/skills/lighthouse-init/SKILL.md',
|
|
49
68
|
upgradeStateRelative,
|
|
50
69
|
'.lighthouse/commands.json',
|
|
@@ -58,13 +77,13 @@ const profileManagedPaths = Object.freeze({
|
|
|
58
77
|
'mobile-expo': ['docs/profiles/mobile-expo/README.md', 'docs/profiles/mobile-expo/adoption.md', 'docs/profiles/mobile-expo/guardrails.md', 'docs/profiles/mobile-expo/testing.md', '.agents/skills/lighthouse-mobile-expo/SKILL.md'],
|
|
59
78
|
'mobile-maui': ['docs/profiles/mobile-maui/README.md', 'docs/profiles/mobile-maui/adoption.md', 'docs/profiles/mobile-maui/guardrails.md', 'docs/profiles/mobile-maui/testing.md', '.agents/skills/lighthouse-mobile-maui/SKILL.md'],
|
|
60
79
|
});
|
|
61
|
-
const previousManagedPaths =
|
|
80
|
+
const previousManagedPaths = [projection, 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.lighthouse/sdlc.json', '.agents/skills/lighthouse-init/SKILL.md'];
|
|
62
81
|
const legacyManagedPaths = [projection];
|
|
63
82
|
const preSemanticManagedPaths = [projection, 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.lighthouse/sdlc.json', upgradeStateRelative];
|
|
64
83
|
const preSemanticWithoutUpgradePaths = preSemanticManagedPaths.slice(0, -1);
|
|
65
84
|
const supportedProfiles = readProfileCatalog().profiles.map(({ id }) => id);
|
|
66
85
|
const branchPattern = '^(feat|fix|docs|refactor|test|chore|build|ci|perf|revert)/[1-9]\\d*-[a-z0-9]+(?:-[a-z0-9]+)*$';
|
|
67
|
-
const authorityVersions = { productShape: '0.
|
|
86
|
+
const authorityVersions = { productShape: '0.23.0', arcShape: '0.1.6' };
|
|
68
87
|
const upgradeSchema = './node_modules/@juangcarmona/lighthouse/assets/schemas/lighthouse-upgrade.schema.json';
|
|
69
88
|
const usage = 'Usage: lighthouse <init|aliases|create-dotnet|profiles|authorities|reconcile|upgrade|capabilities|verify> [--name <product>] [--plan-file <path>] [--profile <profile>] [--scope <harness|sdlc|all>] [--format <text|json>] [--adopt-existing] [--dry-run] [--apply]';
|
|
70
89
|
|
|
@@ -82,7 +101,7 @@ function managedPathsFor(manifest) {
|
|
|
82
101
|
function upgradeScopesFor(manifest) {
|
|
83
102
|
return {
|
|
84
103
|
harness: [projection, 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.agents/skills/lighthouse-init/SKILL.md', ...((manifest?.profiles ?? []).flatMap((profile) => profileManagedPaths[profile] ?? []))],
|
|
85
|
-
sdlc: ['.lighthouse/sdlc.json', '.lighthouse/commands.json'],
|
|
104
|
+
sdlc: ['.lighthouse/sdlc.json', '.lighthouse/commands.json', ...sdlcWorkflowPaths],
|
|
86
105
|
};
|
|
87
106
|
}
|
|
88
107
|
|
|
@@ -91,6 +110,18 @@ function packageAsset(packageName, entry) {
|
|
|
91
110
|
return readFileSync(path, 'utf8');
|
|
92
111
|
}
|
|
93
112
|
|
|
113
|
+
function sdlcWorkflowFiles() {
|
|
114
|
+
const files = {};
|
|
115
|
+
for (const target of sdlcWorkflowPaths) {
|
|
116
|
+
try {
|
|
117
|
+
files[target] = packageAsset('@juangcarmona/lighthouse-sdlc', `workflows/${target.slice('.github/workflows/'.length)}`);
|
|
118
|
+
} catch {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return files;
|
|
123
|
+
}
|
|
124
|
+
|
|
94
125
|
function profileAssets(manifest) {
|
|
95
126
|
const assets = {};
|
|
96
127
|
if ((manifest?.profiles ?? []).includes('visual-theme')) {
|
|
@@ -425,6 +456,7 @@ function managedFiles(manifest) {
|
|
|
425
456
|
'.agents/skills/lighthouse-init/SKILL.md': readFileSync(new URL('../assets/skills/init/SKILL.md', import.meta.url), 'utf8'),
|
|
426
457
|
};
|
|
427
458
|
Object.assign(files, profileAssets(manifest));
|
|
459
|
+
Object.assign(files, sdlcWorkflowFiles());
|
|
428
460
|
const controlPlane = sdlcControlPlane(manifest);
|
|
429
461
|
if (controlPlane) files['.lighthouse/sdlc.json'] = controlPlane;
|
|
430
462
|
files['.lighthouse/commands.json'] = commandAliasProjectionText(commandAliasesProjection());
|
|
@@ -1396,6 +1428,9 @@ async function verify(manifest, format = 'text') {
|
|
|
1396
1428
|
});
|
|
1397
1429
|
report.summary.unconfigured = (report.summary.unconfigured ?? 0) + (pending.length ? 1 : 0);
|
|
1398
1430
|
}
|
|
1431
|
+
const evidenceResult = await verifyEvidenceIndex();
|
|
1432
|
+
report.results.push(evidenceResult);
|
|
1433
|
+
report.summary[evidenceResult.status] = (report.summary[evidenceResult.status] ?? 0) + 1;
|
|
1399
1434
|
if (format === 'json') {
|
|
1400
1435
|
console.log(JSON.stringify(report, null, 2));
|
|
1401
1436
|
if (report.problems.length) process.exitCode = 1;
|
|
@@ -1405,6 +1440,72 @@ async function verify(manifest, format = 'text') {
|
|
|
1405
1440
|
console.log('Lighthouse manifest, repository boundaries and managed projection are valid.');
|
|
1406
1441
|
}
|
|
1407
1442
|
|
|
1443
|
+
async function verifyEvidenceIndex() {
|
|
1444
|
+
const evidencePath = '.lighthouse/evidence/index.json';
|
|
1445
|
+
const result = {
|
|
1446
|
+
id: 'foundation.evidence',
|
|
1447
|
+
owner: '@juangcarmona/lighthouse-sdlc',
|
|
1448
|
+
level: 'recommended',
|
|
1449
|
+
kind: 'hard',
|
|
1450
|
+
verification: 'deterministic',
|
|
1451
|
+
status: 'unconfigured',
|
|
1452
|
+
evidence: [],
|
|
1453
|
+
problems: [],
|
|
1454
|
+
nextAction: `lighthouse-sdlc evidence --build --input <records.json> --output ${evidencePath}`,
|
|
1455
|
+
};
|
|
1456
|
+
if (!(await exists(resolve(root, evidencePath)))) return result;
|
|
1457
|
+
if (!sdlcEvidence) {
|
|
1458
|
+
result.status = 'unavailable';
|
|
1459
|
+
result.problems.push('The installed SDLC package does not expose the evidence contract.');
|
|
1460
|
+
result.nextAction = 'Install a Lighthouse SDLC package that exports the evidence contract.';
|
|
1461
|
+
return result;
|
|
1462
|
+
}
|
|
1463
|
+
let index;
|
|
1464
|
+
try { index = JSON.parse(await readFile(resolve(root, evidencePath), 'utf8')); }
|
|
1465
|
+
catch (error) {
|
|
1466
|
+
result.status = 'failed';
|
|
1467
|
+
result.problems.push(`Cannot read ${evidencePath}: ${error.message}`);
|
|
1468
|
+
result.nextAction = `Repair ${evidencePath} and run lighthouse verify again.`;
|
|
1469
|
+
return result;
|
|
1470
|
+
}
|
|
1471
|
+
const errors = sdlcEvidence.validateEvidenceIndex(index);
|
|
1472
|
+
for (const error of errors) result.problems.push(`${error.field ?? 'index'}: ${error.message}`);
|
|
1473
|
+
const records = [];
|
|
1474
|
+
for (const reference of Array.isArray(index.records) ? index.records : []) {
|
|
1475
|
+
if (!reference || typeof reference !== 'object' || typeof reference.path !== 'string') continue;
|
|
1476
|
+
const path = resolve(root, '.lighthouse/evidence', reference.path);
|
|
1477
|
+
if (!(await exists(path))) {
|
|
1478
|
+
result.problems.push(`Missing evidence record ${reference.path}.`);
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
try {
|
|
1482
|
+
const content = await readFile(path, 'utf8');
|
|
1483
|
+
const record = JSON.parse(content);
|
|
1484
|
+
for (const error of sdlcEvidence.validateEvidenceRecord(record)) result.problems.push(`${reference.path}: ${error.message}`);
|
|
1485
|
+
const observedHash = createHash('sha256').update(JSON.stringify(record)).digest('hex');
|
|
1486
|
+
if (observedHash !== reference.sha256) result.problems.push(`${reference.path}: SHA-256 does not match the index.`);
|
|
1487
|
+
result.evidence.push({ id: reference.id, path: reference.path, status: record.status, sha256: observedHash });
|
|
1488
|
+
records.push(record);
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
result.problems.push(`${reference.path}: ${error.message}`);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
if (!errors.length && records.length === (index.records ?? []).length) {
|
|
1494
|
+
const rebuilt = sdlcEvidence.buildEvidenceIndex(records, {
|
|
1495
|
+
indexId: index.indexId,
|
|
1496
|
+
runId: index.runId,
|
|
1497
|
+
repository: index.repository,
|
|
1498
|
+
revision: index.revision,
|
|
1499
|
+
subjects: (index.coverage ?? []).map((item) => item.subject),
|
|
1500
|
+
recordPaths: Object.fromEntries((index.records ?? []).map((item) => [item.id, item.path])),
|
|
1501
|
+
});
|
|
1502
|
+
if (rebuilt.errors.length || JSON.stringify(rebuilt.index?.coverage) !== JSON.stringify(index.coverage)) result.problems.push('Evidence coverage does not match the referenced records; rebuild the index.');
|
|
1503
|
+
}
|
|
1504
|
+
result.status = result.problems.length ? 'failed' : 'passed';
|
|
1505
|
+
result.nextAction = result.problems.length ? `Repair ${evidencePath} or its records, then run lighthouse verify again.` : null;
|
|
1506
|
+
return result;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1408
1509
|
async function profiles(manifest, format = 'text') {
|
|
1409
1510
|
const composition = composeProfiles(readProfileCatalog(), manifest);
|
|
1410
1511
|
if (format === 'json') {
|