@sema-agent/core 7.15.0 → 7.16.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 (119) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/core/governance-codes.js +2 -0
  3. package/dist/core/memory-engine/delegation-settlement.d.ts +37 -12
  4. package/dist/core/memory-engine/delegation-settlement.js +128 -39
  5. package/dist/core/memory-engine/engine.d.ts +9 -8
  6. package/dist/core/memory-engine/engine.js +115 -120
  7. package/dist/core/memory-engine/file-backend.d.ts +17 -5
  8. package/dist/core/memory-engine/file-backend.js +78 -34
  9. package/dist/core/memory-engine/index.d.ts +2 -2
  10. package/dist/core/memory-engine/index.js +2 -2
  11. package/dist/core/memory-engine/layout.d.ts +152 -54
  12. package/dist/core/memory-engine/layout.js +441 -104
  13. package/dist/core/memory-engine/types.d.ts +8 -3
  14. package/dist/core/memory-engine/types.js +5 -0
  15. package/dist/core/runner/abort-race.d.ts +3 -3
  16. package/dist/core/runner/active-skill-scope.d.ts +2 -2
  17. package/dist/core/runner/assemble-result.d.ts +29 -29
  18. package/dist/core/runner/checkpoint-scope.d.ts +4 -4
  19. package/dist/core/runner/clock-and-limits.d.ts +3 -3
  20. package/dist/core/runner/compaction-call-options.d.ts +11 -11
  21. package/dist/core/runner/compaction-knobs.d.ts +4 -4
  22. package/dist/core/runner/compaction-seams.d.ts +5 -5
  23. package/dist/core/runner/contracts.d.ts +222 -224
  24. package/dist/core/runner/decide-continuation.d.ts +6 -6
  25. package/dist/core/runner/denial-limit-arms.d.ts +8 -8
  26. package/dist/core/runner/derived-route-fallback.d.ts +2 -2
  27. package/dist/core/runner/gate-exit.d.ts +6 -6
  28. package/dist/core/runner/git-status-frame.d.ts +7 -7
  29. package/dist/core/runner/inherited-ask-grants.d.ts +6 -6
  30. package/dist/core/runner/memory-capture-optout.d.ts +1 -1
  31. package/dist/core/runner/memory-consolidation-driver.d.ts +4 -4
  32. package/dist/core/runner/memory-consolidation.d.ts +6 -6
  33. package/dist/core/runner/park-commit.d.ts +6 -6
  34. package/dist/core/runner/permission-rule-lanes.d.ts +19 -19
  35. package/dist/core/runner/prepare-acquire-reconcile.d.ts +4 -4
  36. package/dist/core/runner/prepare-ask-lane.d.ts +3 -3
  37. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -3
  38. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -5
  39. package/dist/core/runner/prepare-config-doors.d.ts +21 -21
  40. package/dist/core/runner/prepare-context-lane.d.ts +6 -6
  41. package/dist/core/runner/prepare-defer-classify.d.ts +2 -2
  42. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
  43. package/dist/core/runner/prepare-file-history.d.ts +2 -2
  44. package/dist/core/runner/prepare-gate-stations.d.ts +5 -5
  45. package/dist/core/runner/prepare-hands-readface.d.ts +33 -33
  46. package/dist/core/runner/prepare-inherited-gate.d.ts +4 -4
  47. package/dist/core/runner/prepare-listings.d.ts +3 -3
  48. package/dist/core/runner/prepare-lsp.d.ts +3 -3
  49. package/dist/core/runner/prepare-memory-engine-session.d.ts +5 -5
  50. package/dist/core/runner/prepare-memory.js +6 -7
  51. package/dist/core/runner/prepare-offload-wrappers.d.ts +2 -2
  52. package/dist/core/runner/prepare-park-ask.d.ts +6 -6
  53. package/dist/core/runner/prepare-policy-chain.d.ts +4 -4
  54. package/dist/core/runner/prepare-prompt-inputs.d.ts +5 -5
  55. package/dist/core/runner/prepare-protocol-tools.d.ts +1 -1
  56. package/dist/core/runner/prepare-question-face.d.ts +4 -4
  57. package/dist/core/runner/prepare-run-refs.d.ts +11 -11
  58. package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
  59. package/dist/core/runner/prepare-suspend-saga.d.ts +4 -4
  60. package/dist/core/runner/prepare-task.d.ts +12 -12
  61. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +2 -3
  62. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -4
  63. package/dist/core/runner/prepare-workspace-restore.d.ts +1 -1
  64. package/dist/core/runner/remote-env-retry.d.ts +3 -3
  65. package/dist/core/runner/resume-admission.d.ts +1 -1
  66. package/dist/core/runner/resume-claim.d.ts +1 -1
  67. package/dist/core/runner/resume-internals-and-config.d.ts +1 -1
  68. package/dist/core/runner/resume-policy-outcome.d.ts +1 -1
  69. package/dist/core/runner/resume-preflight.d.ts +1 -1
  70. package/dist/core/runner/resume-review-outcome.d.ts +1 -1
  71. package/dist/core/runner/run-brain-sinks.d.ts +1 -1
  72. package/dist/core/runner/run-clock-and-content.d.ts +4 -4
  73. package/dist/core/runner/run-compaction-machinery.d.ts +3 -3
  74. package/dist/core/runner/run-git-lane.d.ts +1 -1
  75. package/dist/core/runner/run-harness-handlers.d.ts +2 -2
  76. package/dist/core/runner/run-identity-wiring.d.ts +4 -4
  77. package/dist/core/runner/run-leg.d.ts +3 -3
  78. package/dist/core/runner/run-notification-lane.d.ts +2 -2
  79. package/dist/core/runner/run-reasoning-seat.d.ts +2 -2
  80. package/dist/core/runner/run-settle-and-teardown.d.ts +4 -4
  81. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  82. package/dist/core/runner/run-terminal-adoption.d.ts +5 -5
  83. package/dist/core/runner/runtask.d.ts +14 -14
  84. package/dist/core/runner/session-file-state-replay.d.ts +3 -3
  85. package/dist/core/runner/session-rule-policy.d.ts +2 -2
  86. package/dist/core/runner/steer-admission.d.ts +2 -2
  87. package/dist/core/runner/stream-halt-verbs.d.ts +1 -1
  88. package/dist/core/runner/stream-reap.d.ts +1 -1
  89. package/dist/core/runner/synthetic-tools.d.ts +8 -8
  90. package/dist/core/runner/teardown-bounded.d.ts +2 -2
  91. package/dist/core/runner/tool-defer-gate.d.ts +1 -1
  92. package/dist/core/runner/tool-disclosure.d.ts +33 -33
  93. package/dist/core/runner/tool-end-body.d.ts +3 -3
  94. package/dist/core/runner/tool-face-overlay.d.ts +2 -2
  95. package/dist/core/runner/tool-output-projection.d.ts +2 -2
  96. package/dist/core/runner/turn-attachments.d.ts +53 -53
  97. package/dist/core/runner/usage-accounting.d.ts +4 -4
  98. package/dist/core/runner/workspace-path.d.ts +1 -1
  99. package/dist/engine/llm/types.d.ts +1 -1
  100. package/dist/index.d.ts +2 -2
  101. package/dist/index.js +2 -2
  102. package/dist/tools/fs/bash-readonly-classifier.d.ts +40 -40
  103. package/dist/tools/fs/encoding.d.ts +31 -40
  104. package/dist/tools/fs/fs-bash.d.ts +35 -35
  105. package/dist/tools/fs/fs-pdf.d.ts +8 -8
  106. package/dist/tools/fs/fs-search-tools.d.ts +4 -4
  107. package/dist/tools/fs/fs-shared.d.ts +144 -158
  108. package/dist/tools/fs/fs-write.d.ts +8 -8
  109. package/dist/tools/fs/gh-rate-limit.d.ts +13 -14
  110. package/dist/tools/fs/index.d.ts +74 -78
  111. package/dist/tools/fs/notebook.d.ts +24 -25
  112. package/dist/tools/fs/pdf.d.ts +3 -3
  113. package/dist/tools/fs/read-deny.d.ts +22 -22
  114. package/dist/tools/fs/read-face.d.ts +21 -24
  115. package/dist/tools/fs/repo-map.d.ts +8 -8
  116. package/dist/tools/fs/safety.d.ts +116 -129
  117. package/dist/tools/fs/search.d.ts +63 -71
  118. package/package.json +5 -2
  119. package/test/export-surface.snapshot.json +5 -1
