diff --git a/gitnexus/src/mcp/local/local-backend.ts b/gitnexus/src/mcp/local/local-backend.ts index abd00d392..339d2a36e 100644 --- a/gitnexus/src/mcp/local/local-backend.ts +++ b/gitnexus/src/mcp/local/local-backend.ts @@ -2319,7 +2319,11 @@ export class LocalBackend { const v = row[k]; if (v === null || v === undefined) return ''; if (typeof v === 'object') return JSON.stringify(v); - return String(v); + // Collapse newlines so a multi-line cell value (e.g. a symbol's + // `content`) stays on one physical line. Otherwise the rendered row + // spans multiple lines, which corrupts the table and breaks the + // CLI's `--limit` line-based slicing (#2310 review). + return String(v).replace(/\r?\n/g, ' '); }) .join(' | ') + ' |', diff --git a/gitnexus/test/unit/calltool-dispatch.test.ts b/gitnexus/test/unit/calltool-dispatch.test.ts index 1853f12a0..e85a0d1b2 100644 --- a/gitnexus/test/unit/calltool-dispatch.test.ts +++ b/gitnexus/test/unit/calltool-dispatch.test.ts @@ -3436,6 +3436,24 @@ describe('cypher result formatting', () => { expect(result.row_count).toBe(2); }); + it('keeps one markdown line per row when a cell value contains newlines (#2310)', async () => { + // A multi-line `content` value must not split its row across physical lines — + // otherwise the rendered table is corrupt and the CLI `--limit` line-slice + // keeps the wrong number of rows. + (executeParameterized as any).mockResolvedValue([ + { name: 'a', content: 'export function a() {\n return 1;\n}' }, + { name: 'b', content: 'line1\nline2' }, + ]); + const result = await backend.callTool('cypher', { + query: 'MATCH (n:Function) RETURN n.name AS name, n.content AS content', + }); + const lines = result.markdown.split('\n'); + // header + separator + exactly one line per data row, no embedded newlines. + expect(lines).toHaveLength(2 + result.row_count); + expect(result.row_count).toBe(2); + expect(result.markdown).not.toMatch(/\n[^|]/); + }); + it('returns empty array as-is', async () => { (executeParameterized as any).mockResolvedValue([]); const result = await backend.callTool('cypher', {