@juangcarmona/lighthouse 0.10.0 → 0.11.2

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 CHANGED
@@ -16,6 +16,8 @@ Use `lighthouse reconcile` to preview missing Lighthouse-managed paths in an exi
16
16
 
17
17
  Use `lighthouse upgrade` to preview a versioned foundation update. The command is dry-run by default, accepts `--scope harness`, `--scope sdlc` or `--scope all`, preserves customized projections and stops when ownership evidence is missing. Add `--apply` only after reviewing the plan, then run `lighthouse verify`.
18
18
 
19
+ Agent workflows are opt-in through the SDLC package installer. Initialization does not create an entire worker catalog. Upgrading an older adoption releases workflow ownership while preserving the existing files for explicit review and cleanup.
20
+
19
21
  The harness package owns the baseline command and verification contract. The SDLC package owns lifecycle skills, commands, metacommands, routes and gates, and `lighthouse init` records its selected version in `.lighthouse/sdlc.json`. The UI package owns visual tokens, native recipes, `DESIGN.md` and its agent skill. Selecting `visual-theme` projects the guide and skill into the adopter and enables its deterministic guardrail. Stack profiles own their source templates, guidance and checks; the .NET creator is the packaged entry point for the selected backend profile.
20
22
 
21
23
  ProductShape, ArcShape and OpenSpec keep their own files and commands. Add them through their supported integration routes when the project needs them, and keep their versions and managed paths visible in the project configuration. In an existing repository, run `lighthouse authorities --adopt-existing` to validate the current ProductShape and ArcShape surfaces without running either initializer; review the dry-run plan and add `--apply` only to record both exact versions in Lighthouse-owned configuration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juangcarmona/lighthouse",
3
- "version": "0.10.0",
3
+ "version": "0.11.2",
4
4
  "type": "module",
5
5
  "description": "Lighthouse adoption and verification CLI",
6
6
  "repository": {
@@ -23,7 +23,7 @@
23
23
  "@arcshape/cli": "0.1.6",
24
24
  "@juangcarmona/lighthouse-ui": "0.2.0",
25
25
  "@prodshape/cli": "0.23.0",
26
- "@juangcarmona/lighthouse-sdlc": "0.11.0"
26
+ "@juangcarmona/lighthouse-sdlc": "0.13.0"
27
27
  },
28
28
  "files": [
29
29
  "src/lighthouse.mjs",
@@ -1,6 +1,5 @@
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';
4
3
  import { dirname, join, resolve } from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
6
5
  import { createRequire } from 'node:module';
@@ -10,23 +9,6 @@ const templateRoot = resolve(packageRoot, 'assets/dotnet-template');
10
9
  const cliPackage = JSON.parse(await readFile(resolve(packageRoot, 'package.json'), 'utf8'));
11
10
  const require = createRequire(import.meta.url);
12
11
  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
- ];
30
12
  const args = process.argv.slice(2);
31
13
  const target = args[0] ? resolve(args[0]) : null;
32
14
  const name = args.includes('--name') ? args[args.indexOf('--name') + 1] : null;
