@vibe-commit/capture 0.1.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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/agents/claude_code.d.ts +11 -0
- package/dist/agents/claude_code.js +162 -0
- package/dist/agents/claude_code.js.map +1 -0
- package/dist/agents/codex_cli.d.ts +15 -0
- package/dist/agents/codex_cli.js +67 -0
- package/dist/agents/codex_cli.js.map +1 -0
- package/dist/agents/cursor.d.ts +21 -0
- package/dist/agents/cursor.js +89 -0
- package/dist/agents/cursor.js.map +1 -0
- package/dist/agents/registry.d.ts +184 -0
- package/dist/agents/registry.js +212 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/types.d.ts +279 -0
- package/dist/agents/types.js +67 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/commands/connect.d.ts +36 -0
- package/dist/commands/connect.js +499 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/context.d.ts +45 -0
- package/dist/commands/context.js +14 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/report.d.ts +105 -0
- package/dist/commands/report.js +430 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/status.d.ts +29 -0
- package/dist/commands/status.js +236 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/why.d.ts +349 -0
- package/dist/commands/why.js +937 -0
- package/dist/commands/why.js.map +1 -0
- package/dist/consent.d.ts +59 -0
- package/dist/consent.js +111 -0
- package/dist/consent.js.map +1 -0
- package/dist/copy/claims.d.ts +79 -0
- package/dist/copy/claims.js +343 -0
- package/dist/copy/claims.js.map +1 -0
- package/dist/copy/grades.d.ts +117 -0
- package/dist/copy/grades.js +96 -0
- package/dist/copy/grades.js.map +1 -0
- package/dist/copy/index.d.ts +12 -0
- package/dist/copy/index.js +13 -0
- package/dist/copy/index.js.map +1 -0
- package/dist/copy/strings.d.ts +1328 -0
- package/dist/copy/strings.js +1243 -0
- package/dist/copy/strings.js.map +1 -0
- package/dist/copy/time.d.ts +27 -0
- package/dist/copy/time.js +43 -0
- package/dist/copy/time.js.map +1 -0
- package/dist/credential.d.ts +211 -0
- package/dist/credential.js +298 -0
- package/dist/credential.js.map +1 -0
- package/dist/exit.d.ts +59 -0
- package/dist/exit.js +60 -0
- package/dist/exit.js.map +1 -0
- package/dist/git.d.ts +299 -0
- package/dist/git.js +653 -0
- package/dist/git.js.map +1 -0
- package/dist/hooks/entry.d.ts +277 -0
- package/dist/hooks/entry.js +1092 -0
- package/dist/hooks/entry.js.map +1 -0
- package/dist/hooks/post_commit.d.ts +59 -0
- package/dist/hooks/post_commit.js +160 -0
- package/dist/hooks/post_commit.js.map +1 -0
- package/dist/hooks/post_rewrite.d.ts +59 -0
- package/dist/hooks/post_rewrite.js +161 -0
- package/dist/hooks/post_rewrite.js.map +1 -0
- package/dist/hooks/session_end.d.ts +101 -0
- package/dist/hooks/session_end.js +120 -0
- package/dist/hooks/session_end.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +153 -0
- package/dist/install.js +284 -0
- package/dist/install.js.map +1 -0
- package/dist/json.d.ts +100 -0
- package/dist/json.js +94 -0
- package/dist/json.js.map +1 -0
- package/dist/oauth/discovery.d.ts +96 -0
- package/dist/oauth/discovery.js +163 -0
- package/dist/oauth/discovery.js.map +1 -0
- package/dist/oauth/lock.d.ts +100 -0
- package/dist/oauth/lock.js +287 -0
- package/dist/oauth/lock.js.map +1 -0
- package/dist/oauth/loopback.d.ts +85 -0
- package/dist/oauth/loopback.js +124 -0
- package/dist/oauth/loopback.js.map +1 -0
- package/dist/oauth/pkce.d.ts +58 -0
- package/dist/oauth/pkce.js +77 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/read.d.ts +120 -0
- package/dist/oauth/read.js +144 -0
- package/dist/oauth/read.js.map +1 -0
- package/dist/oauth/session.d.ts +80 -0
- package/dist/oauth/session.js +112 -0
- package/dist/oauth/session.js.map +1 -0
- package/dist/oauth/signin.d.ts +178 -0
- package/dist/oauth/signin.js +225 -0
- package/dist/oauth/signin.js.map +1 -0
- package/dist/oauth/token.d.ts +139 -0
- package/dist/oauth/token.js +183 -0
- package/dist/oauth/token.js.map +1 -0
- package/dist/paths.d.ts +366 -0
- package/dist/paths.js +460 -0
- package/dist/paths.js.map +1 -0
- package/dist/policy.d.ts +195 -0
- package/dist/policy.js +244 -0
- package/dist/policy.js.map +1 -0
- package/dist/post.d.ts +302 -0
- package/dist/post.js +384 -0
- package/dist/post.js.map +1 -0
- package/dist/project.d.ts +99 -0
- package/dist/project.js +192 -0
- package/dist/project.js.map +1 -0
- package/dist/redact.d.ts +175 -0
- package/dist/redact.js +700 -0
- package/dist/redact.js.map +1 -0
- package/dist/runtime.d.ts +30 -0
- package/dist/runtime.js +37 -0
- package/dist/runtime.js.map +1 -0
- package/dist/spawn_budget.d.ts +101 -0
- package/dist/spawn_budget.js +124 -0
- package/dist/spawn_budget.js.map +1 -0
- package/dist/spool.d.ts +390 -0
- package/dist/spool.js +556 -0
- package/dist/spool.js.map +1 -0
- package/dist/state.d.ts +109 -0
- package/dist/state.js +217 -0
- package/dist/state.js.map +1 -0
- package/dist/system_message.d.ts +101 -0
- package/dist/system_message.js +141 -0
- package/dist/system_message.js.map +1 -0
- package/dist/term.d.ts +112 -0
- package/dist/term.js +134 -0
- package/dist/term.js.map +1 -0
- package/dist/version.d.ts +16 -0
- package/dist/version.js +17 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
package/dist/spool.js
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The commit spool — where `post-commit` leaves what it OBSERVED, for the next
|
|
3
|
+
* Claude Code hook to deliver (`CR-170`, D154).
|
|
4
|
+
*
|
|
5
|
+
* ## Why a spool at all, rather than sending from `post-commit`
|
|
6
|
+
*
|
|
7
|
+
* `post-commit` runs inside `git commit`. A network call there would put our
|
|
8
|
+
* latency on the user's own commit, and a failure would surface as noise in a
|
|
9
|
+
* command that has nothing to do with us. So the commit hook does the one thing
|
|
10
|
+
* only it can do — witness the commit — and writes a line. Delivery stays on the
|
|
11
|
+
* hook path that already has a credential, a budget and a retry story.
|
|
12
|
+
*
|
|
13
|
+
* ## ⛔ APPEND, THEN TRUNCATE ON 2xx — NEVER READ-AND-DELETE
|
|
14
|
+
*
|
|
15
|
+
* A send that fails must leave the commit in the spool so the NEXT hook retries
|
|
16
|
+
* it. Deleting on read would lose a commit to a single 500, permanently, and
|
|
17
|
+
* `capture_commits` rows cannot be retracted (D105/D108). So the truncation is
|
|
18
|
+
* the LAST step and it is conditional on the server's 2xx.
|
|
19
|
+
*
|
|
20
|
+
* ## ⛔ THE SESSION GATE IS ABOUT WHICH SESSION, NOT ABOUT WHO TYPED IT
|
|
21
|
+
*
|
|
22
|
+
* ⚠ **This block used to say the gate is what keeps a HUMAN'S commits out. That
|
|
23
|
+
* has been FALSE since `CR-170` shipped** — `activeSessionFor` has never
|
|
24
|
+
* inspected authorship, and `post_commit.ts` says three lines below its own copy
|
|
25
|
+
* of the claim that the hook *"fires for every commit in this work tree,
|
|
26
|
+
* including ones no agent was involved in."* Corrected as a comment fix; no
|
|
27
|
+
* behaviour moved with it (`D5`).
|
|
28
|
+
*
|
|
29
|
+
* What the gate actually asks is whether a capture session is demonstrably live
|
|
30
|
+
* for this repo, and — since the ladder below — **WHICH ONE**. A line is written
|
|
31
|
+
* only when that question has an answer, and the answer carries the rung that
|
|
32
|
+
* produced it so the server can tell an observation from a guess.
|
|
33
|
+
*
|
|
34
|
+
* ## The line is deliberately small, and the file list stays here
|
|
35
|
+
*
|
|
36
|
+
* `{sha, branch, at, files}`. Only the SHA reaches the wire in wave 1 (`CR-170`
|
|
37
|
+
* §4): commit messages were MEASURED at 9/20, 8/20 and 5/20 commits over 4096
|
|
38
|
+
* bytes across the three repos, largest 12,112, so a quarter of real commits
|
|
39
|
+
* would land permanently truncated in a header. Metadata is backfillable from
|
|
40
|
+
* the sha; the observation is not. `files` is carried for wave 2 and never put
|
|
41
|
+
* in a header.
|
|
42
|
+
*/
|
|
43
|
+
import { appendFileSync, chmodSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
44
|
+
import { dirname, join } from "node:path";
|
|
45
|
+
import { repoSessionsDir, sessionStatePath } from "./paths.js";
|
|
46
|
+
/**
|
|
47
|
+
* How many SHAs one hook may put on the wire.
|
|
48
|
+
*
|
|
49
|
+
* A header has a practical size bound and the server pins its own; 40 hex plus a
|
|
50
|
+
* separator is 41 bytes, so 32 is ~1.3 KB and comfortably inside any of them.
|
|
51
|
+
* ⛔ **The rest stay spooled** rather than being dropped — a burst of commits is
|
|
52
|
+
* delivered across several hooks, and hooks are frequent.
|
|
53
|
+
*/
|
|
54
|
+
export const MAX_SPOOLED_SHAS = 32;
|
|
55
|
+
/**
|
|
56
|
+
* A session is "live" for this repo if its state file was touched inside this
|
|
57
|
+
* window.
|
|
58
|
+
*
|
|
59
|
+
* ⚠ **A JUDGEMENT CALL, AND IT IS THE ONE KNOB IN THIS MODULE.** The Claude Code
|
|
60
|
+
* hook writes session state on every fire, so during active work this file is
|
|
61
|
+
* touched every turn. Too short and a long turn's commit is missed; too long and
|
|
62
|
+
* a human's commit hours later is attributed to a session that is morally over.
|
|
63
|
+
*
|
|
64
|
+
* The asymmetry decides it, the same way it did in `spawn_budget.ts`: a MISSED
|
|
65
|
+
* commit is recoverable — the sha is still in git and wave 2 can backfill it —
|
|
66
|
+
* while a WRONGLY ATTRIBUTED commit is a permanent row linking work to a session
|
|
67
|
+
* that did not do it. So this is sized to the length of a plausible single turn,
|
|
68
|
+
* not to the length of a working day.
|
|
69
|
+
*/
|
|
70
|
+
export const SESSION_LIVE_WINDOW_MS = 30 * 60 * 1000;
|
|
71
|
+
/**
|
|
72
|
+
* `<repo>/<session>.spool.jsonl`, beside the session's state file.
|
|
73
|
+
*
|
|
74
|
+
* ⚠ **`.jsonl`, NOT `.json`, and that is load-bearing.** `lastSendForRepo`
|
|
75
|
+
* scans this same directory and filters on `entry.endsWith(".json")`; a spool
|
|
76
|
+
* named `.json` would be parsed as session state, fail, and silently degrade
|
|
77
|
+
* every `vibecommit status` for the repo. Checked rather than assumed — there is
|
|
78
|
+
* a test for exactly that.
|
|
79
|
+
*
|
|
80
|
+
* Null on no repo identity, for the reason `sessionStatePath` is: there is no
|
|
81
|
+
* repo-less bucket and a placeholder key is a cross-repo bleed with an extra step.
|
|
82
|
+
*/
|
|
83
|
+
export function spoolPath(home, key) {
|
|
84
|
+
const state = sessionStatePath(home, key);
|
|
85
|
+
if (state === null)
|
|
86
|
+
return null;
|
|
87
|
+
return state.replace(/\.json$/, ".spool.jsonl");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Append one observed commit. Returns false on any failure.
|
|
91
|
+
*
|
|
92
|
+
* ⛔ **NEVER THROWS.** This runs inside `git commit`: an exception here would
|
|
93
|
+
* surface as a failed hook on the user's own commit, which is exactly the noise
|
|
94
|
+
* `post-commit` must not create. A dropped line is strictly better.
|
|
95
|
+
*
|
|
96
|
+
* `appendFileSync` with `a` is a single positioned write, so two commits racing
|
|
97
|
+
* in one repo interleave whole lines rather than corrupting each other.
|
|
98
|
+
*/
|
|
99
|
+
export function appendSpool(home, key, entry) {
|
|
100
|
+
const path = spoolPath(home, key);
|
|
101
|
+
if (path === null)
|
|
102
|
+
return false;
|
|
103
|
+
try {
|
|
104
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
105
|
+
appendFileSync(path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
|
|
106
|
+
chmodSync(path, 0o600);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Every well-formed entry in the spool, oldest first.
|
|
115
|
+
*
|
|
116
|
+
* A malformed line is SKIPPED rather than fatal: the file is appended to by a
|
|
117
|
+
* different process than reads it, so a torn final line is a real possibility
|
|
118
|
+
* and losing one observation beats losing the whole spool.
|
|
119
|
+
*/
|
|
120
|
+
export function readSpool(home, key) {
|
|
121
|
+
const path = spoolPath(home, key);
|
|
122
|
+
if (path === null)
|
|
123
|
+
return [];
|
|
124
|
+
let raw;
|
|
125
|
+
try {
|
|
126
|
+
raw = readFileSync(path, "utf8");
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const line of raw.split("\n")) {
|
|
133
|
+
if (line.trim() === "")
|
|
134
|
+
continue;
|
|
135
|
+
const entry = parseEntry(line);
|
|
136
|
+
if (entry !== null)
|
|
137
|
+
out.push(entry);
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Drop the first `count` entries — called ONLY after the server's 2xx.
|
|
143
|
+
*
|
|
144
|
+
* ⚠ **RE-READS BEFORE REWRITING, and that is not defensive tidiness.**
|
|
145
|
+
* `post-commit` may have appended while the send was in flight. Slicing the
|
|
146
|
+
* array we read BEFORE the send would rewrite the file without those lines and
|
|
147
|
+
* lose them silently. Appends only ever go to the END, so the first `count`
|
|
148
|
+
* lines are still the same ones we delivered, and dropping exactly that many is
|
|
149
|
+
* correct against the current file rather than against a stale snapshot.
|
|
150
|
+
*
|
|
151
|
+
* ⚠ **A residual race remains and is accepted:** a commit landing between this
|
|
152
|
+
* re-read and the write is lost. The window is one `readFileSync` plus one
|
|
153
|
+
* `writeFileSync` on a small file, the cost is one un-delivered commit row, and
|
|
154
|
+
* closing it properly means a lock file on a path that must never block
|
|
155
|
+
* (`post-commit` runs inside the user's `git commit`). Recorded rather than
|
|
156
|
+
* hidden.
|
|
157
|
+
*/
|
|
158
|
+
export function dropSpooled(home, key, count) {
|
|
159
|
+
const path = spoolPath(home, key);
|
|
160
|
+
if (path === null || count <= 0)
|
|
161
|
+
return false;
|
|
162
|
+
try {
|
|
163
|
+
const lines = readFileSync(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
164
|
+
writeFileSync(path, lines.slice(count).map((l) => `${l}\n`).join(""), { mode: 0o600 });
|
|
165
|
+
chmodSync(path, 0o600);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Which session, if any, is live for this repo right now — ⛔ **AND HOW WE KNOW.**
|
|
174
|
+
*
|
|
175
|
+
* ## The defect this replaces
|
|
176
|
+
*
|
|
177
|
+
* Until the ladder, this function returned *the most recently mtime'd state file
|
|
178
|
+
* within 30 minutes* and said nothing about its own confidence. Two agents in one
|
|
179
|
+
* clone: session A commits, B's state file is newer, **A's commit enters B's
|
|
180
|
+
* spool** — and `cr071:157` makes the resulting edge PERMANENT, with UPDATE and
|
|
181
|
+
* DELETE both raising for `service_role` too. A wrong edge is forever, so the
|
|
182
|
+
* guess had to stop being indistinguishable from the answer.
|
|
183
|
+
*
|
|
184
|
+
* ## The ladder
|
|
185
|
+
*
|
|
186
|
+
* ```
|
|
187
|
+
* env id names a live session ─────────> env_session_id WRITE
|
|
188
|
+
* no live session at all ──────────────> null REFUSE (no row)
|
|
189
|
+
* env id names NONE of them ───────────> env_session_unmatched REFUSE (named)
|
|
190
|
+
* exactly ONE candidate ───────────────> sole_live_session WRITE (D5)
|
|
191
|
+
* TWO OR MORE candidates ──────────────> recency_heuristic HOLD
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* ⛔ **`sole_live_session` ALWAYS WRITES, and that is not a change.** It never
|
|
195
|
+
* inspected authorship and does not start now: a human's `git commit` in a clone
|
|
196
|
+
* with one live session produces a spool line today, at HEAD, and
|
|
197
|
+
* `test/post-commit-spool.test.ts` has a green arm asserting exactly that. What
|
|
198
|
+
* is new is that the server will write a PERMANENT row off this rung, so the rung
|
|
199
|
+
* has to be named on the wire (`D190`).
|
|
200
|
+
*
|
|
201
|
+
* ## ⛔ `env_session_unmatched` — THE THIRD ARM, AND IT IS A REFUSAL WITH A NAME
|
|
202
|
+
*
|
|
203
|
+
* An env id that names **none** of the live candidates is a shape the plan's
|
|
204
|
+
* ladder does not draw, and the code must answer it. It is REAL rather than
|
|
205
|
+
* hypothetical: `agents/registry.ts:11-13` rejects an environment variable as the
|
|
206
|
+
* agent selector precisely because **Codex exports `CLAUDE_PLUGIN_ROOT` itself**,
|
|
207
|
+
* so a Codex agent running under an outer Claude session carries a
|
|
208
|
+
* `CLAUDE_CODE_SESSION_ID` that corroborates nothing here.
|
|
209
|
+
*
|
|
210
|
+
* - ⛔ `sole_live_session` there would name a session we have POSITIVE evidence
|
|
211
|
+
* did not commit — the permanent wrong edge, arrived at from the other
|
|
212
|
+
* direction. Never.
|
|
213
|
+
* - ⛔ `recency_heuristic` is what this ladder used first, and it is wrong for
|
|
214
|
+
* the reason `AttributionRung` records: cardinality one, refused **23514**,
|
|
215
|
+
* and a doubt we do not actually have.
|
|
216
|
+
* - ⛔ **A silent refusal is worse than either in one respect** — nobody can
|
|
217
|
+
* afterwards ask *why did that commit vanish*. That is the failure class this
|
|
218
|
+
* whole wave exists to close, so the refusal is RECORDED and NAMED.
|
|
219
|
+
*
|
|
220
|
+
* ⚠ **The line goes in the recency winner's spool bucket, because that is the
|
|
221
|
+
* only bucket that exists** — an uncorroborated env id deliberately does not open
|
|
222
|
+
* one of its own. The bucket is a FILE LOCATION and the rung is the attribution:
|
|
223
|
+
* the line says, in terms, *this commit is attributed to nobody, and here is
|
|
224
|
+
* why*. It never reaches `x-commits` and never becomes a `commit_attributions`
|
|
225
|
+
* row.
|
|
226
|
+
*
|
|
227
|
+
* ⚠ **So `recency_heuristic` is reachable ONLY with two or more candidates**,
|
|
228
|
+
* which is what `commit_attributions_ambiguity_is_plural` demands. That is a
|
|
229
|
+
* property of the ladder's SHAPE now rather than of a caller's care, and
|
|
230
|
+
* `test/post-commit-spool.test.ts` asserts it directly.
|
|
231
|
+
*
|
|
232
|
+
* ⚠ **A commit made before a session's FIRST hook has fired is still NOT
|
|
233
|
+
* captured**, because the state file it would key on does not exist yet. The env
|
|
234
|
+
* id could key a bucket that has no state file, and deliberately does not: the
|
|
235
|
+
* ladder's first rung requires CORROBORATION, so a stale or inherited variable
|
|
236
|
+
* cannot open a spool of its own.
|
|
237
|
+
*
|
|
238
|
+
* `envSessionId` is a PARAMETER rather than a read of `process.env` here, and it
|
|
239
|
+
* has no default: the compiler is the only thing that will notice a caller which
|
|
240
|
+
* forgot the env path, and this seam has no other check.
|
|
241
|
+
*/
|
|
242
|
+
export function activeSessionFor(home, repoKey, envSessionId, nowMs = Date.now(), windowMs = SESSION_LIVE_WINDOW_MS) {
|
|
243
|
+
const live = liveSessions(home, repoKey, nowMs, windowMs);
|
|
244
|
+
// Rung 1 — the committing process told us, and a state file agrees.
|
|
245
|
+
if (envSessionId !== null && live.some((s) => s.sessionId === envSessionId)) {
|
|
246
|
+
return { sessionId: envSessionId, attribution: "env_session_id" };
|
|
247
|
+
}
|
|
248
|
+
// No candidate at all: nothing has captured this repo inside the window, so
|
|
249
|
+
// there is no bucket to record anything in. The refusal has to be silent here
|
|
250
|
+
// and only here.
|
|
251
|
+
if (live.length === 0)
|
|
252
|
+
return null;
|
|
253
|
+
// ⛔ THE NAMED REFUSAL, AND IT COMES BEFORE BOTH WRITE RUNGS. We were TOLD who
|
|
254
|
+
// committed, and it is none of these. Falling through would attribute the
|
|
255
|
+
// commit to a session we have positive evidence did not make it.
|
|
256
|
+
if (envSessionId !== null) {
|
|
257
|
+
return { sessionId: live[0].sessionId, attribution: "env_session_unmatched" };
|
|
258
|
+
}
|
|
259
|
+
// Rung 2 — D5. One session, nothing contradicting it.
|
|
260
|
+
if (live.length === 1) {
|
|
261
|
+
return { sessionId: live[0].sessionId, attribution: "sole_live_session" };
|
|
262
|
+
}
|
|
263
|
+
// ⛔ Rung 3 — the guess, and TWO OR MORE candidates is now structurally
|
|
264
|
+
// guaranteed here: every path with an env id returned above, and one candidate
|
|
265
|
+
// returned on the line before. `commit_attributions_ambiguity_is_plural`
|
|
266
|
+
// requires exactly that, and a cardinality-one row is refused 23514.
|
|
267
|
+
return { sessionId: live[0].sessionId, attribution: "recency_heuristic" };
|
|
268
|
+
}
|
|
269
|
+
/** Every session state file touched inside the window, most recent first. */
|
|
270
|
+
function liveSessions(home, repoKey, nowMs, windowMs) {
|
|
271
|
+
const dir = repoSessionsDir(home, repoKey);
|
|
272
|
+
if (dir === null)
|
|
273
|
+
return [];
|
|
274
|
+
let entries;
|
|
275
|
+
try {
|
|
276
|
+
entries = readdirSync(dir);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// No directory at all: nothing has ever captured this repo.
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
const live = [];
|
|
283
|
+
for (const entry of entries) {
|
|
284
|
+
// ⚠ State files only. `.spool.jsonl` and `.rewrites.jsonl` live here too and
|
|
285
|
+
// are not sessions — and neither ends with `.json`, which is the same
|
|
286
|
+
// load-bearing spelling `spoolPath` documents.
|
|
287
|
+
if (!entry.endsWith(".json"))
|
|
288
|
+
continue;
|
|
289
|
+
let touchedAt;
|
|
290
|
+
try {
|
|
291
|
+
touchedAt = statSync(join(dir, entry)).mtimeMs;
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (nowMs - touchedAt > windowMs)
|
|
297
|
+
continue;
|
|
298
|
+
live.push({ sessionId: entry.slice(0, -".json".length), at: touchedAt });
|
|
299
|
+
}
|
|
300
|
+
// ⚠ Ties broken by name so the guess is at least DETERMINISTIC. Two state files
|
|
301
|
+
// written in the same millisecond is ordinary on a coarse-grained filesystem,
|
|
302
|
+
// and a rung that picked differently on each read would be unreproducible as
|
|
303
|
+
// well as wrong.
|
|
304
|
+
live.sort((a, b) => (b.at - a.at) || a.sessionId.localeCompare(b.sessionId));
|
|
305
|
+
return live;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* ⛔ **THE TWO RUNGS THAT MAY GO ON `x-commit-attributions` (`D190`).**
|
|
309
|
+
*
|
|
310
|
+
* The server's vocabulary is exactly these two (`edge_derivation.ts:178`) and a
|
|
311
|
+
* rung outside it is **skipped silently** — no edge, no error, no log. So the
|
|
312
|
+
* refusal lives here, on the producing side, rather than being discovered as a
|
|
313
|
+
* zero where edges should have been.
|
|
314
|
+
*
|
|
315
|
+
* ⚠ **This constant is NOT what the wire cell asserts against.** There is no
|
|
316
|
+
* shared type across that seam and inventing one would couple an MIT client to a
|
|
317
|
+
* closed server, so the cell types the literals itself — round-tripping this
|
|
318
|
+
* constant through our own code would only prove it equals itself (`D190 §5`).
|
|
319
|
+
*/
|
|
320
|
+
export const WIRE_RUNGS = ["env_session_id", "sole_live_session"];
|
|
321
|
+
/**
|
|
322
|
+
* The SHAs this hook will carry, their rungs, and how many lines to drop after a
|
|
323
|
+
* 2xx.
|
|
324
|
+
*
|
|
325
|
+
* ⛔ **FULL WIDTH, ALWAYS.** `capture_commits.commit_sha` is 7..40 because the
|
|
326
|
+
* server's `expandShortSha` can fail, so an abbreviated sha makes the web join
|
|
327
|
+
* SILENTLY EMPTY — a wrong answer with no error, D98's class exactly. Anything
|
|
328
|
+
* that is not a full sha is dropped by `parseEntry` rather than sent.
|
|
329
|
+
*
|
|
330
|
+
* ⛔ **`shas` AND `attributions` ARE THE SAME LENGTH BY CONSTRUCTION** — one
|
|
331
|
+
* `push` each, in one iteration, or neither. That is not style.
|
|
332
|
+
* `parseObservedCommits` returns `[]` when the two lists differ in length: **the
|
|
333
|
+
* ENTIRE BATCH is dropped, not the odd entry**, with no edge, no error and no
|
|
334
|
+
* log (`D190 §1`). A `filter` and a `map` over the same array would be the same
|
|
335
|
+
* thing until someone changed one of them.
|
|
336
|
+
*
|
|
337
|
+
* ⛔ **`count` IS LINES CONSUMED, NOT `shas.length`, AND THEY DIVERGE.** A held
|
|
338
|
+
* entry is consumed and never sent. `dropSpooled` drops **the first N lines**, so
|
|
339
|
+
* a caller truncating by `shas.length` would delete a held line at the head and
|
|
340
|
+
* re-send the commit that was actually delivered — forever.
|
|
341
|
+
*
|
|
342
|
+
* ⚠ **A held entry IS dropped on the 2xx, and the observation is lost.** Stated
|
|
343
|
+
* rather than hidden: this wave gives the client no way to transmit a held
|
|
344
|
+
* commit, and leaving it in the file would make every subsequent hook re-read a
|
|
345
|
+
* line that can never be sent. The asymmetry this module already runs on decides
|
|
346
|
+
* it — a MISSED commit is recoverable, the sha is still in git, while a WRONGLY
|
|
347
|
+
* ATTRIBUTED one is a permanent row (`cr071:157`).
|
|
348
|
+
*/
|
|
349
|
+
export function capSpool(entries) {
|
|
350
|
+
const taken = entries.slice(0, MAX_SPOOLED_SHAS);
|
|
351
|
+
const shas = [];
|
|
352
|
+
const attributions = [];
|
|
353
|
+
for (const entry of taken) {
|
|
354
|
+
if (!WIRE_RUNGS.includes(entry.attribution))
|
|
355
|
+
continue;
|
|
356
|
+
shas.push(entry.sha);
|
|
357
|
+
attributions.push(entry.attribution);
|
|
358
|
+
}
|
|
359
|
+
return { shas, attributions, count: taken.length };
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* How many rewrite pairs one hook may put on the wire.
|
|
363
|
+
*
|
|
364
|
+
* ⛔ **ITS OWN CONSTANT, AND SHARING `MAX_SPOOLED_SHAS` WOULD MAKE ONE OF THE TWO
|
|
365
|
+
* WRONG.** The units differ: a sha plus a separator is 41 bytes, a pair plus a
|
|
366
|
+
* separator is **82** (40 + `:` + 40 + `,`). 16 pairs is 1,312 bytes — the same
|
|
367
|
+
* header budget `MAX_SPOOLED_SHAS = 32` was sized against, arrived at through the
|
|
368
|
+
* arithmetic of this header rather than inherited from the other one.
|
|
369
|
+
*
|
|
370
|
+
* ⛔ **The rest stay spooled**, exactly as the commit spool's remainder does.
|
|
371
|
+
*/
|
|
372
|
+
export const MAX_SPOOLED_PAIRS = 16;
|
|
373
|
+
/** `<repo>/<session>.rewrites.jsonl`, beside the commit spool. */
|
|
374
|
+
export function rewriteSpoolPath(home, key) {
|
|
375
|
+
const state = sessionStatePath(home, key);
|
|
376
|
+
if (state === null)
|
|
377
|
+
return null;
|
|
378
|
+
return state.replace(/\.json$/, ".rewrites.jsonl");
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Append rewrite pairs, skipping any already spooled. Returns how many were
|
|
382
|
+
* WRITTEN — ⛔ not how many were offered.
|
|
383
|
+
*
|
|
384
|
+
* ## ⛔ THE DEDUP IS ONE OF T4'S TWO GUARDS, AND IT IS NOT REDUNDANCY
|
|
385
|
+
*
|
|
386
|
+
* `M6` measured the squash hazard and it is **SIZE-DEPENDENT**. A 3→1 squash
|
|
387
|
+
* fires `post-rewrite` twice — `amend A→F`, then `rebase A→F; B→F` — so **the
|
|
388
|
+
* pair `A→F` arrives twice**. A 4→1 squash instead produces rows naming an
|
|
389
|
+
* INTERMEDIATE sha that never existed on any branch; that one is killed by the
|
|
390
|
+
* in-progress-rebase suppression in `hooks/post_rewrite.ts`, which cannot see the
|
|
391
|
+
* duplicate, exactly as this cannot see the intermediate. **One squash size
|
|
392
|
+
* cannot test both.**
|
|
393
|
+
*
|
|
394
|
+
* ⚠ **Dedup is against the FILE, not against the batch**, because the two fires
|
|
395
|
+
* are two processes: a batch-local check would see one pair each time and dedup
|
|
396
|
+
* nothing. ⚠ The read-then-append is not atomic — two rewrites racing in one
|
|
397
|
+
* clone could both miss — but `post-rewrite` runs inside git's own serialised
|
|
398
|
+
* rebase, and the cost of the residual race is one duplicate pair rather than a
|
|
399
|
+
* wrong one.
|
|
400
|
+
*
|
|
401
|
+
* ⛔ **NEVER THROWS.** This runs inside the user's `git rebase`.
|
|
402
|
+
*/
|
|
403
|
+
export function appendRewrites(home, key, pairs) {
|
|
404
|
+
const path = rewriteSpoolPath(home, key);
|
|
405
|
+
if (path === null)
|
|
406
|
+
return 0;
|
|
407
|
+
const seen = new Set(readRewrites(home, key).map(wirePair));
|
|
408
|
+
let written = 0;
|
|
409
|
+
try {
|
|
410
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
411
|
+
for (const pair of pairs) {
|
|
412
|
+
const wire = wirePair(pair);
|
|
413
|
+
if (seen.has(wire))
|
|
414
|
+
continue;
|
|
415
|
+
seen.add(wire);
|
|
416
|
+
appendFileSync(path, `${JSON.stringify(pair)}\n`, { mode: 0o600 });
|
|
417
|
+
written += 1;
|
|
418
|
+
}
|
|
419
|
+
if (written > 0)
|
|
420
|
+
chmodSync(path, 0o600);
|
|
421
|
+
return written;
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
return written;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/** Every well-formed pair in the rewrite spool, oldest first. */
|
|
428
|
+
export function readRewrites(home, key) {
|
|
429
|
+
const path = rewriteSpoolPath(home, key);
|
|
430
|
+
if (path === null)
|
|
431
|
+
return [];
|
|
432
|
+
let raw;
|
|
433
|
+
try {
|
|
434
|
+
raw = readFileSync(path, "utf8");
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
const out = [];
|
|
440
|
+
for (const line of raw.split("\n")) {
|
|
441
|
+
if (line.trim() === "")
|
|
442
|
+
continue;
|
|
443
|
+
const pair = parsePair(line);
|
|
444
|
+
if (pair !== null)
|
|
445
|
+
out.push(pair);
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
/** Drop the first `count` pairs — called ONLY after the server's 2xx. */
|
|
450
|
+
export function dropRewrites(home, key, count) {
|
|
451
|
+
const path = rewriteSpoolPath(home, key);
|
|
452
|
+
if (path === null || count <= 0)
|
|
453
|
+
return false;
|
|
454
|
+
try {
|
|
455
|
+
const lines = readFileSync(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
456
|
+
writeFileSync(path, lines.slice(count).map((l) => `${l}\n`).join(""), { mode: 0o600 });
|
|
457
|
+
chmodSync(path, 0o600);
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* The pairs this hook will carry, and how many to drop after a 2xx.
|
|
466
|
+
*
|
|
467
|
+
* ⛔ **`ancestor:successor`, COLON-SEPARATED.** `parseRewritePairs` drops
|
|
468
|
+
* silently on a wrong separator, on a third colon-field, on a bad sha and on a
|
|
469
|
+
* self-pair — no edge, no error, no log (`D190 §1`).
|
|
470
|
+
*/
|
|
471
|
+
export function capSuccessors(pairs) {
|
|
472
|
+
const taken = pairs.slice(0, MAX_SPOOLED_PAIRS);
|
|
473
|
+
return { pairs: taken.map(wirePair), count: taken.length };
|
|
474
|
+
}
|
|
475
|
+
/** The wire spelling of one pair. Also the dedup key. */
|
|
476
|
+
function wirePair(pair) {
|
|
477
|
+
return `${pair.ancestor}:${pair.successor}`;
|
|
478
|
+
}
|
|
479
|
+
function parsePair(line) {
|
|
480
|
+
let parsed;
|
|
481
|
+
try {
|
|
482
|
+
parsed = JSON.parse(line);
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
488
|
+
return null;
|
|
489
|
+
const o = parsed;
|
|
490
|
+
const { ancestor, successor } = o;
|
|
491
|
+
// ⛔ Re-validated on the way OUT as well as in. A spool file is on disk and
|
|
492
|
+
// outlives the process that wrote it, so what comes back is input.
|
|
493
|
+
if (!isFullSha(ancestor) || !isFullSha(successor))
|
|
494
|
+
return null;
|
|
495
|
+
// ⚠ A self-pair is dropped by the server anyway; dropping it here keeps a
|
|
496
|
+
// no-op from occupying one of the 16 slots a real pair needs.
|
|
497
|
+
if (ancestor === successor)
|
|
498
|
+
return null;
|
|
499
|
+
return { ancestor, successor };
|
|
500
|
+
}
|
|
501
|
+
/** Full width, both widths git uses. ⛔ Never abbreviated. */
|
|
502
|
+
export function isFullSha(value) {
|
|
503
|
+
return typeof value === "string" && /^[0-9a-f]{40}$|^[0-9a-f]{64}$/.test(value);
|
|
504
|
+
}
|
|
505
|
+
function parseEntry(line) {
|
|
506
|
+
let parsed;
|
|
507
|
+
try {
|
|
508
|
+
parsed = JSON.parse(line);
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
514
|
+
return null;
|
|
515
|
+
const o = parsed;
|
|
516
|
+
const sha = o.sha;
|
|
517
|
+
// ⛔ The width check lives here as well as at the boundary: a spool file is
|
|
518
|
+
// on disk and outlives the process that wrote it, so what comes back out is
|
|
519
|
+
// input, not something we already validated.
|
|
520
|
+
if (!isFullSha(sha))
|
|
521
|
+
return null;
|
|
522
|
+
const at = typeof o.at === "string" ? o.at : "";
|
|
523
|
+
const branch = typeof o.branch === "string" && o.branch !== "" ? o.branch : null;
|
|
524
|
+
const files = Array.isArray(o.files) ? o.files.filter((f) => typeof f === "string") : [];
|
|
525
|
+
return { sha, branch, at, files, attribution: parseRung(o.attribution) };
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* ⛔ **AN UNLABELLED LINE IS `recency_heuristic`, AND THAT IS NOT A DEFAULT — IT
|
|
529
|
+
* IS THE CORRECT NAME FOR HOW IT WAS PRODUCED.**
|
|
530
|
+
*
|
|
531
|
+
* A spool file outlives the process that wrote it and survives an upgrade, so a
|
|
532
|
+
* line written by a PRE-LADDER client is ordinary input here. That client picked
|
|
533
|
+
* its session by mtime recency, with no corroboration and no way to tell one
|
|
534
|
+
* candidate from two. Naming it anything else would upgrade a guess to an
|
|
535
|
+
* observation on the strength of a version number.
|
|
536
|
+
*
|
|
537
|
+
* ⚠ **This is `D190 §2`'s NO RUNG, NO EDGE, on the client side.** The server
|
|
538
|
+
* refuses to write an edge for a commit that arrives without a rung, for exactly
|
|
539
|
+
* this reason; here the same commit is refused a place on `x-commits` before it
|
|
540
|
+
* is ever sent. Both directions of the seam agree, independently.
|
|
541
|
+
*/
|
|
542
|
+
function parseRung(value) {
|
|
543
|
+
switch (value) {
|
|
544
|
+
case "env_session_id":
|
|
545
|
+
case "sole_live_session":
|
|
546
|
+
case "env_session_unmatched":
|
|
547
|
+
return value;
|
|
548
|
+
default:
|
|
549
|
+
// ⛔ INCLUDING an unlabelled line and a rung outside the vocabulary. A
|
|
550
|
+
// near-miss spelling is NOT admitted: the server skips an unknown rung
|
|
551
|
+
// silently, which is a zero with no error, so it is resolved here to the
|
|
552
|
+
// value that says *we do not know*.
|
|
553
|
+
return "recency_heuristic";
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
//# sourceMappingURL=spool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spool.js","sourceRoot":"","sources":["../src/spool.ts"],"names":[],"mappings":"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"}
|