@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/paths.d.ts
ADDED
|
@@ -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;
|