@@ -1,13 +1,13 @@
1
1
  import type { ExecutionEnv } from "../../internal/harness-types.js";
2
2
  import { protectivePathTargetOf, type ToolCallLike } from "../../core/tool-registry.js";
3
3
  /**
4
- * batch-B (CC 2.1.187 parity): Read/Edit/Write take the file path as `file_path`. Read the target the same way
4
+ * CC parity: Read/Edit/Write take the file path as `file_path`. Read the target the same way
5
5
  * EVERYWHERE the path is consumed from a tool-call's args — not just the tool body but ALSO every gate that
6
6
  * extracts the write target to confine it (sensitive-path, skill allowPaths, session allowDirs). A gate that only
7
7
  * read `args.path` would see `undefined` for a `file_path` call and either fail-OPEN (circumvent the guard — a real
8
8
  * hole) or fail-closed (deny a legitimate write). Single source.
9
9
  *
10
- * The `path` arm is deliberately BROADER than what Read/Edit/Write accept (ruled 2026-08-03): their schema requires
10
+ * The `path` arm is deliberately BROADER than what Read/Edit/Write accept: their schema requires
11
11
  * `file_path` and rejects the retired `path` alias outright (fs-shared.ts FILE_PATH_PARAMS), but this reader is
12
12
  * shared with gates that judge OTHER tool vocabularies — Glob's real `path` parameter, deployment-authored tools —
13
13
  * and a gate must never see LESS than the tool it is confining might act on. So the asymmetry is the design: the
@@ -15,8 +15,8 @@ import { protectivePathTargetOf, type ToolCallLike } from "../../core/tool-regis
15
15
  */
16
16
  export declare function fileArgPath(args: unknown): string | undefined;
17
17
  /**
18
- * The WRITE TARGET of a path-confinable write call, tool-aware (b — single source for every
19
- * gate that confines a write target, same discipline as {@link fileArgPath} above):
18
+ * The WRITE TARGET of a path-confinable write call, tool-aware — single source for every gate that
19
+ * confines a write target, same discipline as {@link fileArgPath} above:
20
20
  *
21
21
  * · canonical `NotebookEdit` → `notebook_path` FIRST, `file_path`/`path` only as fallback. The tool
22
22
  * itself writes `notebook_path`; its schema tolerates extra keys, so a stray/decoy `file_path` on a
@@ -25,30 +25,30 @@ export declare function fileArgPath(args: unknown): string | undefined;
25
25
  * · every other tool → `file_path`/`path` first ({@link fileArgPath}), `notebook_path` as the
26
26
  * historical fallback (unchanged behavior for the pre-existing gate callers).
27
27
  *
28
- * `canonicalName` is the tool name as mounted (RB-476-A: raw the legacy normalizer is retired;
29
- * this module stays dependency-free of the alias table — "NotebookEdit" is the canonical CC name and
30
- * old→new normalization is identity for it).
28
+ * `canonicalName` is the tool name as mounted, raw: there is no legacy normalizer, and this module
29
+ * stays dependency-free of the alias table — "NotebookEdit" is the canonical CC name and old→new
30
+ * normalization is identity for it.
31
31
  */
32
32
  export declare function writeTargetPath(call: ToolCallLike, pt?: ReturnType<typeof protectivePathTargetOf>): string | undefined;
33
33
  /**
34
- * The "hand" file tools (design/44) — safety invariants ported from CC's `FileEditTool` (design/43 Rule
35
- * 7): without these a weak model blind-edits / overwrites files (P0 safety, not polish). This module is
36
- * the pure/testable core (path resolution + the three edit invariants + content hashing); the tools in
37
- * this dir compose it. Per-task state and the execution env are passed in (factory-closure wiring,
38
- * design/44 §11 ruling A), never read from a shared/global.
34
+ * The "hand" file tools' safety invariants, ported from CC's `FileEditTool`: without these a weak
35
+ * model blind-edits / overwrites files (a safety property, not polish). This module is the
36
+ * pure/testable core (path resolution + the three edit invariants + content hashing); the tools in
37
+ * this dir compose it. Per-task state and the execution env are passed in (factory-closure wiring),
38
+ * never read from a shared/global.
39
39
  */
