harnery 0.33.0 → 0.34.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 (657) hide show
  1. package/dist/commander.d.ts.map +1 -1
  2. package/dist/commander.js +4 -0
  3. package/dist/commands/adapter.js +2 -2
  4. package/dist/commands/agents.d.ts +144 -1
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +1237 -590
  7. package/dist/commands/artifacts.d.ts.map +1 -1
  8. package/dist/commands/artifacts.js +7 -6
  9. package/dist/commands/backup.d.ts +1 -1
  10. package/dist/commands/backup.d.ts.map +1 -1
  11. package/dist/commands/backup.js +29 -16
  12. package/dist/commands/browse.d.ts.map +1 -1
  13. package/dist/commands/browse.js +234 -12
  14. package/dist/commands/checkpoint.d.ts.map +1 -1
  15. package/dist/commands/checkpoint.js +3 -2
  16. package/dist/commands/claude-desktop.d.ts +1 -1
  17. package/dist/commands/claude-desktop.d.ts.map +1 -1
  18. package/dist/commands/claude-desktop.js +3 -74
  19. package/dist/commands/context.js +1 -6
  20. package/dist/commands/decision.d.ts.map +1 -1
  21. package/dist/commands/decision.js +22 -38
  22. package/dist/commands/docs.d.ts.map +1 -1
  23. package/dist/commands/docs.js +91 -28
  24. package/dist/commands/doctor.js +10 -1
  25. package/dist/commands/events.d.ts +5 -0
  26. package/dist/commands/events.d.ts.map +1 -0
  27. package/dist/commands/events.js +189 -0
  28. package/dist/commands/governor.d.ts.map +1 -1
  29. package/dist/commands/governor.js +13 -4
  30. package/dist/commands/init.d.ts +9 -2
  31. package/dist/commands/init.d.ts.map +1 -1
  32. package/dist/commands/init.js +223 -51
  33. package/dist/commands/journal.d.ts.map +1 -1
  34. package/dist/commands/journal.js +4 -13
  35. package/dist/commands/ledger-v3.d.ts +5 -0
  36. package/dist/commands/ledger-v3.d.ts.map +1 -0
  37. package/dist/commands/ledger-v3.js +91 -0
  38. package/dist/commands/presence.d.ts.map +1 -1
  39. package/dist/commands/presence.js +11 -5
  40. package/dist/commands/sync.d.ts +1 -1
  41. package/dist/commands/sync.js +1 -1
  42. package/dist/commands/web.d.ts +6 -0
  43. package/dist/commands/web.d.ts.map +1 -1
  44. package/dist/commands/web.js +67 -7
  45. package/dist/commands/work.d.ts.map +1 -1
  46. package/dist/commands/work.js +22 -5
  47. package/dist/core/adapter.d.ts +5 -0
  48. package/dist/core/adapter.d.ts.map +1 -0
  49. package/dist/core/adapter.js +1 -0
  50. package/dist/core/adapters/attest.d.ts +5 -2
  51. package/dist/core/adapters/attest.d.ts.map +1 -1
  52. package/dist/core/adapters/attest.js +8 -3
  53. package/dist/core/adapters/profiles.d.ts +6 -6
  54. package/dist/core/adapters/profiles.d.ts.map +1 -1
  55. package/dist/core/adapters/profiles.js +4 -4
  56. package/dist/core/agents/canonical-emit.d.ts +17 -15
  57. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  58. package/dist/core/agents/canonical-emit.js +22 -22
  59. package/dist/core/agents/cli.js +207 -234
  60. package/dist/core/agents/codex-archive-v3.d.ts +14 -0
  61. package/dist/core/agents/codex-archive-v3.d.ts.map +1 -0
  62. package/dist/core/agents/codex-archive-v3.js +110 -0
  63. package/dist/core/agents/coord-client.d.ts +19 -81
  64. package/dist/core/agents/coord-client.d.ts.map +1 -1
  65. package/dist/core/agents/coord-client.js +65 -155
  66. package/dist/core/agents/finalization.d.ts +4 -1
  67. package/dist/core/agents/finalization.d.ts.map +1 -1
  68. package/dist/core/agents/finalization.js +89 -32
  69. package/dist/core/agents/index.d.ts +4 -5
  70. package/dist/core/agents/index.d.ts.map +1 -1
  71. package/dist/core/agents/index.js +4 -5
  72. package/dist/core/agents/live-authority-v3.d.ts +59 -0
  73. package/dist/core/agents/live-authority-v3.d.ts.map +1 -0
  74. package/dist/core/agents/live-authority-v3.js +233 -0
  75. package/dist/core/agents/live-lifecycle-v3.d.ts +20 -0
  76. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -0
  77. package/dist/core/agents/live-lifecycle-v3.js +91 -0
  78. package/dist/core/agents/live-observation-v3.d.ts +25 -0
  79. package/dist/core/agents/live-observation-v3.d.ts.map +1 -0
  80. package/dist/core/agents/live-observation-v3.js +157 -0
  81. package/dist/core/agents/render/prompt-context.d.ts +4 -4
  82. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  83. package/dist/core/agents/render/prompt-context.js +23 -58
  84. package/dist/core/agents/render/session-context.d.ts +1 -1
  85. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  86. package/dist/core/agents/render/session-context.js +8 -32
  87. package/dist/core/agents/rules/claim-conflict.d.ts +3 -3
  88. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  89. package/dist/core/agents/rules/claim-conflict.js +79 -127
  90. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  91. package/dist/core/agents/rules/commit-conflict.js +3 -18
  92. package/dist/core/agents/rules/stop-hook.d.ts +9 -50
  93. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  94. package/dist/core/agents/rules/stop-hook.js +193 -236
  95. package/dist/core/agents/session-events.d.ts +5 -7
  96. package/dist/core/agents/session-events.d.ts.map +1 -1
  97. package/dist/core/agents/session-events.js +127 -95
  98. package/dist/core/agents/session-finalization-state-v3.d.ts +30 -0
  99. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -0
  100. package/dist/core/agents/session-finalization-state-v3.js +40 -0
  101. package/dist/core/agents/session-finalizer-v3.d.ts +107 -0
  102. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -0
  103. package/dist/core/agents/session-finalizer-v3.js +734 -0
  104. package/dist/core/agents/session-name-display.d.ts +40 -0
  105. package/dist/core/agents/session-name-display.d.ts.map +1 -0
  106. package/dist/core/agents/session-name-display.js +119 -0
  107. package/dist/core/agents/state/activity-log.d.ts +3 -3
  108. package/dist/core/agents/state/activity-log.d.ts.map +1 -1
  109. package/dist/core/agents/state/activity-log.js +7 -16
  110. package/dist/core/agents/state/heartbeat-writer.d.ts +23 -55
  111. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  112. package/dist/core/agents/state/heartbeat-writer.js +56 -228
  113. package/dist/core/agents/state/live-coordination-view.d.ts +31 -0
  114. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -0
  115. package/dist/core/agents/state/live-coordination-view.js +209 -0
  116. package/dist/core/agents/state/session-state.d.ts +4 -3
  117. package/dist/core/agents/state/session-state.d.ts.map +1 -1
  118. package/dist/core/agents/state/session-state.js +18 -9
  119. package/dist/core/agents/state/stale-sweep.d.ts +1 -1
  120. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  121. package/dist/core/agents/state/stale-sweep.js +26 -15
  122. package/dist/core/artifacts/index.d.ts.map +1 -1
  123. package/dist/core/artifacts/index.js +5 -7
  124. package/dist/core/config.d.ts +33 -2
  125. package/dist/core/config.d.ts.map +1 -1
  126. package/dist/core/config.js +98 -2
  127. package/dist/core/context/index.d.ts +1 -4
  128. package/dist/core/context/index.d.ts.map +1 -1
  129. package/dist/core/context/index.js +9 -9
  130. package/dist/core/events/span-parent.d.ts +15 -0
  131. package/dist/core/events/span-parent.d.ts.map +1 -0
  132. package/dist/core/events/span-parent.js +34 -0
  133. package/dist/core/events/v3/advance.d.ts +14 -0
  134. package/dist/core/events/v3/advance.d.ts.map +1 -0
  135. package/dist/core/events/v3/advance.js +134 -0
  136. package/dist/core/events/v3/authority-outbox.d.ts +78 -0
  137. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -0
  138. package/dist/core/events/v3/authority-outbox.js +399 -0
  139. package/dist/core/events/v3/base-builder.d.ts +20 -0
  140. package/dist/core/events/v3/base-builder.d.ts.map +1 -0
  141. package/dist/core/events/v3/base-builder.js +29 -0
  142. package/dist/core/events/v3/base-contract.d.ts +7576 -0
  143. package/dist/core/events/v3/base-contract.d.ts.map +1 -0
  144. package/dist/core/events/v3/base-contract.js +647 -0
  145. package/dist/core/events/v3/bootstrap.d.ts +34 -0
  146. package/dist/core/events/v3/bootstrap.d.ts.map +1 -0
  147. package/dist/core/events/v3/bootstrap.js +175 -0
  148. package/dist/core/events/v3/builder.d.ts +20 -0
  149. package/dist/core/events/v3/builder.d.ts.map +1 -0
  150. package/dist/core/events/v3/builder.js +28 -0
  151. package/dist/core/events/v3/canonical.d.ts +31 -0
  152. package/dist/core/events/v3/canonical.d.ts.map +1 -0
  153. package/dist/core/events/v3/canonical.js +100 -0
  154. package/dist/core/events/v3/capabilities.d.ts +31 -0
  155. package/dist/core/events/v3/capabilities.d.ts.map +1 -0
  156. package/dist/core/events/v3/capabilities.js +107 -0
  157. package/dist/core/events/v3/capability-drift.d.ts +22 -0
  158. package/dist/core/events/v3/capability-drift.d.ts.map +1 -0
  159. package/dist/core/events/v3/capability-drift.js +134 -0
  160. package/dist/core/events/v3/catalog.d.ts +38 -0
  161. package/dist/core/events/v3/catalog.d.ts.map +1 -0
  162. package/dist/core/events/v3/catalog.js +135 -0
  163. package/dist/core/events/v3/clock-order.d.ts +34 -0
  164. package/dist/core/events/v3/clock-order.d.ts.map +1 -0
  165. package/dist/core/events/v3/clock-order.js +35 -0
  166. package/dist/core/events/v3/contract.d.ts +171 -0
  167. package/dist/core/events/v3/contract.d.ts.map +1 -0
  168. package/dist/core/events/v3/contract.js +180 -0
  169. package/dist/core/events/v3/control.d.ts +111 -0
  170. package/dist/core/events/v3/control.d.ts.map +1 -0
  171. package/dist/core/events/v3/control.js +419 -0
  172. package/dist/core/events/v3/coordination-view.d.ts +69 -0
  173. package/dist/core/events/v3/coordination-view.d.ts.map +1 -0
  174. package/dist/core/events/v3/coordination-view.js +119 -0
  175. package/dist/core/events/v3/economics.d.ts +77 -0
  176. package/dist/core/events/v3/economics.d.ts.map +1 -0
  177. package/dist/core/events/v3/economics.js +274 -0
  178. package/dist/core/events/v3/finalization-view.d.ts +26 -0
  179. package/dist/core/events/v3/finalization-view.d.ts.map +1 -0
  180. package/dist/core/events/v3/finalization-view.js +37 -0
  181. package/dist/core/events/v3/fingerprint-keys.d.ts +25 -0
  182. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -0
  183. package/dist/core/events/v3/fingerprint-keys.js +217 -0
  184. package/dist/core/events/v3/generated.d.ts +4 -0
  185. package/dist/core/events/v3/generated.d.ts.map +1 -0
  186. package/dist/core/events/v3/generated.js +47 -0
  187. package/dist/core/events/v3/ids.d.ts +9 -0
  188. package/dist/core/events/v3/ids.d.ts.map +1 -0
  189. package/dist/core/events/v3/ids.js +25 -0
  190. package/dist/core/events/v3/index.d.ts +35 -0
  191. package/dist/core/events/v3/index.d.ts.map +1 -0
  192. package/dist/core/events/v3/index.js +33 -0
  193. package/dist/core/events/v3/latency.d.ts +63 -0
  194. package/dist/core/events/v3/latency.d.ts.map +1 -0
  195. package/dist/core/events/v3/latency.js +412 -0
  196. package/dist/core/events/v3/live-feed.d.ts +45 -0
  197. package/dist/core/events/v3/live-feed.d.ts.map +1 -0
  198. package/dist/core/events/v3/live-feed.js +246 -0
  199. package/dist/core/events/v3/live-observation.d.ts +35 -0
  200. package/dist/core/events/v3/live-observation.d.ts.map +1 -0
  201. package/dist/core/events/v3/live-observation.js +1 -0
  202. package/dist/core/events/v3/live-routing.d.ts +57 -0
  203. package/dist/core/events/v3/live-routing.d.ts.map +1 -0
  204. package/dist/core/events/v3/live-routing.js +107 -0
  205. package/dist/core/events/v3/producers/command-recorder.d.ts +67 -0
  206. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -0
  207. package/dist/core/events/v3/producers/command-recorder.js +477 -0
  208. package/dist/core/events/v3/producers/command.d.ts +50 -0
  209. package/dist/core/events/v3/producers/command.d.ts.map +1 -0
  210. package/dist/core/events/v3/producers/command.js +135 -0
  211. package/dist/core/events/v3/producers/coordination-recorder.d.ts +53 -0
  212. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -0
  213. package/dist/core/events/v3/producers/coordination-recorder.js +376 -0
  214. package/dist/core/events/v3/producers/coordination.d.ts +90 -0
  215. package/dist/core/events/v3/producers/coordination.d.ts.map +1 -0
  216. package/dist/core/events/v3/producers/coordination.js +229 -0
  217. package/dist/core/events/v3/producers/hook-base.d.ts +54 -0
  218. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -0
  219. package/dist/core/events/v3/producers/hook-base.js +424 -0
  220. package/dist/core/events/v3/producers/hook.d.ts +29 -0
  221. package/dist/core/events/v3/producers/hook.d.ts.map +1 -0
  222. package/dist/core/events/v3/producers/hook.js +71 -0
  223. package/dist/core/events/v3/producers/intake.d.ts +65 -0
  224. package/dist/core/events/v3/producers/intake.d.ts.map +1 -0
  225. package/dist/core/events/v3/producers/intake.js +244 -0
  226. package/dist/core/events/v3/producers/recorder.d.ts +240 -0
  227. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -0
  228. package/dist/core/events/v3/producers/recorder.js +2176 -0
  229. package/dist/core/events/v3/projection.d.ts +101 -0
  230. package/dist/core/events/v3/projection.d.ts.map +1 -0
  231. package/dist/core/events/v3/projection.js +479 -0
  232. package/dist/core/events/v3/reader.d.ts +75 -0
  233. package/dist/core/events/v3/reader.d.ts.map +1 -0
  234. package/dist/core/events/v3/reader.js +778 -0
  235. package/dist/core/events/v3/recovery-record.d.ts +47 -0
  236. package/dist/core/events/v3/recovery-record.d.ts.map +1 -0
  237. package/dist/core/events/v3/recovery-record.js +167 -0
  238. package/dist/core/events/v3/recovery.d.ts +22 -0
  239. package/dist/core/events/v3/recovery.d.ts.map +1 -0
  240. package/dist/core/events/v3/recovery.js +227 -0
  241. package/dist/core/events/v3/span-state.d.ts +52 -0
  242. package/dist/core/events/v3/span-state.d.ts.map +1 -0
  243. package/dist/core/events/v3/span-state.js +142 -0
  244. package/dist/core/events/v3/targets.d.ts +23 -0
  245. package/dist/core/events/v3/targets.d.ts.map +1 -0
  246. package/dist/core/events/v3/targets.js +134 -0
  247. package/dist/core/events/v3/turn-telemetry.d.ts +41 -0
  248. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -0
  249. package/dist/core/events/v3/turn-telemetry.js +168 -0
  250. package/dist/core/events/v3/validate.d.ts +9 -0
  251. package/dist/core/events/v3/validate.d.ts.map +1 -0
  252. package/dist/core/events/v3/validate.js +281 -0
  253. package/dist/core/events/v3/writer.d.ts +56 -0
  254. package/dist/core/events/v3/writer.d.ts.map +1 -0
  255. package/dist/core/events/v3/writer.js +335 -0
  256. package/dist/core/governor/index.d.ts +1 -1
  257. package/dist/core/governor/index.d.ts.map +1 -1
  258. package/dist/core/governor/index.js +1 -1
  259. package/dist/core/governor/plan-api.d.ts +2 -1
  260. package/dist/core/governor/plan-api.d.ts.map +1 -1
  261. package/dist/core/governor/plan-api.js +6 -3
  262. package/dist/core/governor/read.d.ts +1 -1
  263. package/dist/core/governor/read.d.ts.map +1 -1
  264. package/dist/core/governor/read.js +1 -1
  265. package/dist/core/governor/state.d.ts +9 -0
  266. package/dist/core/governor/state.d.ts.map +1 -1
  267. package/dist/core/governor/state.js +40 -3
  268. package/dist/core/guard/config.d.ts +5 -0
  269. package/dist/core/guard/config.d.ts.map +1 -0
  270. package/dist/core/guard/config.js +157 -0
  271. package/dist/core/guard/coordinator.d.ts +14 -0
  272. package/dist/core/guard/coordinator.d.ts.map +1 -0
  273. package/dist/core/guard/coordinator.js +173 -0
  274. package/dist/core/guard/evaluator.d.ts +4 -0
  275. package/dist/core/guard/evaluator.d.ts.map +1 -0
  276. package/dist/core/guard/evaluator.js +242 -0
  277. package/dist/core/guard/evidence-v3.d.ts +15 -0
  278. package/dist/core/guard/evidence-v3.d.ts.map +1 -0
  279. package/dist/core/guard/evidence-v3.js +171 -0
  280. package/dist/core/guard/index.d.ts +10 -0
  281. package/dist/core/guard/index.d.ts.map +1 -0
  282. package/dist/core/guard/index.js +6 -0
  283. package/dist/core/guard/live-source-v3.d.ts +40 -0
  284. package/dist/core/guard/live-source-v3.d.ts.map +1 -0
  285. package/dist/core/guard/live-source-v3.js +128 -0
  286. package/dist/core/guard/storage.d.ts +27 -0
  287. package/dist/core/guard/storage.d.ts.map +1 -0
  288. package/dist/core/guard/storage.js +140 -0
  289. package/dist/core/guard/types.d.ts +119 -0
  290. package/dist/core/guard/types.d.ts.map +1 -0
  291. package/dist/core/guard/types.js +1 -0
  292. package/dist/core/hooks/adapter/detect.d.ts +18 -1
  293. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  294. package/dist/core/hooks/adapter/detect.js +29 -0
  295. package/dist/core/hooks/adapter/events.d.ts +7 -4
  296. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  297. package/dist/core/hooks/adapter/events.js +12 -4
  298. package/dist/core/hooks/adapter/output.d.ts +2 -2
  299. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  300. package/dist/core/hooks/adapter/parse.d.ts +7 -4
  301. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  302. package/dist/core/hooks/adapter/parse.js +63 -25
  303. package/dist/core/hooks/adapter/wiring.d.ts +30 -4
  304. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  305. package/dist/core/hooks/adapter/wiring.js +89 -11
  306. package/dist/core/hooks/cli.d.ts +1 -1
  307. package/dist/core/hooks/cli.js +306 -444
  308. package/dist/core/hooks/effects/image-capture.d.ts +20 -34
  309. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  310. package/dist/core/hooks/effects/image-capture.js +212 -221
  311. package/dist/core/hooks/effects/index.d.ts +1 -9
  312. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  313. package/dist/core/hooks/effects/index.js +1 -24
  314. package/dist/core/hooks/events/input-hash.d.ts +7 -0
  315. package/dist/core/hooks/events/input-hash.d.ts.map +1 -0
  316. package/dist/core/hooks/events/input-hash.js +43 -0
  317. package/dist/core/hooks/index.d.ts +4 -3
  318. package/dist/core/hooks/index.d.ts.map +1 -1
  319. package/dist/core/hooks/index.js +2 -3
  320. package/dist/core/hooks/resolve/coord-root.d.ts +1 -1
  321. package/dist/core/hooks/resolve/coord-root.js +1 -1
  322. package/dist/core/hooks/resolve/owner.d.ts +16 -15
  323. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  324. package/dist/core/hooks/resolve/owner.js +51 -42
  325. package/dist/core/hooks/resolve/transcript.d.ts +33 -3
  326. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  327. package/dist/core/hooks/resolve/transcript.js +161 -14
  328. package/dist/core/hooks/session-name-presence.d.ts +9 -10
  329. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  330. package/dist/core/hooks/session-name-presence.js +15 -15
  331. package/dist/core/hooks/unsafe-cross-shell.d.ts +1 -1
  332. package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -1
  333. package/dist/core/journal/index.d.ts +2 -2
  334. package/dist/core/journal/index.d.ts.map +1 -1
  335. package/dist/core/journal/index.js +9 -22
  336. package/dist/core/presence/blob.d.ts +4 -3
  337. package/dist/core/presence/blob.d.ts.map +1 -1
  338. package/dist/core/presence/blob.js +45 -44
  339. package/dist/core/presence/codec-digest.d.ts +29 -0
  340. package/dist/core/presence/codec-digest.d.ts.map +1 -0
  341. package/dist/core/presence/codec-digest.js +170 -0
  342. package/dist/core/presence/relay-client.d.ts +1 -1
  343. package/dist/core/presence/relay-client.d.ts.map +1 -1
  344. package/dist/core/presence/relay-client.js +5 -5
  345. package/dist/core/work/index.d.ts +1 -1
  346. package/dist/core/work/index.d.ts.map +1 -1
  347. package/dist/core/work/index.js +1 -1
  348. package/dist/core/work/read.d.ts +1 -1
  349. package/dist/core/work/read.d.ts.map +1 -1
  350. package/dist/core/work/read.js +1 -1
  351. package/dist/core/work/runner.d.ts.map +1 -1
  352. package/dist/core/work/runner.js +6 -0
  353. package/dist/core/work/state.d.ts +9 -0
  354. package/dist/core/work/state.d.ts.map +1 -1
  355. package/dist/core/work/state.js +17 -3
  356. package/dist/core/workflow/engine.js +24 -25
  357. package/dist/core/workflow/live-session-v3.d.ts +19 -0
  358. package/dist/core/workflow/live-session-v3.d.ts.map +1 -0
  359. package/dist/core/workflow/live-session-v3.js +86 -0
  360. package/dist/core/workflow/scope-id.d.ts +3 -0
  361. package/dist/core/workflow/scope-id.d.ts.map +1 -0
  362. package/dist/core/workflow/scope-id.js +5 -0
  363. package/dist/core/workflow/workspaces/leases.d.ts +4 -0
  364. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  365. package/dist/core/workflow/workspaces/leases.js +53 -19
  366. package/dist/index.d.ts +1 -1
  367. package/dist/index.js +1 -1
  368. package/dist/lib/browser/client.d.ts +41 -0
  369. package/dist/lib/browser/client.d.ts.map +1 -1
  370. package/dist/lib/browser/client.js +203 -0
  371. package/dist/lib/browser/critique.d.ts +28 -2
  372. package/dist/lib/browser/critique.d.ts.map +1 -1
  373. package/dist/lib/browser/critique.js +74 -18
  374. package/dist/lib/browser/geometry.d.ts +1 -1
  375. package/dist/lib/browser/geometry.d.ts.map +1 -1
  376. package/dist/lib/browser/geometry.js +130 -6
  377. package/dist/lib/browser/index.d.ts +3 -0
  378. package/dist/lib/browser/index.d.ts.map +1 -1
  379. package/dist/lib/browser/index.js +3 -0
  380. package/dist/lib/browser/qa-plan.d.ts +174 -0
  381. package/dist/lib/browser/qa-plan.d.ts.map +1 -0
  382. package/dist/lib/browser/qa-plan.js +321 -0
  383. package/dist/lib/browser/qa-reuse.d.ts +99 -0
  384. package/dist/lib/browser/qa-reuse.d.ts.map +1 -0
  385. package/dist/lib/browser/qa-reuse.js +222 -0
  386. package/dist/lib/browser/qa-snapshot.d.ts +64 -0
  387. package/dist/lib/browser/qa-snapshot.d.ts.map +1 -0
  388. package/dist/lib/browser/qa-snapshot.js +171 -0
  389. package/dist/lib/browser/runts.d.ts.map +1 -1
  390. package/dist/lib/browser/runts.js +22 -1
  391. package/dist/lib/browser/tiling.d.ts +71 -0
  392. package/dist/lib/browser/tiling.d.ts.map +1 -0
  393. package/dist/lib/browser/tiling.js +118 -0
  394. package/dist/lib/claude-desktop.d.ts +0 -47
  395. package/dist/lib/claude-desktop.d.ts.map +1 -1
  396. package/dist/lib/claude-desktop.js +1 -169
  397. package/dist/lib/context/index.d.ts +0 -3
  398. package/dist/lib/context/index.d.ts.map +1 -1
  399. package/dist/lib/context/index.js +20 -42
  400. package/dist/lib/council/index.d.ts +2 -2
  401. package/dist/lib/council/index.d.ts.map +1 -1
  402. package/dist/lib/council/index.js +9 -20
  403. package/dist/lib/docs-frontmatter.d.ts +1 -6
  404. package/dist/lib/docs-frontmatter.d.ts.map +1 -1
  405. package/dist/lib/docs-frontmatter.js +12 -78
  406. package/dist/lib/docs-index.d.ts +9 -0
  407. package/dist/lib/docs-index.d.ts.map +1 -1
  408. package/dist/lib/docs-index.js +32 -3
  409. package/dist/lib/docs-lint.d.ts.map +1 -1
  410. package/dist/lib/docs-lint.js +14 -30
  411. package/dist/lib/docs-metadata-audit.d.ts +19 -0
  412. package/dist/lib/docs-metadata-audit.d.ts.map +1 -0
  413. package/dist/lib/docs-metadata-audit.js +132 -0
  414. package/dist/lib/docs-metadata-managed.d.ts +23 -0
  415. package/dist/lib/docs-metadata-managed.d.ts.map +1 -0
  416. package/dist/lib/docs-metadata-managed.js +78 -0
  417. package/dist/lib/docs-metadata-sync.d.ts +20 -0
  418. package/dist/lib/docs-metadata-sync.d.ts.map +1 -0
  419. package/dist/lib/docs-metadata-sync.js +222 -0
  420. package/dist/lib/docs-metadata-v2-schema.d.ts +273 -0
  421. package/dist/lib/docs-metadata-v2-schema.d.ts.map +1 -0
  422. package/dist/lib/docs-metadata-v2-schema.js +134 -0
  423. package/dist/lib/docs-metadata-v2.d.ts +21 -0
  424. package/dist/lib/docs-metadata-v2.d.ts.map +1 -0
  425. package/dist/lib/docs-metadata-v2.js +424 -0
  426. package/dist/lib/docs-new.d.ts +11 -0
  427. package/dist/lib/docs-new.d.ts.map +1 -0
  428. package/dist/lib/docs-new.js +52 -0
  429. package/dist/lib/docs-sweep.d.ts.map +1 -1
  430. package/dist/lib/docs-sweep.js +45 -29
  431. package/dist/lib/headless/index.d.ts +98 -0
  432. package/dist/lib/headless/index.d.ts.map +1 -0
  433. package/dist/lib/headless/index.js +211 -0
  434. package/dist/lib/identities/assume.d.ts +4 -4
  435. package/dist/lib/identities/assume.d.ts.map +1 -1
  436. package/dist/lib/identities/assume.js +52 -68
  437. package/dist/lib/instructions/apply.d.ts +5 -4
  438. package/dist/lib/instructions/apply.d.ts.map +1 -1
  439. package/dist/lib/instructions/apply.js +32 -20
  440. package/dist/lib/instructions/bundle.d.ts +1 -1
  441. package/dist/lib/instructions/bundle.d.ts.map +1 -1
  442. package/dist/lib/instructions/bundle.js +1 -1
  443. package/dist/lib/instructions/splice.d.ts +0 -1
  444. package/dist/lib/instructions/splice.d.ts.map +1 -1
  445. package/dist/lib/instructions/splice.js +22 -6
  446. package/dist/lib/instructions/templates.d.ts +7 -5
  447. package/dist/lib/instructions/templates.d.ts.map +1 -1
  448. package/dist/lib/instructions/templates.js +79 -9
  449. package/package.json +26 -5
  450. package/schemas/config.schema.json +120 -2
  451. package/schemas/docs-metadata-v2.schema.json +441 -0
  452. package/schemas/event-v3.schema.json +24472 -0
  453. package/src/commander.ts +4 -0
  454. package/src/commands/adapter.ts +2 -2
  455. package/src/commands/agents.ts +1530 -593
  456. package/src/commands/artifacts.ts +7 -6
  457. package/src/commands/backup.ts +29 -21
  458. package/src/commands/browse.ts +349 -8
  459. package/src/commands/checkpoint.ts +4 -3
  460. package/src/commands/claude-desktop.ts +2 -101
  461. package/src/commands/context.ts +1 -6
  462. package/src/commands/decision.ts +27 -44
  463. package/src/commands/docs.ts +100 -33
  464. package/src/commands/doctor.ts +14 -1
  465. package/src/commands/events.ts +256 -0
  466. package/src/commands/governor.ts +18 -4
  467. package/src/commands/init.ts +253 -57
  468. package/src/commands/instructions.ts +1 -1
  469. package/src/commands/journal.ts +4 -13
  470. package/src/commands/ledger-v3.ts +111 -0
  471. package/src/commands/presence.ts +11 -10
  472. package/src/commands/sync.ts +1 -1
  473. package/src/commands/web.ts +67 -8
  474. package/src/commands/work.ts +28 -5
  475. package/src/core/adapter.ts +5 -0
  476. package/src/core/adapters/attest.ts +8 -3
  477. package/src/core/adapters/profiles.ts +4 -6
  478. package/src/core/agents/canonical-emit.ts +34 -25
  479. package/src/core/agents/cli.ts +236 -262
  480. package/src/core/agents/codex-archive-v3.ts +138 -0
  481. package/src/core/agents/coord-client.ts +75 -188
  482. package/src/core/agents/finalization.ts +120 -38
  483. package/src/core/agents/index.ts +4 -5
  484. package/src/core/agents/live-authority-v3.ts +375 -0
  485. package/src/core/agents/live-lifecycle-v3.ts +140 -0
  486. package/src/core/agents/live-observation-v3.ts +216 -0
  487. package/src/core/agents/render/prompt-context.ts +29 -60
  488. package/src/core/agents/render/session-context.ts +10 -31
  489. package/src/core/agents/rules/claim-conflict.ts +85 -126
  490. package/src/core/agents/rules/commit-conflict.ts +3 -14
  491. package/src/core/agents/rules/stop-hook.ts +239 -293
  492. package/src/core/agents/session-events.ts +137 -104
  493. package/src/core/agents/session-finalization-state-v3.ts +90 -0
  494. package/src/core/agents/session-finalizer-v3.ts +953 -0
  495. package/src/core/agents/session-name-display.ts +149 -0
  496. package/src/core/agents/state/activity-log.ts +6 -14
  497. package/src/core/agents/state/heartbeat-writer.ts +82 -277
  498. package/src/core/agents/state/live-coordination-view.ts +290 -0
  499. package/src/core/agents/state/session-state.ts +24 -12
  500. package/src/core/agents/state/stale-sweep.ts +40 -17
  501. package/src/core/artifacts/index.ts +4 -5
  502. package/src/core/config.ts +148 -3
  503. package/src/core/context/index.ts +9 -17
  504. package/src/core/events/span-parent.ts +46 -0
  505. package/src/core/events/v3/advance.ts +168 -0
  506. package/src/core/events/v3/authority-outbox.ts +548 -0
  507. package/src/core/events/v3/base-builder.ts +60 -0
  508. package/src/core/events/v3/base-contract.ts +882 -0
  509. package/src/core/events/v3/bootstrap.ts +226 -0
  510. package/src/core/events/v3/builder.ts +59 -0
  511. package/src/core/events/v3/canonical.ts +153 -0
  512. package/src/core/events/v3/capabilities.ts +171 -0
  513. package/src/core/events/v3/capability-drift.ts +177 -0
  514. package/src/core/events/v3/catalog.ts +202 -0
  515. package/src/core/events/v3/clock-order.ts +41 -0
  516. package/src/core/events/v3/contract.ts +365 -0
  517. package/src/core/events/v3/control.ts +576 -0
  518. package/src/core/events/v3/coordination-view.ts +193 -0
  519. package/src/core/events/v3/economics.ts +384 -0
  520. package/src/core/events/v3/finalization-view.ts +67 -0
  521. package/src/core/events/v3/fingerprint-keys.ts +260 -0
  522. package/src/core/events/v3/generated.ts +48 -0
  523. package/src/core/events/v3/ids.ts +33 -0
  524. package/src/core/events/v3/index.ts +322 -0
  525. package/src/core/events/v3/latency.ts +551 -0
  526. package/src/core/events/v3/live-feed.ts +319 -0
  527. package/src/core/events/v3/live-observation.ts +39 -0
  528. package/src/core/events/v3/live-routing.ts +135 -0
  529. package/src/core/events/v3/producers/command-recorder.ts +631 -0
  530. package/src/core/events/v3/producers/command.ts +201 -0
  531. package/src/core/events/v3/producers/coordination-recorder.ts +538 -0
  532. package/src/core/events/v3/producers/coordination.ts +355 -0
  533. package/src/core/events/v3/producers/hook-base.ts +578 -0
  534. package/src/core/events/v3/producers/hook.ts +119 -0
  535. package/src/core/events/v3/producers/intake.ts +319 -0
  536. package/src/core/events/v3/producers/recorder.ts +2805 -0
  537. package/src/core/events/v3/projection.ts +653 -0
  538. package/src/core/events/v3/reader.ts +1127 -0
  539. package/src/core/events/v3/recovery-record.ts +244 -0
  540. package/src/core/events/v3/recovery.ts +271 -0
  541. package/src/core/events/v3/span-state.ts +187 -0
  542. package/src/core/events/v3/targets.ts +182 -0
  543. package/src/core/events/v3/turn-telemetry.ts +259 -0
  544. package/src/core/events/v3/validate.ts +361 -0
  545. package/src/core/events/v3/writer.ts +419 -0
  546. package/src/core/governor/index.ts +3 -0
  547. package/src/core/governor/plan-api.ts +10 -2
  548. package/src/core/governor/read.ts +3 -0
  549. package/src/core/governor/state.ts +52 -5
  550. package/src/core/guard/config.ts +206 -0
  551. package/src/core/guard/coordinator.ts +227 -0
  552. package/src/core/guard/evaluator.ts +343 -0
  553. package/src/core/guard/evidence-v3.ts +227 -0
  554. package/src/core/guard/index.ts +35 -0
  555. package/src/core/guard/live-source-v3.ts +204 -0
  556. package/src/core/guard/storage.ts +188 -0
  557. package/src/core/guard/types.ts +152 -0
  558. package/src/core/hooks/adapter/detect.ts +28 -1
  559. package/src/core/hooks/adapter/events.ts +14 -6
  560. package/src/core/hooks/adapter/output.ts +7 -2
  561. package/src/core/hooks/adapter/parse.ts +80 -38
  562. package/src/core/hooks/adapter/wiring.ts +119 -13
  563. package/src/core/hooks/cli.ts +352 -466
  564. package/src/core/hooks/effects/image-capture.ts +228 -238
  565. package/src/core/hooks/effects/index.ts +1 -36
  566. package/src/core/hooks/events/input-hash.ts +47 -0
  567. package/src/core/hooks/index.ts +4 -11
  568. package/src/core/hooks/resolve/coord-root.ts +1 -1
  569. package/src/core/hooks/resolve/owner.ts +52 -45
  570. package/src/core/hooks/resolve/transcript.ts +171 -14
  571. package/src/core/hooks/session-name-presence.ts +14 -15
  572. package/src/core/hooks/unsafe-cross-shell.ts +1 -1
  573. package/src/core/journal/index.ts +15 -22
  574. package/src/core/presence/blob.ts +48 -41
  575. package/src/core/presence/codec-digest.ts +215 -0
  576. package/src/core/presence/relay-client.ts +5 -5
  577. package/src/core/work/index.ts +3 -0
  578. package/src/core/work/read.ts +3 -0
  579. package/src/core/work/runner.ts +6 -0
  580. package/src/core/work/state.ts +27 -3
  581. package/src/core/workflow/engine.ts +24 -24
  582. package/src/core/workflow/live-session-v3.ts +126 -0
  583. package/src/core/workflow/scope-id.ts +6 -0
  584. package/src/core/workflow/workspaces/leases.ts +56 -19
  585. package/src/index.ts +1 -1
  586. package/src/lib/browser/client.ts +230 -0
  587. package/src/lib/browser/critique.ts +104 -27
  588. package/src/lib/browser/geometry.ts +143 -14
  589. package/src/lib/browser/index.ts +34 -0
  590. package/src/lib/browser/qa-plan.ts +501 -0
  591. package/src/lib/browser/qa-reuse.ts +306 -0
  592. package/src/lib/browser/qa-snapshot.ts +223 -0
  593. package/src/lib/browser/runts.ts +27 -2
  594. package/src/lib/browser/tiling.ts +164 -0
  595. package/src/lib/claude-desktop.ts +1 -232
  596. package/src/lib/context/index.ts +22 -46
  597. package/src/lib/council/index.ts +9 -21
  598. package/src/lib/docs-frontmatter.ts +8 -79
  599. package/src/lib/docs-index.ts +33 -3
  600. package/src/lib/docs-lint.ts +15 -29
  601. package/src/lib/docs-metadata-audit.ts +166 -0
  602. package/src/lib/docs-metadata-managed.ts +83 -0
  603. package/src/lib/docs-metadata-sync.ts +262 -0
  604. package/src/lib/docs-metadata-v2-schema.ts +138 -0
  605. package/src/lib/docs-metadata-v2.ts +646 -0
  606. package/src/lib/docs-new.ts +63 -0
  607. package/src/lib/docs-sweep.ts +42 -28
  608. package/src/lib/headless/index.ts +287 -0
  609. package/src/lib/identities/assume.ts +67 -86
  610. package/src/lib/instructions/apply.ts +33 -20
  611. package/src/lib/instructions/bundle.ts +2 -2
  612. package/src/lib/instructions/splice.ts +21 -7
  613. package/src/lib/instructions/templates.ts +85 -13
  614. package/dist/core/agents/cli-emit.d.ts +0 -27
  615. package/dist/core/agents/cli-emit.d.ts.map +0 -1
  616. package/dist/core/agents/cli-emit.js +0 -57
  617. package/dist/core/agents/codex-replay.d.ts +0 -29
  618. package/dist/core/agents/codex-replay.d.ts.map +0 -1
  619. package/dist/core/agents/codex-replay.js +0 -138
  620. package/dist/core/agents/events/consume.d.ts +0 -82
  621. package/dist/core/agents/events/consume.d.ts.map +0 -1
  622. package/dist/core/agents/events/consume.js +0 -195
  623. package/dist/core/agents/events/emit.d.ts +0 -42
  624. package/dist/core/agents/events/emit.d.ts.map +0 -1
  625. package/dist/core/agents/events/emit.js +0 -75
  626. package/dist/core/agents/events/ulid.d.ts +0 -11
  627. package/dist/core/agents/events/ulid.d.ts.map +0 -1
  628. package/dist/core/agents/events/ulid.js +0 -47
  629. package/dist/core/agents/state/heartbeat-projector.d.ts +0 -76
  630. package/dist/core/agents/state/heartbeat-projector.d.ts.map +0 -1
  631. package/dist/core/agents/state/heartbeat-projector.js +0 -581
  632. package/dist/core/hooks/events/emit.d.ts +0 -31
  633. package/dist/core/hooks/events/emit.d.ts.map +0 -1
  634. package/dist/core/hooks/events/emit.js +0 -93
  635. package/dist/core/hooks/events/rotate.d.ts +0 -43
  636. package/dist/core/hooks/events/rotate.d.ts.map +0 -1
  637. package/dist/core/hooks/events/rotate.js +0 -142
  638. package/dist/core/hooks/events/schema.d.ts +0 -341
  639. package/dist/core/hooks/events/schema.d.ts.map +0 -1
  640. package/dist/core/hooks/events/schema.js +0 -12
  641. package/dist/core/hooks/events/ulid.d.ts +0 -10
  642. package/dist/core/hooks/events/ulid.d.ts.map +0 -1
  643. package/dist/core/hooks/events/ulid.js +0 -47
  644. package/dist/lib/docs-frontmatter-migrate.d.ts +0 -33
  645. package/dist/lib/docs-frontmatter-migrate.d.ts.map +0 -1
  646. package/dist/lib/docs-frontmatter-migrate.js +0 -364
  647. package/src/core/agents/cli-emit.ts +0 -64
  648. package/src/core/agents/codex-replay.ts +0 -163
  649. package/src/core/agents/events/consume.ts +0 -254
  650. package/src/core/agents/events/emit.ts +0 -115
  651. package/src/core/agents/events/ulid.ts +0 -51
  652. package/src/core/agents/state/heartbeat-projector.ts +0 -645
  653. package/src/core/hooks/events/emit.ts +0 -125
  654. package/src/core/hooks/events/rotate.ts +0 -151
  655. package/src/core/hooks/events/schema.ts +0 -582
  656. package/src/core/hooks/events/ulid.ts +0 -51
  657. package/src/lib/docs-frontmatter-migrate.ts +0 -427
