@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/git.js ADDED
@@ -0,0 +1,653 @@
1
+ /**
2
+ * What "this repository" means ON THE WIRE — `CR-019d`, the client half of
3
+ * `CR-019`.
4
+ *
5
+ * ⚠ **This is not `project.ts`'s question and the two answers are not
6
+ * interchangeable.** `resolveProjectKey()` answers "which work tree is this?"
7
+ * with a local filesystem path, and that path is (since `CR-017d`) half the
8
+ * local state key — it never leaves the machine. `resolveRepoSlug()` answers
9
+ * "which repository is this, in terms the SERVER can resolve?" and its value
10
+ * goes on the wire as `X-Repo-Slug`. Conflating them would put an absolute path
11
+ * from a developer's laptop into a tenant-scoped lookup, so they stay separate
12
+ * functions with separate return shapes.
13
+ *
14
+ * ⛔ **THIS PARAGRAPH USED TO CALL THAT PATH "THE CONSENT KEY" TOO, AND `D184`
15
+ * MADE THAT FALSE.** The consent gate now keys on the canonicalised git COMMON
16
+ * DIR — `resolveProjectKeys(dir).consent` — and `resolveProjectKey`'s own
17
+ * docblock says so in terms (`project.ts:197`). Corrected here rather than left
18
+ * standing, because this is the paragraph a reader meets first and it was
19
+ * teaching the retired model.
20
+ *
21
+ * ## The contract this must satisfy, read from the merged server, not inferred
22
+ *
23
+ * `CR-019b` shipped and `X-Repo-Slug` is REQUIRED. `parseIngestHeaders`
24
+ * (`vibecommit-mcp/src/conversation/ingest_session.ts`) rejects an absent header
25
+ * — and a present one that is empty after `.trim()` — with a 400 whose reason is
26
+ * `missing X-Repo-Slug`, and rejects anything over 256 characters. So there is
27
+ * no lenient path to fall back to: this module always returns a non-empty slug
28
+ * for a directory that has a git toplevel at all.
29
+ *
30
+ * D57 §OV1 is why the server is strict: *"A NULL `repository_id` is
31
+ * fail-closed-invisible per `org_access_model.sql`, so without this every hook
32
+ * capture would be hidden from its own author."* `readable_repository_ids` is a
33
+ * POSITIVE list, so a capture with no repository renders to nobody — including
34
+ * the person who just made it. A 400 is strictly better than that silence.
35
+ *
36
+ * ## The two keys have different GRAIN, and that is correct in both directions
37
+ *
38
+ * This is where the local and wire identities line up, so it is where the
39
+ * mismatch between them belongs on the record. A linked `git worktree` has its
40
+ * own toplevel — verified, `rev-parse --show-toplevel` returns the worktree
41
+ * directory, not the main clone — so two worktrees of one repository are two
42
+ * `repoKey`s and therefore two local offset ledgers, while sharing one `origin`
43
+ * makes them one `repoSlug` and one server `repository_id`.
44
+ *
45
+ * Both halves of that are the behaviour you want. Locally, separate ledgers are
46
+ * the safe direction: the state key exists to keep two work trees from applying
47
+ * each other's byte offsets (`CR-017d`, D58), and over-separating costs a resync
48
+ * at worst. On the wire, one identity is simply true — two worktrees of one
49
+ * repository ARE one repository, and minting two would fragment the audit record
50
+ * of a single repo.
51
+ *
52
+ * ⛔ **THE SPLIT IS NOW THE STATE BUCKET'S ALONE, AND IT USED TO BE EVERYTHING'S
53
+ * (`D184`).** Read the paragraph above as being about the OFFSET LEDGER, because
54
+ * that is all it still governs. Consent no longer follows the toplevel: it keys
55
+ * on the git common dir, which a main clone and every worktree linked to it
56
+ * SHARE. So the two statements a reader is likely to run together are now
57
+ * opposites — two worktrees are **two** state buckets and **one** consent
58
+ * grant — and the `CR-017d`/D58 reasoning above is kept verbatim because it is
59
+ * still exactly right about the half it was written for.
60
+ *
61
+ * ⚠ **The reason they moved apart is the same fact seen from two sides.** A
62
+ * per-worktree consent key made an agent session inside a `git worktree` capture
63
+ * NOTHING, silently, while `D19` makes worktree-per-builder the house convention
64
+ * — so sharing was the fix. A per-worktree state key is what stops two work
65
+ * trees applying each other's byte offsets — so separating is the fix. One
66
+ * value could not do both, and `project.ts` is where the two are produced
67
+ * together precisely so taking the wrong one has to be deliberate.
68
+ *
69
+ * The one reachable oddity: two worktrees driven by ONE Claude Code session (so,
70
+ * one `session_id`) would keep two independent client ledgers against a single
71
+ * server stream key. The server is idempotent on `(session, offset)` and treats
72
+ * a disagreement as a resync, so the cost is re-sent bytes, never a cross-repo
73
+ * bleed. Not designed around; recorded so the next person meets it as a known
74
+ * property rather than a surprise.
75
+ *
76
+ * ## Case, and why this does not lowercase
77
+ *
78
+ * `resolve_repository` canonicalises with `lower(trim(coalesce(slug, '')))` and
79
+ * the App-installation oracle compares `lower(r.repo_full_name) = v_slug`, so
80
+ * both sides are lowercased and case cannot affect resolution. What case DOES
81
+ * affect is the row the server writes: `display_name` is
82
+ * `trim(p_source_repo_full_name)` — the raw value sent here. Preserving the
83
+ * remote's own case therefore gives a correctly-cased display name for free,
84
+ * and lowercasing would throw that away for no gain.
85
+ *
86
+ * @provenance vibecommit-mcp src/conversation/ingest_session.ts — parseIngestHeaders, read
87
+ * @provenance vibecommit-schema org_access_model.sql — readable_repository_ids, read
88
+ */
89
+ import { execFileSync } from "node:child_process";
90
+ import { createHash } from "node:crypto";
91
+ import { spawnTimeoutMs } from "./spawn_budget.js";
92
+ /**
93
+ * Per-spawn CEILINGS — the most any one call may take when no hook budget is
94
+ * armed (a CLI verb). ⚠ **A ceiling is not a bound**: four of these on one hook
95
+ * path summed to 20s against a 5s watchdog, which is `TODOS[87]`. What bounds
96
+ * them is `spawnTimeoutMs`, which hands back the SMALLER of the ceiling and what
97
+ * is left of one shared window.
98
+ */
99
+ const GIT_TIMEOUT_MS = 5_000;
100
+ /** Tighter than the git probes: `ssh -G` is pure config evaluation, measured ~4ms. */
101
+ const SSH_TIMEOUT_MS = 2_000;
102
+ /**
103
+ * Hosts whose URLs are confidently GitHub.com — and therefore the ONLY hosts
104
+ * that emit an UNQUALIFIED two-segment `owner/repo`.
105
+ *
106
+ * `ssh.github.com` is GitHub's documented port-443 SSH endpoint, used by anyone
107
+ * behind a firewall that blocks 22. It is the same host in every sense that
108
+ * matters here, and dropping those users to `local:` would silently cost them
109
+ * repository attribution for a config GitHub itself tells them to use.
110
+ *
111
+ * ⚠ **THIS SET MUST NOT GROW TO INCLUDE GHES OR ANY OTHER FORGE** (D157). It is
112
+ * not "hosts we support" — it is "hosts whose slug the App-installation oracle
113
+ * can resolve". `resolve_repository` matches the oracle on
114
+ * `github_installation_repos.repo_full_name`, which is `owner/repo`, so a host
115
+ * added here starts claiming the github.com namespace. Every other host is
116
+ * host-qualified instead — see `parseRemoteSlug`.
117
+ */
118
+ const GITHUB_HOSTS = new Set(["github.com", "ssh.github.com"]);
119
+ /**
120
+ * A hostname we are willing to hand to `ssh` and to put in a slug.
121
+ *
122
+ * ⚠ **THIS IS AN ARGUMENT-INJECTION GUARD, NOT A VALIDATOR.** `execFileSync`
123
+ * closes the shell-injection hole but NOT the option-injection one: a host is
124
+ * attacker-influenced (it comes from `.git/config`, which travels with a clone),
125
+ * and `fromScpLike`'s host group happily matches a leading `-`. Measured:
126
+ * `ssh -G "-oProxyCommand=…"` is parsed as FLAGS and prints ssh's usage. So a
127
+ * host that is not plainly a hostname never reaches `ssh` — and `--` below is
128
+ * the second, independent guard, because two cheap guards on a spawn beat one.
129
+ *
130
+ * No leading or trailing `-` or `.`, and nothing outside the LDH set, which is
131
+ * what a real DNS name is anyway.
132
+ */
133
+ const HOSTNAME = /^[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/;
134
+ /**
135
+ * A GitHub account name: alphanumerics and hyphens, no leading or trailing
136
+ * hyphen. Deliberately GitHub's own rule rather than "anything without a
137
+ * slash" — see the confidence note on `resolveRepoSlug`.
138
+ */
139
+ const OWNER = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
140
+ /** A GitHub repository name. `.` and `..` are excluded below, not here. */
141
+ const REPO = /^[A-Za-z0-9._-]+$/;
142
+ /**
143
+ * `owner/repo` for a work tree whose `origin` is GitHub, `host/owner/repo` for
144
+ * any other host, else `local:<sha256(abspath)[0:12]>`.
145
+ *
146
+ * **Takes the TOPLEVEL, not a `cwd`.** Every caller already has it: the consent
147
+ * gate resolves `resolveProjectKey(input.cwd)` before anything else runs and
148
+ * refuses when it is null, so re-deriving it here would be a second
149
+ * `git rev-parse` spent against the hook's wall-clock budget (DESIGN.md §13.7)
150
+ * to recompute a value the caller is holding — and a second call could disagree
151
+ * with the first, which is the same reason `CR-017d` reuses it for the state key
152
+ * rather than recomputing.
153
+ *
154
+ * **Never returns empty.** A work tree always has an identity: with a remote it
155
+ * is the remote's, without one it is the path's. That is what lets the server
156
+ * require the header (D58 §ER25).
157
+ *
158
+ * ## ⚠ Why `local:` had to stop being the answer for a real remote (D157)
159
+ *
160
+ * `localSlug` hashes the developer's OWN ABSOLUTE PATH. That is correct for a
161
+ * work tree with no remote — there is nothing else to key on, and two such trees
162
+ * genuinely are two repositories. It is WRONG for a tree that has a remote:
163
+ * every colleague, and every second clone on one machine, mints a DIFFERENT
164
+ * repository for what is provably one repo. Captures never join, the graph
165
+ * cannot span them, and **nothing errors** — the failure is silent by
166
+ * construction, which is what made it survive this long.
167
+ *
168
+ * So `local:` now survives only where it is true: **no remote at all**, or a
169
+ * remote whose path is not `owner/repo` and therefore cannot be named without
170
+ * guessing (`parseRemoteSlug` refuses rather than guesses — `CR-019d`).
171
+ */
172
+ export function resolveRepoSlug(toplevel) {
173
+ const remote = gitRemoteUrl(toplevel);
174
+ const slug = remote === null ? null : parseRemoteSlug(remote);
175
+ return slug ?? localSlug(toplevel);
176
+ }
177
+ /**
178
+ * `origin`'s URL, or null when there is no `origin` (or git cannot say).
179
+ *
180
+ * `execFileSync` with an argument array, never a shell string — a repository
181
+ * path containing a space or a `;` is ordinary on a developer's machine and
182
+ * would be a command-injection sink through `execSync`. Same discipline as
183
+ * `project.ts`'s `gitToplevel`, for the same reason.
184
+ */
185
+ export function gitRemoteUrl(dir) {
186
+ const timeout = spawnTimeoutMs(GIT_TIMEOUT_MS);
187
+ if (timeout === null)
188
+ return null;
189
+ try {
190
+ const out = execFileSync("git", ["-C", dir, "remote", "get-url", "origin"], {
191
+ encoding: "utf8",
192
+ stdio: ["ignore", "pipe", "ignore"],
193
+ timeout,
194
+ });
195
+ const url = out.trim();
196
+ return url === "" ? null : url;
197
+ }
198
+ catch {
199
+ // No `origin`, not a work tree, git missing, timeout — all the same answer:
200
+ // we cannot name a remote, so the caller falls back to the local identity.
201
+ return null;
202
+ }
203
+ }
204
+ /**
205
+ * Run one git command, or return null — the shared probe.
206
+ *
207
+ * ⚠ **AN ARGUMENT ARRAY, NEVER A SHELL STRING.** A repository path containing a
208
+ * space or a `;` is ordinary on a developer's machine and a shell string is a
209
+ * command-injection sink. Identical shape to `gitRemoteUrl` above and
210
+ * `project.ts`'s `gitToplevel`: `-C <dir>`, utf8, stderr discarded, a
211
+ * five-second bound, and a failure that resolves to a VALUE rather than
212
+ * throwing.
213
+ *
214
+ * ⚠ **MOVED HERE FROM `src/commands/why.ts` BY `CR-108`**, because `report`
215
+ * needs the same probe and a second copy is how the two drift. `why`'s screens
216
+ * are byte-identical afterwards — `test/why.test.ts` is the oracle for that,
217
+ * and the move changes no behaviour: this is the same function.
218
+ */
219
+ export function gitProbe(dir, args) {
220
+ const timeout = spawnTimeoutMs(GIT_TIMEOUT_MS);
221
+ if (timeout === null)
222
+ return null;
223
+ try {
224
+ return execFileSync("git", ["-C", dir, ...args], {
225
+ encoding: "utf8",
226
+ stdio: ["ignore", "pipe", "ignore"],
227
+ timeout,
228
+ });
229
+ }
230
+ catch {
231
+ return null;
232
+ }
233
+ }
234
+ /**
235
+ * Is this a shallow clone?
236
+ *
237
+ * ⚠ **LOAD-BEARING RATHER THAN DEFENSIVE** (D67's posture), and it is the same
238
+ * hazard for two different verbs. `git blame` on a shallow clone attributes
239
+ * every line older than the graft boundary to the boundary commit; a
240
+ * REACHABILITY question on a shallow clone is wrong the same way, because the
241
+ * commits that would answer it may simply not be in the clone. Both return a
242
+ * PLAUSIBLE WRONG ANSWER — D98's hazard class — and shallow clones live in CI
243
+ * containers, which is where nobody is reading the output.
244
+ *
245
+ * ⚠ **FAILS CLOSED.** If git cannot answer, we do not know the clone is
246
+ * complete, and an answer we cannot stand behind is worse than a refusal.
247
+ */
248
+ export function isShallowClone(dir) {
249
+ const out = gitProbe(dir, ["rev-parse", "--is-shallow-repository"]);
250
+ return out === null || out.trim() !== "false";
251
+ }
252
+ /**
253
+ * Is `sha` an ancestor of `ref` — i.e. still reachable from it?
254
+ *
255
+ * ⚠ **THE ONE THING THE SERVER CANNOT ANSWER.** `commit_coverage` returns the
256
+ * shas we hold an edge for and deliberately no rate, because reachability is a
257
+ * property of the user's clone and that process has no repository (D67). This
258
+ * is the client half of that split.
259
+ *
260
+ * ⚠ **FAILS CLOSED, and the direction matters.** `merge-base --is-ancestor`
261
+ * exits 0 for yes and 1 for no, so `gitProbe` returns `""` for reachable and
262
+ * `null` for BOTH "not reachable" and "git could not answer". Those are
263
+ * different facts, and collapsing them would silently count an unanswerable
264
+ * probe as unreachable — deflating the rate rather than refusing it. So the
265
+ * caller is handed `null` for "unknown" and must decide, and `report` refuses.
266
+ */
267
+ export function isReachable(dir, sha, ref) {
268
+ // `--quiet` so the exit code is the whole answer and nothing reaches stdout.
269
+ if (gitProbe(dir, ["merge-base", "--is-ancestor", sha, ref]) !== null)
270
+ return true;
271
+ // ⚠ A REFUSAL AND A NEGATIVE ARE DIFFERENT ANSWERS. If `ref` does not resolve,
272
+ // or the sha is not in this clone at all, the question was never asked — so
273
+ // report `null` rather than folding it into a `false` the caller would count.
274
+ //
275
+ // ⚠ And note the phrasing above avoids the token `from` before a double
276
+ // quote: `test/provenance.test.ts`'s linkage wall is a REGEX over raw text,
277
+ // `/(?:from|import)\s*\(?\s*"([^"]+)"/`, so ordinary prose can read as an
278
+ // import specifier escaping the package. `why.ts` carries the same warning.
279
+ // This comment cost one red run to learn, in the direction that is safe.
280
+ if (gitProbe(dir, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]) === null)
281
+ return null;
282
+ return gitProbe(dir, ["cat-file", "-e", `${sha}^{commit}`]) === null ? null : false;
283
+ }
284
+ /**
285
+ * The `git patch-id` of one commit — the CLIENT-ASSERTED half of a successor
286
+ * pair (`CR-133d`, D111 §1).
287
+ *
288
+ * ⚠ **THE CLIENT IS THE ONLY SIDE THAT CAN COMPUTE THIS.** The server provably
289
+ * cannot: no git dependency, no clone, and its only `child_process` is a
290
+ * best-effort `git show` on no live path (D108). A patch id is a fact about a
291
+ * DIFF, so it survives the rewrite that changes the sha — which is what lets a
292
+ * successor pair resolve through a rebase or GitHub's squash-merge button.
293
+ *
294
+ * ⚠ **IT IS EVIDENCE THE CLIENT ASSERTS, NOT SOMETHING WE WITNESSED** (D111 §1
295
+ * condition 3, the same posture as `cli_read` in D73). Whatever consumes this
296
+ * must record it as client-asserted; nothing here establishes that the caller's
297
+ * repository is the one the server thinks it is.
298
+ *
299
+ * ⚠ **NULL RATHER THAN A GUESS, AND THE REASON IS PERMANENCE.** `match_kind` is
300
+ * in the primary key and `reject_history_mutation` raises on every UPDATE
301
+ * including `service_role` (D105, D108), so **a wrong row can never be
302
+ * retracted**. That makes guessing not a degraded-but-acceptable mode. Every
303
+ * unanswerable case below therefore returns `null`, exactly like `isReachable`
304
+ * beside it, and the caller must be able to express "no patch id" as a state
305
+ * rather than as a value.
306
+ *
307
+ * MEASURED cases that return `null` — none of them is an error:
308
+ * - a MERGE commit — `diff-tree` emits no patch for one, and a merge has no
309
+ * single diff to identify;
310
+ * - an EMPTY commit — nothing to hash;
311
+ * - a sha absent from this clone, which is the ordinary SHALLOW-clone case
312
+ * (`isShallowClone` above exists for the same reason);
313
+ * - git missing, a timeout, or output that is not a hex id.
314
+ *
315
+ * ⚠ `--root` is deliberate: without it the INITIAL commit of a repository has
316
+ * no patch id at all, which would silently exclude the one commit every
317
+ * repository has.
318
+ *
319
+ * ⚠ `--no-ext-diff` is load-bearing, not tidiness: a developer's configured
320
+ * external differ would otherwise rewrite the bytes being hashed, and the
321
+ * resulting id would be stable only on that machine.
322
+ *
323
+ * ⚠ **NO SHELL, so no pipe.** `git patch-id` normally reads a pipe; feeding it
324
+ * through `input` keeps the argument-array rule `gitProbe` documents above — a
325
+ * repository path containing a space or a `;` is ordinary.
326
+ */
327
+ export function gitPatchId(dir, ref) {
328
+ const diff = gitProbe(dir, ["diff-tree", "-p", "--root", "--no-color", "--no-ext-diff", ref]);
329
+ // ⚠ An empty diff is NOT a failure — it is a merge, or an empty commit. Both
330
+ // are "no patch id exists", which is the same answer as "we could not ask".
331
+ if (diff === null || diff.trim() === "")
332
+ return null;
333
+ // ⚠ A SECOND SPAWN IN ONE FUNCTION, so it re-reads the budget rather than
334
+ // reusing `gitProbe`'s number — the `diff-tree` above may have consumed most
335
+ // of the window, and a stale timeout would let the pair overrun together.
336
+ const timeout = spawnTimeoutMs(GIT_TIMEOUT_MS);
337
+ if (timeout === null)
338
+ return null;
339
+ let out;
340
+ try {
341
+ out = execFileSync("git", ["-C", dir, "patch-id", "--stable"], {
342
+ input: diff,
343
+ encoding: "utf8",
344
+ stdio: ["pipe", "pipe", "ignore"],
345
+ timeout,
346
+ });
347
+ }
348
+ catch {
349
+ return null;
350
+ }
351
+ // `patch-id` prints "<patch-id> <commit-ish>". Take the id and PROVE it is one
352
+ // — anything else is a git we do not understand, and half an id is worse than
353
+ // none. 40 hex for sha1 repositories, 64 for sha256.
354
+ const id = out.trim().split(/\s+/)[0] ?? "";
355
+ return /^[0-9a-f]{40}$|^[0-9a-f]{64}$/.test(id) ? id : null;
356
+ }
357
+ /** A 40-hex sha1 or 64-hex sha256 object name. Nothing else is a sha. */
358
+ const SHA = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/;
359
+ /**
360
+ * `HEAD`'s sha and branch — **the ONE new spawn `CR-170` adds to the Claude Code
361
+ * hook path**, and it is for REF MOVES, not for commits.
362
+ *
363
+ * Commits are observed by `post-commit` (D154), which cannot see a `pull`, a
364
+ * `checkout` or a `reset --hard` — those move a ref **without creating a
365
+ * commit**, so no commit hook fires and only a direct look at `HEAD` records it.
366
+ *
367
+ * ⛔ **ONE SPAWN, BUT NOT COMBINED WITH `--show-toplevel`.** MEASURED
368
+ * 2026-08-17: `git rev-parse --show-toplevel nosuchref` prints the toplevel and
369
+ * THEN exits **128**, so `execFileSync` throws and the toplevel is lost with it —
370
+ * and on a repository with no commits `rev-parse HEAD` is exactly that case
371
+ * (measured: exit 128, `HEAD` on stdout, a usage line on stderr). Folding the two
372
+ * together would make an empty repo lose its project key, which is the `C3`
373
+ * regression. The toplevel keeps its own spawn; this is the second.
374
+ *
375
+ * ⛔ **THE OUTPUT IS VALIDATED, NEVER TRUSTED.** MEASURED, same day: when the rev
376
+ * argument is itself 40 hex, `rev-parse` exits **0 and ECHOES IT BACK** as though
377
+ * it had resolved something. Nothing distinguishes that from a real answer except
378
+ * checking the shape — the same reason `gitPatchId` tests its own return before
379
+ * believing it. ⚠ Marked as an inference: the echo-back is not reachable from
380
+ * THIS caller, which passes the literal `HEAD`, so the test below is defence
381
+ * against a future caller and against a git we do not understand, not a live path.
382
+ */
383
+ export function headRef(dir) {
384
+ const out = gitProbe(dir, ["rev-parse", "HEAD", "--abbrev-ref", "HEAD"]);
385
+ if (out === null)
386
+ return null;
387
+ const lines = out.trim().split("\n");
388
+ if (lines.length < 2)
389
+ return null;
390
+ const sha = lines[0].trim();
391
+ if (!SHA.test(sha))
392
+ return null;
393
+ const ref = lines[1].trim();
394
+ // `HEAD` is what `--abbrev-ref` prints when detached. Not a branch name.
395
+ return { sha, branch: ref === "" || ref === "HEAD" ? null : ref };
396
+ }
397
+ /** The branch `HEAD` is on, or null on a detached HEAD or a failure. */
398
+ export function currentBranch(dir) {
399
+ const out = gitProbe(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
400
+ if (out === null)
401
+ return null;
402
+ const ref = out.trim();
403
+ // `HEAD` is what `--abbrev-ref` prints when detached. That is not a branch
404
+ // name, and reporting "coverage on HEAD" would name nothing the reader can
405
+ // check, so the caller asks for `--ref` instead of guessing.
406
+ return ref === "" || ref === "HEAD" ? null : ref;
407
+ }
408
+ /**
409
+ * `owner/repo` from a github.com remote, `host/owner/repo` from any other host,
410
+ * or null when the URL cannot be named without guessing.
411
+ *
412
+ * ⚠ **RENAMED FROM `parseGitHubSlug` BY `CR-167`**, because it stopped being
413
+ * about GitHub. A function that answers `gitlab.com/owner/repo` while calling
414
+ * itself `parseGitHubSlug` is the kind of name the next reader trusts and should
415
+ * not. Nothing outside this module and its test imported the old name.
416
+ *
417
+ * ## ⚠ SEGMENT COUNT IS THE DISCRIMINATOR, AND THE SERVER ALREADY PARSES IT
418
+ *
419
+ * `cr033a` §4 branches on the slash count: exactly one slash means the host is
420
+ * IMPLIED to be github.com and segment 1 is the owner; otherwise segment 1 is
421
+ * the host and segment 2 is the owner. This function emits into that grammar
422
+ * rather than inventing one.
423
+ *
424
+ * ⛔ **github.com therefore stays UNQUALIFIED at two segments.**
425
+ * `resolve_repository` matches the App-installation oracle on
426
+ * `github_installation_repos.repo_full_name`, which is `owner/repo`. Qualifying
427
+ * github.com would silently break every oracle join — the exact failure class
428
+ * this change exists to fix, re-introduced from the other side.
429
+ *
430
+ * ## Null is still the safe answer, and that asymmetry is still the design
431
+ *
432
+ * A wrong slug is worse than no slug. `local:` is designed to be safe: it can
433
+ * never collide with a real repository (neither a two- nor a three-segment slug
434
+ * can contain a `:`), and `resolve_repository` resolves it ungated because a
435
+ * local-only work tree has no external owner to check against. A
436
+ * confidently-wrong slug, by contrast, attributes a capture to a repository the
437
+ * user may not own. So every branch below still fails to null rather than
438
+ * guessing, and callers turn null into `local:`.
439
+ *
440
+ * ⚠ **HOST-QUALIFYING DOES NOT WEAKEN THE SUFFIX-ATTACK GUARD, IT RELOCATES IT.**
441
+ * `github.com.evil.example` no longer returns null — it returns
442
+ * `github.com.evil.example/owner/repo`. The security property was never "returns
443
+ * null"; it is **"is never mistaken for a github.com slug"**, and a three-segment
444
+ * slug provably cannot collide with the two-segment github.com namespace. The
445
+ * server agrees independently: `cr033a` §4 reads segment 1 as the HOST, so that
446
+ * slug is scoped to a namespace claim on `github.com.evil.example`, never to
447
+ * `owner`'s GitHub account.
448
+ *
449
+ * Exported for its own tests: the parsing is where the confidence lives, and it
450
+ * is pure apart from one `ssh -G`, so it is tested directly rather than through
451
+ * a spawned `git`.
452
+ */
453
+ export function parseRemoteSlug(remoteUrl) {
454
+ const raw = remoteUrl.trim();
455
+ if (raw === "")
456
+ return null;
457
+ const parts = raw.includes("://") ? fromUrl(raw) : fromScpLike(raw);
458
+ if (parts === null)
459
+ return null;
460
+ const segments = parts.path
461
+ .replace(/\.git$/, "")
462
+ .split("/")
463
+ .filter((segment) => segment !== "");
464
+ // EXACTLY two. A URL with more is not a remote — it is a web URL someone
465
+ // pasted (`.../owner/repo/tree/main`), and silently taking its first two
466
+ // segments would be exactly the confident guess this function refuses to make.
467
+ //
468
+ // ⚠ This bound is checked BEFORE `ssh -G` runs: a URL we are going to refuse
469
+ // anyway must not cost a spawn on the hook's budget.
470
+ if (segments.length !== 2)
471
+ return null;
472
+ const [owner, repo] = segments;
473
+ if (!OWNER.test(owner))
474
+ return null;
475
+ if (!REPO.test(repo) || repo === "." || repo === "..")
476
+ return null;
477
+ // Hostnames are case-insensitive; `GitHub.com` is the same host.
478
+ const host = resolveSshAlias(parts).toLowerCase();
479
+ // A host we would not hand to `ssh` is a host we will not put on the wire
480
+ // either. Same guard, same reason: doubt resolves to `local:`.
481
+ if (!GITHUB_HOSTS.has(host) && !HOSTNAME.test(host))
482
+ return null;
483
+ const slug = GITHUB_HOSTS.has(host) ? `${owner}/${repo}` : `${host}/${owner}/${repo}`;
484
+ // ⚠ THE SERVER'S BOUND, ENFORCED WHERE THE VALUE IS BUILT. `parseIngestHeaders`
485
+ // 400s on a slug over `REPO_SLUG_MAX`, and a 400 loses the capture outright —
486
+ // strictly worse than `local:`, which is always 18 characters and always
487
+ // resolves. `OWNER` and `REPO` are unbounded `+` quantifiers by design (we do
488
+ // not hardcode any forge's limits), so nothing upstream caps this.
489
+ //
490
+ // ⚠ Measured, both halves, because they have different provenance: a 300-char
491
+ // HOST is a regression this change would otherwise introduce (such a remote
492
+ // used to return null and fall to `local:`), while a 200+200 `owner/repo` on
493
+ // github.com already returned 401 characters on `main`. One guard closes both.
494
+ return slug.length <= SLUG_MAX ? slug : null;
495
+ }
496
+ /**
497
+ * `parseIngestHeaders`'s upper bound on `X-Repo-Slug`, mirrored here.
498
+ *
499
+ * Duplicating the server's constant is deliberate: this module cannot import it
500
+ * across the repo boundary, and the alternative is discovering the limit as a
501
+ * 400 in the field. The lower bound needs no check — every branch above returns
502
+ * either `null` or a slug containing at least one non-empty segment.
503
+ *
504
+ * @provenance vibecommit-mcp src/conversation/ingest_session.ts — REPO_SLUG_MAX, read
505
+ */
506
+ const SLUG_MAX = 256;
507
+ /**
508
+ * The remote's host, after resolving an `ssh_config` alias — the half of D157
509
+ * that fixes this project's OWN dogfooding.
510
+ *
511
+ * ⚠ **AN SSH ALIAS IS INVISIBLE TO EVERY OTHER READER OF THE URL.** Our own
512
+ * clones use `git@github-vibecommit:…`, mandatory because plain `git@github.com`
513
+ * resolves to the wrong key, so `github-vibecommit` is not a hostname at all —
514
+ * it is a key into the user's `~/.ssh/config`. Only `ssh` can expand it, so
515
+ * `ssh -G <host>` is consulted and its `hostname` line taken. Measured on
516
+ * OpenSSH 10.3: `ssh -G github-vibecommit` prints `hostname github.com`.
517
+ *
518
+ * ⚠ **ONLY FOR SSH TRANSPORTS, WHICH IS WHAT GIT ITSELF DOES.** An `https://`
519
+ * host is a DNS name that never passes through `ssh_config`; expanding it would
520
+ * let an unrelated `Host` stanza silently rewrite a web remote. So the flag
521
+ * comes from the parse — scp-like form and `ssh://` only, never `https`, `http`
522
+ * or `git://`.
523
+ *
524
+ * ⚠ **AND NEVER FOR A HOST THAT IS ALREADY KNOWN-GITHUB.** Measured identity
525
+ * (`ssh -G github.com` → `github.com`), so the spawn would buy nothing — and
526
+ * this is the common case, which means the overwhelming majority of hooks pay
527
+ * ZERO new process spawns for this change. Only alias and self-hosted users pay,
528
+ * and they pay ~4ms (measured: 10 runs in 41ms, no network I/O — `-G` evaluates
529
+ * config and does not connect or resolve DNS).
530
+ *
531
+ * ⚠ **EVERY FAILURE RETURNS THE HOST UNCHANGED, WHICH IS EXACTLY TODAY'S
532
+ * BEHAVIOUR.** `ssh` absent (a minimal container), too old for `-G` (pre-6.8),
533
+ * a `Match exec` stanza that errors, a timeout, or output with no `hostname`
534
+ * line — all mean "we could not expand this", and an unexpanded host is the
535
+ * status quo rather than a new failure mode. Nothing here can make a slug worse
536
+ * than it is today; it can only fail to improve it.
537
+ *
538
+ * ⚠ **THE BOUND IS 2s, TIGHTER THAN THE 5s ITS NEIGHBOURS USE.** This runs on
539
+ * the hook path, where a synchronous spawn blocks the event loop and therefore
540
+ * defeats the watchdog outright (`TODOS[87]`), so a NEW spawn there earns a
541
+ * tighter bound than the git probes that predate the concern. 2s is ~500x the
542
+ * measured cost; the only way to approach it is a user's own `Match exec`.
543
+ */
544
+ function resolveSshAlias(parts) {
545
+ const { host, ssh } = parts;
546
+ if (!ssh)
547
+ return host;
548
+ if (GITHUB_HOSTS.has(host.toLowerCase()))
549
+ return host;
550
+ // ⛔ Never hand `ssh` an option-shaped string — see `HOSTNAME`.
551
+ if (!HOSTNAME.test(host))
552
+ return host;
553
+ // ⚠ THE FIFTH CONSUMER OF THE HOOK'S SPAWN BUDGET (`CR-167` added it after
554
+ // `TODOS[87]` was written). Its 2s ceiling was already tighter than the git
555
+ // probes' 5s precisely because it runs on the hook path — but a ceiling is not
556
+ // a bound, so it now derives from the same shared window as the rest.
557
+ const timeout = spawnTimeoutMs(SSH_TIMEOUT_MS);
558
+ // Budget spent: the host is returned UNEXPANDED, which is the same answer as
559
+ // ssh being absent — a slug that is merely unqualified, never a wrong one.
560
+ if (timeout === null)
561
+ return host;
562
+ let out;
563
+ try {
564
+ // `--` is the second guard: even if `HOSTNAME` were widened one day, ssh
565
+ // stops reading flags here. Verified on OpenSSH 10.3 — with `--`, an
566
+ // option-shaped host is rejected as a hostname rather than parsed as a flag.
567
+ out = execFileSync("ssh", ["-G", "--", host], {
568
+ encoding: "utf8",
569
+ stdio: ["ignore", "pipe", "ignore"],
570
+ timeout,
571
+ });
572
+ }
573
+ catch {
574
+ return host;
575
+ }
576
+ for (const line of out.split("\n")) {
577
+ // ⚠ `hostname`, NOT `host`. `ssh -G` prints BOTH, and `host` echoes the
578
+ // ALIAS we asked about — taking it would make this function a no-op that
579
+ // looks like it works. Anchored and tokenised so `hostname` cannot match
580
+ // the `host` line's prefix.
581
+ const found = /^hostname[ \t]+(\S+)[ \t]*$/.exec(line);
582
+ // Re-check the ANSWER, not just the question: `ssh_config` is user-supplied
583
+ // and its `HostName` can be anything, including a `%h` token that did not
584
+ // expand.
585
+ if (found !== null)
586
+ return HOSTNAME.test(found[1]) ? found[1] : host;
587
+ }
588
+ return host;
589
+ }
590
+ /**
591
+ * `https://`, `ssh://`, `git://` — anything with a scheme.
592
+ *
593
+ * `ssh` marks the ones git routes through `ssh_config`, and it is TRUE only for
594
+ * `ssh:`. `git:` is the git daemon protocol and `https:`/`http:` are web
595
+ * transports; none of them consults an ssh alias, so neither do we.
596
+ */
597
+ function fromUrl(raw) {
598
+ let url;
599
+ try {
600
+ url = new URL(raw);
601
+ }
602
+ catch {
603
+ return null;
604
+ }
605
+ // `git+ssh://` and friends parse, but a scheme we have not reasoned about is
606
+ // a doubt, and doubt resolves to `local:`.
607
+ if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol))
608
+ return null;
609
+ // `url.hostname` drops any userinfo and port for us, which is what makes
610
+ // `ssh://git@ssh.github.com:443/owner/repo.git` resolve correctly.
611
+ return { host: url.hostname, path: url.pathname, ssh: url.protocol === "ssh:" };
612
+ }
613
+ /**
614
+ * The scp-like form git accepts: `[user@]host:path`, e.g.
615
+ * `git@github.com:owner/repo.git`.
616
+ *
617
+ * Not a URL, so `new URL` cannot read it — and misreading it as one is a real
618
+ * hazard rather than a hypothetical: `new URL("git@github.com:owner/repo")`
619
+ * succeeds with protocol `git@github.com:`, which would sail past a naive
620
+ * scheme check.
621
+ *
622
+ * ⚠ **ALWAYS `ssh: true`** — this form is ssh-only in git, and it is the form
623
+ * every aliased remote takes, so it is the whole reason `resolveSshAlias`
624
+ * exists. `git@github-vibecommit:Vibe-Commit/vibecommit-capture.git` reaches
625
+ * here with host `github-vibecommit`, which is not a hostname at all.
626
+ */
627
+ function fromScpLike(raw) {
628
+ const match = /^(?:[^@/]+@)?([^@/:]+):(.+)$/.exec(raw);
629
+ if (match === null)
630
+ return null;
631
+ const [, host, path] = match;
632
+ return { host, path, ssh: true };
633
+ }
634
+ /**
635
+ * The local-only identity: `local:` plus 12 hex of the toplevel's SHA-256.
636
+ *
637
+ * Shape and prefix are both fixed by the server (D58 §ER25) — `resolve_repository`
638
+ * branches on `v_slug not like 'local:%'` to skip the ownership oracle, because
639
+ * a work tree with no remote has no external owner and gating it would gate a
640
+ * developer against themselves. The prefix is emitted lowercase because this
641
+ * module constructs it: the server's own canonicalisation would also lowercase
642
+ * it, but relying on that for a literal we control would be relying on someone
643
+ * else's normalisation to make our own output correct.
644
+ *
645
+ * A digest rather than the path itself: the path is 1..256-bounded only by luck,
646
+ * it can exceed the server's `REPO_SLUG_MAX`, and it would put an absolute
647
+ * filesystem path — usernames included — into a server-side row and its
648
+ * `display_name`.
649
+ */
650
+ function localSlug(toplevel) {
651
+ return `local:${createHash("sha256").update(toplevel).digest("hex").slice(0, 12)}`;
652
+ }
653
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}