40
- /** What we remember about a file the agent has read (design/44 §4). */
40
+ /** What we remember about a file the agent has read. */
41
41
  export interface ReadEntry {
42
42
  /** SHA-256 of the content at read time. Staleness is detected by content hash, NOT mtime — mtime is
43
- * unreliable across containers/volumes/NFS and across a design/45 suspend→resume on another replica. */
43
+ * unreliable across containers/volumes/NFS and across a suspend→resume on another replica. */
44
44
  hash: string;
45
- /** Total line count at read time, and whether the read was truncated (for the edit truncation hint, S2). */
45
+ /** Total line count at read time, and whether the read was truncated (for the edit truncation hint). */
46
46
  totalLines: number;
47
47
  truncated: boolean;
48
- /** RB-200 F2 — CC `isPartialView` parity (220 @515151 sets it only when the auto-pagination catch branch
49
- * fires, i.e. the model asked for the file by DEFAULT no explicit offset/limit and the token cap
50
- * truncated it anyway; an EXPLICIT offset/limit slice that fits under the cap is NEVER marked this way,
51
- * even though it's equally "not the whole file"). Deliberately narrower than {@link truncated} above,
48
+ /** CC `isPartialView` parity: set ONLY when the auto-pagination branch fires — the model asked for
49
+ * the file by DEFAULT (no explicit offset/limit) and the token cap truncated it anyway. An EXPLICIT
50
+ * offset/limit slice that fits under the cap is NEVER marked this way, even though it is equally
51
+ * "not the whole file". Deliberately narrower than {@link truncated} above,
52
52
  * which also fires for a fully-intentional, successful slice read (`start>1 || end<total`) — folding
53
53
  * THIS flag into that broader one would make {@link OVERSIZE_READ_ESCAPE_HINT}'s documented promise
54
54
  * ("read it in slices with explicit offset/limit to satisfy the read-first rule") false: a model paging
@@ -56,51 +56,50 @@ export interface ReadEntry {
56
56
  * additive: undefined on every non-Read write-back (Write/Edit re-record the file they just fully wrote)
57
57
  * and on every seeded/attachment entry — those are never partial by construction. */
58
58
  isPartialView?: boolean;
59
- /** The line window actually served to the model on the last read (design/64 §7.2(6) dedup stub). Lets
59
+ /** The line window actually served to the model on the last read (the dedup stub's key). Lets
60
60
  * read_file return a `file_unchanged` stub when the SAME window is re-requested and the content hash is
61
61
  * unchanged — saving the re-transmitted body. Optional/additive: undefined (e.g. an entry seeded from an
62
- * older design/45 checkpoint) just means "no dedup", never a false hit. */
62
+ * older checkpoint) just means "no dedup", never a false hit. */
63
63
  view?: {
64
64
  start: number;
65
65
  end: number;
66
66
  };
67
- /** Millisecond timestamp of the last read/write-back that recorded this entry (
68
- * 2026-07-03, compact re-read parity: the post-compaction attachment picks the most RECENTLY
69
- * read files CC sorts readFileState by timestamp). Optional/additive: undefined (an entry
70
- * seeded from an older checkpoint) sorts last, never breaks. */
67
+ /** Millisecond timestamp of the last read/write-back that recorded this entry: the post-compaction
68
+ * attachment picks the most RECENTLY read files, the way CC sorts readFileState by timestamp.
69
+ * Optional/additive: undefined (an entry seeded from an older checkpoint) sorts last, never breaks. */
71
70
  lastReadAt?: number;
72
- /** CC 2.1.204 parity (`seededFromContext`, cc204-bundle @17889409 / seeding @17917159; 198 zero
73
- * hits): TRUE when the Runner pre-seeded this entry because the file's FULL disk-verbatim text
74
- * was injected into the model's context at startup (ProjectMemoryLoad.seededFiles CC's
75
- * CLAUDE.md/nested-memory seeding). A DEFAULT whole-file Read of an UNCHANGED seeded file
71
+ /** CC 2.1.204 parity: TRUE when the Runner pre-seeded this entry because the file's FULL
72
+ * disk-verbatim text was injected into the model's context at startup
73
+ * (ProjectMemoryLoad.seededFiles CC's own memory preload). A DEFAULT whole-file Read of an
74
+ * UNCHANGED seeded file
76
75
  * answers with the already-in-context reminder instead of re-transmitting the body. Any real
77
76
  * Read / edit write-back records a FRESH entry without this flag, so a file that changed on
78
77
  * disk (hash mismatch) always serves real content and the dedup disarms permanently.
79
78
  * Optional/additive: absent = normal entry. */
80
79
  seededFromContext?: boolean;
81
80
  }
82
- /** Per-task record of which files have been read, keyed by canonical path (design/44 §4, §11 ruling A:
83
- * owned by prepare-task, closure-captured by the tools; serialized into a design/45 Checkpoint later). */
81
+ /** Per-task record of which files have been read, keyed by canonical path: owned by prepare-task,
82
+ * closure-captured by the tools, and serialized into the durable checkpoint. */
84
83
  export type ReadFileState = Map<string, ReadEntry>;
85
- /** SHA-256 hex of UTF-8 content — the staleness/identity key (design/44 §4 inv 2, jury M1). */
84
+ /** SHA-256 hex of UTF-8 content — the staleness/identity key. */
86
85
  export declare function sha256(content: string): string;
87
86
  /** A failed path resolution / invariant check, surfaced to the model as a self-correctable tool error. */
