@vibe-commit/capture 0.7.0 → 0.9.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 (152) hide show
  1. package/README.md +238 -7
  2. package/dist/account_not_here.d.ts +15 -0
  3. package/dist/account_not_here.js +25 -0
  4. package/dist/account_not_here.js.map +1 -0
  5. package/dist/agents/claude_code.js +13 -0
  6. package/dist/agents/claude_code.js.map +1 -1
  7. package/dist/agents/codex_cli.js +11 -0
  8. package/dist/agents/codex_cli.js.map +1 -1
  9. package/dist/agents/cursor.js +7 -1
  10. package/dist/agents/cursor.js.map +1 -1
  11. package/dist/agents/registry.d.ts +7 -0
  12. package/dist/agents/registry.js +9 -0
  13. package/dist/agents/registry.js.map +1 -1
  14. package/dist/agents/types.d.ts +32 -0
  15. package/dist/agents/types.js.map +1 -1
  16. package/dist/bounded_id.d.ts +29 -0
  17. package/dist/bounded_id.js +42 -0
  18. package/dist/bounded_id.js.map +1 -0
  19. package/dist/codex_folders.d.ts +340 -0
  20. package/dist/codex_folders.js +1448 -0
  21. package/dist/codex_folders.js.map +1 -0
  22. package/dist/commands/auth.d.ts +7 -0
  23. package/dist/commands/auth.js +21 -0
  24. package/dist/commands/auth.js.map +1 -1
  25. package/dist/commands/backfill.d.ts +29 -7
  26. package/dist/commands/backfill.js +387 -109
  27. package/dist/commands/backfill.js.map +1 -1
  28. package/dist/commands/browser_credential.d.ts +4 -2
  29. package/dist/commands/browser_credential.js +5 -2
  30. package/dist/commands/browser_credential.js.map +1 -1
  31. package/dist/commands/connect.d.ts +10 -1
  32. package/dist/commands/connect.js +14 -9
  33. package/dist/commands/connect.js.map +1 -1
  34. package/dist/commands/connect_account.d.ts +135 -0
  35. package/dist/commands/connect_account.js +321 -0
  36. package/dist/commands/connect_account.js.map +1 -0
  37. package/dist/commands/connect_picker.js +204 -29
  38. package/dist/commands/connect_picker.js.map +1 -1
  39. package/dist/commands/device_credential.d.ts +3 -1
  40. package/dist/commands/device_credential.js +1 -0
  41. package/dist/commands/device_credential.js.map +1 -1
  42. package/dist/commands/finalize.d.ts +67 -0
  43. package/dist/commands/finalize.js +450 -114
  44. package/dist/commands/finalize.js.map +1 -1
  45. package/dist/commands/finish_mint.d.ts +28 -0
  46. package/dist/commands/finish_mint.js +16 -4
  47. package/dist/commands/finish_mint.js.map +1 -1
  48. package/dist/commands/scan.d.ts +28 -0
  49. package/dist/commands/scan.js +11 -0
  50. package/dist/commands/scan.js.map +1 -1
  51. package/dist/commands/status.d.ts +19 -1
  52. package/dist/commands/status.js +123 -14
  53. package/dist/commands/status.js.map +1 -1
  54. package/dist/commands/status_machine.d.ts +12 -3
  55. package/dist/commands/status_machine.js +111 -13
  56. package/dist/commands/status_machine.js.map +1 -1
  57. package/dist/commands/sync_reason.d.ts +17 -0
  58. package/dist/commands/sync_reason.js +25 -0
  59. package/dist/commands/sync_reason.js.map +1 -0
  60. package/dist/consent.d.ts +131 -3
  61. package/dist/consent.js +248 -14
  62. package/dist/consent.js.map +1 -1
  63. package/dist/copy/strings.d.ts +165 -4
  64. package/dist/copy/strings.js +190 -7
  65. package/dist/copy/strings.js.map +1 -1
  66. package/dist/folder_grants.d.ts +44 -0
  67. package/dist/folder_grants.js +101 -0
  68. package/dist/folder_grants.js.map +1 -0
  69. package/dist/git.d.ts +26 -4
  70. package/dist/git.js +82 -11
  71. package/dist/git.js.map +1 -1
  72. package/dist/grants_sync.d.ts +165 -0
  73. package/dist/grants_sync.js +289 -0
  74. package/dist/grants_sync.js.map +1 -0
  75. package/dist/grants_verb.d.ts +70 -0
  76. package/dist/grants_verb.js +121 -0
  77. package/dist/grants_verb.js.map +1 -0
  78. package/dist/hooks/entry.d.ts +30 -50
  79. package/dist/hooks/entry.js +601 -561
  80. package/dist/hooks/entry.js.map +1 -1
  81. package/dist/hooks/fire_lock.d.ts +31 -0
  82. package/dist/hooks/fire_lock.js +120 -12
  83. package/dist/hooks/fire_lock.js.map +1 -1
  84. package/dist/hooks/session_grants.d.ts +59 -0
  85. package/dist/hooks/session_grants.js +91 -0
  86. package/dist/hooks/session_grants.js.map +1 -0
  87. package/dist/hooks/session_start.d.ts +28 -7
  88. package/dist/hooks/session_start.js +55 -11
  89. package/dist/hooks/session_start.js.map +1 -1
  90. package/dist/index.js +22 -5
  91. package/dist/index.js.map +1 -1
  92. package/dist/machine_state.d.ts +44 -1
  93. package/dist/machine_state.js +75 -12
  94. package/dist/machine_state.js.map +1 -1
  95. package/dist/oauth/discovery.js +2 -1
  96. package/dist/oauth/discovery.js.map +1 -1
  97. package/dist/oauth/pkce.js +2 -1
  98. package/dist/oauth/pkce.js.map +1 -1
  99. package/dist/oauth/retire.d.ts +10 -0
  100. package/dist/oauth/retire.js +13 -0
  101. package/dist/oauth/retire.js.map +1 -1
  102. package/dist/paths.d.ts +54 -1
  103. package/dist/paths.js +88 -12
  104. package/dist/paths.js.map +1 -1
  105. package/dist/pending_revoke.d.ts +117 -0
  106. package/dist/pending_revoke.js +233 -0
  107. package/dist/pending_revoke.js.map +1 -0
  108. package/dist/pending_revoke_file.d.ts +55 -0
  109. package/dist/pending_revoke_file.js +74 -0
  110. package/dist/pending_revoke_file.js.map +1 -0
  111. package/dist/physical_path.d.ts +46 -0
  112. package/dist/physical_path.js +151 -0
  113. package/dist/physical_path.js.map +1 -0
  114. package/dist/picker.d.ts +82 -5
  115. package/dist/picker.js +183 -10
  116. package/dist/picker.js.map +1 -1
  117. package/dist/picker_checklist.d.ts +27 -3
  118. package/dist/picker_checklist.js +81 -6
  119. package/dist/picker_checklist.js.map +1 -1
  120. package/dist/policy.d.ts +24 -1
  121. package/dist/policy.js +30 -2
  122. package/dist/policy.js.map +1 -1
  123. package/dist/post.d.ts +55 -2
  124. package/dist/post.js +72 -22
  125. package/dist/post.js.map +1 -1
  126. package/dist/project.d.ts +89 -0
  127. package/dist/project.js +123 -0
  128. package/dist/project.js.map +1 -1
  129. package/dist/redact.d.ts +77 -2
  130. package/dist/redact.js +713 -47
  131. package/dist/redact.js.map +1 -1
  132. package/dist/session_targets.d.ts +713 -0
  133. package/dist/session_targets.js +2602 -0
  134. package/dist/session_targets.js.map +1 -0
  135. package/dist/spool.d.ts +12 -0
  136. package/dist/spool.js +29 -1
  137. package/dist/spool.js.map +1 -1
  138. package/dist/spool_union.d.ts +51 -0
  139. package/dist/spool_union.js +57 -0
  140. package/dist/spool_union.js.map +1 -0
  141. package/dist/state.d.ts +5 -0
  142. package/dist/state.js +19 -1
  143. package/dist/state.js.map +1 -1
  144. package/dist/system_message.d.ts +25 -2
  145. package/dist/system_message.js +29 -5
  146. package/dist/system_message.js.map +1 -1
  147. package/dist/text_tail.d.ts +8 -0
  148. package/dist/text_tail.js +19 -0
  149. package/dist/text_tail.js.map +1 -0
  150. package/dist/version.d.ts +1 -1
  151. package/dist/version.js +1 -1
  152. package/package.json +3 -3
