diff --git a/CHANGELOG.md b/CHANGELOG.md index e02a77eeb..56e1ae2c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - A Swift reference to a type now links to the type's own declaration, not to a file that extends it. An `extension View { … }` or `extension Text { … }` used to stand in for SwiftUI's type, so every view, every `Text("…")` and every `Color.red` in an app linked to whichever file happened to extend it. Those files topped the most-depended-on lists and their impact reached the whole app. A bare name like `@State`, `@Test` or `Result<…>` no longer links to some other type's nested `State` or `Result`, and a qualified name like `Build.Id` links to the `Id` it names. Methods declared in an extension of an SDK type still resolve on the types that conform to it, and a protocol's methods declared in any of its extensions now connect to each conforming type's own implementation. Re-index Swift projects after upgrading. - A Vapor route now links to the handler it names. `use: SearchController.show` used to link to whichever controller's `show` came first, so routes with a common handler name, like `show`, `index` or `get`, pointed at another endpoint's code in callers, impact and `codegraph_explore` answers. Nested types like `API.PackageController.get` and handlers declared in an extension of the controller now resolve too, and `use: self.index` resolves to the collection's own `index`. Re-index Vapor projects after upgrading. - A PHP call written without a receiver, such as `redirect($url)`, `view('books.show')`, `auth()` or `basename($path)`, is a function call, and no longer links to a same-named method, field or class elsewhere in the project. These wrong links showed up in callers, impact and `codegraph_explore` answers wherever a Laravel helper or PHP built-in shared its name with a project member. Re-index PHP projects after upgrading. +- Claude Code now shows the model the full text of `codegraph_status` and of explore's stale-index refusal. Both results also carried `structuredContent`, and Claude Code shows a result's `structuredContent` in place of its text, so the model saw only a freshness JSON object: no refusal message, no retry guidance and no status report. The text already carried the same fields, so both results are now text only. (#2088) ## [1.6.1] - 2026-09-29 diff --git a/__tests__/mcp-stale-refusal.test.ts b/__tests__/mcp-stale-refusal.test.ts index 53362c7a2..8659e8140 100644 --- a/__tests__/mcp-stale-refusal.test.ts +++ b/__tests__/mcp-stale-refusal.test.ts @@ -121,7 +121,9 @@ describe('a degraded index refuses answers from changed files (#1959)', () => { const result = await handler.execute('codegraph_search', { query: 'unicodeSymbol' }); expect(result.isError).toBeFalsy(); expect(result.content[0].text).toContain('cannot answer from this index'); - expect(result.structuredContent).toEqual({ freshness: { stale: [relative], unchecked: [] } }); + expect(result.content[0].text).toContain(`- ${relative}`); + // Claude Code would show structuredContent in place of this text (#2088). + expect(result).not.toHaveProperty('structuredContent'); expect(result).not.toHaveProperty('_cgAnswerFiles'); }); @@ -147,7 +149,8 @@ describe('a degraded index refuses answers from changed files (#1959)', () => { const result = await handler.execute('codegraph_impact', { symbol: 'alphaOnly' }); expect(result.isError).toBeFalsy(); expect(result.content[0].text).toContain('validation budget'); - expect((result.structuredContent!.freshness as { unchecked: string[] }).unchecked.length).toBeGreaterThan(0); + expect(result.content[0].text).toMatch(/validation budget for \d+ files/); + expect(result).not.toHaveProperty('structuredContent'); expect(result.content[0].text).not.toContain('**Impact'); }); diff --git a/__tests__/mcp-status-freshness.test.ts b/__tests__/mcp-status-freshness.test.ts index 19c14f7ce..923f77ae3 100644 --- a/__tests__/mcp-status-freshness.test.ts +++ b/__tests__/mcp-status-freshness.test.ts @@ -42,9 +42,8 @@ describe('MCP status freshness (#1959)', () => { fs.writeFileSync(path.join(root, 'add.ts'), 'export const added = 1;\n'); const result = await handler.execute('codegraph_status', {}); - expect(result.structuredContent).toEqual({ freshness: { - lastIndexedAt: cg.getLastIndexedAt(), changes: { added: 1, modified: 1, removed: 1 }, complete: true, - } }); + // Claude Code would show structuredContent in place of this text (#2088). + expect(result).not.toHaveProperty('structuredContent'); const changed = result.content[0].text; expect(changed).toContain('**Changes since index:** 1 added, 1 modified, 1 removed'); }); diff --git a/src/mcp/tools.ts b/src/mcp/tools.ts index 7553ca5a6..fbb663a91 100644 --- a/src/mcp/tools.ts +++ b/src/mcp/tools.ts @@ -1538,7 +1538,6 @@ export interface ToolResult { _cgExploreEmission?: ExploreEmission; /** Internal structured provenance, preserved by query workers and stripped by execute. */ _cgAnswerFiles?: AnswerFile[]; - structuredContent?: Record; } /** @@ -2757,7 +2756,9 @@ export class ToolHandler { if (validation.unchecked.length > 20) lines.push(`- … ${validation.unchecked.length - 20} more (narrow the query)`); } lines.push('Retry after a successful codegraph sync, or narrow the query.'); - return { ...this.textResult(lines.join('\n')), structuredContent: { freshness: validation } }; + // Text only: Claude Code shows the model a result's structuredContent + // in place of its text (#2088), and the text carries every file. + return this.textResult(lines.join('\n')); } } // Record + STRIP before anything else touches the result: the emission is @@ -8006,9 +8007,8 @@ export class ToolHandler { } } - return { ...this.textResult(lines.join('\n')), structuredContent: { - freshness: { lastIndexedAt, changes, complete: changes !== null }, - } }; + // Text only, for the same reason as the stale-answer path (#2088). + return this.textResult(lines.join('\n')); } /**