Document tool output conventions for file content - #837
Conversation
There was a problem hiding this comment.
Pull request overview
Documents a convention for how model-facing tool output should represent file contents, and adds a regression test intended to prevent future drift between read_file and edit-tool output formats.
Changes:
- Add documentation describing file-content output conventions (
read_filevs edit tools). - Add an AVA “drift guard” spec for tool output conventions.
- Add a changeset entry for a patch release.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
source/tools/tool-output-conventions.spec.ts |
Adds a regression test intended to enforce the documented tool output convention. |
docs/features/tool-output-conventions.md |
Documents the intended conventions for model-facing file content output. |
.changeset/document-tool-output-conventions.md |
Declares a patch changeset for the documentation/test addition. |
Suppressed comments (1)
source/tools/tool-output-conventions.spec.ts:44
- This test will currently fail: none of the edit tools include the string
Updated file context (lines, and the tools emit headers likeUpdated file contents:(string_replace/diff_edit) orFile contents after write:(write_file). Also, usingsome()only proves one file has the header, not that the key tools follow the convention. Consider asserting headers/absolute line numbering on the specific tool source files you care about.
test('bounded edit tools keep absolute line-numbered context headers', (t) => {
const fileOpsDir = join(toolsDir, 'file-ops');
if (!existsSync(fileOpsDir)) {
t.fail('source/tools/file-ops should exist');
return;
}
const files = readdirSync(fileOpsDir).filter(
(file) =>
(file.endsWith('.tsx') || file.endsWith('.ts')) &&
!file.includes('.spec.'),
);
const hasContextHeader = files.some((file) => {
const source = readFileSync(join(fileOpsDir, file), 'utf8');
return source.includes('Updated file context (lines');
});
t.true(
hasContextHeader,
'bounded edit tools should include an absolute line-numbered context header',
);
});
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import test from 'ava'; | ||
|
|
||
| import { existsSync, readFileSync, readdirSync } from 'node:fs'; | ||
| import { join } from 'node:path'; | ||
|
|
||
| const toolsDir = join(process.cwd(), 'source', 'tools'); |
| const source = readFileSync(readFilePath, 'utf8'); | ||
| t.false( | ||
| source.includes('%4d:'), | ||
| 'read_file should not return model-facing content with line-number prefixes', | ||
| ); | ||
| }); |
| Bounded edit-tool responses, such as `string_replace` and `diff_edit`, return partial file windows. Those responses should keep line numbers because the excerpt needs to be placed inside the larger file. | ||
|
|
||
| When an edit tool returns file content: | ||
|
|
||
| - Include a header such as `Updated file context (lines X-Y of N)`. | ||
| - Use absolute file line numbers, not window-relative offsets. | ||
| - Keep omission markers aligned with absolute line numbers. | ||
|
|
|
Hey @yulinlina - thanks for this. Worth flagging up front that the only review so far is Copilot's, and a chunk of it is now out of date, so please don't take all of it at face value. What Copilot got wrong or is now stale The "doc doesn't match the tools" comment was correct when it was written, but The unused-import claim is also wrong, What does still need addressing
Rebase on |
|
You’re right — the Using the existing tool-test harness’s resulting string, the replacement can be: const lines = Array.from({ length: 20 }, (_, i) => `raw-line-${i}`);
const content = lines.join('\n');
writeFileSync(fixturePath, content);
const result = await runReadFile({ path: fixturePath });
expect(result).toBe(content);
expect(result).not.toMatch(/^\s*\d+\s+raw-line-/m);
expect(result).not.toContain('Updated file context (lines');This catches the current For the bounded edit tools, I’ll assert against the actual expect(result).toMatch(/Updated file context \(lines \d+-\d+ of \d+\)/);
expect(result).toMatch(/\[\.\.\. lines \d+-\d+ omitted \.\.\.\]/);I’ll also rebase, keep the |
|
Thanks @yulinlina, that all sounds right. Answering your question directly: yes, move it into I'd go a step further and retire Concretely, what I'd like to see:
One small thing on the snippets: they're written in Then the doc just needs the frontmatter block ( |
|
Hi @yulinlina, thanks for this PR! It looks like a codeowner has left feedback Whenever you get a chance, could you take a look at the open comments? |
Records the convention from #765:
read_filereturns raw content without line numbers, while bounded edit-tool responses keep absolute line-numbered context headers. Also adds a lightweight source-level regression test so future tools don't drift from the split.Addresses #765