@juangcarmona/lighthouse-sdlc 0.6.0 → 0.7.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 CHANGED
@@ -4,6 +4,8 @@ This package provides the provider-neutral delivery contract for Lighthouse adop
4
4
 
5
5
  It owns the six lifecycle skills, their command entrypoints, route, stage and gate descriptors, deterministic triage and decomposition projections, rebuildable state checks and the provider-neutral control-plane contract.
6
6
 
7
+ It publishes `command-aliases.json` as the versioned, machine-readable mapping from selected `/lh:*` aliases to canonical `/lighthouse:*` command assets. Run `lighthouse-sdlc aliases --format json` to inspect it; the configured agent host decides how to expose the aliases.
8
+
7
9
  It also provides the `browser-e2e` evidence contract for Playwright journeys. The contract belongs to the SDLC because it records delivery evidence; the tests, journeys and browser configuration remain owned by the adopter.
8
10
 
9
11
  It does not run agents, store transcripts, dispatch work between repositories or replace ProductShape, ArcShape, OpenSpec or an agent host.
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "./schemas/command-aliases.schema.json",
3
+ "schemaVersion": 1,
4
+ "contractVersion": 1,
5
+ "package": { "name": "@juangcarmona/lighthouse-sdlc" },
6
+ "canonicalNamespace": "/lighthouse",
7
+ "aliasNamespace": "/lh",
8
+ "aliases": [
9
+ { "alias": "/lh:control", "canonical": "/lighthouse:control-plane", "asset": "commands/control-plane.md" },
10
+ { "alias": "/lh:gh-decompose", "canonical": "/lighthouse:github-decompose", "asset": "commands/github-decompose.md" },
11
+ { "alias": "/lh:gh-triage", "canonical": "/lighthouse:github-triage", "asset": "commands/github-triage.md" },
12
+ { "alias": "/lh:result", "canonical": "/lighthouse:stage-result", "asset": "commands/stage-result.md" },
13
+ { "alias": "/lh:e2e", "canonical": "/lighthouse:browser-e2e", "asset": "commands/browser-e2e.md" },
14
+ { "alias": "/lh:caps", "canonical": "/lighthouse:capabilities", "asset": "commands/capabilities.md" },
15
+ { "alias": "/lh:labels", "canonical": "/lighthouse:reconcile-labels", "asset": "commands/reconcile-labels.md" },
16
+ { "alias": "/lh:gh-labels", "canonical": "/lighthouse:github-labels", "asset": "commands/github-labels.md" },
17
+ { "alias": "/lh:mem", "canonical": "/lighthouse:memory", "asset": "commands/memory.md" },
18
+ { "alias": "/lh:impl", "canonical": "/lighthouse:implement", "asset": "commands/implement.md" }
19
+ ]
20
+ }
@@ -0,0 +1,5 @@
1
+ # `/lighthouse:aliases`
2
+
3
+ Use `lighthouse-sdlc aliases --format json` to inspect the SDLC package's versioned agent-command aliases. The output maps each `/lh:*` alias to one canonical `/lighthouse:*` command and its packaged command asset.
4
+
5
+ The alias listing is read-only. The configured agent host decides how to expose the aliases and must reference the canonical command asset instead of copying or maintaining a second command implementation.
package/contract.json CHANGED
@@ -10,7 +10,7 @@
10
10
  { "name": "integrate", "skill": "skills/integrate/SKILL.md", "command": "commands/integrate.md", "gate": "done" },
11
11
  { "name": "review", "skill": "skills/review/SKILL.md", "command": "commands/review.md", "gate": null }
12
12
  ],
13
- "utilities": ["status", "route", "state", "stage-result", "control-plane", "memory", "launch", "decompose", "stacked", "browser-e2e", "github-triage", "github-decompose", "reconcile-labels", "github-labels"],
13
+ "utilities": ["status", "aliases", "route", "state", "stage-result", "control-plane", "memory", "launch", "decompose", "stacked", "browser-e2e", "github-triage", "github-decompose", "reconcile-labels", "github-labels"],
14
14
  "routes": ["supervised", "plan-only", "unattended", "hotfix", "stacked"],
15
15
  "metaCommands": ["ff", "hotfix", "decompose", "stacked"],
16
16
  "gates": ["triaged", "ready", "planned", "done"],
@@ -26,6 +26,8 @@
26
26
  "capabilitiesSchema": "schemas/capabilities.schema.json",