@@ -1,182 +1,189 @@
1
1
  /**
2
- * Stop-hook verdict: rule 1/3, 2/3, 3/3 enforcement evaluated
3
- * from the canonical event stream alone (no transcript scan).
2
+ * End-of-turn ritual verdict derived from the authoritative V3 event ledger.
4
3
  *
5
- * Codex is observe-only: its Stop continuation can replace the user-facing
6
- * answer in clients that retain only the final continuation response.
7
- *
8
- * Enforced adapters:
9
- * Rule 1/3: `state.status_checked` event with matching turn boundary exists.
10
- * Rule 2/3: latest `turn.stop` event has `status_box_present: true` (or the
11
- * stop currently firing carries that field via the in-flight event
12
- * agent-hook emits before the stop hook runs).
13
- * Rule 3/3: `state.task_set` event with matching turn boundary exists.
14
- * Session-naming rule (at most once per session, CC semantics only): the turn
15
- * whose set-task produced `suggested_session_name` must carry an
16
- * in-window `turn.stop` with `session_name_present: true`.
17
- *
18
- * Pure-prose-turn exemption: when zero `tool.pre_use` events fire in
19
- * the current turn, rules 1/3 and 3/3 do not apply. Rule 2/3 still applies
20
- * as a user-visible mobile cue.
21
- *
22
- * Remediation continuity: an adapter whose Stop channel re-prompts by
23
- * submitting a new user message (Cursor's `followup_message`) opens a fresh
24
- * turn to repair the previous one. Such a turn inherits the window of the turn
25
- * it repairs, so ritual events accumulate instead of alternating. See
26
- * `resolveTurnStartMs`.
4
+ * V3 keeps prompt and reply bodies private. The hook producer records only the
5
+ * structural observations needed here: whether the reply showed the status box
6
+ * and current session name, and whether a prompt was emitted by Harnery's own
7
+ * Cursor remediation channel. Task and status evidence comes from the existing
8
+ * `coord.task_changed` and `coord.status_observed` events.
27
9
  */