@@ -109,7 +91,7 @@ try {
109
91
  product: name, application: { shape: 'backend', stacks: ['dotnet'] }, profiles: ['dotnet-backend'],
110
92
  integrations: { productShape: null, arcShape: null },
111
93
  packages: { sdlc: { name: '@juangcarmona/lighthouse-sdlc', version: sdlcContract.version } },
112
- 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
+ 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'],
113
95
  repository: {
114
96
  layout: ['src', 'docs', 'infra', 'scripts'], publicSite: { enabled: true },
115
97
  conventions: { commits: 'conventional', branchPattern: '^(feat|fix|docs|refactor|test|chore|build|ci|perf|revert)/[1-9]\\d*-[a-z0-9]+(?:-[a-z0-9]+)*$' },
@@ -126,14 +108,6 @@ try {
126
108
 
127
109
  const initialized = (await import('node:child_process')).spawnSync(process.execPath, [resolve(stage, 'scripts/lighthouse.mjs'), 'init'], { cwd: stage, encoding: 'utf8' });
128
110
  if (initialized.status !== 0) throw new Error(initialized.stderr || 'Projection generation failed.');
129
- await copy(resolve(sdlcPackageRoot, 'workflows'), resolve(stage, '.github/workflows'));
130
- const upgradePath = resolve(stage, '.lighthouse/upgrade.json');
131
- const upgrade = JSON.parse(await readFile(upgradePath, 'utf8'));
132
- for (const path of sdlcWorkflowPaths) {
133
- const content = await readFile(resolve(stage, path), 'utf8');
134
- upgrade.files[path] = { hash: createHash('sha256').update(content).digest('hex'), scope: 'sdlc' };
135
- }
136
- await writeFile(upgradePath, `${JSON.stringify(upgrade, null, 2)}\n`);
137
111
  if (existing) await rmdir(target);
138
112
  await rename(stage, target);
139
113
  process.stdout.write(`Created ${name} in ${target}. Run dotnet restore ${name}.slnx, then npm run verify.\n`);
@@ -41,22 +41,6 @@ const upgradeStatePath = resolve(root, upgradeStateRelative);
41
41
  const layout = ['src', 'docs', 'infra', 'scripts'];
42
42
  const sdlcPackage = { name: '@juangcarmona/lighthouse-sdlc', version: installedPackageVersion('@juangcarmona/lighthouse-sdlc', 'contract.json') ?? '0.1.0' };
43
43
  const uiPackage = { name: '@juangcarmona/lighthouse-ui', version: installedPackageVersion('@juangcarmona/lighthouse-ui', 'DESIGN.md') ?? '0.1.0' };
44
- const sdlcWorkflowPaths = Object.freeze([
45
- '.github/workflows/lighthouse-sdlc-router.yml',
46
- '.github/workflows/lighthouse-sdlc-triage.md',
47
- '.github/workflows/lighthouse-sdlc-triage.lock.yml',
48
- '.github/workflows/lighthouse-sdlc-refine.md',
49
- '.github/workflows/lighthouse-sdlc-refine.lock.yml',
50
- '.github/workflows/lighthouse-sdlc-propose.md',
51
- '.github/workflows/lighthouse-sdlc-propose.lock.yml',
52
- '.github/workflows/lighthouse-sdlc-implement.md',
53
- '.github/workflows/lighthouse-sdlc-implement.lock.yml',
54
- '.github/workflows/lighthouse-sdlc-integrate.md',
55
- '.github/workflows/lighthouse-sdlc-integrate.lock.yml',
56
- '.github/workflows/lighthouse-sdlc-review.md',
57
- '.github/workflows/lighthouse-sdlc-review.lock.yml',
58
- '.github/workflows/shared/lighthouse-sdlc-stage.md',
59
- ]);
60
44
  const managedPaths = [
61
45
  projection,
62
46
  'scripts/lighthouse-guard.mjs',
@@ -64,7 +48,6 @@ const managedPaths = [
64
48
  '.husky/pre-push',
65
49
  '.github/workflows/lighthouse.yml',
66
50
  '.lighthouse/sdlc.json',
67
- ...sdlcWorkflowPaths,
68
51
  '.agents/skills/lighthouse-init/SKILL.md',
69
52
  upgradeStateRelative,
70
53
  '.lighthouse/commands.json',
@@ -102,7 +85,7 @@ function managedPathsFor(manifest) {
102
85
  function upgradeScopesFor(manifest) {
103
86
  return {
104
87
  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] ?? []))],
105
- sdlc: ['.lighthouse/sdlc.json', '.lighthouse/commands.json', ...sdlcWorkflowPaths],
88
+ sdlc: ['.lighthouse/sdlc.json', '.lighthouse/commands.json'],
106
89
  };
107
90
  }
108
91
 
@@ -111,18 +94,6 @@ function packageAsset(packageName, entry) {
111
94
  return readFileSync(path, 'utf8');
112
95
  }
113
96
 
114
- function sdlcWorkflowFiles() {
115
- const files = {};
116
- for (const target of sdlcWorkflowPaths) {
117
- try {
118
- files[target] = packageAsset('@juangcarmona/lighthouse-sdlc', `workflows/${target.slice('.github/workflows/'.length)}`);
119
- } catch {
120
- return {};
121
- }
122
- }
123
- return files;
124
- }
125
-
126
97
  function profileAssets(manifest) {
127
98
  const assets = {};
128
99
  if ((manifest?.profiles ?? []).includes('visual-theme')) {
@@ -306,11 +277,20 @@ function sdlcControlPlane(manifest) {
306
277
  function render(manifest) {
307
278
  const repository = normalizeRepository(manifest.repository);
308
279
  const publicSite = repository.publicSite.enabled ? 'enabled' : 'disabled';
280
+ const authorities = [
281
+ manifest.integrations.productShape ? 'ProductShape is the adopted authority for product intent and Product Changes.' : 'ProductShape is not adopted; follow the repository-owned product intent and decision records.',
282
+ manifest.integrations.arcShape ? 'ArcShape is the adopted authority for architecture documentation and decisions.' : 'ArcShape is not adopted; follow the repository-owned architecture guidance.',
283
+ ].join(' ');
284
+ const publicSiteContract = !repository.publicSite.enabled
285
+ ? 'The Lighthouse documentation site is disabled.'
286
+ : Object.values(manifest.integrations).some(version => version === null)
287
+ ? 'The Lighthouse documentation site is enabled but unavailable until both authorities are adopted and validate successfully.'
288
+ : 'The Lighthouse documentation site is enabled; publication requires successful ProductShape and ArcShape validation.';
309
289
  const selectedSdlc = normalizeSdlcPackage(manifest);
310
290
  const sdlcLine = selectedSdlc ? `- SDLC package: \`${selectedSdlc.name}@${selectedSdlc.version}\`\n` : '';
311
291
  const selectedUi = manifest.packages?.ui;
312
292
  const uiLine = selectedUi ? `- UI package: \`${selectedUi.name}@${selectedUi.version}\`\n` : '';
313
- return `<!-- Generated by Lighthouse. Edit lighthouse.json and regenerate this file. -->\n\n# ${manifest.product} repository baseline\n\n- Baseline: \`${manifest.baseline}\`\n- Product: \`${manifest.product}\`\n- Profiles: ${manifest.profiles.length ? manifest.profiles.map((x) => `\`${x}\``).join(', ') : 'none'}\n${sdlcLine}${uiLine}- ProductShape: ${manifest.integrations.productShape ?? 'not adopted'}\n- ArcShape: ${manifest.integrations.arcShape ?? 'not adopted'}\n- Public site: \`${publicSite}\`\n- Repository roots: ${repository.layout.map((x) => `\`${x}/\``).join(', ')}\n- Commit convention: \`${repository.conventions.commits}\`\n\n## Delivery contract\n\nProductShape remains the source of product intent and Product Changes. ArcShape remains the source of architecture documentation and decisions. Lighthouse installs the repository baseline and verifies the selected integrations without replacing either authority.\n\nThe repository keeps source code in \`src/\`, documentation in \`docs/\`, infrastructure in \`infra/\` and repository automation in \`scripts/\`. A project may record a narrow, owned exception, but an initializer never overwrites an existing project file.\n\nThe selected SDLC package owns lifecycle skills, commands, meta-commands, routes and gates. The \`.lighthouse/\` control plane records the selected package, command aliases and host adapters without storing agent runtime data. Use \`lighthouse aliases --format json\` or \`.lighthouse/commands.json\` to discover canonical and short agent commands.\n\nWhen the public site is enabled, its publication is allowed only after ProductShape and ArcShape validate successfully. A missing or invalid authority stops the build before deployment and reports the command that can repair the missing input.\n\nThis file is managed. Put repository-specific policy in its own documentation and keep this projection generated from \`lighthouse.json\`.\n`;
293
+ return `<!-- Generated by Lighthouse. Edit lighthouse.json and regenerate this file. -->\n\n# ${manifest.product} repository baseline\n\n- Baseline: \`${manifest.baseline}\`\n- Product: \`${manifest.product}\`\n- Profiles: ${manifest.profiles.length ? manifest.profiles.map((x) => `\`${x}\``).join(', ') : 'none'}\n${sdlcLine}${uiLine}- ProductShape: ${manifest.integrations.productShape ?? 'not adopted'}\n- ArcShape: ${manifest.integrations.arcShape ?? 'not adopted'}\n- Public site: \`${publicSite}\`\n- Repository roots: ${repository.layout.map((x) => `\`${x}/\``).join(', ')}\n- Commit convention: \`${repository.conventions.commits}\`\n\n## Delivery contract\n\n${authorities} Lighthouse installs the repository baseline and verifies the selected integrations.\n\nThe repository keeps source code in \`src/\`, documentation in \`docs/\`, infrastructure in \`infra/\` and repository automation in \`scripts/\`. A project may record a narrow, owned exception, but an initializer never overwrites an existing project file.\n\nThe selected SDLC package owns lifecycle skills, commands, meta-commands, routes and gates. The \`.lighthouse/\` control plane records the selected package, command aliases and host adapters without storing agent runtime data. Use \`lighthouse aliases --format json\` or \`.lighthouse/commands.json\` to discover canonical and short agent commands.\n\n${publicSiteContract} This route publishes product and architecture documentation. Application deployment follows the repository's own delivery policy.\n\nThis file is managed. Put repository-specific policy in its own documentation and keep this projection generated from \`lighthouse.json\`.\n`;
314
294
  }
