fix(2137): skip worktree isolation when .gitmodules detected (#2144)

* feat(sdk): add typed query foundation and gsd-sdk query (Phase 1)

Add sdk/src/query registry and handlers with tests, GSDQueryError, CLI query wiring, and supporting type/tool-scoping hooks. Update CHANGELOG. Vitest 4 constructor mock fixes in milestone-runner tests.

Made-with: Cursor

* fix(2137): skip worktree isolation when .gitmodules detected

When a project contains git submodules, worktree isolation cannot
correctly handle submodule commits — three separate gaps exist in
worktree setup, executor commit protocol, and merge-back. Rather
than patch each gap individually, detect .gitmodules at phase start
and fall back to sequential execution, which handles submodules
transparently (Option B).

Affected workflows: execute-phase.md, quick.md

---------

Co-authored-by: David Sienkowski <dave@sienkowski.com>
This commit is contained in:
Tom Boucher
2026-04-12 08:33:04 -04:00
committed by GitHub
parent 58632e0718
commit 002bcf2a8a
63 changed files with 17872 additions and 23 deletions

429
sdk/src/query/index.ts Normal file
View File

@@ -0,0 +1,429 @@
/**
* 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,
} 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 ────────────────────────────────────────────────
/**
* Set of command names that represent mutation operations.
* Used to wire event emission after successful dispatch.
*/
const MUTATION_COMMANDS = new Set([
'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',
'frontmatter.set', 'frontmatter.merge', 'frontmatter.validate',
'config-set', 'config-set-model-profile', 'config-new-project', 'config-ensure-section',
'commit', 'check-commit',
'template.fill', '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',
]);
// ─── Event builder ────────────────────────────────────────────────────────
/**
* Build a mutation event based on the command prefix and result.
*/
function buildMutationEvent(cmd: string, args: string[], result: QueryResult): GSDEvent {
const base = {
timestamp: new Date().toISOString(),
sessionId: '',
};
if (cmd.startsWith('state.')) {
return {
...base,
type: GSDEventType.StateMutation,
command: cmd,
fields: args.slice(0, 2),
success: true,
} as GSDStateMutationEvent;
}
if (cmd.startsWith('config-')) {
return {
...base,
type: GSDEventType.ConfigMutation,
command: cmd,
key: args[0] ?? '',
success: true,
} as GSDConfigMutationEvent;
}
if (cmd.startsWith('frontmatter.')) {
return {
...base,
type: GSDEventType.FrontmatterMutation,
command: cmd,
file: args[0] ?? '',
fields: args.slice(1),
success: true,
} as GSDFrontmatterMutationEvent;
}
if (cmd === 'commit' || cmd === 'check-commit') {
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('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('validate.') || cmd.startsWith('validate ')) {
return {
...base,
type: GSDEventType.ConfigMutation,
command: cmd,
key: args[0] ?? '',
success: true,
} as GSDConfigMutationEvent;
}
// template.fill / template.select
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;
}
// ─── 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);
// 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);
// 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('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 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;
}