diff --git a/.changeset/3251-non-family-aliases.md b/.changeset/3251-non-family-aliases.md new file mode 100644 index 000000000..dc83a6ff0 --- /dev/null +++ b/.changeset/3251-non-family-aliases.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3305 +--- +**`command-aliases.generated.cjs` now exports `NON_FAMILY_COMMAND_ALIASES` with all 14 previously-missing commands** — the CJS manifest used by the SDK query registry only exposed the 7 "family" command arrays (state, verify, init, phase, phases, validate, roadmap). Commands registered in static catalogs (foundation + domain) had no manifest entry, so tooling that queries the manifest could not discover them. `command-manifest.non-family.ts` is extended with 10 new entries (`check.decision-coverage-plan`, `check.decision-coverage-verify`, `frontmatter.get`, `phase.mvp-mode`, `progress.bar`, `stats.json`, `task.is-behavior-adding`, `todo.match-phase`, `uat.render-checkpoint`, `workstream.list`); the other 4 were already in the source but not exported. Both the TS generated file and CJS manifest now include a `NON_FAMILY_COMMAND_ALIASES` array (40 entries, sorted by canonical). The generator and freshness check are extended to cover the non-family section. (#3305) diff --git a/get-shit-done/bin/lib/command-aliases.generated.cjs b/get-shit-done/bin/lib/command-aliases.generated.cjs index db1862976..5e5f5670d 100644 --- a/get-shit-done/bin/lib/command-aliases.generated.cjs +++ b/get-shit-done/bin/lib/command-aliases.generated.cjs @@ -1,95 +1,807 @@ 'use strict'; /** - * GENERATED FILE — state.*, verify.*, init.*, phase.*, phases.*, validate.*, and roadmap.* alias/subcommand metadata for CJS routing. - * Source: sdk/src/query/command-manifest.{state,verify,init,phase,phases,validate,roadmap}.ts + * GENERATED FILE — state.*, verify.*, init.*, phase.*, phases.*, validate.*, roadmap.*, and non-family alias/subcommand metadata for CJS routing. + * Source: sdk/src/query/command-manifest.{state,verify,init,phase,phases,validate,roadmap,non-family}.ts */ const STATE_COMMAND_ALIASES = [ - { canonical: 'state.load', aliases: [], subcommand: 'load', mutation: false }, - { canonical: 'state.json', aliases: ['state json'], subcommand: 'json', mutation: false }, - { canonical: 'state.get', aliases: ['state get'], subcommand: 'get', mutation: false }, - { canonical: 'state.update', aliases: ['state update'], subcommand: 'update', mutation: true }, - { canonical: 'state.patch', aliases: ['state patch'], subcommand: 'patch', mutation: true }, - { canonical: 'state.begin-phase', aliases: ['state begin-phase'], subcommand: 'begin-phase', mutation: true }, - { canonical: 'state.advance-plan', aliases: ['state advance-plan'], subcommand: 'advance-plan', mutation: true }, - { canonical: 'state.record-metric', aliases: ['state record-metric'], subcommand: 'record-metric', mutation: true }, - { canonical: 'state.update-progress', aliases: ['state update-progress'], subcommand: 'update-progress', mutation: true }, - { canonical: 'state.add-decision', aliases: ['state add-decision'], subcommand: 'add-decision', mutation: true }, - { canonical: 'state.add-blocker', aliases: ['state add-blocker'], subcommand: 'add-blocker', mutation: true }, - { canonical: 'state.resolve-blocker', aliases: ['state resolve-blocker'], subcommand: 'resolve-blocker', mutation: true }, - { canonical: 'state.record-session', aliases: ['state record-session'], subcommand: 'record-session', mutation: true }, - { canonical: 'state.signal-waiting', aliases: ['state signal-waiting'], subcommand: 'signal-waiting', mutation: true }, - { canonical: 'state.signal-resume', aliases: ['state signal-resume'], subcommand: 'signal-resume', mutation: true }, - { canonical: 'state.planned-phase', aliases: ['state planned-phase'], subcommand: 'planned-phase', mutation: true }, - { canonical: 'state.validate', aliases: ['state validate'], subcommand: 'validate', mutation: false }, - { canonical: 'state.sync', aliases: ['state sync'], subcommand: 'sync', mutation: true }, - { canonical: 'state.prune', aliases: ['state prune'], subcommand: 'prune', mutation: true }, - { canonical: 'state.milestone-switch', aliases: ['state milestone-switch'], subcommand: 'milestone-switch', mutation: true }, - { canonical: 'state.add-roadmap-evolution', aliases: ['state add-roadmap-evolution'], subcommand: 'add-roadmap-evolution', mutation: true }, + { + "canonical": "state.load", + "aliases": [], + "subcommand": "load", + "mutation": false + }, + { + "canonical": "state.json", + "aliases": [ + "state json" + ], + "subcommand": "json", + "mutation": false + }, + { + "canonical": "state.get", + "aliases": [ + "state get" + ], + "subcommand": "get", + "mutation": false + }, + { + "canonical": "state.update", + "aliases": [ + "state update" + ], + "subcommand": "update", + "mutation": true + }, + { + "canonical": "state.patch", + "aliases": [ + "state patch" + ], + "subcommand": "patch", + "mutation": true + }, + { + "canonical": "state.begin-phase", + "aliases": [ + "state begin-phase" + ], + "subcommand": "begin-phase", + "mutation": true + }, + { + "canonical": "state.advance-plan", + "aliases": [ + "state advance-plan" + ], + "subcommand": "advance-plan", + "mutation": true + }, + { + "canonical": "state.record-metric", + "aliases": [ + "state record-metric" + ], + "subcommand": "record-metric", + "mutation": true + }, + { + "canonical": "state.update-progress", + "aliases": [ + "state update-progress" + ], + "subcommand": "update-progress", + "mutation": true + }, + { + "canonical": "state.add-decision", + "aliases": [ + "state add-decision" + ], + "subcommand": "add-decision", + "mutation": true + }, + { + "canonical": "state.add-blocker", + "aliases": [ + "state add-blocker" + ], + "subcommand": "add-blocker", + "mutation": true + }, + { + "canonical": "state.resolve-blocker", + "aliases": [ + "state resolve-blocker" + ], + "subcommand": "resolve-blocker", + "mutation": true + }, + { + "canonical": "state.record-session", + "aliases": [ + "state record-session" + ], + "subcommand": "record-session", + "mutation": true + }, + { + "canonical": "state.signal-waiting", + "aliases": [ + "state signal-waiting" + ], + "subcommand": "signal-waiting", + "mutation": true + }, + { + "canonical": "state.signal-resume", + "aliases": [ + "state signal-resume" + ], + "subcommand": "signal-resume", + "mutation": true + }, + { + "canonical": "state.planned-phase", + "aliases": [ + "state planned-phase" + ], + "subcommand": "planned-phase", + "mutation": true + }, + { + "canonical": "state.validate", + "aliases": [ + "state validate" + ], + "subcommand": "validate", + "mutation": false + }, + { + "canonical": "state.sync", + "aliases": [ + "state sync" + ], + "subcommand": "sync", + "mutation": true + }, + { + "canonical": "state.prune", + "aliases": [ + "state prune" + ], + "subcommand": "prune", + "mutation": true + }, + { + "canonical": "state.milestone-switch", + "aliases": [ + "state milestone-switch" + ], + "subcommand": "milestone-switch", + "mutation": true + }, + { + "canonical": "state.add-roadmap-evolution", + "aliases": [ + "state add-roadmap-evolution" + ], + "subcommand": "add-roadmap-evolution", + "mutation": true + } ]; const VERIFY_COMMAND_ALIASES = [ - { canonical: 'verify.plan-structure', aliases: ['verify plan-structure'], subcommand: 'plan-structure', mutation: false }, - { canonical: 'verify.phase-completeness', aliases: ['verify phase-completeness'], subcommand: 'phase-completeness', mutation: false }, - { canonical: 'verify.references', aliases: ['verify references'], subcommand: 'references', mutation: false }, - { canonical: 'verify.commits', aliases: ['verify commits'], subcommand: 'commits', mutation: false }, - { canonical: 'verify.artifacts', aliases: ['verify artifacts'], subcommand: 'artifacts', mutation: false }, - { canonical: 'verify.key-links', aliases: ['verify key-links'], subcommand: 'key-links', mutation: false }, - { canonical: 'verify.schema-drift', aliases: ['verify schema-drift'], subcommand: 'schema-drift', mutation: false }, - { canonical: 'verify.codebase-drift', aliases: ['verify codebase-drift'], subcommand: 'codebase-drift', mutation: false }, + { + "canonical": "verify.plan-structure", + "aliases": [ + "verify plan-structure" + ], + "subcommand": "plan-structure", + "mutation": false + }, + { + "canonical": "verify.phase-completeness", + "aliases": [ + "verify phase-completeness" + ], + "subcommand": "phase-completeness", + "mutation": false + }, + { + "canonical": "verify.references", + "aliases": [ + "verify references" + ], + "subcommand": "references", + "mutation": false + }, + { + "canonical": "verify.commits", + "aliases": [ + "verify commits" + ], + "subcommand": "commits", + "mutation": false + }, + { + "canonical": "verify.artifacts", + "aliases": [ + "verify artifacts" + ], + "subcommand": "artifacts", + "mutation": false + }, + { + "canonical": "verify.key-links", + "aliases": [ + "verify key-links" + ], + "subcommand": "key-links", + "mutation": false + }, + { + "canonical": "verify.schema-drift", + "aliases": [ + "verify schema-drift" + ], + "subcommand": "schema-drift", + "mutation": false + }, + { + "canonical": "verify.codebase-drift", + "aliases": [ + "verify codebase-drift" + ], + "subcommand": "codebase-drift", + "mutation": false + } ]; const INIT_COMMAND_ALIASES = [ - { canonical: 'init.execute-phase', aliases: ['init execute-phase'], subcommand: 'execute-phase', mutation: false }, - { canonical: 'init.plan-phase', aliases: ['init plan-phase'], subcommand: 'plan-phase', mutation: false }, - { canonical: 'init.new-project', aliases: ['init new-project'], subcommand: 'new-project', mutation: false }, - { canonical: 'init.new-milestone', aliases: ['init new-milestone'], subcommand: 'new-milestone', mutation: false }, - { canonical: 'init.quick', aliases: ['init quick'], subcommand: 'quick', mutation: false }, - { canonical: 'init.ingest-docs', aliases: ['init ingest-docs'], subcommand: 'ingest-docs', mutation: false }, - { canonical: 'init.resume', aliases: ['init resume'], subcommand: 'resume', mutation: false }, - { canonical: 'init.verify-work', aliases: ['init verify-work'], subcommand: 'verify-work', mutation: false }, - { canonical: 'init.phase-op', aliases: ['init phase-op'], subcommand: 'phase-op', mutation: false }, - { canonical: 'init.todos', aliases: ['init todos'], subcommand: 'todos', mutation: false }, - { canonical: 'init.milestone-op', aliases: ['init milestone-op'], subcommand: 'milestone-op', mutation: false }, - { canonical: 'init.map-codebase', aliases: ['init map-codebase'], subcommand: 'map-codebase', mutation: false }, - { canonical: 'init.progress', aliases: ['init progress'], subcommand: 'progress', mutation: false }, - { canonical: 'init.manager', aliases: ['init manager'], subcommand: 'manager', mutation: false }, - { canonical: 'init.new-workspace', aliases: ['init new-workspace'], subcommand: 'new-workspace', mutation: false }, - { canonical: 'init.list-workspaces', aliases: ['init list-workspaces'], subcommand: 'list-workspaces', mutation: false }, - { canonical: 'init.remove-workspace', aliases: ['init remove-workspace'], subcommand: 'remove-workspace', mutation: false }, + { + "canonical": "init.execute-phase", + "aliases": [ + "init execute-phase" + ], + "subcommand": "execute-phase", + "mutation": false + }, + { + "canonical": "init.plan-phase", + "aliases": [ + "init plan-phase" + ], + "subcommand": "plan-phase", + "mutation": false + }, + { + "canonical": "init.new-project", + "aliases": [ + "init new-project" + ], + "subcommand": "new-project", + "mutation": false + }, + { + "canonical": "init.new-milestone", + "aliases": [ + "init new-milestone" + ], + "subcommand": "new-milestone", + "mutation": false + }, + { + "canonical": "init.quick", + "aliases": [ + "init quick" + ], + "subcommand": "quick", + "mutation": false + }, + { + "canonical": "init.ingest-docs", + "aliases": [ + "init ingest-docs" + ], + "subcommand": "ingest-docs", + "mutation": false + }, + { + "canonical": "init.resume", + "aliases": [ + "init resume" + ], + "subcommand": "resume", + "mutation": false + }, + { + "canonical": "init.verify-work", + "aliases": [ + "init verify-work" + ], + "subcommand": "verify-work", + "mutation": false + }, + { + "canonical": "init.phase-op", + "aliases": [ + "init phase-op" + ], + "subcommand": "phase-op", + "mutation": false + }, + { + "canonical": "init.todos", + "aliases": [ + "init todos" + ], + "subcommand": "todos", + "mutation": false + }, + { + "canonical": "init.milestone-op", + "aliases": [ + "init milestone-op" + ], + "subcommand": "milestone-op", + "mutation": false + }, + { + "canonical": "init.map-codebase", + "aliases": [ + "init map-codebase" + ], + "subcommand": "map-codebase", + "mutation": false + }, + { + "canonical": "init.progress", + "aliases": [ + "init progress" + ], + "subcommand": "progress", + "mutation": false + }, + { + "canonical": "init.manager", + "aliases": [ + "init manager" + ], + "subcommand": "manager", + "mutation": false + }, + { + "canonical": "init.new-workspace", + "aliases": [ + "init new-workspace" + ], + "subcommand": "new-workspace", + "mutation": false + }, + { + "canonical": "init.list-workspaces", + "aliases": [ + "init list-workspaces" + ], + "subcommand": "list-workspaces", + "mutation": false + }, + { + "canonical": "init.remove-workspace", + "aliases": [ + "init remove-workspace" + ], + "subcommand": "remove-workspace", + "mutation": false + } ]; const PHASE_COMMAND_ALIASES = [ - { canonical: 'phase.list-plans', aliases: ['phase list-plans'], subcommand: 'list-plans', mutation: false }, - { canonical: 'phase.list-artifacts', aliases: ['phase list-artifacts'], subcommand: 'list-artifacts', mutation: false }, - { canonical: 'phase.next-decimal', aliases: ['phase next-decimal'], subcommand: 'next-decimal', mutation: false }, - { canonical: 'phase.add', aliases: ['phase add'], subcommand: 'add', mutation: true }, - { canonical: 'phase.add-batch', aliases: ['phase add-batch'], subcommand: 'add-batch', mutation: true }, - { canonical: 'phase.insert', aliases: ['phase insert'], subcommand: 'insert', mutation: true }, - { canonical: 'phase.remove', aliases: ['phase remove'], subcommand: 'remove', mutation: true }, - { canonical: 'phase.complete', aliases: ['phase complete'], subcommand: 'complete', mutation: true }, - { canonical: 'phase.scaffold', aliases: ['phase scaffold'], subcommand: 'scaffold', mutation: true }, + { + "canonical": "phase.list-plans", + "aliases": [ + "phase list-plans" + ], + "subcommand": "list-plans", + "mutation": false + }, + { + "canonical": "phase.list-artifacts", + "aliases": [ + "phase list-artifacts" + ], + "subcommand": "list-artifacts", + "mutation": false + }, + { + "canonical": "phase.next-decimal", + "aliases": [ + "phase next-decimal" + ], + "subcommand": "next-decimal", + "mutation": false + }, + { + "canonical": "phase.add", + "aliases": [ + "phase add" + ], + "subcommand": "add", + "mutation": true + }, + { + "canonical": "phase.add-batch", + "aliases": [ + "phase add-batch" + ], + "subcommand": "add-batch", + "mutation": true + }, + { + "canonical": "phase.insert", + "aliases": [ + "phase insert" + ], + "subcommand": "insert", + "mutation": true + }, + { + "canonical": "phase.remove", + "aliases": [ + "phase remove" + ], + "subcommand": "remove", + "mutation": true + }, + { + "canonical": "phase.complete", + "aliases": [ + "phase complete" + ], + "subcommand": "complete", + "mutation": true + }, + { + "canonical": "phase.scaffold", + "aliases": [ + "phase scaffold" + ], + "subcommand": "scaffold", + "mutation": true + } ]; const PHASES_COMMAND_ALIASES = [ - { canonical: 'phases.list', aliases: ['phases list'], subcommand: 'list', mutation: false }, - { canonical: 'phases.clear', aliases: ['phases clear'], subcommand: 'clear', mutation: true }, - { canonical: 'phases.archive', aliases: ['phases archive'], subcommand: 'archive', mutation: true }, + { + "canonical": "phases.list", + "aliases": [ + "phases list" + ], + "subcommand": "list", + "mutation": false + }, + { + "canonical": "phases.clear", + "aliases": [ + "phases clear" + ], + "subcommand": "clear", + "mutation": true + }, + { + "canonical": "phases.archive", + "aliases": [ + "phases archive" + ], + "subcommand": "archive", + "mutation": true + } ]; const VALIDATE_COMMAND_ALIASES = [ - { canonical: 'validate.consistency', aliases: ['validate consistency'], subcommand: 'consistency', mutation: false }, - { canonical: 'validate.health', aliases: ['validate health'], subcommand: 'health', mutation: false }, - { canonical: 'validate.agents', aliases: ['validate agents'], subcommand: 'agents', mutation: false }, - { canonical: 'validate.context', aliases: ['validate context'], subcommand: 'context', mutation: false }, + { + "canonical": "validate.consistency", + "aliases": [ + "validate consistency" + ], + "subcommand": "consistency", + "mutation": false + }, + { + "canonical": "validate.health", + "aliases": [ + "validate health" + ], + "subcommand": "health", + "mutation": false + }, + { + "canonical": "validate.agents", + "aliases": [ + "validate agents" + ], + "subcommand": "agents", + "mutation": false + }, + { + "canonical": "validate.context", + "aliases": [ + "validate context" + ], + "subcommand": "context", + "mutation": false + } ]; const ROADMAP_COMMAND_ALIASES = [ - { canonical: 'roadmap.analyze', aliases: ['roadmap analyze'], subcommand: 'analyze', mutation: false }, - { canonical: 'roadmap.get-phase', aliases: ['roadmap get-phase'], subcommand: 'get-phase', mutation: false }, - { canonical: 'roadmap.update-plan-progress', aliases: ['roadmap update-plan-progress'], subcommand: 'update-plan-progress', mutation: true }, - { canonical: 'roadmap.annotate-dependencies', aliases: ['roadmap annotate-dependencies'], subcommand: 'annotate-dependencies', mutation: true }, + { + "canonical": "roadmap.analyze", + "aliases": [ + "roadmap analyze" + ], + "subcommand": "analyze", + "mutation": false + }, + { + "canonical": "roadmap.get-phase", + "aliases": [ + "roadmap get-phase" + ], + "subcommand": "get-phase", + "mutation": false + }, + { + "canonical": "roadmap.update-plan-progress", + "aliases": [ + "roadmap update-plan-progress" + ], + "subcommand": "update-plan-progress", + "mutation": true + }, + { + "canonical": "roadmap.annotate-dependencies", + "aliases": [ + "roadmap annotate-dependencies" + ], + "subcommand": "annotate-dependencies", + "mutation": true + } +]; + +const NON_FAMILY_COMMAND_ALIASES = [ + { + "canonical": "check-commit", + "aliases": [], + "mutation": true + }, + { + "canonical": "check.decision-coverage-plan", + "aliases": [ + "check decision-coverage-plan" + ], + "mutation": false + }, + { + "canonical": "check.decision-coverage-verify", + "aliases": [ + "check decision-coverage-verify" + ], + "mutation": false + }, + { + "canonical": "commit", + "aliases": [], + "mutation": true + }, + { + "canonical": "commit-to-subrepo", + "aliases": [], + "mutation": true + }, + { + "canonical": "config-ensure-section", + "aliases": [], + "mutation": true + }, + { + "canonical": "config-new-project", + "aliases": [], + "mutation": true + }, + { + "canonical": "config-set", + "aliases": [], + "mutation": true + }, + { + "canonical": "config-set-model-profile", + "aliases": [], + "mutation": true + }, + { + "canonical": "docs-init", + "aliases": [], + "mutation": true + }, + { + "canonical": "frontmatter.get", + "aliases": [], + "mutation": false + }, + { + "canonical": "frontmatter.merge", + "aliases": [], + "mutation": true + }, + { + "canonical": "frontmatter.set", + "aliases": [], + "mutation": true + }, + { + "canonical": "frontmatter.validate", + "aliases": [ + "frontmatter validate" + ], + "mutation": true + }, + { + "canonical": "generate-claude-md", + "aliases": [], + "mutation": true + }, + { + "canonical": "generate-claude-profile", + "aliases": [], + "mutation": true + }, + { + "canonical": "generate-dev-preferences", + "aliases": [], + "mutation": true + }, + { + "canonical": "intel.patch-meta", + "aliases": [ + "intel patch-meta" + ], + "mutation": true + }, + { + "canonical": "intel.snapshot", + "aliases": [ + "intel snapshot" + ], + "mutation": true + }, + { + "canonical": "learnings.copy", + "aliases": [ + "learnings copy" + ], + "mutation": true + }, + { + "canonical": "learnings.delete", + "aliases": [ + "learnings delete" + ], + "mutation": true + }, + { + "canonical": "learnings.prune", + "aliases": [ + "learnings prune" + ], + "mutation": true + }, + { + "canonical": "milestone.complete", + "aliases": [ + "milestone complete" + ], + "mutation": true + }, + { + "canonical": "phase.mvp-mode", + "aliases": [ + "phase mvp-mode" + ], + "mutation": false + }, + { + "canonical": "progress.bar", + "aliases": [ + "progress bar" + ], + "mutation": false + }, + { + "canonical": "requirements.mark-complete", + "aliases": [ + "requirements mark-complete" + ], + "mutation": true + }, + { + "canonical": "stats.json", + "aliases": [ + "stats json" + ], + "mutation": false + }, + { + "canonical": "task.is-behavior-adding", + "aliases": [ + "task is-behavior-adding" + ], + "mutation": false + }, + { + "canonical": "template.fill", + "aliases": [], + "mutation": true + }, + { + "canonical": "template.select", + "aliases": [ + "template select" + ], + "mutation": true + }, + { + "canonical": "todo.complete", + "aliases": [ + "todo complete" + ], + "mutation": true + }, + { + "canonical": "todo.match-phase", + "aliases": [ + "todo match-phase" + ], + "mutation": false + }, + { + "canonical": "uat.render-checkpoint", + "aliases": [ + "uat render-checkpoint" + ], + "mutation": false + }, + { + "canonical": "verify-summary", + "aliases": [ + "verify.summary", + "verify summary" + ], + "mutation": false + }, + { + "canonical": "workstream.complete", + "aliases": [ + "workstream complete" + ], + "mutation": true + }, + { + "canonical": "workstream.create", + "aliases": [ + "workstream create" + ], + "mutation": true + }, + { + "canonical": "workstream.list", + "aliases": [ + "workstream list" + ], + "mutation": false + }, + { + "canonical": "workstream.progress", + "aliases": [ + "workstream progress" + ], + "mutation": true + }, + { + "canonical": "workstream.set", + "aliases": [ + "workstream set" + ], + "mutation": true + }, + { + "canonical": "write-profile", + "aliases": [], + "mutation": true + } ]; const STATE_SUBCOMMANDS = STATE_COMMAND_ALIASES.map((entry) => entry.subcommand); @@ -108,6 +820,7 @@ module.exports = { PHASES_COMMAND_ALIASES, VALIDATE_COMMAND_ALIASES, ROADMAP_COMMAND_ALIASES, + NON_FAMILY_COMMAND_ALIASES, STATE_SUBCOMMANDS, VERIFY_SUBCOMMANDS, INIT_SUBCOMMANDS, @@ -115,4 +828,4 @@ module.exports = { PHASES_SUBCOMMANDS, VALIDATE_SUBCOMMANDS, ROADMAP_SUBCOMMANDS, -}; +}; \ No newline at end of file diff --git a/sdk/scripts/check-command-aliases-fresh.mjs b/sdk/scripts/check-command-aliases-fresh.mjs index 0a4a8aa30..e93f9bc67 100644 --- a/sdk/scripts/check-command-aliases-fresh.mjs +++ b/sdk/scripts/check-command-aliases-fresh.mjs @@ -36,8 +36,13 @@ const { PHASES_COMMAND_ALIASES, VALIDATE_COMMAND_ALIASES, ROADMAP_COMMAND_ALIASES, + NON_FAMILY_COMMAND_ALIASES, } = await import('../dist/query/command-aliases.generated.js'); +const { + NON_FAMILY_COMMAND_MANIFEST, +} = await import('../dist/query/command-manifest.non-family.js'); + const cjsAliases = require(resolve(here, '..', '..', 'get-shit-done', 'bin', 'lib', 'command-aliases.generated.cjs')); function toAliasEntries(manifest, family) { @@ -50,6 +55,16 @@ function toAliasEntries(manifest, family) { })); } +function toNonFamilyAliasEntries(manifest) { + return [...manifest] + .sort((a, b) => a.canonical.localeCompare(b.canonical)) + .map((entry) => ({ + canonical: entry.canonical, + aliases: [...entry.aliases], + mutation: entry.mutation, + })); +} + function assertEqual(label, actual, expected) { const a = JSON.stringify(actual); const e = JSON.stringify(expected); @@ -67,6 +82,7 @@ const expectedPhase = toAliasEntries(PHASE_COMMAND_MANIFEST, 'phase'); const expectedPhases = toAliasEntries(PHASES_COMMAND_MANIFEST, 'phases'); const expectedValidate = toAliasEntries(VALIDATE_COMMAND_MANIFEST, 'validate'); const expectedRoadmap = toAliasEntries(ROADMAP_COMMAND_MANIFEST, 'roadmap'); +const expectedNonFamily = toNonFamilyAliasEntries(NON_FAMILY_COMMAND_MANIFEST); assertEqual('TS STATE_COMMAND_ALIASES', STATE_COMMAND_ALIASES, expectedState); assertEqual('TS VERIFY_COMMAND_ALIASES', VERIFY_COMMAND_ALIASES, expectedVerify); @@ -75,6 +91,7 @@ assertEqual('TS PHASE_COMMAND_ALIASES', PHASE_COMMAND_ALIASES, expectedPhase); assertEqual('TS PHASES_COMMAND_ALIASES', PHASES_COMMAND_ALIASES, expectedPhases); assertEqual('TS VALIDATE_COMMAND_ALIASES', VALIDATE_COMMAND_ALIASES, expectedValidate); assertEqual('TS ROADMAP_COMMAND_ALIASES', ROADMAP_COMMAND_ALIASES, expectedRoadmap); +assertEqual('TS NON_FAMILY_COMMAND_ALIASES', NON_FAMILY_COMMAND_ALIASES, expectedNonFamily); assertEqual('CJS STATE_COMMAND_ALIASES', cjsAliases.STATE_COMMAND_ALIASES, expectedState); assertEqual('CJS VERIFY_COMMAND_ALIASES', cjsAliases.VERIFY_COMMAND_ALIASES, expectedVerify); @@ -83,5 +100,6 @@ assertEqual('CJS PHASE_COMMAND_ALIASES', cjsAliases.PHASE_COMMAND_ALIASES, expec assertEqual('CJS PHASES_COMMAND_ALIASES', cjsAliases.PHASES_COMMAND_ALIASES, expectedPhases); assertEqual('CJS VALIDATE_COMMAND_ALIASES', cjsAliases.VALIDATE_COMMAND_ALIASES, expectedValidate); assertEqual('CJS ROADMAP_COMMAND_ALIASES', cjsAliases.ROADMAP_COMMAND_ALIASES, expectedRoadmap); +assertEqual('CJS NON_FAMILY_COMMAND_ALIASES', cjsAliases.NON_FAMILY_COMMAND_ALIASES, expectedNonFamily); console.log('command alias artifacts are fresh'); diff --git a/sdk/scripts/gen-command-aliases.ts b/sdk/scripts/gen-command-aliases.ts index d862e7a6e..cbf2bfd12 100644 --- a/sdk/scripts/gen-command-aliases.ts +++ b/sdk/scripts/gen-command-aliases.ts @@ -11,6 +11,7 @@ import { writeFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { COMMAND_DEFINITIONS_BY_FAMILY } from '../src/query/command-definition.js'; +import { NON_FAMILY_COMMAND_MANIFEST } from '../src/query/command-manifest.non-family.js'; function toSubcommand(canonical: string, family: 'state' | 'verify' | 'init' | 'phase' | 'phases' | 'validate' | 'roadmap'): string { const prefix = `${family}.`; @@ -67,22 +68,66 @@ async function main(): Promise { mutation: entry.mutation, })); - const outPath = fileURLToPath(new URL('../src/query/command-aliases.generated.ts', import.meta.url)); - const header = `/**\n * GENERATED FILE — command alias expansion for state.*, verify.*, init.*, phase.*, phases.*, validate.*, and roadmap.* pilots.\n * Source: sdk/src/query/command-manifest.{state,verify,init,phase,phases,validate,roadmap}.ts\n */\n\n`; - const body = [ - `export const STATE_COMMAND_ALIASES = ${JSON.stringify(stateEntries, null, 2)} as const;`, + // Non-family entries — sorted by canonical for deterministic output. + const nonFamilyEntries = [...NON_FAMILY_COMMAND_MANIFEST] + .sort((a, b) => a.canonical.localeCompare(b.canonical)) + .map((entry) => ({ + canonical: entry.canonical, + aliases: [...entry.aliases], + mutation: entry.mutation, + })); + + // Serialise a FamilyCommandAlias entry as a single-line TS literal. + function serializeFamily(e: { canonical: string; aliases: string[]; subcommand: string; mutation: boolean }): string { + const aliases = `[${e.aliases.map((a) => `'${a}'`).join(', ')}]`; + return `{ canonical: '${e.canonical}', aliases: ${aliases}, subcommand: '${e.subcommand}', mutation: ${e.mutation} }`; + } + + // Serialise a NonFamilyCommandAlias entry as a single-line TS literal. + function serializeNonFamily(e: { canonical: string; aliases: string[]; mutation: boolean }): string { + const aliases = `[${e.aliases.map((a) => `'${a}'`).join(', ')}]`; + return `{ canonical: '${e.canonical}', aliases: ${aliases}, mutation: ${e.mutation} }`; + } + + function renderFamilyArray(entries: { canonical: string; aliases: string[]; subcommand: string; mutation: boolean }[]): string { + return `[\n${entries.map((e) => ` ${serializeFamily(e)},`).join('\n')}\n]`; + } + + function renderNonFamilyArray(entries: { canonical: string; aliases: string[]; mutation: boolean }[]): string { + return `[\n${entries.map((e) => ` ${serializeNonFamily(e)},`).join('\n')}\n]`; + } + + const tsOutPath = fileURLToPath(new URL('../src/query/command-aliases.generated.ts', import.meta.url)); + const tsHeader = `/**\n * GENERATED FILE — command alias expansion for state.*, verify.*, init.*, phase.*, phases.*, validate.*, roadmap.*, and non-family commands.\n * Source: sdk/src/query/command-manifest.{state,verify,init,phase,phases,validate,roadmap,non-family}.ts\n */\n\n`; + const tsBody = [ + 'export interface FamilyCommandAlias {', + ' canonical: string;', + ' aliases: string[];', + ' subcommand: string;', + ' mutation: boolean;', + '}', '', - `export const VERIFY_COMMAND_ALIASES = ${JSON.stringify(verifyEntries, null, 2)} as const;`, + `export const STATE_COMMAND_ALIASES: readonly FamilyCommandAlias[] = ${renderFamilyArray(stateEntries)} as const;`, '', - `export const INIT_COMMAND_ALIASES = ${JSON.stringify(initEntries, null, 2)} as const;`, + `export const VERIFY_COMMAND_ALIASES: readonly FamilyCommandAlias[] = ${renderFamilyArray(verifyEntries)} as const;`, '', - `export const PHASE_COMMAND_ALIASES = ${JSON.stringify(phaseEntries, null, 2)} as const;`, + `export const INIT_COMMAND_ALIASES: readonly FamilyCommandAlias[] = ${renderFamilyArray(initEntries)} as const;`, '', - `export const PHASES_COMMAND_ALIASES = ${JSON.stringify(phasesEntries, null, 2)} as const;`, + `export const PHASE_COMMAND_ALIASES: readonly FamilyCommandAlias[] = ${renderFamilyArray(phaseEntries)} as const;`, '', - `export const VALIDATE_COMMAND_ALIASES = ${JSON.stringify(validateEntries, null, 2)} as const;`, + `export const PHASES_COMMAND_ALIASES: readonly FamilyCommandAlias[] = ${renderFamilyArray(phasesEntries)} as const;`, '', - `export const ROADMAP_COMMAND_ALIASES = ${JSON.stringify(roadmapEntries, null, 2)} as const;`, + `export const VALIDATE_COMMAND_ALIASES: readonly FamilyCommandAlias[] = ${renderFamilyArray(validateEntries)} as const;`, + '', + `export const ROADMAP_COMMAND_ALIASES: readonly FamilyCommandAlias[] = ${renderFamilyArray(roadmapEntries)} as const;`, + '', + 'export interface NonFamilyCommandAlias {', + ' canonical: string;', + ' aliases: string[];', + ' mutation: boolean;', + '}', + '', + `export const NON_FAMILY_COMMAND_ALIASES: readonly NonFamilyCommandAlias[] = ${renderNonFamilyArray(nonFamilyEntries)} as const;`, '', 'export const STATE_SUBCOMMANDS = new Set(STATE_COMMAND_ALIASES.map((entry) => entry.subcommand));', 'export const VERIFY_SUBCOMMANDS = new Set(VERIFY_COMMAND_ALIASES.map((entry) => entry.subcommand));', @@ -92,9 +137,57 @@ async function main(): Promise { 'export const VALIDATE_SUBCOMMANDS = new Set(VALIDATE_COMMAND_ALIASES.map((entry) => entry.subcommand));', 'export const ROADMAP_SUBCOMMANDS = new Set(ROADMAP_COMMAND_ALIASES.map((entry) => entry.subcommand));', '', - ].join('\n'); - await writeFile(outPath, header + body, 'utf-8'); + await writeFile(tsOutPath, tsHeader + tsBody, 'utf-8'); + + // Also generate the CJS mirror used by get-shit-done/bin/lib/ seams. + // CJS is plain JavaScript — no type annotations. + const cjsOutPath = fileURLToPath(new URL('../../get-shit-done/bin/lib/command-aliases.generated.cjs', import.meta.url)); + const cjsHeader = `'use strict';\n\n/**\n * GENERATED FILE — state.*, verify.*, init.*, phase.*, phases.*, validate.*, roadmap.*, and non-family alias/subcommand metadata for CJS routing.\n * Source: sdk/src/query/command-manifest.{state,verify,init,phase,phases,validate,roadmap,non-family}.ts\n */\n\n`; + const cjsBody = [ + `const STATE_COMMAND_ALIASES = ${JSON.stringify(stateEntries, null, 2)};`, + '', + `const VERIFY_COMMAND_ALIASES = ${JSON.stringify(verifyEntries, null, 2)};`, + '', + `const INIT_COMMAND_ALIASES = ${JSON.stringify(initEntries, null, 2)};`, + '', + `const PHASE_COMMAND_ALIASES = ${JSON.stringify(phaseEntries, null, 2)};`, + '', + `const PHASES_COMMAND_ALIASES = ${JSON.stringify(phasesEntries, null, 2)};`, + '', + `const VALIDATE_COMMAND_ALIASES = ${JSON.stringify(validateEntries, null, 2)};`, + '', + `const ROADMAP_COMMAND_ALIASES = ${JSON.stringify(roadmapEntries, null, 2)};`, + '', + `const NON_FAMILY_COMMAND_ALIASES = ${JSON.stringify(nonFamilyEntries, null, 2)};`, + '', + `const STATE_SUBCOMMANDS = STATE_COMMAND_ALIASES.map((entry) => entry.subcommand);`, + `const VERIFY_SUBCOMMANDS = VERIFY_COMMAND_ALIASES.map((entry) => entry.subcommand);`, + `const INIT_SUBCOMMANDS = INIT_COMMAND_ALIASES.map((entry) => entry.subcommand);`, + `const PHASE_SUBCOMMANDS = PHASE_COMMAND_ALIASES.map((entry) => entry.subcommand);`, + `const PHASES_SUBCOMMANDS = PHASES_COMMAND_ALIASES.map((entry) => entry.subcommand);`, + `const VALIDATE_SUBCOMMANDS = VALIDATE_COMMAND_ALIASES.map((entry) => entry.subcommand);`, + `const ROADMAP_SUBCOMMANDS = ROADMAP_COMMAND_ALIASES.map((entry) => entry.subcommand);`, + '', + `module.exports = {`, + ` STATE_COMMAND_ALIASES,`, + ` VERIFY_COMMAND_ALIASES,`, + ` INIT_COMMAND_ALIASES,`, + ` PHASE_COMMAND_ALIASES,`, + ` PHASES_COMMAND_ALIASES,`, + ` VALIDATE_COMMAND_ALIASES,`, + ` ROADMAP_COMMAND_ALIASES,`, + ` NON_FAMILY_COMMAND_ALIASES,`, + ` STATE_SUBCOMMANDS,`, + ` VERIFY_SUBCOMMANDS,`, + ` INIT_SUBCOMMANDS,`, + ` PHASE_SUBCOMMANDS,`, + ` PHASES_SUBCOMMANDS,`, + ` VALIDATE_SUBCOMMANDS,`, + ` ROADMAP_SUBCOMMANDS,`, + `};`, + ].join('\n'); + await writeFile(cjsOutPath, cjsHeader + cjsBody, 'utf-8'); } main().catch((err) => { diff --git a/sdk/src/query/command-aliases.generated.ts b/sdk/src/query/command-aliases.generated.ts index 3e4713c70..e188236eb 100644 --- a/sdk/src/query/command-aliases.generated.ts +++ b/sdk/src/query/command-aliases.generated.ts @@ -1,6 +1,6 @@ /** - * GENERATED FILE — command alias expansion for state.*, verify.*, init.*, phase.*, phases.*, validate.*, and roadmap.* pilots. - * Source: sdk/src/query/command-manifest.{state,verify,init,phase,phases,validate,roadmap}.ts + * GENERATED FILE — command alias expansion for state.*, verify.*, init.*, phase.*, phases.*, validate.*, roadmap.*, and non-family commands. + * Source: sdk/src/query/command-manifest.{state,verify,init,phase,phases,validate,roadmap,non-family}.ts */ export interface FamilyCommandAlias { @@ -97,6 +97,55 @@ export const ROADMAP_COMMAND_ALIASES: readonly FamilyCommandAlias[] = [ { canonical: 'roadmap.annotate-dependencies', aliases: ['roadmap annotate-dependencies'], subcommand: 'annotate-dependencies', mutation: true }, ] as const; +export interface NonFamilyCommandAlias { + canonical: string; + aliases: string[]; + mutation: boolean; +} + +export const NON_FAMILY_COMMAND_ALIASES: readonly NonFamilyCommandAlias[] = [ + { canonical: 'check-commit', aliases: [], mutation: true }, + { canonical: 'check.decision-coverage-plan', aliases: ['check decision-coverage-plan'], mutation: false }, + { canonical: 'check.decision-coverage-verify', aliases: ['check decision-coverage-verify'], mutation: false }, + { canonical: 'commit', aliases: [], mutation: true }, + { canonical: 'commit-to-subrepo', aliases: [], mutation: true }, + { canonical: 'config-ensure-section', aliases: [], mutation: true }, + { canonical: 'config-new-project', aliases: [], mutation: true }, + { canonical: 'config-set', aliases: [], mutation: true }, + { canonical: 'config-set-model-profile', aliases: [], mutation: true }, + { canonical: 'docs-init', aliases: [], mutation: true }, + { canonical: 'frontmatter.get', aliases: [], mutation: false }, + { canonical: 'frontmatter.merge', aliases: [], mutation: true }, + { canonical: 'frontmatter.set', aliases: [], mutation: true }, + { canonical: 'frontmatter.validate', aliases: ['frontmatter validate'], mutation: true }, + { canonical: 'generate-claude-md', aliases: [], mutation: true }, + { canonical: 'generate-claude-profile', aliases: [], mutation: true }, + { canonical: 'generate-dev-preferences', aliases: [], mutation: true }, + { canonical: 'intel.patch-meta', aliases: ['intel patch-meta'], mutation: true }, + { canonical: 'intel.snapshot', aliases: ['intel snapshot'], mutation: true }, + { canonical: 'learnings.copy', aliases: ['learnings copy'], mutation: true }, + { canonical: 'learnings.delete', aliases: ['learnings delete'], mutation: true }, + { canonical: 'learnings.prune', aliases: ['learnings prune'], mutation: true }, + { canonical: 'milestone.complete', aliases: ['milestone complete'], mutation: true }, + { canonical: 'phase.mvp-mode', aliases: ['phase mvp-mode'], mutation: false }, + { canonical: 'progress.bar', aliases: ['progress bar'], mutation: false }, + { canonical: 'requirements.mark-complete', aliases: ['requirements mark-complete'], mutation: true }, + { canonical: 'stats.json', aliases: ['stats json'], mutation: false }, + { canonical: 'task.is-behavior-adding', aliases: ['task is-behavior-adding'], mutation: false }, + { canonical: 'template.fill', aliases: [], mutation: true }, + { canonical: 'template.select', aliases: ['template select'], mutation: true }, + { canonical: 'todo.complete', aliases: ['todo complete'], mutation: true }, + { canonical: 'todo.match-phase', aliases: ['todo match-phase'], mutation: false }, + { canonical: 'uat.render-checkpoint', aliases: ['uat render-checkpoint'], mutation: false }, + { canonical: 'verify-summary', aliases: ['verify.summary', 'verify summary'], mutation: false }, + { canonical: 'workstream.complete', aliases: ['workstream complete'], mutation: true }, + { canonical: 'workstream.create', aliases: ['workstream create'], mutation: true }, + { canonical: 'workstream.list', aliases: ['workstream list'], mutation: false }, + { canonical: 'workstream.progress', aliases: ['workstream progress'], mutation: true }, + { canonical: 'workstream.set', aliases: ['workstream set'], mutation: true }, + { canonical: 'write-profile', aliases: [], mutation: true }, +] as const; + export const STATE_SUBCOMMANDS = new Set(STATE_COMMAND_ALIASES.map((entry) => entry.subcommand)); export const VERIFY_SUBCOMMANDS = new Set(VERIFY_COMMAND_ALIASES.map((entry) => entry.subcommand)); export const INIT_SUBCOMMANDS = new Set(INIT_COMMAND_ALIASES.map((entry) => entry.subcommand)); @@ -104,4 +153,3 @@ export const PHASE_SUBCOMMANDS = new Set(PHASE_COMMAND_ALIASES.map((entr export const PHASES_SUBCOMMANDS = new Set(PHASES_COMMAND_ALIASES.map((entry) => entry.subcommand)); export const VALIDATE_SUBCOMMANDS = new Set(VALIDATE_COMMAND_ALIASES.map((entry) => entry.subcommand)); export const ROADMAP_SUBCOMMANDS = new Set(ROADMAP_COMMAND_ALIASES.map((entry) => entry.subcommand)); - diff --git a/sdk/src/query/command-manifest.non-family.ts b/sdk/src/query/command-manifest.non-family.ts index 223d244cd..a79503c58 100644 --- a/sdk/src/query/command-manifest.non-family.ts +++ b/sdk/src/query/command-manifest.non-family.ts @@ -8,6 +8,10 @@ export interface NonFamilyCommandManifestEntry { } export const NON_FAMILY_COMMAND_MANIFEST: readonly NonFamilyCommandManifestEntry[] = [ + { canonical: 'check.decision-coverage-plan', aliases: ['check decision-coverage-plan'], mutation: false, outputMode: 'json' }, + { canonical: 'check.decision-coverage-verify', aliases: ['check decision-coverage-verify'], mutation: false, outputMode: 'json' }, + + { canonical: 'frontmatter.get', aliases: [], mutation: false, outputMode: 'json' }, { canonical: 'frontmatter.set', aliases: [], mutation: true, outputMode: 'json' }, { canonical: 'frontmatter.merge', aliases: [], mutation: true, outputMode: 'json' }, { canonical: 'frontmatter.validate', aliases: ['frontmatter validate'], mutation: true, outputMode: 'json' }, @@ -28,12 +32,25 @@ export const NON_FAMILY_COMMAND_MANIFEST: readonly NonFamilyCommandManifestEntry { canonical: 'todo.complete', aliases: ['todo complete'], mutation: true, outputMode: 'json' }, { canonical: 'milestone.complete', aliases: ['milestone complete'], mutation: true, outputMode: 'json' }, + { canonical: 'phase.mvp-mode', aliases: ['phase mvp-mode'], mutation: false, outputMode: 'json' }, + + { canonical: 'progress.bar', aliases: ['progress bar'], mutation: false, outputMode: 'json' }, + + { canonical: 'stats.json', aliases: ['stats json'], mutation: false, outputMode: 'json' }, + + { canonical: 'task.is-behavior-adding', aliases: ['task is-behavior-adding'], mutation: false, outputMode: 'json' }, + + { canonical: 'todo.match-phase', aliases: ['todo match-phase'], mutation: false, outputMode: 'json' }, + + { canonical: 'uat.render-checkpoint', aliases: ['uat render-checkpoint'], mutation: false, outputMode: 'json' }, + { canonical: 'workstream.create', aliases: ['workstream create'], mutation: true, outputMode: 'json', }, + { canonical: 'workstream.list', aliases: ['workstream list'], mutation: false, outputMode: 'json' }, { canonical: 'workstream.set', aliases: ['workstream set'], mutation: true, outputMode: 'json' }, { canonical: 'workstream.complete', diff --git a/tests/feat-3251-command-aliases-manifest-coverage.test.cjs b/tests/feat-3251-command-aliases-manifest-coverage.test.cjs new file mode 100644 index 000000000..0cdb14b36 --- /dev/null +++ b/tests/feat-3251-command-aliases-manifest-coverage.test.cjs @@ -0,0 +1,128 @@ +'use strict'; +/** + * Regression guard for issue #3251: + * 14 commands used in workflows must be present in command-aliases.generated.cjs. + * + * Asserts structurally by requiring the manifest and checking each canonical + * command appears in either the family arrays or the non-family array. + * Never greps the source file — see feedback_no_source_grep_tests.md. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const path = require('path'); + +const REPO_ROOT = path.join(__dirname, '..'); +const COMMAND_ALIASES_FILE = path.join( + REPO_ROOT, + 'get-shit-done', + 'bin', + 'lib', + 'command-aliases.generated.cjs', +); + +const MISSING_14 = [ + 'check.decision-coverage-plan', + 'check.decision-coverage-verify', + 'frontmatter.get', + 'frontmatter.set', + 'learnings.copy', + 'milestone.complete', + 'phase.mvp-mode', + 'progress.bar', + 'requirements.mark-complete', + 'stats.json', + 'task.is-behavior-adding', + 'todo.match-phase', + 'uat.render-checkpoint', + 'workstream.list', +]; + +describe('feat-3251: command-aliases.generated.cjs manifest coverage', () => { + let manifest; + + test('manifest file can be required without error', () => { + try { + manifest = require(COMMAND_ALIASES_FILE); + } catch (err) { + assert.fail(`Failed to require manifest: ${err.message}`); + } + assert.ok(manifest, 'manifest should be truthy'); + }); + + test('manifest exports NON_FAMILY_COMMAND_ALIASES array', () => { + manifest = manifest ?? require(COMMAND_ALIASES_FILE); + assert.ok( + Array.isArray(manifest.NON_FAMILY_COMMAND_ALIASES), + 'NON_FAMILY_COMMAND_ALIASES must be an exported array in command-aliases.generated.cjs', + ); + }); + + test('all 14 missing commands are present in the manifest (family or non-family)', () => { + manifest = manifest ?? require(COMMAND_ALIASES_FILE); + + const allCanonicalsInManifest = new Set(); + + // Collect from all family arrays + const familyArrayKeys = [ + 'STATE_COMMAND_ALIASES', + 'VERIFY_COMMAND_ALIASES', + 'INIT_COMMAND_ALIASES', + 'PHASE_COMMAND_ALIASES', + 'PHASES_COMMAND_ALIASES', + 'VALIDATE_COMMAND_ALIASES', + 'ROADMAP_COMMAND_ALIASES', + ]; + for (const key of familyArrayKeys) { + const arr = manifest[key]; + if (!Array.isArray(arr)) continue; + for (const entry of arr) { + if (entry && entry.canonical) allCanonicalsInManifest.add(entry.canonical); + } + } + + // Collect from non-family array + const nonFamily = manifest.NON_FAMILY_COMMAND_ALIASES; + if (Array.isArray(nonFamily)) { + for (const entry of nonFamily) { + if (entry && entry.canonical) allCanonicalsInManifest.add(entry.canonical); + } + } + + const missing = MISSING_14.filter((cmd) => !allCanonicalsInManifest.has(cmd)); + assert.deepStrictEqual( + missing, + [], + `${missing.length} command(s) still missing from manifest: ${missing.join(', ')}`, + ); + }); + + test('each non-family entry has required fields: canonical, aliases, mutation', () => { + manifest = manifest ?? require(COMMAND_ALIASES_FILE); + const nonFamily = manifest.NON_FAMILY_COMMAND_ALIASES; + if (!Array.isArray(nonFamily)) return; // caught by earlier test + + for (const entry of nonFamily) { + assert.ok(typeof entry.canonical === 'string' && entry.canonical.length > 0, + `entry missing canonical: ${JSON.stringify(entry)}`); + assert.ok(Array.isArray(entry.aliases), + `entry missing aliases array for canonical=${entry.canonical}`); + assert.ok(typeof entry.mutation === 'boolean', + `entry missing mutation boolean for canonical=${entry.canonical}`); + } + }); + + test('NON_FAMILY_COMMAND_ALIASES is sorted by canonical (deterministic output)', () => { + manifest = manifest ?? require(COMMAND_ALIASES_FILE); + const nonFamily = manifest.NON_FAMILY_COMMAND_ALIASES; + if (!Array.isArray(nonFamily)) return; // caught by earlier test + + const canonicals = nonFamily.map((e) => e.canonical); + const sorted = [...canonicals].sort((a, b) => a.localeCompare(b)); + assert.deepStrictEqual( + canonicals, + sorted, + 'NON_FAMILY_COMMAND_ALIASES must be sorted by canonical for deterministic regeneration', + ); + }); +});