open-multi-agent-kit 0.96.2 → 0.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +43 -9
  3. package/dist/cli/args.d.ts +2 -3
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +13 -189
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/help.d.ts +3 -0
  8. package/dist/cli/help.d.ts.map +1 -0
  9. package/dist/cli/help.js +194 -0
  10. package/dist/cli/help.js.map +1 -0
  11. package/dist/cli/initial-message.d.ts.map +1 -1
  12. package/dist/cli/initial-message.js +2 -1
  13. package/dist/cli/initial-message.js.map +1 -1
  14. package/dist/cli/resource-paths.d.ts +4 -0
  15. package/dist/cli/resource-paths.d.ts.map +1 -0
  16. package/dist/cli/resource-paths.js +59 -0
  17. package/dist/cli/resource-paths.js.map +1 -0
  18. package/dist/commands/adaptorch-doctor-cli.d.ts +13 -0
  19. package/dist/commands/adaptorch-doctor-cli.d.ts.map +1 -0
  20. package/dist/commands/adaptorch-doctor-cli.js +115 -0
  21. package/dist/commands/adaptorch-doctor-cli.js.map +1 -0
  22. package/dist/commands/resource-doctor-cli.d.ts +4 -0
  23. package/dist/commands/resource-doctor-cli.d.ts.map +1 -1
  24. package/dist/commands/resource-doctor-cli.js +35 -5
  25. package/dist/commands/resource-doctor-cli.js.map +1 -1
  26. package/dist/core/adaptorch-bridge.d.ts +11 -13
  27. package/dist/core/adaptorch-bridge.d.ts.map +1 -1
  28. package/dist/core/adaptorch-bridge.js +11 -13
  29. package/dist/core/adaptorch-bridge.js.map +1 -1
  30. package/dist/core/agent-session-snapshot.d.ts +16 -0
  31. package/dist/core/agent-session-snapshot.d.ts.map +1 -0
  32. package/dist/core/agent-session-snapshot.js +111 -0
  33. package/dist/core/agent-session-snapshot.js.map +1 -0
  34. package/dist/core/agent-session.d.ts +14 -10
  35. package/dist/core/agent-session.d.ts.map +1 -1
  36. package/dist/core/agent-session.js +102 -217
  37. package/dist/core/agent-session.js.map +1 -1
  38. package/dist/core/agent-tool-settings.d.ts +10 -0
  39. package/dist/core/agent-tool-settings.d.ts.map +1 -1
  40. package/dist/core/agent-tool-settings.js +10 -1
  41. package/dist/core/agent-tool-settings.js.map +1 -1
  42. package/dist/core/attachment-store.d.ts +39 -0
  43. package/dist/core/attachment-store.d.ts.map +1 -0
  44. package/dist/core/attachment-store.js +81 -0
  45. package/dist/core/attachment-store.js.map +1 -0
  46. package/dist/core/command-safety.d.ts +8 -0
  47. package/dist/core/command-safety.d.ts.map +1 -1
  48. package/dist/core/command-safety.js +140 -14
  49. package/dist/core/command-safety.js.map +1 -1
  50. package/dist/core/compaction/compaction.d.ts +13 -1
  51. package/dist/core/compaction/compaction.d.ts.map +1 -1
  52. package/dist/core/compaction/compaction.js +60 -62
  53. package/dist/core/compaction/compaction.js.map +1 -1
  54. package/dist/core/compaction/index.d.ts +1 -0
  55. package/dist/core/compaction/index.d.ts.map +1 -1
  56. package/dist/core/compaction/index.js +1 -0
  57. package/dist/core/compaction/index.js.map +1 -1
  58. package/dist/core/compaction/knowledge-triage-format.d.ts +9 -0
  59. package/dist/core/compaction/knowledge-triage-format.d.ts.map +1 -0
  60. package/dist/core/compaction/knowledge-triage-format.js +71 -0
  61. package/dist/core/compaction/knowledge-triage-format.js.map +1 -0
  62. package/dist/core/compaction/knowledge-triage-types.d.ts +7 -0
  63. package/dist/core/compaction/knowledge-triage-types.d.ts.map +1 -0
  64. package/dist/core/compaction/knowledge-triage-types.js +2 -0
  65. package/dist/core/compaction/knowledge-triage-types.js.map +1 -0
  66. package/dist/core/compaction/knowledge-triage.d.ts +29 -0
  67. package/dist/core/compaction/knowledge-triage.d.ts.map +1 -0
  68. package/dist/core/compaction/knowledge-triage.js +189 -0
  69. package/dist/core/compaction/knowledge-triage.js.map +1 -0
  70. package/dist/core/compaction/transaction.d.ts.map +1 -1
  71. package/dist/core/compaction/transaction.js +1 -1
  72. package/dist/core/compaction/transaction.js.map +1 -1
  73. package/dist/core/compaction/utils.d.ts +4 -0
  74. package/dist/core/compaction/utils.d.ts.map +1 -1
  75. package/dist/core/compaction/utils.js +37 -0
  76. package/dist/core/compaction/utils.js.map +1 -1
  77. package/dist/core/completion-sound-io.d.ts +14 -0
  78. package/dist/core/completion-sound-io.d.ts.map +1 -0
  79. package/dist/core/completion-sound-io.js +64 -0
  80. package/dist/core/completion-sound-io.js.map +1 -0
  81. package/dist/core/completion-sound.d.ts +4 -10
  82. package/dist/core/completion-sound.d.ts.map +1 -1
  83. package/dist/core/completion-sound.js +28 -58
  84. package/dist/core/completion-sound.js.map +1 -1
  85. package/dist/core/context-budget-governor-v2.d.ts +1 -1
  86. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  87. package/dist/core/context-budget-governor-v2.js +1 -1
  88. package/dist/core/context-budget-governor-v2.js.map +1 -1
  89. package/dist/core/context-budget-relevance.d.ts +15 -2
  90. package/dist/core/context-budget-relevance.d.ts.map +1 -1
  91. package/dist/core/context-budget-relevance.js +21 -9
  92. package/dist/core/context-budget-relevance.js.map +1 -1
  93. package/dist/core/context-budget-system-prompt-items.d.ts +1 -1
  94. package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
  95. package/dist/core/context-budget-system-prompt-items.js.map +1 -1
  96. package/dist/core/context-budget-system-prompt.d.ts +1 -1
  97. package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
  98. package/dist/core/context-budget-system-prompt.js.map +1 -1
  99. package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
  100. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  101. package/dist/core/context-budget-v2-cache-keys.js +2 -0
  102. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  103. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  104. package/dist/core/context-budget-v2-plan-cache-keys.js +3 -0
  105. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  106. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  107. package/dist/core/context-budget-v2-planner.js +2 -1
  108. package/dist/core/context-budget-v2-planner.js.map +1 -1
  109. package/dist/core/context-budget-v2-scoring.d.ts +17 -0
  110. package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
  111. package/dist/core/context-budget-v2-scoring.js +25 -8
  112. package/dist/core/context-budget-v2-scoring.js.map +1 -1
  113. package/dist/core/context-budget-v2-selection.d.ts +2 -2
  114. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  115. package/dist/core/context-budget-v2-selection.js +18 -1
  116. package/dist/core/context-budget-v2-selection.js.map +1 -1
  117. package/dist/core/context-budget-v2-types.d.ts +17 -0
  118. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  119. package/dist/core/context-budget-v2-types.js +15 -0
  120. package/dist/core/context-budget-v2-types.js.map +1 -1
  121. package/dist/core/context-file.d.ts +23 -0
  122. package/dist/core/context-file.d.ts.map +1 -0
  123. package/dist/core/context-file.js +17 -0
  124. package/dist/core/context-file.js.map +1 -0
  125. package/dist/core/extensions/builtin/command-safety-gate.d.ts +3 -1
  126. package/dist/core/extensions/builtin/command-safety-gate.d.ts.map +1 -1
  127. package/dist/core/extensions/builtin/command-safety-gate.js +7 -0
  128. package/dist/core/extensions/builtin/command-safety-gate.js.map +1 -1
  129. package/dist/core/extensions/types.d.ts +12 -1
  130. package/dist/core/extensions/types.d.ts.map +1 -1
  131. package/dist/core/extensions/types.js.map +1 -1
  132. package/dist/core/keybindings.d.ts +1 -1
  133. package/dist/core/keybindings.d.ts.map +1 -1
  134. package/dist/core/keybindings.js +6 -1
  135. package/dist/core/keybindings.js.map +1 -1
  136. package/dist/core/model-prompt-policy.d.ts +1 -1
  137. package/dist/core/model-prompt-policy.d.ts.map +1 -1
  138. package/dist/core/model-prompt-policy.js.map +1 -1
  139. package/dist/core/model-registry.d.ts +8 -3
  140. package/dist/core/model-registry.d.ts.map +1 -1
  141. package/dist/core/model-registry.js +71 -1
  142. package/dist/core/model-registry.js.map +1 -1
  143. package/dist/core/package-doctor-source-scan.d.ts +9 -0
  144. package/dist/core/package-doctor-source-scan.d.ts.map +1 -1
  145. package/dist/core/package-doctor-source-scan.js +77 -4
  146. package/dist/core/package-doctor-source-scan.js.map +1 -1
  147. package/dist/core/prompt-attachment.d.ts +51 -0
  148. package/dist/core/prompt-attachment.d.ts.map +1 -0
  149. package/dist/core/prompt-attachment.js +186 -0
  150. package/dist/core/prompt-attachment.js.map +1 -0
  151. package/dist/core/prompt-settlement.d.ts +6 -3
  152. package/dist/core/prompt-settlement.d.ts.map +1 -1
  153. package/dist/core/prompt-settlement.js +10 -14
  154. package/dist/core/prompt-settlement.js.map +1 -1
  155. package/dist/core/provider-resilience.d.ts +28 -0
  156. package/dist/core/provider-resilience.d.ts.map +1 -1
  157. package/dist/core/provider-resilience.js +50 -0
  158. package/dist/core/provider-resilience.js.map +1 -1
  159. package/dist/core/provider-retry.d.ts +1 -1
  160. package/dist/core/provider-retry.d.ts.map +1 -1
  161. package/dist/core/provider-retry.js +1 -1
  162. package/dist/core/provider-retry.js.map +1 -1
  163. package/dist/core/provider-usage.d.ts.map +1 -1
  164. package/dist/core/provider-usage.js +68 -2
  165. package/dist/core/provider-usage.js.map +1 -1
  166. package/dist/core/reasoning-router-promotion.d.ts +124 -0
  167. package/dist/core/reasoning-router-promotion.d.ts.map +1 -0
  168. package/dist/core/reasoning-router-promotion.js +121 -0
  169. package/dist/core/reasoning-router-promotion.js.map +1 -0
  170. package/dist/core/reasoning-router-replay-stability.d.ts +65 -0
  171. package/dist/core/reasoning-router-replay-stability.d.ts.map +1 -0
  172. package/dist/core/reasoning-router-replay-stability.js +67 -0
  173. package/dist/core/reasoning-router-replay-stability.js.map +1 -0
  174. package/dist/core/reasoning-router-v4-weights.d.ts +6 -6
  175. package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -1
  176. package/dist/core/reasoning-router-v4-weights.js +3 -3
  177. package/dist/core/reasoning-router-v4-weights.js.map +1 -1
  178. package/dist/core/reasoning-router-v4.d.ts.map +1 -1
  179. package/dist/core/reasoning-router-v4.js +6 -6
  180. package/dist/core/reasoning-router-v4.js.map +1 -1
  181. package/dist/core/redaction.d.ts.map +1 -1
  182. package/dist/core/redaction.js +1 -1
  183. package/dist/core/redaction.js.map +1 -1
  184. package/dist/core/resource-admission.d.ts +2 -1
  185. package/dist/core/resource-admission.d.ts.map +1 -1
  186. package/dist/core/resource-admission.js +11 -0
  187. package/dist/core/resource-admission.js.map +1 -1
  188. package/dist/core/resource-governor-format.d.ts +1 -1
  189. package/dist/core/resource-governor-format.d.ts.map +1 -1
  190. package/dist/core/resource-governor-format.js.map +1 -1
  191. package/dist/core/resource-governor-settings.d.ts +6 -12
  192. package/dist/core/resource-governor-settings.d.ts.map +1 -1
  193. package/dist/core/resource-governor-settings.js.map +1 -1
  194. package/dist/core/resource-governor-types.d.ts +24 -0
  195. package/dist/core/resource-governor-types.d.ts.map +1 -0
  196. package/dist/core/resource-governor-types.js +15 -0
  197. package/dist/core/resource-governor-types.js.map +1 -0
  198. package/dist/core/resource-loader.d.ts +1 -7
  199. package/dist/core/resource-loader.d.ts.map +1 -1
  200. package/dist/core/resource-loader.js.map +1 -1
  201. package/dist/core/resource-observation-journal.d.ts +2 -1
  202. package/dist/core/resource-observation-journal.d.ts.map +1 -1
  203. package/dist/core/resource-observation-journal.js +4 -3
  204. package/dist/core/resource-observation-journal.js.map +1 -1
  205. package/dist/core/resource-observation-report-reader.d.ts +7 -0
  206. package/dist/core/resource-observation-report-reader.d.ts.map +1 -0
  207. package/dist/core/resource-observation-report-reader.js +157 -0
  208. package/dist/core/resource-observation-report-reader.js.map +1 -0
  209. package/dist/core/resource-observation-report.d.ts +24 -0
  210. package/dist/core/resource-observation-report.d.ts.map +1 -0
  211. package/dist/core/resource-observation-report.js +101 -0
  212. package/dist/core/resource-observation-report.js.map +1 -0
  213. package/dist/core/router-feedback-collector.d.ts +1 -1
  214. package/dist/core/router-feedback-collector.d.ts.map +1 -1
  215. package/dist/core/router-feedback-collector.js.map +1 -1
  216. package/dist/core/session-bash-service.d.ts.map +1 -1
  217. package/dist/core/session-bash-service.js +4 -3
  218. package/dist/core/session-bash-service.js.map +1 -1
  219. package/dist/core/session-failure-cause.d.ts +33 -0
  220. package/dist/core/session-failure-cause.d.ts.map +1 -0
  221. package/dist/core/session-failure-cause.js +141 -0
  222. package/dist/core/session-failure-cause.js.map +1 -0
  223. package/dist/core/session-system-prompt.d.ts +1 -1
  224. package/dist/core/session-system-prompt.d.ts.map +1 -1
  225. package/dist/core/session-system-prompt.js.map +1 -1
  226. package/dist/core/session-termination.d.ts +1 -1
  227. package/dist/core/session-termination.d.ts.map +1 -1
  228. package/dist/core/session-termination.js +11 -2
  229. package/dist/core/session-termination.js.map +1 -1
  230. package/dist/core/settings-manager.d.ts +1 -1
  231. package/dist/core/settings-manager.d.ts.map +1 -1
  232. package/dist/core/settings-manager.js.map +1 -1
  233. package/dist/core/system-prompt.d.ts +1 -1
  234. package/dist/core/system-prompt.d.ts.map +1 -1
  235. package/dist/core/system-prompt.js.map +1 -1
  236. package/dist/core/tools/bash.d.ts.map +1 -1
  237. package/dist/core/tools/bash.js +10 -14
  238. package/dist/core/tools/bash.js.map +1 -1
  239. package/dist/core/tools/grep.d.ts.map +1 -1
  240. package/dist/core/tools/grep.js +13 -48
  241. package/dist/core/tools/grep.js.map +1 -1
  242. package/dist/core/tools/read.d.ts.map +1 -1
  243. package/dist/core/tools/read.js +1 -34
  244. package/dist/core/tools/read.js.map +1 -1
  245. package/dist/core/workload-classifier.d.ts +2 -10
  246. package/dist/core/workload-classifier.d.ts.map +1 -1
  247. package/dist/core/workload-classifier.js +17 -0
  248. package/dist/core/workload-classifier.js.map +1 -1
  249. package/dist/core/workload-family-matcher.d.ts +1 -1
  250. package/dist/core/workload-family-matcher.d.ts.map +1 -1
  251. package/dist/core/workload-family-matcher.js.map +1 -1
  252. package/dist/core/workload-shard-execution-types.d.ts +40 -0
  253. package/dist/core/workload-shard-execution-types.d.ts.map +1 -0
  254. package/dist/core/workload-shard-execution-types.js +14 -0
  255. package/dist/core/workload-shard-execution-types.js.map +1 -0
  256. package/dist/core/workload-shard-executor.d.ts +3 -27
  257. package/dist/core/workload-shard-executor.d.ts.map +1 -1
  258. package/dist/core/workload-shard-executor.js.map +1 -1
  259. package/dist/core/workload-shard-runner.d.ts +1 -1
  260. package/dist/core/workload-shard-runner.d.ts.map +1 -1
  261. package/dist/core/workload-shard-runner.js.map +1 -1
  262. package/dist/core/workload-types.d.ts +24 -0
  263. package/dist/core/workload-types.d.ts.map +1 -0
  264. package/dist/core/workload-types.js +15 -0
  265. package/dist/core/workload-types.js.map +1 -0
  266. package/dist/index.d.ts +1 -1
  267. package/dist/index.d.ts.map +1 -1
  268. package/dist/index.js +1 -1
  269. package/dist/index.js.map +1 -1
  270. package/dist/main.d.ts +2 -0
  271. package/dist/main.d.ts.map +1 -1
  272. package/dist/main.js +27 -47
  273. package/dist/main.js.map +1 -1
  274. package/dist/modes/interactive/components/attachment-strip.d.ts +21 -0
  275. package/dist/modes/interactive/components/attachment-strip.d.ts.map +1 -0
  276. package/dist/modes/interactive/components/attachment-strip.js +136 -0
  277. package/dist/modes/interactive/components/attachment-strip.js.map +1 -0
  278. package/dist/modes/interactive/components/footer.d.ts +6 -0
  279. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  280. package/dist/modes/interactive/components/footer.js +26 -1
  281. package/dist/modes/interactive/components/footer.js.map +1 -1
  282. package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
  283. package/dist/modes/interactive/components/status-sidebar.js +12 -1
  284. package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
  285. package/dist/modes/interactive/interactive-mode.d.ts +23 -4
  286. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  287. package/dist/modes/interactive/interactive-mode.js +174 -53
  288. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  289. package/dist/modes/interactive/theme/omk-control-panel.json +1 -1
  290. package/dist/utils/clipboard-image.d.ts.map +1 -1
  291. package/dist/utils/clipboard-image.js +42 -33
  292. package/dist/utils/clipboard-image.js.map +1 -1
  293. package/dist/utils/clipboard.d.ts.map +1 -1
  294. package/dist/utils/clipboard.js +14 -2
  295. package/dist/utils/clipboard.js.map +1 -1
  296. package/docs/adaptorch-preview-spec.md +20 -18
  297. package/docs/adaptorch-preview.md +9 -9
  298. package/docs/compaction.md +25 -4
  299. package/docs/correctness-wall.md +10 -10
  300. package/docs/docs.json +12 -0
  301. package/docs/environment-variables.md +9 -2
  302. package/docs/extensions.md +12 -3
  303. package/docs/index.md +7 -1
  304. package/docs/metrics.md +74 -2
  305. package/docs/provider-resilience.md +11 -0
  306. package/docs/runtime-algorithms.md +359 -0
  307. package/docs/sdk.md +2 -2
  308. package/docs/settings.md +11 -10
  309. package/docs/usage.md +19 -3
  310. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  311. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  312. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  313. package/examples/extensions/gondolin/package-lock.json +2 -2
  314. package/examples/extensions/gondolin/package.json +1 -1
  315. package/examples/extensions/sandbox/package-lock.json +2 -2
  316. package/examples/extensions/sandbox/package.json +1 -1
  317. package/examples/extensions/with-deps/package-lock.json +2 -2
  318. package/examples/extensions/with-deps/package.json +1 -1
  319. package/npm-shrinkwrap.json +18 -18
  320. package/package.json +7 -7
  321. package/dist/core/context-budget-governor.d.ts +0 -67
  322. package/dist/core/context-budget-governor.d.ts.map +0 -1
  323. package/dist/core/context-budget-governor.js +0 -224
  324. package/dist/core/context-budget-governor.js.map +0 -1
  325. package/dist/core/lean-ctx.d.ts +0 -99
  326. package/dist/core/lean-ctx.d.ts.map +0 -1
  327. package/dist/core/lean-ctx.js +0 -225
  328. package/dist/core/lean-ctx.js.map +0 -1
  329. package/dist/core/read-anchors.d.ts +0 -110
  330. package/dist/core/read-anchors.d.ts.map +0 -1
  331. package/dist/core/read-anchors.js +0 -193
  332. package/dist/core/read-anchors.js.map +0 -1
  333. package/dist/core/recovery-checkpoint.d.ts +0 -196
  334. package/dist/core/recovery-checkpoint.d.ts.map +0 -1
  335. package/dist/core/recovery-checkpoint.js +0 -382
  336. package/dist/core/recovery-checkpoint.js.map +0 -1
  337. package/dist/core/sandbox-policy-v2.d.ts +0 -49
  338. package/dist/core/sandbox-policy-v2.d.ts.map +0 -1
  339. package/dist/core/sandbox-policy-v2.js +0 -368
  340. package/dist/core/sandbox-policy-v2.js.map +0 -1
  341. package/dist/core/tools/omp-pure-seams.d.ts +0 -41
  342. package/dist/core/tools/omp-pure-seams.d.ts.map +0 -1
  343. package/dist/core/tools/omp-pure-seams.js +0 -142
  344. package/dist/core/tools/omp-pure-seams.js.map +0 -1
  345. package/dist/core/tools/omp-seam-runtime.d.ts +0 -100
  346. package/dist/core/tools/omp-seam-runtime.d.ts.map +0 -1
  347. package/dist/core/tools/omp-seam-runtime.js +0 -40
  348. package/dist/core/tools/omp-seam-runtime.js.map +0 -1
  349. package/dist/guardrails/audit-logger.d.ts +0 -4
  350. package/dist/guardrails/audit-logger.d.ts.map +0 -1
  351. package/dist/guardrails/audit-logger.js +0 -28
  352. package/dist/guardrails/audit-logger.js.map +0 -1
  353. package/dist/guardrails/freedomd-adapter.d.ts +0 -16
  354. package/dist/guardrails/freedomd-adapter.d.ts.map +0 -1
  355. package/dist/guardrails/freedomd-adapter.js +0 -87
  356. package/dist/guardrails/freedomd-adapter.js.map +0 -1
  357. package/dist/orchestration/lane-grant-auditor.d.ts +0 -4
  358. package/dist/orchestration/lane-grant-auditor.d.ts.map +0 -1
  359. package/dist/orchestration/lane-grant-auditor.js +0 -87
  360. package/dist/orchestration/lane-grant-auditor.js.map +0 -1
  361. package/dist/types/guardrails.d.ts +0 -61
  362. package/dist/types/guardrails.d.ts.map +0 -1
  363. package/dist/types/guardrails.js +0 -9
  364. package/dist/types/guardrails.js.map +0 -1
  365. package/dist/types/lane-grant.d.ts +0 -58
  366. package/dist/types/lane-grant.d.ts.map +0 -1
  367. package/dist/types/lane-grant.js +0 -9
  368. package/dist/types/lane-grant.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"lean-ctx.js","sourceRoot":"","sources":["../../src/core/lean-ctx.ts"],"names":[],"mappings":"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","sourcesContent":["// OMK v6.2 — Lean CTX Engine (TypeScript)\n// =========================================\n// packages/coding-agent/src/core/lean-ctx.ts\n//\n// 매 프롬프트 입력 시 자동으로 context 분석/압축을 수행하는 엔진.\n// 순수 로컬 모드 — API 호출 0회.\n\nimport { EventEmitter } from \"events\";\n\n// ── 상수 및 설정 ────────────────────────────────────────────────────────\n\nconst DEFAULT_CONFIG = {\n\trecencyWindow: 4,\n\trelevanceThreshold: 0.3,\n\tkeyTurnReserve: 2,\n\tsummaryEvery: 3,\n\tmaxContextTokens: 2000,\n\tcompressionTarget: 0.6,\n\n\ttfidfWeight: 0.5,\n\tsemanticWeight: 0.3,\n\trecencyWeight: 0.2,\n\tkeywordBoost: 1.5,\n\n\tlruWeight: 0.4,\n\tfrequencyWeight: 0.3,\n\trecencyDecayWeight: 0.3,\n\tprotectedKeywords: [\"refusal\", \"error\", \"strategy_change\", \"phase_transition\"],\n\n\thardTokenLimit: 15000,\n\tsoftTokenLimit: 12000,\n\twarningThreshold: 0.8,\n\tcriticalThreshold: 0.95,\n\tgracefulDegradation: true,\n\topportunisticExpansion: true,\n\n\tautoCompress: true,\n\tcompressOnEveryPrompt: true,\n\tminTokensToCompress: 500,\n\tmaxTurnsBeforeCompress: 8,\n};\n\n// ── 데이터 모델 ─────────────────────────────────────────────────────────\n\nexport interface TurnEntry {\n\tindex: number;\n\trole: \"user\" | \"assistant\" | \"system\" | \"tool\";\n\tcontent: string;\n\ttimestamp: number;\n\tmetadata?: Record<string, any>;\n\ttokenCount?: number;\n\tisKeyTurn?: boolean;\n\trelevanceScore?: number;\n\taccessCount?: number;\n\tlastAccessed?: number;\n}\n\nexport interface CompressedContext {\n\tturns: TurnEntry[];\n\tsummary: string;\n\toriginalTokens: number;\n\tcompressedTokens: number;\n\tcompressionRatio: number;\n\ttechniqueUsed: string;\n\tkeyTurnsPreserved: number;\n\tevictedCount: number;\n\tmetadata?: Record<string, any>;\n}\n\nexport interface BudgetStatus {\n\tusedTokens: number;\n\ttotalTokens: number;\n\tratio: number;\n\tlevel: \"green\" | \"yellow\" | \"orange\" | \"red\" | \"exceeded\";\n\tremaining: number;\n\tcanAddAgents: boolean;\n}\n\n// ── ContextCompressor ───────────────────────────────────────────────────\n\nexport class ContextCompressor extends EventEmitter {\n\tprivate config: typeof DEFAULT_CONFIG;\n\tprivate charsPerToken = 4;\n\n\tconstructor(config?: Partial<typeof DEFAULT_CONFIG>) {\n\t\tsuper();\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\tcompress(context: TurnEntry[], currentTurn?: TurnEntry): CompressedContext {\n\t\tconst originalTokens = this.estimateTokens(context);\n\n\t\tif (originalTokens < this.config.minTokensToCompress) {\n\t\t\treturn {\n\t\t\t\tturns: context,\n\t\t\t\tsummary: \"\",\n\t\t\t\toriginalTokens,\n\t\t\t\tcompressedTokens: originalTokens,\n\t\t\t\tcompressionRatio: 1.0,\n\t\t\t\ttechniqueUsed: \"none\",\n\t\t\t\tkeyTurnsPreserved: 0,\n\t\t\t\tevictedCount: 0,\n\t\t\t};\n\t\t}\n\n\t\tconst keyTurns = this.extractKeyTurns(context);\n\t\tconst recent = this.applyRecencyWindow(context);\n\t\tconst relevant = this.filterByRelevance(context, currentTurn, keyTurns);\n\t\tconst summary = this.hierarchicalSummarize(context, recent, keyTurns, relevant);\n\t\tconst compressed = this.assembleCompressed(keyTurns, relevant, recent, summary);\n\n\t\tconst compressedTokens = this.estimateTokens(compressed);\n\t\tconst ratio = originalTokens > 0 ? compressedTokens / originalTokens : 1.0;\n\n\t\treturn {\n\t\t\tturns: compressed,\n\t\t\tsummary,\n\t\t\toriginalTokens,\n\t\t\tcompressedTokens,\n\t\t\tcompressionRatio: ratio,\n\t\t\ttechniqueUsed: \"hierarchical+selective\",\n\t\t\tkeyTurnsPreserved: keyTurns.length,\n\t\t\tevictedCount: context.length - compressed.length,\n\t\t};\n\t}\n\n\tprivate estimateTokens(turns: TurnEntry[]): number {\n\t\treturn turns.reduce((sum, t) => sum + Math.ceil(t.content.length / this.charsPerToken), 0);\n\t}\n\n\tprivate extractKeyTurns(context: TurnEntry[]): TurnEntry[] {\n\t\tconst keyTurns: TurnEntry[] = [];\n\t\tconst protectedKeywords = this.config.protectedKeywords;\n\n\t\tfor (const turn of context) {\n\t\t\tconst contentLower = turn.content.toLowerCase();\n\t\t\tlet isKey = protectedKeywords.some((kw) => contentLower.includes(kw.toLowerCase()));\n\n\t\t\tif (turn.metadata?.isKeyTurn || turn.metadata?.phaseTransition) {\n\t\t\t\tisKey = true;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tturn.role === \"assistant\" &&\n\t\t\t\t[\"refuse\", \"cannot\", \"unable\", \"sorry\", \"거부\", \"죄송\"].some((w) => contentLower.includes(w))\n\t\t\t) {\n\t\t\t\tisKey = true;\n\t\t\t}\n\n\t\t\tif (isKey) {\n\t\t\t\tturn.isKeyTurn = true;\n\t\t\t\tkeyTurns.push(turn);\n\t\t\t}\n\t\t}\n\n\t\treturn keyTurns;\n\t}\n\n\tprivate applyRecencyWindow(context: TurnEntry[]): TurnEntry[] {\n\t\tconst windowSize = this.config.recencyWindow;\n\t\tif (context.length <= windowSize) return [...context];\n\t\treturn context.slice(-windowSize);\n\t}\n\n\tprivate filterByRelevance(context: TurnEntry[], currentTurn?: TurnEntry, keyTurns?: TurnEntry[]): TurnEntry[] {\n\t\tif (!currentTurn) return [...context];\n\n\t\tconst threshold = this.config.relevanceThreshold;\n\t\tconst keyIndices = new Set(keyTurns?.map((t) => t.index) ?? []);\n\n\t\treturn context.filter((turn) => {\n\t\t\tif (keyIndices.has(turn.index)) return true;\n\t\t\tconst score = this.calculateRelevance(turn, currentTurn);\n\t\t\treturn score >= threshold;\n\t\t});\n\t}\n\n\tprivate calculateRelevance(turn: TurnEntry, currentTurn: TurnEntry): number {\n\t\tconst tfidfScore = this.tfidfSimilarity(turn.content, currentTurn.content);\n\t\tconst recencyScore = Math.exp(-(Date.now() - turn.timestamp) / 3600000);\n\t\treturn tfidfScore * this.config.tfidfWeight + recencyScore * this.config.recencyWeight;\n\t}\n\n\tprivate tfidfSimilarity(text1: string, text2: string): number {\n\t\tconst words1 = text1.toLowerCase().split(/\\s+/);\n\t\tconst words2 = text2.toLowerCase().split(/\\s+/);\n\t\tconst set1 = new Set(words1);\n\t\tconst set2 = new Set(words2);\n\t\tconst intersection = new Set([...set1].filter((x) => set2.has(x)));\n\t\treturn intersection.size / Math.sqrt(set1.size * set2.size);\n\t}\n\n\tprivate hierarchicalSummarize(\n\t\t_context: TurnEntry[],\n\t\trecent: TurnEntry[],\n\t\tkeyTurns: TurnEntry[],\n\t\trelevant: TurnEntry[],\n\t): string {\n\t\tconst allImportant = [...new Set([...keyTurns, ...relevant, ...recent])].sort((a, b) => a.index - b.index);\n\t\tconst chunks: string[] = [];\n\n\t\tfor (let i = 0; i < allImportant.length; i += this.config.summaryEvery) {\n\t\t\tconst chunk = allImportant.slice(i, i + this.config.summaryEvery);\n\t\t\tconst summary = chunk.map((t) => `[${t.role}]: ${t.content.substring(0, 100)}`).join(\" | \");\n\t\t\tchunks.push(summary);\n\t\t}\n\n\t\treturn chunks.join(\"\\n\");\n\t}\n\n\tprivate assembleCompressed(\n\t\tkeyTurns: TurnEntry[],\n\t\trelevant: TurnEntry[],\n\t\trecent: TurnEntry[],\n\t\t_summary: string,\n\t): TurnEntry[] {\n\t\tconst important = new Map<number, TurnEntry>();\n\n\t\tfor (const turn of keyTurns) important.set(turn.index, turn);\n\t\tfor (const turn of relevant) important.set(turn.index, turn);\n\t\tfor (const turn of recent) important.set(turn.index, turn);\n\n\t\treturn Array.from(important.values()).sort((a, b) => a.index - b.index);\n\t}\n}\n\n// ── TokenBudgetEnforcer ─────────────────────────────────────────────────\n\nexport class TokenBudgetEnforcer extends EventEmitter {\n\tprivate config: typeof DEFAULT_CONFIG;\n\n\tconstructor(config?: Partial<typeof DEFAULT_CONFIG>) {\n\t\tsuper();\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\tcheck(usedTokens: number): BudgetStatus {\n\t\tconst total = this.config.hardTokenLimit;\n\t\tconst ratio = usedTokens / total;\n\t\tconst remaining = total - usedTokens;\n\n\t\tlet level: BudgetStatus[\"level\"] = \"green\";\n\t\tif (ratio >= 1.0) level = \"exceeded\";\n\t\telse if (ratio >= this.config.criticalThreshold) level = \"red\";\n\t\telse if (ratio >= this.config.warningThreshold) level = \"orange\";\n\t\telse if (ratio >= 0.6) level = \"yellow\";\n\n\t\treturn {\n\t\t\tusedTokens,\n\t\t\ttotalTokens: total,\n\t\t\tratio,\n\t\t\tlevel,\n\t\t\tremaining,\n\t\t\tcanAddAgents: level === \"green\" || level === \"yellow\",\n\t\t};\n\t}\n\n\tautoCompressIfNeeded(context: TurnEntry[]): { compressed: TurnEntry[]; status: BudgetStatus } {\n\t\tconst tokens = context.reduce((sum, t) => sum + Math.ceil(t.content.length / 4), 0);\n\t\tconst status = this.check(tokens);\n\n\t\tif (status.level === \"red\" || status.level === \"exceeded\") {\n\t\t\tconst compressor = new ContextCompressor(this.config);\n\t\t\tconst result = compressor.compress(context);\n\t\t\treturn { compressed: result.turns, status: this.check(result.compressedTokens) };\n\t\t}\n\n\t\treturn { compressed: context, status };\n\t}\n}\n\n// ── LeanCTXEngine (통합 엔진) ───────────────────────────────────────────\n\nexport class LeanCTXEngine extends EventEmitter {\n\tprivate compressor: ContextCompressor;\n\tprivate budgetEnforcer: TokenBudgetEnforcer;\n\tprivate history: TurnEntry[] = [];\n\n\tconstructor(config?: Partial<typeof DEFAULT_CONFIG>) {\n\t\tsuper();\n\t\tthis.compressor = new ContextCompressor(config);\n\t\tthis.budgetEnforcer = new TokenBudgetEnforcer(config);\n\t}\n\n\tprocessPrompt(prompt: string, role: \"user\" | \"assistant\" | \"system\" = \"user\"): CompressedContext {\n\t\tconst currentTurn: TurnEntry = {\n\t\t\tindex: this.history.length,\n\t\t\trole,\n\t\t\tcontent: prompt,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\tconst result = this.compressor.compress(this.history, currentTurn);\n\t\tthis.budgetEnforcer.autoCompressIfNeeded(result.turns);\n\n\t\tthis.history.push(currentTurn);\n\t\tif (this.history.length > 100) this.history = this.history.slice(-50);\n\n\t\treturn result;\n\t}\n\n\tpreToolCall(\n\t\t_toolName: string,\n\t\t_toolArgs: Record<string, any>,\n\t\tcontext: TurnEntry[],\n\t): { compressed: TurnEntry[]; status: BudgetStatus } {\n\t\treturn this.budgetEnforcer.autoCompressIfNeeded(context);\n\t}\n\n\tgetStats(): { historyLength: number; lastCompression: number } {\n\t\treturn { historyLength: this.history.length, lastCompression: Date.now() };\n\t}\n\n\tclearHistory(): void {\n\t\tthis.history = [];\n\t}\n}\n\nexport default LeanCTXEngine;\n"]}
