Skip to content

Commit 9d302a8

Browse files
committed
Merge remote-tracking branch 'origin/main' into poteat/diff-parity-2
# Conflicts: # mods/diff/hooks/register.ts # mods/diff/hooks/views/detail/code-blocks-of/code-blocks-of.ts # mods/diff/tests/fixtures/git-in.ts # mods/diff/tests/fixtures/in-repository.ts # mods/diff/tests/fixtures/index.ts # mods/telemetry/tests/fixtures/index.ts
2 parents 0fdc7b2 + 18be13b commit 9d302a8

209 files changed

Lines changed: 5504 additions & 65 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, expect, test, tier } from 'claude-code/testing'
2+
3+
import Ask from '../../hooks/ask'
4+
import Fixtures from '../fixtures'
5+
6+
tier('builtin')
7+
8+
describe('fitted-ask-text-of', () => {
9+
test('an ask that fits rides whole', () => {
10+
expect(
11+
Ask.fittedAskTextOf(Fixtures.ASK_TEXT, Fixtures.ASK_TEXT.length),
12+
).toBe(Fixtures.ASK_TEXT)
13+
})
14+
15+
test('an ask past the room keeps its first lines and the cut note', () => {
16+
const fitted = Ask.fittedAskTextOf(
17+
Fixtures.ASK_TEXT,
18+
Fixtures.ROOM_FOR_TWO_LINES,
19+
)
20+
21+
expect(fitted).toBe(
22+
`${Fixtures.ASK_LINES.slice(0, 2).join('\n')}\n${Ask.ASK_CUT_NOTE}`,
23+
)
24+
25+
expect(fitted?.length).toBeLessThanOrEqual(Fixtures.ROOM_FOR_TWO_LINES)
26+
})
27+
28+
test('an ask whose first hunk line does not fit is not attached', () => {
29+
expect(
30+
Ask.fittedAskTextOf(Fixtures.ASK_TEXT, Fixtures.ROOM_FOR_ONE_LINE),
31+
).toBeUndefined()
32+
})
33+
})
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { describe, expect, test, tier } from 'claude-code/testing'
2+
3+
import Backend from '../../hooks/backend'
4+
import Fixtures from '../fixtures'
5+
6+
tier('builtin')
7+
8+
describe('backend-of', () => {
9+
test('as published: no probes, so git or nothing', async () => {
10+
const inGit = Fixtures.gitHostOf()
11+
const outside = Fixtures.recordingBackendHostOf({})
12+
13+
expect((await Backend.backendOf(inGit.host, []))?.words.lister).toBe('git')
14+
expect(await Backend.backendOf(outside.host, [])).toBeNull()
15+
16+
expect([inGit.programs, outside.programs]).toEqual([
17+
['git', 'git'],
18+
['git'],
19+
])
20+
})
21+
22+
test('a declining probe leaves a git working tree to git', async () => {
23+
const declining = Fixtures.recordingProbeOf(null)
24+
const inGit = Fixtures.gitHostOf()
25+
const backend = await Backend.backendOf(inGit.host, [declining.probe])
26+
27+
expect(backend?.words.lister).toBe('git')
28+
expect(backend?.baseModes).toEqual(['session', 'uncommitted', 'branch'])
29+
expect(declining.asked).toEqual([inGit.host])
30+
expect(inGit.programs).toEqual(['git', 'git'])
31+
})
32+
33+
test('a probe that answers pins before git is asked', async () => {
34+
const inGit = Fixtures.gitHostOf()
35+
36+
const backend = await Backend.backendOf(inGit.host, [
37+
Fixtures.recordingProbeOf('/home/me/src').probe,
38+
])
39+
40+
expect(backend?.repository).toEqual({ toplevel: '/home/me/src' })
41+
expect(inGit.programs).toEqual([])
42+
})
43+
44+
test('the probes are asked in order and the first answer pins', async () => {
45+
const declining = Fixtures.recordingProbeOf(null)
46+
const first = Fixtures.recordingProbeOf('/first')
47+
const second = Fixtures.recordingProbeOf('/second')
48+
const { host } = Fixtures.recordingBackendHostOf({})
49+
50+
const backend = await Backend.backendOf(host, [
51+
declining.probe,
52+
first.probe,
53+
second.probe,
54+
])
55+
56+
expect(backend?.repository).toEqual({ toplevel: '/first' })
57+
58+
expect([
59+
declining.asked.length,
60+
first.asked.length,
61+
second.asked.length,
62+
]).toEqual([1, 1, 0])
63+
64+
expect(declining.asked[0]).toBe(host)
65+
})
66+
})
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, expect, test, tier } from 'claude-code/testing'
2+
3+
import Classify from '../../hooks/classify'
4+
5+
tier('builtin')
6+
7+
describe('is-noise-file', () => {
8+
test('tests, fixtures and snapshots are noise', () => {
9+
for (const path of [
10+
'test/a.ts',
11+
'src/x/__tests__/y.ts',
12+
'lib/foo.test.tsx',
13+
'pkg/bar_test.go',
14+
'web/fixtures/data.json',
15+
'ui/__snapshots__/a.snap',
16+
]) {
17+
expect(Classify.isNoiseFile(path), path).toBe(true)
18+
}
19+
})
20+
21+
test('lockfiles, builds, minified and generated files are noise', () => {
22+
for (const path of [
23+
'bun.lock',
24+
'package-lock.json',
25+
'dist/index.js',
26+
'assets/app.min.js',
27+
'types/api.d.ts',
28+
'src/constants/prompts.generated/x.ts',
29+
'proto/service.pb.go',
30+
]) {
31+
expect(Classify.isNoiseFile(path), path).toBe(true)
32+
}
33+
})
34+
35+
test('source is not, even with test inside a longer word', () => {
36+
for (const path of ['src/contest/entry.ts', 'src/latest.ts', 'README.md']) {
37+
expect(Classify.isNoiseFile(path), path).toBe(false)
38+
}
39+
})
40+
})
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* @param dirty the paths `status` lists as dirty when the session starts
1212
* @returns the answers by key
1313
*/
14-
export const repositoryOf = (
14+
export const answersOf = (
1515
numstat: string,
1616
bodies: Readonly<Record<string, string>>,
1717
dirty: readonly string[] = Object.keys(bodies),
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* The lines of an armed ask: where it came from, then one hunk long enough
3+
* that two lines and the cut note take less room than the whole.
4+
*/
5+
export const ASK_LINES: readonly string[] = [
6+
'The user attached the diff of a.txt from the diff pane to this prompt:',
7+
'@@ -1,3 +1,3 @@',
8+
'-the first old line of a.txt, long enough to take up some room',
9+
'-the second old line of a.txt, long enough to take up some room',
10+
'+the first new line of a.txt, long enough to take up some room',
11+
'+the second new line of a.txt, long enough to take up some room',
12+
]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ASK_LINES } from './ask-lines.js'
2+
3+
/**
4+
* ASK_LINES as the one text an armed ask carries.
5+
*/
6+
export const ASK_TEXT = ASK_LINES.join('\n')
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './ask-lines.js'
2+
export * from './ask-text.js'
3+
export * from './room-for-one-line.js'
4+
export * from './room-for-two-lines.js'
5+
6+
export * as default from '.'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Ask from '../../../hooks/ask'
2+
import { ASK_LINES } from './ask-lines.js'
3+
4+
/**
5+
* Room for ASK_LINES' heading line and the cut note only, so no hunk line.
6+
*/
7+
export const ROOM_FOR_ONE_LINE =
8+
(ASK_LINES[0]?.length ?? 0) + 1 + Ask.ASK_CUT_NOTE.length
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Ask from '../../../hooks/ask'
2+
import { ASK_LINES } from './ask-lines.js'
3+
4+
/**
5+
* Room for ASK_LINES' first two lines and the cut note, each line with its
6+
* newline, and not a character more.
7+
*/
8+
export const ROOM_FOR_TWO_LINES = ASK_LINES.slice(0, 2).reduce(
9+
(sum, line) => sum + line.length + 1,
10+
Ask.ASK_CUT_NOTE.length,
11+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type Backend from '../../../hooks/backend'
2+
3+
/**
4+
* A backend host around one runner: every other member answers nothing (no
5+
* file, no mtime, no listing, time zero, a base callback that ignores).
6+
*
7+
* @param run the host's runner
8+
* @returns the host
9+
*/
10+
export const backendHostOf = (
11+
run: Backend.BackendHost['run'],
12+
): Backend.BackendHost => ({
13+
run,
14+
readFile: () => Promise.reject(new Error('ENOENT')),
15+
mtimeOf: () => Promise.resolve(null),
16+
entryKindsOf: () => Promise.resolve(null),
17+
nowMs: () => Promise.resolve(0),
18+
sessionStartMsOf: () => 0,
19+
onBranchBase: () => undefined,
20+
})

0 commit comments

Comments
 (0)