@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.d.ts
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
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 { type SessionKey } from "./paths.js";
|
|
44
|
+
/**
|
|
45
|
+
* ⛔ **WHICH RUNG OF THE LADDER ANSWERED — the difference between an observation,
|
|
46
|
+
* a guess, and a refusal (`D190`).**
|
|
47
|
+
*
|
|
48
|
+
* Two of these four may reach the wire. The other two may not, and the asymmetry
|
|
49
|
+
* is the whole point:
|
|
50
|
+
*
|
|
51
|
+
* - `env_session_id` — the committing process told us, `M1`. An OBSERVATION.
|
|
52
|
+
* - `sole_live_session` — one session is live in this clone and nothing
|
|
53
|
+
* contradicts it. Weaker, but unambiguous.
|
|
54
|
+
* - ⛔ `recency_heuristic` — **two or more candidates and nothing to choose
|
|
55
|
+
* between them.** The pre-ladder answer, and the defect this wave exists to
|
|
56
|
+
* close: `cr071:157` makes a wrong edge PERMANENT, UPDATE and DELETE both
|
|
57
|
+
* raise, `service_role` included. **HELD, never sent.**
|
|
58
|
+
* - ⛔ `env_session_unmatched` — **the committing process named a session, and
|
|
59
|
+
* no live session here is it.** A REFUSAL, and a different one from *"no
|
|
60
|
+
* session is live at all"*. **Never sent, and never a `commit_attributions`
|
|
61
|
+
* row either.**
|
|
62
|
+
*
|
|
63
|
+
* ## ⛔ WHY THE LAST TWO ARE NOT ONE VALUE, AND IT IS STRUCTURAL
|
|
64
|
+
*
|
|
65
|
+
* The first draft of this ladder called the unmatched-env case
|
|
66
|
+
* `recency_heuristic`, and that is **wrong by a server constraint this repository
|
|
67
|
+
* cannot see**: `commit_attributions_ambiguity_is_plural` requires
|
|
68
|
+
* `cardinality(candidate_session_ids) >= 2`, so a `recency_heuristic` row naming
|
|
69
|
+
* ONE candidate is refused **23514**.
|
|
70
|
+
*
|
|
71
|
+
* ⛔ **CITED TO `D197 §3`, WHICH IS WHERE THAT ARGUMENT LIVES IN FULL** — including
|
|
72
|
+
* the constraint's own comment, verbatim: *"ONE live candidate is the
|
|
73
|
+
* `sole_live_session` rung and writes an edge, ZERO is the refusal that writes
|
|
74
|
+
* nothing anywhere. A writer that mis-walks the ladder gets a 23514 rather than a
|
|
75
|
+
* hold nobody can resolve."*
|
|
76
|
+
*
|
|
77
|
+
* ⚠ **I have NOT read the constraint in the closed schema**, so it is cited to a
|
|
78
|
+
* decision entry anyone here can open rather than characterised from a repository
|
|
79
|
+
* this package must not depend on (`feedback_characterization_is_not_citation`).
|
|
80
|
+
*
|
|
81
|
+
* ⛔ **And citing the DECISION rather than the sibling repo is deliberate, not
|
|
82
|
+
* cosmetic.** `test/provenance.test.ts` reads a sibling-repo name in `src/` as an
|
|
83
|
+
* undeclared closed-core reference, and a new declaration is a LICENSING REVIEW
|
|
84
|
+
* EVENT (D60 §D1a) requiring a human in the PR body. Nothing here was retyped out
|
|
85
|
+
* of closed source, so a declaration would assert a reading that did not happen —
|
|
86
|
+
* and pointing at `D197 §3` removes the event instead of adding one.
|
|
87
|
+
*
|
|
88
|
+
* ⚠ Both of that guard's tripwires fired on the prose of this docblock while it
|
|
89
|
+
* was being written — the repo name, and then the marker tag's own literal name
|
|
90
|
+
* inside a sentence disclaiming it. ⛔ A grep over a repo name cannot tell a
|
|
91
|
+
* citation from a disclaimer (`feedback_gate_on_the_phrase_not_the_token`); the
|
|
92
|
+
* wording moved and the guards were left alone.
|
|
93
|
+
*
|
|
94
|
+
* ⛔ And the old ladder really could produce that row: an unmatched env id with
|
|
95
|
+
* **exactly one** live candidate fell through to `recency_heuristic`, cardinality
|
|
96
|
+
* one. ⚠ Not reachable from this client TODAY — no held commit is ever
|
|
97
|
+
* transmitted — so this is a defect in the VOCABULARY rather than a live 23514.
|
|
98
|
+
* It would have become one the moment a later wave sent held rows, which is
|
|
99
|
+
* precisely when nobody would be looking at this file.
|
|
100
|
+
*
|
|
101
|
+
* ⚠ **The two also differ in meaning, not just in cardinality.**
|
|
102
|
+
* `recency_heuristic` says *we could not tell which of several*;
|
|
103
|
+
* `env_session_unmatched` says *we were told, and the answer names nobody here*.
|
|
104
|
+
* The second is the more certain statement of the two, and collapsing it into the
|
|
105
|
+
* first records a doubt we do not have.
|
|
106
|
+
*
|
|
107
|
+
* ⚠ **The wire vocabulary is the first two** — `WIRE_RUNGS`, below, and `capSpool`
|
|
108
|
+
* is where the other two stop.
|
|
109
|
+
*/
|
|
110
|
+
export type AttributionRung = "env_session_id" | "sole_live_session" | "recency_heuristic" | "env_session_unmatched";
|
|
111
|
+
/** One observed commit. */
|
|
112
|
+
export interface SpoolEntry {
|
|
113
|
+
/** Full 40- or 64-hex. ⛔ Never abbreviated — see `capSpool`. */
|
|
114
|
+
readonly sha: string;
|
|
115
|
+
/** Null on a detached HEAD. */
|
|
116
|
+
readonly branch: string | null;
|
|
117
|
+
/** ISO-8601, from git's own `%cI`. */
|
|
118
|
+
readonly at: string;
|
|
119
|
+
/** Paths touched. Wave 2. ⛔ Never a header. */
|
|
120
|
+
readonly files: readonly string[];
|
|
121
|
+
/**
|
|
122
|
+
* ⛔ **HOW THE SESSION ON THIS LINE WAS DECIDED.** Recorded at OBSERVATION
|
|
123
|
+
* time, because the evidence is gone by the time the next hook fires: the env
|
|
124
|
+
* variable belonged to the committing process, and the mtimes have moved on.
|
|
125
|
+
*/
|
|
126
|
+
readonly attribution: AttributionRung;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* How many SHAs one hook may put on the wire.
|
|
130
|
+
*
|
|
131
|
+
* A header has a practical size bound and the server pins its own; 40 hex plus a
|
|
132
|
+
* separator is 41 bytes, so 32 is ~1.3 KB and comfortably inside any of them.
|
|
133
|
+
* ⛔ **The rest stay spooled** rather than being dropped — a burst of commits is
|
|
134
|
+
* delivered across several hooks, and hooks are frequent.
|
|
135
|
+
*/
|
|
136
|
+
export declare const MAX_SPOOLED_SHAS = 32;
|
|
137
|
+
/**
|
|
138
|
+
* A session is "live" for this repo if its state file was touched inside this
|
|
139
|
+
* window.
|
|
140
|
+
*
|
|
141
|
+
* ⚠ **A JUDGEMENT CALL, AND IT IS THE ONE KNOB IN THIS MODULE.** The Claude Code
|
|
142
|
+
* hook writes session state on every fire, so during active work this file is
|
|
143
|
+
* touched every turn. Too short and a long turn's commit is missed; too long and
|
|
144
|
+
* a human's commit hours later is attributed to a session that is morally over.
|
|
145
|
+
*
|
|
146
|
+
* The asymmetry decides it, the same way it did in `spawn_budget.ts`: a MISSED
|
|
147
|
+
* commit is recoverable — the sha is still in git and wave 2 can backfill it —
|
|
148
|
+
* while a WRONGLY ATTRIBUTED commit is a permanent row linking work to a session
|
|
149
|
+
* that did not do it. So this is sized to the length of a plausible single turn,
|
|
150
|
+
* not to the length of a working day.
|
|
151
|
+
*/
|
|
152
|
+
export declare const SESSION_LIVE_WINDOW_MS: number;
|
|
153
|
+
/**
|
|
154
|
+
* `<repo>/<session>.spool.jsonl`, beside the session's state file.
|
|
155
|
+
*
|
|
156
|
+
* ⚠ **`.jsonl`, NOT `.json`, and that is load-bearing.** `lastSendForRepo`
|
|
157
|
+
* scans this same directory and filters on `entry.endsWith(".json")`; a spool
|
|
158
|
+
* named `.json` would be parsed as session state, fail, and silently degrade
|
|
159
|
+
* every `vibecommit status` for the repo. Checked rather than assumed — there is
|
|
160
|
+
* a test for exactly that.
|
|
161
|
+
*
|
|
162
|
+
* Null on no repo identity, for the reason `sessionStatePath` is: there is no
|
|
163
|
+
* repo-less bucket and a placeholder key is a cross-repo bleed with an extra step.
|
|
164
|
+
*/
|
|
165
|
+
export declare function spoolPath(home: string, key: SessionKey): string | null;
|
|
166
|
+
/**
|
|
167
|
+
* Append one observed commit. Returns false on any failure.
|
|
168
|
+
*
|
|
169
|
+
* ⛔ **NEVER THROWS.** This runs inside `git commit`: an exception here would
|
|
170
|
+
* surface as a failed hook on the user's own commit, which is exactly the noise
|
|
171
|
+
* `post-commit` must not create. A dropped line is strictly better.
|
|
172
|
+
*
|
|
173
|
+
* `appendFileSync` with `a` is a single positioned write, so two commits racing
|
|
174
|
+
* in one repo interleave whole lines rather than corrupting each other.
|
|
175
|
+
*/
|
|
176
|
+
export declare function appendSpool(home: string, key: SessionKey, entry: SpoolEntry): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Every well-formed entry in the spool, oldest first.
|
|
179
|
+
*
|
|
180
|
+
* A malformed line is SKIPPED rather than fatal: the file is appended to by a
|
|
181
|
+
* different process than reads it, so a torn final line is a real possibility
|
|
182
|
+
* and losing one observation beats losing the whole spool.
|
|
183
|
+
*/
|
|
184
|
+
export declare function readSpool(home: string, key: SessionKey): SpoolEntry[];
|
|
185
|
+
/**
|
|
186
|
+
* Drop the first `count` entries — called ONLY after the server's 2xx.
|
|
187
|
+
*
|
|
188
|
+
* ⚠ **RE-READS BEFORE REWRITING, and that is not defensive tidiness.**
|
|
189
|
+
* `post-commit` may have appended while the send was in flight. Slicing the
|
|
190
|
+
* array we read BEFORE the send would rewrite the file without those lines and
|
|
191
|
+
* lose them silently. Appends only ever go to the END, so the first `count`
|
|
192
|
+
* lines are still the same ones we delivered, and dropping exactly that many is
|
|
193
|
+
* correct against the current file rather than against a stale snapshot.
|
|
194
|
+
*
|
|
195
|
+
* ⚠ **A residual race remains and is accepted:** a commit landing between this
|
|
196
|
+
* re-read and the write is lost. The window is one `readFileSync` plus one
|
|
197
|
+
* `writeFileSync` on a small file, the cost is one un-delivered commit row, and
|
|
198
|
+
* closing it properly means a lock file on a path that must never block
|
|
199
|
+
* (`post-commit` runs inside the user's `git commit`). Recorded rather than
|
|
200
|
+
* hidden.
|
|
201
|
+
*/
|
|
202
|
+
export declare function dropSpooled(home: string, key: SessionKey, count: number): boolean;
|
|
203
|
+
/** Which session answered, and which rung answered it. */
|
|
204
|
+
export interface ActiveSession {
|
|
205
|
+
readonly sessionId: string;
|
|
206
|
+
readonly attribution: AttributionRung;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Which session, if any, is live for this repo right now — ⛔ **AND HOW WE KNOW.**
|
|
210
|
+
*
|
|
211
|
+
* ## The defect this replaces
|
|
212
|
+
*
|
|
213
|
+
* Until the ladder, this function returned *the most recently mtime'd state file
|
|
214
|
+
* within 30 minutes* and said nothing about its own confidence. Two agents in one
|
|
215
|
+
* clone: session A commits, B's state file is newer, **A's commit enters B's
|
|
216
|
+
* spool** — and `cr071:157` makes the resulting edge PERMANENT, with UPDATE and
|
|
217
|
+
* DELETE both raising for `service_role` too. A wrong edge is forever, so the
|
|
218
|
+
* guess had to stop being indistinguishable from the answer.
|
|
219
|
+
*
|
|
220
|
+
* ## The ladder
|
|
221
|
+
*
|
|
222
|
+
* ```
|
|
223
|
+
* env id names a live session ─────────> env_session_id WRITE
|
|
224
|
+
* no live session at all ──────────────> null REFUSE (no row)
|
|
225
|
+
* env id names NONE of them ───────────> env_session_unmatched REFUSE (named)
|
|
226
|
+
* exactly ONE candidate ───────────────> sole_live_session WRITE (D5)
|
|
227
|
+
* TWO OR MORE candidates ──────────────> recency_heuristic HOLD
|
|
228
|
+
* ```
|
|
229
|
+
*
|
|
230
|
+
* ⛔ **`sole_live_session` ALWAYS WRITES, and that is not a change.** It never
|
|
231
|
+
* inspected authorship and does not start now: a human's `git commit` in a clone
|
|
232
|
+
* with one live session produces a spool line today, at HEAD, and
|
|
233
|
+
* `test/post-commit-spool.test.ts` has a green arm asserting exactly that. What
|
|
234
|
+
* is new is that the server will write a PERMANENT row off this rung, so the rung
|
|
235
|
+
* has to be named on the wire (`D190`).
|
|
236
|
+
*
|
|
237
|
+
* ## ⛔ `env_session_unmatched` — THE THIRD ARM, AND IT IS A REFUSAL WITH A NAME
|
|
238
|
+
*
|
|
239
|
+
* An env id that names **none** of the live candidates is a shape the plan's
|
|
240
|
+
* ladder does not draw, and the code must answer it. It is REAL rather than
|
|
241
|
+
* hypothetical: `agents/registry.ts:11-13` rejects an environment variable as the
|
|
242
|
+
* agent selector precisely because **Codex exports `CLAUDE_PLUGIN_ROOT` itself**,
|
|
243
|
+
* so a Codex agent running under an outer Claude session carries a
|
|
244
|
+
* `CLAUDE_CODE_SESSION_ID` that corroborates nothing here.
|
|
245
|
+
*
|
|
246
|
+
* - ⛔ `sole_live_session` there would name a session we have POSITIVE evidence
|
|
247
|
+
* did not commit — the permanent wrong edge, arrived at from the other
|
|
248
|
+
* direction. Never.
|
|
249
|
+
* - ⛔ `recency_heuristic` is what this ladder used first, and it is wrong for
|
|
250
|
+
* the reason `AttributionRung` records: cardinality one, refused **23514**,
|
|
251
|
+
* and a doubt we do not actually have.
|
|
252
|
+
* - ⛔ **A silent refusal is worse than either in one respect** — nobody can
|
|
253
|
+
* afterwards ask *why did that commit vanish*. That is the failure class this
|
|
254
|
+
* whole wave exists to close, so the refusal is RECORDED and NAMED.
|
|
255
|
+
*
|
|
256
|
+
* ⚠ **The line goes in the recency winner's spool bucket, because that is the
|
|
257
|
+
* only bucket that exists** — an uncorroborated env id deliberately does not open
|
|
258
|
+
* one of its own. The bucket is a FILE LOCATION and the rung is the attribution:
|
|
259
|
+
* the line says, in terms, *this commit is attributed to nobody, and here is
|
|
260
|
+
* why*. It never reaches `x-commits` and never becomes a `commit_attributions`
|
|
261
|
+
* row.
|
|
262
|
+
*
|
|
263
|
+
* ⚠ **So `recency_heuristic` is reachable ONLY with two or more candidates**,
|
|
264
|
+
* which is what `commit_attributions_ambiguity_is_plural` demands. That is a
|
|
265
|
+
* property of the ladder's SHAPE now rather than of a caller's care, and
|
|
266
|
+
* `test/post-commit-spool.test.ts` asserts it directly.
|
|
267
|
+
*
|
|
268
|
+
* ⚠ **A commit made before a session's FIRST hook has fired is still NOT
|
|
269
|
+
* captured**, because the state file it would key on does not exist yet. The env
|
|
270
|
+
* id could key a bucket that has no state file, and deliberately does not: the
|
|
271
|
+
* ladder's first rung requires CORROBORATION, so a stale or inherited variable
|
|
272
|
+
* cannot open a spool of its own.
|
|
273
|
+
*
|
|
274
|
+
* `envSessionId` is a PARAMETER rather than a read of `process.env` here, and it
|
|
275
|
+
* has no default: the compiler is the only thing that will notice a caller which
|
|
276
|
+
* forgot the env path, and this seam has no other check.
|
|
277
|
+
*/
|
|
278
|
+
export declare function activeSessionFor(home: string, repoKey: string, envSessionId: string | null, nowMs?: number, windowMs?: number): ActiveSession | null;
|
|
279
|
+
/**
|
|
280
|
+
* ⛔ **THE TWO RUNGS THAT MAY GO ON `x-commit-attributions` (`D190`).**
|
|
281
|
+
*
|
|
282
|
+
* The server's vocabulary is exactly these two (`edge_derivation.ts:178`) and a
|
|
283
|
+
* rung outside it is **skipped silently** — no edge, no error, no log. So the
|
|
284
|
+
* refusal lives here, on the producing side, rather than being discovered as a
|
|
285
|
+
* zero where edges should have been.
|
|
286
|
+
*
|
|
287
|
+
* ⚠ **This constant is NOT what the wire cell asserts against.** There is no
|
|
288
|
+
* shared type across that seam and inventing one would couple an MIT client to a
|
|
289
|
+
* closed server, so the cell types the literals itself — round-tripping this
|
|
290
|
+
* constant through our own code would only prove it equals itself (`D190 §5`).
|
|
291
|
+
*/
|
|
292
|
+
export declare const WIRE_RUNGS: readonly AttributionRung[];
|
|
293
|
+
/** What one hook may put on the wire, and how many spool LINES that consumed. */
|
|
294
|
+
export interface Observed {
|
|
295
|
+
/** ⛔ Same length as `attributions`, same order. Wire rungs only. */
|
|
296
|
+
readonly shas: readonly string[];
|
|
297
|
+
/** ⛔ Same length as `shas`, same order. Wire rungs only. */
|
|
298
|
+
readonly attributions: readonly AttributionRung[];
|
|
299
|
+
/** Spool lines consumed — ⛔ **held ones INCLUDED, so they drain.** */
|
|
300
|
+
readonly count: number;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* The SHAs this hook will carry, their rungs, and how many lines to drop after a
|
|
304
|
+
* 2xx.
|
|
305
|
+
*
|
|
306
|
+
* ⛔ **FULL WIDTH, ALWAYS.** `capture_commits.commit_sha` is 7..40 because the
|
|
307
|
+
* server's `expandShortSha` can fail, so an abbreviated sha makes the web join
|
|
308
|
+
* SILENTLY EMPTY — a wrong answer with no error, D98's class exactly. Anything
|
|
309
|
+
* that is not a full sha is dropped by `parseEntry` rather than sent.
|
|
310
|
+
*
|
|
311
|
+
* ⛔ **`shas` AND `attributions` ARE THE SAME LENGTH BY CONSTRUCTION** — one
|
|
312
|
+
* `push` each, in one iteration, or neither. That is not style.
|
|
313
|
+
* `parseObservedCommits` returns `[]` when the two lists differ in length: **the
|
|
314
|
+
* ENTIRE BATCH is dropped, not the odd entry**, with no edge, no error and no
|
|
315
|
+
* log (`D190 §1`). A `filter` and a `map` over the same array would be the same
|
|
316
|
+
* thing until someone changed one of them.
|
|
317
|
+
*
|
|
318
|
+
* ⛔ **`count` IS LINES CONSUMED, NOT `shas.length`, AND THEY DIVERGE.** A held
|
|
319
|
+
* entry is consumed and never sent. `dropSpooled` drops **the first N lines**, so
|
|
320
|
+
* a caller truncating by `shas.length` would delete a held line at the head and
|
|
321
|
+
* re-send the commit that was actually delivered — forever.
|
|
322
|
+
*
|
|
323
|
+
* ⚠ **A held entry IS dropped on the 2xx, and the observation is lost.** Stated
|
|
324
|
+
* rather than hidden: this wave gives the client no way to transmit a held
|
|
325
|
+
* commit, and leaving it in the file would make every subsequent hook re-read a
|
|
326
|
+
* line that can never be sent. The asymmetry this module already runs on decides
|
|
327
|
+
* it — a MISSED commit is recoverable, the sha is still in git, while a WRONGLY
|
|
328
|
+
* ATTRIBUTED one is a permanent row (`cr071:157`).
|
|
329
|
+
*/
|
|
330
|
+
export declare function capSpool(entries: readonly SpoolEntry[]): Observed;
|
|
331
|
+
/** One rewrite: the sha that was destroyed, and the one that replaced it. */
|
|
332
|
+
export interface RewritePair {
|
|
333
|
+
readonly ancestor: string;
|
|
334
|
+
readonly successor: string;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* How many rewrite pairs one hook may put on the wire.
|
|
338
|
+
*
|
|
339
|
+
* ⛔ **ITS OWN CONSTANT, AND SHARING `MAX_SPOOLED_SHAS` WOULD MAKE ONE OF THE TWO
|
|
340
|
+
* WRONG.** The units differ: a sha plus a separator is 41 bytes, a pair plus a
|
|
341
|
+
* separator is **82** (40 + `:` + 40 + `,`). 16 pairs is 1,312 bytes — the same
|
|
342
|
+
* header budget `MAX_SPOOLED_SHAS = 32` was sized against, arrived at through the
|
|
343
|
+
* arithmetic of this header rather than inherited from the other one.
|
|
344
|
+
*
|
|
345
|
+
* ⛔ **The rest stay spooled**, exactly as the commit spool's remainder does.
|
|
346
|
+
*/
|
|
347
|
+
export declare const MAX_SPOOLED_PAIRS = 16;
|
|
348
|
+
/** `<repo>/<session>.rewrites.jsonl`, beside the commit spool. */
|
|
349
|
+
export declare function rewriteSpoolPath(home: string, key: SessionKey): string | null;
|
|
350
|
+
/**
|
|
351
|
+
* Append rewrite pairs, skipping any already spooled. Returns how many were
|
|
352
|
+
* WRITTEN — ⛔ not how many were offered.
|
|
353
|
+
*
|
|
354
|
+
* ## ⛔ THE DEDUP IS ONE OF T4'S TWO GUARDS, AND IT IS NOT REDUNDANCY
|
|
355
|
+
*
|
|
356
|
+
* `M6` measured the squash hazard and it is **SIZE-DEPENDENT**. A 3→1 squash
|
|
357
|
+
* fires `post-rewrite` twice — `amend A→F`, then `rebase A→F; B→F` — so **the
|
|
358
|
+
* pair `A→F` arrives twice**. A 4→1 squash instead produces rows naming an
|
|
359
|
+
* INTERMEDIATE sha that never existed on any branch; that one is killed by the
|
|
360
|
+
* in-progress-rebase suppression in `hooks/post_rewrite.ts`, which cannot see the
|
|
361
|
+
* duplicate, exactly as this cannot see the intermediate. **One squash size
|
|
362
|
+
* cannot test both.**
|
|
363
|
+
*
|
|
364
|
+
* ⚠ **Dedup is against the FILE, not against the batch**, because the two fires
|
|
365
|
+
* are two processes: a batch-local check would see one pair each time and dedup
|
|
366
|
+
* nothing. ⚠ The read-then-append is not atomic — two rewrites racing in one
|
|
367
|
+
* clone could both miss — but `post-rewrite` runs inside git's own serialised
|
|
368
|
+
* rebase, and the cost of the residual race is one duplicate pair rather than a
|
|
369
|
+
* wrong one.
|
|
370
|
+
*
|
|
371
|
+
* ⛔ **NEVER THROWS.** This runs inside the user's `git rebase`.
|
|
372
|
+
*/
|
|
373
|
+
export declare function appendRewrites(home: string, key: SessionKey, pairs: readonly RewritePair[]): number;
|
|
374
|
+
/** Every well-formed pair in the rewrite spool, oldest first. */
|
|
375
|
+
export declare function readRewrites(home: string, key: SessionKey): RewritePair[];
|
|
376
|
+
/** Drop the first `count` pairs — called ONLY after the server's 2xx. */
|
|
377
|
+
export declare function dropRewrites(home: string, key: SessionKey, count: number): boolean;
|
|
378
|
+
/**
|
|
379
|
+
* The pairs this hook will carry, and how many to drop after a 2xx.
|
|
380
|
+
*
|
|
381
|
+
* ⛔ **`ancestor:successor`, COLON-SEPARATED.** `parseRewritePairs` drops
|
|
382
|
+
* silently on a wrong separator, on a third colon-field, on a bad sha and on a
|
|
383
|
+
* self-pair — no edge, no error, no log (`D190 §1`).
|
|
384
|
+
*/
|
|
385
|
+
export declare function capSuccessors(pairs: readonly RewritePair[]): {
|
|
386
|
+
pairs: string[];
|
|
387
|
+
count: number;
|
|
388
|
+
};
|
|
389
|
+
/** Full width, both widths git uses. ⛔ Never abbreviated. */
|
|
390
|
+
export declare function isFullSha(value: unknown): value is string;
|