@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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/agents/claude_code.d.ts +11 -0
  4. package/dist/agents/claude_code.js +162 -0
  5. package/dist/agents/claude_code.js.map +1 -0
  6. package/dist/agents/codex_cli.d.ts +15 -0
  7. package/dist/agents/codex_cli.js +67 -0
  8. package/dist/agents/codex_cli.js.map +1 -0
  9. package/dist/agents/cursor.d.ts +21 -0
  10. package/dist/agents/cursor.js +89 -0
  11. package/dist/agents/cursor.js.map +1 -0
  12. package/dist/agents/registry.d.ts +184 -0
  13. package/dist/agents/registry.js +212 -0
  14. package/dist/agents/registry.js.map +1 -0
  15. package/dist/agents/types.d.ts +279 -0
  16. package/dist/agents/types.js +67 -0
  17. package/dist/agents/types.js.map +1 -0
  18. package/dist/commands/connect.d.ts +36 -0
  19. package/dist/commands/connect.js +499 -0
  20. package/dist/commands/connect.js.map +1 -0
  21. package/dist/commands/context.d.ts +45 -0
  22. package/dist/commands/context.js +14 -0
  23. package/dist/commands/context.js.map +1 -0
  24. package/dist/commands/report.d.ts +105 -0
  25. package/dist/commands/report.js +430 -0
  26. package/dist/commands/report.js.map +1 -0
  27. package/dist/commands/status.d.ts +29 -0
  28. package/dist/commands/status.js +236 -0
  29. package/dist/commands/status.js.map +1 -0
  30. package/dist/commands/why.d.ts +349 -0
  31. package/dist/commands/why.js +937 -0
  32. package/dist/commands/why.js.map +1 -0
  33. package/dist/consent.d.ts +59 -0
  34. package/dist/consent.js +111 -0
  35. package/dist/consent.js.map +1 -0
  36. package/dist/copy/claims.d.ts +79 -0
  37. package/dist/copy/claims.js +343 -0
  38. package/dist/copy/claims.js.map +1 -0
  39. package/dist/copy/grades.d.ts +117 -0
  40. package/dist/copy/grades.js +96 -0
  41. package/dist/copy/grades.js.map +1 -0
  42. package/dist/copy/index.d.ts +12 -0
  43. package/dist/copy/index.js +13 -0
  44. package/dist/copy/index.js.map +1 -0
  45. package/dist/copy/strings.d.ts +1328 -0
  46. package/dist/copy/strings.js +1243 -0
  47. package/dist/copy/strings.js.map +1 -0
  48. package/dist/copy/time.d.ts +27 -0
  49. package/dist/copy/time.js +43 -0
  50. package/dist/copy/time.js.map +1 -0
  51. package/dist/credential.d.ts +211 -0
  52. package/dist/credential.js +298 -0
  53. package/dist/credential.js.map +1 -0
  54. package/dist/exit.d.ts +59 -0
  55. package/dist/exit.js +60 -0
  56. package/dist/exit.js.map +1 -0
  57. package/dist/git.d.ts +299 -0
  58. package/dist/git.js +653 -0
  59. package/dist/git.js.map +1 -0
  60. package/dist/hooks/entry.d.ts +277 -0
  61. package/dist/hooks/entry.js +1092 -0
  62. package/dist/hooks/entry.js.map +1 -0
  63. package/dist/hooks/post_commit.d.ts +59 -0
  64. package/dist/hooks/post_commit.js +160 -0
  65. package/dist/hooks/post_commit.js.map +1 -0
  66. package/dist/hooks/post_rewrite.d.ts +59 -0
  67. package/dist/hooks/post_rewrite.js +161 -0
  68. package/dist/hooks/post_rewrite.js.map +1 -0
  69. package/dist/hooks/session_end.d.ts +101 -0
  70. package/dist/hooks/session_end.js +120 -0
  71. package/dist/hooks/session_end.js.map +1 -0
  72. package/dist/index.d.ts +34 -0
  73. package/dist/index.js +360 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/install.d.ts +153 -0
  76. package/dist/install.js +284 -0
  77. package/dist/install.js.map +1 -0
  78. package/dist/json.d.ts +100 -0
  79. package/dist/json.js +94 -0
  80. package/dist/json.js.map +1 -0
  81. package/dist/oauth/discovery.d.ts +96 -0
  82. package/dist/oauth/discovery.js +163 -0
  83. package/dist/oauth/discovery.js.map +1 -0
  84. package/dist/oauth/lock.d.ts +100 -0
  85. package/dist/oauth/lock.js +287 -0
  86. package/dist/oauth/lock.js.map +1 -0
  87. package/dist/oauth/loopback.d.ts +85 -0
  88. package/dist/oauth/loopback.js +124 -0
  89. package/dist/oauth/loopback.js.map +1 -0
  90. package/dist/oauth/pkce.d.ts +58 -0
  91. package/dist/oauth/pkce.js +77 -0
  92. package/dist/oauth/pkce.js.map +1 -0
  93. package/dist/oauth/read.d.ts +120 -0
  94. package/dist/oauth/read.js +144 -0
  95. package/dist/oauth/read.js.map +1 -0
  96. package/dist/oauth/session.d.ts +80 -0
  97. package/dist/oauth/session.js +112 -0
  98. package/dist/oauth/session.js.map +1 -0
  99. package/dist/oauth/signin.d.ts +178 -0
  100. package/dist/oauth/signin.js +225 -0
  101. package/dist/oauth/signin.js.map +1 -0
  102. package/dist/oauth/token.d.ts +139 -0
  103. package/dist/oauth/token.js +183 -0
  104. package/dist/oauth/token.js.map +1 -0
  105. package/dist/paths.d.ts +366 -0
  106. package/dist/paths.js +460 -0
  107. package/dist/paths.js.map +1 -0
  108. package/dist/policy.d.ts +195 -0
  109. package/dist/policy.js +244 -0
  110. package/dist/policy.js.map +1 -0
  111. package/dist/post.d.ts +302 -0
  112. package/dist/post.js +384 -0
  113. package/dist/post.js.map +1 -0
  114. package/dist/project.d.ts +99 -0
  115. package/dist/project.js +192 -0
  116. package/dist/project.js.map +1 -0
  117. package/dist/redact.d.ts +175 -0
  118. package/dist/redact.js +700 -0
  119. package/dist/redact.js.map +1 -0
  120. package/dist/runtime.d.ts +30 -0
  121. package/dist/runtime.js +37 -0
  122. package/dist/runtime.js.map +1 -0
  123. package/dist/spawn_budget.d.ts +101 -0
  124. package/dist/spawn_budget.js +124 -0
  125. package/dist/spawn_budget.js.map +1 -0
  126. package/dist/spool.d.ts +390 -0
  127. package/dist/spool.js +556 -0
  128. package/dist/spool.js.map +1 -0
  129. package/dist/state.d.ts +109 -0
  130. package/dist/state.js +217 -0
  131. package/dist/state.js.map +1 -0
  132. package/dist/system_message.d.ts +101 -0
  133. package/dist/system_message.js +141 -0
  134. package/dist/system_message.js.map +1 -0
  135. package/dist/term.d.ts +112 -0
  136. package/dist/term.js +134 -0
  137. package/dist/term.js.map +1 -0
  138. package/dist/version.d.ts +16 -0
  139. package/dist/version.js +17 -0
  140. package/dist/version.js.map +1 -0
  141. 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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"}