spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / agents-md / tests / register.test.ts
201 lines · 5.6 KBRaw
1import { describe, expect, mock, test, tier } from 'claude-code/testing'
2
3import Hooks from '../hooks'
4import Fixtures from './fixtures'
5
6tier('builtin')
7
8/**
9 * The blocks the engine hands `prompt.context`.
10 */
11const BLOCKS = [
12 { name: 'claudeMd', text: 'x' },
13 { name: 'currentDate', text: 'today' },
14]
15
16describe('register', () => {
17 test('by default a project with AGENTS.md alone gets it', async ($, on) => {
18 const started = Fixtures.projectOf(
19 on,
20 [Fixtures.ancestorOf('/repo', 'AGENTS.md', '# top\n')],
21 [],
22 )
23
24 on('prompt.context', ($, e) => ({
25 blocks: e.blocks,
26 instructionFiles: e.instructionFiles,
27 }))
28
29 const context = await $.prompt.context({
30 blocks: BLOCKS,
31 instructionFiles: [],
32 })
33
34 expect(context.instructionFiles).toEqual([
35 { path: '/repo/AGENTS.md', kind: 'project', content: '# top\n' },
36 ])
37 expect(context.blocks?.[0]?.text).toContain(
38 'Contents of /repo/AGENTS.md (project instructions, checked into the ' +
39 'codebase):\n\n# top',
40 )
41
42 await started.clock.settle()
43
44 expect(started.lines).toEqual([
45 'no CLAUDE.md found; AGENTS.md loaded: /repo/AGENTS.md',
46 ])
47 })
48
49 test('by default a project with a CLAUDE.md is left to the engine', async ($, on) => {
50 const started = Fixtures.projectOf(
51 on,
52 [Fixtures.ancestorOf('/repo', 'AGENTS.md', '# top\n')],
53 [Fixtures.ancestorOf('/repo/a/b', 'CLAUDE.md', '# mine\n')],
54 )
55 const handed = [
56 { path: '/repo/a/b/CLAUDE.md', kind: 'project' as const, content: '#' },
57 ]
58
59 on('prompt.context', ($, e) => ({
60 blocks: e.blocks,
61 instructionFiles: e.instructionFiles,
62 }))
63
64 expect(
65 await $.prompt.context({ blocks: BLOCKS, instructionFiles: handed }),
66 ).toEqual({ blocks: BLOCKS, instructionFiles: handed })
67
68 await started.clock.settle()
69
70 expect(started.walks).toEqual([])
71 expect(started.lines).toEqual([])
72 })
73
74 test('a failed walk leaves the context as handed', async ($, on) => {
75 Fixtures.unreadableProjectOf(on)
76
77 on('prompt.context', ($, e) => ({
78 blocks: e.blocks,
79 instructionFiles: e.instructionFiles,
80 }))
81
82 expect(
83 await $.prompt.context({ blocks: BLOCKS, instructionFiles: [] }),
84 ).toEqual({ blocks: BLOCKS, instructionFiles: [] })
85 })
86
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
151 test(
152 'the start sends the mode row alone: no walk, no toast',
153 { plugins: [Fixtures.RECORDING] },
154 async ($, on) => {
155 const started = Fixtures.projectOf(
156 on,
157 [Fixtures.ancestorOf('/repo', 'AGENTS.md', '# top\n')],
158 [],
159 )
160
161 expect(await $.session.start(Fixtures.SESSION)).toEqual({
162 cwd: '/repo/a/b',
163 })
164
165 await started.clock.settle()
166
167 expect(Fixtures.rowsOf(started)).toEqual([
168 [
169 'log',
170 {
171 event: Hooks.MODE_EVENT,
172 props: {
173 mode: { value: 'claude-md-or-agents-md', of: [...Hooks.MODES] },
174 is_interactive: true,
175 },
176 },
177 ],
178 ])
179 expect(Hooks.MODE_EVENT).toBe('agents_md_mode')
180 expect(started.walks).toEqual([])
181 expect(started.toasts).toEqual([])
182 },
183 )
184
185 test('with no telemetry noun the start goes on untouched', async ($, on) => {
186 const started = Fixtures.projectOf(
187 on,
188 [Fixtures.ancestorOf('/repo', 'AGENTS.md', '# top\n')],
189 [],
190 )
191
192 expect(
193 await $.session.start({ ...Fixtures.SESSION, isInteractive: false }),
194 ).toEqual({ cwd: '/repo/a/b' })
195
196 await started.clock.settle()
197
198 expect(Fixtures.rowsOf(started)).toEqual([])
199 expect(started.toasts).toEqual([])
200 })
201})