package/dist/project.js CHANGED
@@ -38,6 +38,7 @@
38
38
  import { execFileSync } from "node:child_process";
39
39
  import { existsSync, realpathSync } from "node:fs";
40
40
  import { isAbsolute, join, resolve } from "node:path";
41
+ import { gitProbe } from "./git.js";
41
42
  import { spawnTimeoutMs } from "./spawn_budget.js";
42
43
  /** This spawn's own ceiling, used when no hook budget is armed. */
43
44
  const TOPLEVEL_TIMEOUT_MS = 5_000;
@@ -189,4 +190,126 @@ export function resolveProjectKeys(dir) {
189
190
  export function resolveProjectKey(dir) {
190
191
  return gitToplevel(dir);
191
192
  }
193
+ // ---------------------------------------------------------------------------
194
+ // Redaction roots (moved from `hooks/entry.ts` so `session_targets.ts` can use them without an import cycle).
195
+ // ---------------------------------------------------------------------------
196
+ /** The porcelain record's opening line — `worktree <absolute path>`. */
197
+ const WORKTREE_LINE = "worktree ";
198
+ /**
199
+ * The MAIN work tree of the repository whose common dir this is, or **null**.
200
+ *
201
+ * ## ⛔ `rev-parse --show-toplevel` CANNOT ANSWER THIS, measured
202
+ *
203
+ * `D184 §4` mandates `git -C <common-dir> rev-parse --show-toplevel`. Measured
204
+ * on git 2.39.5 against real repositories, that command exits **128** —
205
+ * *"fatal: this operation must be run in a work tree"* — for **every shape a
206
+ * consent key takes**, because a consent key is always a git DIRECTORY and
207
+ * `--is-inside-work-tree` is `false` inside one:
208
+ *
209
+ * | `-C` | `--show-toplevel` |
210
+ * |---|---|
211
+ * | `<main>/.git` (main clone, and every worktree linked to it) | ⛔ ec 128 |
212
+ * | `<repos>/project.git` (bare) | ⛔ ec 128 |
213
+ *
214
+ * Implemented literally, "a failure means no second root" would fire 100% of
215
+ * the time: the set would be the singleton it is today, forever, and a
216
+ * main-clone file read from a worktree session would come back a marker. The
217
+ * whole change would pass its own tests while doing nothing.
218
+ *
219
+ * ## What answers instead, and why it is not a guess
220
+ *
221
+ * `git worktree list --porcelain` DOES run from a git dir and from a bare
222
+ * directory, and its FIRST record is the main work tree. Bare repositories say
223
+ * so in the record itself — `bare`, with no work tree of their own.
224
+ *
225
+ * ⛔ **The answer is then validated by CONTENT, never trusted by position:**
226
+ * `realpath(<candidate>/.git)` must BE the consented common dir. That check is
227
+ * independently fatal to every wrong answer this could produce — a bare repo
228
+ * (`<repos>/.git` does not exist), a linked worktree returned first by some
229
+ * future git (its `.git` is a FILE, so the realpath is the file, not the common
230
+ * dir), a submodule work tree (same). `D184 §4`'s hazard is what it is for: in a
231
+ * worktree of a bare repo the common dir is `…/repos/project.git`, whose parent
232
+ * is **the folder holding every other repository on the machine**, and a root
233
+ * derived by stripping a suffix would transmit every sibling repo's file
234
+ * contents verbatim.
235
+ *
236
+ * Every failure yields NO SECOND ROOT. Never a fallback, never a parent, never a
237
+ * suffix strip.
238
+ */
239
+ export function mainWorkTree(commonDir) {
240
+ const out = gitProbe(commonDir, ["worktree", "list", "--porcelain"]);
241
+ if (out === null)
242
+ return null;
243
+ const record = [];
244
+ for (const line of out.split("\n")) {
245
+ if (line === "")
246
+ break;
247
+ record.push(line);
248
+ }
249
+ // A BARE repository HAS no work tree, and git says so in the record itself.
250
+ if (record.includes("bare"))
251
+ return null;
252
+ const first = record[0];
253
+ if (first === undefined || !first.startsWith(WORKTREE_LINE))
254
+ return null;
255
+ const candidate = first.slice(WORKTREE_LINE.length);
256
+ if (!isAbsolute(candidate))
257
+ return null;
258
+ try {
259
+ return realpathSync(join(candidate, ".git")) === commonDir ? candidate : null;
260
+ }
261
+ catch {
262
+ return null;
263
+ }
264
+ }
265
+ /**
266
+ * ⛔ **The redaction confinement ROOT SET — `D184 §3`.**
267
+ *
268
+ * ```
269
+ * isInsideAny([worktree, mainToplevel], candidate)
270
+ * ```
271
+ *
272
+ * ## Why it is a set, and why the common dir is not in it
273
+ *
274
+ * `D184` moved the consent key to the git common dir so that a session inside a
275
+ * `git worktree` is admitted at all. ⛔ **The confinement root MUST NOT follow
276
+ * it there.** `isInside` realpaths both sides and fail-closes anything it cannot
277
+ * place to *outside* — which here means REDACT — so a root at `<main>/.git`
278
+ * would judge every file of an external worktree to be outside the main clone
279
+ * and replace its `content`, `oldString`, `newString` and `structuredPatch` with
280
+ * markers. **The capture still succeeds. The turn counts still look right. The
281
+ * content is gone**, and a test asserting *"worktree sessions capture now"*
282
+ * passes on an empty payload.
283
+ *
284
+ * Two roots, because both are genuinely the user's own tree: the worktree the
285
+ * session is running in, and the main clone it was cut from — a builder in a
286
+ * worktree reads the main clone's files constantly.
287
+ *
288
+ * ## ⛔ The `paths.ts:487-489` prohibition, answered — `D184 §5`
289
+ *
290
+ * > *"`roots` comes from the REGISTRY. It must never be derived from input."*
291
+ *
292
+ * **That prohibition is about PROVENANCE, and both members have the right one.**
293
+ * The named hazard is *anything on hook stdin* — attacker-supplied. Neither of
294
+ * these is: `keys` can only be produced by `resolveProjectKeys`, which is git's
295
+ * own answer about the directory the session runs in; `mainToplevel` is git's
296
+ * answer about the repository **the consent gate already approved**, and it is
297
+ * content-validated by `mainWorkTree` above. The worktree root has exactly the
298
+ * same provenance and has been used this way since `CR-024d`.
299
+ *
300
+ * ⚠ **Provenance is the whole argument, so this code preserves it:** nothing
301
+ * here is read from stdin, the derivation is validated by content, and a failure
302
+ * yields no second root rather than a fallback. An empty set would refuse
303
+ * everything (`paths.ts:491-492`), which is the correct direction — but this
304
+ * function cannot return one, because `keys.worktree` is always a member.
305
+ *
306
+ * ⚠ The dedup is cosmetic. `isInsideAny` over a repeated root is a union with
307
+ * itself; it is dropped so that a main-clone session reads as the single root it
308
+ * has always had, and so a cell asserting "no second root" is asserting
309
+ * something.
310
+ */
311
+ export function confinementRoots(keys) {
312
+ const main = mainWorkTree(keys.consent);
313
+ return main === null || main === keys.worktree ? [keys.worktree] : [keys.worktree, main];
314
+ }
192
315
  //# sourceMappingURL=project.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,mEAAmE;AACnE,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAElC;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,QAAQ,CAAC,GAAW,EAAE,GAAW;IACxC,MAAM,OAAO,GAAG,cAAc,CAAC,mBAAmB,CAAC,CAAC;IACpD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE;YAC7D,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,OAAO;SACR,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACzB,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IAC9C,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAChE,CAAC;AAkBD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
package/dist/redact.d.ts CHANGED
@@ -64,6 +64,14 @@ import type { RedactionProfile } from "./agents/types.js";
64
64
  * verdict inherits down the subtree* — and each agent supplies which keys play
65
65
  * which part. A second agent is a second row in the table below, not a branch.
66
66
  */
67
+ /** `values`: every content string withheld. `strict`: the same, and a record holding a data-shaped KEY (a skill name, a path) is withheld as a whole line (C26's form). */
68
+ export type AlwaysWithheld = "values" | "strict";
69
+ export type AggregateVerdict = {
70
+ readonly kind: "withhold";
71
+ } | {
72
+ readonly kind: "keepKeys";
73
+ readonly paths: ReadonlySet<string>;
74
+ };
67
75
  export interface RedactionRules {
68
76
  /** Keys whose STRING value is a filesystem path. The SELECTOR, never redacted. */
69
77
  readonly pathKeys: ReadonlySet<string>;
@@ -79,6 +87,33 @@ export interface RedactionRules {
79
87
  readonly nestedJsonKeys: ReadonlySet<string>;
80
88
  /** Keys holding `[{ lines: [...] }]` hunk arrays whose lines are content. */
81
89
  readonly patchKeys: ReadonlySet<string>;
90
+ /**
91
+ * C37: extra path and content keys that apply ONLY inside a record's `attachment` subtree. An attachment names its file as `path` or
92
+ * `filename` (not `filePath`) and carries it as `content`, `rawContent` or `snippet`: `nested_memory {path, content: {path, content,
93
+ * rawContent}}`, `edited_text_file {filename, snippet}`, `instructions {files: [{path, content}]}`. `path` cannot be a path key
94
+ * everywhere (a tool input's `path` names a SEARCH root, not the subject of its siblings), so the scope is the subtree.
95
+ */
96
+ readonly attachment?: {
97
+ readonly pathKeys: ReadonlySet<string>;
98
+ readonly contentKeys: ReadonlySet<string>;
99
+ };
100
+ /**
101
+ * C39 (iv): a record this dialect's own schema says is a SUMMARY OF THE WHOLE CONVERSATION (Claude's `isCompactSummary` user record: a
102
+ * model-written digest of everything before it, file text included). Its content is withheld in EVERY copy, single-repo copies and the
103
+ * golden's shape included, with the same length-preserving marks as an isolated record. Decided by the record, never by a path.
104
+ */
105
+ readonly alwaysWithheld?: (record: Record<string, unknown>) => AlwaysWithheld | null;
106
+ /**
107
+ * C27 (R17 for records with no folder of their own): what a MULTI-REPOSITORY copy of a session does with a record whose origin cannot be
108
+ * told from the record. `null`: kept as written (the path rule still applies). `withhold`: every content string is withheld, as in an isolated
109
+ * record. `keepKeys`: strings under the listed key paths stay, every other string is withheld (a KEEP type is bound to its measured keys).
110
+ */
111
+ readonly aggregate?: (record: Record<string, unknown>) => AggregateVerdict | null;
112
+ /**
113
+ * C40 (2): keys whose WHOLE subtree is instruction text this dialect injects (Codex's `developer_instructions`, `base_instructions`):
114
+ * every string under such a key is withheld wherever the key appears, in every copy, whatever path sits beside it.
115
+ */
116
+ readonly withheldKeys?: (key: string) => boolean;
82
117
  }
83
118
  /**
84
119
  * The rules a profile LABEL names.
@@ -136,6 +171,44 @@ export interface RedactionCounts {
136
171
  export interface RedactedLine extends RedactionCounts {
137
172
  readonly line: string;
138
173
  }
174
+ /**
175
+ * ⛔ **THE TWO ADDITIONS ANY-FOLDER CAPTURE MAKES TO THIS CHOKEPOINT** (R6 · R17). Both are decided by the CALLER, which knows
176
+ * the target; this module only applies them, length-preservingly, with the same markers as every other redaction.
177
+ *
178
+ * - `hole` (R6, a FOLDER target's EXCLUSION ROOTS): a path inside `projectRoots` is still OUT OF TREE when `hole` says so. A
179
+ * folder like `~/Documents/VibeCommit` contains repos; the folder stream keeps what is inside the folder and outside every git
180
+ * work tree, and withholds anything inside a nested work tree (a backstop for a repo the evidence missed). "Inside A but not
181
+ * inside B" is not expressible as a root set, which is why this is a predicate.
182
+ * - `foreignRecord` (R17, RECORD ISOLATION): a record whose `cwd` resolves inside a work tree OTHER than the target's is a
183
+ * command, its output, or prose from another repo's session. Every content-bearing string in it is withheld; the structural
184
+ * keys (`STRUCTURAL_KEYS`) stay, so the stream still parses and a turn is still a turn. Records run from the launch folder
185
+ * are untouched: that residual (prompts and prose from the parent folder) is stated in the README and the claims register.
186
+ */
187
+ export interface RedactionScope {
188
+ readonly hole?: (candidate: string) => boolean;
189
+ readonly foreignRecord?: (record: Record<string, unknown>) => boolean;
190
+ /**
191
+ * C48 (11), Codex: the FILES a record's call names (an `apply_patch`'s headers in any form, `view_image`'s path; the same for the call's output), and for a
192
+ * call itself the places only its input is judged by. Each is judged like a Claude path: a file outside this copy's roots, or inside a hole, withholds the
193
+ * whole record (length-preserving), in every copy.
194
+ */
195
+ readonly namedFiles?: (record: Record<string, unknown>) => readonly string[];
196
+ /**
197
+ * C27: this copy belongs to a session that named a work tree other than the target's (or whose status is unknown, which counts the same).
198
+ * Records the dialect cannot attribute (`RedactionRules.aggregate`) are withheld from it.
199
+ */
200
+ readonly multi?: boolean;
201
+ /**
202
+ * C40 (1): the open-calls checkpoint of THIS stream, read at the record `at` was last given. `paths`: the call named these files (judged per copy
203
+ * with the copy's roots and holes); `none`: the call named no file (a stated residual: its result is kept); `unknown`: the call is not known
204
+ * (an overflow, a position the checkpoint no longer covers): its result is withheld, failing closed.
205
+ */
206
+ readonly calls?: (id: string) => readonly string[] | "none" | "unknown";
207
+ /** Called by `redactSpan` with the absolute offset of each segment it is about to redact, so `calls` can judge by position. */
208
+ readonly at?: (absoluteOffset: number) => void;
209
+ /** C42: told each parsed record of a span before it is judged (past the scan cursor, the hook learns calls and a second work tree from it). */
210
+ readonly observe?: (record: Record<string, unknown>) => void;
211
+ }
139
212
  /**
140
213
  * Redact one NDJSON line. **Pure, total, and length-preserving.**
141
214
  *
@@ -144,7 +217,7 @@ export interface RedactedLine extends RedactionCounts {
144
217
  * (DESIGN.md §13.7), and a redactor that dropped a line it could not parse would
145
218
  * be silent data loss on a path where D56 §D8 makes loss permanent.
146
219
  */
147
- export declare function redactLine(line: string, projectRoots: readonly string[], rules: RedactionRules, resolver?: Containment): RedactedLine;
220
+ export declare function redactLine(line: string, projectRoots: readonly string[], rules: RedactionRules, resolver?: Containment, scope?: RedactionScope): RedactedLine;
148
221
  /**
149
222
  * Redact every complete line in a raw transcript span.
150
223
  *
@@ -154,7 +227,9 @@ export declare function redactLine(line: string, projectRoots: readonly string[]
154
227
  * which is what keeps a span that bisects a multi-byte character from changing
155
228
  * length when it is re-encoded.
156
229
  */
157
- export declare function redactSpan(span: Buffer, projectRoots: readonly string[], rules: RedactionRules): {
230
+ export declare function redactSpan(span: Buffer, projectRoots: readonly string[], rules: RedactionRules, scope?: RedactionScope,
231
+ /** The absolute offset of `span[0]` in its file (`scope.at` is told each segment's). */
232
+ spanStart?: number): {
158
233
  bytes: Buffer;
159
234
  } & RedactionCounts;
160
235
  /**