27
27
  "browserE2eSchema": "schemas/browser-e2e.schema.json",
28
28
  "capabilityCatalog": "capabilities.json",
29
- "capabilityCatalogSchema": "schemas/capability-catalog.schema.json"
29
+ "capabilityCatalogSchema": "schemas/capability-catalog.schema.json",
30
+ "commandAliases": "command-aliases.json",
31
+ "commandAliasesSchema": "schemas/command-aliases.schema.json"
30
32
  }
31
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juangcarmona/lighthouse-sdlc",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Provider-neutral SDLC skills, commands and route contracts for Lighthouse adopters",
6
6
  "repository": {
@@ -21,6 +21,7 @@
21
21
  },
22
22
  "files": [
23
23
  "commands",
24
+ "command-aliases.json",
24
25
  "contract.json",
25
26
  "capabilities.json",
26
27
  "gates.json",
@@ -46,6 +47,7 @@
46
47
  "./routes": "./src/routes.mjs",
47
48
  "./reconcile": "./src/reconcile.mjs",
48
49
  "./capabilities": "./src/capabilities.mjs",
50
+ "./aliases": "./src/aliases.mjs",
49
51
  "./stacked": "./src/stacked.mjs",
50
52
  "./workflow": "./src/workflow.mjs",
51
53
  "./browser-e2e": "./src/browser-e2e.mjs",
@@ -59,6 +61,8 @@
59
61
  "./schemas/capabilities.schema.json": "./schemas/capabilities.schema.json",
60
62
  "./schemas/capability-catalog.schema.json": "./schemas/capability-catalog.schema.json",
61
63
  "./schemas/browser-e2e.schema.json": "./schemas/browser-e2e.schema.json",
62
- "./capabilities.json": "./capabilities.json"
64
+ "./capabilities.json": "./capabilities.json",
65
+ "./command-aliases.json": "./command-aliases.json",
66
+ "./schemas/command-aliases.schema.json": "./schemas/command-aliases.schema.json"
63
67
  }
