@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
@@ -0,0 +1,366 @@
1
+ /**
2
+ * The `~/.vibecommit/` layout.
3
+ *
4
+ * One module so the fifteen tasks that build on this one do not each invent a
5
+ * path. Every function takes `home` explicitly rather than calling `os.homedir()`
6
+ * internally — the contract test runs the real binary against a temp HOME, and a
7
+ * module that reads the real one cannot be tested without touching the
8
+ * developer's own credentials.
9
+ *
10
+ * `CR-017d` added the repo to the binding key and hangs per-repo send state off
11
+ * `stateDir` — see `sessionStatePath` and `SessionKey` below.
12
+ *
13
+ * @provenance vibecommit-schema 20260809150000_cr017a_repo_binding_key.sql — PK shape, mirrored
14
+ * @provenance vibecommit-schema ingest_sessions.file_key — CHECK bound 1..128, retyped
15
+ */
16
+ /** Root of the client's own state. Never Claude Code's — see the plan's keychain note. */
17
+ export declare function rootDir(home: string): string;
18
+ /** The ingest credential envelope. Mode 0600, enforced on read (`credential.ts`). */
19
+ export declare function credentialsPath(home: string): string;
20
+ /** The per-project consent allow list. Consulted BEFORE any transcript read (D56 §D19). */
21
+ export declare function projectsPath(home: string): string;
22
+ /**
23
+ * The HUMAN lane's signed-in session — `CR-084d`. Mode 0600, enforced on read by
24
+ * the same `readSecretFile` the ingest credential goes through.
25
+ *
26
+ * A SECOND FILE and not a second field in `credentials.json`, deliberately. The
27
+ * two hold different privilege classes on different lanes: `credentials.json`
28
+ * holds the machine's opaque ingest credential and is read by every hook, and
29
+ * this holds a user-principal refresh token read only by interactive verbs. One
30
+ * file would mean the hook path opens the more powerful secret on every turn for
31
+ * no reason, and `VIBECOMMIT_TOKEN` (which overrides the ingest credential and
32
+ * nothing else) would acquire a meaning it does not have.
33
+ */
34
+ export declare function sessionPath(home: string): string;
35
+ /**
36
+ * Client state. `CR-023`'s capture-confirmed key lives here under `(repo, day)`;
37
+ * the three negative `systemMessage` states use a per-session file.
38
+ */
39
+ export declare function stateDir(home: string): string;
40
+ /**
41
+ * Once-per-session marker for a `systemMessage` state.
42
+ *
43
+ * Keyed on `(session, state)` and NOT on the transcript file: `ER23` keys client
44
+ * state per file, which would fire the same message once for `main` and once for
45
+ * every `subagents/agent-*.jsonl` in the same session.
46
+ */
47
+ export declare function sessionNoticePath(home: string, sessionId: string, state: string): string;
48
+ /**
49
+ * Once-per-`(repo, day)` marker for the capture-confirmed notice — `CR-023d`.
50
+ *
51
+ * **The key is neither the session nor "ever", and both alternatives are wrong
52
+ * in a way you can feel.** `PreCompact` mints a fresh `session_id` mid-task, so
53
+ * a per-session key fires the one positive message twice in an afternoon and
54
+ * reads as a bug. A key with no day component fires it exactly once ever, which
55
+ * under-communicates on a tool used daily — and this is the single positive beat
56
+ * in a channel that is otherwise silent by design (style guide §10.1).
57
+ *
58
+ * A per-FILE key would be wrong too, once `CR-124` (W6) starts producing
59
+ * `agent-*` file keys: it would fire once for `main` and once per sub-agent
60
+ * transcript in the same session. The key is deliberately chosen so that landing
61
+ * `CR-124` cannot change how often this message appears.
62
+ *
63
+ * `repo-day/` namespaces this away from `sessionNoticePath`'s
64
+ * `notices/<session>/<state>.json`: the two live at different depths under
65
+ * `notices/`, so no session id can ever resolve onto a repo-day file.
66
+ *
67
+ * The repo is the same SHA-256 digest `sessionStatePath` uses, for the same
68
+ * reason — `sanitise` truncates at 128 characters, so two deep checkouts sharing
69
+ * a prefix would land on one key and the second repo would never see its
70
+ * confirmation. `day` is supplied by the caller as a UTC `YYYY-MM-DD`
71
+ * (`system_message.ts`'s `utcDay`), never read from a local clock here.
72
+ */
73
+ export declare function repoDayNoticePath(home: string, repoKey: string, day: string, state: string): string;
74
+ /**
75
+ * The binding key of the client's send state — `CR-017d`, the client half of
76
+ * `CR-017`.
77
+ *
78
+ * Mirrors the server's `(org_id, repository_id, session_id, file_key)` PK
79
+ * (`vibecommit-schema` `20260809150000_cr017a_repo_binding_key.sql`) at the two
80
+ * dimensions a client has: there is no client-side `org_id` (the server resolves
81
+ * it from the credential) and `file_key` is already the key of
82
+ * `SessionState.files`. `repoKey` leads `sessionId` for the reason `CR-017a`
83
+ * puts `repository_id` second — the repo identity is derived, the session id is
84
+ * whatever Claude Code handed us.
85
+ *
86
+ * An object rather than two positional strings: both fields are strings, so a
87
+ * transposed pair would compile silently and re-open the exact bleed this key
88
+ * exists to close.
89
+ */
90
+ export interface SessionKey {
91
+ /** `resolveProjectKey()`'s git toplevel. REQUIRED — never empty, never a fallback. */
92
+ readonly repoKey: string;
93
+ readonly sessionId: string;
94
+ }
95
+ /**
96
+ * Per-`(repo, session)` send state — `CR-018`'s offset ledger, backlog and
97
+ * terminal stop, keyed by `CR-017d`.
98
+ *
99
+ * **Returns null when there is no repo identity, and that is the fix.** D58: *"The
100
+ * server key gains repo; the client offset state did not, so a `prefix_sha256`
101
+ * resync would duplicate repo 1's turns into repo 2."* Keyed on the session alone
102
+ * this file was machine-wide, so one Claude Code session whose `cwd` moved between
103
+ * two repos read and wrote the same offsets for both. A caller with no repo must
104
+ * therefore get NO path at all rather than a shared bucket — a placeholder key is
105
+ * the bleed with an extra step (D72's "required from day one", mirrored).
106
+ *
107
+ * The repo is a SHA-256 digest and not the sanitised path: `sanitise` truncates at
108
+ * 128 characters, and two work trees sharing a long prefix would land in one
109
+ * directory — a silent collision at exactly the boundary being defended.
110
+ *
111
+ * Still one file per session rather than one per `(session, file)`, because `seq`
112
+ * is monotonic per SESSION across the main transcript and every sub-agent's, so
113
+ * splitting the store would split the counter that has to stay single.
114
+ */
115
+ export declare function sessionStatePath(home: string, key: SessionKey): string | null;
116
+ /**
117
+ * Every session file for ONE repo — the directory `sessionStatePath` writes into.
118
+ *
119
+ * `status` needs this and a hook does not: a hook is handed a `session_id` on
120
+ * stdin, but `vibecommit status` is interactive and has none, so the only way it
121
+ * can answer "when did this repo last send?" is to look at every session under
122
+ * the repo (`CR-021`). Exposed rather than duplicated so there is still exactly
123
+ * one place that knows the layout, and the digest stays private.
124
+ *
125
+ * Null on no repo, for the same reason `sessionStatePath` is: there is no
126
+ * repo-less bucket, and a caller with no repo has nothing to scan.
127
+ */
128
+ export declare function repoSessionsDir(home: string, repoKey: string): string | null;
129
+ /**
130
+ * Where Claude Code writes transcripts.
131
+ *
132
+ * `CLAUDE_CONFIG_DIR` is honoured because Claude Code honours it; hard-coding
133
+ * `~/.claude` would silently capture nothing for anyone who relocated it.
134
+ */
135
+ export declare function transcriptRoot(home: string, env: NodeJS.ProcessEnv): string;
136
+ /**
137
+ * Where Codex CLI writes transcripts — `CR-183`, D164.
138
+ *
139
+ * BESIDE `transcriptRoot` rather than inside it, because the two roots are two
140
+ * facts about two products: `CODEX_HOME ?? ~/.codex` plus `sessions`, against
141
+ * `CLAUDE_CONFIG_DIR ?? ~/.claude` plus `projects`. Folding them into one
142
+ * function taking a discriminant would make the union below read as a branch on
143
+ * a flag rather than as an enumeration of the registry, which is the property
144
+ * the confinement check depends on.
145
+ *
146
+ * ⚠ **The env var is honoured on the same evidence the Claude one is: because
147
+ * the product honours it.** Hard-coding `~/.codex` would silently capture
148
+ * nothing for anyone who relocated it.
149
+ */
150
+ export declare function codexTranscriptRoot(home: string, env: NodeJS.ProcessEnv): string;
151
+ /**
152
+ * Where Cursor writes transcripts — `CR-193`, D177 §2.
153
+ *
154
+ * ⛔ **`projects`, and NOT the `agent-transcripts` directory D177 §2 names —
155
+ * because that root IS NOT EXPRESSIBLE HERE, and the reason is measured rather
156
+ * than argued.**
157
+ *
158
+ * One conversation's transcript lives at
159
+ * `~/.cursor/projects/<munged>/agent-transcripts/<cid>/<cid>.jsonl`, where
160
+ * `<munged>` is the WORKSPACE fsPath with every non-alphanumeric run collapsed
161
+ * to `-`. A `transcriptRoot` member is handed `home` and `env` and nothing else,
162
+ * and `<munged>` is derivable from neither: this machine already carries two of
163
+ * them, `Users-acohan-Documents-VibeCommit` and `empty-window`.
164
+ *
165
+ * ⚠ **And the obvious rescue — derive it from the consented project key — is
166
+ * measured WRONG, not merely awkward.** `resolveProjectKey` is
167
+ * `git rev-parse --show-toplevel`, and the workspace that produced the D177 §10
168
+ * corpus, `/Users/acohan/Documents/VibeCommit`, is **not a git repository at
169
+ * all**. The repository that IS one sits a directory below it, so its project
170
+ * key munges to that workspace's name with the child directory appended — a
171
+ * name with no directory under `projects/` — and a project-key-derived root
172
+ * would refuse the one real transcript there is.
173
+ *
174
+ * ⇒ ⛔ **The interior shape is enforced by `isCursorTranscript` instead**, which
175
+ * runs AFTER the registry-computed boundary and can only refuse. Read that
176
+ * function's docblock for what this changes about D177 §2's stated reason: it
177
+ * inverts it.
178
+ *
179
+ * ⚠ **No environment override, and that is an absence rather than an
180
+ * oversight.** `CLAUDE_CONFIG_DIR` and `CODEX_HOME` are honoured because those
181
+ * products honour them. No Cursor equivalent has been measured, and inventing
182
+ * one would be a configuration surface this client reads and nothing writes.
183
+ */
184
+ export declare function cursorTranscriptRoot(home: string): string;
185
+ /**
186
+ * ⛔ **THE `.jsonl` GATE — `CR-193`, D177 §2**, which supersedes `CR-183`'s
187
+ * exclusion of `transcriptExtension`.
188
+ *
189
+ * The lane's file gate: `transport: "ndjson"` says every member appends NDJSON
190
+ * to a file, and `.jsonl` is what that file is called. Confinement answers
191
+ * *"may this path be read at all"*; this answers *"is this a transcript"*, and
192
+ * the two are different questions — a root can be exactly right and still
193
+ * contain files that are not transcripts.
194
+ *
195
+ * ⚠ **`root` is unused HERE and is a parameter of the member, because Cursor's
196
+ * answer needs it.** Under Claude's and Codex's roots every file is a
197
+ * transcript, so the suffix is the whole test; under Cursor's it is not.
198
+ *
199
+ * ⚠ **No `realpath`, deliberately, and only here.** A symlink whose name ends
200
+ * `.jsonl` and whose target does not is a real shape, and `isCursorTranscript`
201
+ * resolves for exactly that reason — but under these two roots there is nothing
202
+ * to point at: every file under them is a transcript already. Paying two
203
+ * syscalls per hook to defend a set with no members is the cost this note exists
204
+ * to decline.
205
+ */
206
+ export declare function isTranscriptFile(_root: string, path: string): boolean;
207
+ /**
208
+ * The lane's gate PLUS the interior shape Cursor's root cannot declare —
209
+ * `CR-193`, D177 §2.
210
+ *
211
+ * ⛔ **AND THIS IS WHERE D177 §2's STATED REASON INVERTS. Say it in these
212
+ * words, because the polarity is the whole point.** §2 records that the ROOT is
213
+ * what excludes the 32 KB scraped-web-content `.txt`, and that the gate
214
+ * therefore ships as fail-closed defence with **no corpus witness** —
215
+ * `agent-tools/` being a SIBLING of `agent-transcripts/`. That is true of the
216
+ * root §2 names. It is **false of the root that is expressible** (see
217
+ * `cursorTranscriptRoot`): rooted at `projects`, `find -type f` returns **113
218
+ * files, exactly 1 of them the transcript** — 66 `.json`, 34 `.ts`, 8 `.md`,
219
+ * 3 extensionless, 1 `.txt`. ⇒ ⛔ **the gate has 112 corpus witnesses and IS
220
+ * what excludes the `.txt`.** It is not defence in depth here; it is the
221
+ * mechanism.
222
+ *
223
+ * ⚠ **THOSE FIGURES ARE A CORRECTION.** This docblock first said 112/111. The
224
+ * total was summed from a per-extension HISTOGRAM rather than read from
225
+ * `find … | wc -l`, and the histogram's own `.jsonl` row was dropped in the
226
+ * addition — `feedback_bounded_output_is_not_a_count`, on the very measurement
227
+ * that motivates this function. Re-measured three ways that must agree:
228
+ * `-type f` = 113, `-name '*.jsonl'` = 1, `-not -name '*.jsonl'` = 112.
229
+ *
230
+ * ⛔ **The segment test is what puts D177 §2's boundary back.** A `.jsonl`
231
+ * anywhere under `projects` would otherwise be admitted for upload, and
232
+ * `agent-tools/` is precisely where Cursor spills whatever the agent
233
+ * downloaded. Requiring `<root>/<workspace>/agent-transcripts/…` reconstructs
234
+ * the root §2 asked for, as a narrowing applied AFTER the registry-computed
235
+ * boundary — so it can only refuse, never widen (D177 §7's direction test).
236
+ *
237
+ * ⚠ **`M0.3` measured ZERO non-`.jsonl` files INSIDE `agent-transcripts/`** (the
238
+ * whole directory holds one file). So the suffix half of this test still has no
239
+ * witness under the segment half, and absence from a corpus is not absence from
240
+ * the product — `D172 §2`'s `notebook_path` reasoning, unchanged.
241
+ *
242
+ * ⛔ **Resolved, because the segment names are the thing being trusted.**
243
+ * `isInside` already resolves both sides, so a path claiming to be under this
244
+ * root cannot be outside it; what a string test would still miss is a symlink
245
+ * INSIDE the root — `…/agent-transcripts/x.jsonl` pointing at
246
+ * `…/agent-tools/scrape.txt`, which passes confinement on the real path and
247
+ * passes a naive segment test on the claimed one. `isInside` is reused rather
248
+ * than re-implemented (`CR-183`); the resolution below exists only to locate the
249
+ * segments within it.
250
+ */
251
+ export declare function isCursorTranscript(root: string, path: string): boolean;
252
+ /**
253
+ * Where Claude Code writes DELEGATED work — `CR-124`, D76.
254
+ *
255
+ * `<dir>/<session-id>/subagents/`, beside `<dir>/<session-id>.jsonl`. Derived
256
+ * from `transcript_path` because there is no environment variable for it and
257
+ * the hook is never handed one: the session id is the transcript's own basename.
258
+ *
259
+ * Measured on this machine rather than read from documentation — and the
260
+ * measurement carries a trap. The directory holds an `agent-<id>.meta.json`
261
+ * sidecar beside every `agent-<id>.jsonl`, **636 of each**, exactly 1:1. A
262
+ * `agent-*` glob would therefore double every upload and send metadata as if it
263
+ * were transcript content. The caller globs `agent-*.jsonl`.
264
+ *
265
+ * Returns null for a path that is not a `.jsonl`, rather than inventing a
266
+ * directory next to something that was never a transcript.
267
+ */
268
+ export declare function subagentsDir(transcriptPath: string): string | null;
269
+ /**
270
+ * The `file_key` a sub-agent transcript travels under: its basename, minus
271
+ * `.jsonl`.
272
+ *
273
+ * From the FILENAME, deliberately. Every record inside also carries `agentId`,
274
+ * but reading a value out of a file to name that file is analysis this client is
275
+ * not licensed to do (D60 §D6 / §D1a) and does not need.
276
+ *
277
+ * Null when the key would breach the server's 1..128 bound
278
+ * (`ingest_sessions.file_key`'s CHECK, `CR-008`): a delta the server 400s is
279
+ * classed `never` and would advance the offset past bytes it never stored.
280
+ * Longest key measured on this machine is 49 characters, so this is a bound
281
+ * being respected rather than one being hit.
282
+ */
283
+ export declare function subagentFileKey(fileName: string): string | null;
284
+ /**
285
+ * The `file_key` an ANNOUNCED sub-agent transcript travels under — `CR-196`,
286
+ * D177 §9. ⛔ **`agent-<childId>`, SYNTHESIZED HERE, CLIENT-SIDE.**
287
+ *
288
+ * ## ⛔ Why this sits BESIDE `subagentFileKey` and not inside it
289
+ *
290
+ * They are opposite jobs on opposite layouts, and one function doing both would
291
+ * be strictly worse at each:
292
+ *
293
+ * `subagentFileKey` a FILTER. Claude's `subagents/` directory is globbed, so
294
+ * the caller sees `agent-<id>.jsonl`, `agent-<id>.meta.json`
295
+ * and anything else that is in there — and the prefix test
296
+ * is what REFUSES the sidecar and `main.jsonl`. Relaxing it
297
+ * to "mint a prefix if one is missing" would make the
298
+ * discover path accept every one of those.
299
+ * this a CONSTRUCTOR. Cursor's children are `<childId>.jsonl`
300
+ * with **no prefix at all**, and the path was HANDED over
301
+ * rather than found, so there is nothing to filter — the
302
+ * only question is what to call the stream.
303
+ *
304
+ * ## ⛔ The key stays FILENAME-derived, which is what keeps D60 §D6 intact
305
+ *
306
+ * `<childId>` is the announced path's BASENAME. It is not read out of any
307
+ * record inside the file, so *"do not read a value out of a file to name that
308
+ * file"* still holds — the same rule `subagentFileKey`'s own docblock states
309
+ * about `agentId`. What arrives on stdin is a PATH; this reads its name.
310
+ *
311
+ * ## ⛔ WIDENING `ingest_sessions.file_key` WAS CONSIDERED AND DECLINED (D177 §9)
312
+ *
313
+ * Five independent layers refuse a bare `<childId>`, and a DB-only widening
314
+ * would not have worked at all: `subagentFileKey`'s own prefix test, `post.ts`'s
315
+ * `FileKey` type, `cr008`'s CHECK (whose C5/C6/C7 cells pin more than the CHECK
316
+ * line shows), the server's family check — where a 400 classes `never`, advances
317
+ * the offset and stamps a PERMANENT gap — and the health classifier, which
318
+ * would silently reclassify every sub-agent stream as a MAIN stream. Synthesis
319
+ * touches none of them: no migration, no production deploy.
320
+ *
321
+ * ## The prefix is added UNCONDITIONALLY, and that is what makes it injective
322
+ *
323
+ * A child literally named `agent-x.jsonl` becomes `agent-agent-x`, which is
324
+ * ugly and is the only answer that cannot collide: `x` → `agent-x` and
325
+ * `agent-x` → `agent-agent-x` stay distinct, where "prefix only if missing"
326
+ * maps both onto `agent-x` — two streams sharing one offset ledger.
327
+ *
328
+ * Null when the key would breach the server's 1..128 bound, exactly as
329
+ * `subagentFileKey` is, and null for an empty `<childId>`: a bare `agent-` is
330
+ * REJECTED by `cr008`'s CHECK (`like 'agent-_%'`), and its C5 cell pins that.
331
+ */
332
+ export declare function announcedSubagentFileKey(fileName: string): string | null;
333
+ /**
334
+ * Is `candidate` really inside `root`?
335
+ *
336
+ * `/cso` finding 1 (2026-08-09), HIGH. `transcript_path` arrives on hook stdin
337
+ * and was read and uploaded verbatim under the user's ingest credential, with no
338
+ * check that it was a transcript at all. Anything that could put a hook
339
+ * definition in front of Claude Code could therefore point the client at
340
+ * `~/.ssh/id_rsa` and have it shipped off the machine — and the hook contract's
341
+ * exit-0 silence meant nothing would show on any stream. The silence that makes
342
+ * the hook safe for the developer's turn is what made that quiet.
343
+ *
344
+ * `realpathSync` on BOTH sides, so a symlink cannot walk out of the root: a
345
+ * string prefix test on the unresolved path is exactly the check that misses
346
+ * `~/.claude/projects/x -> /etc`. A missing path resolves to "outside", because
347
+ * a transcript we cannot resolve is not one we should read.
348
+ */
349
+ export declare function isInside(root: string, candidate: string): boolean;
350
+ /**
351
+ * Is `candidate` inside ANY of `roots`? — `CR-183`, D164 §5.
352
+ *
353
+ * ⛔ **A WRAPPER, and `isInside` above is byte-unchanged.** The union is a
354
+ * second agent's root being added to the SET the check runs over; it is not a
355
+ * loosening of what "inside" means, and rewriting the resolved-path comparison
356
+ * to take a list would have put the `/cso` finding-1 remediation back on the
357
+ * table for a change that is about membership.
358
+ *
359
+ * ⛔ **`roots` comes from the REGISTRY. It must never be derived from input.**
360
+ * A caller that built this list out of anything on hook stdin would have handed
361
+ * the confinement boundary to the value the boundary exists to constrain.
362
+ *
363
+ * Empty `roots` refuses everything, which is the right direction: no known root
364
+ * means nothing is known to be inside one.
365
+ */
366
+ export declare function isInsideAny(roots: readonly string[], candidate: string): boolean;