fix(memory): preserve QMD results in noisy command output (#115023)

This commit is contained in:
Peter Steinberger
2026-07-28 04:09:28 -04:00
committed by GitHub
parent 521d05f290
commit 81628fb323
2 changed files with 78 additions and 16 deletions
@@ -25,6 +25,35 @@ complete`,
expect(results).toEqual([{ docid: "abc", score: 0.5 }]);
});
it.each([
{
name: "bracketed log prefix",
stdout: '[qmd] initializing search\n[{"docid":"abc","score":0.5}]',
},
{
name: "bracketed log with an empty array",
stdout: '[qmd] filters=[]\n[{"docid":"abc","score":0.5}]',
},
{
name: "bracketed log with result-shaped data",
stdout: '[qmd] previous=[{"docid":"stale","score":1}]\n[{"docid":"abc","score":0.5}]',
},
{
name: "leading no-results marker",
stdout: 'warning: no results found\n[{"docid":"abc","score":0.5}]',
},
{
name: "trailing no-results marker",
stdout: '[{"docid":"abc","score":0.5}]\nwarning: no results found',
},
{
name: "bracketed no-results marker",
stdout: '[qmd] warning: no results found\n[{"docid":"abc","score":0.5}]',
},
])("preserves query results after a $name", ({ stdout }) => {
expect(parseQmdQueryJson(stdout, "")).toEqual([{ docid: "abc", score: 0.5 }]);
});
it("preserves explicit qmd line metadata when present", () => {
const results = parseQmdQueryJson(
'[{"docid":"abc","score":0.5,"start_line":4,"end_line":6,"snippet":"@@ -10,1\\nignored"}]',
@@ -58,6 +87,9 @@ complete`,
it("treats prefixed no-results marker output as an empty result set", () => {
expect(parseQmdQueryJson("warning: no results found", "")).toStrictEqual([]);
expect(parseQmdQueryJson("", "[qmd] warning: no results found\n")).toStrictEqual([]);
expect(
parseQmdQueryJson("[qmd] initializing search\nwarning: no results found", ""),
).toStrictEqual([]);
});
it("keeps bounded stderr context UTF-16 safe", () => {
@@ -81,6 +113,12 @@ complete`,
);
});
it("rejects malformed bracket-heavy stdout without repeated rescanning", () => {
expect(() => parseQmdQueryJson("[".repeat(4_096), "")).toThrow(
/qmd query returned invalid JSON/i,
);
});
it("routes invalid-output diagnostics through console capture", () => {
vi.stubEnv("VITEST", "");
vi.stubEnv("NODE_ENV", "production");
@@ -21,9 +21,8 @@ export type QmdQueryResult = {
export function parseQmdQueryJson(stdout: string, stderr: string): QmdQueryResult[] {
const trimmedStdout = stdout.trim();
const trimmedStderr = stderr.trim();
const stdoutIsMarker = trimmedStdout.length > 0 && isQmdNoResultsOutput(trimmedStdout);
const stderrIsMarker = trimmedStderr.length > 0 && isQmdNoResultsOutput(trimmedStderr);
if (stdoutIsMarker || (!trimmedStdout && stderrIsMarker)) {
if (!trimmedStdout && stderrIsMarker) {
return [];
}
if (!trimmedStdout) {
@@ -39,6 +38,9 @@ export function parseQmdQueryJson(stdout: string, stderr: string): QmdQueryResul
}
const noisyPayload = extractFirstJsonArray(trimmedStdout);
if (!noisyPayload) {
if (isQmdNoResultsOutput(trimmedStdout)) {
return [];
}
throw new Error("qmd query JSON response was not an array");
}
const fallback = parseQmdQueryResultArray(noisyPayload);
@@ -127,26 +129,43 @@ function parseQmdLineNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
}
/** Extract the first complete JSON array from noisy stdout. */
/** Extract the first complete, standalone JSON result array from noisy stdout. */
function extractFirstJsonArray(raw: string): string | null {
const start = raw.indexOf("[");
if (start < 0) {
return null;
}
let start = -1;
let depth = 0;
let inString = false;
let escaped = false;
for (let i = start; i < raw.length; i += 1) {
let atLineStart = true;
for (let i = 0; i < raw.length; i += 1) {
const char = raw[i];
if (char === undefined) {
break;
}
if (start < 0) {
if (char === "\n") {
atLineStart = true;
continue;
}
if (atLineStart && (char === " " || char === "\t" || char === "\r")) {
continue;
}
// QMD emits result arrays on their own line; log fields can contain arrays too.
if (!atLineStart || char !== "[") {
atLineStart = false;
continue;
}
start = i;
depth = 1;
atLineStart = false;
continue;
}
if (inString) {
if (escaped) {
escaped = false;
continue;
}
if (char === "\\") {
} else if (char === "\\") {
escaped = true;
} else if (char === '"') {
inString = false;
@@ -159,12 +178,17 @@ function extractFirstJsonArray(raw: string): string | null {
}
if (char === "[") {
depth += 1;
} else if (char === "]") {
depth -= 1;
if (depth === 0) {
return raw.slice(start, i + 1);
}
continue;
}
if (char !== "]" || --depth !== 0) {
continue;
}
const candidate = raw.slice(start, i + 1);
if (parseQmdQueryResultArray(candidate) !== null) {
return candidate;
}
start = -1;
}
return null;
}