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:
@@ -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');
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user