refactor(query): deepen command/dispatch seams and resolve coderabbit findings
* refactor(query): deepen command definition seam and fold fallback mapping cleanup * refactor(query): add shared dispatch formatting module seam * fix(query): restore QueryResult type import in dispatch deps * test/query: align raw-output policy and definition normalization contracts * refactor(query): deepen diagnosis, invariant report, and error taxonomy seams * refactor(query): deepen dispatch plan, fallback bridge, policy snapshot, and hints seams * refactor(query): deepen validation, fallback policy, capability, and result builder seams * refactor(query): deepen resolution strategy, output classifier, observability, and policy-capability seams * refactor(query): finalize deep strategy/classifier/observability/capability seams * test/query: address coderabbit inline and out-of-diff dispatch nits * fix(query): address remaining coderabbit input-validation and bridge stderr threads * fix(query): address remaining coderabbit dispatch and strategy/output nits
This commit is contained in:
@@ -1,14 +1,8 @@
|
||||
import { execFile } from 'node:child_process';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { extractField } from './registry.js';
|
||||
import { formatSuccess } from './query-dispatch-formatting.js';
|
||||
import type { QueryDispatchResult } from './query-dispatch-contract.js';
|
||||
import { mapFallbackDispatchError, toDispatchFailure } from './query-dispatch-error-mapper.js';
|
||||
|
||||
interface CjsFallbackQueryResult {
|
||||
mode: 'json' | 'text';
|
||||
output: unknown;
|
||||
stderr: string;
|
||||
}
|
||||
import { runFallbackBridge } from './query-fallback-bridge-adapter.js';
|
||||
import { fallbackBridgeNotices } from './query-dispatch-observability.js';
|
||||
|
||||
export interface RunCjsFallbackDispatchInput {
|
||||
projectDir: string;
|
||||
@@ -19,85 +13,21 @@ export interface RunCjsFallbackDispatchInput {
|
||||
pickField?: string;
|
||||
}
|
||||
|
||||
function dottedCommandToCjsArgv(normCmd: string, normArgs: string[]): string[] {
|
||||
if (normCmd.includes('.')) return [...normCmd.split('.'), ...normArgs];
|
||||
return [normCmd, ...normArgs];
|
||||
}
|
||||
|
||||
function execGsdToolsCjsQuery(
|
||||
projectDir: string,
|
||||
gsdToolsPath: string,
|
||||
normCmd: string,
|
||||
normArgs: string[],
|
||||
ws: string | undefined,
|
||||
): Promise<{ stdout: string; stderr: string }> {
|
||||
const cjsArgv = dottedCommandToCjsArgv(normCmd, normArgs);
|
||||
const wsSuffix = ws ? ['--ws', ws] : [];
|
||||
const fullArgv = [gsdToolsPath, ...cjsArgv, ...wsSuffix];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(
|
||||
process.execPath,
|
||||
fullArgv,
|
||||
{ cwd: projectDir, maxBuffer: 10 * 1024 * 1024, timeout: 30_000, killSignal: 'SIGKILL', env: { ...process.env } },
|
||||
(err, stdout, stderr) => {
|
||||
if (err) reject(err);
|
||||
else resolve({ stdout: stdout?.toString() ?? '', stderr: stderr?.toString() ?? '' });
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function parseCliQueryJsonOutput(raw: string, projectDir: string): Promise<unknown> {
|
||||
const trimmed = raw.trim();
|
||||
if (trimmed === '') return null;
|
||||
let jsonStr = trimmed;
|
||||
if (jsonStr.startsWith('@file:')) {
|
||||
const rel = jsonStr.slice(6).trim();
|
||||
const { resolvePathUnderProject } = await import('./helpers.js');
|
||||
const filePath = await resolvePathUnderProject(projectDir, rel);
|
||||
jsonStr = await readFile(filePath, 'utf-8');
|
||||
}
|
||||
return JSON.parse(jsonStr);
|
||||
}
|
||||
|
||||
async function runCjsFallbackQuery(
|
||||
projectDir: string,
|
||||
gsdToolsPath: string,
|
||||
normCmd: string,
|
||||
normArgs: string[],
|
||||
ws: string | undefined,
|
||||
): Promise<CjsFallbackQueryResult> {
|
||||
const { stdout, stderr } = await execGsdToolsCjsQuery(projectDir, gsdToolsPath, normCmd, normArgs, ws);
|
||||
|
||||
try {
|
||||
const output = await parseCliQueryJsonOutput(stdout, projectDir);
|
||||
return { mode: 'json', output, stderr };
|
||||
} catch {
|
||||
return { mode: 'text', output: stdout, stderr };
|
||||
}
|
||||
}
|
||||
|
||||
function formatFallbackOutput(data: unknown, mode: 'json' | 'text', pickField?: string): string | undefined {
|
||||
if (mode === 'text') {
|
||||
const text = String(data ?? '');
|
||||
if (!text.trim()) return undefined;
|
||||
return text.endsWith('\n') ? text : `${text}\n`;
|
||||
}
|
||||
let output: unknown = data;
|
||||
if (pickField) output = extractField(output, pickField);
|
||||
return `${JSON.stringify(output, null, 2)}\n`;
|
||||
return formatSuccess(data, mode, pickField);
|
||||
}
|
||||
|
||||
export async function runCjsFallbackDispatch(input: RunCjsFallbackDispatchInput): Promise<QueryDispatchResult> {
|
||||
const { projectDir, gsdToolsPath, normCmd, normArgs, ws, pickField } = input;
|
||||
const stderr = [
|
||||
`[gsd-sdk] '${normCmd}' not in native registry; falling back to gsd-tools.cjs.`,
|
||||
'[gsd-sdk] Transparent bridge — prefer adding a native handler when parity matters.',
|
||||
];
|
||||
const stderr = fallbackBridgeNotices(normCmd);
|
||||
|
||||
try {
|
||||
const fallback = await runCjsFallbackQuery(projectDir, gsdToolsPath, normCmd, normArgs, ws);
|
||||
const fallback = await runFallbackBridge({ projectDir, gsdToolsPath, normCmd, normArgs, ws });
|
||||
if (fallback.stderr.trim()) stderr.push(fallback.stderr.trimEnd());
|
||||
return {
|
||||
ok: true,
|
||||
@@ -106,9 +36,8 @@ export async function runCjsFallbackDispatch(input: RunCjsFallbackDispatchInput)
|
||||
exit_code: 0,
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
return toDispatchFailure(
|
||||
mapFallbackDispatchError(msg, normCmd, normArgs),
|
||||
mapFallbackDispatchError(err, normCmd, normArgs),
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user