64
68
  }
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/juangcarmona/lighthouse/schemas/lighthouse-sdlc-command-aliases.schema.json",
4
+ "title": "Lighthouse SDLC command aliases",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "contractVersion", "package", "canonicalNamespace", "aliasNamespace", "aliases"],
8
+ "properties": {
9
+ "$schema": {"type": "string"},
10
+ "schemaVersion": {"const": 1},
11
+ "contractVersion": {"const": 1},
12
+ "package": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["name"],
16
+ "properties": {"name": {"const": "@juangcarmona/lighthouse-sdlc"}}
17
+ },
18
+ "canonicalNamespace": {"const": "/lighthouse"},
19
+ "aliasNamespace": {"const": "/lh"},
20
+ "aliases": {
21
+ "type": "array",
22
+ "minItems": 1,
23
+ "items": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["alias", "canonical", "asset"],
27
+ "properties": {
28
+ "alias": {"type": "string", "pattern": "^/lh:[a-z][a-z0-9-]*$"},
29
+ "canonical": {"type": "string", "pattern": "^/lighthouse:[a-z][a-z0-9-]*$"},
30
+ "asset": {"type": "string", "pattern": "^(commands|meta-commands)/[a-z0-9-]+\\.md$"}
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,57 @@
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import { constants } 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, 'command-aliases.json');
8
+ const packageName = '@juangcarmona/lighthouse-sdlc';
9
+ const aliasPattern = /^\/lh:[a-z][a-z0-9-]*$/u;
10
+ const canonicalPattern = /^\/lighthouse:[a-z][a-z0-9-]*$/u;
11
+ const assetPattern = /^(?:commands|meta-commands)\/[a-z0-9-]+\.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 async function readCommandAliases(path = contractPath) {
18
+ return JSON.parse(await readFile(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 command 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) {
49
+ const value = contract ?? await readCommandAliases();
50
+ const errors = validateCommandAliases(value);
51
+ for (const item of value.aliases ?? []) {
52
+ if (typeof item.asset === 'string' && !(await exists(resolve(packageRoot, item.asset)))) errors.push({ code: 'aliases.missing-asset', message: `alias ${item.alias} references missing asset ${item.asset}` });
53
+ }
54
+ return errors;
55
+ }
56
+
57
+ export { contractPath };
package/src/sdlc.mjs CHANGED
@@ -15,6 +15,7 @@ import { replayWorkflow } from './workflow.mjs';
15
15
  import { validateStageResult } from './contracts.mjs';
16
16
  import { browserEvidenceReport } from './browser-e2e.mjs';
17
17
  import { readCapabilityCatalog, validateCapabilityCatalog } from './capabilities.mjs';
18
+ import { readCommandAliases, validateCommandAliases, verifyCommandAliases } from './aliases.mjs';
18
19
 
19
20
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
20
21
  const contractPath = resolve(packageRoot, 'contract.json');
@@ -40,10 +41,10 @@ async function verifyContract() {
40
41
  for (const stage of stages) {
41
42
  for (const asset of [stage.skill, stage.command]) if (!(await exists(resolve(packageRoot, asset)))) problems.push(`missing stage asset ${asset}`);
42
43
  }
43
- for (const asset of [contract.assets?.routes, contract.assets?.gates, contract.assets?.stages, contract.assets?.controlPlaneSchema, contract.assets?.triageSchema, contract.assets?.stageResultSchema, contract.assets?.stateSchema, contract.assets?.decompositionSchema, contract.assets?.capabilitiesSchema, contract.assets?.capabilityCatalog, contract.assets?.capabilityCatalogSchema, contract.assets?.browserE2eSchema]) {
44
+ for (const asset of [contract.assets?.routes, contract.assets?.gates, contract.assets?.stages, contract.assets?.controlPlaneSchema, contract.assets?.triageSchema, contract.assets?.stageResultSchema, contract.assets?.stateSchema, contract.assets?.decompositionSchema, contract.assets?.capabilitiesSchema, contract.assets?.capabilityCatalog, contract.assets?.capabilityCatalogSchema, contract.assets?.browserE2eSchema, contract.assets?.commandAliases, contract.assets?.commandAliasesSchema]) {
44
45
  if (!asset || !(await exists(resolve(packageRoot, asset)))) problems.push(`missing contract asset ${asset ?? '(empty)'}`);
45
46
  }
46
- for (const asset of ['routes.json', 'gates.json', 'stages.json', 'schemas/routes.schema.json', 'schemas/gates.schema.json', 'schemas/stages.schema.json', 'schemas/control-plane.schema.json', 'schemas/triage.schema.json', 'schemas/stage-result.schema.json', 'schemas/workflow-state.schema.json', 'schemas/decomposition.schema.json', 'schemas/capabilities.schema.json', 'schemas/capability-catalog.schema.json', 'schemas/browser-e2e.schema.json', 'src/browser-e2e.mjs', 'src/capabilities.mjs', 'src/contracts.mjs', 'src/control-plane.mjs', 'src/decompose.mjs', 'src/github.mjs', 'src/memory.mjs', 'src/reconcile.mjs', 'src/routes.mjs', 'src/stacked.mjs', 'src/triage.mjs', 'src/workflow.mjs']) {
47
+ for (const asset of ['routes.json', 'gates.json', 'stages.json', 'command-aliases.json', 'schemas/routes.schema.json', 'schemas/gates.schema.json', 'schemas/stages.schema.json', 'schemas/control-plane.schema.json', 'schemas/triage.schema.json', 'schemas/stage-result.schema.json', 'schemas/workflow-state.schema.json', 'schemas/decomposition.schema.json', 'schemas/capabilities.schema.json', 'schemas/capability-catalog.schema.json', 'schemas/browser-e2e.schema.json', 'schemas/command-aliases.schema.json', 'src/aliases.mjs', 'src/browser-e2e.mjs', 'src/capabilities.mjs', 'src/contracts.mjs', 'src/control-plane.mjs', 'src/decompose.mjs', 'src/github.mjs', 'src/memory.mjs', 'src/reconcile.mjs', 'src/routes.mjs', 'src/stacked.mjs', 'src/triage.mjs', 'src/workflow.mjs']) {
47
48
  if (!(await exists(resolve(packageRoot, asset)))) problems.push(`missing package asset ${asset}`);
48
49
  }
49
50
  try {
@@ -65,11 +66,30 @@ async function verifyContract() {
65
66
  const catalog = await readCapabilityCatalog();
66
67
  problems.push(...validateCapabilityCatalog(catalog).map((error) => `${error.code}: ${error.message}`));
67
68
  } catch (error) { problems.push(`Cannot read capabilities.json: ${error.message}`); }
69
+ try {
70
+ const aliases = await readCommandAliases();
71
+ problems.push(...(await verifyCommandAliases(aliases)).map((error) => `${error.code}: ${error.message}`));
72
+ } catch (error) { problems.push(`Cannot read command-aliases.json: ${error.message}`); }
68
73
  for (const command of ['status', ...expectedStages, ...(contract.utilities ?? []).filter((command) => command !== 'status')]) if (!(await exists(resolve(packageRoot, `commands/${command}.md`)))) problems.push(`missing command asset ${command}`);
69
74
  for (const metaCommand of contract.metaCommands ?? []) if (!(await exists(resolve(packageRoot, `meta-commands/${metaCommand}.md`)))) problems.push(`missing meta-command asset ${metaCommand}`);
70
75
  return problems;
71
76
  }
72
77
 
78
+ async function runAliases(args) {
79
+ const format = option(args, '--format') ?? 'json';
80
+ if (!['json', 'text'].includes(format)) { console.error('Usage: lighthouse-sdlc aliases [--format <json|text>]'); process.exit(2); }
81
+ const contract = await readCommandAliases();
82
+ const errors = await verifyCommandAliases(contract);
83
+ const result = { schemaVersion: 1, status: errors.length ? 'invalid' : 'valid', valid: errors.length === 0, package: contract.package, contractVersion: contract.contractVersion, canonicalNamespace: contract.canonicalNamespace, aliasNamespace: contract.aliasNamespace, aliases: contract.aliases, errors };
84
+ if (format === 'json') console.log(JSON.stringify(result, null, 2));
85
+ else {
86
+ console.log(`SDLC command aliases: ${result.status}`);
87
+ for (const alias of result.aliases) console.log(` ${alias.alias} -> ${alias.canonical}`);
88
+ for (const error of result.errors) console.error(` ${error.code}: ${error.message}`);
89
+ }
90
+ if (errors.length) process.exit(1);
91
+ }
92
+
73
93
  function option(args, name) {
74
94
  const index = args.indexOf(name);
75
95
  if (index !== -1) return args[index + 1];
@@ -328,14 +348,16 @@ async function runGithubLabels(args) {
328
348
  }
329
349
 
330
350
  const command = process.argv[2] ?? 'verify';
331
- if (!['contract', 'verify', 'triage', 'state', 'stage-result', 'browser-e2e', 'control-plane', 'memory', 'launch', 'decompose', 'stacked', 'route', 'github-triage', 'github-decompose', 'reconcile-labels', 'github-labels'].includes(command)) {
332
- console.error('Usage: lighthouse-sdlc <contract|verify|triage|state|stage-result|browser-e2e|control-plane|memory|launch|decompose|stacked|route|github-triage|github-decompose|reconcile-labels|github-labels>');
351
+ if (!['contract', 'verify', 'aliases', 'triage', 'state', 'stage-result', 'browser-e2e', 'control-plane', 'memory', 'launch', 'decompose', 'stacked', 'route', 'github-triage', 'github-decompose', 'reconcile-labels', 'github-labels'].includes(command)) {
352
+ console.error('Usage: lighthouse-sdlc <contract|verify|aliases|triage|state|stage-result|browser-e2e|control-plane|memory|launch|decompose|stacked|route|github-triage|github-decompose|reconcile-labels|github-labels>');
333
353
  process.exit(2);
334
354
  }
335
355
  if (command === 'contract') {
336
356
  console.log(await readFile(contractPath, 'utf8'));
337
357
  } else if (command === 'triage') {
338
358
  await runTriage(process.argv.slice(3));
359
+ } else if (command === 'aliases') {
360
+ await runAliases(process.argv.slice(3));
339
361
  } else if (command === 'state') {
340
362
  await runState(process.argv.slice(3));
341
363
  } else if (command === 'control-plane') {
@@ -371,4 +393,4 @@ if (command === 'contract') {
371
393
  console.log('Lighthouse SDLC package contract is valid.');
372
394
  }
373
395
 
374
- export { contractPath, runTriage, verifyContract, runState, runStageResult, runBrowserE2e, runControlPlane, runMemory, runLaunch, runDecompose, runRoute, runStacked, runGithubTriage, runGithubDecompose, runReconcileLabels, runGithubLabels };
396
+ export { contractPath, runTriage, verifyContract, runAliases, runState, runStageResult, runBrowserE2e, runControlPlane, runMemory, runLaunch, runDecompose, runRoute, runStacked, runGithubTriage, runGithubDecompose, runReconcileLabels, runGithubLabels };