feat(sdk): add NON_FAMILY_COMMAND_ALIASES to manifest — 14 missing commands (#3305)

* test(3251): red — assert 14 missing commands in command-aliases.generated.cjs

Parametrized test covering all 14 commands from issue #3251. Requires the
CJS manifest and asserts structurally (never greps source). Currently fails
because NON_FAMILY_COMMAND_ALIASES is not exported and all 14 are missing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(3251): add 14 missing commands to command-aliases.generated.cjs

Adds NON_FAMILY_COMMAND_ALIASES export to command-aliases.generated.cjs and
extends sdk/src/query/command-manifest.non-family.ts with the 10 commands that
were registered in static catalogs but absent from the manifest source-of-truth:
- 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 (frontmatter.set, learnings.copy, milestone.complete,
requirements.mark-complete) were already in non-family.ts but unexported.

Generator (sdk/scripts/gen-command-aliases.ts) now produces both the TS and CJS
artifacts including the non-family section, sorted by canonical for determinism.
Freshness check (sdk/scripts/check-command-aliases-fresh.mjs) verifies TS and CJS
non-family parity against the manifest source-of-truth.

Fixes #3251

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(3251): add changeset and CHANGELOG entry for PR #3305

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(gen-command-aliases): preserve TS type interfaces and annotations on regen (#3251)

- Add FamilyCommandAlias and NonFamilyCommandAlias interface declarations to tsBody so regen never strips them
- Replace JSON.stringify with single-line compact serialisers for TS output (matches committed file format)
- Apply typed annotations (readonly FamilyCommandAlias[] / readonly NonFamilyCommandAlias[]) to all exported TS constants
- CJS path unchanged: remains untyped pure-JS with JSON.stringify multi-line format
- Regenerate sdk/src/query/command-aliases.generated.ts to sync with updated generator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: drop redundant CHANGELOG.md edit (use .changeset/ fragment per CONTRIBUTING.md)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-09 11:46:40 -04:00
committed by GitHub
parent 31e2c22309
commit 04031244b8
7 changed files with 1106 additions and 84 deletions

View File

@@ -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');

View File

@@ -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<void> {
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<string>(STATE_COMMAND_ALIASES.map((entry) => entry.subcommand));',
'export const VERIFY_SUBCOMMANDS = new Set<string>(VERIFY_COMMAND_ALIASES.map((entry) => entry.subcommand));',
@@ -92,9 +137,57 @@ async function main(): Promise<void> {
'export const VALIDATE_SUBCOMMANDS = new Set<string>(VALIDATE_COMMAND_ALIASES.map((entry) => entry.subcommand));',
'export const ROADMAP_SUBCOMMANDS = new Set<string>(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) => {