Files
get-shit-done/sdk/src/query/index.ts
Jeremy McSpadden 0a62e5223e fix(sdk): register init.ingest-docs handler and add registry drift guard (#2442)
The ingest-docs workflow called `gsd-sdk query init.ingest-docs` with a
fallback to `init.default` — neither was registered in createRegistry(),
so the workflow proceeded with `{}` and tried to parse project_exists,
planning_exists, has_git, and project_path from empty.

- Add initIngestDocs handler; register dotted + space aliases
- Simplify workflow call; drop broken fallback
- Repo-wide drift guard scans commands/, agents/, get-shit-done/,
  hooks/, bin/, scripts/, docs/ for `gsd-sdk query <cmd>` and fails
  on any reference with no registered handler (file:line citations)
- Unit tests for the new handler

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:55:37 -05:00

462 lines
20 KiB
TypeScript

/**
* Query module entry point — factory and re-exports.
*
* The `createRegistry()` factory creates a fully-wired `QueryRegistry`
* with all native handlers registered. New handlers are added here
* as they are migrated from gsd-tools.cjs.
*
* @example
* ```typescript
* import { createRegistry } from './query/index.js';
*
* const registry = createRegistry();
* const result = await registry.dispatch('generate-slug', ['My Phase'], projectDir);
* ```
*/
import { QueryRegistry } from './registry.js';
import { generateSlug, currentTimestamp } from './utils.js';
import { frontmatterGet } from './frontmatter.js';
import { configGet, resolveModel } from './config-query.js';
import { stateLoad, stateGet, stateSnapshot } from './state.js';
import { findPhase, phasePlanIndex } from './phase.js';
import { roadmapAnalyze, roadmapGetPhase } from './roadmap.js';
import { progressJson } from './progress.js';
import { frontmatterSet, frontmatterMerge, frontmatterValidate } from './frontmatter-mutation.js';
import {
stateUpdate, statePatch, stateBeginPhase, stateAdvancePlan,
stateRecordMetric, stateUpdateProgress, stateAddDecision,
stateAddBlocker, stateResolveBlocker, stateRecordSession,
} from './state-mutation.js';
import {
configSet, configSetModelProfile, configNewProject, configEnsureSection,
} from './config-mutation.js';
import { commit, checkCommit } from './commit.js';
import { templateFill, templateSelect } from './template.js';
import { verifyPlanStructure, verifyPhaseCompleteness, verifyArtifacts, verifyCommits, verifyReferences, verifySummary, verifyPathExists } from './verify.js';
import { verifyKeyLinks, validateConsistency, validateHealth } from './validate.js';
import {
phaseAdd, phaseInsert, phaseRemove, phaseComplete,
phaseScaffold, phasesClear, phasesArchive,
phasesList, phaseNextDecimal,
} from './phase-lifecycle.js';
import {
initExecutePhase, initPlanPhase, initNewMilestone, initQuick,
initResume, initVerifyWork, initPhaseOp, initTodos, initMilestoneOp,
initMapCodebase, initNewWorkspace, initListWorkspaces, initRemoveWorkspace,
initIngestDocs,
} from './init.js';
import { initNewProject, initProgress, initManager } from './init-complex.js';
import { agentSkills } from './skills.js';
import { roadmapUpdatePlanProgress, requirementsMarkComplete } from './roadmap.js';
import { statePlannedPhase } from './state-mutation.js';
import { verifySchemaDrift } from './verify.js';
import { todoMatchPhase, statsJson, progressBar, listTodos, todoComplete } from './progress.js';
import { milestoneComplete } from './phase-lifecycle.js';
import { summaryExtract, historyDigest } from './summary.js';
import { commitToSubrepo } from './commit.js';
import {
workstreamList, workstreamCreate, workstreamSet, workstreamStatus,
workstreamComplete, workstreamProgress,
} from './workstream.js';
import { docsInit } from './init.js';
import { uatRenderCheckpoint, auditUat } from './uat.js';
import { websearch } from './websearch.js';
import {
intelStatus, intelDiff, intelSnapshot, intelValidate, intelQuery,
intelExtractExports, intelPatchMeta,
} from './intel.js';
import {
learningsCopy, learningsQuery, extractMessages, scanSessions, profileSample, profileQuestionnaire,
writeProfile, generateClaudeProfile, generateDevPreferences, generateClaudeMd,
} from './profile.js';
import { GSDEventStream } from '../event-stream.js';
import {
GSDEventType,
type GSDEvent,
type GSDStateMutationEvent,
type GSDConfigMutationEvent,
type GSDFrontmatterMutationEvent,
type GSDGitCommitEvent,
type GSDTemplateFillEvent,
} from '../types.js';
import type { QueryHandler, QueryResult } from './utils.js';
// ─── Re-exports ────────────────────────────────────────────────────────────
export type { QueryResult, QueryHandler } from './utils.js';
export { extractField } from './registry.js';
// ─── Mutation commands set ────────────────────────────────────────────────
/**
* Command names that perform durable writes (disk, git, or global profile store).
* Used to wire event emission after successful dispatch. Both dotted and
* space-delimited aliases must be listed when both exist.
*
* See QUERY-HANDLERS.md for semantics. Init composition handlers are omitted
* (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',
'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',
'phase.add', 'phase.insert', 'phase.remove', 'phase.complete',
'phase.scaffold', 'phases.clear', 'phases.archive',
'phase add', 'phase insert', 'phase remove', 'phase complete',
'phase scaffold', 'phases clear', 'phases archive',
'roadmap.update-plan-progress', 'roadmap update-plan-progress',
'requirements.mark-complete', 'requirements mark-complete',
'todo.complete', 'todo complete',
'milestone.complete', 'milestone complete',
'workstream.create', 'workstream.set', 'workstream.complete', 'workstream.progress',
'workstream create', 'workstream set', 'workstream complete', 'workstream progress',
'docs-init',
'learnings.copy', 'learnings copy',
'intel.snapshot', 'intel.patch-meta', 'intel snapshot', 'intel patch-meta',
'write-profile', 'generate-claude-profile', 'generate-dev-preferences', 'generate-claude-md',
]);
// ─── Event builder ────────────────────────────────────────────────────────
/**
* Build a mutation event based on the command prefix and result.
*
* `sessionId` is empty until a future phase wires session correlation into
* the query layer; see QUERY-HANDLERS.md.
*/
function buildMutationEvent(cmd: string, args: string[], result: QueryResult): GSDEvent {
const base = {
timestamp: new Date().toISOString(),
sessionId: '',
};
if (cmd.startsWith('template.') || cmd.startsWith('template ')) {
const data = result.data as Record<string, unknown> | null;
return {
...base,
type: GSDEventType.TemplateFill,
templateType: (data?.template as string) ?? args[0] ?? '',
path: (data?.path as string) ?? args[1] ?? '',
created: (data?.created as boolean) ?? false,
} as GSDTemplateFillEvent;
}
if (cmd === 'commit' || cmd === 'check-commit' || cmd === 'commit-to-subrepo') {
const data = result.data as Record<string, unknown> | null;
return {
...base,
type: GSDEventType.GitCommit,
hash: (data?.hash as string) ?? null,
committed: (data?.committed as boolean) ?? false,
reason: (data?.reason as string) ?? '',
} as GSDGitCommitEvent;
}
if (cmd.startsWith('frontmatter.') || cmd.startsWith('frontmatter ')) {
return {
...base,
type: GSDEventType.FrontmatterMutation,
command: cmd,
file: args[0] ?? '',
fields: args.slice(1),
success: true,
} as GSDFrontmatterMutationEvent;
}
if (cmd.startsWith('config-')) {
return {
...base,
type: GSDEventType.ConfigMutation,
command: cmd,
key: args[0] ?? '',
success: true,
} as GSDConfigMutationEvent;
}
if (cmd.startsWith('validate.') || cmd.startsWith('validate ')) {
return {
...base,
type: GSDEventType.ConfigMutation,
command: cmd,
key: args[0] ?? '',
success: true,
} as GSDConfigMutationEvent;
}
if (cmd.startsWith('phase.') || cmd.startsWith('phase ') || cmd.startsWith('phases.') || cmd.startsWith('phases ')) {
return {
...base,
type: GSDEventType.StateMutation,
command: cmd,
fields: args.slice(0, 2),
success: true,
} as GSDStateMutationEvent;
}
if (cmd.startsWith('state.') || cmd.startsWith('state ')) {
return {
...base,
type: GSDEventType.StateMutation,
command: cmd,
fields: args.slice(0, 2),
success: true,
} as GSDStateMutationEvent;
}
// roadmap, requirements, todo, milestone, workstream, intel, profile, learnings, docs-init
return {
...base,
type: GSDEventType.StateMutation,
command: cmd,
fields: args.slice(0, 2),
success: true,
} as GSDStateMutationEvent;
}
// ─── Factory ───────────────────────────────────────────────────────────────
/**
* Create a fully-wired QueryRegistry with all native handlers registered.
*
* @param eventStream - Optional event stream for mutation event emission
* @returns A QueryRegistry instance with all handlers registered
*/
export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
const registry = new QueryRegistry();
registry.register('generate-slug', generateSlug);
registry.register('current-timestamp', currentTimestamp);
registry.register('frontmatter.get', frontmatterGet);
registry.register('config-get', configGet);
registry.register('resolve-model', resolveModel);
registry.register('state.load', stateLoad);
registry.register('state.json', stateLoad);
registry.register('state.get', stateGet);
registry.register('state-snapshot', stateSnapshot);
registry.register('find-phase', findPhase);
registry.register('phase-plan-index', phasePlanIndex);
registry.register('roadmap.analyze', roadmapAnalyze);
registry.register('roadmap.get-phase', roadmapGetPhase);
registry.register('progress', progressJson);
registry.register('progress.json', progressJson);
// Frontmatter mutation handlers
registry.register('frontmatter.set', frontmatterSet);
registry.register('frontmatter.merge', frontmatterMerge);
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);
// Config mutation handlers
registry.register('config-set', configSet);
registry.register('config-set-model-profile', configSetModelProfile);
registry.register('config-new-project', configNewProject);
registry.register('config-ensure-section', configEnsureSection);
// Git commit handlers
registry.register('commit', commit);
registry.register('check-commit', checkCommit);
// Template handlers
registry.register('template.fill', templateFill);
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);
registry.register('verify-summary', verifySummary);
registry.register('verify.summary', verifySummary);
registry.register('verify summary', verifySummary);
registry.register('verify-path-exists', verifyPathExists);
registry.register('verify.path-exists', verifyPathExists);
registry.register('verify path-exists', verifyPathExists);
registry.register('validate.consistency', validateConsistency);
registry.register('validate consistency', validateConsistency);
registry.register('validate.health', validateHealth);
registry.register('validate health', validateHealth);
// Phase lifecycle handlers
registry.register('phase.add', phaseAdd);
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 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);
// 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);
// 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);
// 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('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('todo.match-phase', todoMatchPhase);
registry.register('todo match-phase', todoMatchPhase);
registry.register('list-todos', listTodos);
registry.register('list.todos', listTodos);
registry.register('todo.complete', todoComplete);
registry.register('todo complete', todoComplete);
registry.register('milestone.complete', milestoneComplete);
registry.register('milestone complete', milestoneComplete);
registry.register('summary.extract', summaryExtract);
registry.register('summary extract', summaryExtract);
registry.register('summary-extract', summaryExtract);
registry.register('history.digest', historyDigest);
registry.register('history digest', historyDigest);
registry.register('history-digest', historyDigest);
registry.register('stats.json', statsJson);
registry.register('stats json', statsJson);
registry.register('commit-to-subrepo', commitToSubrepo);
registry.register('progress.bar', progressBar);
registry.register('progress bar', progressBar);
registry.register('workstream.list', workstreamList);
registry.register('workstream list', workstreamList);
registry.register('workstream.create', workstreamCreate);
registry.register('workstream create', workstreamCreate);
registry.register('workstream.set', workstreamSet);
registry.register('workstream set', workstreamSet);
registry.register('workstream.status', workstreamStatus);
registry.register('workstream status', workstreamStatus);
registry.register('workstream.complete', workstreamComplete);
registry.register('workstream complete', workstreamComplete);
registry.register('workstream.progress', workstreamProgress);
registry.register('workstream progress', workstreamProgress);
registry.register('docs-init', docsInit);
registry.register('websearch', websearch);
registry.register('learnings.copy', learningsCopy);
registry.register('learnings copy', learningsCopy);
registry.register('learnings.query', learningsQuery);
registry.register('learnings query', learningsQuery);
registry.register('extract-messages', extractMessages);
registry.register('extract.messages', extractMessages);
registry.register('audit-uat', auditUat);
registry.register('uat.render-checkpoint', uatRenderCheckpoint);
registry.register('uat render-checkpoint', uatRenderCheckpoint);
registry.register('intel.diff', intelDiff);
registry.register('intel diff', intelDiff);
registry.register('intel.snapshot', intelSnapshot);
registry.register('intel snapshot', intelSnapshot);
registry.register('intel.validate', intelValidate);
registry.register('intel validate', intelValidate);
registry.register('intel.status', intelStatus);
registry.register('intel status', intelStatus);
registry.register('intel.query', intelQuery);
registry.register('intel query', intelQuery);
registry.register('intel.extract-exports', intelExtractExports);
registry.register('intel extract-exports', intelExtractExports);
registry.register('intel.patch-meta', intelPatchMeta);
registry.register('intel patch-meta', intelPatchMeta);
registry.register('generate-claude-profile', generateClaudeProfile);
registry.register('generate-dev-preferences', generateDevPreferences);
registry.register('write-profile', writeProfile);
registry.register('profile-questionnaire', profileQuestionnaire);
registry.register('profile-sample', profileSample);
registry.register('scan-sessions', scanSessions);
registry.register('generate-claude-md', generateClaudeMd);
// Wire event emission for mutation commands
if (eventStream) {
for (const cmd of QUERY_MUTATION_COMMANDS) {
const original = registry.getHandler(cmd);
if (original) {
registry.register(cmd, async (args: string[], projectDir: string) => {
const result = await original(args, projectDir);
try {
const event = buildMutationEvent(cmd, args, result);
eventStream.emitEvent(event);
} catch {
// T-11-12: Event emission is fire-and-forget; never block mutation success
}
return result;
});
}
}
}
return registry;
}