28
10
 
29
- import { existsSync, readFileSync } from "node:fs";
30
- import { join } from "node:path";
31
11
  import {
32
12
  agentsRequireGitFinalization,
33
13
  endOfTurnStatusCommand,
34
14
  resolveBinName,
35
15
  } from "../../config.ts";
16
+ import type { EventV3 } from "../../events/v3/contract.ts";
17
+ import { readEventV3ControlState } from "../../events/v3/control.ts";
18
+ import { liveInstanceIdV3 } from "../../events/v3/live-routing.ts";
19
+ import { readLedgerV3 } from "../../events/v3/reader.ts";
20
+ import { readLiveCoordinationRow } from "../state/live-coordination-view.ts";
36
21
 
37
22
  export type { VerdictResult } from "./verdict.ts";
38
23
 
39
24
  import type { VerdictResult } from "./verdict.ts";
40
25
 
41
- interface CanonicalEvent {
42
- event_id: string;
43
- event_type: string;
44
- ts: string;
45
- instance_id: string;
46
- session_id: string;
47
- turn_id?: string;
48
- adapter: string;
49
- source: string;
50
- data: Record<string, unknown>;
51
- }
52
-
53
26
  export interface StopHookRequest {
54
27
  rule: "stop-hook";
55
28
  instance_id: string;
56
29
  session_id?: string;
57
- /** Firing adapter. Selects the end-of-turn ack signal (see `ackSignalFor`).
58
- * Undefined → Claude Code semantics (transcript-scanned status box). */
59
30
  adapter?: string;
60
- /** Wall-clock cutoff for the current turn; events strictly after this are not yet relevant. */
61
31
  now_ms?: number;
62
- /** Override the turn-window discovery (used by tests). */
63
32
  turn_window?: { start_ms: number; end_ms: number };
64
- /** Bypass switch: operator escape hatch identical to HARNERY_AGENT_COORD_BYPASS_STOP. */
65
33
  bypass?: boolean;
66
- /** Headless child spawned by `harn workflow` (HARNERY_WORKFLOW_CHILD=1).
67
- * The end-of-turn ritual exists to surface status to a HUMAN reader; a
68
- * workflow child reports to the engine's transcript instead, so the ritual is
69
- * meaningless there — worse, blocking burns the child's turn budget on
70
- * re-prompts (observed as error_max_turns in the Phase 1 spike). Exempting
71
- * here, rather than disabling the child's hooks wholesale, keeps heartbeat +
72
- * event capture on: the child stays visible to peers and the coord layer. */
73
34
  workflow_child?: boolean;
74
35
  }
