@juangcarmona/lighthouse 0.8.0 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juangcarmona/lighthouse",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
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.4",
23
+ "@arcshape/cli": "0.1.6",
24
24
  "@juangcarmona/lighthouse-ui": "0.2.0",
25
- "@prodshape/cli": "0.22.0",
26
- "@juangcarmona/lighthouse-sdlc": "0.7.0"
25
+ "@prodshape/cli": "0.23.0",
26
+ "@juangcarmona/lighthouse-sdlc": "0.8.0"
27
27
  },
28
28
  "files": [
29
29
  "src/lighthouse.mjs",
@@ -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`);
@@ -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 = managedPaths.slice(0, -1);
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.22.0', arcShape: '0.1.4' };
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') {