feat(sdk): Phase 1 typed query foundation (gsd-sdk query) (#2118)

* 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

* chore: gitignore .cursor for local-only Cursor assets

Made-with: Cursor

* fix(sdk): harden query layer for PR review (paths, locks, CLI, ReDoS)

- resolvePathUnderProject: realpath + relative containment for frontmatter and key_links

- commitToSubrepo: path checks + sanitizeCommitMessage

- statePlannedPhase: readModifyWriteStateMd (lock); MUTATION_COMMANDS + events

- key_links: regexForKeyLinkPattern length/ReDoS guard; phase dirs: reject .. and separators

- gsd-sdk: strip --pick before parseArgs; strict parser; QueryRegistry.commands()

- progress: static GSDError import; tests updated

Made-with: Cursor

* feat(sdk): query follow-up — tests, QUERY-HANDLERS, registry, locks, intel depth

Made-with: Cursor

* docs(sdk): use ASCII punctuation in QUERY-HANDLERS.md

Made-with: Cursor
This commit is contained in:
Rezolv
2026-04-12 18:15:04 -04:00
committed by GitHub
parent 66a5f939b0
commit 6f79b1dd5e
36 changed files with 848 additions and 142 deletions

View File

@@ -74,27 +74,32 @@ function hashFile(filePath: string): string | null {
}
}
function searchJsonEntries(data: unknown, term: string): unknown[] {
/** Max recursion depth when walking JSON for intel queries (avoids stack overflow). */
export const MAX_JSON_SEARCH_DEPTH = 48;
export function searchJsonEntries(data: unknown, term: string, depth = 0): unknown[] {
const lowerTerm = term.toLowerCase();
const results: unknown[] = [];
if (depth > MAX_JSON_SEARCH_DEPTH) return results;
if (!data || typeof data !== 'object') return results;
function matchesInValue(value: unknown): boolean {
function matchesInValue(value: unknown, d: number): boolean {
if (d > MAX_JSON_SEARCH_DEPTH) return false;
if (typeof value === 'string') return value.toLowerCase().includes(lowerTerm);
if (Array.isArray(value)) return value.some(v => matchesInValue(v));
if (value && typeof value === 'object') return Object.values(value as object).some(v => matchesInValue(v));
if (Array.isArray(value)) return value.some(v => matchesInValue(v, d + 1));
if (value && typeof value === 'object') return Object.values(value as object).some(v => matchesInValue(v, d + 1));
return false;
}
if (Array.isArray(data)) {
for (const entry of data) {
if (matchesInValue(entry)) results.push(entry);
if (matchesInValue(entry, depth + 1)) results.push(entry);
}
} else {
for (const [, value] of Object.entries(data as object)) {
if (Array.isArray(value)) {
for (const entry of value) {
if (matchesInValue(entry)) results.push(entry);
if (matchesInValue(entry, depth + 1)) results.push(entry);
}
}
}