75
36
 
76
- /**
77
- * The end-of-turn "I surfaced my status" signal, detected differently per
78
- * adapter because the ritual's *goal* (status visible to the human) is reached
79
- * by different means:
80
- *
81
- * - **Claude Code / Codex** collapse tool calls in the UI, so the human-visible
82
- * signal is the verbatim status-box paste in the reply (rule 2/3, detected
83
- * by scanning the transcript for the `┌─ agent-` prefix, `status_box_present`
84
- * on `turn.stop`).
85
- * - **Cursor** renders Shell output inline, so simply *running* `harn agents
86
- * status` (which emits `state.status_checked`) already puts the box on
87
- * screen. The separate paste is redundant, and undetectable anyway, since
88
- * Cursor's stop payload carries neither a transcript path nor the assistant
89
- * message. So rule 1/3 *is* the ack signal on Cursor; rule 2/3 collapses into
90
- * it rather than being a second requirement.
91
- *
92
- * This is why the fix is not "relax 2/3 because we can't see it": it's "2/3
93
- * and 1/3 are two detections of the same thing, and Cursor's inline UI makes
94
- * 1/3 the right one." The enforcement *channel* (exit-2+stderr vs Cursor's
95
- * `followup_message`) is handled separately in hooks/adapter/output.ts.
96
- */
97
- type AckSignal = "status_box_present" | "status_checked";
98
-
99
- function ackSignalFor(adapter?: string): AckSignal {
100
- return adapter === "cursor" ? "status_checked" : "status_box_present";
101
- }
102
-
103
- const RECENT_EVENT_WINDOW_LINES = 5_000;
104
-
105
- /**
106
- * Machine marker that opens a Stop-remediation message. Written by
107
- * `hooks/adapter/output.ts` on the adapters whose Stop channel is a new user
108
- * message, and read back here off `user_prompt.submit.data.prompt_text` to
109
- * recognize a turn Harnery itself opened.
110
- *
111
- * Leading position is deliberate: `prompt_text` is clamped when recorded, so a
112
- * trailing marker could be truncated away on a long reason.
113
- */
114
37
  export const STOP_REMEDIATION_MARKER = "[harnery:stop-remediation";
