@descent-vtt/spec-harness 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +372 -0
- package/README.md +78 -11
- package/dist/briefs.d.ts +45 -0
- package/dist/briefs.js +73 -0
- package/dist/briefs.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +384 -84
- package/dist/cli.js.map +1 -1
- package/dist/commands.js +7 -2
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts +15 -1
- package/dist/config.js +16 -3
- package/dist/config.js.map +1 -1
- package/dist/configure.d.ts +142 -32
- package/dist/configure.js +316 -89
- package/dist/configure.js.map +1 -1
- package/dist/fs.d.ts +150 -3
- package/dist/fs.js +459 -17
- package/dist/fs.js.map +1 -1
- package/dist/git.d.ts +85 -9
- package/dist/git.js +140 -16
- package/dist/git.js.map +1 -1
- package/dist/hooks.d.ts +111 -0
- package/dist/hooks.js +172 -16
- package/dist/hooks.js.map +1 -1
- package/dist/round.d.ts +8 -0
- package/dist/round.js +32 -9
- package/dist/round.js.map +1 -1
- package/dist/sandbox.js +4 -3
- package/dist/sandbox.js.map +1 -1
- package/dist/server.d.ts +25 -1
- package/dist/server.js +194 -27
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts +6 -3
- package/dist/setup.js +74 -23
- package/dist/setup.js.map +1 -1
- package/dist/siblings.d.ts +42 -7
- package/dist/siblings.js +135 -30
- package/dist/siblings.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.js.map +1 -1
- package/dist/usage.d.ts +66 -12
- package/dist/usage.js +134 -21
- package/dist/usage.js.map +1 -1
- package/dist/workspace.d.ts +80 -1
- package/dist/workspace.js +188 -8
- package/dist/workspace.js.map +1 -1
- package/hooks/hooks.json +16 -0
- package/package.json +2 -2
package/dist/git.d.ts
CHANGED
|
@@ -6,14 +6,45 @@
|
|
|
6
6
|
* call is an argument vector, never a shell string, so a path or a ref cannot
|
|
7
7
|
* become a command.
|
|
8
8
|
*/
|
|
9
|
-
import type { FileChange } from './types.js';
|
|
9
|
+
import type { FileChange, LocalBranch } from './types.js';
|
|
10
10
|
export interface Run {
|
|
11
11
|
readonly code: number;
|
|
12
12
|
readonly stdout: string;
|
|
13
13
|
readonly stderr: string;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* How long a process the harness asks a question of is given to answer:
|
|
17
|
+
* git's reads, spec-brief's list of the briefs. Claude Code gives the hook's
|
|
18
|
+
* entry 60 seconds, as init and the plugin write it, and cancels a hook that
|
|
19
|
+
* reaches them, which before a write lets the write go ahead (ADR-0002). A
|
|
20
|
+
* process that does not answer within this is stopped and its question
|
|
21
|
+
* answered as one that failed, which before a write holds it, well inside
|
|
22
|
+
* that minute; and the slowest such process measured, on a Windows
|
|
23
|
+
* workstation under load and on GitHub's runner, answered in a fraction of
|
|
24
|
+
* it.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ANSWER_MS = 20000;
|
|
27
|
+
/**
|
|
28
|
+
* How long a process that does a round's work is given: a diff of the
|
|
29
|
+
* round, a worktree added for a probe or taken away, a sibling's check of
|
|
30
|
+
* the repository. Its length is the repository's, and none runs before a
|
|
31
|
+
* write.
|
|
32
|
+
*/
|
|
33
|
+
export declare const WORK_MS = 600000;
|
|
34
|
+
/**
|
|
35
|
+
* Whether a process ended because the harness stopped it at its time
|
|
36
|
+
* limit: Node kills it then and says so with `killed`. It kills one for
|
|
37
|
+
* output past `maxBuffer` as well, and names that by its code.
|
|
38
|
+
*/
|
|
39
|
+
export declare function stoppedAtLimit(error: unknown): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Runs git and answers whatever it answered; a missing git is a code of -1.
|
|
42
|
+
* A git that does not answer within `limit` is stopped, and rejected as a
|
|
43
|
+
* `GitError` that says so: read as a failure, a question about the work
|
|
44
|
+
* tree or the branch would answer as a directory in none, or a head on no
|
|
45
|
+
* branch, under which the hook checks no write.
|
|
46
|
+
*/
|
|
47
|
+
export declare function git(args: readonly string[], cwd: string, limit?: number): Promise<Run>;
|
|
17
48
|
export declare class GitError extends Error {
|
|
18
49
|
constructor(message: string);
|
|
19
50
|
}
|
|
@@ -55,21 +86,66 @@ export declare function workTree(cwd: string): Promise<WorkTree | 'unread' | nul
|
|
|
55
86
|
/**
|
|
56
87
|
* Where git runs the hook `name` from, absolute: `core.hooksPath` read as git
|
|
57
88
|
* reads it - relative to the work tree, absolute, or under `~` - and a linked
|
|
58
|
-
* worktree's shared hooks.
|
|
89
|
+
* worktree's shared hooks. Where git cannot say, the first line of what it
|
|
90
|
+
* said instead: under a `core.hooksPath` that names a file it refuses the
|
|
91
|
+
* path as one that is not a directory (measured with git 2.47 and 2.55).
|
|
92
|
+
*/
|
|
93
|
+
export declare function hookPath(name: string, cwd: string): Promise<{
|
|
94
|
+
readonly path: string;
|
|
95
|
+
} | {
|
|
96
|
+
readonly refused: string;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* The checked-out branch's name, or `null` on a detached head: what HEAD
|
|
100
|
+
* points at, with `refs/heads/` taken off and nothing else. `--short` gives
|
|
101
|
+
* the shortest name git reads as that ref and no other, which is another
|
|
102
|
+
* name where a tag, or a ref under `refs/`, has the branch's: measured with
|
|
103
|
+
* git 2.55, on `claude/brief-042-x` beside the tag `claude/brief-042-x` it
|
|
104
|
+
* answers `heads/claude/brief-042-x`, which no template reads, and the
|
|
105
|
+
* brief went unnamed (ADR-0005). A HEAD that points outside `refs/heads/`,
|
|
106
|
+
* as `git symbolic-ref HEAD refs/tags/x` leaves it, is named as git status
|
|
107
|
+
* names it, the ref whole.
|
|
59
108
|
*/
|
|
60
|
-
export declare function hookPath(name: string, cwd: string): Promise<string | null>;
|
|
61
|
-
/** The checked-out branch's short name, or `null` on a detached head. */
|
|
62
109
|
export declare function currentBranch(cwd: string): Promise<string | null>;
|
|
63
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* The remote's default branch as a local ref, such as `origin/main`, or
|
|
112
|
+
* `null`. This is a ref the harness hands git and spec-brief as the base, so
|
|
113
|
+
* it is the short name git reads as that ref and no other: beside a local
|
|
114
|
+
* branch or a tag named `origin/main`, measured with git 2.55, it is
|
|
115
|
+
* `remotes/origin/main`, where `origin/main` would be read as the branch or
|
|
116
|
+
* the tag (gitrevisions(7), the order a ref name is read in).
|
|
117
|
+
*/
|
|
64
118
|
export declare function remoteDefault(cwd: string): Promise<string | null>;
|
|
65
|
-
/**
|
|
66
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Every local branch. The two are one name but where a tag, or a ref under
|
|
121
|
+
* `refs/`, has the branch's: `main` beside the tag `main` is the ref
|
|
122
|
+
* `heads/main`. A ref name holds no control character
|
|
123
|
+
* (git-check-ref-format(1)), so a tab parts the two.
|
|
124
|
+
*/
|
|
125
|
+
export declare function localBranches(cwd: string): Promise<LocalBranch[]>;
|
|
67
126
|
export declare function revision(ref: string, cwd: string): Promise<string | null>;
|
|
68
127
|
export declare function mergeBase(a: string, b: string, cwd: string): Promise<string | null>;
|
|
69
128
|
/** Parses `git diff --name-status -z` output. */
|
|
70
129
|
export declare function parseNameStatus(output: string): FileChange[];
|
|
71
130
|
/** The files changed from one revision to another, renames found. */
|
|
72
131
|
export declare function changes(from: string, to: string, cwd: string): Promise<FileChange[]>;
|
|
132
|
+
/**
|
|
133
|
+
* The paths `git status --porcelain=v2 -z` names, each once, in the order
|
|
134
|
+
* git gives them: a rename or a copy by both its paths. A path with a space
|
|
135
|
+
* in it is the rest of its record, which `-z` prints as is.
|
|
136
|
+
*/
|
|
137
|
+
export declare function parseStatus(output: string): string[];
|
|
138
|
+
/**
|
|
139
|
+
* Every path the work tree holds changed from HEAD: tracked files that
|
|
140
|
+
* differ in the index or in the work tree, unmerged ones, and each
|
|
141
|
+
* untracked file, those in an untracked directory one by one. One start of
|
|
142
|
+
* git, with no optional lock taken: a status that writes the index as it
|
|
143
|
+
* refreshes it holds a lock that can make another git running beside it
|
|
144
|
+
* fail, and the manual has a status run in the background take none
|
|
145
|
+
* (git-status(1), *Background Refresh*, and git(1), `--no-optional-locks`,
|
|
146
|
+
* as Git for Windows 2.55.0 has them, read 2026-10-19).
|
|
147
|
+
*/
|
|
148
|
+
export declare function changedPaths(cwd: string): Promise<string[]>;
|
|
73
149
|
/** The files a commit would change: what is staged. */
|
|
74
150
|
export declare function stagedChanges(cwd: string): Promise<FileChange[]>;
|
|
75
151
|
/** A file's content at a revision, or `null` when it does not exist there. */
|
package/dist/git.js
CHANGED
|
@@ -7,12 +7,54 @@
|
|
|
7
7
|
* become a command.
|
|
8
8
|
*/
|
|
9
9
|
import { execFile } from 'node:child_process';
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* How long a process the harness asks a question of is given to answer:
|
|
12
|
+
* git's reads, spec-brief's list of the briefs. Claude Code gives the hook's
|
|
13
|
+
* entry 60 seconds, as init and the plugin write it, and cancels a hook that
|
|
14
|
+
* reaches them, which before a write lets the write go ahead (ADR-0002). A
|
|
15
|
+
* process that does not answer within this is stopped and its question
|
|
16
|
+
* answered as one that failed, which before a write holds it, well inside
|
|
17
|
+
* that minute; and the slowest such process measured, on a Windows
|
|
18
|
+
* workstation under load and on GitHub's runner, answered in a fraction of
|
|
19
|
+
* it.
|
|
20
|
+
*/
|
|
21
|
+
export const ANSWER_MS = 20_000;
|
|
22
|
+
/**
|
|
23
|
+
* How long a process that does a round's work is given: a diff of the
|
|
24
|
+
* round, a worktree added for a probe or taken away, a sibling's check of
|
|
25
|
+
* the repository. Its length is the repository's, and none runs before a
|
|
26
|
+
* write.
|
|
27
|
+
*/
|
|
28
|
+
export const WORK_MS = 600_000;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a process ended because the harness stopped it at its time
|
|
31
|
+
* limit: Node kills it then and says so with `killed`. It kills one for
|
|
32
|
+
* output past `maxBuffer` as well, and names that by its code.
|
|
33
|
+
*/
|
|
34
|
+
export function stoppedAtLimit(error) {
|
|
35
|
+
const failure = error;
|
|
36
|
+
return failure !== null && failure.killed === true && failure.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';
|
|
37
|
+
}
|
|
38
|
+
/** The git command a line of arguments runs, past the `-c` settings before it: every line the harness hands git names one. */
|
|
39
|
+
function subcommand(args) {
|
|
40
|
+
return args.find((arg, index) => !arg.startsWith('-') && args[index - 1] !== '-c');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Runs git and answers whatever it answered; a missing git is a code of -1.
|
|
44
|
+
* A git that does not answer within `limit` is stopped, and rejected as a
|
|
45
|
+
* `GitError` that says so: read as a failure, a question about the work
|
|
46
|
+
* tree or the branch would answer as a directory in none, or a head on no
|
|
47
|
+
* branch, under which the hook checks no write.
|
|
48
|
+
*/
|
|
49
|
+
export function git(args, cwd, limit = ANSWER_MS) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
13
51
|
// windowsHide only keeps a console window from flashing up on Windows,
|
|
14
52
|
// and the sweep runs on Linux, where its mutant is equivalent.
|
|
15
|
-
execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {
|
|
53
|
+
execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true, timeout: limit }, (error, stdout, stderr) => {
|
|
54
|
+
if (stoppedAtLimit(error)) {
|
|
55
|
+
reject(new GitError(`git ${subcommand(args)} did not answer within ${limit / 1000} seconds, and was stopped`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
16
58
|
// Every caller tells 0 from any other code, never one failure from
|
|
17
59
|
// another, so which code a failure gets is equivalent to its mutants.
|
|
18
60
|
const code = error === null ? 0 : typeof error.code === 'number' ? error.code : -1;
|
|
@@ -79,25 +121,59 @@ export async function workTree(cwd) {
|
|
|
79
121
|
/**
|
|
80
122
|
* Where git runs the hook `name` from, absolute: `core.hooksPath` read as git
|
|
81
123
|
* reads it - relative to the work tree, absolute, or under `~` - and a linked
|
|
82
|
-
* worktree's shared hooks.
|
|
124
|
+
* worktree's shared hooks. Where git cannot say, the first line of what it
|
|
125
|
+
* said instead: under a `core.hooksPath` that names a file it refuses the
|
|
126
|
+
* path as one that is not a directory (measured with git 2.47 and 2.55).
|
|
83
127
|
*/
|
|
84
128
|
export async function hookPath(name, cwd) {
|
|
85
|
-
|
|
129
|
+
const run = await git(['rev-parse', '--path-format=absolute', '--git-path', `hooks/${name}`], cwd);
|
|
130
|
+
if (run.code === 0)
|
|
131
|
+
return { path: run.stdout.trim() };
|
|
132
|
+
return { refused: run.stderr.split(/\r?\n/)[0] };
|
|
86
133
|
}
|
|
87
|
-
|
|
134
|
+
const HEADS = 'refs/heads/';
|
|
135
|
+
/**
|
|
136
|
+
* The checked-out branch's name, or `null` on a detached head: what HEAD
|
|
137
|
+
* points at, with `refs/heads/` taken off and nothing else. `--short` gives
|
|
138
|
+
* the shortest name git reads as that ref and no other, which is another
|
|
139
|
+
* name where a tag, or a ref under `refs/`, has the branch's: measured with
|
|
140
|
+
* git 2.55, on `claude/brief-042-x` beside the tag `claude/brief-042-x` it
|
|
141
|
+
* answers `heads/claude/brief-042-x`, which no template reads, and the
|
|
142
|
+
* brief went unnamed (ADR-0005). A HEAD that points outside `refs/heads/`,
|
|
143
|
+
* as `git symbolic-ref HEAD refs/tags/x` leaves it, is named as git status
|
|
144
|
+
* names it, the ref whole.
|
|
145
|
+
*/
|
|
88
146
|
export async function currentBranch(cwd) {
|
|
89
|
-
|
|
147
|
+
const ref = await value(['symbolic-ref', '--quiet', 'HEAD'], cwd);
|
|
148
|
+
return ref !== null && ref.startsWith(HEADS) ? ref.slice(HEADS.length) : ref;
|
|
90
149
|
}
|
|
91
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* The remote's default branch as a local ref, such as `origin/main`, or
|
|
152
|
+
* `null`. This is a ref the harness hands git and spec-brief as the base, so
|
|
153
|
+
* it is the short name git reads as that ref and no other: beside a local
|
|
154
|
+
* branch or a tag named `origin/main`, measured with git 2.55, it is
|
|
155
|
+
* `remotes/origin/main`, where `origin/main` would be read as the branch or
|
|
156
|
+
* the tag (gitrevisions(7), the order a ref name is read in).
|
|
157
|
+
*/
|
|
92
158
|
export async function remoteDefault(cwd) {
|
|
93
159
|
return value(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], cwd);
|
|
94
160
|
}
|
|
95
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* Every local branch. The two are one name but where a tag, or a ref under
|
|
163
|
+
* `refs/`, has the branch's: `main` beside the tag `main` is the ref
|
|
164
|
+
* `heads/main`. A ref name holds no control character
|
|
165
|
+
* (git-check-ref-format(1)), so a tab parts the two.
|
|
166
|
+
*/
|
|
96
167
|
export async function localBranches(cwd) {
|
|
97
|
-
const listed = await value(['for-each-ref', '--format=%(refname:short)',
|
|
168
|
+
const listed = await value(['for-each-ref', '--format=%(refname:lstrip=2)%09%(refname:short)', HEADS], cwd);
|
|
98
169
|
// for-each-ref fails only where git itself does, outside a repository,
|
|
99
170
|
// where init never runs: the null check is there for the type.
|
|
100
|
-
|
|
171
|
+
if (listed === null || listed === '')
|
|
172
|
+
return [];
|
|
173
|
+
return listed.split('\n').map((line) => {
|
|
174
|
+
const parts = line.split('\t');
|
|
175
|
+
return { name: parts[0], ref: parts[1] };
|
|
176
|
+
});
|
|
101
177
|
}
|
|
102
178
|
export async function revision(ref, cwd) {
|
|
103
179
|
return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);
|
|
@@ -130,14 +206,62 @@ export function parseNameStatus(output) {
|
|
|
130
206
|
}
|
|
131
207
|
/** The files changed from one revision to another, renames found. */
|
|
132
208
|
export async function changes(from, to, cwd) {
|
|
133
|
-
const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd);
|
|
209
|
+
const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd, WORK_MS);
|
|
134
210
|
if (run.code !== 0)
|
|
135
211
|
throw new GitError(`git diff ${from} ${to} failed: ${run.stderr.trim()}`);
|
|
136
212
|
return parseNameStatus(run.stdout);
|
|
137
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* How many words stand before the path in each kind of record of
|
|
216
|
+
* `git status --porcelain=v2`, as git-status(1) has them (Git for Windows
|
|
217
|
+
* 2.55.0, *Porcelain Format Version 2*, read 2026-10-19): an ordinary
|
|
218
|
+
* change, `1`, and seven; a rename or a copy, `2`, and eight, its path
|
|
219
|
+
* followed by the path it came from as a record of its own under `-z`; an
|
|
220
|
+
* unmerged path, `u`, and nine; an untracked or an ignored path, `?` or
|
|
221
|
+
* `!`, and none. A header, `#`, names no path.
|
|
222
|
+
*/
|
|
223
|
+
const STATUS_FIELDS = { '1': 8, '2': 9, u: 10, '?': 1, '!': 1 };
|
|
224
|
+
/**
|
|
225
|
+
* The paths `git status --porcelain=v2 -z` names, each once, in the order
|
|
226
|
+
* git gives them: a rename or a copy by both its paths. A path with a space
|
|
227
|
+
* in it is the rest of its record, which `-z` prints as is.
|
|
228
|
+
*/
|
|
229
|
+
export function parseStatus(output) {
|
|
230
|
+
const records = output.split('\0');
|
|
231
|
+
const paths = new Set();
|
|
232
|
+
for (let at = 0; at < records.length; at += 1) {
|
|
233
|
+
const record = records[at];
|
|
234
|
+
const fields = STATUS_FIELDS[record.charAt(0)];
|
|
235
|
+
if (fields === undefined)
|
|
236
|
+
continue;
|
|
237
|
+
paths.add(record.split(' ').slice(fields).join(' '));
|
|
238
|
+
if (record.startsWith('2')) {
|
|
239
|
+
at += 1;
|
|
240
|
+
paths.add(records[at] ?? '');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
paths.delete('');
|
|
244
|
+
return [...paths];
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Every path the work tree holds changed from HEAD: tracked files that
|
|
248
|
+
* differ in the index or in the work tree, unmerged ones, and each
|
|
249
|
+
* untracked file, those in an untracked directory one by one. One start of
|
|
250
|
+
* git, with no optional lock taken: a status that writes the index as it
|
|
251
|
+
* refreshes it holds a lock that can make another git running beside it
|
|
252
|
+
* fail, and the manual has a status run in the background take none
|
|
253
|
+
* (git-status(1), *Background Refresh*, and git(1), `--no-optional-locks`,
|
|
254
|
+
* as Git for Windows 2.55.0 has them, read 2026-10-19).
|
|
255
|
+
*/
|
|
256
|
+
export async function changedPaths(cwd) {
|
|
257
|
+
const run = await git(['--no-optional-locks', 'status', '--porcelain=v2', '-z', '--untracked-files=all'], cwd);
|
|
258
|
+
if (run.code !== 0)
|
|
259
|
+
throw new GitError(`git status failed: ${run.stderr.trim()}`);
|
|
260
|
+
return parseStatus(run.stdout);
|
|
261
|
+
}
|
|
138
262
|
/** The files a commit would change: what is staged. */
|
|
139
263
|
export async function stagedChanges(cwd) {
|
|
140
|
-
const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd);
|
|
264
|
+
const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd, WORK_MS);
|
|
141
265
|
if (run.code !== 0)
|
|
142
266
|
throw new GitError(`git diff --cached failed: ${run.stderr.trim()}`);
|
|
143
267
|
return parseNameStatus(run.stdout);
|
|
@@ -181,7 +305,7 @@ export async function verifyCommit(sha, allowedSignersFile, cwd) {
|
|
|
181
305
|
return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };
|
|
182
306
|
}
|
|
183
307
|
export async function addWorktree(path, rev, cwd) {
|
|
184
|
-
const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd);
|
|
308
|
+
const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd, WORK_MS);
|
|
185
309
|
if (run.code !== 0)
|
|
186
310
|
throw new GitError(`git worktree add failed: ${run.stderr.trim()}`);
|
|
187
311
|
}
|
|
@@ -198,6 +322,6 @@ export async function removeWorktree(path, cwd) {
|
|
|
198
322
|
// The sandbox asks again when this answers false, and git, asked again
|
|
199
323
|
// about a worktree it has forgotten, changes nothing: the mutant that
|
|
200
324
|
// always answers false is equivalent.
|
|
201
|
-
return (await git(['worktree', 'remove', '--force', path], cwd)).code === 0;
|
|
325
|
+
return (await git(['worktree', 'remove', '--force', path], cwd, WORK_MS)).code === 0;
|
|
202
326
|
}
|
|
203
327
|
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets git.\n *\n * Reads, except for two things ADR-0003 allows and bounds: a temporary\n * worktree, added and always removed by the sandbox, and nothing else. Every\n * call is an argument vector, never a shell string, so a path or a ref cannot\n * become a command.\n */\n\nimport { execFile } from 'node:child_process';\n\nimport type { FileChange } from './types.js';\n\nexport interface Run {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\n/** Runs git and answers whatever it answered; a missing git is a code of -1. */\nexport function git(args: readonly string[], cwd: string): Promise<Run> {\n return new Promise((resolve) => {\n // windowsHide only keeps a console window from flashing up on Windows,\n // and the sweep runs on Linux, where its mutant is equivalent.\n execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {\n // Every caller tells 0 from any other code, never one failure from\n // another, so which code a failure gets is equivalent to its mutants.\n const code = error === null ? 0 : typeof (error as { code?: unknown }).code === 'number' ? (error as { code: number }).code : -1;\n resolve({ code, stdout, stderr });\n });\n });\n}\n\nasync function value(args: readonly string[], cwd: string): Promise<string | null> {\n const run = await git(args, cwd);\n return run.code === 0 ? run.stdout.trim() : null;\n}\n\nexport class GitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n/** The top of the work tree holding `cwd`, POSIX-separated, or `null` outside one. */\nexport async function workTreeRoot(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--show-toplevel'], cwd);\n}\n\n/** The git directory every worktree of this repository shares. */\nexport async function commonDirectory(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);\n}\n\n/** A work tree as one start of git names it: its top, and the git directory its worktrees share. */\nexport interface WorkTree {\n readonly root: string;\n readonly commonDir: string;\n}\n\n/**\n * The top of the work tree holding `cwd` and the common directory, asked of\n * git in one start where {@link workTreeRoot} and {@link commonDirectory}\n * are two: `null` outside a work tree, and `'unread'` where the one answer\n * cannot be taken for the two, which are then to be asked for as they were.\n *\n * Measured with git 2.43 and 2.55, rev-parse answers the two in the order\n * they stand on its line, a line each, and prints nothing where the first\n * fails. Its manual says of that only that `--show-toplevel` reports an\n * error where there is no working tree (git-rev-parse(1), Options for\n * Files, as git 2.55.0 has it, read 2026-10-14), so nothing is read on\n * trust. Two lines that are each what `trim` would leave, from a git that\n * did not fail, are the two answers. Nothing printed, by a git that\n * failed, is the first question failing, the one {@link workTreeRoot}\n * asks: `cwd` is in no work tree. Anything else is not read: a path that\n * holds a line break is more lines than two, and so is what a git before\n * 2.31 would print, to which `--path-format=absolute` is a flag to hand\n * back (the same manual, Options for Filtering; the option is new in the\n * release notes of 2.31.0). No such git was measured.\n *\n * `--path-format` reaches only what follows it on the line (Options for\n * Files), so the top is printed as it is where it is asked for alone. The\n * common directory is asked for from `cwd` here, and from the top by\n * {@link commonDirectory}: the same directory only where `cwd` is the top.\n */\nexport async function workTree(cwd: string): Promise<WorkTree | 'unread' | null> {\n const run = await git(['rev-parse', '--show-toplevel', '--path-format=absolute', '--git-common-dir'], cwd);\n if (run.stdout === '') return run.code === 0 ? 'unread' : null;\n const lines = run.stdout.split('\\n');\n if (run.code !== 0 || lines.length !== 3 || lines[2] !== '') return 'unread';\n const root = lines[0]!;\n const commonDir = lines[1]!;\n if (root === '' || commonDir === '' || root.trim() !== root || commonDir.trim() !== commonDir) return 'unread';\n return { root, commonDir };\n}\n\n/**\n * Where git runs the hook `name` from, absolute: `core.hooksPath` read as git\n * reads it - relative to the work tree, absolute, or under `~` - and a linked\n * worktree's shared hooks. `null` when git cannot say, before 2.31.\n */\nexport async function hookPath(name: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-path', `hooks/${name}`], cwd);\n}\n\n/** The checked-out branch's short name, or `null` on a detached head. */\nexport async function currentBranch(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'HEAD'], cwd);\n}\n\n/** The remote's default branch as a local ref, such as `origin/main`, or `null`. */\nexport async function remoteDefault(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], cwd);\n}\n\n/** Every local branch, by its short name. */\nexport async function localBranches(cwd: string): Promise<string[]> {\n const listed = await value(['for-each-ref', '--format=%(refname:short)', 'refs/heads/'], cwd);\n // for-each-ref fails only where git itself does, outside a repository,\n // where init never runs: the null check is there for the type.\n return listed === null || listed === '' ? [] : listed.split('\\n');\n}\n\nexport async function revision(ref: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);\n}\n\nexport async function mergeBase(a: string, b: string, cwd: string): Promise<string | null> {\n return value(['merge-base', a, b], cwd);\n}\n\n/** Parses `git diff --name-status -z` output. */\nexport function parseNameStatus(output: string): FileChange[] {\n const fields = output.split('\\0');\n const out: FileChange[] = [];\n for (let i = 0; i < fields.length; ) {\n const code = fields[i] as string;\n if (code === '') {\n i += 1;\n continue;\n }\n const letter = code.charAt(0);\n const status = letter as FileChange['status'];\n if (status === 'R' || status === 'C') {\n out.push({ status, from: fields[i + 1] ?? '', path: fields[i + 2] ?? '' });\n i += 3;\n } else {\n out.push({ status, path: fields[i + 1] ?? '' });\n i += 2;\n }\n }\n return out;\n}\n\n/** The files changed from one revision to another, renames found. */\nexport async function changes(from: string, to: string, cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd);\n if (run.code !== 0) throw new GitError(`git diff ${from} ${to} failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** The files a commit would change: what is staged. */\nexport async function stagedChanges(cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd);\n if (run.code !== 0) throw new GitError(`git diff --cached failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** A file's content at a revision, or `null` when it does not exist there. */\nexport async function show(ref: string, path: string, cwd: string): Promise<string | null> {\n const run = await git(['show', `${ref}:${path}`], cwd);\n return run.code === 0 ? run.stdout : null;\n}\n\n/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */\nexport function isUncommitted(sha: string): boolean {\n return /^0+$/.test(sha);\n}\n\n/**\n * The commit that last changed one line of a file, as of a revision, or of\n * the working tree when `ref` is `null` - where a line nobody committed yet\n * belongs to the all-zero commit.\n */\nexport async function blameLine(ref: string | null, path: string, line: number, cwd: string): Promise<string | null> {\n const run = await git(['blame', '--porcelain', '-L', `${line},${line}`, ...(ref === null ? [] : [ref]), '--', path], cwd);\n // A blame that fails prints nothing on stdout, which names no commit. Its\n // output opens with the commit, so the anchor is equivalent to its mutant.\n const sha = /^([0-9a-f]{40,64}) /.exec(run.stdout)?.[1];\n return sha ?? null;\n}\n\n/** A good signature names its allowed signer's principal; any other names none. */\nexport type Signature =\n | { readonly good: true; readonly principal: string; readonly detail: string }\n | { readonly good: false; readonly principal: null; readonly detail: string };\n\n/**\n * Whether a commit carries a good SSH signature by a principal in the given\n * allowed-signers file. The file is passed in, never read from the commit's\n * own tree: a round cannot vouch for itself (ADR-0006).\n */\nexport async function verifyCommit(sha: string, allowedSignersFile: string, cwd: string): Promise<Signature> {\n const run = await git(\n ['-c', `gpg.ssh.allowedSignersFile=${allowedSignersFile}`, '-c', 'gpg.format=ssh', 'verify-commit', '--raw', sha],\n cwd,\n );\n const text = `${run.stderr}\\n${run.stdout}`;\n const principal = /Good \"git\" signature for (\\S+)/.exec(text)?.[1];\n // git writes the check to stderr, so the trim changes the first line only\n // for output on stdout alone, which verify-commit does not print; and split\n // always answers a first part.\n const detail = text.trim().split('\\n')[0]!;\n // git exits 0 only for a signature by a listed principal, which it names:\n // either condition alone is equivalent to both, and both trust neither.\n return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };\n}\n\nexport async function addWorktree(path: string, rev: string, cwd: string): Promise<void> {\n const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd);\n if (run.code !== 0) throw new GitError(`git worktree add failed: ${run.stderr.trim()}`);\n}\n\n/**\n * Has git remove the worktree at `path`, and no other, with its record of it,\n * and answers whether git did. Git deletes what it can of the worktree's\n * directory and forgets the worktree even when it cannot delete it all. It\n * refuses a worktree whose directory has lost its `.git` file, and removes\n * its record of one whose directory is gone, found by the path it was added\n * at: `git worktree prune` would also forget every other worktree git has\n * lost track of, the person's among them.\n */\nexport async function removeWorktree(path: string, cwd: string): Promise<boolean> {\n // The sandbox asks again when this answers false, and git, asked again\n // about a worktree it has forgotten, changes nothing: the mutant that\n // always answers false is equivalent.\n return (await git(['worktree', 'remove', '--force', path], cwd)).code === 0;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets git.\n *\n * Reads, except for two things ADR-0003 allows and bounds: a temporary\n * worktree, added and always removed by the sandbox, and nothing else. Every\n * call is an argument vector, never a shell string, so a path or a ref cannot\n * become a command.\n */\n\nimport { execFile } from 'node:child_process';\n\nimport type { FileChange, LocalBranch } from './types.js';\n\nexport interface Run {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\n/**\n * How long a process the harness asks a question of is given to answer:\n * git's reads, spec-brief's list of the briefs. Claude Code gives the hook's\n * entry 60 seconds, as init and the plugin write it, and cancels a hook that\n * reaches them, which before a write lets the write go ahead (ADR-0002). A\n * process that does not answer within this is stopped and its question\n * answered as one that failed, which before a write holds it, well inside\n * that minute; and the slowest such process measured, on a Windows\n * workstation under load and on GitHub's runner, answered in a fraction of\n * it.\n */\nexport const ANSWER_MS = 20_000;\n\n/**\n * How long a process that does a round's work is given: a diff of the\n * round, a worktree added for a probe or taken away, a sibling's check of\n * the repository. Its length is the repository's, and none runs before a\n * write.\n */\nexport const WORK_MS = 600_000;\n\n/**\n * Whether a process ended because the harness stopped it at its time\n * limit: Node kills it then and says so with `killed`. It kills one for\n * output past `maxBuffer` as well, and names that by its code.\n */\nexport function stoppedAtLimit(error: unknown): boolean {\n const failure = error as { killed?: unknown; code?: unknown } | null;\n return failure !== null && failure.killed === true && failure.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';\n}\n\n/** The git command a line of arguments runs, past the `-c` settings before it: every line the harness hands git names one. */\nfunction subcommand(args: readonly string[]): string {\n return args.find((arg, index) => !arg.startsWith('-') && args[index - 1] !== '-c')!;\n}\n\n/**\n * Runs git and answers whatever it answered; a missing git is a code of -1.\n * A git that does not answer within `limit` is stopped, and rejected as a\n * `GitError` that says so: read as a failure, a question about the work\n * tree or the branch would answer as a directory in none, or a head on no\n * branch, under which the hook checks no write.\n */\nexport function git(args: readonly string[], cwd: string, limit = ANSWER_MS): Promise<Run> {\n return new Promise((resolve, reject) => {\n // windowsHide only keeps a console window from flashing up on Windows,\n // and the sweep runs on Linux, where its mutant is equivalent.\n execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true, timeout: limit }, (error, stdout, stderr) => {\n if (stoppedAtLimit(error)) {\n reject(new GitError(`git ${subcommand(args)} did not answer within ${limit / 1000} seconds, and was stopped`));\n return;\n }\n // Every caller tells 0 from any other code, never one failure from\n // another, so which code a failure gets is equivalent to its mutants.\n const code = error === null ? 0 : typeof (error as { code?: unknown }).code === 'number' ? (error as { code: number }).code : -1;\n resolve({ code, stdout, stderr });\n });\n });\n}\n\nasync function value(args: readonly string[], cwd: string): Promise<string | null> {\n const run = await git(args, cwd);\n return run.code === 0 ? run.stdout.trim() : null;\n}\n\nexport class GitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n/** The top of the work tree holding `cwd`, POSIX-separated, or `null` outside one. */\nexport async function workTreeRoot(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--show-toplevel'], cwd);\n}\n\n/** The git directory every worktree of this repository shares. */\nexport async function commonDirectory(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);\n}\n\n/** A work tree as one start of git names it: its top, and the git directory its worktrees share. */\nexport interface WorkTree {\n readonly root: string;\n readonly commonDir: string;\n}\n\n/**\n * The top of the work tree holding `cwd` and the common directory, asked of\n * git in one start where {@link workTreeRoot} and {@link commonDirectory}\n * are two: `null` outside a work tree, and `'unread'` where the one answer\n * cannot be taken for the two, which are then to be asked for as they were.\n *\n * Measured with git 2.43 and 2.55, rev-parse answers the two in the order\n * they stand on its line, a line each, and prints nothing where the first\n * fails. Its manual says of that only that `--show-toplevel` reports an\n * error where there is no working tree (git-rev-parse(1), Options for\n * Files, as git 2.55.0 has it, read 2026-10-14), so nothing is read on\n * trust. Two lines that are each what `trim` would leave, from a git that\n * did not fail, are the two answers. Nothing printed, by a git that\n * failed, is the first question failing, the one {@link workTreeRoot}\n * asks: `cwd` is in no work tree. Anything else is not read: a path that\n * holds a line break is more lines than two, and so is what a git before\n * 2.31 would print, to which `--path-format=absolute` is a flag to hand\n * back (the same manual, Options for Filtering; the option is new in the\n * release notes of 2.31.0). No such git was measured.\n *\n * `--path-format` reaches only what follows it on the line (Options for\n * Files), so the top is printed as it is where it is asked for alone. The\n * common directory is asked for from `cwd` here, and from the top by\n * {@link commonDirectory}: the same directory only where `cwd` is the top.\n */\nexport async function workTree(cwd: string): Promise<WorkTree | 'unread' | null> {\n const run = await git(['rev-parse', '--show-toplevel', '--path-format=absolute', '--git-common-dir'], cwd);\n if (run.stdout === '') return run.code === 0 ? 'unread' : null;\n const lines = run.stdout.split('\\n');\n if (run.code !== 0 || lines.length !== 3 || lines[2] !== '') return 'unread';\n const root = lines[0]!;\n const commonDir = lines[1]!;\n if (root === '' || commonDir === '' || root.trim() !== root || commonDir.trim() !== commonDir) return 'unread';\n return { root, commonDir };\n}\n\n/**\n * Where git runs the hook `name` from, absolute: `core.hooksPath` read as git\n * reads it - relative to the work tree, absolute, or under `~` - and a linked\n * worktree's shared hooks. Where git cannot say, the first line of what it\n * said instead: under a `core.hooksPath` that names a file it refuses the\n * path as one that is not a directory (measured with git 2.47 and 2.55).\n */\nexport async function hookPath(name: string, cwd: string): Promise<{ readonly path: string } | { readonly refused: string }> {\n const run = await git(['rev-parse', '--path-format=absolute', '--git-path', `hooks/${name}`], cwd);\n if (run.code === 0) return { path: run.stdout.trim() };\n return { refused: run.stderr.split(/\\r?\\n/)[0]! };\n}\n\nconst HEADS = 'refs/heads/';\n\n/**\n * The checked-out branch's name, or `null` on a detached head: what HEAD\n * points at, with `refs/heads/` taken off and nothing else. `--short` gives\n * the shortest name git reads as that ref and no other, which is another\n * name where a tag, or a ref under `refs/`, has the branch's: measured with\n * git 2.55, on `claude/brief-042-x` beside the tag `claude/brief-042-x` it\n * answers `heads/claude/brief-042-x`, which no template reads, and the\n * brief went unnamed (ADR-0005). A HEAD that points outside `refs/heads/`,\n * as `git symbolic-ref HEAD refs/tags/x` leaves it, is named as git status\n * names it, the ref whole.\n */\nexport async function currentBranch(cwd: string): Promise<string | null> {\n const ref = await value(['symbolic-ref', '--quiet', 'HEAD'], cwd);\n return ref !== null && ref.startsWith(HEADS) ? ref.slice(HEADS.length) : ref;\n}\n\n/**\n * The remote's default branch as a local ref, such as `origin/main`, or\n * `null`. This is a ref the harness hands git and spec-brief as the base, so\n * it is the short name git reads as that ref and no other: beside a local\n * branch or a tag named `origin/main`, measured with git 2.55, it is\n * `remotes/origin/main`, where `origin/main` would be read as the branch or\n * the tag (gitrevisions(7), the order a ref name is read in).\n */\nexport async function remoteDefault(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], cwd);\n}\n\n/**\n * Every local branch. The two are one name but where a tag, or a ref under\n * `refs/`, has the branch's: `main` beside the tag `main` is the ref\n * `heads/main`. A ref name holds no control character\n * (git-check-ref-format(1)), so a tab parts the two.\n */\nexport async function localBranches(cwd: string): Promise<LocalBranch[]> {\n const listed = await value(['for-each-ref', '--format=%(refname:lstrip=2)%09%(refname:short)', HEADS], cwd);\n // for-each-ref fails only where git itself does, outside a repository,\n // where init never runs: the null check is there for the type.\n if (listed === null || listed === '') return [];\n return listed.split('\\n').map((line) => {\n const parts = line.split('\\t');\n return { name: parts[0]!, ref: parts[1]! };\n });\n}\n\nexport async function revision(ref: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);\n}\n\nexport async function mergeBase(a: string, b: string, cwd: string): Promise<string | null> {\n return value(['merge-base', a, b], cwd);\n}\n\n/** Parses `git diff --name-status -z` output. */\nexport function parseNameStatus(output: string): FileChange[] {\n const fields = output.split('\\0');\n const out: FileChange[] = [];\n for (let i = 0; i < fields.length; ) {\n const code = fields[i] as string;\n if (code === '') {\n i += 1;\n continue;\n }\n const letter = code.charAt(0);\n const status = letter as FileChange['status'];\n if (status === 'R' || status === 'C') {\n out.push({ status, from: fields[i + 1] ?? '', path: fields[i + 2] ?? '' });\n i += 3;\n } else {\n out.push({ status, path: fields[i + 1] ?? '' });\n i += 2;\n }\n }\n return out;\n}\n\n/** The files changed from one revision to another, renames found. */\nexport async function changes(from: string, to: string, cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd, WORK_MS);\n if (run.code !== 0) throw new GitError(`git diff ${from} ${to} failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/**\n * How many words stand before the path in each kind of record of\n * `git status --porcelain=v2`, as git-status(1) has them (Git for Windows\n * 2.55.0, *Porcelain Format Version 2*, read 2026-10-19): an ordinary\n * change, `1`, and seven; a rename or a copy, `2`, and eight, its path\n * followed by the path it came from as a record of its own under `-z`; an\n * unmerged path, `u`, and nine; an untracked or an ignored path, `?` or\n * `!`, and none. A header, `#`, names no path.\n */\nconst STATUS_FIELDS: Readonly<Record<string, number>> = { '1': 8, '2': 9, u: 10, '?': 1, '!': 1 };\n\n/**\n * The paths `git status --porcelain=v2 -z` names, each once, in the order\n * git gives them: a rename or a copy by both its paths. A path with a space\n * in it is the rest of its record, which `-z` prints as is.\n */\nexport function parseStatus(output: string): string[] {\n const records = output.split('\\0');\n const paths = new Set<string>();\n for (let at = 0; at < records.length; at += 1) {\n const record = records[at]!;\n const fields = STATUS_FIELDS[record.charAt(0)];\n if (fields === undefined) continue;\n paths.add(record.split(' ').slice(fields).join(' '));\n if (record.startsWith('2')) {\n at += 1;\n paths.add(records[at] ?? '');\n }\n }\n paths.delete('');\n return [...paths];\n}\n\n/**\n * Every path the work tree holds changed from HEAD: tracked files that\n * differ in the index or in the work tree, unmerged ones, and each\n * untracked file, those in an untracked directory one by one. One start of\n * git, with no optional lock taken: a status that writes the index as it\n * refreshes it holds a lock that can make another git running beside it\n * fail, and the manual has a status run in the background take none\n * (git-status(1), *Background Refresh*, and git(1), `--no-optional-locks`,\n * as Git for Windows 2.55.0 has them, read 2026-10-19).\n */\nexport async function changedPaths(cwd: string): Promise<string[]> {\n const run = await git(['--no-optional-locks', 'status', '--porcelain=v2', '-z', '--untracked-files=all'], cwd);\n if (run.code !== 0) throw new GitError(`git status failed: ${run.stderr.trim()}`);\n return parseStatus(run.stdout);\n}\n\n/** The files a commit would change: what is staged. */\nexport async function stagedChanges(cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd, WORK_MS);\n if (run.code !== 0) throw new GitError(`git diff --cached failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** A file's content at a revision, or `null` when it does not exist there. */\nexport async function show(ref: string, path: string, cwd: string): Promise<string | null> {\n const run = await git(['show', `${ref}:${path}`], cwd);\n return run.code === 0 ? run.stdout : null;\n}\n\n/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */\nexport function isUncommitted(sha: string): boolean {\n return /^0+$/.test(sha);\n}\n\n/**\n * The commit that last changed one line of a file, as of a revision, or of\n * the working tree when `ref` is `null` - where a line nobody committed yet\n * belongs to the all-zero commit.\n */\nexport async function blameLine(ref: string | null, path: string, line: number, cwd: string): Promise<string | null> {\n const run = await git(['blame', '--porcelain', '-L', `${line},${line}`, ...(ref === null ? [] : [ref]), '--', path], cwd);\n // A blame that fails prints nothing on stdout, which names no commit. Its\n // output opens with the commit, so the anchor is equivalent to its mutant.\n const sha = /^([0-9a-f]{40,64}) /.exec(run.stdout)?.[1];\n return sha ?? null;\n}\n\n/** A good signature names its allowed signer's principal; any other names none. */\nexport type Signature =\n | { readonly good: true; readonly principal: string; readonly detail: string }\n | { readonly good: false; readonly principal: null; readonly detail: string };\n\n/**\n * Whether a commit carries a good SSH signature by a principal in the given\n * allowed-signers file. The file is passed in, never read from the commit's\n * own tree: a round cannot vouch for itself (ADR-0006).\n */\nexport async function verifyCommit(sha: string, allowedSignersFile: string, cwd: string): Promise<Signature> {\n const run = await git(\n ['-c', `gpg.ssh.allowedSignersFile=${allowedSignersFile}`, '-c', 'gpg.format=ssh', 'verify-commit', '--raw', sha],\n cwd,\n );\n const text = `${run.stderr}\\n${run.stdout}`;\n const principal = /Good \"git\" signature for (\\S+)/.exec(text)?.[1];\n // git writes the check to stderr, so the trim changes the first line only\n // for output on stdout alone, which verify-commit does not print; and split\n // always answers a first part.\n const detail = text.trim().split('\\n')[0]!;\n // git exits 0 only for a signature by a listed principal, which it names:\n // either condition alone is equivalent to both, and both trust neither.\n return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };\n}\n\nexport async function addWorktree(path: string, rev: string, cwd: string): Promise<void> {\n const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd, WORK_MS);\n if (run.code !== 0) throw new GitError(`git worktree add failed: ${run.stderr.trim()}`);\n}\n\n/**\n * Has git remove the worktree at `path`, and no other, with its record of it,\n * and answers whether git did. Git deletes what it can of the worktree's\n * directory and forgets the worktree even when it cannot delete it all. It\n * refuses a worktree whose directory has lost its `.git` file, and removes\n * its record of one whose directory is gone, found by the path it was added\n * at: `git worktree prune` would also forget every other worktree git has\n * lost track of, the person's among them.\n */\nexport async function removeWorktree(path: string, cwd: string): Promise<boolean> {\n // The sandbox asks again when this answers false, and git, asked again\n // about a worktree it has forgotten, changes nothing: the mutant that\n // always answers false is equivalent.\n return (await git(['worktree', 'remove', '--force', path], cwd, WORK_MS)).code === 0;\n}\n"]}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -40,21 +40,132 @@ export interface HookRequest {
|
|
|
40
40
|
readonly cwd: string | null;
|
|
41
41
|
/** Files the tool writes, as the tool named them - absolute, usually. */
|
|
42
42
|
readonly paths: readonly string[];
|
|
43
|
+
/** The session and the tool call the question is about, when it says: `session_id` and `tool_use_id`. */
|
|
44
|
+
readonly session: string | null;
|
|
45
|
+
readonly call: string | null;
|
|
43
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The event Claude Code runs as a session starts, resumes, is cleared or
|
|
49
|
+
* compacted, or is forked: no tool is named, and the hook checks what every
|
|
50
|
+
* write of the session will need (ADR-0005).
|
|
51
|
+
*/
|
|
52
|
+
export declare const SESSION_START = "SessionStart";
|
|
53
|
+
/**
|
|
54
|
+
* The event Claude Code runs as its main agent finishes a turn, under which
|
|
55
|
+
* the hook reports what the work tree holds, where the configuration asks
|
|
56
|
+
* for it (ADR-0005). No tool is named.
|
|
57
|
+
*/
|
|
58
|
+
export declare const TURN_END = "Stop";
|
|
44
59
|
/** Reads a Claude Code hook's stdin, or says why it cannot. */
|
|
45
60
|
export declare function parseClaudeHook(raw: string): HookRequest | {
|
|
46
61
|
readonly error: string;
|
|
47
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* The answer as a session starts: what would hold every write of it, in
|
|
65
|
+
* one line, for the person as `systemMessage` and for Claude as
|
|
66
|
+
* `additionalContext`; or nothing, where all is well. Always exit 0: the
|
|
67
|
+
* event cannot block, and a check that fails must not keep a session from
|
|
68
|
+
* starting.
|
|
69
|
+
*/
|
|
70
|
+
export declare function sessionResponse(problem: string | null): HookResponse;
|
|
71
|
+
/**
|
|
72
|
+
* The answer as a turn ends: the report, in one `systemMessage`, or
|
|
73
|
+
* nothing, where there is nothing to report. Always exit 0, and never a
|
|
74
|
+
* field that has Claude go on: `decision`, `additionalContext` and exit 2
|
|
75
|
+
* each continue the turn under Stop, and `continue: false` would stop one
|
|
76
|
+
* another hook continues. Claude Code shows a `systemMessage` to the person
|
|
77
|
+
* from an entry it waits on, and hands it to Claude on the next turn from
|
|
78
|
+
* one it runs in the background, as `init` writes it (ADR-0005, ADR-0012).
|
|
79
|
+
*/
|
|
80
|
+
export declare function turnResponse(report: string | null): HookResponse;
|
|
48
81
|
export interface HookResponse {
|
|
49
82
|
/** What to print on stdout: JSON, or nothing. */
|
|
50
83
|
readonly stdout: string;
|
|
51
84
|
readonly exitCode: number;
|
|
52
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The answer before the one write a configuration that does not load lets
|
|
88
|
+
* through, a write to the configuration file itself: no decision, so that
|
|
89
|
+
* the person's own permission prompt stands as for any write the guard
|
|
90
|
+
* passes, and the line that says why every other write is held, for the
|
|
91
|
+
* person as `systemMessage` and for Claude as `additionalContext`, which
|
|
92
|
+
* Claude Code's hooks reference has a PreToolUse hook's answer carry
|
|
93
|
+
* (ADR-0005). Exit 0.
|
|
94
|
+
*/
|
|
95
|
+
export declare function mendResponse(said: string): HookResponse;
|
|
96
|
+
/** The answer that says nothing and decides nothing: the write goes on as the person's own permissions have it. */
|
|
97
|
+
export declare const NONE: HookResponse;
|
|
98
|
+
/**
|
|
99
|
+
* The answer where the hook checks nothing and says so, a question it cannot
|
|
100
|
+
* read or a line of its own that turns the guard off: one line, as
|
|
101
|
+
* `systemMessage`, which Claude Code's hooks reference has shown to the
|
|
102
|
+
* person on every event the hook answers, and exit 0. It decides nothing,
|
|
103
|
+
* so the call goes on as it would without the hook. It was exit 1, which
|
|
104
|
+
* the reference counts as a hook that failed: under `onFailure: "block"`,
|
|
105
|
+
* which holds the call of a hook that cannot start, it held every write of
|
|
106
|
+
* a session for a line only a write can mend (ADR-0005).
|
|
107
|
+
*/
|
|
108
|
+
export declare function warningResponse(said: string): HookResponse;
|
|
109
|
+
/** What a warning after a write says: a decision's message and its next step. */
|
|
110
|
+
export type Warning = Pick<Decision, 'message' | 'hint'>;
|
|
111
|
+
/** The answer after a write: the warnings, as additional context for the model, or nothing. */
|
|
112
|
+
export declare function afterResponse(warnings: readonly Warning[]): HookResponse;
|
|
53
113
|
/**
|
|
54
114
|
* The answer to a Claude Code hook: a refusal or a question before a write,
|
|
55
115
|
* a warning after one, or nothing.
|
|
56
116
|
*/
|
|
57
117
|
export declare function claudeResponse(event: string, decisions: readonly Decision[]): HookResponse;
|
|
118
|
+
/** The decisions a run after the write would say: those that warn, each by its message and next step. */
|
|
119
|
+
export declare function warningsOf(decisions: readonly Decision[]): Warning[];
|
|
120
|
+
/**
|
|
121
|
+
* What the hook reads besides its question and its line, as it decides: the
|
|
122
|
+
* project Claude Code names, the brief the environment names, and the
|
|
123
|
+
* branch a forge's CI names on a detached head (ADR-0004).
|
|
124
|
+
*/
|
|
125
|
+
export declare const CALL_VARIABLES: readonly string[];
|
|
126
|
+
/**
|
|
127
|
+
* A tool call, as both of its hooks see it: the release, the session and
|
|
128
|
+
* the call, the tool and its paths, the session's directory, what the
|
|
129
|
+
* hook's line gives, the directory the hook was started in, and the
|
|
130
|
+
* variables it reads (ADR-0002). What a run before the write keeps is
|
|
131
|
+
* answered after it only to a run for which all of it is the same.
|
|
132
|
+
*/
|
|
133
|
+
export interface CallRecord {
|
|
134
|
+
readonly release: string;
|
|
135
|
+
readonly session: string;
|
|
136
|
+
readonly call: string;
|
|
137
|
+
readonly tool: string;
|
|
138
|
+
readonly paths: readonly string[];
|
|
139
|
+
readonly cwd: string | null;
|
|
140
|
+
readonly line: readonly (string | null)[];
|
|
141
|
+
readonly started: string;
|
|
142
|
+
readonly env: readonly (string | null)[];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The session a question is in, which spec-brief's answers are kept for,
|
|
146
|
+
* or `null` where it names none in the form Claude Code's ids have: such a
|
|
147
|
+
* question has every answer asked for afresh (ADR-0002).
|
|
148
|
+
*/
|
|
149
|
+
export declare function sessionOf(request: HookRequest): string | null;
|
|
150
|
+
/**
|
|
151
|
+
* The call a question is about, or `null` where it does not say which
|
|
152
|
+
* session and call it is, or says it in a form no file is named by.
|
|
153
|
+
*/
|
|
154
|
+
export declare function callRecord(request: HookRequest, line: Readonly<{
|
|
155
|
+
root: string | undefined;
|
|
156
|
+
brief: string | undefined;
|
|
157
|
+
base: string | undefined;
|
|
158
|
+
}>, started: string, env: Readonly<Record<string, string | undefined>>, release: string): CallRecord | null;
|
|
159
|
+
/** The name of the file a call's decisions are kept under. */
|
|
160
|
+
export declare function callFile(record: CallRecord): string;
|
|
161
|
+
/** What a run before the write keeps for the run after it: the call, and the warnings it decided. */
|
|
162
|
+
export declare function keptCall(record: CallRecord, warnings: readonly Warning[]): string;
|
|
163
|
+
/**
|
|
164
|
+
* The warnings kept for this call, or `null` where what was kept is not
|
|
165
|
+
* JSON, is another call's, or holds anything but warnings: such a call is
|
|
166
|
+
* decided as it was before anything was kept (ADR-0002).
|
|
167
|
+
*/
|
|
168
|
+
export declare function warningsKept(text: string, record: CallRecord): Warning[] | null;
|
|
58
169
|
/**
|
|
59
170
|
* The answer to git's pre-commit hook: every file the commit would change
|
|
60
171
|
* that the round may not, and exit 1 if any is refused. A warning prints and
|