mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-06 08:35:21 +08:00
fix(ui): read the lean-contract footer lines in CodeAtlasWeb's compact parser
On current main the compact text form of query_graph and search_graph
carries footer lines the frontend's parser did not know (returned,
total_relation, truncated; search_mode now follows the head):
rows: 2 (cols: n.name n.file_path)
loadConfig src/config.ts
isProduction src/config.ts
returned: 2
total: 2
total_relation: eq
has_more: false
truncated: false
The parser refused the first unknown line, and since the reader resolves
a file's module symbol through query_graph before it asks for the
source, no file could be opened on main ("query_graph: Antwort war nicht
lesbar: unbekannte Fusszeile einer kompakten Antwort: returned: 1").
Both parsers now read the new keys as fields (returned, totalRelation,
hasMore, truncated) rather than skipping them, so a capped answer stays
nameable, and refuse anything else as before. Two tests carry main's
exact output for the fixture. The recorded fixtures from before the
contract keep passing, as the fields are optional.
Verified against the embedded build on the re-stacked branch: files
open, the twin answers, search returns hits; provider tests 126 passed.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -120,6 +120,28 @@ describe('parseCompactRows', () => {
|
||||
expect(() => parseCompactRows(broken)).toThrow(/rows: 2 an, geliefert wurden 1/);
|
||||
});
|
||||
|
||||
it('liest die Zeilenform des schlanken Vertrags mit ihren Fusszeilen', () => {
|
||||
const lean = [
|
||||
'rows: 2 (cols: n.name n.file_path)',
|
||||
' loadConfig src/config.ts',
|
||||
' isProduction src/config.ts',
|
||||
'returned: 2',
|
||||
'total: 2',
|
||||
'total_relation: eq',
|
||||
'has_more: false',
|
||||
'truncated: false',
|
||||
'',
|
||||
].join('\n');
|
||||
const parsed = parseCompactRows(lean);
|
||||
expect(parsed.rows).toEqual([['loadConfig', 'src/config.ts'], ['isProduction', 'src/config.ts']]);
|
||||
expect(parsed.total).toBe(2);
|
||||
expect(parsed.returned).toBe(2);
|
||||
expect(parsed.totalRelation).toBe('eq');
|
||||
expect(parsed.hasMore).toBe(false);
|
||||
expect(parsed.truncated).toBe(false);
|
||||
expect(parsed.hint).toBeUndefined();
|
||||
});
|
||||
|
||||
it('wirft ohne total-Zeile, statt total zu erfinden', () => {
|
||||
const broken = ['rows: 1 (cols: b.name)', ' validateUser', ''].join('\n');
|
||||
expect(() => parseCompactRows(broken)).toThrow(/ohne total-Zeile/);
|
||||
@@ -162,6 +184,35 @@ describe('parseSearchResults', () => {
|
||||
it('nimmt die Zeilenform nicht als Suchform an', () => {
|
||||
expect(() => parseSearchResults(CALLS_RESPONSE)).toThrow(/unbekannte Zeile|unbekannter Kopf/);
|
||||
});
|
||||
|
||||
/*
|
||||
* Der schlanke Ausgabevertrag (#1597) haengt an beide Formen weitere
|
||||
* Fusszeilen: returned, total_relation, truncated (und bei der Suche
|
||||
* steht search_mode jetzt nach dem Kopf). Woertlich, was der Server auf
|
||||
* main am 2026-09-06 fuer fixtures/atlas-sample geantwortet hat; ein
|
||||
* Parser, der eine dieser Zeilen nicht kennt, lieferte vorher gar nichts.
|
||||
*/
|
||||
it('liest die Suchantwort des schlanken Vertrags mit ihren Fusszeilen', () => {
|
||||
const lean = [
|
||||
'results: 1 (cols: qn label file lines rank)',
|
||||
' atlas-sample.src.services.userService.createUser Function src/services/userService.ts 23-36 -15.26',
|
||||
'total: 1',
|
||||
'total_relation: eq',
|
||||
'search_mode: bm25',
|
||||
'returned: 1',
|
||||
'has_more: false',
|
||||
'truncated: false',
|
||||
'',
|
||||
].join('\n');
|
||||
const parsed = parseSearchResults(lean);
|
||||
expect(parsed.rows).toHaveLength(1);
|
||||
expect(parsed.total).toBe(1);
|
||||
expect(parsed.mode).toBe('bm25');
|
||||
expect(parsed.hasMore).toBe(false);
|
||||
expect(parsed.returned).toBe(1);
|
||||
expect(parsed.totalRelation).toBe('eq');
|
||||
expect(parsed.truncated).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hilfsfunktionen', () => {
|
||||
|
||||
@@ -29,6 +29,17 @@ export interface CompactRows {
|
||||
total: number;
|
||||
/** Der `hint:`-Text, den der Server bei leeren Ergebnissen mitschickt. */
|
||||
hint?: string;
|
||||
/**
|
||||
* Die Fusszeilen, die der Server seit dem schlanken Ausgabevertrag
|
||||
* (#1597) mitschickt: wie viele Zeilen geliefert wurden, ob `total` exakt
|
||||
* oder eine Untergrenze ist (`eq` / `gte`), ob mehr da waere und ob
|
||||
* gekappt wurde. Optional, weil die Aufzeichnungen von vor dem Vertrag
|
||||
* sie nicht tragen; wo sie da sind, sind sie die ehrlichere Auskunft.
|
||||
*/
|
||||
returned?: number;
|
||||
totalRelation?: string;
|
||||
hasMore?: boolean;
|
||||
truncated?: boolean;
|
||||
}
|
||||
|
||||
/** Eine geparste search_graph-Antwort. */
|
||||
@@ -43,6 +54,10 @@ export interface SearchResults {
|
||||
rows: string[][];
|
||||
/** True, wenn der Server abgeschnitten hat und mehr da waere. */
|
||||
hasMore: boolean;
|
||||
/** Die weiteren Fusszeilen des schlanken Vertrags (#1597), siehe CompactRows. */
|
||||
returned?: number;
|
||||
totalRelation?: string;
|
||||
truncated?: boolean;
|
||||
}
|
||||
|
||||
const ROWS_HEAD = /^rows:\s*(\d+)\s*\(cols:\s*([^)]*)\)\s*$/;
|
||||
@@ -51,6 +66,9 @@ const TOTAL_LINE = /^total:\s*(\d+)\s*$/;
|
||||
const HINT_LINE = /^hint:\s*(.*)$/;
|
||||
const MODE_LINE = /^search_mode:\s*(\S+)\s*$/;
|
||||
const HAS_MORE_LINE = /^has_more:\s*(true|false)\s*$/;
|
||||
const RETURNED_LINE = /^returned:\s*(\d+)\s*$/;
|
||||
const TOTAL_RELATION_LINE = /^total_relation:\s*(\S+)\s*$/;
|
||||
const TRUNCATED_LINE = /^truncated:\s*(true|false)\s*$/;
|
||||
const DATA_LINE = /^ {2}\S/;
|
||||
|
||||
/** Zerlegt eine Datenzeile in Zellen und nimmt Anfuehrungszeichen weg. */
|
||||
@@ -177,6 +195,10 @@ export function parseCompactRows(text: string): CompactRows {
|
||||
|
||||
let total: number | undefined;
|
||||
let hint: string | undefined;
|
||||
let returned: number | undefined;
|
||||
let totalRelation: string | undefined;
|
||||
let hasMore: boolean | undefined;
|
||||
let truncated: boolean | undefined;
|
||||
for (; i < lines.length; i += 1) {
|
||||
const line = lines[i];
|
||||
if (line.trim().length === 0) {
|
||||
@@ -192,6 +214,26 @@ export function parseCompactRows(text: string): CompactRows {
|
||||
hint = unquote(hintMatch[1]);
|
||||
continue;
|
||||
}
|
||||
const returnedMatch = RETURNED_LINE.exec(line);
|
||||
if (returnedMatch !== null) {
|
||||
returned = Number.parseInt(returnedMatch[1], 10);
|
||||
continue;
|
||||
}
|
||||
const relationMatch = TOTAL_RELATION_LINE.exec(line);
|
||||
if (relationMatch !== null) {
|
||||
totalRelation = relationMatch[1];
|
||||
continue;
|
||||
}
|
||||
const hasMoreMatch = HAS_MORE_LINE.exec(line);
|
||||
if (hasMoreMatch !== null) {
|
||||
hasMore = hasMoreMatch[1] === 'true';
|
||||
continue;
|
||||
}
|
||||
const truncatedMatch = TRUNCATED_LINE.exec(line);
|
||||
if (truncatedMatch !== null) {
|
||||
truncated = truncatedMatch[1] === 'true';
|
||||
continue;
|
||||
}
|
||||
throw new Error(`unbekannte Fusszeile einer kompakten Antwort: ${line.trim()}`);
|
||||
}
|
||||
|
||||
@@ -203,6 +245,18 @@ export function parseCompactRows(text: string): CompactRows {
|
||||
if (hint !== undefined) {
|
||||
out.hint = hint;
|
||||
}
|
||||
if (returned !== undefined) {
|
||||
out.returned = returned;
|
||||
}
|
||||
if (totalRelation !== undefined) {
|
||||
out.totalRelation = totalRelation;
|
||||
}
|
||||
if (hasMore !== undefined) {
|
||||
out.hasMore = hasMore;
|
||||
}
|
||||
if (truncated !== undefined) {
|
||||
out.truncated = truncated;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -218,6 +272,9 @@ export function parseSearchResults(text: string): SearchResults {
|
||||
let declared: number | undefined;
|
||||
let rows: string[][] | undefined;
|
||||
let hasMore: boolean | undefined;
|
||||
let returned: number | undefined;
|
||||
let totalRelation: string | undefined;
|
||||
let truncated: boolean | undefined;
|
||||
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
@@ -245,6 +302,24 @@ export function parseSearchResults(text: string): SearchResults {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
const returnedMatch = RETURNED_LINE.exec(line);
|
||||
if (returnedMatch !== null) {
|
||||
returned = Number.parseInt(returnedMatch[1], 10);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
const relationMatch = TOTAL_RELATION_LINE.exec(line);
|
||||
if (relationMatch !== null) {
|
||||
totalRelation = relationMatch[1];
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
const truncatedMatch = TRUNCATED_LINE.exec(line);
|
||||
if (truncatedMatch !== null) {
|
||||
truncated = truncatedMatch[1] === 'true';
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
const head = RESULTS_HEAD.exec(line);
|
||||
if (head !== null) {
|
||||
declared = Number.parseInt(head[1], 10);
|
||||
@@ -277,7 +352,17 @@ export function parseSearchResults(text: string): SearchResults {
|
||||
throw new Error('Suchantwort ohne has_more-Zeile');
|
||||
}
|
||||
|
||||
return { total, mode, columns, rows, hasMore };
|
||||
const out: SearchResults = { total, mode, columns, rows, hasMore };
|
||||
if (returned !== undefined) {
|
||||
out.returned = returned;
|
||||
}
|
||||
if (totalRelation !== undefined) {
|
||||
out.totalRelation = totalRelation;
|
||||
}
|
||||
if (truncated !== undefined) {
|
||||
out.truncated = truncated;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Verbindet Spaltennamen und Zellen zu einem Objekt je Zeile. */
|
||||
|
||||
Reference in New Issue
Block a user