Skip to content

Commit 653d32f

Browse files
7487poteat
authored andcommitted
fix(agents-md): retain instructions after truncated reads
1 parent ddcb43a commit 653d32f

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

‎mods/agents-md/hooks/register.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,14 @@ export function register(on: On, options: PluginOptions): void {
223223
...Files.filesOf(claude),
224224
]).filter(file => !sent.has(file.path))
225225
const attached = fresh.filter(file => !Frames.isFileAt(file, read))
226-
const isWholeRead = e.offset === undefined && e.limit === undefined
226+
const output = result.result
227+
const isPartialText =
228+
output?.type === 'text' &&
229+
(output.file.truncatedByTokenCap ||
230+
output.file.startLine !== 1 ||
231+
output.file.numLines < output.file.totalLines)
232+
const isWholeRead =
233+
e.offset === undefined && e.limit === undefined && !isPartialText
227234

228235
for (const file of fresh) {
229236
const isSent =

‎mods/agents-md/tests/register.test.ts‎

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, test, tier } from 'claude-code/testing'
1+
import { describe, expect, mock, test, tier } from 'claude-code/testing'
22

33
import Hooks from '../hooks'
44
import Fixtures from './fixtures'
@@ -84,6 +84,70 @@ describe('register', () => {
8484
).toEqual({ blocks: BLOCKS, instructionFiles: [] })
8585
})
8686

87+
for (const [name, args, file, attaches] of [
88+
[
89+
'an auto-paginated read',
90+
{},
91+
{ content: '# head\nfollow', truncatedByTokenCap: true },
92+
true,
93+
],
94+
['an incomplete line range', {}, { content: '# head', numLines: 1 }, true],
95+
['a complete read', {}, {}, false],
96+
['an explicit limit', { limit: 1 }, { content: '# head', numLines: 1 }, true],
97+
[
98+
'an explicit offset',
99+
{ offset: 2 },
100+
{ content: 'follow the tail rule', numLines: 1, startLine: 2 },
101+
true,
102+
],
103+
['an unstructured result', {}, undefined, false],
104+
] as const) {
105+
test(`a nested AGENTS.md after ${name}`, async ($, on) => {
106+
const dir = `${Fixtures.SESSION.cwd}/nested`
107+
const path = `${dir}/AGENTS.md`
108+
const content = '# head\nfollow the tail rule'
109+
const started = Fixtures.projectOf(
110+
on,
111+
[Fixtures.ancestorOf(dir, 'AGENTS.md', content)],
112+
[],
113+
)
114+
115+
mock.env(on, {})
116+
on('session.cwd', () => ({ value: Fixtures.SESSION.cwd }))
117+
on('tool.call', ($, e) => ({
118+
result:
119+
e.tool === 'Read' && file !== undefined
120+
? {
121+
type: 'text',
122+
file: Object.assign(
123+
{
124+
filePath: e.file_path,
125+
content,
126+
numLines: 2,
127+
startLine: 1,
128+
totalLines: 2,
129+
},
130+
file,
131+
),
132+
}
133+
: 'read',
134+
}))
135+
136+
const direct = await $.tool.call({ tool: 'Read', file_path: path, ...args })
137+
const source = { tool: 'Read', file_path: `${dir}/source.ts` } as const
138+
const after = await $.tool.call(source)
139+
const repeated = await $.tool.call(source)
140+
141+
expect(direct.context).toBeUndefined()
142+
expect(after.context).toEqual(
143+
attaches ? [`Contents of ${path}:\n\n${content}`] : undefined,
144+
)
145+
expect(repeated.context).toBeUndefined()
146+
147+
await started.clock.settle()
148+
})
149+
}
150+
87151
test(
88152
'the start sends the mode row alone: no walk, no toast',
89153
{ plugins: [Fixtures.RECORDING] },

0 commit comments

Comments
 (0)