88
87
  export interface FsViolation {
89
88
  code: "path_not_in_root" | "not_read" | "stale" | "ambiguous_edit" | "invalid" | "read_path_denied";
90
- /** RB-264 N1: set on the `not_read` violation raised by an AUTO-TRUNCATED read
89
+ /** Set on the `not_read` violation raised by an AUTO-TRUNCATED read
91
90
  * ({@link ReadEntry.isPartialView}) rather than by a never-read file. Both carry the same CC-verbatim
92
91
  * message; only this one needs {@link PARTIAL_VIEW_READ_ESCAPE_HINT} appended, because for it a plain
93
92
  * re-read is a fixpoint. */
94
93
  partialView?: true;
95
- /** RB-371 ④: set on `path_not_in_root` (and design/199 `read_path_denied`) — the CANONICAL key the
94
+ /** Set on `path_not_in_root` and `read_path_denied` — the CANONICAL key the
96
95
  * refused path resolved to (symlinks followed, tracked cwd applied), i.e. the value the fence
97
96
  * actually judged. Rides into the tool result's `details` (see {@link violationDetails}) so a
98
97
  * consumer can count/aggregate boundary refusals from fields instead of regexing the prose. */
99
98
  target?: string;
100
- /** RB-371 ④: set ONLY on `path_not_in_root` — the containment roots in effect (primary first,
99
+ /** Set ONLY on `path_not_in_root` — the containment roots in effect (primary first,
101
100
  * then additionalDirectories), canonical form. */
102
101
  roots?: readonly string[];
103
- /** design/199 件B: set ONLY on `read_path_denied` — the deny-list pattern (verbatim entry text)
102
+ /** Set ONLY on `read_path_denied` — the deny-list pattern (verbatim entry text)
104
103
  * the judged target matched. */
105
104
  pattern?: string;
106
105
  message: string;
@@ -108,21 +107,17 @@ export interface FsViolation {
108
107
  /**
109
108
  * Absolute in EITHER family: POSIX `/…` or drive-letter `C:\…`/`C:/…`.
110
109
  *
111
- * REF-C1 — this is the ONE definition of "already absolute, therefore do not prepend a base". It was
112
- * independently re-typed at nine coordinates (search.ts ×4, bash-readonly-classifier.ts,
113
- * core/lsp.ts, core/runner/prepare-task.ts, orchestration/run-spec.ts and here), three of which
114
- * carried a comment claiming the same philosophy as this file while keeping their own copy. The
115
- * failure mode is not the duplication itself but the drift it invites: a family added at one
116
- * coordinate leaves the other eight judging the same string differently, and this predicate gates a
110
+ * This is the ONE definition of "already absolute, therefore do not prepend a base". The failure mode
111
+ * of a private re-typing is not the duplication but the drift it invites: a family added at one
112
+ * coordinate leaves every other copy judging the same string differently, and this predicate gates a
117
113
  * security fence (frozen-path deny, readonly-boundary containment) as well as URI key minting.
118
114
  * Callers that need a family the predicate does not cover must widen it HERE, not locally.
119
115
  *
120
- * The UNC arm (`\\host\share\…`) was exactly that drift, found already in flight: core/lsp.ts carried it
121
- * privately while the other eight coordinates did not, so the LSP tool leg resolved a UNC path as
122
- * absolute while the edit-nudge leg prefixed it with the task root two URI keys for one file. Folding
123
- * it in here settles the disagreement in the direction the rest of this file already takes: a literal
124
- * `\\host\share\…` is win-form (see {@link isWinFormPath}) and `within()` lands it outside every
125
- * POSIX/drive root, i.e. it is a location the working directory does not contain.
116
+ * The UNC arm (`\\host\share\…`) is the shape that drift produced: held privately in one consumer, it
117
+ * made that consumer resolve a UNC path as absolute while a sibling prefixed it with the task root —
118
+ * two URI keys for one file. Folding it in here settles the disagreement in the direction the rest of
119
+ * this file takes: a literal `\\host\share\…` is win-form (see {@link isWinFormPath}) and `within()`
120
+ * lands it outside every POSIX/drive root, i.e. a location the working directory does not contain.
126
121
  */
127
122
  export declare function isAbsolutePathForm(p: string): boolean;
128
123
  /**
@@ -133,10 +128,10 @@ export declare function isAbsolutePathForm(p: string): boolean;
133
128
  * contains a literal backslash (legal on POSIX), and `//host/share` is therefore POSIX-form too — which is
134
129
  * the whole point: on POSIX that spelling is an ordinary absolute path, not a network share.
135
130
  *
136
- * REF-C2 — this is the ONE definition of the win-family spelling. core/file-snapshot-store.ts held a
137
- * byte-identical private twin (its manifest key/restore-target folds are conditioned on it) and now
138
- * imports this one; core/ already depends on this module at seven other coordinates, and this file's own
139
- * imports are `node:crypto` plus a type, so there is no layering wall between them.
131
+ * This is the ONE definition of the win-family spelling; core/file-snapshot-store.ts conditions its
132
+ * manifest key / restore-target folds on this import rather than a private twin. core/ already depends
133
+ * on this module elsewhere, and this file's own imports are `node:crypto` plus a type, so there is no
134
+ * layering wall between them.
140
135
  */
141
136
  export declare function isWinFormPath(p: string): boolean;
142
137
  /**
@@ -153,7 +148,7 @@ export declare function isWinFormPath(p: string): boolean;
153
148
  export declare function expandHomeTilde(p: string, home: string): string;
154
149
  export declare function isBlockedDevicePath(key: string): boolean;
155
150
  /**
156
- * RB-153 — purely LEXICAL path normalization: collapse repeated separators, drop `.` segments, and
151
+ * Purely LEXICAL path normalization: collapse repeated separators, drop `.` segments, and
157
152
  * resolve `..` against the preceding segment. No filesystem access, no cwd: this is a spelling
158
153
  * normalizer, not a resolver. (Deliberately NOT `canonicalizeNewPath`'s job — that one walks the real
159
154
  * filesystem precisely because folding `..` lexically across a SYMLINKED parent is unsound. Use this one
@@ -164,9 +159,8 @@ export declare function isBlockedDevicePath(key: string): boolean;
164
159
  * three different ways. Callers that match a device list against a raw argument must normalize first or
165
160
  * the list is a spelling filter rather than a device filter.
166
161
  *
167
- * REF-C3coverage, after folding in the two private re-implementations that had grown around this one
168
- * (the bash classifier's drive-aware wrapper and prepare-task's `normalizeSegments`). The union they
169
- * covered between them is now the contract here:
162
+ * Coveragethe union of every fold the callers need, held once here rather than re-implemented per
163
+ * consumer:
170
164
  * · POSIX absolute — `/a/./b` → `/a/b`; `/..` is `/`, matching the kernel.
171
165
  * · drive-letter (`C:\a\..\b`) and UNC (`\\host\share\..\x`) — prefix preserved, folding applies
172
166
  * to the tail only, and the REJOIN uses the path's own native separator so one file cannot acquire
@@ -175,14 +169,14 @@ export declare function isBlockedDevicePath(key: string): boolean;
175
169
  * `dev/zero` under some other directory is an ordinary file), and a leading `..` is KEPT rather than
176
170
  * popped into nothing — dropping it would move the path.
177
171
  *
178
- * FAMILY-awareness is the one thing none of the three had right. A backslash is a separator only in a
179
- * win-form path; in a POSIX path it is an ordinary filename character, and the classifier's old
180
- * unconditional `\`→`/` fold rewrote a POSIX file literally named `a\..\b` into a different location
181
- * before the boundary check ever saw it (both directions: false refusal and false allow).
172
+ * FAMILY-awareness is the part a private copy tends to get wrong. A backslash is a separator only in a
173
+ * win-form path; in a POSIX path it is an ordinary filename character, and an unconditional `\`→`/`
174
+ * fold rewrites a POSIX file literally named `a\..\b` into a different location before the boundary
175
+ * check ever sees it — in both directions: false refusal and false allow.
182
176
  */
183
177
  export declare function normalizeAbsPathLexically(p: string): string;
184
178
  /**
185
- * The READ-leg fold (REF-C3 receipt, R1 verify ISSUE-1): removes `.` segments and duplicate
179
+ * The READ-leg fold: removes `.` segments and duplicate
186
180
  * separators ONLY — transformations that name the SAME file on every real filesystem, symlinks
187
181
  * included. `..` is deliberately KEPT: `a/../x` and `x` are different files when `a` is a symlink,
188
182
  * and this function's callers go on to OPEN the result (fileInfo/readText), not just to mint a
@@ -197,7 +191,7 @@ export declare function hasBinaryExtension(path: string): boolean;
197
191
  export declare function imageMimeForRead(path: string): string | undefined;
198
192
  /** Verify the raw bytes actually ARE the image format the extension claims (magic-number sniff). Extension alone
199
193
  * is forgeable: a `.png` holding text / an empty file / a truncated blob would otherwise become a malformed
200
- * `ImageContent` that makes the NEXT provider request fail (400). Pure — no I/O. (batch-C.) */
194
+ * `ImageContent` that makes the NEXT provider request fail (400). Pure — no I/O. */
201
195
  export declare function imageMagicMatches(bytes: Uint8Array, mimeType: string): boolean;
202
196
  /** One contiguous byte pattern that must appear at a fixed offset for its signature to match. */
203
197
  export interface BinaryMagicSegment {
@@ -216,11 +210,11 @@ export interface BinaryMagicSignature {
216
210
  readonly segments: readonly BinaryMagicSegment[];
217
211
  }
218
212
  /**
219
- * ANG-5 (external black-box re-review, ruled 2026-08-03) magic-byte criteria for the formats
220
- * {@link hasBinaryExtension} names. That list carries 103 extensions; before this table only PDF
221
- * (`pdfMagicMatches`, in the PDF lane) and the four API image formats ({@link imageMagicMatches}) had a
222
- * content criterion, so for every other listed format the NAME was the only evidence available. Where the
223
- * content IS reachable that left identification to the statistical fallback ({@link isBinaryContent}),
213
+ * Magic-byte criteria for the formats {@link hasBinaryExtension} names. That list carries 103
214
+ * extensions; without this table only PDF (`pdfMagicMatches`, in the PDF lane) and the four API image
215
+ * formats ({@link imageMagicMatches}) have a content criterion, so for every other listed format the
216
+ * NAME is the only evidence available. Where the content IS reachable that leaves identification to
217
+ * the statistical fallback ({@link isBinaryContent}),
224
218
  * which samples the first 8192 decoded characters and passes anything whose control-character ratio
225
219
  * stays under 10% — a short archive/executable header followed by printable payload clears that bar
226
220
  * and gets delivered as text.
@@ -249,36 +243,33 @@ export declare const BINARY_MAGIC_SIGNATURES: readonly BinaryMagicSignature[];
249
243
  */
250
244
  export declare function binaryMagicFormat(bytes: Uint8Array): string | undefined;
251
245
  /**
252
- * Content sniff for binary data (design/64 §17.1, CC `constants/files.ts` `isBinaryContent`): a NUL byte,
246
+ * Content sniff for binary data (CC `isBinaryContent` parity): a NUL byte,
253
247
  * or more than 10% non-printable characters in the sample, means binary. Second layer behind
254
248
  * {@link hasBinaryExtension} — catches extension-less or mis-named binaries (a `Dockerfile` that is really
255
249
  * a compiled blob) that would otherwise be read as mojibake. Plain text (incl. UTF-8, tabs/newlines) passes.
256
250
  */
257
251
  export declare function isBinaryContent(sample: string): boolean;
258
252
  /**
259
- * CC's own UNC/network-path predicate, verbatim (220 @515523:
260
- * `n.startsWith("\\\\") || n.startsWith("//")`). Pure.
253
+ * CC's own UNC/network-path predicate, verbatim (`n.startsWith("\\\\") || n.startsWith("//")`). Pure.
261
254
  *
262
- * RB-279: the predicate is CC's, but CC's POLARITY is the opposite of what sema used it for. In CC this
263
- * test is an EARLY ALLOW `return { result: !0 }`, skipping the binary-extension and device checks that
264
- * follow it and CC applies it to the NORMALIZED path (`Li` `path.normalize`), not the raw argument.
265
- * See {@link canonicalizeTarget} for how sema now mirrors that (and for the two deliberate differences).
266
- * This function stays a pure spelling test with no verdict of its own.
255
+ * In CC this test is an EARLY ALLOW it skips the binary-extension and device checks that follow it
256
+ * and CC applies it to the NORMALIZED path, not the raw argument. See {@link canonicalizeTarget} for
257
+ * how sema mirrors that and for the two deliberate differences. This function stays a pure spelling
258
+ * test with no verdict of its own.
267
259
  */
268
260
  export declare function isUncPath(path: string): boolean;
269
- /** design/199 件B — the family-aware LEXICAL view of a possibly-relative spelling against a
270
- * resolution base (no I/O, `.`/`..` folded via {@link normalizeAbsPathLexically}): the §3.3 second
271
- * deny-judgment view. It asks "does the NAME land on a guarded path", complementing the canonical
261
+ /** The family-aware LEXICAL view of a possibly-relative spelling against a resolution base (no I/O,
262
+ * `.`/`..` folded via {@link normalizeAbsPathLexically}): the SECOND deny-judgment view. It asks
263
+ * "does the NAME land on a guarded path", complementing the canonical
272
264
  * view's "does the TARGET" — a symlink AT a guarded spelling resolves away from it canonically,
273
265
  * and only this view still names it. */
274
266
  export declare function lexicalViewOf(spelled: string, base: string): string;
275
267
  /**
276
- * Resolve a model-supplied path to a canonical key AND enforce rootPath containment (design/44 §4
277
- * inv 5/6/7). Existing paths use `canonicalPath` (resolves symlinks, so the same file can't get two
278
- * keys). New paths canonicalize the **deepest existing ancestor** then rejoin the missing tail — a
279
- * symlinked parent therefore cannot land the target outside root. Returns the canonical key, or a
280
- * `path_not_in_root` violation. (Defense-in-depth at the tool layer; NOT a substitute for a sandboxed
281
- * `executionEnv` — see design/44 §5.)
268
+ * Resolve a model-supplied path to a canonical key AND enforce rootPath containment. Existing paths
269
+ * use `canonicalPath` (resolves symlinks, so the same file cannot get two keys). New paths
270
+ * canonicalize the **deepest existing ancestor** then rejoin the missing tail — a symlinked parent
271
+ * therefore cannot land the target outside root. Returns the canonical key, or a `path_not_in_root`
272
+ * violation. Defense-in-depth at the tool layer; NOT a substitute for a sandboxed `executionEnv`.
282
273
  */
283
274
  export declare function resolveKey(env: ExecutionEnv, rootCanonical: string, path: string, signal?: AbortSignal, baseCwd?: string, additionalRootsCanonical?: readonly string[], exactFileReadExemption?: (canonicalKey: string) => boolean, readDeny?: {
284
275
  matchTarget(canonicalKey: string, lexicalView?: string, opts?: {
@@ -299,9 +290,9 @@ export declare function resolveKey(env: ExecutionEnv, rootCanonical: string, pat
299
290
  * doesn't exist yet, the deepest EXISTING ancestor is canonicalized then the missing tail rejoined —
300
291
  * so a symlinked parent cannot land the target elsewhere). NO containment check — this is the pure
301
292
  * canonicalization shared by `resolveKey` (which adds rootPath containment) and
302
- * `createSensitivePathPolicy` (design/72 §2.1, which matches the real target against a guarded list).
293
+ * `createSensitivePathPolicy` (which matches the real target against a guarded list).
303
294
  * A single source of truth for symlink resolution: a sensitive-path policy that matched the raw path
304
- * instead of the real target would compare against the spelling, not the destination (§2.1).
295
+ * instead of the real target would compare against the spelling, not the destination.
305
296
  */
306
297
  export declare function canonicalizeTarget(env: ExecutionEnv, path: string, signal?: AbortSignal, baseCwd?: string): Promise<{
307
298
  ok: true;
@@ -315,16 +306,16 @@ export declare function canonicalizeTarget(env: ExecutionEnv, path: string, sign
315
306
  /** Render a `FsViolation` as the model-facing tool error text (returned, never thrown — model retries). */
316
307
  export declare function violationText(toolName: string, v: FsViolation): string;
317
308
  /**
318
- * RB-371 ④ — structured `details` for a rendered violation: non-`undefined` ONLY for
319
- * `path_not_in_root`, the one code with per-refusal coordinates worth counting (which canonical
320
- * target, against which roots). Designed to be passed straight as `errorResult`'s second argument by
321
- * every renderer of a {@link resolveKey} violation — `undefined` keeps the pre-existing detail-less
322
- * shape for all other violation codes, so nothing else changes byte-wise.
309
+ * Structured `details` for a rendered violation: non-`undefined` ONLY for the two codes with
310
+ * per-refusal coordinates worth counting (which canonical target, against which roots or which deny
311
+ * pattern). Designed to be passed straight as `errorResult`'s second argument by every renderer of a
312
+ * {@link resolveKey} violation — `undefined` keeps the detail-less shape for all other violation
313
+ * codes.
323
314
  *
324
- * RB-397-c (回溯复查批): `type` is the WIRE discriminator the leg's card projector (`createStructuredProjector`, design/388 L4)
325
- * (tool-output-projection.ts) keys on `details.type`, so without it the card never reached
326
- * `tool_end.structured` and the "count from fields" promise above held for in-process readers only.
327
- * `code` stays as-is for the existing RB-371 consumers the new key is strictly additive.
315
+ * `type` is the WIRE discriminator: the card projector (`createStructuredProjector`,
316
+ * tool-output-projection.ts) keys on `details.type`, so without it the card never reaches
317
+ * `tool_end.structured` and the "count from fields" promise above holds for in-process readers only.
318
+ * `code` carries the same value for consumers that read it.
328
319
  */
329
320
  export declare function violationDetails(v: FsViolation): {
330
321
  type: "path_not_in_root";
@@ -338,7 +329,7 @@ export declare function violationDetails(v: FsViolation): {
338
329
  pattern: string;
339
330
  } | undefined;
340
331
  /**
341
- * RB-371 ② — does `p` sit inside ANY of the canonical containment roots? Exposed for the Bash
332
+ * Does `p` sit inside ANY of the canonical containment roots? Exposed for the Bash
342
333
  * tool's post-`cd` disclosure note (fs-bash.ts): the note reuses the SAME segment-aware `within`
343
334
  * the fence itself uses, so the disclosure and the fence can never disagree about what counts as
344
335
  * "inside". Pure/lexical — callers own any canonicalization of `p` (the tracked cwd stays raw by
@@ -346,37 +337,34 @@ export declare function violationDetails(v: FsViolation): {
346
337
  */
347
338
  export declare function withinAnyRoot(rootsCanonical: readonly string[], p: string): boolean;
348
339
  /**
349
- * RB-371 ① — the escape-hatch tail for the `path_not_in_root` refusal: its three sibling hints
350
- * below each name a sanctioned next step; this refusal named none, leaving the model to oscillate
351
- * between "the boundary is hard" and hunting for a way around it.
340
+ * The escape-hatch tail for the `path_not_in_root` refusal: like its three sibling hints below, it
341
+ * names a sanctioned next step, so the model does not oscillate between "the boundary is hard" and
342
+ * hunting for a way around it.
352
343
  *
353
344
  * The card names the SANCTIONED way out and nothing else: widening the declaration
354
- * (`additionalDirectories` / read-only `additionalReadDirectories`, design/119), or the
355
- * deployment-level `readFace: "open"` for reads.
345
+ * (`additionalDirectories` / read-only `additionalReadDirectories`), or the deployment-level
346
+ * `readFace: "open"` for reads.
356
347
  *
357
- * A trailing sentence naming the shell as unconfined by this fence was REMOVED (it had been kept on
358
- * the argument that a statement of fact is not a recommendation, and that omitting it would only let
359
- * the model discover the same thing by trial). A live retest settled that argument the other way: a
360
- * refused agent read the sentence as the next step, went to the shell, and rewrote through it the
361
- * very file the fence had just protected. A refusal card is a PROMPT surface naming, at the moment
362
- * of denial, a second tool that does not enforce the boundary is a recipe whatever its grammar, and
363
- * the model cannot be relied on to hear "fact, not advice". The underlying fact is unchanged and
364
- * still documented where humans read it (design/44 §5: the shell is deliberately unconfined by this
365
- * fence, and every shell call still passes the deployment's approval policy); it is simply no longer
366
- * handed to the model inside a denial. What the card says now: this is denied, and here is the
367
- * sanctioned way to be allowed.
348
+ * It deliberately does NOT name the shell as unconfined by this fence, even though that is a true
349
+ * statement rather than a recommendation. A live retest settled the question: a refused agent read
350
+ * such a sentence as the next step, went to the shell, and rewrote through it the very file the fence
351
+ * had just protected. A refusal card is a PROMPT surface — naming, at the moment of denial, a second
352
+ * tool that does not enforce the boundary is a recipe whatever its grammar, and the model cannot be
353
+ * relied on to hear "fact, not advice". The underlying fact is documented where humans read it (the
354
+ * shell is deliberately unconfined by this fence, and every shell call still passes the deployment's
355
+ * approval policy); it is simply not handed to the model inside a denial.
368
356
  */
369
357
  export declare const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories \u2014 or additionalReadDirectories for read-only access; a deployment that wants reads open everywhere can declare readFace: \"open\" instead of listing directories.)";
370
358
  /** inv 1 (read-before-edit): a file must have been read this task before it can be edited/overwritten.
371
359
  * Message is CC 2.1.198 live-verbatim (all-tools-live-probe 2026-07-08 §2.1/§3.1/§5.1 — one message for
372
360
  * Edit/Write/NotebookEdit: "before writing to it", not the old sema "before editing").
373
361
  *
374
- * RB-200 F2 (CC 220 corpus-confirmed, @367198/@367671 — Edit and Write spell the read gate identically,
375
- * `!p || p.isPartialView`): an AUTO-TRUNCATED read (the model asked for the
376
- * file by default, no explicit offset/limit, and the token cap cut it anyway — {@link ReadEntry.isPartialView})
377
- * used to satisfy this gate exactly like a full read (`state.has(key)` alone was the whole check) the
378
- * model could Write (full overwrite) or Edit a file having genuinely seen only its first page, silently
379
- * destroying or blind-editing the unseen remainder. CC's own gate treats "never read" and "auto-truncated
362
+ * CC parity (Edit and Write spell the read gate identically, `!p || p.isPartialView`): an
363
+ * AUTO-TRUNCATED read (the model asked for the file by default, no explicit offset/limit, and the token
364
+ * cap cut it anyway — {@link ReadEntry.isPartialView}) must NOT satisfy this gate. With `state.has(key)`
365
+ * alone as the whole check, the model could Write (full overwrite) or Edit a file having genuinely seen
366
+ * only its first page, silently destroying or blind-editing the unseen remainder. CC's own gate treats
367
+ * "never read" and "auto-truncated
380
368
  * partial view" identically for BOTH tools — no softer rule for Edit (whose old_string match only touches
381
369
  * a small snippet) versus Write (which replaces everything), so this doesn't carve one out either.
382
370
  *
@@ -394,8 +382,7 @@ export declare function requireRead(state: ReadFileState, key: string): FsViolat
394
382
  */
395
383
  export declare const OVERSIZE_READ_ESCAPE_HINT = "(This file is over the Read tool's whole-file byte cap, so a default Read is refused \u2014 read it in slices with explicit offset/limit to satisfy the read-first rule, or inspect/transform it with bash (e.g. `sed -n`, `grep`) instead.)";
396
384
  /**
397
- * RB-264 N1 (repro `notebookedit-tool-read-gate-deadlock` assertion A.c2)
398
- * the escape-hatch parenthetical for the OTHER shape of the read-before-edit refusal: the file was read,
385
+ * The escape-hatch parenthetical for the OTHER shape of the read-before-edit refusal: the file was read,
399
386
  * but the read was AUTO-PAGINATED by the output token cap ({@link ReadEntry.isPartialView}), so only the
400
387
  * first page was ever seen. The oversize branch above has named its escape route since the fidelity
401
388
  * sweep; this branch named none — and it is the worse of the two, because the refusal's own advice
@@ -406,29 +393,29 @@ export declare const OVERSIZE_READ_ESCAPE_HINT = "(This file is over the Read to
406
393
  */
407
394
  export declare const PARTIAL_VIEW_READ_ESCAPE_HINT = "(Your last Read of this file returned only a PARTIAL view \u2014 the output token cap paginated it, so a default Read will keep returning the same page. Re-read it with explicit offset/limit (start from the page marker's next-page hint) until you have seen the part you are about to change; an explicit slice that fits satisfies the read-first rule. Or inspect/transform it with bash (e.g. `sed -n`, `grep`) instead.)";
408
395
  /**
409
- * RB-264 ⑥W1 — the escape-hatch parenthetical for the ONE shape of the
410
- * read-first deadlock that nothing observable diagnoses: a file the Read tool REFUSES (binary /
396
+ * The escape-hatch parenthetical for the ONE shape of the read-first deadlock that nothing observable
397
+ * diagnoses: a file the Read tool REFUSES (binary /
411
398
  * BOM-less UTF-16 / unknown encoding) can never satisfy read-before-write, and — unlike the oversize
412
399
  * and partial-view shapes — leaves no trace the refusal renderer can stat for. It is therefore the
413
400
  * renderer's FALLBACK hint (least specific, appended only when no sharper branch fired); appending it
414
401
  * beside a sharper one would assert something false, since both of those shapes are files Read handled.
415
402
  * Kept byte-identical to the sentence Write rendered inline before it moved here.
416
403
  *
417
- * ANG-6 (external black-box re-review, ruled 2026-08-03): the deadlock is a property of the READ GATE,
404
+ * The deadlock is a property of the READ GATE,
418
405
  * not of Write — Edit and NotebookEdit hit the identical dead end and used to render the bare sentence
419
406
  * with no way out named. All three now pass this one constant, which is why it is no longer named for
420
407
  * the tool that happened to have it first: one sentence, three call sites, nothing to drift apart.
421
408
  */
422
409
  export declare const READ_REFUSED_ESCAPE_HINT = "(If the Read tool refuses this file (binary/unknown encoding), overwrite or convert it with bash instead \u2014 e.g. `rm` + rewrite, or `iconv`.)";
423
410
  /**
424
- * Edit "no-op" guard (design/64 §7.2(4), CC `FileEditTool:148-153`): refuse an edit whose `old_string`
411
+ * Edit "no-op" guard (CC FileEditTool parity): refuse an edit whose `old_string`
425
412
  * equals `new_string` — without it the edit passes the match check and writes the file back unchanged (a
426
413
  * silent no-op that wastes a turn and dirties mtime). Verbatim CC message so a CC-trained model recognizes it.
427
414
  */
428
415
  export declare function checkNoChange(oldString: string, newString: string): FsViolation | undefined;
429
416
  /** inv 2 (staleness): the file's current content hash must match what was recorded at read time.
430
417
  *
431
- * RB-436-g: takes `ReadEntry | undefined` and treats an ABSENT entry as stale (fail-closed). The three
418
+ * Takes `ReadEntry | undefined` and treats an ABSENT entry as stale (fail-closed). The three
432
419
  * write-side callers prove the entry exists with `requireRead`, then re-read the file — an `await` — and
433
420
  * only then reach here, so each was asserting a pre-await invariant across a suspension point with `!`.
434
421
  * Nothing deletes read state mid-turn today (the compaction sweep runs at a turn boundary), so behaviour
@@ -441,7 +428,7 @@ export declare function countOccurrences(haystack: string, needle: string): numb
441
428
  * Suggest a sibling filename for a missing path (CC `File does not exist… Did you mean X?` self-heal
442
429
  * path, bundle :335883/:478419). Honest scope (1.253 双轨终审): this is the FALLBACK tier — an
443
430
  * approximation of CC's Cxe same-directory scan plus a sema-added case-insensitive exact-name match
444
- * (the case-typo class CC misses). RB-285 (归因更正): CC's FIRST-tier suggester is `G_e` — a GATED
431
+ * (the case-typo class CC misses). Registered delta: CC's FIRST-tier suggester is a GATED
445
432
  * sibling-tree REBASE (`join(cwd, relative(dirname(cwd), missing))`, only for a path under the cwd's
446
433
  * parent but not under the cwd), NOT the "re-resolve the filename against cwd" this comment used to
447
434
  * claim; the full geometry and the sema delta are recorded at the call site (fs-shared.ts
@@ -460,14 +447,14 @@ export declare function normalizeQuotes(s: string): string;
460
447
  */
461
448
  export declare function resolveQuoteMatch(content: string, oldString: string): string | undefined;
462
449
  /**
463
- * RB-285 M9 (CC `Pdt` layers 3+4, @366594-366600): resolve `oldString` against `content` across the
450
+ * CC's forgiving-match layers 3+4: resolve `oldString` against `content` across the
464
451
  * escape/literal spelling boundary. Returns the ACTUAL span present in `content` (which is what must be
465
452
  * replaced), or `undefined` when neither spelling is present. Pure — call only after an exact match and
466
453
  * the curly-quote layer have both missed.
467
454
  */
468
455
  export declare function resolveEscapeMatch(content: string, oldString: string): string | undefined;
469
456
  /**
470
- * RB-285 M9 (CC `YZu` verbatim, @366561-366587): rewrite `newString` into the SAME spelling the matched
457
+ * CC `YZu` verbatim: rewrite `newString` into the SAME spelling the matched
471
458
  * span uses. Without this, a match found through {@link resolveEscapeMatch} would replace an escaped
472
459
  * span with a literal-character replacement (or vice versa) and silently change the file's encoding
473
460
  * convention at that spot. Hex case follows what the file itself used, per-character where known and by
@@ -477,7 +464,7 @@ export declare function adaptNewStringEscapes(oldString: string, matchedOld: str
477
464
  /** CC `KZu` (@366558): the old_string shapes for which the escape layers above were even attempted —
478
465
  * gates the explanatory note appended to a no-match refusal so it is never a false claim. */
479
466
  export declare function escapeMatchWasAttempted(oldString: string): boolean;
480
- /** RB-285 M9 — CC `KZu`'s note (@367245-367248) verbatim. */
467
+ /** CC `KZu`'s note, verbatim. */
481
468
  export declare const ESCAPE_MATCH_MISS_NOTE = "\n(note: Edit also tried swapping \\uXXXX escapes and their characters; neither form matched, so the mismatch is likely elsewhere in old_string. Re-read the file and copy the exact surrounding text.)";
482
469
  /**
483
470
  * When the quote-forgiving match resolved to a curly-quoted file span, convert the straight quotes in