refactor(query): manifest-backed routing seam + family adapters (#2908)

Merging validated command-seam foundation.
This commit is contained in:
Tom Boucher
2026-04-30 14:04:50 -04:00
committed by GitHub
parent 6dce1de4a7
commit 444db1714b
29 changed files with 1478 additions and 411 deletions

View File

@@ -246,6 +246,7 @@
"cli_modules": [
"artifacts.cjs",
"audit.cjs",
"command-aliases.generated.cjs",
"commands.cjs",
"config-schema.cjs",
"config.cjs",
@@ -258,23 +259,30 @@
"gap-checker.cjs",
"graphify.cjs",
"gsd2-import.cjs",
"init-command-router.cjs",
"init.cjs",
"install-profiles.cjs",
"intel.cjs",
"learnings.cjs",
"milestone.cjs",
"model-profiles.cjs",
"phase-command-router.cjs",
"phase.cjs",
"phases-command-router.cjs",
"planning-workspace.cjs",
"profile-output.cjs",
"profile-pipeline.cjs",
"roadmap-command-router.cjs",
"roadmap.cjs",
"schema-detect.cjs",
"secrets.cjs",
"security.cjs",
"state-command-router.cjs",
"state.cjs",
"template.cjs",
"uat.cjs",
"validate-command-router.cjs",
"verify-command-router.cjs",
"verify.cjs",
"workstream.cjs"
],

View File

@@ -348,7 +348,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
---
## CLI Modules (33 shipped)
## CLI Modules (41 shipped)
Full listing: `get-shit-done/bin/lib/*.cjs`.
@@ -356,6 +356,7 @@ Full listing: `get-shit-done/bin/lib/*.cjs`.
|--------|----------------|
| `artifacts.cjs` | Canonical artifact registry — known `.planning/` root file names; used by `gsd-health` W019 lint |
| `audit.cjs` | Audit dispatch, audit open sessions, audit storage helpers |
| `command-aliases.generated.cjs` | Generated CJS alias/subcommand metadata for manifest-backed family routers |
| `commands.cjs` | Misc CLI commands (slug, timestamp, todos, scaffolding, stats) |
| `config-schema.cjs` | Single source of truth for `VALID_CONFIG_KEYS` and dynamic key patterns; imported by both the validator and the config-schema-docs parity test |
| `config.cjs` | `config.json` read/write, section initialization; imports validator from `config-schema.cjs` |
@@ -368,23 +369,30 @@ Full listing: `get-shit-done/bin/lib/*.cjs`.
| `gap-checker.cjs` | Post-planning gap analysis (#2493): unified REQUIREMENTS.md + CONTEXT.md decisions vs PLAN.md coverage report (`gsd-tools gap-analysis`) |
| `graphify.cjs` | Knowledge-graph build/query/status/diff for `/gsd-graphify` |
| `gsd2-import.cjs` | External-plan ingest for `/gsd-from-gsd2` |
| `init-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools init` |
| `init.cjs` | Compound context loading for each workflow type |
| `install-profiles.cjs` | Install profile allowlist + skill staging for `--minimal` install (#2762); single source of truth for which `gsd-*` skills/agents land in runtime config dirs |
| `intel.cjs` | Codebase intel store backing `/gsd-intel` and `gsd-intel-updater` |
| `learnings.cjs` | Cross-phase learnings extraction for `/gsd-extract-learnings` |
| `milestone.cjs` | Milestone archival, requirements marking |
| `model-profiles.cjs` | Model profile resolution table (authoritative profile data) |
| `phase-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools phase` |
| `phase.cjs` | Phase directory operations, decimal numbering, plan indexing |
| `phases-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools phases` |
| `planning-workspace.cjs` | Planning path/workstream seam (`planningDir`, `planningPaths`, active-workstream routing, `.planning/.lock` orchestration) |
| `profile-output.cjs` | Profile rendering, USER-PROFILE.md and dev-preferences.md generation |
| `profile-pipeline.cjs` | User behavioral profiling data pipeline, session file scanning |
| `roadmap-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools roadmap` |
| `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress |
| `schema-detect.cjs` | Schema-drift detection for ORM patterns (Prisma, Drizzle, etc.) |
| `secrets.cjs` | Secret-config masking convention (`****<last-4>`) for integration keys managed by `/gsd-settings-integrations` — keeps plaintext out of `config-set` output |
| `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON/shell helpers |
| `state-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools state` |
| `state.cjs` | STATE.md parsing, updating, progression, metrics |
| `template.cjs` | Template selection and filling with variable substitution |
| `uat.cjs` | UAT file parsing, verification debt tracking, audit-uat support |
| `validate-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools validate` |
| `verify-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools verify` |
| `verify.cjs` | Plan structure, phase completeness, reference, commit validation |
| `workstream.cjs` | Workstream CRUD, migration, session-scoped active pointer |

View File

@@ -190,6 +190,13 @@ const workstream = require('./lib/workstream.cjs');
const docs = require('./lib/docs.cjs');
const learnings = require('./lib/learnings.cjs');
const gapChecker = require('./lib/gap-checker.cjs');
const { routeStateCommand } = require('./lib/state-command-router.cjs');
const { routeVerifyCommand } = require('./lib/verify-command-router.cjs');
const { routeInitCommand } = require('./lib/init-command-router.cjs');
const { routePhaseCommand } = require('./lib/phase-command-router.cjs');
const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
const { routeValidateCommand } = require('./lib/validate-command-router.cjs');
const { routeRoadmapCommand } = require('./lib/roadmap-command-router.cjs');
// ─── Arg parsing helpers ──────────────────────────────────────────────────────
@@ -430,73 +437,14 @@ function extractField(obj, fieldPath) {
async function runCommand(command, args, cwd, raw, defaultValue) {
switch (command) {
case 'state': {
const subcommand = args[1];
if (subcommand === 'json') {
state.cmdStateJson(cwd, raw);
} else if (subcommand === 'update') {
state.cmdStateUpdate(cwd, args[2], args[3]);
} else if (subcommand === 'get') {
state.cmdStateGet(cwd, args[2], raw);
} else if (subcommand === 'patch') {
const patches = {};
for (let i = 2; i < args.length; i += 2) {
const key = args[i].replace(/^--/, '');
const value = args[i + 1];
if (key && value !== undefined) {
patches[key] = value;
}
}
state.cmdStatePatch(cwd, patches, raw);
} else if (subcommand === 'advance-plan') {
state.cmdStateAdvancePlan(cwd, raw);
} else if (subcommand === 'record-metric') {
const { phase: p, plan, duration, tasks, files } = parseNamedArgs(args, ['phase', 'plan', 'duration', 'tasks', 'files']);
state.cmdStateRecordMetric(cwd, { phase: p, plan, duration, tasks, files }, raw);
} else if (subcommand === 'update-progress') {
state.cmdStateUpdateProgress(cwd, raw);
} else if (subcommand === 'add-decision') {
const { phase: p, summary, 'summary-file': summary_file, rationale, 'rationale-file': rationale_file } = parseNamedArgs(args, ['phase', 'summary', 'summary-file', 'rationale', 'rationale-file']);
state.cmdStateAddDecision(cwd, { phase: p, summary, summary_file, rationale: rationale || '', rationale_file }, raw);
} else if (subcommand === 'add-blocker') {
const { text, 'text-file': text_file } = parseNamedArgs(args, ['text', 'text-file']);
state.cmdStateAddBlocker(cwd, { text, text_file }, raw);
} else if (subcommand === 'resolve-blocker') {
state.cmdStateResolveBlocker(cwd, parseNamedArgs(args, ['text']).text, raw);
} else if (subcommand === 'record-session') {
const { 'stopped-at': stopped_at, 'resume-file': resume_file } = parseNamedArgs(args, ['stopped-at', 'resume-file']);
state.cmdStateRecordSession(cwd, { stopped_at, resume_file: resume_file || 'None' }, raw);
} else if (subcommand === 'begin-phase') {
const { phase: p, name, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
state.cmdStateBeginPhase(cwd, p, name, plans !== null ? parseInt(plans, 10) : null, raw);
} else if (subcommand === 'signal-waiting') {
const { type, question, options, phase: p } = parseNamedArgs(args, ['type', 'question', 'options', 'phase']);
state.cmdSignalWaiting(cwd, type, question, options, p, raw);
} else if (subcommand === 'signal-resume') {
state.cmdSignalResume(cwd, raw);
} else if (subcommand === 'planned-phase') {
const { phase: p, name, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
state.cmdStatePlannedPhase(cwd, p, plans !== null ? parseInt(plans, 10) : null, raw);
} else if (subcommand === 'validate') {
state.cmdStateValidate(cwd, raw);
} else if (subcommand === 'sync') {
const { verify } = parseNamedArgs(args, [], ['verify']);
state.cmdStateSync(cwd, { verify }, raw);
} else if (subcommand === 'prune') {
const { 'keep-recent': keepRecent, 'dry-run': dryRun } = parseNamedArgs(args, ['keep-recent'], ['dry-run']);
state.cmdStatePrune(cwd, { keepRecent: keepRecent || '3', dryRun: !!dryRun }, raw);
} else if (subcommand === 'complete-phase') {
state.cmdStateCompletePhase(cwd, raw);
} else if (subcommand === 'milestone-switch') {
// Bug #2630: reset STATE.md frontmatter + Current Position for new milestone.
// NB: the flag is `--milestone`, not `--version` — gsd-tools reserves
// `--version` as a globally-invalid help flag (see NEVER_VALID_FLAGS above).
const { milestone, name } = parseNamedArgs(args, ['milestone', 'name']);
state.cmdStateMilestoneSwitch(cwd, milestone, name, raw);
} else if (subcommand === undefined || subcommand === 'load') {
state.cmdStateLoad(cwd, raw);
} else {
error(`Unknown state subcommand: "${subcommand}". Available: load, json, get, patch, update, advance-plan, record-metric, update-progress, add-decision, add-blocker, resolve-blocker, record-session, begin-phase, signal-waiting, signal-resume, planned-phase, validate, sync, prune, complete-phase, milestone-switch`);
}
routeStateCommand({
state,
args,
cwd,
raw,
parseNamedArgs,
error,
});
break;
}
@@ -590,27 +538,13 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
}
case 'verify': {
const subcommand = args[1];
if (subcommand === 'plan-structure') {
verify.cmdVerifyPlanStructure(cwd, args[2], raw);
} else if (subcommand === 'phase-completeness') {
verify.cmdVerifyPhaseCompleteness(cwd, args[2], raw);
} else if (subcommand === 'references') {
verify.cmdVerifyReferences(cwd, args[2], raw);
} else if (subcommand === 'commits') {
verify.cmdVerifyCommits(cwd, args.slice(2), raw);
} else if (subcommand === 'artifacts') {
verify.cmdVerifyArtifacts(cwd, args[2], raw);
} else if (subcommand === 'key-links') {
verify.cmdVerifyKeyLinks(cwd, args[2], raw);
} else if (subcommand === 'schema-drift') {
const skipFlag = args.includes('--skip');
verify.cmdVerifySchemaDrift(cwd, args[2], skipFlag, raw);
} else if (subcommand === 'codebase-drift') {
verify.cmdVerifyCodebaseDrift(cwd, raw);
} else {
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, schema-drift, codebase-drift');
}
routeVerifyCommand({
verify,
args,
cwd,
raw,
error,
});
break;
}
@@ -680,37 +614,25 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
}
case 'phases': {
const subcommand = args[1];
if (subcommand === 'list') {
const typeIndex = args.indexOf('--type');
const phaseIndex = args.indexOf('--phase');
const options = {
type: typeIndex !== -1 ? args[typeIndex + 1] : null,
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
includeArchived: args.includes('--include-archived'),
};
phase.cmdPhasesList(cwd, options, raw);
} else if (subcommand === 'clear') {
milestone.cmdPhasesClear(cwd, raw, args.slice(2));
} else {
error('Unknown phases subcommand. Available: list, clear');
}
routePhasesCommand({
phase,
milestone,
args,
cwd,
raw,
error,
});
break;
}
case 'roadmap': {
const subcommand = args[1];
if (subcommand === 'get-phase') {
roadmap.cmdRoadmapGetPhase(cwd, args[2], raw);
} else if (subcommand === 'analyze') {
roadmap.cmdRoadmapAnalyze(cwd, raw);
} else if (subcommand === 'update-plan-progress') {
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
} else if (subcommand === 'annotate-dependencies') {
roadmap.cmdRoadmapAnnotateDependencies(cwd, args[2], raw);
} else {
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress, annotate-dependencies');
}
routeRoadmapCommand({
roadmap,
args,
cwd,
raw,
error,
});
break;
}
@@ -732,42 +654,13 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
}
case 'phase': {
const subcommand = args[1];
if (subcommand === 'next-decimal') {
phase.cmdPhaseNextDecimal(cwd, args[2], raw);
} else if (subcommand === 'add') {
const idIdx = args.indexOf('--id');
let customId = null;
const descArgs = [];
for (let i = 2; i < args.length; i++) {
if (args[i] === '--id' && i + 1 < args.length) {
customId = args[i + 1];
i++; // skip value
} else {
descArgs.push(args[i]);
}
}
phase.cmdPhaseAdd(cwd, descArgs.join(' '), raw, customId);
} else if (subcommand === 'add-batch') {
// Accepts JSON array of descriptions via --descriptions '[...]' or positional args
const descFlagIdx = args.indexOf('--descriptions');
let descriptions;
if (descFlagIdx !== -1 && args[descFlagIdx + 1]) {
try { descriptions = JSON.parse(args[descFlagIdx + 1]); } catch (e) { error('--descriptions must be a JSON array'); }
} else {
descriptions = args.slice(2).filter(a => a !== '--raw');
}
phase.cmdPhaseAddBatch(cwd, descriptions, raw);
} else if (subcommand === 'insert') {
phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
} else if (subcommand === 'remove') {
const forceFlag = args.includes('--force');
phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
} else if (subcommand === 'complete') {
phase.cmdPhaseComplete(cwd, args[2], raw);
} else {
error('Unknown phase subcommand. Available: next-decimal, add, add-batch, insert, remove, complete');
}
routePhaseCommand({
phase,
args,
cwd,
raw,
error,
});
break;
}
@@ -784,58 +677,15 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
}
case 'validate': {
const subcommand = args[1];
if (subcommand === 'consistency') {
verify.cmdValidateConsistency(cwd, raw);
} else if (subcommand === 'health') {
const repairFlag = args.includes('--repair');
const backfillFlag = args.includes('--backfill');
verify.cmdValidateHealth(cwd, { repair: repairFlag, backfill: backfillFlag }, raw);
} else if (subcommand === 'agents') {
verify.cmdValidateAgents(cwd, raw);
} else if (subcommand === 'context') {
// The model self-reports tokensUsed and contextWindow — the SDK has
// no privileged access to either. Recommendation copy lives here
// (the renderer), not in the classifier, so it can change without
// re-validating the math layer.
const opts = parseNamedArgs(args, ['tokens-used', 'context-window']);
if (opts['tokens-used'] === null) {
error('--tokens-used <integer> is required for `validate context`');
break;
}
if (opts['context-window'] === null) {
error('--context-window <integer> is required for `validate context`');
break;
}
const { classifyContextUtilization, STATES } = require('./lib/context-utilization.cjs');
const RECOMMENDATIONS = {
[STATES.HEALTHY]: null,
[STATES.WARNING]: 'Context is approaching the fracture zone — consider /gsd-thread to continue in a fresh window.',
[STATES.CRITICAL]: 'Reasoning quality may degrade past 70% utilization (fracture point). Run /gsd-thread now to preserve output quality.',
};
let classified;
try {
classified = classifyContextUtilization(Number(opts['tokens-used']), Number(opts['context-window']));
} catch (e) {
// Translate the classifier's TypeError into a CLI-shaped error
// message that names the offending flag.
const flag = /tokensUsed/.test(e.message) ? '--tokens-used' : '--context-window';
error(`${flag} must be a non-negative integer (window > 0), got the values supplied`);
break;
}
const result = { ...classified, recommendation: RECOMMENDATIONS[classified.state] };
if (args.includes('--json')) {
core.output(result, raw);
} else {
const lines = [`Context utilization: ${result.percent}% (${result.state})`];
if (result.recommendation) lines.push(result.recommendation);
// Use core.output's rawValue path for the sync-flush guarantee
// — process.stdout.write can be truncated on process exit.
core.output(result, true, lines.join('\n'));
}
} else {
error('Unknown validate subcommand. Available: consistency, health, agents, context');
}
routeValidateCommand({
verify,
args,
cwd,
raw,
parseNamedArgs,
output: core.output,
error,
});
break;
}
@@ -904,66 +754,14 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
}
case 'init': {
const workflow = args[1];
switch (workflow) {
case 'execute-phase': {
const { validate: epValidate, tdd: epTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: epValidate, tdd: epTdd });
break;
}
case 'plan-phase': {
const { validate: ppValidate, tdd: ppTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: ppValidate, tdd: ppTdd });
break;
}
case 'new-project':
init.cmdInitNewProject(cwd, raw);
break;
case 'new-milestone':
init.cmdInitNewMilestone(cwd, raw);
break;
case 'quick':
init.cmdInitQuick(cwd, args.slice(2).join(' '), raw);
break;
case 'ingest-docs':
init.cmdInitIngestDocs(cwd, raw);
break;
case 'resume':
init.cmdInitResume(cwd, raw);
break;
case 'verify-work':
init.cmdInitVerifyWork(cwd, args[2], raw);
break;
case 'phase-op':
init.cmdInitPhaseOp(cwd, args[2], raw);
break;
case 'todos':
init.cmdInitTodos(cwd, args[2], raw);
break;
case 'milestone-op':
init.cmdInitMilestoneOp(cwd, raw);
break;
case 'map-codebase':
init.cmdInitMapCodebase(cwd, raw);
break;
case 'progress':
init.cmdInitProgress(cwd, raw);
break;
case 'manager':
init.cmdInitManager(cwd, raw);
break;
case 'new-workspace':
init.cmdInitNewWorkspace(cwd, raw);
break;
case 'list-workspaces':
init.cmdInitListWorkspaces(cwd, raw);
break;
case 'remove-workspace':
init.cmdInitRemoveWorkspace(cwd, args[2], raw);
break;
default:
error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, ingest-docs, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress, manager, new-workspace, list-workspaces, remove-workspace`);
}
routeInitCommand({
init,
args,
cwd,
raw,
parseNamedArgs,
error,
});
break;
}

View File

@@ -0,0 +1,118 @@
'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
*/
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 },
];
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 },
];
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 },
];
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 },
];
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 },
];
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 },
];
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 },
];
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,
STATE_SUBCOMMANDS,
VERIFY_SUBCOMMANDS,
INIT_SUBCOMMANDS,
PHASE_SUBCOMMANDS,
PHASES_SUBCOMMANDS,
VALIDATE_SUBCOMMANDS,
ROADMAP_SUBCOMMANDS,
};

View File

@@ -0,0 +1,70 @@
'use strict';
const { INIT_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
function routeInitCommand({ init, args, cwd, raw, parseNamedArgs, error }) {
const workflow = args[1];
switch (workflow) {
case 'execute-phase': {
const { validate: epValidate, tdd: epTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: epValidate, tdd: epTdd });
break;
}
case 'plan-phase': {
const { validate: ppValidate, tdd: ppTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: ppValidate, tdd: ppTdd });
break;
}
case 'new-project':
init.cmdInitNewProject(cwd, raw);
break;
case 'new-milestone':
init.cmdInitNewMilestone(cwd, raw);
break;
case 'quick':
init.cmdInitQuick(cwd, args.slice(2).join(' '), raw);
break;
case 'ingest-docs':
init.cmdInitIngestDocs(cwd, raw);
break;
case 'resume':
init.cmdInitResume(cwd, raw);
break;
case 'verify-work':
init.cmdInitVerifyWork(cwd, args[2], raw);
break;
case 'phase-op':
init.cmdInitPhaseOp(cwd, args[2], raw);
break;
case 'todos':
init.cmdInitTodos(cwd, args[2], raw);
break;
case 'milestone-op':
init.cmdInitMilestoneOp(cwd, raw);
break;
case 'map-codebase':
init.cmdInitMapCodebase(cwd, raw);
break;
case 'progress':
init.cmdInitProgress(cwd, raw);
break;
case 'manager':
init.cmdInitManager(cwd, raw);
break;
case 'new-workspace':
init.cmdInitNewWorkspace(cwd, raw);
break;
case 'list-workspaces':
init.cmdInitListWorkspaces(cwd, raw);
break;
case 'remove-workspace':
init.cmdInitRemoveWorkspace(cwd, args[2], raw);
break;
default:
error(`Unknown init workflow: ${workflow}\nAvailable: ${INIT_SUBCOMMANDS.join(', ')}`);
}
}
module.exports = {
routeInitCommand,
};

View File

@@ -0,0 +1,49 @@
'use strict';
const { PHASE_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
function routePhaseCommand({ phase, args, cwd, raw, error }) {
const subcommand = args[1];
if (subcommand === 'next-decimal') {
phase.cmdPhaseNextDecimal(cwd, args[2], raw);
} else if (subcommand === 'add') {
let customId = null;
const descArgs = [];
for (let i = 2; i < args.length; i++) {
if (args[i] === '--id' && i + 1 < args.length) {
customId = args[i + 1];
i++;
} else {
descArgs.push(args[i]);
}
}
phase.cmdPhaseAdd(cwd, descArgs.join(' '), raw, customId);
} else if (subcommand === 'add-batch') {
const descFlagIdx = args.indexOf('--descriptions');
let descriptions;
if (descFlagIdx !== -1 && args[descFlagIdx + 1]) {
try {
descriptions = JSON.parse(args[descFlagIdx + 1]);
} catch {
error('--descriptions must be a JSON array');
}
} else {
descriptions = args.slice(2).filter(a => a !== '--raw');
}
phase.cmdPhaseAddBatch(cwd, descriptions, raw);
} else if (subcommand === 'insert') {
phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
} else if (subcommand === 'remove') {
const forceFlag = args.includes('--force');
phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
} else if (subcommand === 'complete') {
phase.cmdPhaseComplete(cwd, args[2], raw);
} else {
error(`Unknown phase subcommand. Available: ${PHASE_SUBCOMMANDS.filter((s) => s !== 'list-plans' && s !== 'list-artifacts' && s !== 'scaffold').join(', ')}`);
}
}
module.exports = {
routePhaseCommand,
};

View File

@@ -0,0 +1,36 @@
'use strict';
const { PHASES_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
/**
* Manifest-backed phases subcommand router.
* Keeps gsd-tools.cjs thin while preserving current CJS semantics:
* - list
* - clear
*
* Note: `archive` is currently SDK-only (`phases.archive` handler in SDK query
* registry). CJS `gsd-tools phases` intentionally supports list/clear only.
*/
function routePhasesCommand({ phase, milestone, args, cwd, raw, error }) {
const subcommand = args[1];
if (subcommand === 'list') {
const typeIndex = args.indexOf('--type');
const phaseIndex = args.indexOf('--phase');
const options = {
type: typeIndex !== -1 ? args[typeIndex + 1] : null,
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
includeArchived: args.includes('--include-archived'),
};
phase.cmdPhasesList(cwd, options, raw);
} else if (subcommand === 'clear') {
milestone.cmdPhasesClear(cwd, raw, args.slice(2));
} else {
const cjsSupported = PHASES_SUBCOMMANDS.filter((s) => s !== 'archive');
error(`Unknown phases subcommand. Available: ${cjsSupported.join(', ')}`);
}
}
module.exports = {
routePhasesCommand,
};

View File

@@ -0,0 +1,23 @@
'use strict';
const { ROADMAP_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
const subcommand = args[1];
if (subcommand === 'get-phase') {
roadmap.cmdRoadmapGetPhase(cwd, args[2], raw);
} else if (subcommand === 'analyze') {
roadmap.cmdRoadmapAnalyze(cwd, raw);
} else if (subcommand === 'update-plan-progress') {
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
} else if (subcommand === 'annotate-dependencies') {
roadmap.cmdRoadmapAnnotateDependencies(cwd, args[2], raw);
} else {
error(`Unknown roadmap subcommand. Available: ${ROADMAP_SUBCOMMANDS.join(', ')}`);
}
}
module.exports = {
routeRoadmapCommand,
};

View File

@@ -0,0 +1,90 @@
'use strict';
const { STATE_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
/**
* Manifest-backed state subcommand router.
* Keeps gsd-tools.cjs thin while preserving existing command semantics.
*/
function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
const subcommand = args[1];
if (subcommand === 'json') {
state.cmdStateJson(cwd, raw);
} else if (subcommand === 'update') {
state.cmdStateUpdate(cwd, args[2], args[3]);
} else if (subcommand === 'get') {
state.cmdStateGet(cwd, args[2], raw);
} else if (subcommand === 'patch') {
const patches = {};
for (let i = 2; i < args.length; i += 2) {
const key = args[i].replace(/^--/, '');
const value = args[i + 1];
if (key && value !== undefined) {
patches[key] = value;
}
}
state.cmdStatePatch(cwd, patches, raw);
} else if (subcommand === 'advance-plan') {
state.cmdStateAdvancePlan(cwd, raw);
} else if (subcommand === 'record-metric') {
const { phase: p, plan, duration, tasks, files } = parseNamedArgs(args, ['phase', 'plan', 'duration', 'tasks', 'files']);
state.cmdStateRecordMetric(cwd, { phase: p, plan, duration, tasks, files }, raw);
} else if (subcommand === 'update-progress') {
state.cmdStateUpdateProgress(cwd, raw);
} else if (subcommand === 'add-decision') {
const { phase: p, summary, 'summary-file': summary_file, rationale, 'rationale-file': rationale_file } = parseNamedArgs(args, ['phase', 'summary', 'summary-file', 'rationale', 'rationale-file']);
state.cmdStateAddDecision(cwd, { phase: p, summary, summary_file, rationale: rationale || '', rationale_file }, raw);
} else if (subcommand === 'add-blocker') {
const { text, 'text-file': text_file } = parseNamedArgs(args, ['text', 'text-file']);
state.cmdStateAddBlocker(cwd, { text, text_file }, raw);
} else if (subcommand === 'resolve-blocker') {
state.cmdStateResolveBlocker(cwd, parseNamedArgs(args, ['text']).text, raw);
} else if (subcommand === 'record-session') {
const { 'stopped-at': stopped_at, 'resume-file': resume_file } = parseNamedArgs(args, ['stopped-at', 'resume-file']);
state.cmdStateRecordSession(cwd, { stopped_at, resume_file: resume_file || 'None' }, raw);
} else if (subcommand === 'begin-phase') {
const { phase: p, name, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
const parsedPlans = plans == null ? null : Number.parseInt(plans, 10);
if (plans != null && Number.isNaN(parsedPlans)) {
return error('Invalid --plans value. Expected an integer.');
}
state.cmdStateBeginPhase(cwd, p, name, parsedPlans, raw);
} else if (subcommand === 'signal-waiting') {
const { type, question, options, phase: p } = parseNamedArgs(args, ['type', 'question', 'options', 'phase']);
state.cmdSignalWaiting(cwd, type, question, options, p, raw);
} else if (subcommand === 'signal-resume') {
state.cmdSignalResume(cwd, raw);
} else if (subcommand === 'planned-phase') {
const { phase: p, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
const parsedPlans = plans == null ? null : Number.parseInt(plans, 10);
if (plans != null && Number.isNaN(parsedPlans)) {
return error('Invalid --plans value. Expected an integer.');
}
state.cmdStatePlannedPhase(cwd, p, parsedPlans, raw);
} else if (subcommand === 'validate') {
state.cmdStateValidate(cwd, raw);
} else if (subcommand === 'sync') {
const { verify } = parseNamedArgs(args, [], ['verify']);
state.cmdStateSync(cwd, { verify }, raw);
} else if (subcommand === 'prune') {
const { 'keep-recent': keepRecent, 'dry-run': dryRun } = parseNamedArgs(args, ['keep-recent'], ['dry-run']);
state.cmdStatePrune(cwd, { keepRecent: keepRecent || '3', dryRun: !!dryRun }, raw);
} else if (subcommand === 'complete-phase') {
state.cmdStateCompletePhase(cwd, raw);
} else if (subcommand === 'milestone-switch') {
const { milestone, name } = parseNamedArgs(args, ['milestone', 'name']);
state.cmdStateMilestoneSwitch(cwd, milestone, name, raw);
} else if (subcommand === 'add-roadmap-evolution') {
error('state add-roadmap-evolution is SDK-only. Use: gsd-sdk query state.add-roadmap-evolution ...');
} else if (subcommand === undefined || subcommand === 'load') {
state.cmdStateLoad(cwd, raw);
} else {
const available = ['load', 'complete-phase', ...STATE_SUBCOMMANDS.filter((s) => s !== 'load')];
error(`Unknown state subcommand: "${subcommand}". Available: ${available.join(', ')}`);
}
}
module.exports = {
routeStateCommand,
};

View File

@@ -0,0 +1,55 @@
'use strict';
const { VALIDATE_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
function routeValidateCommand({ verify, args, cwd, raw, parseNamedArgs, output, error }) {
const subcommand = args[1];
if (subcommand === 'consistency') {
verify.cmdValidateConsistency(cwd, raw);
} else if (subcommand === 'health') {
const repairFlag = args.includes('--repair');
const backfillFlag = args.includes('--backfill');
verify.cmdValidateHealth(cwd, { repair: repairFlag, backfill: backfillFlag }, raw);
} else if (subcommand === 'agents') {
verify.cmdValidateAgents(cwd, raw);
} else if (subcommand === 'context') {
const opts = parseNamedArgs(args, ['tokens-used', 'context-window']);
if (opts['tokens-used'] === null) {
error('--tokens-used <integer> is required for `validate context`');
return;
}
if (opts['context-window'] === null) {
error('--context-window <integer> is required for `validate context`');
return;
}
const { classifyContextUtilization, STATES } = require('./context-utilization.cjs');
const RECOMMENDATIONS = {
[STATES.HEALTHY]: null,
[STATES.WARNING]: 'Context is approaching the fracture zone — consider /gsd-thread to continue in a fresh window.',
[STATES.CRITICAL]: 'Reasoning quality may degrade past 70% utilization (fracture point). Run /gsd-thread now to preserve output quality.',
};
let classified;
try {
classified = classifyContextUtilization(Number(opts['tokens-used']), Number(opts['context-window']));
} catch (e) {
const flag = /tokensUsed/.test(e.message) ? '--tokens-used' : '--context-window';
error(`${flag} must be a non-negative integer (window > 0), got the values supplied`);
return;
}
const result = { ...classified, recommendation: RECOMMENDATIONS[classified.state] };
if (args.includes('--json')) {
output(result, raw);
} else {
const lines = [`Context utilization: ${result.percent}% (${result.state})`];
if (result.recommendation) lines.push(result.recommendation);
output(result, true, lines.join('\n'));
}
} else {
error(`Unknown validate subcommand. Available: ${VALIDATE_SUBCOMMANDS.join(', ')}`);
}
}
module.exports = {
routeValidateCommand,
};

View File

@@ -0,0 +1,34 @@
'use strict';
const { VERIFY_SUBCOMMANDS } = require('./command-aliases.generated.cjs');
function routeVerifyCommand({ verify, args, cwd, raw, error }) {
const subcommand = args[1];
if (subcommand === 'plan-structure') {
verify.cmdVerifyPlanStructure(cwd, args[2], raw);
} else if (subcommand === 'phase-completeness') {
verify.cmdVerifyPhaseCompleteness(cwd, args[2], raw);
} else if (subcommand === 'references') {
verify.cmdVerifyReferences(cwd, args[2], raw);
} else if (subcommand === 'commits') {
verify.cmdVerifyCommits(cwd, args.slice(2), raw);
} else if (subcommand === 'artifacts') {
verify.cmdVerifyArtifacts(cwd, args[2], raw);
} else if (subcommand === 'key-links') {
verify.cmdVerifyKeyLinks(cwd, args[2], raw);
} else if (subcommand === 'schema-drift') {
const rest = args.slice(2);
const skipFlag = rest.includes('--skip');
const phaseArg = rest.find((arg) => !arg.startsWith('-'));
verify.cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw);
} else if (subcommand === 'codebase-drift') {
verify.cmdVerifyCodebaseDrift(cwd, raw);
} else {
error(`Unknown verify subcommand. Available: ${VERIFY_SUBCOMMANDS.join(', ')}`);
}
}
module.exports = {
routeVerifyCommand,
};

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env node
import { createRequire } from 'node:module';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const require = createRequire(import.meta.url);
const here = dirname(fileURLToPath(import.meta.url));
const {
STATE_COMMAND_MANIFEST,
} = await import('../dist/query/command-manifest.state.js');
const {
VERIFY_COMMAND_MANIFEST,
} = await import('../dist/query/command-manifest.verify.js');
const {
INIT_COMMAND_MANIFEST,
} = await import('../dist/query/command-manifest.init.js');
const {
PHASE_COMMAND_MANIFEST,
} = await import('../dist/query/command-manifest.phase.js');
const {
PHASES_COMMAND_MANIFEST,
} = await import('../dist/query/command-manifest.phases.js');
const {
VALIDATE_COMMAND_MANIFEST,
} = await import('../dist/query/command-manifest.validate.js');
const {
ROADMAP_COMMAND_MANIFEST,
} = await import('../dist/query/command-manifest.roadmap.js');
const {
STATE_COMMAND_ALIASES,
VERIFY_COMMAND_ALIASES,
INIT_COMMAND_ALIASES,
PHASE_COMMAND_ALIASES,
PHASES_COMMAND_ALIASES,
VALIDATE_COMMAND_ALIASES,
ROADMAP_COMMAND_ALIASES,
} = await import('../dist/query/command-aliases.generated.js');
const cjsAliases = require(resolve(here, '..', '..', 'get-shit-done', 'bin', 'lib', 'command-aliases.generated.cjs'));
function toAliasEntries(manifest, family) {
const prefix = `${family}.`;
return manifest.map((entry) => ({
canonical: entry.canonical,
aliases: [...entry.aliases],
subcommand: entry.canonical.slice(prefix.length),
mutation: entry.mutation,
}));
}
function assertEqual(label, actual, expected) {
const a = JSON.stringify(actual);
const e = JSON.stringify(expected);
if (a !== e) {
throw new Error(
`${label} drift detected. Regenerate command alias artifacts and commit them.`,
);
}
}
const expectedState = toAliasEntries(STATE_COMMAND_MANIFEST, 'state');
const expectedVerify = toAliasEntries(VERIFY_COMMAND_MANIFEST, 'verify');
const expectedInit = toAliasEntries(INIT_COMMAND_MANIFEST, 'init');
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');
assertEqual('TS STATE_COMMAND_ALIASES', STATE_COMMAND_ALIASES, expectedState);
assertEqual('TS VERIFY_COMMAND_ALIASES', VERIFY_COMMAND_ALIASES, expectedVerify);
assertEqual('TS INIT_COMMAND_ALIASES', INIT_COMMAND_ALIASES, expectedInit);
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('CJS STATE_COMMAND_ALIASES', cjsAliases.STATE_COMMAND_ALIASES, expectedState);
assertEqual('CJS VERIFY_COMMAND_ALIASES', cjsAliases.VERIFY_COMMAND_ALIASES, expectedVerify);
assertEqual('CJS INIT_COMMAND_ALIASES', cjsAliases.INIT_COMMAND_ALIASES, expectedInit);
assertEqual('CJS PHASE_COMMAND_ALIASES', cjsAliases.PHASE_COMMAND_ALIASES, expectedPhase);
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);
console.log('command alias artifacts are fresh');

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env node
/**
* Build-time alias generator skeleton for command-manifest-driven routing.
*
* This pilot commits generated artifacts directly; this script documents and
* preserves the generation seam so future command families can be migrated
* without hand-maintained alias duplication.
*/
import { writeFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { STATE_COMMAND_MANIFEST } from '../src/query/command-manifest.state.js';
import { VERIFY_COMMAND_MANIFEST } from '../src/query/command-manifest.verify.js';
import { INIT_COMMAND_MANIFEST } from '../src/query/command-manifest.init.js';
import { PHASE_COMMAND_MANIFEST } from '../src/query/command-manifest.phase.js';
import { PHASES_COMMAND_MANIFEST } from '../src/query/command-manifest.phases.js';
import { VALIDATE_COMMAND_MANIFEST } from '../src/query/command-manifest.validate.js';
import { ROADMAP_COMMAND_MANIFEST } from '../src/query/command-manifest.roadmap.js';
function toSubcommand(canonical: string, family: 'state' | 'verify' | 'init' | 'phase' | 'phases' | 'validate' | 'roadmap'): string {
const prefix = `${family}.`;
return canonical.startsWith(prefix) ? canonical.slice(prefix.length) : canonical;
}
async function main(): Promise<void> {
const stateEntries = STATE_COMMAND_MANIFEST.map((entry) => ({
canonical: entry.canonical,
aliases: entry.aliases,
subcommand: toSubcommand(entry.canonical, 'state'),
mutation: entry.mutation,
}));
const verifyEntries = VERIFY_COMMAND_MANIFEST.map((entry) => ({
canonical: entry.canonical,
aliases: entry.aliases,
subcommand: toSubcommand(entry.canonical, 'verify'),
mutation: entry.mutation,
}));
const initEntries = INIT_COMMAND_MANIFEST.map((entry) => ({
canonical: entry.canonical,
aliases: entry.aliases,
subcommand: toSubcommand(entry.canonical, 'init'),
mutation: entry.mutation,
}));
const phaseEntries = PHASE_COMMAND_MANIFEST.map((entry) => ({
canonical: entry.canonical,
aliases: entry.aliases,
subcommand: toSubcommand(entry.canonical, 'phase'),
mutation: entry.mutation,
}));
const phasesEntries = PHASES_COMMAND_MANIFEST.map((entry) => ({
canonical: entry.canonical,
aliases: entry.aliases,
subcommand: toSubcommand(entry.canonical, 'phases'),
mutation: entry.mutation,
}));
const validateEntries = VALIDATE_COMMAND_MANIFEST.map((entry) => ({
canonical: entry.canonical,
aliases: entry.aliases,
subcommand: toSubcommand(entry.canonical, 'validate'),
mutation: entry.mutation,
}));
const roadmapEntries = ROADMAP_COMMAND_MANIFEST.map((entry) => ({
canonical: entry.canonical,
aliases: entry.aliases,
subcommand: toSubcommand(entry.canonical, 'roadmap'),
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;`,
'',
`export const VERIFY_COMMAND_ALIASES = ${JSON.stringify(verifyEntries, null, 2)} as const;`,
'',
`export const INIT_COMMAND_ALIASES = ${JSON.stringify(initEntries, null, 2)} as const;`,
'',
`export const PHASE_COMMAND_ALIASES = ${JSON.stringify(phaseEntries, null, 2)} as const;`,
'',
`export const PHASES_COMMAND_ALIASES = ${JSON.stringify(phasesEntries, null, 2)} as const;`,
'',
`export const VALIDATE_COMMAND_ALIASES = ${JSON.stringify(validateEntries, null, 2)} as const;`,
'',
`export const ROADMAP_COMMAND_ALIASES = ${JSON.stringify(roadmapEntries, null, 2)} 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]);',
'',
].join('\n');
await writeFile(outPath, header + body, 'utf-8');
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -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.

View 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]);

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;
}

View 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;

View 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;

View 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');
});
});

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@ const path = require('path');
const REPO_ROOT = path.join(__dirname, '..');
const REGISTRY_FILE = path.join(REPO_ROOT, 'sdk', 'src', 'query', 'index.ts');
const COMMAND_ALIASES_FILE = path.join(REPO_ROOT, 'get-shit-done', 'bin', 'lib', 'command-aliases.generated.cjs');
// Prose tokens that repeatedly appear after `gsd-sdk query` in English
// documentation but aren't real command names.
@@ -41,9 +42,39 @@ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build']);
function collectRegisteredNames() {
const src = fs.readFileSync(REGISTRY_FILE, 'utf8');
const names = new Set();
// Static registrations in index.ts
const re = /registry\.register\(\s*['"]([^'"]+)['"]/g;
let m;
while ((m = re.exec(src)) !== null) names.add(m[1]);
// Manifest-generated family aliases registered via loop in index.ts.
// Keep this in sync with command-manifest-driven routing seams.
try {
// eslint-disable-next-line global-require, import/no-dynamic-require
const aliases = require(COMMAND_ALIASES_FILE);
const familyArrays = [
aliases.STATE_COMMAND_ALIASES,
aliases.VERIFY_COMMAND_ALIASES,
aliases.INIT_COMMAND_ALIASES,
aliases.PHASE_COMMAND_ALIASES,
aliases.PHASES_COMMAND_ALIASES,
aliases.VALIDATE_COMMAND_ALIASES,
aliases.ROADMAP_COMMAND_ALIASES,
];
for (const arr of familyArrays) {
if (!Array.isArray(arr)) continue;
for (const entry of arr) {
if (entry?.canonical) names.add(entry.canonical);
if (Array.isArray(entry?.aliases)) {
for (const alias of entry.aliases) names.add(alias);
}
}
}
} catch {
// If generated aliases are unavailable, fall back to static extraction only.
}
return names;
}