115
38
 
116
- /** How far back the remediation walk may travel. Adapters cap their own
117
- * followup chains (Cursor's `loop_limit`), and each prompt can appear once per
118
- * observing adapter, so this only guards a pathological stream. */
119
39
  const MAX_REMEDIATION_WALK_BACK = 16;
120
40
 
121
- function isStopRemediationPrompt(event: CanonicalEvent): boolean {
122
- const text = event.data.prompt_text;
123
- if (typeof text !== "string") return false;
124
- return text.includes(STOP_REMEDIATION_MARKER);
41
+ export function evaluateStopHook(coordRoot: string, req: StopHookRequest): VerdictResult {
42
+ const unconditional = unconditionalVerdict(req);
43
+ if (unconditional) return unconditional;
44
+
45
+ const control = readEventV3ControlState(coordRoot);
46
+ if (control.state !== "candidate" && control.state !== "active") {
47
+ return evidenceUnavailable(
48
+ `V3 control state is ${control.state}; authoritative Stop evidence is unavailable`,
49
+ );
50
+ }
51
+
52
+ try {
53
+ const ledger =
54
+ control.state === "candidate"
55
+ ? readLedgerV3(coordRoot, { authority: "candidate" })
56
+ : readLedgerV3(coordRoot);
57
+ if (!ledger.complete || ledger.diagnostics.length > 0) {
58
+ return evidenceUnavailable(
59
+ "V3 ledger integrity is not authority-safe; Stop remains fail-open",
60
+ );
61
+ }
62
+ return evaluateStopHookV3Events(
63
+ coordRoot,
64
+ req,
65
+ ledger.events.map(({ event }) => event),
66
+ );
67
+ } catch {
68
+ return evidenceUnavailable("V3 ledger is not readable; Stop remains fail-open");
69
+ }
125
70
  }
126
71
 
127
- /**
128
- * Start of the window this Stop verdict judges.
129
- *
130
- * Normally the most recent `user_prompt.submit`. The exception is the
131
- * remediation chain: Cursor does not continue a turn on Stop, it auto-submits
132
- * our message as a new user turn (`followup_message`). Scoping the verdict to
133
- * that new turn alone makes the ritual unsatisfiable in one pass, because
134
- * remediation itself runs a tool, so the repair turn needs every rule again
135
- * while the signal from the previous turn sits just outside the window. Running
136
- * `set-task` then clears rule 3/3 and fails rule 1/3, running `status` clears
137
- * 1/3 and fails 3/3, and the pair alternates until the adapter's loop cap.
138
- *
139
- * Anchoring the window at the last prompt Harnery did not author makes progress
140
- * monotonic: each repair adds a signal and none are lost, so the chain ends.
141
- * A genuine human prompt always anchors a fresh window, so this cannot be used
142
- * to inherit ritual credit across real turns.
143
- */
144
- function resolveTurnStartMs(
145
- ownerEvents: CanonicalEvent[],
146
- nowMs: number,
147
- fallbackMs: number,
148
- ): number {
149
- // Honor the cutoff: `now_ms` marks what is relevant yet, so a prompt after it
150
- // cannot anchor this turn. In production the cutoff is the clock and nothing
151
- // follows it, but a replay over recorded history passes a past cutoff, and an
152
- // unclamped search would anchor on a later turn and hand back an empty window.
153
- const prompts = ownerEvents.filter((e) => {
154
- if (e.event_type !== "user_prompt.submit") return false;
155
- const t = Date.parse(e.ts);
156
- return Number.isFinite(t) && t <= nowMs;
72
+ /** Pure V3 evaluator used by replay and regression tests after authority checks. */
73
+ export function evaluateStopHookV3Events(
74
+ coordRoot: string,
75
+ req: StopHookRequest,
76
+ events: readonly EventV3[],
77
+ ): VerdictResult {
78
+ const unconditional = unconditionalVerdict(req);
79
+ if (unconditional) return unconditional;
80
+
81
+ const owner = liveInstanceIdV3(req.instance_id);
82
+ const endMs = req.turn_window?.end_ms ?? req.now_ms ?? Date.now();
83
+ const currentTerminal = [...events]
84
+ .reverse()
85
+ .find(
86
+ (event) =>
87
+ event.event_type === "turn.completed" &&
88
+ event.scope.instance_id === owner &&
89
+ eventTimeMs(event) <= endMs,
90
+ );
91
+ if (currentTerminal?.event_type !== "turn.completed") {
92
+ return {
93
+ allow: true,
94
+ exit_code: 0,
95
+ rule: "stop-hook.no_history",
96
+ reason: "no authoritative V3 turn terminal for this owner; nothing to evaluate",
97
+ };
98
+ }
99
+
100
+ if (!("generation_id" in currentTerminal.scope)) {
101
+ return evidenceUnavailable("turn.completed has no V3 generation scope; Stop remains fail-open");
102
+ }
103
+ const generationId = currentTerminal.scope.generation_id;
104
+ const ownerEvents = events.filter(
105
+ (event) =>
106
+ event.scope.instance_id === owner &&
107
+ "generation_id" in event.scope &&
108
+ event.scope.generation_id === generationId,
109
+ );
110
+ const startMs =
111
+ req.turn_window?.start_ms ??
112
+ resolveTurnStartMs(ownerEvents, endMs, Math.max(0, endMs - 5 * 60 * 1000));
113
+ const inTurn = ownerEvents.filter((event) => {
114
+ const time = eventTimeMs(event);
115
+ return Number.isFinite(time) && time >= startMs && time <= endMs;
157
116
  });
158
- if (prompts.length === 0) return fallbackMs;
159
117
 
160
- let index = prompts.length - 1;
161
- let hops = 0;
162
- while (index > 0 && hops < MAX_REMEDIATION_WALK_BACK) {
163
- const prompt = prompts[index];
164
- if (!prompt || !isStopRemediationPrompt(prompt)) break;
165
- index -= 1;
166
- hops += 1;
118
+ const usedTool = inTurn.some((event) => event.event_type === "tool.requested");
119
+ const statusEvents = inTurn.filter(
120
+ (event) =>
121
+ event.event_type === "coord.status_observed" && event.payload.subject_instance_id === owner,
122
+ );
123
+ const statusChecked = statusEvents.length > 0;
124
+ const requiredStatusChecked = agentsRequireGitFinalization(coordRoot)
125
+ ? statusEvents.some(
126
+ (event) =>
127
+ event.event_type === "coord.status_observed" &&
128
+ event.payload.status === "end_turn_checked",
129
+ )
130
+ : statusChecked;
131
+ const taskSet = inTurn.some(
132
+ (event) =>
133
+ event.event_type === "coord.task_changed" && event.payload.subject_instance_id === owner,
134
+ );
135
+
136
+ const turnTerminals = inTurn.filter(
137
+ (event): event is Extract<EventV3, { event_type: "turn.completed" }> =>
138
+ event.event_type === "turn.completed",
139
+ );
140
+ const latestTerminal = turnTerminals.at(-1);
141
+ const latestBox = observedBoolean(latestTerminal?.payload.ritual?.status_box_present);
142
+ if (req.adapter !== "cursor" && latestBox === undefined) {
143
+ return evidenceUnavailable(
144
+ "turn.completed has no observed V3 status-box result; Stop remains fail-open",
145
+ );
167
146
  }
168
147
 
169
- const anchor = prompts[index];
170
- const parsed = anchor ? Date.parse(anchor.ts) : Number.NaN;
171
- return Number.isFinite(parsed) ? parsed : fallbackMs;
148
+ const ackPresent = req.adapter === "cursor" ? requiredStatusChecked : latestBox === true;
149
+ const ackBlock =
150
+ req.adapter === "cursor" ? () => rule13Block(coordRoot) : () => rule23Block(coordRoot);
151
+
152
+ // Pure prose turns keep the human-visible acknowledgement but do not require
153
+ // task or final-status mutations. This preserves the pre-V3 policy exactly.
154
+ if (!usedTool) {
155
+ if (!ackPresent) return ackBlock();
156
+ return {
157
+ allow: true,
158
+ exit_code: 0,
159
+ rule: "stop-hook.pure_prose_pass",
160
+ reason: "no tool calls this turn; task and final-status mutations were not required",
161
+ };
162
+ }
163
+
164
+ if (!requiredStatusChecked) return rule13Block(coordRoot);
165
+ if (!ackPresent) return ackBlock();
166
+ if (!taskSet) return rule33Block(coordRoot);
167
+
168
+ // Claude Code can prove session-name presentation from assistant-only text.
169
+ // Cursor cannot expose that text and Codex returned observe-only above.
170
+ if (req.adapter !== "cursor") {
171
+ const naming = turnTerminals
172
+ .map((event) => observedNaming(event.payload.ritual?.session_name))
173
+ .filter((value): value is { required: boolean; present: boolean } => value !== undefined);
174
+ if (
175
+ naming.some(({ required }) => required) &&
176
+ !naming.some(({ present }) => present) &&
177
+ !sessionNameSeenStamped(coordRoot, req.instance_id)
178
+ ) {
179
+ return sessionNameBlock(coordRoot, req.instance_id);
180
+ }
181
+ }
182
+
183
+ return { allow: true, exit_code: 0, rule: "stop-hook.pass" };
172
184
  }
173
185
 
174
- /**
175
- * Evaluate the Stop-hook verdict. Returns the first failing rule (or allow
176
- * when all three pass). Soft-fails open when the event stream isn't
177
- * readable, fail-open posture on a verdict failure.
178
- */
179
- export function evaluateStopHook(coordRoot: string, req: StopHookRequest): VerdictResult {
186
+ function unconditionalVerdict(req: StopHookRequest): VerdictResult | undefined {
180
187
  if (req.bypass) {
181
188
  return {
182
189
  allow: true,
@@ -185,7 +192,6 @@ export function evaluateStopHook(coordRoot: string, req: StopHookRequest): Verdi
185
192
  reason: "HARNERY_AGENT_COORD_BYPASS_STOP=1",
186
193
  };
187
194
  }
188
-
189
195
  if (req.workflow_child) {
190
196
  return {
191
197
  allow: true,
@@ -194,13 +200,6 @@ export function evaluateStopHook(coordRoot: string, req: StopHookRequest): Verdi
194
200
  reason: "HARNERY_WORKFLOW_CHILD=1: headless workflow child; ritual not applicable",
195
201
  };
196
202
  }
197
-
198
- // A blocked Codex Stop does not reject the turn. Codex creates a continuation
199
- // prompt and asks the model to answer again. Some clients retain only that
200
- // final continuation response, so a coordination reminder can replace a
201
- // correct user-facing answer with status output. The Stop path has already
202
- // emitted turn.stop and projected the coordination state before this verdict
203
- // runs. Keep that telemetry, but never continue a Codex turn for this ritual.
204
203
  if (req.adapter === "codex") {
205
204
  return {
206
205
  allow: true,
@@ -209,137 +208,73 @@ export function evaluateStopHook(coordRoot: string, req: StopHookRequest): Verdi
209
208
  reason: "Codex Stop continuations must not replace the user-facing answer",
210
209
  };
211
210
  }
211
+ return undefined;
212
+ }
212
213
 
213
- let events: CanonicalEvent[];
214
- try {
215
- events = readRecentEvents(coordRoot, RECENT_EVENT_WINDOW_LINES);
216
- } catch {
217
- return {
218
- allow: true,
219
- exit_code: 0,
220
- rule: "stop-hook.fail_open",
221
- reason: "events.ndjson not readable; failing open",
222
- };
223
- }
214
+ function resolveTurnStartMs(
215
+ ownerEvents: readonly EventV3[],
216
+ nowMs: number,
217
+ fallbackMs: number,
218
+ ): number {
219
+ const turns = ownerEvents.filter(
220
+ (event): event is Extract<EventV3, { event_type: "turn.started" }> =>
221
+ event.event_type === "turn.started" && eventTimeMs(event) <= nowMs,
222
+ );
223
+ if (turns.length === 0) return fallbackMs;
224
224
 
225
- const ownerEvents = events.filter((e) => e.instance_id === req.instance_id);
226
- if (ownerEvents.length === 0) {
227
- return {
228
- allow: true,
229
- exit_code: 0,
230
- rule: "stop-hook.no_history",
231
- reason: "no canonical events for this owner; nothing to evaluate",
232
- };
225
+ let index = turns.length - 1;
226
+ let hops = 0;
227
+ while (index > 0 && hops < MAX_REMEDIATION_WALK_BACK) {
228
+ const turn = turns[index];
229
+ if (!turn?.payload.stop_remediation) break;
230
+ index -= 1;
231
+ hops += 1;
233
232
  }
233
+ return eventTimeMs(turns[index]!);
234
+ }
234
235
 
235
- // Turn window: from the most recent user_prompt.submit (this owner) up to
236
- // either the explicit now_ms or the last event we see, walking back over any
237
- // Harnery-authored Stop-remediation prompts so a repair turn is judged
238
- // together with the turn it repairs (see `resolveTurnStartMs`). Fall back to a
239
- // 5-minute window when no user_prompt.submit is in scope (fresh wiring,
240
- // out-of-window prompt, etc.) so a stale stream doesn't silently pass.
241
- const nowMs = req.now_ms ?? Date.now();
242
- const startMs = req.turn_window
243
- ? req.turn_window.start_ms
244
- : resolveTurnStartMs(ownerEvents, nowMs, nowMs - 5 * 60 * 1000);
245
- const endMs = req.turn_window ? req.turn_window.end_ms : nowMs;
246
-
247
- const inTurn = ownerEvents.filter((e) => {
248
- const t = Date.parse(e.ts);
249
- return Number.isFinite(t) && t >= startMs && t <= endMs;
250
- });
251
-
252
- const toolPreUseInTurn = inTurn.some((e) => e.event_type === "tool.pre_use");
253
- const statusEvents = inTurn.filter((e) => e.event_type === "state.status_checked");
254
- const statusChecked = statusEvents.length > 0;
255
- const requiredStatusChecked = agentsRequireGitFinalization(coordRoot)
256
- ? statusEvents.some((e) => e.data.git_finalization_checked === true)
257
- : statusChecked;
258
- const taskSet = inTurn.some((e) => e.event_type === "state.task_set");
259
-
260
- // Rule 2/3: status_box_present on the most recent turn.stop for this owner.
261
- // The turn.stop event fires from agent-hook stop, which is wired BEFORE
262
- // the stop-hook verdict, so by the time we evaluate, the just-
263
- // fired turn.stop is already in the stream.
264
- const latestTurnStop = [...inTurn].reverse().find((e) => e.event_type === "turn.stop");
265
- const boxPresent = latestTurnStop ? Boolean(latestTurnStop.data.status_box_present) : false;
266
-
267
- // Adapter-aware end-of-turn ack signal (see `ackSignalFor`). On Cursor the
268
- // ack is `status_checked` (running `harn agents status` shows the box inline);
269
- // on Claude Code / Codex it's the transcript-scanned `status_box_present`.
270
- // The matching block helper carries the right "how to fix" message.
271
- const ackSignal = ackSignalFor(req.adapter);
272
- const ackPresent =
273
- ackSignal === "status_checked"
274
- ? toolPreUseInTurn
275
- ? requiredStatusChecked
276
- : statusChecked
277
- : boxPresent;
278
- const ackBlock =
279
- ackSignal === "status_checked" ? () => rule13Block(coordRoot) : () => rule23Block(coordRoot);
280
-
281
- // Pure-prose-turn exemption: only the ack signal applies. Parity
282
- // across adapters: CC requires the box; Cursor requires status_checked.
283
- if (!toolPreUseInTurn) {
284
- if (!ackPresent) {
285
- return ackBlock();
286
- }
287
- return {
288
- allow: true,
289
- exit_code: 0,
290
- rule: "stop-hook.pure_prose_pass",
291
- reason: "no tool calls this turn; rules 1/3 + 3/3 skipped",
292
- };
293
- }
236
+ function eventTimeMs(event: EventV3): number {
237
+ return Date.parse(event.time.observed_at);
238
+ }
294
239
 
295
- if (!requiredStatusChecked) return rule13Block(coordRoot);
296
- // On Cursor `ackPresent === statusChecked` (already true here), so this is a
297
- // no-op and rule 2/3 is not enforced; on CC/Codex it's the box-paste check.
298
- if (!ackPresent) return ackBlock();
299
- if (!taskSet) return rule33Block();
240
+ function observedBoolean(value: unknown): boolean | undefined {
241
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
242
+ const observation = value as { state?: unknown; value?: unknown };
243
+ return observation.state === "observed" && typeof observation.value === "boolean"
244
+ ? observation.value
245
+ : undefined;
246
+ }
300
247
 
301
- // Session-naming rule (at most once per session, evaluated last so
302
- // remediation stays monotonic in the established order): the turn whose
303
- // set-task produced the suggested session name must show that name in the
304
- // reply. Claude Code semantics only — Cursor's Stop payload carries neither
305
- // a transcript nor the assistant message, so the fence is undetectable there
306
- // (the same reason rule 2/3 collapses into 1/3 on Cursor), and Codex
307
- // returned observe-only above. Detection rides `turn.stop`'s
308
- // `session_name_present` (assistant-text-only transcript scan), mirroring
309
- // how rule 2/3 rides `status_box_present`.
310
- if (ackSignal === "status_box_present") {
311
- const namingEvent = inTurn.find(
312
- (e) =>
313
- ((e.event_type === "state.task_set" && e.data.first_of_session === true) ||
314
- (e.event_type === "state.task_state" && e.data.name_reminted === true)) &&
315
- typeof e.data.suggested_session_name === "string" &&
316
- e.data.suggested_session_name.length > 0,
317
- );
318
- if (namingEvent) {
319
- // ANY in-window turn.stop with the flag satisfies the rule, not just the
320
- // latest: once seen, `session_name_seen_at` is stamped and later
321
- // turn.stops OMIT the field, so a later block on a different rule (whose
322
- // repair fires a fresh turn.stop) must not un-satisfy this one.
323
- const namePresent = inTurn.some(
324
- (e) => e.event_type === "turn.stop" && e.data.session_name_present === true,
325
- );
326
- if (!namePresent) {
327
- return sessionNameBlock(String(namingEvent.data.suggested_session_name));
328
- }
329
- }
248
+ function observedNaming(value: unknown): { required: boolean; present: boolean } | undefined {
249
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
250
+ const observation = value as { state?: unknown; value?: unknown };
251
+ if (
252
+ observation.state !== "observed" ||
253
+ !observation.value ||
254
+ typeof observation.value !== "object" ||
255
+ Array.isArray(observation.value)
256
+ ) {
257
+ return undefined;
330
258
  }
259
+ const naming = observation.value as { required?: unknown; present?: unknown };
260
+ return typeof naming.required === "boolean" && typeof naming.present === "boolean"
261
+ ? { required: naming.required, present: naming.present }
262
+ : undefined;
263
+ }
331
264
 
265
+ function evidenceUnavailable(reason: string): VerdictResult {
332
266
  return {
333
267
  allow: true,
334
268
  exit_code: 0,
335
- rule: "stop-hook.pass",
269
+ rule: "stop-hook.v3_evidence_unavailable",
270
+ reason,
336
271
  };
337
272
  }
338
273
 
339
274
  function rule13Block(coordRoot?: string): VerdictResult {
340
275
  const missingEvidence = agentsRequireGitFinalization(coordRoot)
341
- ? "no Git-finalized state.status_checked event found in this turn"
342
- : "no state.status_checked event found in this turn";
276
+ ? "no end_turn_checked coord.status_observed event found in this turn"
277
+ : "no coord.status_observed event found in this turn";
343
278
  return {
344
279
  allow: false,
345
280
  exit_code: 2,
@@ -353,47 +288,58 @@ function rule23Block(coordRoot?: string): VerdictResult {
353
288
  allow: false,
354
289
  exit_code: 2,
355
290
  rule: "stop-hook.rule_2_3",
356
- reason: `End-of-turn rule (2/3): turn.stop did not see the agent-status box in your reply text. Paste the \`${endOfTurnStatusCommand(coordRoot)}\` output verbatim as a fenced code block (the \`┌─ agent-\` prefix is the detection signal).`,
291
+ reason: `End-of-turn rule (2/3): turn.completed did not observe the agent-status box in your reply text. Paste the \`${endOfTurnStatusCommand(coordRoot)}\` output verbatim as a fenced code block (the \`┌─ agent-\` prefix is the detection signal).`,
357
292
  };
358
293
  }
359
294
 
360
- function sessionNameBlock(name: string): VerdictResult {
295
+ /**
296
+ * A remediation stop cannot land a fresh `turn.completed`: the turn's first
297
+ * stop closed the turn span, continuations open no new one, so the recorder
298
+ * ignores their terminals. When that first terminal lost the transcript flush
299
+ * race and recorded `present: false`, in-window ledger evidence can never
300
+ * change, and the naming rule blocked every retry forever. The sighting stamp
301
+ * on the live coordination row is the durable record that the current
302
+ * suggested name WAS shown in an assistant reply (session-name-presence.ts
303
+ * writes it on the first sighting), so honor it before blocking. Ledger
304
+ * evidence stays primary; the stamp is consulted only after in-window
305
+ * terminals said absent.
306
+ */
307
+ function sessionNameSeenStamped(coordRoot: string, instanceId: string): boolean {
308
+ try {
309
+ const row = readLiveCoordinationRow(coordRoot, instanceId);
310
+ return (
311
+ typeof row?.suggested_session_name === "string" &&
312
+ row.suggested_session_name.length > 0 &&
313
+ row.session_name_seen_for === row.suggested_session_name
314
+ );
315
+ } catch {
316
+ return false;
317
+ }
318
+ }
319
+
320
+ function sessionNameBlock(coordRoot: string, instanceId: string): VerdictResult {
321
+ let name: string | undefined;
322
+ try {
323
+ name = readLiveCoordinationRow(coordRoot, instanceId)?.suggested_session_name;
324
+ } catch {
325
+ // The ledger evidence still authorizes the block; the cache is UX only.
326
+ }
327
+ const instruction = name
328
+ ? `Reproduce it by itself in a fenced code block: ${name}`
329
+ : `Run \`${resolveBinName(coordRoot)} agents suggest-name\`, then reproduce its output by itself in a fenced code block.`;
361
330
  return {
362
331
  allow: false,
363
332
  exit_code: 2,
364
333
  rule: "stop-hook.session_name",
365
- reason: `Session-naming rule: this turn's set-task produced the session name, but your reply doesn't show it. Reproduce it by itself in a fenced code block so the operator can one-click-copy it as the session/tab title: ${name}`,
334
+ reason: `Session-naming rule: V3 observed that the current session name was required but absent. ${instruction}`,
366
335
  };
367
336
  }
368
337
 
369
- function rule33Block(): VerdictResult {
338
+ function rule33Block(coordRoot?: string): VerdictResult {
370
339
  return {
371
340
  allow: false,
372
341
  exit_code: 2,
373
342
  rule: "stop-hook.rule_3_3",
374
- reason: `End-of-turn rule (3/3): no state.task_set event found in this turn; run \`${resolveBinName()} agents set-task "<short focus>"\` to declare what you're working on. Pass an empty string if the turn was purely conversational.`,
343
+ reason: `End-of-turn rule (3/3): no coord.task_changed event found in this turn; run \`${resolveBinName(coordRoot)} agents set-task "<short focus>"\` to declare what you're working on. Pass an empty string if the turn was purely conversational.`,
375
344
  };
376
345
  }
377
-
378
- /**
379
- * Read the last N JSON lines from events.ndjson. The Stop-hook verdict needs
380
- * just the most recent turn so we tail-read aggressively.
381
- */
382
- function readRecentEvents(coordRoot: string, maxLines: number): CanonicalEvent[] {
383
- const path = join(coordRoot, ".harnery", "events.ndjson");
384
- if (!existsSync(path)) return [];
385
- const raw = readFileSync(path, "utf8");
386
- const lines = raw.split("\n");
387
- const start = Math.max(0, lines.length - maxLines);
388
- const out: CanonicalEvent[] = [];
389
- for (let i = start; i < lines.length; i++) {
390
- const line = lines[i];
391
- if (!line?.trim()) continue;
392
- try {
393
- out.push(JSON.parse(line) as CanonicalEvent);
394
- } catch {
395
- /* skip malformed */
396
- }
397
- }
398
- return out;
399
- }