@vellumai/assistant 0.4.17 → 0.4.18

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 (515) hide show
  1. package/eslint.config.mjs +2 -2
  2. package/package.json +1 -1
  3. package/src/__tests__/access-request-decision.test.ts +128 -120
  4. package/src/__tests__/account-registry.test.ts +121 -110
  5. package/src/__tests__/active-skill-tools.test.ts +200 -172
  6. package/src/__tests__/actor-token-service.test.ts +341 -274
  7. package/src/__tests__/agent-loop-thinking.test.ts +28 -19
  8. package/src/__tests__/agent-loop.test.ts +798 -378
  9. package/src/__tests__/anthropic-provider.test.ts +405 -247
  10. package/src/__tests__/app-builder-tool-scripts.test.ts +97 -97
  11. package/src/__tests__/app-bundler.test.ts +112 -79
  12. package/src/__tests__/app-executors.test.ts +205 -178
  13. package/src/__tests__/app-git-history.test.ts +90 -73
  14. package/src/__tests__/app-git-service.test.ts +67 -53
  15. package/src/__tests__/app-open-proxy.test.ts +29 -25
  16. package/src/__tests__/approval-conversation-turn.test.ts +100 -81
  17. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +45 -17
  18. package/src/__tests__/approval-message-composer.test.ts +119 -119
  19. package/src/__tests__/approval-primitive.test.ts +264 -233
  20. package/src/__tests__/approval-routes-http.test.ts +4 -3
  21. package/src/__tests__/asset-materialize-tool.test.ts +250 -178
  22. package/src/__tests__/asset-search-tool.test.ts +251 -191
  23. package/src/__tests__/assistant-attachment-directive.test.ts +187 -142
  24. package/src/__tests__/assistant-attachments.test.ts +254 -186
  25. package/src/__tests__/assistant-event-hub.test.ts +105 -63
  26. package/src/__tests__/assistant-event.test.ts +66 -58
  27. package/src/__tests__/assistant-events-sse-hardening.test.ts +113 -73
  28. package/src/__tests__/assistant-feature-flag-guard.test.ts +78 -52
  29. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +48 -45
  30. package/src/__tests__/assistant-feature-flags-integration.test.ts +118 -77
  31. package/src/__tests__/assistant-id-boundary-guard.test.ts +158 -104
  32. package/src/__tests__/attachments-store.test.ts +240 -183
  33. package/src/__tests__/attachments.test.ts +70 -62
  34. package/src/__tests__/audit-log-rotation.test.ts +50 -35
  35. package/src/__tests__/browser-fill-credential.test.ts +169 -101
  36. package/src/__tests__/browser-manager.test.ts +97 -75
  37. package/src/__tests__/browser-runtime-check.test.ts +16 -15
  38. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +12 -10
  39. package/src/__tests__/browser-skill-endstate.test.ts +97 -72
  40. package/src/__tests__/bundle-scanner.test.ts +47 -22
  41. package/src/__tests__/bundled-asset.test.ts +74 -47
  42. package/src/__tests__/call-constants.test.ts +19 -19
  43. package/src/__tests__/call-controller.test.ts +0 -1
  44. package/src/__tests__/call-conversation-messages.test.ts +90 -65
  45. package/src/__tests__/call-domain.test.ts +149 -121
  46. package/src/__tests__/call-pointer-message-composer.test.ts +113 -83
  47. package/src/__tests__/call-pointer-messages.test.ts +213 -154
  48. package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +9 -10
  49. package/src/__tests__/call-recovery.test.ts +232 -212
  50. package/src/__tests__/call-routes-http.test.ts +0 -1
  51. package/src/__tests__/call-start-guardian-guard.test.ts +32 -30
  52. package/src/__tests__/call-state-machine.test.ts +62 -51
  53. package/src/__tests__/call-state.test.ts +89 -75
  54. package/src/__tests__/call-store.test.ts +387 -316
  55. package/src/__tests__/callback-handoff-copy.test.ts +84 -82
  56. package/src/__tests__/canonical-guardian-store.test.ts +331 -280
  57. package/src/__tests__/channel-approval-routes.test.ts +1643 -1115
  58. package/src/__tests__/channel-approval.test.ts +139 -137
  59. package/src/__tests__/channel-approvals.test.ts +0 -1
  60. package/src/__tests__/channel-delivery-store.test.ts +232 -194
  61. package/src/__tests__/channel-guardian.test.ts +5 -3
  62. package/src/__tests__/channel-invite-transport.test.ts +107 -92
  63. package/src/__tests__/channel-policy.test.ts +42 -38
  64. package/src/__tests__/channel-readiness-service.test.ts +119 -102
  65. package/src/__tests__/channel-reply-delivery.test.ts +147 -118
  66. package/src/__tests__/channel-retry-sweep.test.ts +153 -110
  67. package/src/__tests__/checker.test.ts +3309 -1850
  68. package/src/__tests__/clarification-resolver.test.ts +91 -79
  69. package/src/__tests__/classifier.test.ts +64 -54
  70. package/src/__tests__/claude-code-skill-regression.test.ts +42 -37
  71. package/src/__tests__/claude-code-tool-profiles.test.ts +31 -29
  72. package/src/__tests__/clawhub.test.ts +92 -82
  73. package/src/__tests__/cli.test.ts +30 -30
  74. package/src/__tests__/clipboard.test.ts +53 -46
  75. package/src/__tests__/commit-guarantee.test.ts +59 -52
  76. package/src/__tests__/commit-message-enrichment-service.test.ts +203 -75
  77. package/src/__tests__/compaction.benchmark.test.ts +33 -31
  78. package/src/__tests__/computer-use-session-compaction.test.ts +60 -50
  79. package/src/__tests__/computer-use-session-lifecycle.test.ts +145 -117
  80. package/src/__tests__/computer-use-session-working-dir.test.ts +62 -48
  81. package/src/__tests__/computer-use-skill-baseline.test.ts +22 -19
  82. package/src/__tests__/computer-use-skill-endstate.test.ts +45 -31
  83. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +121 -88
  84. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +65 -42
  85. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +33 -18
  86. package/src/__tests__/computer-use-tools.test.ts +121 -98
  87. package/src/__tests__/config-schema.test.ts +443 -347
  88. package/src/__tests__/config-watcher.test.ts +96 -81
  89. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +148 -133
  90. package/src/__tests__/conflict-intent-tokenization.test.ts +96 -78
  91. package/src/__tests__/conflict-policy.test.ts +151 -80
  92. package/src/__tests__/conflict-store.test.ts +203 -157
  93. package/src/__tests__/connection-policy.test.ts +89 -59
  94. package/src/__tests__/contacts-tools.test.ts +247 -178
  95. package/src/__tests__/context-memory-e2e.test.ts +306 -214
  96. package/src/__tests__/context-token-estimator.test.ts +114 -74
  97. package/src/__tests__/context-window-manager.test.ts +269 -167
  98. package/src/__tests__/contradiction-checker.test.ts +161 -135
  99. package/src/__tests__/conversation-attention-store.test.ts +350 -290
  100. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  101. package/src/__tests__/conversation-pairing.test.ts +220 -113
  102. package/src/__tests__/conversation-store.test.ts +390 -235
  103. package/src/__tests__/credential-broker-browser-fill.test.ts +325 -250
  104. package/src/__tests__/credential-broker-server-use.test.ts +283 -243
  105. package/src/__tests__/credential-broker.test.ts +128 -74
  106. package/src/__tests__/credential-host-pattern-match.test.ts +64 -44
  107. package/src/__tests__/credential-metadata-store.test.ts +360 -311
  108. package/src/__tests__/credential-policy-validate.test.ts +81 -65
  109. package/src/__tests__/credential-resolve.test.ts +212 -145
  110. package/src/__tests__/credential-security-e2e.test.ts +144 -103
  111. package/src/__tests__/credential-security-invariants.test.ts +253 -208
  112. package/src/__tests__/credential-selection.test.ts +254 -146
  113. package/src/__tests__/credential-vault-unit.test.ts +531 -341
  114. package/src/__tests__/credential-vault.test.ts +761 -484
  115. package/src/__tests__/daemon-assistant-events.test.ts +91 -66
  116. package/src/__tests__/daemon-lifecycle.test.ts +258 -190
  117. package/src/__tests__/daemon-server-session-init.test.ts +0 -1
  118. package/src/__tests__/date-context.test.ts +314 -249
  119. package/src/__tests__/db-migration-rollback.test.ts +259 -130
  120. package/src/__tests__/db-schedule-syntax-migration.test.ts +78 -41
  121. package/src/__tests__/delete-managed-skill-tool.test.ts +77 -53
  122. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
  123. package/src/__tests__/dictation-mode-detection.test.ts +77 -55
  124. package/src/__tests__/dictation-profile-store.test.ts +70 -56
  125. package/src/__tests__/dictation-text-processing.test.ts +53 -35
  126. package/src/__tests__/diff.test.ts +102 -98
  127. package/src/__tests__/domain-normalize.test.ts +54 -54
  128. package/src/__tests__/domain-policy.test.ts +71 -55
  129. package/src/__tests__/dynamic-page-surface.test.ts +31 -33
  130. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +69 -69
  131. package/src/__tests__/edit-engine.test.ts +56 -56
  132. package/src/__tests__/elevenlabs-client.test.ts +117 -91
  133. package/src/__tests__/elevenlabs-config.test.ts +32 -31
  134. package/src/__tests__/email-classifier.test.ts +15 -12
  135. package/src/__tests__/email-cli.test.ts +121 -108
  136. package/src/__tests__/emit-signal-routing-intent.test.ts +76 -69
  137. package/src/__tests__/encrypted-store.test.ts +180 -154
  138. package/src/__tests__/entity-extractor.test.ts +108 -87
  139. package/src/__tests__/entity-search.test.ts +664 -258
  140. package/src/__tests__/ephemeral-permissions.test.ts +224 -188
  141. package/src/__tests__/event-bus.test.ts +81 -77
  142. package/src/__tests__/extract-email.test.ts +29 -20
  143. package/src/__tests__/file-edit-tool.test.ts +62 -44
  144. package/src/__tests__/file-ops-service.test.ts +131 -114
  145. package/src/__tests__/file-read-tool.test.ts +48 -31
  146. package/src/__tests__/file-write-tool.test.ts +43 -37
  147. package/src/__tests__/filesystem-tools.test.ts +238 -209
  148. package/src/__tests__/followup-tools.test.ts +237 -162
  149. package/src/__tests__/forbidden-legacy-symbols.test.ts +19 -20
  150. package/src/__tests__/frontmatter.test.ts +96 -81
  151. package/src/__tests__/fuzzy-match-property.test.ts +75 -81
  152. package/src/__tests__/fuzzy-match.test.ts +71 -65
  153. package/src/__tests__/gateway-client-managed-outbound.test.ts +76 -57
  154. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  155. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  156. package/src/__tests__/gemini-image-service.test.ts +113 -100
  157. package/src/__tests__/gemini-provider.test.ts +297 -220
  158. package/src/__tests__/get-weather.test.ts +188 -114
  159. package/src/__tests__/gmail-integration.test.ts +0 -1
  160. package/src/__tests__/guardian-action-conversation-turn.test.ts +226 -171
  161. package/src/__tests__/guardian-action-copy-generator.test.ts +111 -93
  162. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
  163. package/src/__tests__/guardian-action-followup-store.test.ts +199 -167
  164. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +297 -250
  165. package/src/__tests__/guardian-action-late-reply.test.ts +462 -316
  166. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +23 -18
  167. package/src/__tests__/guardian-action-store.test.ts +158 -109
  168. package/src/__tests__/guardian-action-sweep.test.ts +114 -100
  169. package/src/__tests__/guardian-actions-endpoint.test.ts +440 -256
  170. package/src/__tests__/guardian-control-plane-policy.test.ts +497 -331
  171. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +217 -215
  172. package/src/__tests__/guardian-dispatch.test.ts +316 -256
  173. package/src/__tests__/guardian-grant-minting.test.ts +247 -178
  174. package/src/__tests__/guardian-outbound-http.test.ts +5 -3
  175. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +99 -96
  176. package/src/__tests__/guardian-question-copy.test.ts +17 -17
  177. package/src/__tests__/guardian-question-mode.test.ts +134 -100
  178. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  179. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  180. package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
  181. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  182. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
  183. package/src/__tests__/handle-user-message-secret-resume.test.ts +0 -1
  184. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +92 -76
  185. package/src/__tests__/handlers-cu-observation-blob.test.ts +103 -70
  186. package/src/__tests__/handlers-ipc-blob-probe.test.ts +77 -51
  187. package/src/__tests__/handlers-slack-config.test.ts +63 -54
  188. package/src/__tests__/handlers-task-submit-slash.test.ts +18 -18
  189. package/src/__tests__/handlers-telegram-config.test.ts +662 -329
  190. package/src/__tests__/handlers-twitter-config.test.ts +525 -298
  191. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -2
  192. package/src/__tests__/headless-browser-interactions.test.ts +444 -280
  193. package/src/__tests__/headless-browser-navigate.test.ts +116 -79
  194. package/src/__tests__/headless-browser-read-tools.test.ts +123 -86
  195. package/src/__tests__/headless-browser-snapshot.test.ts +71 -56
  196. package/src/__tests__/heartbeat-service.test.ts +76 -58
  197. package/src/__tests__/history-repair-observability.test.ts +14 -14
  198. package/src/__tests__/history-repair.test.ts +171 -167
  199. package/src/__tests__/home-base-bootstrap.test.ts +30 -27
  200. package/src/__tests__/hooks-blocking.test.ts +86 -37
  201. package/src/__tests__/hooks-cli.test.ts +104 -68
  202. package/src/__tests__/hooks-config.test.ts +81 -43
  203. package/src/__tests__/hooks-discovery.test.ts +106 -96
  204. package/src/__tests__/hooks-integration.test.ts +78 -72
  205. package/src/__tests__/hooks-manager.test.ts +99 -61
  206. package/src/__tests__/hooks-runner.test.ts +94 -71
  207. package/src/__tests__/hooks-settings.test.ts +69 -64
  208. package/src/__tests__/hooks-templates.test.ts +85 -54
  209. package/src/__tests__/hooks-ts-runner.test.ts +82 -45
  210. package/src/__tests__/hooks-watch.test.ts +32 -22
  211. package/src/__tests__/host-file-edit-tool.test.ts +190 -148
  212. package/src/__tests__/host-file-read-tool.test.ts +86 -63
  213. package/src/__tests__/host-file-write-tool.test.ts +98 -64
  214. package/src/__tests__/host-shell-tool.test.ts +342 -233
  215. package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
  216. package/src/__tests__/ingress-member-store.test.ts +163 -159
  217. package/src/__tests__/ingress-reconcile.test.ts +0 -1
  218. package/src/__tests__/ingress-routes-http.test.ts +441 -356
  219. package/src/__tests__/ingress-url-consistency.test.ts +125 -64
  220. package/src/__tests__/integration-status.test.ts +93 -73
  221. package/src/__tests__/intent-routing.test.ts +148 -118
  222. package/src/__tests__/invite-redemption-service.test.ts +163 -121
  223. package/src/__tests__/ipc-blob-store.test.ts +104 -91
  224. package/src/__tests__/ipc-contract-inventory.test.ts +27 -15
  225. package/src/__tests__/ipc-contract.test.ts +24 -23
  226. package/src/__tests__/ipc-protocol.test.ts +52 -46
  227. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +61 -50
  228. package/src/__tests__/ipc-snapshot.test.ts +1135 -1056
  229. package/src/__tests__/ipc-validate.test.ts +240 -179
  230. package/src/__tests__/key-migration.test.ts +123 -90
  231. package/src/__tests__/keychain.test.ts +150 -123
  232. package/src/__tests__/lifecycle-docs-guard.test.ts +65 -64
  233. package/src/__tests__/llm-usage-store.test.ts +112 -87
  234. package/src/__tests__/managed-skill-lifecycle.test.ts +147 -108
  235. package/src/__tests__/managed-store.test.ts +411 -360
  236. package/src/__tests__/mcp-cli.test.ts +189 -123
  237. package/src/__tests__/mcp-health-check.test.ts +26 -21
  238. package/src/__tests__/media-generate-image.test.ts +122 -99
  239. package/src/__tests__/media-reuse-story.e2e.test.ts +282 -214
  240. package/src/__tests__/media-visibility-policy.test.ts +86 -38
  241. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +146 -100
  242. package/src/__tests__/memory-lifecycle-e2e.test.ts +385 -297
  243. package/src/__tests__/memory-query-builder.test.ts +32 -33
  244. package/src/__tests__/memory-recall-quality.test.ts +761 -407
  245. package/src/__tests__/memory-regressions.experimental.test.ts +443 -380
  246. package/src/__tests__/memory-regressions.test.ts +3725 -2642
  247. package/src/__tests__/memory-retrieval-budget.test.ts +7 -8
  248. package/src/__tests__/memory-retrieval.benchmark.test.ts +144 -109
  249. package/src/__tests__/memory-upsert-concurrency.test.ts +292 -201
  250. package/src/__tests__/messaging-send-tool.test.ts +36 -29
  251. package/src/__tests__/migration-cli-flows.test.ts +69 -53
  252. package/src/__tests__/migration-ordering.test.ts +103 -86
  253. package/src/__tests__/mime-builder.test.ts +55 -32
  254. package/src/__tests__/mock-signup-server.test.ts +384 -246
  255. package/src/__tests__/model-intents.test.ts +61 -37
  256. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +9 -12
  257. package/src/__tests__/no-is-trusted-guard.test.ts +24 -21
  258. package/src/__tests__/non-member-access-request.test.ts +3 -2
  259. package/src/__tests__/notification-broadcaster.test.ts +99 -81
  260. package/src/__tests__/notification-decision-fallback.test.ts +223 -178
  261. package/src/__tests__/notification-decision-strategy.test.ts +375 -337
  262. package/src/__tests__/notification-deep-link.test.ts +67 -61
  263. package/src/__tests__/notification-guardian-path.test.ts +248 -206
  264. package/src/__tests__/notification-routing-intent.test.ts +166 -93
  265. package/src/__tests__/notification-thread-candidate-validation.test.ts +78 -75
  266. package/src/__tests__/notification-thread-candidates.test.ts +64 -61
  267. package/src/__tests__/oauth-callback-registry.test.ts +40 -30
  268. package/src/__tests__/oauth-connect-handler.test.ts +109 -89
  269. package/src/__tests__/oauth-scope-policy.test.ts +63 -55
  270. package/src/__tests__/oauth2-gateway-transport.test.ts +252 -174
  271. package/src/__tests__/onboarding-starter-tasks.test.ts +93 -89
  272. package/src/__tests__/onboarding-template-contract.test.ts +93 -94
  273. package/src/__tests__/openai-provider.test.ts +366 -274
  274. package/src/__tests__/pairing-concurrent.test.ts +18 -12
  275. package/src/__tests__/pairing-routes.test.ts +45 -41
  276. package/src/__tests__/parallel-tool.benchmark.test.ts +108 -58
  277. package/src/__tests__/parser.test.ts +316 -226
  278. package/src/__tests__/path-classifier.test.ts +24 -25
  279. package/src/__tests__/path-policy.test.ts +187 -147
  280. package/src/__tests__/phone.test.ts +36 -36
  281. package/src/__tests__/platform-move-helper.test.ts +48 -40
  282. package/src/__tests__/platform-socket-path.test.ts +23 -24
  283. package/src/__tests__/platform-workspace-migration.test.ts +464 -414
  284. package/src/__tests__/platform.test.ts +61 -53
  285. package/src/__tests__/playbook-execution.test.ts +397 -265
  286. package/src/__tests__/playbook-tools.test.ts +267 -196
  287. package/src/__tests__/prebuilt-home-base-seed.test.ts +30 -27
  288. package/src/__tests__/pricing.test.ts +316 -136
  289. package/src/__tests__/profile-compiler.test.ts +206 -188
  290. package/src/__tests__/provider-commit-message-generator.test.ts +114 -106
  291. package/src/__tests__/provider-error-scenarios.test.ts +212 -158
  292. package/src/__tests__/provider-fail-open-selection.test.ts +51 -44
  293. package/src/__tests__/provider-registry-ollama.test.ts +13 -9
  294. package/src/__tests__/provider-streaming.benchmark.test.ts +232 -183
  295. package/src/__tests__/proxy-approval-callback.test.ts +180 -119
  296. package/src/__tests__/public-ingress-urls.test.ts +112 -94
  297. package/src/__tests__/qdrant-manager.test.ts +147 -98
  298. package/src/__tests__/ratelimit.test.ts +152 -82
  299. package/src/__tests__/recording-handler.test.ts +273 -151
  300. package/src/__tests__/recording-intent-fallback.test.ts +94 -75
  301. package/src/__tests__/recording-intent-handler.test.ts +0 -1
  302. package/src/__tests__/recording-intent.test.ts +578 -379
  303. package/src/__tests__/recording-state-machine.test.ts +530 -316
  304. package/src/__tests__/recurrence-engine-rruleset.test.ts +150 -92
  305. package/src/__tests__/recurrence-engine.test.ts +81 -41
  306. package/src/__tests__/recurrence-types.test.ts +63 -44
  307. package/src/__tests__/relay-server.test.ts +2131 -1602
  308. package/src/__tests__/reminder-store.test.ts +158 -80
  309. package/src/__tests__/reminder.test.ts +113 -109
  310. package/src/__tests__/remote-skill-policy.test.ts +96 -72
  311. package/src/__tests__/request-file-tool.test.ts +74 -67
  312. package/src/__tests__/response-tier.test.ts +131 -74
  313. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  314. package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
  315. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  316. package/src/__tests__/sandbox-diagnostics.test.ts +66 -56
  317. package/src/__tests__/sandbox-host-parity.test.ts +377 -301
  318. package/src/__tests__/scaffold-managed-skill-tool.test.ts +213 -161
  319. package/src/__tests__/schedule-store.test.ts +268 -205
  320. package/src/__tests__/schedule-tools.test.ts +702 -524
  321. package/src/__tests__/scheduler-recurrence.test.ts +240 -130
  322. package/src/__tests__/scoped-approval-grants.test.ts +258 -168
  323. package/src/__tests__/scoped-grant-security-matrix.test.ts +160 -146
  324. package/src/__tests__/script-proxy-certs.test.ts +38 -35
  325. package/src/__tests__/script-proxy-connect-tunnel.test.ts +71 -46
  326. package/src/__tests__/script-proxy-decision-trace.test.ts +161 -84
  327. package/src/__tests__/script-proxy-http-forwarder.test.ts +146 -129
  328. package/src/__tests__/script-proxy-injection-runtime.test.ts +139 -113
  329. package/src/__tests__/script-proxy-mitm-handler.test.ts +226 -142
  330. package/src/__tests__/script-proxy-policy-runtime.test.ts +126 -86
  331. package/src/__tests__/script-proxy-policy.test.ts +308 -153
  332. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +74 -62
  333. package/src/__tests__/script-proxy-router.test.ts +111 -77
  334. package/src/__tests__/script-proxy-session-manager.test.ts +156 -113
  335. package/src/__tests__/script-proxy-session-runtime.test.ts +28 -24
  336. package/src/__tests__/secret-allowlist.test.ts +105 -90
  337. package/src/__tests__/secret-ingress-handler.test.ts +41 -30
  338. package/src/__tests__/secret-onetime-send.test.ts +67 -50
  339. package/src/__tests__/secret-prompt-log-hygiene.test.ts +35 -31
  340. package/src/__tests__/secret-response-routing.test.ts +50 -41
  341. package/src/__tests__/secret-scanner-executor.test.ts +152 -111
  342. package/src/__tests__/secret-scanner.test.ts +495 -413
  343. package/src/__tests__/secure-keys.test.ts +132 -121
  344. package/src/__tests__/send-endpoint-busy.test.ts +0 -1
  345. package/src/__tests__/send-notification-tool.test.ts +43 -42
  346. package/src/__tests__/sensitive-output-placeholders.test.ts +72 -64
  347. package/src/__tests__/sequence-store.test.ts +335 -167
  348. package/src/__tests__/server-history-render.test.ts +341 -202
  349. package/src/__tests__/session-abort-tool-results.test.ts +133 -70
  350. package/src/__tests__/session-confirmation-signals.test.ts +252 -160
  351. package/src/__tests__/session-conflict-gate.test.ts +775 -585
  352. package/src/__tests__/session-error.test.ts +222 -191
  353. package/src/__tests__/session-evictor.test.ts +79 -62
  354. package/src/__tests__/session-init.benchmark.test.ts +170 -108
  355. package/src/__tests__/session-load-history-repair.test.ts +273 -139
  356. package/src/__tests__/session-messaging-secret-redirect.test.ts +130 -90
  357. package/src/__tests__/session-pre-run-repair.test.ts +106 -59
  358. package/src/__tests__/session-profile-injection.test.ts +198 -130
  359. package/src/__tests__/session-provider-retry-repair.test.ts +223 -141
  360. package/src/__tests__/session-queue.test.ts +624 -321
  361. package/src/__tests__/session-runtime-assembly.test.ts +425 -329
  362. package/src/__tests__/session-runtime-workspace.test.ts +69 -61
  363. package/src/__tests__/session-skill-tools.test.ts +973 -678
  364. package/src/__tests__/session-slash-known.test.ts +185 -133
  365. package/src/__tests__/session-slash-queue.test.ts +147 -81
  366. package/src/__tests__/session-slash-unknown.test.ts +135 -90
  367. package/src/__tests__/session-surfaces-task-progress.test.ts +122 -87
  368. package/src/__tests__/session-tool-setup-app-refresh.test.ts +338 -177
  369. package/src/__tests__/session-tool-setup-memory-scope.test.ts +63 -40
  370. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +60 -37
  371. package/src/__tests__/session-tool-setup-tools-disabled.test.ts +28 -26
  372. package/src/__tests__/session-undo.test.ts +43 -30
  373. package/src/__tests__/session-workspace-cache-state.test.ts +108 -67
  374. package/src/__tests__/session-workspace-injection.test.ts +245 -117
  375. package/src/__tests__/session-workspace-tool-tracking.test.ts +260 -93
  376. package/src/__tests__/shared-filesystem-errors.test.ts +47 -47
  377. package/src/__tests__/shell-credential-ref.test.ts +126 -90
  378. package/src/__tests__/shell-identity.test.ts +134 -111
  379. package/src/__tests__/shell-parser-fuzz.test.ts +263 -179
  380. package/src/__tests__/shell-parser-property.test.ts +435 -288
  381. package/src/__tests__/shell-tool-proxy-mode.test.ts +142 -70
  382. package/src/__tests__/size-guard.test.ts +42 -44
  383. package/src/__tests__/skill-feature-flags-integration.test.ts +79 -52
  384. package/src/__tests__/skill-feature-flags.test.ts +75 -47
  385. package/src/__tests__/skill-include-graph.test.ts +143 -148
  386. package/src/__tests__/skill-load-feature-flag.test.ts +94 -59
  387. package/src/__tests__/skill-load-tool.test.ts +371 -199
  388. package/src/__tests__/skill-projection-feature-flag.test.ts +131 -88
  389. package/src/__tests__/skill-projection.benchmark.test.ts +93 -65
  390. package/src/__tests__/skill-script-runner-host.test.ts +460 -250
  391. package/src/__tests__/skill-script-runner-sandbox.test.ts +168 -108
  392. package/src/__tests__/skill-script-runner.test.ts +115 -74
  393. package/src/__tests__/skill-tool-factory.test.ts +140 -96
  394. package/src/__tests__/skill-tool-manifest.test.ts +306 -210
  395. package/src/__tests__/skill-version-hash.test.ts +70 -56
  396. package/src/__tests__/skills.test.ts +0 -1
  397. package/src/__tests__/slack-channel-config.test.ts +127 -84
  398. package/src/__tests__/slack-skill.test.ts +60 -47
  399. package/src/__tests__/slash-commands-catalog.test.ts +37 -31
  400. package/src/__tests__/slash-commands-parser.test.ts +71 -64
  401. package/src/__tests__/slash-commands-resolver.test.ts +143 -107
  402. package/src/__tests__/slash-commands-rewrite.test.ts +22 -22
  403. package/src/__tests__/speaker-identification.test.ts +28 -25
  404. package/src/__tests__/starter-bundle.test.ts +27 -23
  405. package/src/__tests__/starter-task-flow.test.ts +67 -52
  406. package/src/__tests__/subagent-manager-notify.test.ts +154 -108
  407. package/src/__tests__/subagent-tools.test.ts +311 -270
  408. package/src/__tests__/subagent-types.test.ts +40 -40
  409. package/src/__tests__/surface-mutex-cleanup.test.ts +42 -30
  410. package/src/__tests__/swarm-dag-pathological.test.ts +122 -111
  411. package/src/__tests__/swarm-orchestrator.test.ts +135 -101
  412. package/src/__tests__/swarm-plan-validator.test.ts +125 -73
  413. package/src/__tests__/swarm-recursion.test.ts +58 -46
  414. package/src/__tests__/swarm-router-planner.test.ts +99 -74
  415. package/src/__tests__/swarm-session-integration.test.ts +148 -91
  416. package/src/__tests__/swarm-tool.test.ts +65 -45
  417. package/src/__tests__/swarm-worker-backend.test.ts +59 -45
  418. package/src/__tests__/swarm-worker-runner.test.ts +133 -118
  419. package/src/__tests__/system-prompt.test.ts +290 -256
  420. package/src/__tests__/task-compiler.test.ts +176 -120
  421. package/src/__tests__/task-management-tools.test.ts +561 -456
  422. package/src/__tests__/task-memory-cleanup.test.ts +627 -362
  423. package/src/__tests__/task-runner.test.ts +117 -94
  424. package/src/__tests__/task-scheduler.test.ts +113 -84
  425. package/src/__tests__/task-tools.test.ts +349 -264
  426. package/src/__tests__/terminal-sandbox.test.ts +138 -108
  427. package/src/__tests__/terminal-tools.test.ts +350 -305
  428. package/src/__tests__/thread-seed-composer.test.ts +307 -180
  429. package/src/__tests__/tool-approval-handler.test.ts +238 -137
  430. package/src/__tests__/tool-audit-listener.test.ts +69 -69
  431. package/src/__tests__/tool-domain-event-publisher.test.ts +142 -132
  432. package/src/__tests__/tool-execution-abort-cleanup.test.ts +153 -146
  433. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +136 -105
  434. package/src/__tests__/tool-executor-lifecycle-events.test.ts +355 -239
  435. package/src/__tests__/tool-executor-redaction.test.ts +112 -109
  436. package/src/__tests__/tool-executor-shell-integration.test.ts +130 -79
  437. package/src/__tests__/tool-executor.test.ts +1274 -674
  438. package/src/__tests__/tool-grant-request-escalation.test.ts +401 -283
  439. package/src/__tests__/tool-metrics-listener.test.ts +97 -85
  440. package/src/__tests__/tool-notification-listener.test.ts +42 -25
  441. package/src/__tests__/tool-permission-simulate-handler.test.ts +137 -113
  442. package/src/__tests__/tool-policy.test.ts +44 -25
  443. package/src/__tests__/tool-profiling-listener.test.ts +99 -93
  444. package/src/__tests__/tool-result-truncation.test.ts +5 -4
  445. package/src/__tests__/tool-trace-listener.test.ts +131 -111
  446. package/src/__tests__/top-level-renderer.test.ts +62 -58
  447. package/src/__tests__/top-level-scanner.test.ts +68 -64
  448. package/src/__tests__/trace-emitter.test.ts +56 -56
  449. package/src/__tests__/trust-context-guards.test.ts +65 -65
  450. package/src/__tests__/trust-store.test.ts +1239 -806
  451. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  452. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  453. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +3 -2
  454. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -2
  455. package/src/__tests__/trusted-contact-verification.test.ts +251 -231
  456. package/src/__tests__/turn-commit.test.ts +259 -200
  457. package/src/__tests__/twilio-provider.test.ts +140 -126
  458. package/src/__tests__/twilio-rest.test.ts +22 -18
  459. package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -1
  460. package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
  461. package/src/__tests__/twilio-routes.test.ts +0 -1
  462. package/src/__tests__/twitter-auth-handler.test.ts +184 -139
  463. package/src/__tests__/twitter-cli-error-shaping.test.ts +88 -73
  464. package/src/__tests__/twitter-cli-routing.test.ts +146 -99
  465. package/src/__tests__/twitter-oauth-client.test.ts +82 -65
  466. package/src/__tests__/update-bulletin-format.test.ts +69 -66
  467. package/src/__tests__/update-bulletin-state.test.ts +66 -60
  468. package/src/__tests__/update-bulletin.test.ts +150 -114
  469. package/src/__tests__/update-template-contract.test.ts +15 -10
  470. package/src/__tests__/url-safety.test.ts +288 -265
  471. package/src/__tests__/user-reference.test.ts +32 -32
  472. package/src/__tests__/view-image-tool.test.ts +118 -96
  473. package/src/__tests__/voice-invite-redemption.test.ts +111 -106
  474. package/src/__tests__/voice-quality.test.ts +117 -102
  475. package/src/__tests__/voice-scoped-grant-consumer.test.ts +204 -146
  476. package/src/__tests__/voice-session-bridge.test.ts +351 -216
  477. package/src/__tests__/weather-skill-regression.test.ts +170 -120
  478. package/src/__tests__/web-fetch.test.ts +664 -526
  479. package/src/__tests__/web-search.test.ts +379 -213
  480. package/src/__tests__/work-item-output.test.ts +90 -53
  481. package/src/__tests__/workspace-git-service.test.ts +437 -356
  482. package/src/__tests__/workspace-heartbeat-service.test.ts +125 -91
  483. package/src/__tests__/workspace-lifecycle.test.ts +98 -64
  484. package/src/__tests__/workspace-policy.test.ts +139 -71
  485. package/src/commands/__tests__/cc-command-registry.test.ts +142 -134
  486. package/src/config/__tests__/feature-flag-registry-guard.test.ts +48 -39
  487. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +25 -10
  488. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
  489. package/src/config/bundled-skills/messaging/SKILL.md +4 -3
  490. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +15 -5
  491. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -5
  492. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +34 -32
  493. package/src/config/bundled-tool-registry.ts +2 -0
  494. package/src/config/env.ts +3 -4
  495. package/src/memory/db-connection.ts +16 -10
  496. package/src/messaging/providers/gmail/adapter.ts +10 -3
  497. package/src/messaging/providers/gmail/client.ts +280 -72
  498. package/src/runtime/auth/__tests__/context.test.ts +75 -65
  499. package/src/runtime/auth/__tests__/credential-service.test.ts +137 -114
  500. package/src/runtime/auth/__tests__/guard-tests.test.ts +84 -90
  501. package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +40 -40
  502. package/src/runtime/auth/__tests__/middleware.test.ts +80 -74
  503. package/src/runtime/auth/__tests__/policy.test.ts +9 -9
  504. package/src/runtime/auth/__tests__/route-policy.test.ts +76 -65
  505. package/src/runtime/auth/__tests__/scopes.test.ts +68 -60
  506. package/src/runtime/auth/__tests__/subject.test.ts +54 -54
  507. package/src/runtime/auth/__tests__/token-service.test.ts +115 -108
  508. package/src/runtime/auth/scopes.ts +3 -0
  509. package/src/runtime/auth/token-service.ts +4 -1
  510. package/src/runtime/auth/types.ts +2 -1
  511. package/src/runtime/http-server.ts +2 -1
  512. package/src/security/secure-keys.ts +103 -53
  513. package/src/tools/browser/__tests__/auth-cache.test.ts +69 -63
  514. package/src/tools/browser/__tests__/auth-detector.test.ts +218 -157
  515. package/src/tools/browser/__tests__/jit-auth.test.ts +83 -99
