mixdog 0.7.1

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 (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,130 @@
1
+ // Fuzz test: _findActualString invariants under random drift.
2
+ //
3
+ // Approach: pick a random substring of randomly-generated content, apply
4
+ // one of several deterministic drift transforms (NFC/NFD swap, codepoint
5
+ // fold variants, trailing-whitespace injection, indent shift, line-end
6
+ // drop) to the substring, and feed it through _findActualString.
7
+ //
8
+ // Invariants checked when the matcher returns a non-null slice:
9
+ // • returned string IS a substring of `content` (content.includes(slice))
10
+ // • content.indexOf(slice) finds a position (no synthesised bytes)
11
+ // • slice.normalize('NFD').indexOf(searchAfterDriftNfd) === 0 or close
12
+ // — the matched slice's NFD form contains the search's NFD form at
13
+ // the start, confirming the matcher did not pick something unrelated
14
+ //
15
+ // Seed: a deterministic LCG so the same DEV_FUZZ_SEED env reproduces
16
+ // failures bit-for-bit.
17
+
18
+ import { findActualString as _findActualString } from '../src/agent/orchestrator/tools/edit-normalize.mjs';
19
+
20
+ const ITERATIONS = Number(process.env.DEV_FUZZ_ITER) || 1000;
21
+ const SEED = Number(process.env.DEV_FUZZ_SEED) || 0xC0DE1234;
22
+
23
+ // LCG PRNG for reproducible fuzz.
24
+ let _state = SEED >>> 0;
25
+ function rand() {
26
+ _state = (_state * 1664525 + 1013904223) >>> 0;
27
+ return _state / 0x100000000;
28
+ }
29
+ function randInt(min, max) {
30
+ return min + Math.floor(rand() * (max - min + 1));
31
+ }
32
+ function pick(arr) {
33
+ return arr[Math.floor(rand() * arr.length)];
34
+ }
35
+
36
+ const ASCII = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_(){}[],.;:!? \t\n';
37
+ const HANGUL = '가나다라마바사아자차카타파하각간갈감갑값나라말사람';
38
+ const FOLD_VARIANTS = '      ‐–—―−-‘’“”"'';
39
+
40
+ function randomChar() {
41
+ const r = rand();
42
+ if (r < 0.70) return ASCII[Math.floor(rand() * ASCII.length)];
43
+ if (r < 0.85) return HANGUL[Math.floor(rand() * HANGUL.length)];
44
+ return FOLD_VARIANTS[Math.floor(rand() * FOLD_VARIANTS.length)];
45
+ }
46
+
47
+ function randomContent() {
48
+ const len = randInt(80, 300);
49
+ let out = '';
50
+ for (let i = 0; i < len; i++) out += randomChar();
51
+ return out;
52
+ }
53
+
54
+ // Drift transforms — each takes a slice and produces a possibly different
55
+ // representation. The matcher must still locate the original slice.
56
+ const drifts = [
57
+ (s) => s, // identity
58
+ (s) => s.normalize('NFC'), // NFC
59
+ (s) => s.normalize('NFD'), // NFD
60
+ (s) => s.replace(/-/g, '—'), // hyphen → em dash
61
+ (s) => s.replace(/'/g, '‘'), // straight → curly single
62
+ (s) => s.replace(/"/g, '“'), // straight → curly double
63
+ (s) => s.replace(/ /g, ' '), // space → NBSP
64
+ (s) => s.replace(/—/g, '-'), // em dash → hyphen
65
+ (s) => s.replace(/‘/g, "'"), // curly → straight
66
+ (s) => s.replace(/“/g, '"'), // curly → straight
67
+ (s) => s.replace(/ /g, ' '), // NBSP → space
68
+ (s) => s.split('\n').map((l) => l + ' ').join('\n'), // add trailing spaces
69
+ (s) => s.split('\n').map((l) => l.replace(/ +$/, '')).join('\n'), // strip trailing
70
+ (s) => s.split('\n').map((l) => ' ' + l).join('\n'), // add leading indent
71
+ (s) => s.split('\n').map((l) => l.replace(/^ +/, '')).join('\n'), // strip leading
72
+ ];
73
+
74
+ let matched = 0;
75
+ let unmatched = 0;
76
+ let invariantFails = 0;
77
+ const failSamples = [];
78
+
79
+ for (let i = 0; i < ITERATIONS; i++) {
80
+ const content = randomContent();
81
+ const start = randInt(0, Math.max(0, content.length - 10));
82
+ const len = randInt(3, Math.min(60, content.length - start));
83
+ const original = content.substring(start, start + len);
84
+ if (original.length < 2) continue;
85
+ const drift = pick(drifts);
86
+ const search = drift(original);
87
+ const info = {};
88
+ let result;
89
+ try {
90
+ result = _findActualString(content, search, info);
91
+ } catch (err) {
92
+ invariantFails++;
93
+ failSamples.push({ reason: 'throw', err: String(err), content, search });
94
+ if (failSamples.length > 5) break;
95
+ continue;
96
+ }
97
+ if (result === null) {
98
+ unmatched++;
99
+ continue;
100
+ }
101
+ matched++;
102
+ if (typeof result !== 'string') {
103
+ invariantFails++;
104
+ failSamples.push({ reason: 'non-string', result, content, search });
105
+ if (failSamples.length > 5) break;
106
+ continue;
107
+ }
108
+ if (!content.includes(result)) {
109
+ invariantFails++;
110
+ failSamples.push({ reason: 'not-substring', result, content, search });
111
+ if (failSamples.length > 5) break;
112
+ }
113
+ }
114
+
115
+ console.log(`edit-normalize-fuzz: ${ITERATIONS} iterations (seed=0x${SEED.toString(16).toUpperCase()})`);
116
+ console.log(` matched: ${matched}`);
117
+ console.log(` unmatched: ${unmatched}`);
118
+ console.log(` invariant fails: ${invariantFails}`);
119
+
120
+ if (invariantFails > 0) {
121
+ console.error('\nFAIL samples:');
122
+ for (const s of failSamples) {
123
+ console.error(` reason: ${s.reason}`);
124
+ console.error(` content: ${JSON.stringify(s.content).slice(0, 200)}`);
125
+ console.error(` search: ${JSON.stringify(s.search).slice(0, 200)}`);
126
+ if (s.result !== undefined) console.error(` result: ${JSON.stringify(s.result).slice(0, 200)}`);
127
+ if (s.err) console.error(` err: ${s.err}`);
128
+ }
129
+ process.exit(1);
130
+ }
@@ -0,0 +1,401 @@
1
+ // Smoke test: edit-normalize.mjs edit-input normalization helpers.
2
+ //
3
+ // Validates the invariant-safe matching helpers extracted live from the
4
+ // implementation so the smoke stays in sync:
5
+ // • _normalizeForMatch — curly-quote → straight fold (length-preserving).
6
+ // • _nfcFoldMatch — NFC/NFD canonical matcher (byte-exact slice).
7
+ // • _stripTrailingWhitespacePerLine — insert-side trailing space/tab strip
8
+ // with line-terminator preservation.
9
+ // • _preserveQuoteTypography — heuristic insert-side typography helper.
10
+ //
11
+ // Last-resort rstrip-fold / indent-fold / eol-fold tiers are enabled with
12
+ // per-tier ambiguity rejection (diagnoseFoldTierAmbiguity). Case, dash,
13
+ // fullwidth and Unicode-space folding remain removed.
14
+ //
15
+ // Invariants:
16
+ // 1. Every folded codepoint is a single UTF-16 unit (BMP only).
17
+ // 2. Fold preserves input.length so indexOf hits re-index original bytes.
18
+ // 3. nfc-fold returns content.substring(start, end) — never synthesised.
19
+ // 4. Trailing-whitespace strip preserves LF / CRLF / lone CR byte-exact.
20
+ // 5. Non-curly codepoints (dash, fullwidth, space, ZWSP, BOM, ASCII)
21
+ // never match the fold.
22
+
23
+ import {
24
+ normalizeForMatch as _normalizeForMatch,
25
+ nfcFoldMatch as _nfcFoldMatch,
26
+ findActualString as _findActualString,
27
+ diagnoseFoldTierAmbiguity as _diagnoseFoldTierAmbiguity,
28
+ diagnoseEolFoldAmbiguity as _diagnoseEolFoldAmbiguity,
29
+ stripTrailingWhitespacePerLine as _stripTrailingWhitespacePerLine,
30
+ preserveQuoteTypography as _preserveQuoteTypography,
31
+ } from '../src/agent/orchestrator/tools/edit-normalize.mjs';
32
+
33
+ let failures = 0;
34
+ function fail(name, msg) { console.error(`FAIL ${name}: ${msg}`); failures++; }
35
+
36
+ // ── Section 1: fold codepoint matrix (curly quotes ONLY) ───────────────────
37
+ const matchCases = [
38
+ ['U+2018 left single quote', '\u2018', "'"],
39
+ ['U+2019 right single quote', '\u2019', "'"],
40
+ ['U+201A single low-9', '\u201A', "'"],
41
+ ['U+201B single high-rev-9', '\u201B', "'"],
42
+ ['U+201C left double quote', '\u201C', '"'],
43
+ ['U+201D right double quote', '\u201D', '"'],
44
+ ['U+201E double low-9', '\u201E', '"'],
45
+ ['U+201F double high-rev-9', '\u201F', '"'],
46
+ ];
47
+
48
+ // Heuristic-risky folds intentionally removed: these must pass through
49
+ // UNCHANGED now (each could map genuinely-different text to the same view).
50
+ const skipCases = [
51
+ ['U+2010 hyphen', '\u2010'],
52
+ ['U+2013 en dash', '\u2013'],
53
+ ['U+2014 em dash', '\u2014'],
54
+ ['U+2015 horizontal bar', '\u2015'],
55
+ ['U+2212 minus', '\u2212'],
56
+ ['U+FF0D fullwidth hyphen-minus', '\uFF0D'],
57
+ ['U+FF07 fullwidth apostrophe', '\uFF07'],
58
+ ['U+FF02 fullwidth quote', '\uFF02'],
59
+ ['U+00A0 NBSP', '\u00A0'],
60
+ ['U+2003 em space', '\u2003'],
61
+ ['U+202F narrow NBSP', '\u202F'],
62
+ ['U+205F medium math space', '\u205F'],
63
+ ['U+3000 ideographic space', '\u3000'],
64
+ ['U+200B ZWSP', '\u200B'],
65
+ ['U+FEFF BOM', '\uFEFF'],
66
+ ['U+0020 ASCII space', ' '],
67
+ ['U+002D ASCII hyphen', '-'],
68
+ ['U+0027 ASCII apos', "'"],
69
+ ['U+0022 ASCII quote', '"'],
70
+ ['U+0041 ASCII A', 'A'],
71
+ ['U+AC00 Hangul GA', '\uAC00'],
72
+ ];
73
+
74
+ for (const [name, input, expected] of matchCases) {
75
+ if (input.length !== 1) { fail(name, `single UTF-16 unit expected, got length ${input.length}`); continue; }
76
+ const folded = _normalizeForMatch(input);
77
+ if (folded !== expected) { fail(name, `folded to ${JSON.stringify(folded)}, expected ${JSON.stringify(expected)}`); continue; }
78
+ if (folded.length !== input.length) { fail(name, `length drift (${input.length} -> ${folded.length})`); continue; }
79
+ }
80
+ for (const [name, input] of skipCases) {
81
+ const folded = _normalizeForMatch(input);
82
+ if (folded !== input) { fail(name, `should not be folded, got ${JSON.stringify(folded)}`); }
83
+ }
84
+
85
+ // Mixed string: curly quotes fold, dash + ideographic space stay verbatim.
86
+ const mixed = 'foo\u2014bar baz\u2018qux\u2019\u3000end';
87
+ const mixedFolded = _normalizeForMatch(mixed);
88
+ if (mixedFolded !== 'foo\u2014bar baz\'qux\'\u3000end') fail('mixed-fold', `got ${JSON.stringify(mixedFolded)}`);
89
+ if (mixedFolded.length !== mixed.length) fail('mixed-fold-length', `${mixed.length} -> ${mixedFolded.length}`);
90
+
91
+ // ── Section 2: findActualString pipeline (exact -> fold -> nfc) ─────────────
92
+ // Curly-quote fold tier still recovers a unique straight-vs-curly mismatch.
93
+ {
94
+ const content = 'the value is \u201Chello\u201D today\n';
95
+ const search = 'the value is "hello" today';
96
+ const info = {};
97
+ const slice = _findActualString(content, search, info);
98
+ if (slice === null) fail('pipeline:curly-fold', 'expected a fold match, got null');
99
+ else if (info.stage !== 'fold') fail('pipeline:curly-fold', `expected stage fold, got ${info.stage}`);
100
+ else if (!content.includes(slice)) fail('pipeline:curly-fold', 'returned slice is NOT a substring of content');
101
+ }
102
+
103
+ // Fold-tier uniqueness gate: two curly variants of the same straight search
104
+ // must collapse to null rather than silently picking the first.
105
+ {
106
+ const content = 'say \u2018hi\u2019 / say \u201Bhi\u2019\n';
107
+ const search = "say 'hi'";
108
+ const info = {};
109
+ const slice = _findActualString(content, search, info);
110
+ if (slice !== null) fail('pipeline:fold-multi-match-rejected', `expected null, got ${JSON.stringify(slice)} (stage=${info.stage})`);
111
+ }
112
+
113
+ // rstrip-fold: unique trailing-whitespace drift on a line body.
114
+ {
115
+ const content = 'header\nfoo \nbar\nfooter\n';
116
+ const search = 'foo\nbar';
117
+ const info = {};
118
+ const slice = _findActualString(content, search, info);
119
+ if (slice === null) fail('pipeline:rstrip-fold', 'expected unique match');
120
+ else if (info.stage !== 'rstrip-fold') fail('pipeline:rstrip-fold', `stage ${info.stage}`);
121
+ else if (slice !== 'foo \nbar') fail('pipeline:rstrip-fold', `slice ${JSON.stringify(slice)}`);
122
+ }
123
+
124
+ // indent-fold: leading tabs in file vs spaces in search (CC tab width 2).
125
+ {
126
+ const content = 'header\n\tif (x) {\n\t\ty;\n\t}\nfooter\n';
127
+ const search = ' if (x) {\n y;\n }';
128
+ const info = {};
129
+ const slice = _findActualString(content, search, info);
130
+ if (slice === null) fail('pipeline:indent-fold', 'expected unique match');
131
+ else if (info.stage !== 'indent-fold') fail('pipeline:indent-fold', `stage ${info.stage}`);
132
+ else if (!content.includes(slice)) fail('pipeline:indent-fold', 'slice not substring');
133
+ }
134
+
135
+ // eol-fold: search has trailing newline; file slice does not.
136
+ {
137
+ const content = 'alpha';
138
+ const search = 'alpha\n';
139
+ const info = {};
140
+ const slice = _findActualString(content, search, info);
141
+ if (slice === null) fail('pipeline:eol-fold', 'expected match');
142
+ else if (info.stage !== 'eol-fold') fail('pipeline:eol-fold', `stage ${info.stage}`);
143
+ else if (slice !== 'alpha') fail('pipeline:eol-fold', `got ${JSON.stringify(slice)}`);
144
+ }
145
+
146
+ // eol-fold must not match bare text mid-line.
147
+ {
148
+ const content = 'prefix alpha suffix\n';
149
+ const search = 'alpha\n';
150
+ const slice = _findActualString(content, search, {});
151
+ if (slice !== null) fail('pipeline:eol-fold-midline-reject', JSON.stringify(slice));
152
+ }
153
+
154
+ // crlf-fold wins over rstrip-fold when both sites exist.
155
+ {
156
+ const content = 'foo \nbar\nzzz\nfoo\r\nbar\n';
157
+ const search = 'foo\nbar';
158
+ const info = {};
159
+ const slice = _findActualString(content, search, info);
160
+ if (slice === null) fail('pipeline:crlf-beats-rstrip', 'expected crlf site');
161
+ else if (info.stage !== 'crlf-fold') fail('pipeline:crlf-beats-rstrip', `stage ${info.stage}`);
162
+ else if (slice !== 'foo\r\nbar') fail('pipeline:crlf-beats-rstrip', `slice ${JSON.stringify(slice)}`);
163
+ }
164
+
165
+ // Ambiguity: two rstrip-fold sites → diagnose code 9 payload.
166
+ {
167
+ const content = 'foo \nbar\nzzz\nfoo \nbar\n';
168
+ const search = 'foo\nbar';
169
+ const amb = _diagnoseFoldTierAmbiguity(content, search);
170
+ if (!amb || amb.stage !== 'rstrip-fold' || amb.count !== 2) {
171
+ fail('pipeline:rstrip-ambiguity', JSON.stringify(amb));
172
+ }
173
+ }
174
+
175
+ // Ambiguity: two indent-fold sites.
176
+ {
177
+ const content = 'header\n\tif (a) {}\nmid\n\tif (a) {}\nfooter\n';
178
+ const search = ' if (a) {}';
179
+ const slice = _findActualString(content, search, {});
180
+ const amb = _diagnoseFoldTierAmbiguity(content, search);
181
+ if (slice !== null) fail('pipeline:indent-ambiguity-find', JSON.stringify(slice));
182
+ if (!amb || amb.stage !== 'indent-fold' || amb.count !== 2) {
183
+ fail('pipeline:indent-ambiguity', JSON.stringify(amb));
184
+ }
185
+ }
186
+
187
+ // Ambiguity: two eol-fold boundary sites.
188
+ {
189
+ const content = 'alpha\nbeta\nalpha';
190
+ const search = 'alpha\n';
191
+ const amb = _diagnoseEolFoldAmbiguity(content, search);
192
+ if (!amb || amb.stage !== 'eol-fold' || amb.count !== 2) {
193
+ fail('pipeline:eol-ambiguity', JSON.stringify(amb));
194
+ }
195
+ }
196
+
197
+ // Whole-block re-indent still does not match (indent-shift not enabled).
198
+ {
199
+ const content = 'header\nif (x) {\n y;\n}\nfooter\n';
200
+ const search = ' if (x) {\n y;\n }';
201
+ const slice = _findActualString(content, search, {});
202
+ if (slice !== null) fail('pipeline:indent-shift-still-removed', JSON.stringify(slice));
203
+ }
204
+
205
+ // ── Section 3: _stripTrailingWhitespacePerLine (insert-side helper) ─────────
206
+ const stripCases = [
207
+ { name: 'LF-trailing-spaces', input: 'foo \nbar\t\nbaz\n', output: 'foo\nbar\nbaz\n' },
208
+ { name: 'CRLF-preserved', input: 'foo \r\nbar\t\r\nbaz\r\n', output: 'foo\r\nbar\r\nbaz\r\n' },
209
+ { name: 'lone-CR-preserved', input: 'foo \rbar\t\rbaz\r', output: 'foo\rbar\rbaz\r' },
210
+ { name: 'no-trailing-no-change', input: 'foo\nbar\nbaz\n', output: 'foo\nbar\nbaz\n' },
211
+ { name: 'mixed-LF-CRLF', input: 'foo \nbar \r\nbaz \n', output: 'foo\nbar\r\nbaz\n' },
212
+ { name: 'final-line-no-terminator', input: 'foo \nbar ', output: 'foo\nbar' },
213
+ { name: 'blank-lines-untouched', input: 'foo\n\n \nbar\n', output: 'foo\n\n\nbar\n' },
214
+ ];
215
+
216
+ for (const { name, input, output } of stripCases) {
217
+ const got = _stripTrailingWhitespacePerLine(input);
218
+ if (got !== output) { fail(`strip:${name}`, `got ${JSON.stringify(got)}, expected ${JSON.stringify(output)}`); }
219
+ }
220
+
221
+ // ── Section 4: _nfcFoldMatch ────────────────────────────────────────────────
222
+ // 가 NFC = U+AC00 (1 char); NFD = U+1100 U+1161 (2 chars).
223
+ // 각 = U+AC01 (1 NFC) vs U+1100 U+1161 U+11A8 (3 NFD).
224
+ const NFC_GA = '\uAC00';
225
+ const NFD_GA = '\u1100\u1161';
226
+ const NFC_GAK = '\uAC01';
227
+ const NFD_GAK = '\u1100\u1161\u11A8';
228
+
229
+ const nfcCases = [
230
+ {
231
+ name: 'NFD-content-NFC-search-hangul',
232
+ content: 'prefix-' + NFD_GAK + '-suffix\n',
233
+ search: 'prefix-' + NFC_GAK + '-suffix',
234
+ expected: 'prefix-' + NFD_GAK + '-suffix',
235
+ },
236
+ {
237
+ name: 'NFC-content-NFD-search-hangul',
238
+ content: 'prefix-' + NFC_GAK + '-suffix\n',
239
+ search: 'prefix-' + NFD_GAK + '-suffix',
240
+ expected: 'prefix-' + NFC_GAK + '-suffix',
241
+ },
242
+ {
243
+ name: 'NFC-no-drift-skip',
244
+ content: 'plain text',
245
+ search: 'plain text',
246
+ expected: null,
247
+ },
248
+ {
249
+ name: 'NFC-multi-match-rejected',
250
+ content: NFD_GAK + ' / ' + NFD_GAK + '\n',
251
+ search: NFC_GAK,
252
+ expected: null,
253
+ },
254
+ {
255
+ name: 'NFC-not-found',
256
+ content: 'completely unrelated\n',
257
+ search: NFC_GAK,
258
+ expected: null,
259
+ },
260
+ {
261
+ name: 'NFC-empty-search',
262
+ content: NFD_GA + '\n',
263
+ search: '',
264
+ expected: null,
265
+ },
266
+ {
267
+ name: 'NFC-cafe-latin',
268
+ content: 'cafe\u0301 menu\n',
269
+ search: 'caf\u00E9 menu',
270
+ expected: 'cafe\u0301 menu',
271
+ },
272
+ {
273
+ name: 'NFC-partial-line-byte-slice',
274
+ content: 'header\nthe ' + NFD_GA + ' is here\nfooter\n',
275
+ search: NFC_GA,
276
+ expected: NFD_GA,
277
+ },
278
+ ];
279
+
280
+ for (const { name, content, search, expected } of nfcCases) {
281
+ const got = _nfcFoldMatch(content, search);
282
+ if (got !== expected) { fail(`nfc:${name}`, `got ${JSON.stringify(got)}, expected ${JSON.stringify(expected)}`); continue; }
283
+ if (got !== null && !content.includes(got)) { fail(`nfc:${name}`, `returned slice is NOT a substring of content`); }
284
+ }
285
+
286
+ // ── Section 5: edge cases ───────────────────────────────────────────────────
287
+ const edgeCases = [
288
+ {
289
+ name: 'fold-empty-string',
290
+ run: () => _normalizeForMatch(''),
291
+ expected: '',
292
+ },
293
+ {
294
+ name: 'fold-pure-ascii-no-change',
295
+ run: () => _normalizeForMatch('plain ASCII line 123'),
296
+ expected: 'plain ASCII line 123',
297
+ },
298
+ {
299
+ name: 'strip-empty-input',
300
+ run: () => _stripTrailingWhitespacePerLine(''),
301
+ expected: '',
302
+ },
303
+ {
304
+ name: 'strip-only-newlines',
305
+ run: () => _stripTrailingWhitespacePerLine('\n\n\n'),
306
+ expected: '\n\n\n',
307
+ },
308
+ {
309
+ name: 'strip-tab-only-line',
310
+ run: () => _stripTrailingWhitespacePerLine('foo\n\t\nbar\n'),
311
+ expected: 'foo\n\nbar\n',
312
+ },
313
+ {
314
+ name: 'fold-BOM-not-folded',
315
+ run: () => _normalizeForMatch('\uFEFFfoo'),
316
+ expected: '\uFEFFfoo',
317
+ },
318
+ {
319
+ name: 'fold-ZWSP-not-folded',
320
+ run: () => _normalizeForMatch('foo\u200Bbar'),
321
+ expected: 'foo\u200Bbar',
322
+ },
323
+ ];
324
+
325
+ for (const { name, run, expected } of edgeCases) {
326
+ const got = run();
327
+ if (got !== expected) { fail(`edge:${name}`, `got ${JSON.stringify(got)}, expected ${JSON.stringify(expected)}`); }
328
+ }
329
+
330
+ // ── Section 6: preserveQuoteTypography (heuristic exception, CC parity) ──
331
+ // L/R curly quote codepoints — explicit escape to avoid encoding ambiguity.
332
+ const LSQ = '\u2018', RSQ = '\u2019', LDQ = '\u201C', RDQ = '\u201D';
333
+ const typoCases = [
334
+ {
335
+ name: 'double-quote-preserved',
336
+ old: 'foo "bar" baz',
337
+ matched: `foo ${LDQ}bar${RDQ} baz`,
338
+ new_: 'updated "content" here',
339
+ expected: `updated ${LDQ}content${RDQ} here`,
340
+ },
341
+ {
342
+ name: 'oldstring-equals-matched-no-change',
343
+ old: 'plain text',
344
+ matched: 'plain text',
345
+ new_: 'still plain "ascii"',
346
+ expected: 'still plain "ascii"',
347
+ },
348
+ {
349
+ name: 'no-curly-in-matched-no-change',
350
+ old: 'foo "bar"',
351
+ matched: 'foo "bar"',
352
+ new_: 'new "stuff"',
353
+ expected: 'new "stuff"',
354
+ },
355
+ {
356
+ name: 'contraction-right-single',
357
+ old: "it's a test",
358
+ matched: `it${RSQ}s a test`,
359
+ new_: "they're here",
360
+ expected: `they${RSQ}re here`,
361
+ },
362
+ {
363
+ name: 'opening-single-quote',
364
+ old: "say 'hi'",
365
+ matched: `say ${LSQ}hi${RSQ}`,
366
+ new_: "say 'bye'",
367
+ expected: `say ${LSQ}bye${RSQ}`,
368
+ },
369
+ {
370
+ name: 'opening-at-string-start',
371
+ old: '"hello"',
372
+ matched: `${LDQ}hello${RDQ}`,
373
+ new_: '"world"',
374
+ expected: `${LDQ}world${RDQ}`,
375
+ },
376
+ {
377
+ name: 'opening-after-em-dash',
378
+ old: 'said \u2014 "yes"',
379
+ matched: `said \u2014 ${LDQ}yes${RDQ}`,
380
+ new_: 'said \u2014 "no"',
381
+ expected: `said \u2014 ${LDQ}no${RDQ}`,
382
+ },
383
+ ];
384
+
385
+ for (const { name, old, matched, new_, expected } of typoCases) {
386
+ const got = _preserveQuoteTypography(old, matched, new_);
387
+ if (got !== expected) {
388
+ fail(`typo:${name}`, `got ${JSON.stringify(got)}, expected ${JSON.stringify(expected)}`);
389
+ }
390
+ }
391
+
392
+ const totalChecks =
393
+ matchCases.length + skipCases.length + 2
394
+ + 7 + stripCases.length + nfcCases.length + edgeCases.length
395
+ + typoCases.length;
396
+
397
+ if (failures > 0) {
398
+ console.error(`\n${failures} smoke check(s) failed.`);
399
+ process.exit(1);
400
+ }
401
+ console.log(`edit-normalize-smoke: ${totalChecks} checks passed.`);