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

mods/diff: each helper sits in a folder of its own, named for what it exports, and barrels list their folders in orderBrowse files
poteat authored on Sep 11, 2026 parent 652580d · commit a9d339b13a25
27 changed files+131 -119compared with 652580d
Amods/diff/hooks/ask/ask-cut-note/ask-cut-note.ts
Amods/diff/hooks/ask/ask-cut-note/index.ts
Dmods/diff/hooks/ask/ask-cut-note.ts
Mmods/diff/hooks/ask/fitted-ask-text-of.ts
Mmods/diff/hooks/ask/index.ts
Mmods/diff/hooks/backend/types/backend-host/backend-host.ts
Amods/diff/hooks/git/fetch-diff/fetch-diff.ts
Amods/diff/hooks/git/fetch-diff/index.ts
Dmods/diff/hooks/git/fetch-diff.ts
Amods/diff/hooks/git/fetch-file-hunks/fetch-file-hunks.ts
Amods/diff/hooks/git/fetch-file-hunks/index.ts
Dmods/diff/hooks/git/fetch-file-hunks.ts
Mmods/diff/hooks/git/git-backend-of.ts
Mmods/diff/hooks/git/index.ts
Mmods/diff/hooks/git/probes/index.ts
Mmods/diff/hooks/git/probes/stats-of.ts
Mmods/diff/hooks/git/probes/unstaged-numstat.ts
Mmods/diff/hooks/git/probes/untracked-files/untracked-files.ts
Amods/diff/hooks/limits/sizes/bytes/host-output-cap-bytes.ts
Amods/diff/hooks/limits/sizes/bytes/index.ts
Amods/diff/hooks/limits/sizes/bytes/max-diff-bytes.ts
Dmods/diff/hooks/limits/sizes/host-output-cap-bytes.ts
Mmods/diff/hooks/limits/sizes/index.ts
Dmods/diff/hooks/limits/sizes/max-diff-bytes.ts
Mmods/diff/hooks/names/texts/index.ts
Amods/diff/hooks/names/texts/untracked-withheld-text-of.ts
Dmods/diff/hooks/names/texts/untracked-withheld-text.ts
@@ -1,0 +1,5 @@
1+/**
2+ * The line an ask ends with when its diff was cut to fit the prompt.
3+ */
4+export const ASK_CUT_NOTE =
5+ '(The rest of this diff was cut: it did not fit in the prompt.)'
@@ -1,0 +1,3 @@
1+export * from './ask-cut-note.js'
2+
3+export * as default from '.'
Dmods/diff/hooks/ask/ask-cut-note.ts+0 -5
@@ -1,5 +1,0 @@
1-/**
2- * The line an ask ends with when its diff was cut to fit the prompt.
3- */
4-export const ASK_CUT_NOTE =
5- '(The rest of this diff was cut: it did not fit in the prompt.)'
@@ -1,4 +1,4 @@
1-import { ASK_CUT_NOTE } from './ask-cut-note.js'
1+import { ASK_CUT_NOTE } from './ask-cut-note'
22
33 /**
44 * An armed ask's text as it fits in the room a prompt's context has left:
@@ -1,6 +1,6 @@
11 export * from './armed-ask'
22 export * from './armed-ask-of.js'
3-export * from './ask-cut-note.js'
3+export * from './ask-cut-note'
44 export * from './fitted-ask-text-of.js'
55
66 export * as default from '.'
@@ -1,5 +1,5 @@
11 import type Git from '../../../git'
2-import type Host from '../../../host'
2+import type { Host } from '../../../host'
33
44 /**
55 * What pinning a backend takes from the bound host: its raw process runner,
@@ -9,7 +9,7 @@
99 * to the runner; the session's start is read as it stands at each fetch
1010 * (a later `session.start` moves it).
1111 */
12-export type BackendHost = Pick<Host.Host, 'run' | 'readFile'> &
12+export type BackendHost = Pick<Host, 'run' | 'readFile'> &
1313 Pick<Git.GitDeps, 'mtimeOf' | 'entryKindsOf' | 'onBranchBase'> & {
1414 /**
1515 * The engine's clock, in milliseconds (`$.clock.now`).
@@ -1,0 +1,33 @@
1+import Probes from '../probes'
2+import Tiers from '../tiers'
3+import type Types from '../types'
4+
5+/**
6+ * One fetch of the session's pinned repository, as the built-in panel
7+ * fetches; every git child names it so paths stay root-relative.
8+ *
9+ * Mid-merge, mid-rebase, mid-cherry-pick or mid-revert, or when git cannot
10+ * answer, it is unavailable and the caller keeps what it had.
11+ *
12+ * @param deps the host: pinned git, the repository, the file probes
13+ * @param mode what to compare against
14+ * @returns the outcome
15+ */
16+export async function fetchDiff(
17+ deps: Types.GitDeps,
18+ mode: Types.BaseMode,
19+): Promise<Types.FetchOutcome> {
20+ const isBusy = await Probes.isTransient(
21+ deps.entryKindsOf,
22+ deps.repository.gitDir,
23+ )
24+
25+ if (isBusy) {
26+ return { kind: 'unavailable' }
27+ }
28+
29+ const context = Tiers.fetchContextOf(deps, mode)
30+ const isBranch = mode === 'branch'
31+
32+ return isBranch ? Tiers.branchTier(context) : Tiers.workingTreeTier(context)
33+}
@@ -1,0 +1,3 @@
1+export * from './fetch-diff.js'
2+
3+export * as default from '.'
Dmods/diff/hooks/git/fetch-diff.ts+0 -33
@@ -1,33 +1,0 @@
1-import Probes from './probes'
2-import Tiers from './tiers'
3-import type Types from './types'
4-
5-/**
6- * One fetch of the session's pinned repository, as the built-in panel
7- * fetches; every git child names it so paths stay root-relative.
8- *
9- * Mid-merge, mid-rebase, mid-cherry-pick or mid-revert, or when git cannot
10- * answer, it is unavailable and the caller keeps what it had.
11- *
12- * @param deps the host: pinned git, the repository, the file probes
13- * @param mode what to compare against
14- * @returns the outcome
15- */
16-export async function fetchDiff(
17- deps: Types.GitDeps,
18- mode: Types.BaseMode,
19-): Promise<Types.FetchOutcome> {
20- const isBusy = await Probes.isTransient(
21- deps.entryKindsOf,
22- deps.repository.gitDir,
23- )
24-
25- if (isBusy) {
26- return { kind: 'unavailable' }
27- }
28-
29- const context = Tiers.fetchContextOf(deps, mode)
30- const isBranch = mode === 'branch'
31-
32- return isBranch ? Tiers.branchTier(context) : Tiers.workingTreeTier(context)
33-}
@@ -1,0 +1,41 @@
1+import Argv from '../argv'
2+import { EMPTY_FILE_HUNKS } from '../empty-file-hunks'
3+import Parse from '../parse'
4+import type Types from '../types'
5+
6+/**
7+ * One file's hunks against the base its row was read against, so body and
8+ * counts agree, by literal pathspec, as the built-in panel reads hunks.
9+ *
10+ * A rename passes both its paths so git pairs them; an untracked or binary
11+ * row, or on an unborn HEAD a file edited after staging, has no honest
12+ * body and answers no hunks.
13+ *
14+ * @param run runs git against the pinned repository
15+ * @param data the fetch the row belongs to
16+ * @param file the row
17+ * @returns the parsed body, or null when git failed
18+ */
19+export async function fetchFileHunks(
20+ run: Types.GitRun,
21+ data: Types.DiffData,
22+ file: Types.FileStat,
23+): Promise<Types.FileHunks | null> {
24+ const hasNoBody =
25+ file.isUntracked || file.isBinary || data.stalePaths.includes(file.path)
26+
27+ if (hasNoBody) {
28+ return EMPTY_FILE_HUNKS
29+ }
30+
31+ const { exitCode, stdout } = await run([
32+ '--literal-pathspecs',
33+ ...Argv.DIFF_LEADING_ARGS,
34+ data.baseRef,
35+ '--',
36+ ...(file.renamedFrom === null ? [] : [file.renamedFrom]),
37+ file.path,
38+ ])
39+
40+ return exitCode === 0 ? Parse.parseFileDiff(stdout) : null
41+}
@@ -1,0 +1,3 @@
1+export * from './fetch-file-hunks.js'
2+
3+export * as default from '.'
Dmods/diff/hooks/git/fetch-file-hunks.ts+0 -41
@@ -1,41 +1,0 @@
1-import Argv from './argv'
2-import { EMPTY_FILE_HUNKS } from './empty-file-hunks'
3-import GitParse from './parse'
4-import type Types from './types'
5-
6-/**
7- * One file's hunks against the base its row was read against, so body and
8- * counts agree, by literal pathspec, as the built-in panel reads hunks.
9- *
10- * A rename passes both its paths so git pairs them; an untracked or binary
11- * row, or on an unborn HEAD a file edited after staging, has no honest
12- * body and answers no hunks.
13- *
14- * @param run runs git against the pinned repository
15- * @param data the fetch the row belongs to
16- * @param file the row
17- * @returns the parsed body, or null when git failed
18- */
19-export async function fetchFileHunks(
20- run: Types.GitRun,
21- data: Types.DiffData,
22- file: Types.FileStat,
23-): Promise<Types.FileHunks | null> {
24- const hasNoBody =
25- file.isUntracked || file.isBinary || data.stalePaths.includes(file.path)
26-
27- if (hasNoBody) {
28- return EMPTY_FILE_HUNKS
29- }
30-
31- const { exitCode, stdout } = await run([
32- '--literal-pathspecs',
33- ...Argv.DIFF_LEADING_ARGS,
34- data.baseRef,
35- '--',
36- ...(file.renamedFrom === null ? [] : [file.renamedFrom]),
37- file.path,
38- ])
39-
40- return exitCode === 0 ? GitParse.parseFileDiff(stdout) : null
41-}
@@ -2,8 +2,8 @@
22 import Limits from '../limits'
33 import Argv from './argv'
44 import { FAILED_RUN } from './failed-run'
5-import { fetchDiff } from './fetch-diff.js'
6-import { fetchFileHunks } from './fetch-file-hunks.js'
5+import { fetchDiff } from './fetch-diff'
6+import { fetchFileHunks } from './fetch-file-hunks'
77 import { GIT_BASE_MODES } from './git-base-modes'
88 import { GIT_WORDS } from './git-words'
99 import Probes from './probes'
@@ -2,8 +2,8 @@
22 export * from './cut-hunks'
33 export * from './empty-file-hunks'
44 export * from './failed-run'
5-export * from './fetch-diff.js'
6-export * from './fetch-file-hunks.js'
5+export * from './fetch-diff'
6+export * from './fetch-file-hunks'
77 export * from './git-backend-of.js'
88 export * from './git-base-modes'
99 export * from './git-words'
@@ -22,7 +22,7 @@
2222 export * from './transient-state-files'
2323 export * from './unstaged-numstat.js'
2424 export * from './untracked-files'
25-export * from './untracked-rows-of'
2625 export * from './untracked-place'
26+export * from './untracked-rows-of'
2727
2828 export * as default from '.'
@@ -1,6 +1,6 @@
11 import Limits from '../../limits'
22 import Argv from '../argv'
3-import GitParse from '../parse'
3+import Parse from '../parse'
44 import type Types from '../types'
55
66 /**
@@ -22,7 +22,7 @@
2222
2323 const shortstat = await run([...Argv.DIFF_LEADING_ARGS, base, '--shortstat'])
2424 const totals =
25- shortstat.exitCode === 0 ? GitParse.parseShortstat(shortstat.stdout) : null
25+ shortstat.exitCode === 0 ? Parse.parseShortstat(shortstat.stdout) : null
2626 const isPastDetails =
2727 totals !== null && totals.filesCount > Limits.MAX_FILES_FOR_DETAILS
2828
@@ -36,7 +36,7 @@
3636 '--numstat',
3737 '-z',
3838 ])
39- const isRead = GitParse.isWholeAnswer(numstat)
39+ const isRead = Parse.isWholeAnswer(numstat)
4040
41- return isRead ? GitParse.parseNumstat(numstat.stdout, Limits.MAX_FILES) : null
41+ return isRead ? Parse.parseNumstat(numstat.stdout, Limits.MAX_FILES) : null
4242 }
@@ -1,5 +1,5 @@
11 import Argv from '../argv'
2-import GitParse from '../parse'
2+import Parse from '../parse'
33 import type Types from '../types'
44
55 /**
@@ -15,9 +15,9 @@
1515 run: Types.GitRun,
1616 ): Promise<Types.NumstatResult | null> {
1717 const numstat = await run([...Argv.DIFF_LEADING_ARGS, '--numstat', '-z'])
18- const isRead = GitParse.isWholeAnswer(numstat)
18+ const isRead = Parse.isWholeAnswer(numstat)
1919
2020 return isRead
21- ? GitParse.parseNumstat(numstat.stdout, Number.POSITIVE_INFINITY)
21+ ? Parse.parseNumstat(numstat.stdout, Number.POSITIVE_INFINITY)
2222 : null
2323 }
@@ -1,5 +1,5 @@
11 import Argv from '../../argv'
2-import GitParse from '../../parse'
2+import Parse from '../../parse'
33 import type Types from '../../types'
44 import type { UntrackedPlace } from '../untracked-place'
55 import { untrackedRowsOf } from '../untracked-rows-of'
@@ -30,7 +30,7 @@
3030 '--full-name',
3131 ])
3232
33- if (!GitParse.isWholeAnswer(listing)) {
33+ if (!Parse.isWholeAnswer(listing)) {
3434 return null
3535 }
3636
@@ -1,0 +1,5 @@
1+/**
2+ * The most of a child's stdout `$.process.run` hands back, in bytes
3+ * (process-host OUTPUT_LIMIT); a listing that long was cut, silently.
4+ */
5+export const HOST_OUTPUT_CAP_BYTES = 4 * 1024 * 1024
@@ -1,0 +1,4 @@
1+export * from './host-output-cap-bytes.js'
2+export * from './max-diff-bytes.js'
3+
4+export * as default from '.'
@@ -1,0 +1,5 @@
1+/**
2+ * A file whose unified diff text is longer than this is a "large file": its
3+ * hunks are not parsed (the built-in panel's size cap).
4+ */
5+export const MAX_DIFF_BYTES = 1_000_000
Dmods/diff/hooks/limits/sizes/host-output-cap-bytes.ts+0 -5
@@ -1,5 +1,0 @@
1-/**
2- * The most of a child's stdout `$.process.run` hands back, in bytes
3- * (process-host OUTPUT_LIMIT); a listing that long was cut, silently.
4- */
5-export const HOST_OUTPUT_CAP_BYTES = 4 * 1024 * 1024
@@ -1,5 +1,4 @@
1-export * from './host-output-cap-bytes.js'
2-export * from './max-diff-bytes.js'
1+export * from './bytes'
32 export * from './max-files.js'
43 export * from './max-files-for-details.js'
54 export * from './max-lines-per-file.js'
Dmods/diff/hooks/limits/sizes/max-diff-bytes.ts+0 -5
@@ -1,5 +1,0 @@
1-/**
2- * A file whose unified diff text is longer than this is a "large file": its
3- * hunks are not parsed (the built-in panel's size cap).
4- */
5-export const MAX_DIFF_BYTES = 1_000_000
@@ -3,6 +3,6 @@
33 export * from './not-in-repository-text.js'
44 export * from './register-failed-text-of.js'
55 export * from './resize-terminal-text.js'
6-export * from './untracked-withheld-text.js'
6+export * from './untracked-withheld-text-of.js'
77
88 export * as default from '.'
@@ -1,0 +1,9 @@
1+/**
2+ * The pane's word for a fetch whose untracked listing the backend did not
3+ * give whole: the tracked rows stand, the new files are not counted.
4+ *
5+ * @param lister the program that lists untracked files (BackendWords)
6+ * @returns the note
7+ */
8+export const untrackedWithheldTextOf = (lister: string) =>
9+ `Untracked files unavailable (${lister} could not list them); not counted`
Dmods/diff/hooks/names/texts/untracked-withheld-text.ts+0 -9
@@ -1,9 +1,0 @@
1-/**
2- * The pane's word for a fetch whose untracked listing the backend did not
3- * give whole: the tracked rows stand, the new files are not counted.
4- *
5- * @param lister the program that lists untracked files (BackendWords)
6- * @returns the note
7- */
8-export const untrackedWithheldTextOf = (lister: string) =>
9- `Untracked files unavailable (${lister} could not list them); not counted`