@@ -27,14 +27,13 @@ import {
27
27
  realpathSync,
28
28
  rmSync,
29
29
  writeFileSync,
30
- } from 'node:fs';
31
- import { tmpdir } from 'node:os';
32
- import { join } from 'node:path';
33
-
34
- import { afterEach, describe, expect, mock, test } from 'bun:test';
30
+ } from "node:fs";
31
+ import { tmpdir } from "node:os";
32
+ import { join } from "node:path";
33
+ import { afterEach, describe, expect, mock, test } from "bun:test";
35
34
 
36
35
  // Mock the logger before any transitive imports that depend on pino
37
- mock.module('../util/logger.js', () => ({
36
+ mock.module("../util/logger.js", () => ({
38
37
  getLogger: () => ({
39
38
  error: () => {},
40
39
  warn: () => {},
@@ -43,15 +42,24 @@ mock.module('../util/logger.js', () => ({
43
42
  }),
44
43
  }));
45
44
 
46
- import { applyEdit } from '../tools/shared/filesystem/edit-engine.js';
47
- import { FileSystemOps, type PathPolicy } from '../tools/shared/filesystem/file-ops-service.js';
48
- import { hostPolicy,sandboxPolicy } from '../tools/shared/filesystem/path-policy.js';
49
- import { formatShellOutput, MAX_OUTPUT_LENGTH } from '../tools/shared/shell-output.js';
45
+ import { applyEdit } from "../tools/shared/filesystem/edit-engine.js";
46
+ import {
47
+ FileSystemOps,
48
+ type PathPolicy,
49
+ } from "../tools/shared/filesystem/file-ops-service.js";
50
+ import {
51
+ hostPolicy,
52
+ sandboxPolicy,
53
+ } from "../tools/shared/filesystem/path-policy.js";
54
+ import {
55
+ formatShellOutput,
56
+ MAX_OUTPUT_LENGTH,
57
+ } from "../tools/shared/shell-output.js";
50
58
 
51
59
  // Dynamically import modules that depend on the mocked logger
52
- const { NativeBackend } = await import('../tools/terminal/backends/native.js');
53
- const { wrapCommand } = await import('../tools/terminal/sandbox.js');
54
- const { ToolError } = await import('../util/errors.js');
60
+ const { NativeBackend } = await import("../tools/terminal/backends/native.js");
61
+ const { wrapCommand } = await import("../tools/terminal/sandbox.js");
62
+ const { ToolError } = await import("../util/errors.js");
55
63
 
56
64
  // ---------------------------------------------------------------------------
57
65
  // Helpers
@@ -60,7 +68,7 @@ const { ToolError } = await import('../util/errors.js');
60
68
  const testDirs: string[] = [];
61
69
 
62
70
  function makeTempDir(): string {
63
- const dir = realpathSync(mkdtempSync(join(tmpdir(), 'parity-test-')));
71
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "parity-test-")));
64
72
  testDirs.push(dir);
65
73
  return dir;
66
74
  }
@@ -85,7 +93,10 @@ function hostPolicyFn(): PathPolicy {
85
93
  * Run the same operation against both sandbox and host FileSystemOps
86
94
  * and return both results for comparison.
87
95
  */
88
- function dualOps(boundary: string): { sandbox: FileSystemOps; host: FileSystemOps } {
96
+ function dualOps(boundary: string): {
97
+ sandbox: FileSystemOps;
98
+ host: FileSystemOps;
99
+ } {
89
100
  return {
90
101
  sandbox: new FileSystemOps(sandboxPolicyFor(boundary)),
91
102
  host: new FileSystemOps(hostPolicyFn()),
@@ -96,16 +107,16 @@ function dualOps(boundary: string): { sandbox: FileSystemOps; host: FileSystemOp
96
107
  // 1. File read parity
97
108
  // ===========================================================================
98
109
 
99
- describe('Read parity: sandbox vs host produce identical content', () => {
100
- test('simple file read returns same numbered content', () => {
110
+ describe("Read parity: sandbox vs host produce identical content", () => {
111
+ test("simple file read returns same numbered content", () => {
101
112
  const dir = makeTempDir();
102
- const content = 'line one\nline two\nline three\n';
103
- writeFileSync(join(dir, 'data.txt'), content);
113
+ const content = "line one\nline two\nline three\n";
114
+ writeFileSync(join(dir, "data.txt"), content);
104
115
 
105
116
  const { sandbox, host } = dualOps(dir);
106
117
 
107
- const sandboxResult = sandbox.readFileSafe({ path: 'data.txt' });
108
- const hostResult = host.readFileSafe({ path: join(dir, 'data.txt') });
118
+ const sandboxResult = sandbox.readFileSafe({ path: "data.txt" });
119
+ const hostResult = host.readFileSafe({ path: join(dir, "data.txt") });
109
120
 
110
121
  expect(sandboxResult.ok).toBe(true);
111
122
  expect(hostResult.ok).toBe(true);
@@ -115,14 +126,22 @@ describe('Read parity: sandbox vs host produce identical content', () => {
115
126
  expect(sandboxResult.value.content).toBe(hostResult.value.content);
116
127
  });
117
128
 
118
- test('read with offset and limit returns same slice', () => {
129
+ test("read with offset and limit returns same slice", () => {
119
130
  const dir = makeTempDir();
120
- writeFileSync(join(dir, 'lines.txt'), 'a\nb\nc\nd\ne\nf\n');
131
+ writeFileSync(join(dir, "lines.txt"), "a\nb\nc\nd\ne\nf\n");
121
132
 
122
133
  const { sandbox, host } = dualOps(dir);
123
134
 
124
- const sandboxResult = sandbox.readFileSafe({ path: 'lines.txt', offset: 2, limit: 3 });
125
- const hostResult = host.readFileSafe({ path: join(dir, 'lines.txt'), offset: 2, limit: 3 });
135
+ const sandboxResult = sandbox.readFileSafe({
136
+ path: "lines.txt",
137
+ offset: 2,
138
+ limit: 3,
139
+ });
140
+ const hostResult = host.readFileSafe({
141
+ path: join(dir, "lines.txt"),
142
+ offset: 2,
143
+ limit: 3,
144
+ });
126
145
 
127
146
  expect(sandboxResult.ok).toBe(true);
128
147
  expect(hostResult.ok).toBe(true);
@@ -131,47 +150,49 @@ describe('Read parity: sandbox vs host produce identical content', () => {
131
150
  expect(sandboxResult.value.content).toBe(hostResult.value.content);
132
151
  });
133
152
 
134
- test('reading a missing file returns NOT_FOUND from both', () => {
153
+ test("reading a missing file returns NOT_FOUND from both", () => {
135
154
  const dir = makeTempDir();
136
155
 
137
156
  const { sandbox, host } = dualOps(dir);
138
157
 
139
- const sandboxResult = sandbox.readFileSafe({ path: 'nonexistent.txt' });
140
- const hostResult = host.readFileSafe({ path: join(dir, 'nonexistent.txt') });
158
+ const sandboxResult = sandbox.readFileSafe({ path: "nonexistent.txt" });
159
+ const hostResult = host.readFileSafe({
160
+ path: join(dir, "nonexistent.txt"),
161
+ });
141
162
 
142
163
  expect(sandboxResult.ok).toBe(false);
143
164
  expect(hostResult.ok).toBe(false);
144
165
  if (sandboxResult.ok || hostResult.ok) return;
145
166
 
146
- expect(sandboxResult.error.code).toBe('NOT_FOUND');
147
- expect(hostResult.error.code).toBe('NOT_FOUND');
167
+ expect(sandboxResult.error.code).toBe("NOT_FOUND");
168
+ expect(hostResult.error.code).toBe("NOT_FOUND");
148
169
  });
149
170
 
150
- test('reading a directory returns NOT_A_FILE from both', () => {
171
+ test("reading a directory returns NOT_A_FILE from both", () => {
151
172
  const dir = makeTempDir();
152
- mkdirSync(join(dir, 'subdir'));
173
+ mkdirSync(join(dir, "subdir"));
153
174
 
154
175
  const { sandbox, host } = dualOps(dir);
155
176
 
156
- const sandboxResult = sandbox.readFileSafe({ path: 'subdir' });
157
- const hostResult = host.readFileSafe({ path: join(dir, 'subdir') });
177
+ const sandboxResult = sandbox.readFileSafe({ path: "subdir" });
178
+ const hostResult = host.readFileSafe({ path: join(dir, "subdir") });
158
179
 
159
180
  expect(sandboxResult.ok).toBe(false);
160
181
  expect(hostResult.ok).toBe(false);
161
182
  if (sandboxResult.ok || hostResult.ok) return;
162
183
 
163
- expect(sandboxResult.error.code).toBe('NOT_A_FILE');
164
- expect(hostResult.error.code).toBe('NOT_A_FILE');
184
+ expect(sandboxResult.error.code).toBe("NOT_A_FILE");
185
+ expect(hostResult.error.code).toBe("NOT_A_FILE");
165
186
  });
166
187
 
167
- test('empty file read returns same content from both', () => {
188
+ test("empty file read returns same content from both", () => {
168
189
  const dir = makeTempDir();
169
- writeFileSync(join(dir, 'empty.txt'), '');
190
+ writeFileSync(join(dir, "empty.txt"), "");
170
191
 
171
192
  const { sandbox, host } = dualOps(dir);
172
193
 
173
- const sandboxResult = sandbox.readFileSafe({ path: 'empty.txt' });
174
- const hostResult = host.readFileSafe({ path: join(dir, 'empty.txt') });
194
+ const sandboxResult = sandbox.readFileSafe({ path: "empty.txt" });
195
+ const hostResult = host.readFileSafe({ path: join(dir, "empty.txt") });
175
196
 
176
197
  expect(sandboxResult.ok).toBe(true);
177
198
  expect(hostResult.ok).toBe(true);
@@ -180,15 +201,16 @@ describe('Read parity: sandbox vs host produce identical content', () => {
180
201
  expect(sandboxResult.value.content).toBe(hostResult.value.content);
181
202
  });
182
203
 
183
- test('file with unicode content returns same from both', () => {
204
+ test("file with unicode content returns same from both", () => {
184
205
  const dir = makeTempDir();
185
- const unicode = 'Hello\nEmoji: \u{1F600}\nCJK: \u4F60\u597D\nAccent: caf\u00E9\n';
186
- writeFileSync(join(dir, 'unicode.txt'), unicode);
206
+ const unicode =
207
+ "Hello\nEmoji: \u{1F600}\nCJK: \u4F60\u597D\nAccent: caf\u00E9\n";
208
+ writeFileSync(join(dir, "unicode.txt"), unicode);
187
209
 
188
210
  const { sandbox, host } = dualOps(dir);
189
211
 
190
- const sandboxResult = sandbox.readFileSafe({ path: 'unicode.txt' });
191
- const hostResult = host.readFileSafe({ path: join(dir, 'unicode.txt') });
212
+ const sandboxResult = sandbox.readFileSafe({ path: "unicode.txt" });
213
+ const hostResult = host.readFileSafe({ path: join(dir, "unicode.txt") });
192
214
 
193
215
  expect(sandboxResult.ok).toBe(true);
194
216
  expect(hostResult.ok).toBe(true);
@@ -202,14 +224,20 @@ describe('Read parity: sandbox vs host produce identical content', () => {
202
224
  // 2. File write parity
203
225
  // ===========================================================================
204
226
 
205
- describe('Write parity: sandbox vs host produce identical results', () => {
206
- test('writing a new file returns same shape from both', () => {
227
+ describe("Write parity: sandbox vs host produce identical results", () => {
228
+ test("writing a new file returns same shape from both", () => {
207
229
  const dir = makeTempDir();
208
230
 
209
231
  const { sandbox, host } = dualOps(dir);
210
232
 
211
- const sandboxResult = sandbox.writeFileSafe({ path: 'new-s.txt', content: 'hello' });
212
- const hostResult = host.writeFileSafe({ path: join(dir, 'new-h.txt'), content: 'hello' });
233
+ const sandboxResult = sandbox.writeFileSafe({
234
+ path: "new-s.txt",
235
+ content: "hello",
236
+ });
237
+ const hostResult = host.writeFileSafe({
238
+ path: join(dir, "new-h.txt"),
239
+ content: "hello",
240
+ });
213
241
 
214
242
  expect(sandboxResult.ok).toBe(true);
215
243
  expect(hostResult.ok).toBe(true);
@@ -221,15 +249,21 @@ describe('Write parity: sandbox vs host produce identical results', () => {
221
249
  expect(sandboxResult.value.oldContent).toBe(hostResult.value.oldContent);
222
250
  });
223
251
 
224
- test('overwriting an existing file returns old content from both', () => {
252
+ test("overwriting an existing file returns old content from both", () => {
225
253
  const dir = makeTempDir();
226
- writeFileSync(join(dir, 'existing-s.txt'), 'old');
227
- writeFileSync(join(dir, 'existing-h.txt'), 'old');
254
+ writeFileSync(join(dir, "existing-s.txt"), "old");
255
+ writeFileSync(join(dir, "existing-h.txt"), "old");
228
256
 
229
257
  const { sandbox, host } = dualOps(dir);
230
258
 
231
- const sandboxResult = sandbox.writeFileSafe({ path: 'existing-s.txt', content: 'new' });
232
- const hostResult = host.writeFileSafe({ path: join(dir, 'existing-h.txt'), content: 'new' });
259
+ const sandboxResult = sandbox.writeFileSafe({
260
+ path: "existing-s.txt",
261
+ content: "new",
262
+ });
263
+ const hostResult = host.writeFileSafe({
264
+ path: join(dir, "existing-h.txt"),
265
+ content: "new",
266
+ });
233
267
 
234
268
  expect(sandboxResult.ok).toBe(true);
235
269
  expect(hostResult.ok).toBe(true);
@@ -237,30 +271,36 @@ describe('Write parity: sandbox vs host produce identical results', () => {
237
271
 
238
272
  expect(sandboxResult.value.isNewFile).toBe(false);
239
273
  expect(hostResult.value.isNewFile).toBe(false);
240
- expect(sandboxResult.value.oldContent).toBe('old');
241
- expect(hostResult.value.oldContent).toBe('old');
242
- expect(sandboxResult.value.newContent).toBe('new');
243
- expect(hostResult.value.newContent).toBe('new');
274
+ expect(sandboxResult.value.oldContent).toBe("old");
275
+ expect(hostResult.value.oldContent).toBe("old");
276
+ expect(sandboxResult.value.newContent).toBe("new");
277
+ expect(hostResult.value.newContent).toBe("new");
244
278
 
245
279
  // Verify actual files on disk
246
- expect(readFileSync(join(dir, 'existing-s.txt'), 'utf-8')).toBe('new');
247
- expect(readFileSync(join(dir, 'existing-h.txt'), 'utf-8')).toBe('new');
280
+ expect(readFileSync(join(dir, "existing-s.txt"), "utf-8")).toBe("new");
281
+ expect(readFileSync(join(dir, "existing-h.txt"), "utf-8")).toBe("new");
248
282
  });
249
283
 
250
- test('creating nested directories works from both', () => {
284
+ test("creating nested directories works from both", () => {
251
285
  const dir = makeTempDir();
252
286
 
253
287
  const { sandbox, host } = dualOps(dir);
254
288
 
255
- const sandboxResult = sandbox.writeFileSafe({ path: 'a/b/deep-s.txt', content: 'deep' });
256
- const hostResult = host.writeFileSafe({ path: join(dir, 'c/d/deep-h.txt'), content: 'deep' });
289
+ const sandboxResult = sandbox.writeFileSafe({
290
+ path: "a/b/deep-s.txt",
291
+ content: "deep",
292
+ });
293
+ const hostResult = host.writeFileSafe({
294
+ path: join(dir, "c/d/deep-h.txt"),
295
+ content: "deep",
296
+ });
257
297
 
258
298
  expect(sandboxResult.ok).toBe(true);
259
299
  expect(hostResult.ok).toBe(true);
260
300
  if (!sandboxResult.ok || !hostResult.ok) return;
261
301
 
262
- expect(existsSync(join(dir, 'a/b/deep-s.txt'))).toBe(true);
263
- expect(existsSync(join(dir, 'c/d/deep-h.txt'))).toBe(true);
302
+ expect(existsSync(join(dir, "a/b/deep-s.txt"))).toBe(true);
303
+ expect(existsSync(join(dir, "c/d/deep-h.txt"))).toBe(true);
264
304
  expect(sandboxResult.value.isNewFile).toBe(true);
265
305
  expect(hostResult.value.isNewFile).toBe(true);
266
306
  });
@@ -270,24 +310,24 @@ describe('Write parity: sandbox vs host produce identical results', () => {
270
310
  // 3. File edit parity
271
311
  // ===========================================================================
272
312
 
273
- describe('Edit parity: sandbox vs host produce identical edits', () => {
274
- test('unique match edit produces same result from both', () => {
313
+ describe("Edit parity: sandbox vs host produce identical edits", () => {
314
+ test("unique match edit produces same result from both", () => {
275
315
  const dir = makeTempDir();
276
- writeFileSync(join(dir, 'edit-s.txt'), 'one two three');
277
- writeFileSync(join(dir, 'edit-h.txt'), 'one two three');
316
+ writeFileSync(join(dir, "edit-s.txt"), "one two three");
317
+ writeFileSync(join(dir, "edit-h.txt"), "one two three");
278
318
 
279
319
  const { sandbox, host } = dualOps(dir);
280
320
 
281
321
  const sandboxResult = sandbox.editFileSafe({
282
- path: 'edit-s.txt',
283
- oldString: 'two',
284
- newString: 'TWO',
322
+ path: "edit-s.txt",
323
+ oldString: "two",
324
+ newString: "TWO",
285
325
  replaceAll: false,
286
326
  });
287
327
  const hostResult = host.editFileSafe({
288
- path: join(dir, 'edit-h.txt'),
289
- oldString: 'two',
290
- newString: 'TWO',
328
+ path: join(dir, "edit-h.txt"),
329
+ oldString: "two",
330
+ newString: "TWO",
291
331
  replaceAll: false,
292
332
  });
293
333
 
@@ -297,29 +337,29 @@ describe('Edit parity: sandbox vs host produce identical edits', () => {
297
337
 
298
338
  expect(sandboxResult.value.matchCount).toBe(1);
299
339
  expect(hostResult.value.matchCount).toBe(1);
300
- expect(sandboxResult.value.newContent).toBe('one TWO three');
301
- expect(hostResult.value.newContent).toBe('one TWO three');
340
+ expect(sandboxResult.value.newContent).toBe("one TWO three");
341
+ expect(hostResult.value.newContent).toBe("one TWO three");
302
342
  expect(sandboxResult.value.matchMethod).toBe(hostResult.value.matchMethod);
303
343
  });
304
344
 
305
- test('replaceAll edit produces same result from both', () => {
345
+ test("replaceAll edit produces same result from both", () => {
306
346
  const dir = makeTempDir();
307
- const original = 'foo bar foo baz foo';
308
- writeFileSync(join(dir, 'ra-s.txt'), original);
309
- writeFileSync(join(dir, 'ra-h.txt'), original);
347
+ const original = "foo bar foo baz foo";
348
+ writeFileSync(join(dir, "ra-s.txt"), original);
349
+ writeFileSync(join(dir, "ra-h.txt"), original);
310
350
 
311
351
  const { sandbox, host } = dualOps(dir);
312
352
 
313
353
  const sandboxResult = sandbox.editFileSafe({
314
- path: 'ra-s.txt',
315
- oldString: 'foo',
316
- newString: 'qux',
354
+ path: "ra-s.txt",
355
+ oldString: "foo",
356
+ newString: "qux",
317
357
  replaceAll: true,
318
358
  });
319
359
  const hostResult = host.editFileSafe({
320
- path: join(dir, 'ra-h.txt'),
321
- oldString: 'foo',
322
- newString: 'qux',
360
+ path: join(dir, "ra-h.txt"),
361
+ oldString: "foo",
362
+ newString: "qux",
323
363
  replaceAll: true,
324
364
  });
325
365
 
@@ -330,26 +370,26 @@ describe('Edit parity: sandbox vs host produce identical edits', () => {
330
370
  expect(sandboxResult.value.matchCount).toBe(3);
331
371
  expect(hostResult.value.matchCount).toBe(3);
332
372
  expect(sandboxResult.value.newContent).toBe(hostResult.value.newContent);
333
- expect(sandboxResult.value.newContent).toBe('qux bar qux baz qux');
373
+ expect(sandboxResult.value.newContent).toBe("qux bar qux baz qux");
334
374
  });
335
375
 
336
- test('missing old_string returns MATCH_NOT_FOUND from both', () => {
376
+ test("missing old_string returns MATCH_NOT_FOUND from both", () => {
337
377
  const dir = makeTempDir();
338
- writeFileSync(join(dir, 'mnf-s.txt'), 'hello world');
339
- writeFileSync(join(dir, 'mnf-h.txt'), 'hello world');
378
+ writeFileSync(join(dir, "mnf-s.txt"), "hello world");
379
+ writeFileSync(join(dir, "mnf-h.txt"), "hello world");
340
380
 
341
381
  const { sandbox, host } = dualOps(dir);
342
382
 
343
383
  const sandboxResult = sandbox.editFileSafe({
344
- path: 'mnf-s.txt',
345
- oldString: 'xyz',
346
- newString: 'abc',
384
+ path: "mnf-s.txt",
385
+ oldString: "xyz",
386
+ newString: "abc",
347
387
  replaceAll: false,
348
388
  });
349
389
  const hostResult = host.editFileSafe({
350
- path: join(dir, 'mnf-h.txt'),
351
- oldString: 'xyz',
352
- newString: 'abc',
390
+ path: join(dir, "mnf-h.txt"),
391
+ oldString: "xyz",
392
+ newString: "abc",
353
393
  replaceAll: false,
354
394
  });
355
395
 
@@ -357,28 +397,28 @@ describe('Edit parity: sandbox vs host produce identical edits', () => {
357
397
  expect(hostResult.ok).toBe(false);
358
398
  if (sandboxResult.ok || hostResult.ok) return;
359
399
 
360
- expect(sandboxResult.error.code).toBe('MATCH_NOT_FOUND');
361
- expect(hostResult.error.code).toBe('MATCH_NOT_FOUND');
400
+ expect(sandboxResult.error.code).toBe("MATCH_NOT_FOUND");
401
+ expect(hostResult.error.code).toBe("MATCH_NOT_FOUND");
362
402
  });
363
403
 
364
- test('ambiguous match returns MATCH_AMBIGUOUS from both', () => {
404
+ test("ambiguous match returns MATCH_AMBIGUOUS from both", () => {
365
405
  const dir = makeTempDir();
366
- const content = 'repeat\nrepeat\n';
367
- writeFileSync(join(dir, 'amb-s.txt'), content);
368
- writeFileSync(join(dir, 'amb-h.txt'), content);
406
+ const content = "repeat\nrepeat\n";
407
+ writeFileSync(join(dir, "amb-s.txt"), content);
408
+ writeFileSync(join(dir, "amb-h.txt"), content);
369
409
 
370
410
  const { sandbox, host } = dualOps(dir);
371
411
 
372
412
  const sandboxResult = sandbox.editFileSafe({
373
- path: 'amb-s.txt',
374
- oldString: 'repeat',
375
- newString: 'unique',
413
+ path: "amb-s.txt",
414
+ oldString: "repeat",
415
+ newString: "unique",
376
416
  replaceAll: false,
377
417
  });
378
418
  const hostResult = host.editFileSafe({
379
- path: join(dir, 'amb-h.txt'),
380
- oldString: 'repeat',
381
- newString: 'unique',
419
+ path: join(dir, "amb-h.txt"),
420
+ oldString: "repeat",
421
+ newString: "unique",
382
422
  replaceAll: false,
383
423
  });
384
424
 
@@ -386,25 +426,25 @@ describe('Edit parity: sandbox vs host produce identical edits', () => {
386
426
  expect(hostResult.ok).toBe(false);
387
427
  if (sandboxResult.ok || hostResult.ok) return;
388
428
 
389
- expect(sandboxResult.error.code).toBe('MATCH_AMBIGUOUS');
390
- expect(hostResult.error.code).toBe('MATCH_AMBIGUOUS');
429
+ expect(sandboxResult.error.code).toBe("MATCH_AMBIGUOUS");
430
+ expect(hostResult.error.code).toBe("MATCH_AMBIGUOUS");
391
431
  });
392
432
 
393
- test('editing a nonexistent file returns NOT_FOUND from both', () => {
433
+ test("editing a nonexistent file returns NOT_FOUND from both", () => {
394
434
  const dir = makeTempDir();
395
435
 
396
436
  const { sandbox, host } = dualOps(dir);
397
437
 
398
438
  const sandboxResult = sandbox.editFileSafe({
399
- path: 'nope.txt',
400
- oldString: 'a',
401
- newString: 'b',
439
+ path: "nope.txt",
440
+ oldString: "a",
441
+ newString: "b",
402
442
  replaceAll: false,
403
443
  });
404
444
  const hostResult = host.editFileSafe({
405
- path: join(dir, 'nope.txt'),
406
- oldString: 'a',
407
- newString: 'b',
445
+ path: join(dir, "nope.txt"),
446
+ oldString: "a",
447
+ newString: "b",
408
448
  replaceAll: false,
409
449
  });
410
450
 
@@ -412,8 +452,8 @@ describe('Edit parity: sandbox vs host produce identical edits', () => {
412
452
  expect(hostResult.ok).toBe(false);
413
453
  if (sandboxResult.ok || hostResult.ok) return;
414
454
 
415
- expect(sandboxResult.error.code).toBe('NOT_FOUND');
416
- expect(hostResult.error.code).toBe('NOT_FOUND');
455
+ expect(sandboxResult.error.code).toBe("NOT_FOUND");
456
+ expect(hostResult.error.code).toBe("NOT_FOUND");
417
457
  });
418
458
  });
419
459
 
@@ -421,22 +461,22 @@ describe('Edit parity: sandbox vs host produce identical edits', () => {
421
461
  // 4. Edit engine consistency (pure function)
422
462
  // ===========================================================================
423
463
 
424
- describe('applyEdit engine: deterministic across invocations', () => {
425
- test('exact single match is idempotent', () => {
426
- const content = 'alpha beta gamma';
427
- const r1 = applyEdit(content, 'beta', 'BETA', false);
428
- const r2 = applyEdit(content, 'beta', 'BETA', false);
464
+ describe("applyEdit engine: deterministic across invocations", () => {
465
+ test("exact single match is idempotent", () => {
466
+ const content = "alpha beta gamma";
467
+ const r1 = applyEdit(content, "beta", "BETA", false);
468
+ const r2 = applyEdit(content, "beta", "BETA", false);
429
469
 
430
470
  expect(r1).toEqual(r2);
431
471
  expect(r1.ok).toBe(true);
432
472
  if (!r1.ok) return;
433
- expect(r1.updatedContent).toBe('alpha BETA gamma');
473
+ expect(r1.updatedContent).toBe("alpha BETA gamma");
434
474
  });
435
475
 
436
- test('replaceAll is idempotent', () => {
437
- const content = 'x y x z x';
438
- const r1 = applyEdit(content, 'x', 'X', true);
439
- const r2 = applyEdit(content, 'x', 'X', true);
476
+ test("replaceAll is idempotent", () => {
477
+ const content = "x y x z x";
478
+ const r1 = applyEdit(content, "x", "X", true);
479
+ const r2 = applyEdit(content, "x", "X", true);
440
480
 
441
481
  expect(r1).toEqual(r2);
442
482
  expect(r1.ok).toBe(true);
@@ -444,56 +484,56 @@ describe('applyEdit engine: deterministic across invocations', () => {
444
484
  expect(r1.matchCount).toBe(3);
445
485
  });
446
486
 
447
- test('not-found is consistent', () => {
448
- const content = 'hello';
449
- const r1 = applyEdit(content, 'missing', 'found', false);
450
- const r2 = applyEdit(content, 'missing', 'found', false);
487
+ test("not-found is consistent", () => {
488
+ const content = "hello";
489
+ const r1 = applyEdit(content, "missing", "found", false);
490
+ const r2 = applyEdit(content, "missing", "found", false);
451
491
 
452
492
  expect(r1).toEqual(r2);
453
493
  expect(r1.ok).toBe(false);
454
494
  if (r1.ok) return;
455
- expect(r1.reason).toBe('not_found');
495
+ expect(r1.reason).toBe("not_found");
456
496
  });
457
497
 
458
- test('ambiguous is consistent', () => {
459
- const content = 'dup dup dup';
460
- const r1 = applyEdit(content, 'dup', 'uniq', false);
461
- const r2 = applyEdit(content, 'dup', 'uniq', false);
498
+ test("ambiguous is consistent", () => {
499
+ const content = "dup dup dup";
500
+ const r1 = applyEdit(content, "dup", "uniq", false);
501
+ const r2 = applyEdit(content, "dup", "uniq", false);
462
502
 
463
503
  expect(r1).toEqual(r2);
464
504
  expect(r1.ok).toBe(false);
465
505
  if (r1.ok) return;
466
- expect(r1.reason).toBe('ambiguous');
467
- if (r1.reason !== 'ambiguous') return;
506
+ expect(r1.reason).toBe("ambiguous");
507
+ if (r1.reason !== "ambiguous") return;
468
508
  expect(r1.matchCount).toBe(3);
469
509
  });
470
510
 
471
- test('multiline content is handled correctly', () => {
472
- const content = 'line1\nline2\nline3\nline4\n';
473
- const result = applyEdit(content, 'line2\nline3', 'replaced', false);
511
+ test("multiline content is handled correctly", () => {
512
+ const content = "line1\nline2\nline3\nline4\n";
513
+ const result = applyEdit(content, "line2\nline3", "replaced", false);
474
514
 
475
515
  expect(result.ok).toBe(true);
476
516
  if (!result.ok) return;
477
- expect(result.updatedContent).toBe('line1\nreplaced\nline4\n');
517
+ expect(result.updatedContent).toBe("line1\nreplaced\nline4\n");
478
518
  expect(result.matchCount).toBe(1);
479
519
  });
480
520
 
481
- test('replacing with empty string works', () => {
482
- const content = 'keep remove keep';
483
- const result = applyEdit(content, ' remove', '', false);
521
+ test("replacing with empty string works", () => {
522
+ const content = "keep remove keep";
523
+ const result = applyEdit(content, " remove", "", false);
484
524
 
485
525
  expect(result.ok).toBe(true);
486
526
  if (!result.ok) return;
487
- expect(result.updatedContent).toBe('keep keep');
527
+ expect(result.updatedContent).toBe("keep keep");
488
528
  });
489
529
 
490
- test('special regex characters in old_string are treated as literals', () => {
491
- const content = 'price is $100.00 (USD)';
492
- const result = applyEdit(content, '$100.00', '$200.00', false);
530
+ test("special regex characters in old_string are treated as literals", () => {
531
+ const content = "price is $100.00 (USD)";
532
+ const result = applyEdit(content, "$100.00", "$200.00", false);
493
533
 
494
534
  expect(result.ok).toBe(true);
495
535
  if (!result.ok) return;
496
- expect(result.updatedContent).toBe('price is $200.00 (USD)');
536
+ expect(result.updatedContent).toBe("price is $200.00 (USD)");
497
537
  });
498
538
  });
499
539
 
@@ -501,57 +541,62 @@ describe('applyEdit engine: deterministic across invocations', () => {
501
541
  // 5. Path policy divergence — expected differences
502
542
  // ===========================================================================
503
543
 
504
- describe('Path policy divergence: sandbox blocks escapes, host requires absolute', () => {
505
- test('sandbox blocks path traversal (../), host allows any absolute path', () => {
544
+ describe("Path policy divergence: sandbox blocks escapes, host requires absolute", () => {
545
+ test("sandbox blocks path traversal (../), host allows any absolute path", () => {
506
546
  const dir = makeTempDir();
507
- writeFileSync(join(dir, 'inside.txt'), 'safe content');
547
+ writeFileSync(join(dir, "inside.txt"), "safe content");
508
548
 
509
549
  const sandboxOps = new FileSystemOps(sandboxPolicyFor(dir));
510
550
  const hostOps = new FileSystemOps(hostPolicyFn());
511
551
 
512
552
  // Sandbox: path traversal should be rejected
513
- const sandboxResult = sandboxOps.readFileSafe({ path: '../../../etc/hostname' });
553
+ const sandboxResult = sandboxOps.readFileSafe({
554
+ path: "../../../etc/hostname",
555
+ });
514
556
  expect(sandboxResult.ok).toBe(false);
515
557
  if (!sandboxResult.ok) {
516
- expect(sandboxResult.error.code).toBe('PATH_OUT_OF_BOUNDS');
558
+ expect(sandboxResult.error.code).toBe("PATH_OUT_OF_BOUNDS");
517
559
  }
518
560
 
519
561
  // Host: relative paths are rejected (requires absolute)
520
- const hostResult = hostOps.readFileSafe({ path: 'relative.txt' });
562
+ const hostResult = hostOps.readFileSafe({ path: "relative.txt" });
521
563
  expect(hostResult.ok).toBe(false);
522
564
  if (!hostResult.ok) {
523
- expect(hostResult.error.code).toBe('PATH_NOT_ABSOLUTE');
565
+ expect(hostResult.error.code).toBe("PATH_NOT_ABSOLUTE");
524
566
  }
525
567
  });
526
568
 
527
- test('sandbox allows relative paths within boundary', () => {
569
+ test("sandbox allows relative paths within boundary", () => {
528
570
  const dir = makeTempDir();
529
- writeFileSync(join(dir, 'valid.txt'), 'data');
571
+ writeFileSync(join(dir, "valid.txt"), "data");
530
572
 
531
573
  const sandboxOps = new FileSystemOps(sandboxPolicyFor(dir));
532
574
 
533
- const result = sandboxOps.readFileSafe({ path: 'valid.txt' });
575
+ const result = sandboxOps.readFileSafe({ path: "valid.txt" });
534
576
  expect(result.ok).toBe(true);
535
577
  });
536
578
 
537
- test('host requires absolute path even for simple filenames', () => {
579
+ test("host requires absolute path even for simple filenames", () => {
538
580
  const hostOps = new FileSystemOps(hostPolicyFn());
539
581
 
540
- const result = hostOps.readFileSafe({ path: 'just-a-name.txt' });
582
+ const result = hostOps.readFileSafe({ path: "just-a-name.txt" });
541
583
  expect(result.ok).toBe(false);
542
584
  if (!result.ok) {
543
- expect(result.error.code).toBe('PATH_NOT_ABSOLUTE');
585
+ expect(result.error.code).toBe("PATH_NOT_ABSOLUTE");
544
586
  }
545
587
  });
546
588
 
547
- test('sandbox rejects absolute paths outside boundary', () => {
589
+ test("sandbox rejects absolute paths outside boundary", () => {
548
590
  const dir = makeTempDir();
549
591
  const sandboxOps = new FileSystemOps(sandboxPolicyFor(dir));
550
592
 
551
- const result = sandboxOps.writeFileSafe({ path: '/tmp/somewhere-else.txt', content: 'bad' });
593
+ const result = sandboxOps.writeFileSafe({
594
+ path: "/tmp/somewhere-else.txt",
595
+ content: "bad",
596
+ });
552
597
  expect(result.ok).toBe(false);
553
598
  if (!result.ok) {
554
- expect(result.error.code).toBe('PATH_OUT_OF_BOUNDS');
599
+ expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
555
600
  }
556
601
  });
557
602
  });
@@ -560,21 +605,21 @@ describe('Path policy divergence: sandbox blocks escapes, host requires absolute
560
605
  // 6. Sandbox backend parity — NativeBackend & DockerBackend SandboxResult shape
561
606
  // ===========================================================================
562
607
 
563
- describe('SandboxResult shape consistency across backends', () => {
564
- test('NativeBackend.wrap returns required fields', () => {
608
+ describe("SandboxResult shape consistency across backends", () => {
609
+ test("NativeBackend.wrap returns required fields", () => {
565
610
  const native = new NativeBackend();
566
611
 
567
612
  // On macOS this will succeed; on other platforms it will throw ToolError
568
613
  try {
569
- const result = native.wrap('echo test', '/tmp');
570
- expect(typeof result.command).toBe('string');
614
+ const result = native.wrap("echo test", "/tmp");
615
+ expect(typeof result.command).toBe("string");
571
616
  expect(Array.isArray(result.args)).toBe(true);
572
- expect(typeof result.sandboxed).toBe('boolean');
617
+ expect(typeof result.sandboxed).toBe("boolean");
573
618
  expect(result.sandboxed).toBe(true);
574
619
 
575
620
  // All args must be strings
576
621
  for (const arg of result.args) {
577
- expect(typeof arg).toBe('string');
622
+ expect(typeof arg).toBe("string");
578
623
  }
579
624
  } catch (err) {
580
625
  // NativeBackend explicitly throws ToolError on unsupported platforms or
@@ -582,29 +627,32 @@ describe('SandboxResult shape consistency across backends', () => {
582
627
  // throw system errors (ErrnoException). Both are legitimate — but
583
628
  // programming errors like TypeError/ReferenceError should still fail.
584
629
  const isToolError = err instanceof ToolError;
585
- const isSystemError = err instanceof Error && 'syscall' in err && typeof (err as NodeJS.ErrnoException).code === 'string';
630
+ const isSystemError =
631
+ err instanceof Error &&
632
+ "syscall" in err &&
633
+ typeof (err as NodeJS.ErrnoException).code === "string";
586
634
  expect(isToolError || isSystemError).toBe(true);
587
635
  }
588
636
  });
589
637
 
590
- test('wrapCommand disabled returns bash with sandboxed=false', () => {
591
- const result = wrapCommand('echo hi', '/tmp', { enabled: false });
638
+ test("wrapCommand disabled returns bash with sandboxed=false", () => {
639
+ const result = wrapCommand("echo hi", "/tmp", { enabled: false });
592
640
 
593
- expect(result.command).toBe('bash');
594
- expect(result.args).toEqual(['-c', '--', 'echo hi']);
641
+ expect(result.command).toBe("bash");
642
+ expect(result.args).toEqual(["-c", "--", "echo hi"]);
595
643
  expect(result.sandboxed).toBe(false);
596
644
  });
597
645
 
598
- test('wrapCommand disabled result has same shape as enabled result', () => {
599
- const disabled = wrapCommand('echo hi', '/tmp', { enabled: false });
646
+ test("wrapCommand disabled result has same shape as enabled result", () => {
647
+ const disabled = wrapCommand("echo hi", "/tmp", { enabled: false });
600
648
 
601
649
  // Both must have: command (string), args (string[]), sandboxed (boolean)
602
- expect(typeof disabled.command).toBe('string');
650
+ expect(typeof disabled.command).toBe("string");
603
651
  expect(Array.isArray(disabled.args)).toBe(true);
604
- expect(typeof disabled.sandboxed).toBe('boolean');
652
+ expect(typeof disabled.sandboxed).toBe("boolean");
605
653
 
606
654
  for (const arg of disabled.args) {
607
- expect(typeof arg).toBe('string');
655
+ expect(typeof arg).toBe("string");
608
656
  }
609
657
  });
610
658
  });
@@ -613,58 +661,60 @@ describe('SandboxResult shape consistency across backends', () => {
613
661
  // 7. Terminal output format consistency
614
662
  // ===========================================================================
615
663
 
616
- describe('Terminal output format: formatShellOutput shared by sandbox and host', () => {
617
- test('successful command output has no XML status tags', () => {
618
- const result = formatShellOutput('hello world', '', 0, false, 120);
664
+ describe("Terminal output format: formatShellOutput shared by sandbox and host", () => {
665
+ test("successful command output has no XML status tags", () => {
666
+ const result = formatShellOutput("hello world", "", 0, false, 120);
619
667
 
620
- expect(result.content).toBe('hello world');
621
- expect(result.content).not.toContain('<command_exit');
622
- expect(result.content).not.toContain('<command_completed');
668
+ expect(result.content).toBe("hello world");
669
+ expect(result.content).not.toContain("<command_exit");
670
+ expect(result.content).not.toContain("<command_completed");
623
671
  expect(result.isError).toBe(false);
624
672
  expect(result.status).toBeUndefined();
625
673
  });
626
674
 
627
- test('empty output on success produces <command_completed /> tag', () => {
628
- const result = formatShellOutput('', '', 0, false, 120);
675
+ test("empty output on success produces <command_completed /> tag", () => {
676
+ const result = formatShellOutput("", "", 0, false, 120);
629
677
 
630
- expect(result.content).toBe('<command_completed />');
678
+ expect(result.content).toBe("<command_completed />");
631
679
  expect(result.isError).toBe(false);
632
680
  });
633
681
 
634
- test('non-zero exit code with empty output produces <command_exit /> tag', () => {
635
- const result = formatShellOutput('', '', 42, false, 120);
682
+ test("non-zero exit code with empty output produces <command_exit /> tag", () => {
683
+ const result = formatShellOutput("", "", 42, false, 120);
636
684
 
637
685
  expect(result.content).toBe('<command_exit code="42" />');
638
686
  expect(result.isError).toBe(true);
639
687
  });
640
688
 
641
- test('stderr is appended to stdout with a newline separator', () => {
642
- const result = formatShellOutput('out', 'err', 0, false, 120);
689
+ test("stderr is appended to stdout with a newline separator", () => {
690
+ const result = formatShellOutput("out", "err", 0, false, 120);
643
691
 
644
- expect(result.content).toBe('out\nerr');
692
+ expect(result.content).toBe("out\nerr");
645
693
  });
646
694
 
647
- test('stderr-only output uses stderr as the output', () => {
648
- const result = formatShellOutput('', 'error message', 0, false, 120);
695
+ test("stderr-only output uses stderr as the output", () => {
696
+ const result = formatShellOutput("", "error message", 0, false, 120);
649
697
 
650
- expect(result.content).toBe('error message');
698
+ expect(result.content).toBe("error message");
651
699
  });
652
700
 
653
- test('output truncation uses the shared MAX_OUTPUT_LENGTH constant', () => {
654
- const longOutput = 'x'.repeat(MAX_OUTPUT_LENGTH + 100);
655
- const result = formatShellOutput(longOutput, '', 0, false, 120);
701
+ test("output truncation uses the shared MAX_OUTPUT_LENGTH constant", () => {
702
+ const longOutput = "x".repeat(MAX_OUTPUT_LENGTH + 100);
703
+ const result = formatShellOutput(longOutput, "", 0, false, 120);
656
704
 
657
- expect(result.content.length).toBe(MAX_OUTPUT_LENGTH + 1 + '<output_truncated limit="50K" />'.length);
658
- expect(result.content).toContain('<output_truncated');
705
+ expect(result.content.length).toBe(
706
+ MAX_OUTPUT_LENGTH + 1 + '<output_truncated limit="50K" />'.length,
707
+ );
708
+ expect(result.content).toContain("<output_truncated");
659
709
  });
660
710
 
661
- test('timed-out command appends timeout tag and sets isError', () => {
662
- const result = formatShellOutput('partial', '', 137, true, 30);
711
+ test("timed-out command appends timeout tag and sets isError", () => {
712
+ const result = formatShellOutput("partial", "", 137, true, 30);
663
713
 
664
- expect(result.content).toContain('partial');
714
+ expect(result.content).toContain("partial");
665
715
  expect(result.content).toContain('<command_timeout seconds="30" />');
666
716
  expect(result.isError).toBe(true);
667
- expect(result.status).toContain('<command_timeout');
717
+ expect(result.status).toContain("<command_timeout");
668
718
  });
669
719
  });
670
720
 
@@ -672,39 +722,45 @@ describe('Terminal output format: formatShellOutput shared by sandbox and host',
672
722
  // 8. Regression tests for edge cases found during migration
673
723
  // ===========================================================================
674
724
 
675
- describe('Regression: edge cases in shared FileSystemOps', () => {
676
- test('writing empty content creates a file with empty content', () => {
725
+ describe("Regression: edge cases in shared FileSystemOps", () => {
726
+ test("writing empty content creates a file with empty content", () => {
677
727
  const dir = makeTempDir();
678
728
 
679
729
  const { sandbox, host } = dualOps(dir);
680
730
 
681
- const sandboxResult = sandbox.writeFileSafe({ path: 'empty-s.txt', content: '' });
682
- const hostResult = host.writeFileSafe({ path: join(dir, 'empty-h.txt'), content: '' });
731
+ const sandboxResult = sandbox.writeFileSafe({
732
+ path: "empty-s.txt",
733
+ content: "",
734
+ });
735
+ const hostResult = host.writeFileSafe({
736
+ path: join(dir, "empty-h.txt"),
737
+ content: "",
738
+ });
683
739
 
684
740
  expect(sandboxResult.ok).toBe(true);
685
741
  expect(hostResult.ok).toBe(true);
686
742
 
687
- expect(readFileSync(join(dir, 'empty-s.txt'), 'utf-8')).toBe('');
688
- expect(readFileSync(join(dir, 'empty-h.txt'), 'utf-8')).toBe('');
743
+ expect(readFileSync(join(dir, "empty-s.txt"), "utf-8")).toBe("");
744
+ expect(readFileSync(join(dir, "empty-h.txt"), "utf-8")).toBe("");
689
745
  });
690
746
 
691
- test('editing a file with only whitespace works correctly', () => {
747
+ test("editing a file with only whitespace works correctly", () => {
692
748
  const dir = makeTempDir();
693
- writeFileSync(join(dir, 'ws-s.txt'), ' \n \n ');
694
- writeFileSync(join(dir, 'ws-h.txt'), ' \n \n ');
749
+ writeFileSync(join(dir, "ws-s.txt"), " \n \n ");
750
+ writeFileSync(join(dir, "ws-h.txt"), " \n \n ");
695
751
 
696
752
  const { sandbox, host } = dualOps(dir);
697
753
 
698
754
  const sandboxResult = sandbox.editFileSafe({
699
- path: 'ws-s.txt',
700
- oldString: ' \n \n ',
701
- newString: 'replaced',
755
+ path: "ws-s.txt",
756
+ oldString: " \n \n ",
757
+ newString: "replaced",
702
758
  replaceAll: false,
703
759
  });
704
760
  const hostResult = host.editFileSafe({
705
- path: join(dir, 'ws-h.txt'),
706
- oldString: ' \n \n ',
707
- newString: 'replaced',
761
+ path: join(dir, "ws-h.txt"),
762
+ oldString: " \n \n ",
763
+ newString: "replaced",
708
764
  replaceAll: false,
709
765
  });
710
766
 
@@ -712,21 +768,21 @@ describe('Regression: edge cases in shared FileSystemOps', () => {
712
768
  expect(hostResult.ok).toBe(true);
713
769
  if (!sandboxResult.ok || !hostResult.ok) return;
714
770
 
715
- expect(sandboxResult.value.newContent).toBe('replaced');
716
- expect(hostResult.value.newContent).toBe('replaced');
771
+ expect(sandboxResult.value.newContent).toBe("replaced");
772
+ expect(hostResult.value.newContent).toBe("replaced");
717
773
  });
718
774
 
719
- test('write then read roundtrip produces consistent content', () => {
775
+ test("write then read roundtrip produces consistent content", () => {
720
776
  const dir = makeTempDir();
721
- const content = 'line 1\nline 2\nline 3';
777
+ const content = "line 1\nline 2\nline 3";
722
778
 
723
779
  const { sandbox, host } = dualOps(dir);
724
780
 
725
781
  // Write via sandbox, read via both
726
- sandbox.writeFileSafe({ path: 'roundtrip.txt', content });
782
+ sandbox.writeFileSafe({ path: "roundtrip.txt", content });
727
783
 
728
- const sandboxRead = sandbox.readFileSafe({ path: 'roundtrip.txt' });
729
- const hostRead = host.readFileSafe({ path: join(dir, 'roundtrip.txt') });
784
+ const sandboxRead = sandbox.readFileSafe({ path: "roundtrip.txt" });
785
+ const hostRead = host.readFileSafe({ path: join(dir, "roundtrip.txt") });
730
786
 
731
787
  expect(sandboxRead.ok).toBe(true);
732
788
  expect(hostRead.ok).toBe(true);
@@ -735,43 +791,43 @@ describe('Regression: edge cases in shared FileSystemOps', () => {
735
791
  expect(sandboxRead.value.content).toBe(hostRead.value.content);
736
792
  });
737
793
 
738
- test('write then edit then read roundtrip is consistent', () => {
794
+ test("write then edit then read roundtrip is consistent", () => {
739
795
  const dir = makeTempDir();
740
- const initial = 'const x = 1;\nconst y = 2;\nconst z = 3;';
796
+ const initial = "const x = 1;\nconst y = 2;\nconst z = 3;";
741
797
 
742
798
  const { sandbox, host } = dualOps(dir);
743
799
 
744
- sandbox.writeFileSafe({ path: 'code.ts', content: initial });
800
+ sandbox.writeFileSafe({ path: "code.ts", content: initial });
745
801
 
746
802
  sandbox.editFileSafe({
747
- path: 'code.ts',
748
- oldString: 'const y = 2;',
749
- newString: 'const y = 42;',
803
+ path: "code.ts",
804
+ oldString: "const y = 2;",
805
+ newString: "const y = 42;",
750
806
  replaceAll: false,
751
807
  });
752
808
 
753
- const sandboxRead = sandbox.readFileSafe({ path: 'code.ts' });
754
- const hostRead = host.readFileSafe({ path: join(dir, 'code.ts') });
809
+ const sandboxRead = sandbox.readFileSafe({ path: "code.ts" });
810
+ const hostRead = host.readFileSafe({ path: join(dir, "code.ts") });
755
811
 
756
812
  expect(sandboxRead.ok).toBe(true);
757
813
  expect(hostRead.ok).toBe(true);
758
814
  if (!sandboxRead.ok || !hostRead.ok) return;
759
815
 
760
816
  expect(sandboxRead.value.content).toBe(hostRead.value.content);
761
- expect(sandboxRead.value.content).toContain('const y = 42;');
762
- expect(sandboxRead.value.content).not.toContain('const y = 2;');
817
+ expect(sandboxRead.value.content).toContain("const y = 42;");
818
+ expect(sandboxRead.value.content).not.toContain("const y = 2;");
763
819
  });
764
820
 
765
- test('file with very long lines is handled identically', () => {
821
+ test("file with very long lines is handled identically", () => {
766
822
  const dir = makeTempDir();
767
- const longLine = 'a'.repeat(10_000);
768
- writeFileSync(join(dir, 'long-s.txt'), longLine);
769
- writeFileSync(join(dir, 'long-h.txt'), longLine);
823
+ const longLine = "a".repeat(10_000);
824
+ writeFileSync(join(dir, "long-s.txt"), longLine);
825
+ writeFileSync(join(dir, "long-h.txt"), longLine);
770
826
 
771
827
  const { sandbox, host } = dualOps(dir);
772
828
 
773
- const sandboxResult = sandbox.readFileSafe({ path: 'long-s.txt' });
774
- const hostResult = host.readFileSafe({ path: join(dir, 'long-h.txt') });
829
+ const sandboxResult = sandbox.readFileSafe({ path: "long-s.txt" });
830
+ const hostResult = host.readFileSafe({ path: join(dir, "long-h.txt") });
775
831
 
776
832
  expect(sandboxResult.ok).toBe(true);
777
833
  expect(hostResult.ok).toBe(true);
@@ -780,80 +836,82 @@ describe('Regression: edge cases in shared FileSystemOps', () => {
780
836
  expect(sandboxResult.value.content).toBe(hostResult.value.content);
781
837
  });
782
838
 
783
- test('concurrent writes to different files in same dir are isolated', () => {
839
+ test("concurrent writes to different files in same dir are isolated", () => {
784
840
  const dir = makeTempDir();
785
841
 
786
842
  const { sandbox } = dualOps(dir);
787
843
 
788
844
  // Simulate two "concurrent" writes (sequential here, but tests isolation)
789
- const r1 = sandbox.writeFileSafe({ path: 'a.txt', content: 'content-a' });
790
- const r2 = sandbox.writeFileSafe({ path: 'b.txt', content: 'content-b' });
845
+ const r1 = sandbox.writeFileSafe({ path: "a.txt", content: "content-a" });
846
+ const r2 = sandbox.writeFileSafe({ path: "b.txt", content: "content-b" });
791
847
 
792
848
  expect(r1.ok).toBe(true);
793
849
  expect(r2.ok).toBe(true);
794
850
 
795
- expect(readFileSync(join(dir, 'a.txt'), 'utf-8')).toBe('content-a');
796
- expect(readFileSync(join(dir, 'b.txt'), 'utf-8')).toBe('content-b');
851
+ expect(readFileSync(join(dir, "a.txt"), "utf-8")).toBe("content-a");
852
+ expect(readFileSync(join(dir, "b.txt"), "utf-8")).toBe("content-b");
797
853
  });
798
854
 
799
- test('edit preserves file content exactly except for the replacement', () => {
855
+ test("edit preserves file content exactly except for the replacement", () => {
800
856
  const dir = makeTempDir();
801
857
  // Content with trailing newline, tabs, and special chars
802
- const content = '\tfirst line\n\tsecond line\n\tthird line\n';
803
- writeFileSync(join(dir, 'precise.txt'), content);
858
+ const content = "\tfirst line\n\tsecond line\n\tthird line\n";
859
+ writeFileSync(join(dir, "precise.txt"), content);
804
860
 
805
861
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
806
862
  const result = ops.editFileSafe({
807
- path: 'precise.txt',
808
- oldString: '\tsecond line',
809
- newString: '\treplaced line',
863
+ path: "precise.txt",
864
+ oldString: "\tsecond line",
865
+ newString: "\treplaced line",
810
866
  replaceAll: false,
811
867
  });
812
868
 
813
869
  expect(result.ok).toBe(true);
814
870
  if (!result.ok) return;
815
871
 
816
- expect(result.value.newContent).toBe('\tfirst line\n\treplaced line\n\tthird line\n');
817
- expect(readFileSync(join(dir, 'precise.txt'), 'utf-8')).toBe(
818
- '\tfirst line\n\treplaced line\n\tthird line\n',
872
+ expect(result.value.newContent).toBe(
873
+ "\tfirst line\n\treplaced line\n\tthird line\n",
874
+ );
875
+ expect(readFileSync(join(dir, "precise.txt"), "utf-8")).toBe(
876
+ "\tfirst line\n\treplaced line\n\tthird line\n",
819
877
  );
820
878
  });
821
879
 
822
- test('read with offset=1 and no limit returns all lines', () => {
880
+ test("read with offset=1 and no limit returns all lines", () => {
823
881
  const dir = makeTempDir();
824
- writeFileSync(join(dir, 'full.txt'), 'one\ntwo\nthree\n');
882
+ writeFileSync(join(dir, "full.txt"), "one\ntwo\nthree\n");
825
883
 
826
884
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
827
- const result = ops.readFileSafe({ path: 'full.txt', offset: 1 });
885
+ const result = ops.readFileSafe({ path: "full.txt", offset: 1 });
828
886
 
829
887
  expect(result.ok).toBe(true);
830
888
  if (!result.ok) return;
831
889
 
832
- expect(result.value.content).toContain('one');
833
- expect(result.value.content).toContain('two');
834
- expect(result.value.content).toContain('three');
890
+ expect(result.value.content).toContain("one");
891
+ expect(result.value.content).toContain("two");
892
+ expect(result.value.content).toContain("three");
835
893
  });
836
894
 
837
- test('symlink within sandbox boundary is readable', () => {
895
+ test("symlink within sandbox boundary is readable", () => {
838
896
  const dir = makeTempDir();
839
- const targetFile = join(dir, 'target.txt');
840
- writeFileSync(targetFile, 'linked content');
897
+ const targetFile = join(dir, "target.txt");
898
+ writeFileSync(targetFile, "linked content");
841
899
 
842
- const linkPath = join(dir, 'link.txt');
900
+ const linkPath = join(dir, "link.txt");
843
901
  try {
844
902
  // eslint-disable-next-line @typescript-eslint/no-require-imports
845
- require('node:fs').symlinkSync(targetFile, linkPath);
903
+ require("node:fs").symlinkSync(targetFile, linkPath);
846
904
  } catch {
847
905
  // Symlink creation may fail on some systems — skip gracefully
848
906
  return;
849
907
  }
850
908
 
851
909
  const ops = new FileSystemOps(sandboxPolicyFor(dir));
852
- const result = ops.readFileSafe({ path: 'link.txt' });
910
+ const result = ops.readFileSafe({ path: "link.txt" });
853
911
 
854
912
  expect(result.ok).toBe(true);
855
913
  if (!result.ok) return;
856
- expect(result.value.content).toContain('linked content');
914
+ expect(result.value.content).toContain("linked content");
857
915
  });
858
916
  });
859
917
 
@@ -861,20 +919,25 @@ describe('Regression: edge cases in shared FileSystemOps', () => {
861
919
  // 9. NativeBackend shape verification
862
920
  // ===========================================================================
863
921
 
864
- describe('NativeBackend: SandboxResult shape', () => {
865
- test('NativeBackend has a wrap method', () => {
922
+ describe("NativeBackend: SandboxResult shape", () => {
923
+ test("NativeBackend has a wrap method", () => {
866
924
  const native = new NativeBackend();
867
- expect(typeof native.wrap).toBe('function');
925
+ expect(typeof native.wrap).toBe("function");
868
926
  });
869
927
 
870
- test('disabled sandbox returns consistent bash -c -- invocation', () => {
928
+ test("disabled sandbox returns consistent bash -c -- invocation", () => {
871
929
  // Various commands should all be wrapped consistently when disabled
872
- const commands = ['echo hello', 'ls -la', 'cat /etc/hosts', 'true && false'];
930
+ const commands = [
931
+ "echo hello",
932
+ "ls -la",
933
+ "cat /etc/hosts",
934
+ "true && false",
935
+ ];
873
936
  for (const cmd of commands) {
874
- const result = wrapCommand(cmd, '/tmp', { enabled: false });
875
- expect(result.command).toBe('bash');
876
- expect(result.args[0]).toBe('-c');
877
- expect(result.args[1]).toBe('--');
937
+ const result = wrapCommand(cmd, "/tmp", { enabled: false });
938
+ expect(result.command).toBe("bash");
939
+ expect(result.args[0]).toBe("-c");
940
+ expect(result.args[1]).toBe("--");
878
941
  expect(result.args[2]).toBe(cmd);
879
942
  expect(result.sandboxed).toBe(false);
880
943
  }
@@ -885,15 +948,15 @@ describe('NativeBackend: SandboxResult shape', () => {
885
948
  // 10. Error handling consistency
886
949
  // ===========================================================================
887
950
 
888
- describe('Error handling consistency across code paths', () => {
889
- test('FsError codes are consistent between sandbox and host for same conditions', () => {
951
+ describe("Error handling consistency across code paths", () => {
952
+ test("FsError codes are consistent between sandbox and host for same conditions", () => {
890
953
  const dir = makeTempDir();
891
954
 
892
955
  const { sandbox, host } = dualOps(dir);
893
956
 
894
957
  // NOT_FOUND
895
- const sfNotFound = sandbox.readFileSafe({ path: 'missing.txt' });
896
- const hfNotFound = host.readFileSafe({ path: join(dir, 'missing.txt') });
958
+ const sfNotFound = sandbox.readFileSafe({ path: "missing.txt" });
959
+ const hfNotFound = host.readFileSafe({ path: join(dir, "missing.txt") });
897
960
  expect(sfNotFound.ok).toBe(false);
898
961
  expect(hfNotFound.ok).toBe(false);
899
962
  if (!sfNotFound.ok && !hfNotFound.ok) {
@@ -901,9 +964,9 @@ describe('Error handling consistency across code paths', () => {
901
964
  }
902
965
 
903
966
  // NOT_A_FILE
904
- mkdirSync(join(dir, 'dirA'));
905
- const sfNotFile = sandbox.readFileSafe({ path: 'dirA' });
906
- const hfNotFile = host.readFileSafe({ path: join(dir, 'dirA') });
967
+ mkdirSync(join(dir, "dirA"));
968
+ const sfNotFile = sandbox.readFileSafe({ path: "dirA" });
969
+ const hfNotFile = host.readFileSafe({ path: join(dir, "dirA") });
907
970
  expect(sfNotFile.ok).toBe(false);
908
971
  expect(hfNotFile.ok).toBe(false);
909
972
  if (!sfNotFile.ok && !hfNotFile.ok) {
@@ -911,34 +974,47 @@ describe('Error handling consistency across code paths', () => {
911
974
  }
912
975
  });
913
976
 
914
- test('write error codes match between sandbox and host for same conditions', () => {
977
+ test("write error codes match between sandbox and host for same conditions", () => {
915
978
  const dir = makeTempDir();
916
979
 
917
980
  const { sandbox, host } = dualOps(dir);
918
981
 
919
982
  // Both should succeed for valid operations
920
- const sfWrite = sandbox.writeFileSafe({ path: 'ok-s.txt', content: 'ok' });
921
- const hfWrite = host.writeFileSafe({ path: join(dir, 'ok-h.txt'), content: 'ok' });
983
+ const sfWrite = sandbox.writeFileSafe({ path: "ok-s.txt", content: "ok" });
984
+ const hfWrite = host.writeFileSafe({
985
+ path: join(dir, "ok-h.txt"),
986
+ content: "ok",
987
+ });
922
988
 
923
989
  expect(sfWrite.ok).toBe(true);
924
990
  expect(hfWrite.ok).toBe(true);
925
991
  });
926
992
 
927
- test('edit MATCH_NOT_FOUND vs MATCH_AMBIGUOUS error codes match between paths', () => {
993
+ test("edit MATCH_NOT_FOUND vs MATCH_AMBIGUOUS error codes match between paths", () => {
928
994
  const dir = makeTempDir();
929
- writeFileSync(join(dir, 'err-s.txt'), 'unique text');
930
- writeFileSync(join(dir, 'err-h.txt'), 'unique text');
995
+ writeFileSync(join(dir, "err-s.txt"), "unique text");
996
+ writeFileSync(join(dir, "err-h.txt"), "unique text");
931
997
 
932
998
  const { sandbox, host } = dualOps(dir);
933
999
 
934
1000
  // MATCH_NOT_FOUND
935
- const sfMnf = sandbox.editFileSafe({ path: 'err-s.txt', oldString: 'nope', newString: 'x', replaceAll: false });
936
- const hfMnf = host.editFileSafe({ path: join(dir, 'err-h.txt'), oldString: 'nope', newString: 'x', replaceAll: false });
1001
+ const sfMnf = sandbox.editFileSafe({
1002
+ path: "err-s.txt",
1003
+ oldString: "nope",
1004
+ newString: "x",
1005
+ replaceAll: false,
1006
+ });
1007
+ const hfMnf = host.editFileSafe({
1008
+ path: join(dir, "err-h.txt"),
1009
+ oldString: "nope",
1010
+ newString: "x",
1011
+ replaceAll: false,
1012
+ });
937
1013
  expect(sfMnf.ok).toBe(false);
938
1014
  expect(hfMnf.ok).toBe(false);
939
1015
  if (!sfMnf.ok && !hfMnf.ok) {
940
1016
  expect(sfMnf.error.code).toBe(hfMnf.error.code);
941
- expect(sfMnf.error.code).toBe('MATCH_NOT_FOUND');
1017
+ expect(sfMnf.error.code).toBe("MATCH_NOT_FOUND");
942
1018
  }
943
1019
  });
944
1020
  });