@@ -1,110 +0,0 @@
1
- /**
2
- * `omk.read-anchor` — pure hash-anchored read/edit primitives.
3
- *
4
- * A read anchor binds a read result to the exact bytes the model saw: the
5
- * newline-normalized SHA-256 of the whole file plus the SHA-256 of the specific
6
- * line block that was read. Edits can then carry the anchor so the core can
7
- * reject stale edits (the file changed under the model) before applying them.
8
- *
9
- * All hashing in this module is performed on newline-normalized (LF) UTF-8
10
- * content so anchors are robust to CRLF/CR differences, matching the edit
11
- * pipeline which normalizes to LF before matching.
12
- *
13
- * Side-effect free: no filesystem access; `node:crypto` only.
14
- *
15
- * Derived clean-room from the lane C plan
16
- * (`.omk/runs/omk-package-hardening-plan/recovery-readseek.md`); no legacy
17
- * package source is imported or copied.
18
- */
19
- export declare const READ_ANCHOR_SCHEMA_VERSION = "omk.read-anchor.v1";
20
- export interface ReadAnchorRange {
21
- /** 1-indexed start line (resolved). */
22
- readonly offset: number;
23
- /** Number of lines included in the block (resolved). */
24
- readonly limit: number;
25
- /** 1-indexed last line included, inclusive. Equals `offset - 1` for an empty block. */
26
- readonly endLine: number;
27
- }
28
- export interface ReadAnchor {
29
- readonly schemaVersion: typeof READ_ANCHOR_SCHEMA_VERSION;
30
- readonly readId: string;
31
- readonly path: string;
32
- /** SHA-256 (hex) of the newline-normalized full file content. */
33
- readonly fileSha256: string;
34
- readonly range: ReadAnchorRange;
35
- /** SHA-256 (hex) of the newline-normalized read block content. */
36
- readonly blockSha256: string;
37
- }
38
- export interface CreateReadAnchorInput {
39
- readonly path: string;
40
- /** Full file content as read. */
41
- readonly content: string;
42
- /** 1-indexed start line; defaults to 1 (whole file). */
43
- readonly offset?: number;
44
- /** Maximum number of lines; defaults to the rest of the file. */
45
- readonly limit?: number;
46
- /** Optional deterministic id (otherwise a UUID is generated). */
47
- readonly readId?: string;
48
- }
49
- /**
50
- * Create a hash anchor for a read. Hashes both the full (normalized) file and
51
- * the specific line block so a later edit can prove it is operating on the same
52
- * bytes the model saw.
53
- */
54
- export declare function createReadAnchor(input: CreateReadAnchorInput): ReadAnchor;
55
- export interface ReadAnchorVerification {
56
- readonly ok: boolean;
57
- readonly fileMatches: boolean;
58
- readonly blockMatches: boolean;
59
- readonly currentFileSha256: string;
60
- readonly currentBlockSha256: string;
61
- }
62
- /**
63
- * Verify an anchor against the current file content. `fileMatches` is the strict
64
- * whole-file check; `blockMatches` re-extracts the anchored range and compares
65
- * the block hash (useful for lenient relocation diagnostics).
66
- */
67
- export declare function verifyReadAnchor(anchor: ReadAnchor, currentContent: string): ReadAnchorVerification;
68
- /** Validate an untrusted value as a {@link ReadAnchor}. Pure and fail-closed. */
69
- export declare function validateReadAnchor(value: unknown): {
70
- ok: boolean;
71
- errors: readonly string[];
72
- anchor?: ReadAnchor;
73
- };
74
- export type AnchoredEditMode = "strict" | "lenient";
75
- export type AnchoredEditVerdict = "allow" | "allow-lenient" | "reject-stale";
76
- export interface AnchoredEditDecisionInput {
77
- readonly anchor: ReadAnchor;
78
- /** Current full file content at edit time. */
79
- readonly currentContent: string;
80
- /** Defaults to `strict`. */
81
- readonly mode?: AnchoredEditMode;
82
- /**
83
- * The exact anchored block text (typically the edit's `oldText`). Required
84
- * for lenient relocation when the file has changed; must hash to
85
- * `anchor.blockSha256` to be trusted.
86
- */
87
- readonly anchoredBlockText?: string;
88
- }
89
- export interface AnchoredEditDecision {
90
- readonly verdict: AnchoredEditVerdict;
91
- readonly reason: string;
92
- readonly currentFileSha256: string;
93
- readonly fileChanged: boolean;
94
- /** Lenient mode only: whether the anchored block was uniquely relocated. */
95
- readonly blockRelocated?: boolean;
96
- /** Lenient mode only: occurrences of the anchored block in current content. */
97
- readonly occurrences?: number;
98
- }
99
- /**
100
- * Decide whether an anchored edit may proceed.
101
- *
102
- * - Unchanged file -> `allow` (the common fast path).
103
- * - Changed file, strict mode -> `reject-stale` (the default; forces a re-read).
104
- * - Changed file, lenient mode -> `allow-lenient` only when the anchored block
105
- * text is supplied, hashes to the recorded `blockSha256`, and still occurs
106
- * exactly once in the current content (unique, non-overlapping relocation).
107
- * Zero or multiple occurrences -> `reject-stale`.
108
- */
109
- export declare function decideAnchoredEdit(input: AnchoredEditDecisionInput): AnchoredEditDecision;
110
- //# sourceMappingURL=read-anchors.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"read-anchors.d.ts","sourceRoot":"","sources":["../../src/core/read-anchors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `omk.read-anchor` — pure hash-anchored read/edit primitives.\n *\n * A read anchor binds a read result to the exact bytes the model saw: the\n * newline-normalized SHA-256 of the whole file plus the SHA-256 of the specific\n * line block that was read. Edits can then carry the anchor so the core can\n * reject stale edits (the file changed under the model) before applying them.\n *\n * All hashing in this module is performed on newline-normalized (LF) UTF-8\n * content so anchors are robust to CRLF/CR differences, matching the edit\n * pipeline which normalizes to LF before matching.\n *\n * Side-effect free: no filesystem access; `node:crypto` only.\n *\n * Derived clean-room from the lane C plan\n * (`.omk/runs/omk-package-hardening-plan/recovery-readseek.md`); no legacy\n * package source is imported or copied.\n */\n\nimport { createHash, randomUUID } from \"node:crypto\";\n\nexport const READ_ANCHOR_SCHEMA_VERSION = \"omk.read-anchor.v1\";\n\nexport interface ReadAnchorRange {\n\t/** 1-indexed start line (resolved). */\n\treadonly offset: number;\n\t/** Number of lines included in the block (resolved). */\n\treadonly limit: number;\n\t/** 1-indexed last line included, inclusive. Equals `offset - 1` for an empty block. */\n\treadonly endLine: number;\n}\n\nexport interface ReadAnchor {\n\treadonly schemaVersion: typeof READ_ANCHOR_SCHEMA_VERSION;\n\treadonly readId: string;\n\treadonly path: string;\n\t/** SHA-256 (hex) of the newline-normalized full file content. */\n\treadonly fileSha256: string;\n\treadonly range: ReadAnchorRange;\n\t/** SHA-256 (hex) of the newline-normalized read block content. */\n\treadonly blockSha256: string;\n}\n\nconst HEX64 = /^[0-9a-f]{64}$/i;\n\nfunction normalizeNewlines(content: string): string {\n\treturn content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n}\n\nfunction sha256Hex(content: string): string {\n\treturn createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n}\n\ninterface ExtractedBlock {\n\treadonly text: string;\n\treadonly range: ReadAnchorRange;\n}\n\n/**\n * Extract a line block from already newline-normalized content using read-tool\n * semantics: `offset` is a 1-indexed start line, `limit` is the maximum number\n * of lines. Out-of-range requests clamp to an empty block at the end of file.\n */\nfunction extractBlock(normalized: string, offset: number | undefined, limit: number | undefined): ExtractedBlock {\n\tconst lines = normalized.split(\"\\n\");\n\tconst start = Math.max(1, Math.floor(offset ?? 1));\n\tif (start > lines.length) {\n\t\treturn { text: \"\", range: { offset: start, limit: 0, endLine: start - 1 } };\n\t}\n\tconst available = lines.length - (start - 1);\n\tconst count = limit === undefined ? available : Math.max(0, Math.min(Math.floor(limit), available));\n\tconst blockLines = lines.slice(start - 1, start - 1 + count);\n\treturn {\n\t\ttext: blockLines.join(\"\\n\"),\n\t\trange: { offset: start, limit: count, endLine: start - 1 + count },\n\t};\n}\n\nexport interface CreateReadAnchorInput {\n\treadonly path: string;\n\t/** Full file content as read. */\n\treadonly content: string;\n\t/** 1-indexed start line; defaults to 1 (whole file). */\n\treadonly offset?: number;\n\t/** Maximum number of lines; defaults to the rest of the file. */\n\treadonly limit?: number;\n\t/** Optional deterministic id (otherwise a UUID is generated). */\n\treadonly readId?: string;\n}\n\n/**\n * Create a hash anchor for a read. Hashes both the full (normalized) file and\n * the specific line block so a later edit can prove it is operating on the same\n * bytes the model saw.\n */\nexport function createReadAnchor(input: CreateReadAnchorInput): ReadAnchor {\n\tconst normalized = normalizeNewlines(input.content);\n\tconst block = extractBlock(normalized, input.offset, input.limit);\n\treturn {\n\t\tschemaVersion: READ_ANCHOR_SCHEMA_VERSION,\n\t\treadId: input.readId ?? randomUUID(),\n\t\tpath: input.path,\n\t\tfileSha256: sha256Hex(normalized),\n\t\trange: block.range,\n\t\tblockSha256: sha256Hex(block.text),\n\t};\n}\n\nexport interface ReadAnchorVerification {\n\treadonly ok: boolean;\n\treadonly fileMatches: boolean;\n\treadonly blockMatches: boolean;\n\treadonly currentFileSha256: string;\n\treadonly currentBlockSha256: string;\n}\n\n/**\n * Verify an anchor against the current file content. `fileMatches` is the strict\n * whole-file check; `blockMatches` re-extracts the anchored range and compares\n * the block hash (useful for lenient relocation diagnostics).\n */\nexport function verifyReadAnchor(anchor: ReadAnchor, currentContent: string): ReadAnchorVerification {\n\tconst normalized = normalizeNewlines(currentContent);\n\tconst currentFileSha256 = sha256Hex(normalized);\n\tconst block = extractBlock(normalized, anchor.range.offset, anchor.range.limit);\n\tconst currentBlockSha256 = sha256Hex(block.text);\n\tconst fileMatches = currentFileSha256 === anchor.fileSha256;\n\tconst blockMatches = currentBlockSha256 === anchor.blockSha256;\n\treturn {\n\t\tok: fileMatches && blockMatches,\n\t\tfileMatches,\n\t\tblockMatches,\n\t\tcurrentFileSha256,\n\t\tcurrentBlockSha256,\n\t};\n}\n\n/** Validate an untrusted value as a {@link ReadAnchor}. Pure and fail-closed. */\nexport function validateReadAnchor(value: unknown): { ok: boolean; errors: readonly string[]; anchor?: ReadAnchor } {\n\tconst errors: string[] = [];\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\treturn { ok: false, errors: [\"anchor must be an object\"] };\n\t}\n\tconst anchor = value as Record<string, unknown>;\n\tif (anchor.schemaVersion !== READ_ANCHOR_SCHEMA_VERSION) {\n\t\terrors.push(`schemaVersion must be ${READ_ANCHOR_SCHEMA_VERSION}`);\n\t}\n\tif (typeof anchor.readId !== \"string\" || anchor.readId.length === 0)\n\t\terrors.push(\"readId must be a non-empty string\");\n\tif (typeof anchor.path !== \"string\" || anchor.path.length === 0) errors.push(\"path must be a non-empty string\");\n\tif (typeof anchor.fileSha256 !== \"string\" || !HEX64.test(anchor.fileSha256)) {\n\t\terrors.push(\"fileSha256 must be a 64-char hex digest\");\n\t}\n\tif (typeof anchor.blockSha256 !== \"string\" || !HEX64.test(anchor.blockSha256)) {\n\t\terrors.push(\"blockSha256 must be a 64-char hex digest\");\n\t}\n\tconst range = anchor.range;\n\tif (typeof range !== \"object\" || range === null || Array.isArray(range)) {\n\t\terrors.push(\"range must be an object\");\n\t} else {\n\t\tconst r = range as Record<string, unknown>;\n\t\tfor (const key of [\"offset\", \"limit\", \"endLine\"]) {\n\t\t\tif (typeof r[key] !== \"number\" || !Number.isInteger(r[key]) || (r[key] as number) < 0) {\n\t\t\t\terrors.push(`range.${key} must be a non-negative integer`);\n\t\t\t}\n\t\t}\n\t}\n\tif (errors.length > 0) return { ok: false, errors };\n\treturn { ok: true, errors: [], anchor: value as unknown as ReadAnchor };\n}\n\nexport type AnchoredEditMode = \"strict\" | \"lenient\";\n\nexport type AnchoredEditVerdict = \"allow\" | \"allow-lenient\" | \"reject-stale\";\n\nexport interface AnchoredEditDecisionInput {\n\treadonly anchor: ReadAnchor;\n\t/** Current full file content at edit time. */\n\treadonly currentContent: string;\n\t/** Defaults to `strict`. */\n\treadonly mode?: AnchoredEditMode;\n\t/**\n\t * The exact anchored block text (typically the edit's `oldText`). Required\n\t * for lenient relocation when the file has changed; must hash to\n\t * `anchor.blockSha256` to be trusted.\n\t */\n\treadonly anchoredBlockText?: string;\n}\n\nexport interface AnchoredEditDecision {\n\treadonly verdict: AnchoredEditVerdict;\n\treadonly reason: string;\n\treadonly currentFileSha256: string;\n\treadonly fileChanged: boolean;\n\t/** Lenient mode only: whether the anchored block was uniquely relocated. */\n\treadonly blockRelocated?: boolean;\n\t/** Lenient mode only: occurrences of the anchored block in current content. */\n\treadonly occurrences?: number;\n}\n\nfunction countOccurrences(haystack: string, needle: string): number {\n\tif (needle.length === 0) return 0;\n\tlet count = 0;\n\tlet index = haystack.indexOf(needle);\n\twhile (index !== -1) {\n\t\tcount += 1;\n\t\tindex = haystack.indexOf(needle, index + needle.length);\n\t}\n\treturn count;\n}\n\nconst STALE_REREAD_HINT = \"stale read: the file changed since it was read; re-read the file before editing\";\n\n/**\n * Decide whether an anchored edit may proceed.\n *\n * - Unchanged file -> `allow` (the common fast path).\n * - Changed file, strict mode -> `reject-stale` (the default; forces a re-read).\n * - Changed file, lenient mode -> `allow-lenient` only when the anchored block\n * text is supplied, hashes to the recorded `blockSha256`, and still occurs\n * exactly once in the current content (unique, non-overlapping relocation).\n * Zero or multiple occurrences -> `reject-stale`.\n */\nexport function decideAnchoredEdit(input: AnchoredEditDecisionInput): AnchoredEditDecision {\n\tconst mode = input.mode ?? \"strict\";\n\tconst normalized = normalizeNewlines(input.currentContent);\n\tconst currentFileSha256 = sha256Hex(normalized);\n\tconst fileChanged = currentFileSha256 !== input.anchor.fileSha256;\n\n\tif (!fileChanged) {\n\t\treturn { verdict: \"allow\", reason: \"current file hash matches the anchor\", currentFileSha256, fileChanged };\n\t}\n\n\tif (mode === \"strict\") {\n\t\treturn { verdict: \"reject-stale\", reason: STALE_REREAD_HINT, currentFileSha256, fileChanged };\n\t}\n\n\tconst blockText = input.anchoredBlockText;\n\tif (blockText === undefined || blockText.length === 0) {\n\t\treturn {\n\t\t\tverdict: \"reject-stale\",\n\t\t\treason: \"lenient relocation requires the anchored block text\",\n\t\t\tcurrentFileSha256,\n\t\t\tfileChanged,\n\t\t};\n\t}\n\n\tconst normalizedBlock = normalizeNewlines(blockText);\n\tif (sha256Hex(normalizedBlock) !== input.anchor.blockSha256) {\n\t\treturn {\n\t\t\tverdict: \"reject-stale\",\n\t\t\treason: \"supplied block text does not match the anchored block hash\",\n\t\t\tcurrentFileSha256,\n\t\t\tfileChanged,\n\t\t};\n\t}\n\n\tconst occurrences = countOccurrences(normalized, normalizedBlock);\n\tif (occurrences === 1) {\n\t\treturn {\n\t\t\tverdict: \"allow-lenient\",\n\t\t\treason: \"file changed but the anchored block is still uniquely locatable\",\n\t\t\tcurrentFileSha256,\n\t\t\tfileChanged,\n\t\t\tblockRelocated: true,\n\t\t\toccurrences,\n\t\t};\n\t}\n\treturn {\n\t\tverdict: \"reject-stale\",\n\t\treason:\n\t\t\toccurrences === 0\n\t\t\t\t? \"anchored block no longer exists in the current file\"\n\t\t\t\t: \"anchored block is ambiguous (multiple matches) in the current file\",\n\t\tcurrentFileSha256,\n\t\tfileChanged,\n\t\tblockRelocated: false,\n\t\toccurrences,\n\t};\n}\n"]}
@@ -1,193 +0,0 @@
1
- /**
2
- * `omk.read-anchor` — pure hash-anchored read/edit primitives.
3
- *
4
- * A read anchor binds a read result to the exact bytes the model saw: the
5
- * newline-normalized SHA-256 of the whole file plus the SHA-256 of the specific
6
- * line block that was read. Edits can then carry the anchor so the core can
7
- * reject stale edits (the file changed under the model) before applying them.
8
- *
9
- * All hashing in this module is performed on newline-normalized (LF) UTF-8
10
- * content so anchors are robust to CRLF/CR differences, matching the edit
11
- * pipeline which normalizes to LF before matching.
12
- *
13
- * Side-effect free: no filesystem access; `node:crypto` only.
14
- *
15
- * Derived clean-room from the lane C plan
16
- * (`.omk/runs/omk-package-hardening-plan/recovery-readseek.md`); no legacy
17
- * package source is imported or copied.
18
- */
19
- import { createHash, randomUUID } from "node:crypto";
20
- export const READ_ANCHOR_SCHEMA_VERSION = "omk.read-anchor.v1";
21
- const HEX64 = /^[0-9a-f]{64}$/i;
22
- function normalizeNewlines(content) {
23
- return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
24
- }
25
- function sha256Hex(content) {
26
- return createHash("sha256").update(content, "utf8").digest("hex");
27
- }
28
- /**
29
- * Extract a line block from already newline-normalized content using read-tool
30
- * semantics: `offset` is a 1-indexed start line, `limit` is the maximum number
31
- * of lines. Out-of-range requests clamp to an empty block at the end of file.
32
- */
33
- function extractBlock(normalized, offset, limit) {
34
- const lines = normalized.split("\n");
35
- const start = Math.max(1, Math.floor(offset ?? 1));
36
- if (start > lines.length) {
37
- return { text: "", range: { offset: start, limit: 0, endLine: start - 1 } };
38
- }
39
- const available = lines.length - (start - 1);
40
- const count = limit === undefined ? available : Math.max(0, Math.min(Math.floor(limit), available));
41
- const blockLines = lines.slice(start - 1, start - 1 + count);
42
- return {
43
- text: blockLines.join("\n"),
44
- range: { offset: start, limit: count, endLine: start - 1 + count },
45
- };
46
- }
47
- /**
48
- * Create a hash anchor for a read. Hashes both the full (normalized) file and
49
- * the specific line block so a later edit can prove it is operating on the same
50
- * bytes the model saw.
51
- */
52
- export function createReadAnchor(input) {
53
- const normalized = normalizeNewlines(input.content);
54
- const block = extractBlock(normalized, input.offset, input.limit);
55
- return {
56
- schemaVersion: READ_ANCHOR_SCHEMA_VERSION,
57
- readId: input.readId ?? randomUUID(),
58
- path: input.path,
59
- fileSha256: sha256Hex(normalized),
60
- range: block.range,
61
- blockSha256: sha256Hex(block.text),
62
- };
63
- }
64
- /**
65
- * Verify an anchor against the current file content. `fileMatches` is the strict
66
- * whole-file check; `blockMatches` re-extracts the anchored range and compares
67
- * the block hash (useful for lenient relocation diagnostics).
68
- */
69
- export function verifyReadAnchor(anchor, currentContent) {
70
- const normalized = normalizeNewlines(currentContent);
71
- const currentFileSha256 = sha256Hex(normalized);
72
- const block = extractBlock(normalized, anchor.range.offset, anchor.range.limit);
73
- const currentBlockSha256 = sha256Hex(block.text);
74
- const fileMatches = currentFileSha256 === anchor.fileSha256;
75
- const blockMatches = currentBlockSha256 === anchor.blockSha256;
76
- return {
77
- ok: fileMatches && blockMatches,
78
- fileMatches,
79
- blockMatches,
80
- currentFileSha256,
81
- currentBlockSha256,
82
- };
83
- }
84
- /** Validate an untrusted value as a {@link ReadAnchor}. Pure and fail-closed. */
85
- export function validateReadAnchor(value) {
86
- const errors = [];
87
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
88
- return { ok: false, errors: ["anchor must be an object"] };
89
- }
90
- const anchor = value;
91
- if (anchor.schemaVersion !== READ_ANCHOR_SCHEMA_VERSION) {
92
- errors.push(`schemaVersion must be ${READ_ANCHOR_SCHEMA_VERSION}`);
93
- }
94
- if (typeof anchor.readId !== "string" || anchor.readId.length === 0)
95
- errors.push("readId must be a non-empty string");
96
- if (typeof anchor.path !== "string" || anchor.path.length === 0)
97
- errors.push("path must be a non-empty string");
98
- if (typeof anchor.fileSha256 !== "string" || !HEX64.test(anchor.fileSha256)) {
99
- errors.push("fileSha256 must be a 64-char hex digest");
100
- }
101
- if (typeof anchor.blockSha256 !== "string" || !HEX64.test(anchor.blockSha256)) {
102
- errors.push("blockSha256 must be a 64-char hex digest");
103
- }
104
- const range = anchor.range;
105
- if (typeof range !== "object" || range === null || Array.isArray(range)) {
106
- errors.push("range must be an object");
107
- }
108
- else {
109
- const r = range;
110
- for (const key of ["offset", "limit", "endLine"]) {
111
- if (typeof r[key] !== "number" || !Number.isInteger(r[key]) || r[key] < 0) {
112
- errors.push(`range.${key} must be a non-negative integer`);
113
- }
114
- }
115
- }
116
- if (errors.length > 0)
117
- return { ok: false, errors };
118
- return { ok: true, errors: [], anchor: value };
119
- }
120
- function countOccurrences(haystack, needle) {
121
- if (needle.length === 0)
122
- return 0;
123
- let count = 0;
124
- let index = haystack.indexOf(needle);
125
- while (index !== -1) {
126
- count += 1;
127
- index = haystack.indexOf(needle, index + needle.length);
128
- }
129
- return count;
130
- }
131
- const STALE_REREAD_HINT = "stale read: the file changed since it was read; re-read the file before editing";
132
- /**
133
- * Decide whether an anchored edit may proceed.
134
- *
135
- * - Unchanged file -> `allow` (the common fast path).
136
- * - Changed file, strict mode -> `reject-stale` (the default; forces a re-read).
137
- * - Changed file, lenient mode -> `allow-lenient` only when the anchored block
138
- * text is supplied, hashes to the recorded `blockSha256`, and still occurs
139
- * exactly once in the current content (unique, non-overlapping relocation).
140
- * Zero or multiple occurrences -> `reject-stale`.
141
- */
142
- export function decideAnchoredEdit(input) {
143
- const mode = input.mode ?? "strict";
144
- const normalized = normalizeNewlines(input.currentContent);
145
- const currentFileSha256 = sha256Hex(normalized);
146
- const fileChanged = currentFileSha256 !== input.anchor.fileSha256;
147
- if (!fileChanged) {
148
- return { verdict: "allow", reason: "current file hash matches the anchor", currentFileSha256, fileChanged };
149
- }
150
- if (mode === "strict") {
151
- return { verdict: "reject-stale", reason: STALE_REREAD_HINT, currentFileSha256, fileChanged };
152
- }
153
- const blockText = input.anchoredBlockText;
154
- if (blockText === undefined || blockText.length === 0) {
155
- return {
156
- verdict: "reject-stale",
157
- reason: "lenient relocation requires the anchored block text",
158
- currentFileSha256,
159
- fileChanged,
160
- };
161
- }
162
- const normalizedBlock = normalizeNewlines(blockText);
163
- if (sha256Hex(normalizedBlock) !== input.anchor.blockSha256) {
164
- return {
165
- verdict: "reject-stale",
166
- reason: "supplied block text does not match the anchored block hash",
167
- currentFileSha256,
168
- fileChanged,
169
- };
170
- }
171
- const occurrences = countOccurrences(normalized, normalizedBlock);
172
- if (occurrences === 1) {
173
- return {
174
- verdict: "allow-lenient",
175
- reason: "file changed but the anchored block is still uniquely locatable",
176
- currentFileSha256,
177
- fileChanged,
178
- blockRelocated: true,
179
- occurrences,
180
- };
181
- }
182
- return {
183
- verdict: "reject-stale",
184
- reason: occurrences === 0
185
- ? "anchored block no longer exists in the current file"
186
- : "anchored block is ambiguous (multiple matches) in the current file",
187
- currentFileSha256,
188
- fileChanged,
189
- blockRelocated: false,
190
- occurrences,
191
- };
192
- }
193
- //# sourceMappingURL=read-anchors.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"read-anchors.js","sourceRoot":"","sources":["../../src/core/read-anchors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `omk.read-anchor` — pure hash-anchored read/edit primitives.\n *\n * A read anchor binds a read result to the exact bytes the model saw: the\n * newline-normalized SHA-256 of the whole file plus the SHA-256 of the specific\n * line block that was read. Edits can then carry the anchor so the core can\n * reject stale edits (the file changed under the model) before applying them.\n *\n * All hashing in this module is performed on newline-normalized (LF) UTF-8\n * content so anchors are robust to CRLF/CR differences, matching the edit\n * pipeline which normalizes to LF before matching.\n *\n * Side-effect free: no filesystem access; `node:crypto` only.\n *\n * Derived clean-room from the lane C plan\n * (`.omk/runs/omk-package-hardening-plan/recovery-readseek.md`); no legacy\n * package source is imported or copied.\n */\n\nimport { createHash, randomUUID } from \"node:crypto\";\n\nexport const READ_ANCHOR_SCHEMA_VERSION = \"omk.read-anchor.v1\";\n\nexport interface ReadAnchorRange {\n\t/** 1-indexed start line (resolved). */\n\treadonly offset: number;\n\t/** Number of lines included in the block (resolved). */\n\treadonly limit: number;\n\t/** 1-indexed last line included, inclusive. Equals `offset - 1` for an empty block. */\n\treadonly endLine: number;\n}\n\nexport interface ReadAnchor {\n\treadonly schemaVersion: typeof READ_ANCHOR_SCHEMA_VERSION;\n\treadonly readId: string;\n\treadonly path: string;\n\t/** SHA-256 (hex) of the newline-normalized full file content. */\n\treadonly fileSha256: string;\n\treadonly range: ReadAnchorRange;\n\t/** SHA-256 (hex) of the newline-normalized read block content. */\n\treadonly blockSha256: string;\n}\n\nconst HEX64 = /^[0-9a-f]{64}$/i;\n\nfunction normalizeNewlines(content: string): string {\n\treturn content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n}\n\nfunction sha256Hex(content: string): string {\n\treturn createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n}\n\ninterface ExtractedBlock {\n\treadonly text: string;\n\treadonly range: ReadAnchorRange;\n}\n\n/**\n * Extract a line block from already newline-normalized content using read-tool\n * semantics: `offset` is a 1-indexed start line, `limit` is the maximum number\n * of lines. Out-of-range requests clamp to an empty block at the end of file.\n */\nfunction extractBlock(normalized: string, offset: number | undefined, limit: number | undefined): ExtractedBlock {\n\tconst lines = normalized.split(\"\\n\");\n\tconst start = Math.max(1, Math.floor(offset ?? 1));\n\tif (start > lines.length) {\n\t\treturn { text: \"\", range: { offset: start, limit: 0, endLine: start - 1 } };\n\t}\n\tconst available = lines.length - (start - 1);\n\tconst count = limit === undefined ? available : Math.max(0, Math.min(Math.floor(limit), available));\n\tconst blockLines = lines.slice(start - 1, start - 1 + count);\n\treturn {\n\t\ttext: blockLines.join(\"\\n\"),\n\t\trange: { offset: start, limit: count, endLine: start - 1 + count },\n\t};\n}\n\nexport interface CreateReadAnchorInput {\n\treadonly path: string;\n\t/** Full file content as read. */\n\treadonly content: string;\n\t/** 1-indexed start line; defaults to 1 (whole file). */\n\treadonly offset?: number;\n\t/** Maximum number of lines; defaults to the rest of the file. */\n\treadonly limit?: number;\n\t/** Optional deterministic id (otherwise a UUID is generated). */\n\treadonly readId?: string;\n}\n\n/**\n * Create a hash anchor for a read. Hashes both the full (normalized) file and\n * the specific line block so a later edit can prove it is operating on the same\n * bytes the model saw.\n */\nexport function createReadAnchor(input: CreateReadAnchorInput): ReadAnchor {\n\tconst normalized = normalizeNewlines(input.content);\n\tconst block = extractBlock(normalized, input.offset, input.limit);\n\treturn {\n\t\tschemaVersion: READ_ANCHOR_SCHEMA_VERSION,\n\t\treadId: input.readId ?? randomUUID(),\n\t\tpath: input.path,\n\t\tfileSha256: sha256Hex(normalized),\n\t\trange: block.range,\n\t\tblockSha256: sha256Hex(block.text),\n\t};\n}\n\nexport interface ReadAnchorVerification {\n\treadonly ok: boolean;\n\treadonly fileMatches: boolean;\n\treadonly blockMatches: boolean;\n\treadonly currentFileSha256: string;\n\treadonly currentBlockSha256: string;\n}\n\n/**\n * Verify an anchor against the current file content. `fileMatches` is the strict\n * whole-file check; `blockMatches` re-extracts the anchored range and compares\n * the block hash (useful for lenient relocation diagnostics).\n */\nexport function verifyReadAnchor(anchor: ReadAnchor, currentContent: string): ReadAnchorVerification {\n\tconst normalized = normalizeNewlines(currentContent);\n\tconst currentFileSha256 = sha256Hex(normalized);\n\tconst block = extractBlock(normalized, anchor.range.offset, anchor.range.limit);\n\tconst currentBlockSha256 = sha256Hex(block.text);\n\tconst fileMatches = currentFileSha256 === anchor.fileSha256;\n\tconst blockMatches = currentBlockSha256 === anchor.blockSha256;\n\treturn {\n\t\tok: fileMatches && blockMatches,\n\t\tfileMatches,\n\t\tblockMatches,\n\t\tcurrentFileSha256,\n\t\tcurrentBlockSha256,\n\t};\n}\n\n/** Validate an untrusted value as a {@link ReadAnchor}. Pure and fail-closed. */\nexport function validateReadAnchor(value: unknown): { ok: boolean; errors: readonly string[]; anchor?: ReadAnchor } {\n\tconst errors: string[] = [];\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\treturn { ok: false, errors: [\"anchor must be an object\"] };\n\t}\n\tconst anchor = value as Record<string, unknown>;\n\tif (anchor.schemaVersion !== READ_ANCHOR_SCHEMA_VERSION) {\n\t\terrors.push(`schemaVersion must be ${READ_ANCHOR_SCHEMA_VERSION}`);\n\t}\n\tif (typeof anchor.readId !== \"string\" || anchor.readId.length === 0)\n\t\terrors.push(\"readId must be a non-empty string\");\n\tif (typeof anchor.path !== \"string\" || anchor.path.length === 0) errors.push(\"path must be a non-empty string\");\n\tif (typeof anchor.fileSha256 !== \"string\" || !HEX64.test(anchor.fileSha256)) {\n\t\terrors.push(\"fileSha256 must be a 64-char hex digest\");\n\t}\n\tif (typeof anchor.blockSha256 !== \"string\" || !HEX64.test(anchor.blockSha256)) {\n\t\terrors.push(\"blockSha256 must be a 64-char hex digest\");\n\t}\n\tconst range = anchor.range;\n\tif (typeof range !== \"object\" || range === null || Array.isArray(range)) {\n\t\terrors.push(\"range must be an object\");\n\t} else {\n\t\tconst r = range as Record<string, unknown>;\n\t\tfor (const key of [\"offset\", \"limit\", \"endLine\"]) {\n\t\t\tif (typeof r[key] !== \"number\" || !Number.isInteger(r[key]) || (r[key] as number) < 0) {\n\t\t\t\terrors.push(`range.${key} must be a non-negative integer`);\n\t\t\t}\n\t\t}\n\t}\n\tif (errors.length > 0) return { ok: false, errors };\n\treturn { ok: true, errors: [], anchor: value as unknown as ReadAnchor };\n}\n\nexport type AnchoredEditMode = \"strict\" | \"lenient\";\n\nexport type AnchoredEditVerdict = \"allow\" | \"allow-lenient\" | \"reject-stale\";\n\nexport interface AnchoredEditDecisionInput {\n\treadonly anchor: ReadAnchor;\n\t/** Current full file content at edit time. */\n\treadonly currentContent: string;\n\t/** Defaults to `strict`. */\n\treadonly mode?: AnchoredEditMode;\n\t/**\n\t * The exact anchored block text (typically the edit's `oldText`). Required\n\t * for lenient relocation when the file has changed; must hash to\n\t * `anchor.blockSha256` to be trusted.\n\t */\n\treadonly anchoredBlockText?: string;\n}\n\nexport interface AnchoredEditDecision {\n\treadonly verdict: AnchoredEditVerdict;\n\treadonly reason: string;\n\treadonly currentFileSha256: string;\n\treadonly fileChanged: boolean;\n\t/** Lenient mode only: whether the anchored block was uniquely relocated. */\n\treadonly blockRelocated?: boolean;\n\t/** Lenient mode only: occurrences of the anchored block in current content. */\n\treadonly occurrences?: number;\n}\n\nfunction countOccurrences(haystack: string, needle: string): number {\n\tif (needle.length === 0) return 0;\n\tlet count = 0;\n\tlet index = haystack.indexOf(needle);\n\twhile (index !== -1) {\n\t\tcount += 1;\n\t\tindex = haystack.indexOf(needle, index + needle.length);\n\t}\n\treturn count;\n}\n\nconst STALE_REREAD_HINT = \"stale read: the file changed since it was read; re-read the file before editing\";\n\n/**\n * Decide whether an anchored edit may proceed.\n *\n * - Unchanged file -> `allow` (the common fast path).\n * - Changed file, strict mode -> `reject-stale` (the default; forces a re-read).\n * - Changed file, lenient mode -> `allow-lenient` only when the anchored block\n * text is supplied, hashes to the recorded `blockSha256`, and still occurs\n * exactly once in the current content (unique, non-overlapping relocation).\n * Zero or multiple occurrences -> `reject-stale`.\n */\nexport function decideAnchoredEdit(input: AnchoredEditDecisionInput): AnchoredEditDecision {\n\tconst mode = input.mode ?? \"strict\";\n\tconst normalized = normalizeNewlines(input.currentContent);\n\tconst currentFileSha256 = sha256Hex(normalized);\n\tconst fileChanged = currentFileSha256 !== input.anchor.fileSha256;\n\n\tif (!fileChanged) {\n\t\treturn { verdict: \"allow\", reason: \"current file hash matches the anchor\", currentFileSha256, fileChanged };\n\t}\n\n\tif (mode === \"strict\") {\n\t\treturn { verdict: \"reject-stale\", reason: STALE_REREAD_HINT, currentFileSha256, fileChanged };\n\t}\n\n\tconst blockText = input.anchoredBlockText;\n\tif (blockText === undefined || blockText.length === 0) {\n\t\treturn {\n\t\t\tverdict: \"reject-stale\",\n\t\t\treason: \"lenient relocation requires the anchored block text\",\n\t\t\tcurrentFileSha256,\n\t\t\tfileChanged,\n\t\t};\n\t}\n\n\tconst normalizedBlock = normalizeNewlines(blockText);\n\tif (sha256Hex(normalizedBlock) !== input.anchor.blockSha256) {\n\t\treturn {\n\t\t\tverdict: \"reject-stale\",\n\t\t\treason: \"supplied block text does not match the anchored block hash\",\n\t\t\tcurrentFileSha256,\n\t\t\tfileChanged,\n\t\t};\n\t}\n\n\tconst occurrences = countOccurrences(normalized, normalizedBlock);\n\tif (occurrences === 1) {\n\t\treturn {\n\t\t\tverdict: \"allow-lenient\",\n\t\t\treason: \"file changed but the anchored block is still uniquely locatable\",\n\t\t\tcurrentFileSha256,\n\t\t\tfileChanged,\n\t\t\tblockRelocated: true,\n\t\t\toccurrences,\n\t\t};\n\t}\n\treturn {\n\t\tverdict: \"reject-stale\",\n\t\treason:\n\t\t\toccurrences === 0\n\t\t\t\t? \"anchored block no longer exists in the current file\"\n\t\t\t\t: \"anchored block is ambiguous (multiple matches) in the current file\",\n\t\tcurrentFileSha256,\n\t\tfileChanged,\n\t\tblockRelocated: false,\n\t\toccurrences,\n\t};\n}\n"]}
@@ -1,196 +0,0 @@
1
- /**
2
- * `omk.recovery.checkpoint` — pure decision algorithms for native worktree/
3
- * conversation recovery checkpoints.
4
- *
5
- * This module is intentionally side-effect free: it never reads the filesystem,
6
- * never shells out to git, and never mutates a worktree. It validates a
7
- * file-backed {@link RecoveryCheckpoint}, classifies a restore preflight against
8
- * an externally-observed worktree snapshot, decides how OMK-created untracked
9
- * files should be disposed (delete vs quarantine), and produces a
10
- * conversation/code/both restore plan. The caller is responsible for gathering
11
- * the observed state (git status, per-file hashes) and for executing the plan.
12
- *
13
- * Derived clean-room from the lane C plan
14
- * (`.omk/runs/omk-package-hardening-plan/recovery-readseek.md`) and the
15
- * adoption algorithm plan; no legacy package source is imported or copied.
16
- *
17
- * Dependency-free apart from `node:path` (pure path math, no I/O).
18
- */
19
- export declare const RECOVERY_CHECKPOINT_SCHEMA_VERSION = "omk.recovery.checkpoint.v1";
20
- export type RecoveryVcs = "git" | "none";
21
- export type TouchedFileMode = "tracked" | "untracked" | "deleted";
22
- export interface RecoveryCheckpointSession {
23
- readonly sessionFile?: string;
24
- readonly leafId: string | null;
25
- readonly branchPathIds: readonly string[];
26
- readonly contextHash: string;
27
- }
28
- export interface RecoveryTouchedFile {
29
- readonly path: string;
30
- readonly beforeSha256?: string;
31
- readonly afterSha256?: string;
32
- readonly mode: TouchedFileMode;
33
- }
34
- export interface RecoveryCheckpointWorkspace {
35
- readonly repoRoot: string;
36
- readonly vcs: RecoveryVcs;
37
- readonly head?: string;
38
- readonly statusPorcelainSha256: string;
39
- readonly touchedFiles: readonly RecoveryTouchedFile[];
40
- readonly reversePatchArtifact?: string;
41
- }
42
- export interface RecoveryCheckpointTool {
43
- readonly turnIndex: number;
44
- readonly beforeToolCallId?: string;
45
- readonly afterToolCallId?: string;
46
- readonly mutatingTools: readonly string[];
47
- }
48
- export interface RecoveryCheckpointLedger {
49
- readonly eventId: string;
50
- readonly previousEventHash: string;
51
- readonly eventHash: string;
52
- }
53
- export interface RecoveryCheckpoint {
54
- readonly schemaVersion: typeof RECOVERY_CHECKPOINT_SCHEMA_VERSION;
55
- readonly checkpointId: string;
56
- readonly createdAt: string;
57
- readonly session: RecoveryCheckpointSession;
58
- readonly workspace: RecoveryCheckpointWorkspace;
59
- readonly tool?: RecoveryCheckpointTool;
60
- readonly ledger: RecoveryCheckpointLedger;
61
- }
62
- export interface RecoveryCheckpointValidation {
63
- readonly ok: boolean;
64
- readonly errors: readonly string[];
65
- readonly checkpoint?: RecoveryCheckpoint;
66
- }
67
- /** A touched-file path must be repo-relative and must not escape the repo root. */
68
- export declare function isUnsafeTouchedPath(path: unknown): boolean;
69
- /**
70
- * Validate an untrusted value as a {@link RecoveryCheckpoint}. Pure and
71
- * fail-closed: any structural problem yields `ok: false` with explicit errors
72
- * and no `checkpoint` payload.
73
- */
74
- export declare function validateRecoveryCheckpoint(value: unknown): RecoveryCheckpointValidation;
75
- /**
76
- * The ledger anchor as independently re-read from the hash-chained ledger.
77
- * Compared against the checkpoint's embedded anchor before any mutation.
78
- */
79
- export interface ObservedLedgerAnchor {
80
- readonly eventId: string;
81
- readonly previousEventHash: string;
82
- readonly eventHash: string;
83
- }
84
- export interface LedgerAnchorVerification {
85
- readonly ok: boolean;
86
- readonly mismatches: readonly string[];
87
- }
88
- /**
89
- * Compare a checkpoint's embedded ledger anchor with the anchor observed in the
90
- * live ledger. A mismatch means the checkpoint references an event that no
91
- * longer matches the durable audit trail (tamper or divergence) and the restore
92
- * must fail closed.
93
- */
94
- export declare function verifyCheckpointLedgerAnchor(checkpoint: RecoveryCheckpoint, observed: ObservedLedgerAnchor): LedgerAnchorVerification;
95
- export interface ObservedTouchedFile {
96
- readonly path: string;
97
- readonly exists: boolean;
98
- readonly sha256?: string;
99
- readonly isSymlink?: boolean;
100
- /** True when the path (or its symlink target) resolves outside the repo root. */
101
- readonly escapesRepo?: boolean;
102
- }
103
- export interface ObservedWorktree {
104
- readonly head?: string;
105
- readonly statusPorcelainSha256?: string;
106
- /** Current dirty paths (repo-relative) from `git status --porcelain`. */
107
- readonly dirtyPaths: readonly string[];
108
- readonly touchedFiles: readonly ObservedTouchedFile[];
109
- }
110
- export interface RestorePreflightOptions {
111
- /** Allow restoring even though HEAD moved since the checkpoint. */
112
- readonly allowNonHeadRestore?: boolean;
113
- }
114
- export type RestorePreflightVerdict = "clean" | "blocked";
115
- export type RestorePreflightIssueCode = "unrelated-dirty-path" | "touched-hash-mismatch" | "missing-touched-file" | "symlink-escape" | "head-mismatch";
116
- export interface RestorePreflightIssue {
117
- readonly code: RestorePreflightIssueCode;
118
- readonly path?: string;
119
- readonly detail: string;
120
- }
121
- export interface RestorePreflightResult {
122
- readonly verdict: RestorePreflightVerdict;
123
- readonly issues: readonly RestorePreflightIssue[];
124
- readonly unrelatedDirtyPaths: readonly string[];
125
- readonly hashMismatches: readonly string[];
126
- readonly symlinkEscapes: readonly string[];
127
- readonly headChanged: boolean;
128
- }
129
- /**
130
- * Classify whether a code restore can proceed against the live worktree.
131
- *
132
- * Fail-closed rules (from the lane C restore algorithm):
133
- * - any dirty path outside the recorded touched set blocks the restore;
134
- * - any touched file whose current hash differs from the recorded `afterSha256`
135
- * blocks the restore;
136
- * - any symlink escaping the repo root blocks the restore;
137
- * - a moved HEAD blocks the restore unless `allowNonHeadRestore` is set.
138
- */
139
- export declare function classifyRestorePreflight(checkpoint: RecoveryCheckpoint, observed: ObservedWorktree, options?: RestorePreflightOptions): RestorePreflightResult;
140
- export type UntrackedDisposition = "delete" | "quarantine" | "skip";
141
- export interface UntrackedDispositionDecision {
142
- readonly disposition: UntrackedDisposition;
143
- readonly reason: string;
144
- }
145
- export interface ObservedUntrackedFile {
146
- readonly exists: boolean;
147
- readonly sha256?: string;
148
- }
149
- /**
150
- * Decide how an OMK-created untracked file should be handled on restore.
151
- *
152
- * - `skip`: the file is already gone, nothing to undo.
153
- * - `delete`: the file still exactly matches what OMK created, safe to remove.
154
- * - `quarantine`: the file changed since OMK created it; move it under the
155
- * recovery quarantine directory rather than destroying user edits.
156
- */
157
- export declare function decideUntrackedDisposition(touched: RecoveryTouchedFile, observed: ObservedUntrackedFile): UntrackedDispositionDecision;
158
- export type RestoreMode = "conversation" | "code" | "both";
159
- export type RestorePhaseKind = "conversation" | "code";
160
- export type RestorePhaseAction = "navigate-tree" | "apply-reverse-patch";
161
- export interface RestorePlanPhase {
162
- readonly phase: RestorePhaseKind;
163
- readonly action: RestorePhaseAction;
164
- readonly targetLeafId?: string | null;
165
- readonly recordedPaths?: readonly string[];
166
- }
167
- export interface RestorePlan {
168
- readonly mode: RestoreMode;
169
- readonly status: "ready" | "blocked";
170
- readonly phases: readonly RestorePlanPhase[];
171
- readonly blockers: readonly string[];
172
- /** For `both`: the leaf to keep if the conversation phase fails after code restore. */
173
- readonly preservePreviousLeafId?: string | null;
174
- readonly inDoubtPolicy?: "preserve-previous-leaf";
175
- }
176
- export interface PlanRestoreInput {
177
- readonly mode: RestoreMode;
178
- readonly checkpoint: RecoveryCheckpoint;
179
- /** Required for `code` and `both` modes. */
180
- readonly preflight?: RestorePreflightResult;
181
- /** The current session leaf, preserved for manual recovery in `both` mode. */
182
- readonly currentLeafId?: string | null;
183
- }
184
- /**
185
- * Build a restore plan for the requested mode.
186
- *
187
- * - `conversation`: navigate the session tree to the checkpoint leaf; no file
188
- * writes, so no worktree preflight is required.
189
- * - `code`: apply the recorded reverse patch path-by-path; requires a clean
190
- * preflight.
191
- * - `both`: restore code first, then navigate the tree; requires a clean
192
- * preflight. If the second (conversation) phase fails at execution time the
193
- * caller marks the operation `in_doubt` and keeps `preservePreviousLeafId`.
194
- */
195
- export declare function planRestore(input: PlanRestoreInput): RestorePlan;
196
- //# sourceMappingURL=recovery-checkpoint.d.ts.map