315
295
 
316
296
  function guardrailScript(manifest) {
@@ -457,7 +437,6 @@ function managedFiles(manifest) {
457
437
  '.agents/skills/lighthouse-init/SKILL.md': readFileSync(new URL('../assets/skills/init/SKILL.md', import.meta.url), 'utf8'),
458
438
  };
459
439
  Object.assign(files, profileAssets(manifest));
460
- Object.assign(files, sdlcWorkflowFiles());
461
440
  const controlPlane = sdlcControlPlane(manifest);
462
441
  if (controlPlane) files['.lighthouse/sdlc.json'] = controlPlane;
463
442
  files['.lighthouse/commands.json'] = commandAliasProjectionText(commandAliasesProjection());
@@ -542,6 +521,10 @@ function classifyUpgradeFile(path, current, expected, recordedHash) {
542
521
  return { classification: 'conflicting', action: 'review', reason: 'ownership is not recorded for this path' };
543
522
  }
544
523
 
524
+ function isOptionalWorkflow(path) {
525
+ return /^\.github\/workflows\/lighthouse-sdlc-(router\.yml|(?:triage|refine|propose|implement|integrate|review)\.(?:md|lock\.yml))$/.test(path) || path === '.github/workflows/shared/lighthouse-sdlc-stage.md';
526
+ }
527
+
545
528
  async function planUpgrade(manifest, scopes) {
546
529
  const state = await readUpgradeState();
547
530
  const problems = stateProblems(state);
@@ -557,6 +540,9 @@ async function planUpgrade(manifest, scopes) {
557
540
  const result = classifyUpgradeFile(path, current, expected, recordedHash);
558
541
  changes.push({ path, scope: Object.entries(upgradeScopes).find(([, values]) => values.includes(path))?.[0], ...result });
559
542
  }
543
+ if (scopes.has('sdlc') && manifest.managed.some(isOptionalWorkflow)) {
544
+ changes.push({ path: 'lighthouse.json', scope: 'sdlc', classification: 'adoptable', action: 'update', reason: 'release optional workflow ownership; preserve all workflow files' });
545
+ }
560
546
  if (state?.baseline && state.baseline !== manifest.baseline) {
561
547
  changes.unshift({ path: 'baseline', scope: 'harness', classification: 'breaking', action: 'review', reason: `baseline ${state.baseline} is not compatible with ${manifest.baseline}` });
562
548
  }
@@ -688,7 +674,11 @@ function validate(manifest) {
688
674
  const preSemanticWithoutUpgrade = Array.isArray(manifest.managed)
689
675
  && manifest.managed.length === preSemanticWithoutUpgradePaths.length
690
676
  && manifest.managed.every((entry, index) => entry === preSemanticWithoutUpgradePaths[index]);
691
- if (!legacyManaged && !profilelessManaged && !previousCompleteManaged && !completeManaged && !preSemanticManaged && !preSemanticWithoutUpgrade) {
677
+ // Earlier foundations installed every workflow. Accept that ownership inventory during upgrade;
678
+ // the foundation now leaves those files to explicit workflow adoption and never deletes them.
679
+ const withoutOldWorkflows = Array.isArray(manifest.managed) ? manifest.managed.filter(path => !isOptionalWorkflow(path)) : [];
680
+ const previousWorkflowManaged = withoutOldWorkflows.length === expectedManagedPaths.length && withoutOldWorkflows.every((path, index) => path === expectedManagedPaths[index]);
681
+ if (!previousWorkflowManaged && !legacyManaged && !profilelessManaged && !previousCompleteManaged && !completeManaged && !preSemanticManaged && !preSemanticWithoutUpgrade) {
692
682
  problems.push(`managed must be ${legacyManagedPaths.join(', ')}, the previous Lighthouse guardrail set or the selected profile set`);
693
683
  }
694
684
  if ('repository' in manifest) {
@@ -990,7 +980,10 @@ async function applyUpgrade(manifest, plan) {
990
980
  for (const change of updates) {
991
981
  const target = resolve(root, change.path);
992
982
  await mkdir(dirname(target), { recursive: true });
993
- await writeTextAtomically(target, files[change.path]);
983
+ const content = change.path === 'lighthouse.json'
984
+ ? `${JSON.stringify({ ...manifest, managed: manifest.managed.filter(path => !isOptionalWorkflow(path)) }, null, 2)}\n`
985
+ : files[change.path];
986
+ await writeTextAtomically(target, content);
994
987
  }
995
988
  const next = upgradeState(manifest, files, { ...previous, lastAppliedMigration: `upgrade-${cliPackage.version}` });
996
989
  for (const path of Object.keys(next.files)) {