refactor(query): manifest-backed routing seam + family adapters (#2908)
Merging validated command-seam foundation.
This commit is contained in:
@@ -10,6 +10,20 @@ This document records contracts for the typed query layer consumed by `gsd-sdk q
|
||||
- CJS `**summary-extract**` → SDK `**summary.extract**` / `**summary extract**` / `**history-digest**` (see `index.ts`).
|
||||
- CJS top-level `**scaffold <type> ...**` → SDK `**phase.scaffold**` / `**phase scaffold**` with the scaffold type as the first argument (no separate `scaffold` alias on the registry).
|
||||
|
||||
### Manifest-backed family ownership
|
||||
|
||||
These families are sourced from `command-manifest.*.ts` files and expanded into generated alias artifacts (`command-aliases.generated.ts` + CJS mirror):
|
||||
|
||||
- `state.*` → `command-manifest.state.ts`
|
||||
- `verify.*` → `command-manifest.verify.ts`
|
||||
- `init.*` → `command-manifest.init.ts`
|
||||
- `phase.*` → `command-manifest.phase.ts`
|
||||
- `phases.*` → `command-manifest.phases.ts`
|
||||
- `validate.*` → `command-manifest.validate.ts`
|
||||
- `roadmap.*` → `command-manifest.roadmap.ts`
|
||||
|
||||
CJS routing seams mirror these families with thin adapters (`state/verify/init/phase/phases/validate/roadmap-command-router.cjs`) so `gsd-tools.cjs` stays orchestration-only.
|
||||
|
||||
## `gsd-sdk query` routing
|
||||
|
||||
1. **`normalizeQueryCommand()`** (`normalize-query-command.ts`) — maps the first argv tokens to the same **command + subcommand** patterns as `gsd-tools` `runCommand()` where needed (e.g. `state json` → `state.json`, `init execute-phase 9` → `init.execute-phase` with args `['9']`, `scaffold …` → `phase.scaffold`). Re-exported from **`@gsd-build/sdk`** and **`createRegistry`’s module** (`sdk/src/query/index.ts`) so programmatic callers can mirror CLI tokenization without importing a deep path.
|
||||
|
||||
122
sdk/src/query/command-aliases.generated.ts
Normal file
122
sdk/src/query/command-aliases.generated.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
export interface FamilyCommandAlias {
|
||||
canonical: string;
|
||||
aliases: string[];
|
||||
subcommand: string;
|
||||
mutation: boolean;
|
||||
}
|
||||
|
||||
export const STATE_COMMAND_ALIASES: readonly FamilyCommandAlias[] = [
|
||||
{ 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 },
|
||||
] as const;
|
||||
|
||||
export const VERIFY_COMMAND_ALIASES: readonly FamilyCommandAlias[] = [
|
||||
{ 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 },
|
||||
] as const;
|
||||
|
||||
export const INIT_COMMAND_ALIASES: readonly FamilyCommandAlias[] = [
|
||||
{ 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 },
|
||||
] as const;
|
||||
|
||||
export const PHASE_COMMAND_ALIASES: readonly FamilyCommandAlias[] = [
|
||||
{ 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 },
|
||||
] as const;
|
||||
|
||||
export const PHASES_COMMAND_ALIASES: readonly FamilyCommandAlias[] = [
|
||||
{ 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 },
|
||||
] as const;
|
||||
|
||||
export const VALIDATE_COMMAND_ALIASES: readonly FamilyCommandAlias[] = [
|
||||
{ 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 },
|
||||
] as const;
|
||||
|
||||
export const ROADMAP_COMMAND_ALIASES: readonly FamilyCommandAlias[] = [
|
||||
{ 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 },
|
||||
] 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));
|
||||
export const INIT_SUBCOMMANDS = new Set<string>(INIT_COMMAND_ALIASES.map((entry) => entry.subcommand));
|
||||
export const PHASE_SUBCOMMANDS = new Set<string>(PHASE_COMMAND_ALIASES.map((entry) => entry.subcommand));
|
||||
export const PHASES_SUBCOMMANDS = new Set<string>(PHASES_COMMAND_ALIASES.map((entry) => entry.subcommand));
|
||||
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));
|
||||
|
||||
export const STATE_MUTATION_COMMANDS: readonly string[] = STATE_COMMAND_ALIASES
|
||||
.filter((entry) => entry.mutation)
|
||||
.flatMap((entry) => [entry.canonical, ...entry.aliases]);
|
||||
|
||||
export const PHASE_MUTATION_COMMANDS: readonly string[] = PHASE_COMMAND_ALIASES
|
||||
.filter((entry) => entry.mutation)
|
||||
.flatMap((entry) => [entry.canonical, ...entry.aliases]);
|
||||
|
||||
export const PHASES_MUTATION_COMMANDS: readonly string[] = PHASES_COMMAND_ALIASES
|
||||
.filter((entry) => entry.mutation)
|
||||
.flatMap((entry) => [entry.canonical, ...entry.aliases]);
|
||||
|
||||
export const ROADMAP_MUTATION_COMMANDS: readonly string[] = ROADMAP_COMMAND_ALIASES
|
||||
.filter((entry) => entry.mutation)
|
||||
.flatMap((entry) => [entry.canonical, ...entry.aliases]);
|
||||
24
sdk/src/query/command-manifest.init.ts
Normal file
24
sdk/src/query/command-manifest.init.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { CommandManifestEntry } from './command-manifest.types.js';
|
||||
|
||||
/**
|
||||
* Canonical init.* command manifest.
|
||||
*/
|
||||
export const INIT_COMMAND_MANIFEST: readonly CommandManifestEntry[] = [
|
||||
{ family: 'init', canonical: 'init.execute-phase', aliases: ['init execute-phase'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.plan-phase', aliases: ['init plan-phase'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.new-project', aliases: ['init new-project'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.new-milestone', aliases: ['init new-milestone'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.quick', aliases: ['init quick'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.ingest-docs', aliases: ['init ingest-docs'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.resume', aliases: ['init resume'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.verify-work', aliases: ['init verify-work'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.phase-op', aliases: ['init phase-op'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.todos', aliases: ['init todos'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.milestone-op', aliases: ['init milestone-op'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.map-codebase', aliases: ['init map-codebase'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.progress', aliases: ['init progress'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.manager', aliases: ['init manager'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.new-workspace', aliases: ['init new-workspace'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.list-workspaces', aliases: ['init list-workspaces'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'init', canonical: 'init.remove-workspace', aliases: ['init remove-workspace'], mutation: false, outputMode: 'json' },
|
||||
] as const;
|
||||
16
sdk/src/query/command-manifest.phase.ts
Normal file
16
sdk/src/query/command-manifest.phase.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { CommandManifestEntry } from './command-manifest.types.js';
|
||||
|
||||
/**
|
||||
* Canonical phase.* command manifest.
|
||||
*/
|
||||
export const PHASE_COMMAND_MANIFEST: readonly CommandManifestEntry[] = [
|
||||
{ family: 'phase', canonical: 'phase.list-plans', aliases: ['phase list-plans'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'phase', canonical: 'phase.list-artifacts', aliases: ['phase list-artifacts'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'phase', canonical: 'phase.next-decimal', aliases: ['phase next-decimal'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'phase', canonical: 'phase.add', aliases: ['phase add'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'phase', canonical: 'phase.add-batch', aliases: ['phase add-batch'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'phase', canonical: 'phase.insert', aliases: ['phase insert'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'phase', canonical: 'phase.remove', aliases: ['phase remove'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'phase', canonical: 'phase.complete', aliases: ['phase complete'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'phase', canonical: 'phase.scaffold', aliases: ['phase scaffold'], mutation: true, outputMode: 'json' },
|
||||
] as const;
|
||||
11
sdk/src/query/command-manifest.phases.ts
Normal file
11
sdk/src/query/command-manifest.phases.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { CommandManifestEntry } from './command-manifest.types.js';
|
||||
|
||||
/**
|
||||
* Canonical phases.* command manifest.
|
||||
* Note: `phases.archive` is SDK-only; CJS `gsd-tools phases` currently supports list/clear.
|
||||
*/
|
||||
export const PHASES_COMMAND_MANIFEST: readonly CommandManifestEntry[] = [
|
||||
{ family: 'phases', canonical: 'phases.list', aliases: ['phases list'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'phases', canonical: 'phases.clear', aliases: ['phases clear'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'phases', canonical: 'phases.archive', aliases: ['phases archive'], mutation: true, outputMode: 'json' },
|
||||
] as const;
|
||||
11
sdk/src/query/command-manifest.roadmap.ts
Normal file
11
sdk/src/query/command-manifest.roadmap.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { CommandManifestEntry } from './command-manifest.types.js';
|
||||
|
||||
/**
|
||||
* Canonical roadmap.* command manifest.
|
||||
*/
|
||||
export const ROADMAP_COMMAND_MANIFEST: readonly CommandManifestEntry[] = [
|
||||
{ family: 'roadmap', canonical: 'roadmap.analyze', aliases: ['roadmap analyze'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'roadmap', canonical: 'roadmap.get-phase', aliases: ['roadmap get-phase'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'roadmap', canonical: 'roadmap.update-plan-progress', aliases: ['roadmap update-plan-progress'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'roadmap', canonical: 'roadmap.annotate-dependencies', aliases: ['roadmap annotate-dependencies'], mutation: true, outputMode: 'json' },
|
||||
] as const;
|
||||
31
sdk/src/query/command-manifest.state.ts
Normal file
31
sdk/src/query/command-manifest.state.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { CommandManifestEntry } from './command-manifest.types.js';
|
||||
|
||||
/**
|
||||
* Canonical state.* command manifest.
|
||||
*
|
||||
* Source of truth for the state family seam. Adapters derive registry aliases,
|
||||
* mutation classification, and CJS subcommand routing metadata from this list.
|
||||
*/
|
||||
export const STATE_COMMAND_MANIFEST: readonly CommandManifestEntry[] = [
|
||||
{ family: 'state', canonical: 'state.load', aliases: [], mutation: false, outputMode: 'raw' },
|
||||
{ family: 'state', canonical: 'state.json', aliases: ['state json'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.get', aliases: ['state get'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.update', aliases: ['state update'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.patch', aliases: ['state patch'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.begin-phase', aliases: ['state begin-phase'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.advance-plan', aliases: ['state advance-plan'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.record-metric', aliases: ['state record-metric'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.update-progress', aliases: ['state update-progress'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.add-decision', aliases: ['state add-decision'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.add-blocker', aliases: ['state add-blocker'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.resolve-blocker', aliases: ['state resolve-blocker'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.record-session', aliases: ['state record-session'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.signal-waiting', aliases: ['state signal-waiting'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.signal-resume', aliases: ['state signal-resume'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.planned-phase', aliases: ['state planned-phase'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.validate', aliases: ['state validate'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.sync', aliases: ['state sync'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.prune', aliases: ['state prune'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.milestone-switch', aliases: ['state milestone-switch'], mutation: true, outputMode: 'json' },
|
||||
{ family: 'state', canonical: 'state.add-roadmap-evolution', aliases: ['state add-roadmap-evolution'], mutation: true, outputMode: 'json' },
|
||||
] as const;
|
||||
17
sdk/src/query/command-manifest.ts
Normal file
17
sdk/src/query/command-manifest.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { STATE_COMMAND_MANIFEST } from './command-manifest.state.js';
|
||||
import { VERIFY_COMMAND_MANIFEST } from './command-manifest.verify.js';
|
||||
import { INIT_COMMAND_MANIFEST } from './command-manifest.init.js';
|
||||
import { PHASE_COMMAND_MANIFEST } from './command-manifest.phase.js';
|
||||
import { PHASES_COMMAND_MANIFEST } from './command-manifest.phases.js';
|
||||
import { VALIDATE_COMMAND_MANIFEST } from './command-manifest.validate.js';
|
||||
import { ROADMAP_COMMAND_MANIFEST } from './command-manifest.roadmap.js';
|
||||
|
||||
export const COMMAND_MANIFEST = [
|
||||
...STATE_COMMAND_MANIFEST,
|
||||
...VERIFY_COMMAND_MANIFEST,
|
||||
...INIT_COMMAND_MANIFEST,
|
||||
...PHASE_COMMAND_MANIFEST,
|
||||
...PHASES_COMMAND_MANIFEST,
|
||||
...VALIDATE_COMMAND_MANIFEST,
|
||||
...ROADMAP_COMMAND_MANIFEST,
|
||||
] as const;
|
||||
11
sdk/src/query/command-manifest.types.ts
Normal file
11
sdk/src/query/command-manifest.types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export type CommandFamily = 'state' | 'verify' | 'init' | 'phase' | 'phases' | 'validate' | 'roadmap';
|
||||
|
||||
export type OutputMode = 'json' | 'raw';
|
||||
|
||||
export interface CommandManifestEntry {
|
||||
family: CommandFamily;
|
||||
canonical: string;
|
||||
aliases: string[];
|
||||
mutation: boolean;
|
||||
outputMode: OutputMode;
|
||||
}
|
||||
11
sdk/src/query/command-manifest.validate.ts
Normal file
11
sdk/src/query/command-manifest.validate.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { CommandManifestEntry } from './command-manifest.types.js';
|
||||
|
||||
/**
|
||||
* Canonical validate.* command manifest.
|
||||
*/
|
||||
export const VALIDATE_COMMAND_MANIFEST: readonly CommandManifestEntry[] = [
|
||||
{ family: 'validate', canonical: 'validate.consistency', aliases: ['validate consistency'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'validate', canonical: 'validate.health', aliases: ['validate health'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'validate', canonical: 'validate.agents', aliases: ['validate agents'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'validate', canonical: 'validate.context', aliases: ['validate context'], mutation: false, outputMode: 'json' },
|
||||
] as const;
|
||||
15
sdk/src/query/command-manifest.verify.ts
Normal file
15
sdk/src/query/command-manifest.verify.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { CommandManifestEntry } from './command-manifest.types.js';
|
||||
|
||||
/**
|
||||
* Canonical verify.* command manifest.
|
||||
*/
|
||||
export const VERIFY_COMMAND_MANIFEST: readonly CommandManifestEntry[] = [
|
||||
{ family: 'verify', canonical: 'verify.plan-structure', aliases: ['verify plan-structure'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'verify', canonical: 'verify.phase-completeness', aliases: ['verify phase-completeness'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'verify', canonical: 'verify.references', aliases: ['verify references'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'verify', canonical: 'verify.commits', aliases: ['verify commits'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'verify', canonical: 'verify.artifacts', aliases: ['verify artifacts'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'verify', canonical: 'verify.key-links', aliases: ['verify key-links'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'verify', canonical: 'verify.schema-drift', aliases: ['verify schema-drift'], mutation: false, outputMode: 'json' },
|
||||
{ family: 'verify', canonical: 'verify.codebase-drift', aliases: ['verify codebase-drift'], mutation: false, outputMode: 'json' },
|
||||
] as const;
|
||||
118
sdk/src/query/command-seam-coverage.test.ts
Normal file
118
sdk/src/query/command-seam-coverage.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
import { createRegistry } from './index.js';
|
||||
import { STATE_COMMAND_MANIFEST } from './command-manifest.state.js';
|
||||
import { VERIFY_COMMAND_MANIFEST } from './command-manifest.verify.js';
|
||||
import { INIT_COMMAND_MANIFEST } from './command-manifest.init.js';
|
||||
import { PHASE_COMMAND_MANIFEST } from './command-manifest.phase.js';
|
||||
import { PHASES_COMMAND_MANIFEST } from './command-manifest.phases.js';
|
||||
import { VALIDATE_COMMAND_MANIFEST } from './command-manifest.validate.js';
|
||||
import { ROADMAP_COMMAND_MANIFEST } from './command-manifest.roadmap.js';
|
||||
import {
|
||||
STATE_COMMAND_ALIASES,
|
||||
VERIFY_COMMAND_ALIASES,
|
||||
INIT_COMMAND_ALIASES,
|
||||
PHASE_COMMAND_ALIASES,
|
||||
PHASES_COMMAND_ALIASES,
|
||||
VALIDATE_COMMAND_ALIASES,
|
||||
ROADMAP_COMMAND_ALIASES,
|
||||
} from './command-aliases.generated.js';
|
||||
|
||||
function subcommandFor(canonical: string, family: 'state' | 'verify' | 'init' | 'phase' | 'phases' | 'validate' | 'roadmap'): string {
|
||||
return canonical.slice(`${family}.`.length);
|
||||
}
|
||||
|
||||
describe('command seam coverage (manifest -> generated -> adapters)', () => {
|
||||
it('state/verify/init/phase/phases/validate/roadmap manifest canonicals are present in generated alias artifacts', () => {
|
||||
const generated = new Map<string, { aliases: string[]; subcommand: string; mutation: boolean }>();
|
||||
for (const entry of [...STATE_COMMAND_ALIASES, ...VERIFY_COMMAND_ALIASES, ...INIT_COMMAND_ALIASES, ...PHASE_COMMAND_ALIASES, ...PHASES_COMMAND_ALIASES, ...VALIDATE_COMMAND_ALIASES, ...ROADMAP_COMMAND_ALIASES]) {
|
||||
generated.set(entry.canonical, { aliases: [...entry.aliases], subcommand: entry.subcommand, mutation: !!entry.mutation });
|
||||
}
|
||||
|
||||
for (const entry of STATE_COMMAND_MANIFEST) {
|
||||
const g = generated.get(entry.canonical);
|
||||
expect(g, `missing generated canonical ${entry.canonical}`).toBeTruthy();
|
||||
expect(g?.subcommand).toBe(subcommandFor(entry.canonical, 'state'));
|
||||
expect(g?.aliases ?? []).toEqual(entry.aliases);
|
||||
expect(g?.mutation).toBe(entry.mutation);
|
||||
}
|
||||
|
||||
for (const entry of VERIFY_COMMAND_MANIFEST) {
|
||||
const g = generated.get(entry.canonical);
|
||||
expect(g, `missing generated canonical ${entry.canonical}`).toBeTruthy();
|
||||
expect(g?.subcommand).toBe(subcommandFor(entry.canonical, 'verify'));
|
||||
expect(g?.aliases ?? []).toEqual(entry.aliases);
|
||||
expect(g?.mutation).toBe(entry.mutation);
|
||||
}
|
||||
|
||||
for (const entry of INIT_COMMAND_MANIFEST) {
|
||||
const g = generated.get(entry.canonical);
|
||||
expect(g, `missing generated canonical ${entry.canonical}`).toBeTruthy();
|
||||
expect(g?.subcommand).toBe(subcommandFor(entry.canonical, 'init'));
|
||||
expect(g?.aliases ?? []).toEqual(entry.aliases);
|
||||
expect(g?.mutation).toBe(entry.mutation);
|
||||
}
|
||||
|
||||
for (const entry of PHASE_COMMAND_MANIFEST) {
|
||||
const g = generated.get(entry.canonical);
|
||||
expect(g, `missing generated canonical ${entry.canonical}`).toBeTruthy();
|
||||
expect(g?.subcommand).toBe(subcommandFor(entry.canonical, 'phase'));
|
||||
expect(g?.aliases ?? []).toEqual(entry.aliases);
|
||||
expect(g?.mutation).toBe(entry.mutation);
|
||||
}
|
||||
|
||||
for (const entry of PHASES_COMMAND_MANIFEST) {
|
||||
const g = generated.get(entry.canonical);
|
||||
expect(g, `missing generated canonical ${entry.canonical}`).toBeTruthy();
|
||||
expect(g?.subcommand).toBe(subcommandFor(entry.canonical, 'phases'));
|
||||
expect(g?.aliases ?? []).toEqual(entry.aliases);
|
||||
expect(g?.mutation).toBe(entry.mutation);
|
||||
}
|
||||
|
||||
for (const entry of VALIDATE_COMMAND_MANIFEST) {
|
||||
const g = generated.get(entry.canonical);
|
||||
expect(g, `missing generated canonical ${entry.canonical}`).toBeTruthy();
|
||||
expect(g?.subcommand).toBe(subcommandFor(entry.canonical, 'validate'));
|
||||
expect(g?.aliases ?? []).toEqual(entry.aliases);
|
||||
expect(g?.mutation).toBe(entry.mutation);
|
||||
}
|
||||
|
||||
for (const entry of ROADMAP_COMMAND_MANIFEST) {
|
||||
const g = generated.get(entry.canonical);
|
||||
expect(g, `missing generated canonical ${entry.canonical}`).toBeTruthy();
|
||||
expect(g?.subcommand).toBe(subcommandFor(entry.canonical, 'roadmap'));
|
||||
expect(g?.aliases ?? []).toEqual(entry.aliases);
|
||||
expect(g?.mutation).toBe(entry.mutation);
|
||||
}
|
||||
});
|
||||
|
||||
it('registry has every canonical + alias for migrated families', () => {
|
||||
const registry = createRegistry();
|
||||
for (const entry of [...STATE_COMMAND_ALIASES, ...VERIFY_COMMAND_ALIASES, ...INIT_COMMAND_ALIASES, ...PHASE_COMMAND_ALIASES, ...PHASES_COMMAND_ALIASES, ...VALIDATE_COMMAND_ALIASES, ...ROADMAP_COMMAND_ALIASES]) {
|
||||
expect(registry.has(entry.canonical), `missing registry canonical ${entry.canonical}`).toBe(true);
|
||||
for (const alias of entry.aliases) {
|
||||
expect(registry.has(alias), `missing registry alias ${alias}`).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('CJS seam adapters export expected router functions', () => {
|
||||
const require = createRequire(import.meta.url);
|
||||
const stateRouter = require('../../../get-shit-done/bin/lib/state-command-router.cjs');
|
||||
const verifyRouter = require('../../../get-shit-done/bin/lib/verify-command-router.cjs');
|
||||
const initRouter = require('../../../get-shit-done/bin/lib/init-command-router.cjs');
|
||||
const phaseRouter = require('../../../get-shit-done/bin/lib/phase-command-router.cjs');
|
||||
const phasesRouter = require('../../../get-shit-done/bin/lib/phases-command-router.cjs');
|
||||
const validateRouter = require('../../../get-shit-done/bin/lib/validate-command-router.cjs');
|
||||
const roadmapRouter = require('../../../get-shit-done/bin/lib/roadmap-command-router.cjs');
|
||||
|
||||
expect(typeof stateRouter.routeStateCommand).toBe('function');
|
||||
expect(typeof verifyRouter.routeVerifyCommand).toBe('function');
|
||||
expect(typeof initRouter.routeInitCommand).toBe('function');
|
||||
expect(typeof phaseRouter.routePhaseCommand).toBe('function');
|
||||
expect(typeof phasesRouter.routePhasesCommand).toBe('function');
|
||||
expect(typeof validateRouter.routeValidateCommand).toBe('function');
|
||||
expect(typeof roadmapRouter.routeRoadmapCommand).toBe('function');
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,19 @@ import { frontmatterGet } from './frontmatter.js';
|
||||
import { configGet, configPath, resolveModel } from './config-query.js';
|
||||
import { stateJson, stateGet, stateSnapshot } from './state.js';
|
||||
import { stateProjectLoad } from './state-project-load.js';
|
||||
import {
|
||||
STATE_COMMAND_ALIASES,
|
||||
STATE_MUTATION_COMMANDS,
|
||||
VERIFY_COMMAND_ALIASES,
|
||||
INIT_COMMAND_ALIASES,
|
||||
PHASE_COMMAND_ALIASES,
|
||||
PHASE_MUTATION_COMMANDS,
|
||||
PHASES_COMMAND_ALIASES,
|
||||
PHASES_MUTATION_COMMANDS,
|
||||
VALIDATE_COMMAND_ALIASES,
|
||||
ROADMAP_COMMAND_ALIASES,
|
||||
ROADMAP_MUTATION_COMMANDS,
|
||||
} from './command-aliases.generated.js';
|
||||
import { findPhase, phasePlanIndex } from './phase.js';
|
||||
import { phaseListPlans, phaseListArtifacts } from './phase-list-queries.js';
|
||||
import { planTaskStructure } from './plan-task-structure.js';
|
||||
@@ -126,28 +139,14 @@ export { normalizeQueryCommand } from './normalize-query-command.js';
|
||||
* (they emit JSON for workflows; agents perform writes).
|
||||
*/
|
||||
export const QUERY_MUTATION_COMMANDS = new Set<string>([
|
||||
'state.update', 'state.patch', 'state.begin-phase', 'state.advance-plan',
|
||||
'state.record-metric', 'state.update-progress', 'state.add-decision',
|
||||
'state.add-blocker', 'state.resolve-blocker', 'state.record-session',
|
||||
'state.planned-phase', 'state planned-phase',
|
||||
'state.signal-waiting', 'state signal-waiting',
|
||||
'state.signal-resume', 'state signal-resume',
|
||||
'state.sync', 'state sync',
|
||||
'state.prune', 'state prune',
|
||||
'state.milestone-switch', 'state milestone-switch',
|
||||
'state.add-roadmap-evolution', 'state add-roadmap-evolution',
|
||||
...STATE_MUTATION_COMMANDS,
|
||||
'frontmatter.set', 'frontmatter.merge', 'frontmatter.validate', 'frontmatter validate',
|
||||
'config-set', 'config-set-model-profile', 'config-new-project', 'config-ensure-section',
|
||||
'commit', 'check-commit', 'commit-to-subrepo',
|
||||
'template.fill', 'template.select', 'template select',
|
||||
'validate.health', 'validate health',
|
||||
'validate.context', 'validate context',
|
||||
'phase.add', 'phase.add-batch', 'phase.insert', 'phase.remove', 'phase.complete',
|
||||
'phase.scaffold', 'phases.clear', 'phases.archive',
|
||||
'phase add', 'phase add-batch', 'phase insert', 'phase remove', 'phase complete',
|
||||
'phase scaffold', 'phases clear', 'phases archive',
|
||||
'roadmap.update-plan-progress', 'roadmap update-plan-progress',
|
||||
'roadmap.annotate-dependencies', 'roadmap annotate-dependencies',
|
||||
...PHASE_MUTATION_COMMANDS,
|
||||
...PHASES_MUTATION_COMMANDS,
|
||||
...ROADMAP_MUTATION_COMMANDS,
|
||||
'requirements.mark-complete', 'requirements mark-complete',
|
||||
'todo.complete', 'todo complete',
|
||||
'milestone.complete', 'milestone complete',
|
||||
@@ -284,22 +283,62 @@ export function createRegistry(
|
||||
registry.register('config-get', configGet);
|
||||
registry.register('config-path', configPath);
|
||||
registry.register('resolve-model', resolveModel);
|
||||
registry.register('state.load', stateProjectLoad);
|
||||
registry.register('state.json', stateJson);
|
||||
registry.register('state.get', stateGet);
|
||||
const stateHandlers: Record<string, QueryHandler> = {
|
||||
'state.load': stateProjectLoad,
|
||||
'state.json': stateJson,
|
||||
'state.get': stateGet,
|
||||
'state.update': stateUpdate,
|
||||
'state.patch': statePatch,
|
||||
'state.begin-phase': stateBeginPhase,
|
||||
'state.advance-plan': stateAdvancePlan,
|
||||
'state.record-metric': stateRecordMetric,
|
||||
'state.update-progress': stateUpdateProgress,
|
||||
'state.add-decision': stateAddDecision,
|
||||
'state.add-blocker': stateAddBlocker,
|
||||
'state.resolve-blocker': stateResolveBlocker,
|
||||
'state.record-session': stateRecordSession,
|
||||
'state.signal-waiting': stateSignalWaiting,
|
||||
'state.signal-resume': stateSignalResume,
|
||||
'state.planned-phase': statePlannedPhase,
|
||||
'state.validate': stateValidate,
|
||||
'state.sync': stateSync,
|
||||
'state.prune': statePrune,
|
||||
'state.milestone-switch': stateMilestoneSwitch,
|
||||
'state.add-roadmap-evolution': stateAddRoadmapEvolution,
|
||||
};
|
||||
|
||||
for (const entry of STATE_COMMAND_ALIASES) {
|
||||
const handler = stateHandlers[entry.canonical];
|
||||
if (!handler) continue;
|
||||
registry.register(entry.canonical, handler);
|
||||
for (const alias of entry.aliases) {
|
||||
registry.register(alias, handler);
|
||||
}
|
||||
}
|
||||
|
||||
registry.register('state-snapshot', stateSnapshot);
|
||||
registry.register('find-phase', findPhase);
|
||||
registry.register('phase-plan-index', phasePlanIndex);
|
||||
registry.register('phase.list-plans', phaseListPlans);
|
||||
registry.register('phase list-plans', phaseListPlans);
|
||||
registry.register('phase.list-artifacts', phaseListArtifacts);
|
||||
registry.register('phase list-artifacts', phaseListArtifacts);
|
||||
registry.register('plan.task-structure', planTaskStructure);
|
||||
registry.register('plan task-structure', planTaskStructure);
|
||||
registry.register('requirements.extract-from-plans', requirementsExtractFromPlans);
|
||||
registry.register('requirements extract-from-plans', requirementsExtractFromPlans);
|
||||
registry.register('roadmap.analyze', roadmapAnalyze);
|
||||
registry.register('roadmap.get-phase', roadmapGetPhase);
|
||||
const roadmapHandlers: Record<string, QueryHandler> = {
|
||||
'roadmap.analyze': roadmapAnalyze,
|
||||
'roadmap.get-phase': roadmapGetPhase,
|
||||
'roadmap.update-plan-progress': roadmapUpdatePlanProgress,
|
||||
'roadmap.annotate-dependencies': roadmapAnnotateDependencies,
|
||||
};
|
||||
|
||||
for (const entry of ROADMAP_COMMAND_ALIASES) {
|
||||
const handler = roadmapHandlers[entry.canonical];
|
||||
if (!handler) continue;
|
||||
registry.register(entry.canonical, handler);
|
||||
for (const alias of entry.aliases) {
|
||||
registry.register(alias, handler);
|
||||
}
|
||||
}
|
||||
|
||||
registry.register('progress', progressJson);
|
||||
registry.register('progress.json', progressJson);
|
||||
|
||||
@@ -309,32 +348,6 @@ export function createRegistry(
|
||||
registry.register('frontmatter.validate', frontmatterValidate);
|
||||
registry.register('frontmatter validate', frontmatterValidate);
|
||||
|
||||
// State mutation handlers
|
||||
registry.register('state.update', stateUpdate);
|
||||
registry.register('state.patch', statePatch);
|
||||
registry.register('state.begin-phase', stateBeginPhase);
|
||||
registry.register('state.advance-plan', stateAdvancePlan);
|
||||
registry.register('state.record-metric', stateRecordMetric);
|
||||
registry.register('state.update-progress', stateUpdateProgress);
|
||||
registry.register('state.add-decision', stateAddDecision);
|
||||
registry.register('state.add-blocker', stateAddBlocker);
|
||||
registry.register('state.resolve-blocker', stateResolveBlocker);
|
||||
registry.register('state.record-session', stateRecordSession);
|
||||
registry.register('state.signal-waiting', stateSignalWaiting);
|
||||
registry.register('state.signal-resume', stateSignalResume);
|
||||
registry.register('state.validate', stateValidate);
|
||||
registry.register('state.sync', stateSync);
|
||||
registry.register('state.prune', statePrune);
|
||||
registry.register('state.milestone-switch', stateMilestoneSwitch);
|
||||
registry.register('state.add-roadmap-evolution', stateAddRoadmapEvolution);
|
||||
registry.register('state milestone-switch', stateMilestoneSwitch);
|
||||
registry.register('state add-roadmap-evolution', stateAddRoadmapEvolution);
|
||||
registry.register('state signal-waiting', stateSignalWaiting);
|
||||
registry.register('state signal-resume', stateSignalResume);
|
||||
registry.register('state validate', stateValidate);
|
||||
registry.register('state sync', stateSync);
|
||||
registry.register('state prune', statePrune);
|
||||
|
||||
// Config mutation handlers
|
||||
registry.register('config-set', configSet);
|
||||
registry.register('config-set-model-profile', configSetModelProfile);
|
||||
@@ -350,19 +363,26 @@ export function createRegistry(
|
||||
registry.register('template.select', templateSelect);
|
||||
registry.register('template select', templateSelect);
|
||||
|
||||
// Verification handlers
|
||||
registry.register('verify.plan-structure', verifyPlanStructure);
|
||||
registry.register('verify plan-structure', verifyPlanStructure);
|
||||
registry.register('verify.phase-completeness', verifyPhaseCompleteness);
|
||||
registry.register('verify phase-completeness', verifyPhaseCompleteness);
|
||||
registry.register('verify.artifacts', verifyArtifacts);
|
||||
registry.register('verify artifacts', verifyArtifacts);
|
||||
registry.register('verify.key-links', verifyKeyLinks);
|
||||
registry.register('verify key-links', verifyKeyLinks);
|
||||
registry.register('verify.commits', verifyCommits);
|
||||
registry.register('verify commits', verifyCommits);
|
||||
registry.register('verify.references', verifyReferences);
|
||||
registry.register('verify references', verifyReferences);
|
||||
const verifyHandlers: Record<string, QueryHandler> = {
|
||||
'verify.plan-structure': verifyPlanStructure,
|
||||
'verify.phase-completeness': verifyPhaseCompleteness,
|
||||
'verify.references': verifyReferences,
|
||||
'verify.commits': verifyCommits,
|
||||
'verify.artifacts': verifyArtifacts,
|
||||
'verify.key-links': verifyKeyLinks,
|
||||
'verify.schema-drift': verifySchemaDrift,
|
||||
'verify.codebase-drift': verifyCodebaseDrift,
|
||||
};
|
||||
|
||||
for (const entry of VERIFY_COMMAND_ALIASES) {
|
||||
const handler = verifyHandlers[entry.canonical];
|
||||
if (!handler) continue;
|
||||
registry.register(entry.canonical, handler);
|
||||
for (const alias of entry.aliases) {
|
||||
registry.register(alias, handler);
|
||||
}
|
||||
}
|
||||
|
||||
registry.register('verify-summary', verifySummary);
|
||||
registry.register('verify.summary', verifySummary);
|
||||
registry.register('verify summary', verifySummary);
|
||||
@@ -377,14 +397,21 @@ export function createRegistry(
|
||||
registry.register('check decision-coverage-plan', checkDecisionCoveragePlan);
|
||||
registry.register('check.decision-coverage-verify', checkDecisionCoverageVerify);
|
||||
registry.register('check decision-coverage-verify', checkDecisionCoverageVerify);
|
||||
registry.register('validate.consistency', validateConsistency);
|
||||
registry.register('validate consistency', validateConsistency);
|
||||
registry.register('validate.health', validateHealth);
|
||||
registry.register('validate health', validateHealth);
|
||||
registry.register('validate.agents', validateAgents);
|
||||
registry.register('validate agents', validateAgents);
|
||||
registry.register('validate.context', validateContext);
|
||||
registry.register('validate context', validateContext);
|
||||
const validateHandlers: Record<string, QueryHandler> = {
|
||||
'validate.consistency': validateConsistency,
|
||||
'validate.health': validateHealth,
|
||||
'validate.agents': validateAgents,
|
||||
'validate.context': validateContext,
|
||||
};
|
||||
|
||||
for (const entry of VALIDATE_COMMAND_ALIASES) {
|
||||
const handler = validateHandlers[entry.canonical];
|
||||
if (!handler) continue;
|
||||
registry.register(entry.canonical, handler);
|
||||
for (const alias of entry.aliases) {
|
||||
registry.register(alias, handler);
|
||||
}
|
||||
}
|
||||
|
||||
// Decision routing (SDK-only — no `gsd-tools.cjs` mirror yet; see QUERY-HANDLERS.md)
|
||||
registry.register('check.config-gates', checkConfigGates);
|
||||
@@ -406,82 +433,75 @@ export function createRegistry(
|
||||
registry.register('check.ship-ready', checkShipReady);
|
||||
registry.register('check ship-ready', checkShipReady);
|
||||
|
||||
// Phase lifecycle handlers
|
||||
registry.register('phase.add', phaseAdd);
|
||||
registry.register('phase.add-batch', phaseAddBatch);
|
||||
registry.register('phase.insert', phaseInsert);
|
||||
registry.register('phase.remove', phaseRemove);
|
||||
registry.register('phase.complete', phaseComplete);
|
||||
registry.register('phase.scaffold', phaseScaffold);
|
||||
registry.register('phases.clear', phasesClear);
|
||||
registry.register('phases.archive', phasesArchive);
|
||||
registry.register('phases.list', phasesList);
|
||||
registry.register('phase.next-decimal', phaseNextDecimal);
|
||||
// Space-delimited aliases for CJS compatibility
|
||||
registry.register('phase add', phaseAdd);
|
||||
registry.register('phase add-batch', phaseAddBatch);
|
||||
registry.register('phase insert', phaseInsert);
|
||||
registry.register('phase remove', phaseRemove);
|
||||
registry.register('phase complete', phaseComplete);
|
||||
registry.register('phase scaffold', phaseScaffold);
|
||||
registry.register('phases clear', phasesClear);
|
||||
registry.register('phases archive', phasesArchive);
|
||||
registry.register('phases list', phasesList);
|
||||
registry.register('phase next-decimal', phaseNextDecimal);
|
||||
const phaseHandlers: Record<string, QueryHandler> = {
|
||||
'phase.list-plans': phaseListPlans,
|
||||
'phase.list-artifacts': phaseListArtifacts,
|
||||
'phase.add': phaseAdd,
|
||||
'phase.add-batch': phaseAddBatch,
|
||||
'phase.insert': phaseInsert,
|
||||
'phase.remove': phaseRemove,
|
||||
'phase.complete': phaseComplete,
|
||||
'phase.scaffold': phaseScaffold,
|
||||
'phase.next-decimal': phaseNextDecimal,
|
||||
};
|
||||
|
||||
// Init composition handlers
|
||||
registry.register('init.execute-phase', initExecutePhase);
|
||||
registry.register('init.plan-phase', initPlanPhase);
|
||||
registry.register('init.new-milestone', initNewMilestone);
|
||||
registry.register('init.quick', initQuick);
|
||||
registry.register('init.resume', initResume);
|
||||
registry.register('init.verify-work', initVerifyWork);
|
||||
registry.register('init.phase-op', initPhaseOp);
|
||||
registry.register('init.todos', initTodos);
|
||||
registry.register('init.milestone-op', initMilestoneOp);
|
||||
registry.register('init.map-codebase', initMapCodebase);
|
||||
registry.register('init.new-workspace', initNewWorkspace);
|
||||
registry.register('init.list-workspaces', initListWorkspaces);
|
||||
registry.register('init.remove-workspace', initRemoveWorkspace);
|
||||
registry.register('init.ingest-docs', initIngestDocs);
|
||||
// Space-delimited aliases for CJS compatibility
|
||||
registry.register('init execute-phase', initExecutePhase);
|
||||
registry.register('init plan-phase', initPlanPhase);
|
||||
registry.register('init new-milestone', initNewMilestone);
|
||||
registry.register('init quick', initQuick);
|
||||
registry.register('init resume', initResume);
|
||||
registry.register('init verify-work', initVerifyWork);
|
||||
registry.register('init phase-op', initPhaseOp);
|
||||
registry.register('init todos', initTodos);
|
||||
registry.register('init milestone-op', initMilestoneOp);
|
||||
registry.register('init map-codebase', initMapCodebase);
|
||||
registry.register('init new-workspace', initNewWorkspace);
|
||||
registry.register('init list-workspaces', initListWorkspaces);
|
||||
registry.register('init remove-workspace', initRemoveWorkspace);
|
||||
registry.register('init ingest-docs', initIngestDocs);
|
||||
for (const entry of PHASE_COMMAND_ALIASES) {
|
||||
const handler = phaseHandlers[entry.canonical];
|
||||
if (!handler) continue;
|
||||
registry.register(entry.canonical, handler);
|
||||
for (const alias of entry.aliases) {
|
||||
registry.register(alias, handler);
|
||||
}
|
||||
}
|
||||
|
||||
// Complex init handlers
|
||||
registry.register('init.new-project', initNewProject);
|
||||
registry.register('init.progress', initProgress);
|
||||
registry.register('init.manager', initManager);
|
||||
registry.register('init new-project', initNewProject);
|
||||
registry.register('init progress', initProgress);
|
||||
registry.register('init manager', initManager);
|
||||
const phasesHandlers: Record<string, QueryHandler> = {
|
||||
'phases.list': phasesList,
|
||||
'phases.clear': phasesClear,
|
||||
'phases.archive': phasesArchive,
|
||||
};
|
||||
|
||||
for (const entry of PHASES_COMMAND_ALIASES) {
|
||||
const handler = phasesHandlers[entry.canonical];
|
||||
if (!handler) continue;
|
||||
registry.register(entry.canonical, handler);
|
||||
for (const alias of entry.aliases) {
|
||||
registry.register(alias, handler);
|
||||
}
|
||||
}
|
||||
|
||||
const initHandlers: Record<string, QueryHandler> = {
|
||||
'init.execute-phase': initExecutePhase,
|
||||
'init.plan-phase': initPlanPhase,
|
||||
'init.new-project': initNewProject,
|
||||
'init.new-milestone': initNewMilestone,
|
||||
'init.quick': initQuick,
|
||||
'init.ingest-docs': initIngestDocs,
|
||||
'init.resume': initResume,
|
||||
'init.verify-work': initVerifyWork,
|
||||
'init.phase-op': initPhaseOp,
|
||||
'init.todos': initTodos,
|
||||
'init.milestone-op': initMilestoneOp,
|
||||
'init.map-codebase': initMapCodebase,
|
||||
'init.progress': initProgress,
|
||||
'init.manager': initManager,
|
||||
'init.new-workspace': initNewWorkspace,
|
||||
'init.list-workspaces': initListWorkspaces,
|
||||
'init.remove-workspace': initRemoveWorkspace,
|
||||
};
|
||||
|
||||
for (const entry of INIT_COMMAND_ALIASES) {
|
||||
const handler = initHandlers[entry.canonical];
|
||||
if (!handler) continue;
|
||||
registry.register(entry.canonical, handler);
|
||||
for (const alias of entry.aliases) {
|
||||
registry.register(alias, handler);
|
||||
}
|
||||
}
|
||||
|
||||
// Domain-specific handlers (fully implemented)
|
||||
registry.register('agent-skills', agentSkills);
|
||||
registry.register('roadmap.update-plan-progress', roadmapUpdatePlanProgress);
|
||||
registry.register('roadmap update-plan-progress', roadmapUpdatePlanProgress);
|
||||
registry.register('roadmap.annotate-dependencies', roadmapAnnotateDependencies);
|
||||
registry.register('roadmap annotate-dependencies', roadmapAnnotateDependencies);
|
||||
registry.register('requirements.mark-complete', requirementsMarkComplete);
|
||||
registry.register('requirements mark-complete', requirementsMarkComplete);
|
||||
registry.register('state.planned-phase', statePlannedPhase);
|
||||
registry.register('state planned-phase', statePlannedPhase);
|
||||
registry.register('verify.schema-drift', verifySchemaDrift);
|
||||
registry.register('verify schema-drift', verifySchemaDrift);
|
||||
registry.register('verify.codebase-drift', verifyCodebaseDrift);
|
||||
registry.register('verify codebase-drift', verifyCodebaseDrift);
|
||||
registry.register('todo.match-phase', todoMatchPhase);
|
||||
registry.register('todo match-phase', todoMatchPhase);
|
||||
registry.register('list-todos', listTodos);
|
||||
|
||||
@@ -7,15 +7,28 @@ describe('normalizeQueryCommand', () => {
|
||||
expect(normalizeQueryCommand('state', ['validate'])).toEqual(['state.validate', []]);
|
||||
});
|
||||
|
||||
it('merges verify known subcommands only', () => {
|
||||
expect(normalizeQueryCommand('verify', ['plan-structure', 'x.md'])).toEqual(['verify.plan-structure', ['x.md']]);
|
||||
expect(normalizeQueryCommand('verify', ['unknown-op'])).toEqual(['verify', ['unknown-op']]);
|
||||
});
|
||||
|
||||
it('maps bare state to state.load', () => {
|
||||
expect(normalizeQueryCommand('state', [])).toEqual(['state.load', []]);
|
||||
});
|
||||
|
||||
it('does not merge unknown state subcommands', () => {
|
||||
expect(normalizeQueryCommand('state', ['not-a-subcommand'])).toEqual(['state', ['not-a-subcommand']]);
|
||||
});
|
||||
|
||||
it('merges init workflows', () => {
|
||||
expect(normalizeQueryCommand('init', ['execute-phase', '9'])).toEqual(['init.execute-phase', ['9']]);
|
||||
expect(normalizeQueryCommand('init', ['new-project'])).toEqual(['init.new-project', []]);
|
||||
});
|
||||
|
||||
it('does not merge unknown init subcommands', () => {
|
||||
expect(normalizeQueryCommand('init', ['made-up-init-op', 'x'])).toEqual(['init', ['made-up-init-op', 'x']]);
|
||||
});
|
||||
|
||||
it('maps scaffold to phase.scaffold', () => {
|
||||
expect(normalizeQueryCommand('scaffold', ['phase-dir', '--phase', '1'])).toEqual([
|
||||
'phase.scaffold',
|
||||
@@ -33,7 +46,7 @@ describe('normalizeQueryCommand', () => {
|
||||
expect(normalizeQueryCommand('generate-slug', ['Hello'])).toEqual(['generate-slug', ['Hello']]);
|
||||
});
|
||||
|
||||
it('merges phase add-batch for future handler', () => {
|
||||
it('merges check/route helper commands', () => {
|
||||
expect(normalizeQueryCommand('check', ['config-gates', 'plan-phase'])).toEqual([
|
||||
'check.config-gates',
|
||||
['plan-phase'],
|
||||
@@ -41,10 +54,49 @@ describe('normalizeQueryCommand', () => {
|
||||
expect(normalizeQueryCommand('check', ['phase-ready', '3'])).toEqual(['check.phase-ready', ['3']]);
|
||||
expect(normalizeQueryCommand('check', ['auto-mode'])).toEqual(['check.auto-mode', []]);
|
||||
expect(normalizeQueryCommand('route', ['next-action'])).toEqual(['route.next-action', []]);
|
||||
});
|
||||
|
||||
it('merges known phase subcommands and preserves unknown ones', () => {
|
||||
expect(normalizeQueryCommand('phase', ['add-batch', '--descriptions', '[]'])).toEqual([
|
||||
'phase.add-batch',
|
||||
['--descriptions', '[]'],
|
||||
]);
|
||||
expect(normalizeQueryCommand('phase', ['made-up-phase-op', 'x'])).toEqual([
|
||||
'phase',
|
||||
['made-up-phase-op', 'x'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('merges known phases subcommands and preserves unknown ones', () => {
|
||||
expect(normalizeQueryCommand('phases', ['clear', 'v1.0'])).toEqual([
|
||||
'phases.clear',
|
||||
['v1.0'],
|
||||
]);
|
||||
expect(normalizeQueryCommand('phases', ['made-up-phases-op', 'x'])).toEqual([
|
||||
'phases',
|
||||
['made-up-phases-op', 'x'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('merges known validate subcommands and preserves unknown ones', () => {
|
||||
expect(normalizeQueryCommand('validate', ['consistency'])).toEqual([
|
||||
'validate.consistency',
|
||||
[],
|
||||
]);
|
||||
expect(normalizeQueryCommand('validate', ['made-up-validate-op', 'x'])).toEqual([
|
||||
'validate',
|
||||
['made-up-validate-op', 'x'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('merges known roadmap subcommands and preserves unknown ones', () => {
|
||||
expect(normalizeQueryCommand('roadmap', ['analyze'])).toEqual([
|
||||
'roadmap.analyze',
|
||||
[],
|
||||
]);
|
||||
expect(normalizeQueryCommand('roadmap', ['made-up-roadmap-op', 'x'])).toEqual([
|
||||
'roadmap',
|
||||
['made-up-roadmap-op', 'x'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,16 +7,23 @@
|
||||
* under `runCommand()` so two-token (and longer) invocations resolve to dotted registry names.
|
||||
*/
|
||||
|
||||
import {
|
||||
STATE_SUBCOMMANDS,
|
||||
VERIFY_SUBCOMMANDS,
|
||||
INIT_SUBCOMMANDS,
|
||||
PHASE_SUBCOMMANDS,
|
||||
PHASES_SUBCOMMANDS,
|
||||
VALIDATE_SUBCOMMANDS,
|
||||
ROADMAP_SUBCOMMANDS,
|
||||
} from './command-aliases.generated.js';
|
||||
|
||||
const MERGE_FIRST_WITH_SUBCOMMAND = new Set<string>([
|
||||
'state',
|
||||
'template',
|
||||
'frontmatter',
|
||||
'verify',
|
||||
'phase',
|
||||
'phases',
|
||||
'roadmap',
|
||||
'requirements',
|
||||
'validate',
|
||||
'init',
|
||||
'workstream',
|
||||
'intel',
|
||||
@@ -43,6 +50,62 @@ export function normalizeQueryCommand(command: string, args: string[]): [string,
|
||||
return ['state.load', []];
|
||||
}
|
||||
|
||||
if (command === 'state' && args.length > 0) {
|
||||
const sub = args[0];
|
||||
if (STATE_SUBCOMMANDS.has(sub)) {
|
||||
return [`state.${sub}`, args.slice(1)];
|
||||
}
|
||||
return [command, args];
|
||||
}
|
||||
|
||||
if (command === 'verify' && args.length > 0) {
|
||||
const sub = args[0];
|
||||
if (VERIFY_SUBCOMMANDS.has(sub)) {
|
||||
return [`verify.${sub}`, args.slice(1)];
|
||||
}
|
||||
return [command, args];
|
||||
}
|
||||
|
||||
if (command === 'init' && args.length > 0) {
|
||||
const sub = args[0];
|
||||
if (INIT_SUBCOMMANDS.has(sub)) {
|
||||
return [`init.${sub}`, args.slice(1)];
|
||||
}
|
||||
return [command, args];
|
||||
}
|
||||
|
||||
if (command === 'phase' && args.length > 0) {
|
||||
const sub = args[0];
|
||||
if (PHASE_SUBCOMMANDS.has(sub)) {
|
||||
return [`phase.${sub}`, args.slice(1)];
|
||||
}
|
||||
return [command, args];
|
||||
}
|
||||
|
||||
if (command === 'phases' && args.length > 0) {
|
||||
const sub = args[0];
|
||||
if (PHASES_SUBCOMMANDS.has(sub)) {
|
||||
return [`phases.${sub}`, args.slice(1)];
|
||||
}
|
||||
return [command, args];
|
||||
}
|
||||
|
||||
if (command === 'validate' && args.length > 0) {
|
||||
const sub = args[0];
|
||||
if (VALIDATE_SUBCOMMANDS.has(sub)) {
|
||||
return [`validate.${sub}`, args.slice(1)];
|
||||
}
|
||||
return [command, args];
|
||||
}
|
||||
|
||||
if (command === 'roadmap' && args.length > 0) {
|
||||
const sub = args[0];
|
||||
if (ROADMAP_SUBCOMMANDS.has(sub)) {
|
||||
return [`roadmap.${sub}`, args.slice(1)];
|
||||
}
|
||||
return [command, args];
|
||||
}
|
||||
|
||||
if (MERGE_FIRST_WITH_SUBCOMMAND.has(command) && args.length > 0) {
|
||||
const sub = args[0];
|
||||
return [`${command}.${sub}`, args.slice(1)];
|
||||
|
||||
Reference in New Issue
Block a user