@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VibeCommit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @vibe-commit/capture
2
+
3
+ The VibeCommit capture client. Records Claude Code sessions against the commits they produced.
4
+
5
+ ```
6
+ npm install -g @vibe-commit/capture
7
+ vibecommit connect
8
+ ```
9
+
10
+ MIT licensed. This is the client that runs on your machine.
11
+ See [what we upload](https://vibecommit.ai/docs/what-we-upload).
12
+
13
+ <!--
14
+ The sentence that used to sit here claimed this client was open source so you could read
15
+ exactly what leaves it. That is FALSE as of D64 (2026-08-09): the repo is private, with the
16
+ MIT licence intact. The claim is BLOCKED by `docs/claims-register.md`, and this README ships
17
+ inside the npm tarball, so it would have carried the claim to the registry on first publish.
18
+ It is DELETED here rather than replaced — `CR-111` owns the replacement trust argument, and
19
+ the approved direction points at `Vibe-Commit/claude-plugin`, which vendors the exact binary.
20
+ "Inspect the build" is WEAKER than "read the source" and must not be dressed up as it.
21
+ -->
22
+
23
+
24
+ ## Commands
25
+
26
+ | Command | What it does |
27
+ |---|---|
28
+ | `vibecommit connect` | Connect this repo and capture the current session. |
29
+ | `vibecommit status` | Show whether capture is on for this repo and when it last succeeded. |
30
+ | `vibecommit off` | Stop capturing for this repo. |
31
+ | `vibecommit why <file>:<line>` | Show the conversation turns recorded against the commit that last changed this line. |
32
+ | `vibecommit report --since <when>` | Summarise capture coverage over a time window. |
33
+
34
+ ## Status
35
+
36
+ **Implemented**, not a scaffold. All five commands above have real bodies — OAuth sign-in
37
+ and credential storage, git inspection, redaction, and the Claude Code hook contract — backed
38
+ by an extensive test suite. Beyond the five interactive commands, this binary also answers a
39
+ hidden `hook` invocation (dispatched outside `--help`, since no one types it) for Claude Code's
40
+ `Stop`, `PreCompact`, and `SessionEnd` events; that hook mode is what
41
+ [`Vibe-Commit/claude-plugin`](https://github.com/Vibe-Commit/claude-plugin) vendors and wires up
42
+ by default when you install the Claude Code plugin instead of this package directly.
43
+
44
+ What is established, and what future changes must not undo:
45
+
46
+ - **All user-facing copy lives in `src/copy/`.** No command body contains an inline user-facing
47
+ string literal. A claims gate can only check copy it can find.
48
+ - **One commit-header renderer**, in `src/copy/grades.ts`, which takes the edge grade. Evidence
49
+ qualifiers are adjacent to the claim by construction rather than by review.
50
+ - **The exit-code contract is split** (`src/exit.ts`): hook invocations always exit 0 and stay
51
+ silent; interactive verbs get normal CLI semantics.
52
+
53
+ ## Development
54
+
55
+ ```
56
+ npm install
57
+ npm test # node --test --import tsx
58
+ npm run typecheck
59
+ npm run build
60
+ ```
61
+
62
+ Node >= 22.15.0.
63
+
64
+ ## License
65
+
66
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The `claude-code` dialect — `CR-183`.
3
+ *
4
+ * ⛔ **NOTHING ABOUT CLAUDE CODE'S BEHAVIOUR CHANGES HERE.** Every value below is
5
+ * the value the client already used, moved to where a second agent can be
6
+ * described beside it. The one function member, `locateCurrentTranscript`, is
7
+ * `commands/connect.ts`'s own `findCurrentTranscript` verbatim except for the
8
+ * confinement call, which now runs against the registry's union.
9
+ */
10
+ import type { TranscriptDialect } from "./types.js";
11
+ export declare const CLAUDE_CODE: TranscriptDialect;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * The `claude-code` dialect — `CR-183`.
3
+ *
4
+ * ⛔ **NOTHING ABOUT CLAUDE CODE'S BEHAVIOUR CHANGES HERE.** Every value below is
5
+ * the value the client already used, moved to where a second agent can be
6
+ * described beside it. The one function member, `locateCurrentTranscript`, is
7
+ * `commands/connect.ts`'s own `findCurrentTranscript` verbatim except for the
8
+ * confinement call, which now runs against the registry's union.
9
+ */
10
+ import { readdirSync, statSync } from "node:fs";
11
+ import { basename, join } from "node:path";
12
+ import { isInside, isTranscriptFile, transcriptRoot } from "../paths.js";
13
+ /**
14
+ * The client-side backstop, per event.
15
+ *
16
+ * ⛔ **This is `hooks/entry.ts`'s `DEFAULT_HOOK_BUDGET_MS`, and the constant does
17
+ * not move.** It is restated as a literal here for one reason: `entry.ts`
18
+ * imports this registry, so importing the constant back would close an import
19
+ * cycle whose failure mode is a temporal-dead-zone `undefined` at module load —
20
+ * a budget of `NaN`, silently. `test/agent-registry.test.ts` asserts the two
21
+ * agree, which is the coupling the import would have provided.
22
+ */
23
+ const CLAUDE_CLIENT_BUDGET_MS = 5_000;
24
+ /**
25
+ * What `claude-plugin/hooks/hooks.json` registers, in MILLISECONDS.
26
+ *
27
+ * ⚠ **That file is hand-written in another repository and nothing generates it
28
+ * from here**, so these three numbers are a MIRROR, not a source. They are
29
+ * pinned as literals so the `clientBudgetMs < registeredTimeoutMs` relation can
30
+ * be asserted at all — see `test/agent-registry.test.ts`, which also states what
31
+ * the assertion cannot see: a drift in the file itself.
32
+ *
33
+ * Its own timeouts are in SECONDS (8, 8, 10). Every value is above the client
34
+ * budget above, which is the relation that lets the client's watchdog fire
35
+ * first; below that line the agent kills the process and the exit-0 contract
36
+ * goes with it.
37
+ */
38
+ const CLAUDE_REGISTERED_STOP_MS = 8_000;
39
+ const CLAUDE_REGISTERED_PRECOMPACT_MS = 8_000;
40
+ const CLAUDE_REGISTERED_SESSION_END_MS = 10_000;
41
+ /**
42
+ * The transcript for the session running right now.
43
+ *
44
+ * A hook is HANDED `transcript_path` on stdin. `connect` is typed by a human and
45
+ * gets nothing, so it has to find one: Claude Code stores transcripts at
46
+ * `<transcriptRoot>/<encoded-project-path>/<session-id>.jsonl`, where the
47
+ * encoding replaces `/` and `.` with `-`. That was derived by comparing every
48
+ * directory on this machine against the `cwd` its transcripts record — 15 of 17
49
+ * matched exactly, and both misses were sessions whose `cwd` MOVED mid-session,
50
+ * not a different encoding.
51
+ *
52
+ * Which is also why this looks up the directory for THIS project rather than
53
+ * taking the newest transcript anywhere: a session that started in another
54
+ * project and `cd`'d here lives under that project's directory, and uploading it
55
+ * would attach one project's session to another's repo — the same confusion
56
+ * `CR-017d`'s key exists to prevent.
57
+ *
58
+ * **Confined.** The result is gated, because this is a reader of transcript
59
+ * bytes and `/cso` finding 1 is about exactly this class of read. A candidate
60
+ * that resolves outside the root is not returned.
61
+ *
62
+ * ⛔ **Against THIS DIALECT'S OWN ROOT, and not against the registry union —
63
+ * which is a deliberate difference from the two hook call sites.** The union
64
+ * exists for a path that ARRIVES from outside, where which agent produced it is
65
+ * not known. This function BUILDS its candidates by joining its own root, so
66
+ * every other root in the registry is unreachable here by construction and
67
+ * admitting them would be a widening that buys nothing. (It is also what keeps
68
+ * this module out of an import cycle with the registry, whose failure mode is a
69
+ * temporal-dead-zone `undefined` at module load rather than an error.)
70
+ */
71
+ function locateCurrentTranscript(home, env, projectKey) {
72
+ const root = transcriptRoot(home, env);
73
+ const dir = join(root, projectKey.replace(/[/.]/g, "-"));
74
+ let entries;
75
+ try {
76
+ entries = readdirSync(dir);
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ let best = null;
82
+ for (const entry of entries) {
83
+ if (!entry.endsWith(".jsonl"))
84
+ continue;
85
+ const path = join(dir, entry);
86
+ if (!isInside(root, path))
87
+ continue;
88
+ let stat;
89
+ try {
90
+ stat = statSync(path);
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ if (!stat.isFile() || stat.size === 0)
96
+ continue;
97
+ if (best === null || stat.mtimeMs > best.mtime) {
98
+ best = {
99
+ path,
100
+ sessionId: basename(entry, ".jsonl"),
101
+ size: stat.size,
102
+ mtime: stat.mtimeMs,
103
+ };
104
+ }
105
+ }
106
+ return best === null ? null : { path: best.path, sessionId: best.sessionId, size: best.size };
107
+ }
108
+ /**
109
+ * ⛔ **MEASURED (`M1`), 2026-08-21, BOTH ARMS.**
110
+ *
111
+ * Claude Code exports `CLAUDE_CODE_SESSION_ID` into the Bash tool's environment
112
+ * and git passes it through to `post-commit`:
113
+ *
114
+ * - agent commit → the uuid, **byte-identical to the transcript filename**;
115
+ * - human commit, the variable stripped → `<UNSET>`, so `null` here;
116
+ * - commit from a linked worktree → **the same uuid**.
117
+ *
118
+ * That third row is why this is worth a member: the value survives the worktree
119
+ * boundary that the mtime heuristic in `spool.ts` cannot see across.
120
+ *
121
+ * ⚠ **`null` on empty or whitespace, not `""`.** An exported-but-empty variable
122
+ * is the shell's idea of unset, and a `""` session id would key a spool bucket
123
+ * every unset process shares.
124
+ */
125
+ function claudeSessionIdFromEnv(env) {
126
+ const raw = env.CLAUDE_CODE_SESSION_ID;
127
+ if (raw === undefined)
128
+ return null;
129
+ const value = raw.trim();
130
+ return value === "" ? null : value;
131
+ }
132
+ export const CLAUDE_CODE = {
133
+ id: "claude-code",
134
+ transport: "ndjson",
135
+ transcriptRoot,
136
+ // ⛔ `CR-193`, D177 §2 — the lane's `.jsonl` gate, and NOTHING MORE under this
137
+ // root. `<projects>/<encoded-project-path>/` holds `<session-id>.jsonl` and a
138
+ // `subagents/` directory, so every file here is already a transcript; there is
139
+ // no interior shape to require, unlike Cursor's.
140
+ admitsFile: isTranscriptFile,
141
+ events: {
142
+ Stop: {
143
+ registeredTimeoutMs: CLAUDE_REGISTERED_STOP_MS,
144
+ clientBudgetMs: CLAUDE_CLIENT_BUDGET_MS,
145
+ },
146
+ PreCompact: {
147
+ registeredTimeoutMs: CLAUDE_REGISTERED_PRECOMPACT_MS,
148
+ clientBudgetMs: CLAUDE_CLIENT_BUDGET_MS,
149
+ },
150
+ SessionEnd: {
151
+ registeredTimeoutMs: CLAUDE_REGISTERED_SESSION_END_MS,
152
+ clientBudgetMs: CLAUDE_CLIENT_BUDGET_MS,
153
+ },
154
+ },
155
+ // `<session>/subagents/agent-*.jsonl`, globbed — the paths are never handed
156
+ // over. `CR-124`, D76.
157
+ delegatedTranscripts: "discover",
158
+ redaction: "claude-code",
159
+ sessionIdFromEnv: claudeSessionIdFromEnv,
160
+ locateCurrentTranscript,
161
+ };
162
+ //# sourceMappingURL=claude_code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_code.js","sourceRoot":"","sources":["../../src/agents/claude_code.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The `codex-cli` dialect — `CR-183`, D164.
3
+ *
4
+ * ⚠ **DATA, described here; the readers land in later tasks of this wave.** What
5
+ * this entry does today is put Codex's transcript root into the confinement
6
+ * union and give `connect` a truthful answer about having no locator. The
7
+ * scrubber it names is `CR-185`'s, and the delegated-transcript mechanism it
8
+ * declares has no reader at all yet (see `DelegatedTranscripts`).
9
+ *
10
+ * ⛔ **Every number and mechanism below is CARRIED FROM THE TASK BRIEF, not
11
+ * measured by this task.** They are recorded as the brief stated them so a later
12
+ * reader can check them against the product rather than against this file.
13
+ */
14
+ import type { TranscriptDialect } from "./types.js";
15
+ export declare const CODEX_CLI: TranscriptDialect;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The `codex-cli` dialect — `CR-183`, D164.
3
+ *
4
+ * ⚠ **DATA, described here; the readers land in later tasks of this wave.** What
5
+ * this entry does today is put Codex's transcript root into the confinement
6
+ * union and give `connect` a truthful answer about having no locator. The
7
+ * scrubber it names is `CR-185`'s, and the delegated-transcript mechanism it
8
+ * declares has no reader at all yet (see `DelegatedTranscripts`).
9
+ *
10
+ * ⛔ **Every number and mechanism below is CARRIED FROM THE TASK BRIEF, not
11
+ * measured by this task.** They are recorded as the brief stated them so a later
12
+ * reader can check them against the product rather than against this file.
13
+ */
14
+ import { codexTranscriptRoot, isTranscriptFile } from "../paths.js";
15
+ /**
16
+ * `SessionEnd` is the tight one — 3 s registered, 2.2 s client.
17
+ *
18
+ * That cap is why `CR-182`'s gap-stamp reserve exists at all: at 2200 ms a slow
19
+ * send routinely consumed the whole budget and the honest hole went unrecorded,
20
+ * which is the one outcome D56 §D8 says this client may not produce.
21
+ */
22
+ const CODEX_SESSION_END_REGISTERED_MS = 3_000;
23
+ const CODEX_SESSION_END_CLIENT_MS = 2_200;
24
+ /** `Stop` and `PreCompact` are registered far wider — 30 s, client 8 s. */
25
+ const CODEX_TURN_REGISTERED_MS = 30_000;
26
+ const CODEX_TURN_CLIENT_MS = 8_000;
27
+ export const CODEX_CLI = {
28
+ id: "codex-cli",
29
+ transport: "ndjson",
30
+ transcriptRoot: codexTranscriptRoot,
31
+ // ⛔ `CR-193`, D177 §2 — the lane's `.jsonl` gate. `~/.codex/sessions` is
32
+ // date-nested rollout transcripts and nothing else, so there is no interior
33
+ // shape to require here either. ⚠ CARRIED FROM THE BRIEF like the numbers
34
+ // above: this task did not re-walk a Codex tree to confirm it.
35
+ admitsFile: isTranscriptFile,
36
+ events: {
37
+ Stop: {
38
+ registeredTimeoutMs: CODEX_TURN_REGISTERED_MS,
39
+ clientBudgetMs: CODEX_TURN_CLIENT_MS,
40
+ },
41
+ PreCompact: {
42
+ registeredTimeoutMs: CODEX_TURN_REGISTERED_MS,
43
+ clientBudgetMs: CODEX_TURN_CLIENT_MS,
44
+ },
45
+ SessionEnd: {
46
+ registeredTimeoutMs: CODEX_SESSION_END_REGISTERED_MS,
47
+ clientBudgetMs: CODEX_SESSION_END_CLIENT_MS,
48
+ },
49
+ },
50
+ // The delegated path ARRIVES on stdin as `agent_transcript_path` — there is no
51
+ // directory to glob. Nothing parses it yet; the discriminator is here so the
52
+ // Claude glob cannot run against a layout that has no `subagents/`.
53
+ delegatedTranscripts: "announce",
54
+ redaction: "codex-cli",
55
+ // ⛔ **UNMEASURED.** `null` here does NOT mean "Codex CLI has no session id in
56
+ // its environment" — it means nobody has run the measurement `M1` ran for
57
+ // Claude Code (agent commit, human commit, worktree commit) against Codex, so
58
+ // this client has no variable it can honestly name. ⚠ Read it as the absence
59
+ // of a measurement, not as a fact about the agent. Measuring it is a change to
60
+ // this line and nothing else.
61
+ sessionIdFromEnv: () => null,
62
+ // ⛔ NO `locateCurrentTranscript`, and the omission is the decision. Finding
63
+ // the repository would mean reading a `cwd` out of the transcript's own
64
+ // records — ANALYSIS, which D60 §D6 forbids this client. `connect` therefore
65
+ // skips the test-capture beat rather than guessing.
66
+ };
67
+ //# sourceMappingURL=codex_cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex_cli.js","sourceRoot":"","sources":["../../src/agents/codex_cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpE;;;;;;GAMG;AACH,MAAM,+BAA+B,GAAG,KAAK,CAAC;AAC9C,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAE1C,2EAA2E;AAC3E,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC,MAAM,CAAC,MAAM,SAAS,GAAsB;IAC1C,EAAE,EAAE,WAAW;IACf,SAAS,EAAE,QAAQ;IACnB,cAAc,EAAE,mBAAmB;IACnC,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,+DAA+D;IAC/D,UAAU,EAAE,gBAAgB;IAC5B,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,mBAAmB,EAAE,wBAAwB;YAC7C,cAAc,EAAE,oBAAoB;SACrC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,wBAAwB;YAC7C,cAAc,EAAE,oBAAoB;SACrC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,+BAA+B;YACpD,cAAc,EAAE,2BAA2B;SAC5C;KACF;IACD,+EAA+E;IAC/E,6EAA6E;IAC7E,oEAAoE;IACpE,oBAAoB,EAAE,UAAU;IAChC,SAAS,EAAE,WAAW;IACtB,8EAA8E;IAC9E,0EAA0E;IAC1E,8EAA8E;IAC9E,6EAA6E;IAC7E,+EAA+E;IAC/E,8BAA8B;IAC9B,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI;IAC5B,4EAA4E;IAC5E,wEAAwE;IACxE,6EAA6E;IAC7E,oDAAoD;CACrD,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The `cursor` dialect — `CR-193`, D177.
3
+ *
4
+ * ⛔ **THE ID IS `cursor`. NEVER `cursor-ide`, never `cursor-mcp`** (D177 §1).
5
+ * The server's client-name alias map already folds both of those spellings onto
6
+ * `cursor`, and a cell over there asserts that every producible id is one of
7
+ * that map's TARGETS — so a different spelling here reds a test in another
8
+ * repository, on day one, for a reason nothing on this side would report.
9
+ * ⚠ Cited from D177 §1 rather than read: this task has no access to that tree.
10
+ *
11
+ * ⛔ **`transport: "ndjson"` DOES NOT CHANGE, and the lock-out doing its job is
12
+ * it NOT FIRING** (D177 §1). D164 §2 installed that literal type precisely so
13
+ * *"an Aider or **Cursor** dialect cannot be registered without editing the
14
+ * interface"* — and Cursor turns out to meet D164 §1's membership rule exactly:
15
+ * it appends NDJSON to a file and hands a hook its path.
16
+ *
17
+ * ⚠ **What is DATA here and what is MEASURED is not the same set**, and the
18
+ * difference is marked on each member below rather than left to a reader.
19
+ */
20
+ import type { TranscriptDialect } from "./types.js";
21
+ export declare const CURSOR: TranscriptDialect;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The `cursor` dialect — `CR-193`, D177.
3
+ *
4
+ * ⛔ **THE ID IS `cursor`. NEVER `cursor-ide`, never `cursor-mcp`** (D177 §1).
5
+ * The server's client-name alias map already folds both of those spellings onto
6
+ * `cursor`, and a cell over there asserts that every producible id is one of
7
+ * that map's TARGETS — so a different spelling here reds a test in another
8
+ * repository, on day one, for a reason nothing on this side would report.
9
+ * ⚠ Cited from D177 §1 rather than read: this task has no access to that tree.
10
+ *
11
+ * ⛔ **`transport: "ndjson"` DOES NOT CHANGE, and the lock-out doing its job is
12
+ * it NOT FIRING** (D177 §1). D164 §2 installed that literal type precisely so
13
+ * *"an Aider or **Cursor** dialect cannot be registered without editing the
14
+ * interface"* — and Cursor turns out to meet D164 §1's membership rule exactly:
15
+ * it appends NDJSON to a file and hands a hook its path.
16
+ *
17
+ * ⚠ **What is DATA here and what is MEASURED is not the same set**, and the
18
+ * difference is marked on each member below rather than left to a reader.
19
+ */
20
+ import { cursorTranscriptRoot, isCursorTranscript } from "../paths.js";
21
+ /**
22
+ * ⛔ **UNMEASURED, AND ADOPTED AS A FLOOR RATHER THAN GUESSED.**
23
+ *
24
+ * The plan's `D5` — the exact `hook_event_name` strings Cursor sends, and the
25
+ * timeouts its `~/.cursor/hooks.json` registers — is **founder-blocked and
26
+ * gates `CR-195`**, the unit that writes that file. Nothing installs a Cursor
27
+ * hook today, so no Cursor hook has ever fired and these numbers are currently
28
+ * unreachable in production.
29
+ *
30
+ * ⚠ **The direction of safety is what picks them.** Too WIDE and the agent
31
+ * kills the process before this client's watchdog fires — which loses the
32
+ * exit-0/silent-stdout contract, the one outcome DESIGN.md §13.7 forbids. Too
33
+ * TIGHT and an invocation sends less and the next one picks it up. So the
34
+ * registry's tightest existing pair is taken as a FLOOR: it holds whatever
35
+ * Cursor turns out to register, down to Codex's 3 s cap.
36
+ *
37
+ * ⛔ **This is a refusal to guess, not a measurement of Cursor**, and `CR-195`
38
+ * owns the real numbers once `D5` comes back.
39
+ */
40
+ const CURSOR_REGISTERED_MS = 3_000;
41
+ const CURSOR_CLIENT_MS = 2_200;
42
+ export const CURSOR = {
43
+ id: "cursor",
44
+ transport: "ndjson",
45
+ // ⛔ `projects`, one level ABOVE the root D177 §2 names, because `<munged>` is
46
+ // the workspace fsPath and is derivable from neither `home` nor `env`. The
47
+ // measurement that rules out deriving it from the project key, and what that
48
+ // costs, are in `cursorTranscriptRoot`'s own docblock.
49
+ transcriptRoot: cursorTranscriptRoot,
50
+ // ⛔ THE GATE D177 §2 REQUIRES TO SHIP IN THIS UNIT — `.jsonl`, plus the
51
+ // `<workspace>/agent-transcripts/` shape that puts §2's boundary back.
52
+ admitsFile: isCursorTranscript,
53
+ events: {
54
+ Stop: {
55
+ registeredTimeoutMs: CURSOR_REGISTERED_MS,
56
+ clientBudgetMs: CURSOR_CLIENT_MS,
57
+ },
58
+ PreCompact: {
59
+ registeredTimeoutMs: CURSOR_REGISTERED_MS,
60
+ clientBudgetMs: CURSOR_CLIENT_MS,
61
+ },
62
+ SessionEnd: {
63
+ registeredTimeoutMs: CURSOR_REGISTERED_MS,
64
+ clientBudgetMs: CURSOR_CLIENT_MS,
65
+ },
66
+ },
67
+ // ⛔ DECLARED EXPLICITLY, and it is load-bearing now in a way it was not
68
+ // before (D177 §2, §9). `subagentsDir()` would compute
69
+ // `<root>/<cid>/<cid>/subagents` for a Cursor transcript — ONE LEVEL TOO DEEP,
70
+ // since the real layout is `<root>/<parentId>/subagents/<childId>.jsonl` — and
71
+ // that wrong directory is inert only because `announce` short-circuits before
72
+ // it. ⚠ `CR-204` made the selection ROOT-derived rather than flag-derived, so
73
+ // a wrong declaration here is no longer a line nobody reaches.
74
+ delegatedTranscripts: "announce",
75
+ redaction: "cursor",
76
+ // ⛔ **UNMEASURED.** `null` here does NOT mean "Cursor has no session id in its
77
+ // environment" — it means nobody has run the measurement `M1` ran for Claude
78
+ // Code (agent commit, human commit, worktree commit) against Cursor, so this
79
+ // client has no variable it can honestly name. ⚠ Read it as the absence of a
80
+ // measurement, not as a fact about the agent. Measuring it is a change to this
81
+ // line and nothing else.
82
+ sessionIdFromEnv: () => null,
83
+ // ⛔ NO `locateCurrentTranscript`, and the omission is the decision — the same
84
+ // one Codex made. Finding the conversation for THIS project would mean munging
85
+ // a workspace path this client is never told, or reading a `cwd` out of the
86
+ // transcript's own records, which is ANALYSIS (D60 §D6). `connect` skips the
87
+ // test-capture beat rather than guessing.
88
+ };
89
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,oBAAoB,GAAG,KAAK,CAAC;AACnC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B,MAAM,CAAC,MAAM,MAAM,GAAsB;IACvC,EAAE,EAAE,QAAQ;IACZ,SAAS,EAAE,QAAQ;IACnB,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,uDAAuD;IACvD,cAAc,EAAE,oBAAoB;IACpC,wEAAwE;IACxE,uEAAuE;IACvE,UAAU,EAAE,kBAAkB;IAC9B,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;KACF;IACD,wEAAwE;IACxE,uDAAuD;IACvD,+EAA+E;IAC/E,+EAA+E;IAC/E,8EAA8E;IAC9E,8EAA8E;IAC9E,+DAA+D;IAC/D,oBAAoB,EAAE,UAAU;IAChC,SAAS,EAAE,QAAQ;IACnB,+EAA+E;IAC/E,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,yBAAyB;IACzB,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI;IAC5B,8EAA8E;IAC9E,+EAA+E;IAC/E,4EAA4E;IAC5E,6EAA6E;IAC7E,0CAA0C;CAC3C,CAAC"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The registry — `CR-183`, D164.
3
+ *
4
+ * The one place that knows which dialects exist. Two things are derived from it
5
+ * and from nothing else: which adapter a `--agent=` value selects, and the set
6
+ * of roots the confinement check runs over.
7
+ *
8
+ * ## ⛔ Why `--agent=` is the selector, and why two alternatives are SECURITY
9
+ * rejections rather than taste
10
+ *
11
+ * - **an environment variable** — rejected because Codex exports
12
+ * `CLAUDE_PLUGIN_ROOT` itself, so the obvious variable is set by the wrong
13
+ * agent and says the wrong thing.
14
+ * - **`hook_event_name`** — rejected because both members of this lane fire
15
+ * the same three events. It discriminates nothing.
16
+ * - ⛔ **the `transcript_path` prefix, as the PRIMARY selector** — rejected
17
+ * because that is *the attacker-influenced input `isInside` exists to
18
+ * constrain.* A path that CLAIMS to sit under a root may not be what
19
+ * decides which root we will read from.
20
+ *
21
+ * ⚠ **`CR-185` picking the SCRUBBER by the containing root is not a
22
+ * contradiction — read D164 §5 before you think it is.** Choosing a STRICTER
23
+ * scrubber because a path sits outside every known root is fail-closed;
24
+ * choosing a WIDER root because a path claims to be there is not. The
25
+ * confinement boundary is computed from this registry either way, so a wrong
26
+ * SELECTION can pick a suboptimal redaction profile and can never widen the
27
+ * boundary.
28
+ */
29
+ import { type AgentId, type SelectedAgentId, type TranscriptDialect } from "./types.js";
30
+ /**
31
+ * Every dialect, in registration order.
32
+ *
33
+ * ⛔ **The ids are D164 §1's, spelled exactly.** `test/agent-registry.test.ts`
34
+ * pins them against the literal list, because the four-way cross-repo spelling
35
+ * fails silently in the direction that matters: emit `codex_cli` and the server
36
+ * does not recognise it, the default extractor runs, and the row lands `unknown`
37
+ * permanently with no error anywhere.
38
+ */
39
+ export declare const DIALECTS: readonly TranscriptDialect[];
40
+ /**
41
+ * What an invocation with no `--agent=` is.
42
+ *
43
+ * Claude Code, because that is what every install shipped before this flag
44
+ * existed — the `.claude-plugin` bundle's command carries no `--agent=` and
45
+ * must keep working unchanged.
46
+ */
47
+ export declare const DEFAULT_AGENT_ID: AgentId;
48
+ /**
49
+ * Read the adapter id off argv.
50
+ *
51
+ * ⛔ **CALLED FROM `index.ts`, beside `invocationMode`, and NOWHERE ELSE.** The
52
+ * hook path never sees argv: `runHook` takes a `HookEnvironment`, and the id
53
+ * reaches it as a field on that object — the same seam that already injects
54
+ * `home` and `nodeVersion` so the whole path can be driven by a test. ⛔ And
55
+ * `parseHookInput` must NOT select the adapter: stdin is the input the
56
+ * confinement boundary exists to constrain, and a selector read from there is
57
+ * the rejection above with an extra step.
58
+ *
59
+ * ⚠ **Resolving an id is NOT entering hook mode**, and the difference is the
60
+ * trap this wave is most likely to ship. `invocationMode` returns `"hook"` only
61
+ * when `argv[0] === "hook"` (or a legacy env var is set), and Codex sets
62
+ * neither — so a registered command whose FIRST argument is this flag runs the
63
+ * binary INTERACTIVELY, inside a hook. ⛔ **The registered command must lead
64
+ * with the `hook` verb.** `test/agent-registry.test.ts` holds both arms.
65
+ *
66
+ * Returns the LITERAL it was given. It is deliberately not narrowed to
67
+ * `AgentId`: an id we do not recognise still has to travel, and the honest thing
68
+ * to carry is what the operator wrote.
69
+ */
70
+ export declare function resolveAgentId(argv: readonly string[]): string;
71
+ /**
72
+ * The dialect an id selects.
73
+ *
74
+ * ⛔ **AN UNKNOWN ID FALLS BACK; IT NEVER STOPS CAPTURE (plan §D7, founder
75
+ * ruling).** The client still reads, still redacts and still sends. Refusing was
76
+ * considered and rejected: a misconfigured hook that captures NOTHING is a worse
77
+ * outcome than one that captures conservatively, and the server has an honest
78
+ * label (`unknown`, D164 §1) for a producer it does not recognise — so nothing
79
+ * downstream has to guess.
80
+ *
81
+ * ⚠ **The fallback is a BEHAVIOUR choice only.** The literal the operator gave
82
+ * is not rewritten by this function; callers that put the agent on the wire read
83
+ * the id, not the dialect.
84
+ */
85
+ export declare function dialectFor(agentId: string | undefined): TranscriptDialect;
86
+ /**
87
+ * Every transcript root, deduped — ⛔ **the confinement boundary, computed from
88
+ * the REGISTRY and never from input.**
89
+ *
90
+ * A union rather than "the selected dialect's root" on purpose. The selection
91
+ * comes from a flag an install may have got wrong; the boundary must not. With
92
+ * the union, a wrong selection costs a suboptimal redaction profile (D164 §5)
93
+ * and cannot make a file readable that was not readable before.
94
+ *
95
+ * Deduped because two dialects can be pointed at one directory by environment —
96
+ * `CODEX_HOME` and `CLAUDE_CONFIG_DIR` are both operator-set — and a duplicated
97
+ * root would make `isInsideAny` do the same `realpath` pair twice for no reason.
98
+ */
99
+ export declare function transcriptRoots(home: string, env: NodeJS.ProcessEnv): readonly string[];
100
+ /**
101
+ * ⛔ **MAY THIS PATH BE READ AS A TRANSCRIPT? — confinement, THEN the file
102
+ * gate (`CR-193`, D177 §2).**
103
+ *
104
+ * Two different questions, asked in an order that cannot be swapped:
105
+ *
106
+ * 1. `isInsideAny(transcriptRoots(...))` — *may this path be read at all.*
107
+ * Registry-computed, never derived from input, byte-unchanged from
108
+ * `CR-183`. `/cso` finding 1.
109
+ * 2. the containing dialect's `admitsFile` — *is this file a transcript.* A
110
+ * root can be exactly right and still hold files that are not transcripts;
111
+ * there are 112 of them under Cursor's.
112
+ *
113
+ * ⛔ **STEP 2 CAN ONLY REFUSE.** It runs over a path step 1 already admitted, so
114
+ * every outcome is a narrowing — D177 §7's direction test, which is what makes
115
+ * this the accepted shape rather than the rejected `transcript_path`-as-boundary
116
+ * one. The boundary is still the registry's.
117
+ *
118
+ * ⚠ **The `unknown` arm keeps step 1's answer, deliberately.** A path inside two
119
+ * roots is admitted by the boundary and is `unknown` to `agentForTranscript`;
120
+ * `dialectFor` then falls back to Claude, whose gate is the plain `.jsonl` test.
121
+ * Refusing on `unknown` here would be a THIRD boundary rule, and it would refuse
122
+ * captures D177 §7 decided to keep (the header is omitted, the bytes still go).
123
+ *
124
+ * ⚠ Two root resolutions rather than one — `isInsideAny` resolves, then
125
+ * `agentForTranscript` resolves again. Measured against the alternative, which
126
+ * is folding the two into one pass by editing `agentForTranscript`: that
127
+ * function is the wire's producer oracle and `test/wire-agent.test.ts` gates it,
128
+ * so a handful of `realpath` calls inside a multi-second budget is the cheaper
129
+ * side of that trade.
130
+ */
131
+ export declare function admitsTranscript(home: string, env: NodeJS.ProcessEnv, transcriptPath: string): boolean;
132
+ /**
133
+ * ⛔ **WHICH AGENT PRODUCED THIS TRANSCRIPT — the containing registry root's,
134
+ * NEVER the `--agent=` flag's (`CR-204`, D177 §7).**
135
+ *
136
+ * ## Why the flag cannot be the answer
137
+ *
138
+ * `DEFAULT_AGENT_ID` is `claude-code` and **no install passes the flag** — the
139
+ * `.claude-plugin` bundle's command is `node "…/bin/index.js" hook`, no flag, as
140
+ * the docblock above records. So a flag-derived wire value is `claude-code` for
141
+ * every path that did not remember to set it: a FALSE PROVENANCE CLAIM,
142
+ * permanent, in the artifact whose entire worth is faithfulness. The wire is not
143
+ * a place to be wrong in a plausible direction
144
+ * (`project_plausible_answer_hazard_class`).
145
+ *
146
+ * ## ⛔ This is `CR-185`'s shape, not `CR-183`'s rejected one
147
+ *
148
+ * `rulesForTranscript` in `redact.ts` is the same three lines with `RULES` in
149
+ * place of `id`, and its own docblock carries the distinction verbatim:
150
+ *
151
+ * - choosing a **stricter scrubber** — or a LESS specific producer — because a
152
+ * path sits outside every known root is **fail-closed**;
153
+ * - choosing a **wider root** because a path *claims* to be there is not.
154
+ *
155
+ * ⛔ **The confinement boundary is untouched and still runs FIRST.** This is a
156
+ * LOOKUP AGAINST an already-computed boundary: `transcriptRoots` decides what
157
+ * may be read, `isInsideAny` refuses everything else, and only then does this
158
+ * ask which of the roots that already admitted the file names its producer. It
159
+ * can never widen anything, and its arity is 3 — ⛔ **the flag is not a
160
+ * parameter of it at all.**
161
+ *
162
+ * ## Zero or two → `unknown`, ⛔ which the wire step turns into an OMISSION
163
+ *
164
+ * Exactly one match names the agent. Two dialects can be pointed at one
165
+ * directory by environment (`CLAUDE_CONFIG_DIR` and `CODEX_HOME` are both
166
+ * operator-set and both sides resolve through `realpath`), and a path inside two
167
+ * roots is a path whose producer is not known. `unknown` is D164 §3's honest
168
+ * label for exactly that.
169
+ *
170
+ * ⛔ **It is not, however, what goes on the wire.** `buildIngestHeaders` OMITS
171
+ * `x-agent` for this answer, because the server treats an absent header and a
172
+ * present `unknown` as different facts: absent is *"nothing to say"* and leaves
173
+ * a stream's pinned agent alone, while a present `unknown` resolves and
174
+ * DISAGREES with any non-`unknown` pin — which drops the buffered tail and sheds
175
+ * the in-flight turn. Selecting `unknown` here is therefore a decision to say
176
+ * nothing, not a value to assert. `post.ts` carries the full argument.
177
+ *
178
+ * ⚠ **The zero-match arm has NO CLIENT CALLER on the hook path**, said plainly
179
+ * rather than left to be assumed (`feedback_unexercisable_branch_not_verified`):
180
+ * `isInsideAny(transcriptRoots(...))` refuses everything outside the union
181
+ * before a delta is ever built. It is a second independent line, and
182
+ * `test/agent-registry.test.ts` exercises it by calling this function directly.
183
+ */
184
+ export declare function agentForTranscript(home: string, env: NodeJS.ProcessEnv, transcriptPath: string): SelectedAgentId;