feat(sdk): golden parity harness and query handler CJS alignment (#2302 Track A) (#2341)

* feat(sdk): golden parity harness and query handler CJS alignment (#2302 Track A)

Golden/read-only parity tests and registry alignment, query handler fixes
(check-completion, state-mutation, commit, validate, summary, etc.), and
WAITING.json dual-write for .gsd/.planning readers.

Refs gsd-build/get-shit-done#2341

* fix(sdk): getMilestoneInfo matches GSD ROADMAP (🟡, last bold, STATE fallback)

- Recognize in-flight 🟡 milestone bullets like 🚧.
- Derive from last **vX.Y Title** before ## Phases when emoji absent.
- Fall back to STATE.md milestone when ROADMAP is missing; use last bare vX.Y
  in cleaned text instead of first (avoids v1.0 from shipped list).
- Fixes init.execute-phase milestone_version and buildStateFrontmatter after
  state.begin-phase (syncStateFrontmatter).

* feat(sdk): phase list, plan task structure, requirements extract handlers

- Register phase.list-plans, phase.list-artifacts, plan.task-structure,
  requirements.extract-from-plans (SDK-only; golden-policy exceptions).
- Add unit tests; document in QUERY-HANDLERS.md.
- writeProfile: honor --output, render dimensions, return profile_path and dimensions_scored.

* feat(sdk): centralize getGsdAgentsDir in query helpers

Extract agent directory resolution to helpers (GSD_AGENTS_DIR, primary
~/.claude/agents, legacy path). Use from init and docs-init init bundles.

docs(15): add 15-CONTEXT for autonomous phase-15 run.

* feat(sdk): query CLI CJS fallback and session correlation

- createRegistry(eventStream, sessionId) threads correlation into mutation events
- gsd-sdk query falls back to gsd-tools.cjs when no native handler matches
  (disable with GSD_QUERY_FALLBACK=off); stderr bridge warnings
- Export createRegistry from @gsd-build/sdk; add sdk/README.md
- Update QUERY-HANDLERS.md and registry module docs for fallback + sessionId
- Agents: prefer node dist/cli.js query over cat/grep for STATE and plans

* fix(sdk): init phase_found parity, docs-init agents path, state field extract

- Normalize findPhase not-found to null before roadmap fallback (matches findPhaseInternal)

- docs-init: use detectRuntime + resolveAgentsDir for checkAgentsInstalled

- state.cjs stateExtractField: horizontal whitespace only after colon (YAML progress guard)

- Tests: commit_docs default true; config-get golden uses temp config; golden integration green

Refs: #2302

* refactor(sdk): share SessionJsonlRecord in profile-extract-messages

CodeRabbit nit: dedupe JSONL record shape for isGenuineUserMessage and streamExtractMessages.

* fix(sdk): address CodeRabbit major threads (paths, gates, audit, verify)

- Resolve @file: and CLI JSON indirection relative to projectDir; guard empty normalized query command

- plan.task-structure + intel extract/patch-meta: resolvePathUnderProject containment

- check.config-gates: safe string booleans; plan_checker alias precedence over plan_check default

- state.validate/sync: phaseTokenMatches + comparePhaseNum ordering

- verify.schema-drift: token match phase dirs; files_modified from parsed frontmatter

- audit-open: has_scan_errors, unreadable rows, human report when scans fail

- requirements PLANNED key PLAN for root PLAN.md; gsd-tools timeout note

- ingest-docs: repo-root path containment; classifier output slug-hash

Golden parity test strips has_scan_errors until CJS adds field.

* fix: Resolve CodeRabbit security and quality findings
- Secure intel.ts and cli.ts against path traversal
- Catch and validate git add status in commit.ts
- Expand roadmap milestone marker extraction
- Fix parsing array-of-objects in frontmatter YAML
- Fix unhandled config evaluations
- Improve coverage test parity mapping

* test: raise planner character extraction limit to 48K

* fix(sdk): resolve TS build error in docs-init passing config
This commit is contained in:
Rezolv
2026-04-20 18:09:02 -04:00
committed by GitHub
parent c8807e38d7
commit c5b1445529
109 changed files with 11588 additions and 1030 deletions

View File

@@ -17,9 +17,13 @@
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 { configGet, configPath, resolveModel } from './config-query.js';
import { stateJson, stateGet, stateSnapshot } from './state.js';
import { stateProjectLoad } from './state-project-load.js';
import { findPhase, phasePlanIndex } from './phase.js';
import { phaseListPlans, phaseListArtifacts } from './phase-list-queries.js';
import { planTaskStructure } from './plan-task-structure.js';
import { requirementsExtractFromPlans } from './requirements-extract-from-plans.js';
import { roadmapAnalyze, roadmapGetPhase } from './roadmap.js';
import { progressJson } from './progress.js';
import { frontmatterSet, frontmatterMerge, frontmatterValidate } from './frontmatter-mutation.js';
@@ -27,6 +31,7 @@ import {
stateUpdate, statePatch, stateBeginPhase, stateAdvancePlan,
stateRecordMetric, stateUpdateProgress, stateAddDecision,
stateAddBlocker, stateResolveBlocker, stateRecordSession,
stateSignalWaiting, stateSignalResume, stateValidate, stateSync, statePrune,
} from './state-mutation.js';
import {
configSet, configSetModelProfile, configNewProject, configEnsureSection,
@@ -34,9 +39,9 @@ import {
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 { verifyKeyLinks, validateConsistency, validateHealth, validateAgents } from './validate.js';
import {
phaseAdd, phaseInsert, phaseRemove, phaseComplete,
phaseAdd, phaseAddBatch, phaseInsert, phaseRemove, phaseComplete,
phaseScaffold, phasesClear, phasesArchive,
phasesList, phaseNextDecimal,
} from './phase-lifecycle.js';
@@ -48,28 +53,46 @@ import {
} from './init.js';
import { initNewProject, initProgress, initManager } from './init-complex.js';
import { agentSkills } from './skills.js';
import { roadmapUpdatePlanProgress, requirementsMarkComplete } from './roadmap.js';
import { requirementsMarkComplete } from './roadmap.js';
import { roadmapUpdatePlanProgress } from './roadmap-update-plan-progress.js';
import { statePlannedPhase } from './state-mutation.js';
import { verifySchemaDrift } from './verify.js';
import { todoMatchPhase, statsJson, progressBar, listTodos, todoComplete } from './progress.js';
import {
todoMatchPhase, statsJson, statsTable, progressBar, progressTable, 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,
workstreamGet, workstreamList, workstreamCreate, workstreamSet, workstreamStatus,
workstreamComplete, workstreamProgress,
} from './workstream.js';
import { docsInit } from './init.js';
import { docsInit } from './docs-init.js';
import { uatRenderCheckpoint, auditUat } from './uat.js';
import { websearch } from './websearch.js';
import {
intelStatus, intelDiff, intelSnapshot, intelValidate, intelQuery,
intelExtractExports, intelPatchMeta,
intelExtractExports, intelPatchMeta, intelUpdate,
} from './intel.js';
import {
learningsCopy, learningsQuery, extractMessages, scanSessions, profileSample, profileQuestionnaire,
writeProfile, generateClaudeProfile, generateDevPreferences, generateClaudeMd,
learningsCopy, learningsQuery, learningsListHandler, learningsPrune, learningsDelete,
extractMessages, scanSessions, profileSample, profileQuestionnaire,
} from './profile.js';
import {
writeProfile, generateClaudeProfile, generateDevPreferences, generateClaudeMd,
} from './profile-output.js';
import { skillManifest } from './skill-manifest.js';
import { auditOpen } from './audit-open.js';
import { detectCustomFiles } from './detect-custom-files.js';
import { checkConfigGates } from './config-gates.js';
import { checkAutoMode } from './check-auto-mode.js';
import { checkPhaseReady } from './phase-ready.js';
import { routeNextAction } from './route-next-action.js';
import { detectPhaseType } from './detect-phase-type.js';
import { checkCompletion } from './check-completion.js';
import { checkGates } from './check-gates.js';
import { checkVerificationStatus } from './check-verification-status.js';
import { checkShipReady } from './check-ship-ready.js';
import { GSDEventStream } from '../event-stream.js';
import {
GSDEventType,
@@ -86,6 +109,8 @@ import type { QueryHandler, QueryResult } from './utils.js';
export type { QueryResult, QueryHandler } from './utils.js';
export { extractField } from './registry.js';
/** Same argv normalization as `gsd-sdk query` — use when calling `registry.dispatch()` with CLI-style `command` + `args`. */
export { normalizeQueryCommand } from './normalize-query-command.js';
// ─── Mutation commands set ────────────────────────────────────────────────
@@ -102,14 +127,18 @@ export const QUERY_MUTATION_COMMANDS = new Set<string>([
'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',
'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.add', 'phase.add-batch', 'phase.insert', 'phase.remove', 'phase.complete',
'phase.scaffold', 'phases.clear', 'phases.archive',
'phase add', 'phase insert', 'phase remove', 'phase complete',
'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',
'requirements.mark-complete', 'requirements mark-complete',
@@ -119,6 +148,8 @@ export const QUERY_MUTATION_COMMANDS = new Set<string>([
'workstream create', 'workstream set', 'workstream complete', 'workstream progress',
'docs-init',
'learnings.copy', 'learnings copy',
'learnings.prune', 'learnings prune',
'learnings.delete', 'learnings delete',
'intel.snapshot', 'intel.patch-meta', 'intel snapshot', 'intel patch-meta',
'write-profile', 'generate-claude-profile', 'generate-dev-preferences', 'generate-claude-md',
]);
@@ -128,13 +159,17 @@ export const QUERY_MUTATION_COMMANDS = new Set<string>([
/**
* 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.
* @param correlationSessionId - Optional session correlation id (from {@link createRegistry})
*/
function buildMutationEvent(cmd: string, args: string[], result: QueryResult): GSDEvent {
function buildMutationEvent(
correlationSessionId: string,
cmd: string,
args: string[],
result: QueryResult,
): GSDEvent {
const base = {
timestamp: new Date().toISOString(),
sessionId: '',
sessionId: correlationSessionId,
};
if (cmd.startsWith('template.') || cmd.startsWith('template ')) {
@@ -226,22 +261,36 @@ function buildMutationEvent(cmd: string, args: string[], result: QueryResult): G
* Create a fully-wired QueryRegistry with all native handlers registered.
*
* @param eventStream - Optional event stream for mutation event emission
* @param correlationSessionId - Optional session id threaded into mutation-related events
* @returns A QueryRegistry instance with all handlers registered
*/
export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
export function createRegistry(
eventStream?: GSDEventStream,
correlationSessionId?: string,
): QueryRegistry {
const mutationSessionId = correlationSessionId ?? '';
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('config-path', configPath);
registry.register('resolve-model', resolveModel);
registry.register('state.load', stateLoad);
registry.register('state.json', stateLoad);
registry.register('state.load', stateProjectLoad);
registry.register('state.json', stateJson);
registry.register('state.get', stateGet);
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);
registry.register('progress', progressJson);
@@ -264,6 +313,16 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
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 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);
@@ -303,9 +362,32 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
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);
// Decision routing (SDK-only — no `gsd-tools.cjs` mirror yet; see QUERY-HANDLERS.md)
registry.register('check.config-gates', checkConfigGates);
registry.register('check config-gates', checkConfigGates);
registry.register('check.auto-mode', checkAutoMode);
registry.register('check auto-mode', checkAutoMode);
registry.register('check.phase-ready', checkPhaseReady);
registry.register('check phase-ready', checkPhaseReady);
registry.register('route.next-action', routeNextAction);
registry.register('route next-action', routeNextAction);
registry.register('detect.phase-type', detectPhaseType);
registry.register('detect phase-type', detectPhaseType);
registry.register('check.completion', checkCompletion);
registry.register('check completion', checkCompletion);
registry.register('check.gates', checkGates);
registry.register('check gates', checkGates);
registry.register('check.verification-status', checkVerificationStatus);
registry.register('check verification-status', checkVerificationStatus);
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);
@@ -316,6 +398,7 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
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);
@@ -388,11 +471,18 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
registry.register('history.digest', historyDigest);
registry.register('history digest', historyDigest);
registry.register('history-digest', historyDigest);
registry.register('stats', statsJson);
registry.register('stats.json', statsJson);
registry.register('stats json', statsJson);
registry.register('stats.table', statsTable);
registry.register('stats table', statsTable);
registry.register('commit-to-subrepo', commitToSubrepo);
registry.register('progress.bar', progressBar);
registry.register('progress bar', progressBar);
registry.register('progress.table', progressTable);
registry.register('progress table', progressTable);
registry.register('workstream.get', workstreamGet);
registry.register('workstream get', workstreamGet);
registry.register('workstream.list', workstreamList);
registry.register('workstream list', workstreamList);
registry.register('workstream.create', workstreamCreate);
@@ -411,6 +501,17 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
registry.register('learnings copy', learningsCopy);
registry.register('learnings.query', learningsQuery);
registry.register('learnings query', learningsQuery);
registry.register('learnings.list', learningsListHandler);
registry.register('learnings list', learningsListHandler);
registry.register('learnings.prune', learningsPrune);
registry.register('learnings prune', learningsPrune);
registry.register('learnings.delete', learningsDelete);
registry.register('learnings delete', learningsDelete);
registry.register('skill-manifest', skillManifest);
registry.register('skill manifest', skillManifest);
registry.register('audit-open', auditOpen);
registry.register('audit open', auditOpen);
registry.register('detect-custom-files', detectCustomFiles);
registry.register('extract-messages', extractMessages);
registry.register('extract.messages', extractMessages);
registry.register('audit-uat', auditUat);
@@ -430,6 +531,8 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
registry.register('intel extract-exports', intelExtractExports);
registry.register('intel.patch-meta', intelPatchMeta);
registry.register('intel patch-meta', intelPatchMeta);
registry.register('intel.update', intelUpdate);
registry.register('intel update', intelUpdate);
registry.register('generate-claude-profile', generateClaudeProfile);
registry.register('generate-dev-preferences', generateDevPreferences);
registry.register('write-profile', writeProfile);
@@ -446,7 +549,7 @@ export function createRegistry(eventStream?: GSDEventStream): QueryRegistry {
registry.register(cmd, async (args: string[], projectDir: string) => {
const result = await original(args, projectDir);
try {
const event = buildMutationEvent(cmd, args, result);
const event = buildMutationEvent(mutationSessionId, cmd, args, result);
eventStream.emitEvent(event);
} catch {
// T-11-12: Event emission is fire-and-forget; never block mutation success