@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,212 @@
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 { isInside, isInsideAny } from "../paths.js";
30
+ import { CLAUDE_CODE } from "./claude_code.js";
31
+ import { CODEX_CLI } from "./codex_cli.js";
32
+ import { CURSOR } from "./cursor.js";
33
+ import { UNKNOWN_AGENT_ID, } from "./types.js";
34
+ /**
35
+ * Every dialect, in registration order.
36
+ *
37
+ * ⛔ **The ids are D164 §1's, spelled exactly.** `test/agent-registry.test.ts`
38
+ * pins them against the literal list, because the four-way cross-repo spelling
39
+ * fails silently in the direction that matters: emit `codex_cli` and the server
40
+ * does not recognise it, the default extractor runs, and the row lands `unknown`
41
+ * permanently with no error anywhere.
42
+ */
43
+ export const DIALECTS = [CLAUDE_CODE, CODEX_CLI, CURSOR];
44
+ /**
45
+ * What an invocation with no `--agent=` is.
46
+ *
47
+ * Claude Code, because that is what every install shipped before this flag
48
+ * existed — the `.claude-plugin` bundle's command carries no `--agent=` and
49
+ * must keep working unchanged.
50
+ */
51
+ export const DEFAULT_AGENT_ID = "claude-code";
52
+ /** The flag. ⛔ `--agent=<value>`; there is no space-separated form. */
53
+ const AGENT_FLAG = "--agent=";
54
+ /**
55
+ * Read the adapter id off argv.
56
+ *
57
+ * ⛔ **CALLED FROM `index.ts`, beside `invocationMode`, and NOWHERE ELSE.** The
58
+ * hook path never sees argv: `runHook` takes a `HookEnvironment`, and the id
59
+ * reaches it as a field on that object — the same seam that already injects
60
+ * `home` and `nodeVersion` so the whole path can be driven by a test. ⛔ And
61
+ * `parseHookInput` must NOT select the adapter: stdin is the input the
62
+ * confinement boundary exists to constrain, and a selector read from there is
63
+ * the rejection above with an extra step.
64
+ *
65
+ * ⚠ **Resolving an id is NOT entering hook mode**, and the difference is the
66
+ * trap this wave is most likely to ship. `invocationMode` returns `"hook"` only
67
+ * when `argv[0] === "hook"` (or a legacy env var is set), and Codex sets
68
+ * neither — so a registered command whose FIRST argument is this flag runs the
69
+ * binary INTERACTIVELY, inside a hook. ⛔ **The registered command must lead
70
+ * with the `hook` verb.** `test/agent-registry.test.ts` holds both arms.
71
+ *
72
+ * Returns the LITERAL it was given. It is deliberately not narrowed to
73
+ * `AgentId`: an id we do not recognise still has to travel, and the honest thing
74
+ * to carry is what the operator wrote.
75
+ */
76
+ export function resolveAgentId(argv) {
77
+ for (const arg of argv) {
78
+ if (!arg.startsWith(AGENT_FLAG))
79
+ continue;
80
+ const value = arg.slice(AGENT_FLAG.length).trim();
81
+ if (value !== "")
82
+ return value;
83
+ }
84
+ return DEFAULT_AGENT_ID;
85
+ }
86
+ /**
87
+ * The dialect an id selects.
88
+ *
89
+ * ⛔ **AN UNKNOWN ID FALLS BACK; IT NEVER STOPS CAPTURE (plan §D7, founder
90
+ * ruling).** The client still reads, still redacts and still sends. Refusing was
91
+ * considered and rejected: a misconfigured hook that captures NOTHING is a worse
92
+ * outcome than one that captures conservatively, and the server has an honest
93
+ * label (`unknown`, D164 §1) for a producer it does not recognise — so nothing
94
+ * downstream has to guess.
95
+ *
96
+ * ⚠ **The fallback is a BEHAVIOUR choice only.** The literal the operator gave
97
+ * is not rewritten by this function; callers that put the agent on the wire read
98
+ * the id, not the dialect.
99
+ */
100
+ export function dialectFor(agentId) {
101
+ return DIALECTS.find((dialect) => dialect.id === agentId) ?? CLAUDE_CODE;
102
+ }
103
+ /**
104
+ * Every transcript root, deduped — ⛔ **the confinement boundary, computed from
105
+ * the REGISTRY and never from input.**
106
+ *
107
+ * A union rather than "the selected dialect's root" on purpose. The selection
108
+ * comes from a flag an install may have got wrong; the boundary must not. With
109
+ * the union, a wrong selection costs a suboptimal redaction profile (D164 §5)
110
+ * and cannot make a file readable that was not readable before.
111
+ *
112
+ * Deduped because two dialects can be pointed at one directory by environment —
113
+ * `CODEX_HOME` and `CLAUDE_CONFIG_DIR` are both operator-set — and a duplicated
114
+ * root would make `isInsideAny` do the same `realpath` pair twice for no reason.
115
+ */
116
+ export function transcriptRoots(home, env) {
117
+ return [...new Set(DIALECTS.map((dialect) => dialect.transcriptRoot(home, env)))];
118
+ }
119
+ /**
120
+ * ⛔ **MAY THIS PATH BE READ AS A TRANSCRIPT? — confinement, THEN the file
121
+ * gate (`CR-193`, D177 §2).**
122
+ *
123
+ * Two different questions, asked in an order that cannot be swapped:
124
+ *
125
+ * 1. `isInsideAny(transcriptRoots(...))` — *may this path be read at all.*
126
+ * Registry-computed, never derived from input, byte-unchanged from
127
+ * `CR-183`. `/cso` finding 1.
128
+ * 2. the containing dialect's `admitsFile` — *is this file a transcript.* A
129
+ * root can be exactly right and still hold files that are not transcripts;
130
+ * there are 112 of them under Cursor's.
131
+ *
132
+ * ⛔ **STEP 2 CAN ONLY REFUSE.** It runs over a path step 1 already admitted, so
133
+ * every outcome is a narrowing — D177 §7's direction test, which is what makes
134
+ * this the accepted shape rather than the rejected `transcript_path`-as-boundary
135
+ * one. The boundary is still the registry's.
136
+ *
137
+ * ⚠ **The `unknown` arm keeps step 1's answer, deliberately.** A path inside two
138
+ * roots is admitted by the boundary and is `unknown` to `agentForTranscript`;
139
+ * `dialectFor` then falls back to Claude, whose gate is the plain `.jsonl` test.
140
+ * Refusing on `unknown` here would be a THIRD boundary rule, and it would refuse
141
+ * captures D177 §7 decided to keep (the header is omitted, the bytes still go).
142
+ *
143
+ * ⚠ Two root resolutions rather than one — `isInsideAny` resolves, then
144
+ * `agentForTranscript` resolves again. Measured against the alternative, which
145
+ * is folding the two into one pass by editing `agentForTranscript`: that
146
+ * function is the wire's producer oracle and `test/wire-agent.test.ts` gates it,
147
+ * so a handful of `realpath` calls inside a multi-second budget is the cheaper
148
+ * side of that trade.
149
+ */
150
+ export function admitsTranscript(home, env, transcriptPath) {
151
+ if (!isInsideAny(transcriptRoots(home, env), transcriptPath))
152
+ return false;
153
+ const dialect = dialectFor(agentForTranscript(home, env, transcriptPath));
154
+ return dialect.admitsFile(dialect.transcriptRoot(home, env), transcriptPath);
155
+ }
156
+ /**
157
+ * ⛔ **WHICH AGENT PRODUCED THIS TRANSCRIPT — the containing registry root's,
158
+ * NEVER the `--agent=` flag's (`CR-204`, D177 §7).**
159
+ *
160
+ * ## Why the flag cannot be the answer
161
+ *
162
+ * `DEFAULT_AGENT_ID` is `claude-code` and **no install passes the flag** — the
163
+ * `.claude-plugin` bundle's command is `node "…/bin/index.js" hook`, no flag, as
164
+ * the docblock above records. So a flag-derived wire value is `claude-code` for
165
+ * every path that did not remember to set it: a FALSE PROVENANCE CLAIM,
166
+ * permanent, in the artifact whose entire worth is faithfulness. The wire is not
167
+ * a place to be wrong in a plausible direction
168
+ * (`project_plausible_answer_hazard_class`).
169
+ *
170
+ * ## ⛔ This is `CR-185`'s shape, not `CR-183`'s rejected one
171
+ *
172
+ * `rulesForTranscript` in `redact.ts` is the same three lines with `RULES` in
173
+ * place of `id`, and its own docblock carries the distinction verbatim:
174
+ *
175
+ * - choosing a **stricter scrubber** — or a LESS specific producer — because a
176
+ * path sits outside every known root is **fail-closed**;
177
+ * - choosing a **wider root** because a path *claims* to be there is not.
178
+ *
179
+ * ⛔ **The confinement boundary is untouched and still runs FIRST.** This is a
180
+ * LOOKUP AGAINST an already-computed boundary: `transcriptRoots` decides what
181
+ * may be read, `isInsideAny` refuses everything else, and only then does this
182
+ * ask which of the roots that already admitted the file names its producer. It
183
+ * can never widen anything, and its arity is 3 — ⛔ **the flag is not a
184
+ * parameter of it at all.**
185
+ *
186
+ * ## Zero or two → `unknown`, ⛔ which the wire step turns into an OMISSION
187
+ *
188
+ * Exactly one match names the agent. Two dialects can be pointed at one
189
+ * directory by environment (`CLAUDE_CONFIG_DIR` and `CODEX_HOME` are both
190
+ * operator-set and both sides resolve through `realpath`), and a path inside two
191
+ * roots is a path whose producer is not known. `unknown` is D164 §3's honest
192
+ * label for exactly that.
193
+ *
194
+ * ⛔ **It is not, however, what goes on the wire.** `buildIngestHeaders` OMITS
195
+ * `x-agent` for this answer, because the server treats an absent header and a
196
+ * present `unknown` as different facts: absent is *"nothing to say"* and leaves
197
+ * a stream's pinned agent alone, while a present `unknown` resolves and
198
+ * DISAGREES with any non-`unknown` pin — which drops the buffered tail and sheds
199
+ * the in-flight turn. Selecting `unknown` here is therefore a decision to say
200
+ * nothing, not a value to assert. `post.ts` carries the full argument.
201
+ *
202
+ * ⚠ **The zero-match arm has NO CLIENT CALLER on the hook path**, said plainly
203
+ * rather than left to be assumed (`feedback_unexercisable_branch_not_verified`):
204
+ * `isInsideAny(transcriptRoots(...))` refuses everything outside the union
205
+ * before a delta is ever built. It is a second independent line, and
206
+ * `test/agent-registry.test.ts` exercises it by calling this function directly.
207
+ */
208
+ export function agentForTranscript(home, env, transcriptPath) {
209
+ const matches = DIALECTS.filter((dialect) => isInside(dialect.transcriptRoot(home, env), transcriptPath));
210
+ return matches.length === 1 ? matches[0].id : UNKNOWN_AGENT_ID;
211
+ }
212
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/agents/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,gBAAgB,GAIjB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAiC,CAAC,WAAW,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;AAEvF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAY,aAAa,CAAC;AAEvD,uEAAuE;AACvE,MAAM,UAAU,GAAG,UAAU,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,cAAc,CAAC,IAAuB;IACpD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAS;QAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,EAAE;YAAE,OAAO,KAAK,CAAC;IACjC,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,UAAU,CAAC,OAA2B;IACpD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,WAAW,CAAC;AAC3E,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,GAAsB;IAClE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,GAAsB,EACtB,cAAsB;IAEtB,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,cAAc,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3E,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;IAC1E,OAAO,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,cAAc,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,GAAsB,EACtB,cAAsB;IAEtB,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1C,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,cAAc,CAAC,CAC5D,CAAC;IACF,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC;AACjE,CAAC"}
@@ -0,0 +1,279 @@
1
+ /**
2
+ * The transcript lane, as a type.
3
+ *
4
+ * `CR-183`, D164 §1. The registry key is the transcript FORMAT, not the agent:
5
+ * Codex CLI is the second member of ONE lane, not a special case.
6
+ *
7
+ * ## The scope statement, verbatim — it is the whole membership rule
8
+ *
9
+ * > Agents that append NDJSON to a file and hand a hook its path.
10
+ *
11
+ * Read it as an exclusion, because that is the work it does. An agent whose
12
+ * transcript is Markdown, or a database, or is never handed to a hook at all, is
13
+ * NOT a member — and adding one is a different capture STRATEGY, not another
14
+ * entry in this file. `docs/architecture_refactor.md` lists eleven agents; this
15
+ * lane covers five of them after `CR-193` (D177 §1). "Config not code" is true
16
+ * WITHIN the lane and false across lanes (D164), and the type below is what
17
+ * makes that difference mechanical rather than a promise in a comment.
18
+ *
19
+ * ## Why the lock-out is `transport` and not a runtime check
20
+ *
21
+ * `transport: "ndjson"` is a LITERAL type, so a dialect for a Markdown-transcript
22
+ * agent cannot be registered without EDITING THIS INTERFACE. That is a compile
23
+ * error in a diff a human reads, rather than a config line nobody reviews.
24
+ *
25
+ * It is not decoration. D164's first ruling names the defect it prevents:
26
+ * `redact.ts` parses each line as JSON and returns any NON-JSON line UNCHANGED,
27
+ * which is correct for a partial line at a span boundary and catastrophic for a
28
+ * transcript that is not JSON at all — every line would ship unredacted.
29
+ * Redaction correctness is a property of the FORMAT, so the format is what the
30
+ * type system has to pin.
31
+ *
32
+ * ## The members are limited to what two confirmed differences justify
33
+ *
34
+ * `transcriptRoot`, `admitsFile`, `events`, `delegatedTranscripts`, `redaction`
35
+ * and the optional `locateCurrentTranscript` differ between the three members
36
+ * that exist. `id` names them and `transport` locks the lane. Nothing else is
37
+ * here, and the omissions are deliberate rather than pending: a field whose only
38
+ * member set is a guess about an agent nobody has measured is a decision made
39
+ * early with no evidence, and it will be wrong in the direction that is
40
+ * expensive to undo.
41
+ *
42
+ * ⚠ **`admitsFile` is `CR-193`'s, and it clears that bar rather than bending
43
+ * it** (D177 §2): it was one of `CR-183`'s seven speculative exclusions until a
44
+ * MEASUREMENT un-excluded it — 113 files under Cursor's root, exactly one of
45
+ * them a transcript.
46
+ */
47
+ /**
48
+ * The canonical agent ids — ⛔ **D164 §1 owns this list; this file CITES it.**
49
+ *
50
+ * ⛔ **D177 §1 widened it to FOUR:** `claude-code` | `codex-cli` | `cursor` |
51
+ * `unknown`, and four artifacts in three repos must spell them identically.
52
+ * ⚠ **Only three of the four are dialects.** `unknown` is the SERVER's honest
53
+ * label for a capture whose producer it did not recognise — the client never
54
+ * asserts it, because the client sends the literal it was given (see
55
+ * `resolveAgentId`). So a client-side `unknown` dialect would be a fourth
56
+ * spelling of a value this side never produces.
57
+ *
58
+ * ⛔ **`cursor`, NEVER `cursor-ide` and never `cursor-mcp`** (D177 §1). Not a
59
+ * style preference: `CLIENT_NAME_ALIASES` already maps both `cursor-mcp` and
60
+ * `cursor` onto `cursor`, and `agent_ids.test.ts` asserts that every producible
61
+ * id is one of that map's TARGETS — so `cursor-ide` reds a cell in another
62
+ * repository on day one.
63
+ *
64
+ * The four-way spelling is unguarded in the direction that actually fails: emit
65
+ * `codex_cli` instead of `codex-cli` and the server does not recognise it, the
66
+ * default extractor runs, and the row lands `unknown` permanently with no error
67
+ * anywhere. `test/agent-registry.test.ts` pins the literals.
68
+ */
69
+ export type AgentId = "claude-code" | "codex-cli" | "cursor";
70
+ /**
71
+ * D164 §1's third value — a producer nobody can name, never a dialect.
72
+ *
73
+ * ⛔ **AND IT NEVER TRAVELS.** The docblock above stays literally true after
74
+ * `CR-204`: this is the SELECTOR's answer for a transcript no single registry
75
+ * root contains, and `buildIngestHeaders` turns that answer into an **omitted**
76
+ * header rather than a sent one. The server distinguishes the two — an absent
77
+ * header is *"nothing to say"* and cannot disagree with the value a stream is
78
+ * already pinned to, while a present `unknown` resolves and **does** disagree,
79
+ * which drops the buffered tail and sheds the in-flight turn. See `post.ts`.
80
+ */
81
+ export declare const UNKNOWN_AGENT_ID = "unknown";
82
+ /**
83
+ * What the ROOT SELECTOR can answer — ⛔ **D164 §1's list, and nothing else.**
84
+ *
85
+ * ⚠ **Not "what travels":** two of the three do, and `unknown` is the answer
86
+ * that means *send nothing*. Named for the selection rather than the wire so
87
+ * that distinction cannot quietly rot into "the wire carries `unknown`".
88
+ *
89
+ * A separate type from `AgentId` for the same reason `RedactionProfile` is one:
90
+ * the fail-closed case has no agent id to be named after. And separate from
91
+ * `string` — which is what `resolveAgentId` returns — because that is the
92
+ * structural half of D177 §7. The `--agent=` flag's value is a `string` and is
93
+ * therefore **not assignable here**: a later edit cannot put the flag on the
94
+ * wire by mistake, only by widening this type in a diff a human reads.
95
+ */
96
+ export type SelectedAgentId = AgentId | typeof UNKNOWN_AGENT_ID;
97
+ /**
98
+ * Which scrubber a span goes through — a LABEL, not an implementation.
99
+ *
100
+ * ⛔ `CR-185` owns `src/redact.ts` and what each profile DOES. What lives here is
101
+ * only the name, because the selection has to be expressible before the
102
+ * behaviour exists.
103
+ *
104
+ * ⚠ **A separate vocabulary from `AgentId`, deliberately, even though the two
105
+ * coincide today.** D164 §5 makes the profile a property of WHICH REGISTRY ROOT
106
+ * CONTAINS the resolved transcript path — never of the `--agent=` flag — and the
107
+ * fail-closed case (a path inside no known root at all) has no agent id to be
108
+ * named after. Typing it as its own union is what stops a later edit passing an
109
+ * agent id here and quietly making the two things one.
110
+ */
111
+ export type RedactionProfile = "claude-code" | "codex-cli" | "cursor";
112
+ /**
113
+ * How this agent's DELEGATED transcripts are found — announce vs discover.
114
+ *
115
+ * The second of the two confirmed differences, and the one that is a genuine
116
+ * mechanism split rather than a different string:
117
+ *
118
+ * `discover` the paths are not handed over. Claude Code writes them to
119
+ * `<session>/subagents/` and the hook globs `agent-*.jsonl` there
120
+ * (`hooks/entry.ts`, `CR-124`).
121
+ * `announce` the path ARRIVES, on hook stdin, as `agent_transcript_path`.
122
+ *
123
+ * ⛔ **`announce` HAS A READER AS OF `CR-196`** (D177 §9), and this sentence is
124
+ * the one that used to say it did not. From `CR-183` until that unit, nothing in
125
+ * this client parsed `agent_transcript_path`, so a dialect declaring `announce`
126
+ * delegated nothing at all and the discriminator's only effect was to SUPPRESS
127
+ * Claude's glob. `announcedSubagentFiles` in `hooks/entry.ts` is the reader.
128
+ *
129
+ * ⚠ **What it does NOT change:** the announced path is INPUT and is confined
130
+ * exactly as a globbed one is (`admitsTranscript` — the registry-computed
131
+ * boundary plus the file gate), and the stream's `file_key` is still derived
132
+ * from a FILENAME rather than from anything inside the file. Cursor's children
133
+ * carry no `agent-` prefix, so the key is SYNTHESIZED client-side
134
+ * (`announcedSubagentFileKey`); widening the server's stream-key column was
135
+ * considered and DECLINED, because five independent layers refuse a bare id.
136
+ */
137
+ export type DelegatedTranscripts = "discover" | "announce";
138
+ /**
139
+ * The three hook events DESIGN.md §13.7 names.
140
+ *
141
+ * Both members of this lane fire the same three; that is exactly why
142
+ * `hook_event_name` was REJECTED as an adapter selector (`CR-183`) — the events
143
+ * are shared, so they discriminate nothing.
144
+ */
145
+ export declare const HOOK_EVENTS: readonly ["Stop", "PreCompact", "SessionEnd"];
146
+ export type HookEventName = (typeof HOOK_EVENTS)[number];
147
+ /**
148
+ * One event's two clocks, and the relation between them is the point.
149
+ *
150
+ * ⛔ `clientBudgetMs` MUST be strictly less than `registeredTimeoutMs`, or the
151
+ * client's own watchdog never fires and the agent kills the process instead —
152
+ * which loses the exit-0/silent-stdout contract the watchdog exists to hold.
153
+ * That relation sat in a comment on `DEFAULT_HOOK_BUDGET_MS` and was checked by
154
+ * nothing; `test/agent-registry.test.ts` asserts it for every adapter event.
155
+ */
156
+ export interface EventBudget {
157
+ /** What the agent's hook config registers, in MILLISECONDS. */
158
+ readonly registeredTimeoutMs: number;
159
+ /** What this client gives itself. Strictly the smaller of the two. */
160
+ readonly clientBudgetMs: number;
161
+ }
162
+ /** Where `connect` found the session running right now. */
163
+ export interface LocatedTranscript {
164
+ readonly path: string;
165
+ readonly sessionId: string;
166
+ readonly size: number;
167
+ }
168
+ /**
169
+ * One member of the lane.
170
+ *
171
+ * Every function member takes `home` and `env` explicitly, for the reason
172
+ * `paths.ts` does: a module that reads the real HOME cannot be tested without
173
+ * touching the developer's own transcripts.
174
+ */
175
+ export interface TranscriptDialect {
176
+ readonly id: AgentId;
177
+ /**
178
+ * ⛔ THE LANE LOCK. A literal type, not a string — see the module docblock.
179
+ */
180
+ readonly transport: "ndjson";
181
+ /**
182
+ * The directory this agent writes transcripts under.
183
+ *
184
+ * ⛔ The CONFINEMENT BOUNDARY is computed from these and NEVER from input.
185
+ * `transcript_path` arrives on hook stdin and is attacker-influenced in the
186
+ * one way that matters — it reaches a read whose result is uploaded — so a
187
+ * path that CLAIMS to be somewhere may never widen where we will read from.
188
+ */
189
+ readonly transcriptRoot: (home: string, env: NodeJS.ProcessEnv) => string;
190
+ /**
191
+ * ⛔ **IS THIS FILE, INSIDE THAT ROOT, A TRANSCRIPT? — `CR-193`, D177 §2.**
192
+ *
193
+ * D177 §2 supersedes `CR-183`'s exclusion of `transcriptExtension`: *"the root
194
+ * must be `.jsonl`-gated."* This is that field, ⚠ **a predicate rather than an
195
+ * extension string, because a bare extension cannot express what Cursor needs
196
+ * it to.** Cursor's transcripts sit two levels below the deepest root
197
+ * `(home, env)` can express — `<root>/<workspace>/agent-transcripts/…` — and
198
+ * the three sibling directories at that depth hold scraped web content, a
199
+ * scratch React project and MCP tool schemas (`isCursorTranscript`).
200
+ *
201
+ * ⛔ **REQUIRED, not optional, and that is the same device `transport` uses.**
202
+ * A dialect added later must ANSWER this rather than inherit a default, and
203
+ * the compiler is what asks. An optional member would need a registry totality
204
+ * pin naming the key to be worth anything at all
205
+ * (`project_optional_member_needs_registry_pin`); a required one gets that
206
+ * from the type system for free.
207
+ *
208
+ * ## ⚠ WHY THIS IS NOT THE DECLARED FIELD `plan §T1` REJECTED
209
+ *
210
+ * `plan §D9` proposed a declared *capability* member and `plan §T1` reversed
211
+ * it, for a reason that reads at a glance like an objection to this field:
212
+ * `shellInvocationsOf?` is **already optional on the interface, so the members
213
+ * themselves carry the truth**, and a declared field beside them is a SECOND
214
+ * source that can disagree with the first.
215
+ *
216
+ * ⛔ **Here no member carries it.** Nothing about `transcriptRoot`,
217
+ * `transport` or `delegatedTranscripts` says which files under a root are
218
+ * transcripts, and — measured — the answer differs between dialects: under
219
+ * Claude's and Codex's roots every file is one, under Cursor's 112 of 113 are
220
+ * not. So this is the **only** source rather than a second one, which is
221
+ * exactly the condition `T1` turns on. ⚠ Said here because the next reader
222
+ * sees a declared field on this interface and reasonably wonders whether `T1`
223
+ * was violated.
224
+ *
225
+ * ⚠ **A NARROWING, and only ever a narrowing.** It runs AFTER
226
+ * `isInsideAny(transcriptRoots(...))` — see `admitsTranscript` — so it can
227
+ * refuse a path the boundary admitted and can never admit one it refused.
228
+ * That is D177 §7's direction test: fail-closed both ways round.
229
+ */
230
+ readonly admitsFile: (root: string, path: string) => boolean;
231
+ /** Per-event clocks. See `EventBudget` for the relation that binds them. */
232
+ readonly events: Readonly<Record<HookEventName, EventBudget>>;
233
+ readonly delegatedTranscripts: DelegatedTranscripts;
234
+ readonly redaction: RedactionProfile;
235
+ /**
236
+ * ⛔ **WHICH SESSION IS COMMITTING — read from the environment, not inferred
237
+ * from HEAD (`D3`, `D183`).**
238
+ *
239
+ * `spool.ts`'s `activeSessionFor()` picks the most recently mtime'd session
240
+ * state file within 30 minutes. Two agents in one clone: session A commits, B's
241
+ * file is newer, **A's commit enters B's spool**, and `cr071:157` makes the
242
+ * resulting edge PERMANENT — UPDATE and DELETE both raise, `service_role`
243
+ * included. An environment variable the committing process already carries is
244
+ * an OBSERVATION; the mtime race is an inference, and it is wrong silently.
245
+ *
246
+ * ⚠ **`env` is passed, never read from `process.env` here** — the same reason
247
+ * every other function member takes `home` and `env`: a module that reads the
248
+ * real environment cannot be tested without the developer's own session id.
249
+ *
250
+ * ⛔ **REQUIRED, not optional, and that is `admitsFile`'s device.** A dialect
251
+ * added later must ANSWER this rather than inherit a default, and the compiler
252
+ * is what asks. An optional member would be behaviourally identical to a
253
+ * hoisted `undefined` branch and would be worth only what a registry totality
254
+ * pin naming the key made it worth
255
+ * (`project_optional_member_needs_registry_pin`). ⚠ The pin in
256
+ * `test/agent-registry.test.ts` is kept ANYWAY, because the compiler cannot see
257
+ * the other mutation: a dialect DELETED FROM THE REGISTRY has no missing member
258
+ * to complain about.
259
+ *
260
+ * ⛔ **`null` FROM AN UNMEASURED DIALECT IS NOT "THIS AGENT HAS NO SESSION
261
+ * ID".** Two of the three return `null` because nobody has measured them, and
262
+ * the docblock at each one says **UNMEASURED** by that word so a later reader
263
+ * can tell the two apart. `null` also remains the honest answer for the
264
+ * MEASURED dialect when the variable is absent — a human's `git commit` strips
265
+ * it — and that arm is measured (`M1`).
266
+ */
267
+ readonly sessionIdFromEnv: (env: NodeJS.ProcessEnv) => string | null;
268
+ /**
269
+ * Find the session running right now — for `connect`, which is typed by a
270
+ * human and is handed no `transcript_path`.
271
+ *
272
+ * ⛔ **OPTIONAL, and its absence is a real answer.** An agent with no locator
273
+ * makes `connect` SKIP the test-capture beat entirely rather than fall back to
274
+ * something. The available fallback would be to read a `cwd` out of the
275
+ * transcript's own records to find the repository, and that is ANALYSIS, which
276
+ * D60 §D6 forbids this client outright.
277
+ */
278
+ readonly locateCurrentTranscript?: (home: string, env: NodeJS.ProcessEnv, projectKey: string) => LocatedTranscript | null;
279
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The transcript lane, as a type.
3
+ *
4
+ * `CR-183`, D164 §1. The registry key is the transcript FORMAT, not the agent:
5
+ * Codex CLI is the second member of ONE lane, not a special case.
6
+ *
7
+ * ## The scope statement, verbatim — it is the whole membership rule
8
+ *
9
+ * > Agents that append NDJSON to a file and hand a hook its path.
10
+ *
11
+ * Read it as an exclusion, because that is the work it does. An agent whose
12
+ * transcript is Markdown, or a database, or is never handed to a hook at all, is
13
+ * NOT a member — and adding one is a different capture STRATEGY, not another
14
+ * entry in this file. `docs/architecture_refactor.md` lists eleven agents; this
15
+ * lane covers five of them after `CR-193` (D177 §1). "Config not code" is true
16
+ * WITHIN the lane and false across lanes (D164), and the type below is what
17
+ * makes that difference mechanical rather than a promise in a comment.
18
+ *
19
+ * ## Why the lock-out is `transport` and not a runtime check
20
+ *
21
+ * `transport: "ndjson"` is a LITERAL type, so a dialect for a Markdown-transcript
22
+ * agent cannot be registered without EDITING THIS INTERFACE. That is a compile
23
+ * error in a diff a human reads, rather than a config line nobody reviews.
24
+ *
25
+ * It is not decoration. D164's first ruling names the defect it prevents:
26
+ * `redact.ts` parses each line as JSON and returns any NON-JSON line UNCHANGED,
27
+ * which is correct for a partial line at a span boundary and catastrophic for a
28
+ * transcript that is not JSON at all — every line would ship unredacted.
29
+ * Redaction correctness is a property of the FORMAT, so the format is what the
30
+ * type system has to pin.
31
+ *
32
+ * ## The members are limited to what two confirmed differences justify
33
+ *
34
+ * `transcriptRoot`, `admitsFile`, `events`, `delegatedTranscripts`, `redaction`
35
+ * and the optional `locateCurrentTranscript` differ between the three members
36
+ * that exist. `id` names them and `transport` locks the lane. Nothing else is
37
+ * here, and the omissions are deliberate rather than pending: a field whose only
38
+ * member set is a guess about an agent nobody has measured is a decision made
39
+ * early with no evidence, and it will be wrong in the direction that is
40
+ * expensive to undo.
41
+ *
42
+ * ⚠ **`admitsFile` is `CR-193`'s, and it clears that bar rather than bending
43
+ * it** (D177 §2): it was one of `CR-183`'s seven speculative exclusions until a
44
+ * MEASUREMENT un-excluded it — 113 files under Cursor's root, exactly one of
45
+ * them a transcript.
46
+ */
47
+ /**
48
+ * D164 §1's third value — a producer nobody can name, never a dialect.
49
+ *
50
+ * ⛔ **AND IT NEVER TRAVELS.** The docblock above stays literally true after
51
+ * `CR-204`: this is the SELECTOR's answer for a transcript no single registry
52
+ * root contains, and `buildIngestHeaders` turns that answer into an **omitted**
53
+ * header rather than a sent one. The server distinguishes the two — an absent
54
+ * header is *"nothing to say"* and cannot disagree with the value a stream is
55
+ * already pinned to, while a present `unknown` resolves and **does** disagree,
56
+ * which drops the buffered tail and sheds the in-flight turn. See `post.ts`.
57
+ */
58
+ export const UNKNOWN_AGENT_ID = "unknown";
59
+ /**
60
+ * The three hook events DESIGN.md §13.7 names.
61
+ *
62
+ * Both members of this lane fire the same three; that is exactly why
63
+ * `hook_event_name` was REJECTED as an adapter selector (`CR-183`) — the events
64
+ * are shared, so they discriminate nothing.
65
+ */
66
+ export const HOOK_EVENTS = ["Stop", "PreCompact", "SessionEnd"];
67
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AA0BH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAC;AA6D1C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,CAAU,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `vibecommit connect` — the install moment.
3
+ *
4
+ * This task owns the CONSENT GATE and the runtime floor. Two later tasks own the
5
+ * rest of the screen and must not be front-run:
6
+ * - `CR-111d` landed the disclosure copy, transcribed from style guide §10.2.
7
+ * D64 falsified the trust argument the earlier draft used (this repo is
8
+ * private, so "read the source" is false and BLOCKED). The approved
9
+ * replacement links `Vibe-Commit/claude-plugin`, which vendors the exact
10
+ * binary — and "inspect the build" is WEAKER than "read the source" and may
11
+ * not be dressed up as it.
12
+ * - `CR-025` owns the ending: capture the current session and print the live
13
+ * `/app/commits/<sha>` URL. The seam is marked below.
14
+ *
15
+ * `CR-084d` lands the browser sign-in beat (PKCE loopback → a user-principal
16
+ * token for the READ lane) behind `--sign-in`. It is opt-in and not part of the
17
+ * default install for one reason: **sign-in does not produce an ingest
18
+ * credential**, so it cannot replace the beat below. No route in
19
+ * `vibecommit-mcp` mints a `vcik_` — `mintIngestCredential` exists and has no
20
+ * call site outside that repo's own tests — so `VIBECOMMIT_TOKEN` remains the
21
+ * supported path for capture, exactly as before.
22
+ *
23
+ * @provenance vibecommit-mcp src/oauth/ingest_credential.ts — no route mints one, verified
24
+ */
25
+ import { type CommandContext } from "./context.js";
26
+ /**
27
+ * What `connect` was asked to do beyond the default.
28
+ *
29
+ * A parameter rather than a field on `CommandContext`: the context is ambient
30
+ * state (env, home, the clock), and which flags were typed is an argument.
31
+ */
32
+ export interface ConnectOptions {
33
+ /** `--sign-in`. Off by default — see the module note for why. */
34
+ readonly signIn?: boolean;
35
+ }
36
+ export declare function connect(ctx: CommandContext, options?: ConnectOptions): Promise<number>;