Files
msd-core/sdk/src/query/detect-custom-files.ts
Rezolv c5b1445529 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
2026-04-20 18:09:02 -04:00

98 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Detect user-added files under GSD-managed install dirs not listed in the manifest.
*
* Port of `detect-custom-files` from `get-shit-done/bin/gsd-tools.cjs` (lines 1161–1239).
*/
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join, relative, resolve } from 'node:path';
import type { QueryHandler } from './utils.js';
const GSD_MANAGED_DIRS = [
'get-shit-done',
'agents',
join('commands', 'gsd'),
'hooks',
'command',
'skills',
];
function walkDir(dir: string, baseDir: string): string[] {
const results: string[] = [];
if (!existsSync(dir)) return results;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...walkDir(fullPath, baseDir));
} else {
const relPath = relative(baseDir, fullPath).split('\\').join('/');
results.push(relPath);
}
}
return results;
}
/**
* Args: `--config-dir <path>` (required) — runtime config directory to scan.
*/
export const detectCustomFiles: QueryHandler = async (args) => {
const configDirIdx = args.indexOf('--config-dir');
const configDir = configDirIdx !== -1 ? args[configDirIdx + 1] : null;
if (!configDir) {
return { data: { error: 'Usage: detect-custom-files --config-dir <path>' } };
}
const resolvedConfigDir = resolve(configDir);
if (!existsSync(resolvedConfigDir)) {
return { data: { error: `Config directory not found: ${resolvedConfigDir}` } };
}
const manifestPath = join(resolvedConfigDir, 'gsd-file-manifest.json');
if (!existsSync(manifestPath)) {
return {
data: {
custom_files: [] as string[],
custom_count: 0,
manifest_found: false,
},
};
}
let manifest: { version?: string; files?: Record<string, unknown> };
try {
manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { version?: string; files?: Record<string, unknown> };
} catch {
return {
data: {
custom_files: [] as string[],
custom_count: 0,
manifest_found: false,
error: 'manifest parse error',
},
};
}
const manifestKeys = new Set(Object.keys(manifest.files || {}));
const customFiles: string[] = [];
for (const managedDir of GSD_MANAGED_DIRS) {
const absDir = join(resolvedConfigDir, managedDir);
if (!existsSync(absDir)) continue;
for (const relPath of walkDir(absDir, resolvedConfigDir)) {
if (!manifestKeys.has(relPath)) {
customFiles.push(relPath);
}
}
}
return {
data: {
custom_files: customFiles,
custom_count: customFiles.length,
manifest_found: true,
manifest_version: manifest.version ?? null,
},
};
};