diff --git a/packages/coding-agent/src/prompts/tools/read.md b/packages/coding-agent/src/prompts/tools/read.md index 2bc641189..1cc01b0ba 100644 --- a/packages/coding-agent/src/prompts/tools/read.md +++ b/packages/coding-agent/src/prompts/tools/read.md @@ -25,7 +25,7 @@ Read files, directories, archives, SQLite, images, documents, internal resources - Directory → depth-limited dirent listing. {{#if IS_HL_MODE}} -- File + selector → snapshot tag header + numbered lines: `[src/foo.ts#1A2B]` then `41:def alpha():`. Copy `[PATH#TAG]` for anchored edits; ops use bare line numbers. NEVER fabricate the tag. +- File + selector → filename-only snapshot header + numbered lines: `[foo.ts#1A2B]` then `41:def alpha():`. Copy `[FILENAME#TAG]` for anchored edits; ops use bare line numbers. NEVER fabricate the tag. {{else}} {{#if IS_LINE_NUMBER_MODE}} - File + selector → numbered lines: `41|def alpha():`. diff --git a/packages/coding-agent/src/tools/read.ts b/packages/coding-agent/src/tools/read.ts index 2dd814528..0b26e37b7 100644 --- a/packages/coding-agent/src/tools/read.ts +++ b/packages/coding-agent/src/tools/read.ts @@ -170,6 +170,10 @@ interface HashlineHeaderContext { fullText?: string; } +function formatReadHashlineHeader(displayPath: string, tag: string): string { + return formatHashlineHeader(path.basename(displayPath), tag); +} + function recordFullHashlineContext( session: ToolSession, absolutePath: string | undefined, @@ -180,7 +184,7 @@ function recordFullHashlineContext( const normalized = normalizeToLF(fullText); const tag = getFileSnapshotStore(session).record(canonicalSnapshotKey(absolutePath), normalized); return { - header: formatHashlineHeader(displayPath, tag), + header: formatReadHashlineHeader(displayPath, tag), tag, fullText: normalized, }; @@ -203,7 +207,7 @@ async function readHashlineHeaderContext( } function hashlineHeaderContext(displayPath: string, tag: string): HashlineHeaderContext { - return { header: formatHashlineHeader(displayPath, tag), tag }; + return { header: formatReadHashlineHeader(displayPath, tag), tag }; } function prependHashlineHeader(text: string, context: HashlineHeaderContext | undefined): string { @@ -1598,7 +1602,7 @@ export class ReadTool implements AgentTool { const tag = await recordFileSnapshot(this.session, absolutePath); if (tag) { recordSeenLinesFromBody(this.session, absolutePath, tag, outputText); - outputText = `${formatHashlineHeader(formatPathRelativeToCwd(absolutePath, this.session.cwd), tag)}\n${outputText}`; + outputText = `${formatReadHashlineHeader(formatPathRelativeToCwd(absolutePath, this.session.cwd), tag)}\n${outputText}`; } } if (notices.length > 0) { diff --git a/packages/coding-agent/test/read-multi-range.test.ts b/packages/coding-agent/test/read-multi-range.test.ts index 975fe0ce2..99c30f4aa 100644 --- a/packages/coding-agent/test/read-multi-range.test.ts +++ b/packages/coding-agent/test/read-multi-range.test.ts @@ -48,13 +48,29 @@ describe("read tool multi-range selector", () => { await fs.rm(tmpDir, { recursive: true, force: true }); }); + it("uses only the filename in hashline headers for nested files", async () => { + const filePath = path.join(tmpDir, "src", "nested", "numbered.txt"); + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, "alpha\nbeta\n"); + + const tool = new ReadTool(createSession(tmpDir)); + const text = textOutput(await tool.execute("call-filename-header", { path: filePath })); + const firstLine = text.split("\n")[0]; + + expect(firstLine).toMatch(/^\[numbered\.txt#[0-9A-F]{4}\]$/); + expect(firstLine).not.toContain("src"); + }); + it("returns both ranges separated by an elision marker", async () => { - const filePath = path.join(tmpDir, "numbered.txt"); + const filePath = path.join(tmpDir, "src", "numbered.txt"); + await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, makeNumberedContent(50)); const tool = new ReadTool(createSession(tmpDir)); const result = await tool.execute("call-multi", { path: `${filePath}:3-5,20-22` }); const text = textOutput(result); + const firstLine = text.split("\n")[0]; + expect(firstLine).toMatch(/^\[numbered\.txt#[0-9A-F]{4}\]$/); expect(text).toContain("line 3"); expect(text).toContain("line 4"); diff --git a/packages/coding-agent/test/read-summary.test.ts b/packages/coding-agent/test/read-summary.test.ts index 0539b3fd9..e21f12897 100644 --- a/packages/coding-agent/test/read-summary.test.ts +++ b/packages/coding-agent/test/read-summary.test.ts @@ -62,7 +62,8 @@ describe("read summary", () => { }); it("summarizes parseable TypeScript files without an explicit selector", async () => { - const fixture = path.join(tmpDir, "fixture.ts"); + const fixture = path.join(tmpDir, "src", "fixture.ts"); + await fs.mkdir(path.dirname(fixture), { recursive: true }); await fs.writeFile( fixture, "export function alpha(value: string): string {\n\tconst clean = value.trim();\n\tconst label = clean || 'alpha';\n\treturn label.toUpperCase();\n}\n\nexport function beta(): number {\n\tconst one = 1;\n\tconst two = 2;\n\treturn one + two;\n}\n", @@ -71,6 +72,8 @@ describe("read summary", () => { const tool = new ReadTool(createSession(tmpDir)); const result = await tool.execute("read-summary-ts", { path: fixture }); const text = textOutput(result); + const firstLine = text.split("\n")[0]; + expect(firstLine).toMatch(/^\[fixture\.ts#[0-9A-F]{4}\]$/); expect(text).toContain("export function alpha(value: string): string { … }"); expect(text).toContain("export function beta(): number { … }");