Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- `codegraph_explore` and `codegraph_node` now name the near-identical copies of the function you are looking at (for example the same helper pasted into three files), so a fix made in one copy is not forgotten in the others. Test files, generated code and vendored libraries are left out.

- Ask about "my changes", "this branch" or a range like `main..HEAD`, and `codegraph_explore` (and the Claude Code prompt hook) answers from the diff: the functions your edits touched, most-called first, with their callers and the tests that cover them.
- Ask about "my changes", "this branch" or a range like `main..HEAD`, and `codegraph_explore` (and the Claude Code prompt hook) answers from the diff: the functions your edits touched, most-called first, with their callers and the tests that cover them. When nothing has changed, a longer question that only mentions changes while describing a feature ("the panel's working tree changes row") is searched like any other question.
- `codegraph_explore` flows and `codegraph_node` caller and callee lists now mark a call that was matched only by the function's name, with no import or known receiver type to confirm it. About half of those links are wrong on real projects, so the mark tells your agent which single hop to check rather than re-reading the whole flow.

- **`codegraph_sessions` answers questions asked in full sentences, and cuts out harness text.** Common words ("why", "did", "the") are dropped from the query. When too few passages hold every remaining word to fill the list, passages holding some of the words follow them, most words first, each marked as a partial match. Skill bodies, system reminders, subagent notices and summarizer input are no longer indexed; each of those held so much of the session that it outranked the one passage that answered the query. Prose longer than 4,000 characters is indexed as paragraph-sized passages, and identical passages (the same "continue" in ten sessions) appear once. Each hit now prints the transcript path it came from, and the JSON output carries it as `file`. Existing session indexes rebuild once on the next query.
Expand Down
22 changes: 22 additions & 0 deletions __tests__/explore-changes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,28 @@ describe('codegraph_explore answers change questions from the diff', () => {
expect(collectChanges(dir, 'versions 1..10')).toBeNull();
});

it('searches a feature description that names working tree changes when nothing changed', async () => {
const text = await explore('checkout panel shows the pricing working tree changes with additions and deletions');
expect(text).toContain('**Changes (uncommitted edits) — none');
expect(text).toMatch(/export function pricing/);

const bare = await explore('review my changes');
expect(bare).toContain('**Changes (uncommitted edits) — none');
expect(bare).not.toMatch(/export function pricing/);

// Two words beside the phrase still read as a change question.
const short = await explore('pricing my changes');
expect(short).not.toMatch(/export function pricing/);
});

it('keeps answering from the diff when something changed, however long the question', async () => {
const checkout = path.join(dir, 'src/checkout.ts');
fs.writeFileSync(checkout, fs.readFileSync(checkout, 'utf-8').replace('pricing(total)', 'pricing(total) + 1'));
const text = await explore('list what calls unrelated after my changes');
expect(text).toContain('**Changes (uncommitted edits) — 1 file, 1 symbol');
expect(text).not.toMatch(/export function unrelated/);
});

it('leaves ordinary queries alone', async () => {
const text = await explore('how does checkout reach pricing...');
expect(text).not.toContain('**Changes');
Expand Down
11 changes: 8 additions & 3 deletions src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4556,10 +4556,15 @@ export class ToolHandler {
const changedNodes = changes ? this.changedSymbols(cg, changes) : [];
// Only the code-shaped part of the rest (symbols, paths) still matches: the
// prose around a change question ("list what calls it so I can re-test")
// pulls in unrelated flows ahead of the changes.
const rawMatch = changes
// pulls in unrelated flows ahead of the changes. With nothing changed there
// is nothing for that prose to crowd out, and a longer description that
// merely names a feature ("the panel's working tree changes row") is a code
// question: the rest of the query then matches in full.
const describesCode = changes !== null && changes.files.length === 0
&& changes.remainingQuery.split(/\s+/).filter(Boolean).length >= 3;
const rawMatch = changes && !describesCode
? [...extractCodeTokens(changes.remainingQuery), ...changes.remainingQuery.split(/\s+/).filter((w) => w.includes('/'))].join(' ')
: rawQuery;
: changes ? changes.remainingQuery : rawQuery;

let pinnedFiles: string[] = [];
let unresolvedPathSpans: string[] = [];
Expand Down
Loading