harnery 0.0.1 → 0.2.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 (445) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/bin/agent-coord +42 -0
  4. package/bin/agent-hook +44 -0
  5. package/bin/harn +40 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +18 -0
  9. package/dist/commander.d.ts +128 -0
  10. package/dist/commander.d.ts.map +1 -0
  11. package/dist/commander.js +126 -0
  12. package/dist/commands/agents.d.ts +18 -0
  13. package/dist/commands/agents.d.ts.map +1 -0
  14. package/dist/commands/agents.js +3946 -0
  15. package/dist/commands/backup.d.ts +22 -0
  16. package/dist/commands/backup.d.ts.map +1 -0
  17. package/dist/commands/backup.js +262 -0
  18. package/dist/commands/browse-ai.d.ts +4 -0
  19. package/dist/commands/browse-ai.d.ts.map +1 -0
  20. package/dist/commands/browse-ai.js +156 -0
  21. package/dist/commands/browse.d.ts +4 -0
  22. package/dist/commands/browse.d.ts.map +1 -0
  23. package/dist/commands/browse.js +590 -0
  24. package/dist/commands/callers.d.ts +4 -0
  25. package/dist/commands/callers.d.ts.map +1 -0
  26. package/dist/commands/callers.js +276 -0
  27. package/dist/commands/completion.d.ts +17 -0
  28. package/dist/commands/completion.d.ts.map +1 -0
  29. package/dist/commands/completion.js +158 -0
  30. package/dist/commands/config-get.d.ts +4 -0
  31. package/dist/commands/config-get.d.ts.map +1 -0
  32. package/dist/commands/config-get.js +131 -0
  33. package/dist/commands/context.d.ts +11 -0
  34. package/dist/commands/context.d.ts.map +1 -0
  35. package/dist/commands/context.js +185 -0
  36. package/dist/commands/cookies.d.ts +4 -0
  37. package/dist/commands/cookies.d.ts.map +1 -0
  38. package/dist/commands/cookies.js +140 -0
  39. package/dist/commands/docs.d.ts +4 -0
  40. package/dist/commands/docs.d.ts.map +1 -0
  41. package/dist/commands/docs.js +137 -0
  42. package/dist/commands/doctor.d.ts +25 -0
  43. package/dist/commands/doctor.d.ts.map +1 -0
  44. package/dist/commands/doctor.js +200 -0
  45. package/dist/commands/edit-batch.d.ts +18 -0
  46. package/dist/commands/edit-batch.d.ts.map +1 -0
  47. package/dist/commands/edit-batch.js +172 -0
  48. package/dist/commands/eml.d.ts +4 -0
  49. package/dist/commands/eml.d.ts.map +1 -0
  50. package/dist/commands/eml.js +428 -0
  51. package/dist/commands/env.d.ts +4 -0
  52. package/dist/commands/env.d.ts.map +1 -0
  53. package/dist/commands/env.js +201 -0
  54. package/dist/commands/fetch.d.ts +4 -0
  55. package/dist/commands/fetch.d.ts.map +1 -0
  56. package/dist/commands/fetch.js +99 -0
  57. package/dist/commands/file-history.d.ts +4 -0
  58. package/dist/commands/file-history.d.ts.map +1 -0
  59. package/dist/commands/file-history.js +152 -0
  60. package/dist/commands/grep.d.ts +4 -0
  61. package/dist/commands/grep.d.ts.map +1 -0
  62. package/dist/commands/grep.js +317 -0
  63. package/dist/commands/init.d.ts +82 -0
  64. package/dist/commands/init.d.ts.map +1 -0
  65. package/dist/commands/init.js +288 -0
  66. package/dist/commands/outline.d.ts +4 -0
  67. package/dist/commands/outline.d.ts.map +1 -0
  68. package/dist/commands/outline.js +494 -0
  69. package/dist/commands/presence.d.ts +12 -0
  70. package/dist/commands/presence.d.ts.map +1 -0
  71. package/dist/commands/presence.js +123 -0
  72. package/dist/commands/read.d.ts +7 -0
  73. package/dist/commands/read.d.ts.map +1 -0
  74. package/dist/commands/read.js +46 -0
  75. package/dist/commands/scratch.d.ts +4 -0
  76. package/dist/commands/scratch.d.ts.map +1 -0
  77. package/dist/commands/scratch.js +426 -0
  78. package/dist/commands/session.d.ts +4 -0
  79. package/dist/commands/session.d.ts.map +1 -0
  80. package/dist/commands/session.js +162 -0
  81. package/dist/commands/sync.d.ts +24 -0
  82. package/dist/commands/sync.d.ts.map +1 -0
  83. package/dist/commands/sync.js +275 -0
  84. package/dist/commands/toc.d.ts +5 -0
  85. package/dist/commands/toc.d.ts.map +1 -0
  86. package/dist/commands/toc.js +153 -0
  87. package/dist/commands/tokens.d.ts +4 -0
  88. package/dist/commands/tokens.d.ts.map +1 -0
  89. package/dist/commands/tokens.js +48 -0
  90. package/dist/commands/tunnel.d.ts +4 -0
  91. package/dist/commands/tunnel.d.ts.map +1 -0
  92. package/dist/commands/tunnel.js +513 -0
  93. package/dist/commands/uninstall.d.ts +22 -0
  94. package/dist/commands/uninstall.d.ts.map +1 -0
  95. package/dist/commands/uninstall.js +126 -0
  96. package/dist/commands/web.d.ts +4 -0
  97. package/dist/commands/web.d.ts.map +1 -0
  98. package/dist/commands/web.js +165 -0
  99. package/dist/core/agents/canonical-emit.d.ts +27 -0
  100. package/dist/core/agents/canonical-emit.d.ts.map +1 -0
  101. package/dist/core/agents/canonical-emit.js +72 -0
  102. package/dist/core/agents/cli-emit.d.ts +27 -0
  103. package/dist/core/agents/cli-emit.d.ts.map +1 -0
  104. package/dist/core/agents/cli-emit.js +57 -0
  105. package/dist/core/agents/cli.d.ts +10 -0
  106. package/dist/core/agents/cli.d.ts.map +1 -0
  107. package/dist/core/agents/cli.js +757 -0
  108. package/dist/core/agents/codex-replay.d.ts +29 -0
  109. package/dist/core/agents/codex-replay.d.ts.map +1 -0
  110. package/dist/core/agents/codex-replay.js +138 -0
  111. package/dist/core/agents/coord-client.d.ts +98 -0
  112. package/dist/core/agents/coord-client.d.ts.map +1 -0
  113. package/dist/core/agents/coord-client.js +212 -0
  114. package/dist/core/agents/events/consume.d.ts +59 -0
  115. package/dist/core/agents/events/consume.d.ts.map +1 -0
  116. package/dist/core/agents/events/consume.js +147 -0
  117. package/dist/core/agents/events/emit.d.ts +42 -0
  118. package/dist/core/agents/events/emit.d.ts.map +1 -0
  119. package/dist/core/agents/events/emit.js +70 -0
  120. package/dist/core/agents/events/ulid.d.ts +11 -0
  121. package/dist/core/agents/events/ulid.d.ts.map +1 -0
  122. package/dist/core/agents/events/ulid.js +47 -0
  123. package/dist/core/agents/index.d.ts +14 -0
  124. package/dist/core/agents/index.d.ts.map +1 -0
  125. package/dist/core/agents/index.js +13 -0
  126. package/dist/core/agents/paths.d.ts +6 -0
  127. package/dist/core/agents/paths.d.ts.map +1 -0
  128. package/dist/core/agents/paths.js +17 -0
  129. package/dist/core/agents/render/prompt-context.d.ts +43 -0
  130. package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
  131. package/dist/core/agents/render/prompt-context.js +335 -0
  132. package/dist/core/agents/render/session-context.d.ts +39 -0
  133. package/dist/core/agents/render/session-context.d.ts.map +1 -0
  134. package/dist/core/agents/render/session-context.js +283 -0
  135. package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
  136. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
  137. package/dist/core/agents/rules/claim-conflict.js +244 -0
  138. package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
  139. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
  140. package/dist/core/agents/rules/commit-conflict.js +244 -0
  141. package/dist/core/agents/rules/stop-hook.d.ts +44 -0
  142. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
  143. package/dist/core/agents/rules/stop-hook.js +161 -0
  144. package/dist/core/agents/session-events.d.ts +41 -0
  145. package/dist/core/agents/session-events.d.ts.map +1 -0
  146. package/dist/core/agents/session-events.js +205 -0
  147. package/dist/core/agents/state/activity-log.d.ts +18 -0
  148. package/dist/core/agents/state/activity-log.d.ts.map +1 -0
  149. package/dist/core/agents/state/activity-log.js +34 -0
  150. package/dist/core/agents/state/council.d.ts +39 -0
  151. package/dist/core/agents/state/council.d.ts.map +1 -0
  152. package/dist/core/agents/state/council.js +216 -0
  153. package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
  154. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
  155. package/dist/core/agents/state/heartbeat-projector.js +436 -0
  156. package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
  157. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
  158. package/dist/core/agents/state/heartbeat-writer.js +271 -0
  159. package/dist/core/agents/state/names.d.ts +35 -0
  160. package/dist/core/agents/state/names.d.ts.map +1 -0
  161. package/dist/core/agents/state/names.js +376 -0
  162. package/dist/core/agents/state/pidmap.d.ts +11 -0
  163. package/dist/core/agents/state/pidmap.d.ts.map +1 -0
  164. package/dist/core/agents/state/pidmap.js +32 -0
  165. package/dist/core/agents/state/scratch.d.ts +27 -0
  166. package/dist/core/agents/state/scratch.d.ts.map +1 -0
  167. package/dist/core/agents/state/scratch.js +90 -0
  168. package/dist/core/agents/state/shell-mutation.d.ts +17 -0
  169. package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
  170. package/dist/core/agents/state/shell-mutation.js +41 -0
  171. package/dist/core/agents/state/stale-sweep.d.ts +16 -0
  172. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
  173. package/dist/core/agents/state/stale-sweep.js +166 -0
  174. package/dist/core/config.d.ts +29 -0
  175. package/dist/core/config.d.ts.map +1 -0
  176. package/dist/core/config.js +108 -0
  177. package/dist/core/hooks/cli.d.ts +21 -0
  178. package/dist/core/hooks/cli.d.ts.map +1 -0
  179. package/dist/core/hooks/cli.js +1123 -0
  180. package/dist/core/hooks/effects/image-capture.d.ts +43 -0
  181. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
  182. package/dist/core/hooks/effects/image-capture.js +288 -0
  183. package/dist/core/hooks/effects/index.d.ts +64 -0
  184. package/dist/core/hooks/effects/index.d.ts.map +1 -0
  185. package/dist/core/hooks/effects/index.js +197 -0
  186. package/dist/core/hooks/events/emit.d.ts +31 -0
  187. package/dist/core/hooks/events/emit.d.ts.map +1 -0
  188. package/dist/core/hooks/events/emit.js +89 -0
  189. package/dist/core/hooks/events/schema.d.ts +235 -0
  190. package/dist/core/hooks/events/schema.d.ts.map +1 -0
  191. package/dist/core/hooks/events/schema.js +12 -0
  192. package/dist/core/hooks/events/ulid.d.ts +10 -0
  193. package/dist/core/hooks/events/ulid.d.ts.map +1 -0
  194. package/dist/core/hooks/events/ulid.js +47 -0
  195. package/dist/core/hooks/harness/detect.d.ts +9 -0
  196. package/dist/core/hooks/harness/detect.d.ts.map +1 -0
  197. package/dist/core/hooks/harness/detect.js +29 -0
  198. package/dist/core/hooks/harness/events.d.ts +45 -0
  199. package/dist/core/hooks/harness/events.d.ts.map +1 -0
  200. package/dist/core/hooks/harness/events.js +71 -0
  201. package/dist/core/hooks/harness/output.d.ts +46 -0
  202. package/dist/core/hooks/harness/output.d.ts.map +1 -0
  203. package/dist/core/hooks/harness/output.js +87 -0
  204. package/dist/core/hooks/harness/parse.d.ts +67 -0
  205. package/dist/core/hooks/harness/parse.d.ts.map +1 -0
  206. package/dist/core/hooks/harness/parse.js +132 -0
  207. package/dist/core/hooks/index.d.ts +8 -0
  208. package/dist/core/hooks/index.d.ts.map +1 -0
  209. package/dist/core/hooks/index.js +7 -0
  210. package/dist/core/hooks/resolve/anchor.d.ts +37 -0
  211. package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
  212. package/dist/core/hooks/resolve/anchor.js +48 -0
  213. package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
  214. package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
  215. package/dist/core/hooks/resolve/coord-root.js +27 -0
  216. package/dist/core/hooks/resolve/intent.d.ts +33 -0
  217. package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
  218. package/dist/core/hooks/resolve/intent.js +79 -0
  219. package/dist/core/hooks/resolve/owner.d.ts +42 -0
  220. package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
  221. package/dist/core/hooks/resolve/owner.js +140 -0
  222. package/dist/core/hooks/resolve/transcript.d.ts +26 -0
  223. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
  224. package/dist/core/hooks/resolve/transcript.js +73 -0
  225. package/dist/index.d.ts +15 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +13 -0
  228. package/dist/lib/agent-browser/client.d.ts +99 -0
  229. package/dist/lib/agent-browser/client.d.ts.map +1 -0
  230. package/dist/lib/agent-browser/client.js +177 -0
  231. package/dist/lib/agent-browser/index.d.ts +2 -0
  232. package/dist/lib/agent-browser/index.d.ts.map +1 -0
  233. package/dist/lib/agent-browser/index.js +1 -0
  234. package/dist/lib/browser/client.d.ts +193 -0
  235. package/dist/lib/browser/client.d.ts.map +1 -0
  236. package/dist/lib/browser/client.js +325 -0
  237. package/dist/lib/browser/dev-overlay.d.ts +23 -0
  238. package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
  239. package/dist/lib/browser/dev-overlay.js +153 -0
  240. package/dist/lib/browser/index.d.ts +5 -0
  241. package/dist/lib/browser/index.d.ts.map +1 -0
  242. package/dist/lib/browser/index.js +2 -0
  243. package/dist/lib/browser/layout.d.ts +79 -0
  244. package/dist/lib/browser/layout.d.ts.map +1 -0
  245. package/dist/lib/browser/layout.js +220 -0
  246. package/dist/lib/browser/visibility.d.ts +86 -0
  247. package/dist/lib/browser/visibility.d.ts.map +1 -0
  248. package/dist/lib/browser/visibility.js +333 -0
  249. package/dist/lib/browser/visual-diff.d.ts +38 -0
  250. package/dist/lib/browser/visual-diff.d.ts.map +1 -0
  251. package/dist/lib/browser/visual-diff.js +107 -0
  252. package/dist/lib/completion/bash.d.ts +25 -0
  253. package/dist/lib/completion/bash.d.ts.map +1 -0
  254. package/dist/lib/completion/bash.js +284 -0
  255. package/dist/lib/completion/fish.d.ts +16 -0
  256. package/dist/lib/completion/fish.d.ts.map +1 -0
  257. package/dist/lib/completion/fish.js +118 -0
  258. package/dist/lib/completion/index.d.ts +5 -0
  259. package/dist/lib/completion/index.d.ts.map +1 -0
  260. package/dist/lib/completion/index.js +4 -0
  261. package/dist/lib/completion/walk.d.ts +68 -0
  262. package/dist/lib/completion/walk.d.ts.map +1 -0
  263. package/dist/lib/completion/walk.js +102 -0
  264. package/dist/lib/completion/zsh.d.ts +13 -0
  265. package/dist/lib/completion/zsh.d.ts.map +1 -0
  266. package/dist/lib/completion/zsh.js +249 -0
  267. package/dist/lib/context/index.d.ts +107 -0
  268. package/dist/lib/context/index.d.ts.map +1 -0
  269. package/dist/lib/context/index.js +275 -0
  270. package/dist/lib/cookies/client.d.ts +131 -0
  271. package/dist/lib/cookies/client.d.ts.map +1 -0
  272. package/dist/lib/cookies/client.js +239 -0
  273. package/dist/lib/cookies/index.d.ts +2 -0
  274. package/dist/lib/cookies/index.d.ts.map +1 -0
  275. package/dist/lib/cookies/index.js +1 -0
  276. package/dist/lib/council/index.d.ts +266 -0
  277. package/dist/lib/council/index.d.ts.map +1 -0
  278. package/dist/lib/council/index.js +674 -0
  279. package/dist/lib/docs-index.d.ts +28 -0
  280. package/dist/lib/docs-index.d.ts.map +1 -0
  281. package/dist/lib/docs-index.js +169 -0
  282. package/dist/lib/docs-lint.d.ts +26 -0
  283. package/dist/lib/docs-lint.d.ts.map +1 -0
  284. package/dist/lib/docs-lint.js +378 -0
  285. package/dist/lib/docs-sweep.d.ts +34 -0
  286. package/dist/lib/docs-sweep.d.ts.map +1 -0
  287. package/dist/lib/docs-sweep.js +304 -0
  288. package/dist/lib/docs.d.ts +27 -0
  289. package/dist/lib/docs.d.ts.map +1 -0
  290. package/dist/lib/docs.js +142 -0
  291. package/dist/lib/env.d.ts +11 -0
  292. package/dist/lib/env.d.ts.map +1 -0
  293. package/dist/lib/env.js +12 -0
  294. package/dist/lib/exec.d.ts +32 -0
  295. package/dist/lib/exec.d.ts.map +1 -0
  296. package/dist/lib/exec.js +54 -0
  297. package/dist/lib/format.d.ts +29 -0
  298. package/dist/lib/format.d.ts.map +1 -0
  299. package/dist/lib/format.js +139 -0
  300. package/dist/lib/http/client.d.ts +56 -0
  301. package/dist/lib/http/client.d.ts.map +1 -0
  302. package/dist/lib/http/client.js +160 -0
  303. package/dist/lib/http/index.d.ts +2 -0
  304. package/dist/lib/http/index.d.ts.map +1 -0
  305. package/dist/lib/http/index.js +1 -0
  306. package/dist/lib/identities/index.d.ts +77 -0
  307. package/dist/lib/identities/index.d.ts.map +1 -0
  308. package/dist/lib/identities/index.js +190 -0
  309. package/dist/lib/machine.d.ts +19 -0
  310. package/dist/lib/machine.d.ts.map +1 -0
  311. package/dist/lib/machine.js +61 -0
  312. package/dist/lib/presence.d.ts +48 -0
  313. package/dist/lib/presence.d.ts.map +1 -0
  314. package/dist/lib/presence.js +123 -0
  315. package/dist/lib/readability/client.d.ts +32 -0
  316. package/dist/lib/readability/client.d.ts.map +1 -0
  317. package/dist/lib/readability/client.js +119 -0
  318. package/dist/lib/readability/index.d.ts +2 -0
  319. package/dist/lib/readability/index.d.ts.map +1 -0
  320. package/dist/lib/readability/index.js +1 -0
  321. package/dist/lib/scratch/index.d.ts +74 -0
  322. package/dist/lib/scratch/index.d.ts.map +1 -0
  323. package/dist/lib/scratch/index.js +393 -0
  324. package/dist/lib/tunnel/gate.d.ts +12 -0
  325. package/dist/lib/tunnel/gate.d.ts.map +1 -0
  326. package/dist/lib/tunnel/gate.js +101 -0
  327. package/dist/lib/tunnel/state.d.ts +34 -0
  328. package/dist/lib/tunnel/state.d.ts.map +1 -0
  329. package/dist/lib/tunnel/state.js +132 -0
  330. package/package.json +160 -8
  331. package/schemas/.gitkeep +0 -0
  332. package/schemas/config.schema.json +109 -0
  333. package/src/cli.ts +22 -0
  334. package/src/commander.ts +242 -0
  335. package/src/commands/.gitkeep +0 -0
  336. package/src/commands/agents.ts +4567 -0
  337. package/src/commands/backup.ts +305 -0
  338. package/src/commands/browse-ai.ts +198 -0
  339. package/src/commands/browse.ts +849 -0
  340. package/src/commands/callers.ts +363 -0
  341. package/src/commands/completion.ts +193 -0
  342. package/src/commands/config-get.ts +161 -0
  343. package/src/commands/context.ts +209 -0
  344. package/src/commands/cookies.ts +198 -0
  345. package/src/commands/docs.ts +174 -0
  346. package/src/commands/doctor.ts +231 -0
  347. package/src/commands/edit-batch.ts +233 -0
  348. package/src/commands/eml.ts +519 -0
  349. package/src/commands/env.ts +254 -0
  350. package/src/commands/fetch.ts +136 -0
  351. package/src/commands/file-history.ts +202 -0
  352. package/src/commands/grep.ts +371 -0
  353. package/src/commands/init.ts +335 -0
  354. package/src/commands/outline.ts +564 -0
  355. package/src/commands/presence.ts +152 -0
  356. package/src/commands/read.ts +64 -0
  357. package/src/commands/scratch.ts +445 -0
  358. package/src/commands/session.ts +187 -0
  359. package/src/commands/sync.ts +306 -0
  360. package/src/commands/toc.ts +218 -0
  361. package/src/commands/tokens.ts +79 -0
  362. package/src/commands/tunnel.ts +633 -0
  363. package/src/commands/uninstall.ts +144 -0
  364. package/src/commands/web.ts +193 -0
  365. package/src/core/agents/canonical-emit.ts +77 -0
  366. package/src/core/agents/cli-emit.ts +64 -0
  367. package/src/core/agents/cli.ts +838 -0
  368. package/src/core/agents/codex-replay.ts +163 -0
  369. package/src/core/agents/coord-client.ts +249 -0
  370. package/src/core/agents/events/consume.ts +196 -0
  371. package/src/core/agents/events/emit.ts +108 -0
  372. package/src/core/agents/events/ulid.ts +51 -0
  373. package/src/core/agents/index.ts +14 -0
  374. package/src/core/agents/paths.ts +16 -0
  375. package/src/core/agents/render/prompt-context.ts +401 -0
  376. package/src/core/agents/render/session-context.ts +341 -0
  377. package/src/core/agents/rules/claim-conflict.ts +282 -0
  378. package/src/core/agents/rules/commit-conflict.ts +303 -0
  379. package/src/core/agents/rules/stop-hook.ts +229 -0
  380. package/src/core/agents/session-events.ts +228 -0
  381. package/src/core/agents/state/activity-log.ts +33 -0
  382. package/src/core/agents/state/council.ts +265 -0
  383. package/src/core/agents/state/heartbeat-projector.ts +488 -0
  384. package/src/core/agents/state/heartbeat-writer.ts +333 -0
  385. package/src/core/agents/state/names.ts +399 -0
  386. package/src/core/agents/state/pidmap.ts +38 -0
  387. package/src/core/agents/state/scratch.ts +121 -0
  388. package/src/core/agents/state/shell-mutation.ts +44 -0
  389. package/src/core/agents/state/stale-sweep.ts +190 -0
  390. package/src/core/config.ts +111 -0
  391. package/src/core/hooks/cli.ts +1247 -0
  392. package/src/core/hooks/effects/image-capture.ts +330 -0
  393. package/src/core/hooks/effects/index.ts +210 -0
  394. package/src/core/hooks/events/emit.ts +120 -0
  395. package/src/core/hooks/events/schema.ts +430 -0
  396. package/src/core/hooks/events/ulid.ts +51 -0
  397. package/src/core/hooks/harness/detect.ts +30 -0
  398. package/src/core/hooks/harness/events.ts +102 -0
  399. package/src/core/hooks/harness/output.ts +100 -0
  400. package/src/core/hooks/harness/parse.ts +180 -0
  401. package/src/core/hooks/index.ts +16 -0
  402. package/src/core/hooks/resolve/anchor.ts +51 -0
  403. package/src/core/hooks/resolve/coord-root.ts +25 -0
  404. package/src/core/hooks/resolve/intent.ts +89 -0
  405. package/src/core/hooks/resolve/owner.ts +140 -0
  406. package/src/core/hooks/resolve/transcript.ts +72 -0
  407. package/src/hooks/.gitkeep +0 -0
  408. package/src/index.ts +15 -0
  409. package/src/lib/agent-browser/client.ts +239 -0
  410. package/src/lib/agent-browser/index.ts +1 -0
  411. package/src/lib/browser/client.ts +449 -0
  412. package/src/lib/browser/dev-overlay.ts +207 -0
  413. package/src/lib/browser/index.ts +24 -0
  414. package/src/lib/browser/layout.ts +288 -0
  415. package/src/lib/browser/visibility.ts +419 -0
  416. package/src/lib/browser/visual-diff.ts +150 -0
  417. package/src/lib/completion/bash.ts +291 -0
  418. package/src/lib/completion/fish.ts +134 -0
  419. package/src/lib/completion/index.ts +10 -0
  420. package/src/lib/completion/walk.ts +184 -0
  421. package/src/lib/completion/zsh.ts +262 -0
  422. package/src/lib/context/index.ts +386 -0
  423. package/src/lib/cookies/client.ts +301 -0
  424. package/src/lib/cookies/index.ts +13 -0
  425. package/src/lib/council/index.ts +803 -0
  426. package/src/lib/docs-index.ts +216 -0
  427. package/src/lib/docs-lint.ts +413 -0
  428. package/src/lib/docs-sweep.ts +348 -0
  429. package/src/lib/docs.ts +199 -0
  430. package/src/lib/env.ts +12 -0
  431. package/src/lib/exec.ts +74 -0
  432. package/src/lib/format.ts +147 -0
  433. package/src/lib/http/client.ts +211 -0
  434. package/src/lib/http/index.ts +1 -0
  435. package/src/lib/identities/index.ts +210 -0
  436. package/src/lib/machine.ts +61 -0
  437. package/src/lib/presence.ts +154 -0
  438. package/src/lib/readability/client.ts +156 -0
  439. package/src/lib/readability/index.ts +5 -0
  440. package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
  441. package/src/lib/scratch/index.ts +470 -0
  442. package/src/lib/tunnel/gate.ts +113 -0
  443. package/src/lib/tunnel/state.ts +167 -0
  444. package/src/web/.gitkeep +0 -0
  445. package/index.js +0 -1
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Pre-commit E-guard verdict. The caller (typically a bash pre-commit hook)
3
+ * sends a JSON request with the staged paths, resolves its own instance_id
4
+ * via pid-map, and prints the verdict messages + exits with
5
+ * verdict.exit_code.
6
+ *
7
+ * Three outcomes:
8
+ * 1. allow (no conflicts): exit 0.
9
+ * 2. allow + suppressed (self-attribution heuristic: holder's files ⊆
10
+ * staged set AND no live foreign pid anchors them): exit 0, prints
11
+ * "treating as self under transient identity".
12
+ * 3. block: exit 1, prints "Commit blocked by multi-agent coordination".
13
+ *
14
+ * `bypass: true` flips conflict → allow + warning lines (the
15
+ * `HARNERY_AGENT_COORD_BYPASS=1` escape hatch).
16
+ *
17
+ * Gitlink discrimination: a parent-repo staged submodule path is a pointer
18
+ * bump, NOT a claim on the submodule's contents. The caller supplies
19
+ * `staged_gitlinks[]` (cheap to compute via `git ls-files --stage`); paths
20
+ * in that set are matched with the staged-is-gitlink rule.
21
+ */
22
+
23
+ import { spawnSync } from "node:child_process";
24
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
25
+ import { join } from "node:path";
26
+ import { coordEnv } from "../../../lib/env.ts";
27
+
28
+ const DEFAULT_FRESHNESS_SECS = 600;
29
+
30
+ interface PeerHeartbeat {
31
+ instance_id: string;
32
+ session_id?: string;
33
+ name?: string;
34
+ files_touched?: string[];
35
+ last_heartbeat?: string;
36
+ }
37
+
38
+ interface Conflict {
39
+ /** Staged path that triggered the match. */
40
+ staged_path: string;
41
+ /** The peer's claimed path (may be a parent/child of staged_path). */
42
+ claimed_path: string;
43
+ /** Peer's instance_id. */
44
+ instance_id: string;
45
+ /** Peer's display name (or first 8 chars of instance_id). */
46
+ short_name: string;
47
+ }
48
+
49
+ export interface CommitVerdictRequest {
50
+ instance_id: string;
51
+ /** Group key. Subagents inherit parent's session_id. */
52
+ session_id: string;
53
+ /** Canonical monorepo-relative paths. */
54
+ staged_paths: string[];
55
+ /** Paths in `staged_paths` that resolve to submodule gitlinks (mode 160000)
56
+ * in the index. Used for gitlink-discrimination prefix matching. */
57
+ staged_gitlinks?: string[];
58
+ /** `HARNERY_AGENT_COORD_BYPASS=1` was set. Conflicts become warnings, not blocks. */
59
+ bypass?: boolean;
60
+ }
61
+
62
+ export interface CommitVerdictResult {
63
+ allow: boolean;
64
+ exit_code: number;
65
+ rule: string;
66
+ /** Conflict details (for the caller to print). */
67
+ conflicts: Conflict[];
68
+ /** Path-specific gates that fired (for `coord_log` lines). */
69
+ log_lines: string[];
70
+ /** Human-readable header for printing. */
71
+ message: string;
72
+ /** When true, conflicts were detected but suppressed (self-attribution). */
73
+ suppressed_self_attribution?: boolean;
74
+ }
75
+
76
+ export function evaluateCommit(coordRoot: string, req: CommitVerdictRequest): CommitVerdictResult {
77
+ if (req.staged_paths.length === 0) {
78
+ return {
79
+ allow: true,
80
+ exit_code: 0,
81
+ rule: "commit.pass",
82
+ conflicts: [],
83
+ log_lines: [],
84
+ message: "",
85
+ };
86
+ }
87
+
88
+ const peers = readActivePeers(coordRoot);
89
+ const cutoffMs = Date.now() - freshnessSecs() * 1000;
90
+ const stagedSet = new Set(req.staged_paths);
91
+ const gitlinkSet = new Set(req.staged_gitlinks ?? []);
92
+
93
+ const conflicts: Conflict[] = [];
94
+ for (const peer of peers) {
95
+ if (peer.instance_id === req.instance_id) continue;
96
+ const peerSession = peer.session_id ?? peer.instance_id;
97
+ if (req.session_id && peerSession === req.session_id) continue; // same group
98
+ const ts = peer.last_heartbeat ? Date.parse(peer.last_heartbeat) : 0;
99
+ if (!Number.isFinite(ts) || ts < cutoffMs) continue; // stale
100
+ const files = peer.files_touched ?? [];
101
+ if (files.length === 0) continue;
102
+
103
+ for (const staged of req.staged_paths) {
104
+ const stagedIsGitlink = gitlinkSet.has(staged);
105
+ const hit = findOverlap(staged, files, coordRoot, stagedIsGitlink);
106
+ if (!hit) continue;
107
+ conflicts.push({
108
+ staged_path: staged,
109
+ claimed_path: hit,
110
+ instance_id: peer.instance_id,
111
+ short_name: shortName(peer),
112
+ });
113
+ break; // one conflict per peer is enough
114
+ }
115
+ }
116
+
117
+ if (conflicts.length === 0) {
118
+ return {
119
+ allow: true,
120
+ exit_code: 0,
121
+ rule: "commit.pass",
122
+ conflicts: [],
123
+ log_lines: [],
124
+ message: "",
125
+ };
126
+ }
127
+
128
+ if (req.bypass) {
129
+ return {
130
+ allow: true,
131
+ exit_code: 0,
132
+ rule: "commit.bypass",
133
+ conflicts,
134
+ log_lines: conflicts.map(
135
+ (c) => `COMMIT_BYPASSED path=${c.staged_path} owner=${c.short_name}`,
136
+ ),
137
+ message:
138
+ "⚠ Multi-Agent coordination: bypass active; staged paths claimed by other agents will be committed anyway:",
139
+ };
140
+ }
141
+
142
+ // Self-attribution check (Fix #2): if every conflicting holder is plausibly
143
+ // us under a transient identity (held files_touched ⊆ the staged set AND no
144
+ // live foreign PID anchors the holder via pid-map), suppress the block.
145
+ const allSelfAttributed = conflicts.every((c) =>
146
+ isHolderSelfAttributed(coordRoot, c.instance_id, stagedSet, peers),
147
+ );
148
+ if (allSelfAttributed) {
149
+ return {
150
+ allow: true,
151
+ exit_code: 0,
152
+ rule: "commit.suppressed",
153
+ conflicts,
154
+ log_lines: conflicts.map(
155
+ (c) =>
156
+ `COMMIT_SUPPRESSED path=${c.staged_path} owner=${c.short_name} reason=self_attribution`,
157
+ ),
158
+ message:
159
+ "⚠ Multi-Agent coordination: self-attribution detected; staged paths\n" +
160
+ " are claimed by an unanchored heartbeat that holds only files in\n" +
161
+ " this commit's staged set. Treating as self under a transient\n" +
162
+ " identity; commit will proceed.",
163
+ suppressed_self_attribution: true,
164
+ };
165
+ }
166
+
167
+ return {
168
+ allow: false,
169
+ exit_code: 1,
170
+ rule: "commit.conflict",
171
+ conflicts,
172
+ log_lines: conflicts.map((c) => `COMMIT_BLOCKED path=${c.staged_path} owner=${c.short_name}`),
173
+ message:
174
+ "✗ Commit blocked by multi-agent coordination (E guard).\n\n" +
175
+ " The following staged paths are currently claimed by other\n" +
176
+ " active agents:",
177
+ };
178
+ }
179
+
180
+ function findOverlap(
181
+ staged: string,
182
+ files: readonly string[],
183
+ coordRoot: string,
184
+ stagedIsGitlink: boolean,
185
+ ): string | null {
186
+ for (const claimed of files) {
187
+ if (claimed === staged) return claimed;
188
+ if (claimed.startsWith(`${staged}/`)) {
189
+ // Staged is a submodule gitlink, claimed is a file inside it. Disjoint.
190
+ if (stagedIsGitlink) continue;
191
+ return claimed;
192
+ }
193
+ if (staged.startsWith(`${claimed}/`)) {
194
+ // Claimed is a gitlink, staged is a file inside it. Disjoint.
195
+ if (isGitlinkInIndex(coordRoot, claimed)) continue;
196
+ return claimed;
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+
202
+ function isGitlinkInIndex(coordRoot: string, path: string): boolean {
203
+ const result = spawnSync("git", ["ls-files", "--stage", "--", path], {
204
+ cwd: coordRoot,
205
+ encoding: "utf8",
206
+ timeout: 2000,
207
+ });
208
+ if (result.status !== 0) return false;
209
+ // ls-files --stage emits "<mode> <sha> <stage>\t<path>"; mode 160000 = gitlink.
210
+ return result.stdout.trim().startsWith("160000 ");
211
+ }
212
+
213
+ function isHolderSelfAttributed(
214
+ coordRoot: string,
215
+ holderId: string,
216
+ stagedSet: Set<string>,
217
+ peers: readonly PeerHeartbeat[],
218
+ ): boolean {
219
+ // Gate B (cheaper): live foreign pid-map entry blocks self-attribution.
220
+ if (holderHasLiveForeignPid(coordRoot, holderId)) return false;
221
+
222
+ // Gate A: every held path is either in the staged set or already clean in HEAD.
223
+ const holder = peers.find((p) => p.instance_id === holderId);
224
+ if (!holder) return false;
225
+ const files = holder.files_touched ?? [];
226
+ if (files.length === 0) return false;
227
+ for (const held of files) {
228
+ if (stagedSet.has(held)) continue;
229
+ if (isPathCleanInHead(coordRoot, held)) continue;
230
+ return false;
231
+ }
232
+ return true;
233
+ }
234
+
235
+ function holderHasLiveForeignPid(coordRoot: string, holderId: string): boolean {
236
+ const dir = join(coordRoot, ".harnery", "pid-map");
237
+ if (!existsSync(dir)) return false;
238
+ for (const f of readdirSync(dir)) {
239
+ let row = "";
240
+ try {
241
+ row = readFileSync(join(dir, f), "utf8").trim();
242
+ } catch {
243
+ continue;
244
+ }
245
+ const owner = row.split("\t")[0]?.trim() ?? "";
246
+ if (owner !== holderId) continue;
247
+ // Process still alive?
248
+ const pid = Number.parseInt(f, 10);
249
+ if (!Number.isFinite(pid)) continue;
250
+ try {
251
+ process.kill(pid, 0); // signal 0 = liveness probe
252
+ return true; // live foreign pid found
253
+ } catch {
254
+ // ESRCH (no such process): pid-map entry is stale, skip
255
+ }
256
+ }
257
+ return false;
258
+ }
259
+
260
+ function isPathCleanInHead(coordRoot: string, relPath: string): boolean {
261
+ // Path is tracked + diff-clean against HEAD = "already committed, holder
262
+ // hasn't released the claim yet". Counts as self-attributable.
263
+ const tracked = spawnSync("git", ["ls-files", "--error-unmatch", "--", relPath], {
264
+ cwd: coordRoot,
265
+ encoding: "utf8",
266
+ timeout: 2000,
267
+ });
268
+ if (tracked.status !== 0) return false;
269
+ const diff = spawnSync("git", ["diff", "--quiet", "HEAD", "--", relPath], {
270
+ cwd: coordRoot,
271
+ encoding: "utf8",
272
+ timeout: 2000,
273
+ });
274
+ return diff.status === 0;
275
+ }
276
+
277
+ function readActivePeers(coordRoot: string): PeerHeartbeat[] {
278
+ const dir = join(coordRoot, ".harnery", "active");
279
+ if (!existsSync(dir)) return [];
280
+ const out: PeerHeartbeat[] = [];
281
+ for (const f of readdirSync(dir)) {
282
+ if (!f.endsWith(".json")) continue;
283
+ try {
284
+ const hb = JSON.parse(readFileSync(join(dir, f), "utf8")) as PeerHeartbeat;
285
+ if (hb.instance_id) out.push(hb);
286
+ } catch {
287
+ /* skip */
288
+ }
289
+ }
290
+ return out;
291
+ }
292
+
293
+ function freshnessSecs(): number {
294
+ const raw = coordEnv("AGENT_FRESHNESS");
295
+ if (!raw) return DEFAULT_FRESHNESS_SECS;
296
+ const n = Number.parseInt(raw, 10);
297
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_FRESHNESS_SECS;
298
+ }
299
+
300
+ function shortName(peer: PeerHeartbeat): string {
301
+ if (peer.name && peer.name.length > 0) return `agent-${peer.name}`;
302
+ return `agent-${peer.instance_id.slice(0, 8)}`;
303
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Stop-hook verdict: rule 1/3, 2/3, 3/3 enforcement evaluated
3
+ * from the canonical event stream alone (no transcript scan).
4
+ *
5
+ * Rule 1/3: `state.status_checked` event with matching turn boundary exists.
6
+ * Rule 2/3: latest `turn.stop` event has `status_box_present: true` (or the
7
+ * stop currently firing carries that field via the in-flight event
8
+ * agent-hook emits before the stop hook runs).
9
+ * Rule 3/3: `state.task_set` event with matching turn boundary exists.
10
+ *
11
+ * Pure-prose-turn exemption: when zero `tool.pre_use` events fire in
12
+ * the current turn, rules 1/3 and 3/3 do not apply. Rule 2/3 still applies
13
+ * as a user-visible mobile cue.
14
+ */
15
+
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { resolveBinName } from "../../config.ts";
19
+
20
+ export type VerdictResult = {
21
+ allow: boolean;
22
+ exit_code: 0 | 2;
23
+ rule: string;
24
+ reason?: string;
25
+ };
26
+
27
+ interface CanonicalEvent {
28
+ event_id: string;
29
+ event_type: string;
30
+ ts: string;
31
+ instance_id: string;
32
+ session_id: string;
33
+ turn_id?: string;
34
+ harness: string;
35
+ source: string;
36
+ data: Record<string, unknown>;
37
+ }
38
+
39
+ export interface StopHookRequest {
40
+ rule: "stop-hook";
41
+ instance_id: string;
42
+ session_id?: string;
43
+ /** Firing harness. Selects the end-of-turn ack signal (see `ackSignalFor`).
44
+ * Undefined → Claude Code semantics (transcript-scanned status box). */
45
+ harness?: string;
46
+ /** Wall-clock cutoff for the current turn; events strictly after this are not yet relevant. */
47
+ now_ms?: number;
48
+ /** Override the turn-window discovery (used by tests). */
49
+ turn_window?: { start_ms: number; end_ms: number };
50
+ /** Bypass switch: operator escape hatch identical to HARNERY_AGENT_COORD_BYPASS_STOP. */
51
+ bypass?: boolean;
52
+ }
53
+
54
+ /**
55
+ * The end-of-turn "I surfaced my status" signal, detected differently per
56
+ * harness because the ritual's *goal* (status visible to the human) is reached
57
+ * by different means:
58
+ *
59
+ * - **Claude Code / Codex** collapse tool calls in the UI, so the human-visible
60
+ * signal is the verbatim status-box paste in the reply (rule 2/3, detected
61
+ * by scanning the transcript for the `┌─ agent-` prefix, `status_box_present`
62
+ * on `turn.stop`).
63
+ * - **Cursor** renders Shell output inline, so simply *running* `harn agents
64
+ * status` (which emits `state.status_checked`) already puts the box on
65
+ * screen. The separate paste is redundant, and undetectable anyway, since
66
+ * Cursor's stop payload carries neither a transcript path nor the assistant
67
+ * message. So rule 1/3 *is* the ack signal on Cursor; rule 2/3 collapses into
68
+ * it rather than being a second requirement.
69
+ *
70
+ * This is why the fix is not "relax 2/3 because we can't see it": it's "2/3
71
+ * and 1/3 are two detections of the same thing, and Cursor's inline UI makes
72
+ * 1/3 the right one." The enforcement *channel* (exit-2+stderr vs Cursor's
73
+ * `followup_message`) is handled separately in hooks/harness/output.ts.
74
+ */
75
+ type AckSignal = "status_box_present" | "status_checked";
76
+
77
+ function ackSignalFor(harness?: string): AckSignal {
78
+ return harness === "cursor" ? "status_checked" : "status_box_present";
79
+ }
80
+
81
+ const RECENT_EVENT_WINDOW_LINES = 5_000;
82
+
83
+ /**
84
+ * Evaluate the Stop-hook verdict. Returns the first failing rule (or allow
85
+ * when all three pass). Soft-fails open when the event stream isn't
86
+ * readable, fail-open posture on a verdict failure.
87
+ */
88
+ export function evaluateStopHook(coordRoot: string, req: StopHookRequest): VerdictResult {
89
+ if (req.bypass) {
90
+ return {
91
+ allow: true,
92
+ exit_code: 0,
93
+ rule: "stop-hook.bypass",
94
+ reason: "HARNERY_AGENT_COORD_BYPASS_STOP=1",
95
+ };
96
+ }
97
+
98
+ let events: CanonicalEvent[];
99
+ try {
100
+ events = readRecentEvents(coordRoot, RECENT_EVENT_WINDOW_LINES);
101
+ } catch {
102
+ return {
103
+ allow: true,
104
+ exit_code: 0,
105
+ rule: "stop-hook.fail_open",
106
+ reason: "events.ndjson not readable; failing open",
107
+ };
108
+ }
109
+
110
+ const ownerEvents = events.filter((e) => e.instance_id === req.instance_id);
111
+ if (ownerEvents.length === 0) {
112
+ return {
113
+ allow: true,
114
+ exit_code: 0,
115
+ rule: "stop-hook.no_history",
116
+ reason: "no canonical events for this owner; defer to legacy or skip",
117
+ };
118
+ }
119
+
120
+ // Turn window: from the most recent user_prompt.submit (this owner) up to
121
+ // either the explicit now_ms or the last event we see. Fall back to a
122
+ // 5-minute window when no user_prompt.submit is in scope (fresh wiring,
123
+ // out-of-window prompt, etc.) so a stale stream doesn't silently pass.
124
+ const nowMs = req.now_ms ?? Date.now();
125
+ const lastUserPrompt = [...ownerEvents]
126
+ .reverse()
127
+ .find((e) => e.event_type === "user_prompt.submit");
128
+ const startMs = lastUserPrompt ? Date.parse(lastUserPrompt.ts) : nowMs - 5 * 60 * 1000;
129
+
130
+ const inTurn = ownerEvents.filter((e) => {
131
+ const t = Date.parse(e.ts);
132
+ return Number.isFinite(t) && t >= startMs && t <= nowMs;
133
+ });
134
+
135
+ const toolPreUseInTurn = inTurn.some((e) => e.event_type === "tool.pre_use");
136
+ const statusChecked = inTurn.some((e) => e.event_type === "state.status_checked");
137
+ const taskSet = inTurn.some((e) => e.event_type === "state.task_set");
138
+
139
+ // Rule 2/3: status_box_present on the most recent turn.stop for this owner.
140
+ // The turn.stop event fires from agent-hook stop, which is wired BEFORE
141
+ // the stop-hook verdict, so by the time we evaluate, the just-
142
+ // fired turn.stop is already in the stream.
143
+ const latestTurnStop = [...inTurn].reverse().find((e) => e.event_type === "turn.stop");
144
+ const boxPresent = latestTurnStop ? Boolean(latestTurnStop.data.status_box_present) : false;
145
+
146
+ // Harness-aware end-of-turn ack signal (see `ackSignalFor`). On Cursor the
147
+ // ack is `status_checked` (running `harn agents status` shows the box inline);
148
+ // on Claude Code / Codex it's the transcript-scanned `status_box_present`.
149
+ // The matching block helper carries the right "how to fix" message.
150
+ const ackSignal = ackSignalFor(req.harness);
151
+ const ackPresent = ackSignal === "status_checked" ? statusChecked : boxPresent;
152
+ const ackBlock = ackSignal === "status_checked" ? rule13Block : rule23Block;
153
+
154
+ // Pure-prose-turn exemption: only the ack signal applies. Parity
155
+ // across harnesses: CC requires the box; Cursor requires status_checked.
156
+ if (!toolPreUseInTurn) {
157
+ if (!ackPresent) {
158
+ return ackBlock();
159
+ }
160
+ return {
161
+ allow: true,
162
+ exit_code: 0,
163
+ rule: "stop-hook.pure_prose_pass",
164
+ reason: "no tool calls this turn; rules 1/3 + 3/3 skipped",
165
+ };
166
+ }
167
+
168
+ if (!statusChecked) return rule13Block();
169
+ // On Cursor `ackPresent === statusChecked` (already true here), so this is a
170
+ // no-op and rule 2/3 is not enforced; on CC/Codex it's the box-paste check.
171
+ if (!ackPresent) return ackBlock();
172
+ if (!taskSet) return rule33Block();
173
+
174
+ return {
175
+ allow: true,
176
+ exit_code: 0,
177
+ rule: "stop-hook.pass",
178
+ };
179
+ }
180
+
181
+ function rule13Block(): VerdictResult {
182
+ return {
183
+ allow: false,
184
+ exit_code: 2,
185
+ rule: "stop-hook.rule_1_3",
186
+ reason: `End-of-turn rule (1/3): no state.status_checked event found in this turn; run \`${resolveBinName()} agents status\` as your last tool call.`,
187
+ };
188
+ }
189
+
190
+ function rule23Block(): VerdictResult {
191
+ return {
192
+ allow: false,
193
+ exit_code: 2,
194
+ rule: "stop-hook.rule_2_3",
195
+ reason: `End-of-turn rule (2/3): turn.stop did not see the agent-status box in your reply text. Paste the \`${resolveBinName()} agents status\` output verbatim as a fenced code block (the \`┌─ agent-\` prefix is the detection signal).`,
196
+ };
197
+ }
198
+
199
+ function rule33Block(): VerdictResult {
200
+ return {
201
+ allow: false,
202
+ exit_code: 2,
203
+ rule: "stop-hook.rule_3_3",
204
+ 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.`,
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Read the last N JSON lines from events.ndjson. The Stop-hook verdict needs
210
+ * just the most recent turn so we tail-read aggressively.
211
+ */
212
+ function readRecentEvents(coordRoot: string, maxLines: number): CanonicalEvent[] {
213
+ const path = join(coordRoot, ".harnery", "events.ndjson");
214
+ if (!existsSync(path)) return [];
215
+ const raw = readFileSync(path, "utf8");
216
+ const lines = raw.split("\n");
217
+ const start = Math.max(0, lines.length - maxLines);
218
+ const out: CanonicalEvent[] = [];
219
+ for (let i = start; i < lines.length; i++) {
220
+ const line = lines[i];
221
+ if (!line?.trim()) continue;
222
+ try {
223
+ out.push(JSON.parse(line) as CanonicalEvent);
224
+ } catch {
225
+ /* skip malformed */
226
+ }
227
+ }
228
+ return out;
229
+ }