harnery 0.32.0 → 0.34.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 (665) hide show
  1. package/README.md +2 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +7 -1
  4. package/dist/commands/adapter.js +2 -2
  5. package/dist/commands/agents.d.ts +144 -1
  6. package/dist/commands/agents.d.ts.map +1 -1
  7. package/dist/commands/agents.js +1408 -565
  8. package/dist/commands/artifacts.d.ts.map +1 -1
  9. package/dist/commands/artifacts.js +7 -6
  10. package/dist/commands/backup.d.ts +1 -1
  11. package/dist/commands/backup.d.ts.map +1 -1
  12. package/dist/commands/backup.js +29 -16
  13. package/dist/commands/browse.d.ts.map +1 -1
  14. package/dist/commands/browse.js +234 -12
  15. package/dist/commands/checkpoint.d.ts.map +1 -1
  16. package/dist/commands/checkpoint.js +3 -2
  17. package/dist/commands/claude-desktop.d.ts +2 -2
  18. package/dist/commands/claude-desktop.d.ts.map +1 -1
  19. package/dist/commands/claude-desktop.js +2 -2
  20. package/dist/commands/context.js +1 -6
  21. package/dist/commands/decision.d.ts.map +1 -1
  22. package/dist/commands/decision.js +22 -38
  23. package/dist/commands/docs.d.ts.map +1 -1
  24. package/dist/commands/docs.js +91 -28
  25. package/dist/commands/doctor.d.ts +4 -2
  26. package/dist/commands/doctor.d.ts.map +1 -1
  27. package/dist/commands/doctor.js +55 -7
  28. package/dist/commands/events.d.ts +5 -0
  29. package/dist/commands/events.d.ts.map +1 -0
  30. package/dist/commands/events.js +189 -0
  31. package/dist/commands/governor.d.ts.map +1 -1
  32. package/dist/commands/governor.js +13 -4
  33. package/dist/commands/init.d.ts +10 -2
  34. package/dist/commands/init.d.ts.map +1 -1
  35. package/dist/commands/init.js +233 -51
  36. package/dist/commands/instructions.d.ts +4 -0
  37. package/dist/commands/instructions.d.ts.map +1 -0
  38. package/dist/commands/instructions.js +100 -0
  39. package/dist/commands/journal.d.ts.map +1 -1
  40. package/dist/commands/journal.js +4 -13
  41. package/dist/commands/ledger-v3.d.ts +5 -0
  42. package/dist/commands/ledger-v3.d.ts.map +1 -0
  43. package/dist/commands/ledger-v3.js +91 -0
  44. package/dist/commands/presence.d.ts.map +1 -1
  45. package/dist/commands/presence.js +15 -6
  46. package/dist/commands/sync.d.ts +1 -1
  47. package/dist/commands/sync.js +1 -1
  48. package/dist/commands/web.d.ts +6 -0
  49. package/dist/commands/web.d.ts.map +1 -1
  50. package/dist/commands/web.js +67 -7
  51. package/dist/commands/work.d.ts.map +1 -1
  52. package/dist/commands/work.js +22 -5
  53. package/dist/core/adapter.d.ts +5 -0
  54. package/dist/core/adapter.d.ts.map +1 -0
  55. package/dist/core/adapter.js +1 -0
  56. package/dist/core/adapters/attest.d.ts +5 -2
  57. package/dist/core/adapters/attest.d.ts.map +1 -1
  58. package/dist/core/adapters/attest.js +8 -3
  59. package/dist/core/adapters/profiles.d.ts +6 -6
  60. package/dist/core/adapters/profiles.d.ts.map +1 -1
  61. package/dist/core/adapters/profiles.js +4 -4
  62. package/dist/core/agents/canonical-emit.d.ts +17 -15
  63. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  64. package/dist/core/agents/canonical-emit.js +27 -25
  65. package/dist/core/agents/cli.js +212 -235
  66. package/dist/core/agents/codex-archive-v3.d.ts +14 -0
  67. package/dist/core/agents/codex-archive-v3.d.ts.map +1 -0
  68. package/dist/core/agents/codex-archive-v3.js +110 -0
  69. package/dist/core/agents/coord-client.d.ts +19 -70
  70. package/dist/core/agents/coord-client.d.ts.map +1 -1
  71. package/dist/core/agents/coord-client.js +65 -155
  72. package/dist/core/agents/finalization.d.ts +4 -1
  73. package/dist/core/agents/finalization.d.ts.map +1 -1
  74. package/dist/core/agents/finalization.js +89 -32
  75. package/dist/core/agents/index.d.ts +5 -5
  76. package/dist/core/agents/index.d.ts.map +1 -1
  77. package/dist/core/agents/index.js +5 -5
  78. package/dist/core/agents/live-authority-v3.d.ts +59 -0
  79. package/dist/core/agents/live-authority-v3.d.ts.map +1 -0
  80. package/dist/core/agents/live-authority-v3.js +233 -0
  81. package/dist/core/agents/live-lifecycle-v3.d.ts +20 -0
  82. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -0
  83. package/dist/core/agents/live-lifecycle-v3.js +91 -0
  84. package/dist/core/agents/live-observation-v3.d.ts +25 -0
  85. package/dist/core/agents/live-observation-v3.d.ts.map +1 -0
  86. package/dist/core/agents/live-observation-v3.js +157 -0
  87. package/dist/core/agents/render/prompt-context.d.ts +10 -4
  88. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  89. package/dist/core/agents/render/prompt-context.js +61 -55
  90. package/dist/core/agents/render/session-context.d.ts +1 -1
  91. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  92. package/dist/core/agents/render/session-context.js +15 -33
  93. package/dist/core/agents/rules/claim-conflict.d.ts +3 -3
  94. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  95. package/dist/core/agents/rules/claim-conflict.js +79 -127
  96. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  97. package/dist/core/agents/rules/commit-conflict.js +3 -18
  98. package/dist/core/agents/rules/stop-hook.d.ts +9 -50
  99. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  100. package/dist/core/agents/rules/stop-hook.js +193 -236
  101. package/dist/core/agents/session-events.d.ts +5 -7
  102. package/dist/core/agents/session-events.d.ts.map +1 -1
  103. package/dist/core/agents/session-events.js +127 -95
  104. package/dist/core/agents/session-finalization-state-v3.d.ts +30 -0
  105. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -0
  106. package/dist/core/agents/session-finalization-state-v3.js +40 -0
  107. package/dist/core/agents/session-finalizer-v3.d.ts +107 -0
  108. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -0
  109. package/dist/core/agents/session-finalizer-v3.js +734 -0
  110. package/dist/core/agents/session-name-display.d.ts +40 -0
  111. package/dist/core/agents/session-name-display.d.ts.map +1 -0
  112. package/dist/core/agents/session-name-display.js +119 -0
  113. package/dist/core/agents/state/activity-log.d.ts +3 -3
  114. package/dist/core/agents/state/activity-log.d.ts.map +1 -1
  115. package/dist/core/agents/state/activity-log.js +7 -16
  116. package/dist/core/agents/state/heartbeat-writer.d.ts +29 -46
  117. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  118. package/dist/core/agents/state/heartbeat-writer.js +59 -199
  119. package/dist/core/agents/state/live-coordination-view.d.ts +31 -0
  120. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -0
  121. package/dist/core/agents/state/live-coordination-view.js +209 -0
  122. package/dist/core/agents/state/session-state.d.ts +45 -0
  123. package/dist/core/agents/state/session-state.d.ts.map +1 -0
  124. package/dist/core/agents/state/session-state.js +90 -0
  125. package/dist/core/agents/state/stale-sweep.d.ts +1 -1
  126. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  127. package/dist/core/agents/state/stale-sweep.js +26 -15
  128. package/dist/core/artifacts/index.d.ts.map +1 -1
  129. package/dist/core/artifacts/index.js +5 -7
  130. package/dist/core/config.d.ts +33 -2
  131. package/dist/core/config.d.ts.map +1 -1
  132. package/dist/core/config.js +98 -2
  133. package/dist/core/context/index.d.ts +1 -4
  134. package/dist/core/context/index.d.ts.map +1 -1
  135. package/dist/core/context/index.js +9 -9
  136. package/dist/core/events/span-parent.d.ts +15 -0
  137. package/dist/core/events/span-parent.d.ts.map +1 -0
  138. package/dist/core/events/span-parent.js +34 -0
  139. package/dist/core/events/v3/advance.d.ts +14 -0
  140. package/dist/core/events/v3/advance.d.ts.map +1 -0
  141. package/dist/core/events/v3/advance.js +134 -0
  142. package/dist/core/events/v3/authority-outbox.d.ts +78 -0
  143. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -0
  144. package/dist/core/events/v3/authority-outbox.js +399 -0
  145. package/dist/core/events/v3/base-builder.d.ts +20 -0
  146. package/dist/core/events/v3/base-builder.d.ts.map +1 -0
  147. package/dist/core/events/v3/base-builder.js +29 -0
  148. package/dist/core/events/v3/base-contract.d.ts +7576 -0
  149. package/dist/core/events/v3/base-contract.d.ts.map +1 -0
  150. package/dist/core/events/v3/base-contract.js +647 -0
  151. package/dist/core/events/v3/bootstrap.d.ts +34 -0
  152. package/dist/core/events/v3/bootstrap.d.ts.map +1 -0
  153. package/dist/core/events/v3/bootstrap.js +175 -0
  154. package/dist/core/events/v3/builder.d.ts +20 -0
  155. package/dist/core/events/v3/builder.d.ts.map +1 -0
  156. package/dist/core/events/v3/builder.js +28 -0
  157. package/dist/core/events/v3/canonical.d.ts +31 -0
  158. package/dist/core/events/v3/canonical.d.ts.map +1 -0
  159. package/dist/core/events/v3/canonical.js +100 -0
  160. package/dist/core/events/v3/capabilities.d.ts +31 -0
  161. package/dist/core/events/v3/capabilities.d.ts.map +1 -0
  162. package/dist/core/events/v3/capabilities.js +107 -0
  163. package/dist/core/events/v3/capability-drift.d.ts +22 -0
  164. package/dist/core/events/v3/capability-drift.d.ts.map +1 -0
  165. package/dist/core/events/v3/capability-drift.js +134 -0
  166. package/dist/core/events/v3/catalog.d.ts +38 -0
  167. package/dist/core/events/v3/catalog.d.ts.map +1 -0
  168. package/dist/core/events/v3/catalog.js +135 -0
  169. package/dist/core/events/v3/clock-order.d.ts +34 -0
  170. package/dist/core/events/v3/clock-order.d.ts.map +1 -0
  171. package/dist/core/events/v3/clock-order.js +35 -0
  172. package/dist/core/events/v3/contract.d.ts +171 -0
  173. package/dist/core/events/v3/contract.d.ts.map +1 -0
  174. package/dist/core/events/v3/contract.js +180 -0
  175. package/dist/core/events/v3/control.d.ts +111 -0
  176. package/dist/core/events/v3/control.d.ts.map +1 -0
  177. package/dist/core/events/v3/control.js +419 -0
  178. package/dist/core/events/v3/coordination-view.d.ts +69 -0
  179. package/dist/core/events/v3/coordination-view.d.ts.map +1 -0
  180. package/dist/core/events/v3/coordination-view.js +119 -0
  181. package/dist/core/events/v3/economics.d.ts +77 -0
  182. package/dist/core/events/v3/economics.d.ts.map +1 -0
  183. package/dist/core/events/v3/economics.js +274 -0
  184. package/dist/core/events/v3/finalization-view.d.ts +26 -0
  185. package/dist/core/events/v3/finalization-view.d.ts.map +1 -0
  186. package/dist/core/events/v3/finalization-view.js +37 -0
  187. package/dist/core/events/v3/fingerprint-keys.d.ts +25 -0
  188. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -0
  189. package/dist/core/events/v3/fingerprint-keys.js +217 -0
  190. package/dist/core/events/v3/generated.d.ts +4 -0
  191. package/dist/core/events/v3/generated.d.ts.map +1 -0
  192. package/dist/core/events/v3/generated.js +47 -0
  193. package/dist/core/events/v3/ids.d.ts +9 -0
  194. package/dist/core/events/v3/ids.d.ts.map +1 -0
  195. package/dist/core/events/v3/ids.js +25 -0
  196. package/dist/core/events/v3/index.d.ts +35 -0
  197. package/dist/core/events/v3/index.d.ts.map +1 -0
  198. package/dist/core/events/v3/index.js +33 -0
  199. package/dist/core/events/v3/latency.d.ts +63 -0
  200. package/dist/core/events/v3/latency.d.ts.map +1 -0
  201. package/dist/core/events/v3/latency.js +412 -0
  202. package/dist/core/events/v3/live-feed.d.ts +45 -0
  203. package/dist/core/events/v3/live-feed.d.ts.map +1 -0
  204. package/dist/core/events/v3/live-feed.js +246 -0
  205. package/dist/core/events/v3/live-observation.d.ts +35 -0
  206. package/dist/core/events/v3/live-observation.d.ts.map +1 -0
  207. package/dist/core/events/v3/live-observation.js +1 -0
  208. package/dist/core/events/v3/live-routing.d.ts +57 -0
  209. package/dist/core/events/v3/live-routing.d.ts.map +1 -0
  210. package/dist/core/events/v3/live-routing.js +107 -0
  211. package/dist/core/events/v3/producers/command-recorder.d.ts +67 -0
  212. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -0
  213. package/dist/core/events/v3/producers/command-recorder.js +477 -0
  214. package/dist/core/events/v3/producers/command.d.ts +50 -0
  215. package/dist/core/events/v3/producers/command.d.ts.map +1 -0
  216. package/dist/core/events/v3/producers/command.js +135 -0
  217. package/dist/core/events/v3/producers/coordination-recorder.d.ts +53 -0
  218. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -0
  219. package/dist/core/events/v3/producers/coordination-recorder.js +376 -0
  220. package/dist/core/events/v3/producers/coordination.d.ts +90 -0
  221. package/dist/core/events/v3/producers/coordination.d.ts.map +1 -0
  222. package/dist/core/events/v3/producers/coordination.js +229 -0
  223. package/dist/core/events/v3/producers/hook-base.d.ts +54 -0
  224. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -0
  225. package/dist/core/events/v3/producers/hook-base.js +424 -0
  226. package/dist/core/events/v3/producers/hook.d.ts +29 -0
  227. package/dist/core/events/v3/producers/hook.d.ts.map +1 -0
  228. package/dist/core/events/v3/producers/hook.js +71 -0
  229. package/dist/core/events/v3/producers/intake.d.ts +65 -0
  230. package/dist/core/events/v3/producers/intake.d.ts.map +1 -0
  231. package/dist/core/events/v3/producers/intake.js +244 -0
  232. package/dist/core/events/v3/producers/recorder.d.ts +240 -0
  233. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -0
  234. package/dist/core/events/v3/producers/recorder.js +2176 -0
  235. package/dist/core/events/v3/projection.d.ts +101 -0
  236. package/dist/core/events/v3/projection.d.ts.map +1 -0
  237. package/dist/core/events/v3/projection.js +479 -0
  238. package/dist/core/events/v3/reader.d.ts +75 -0
  239. package/dist/core/events/v3/reader.d.ts.map +1 -0
  240. package/dist/core/events/v3/reader.js +778 -0
  241. package/dist/core/events/v3/recovery-record.d.ts +47 -0
  242. package/dist/core/events/v3/recovery-record.d.ts.map +1 -0
  243. package/dist/core/events/v3/recovery-record.js +167 -0
  244. package/dist/core/events/v3/recovery.d.ts +22 -0
  245. package/dist/core/events/v3/recovery.d.ts.map +1 -0
  246. package/dist/core/events/v3/recovery.js +227 -0
  247. package/dist/core/events/v3/span-state.d.ts +52 -0
  248. package/dist/core/events/v3/span-state.d.ts.map +1 -0
  249. package/dist/core/events/v3/span-state.js +142 -0
  250. package/dist/core/events/v3/targets.d.ts +23 -0
  251. package/dist/core/events/v3/targets.d.ts.map +1 -0
  252. package/dist/core/events/v3/targets.js +134 -0
  253. package/dist/core/events/v3/turn-telemetry.d.ts +41 -0
  254. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -0
  255. package/dist/core/events/v3/turn-telemetry.js +168 -0
  256. package/dist/core/events/v3/validate.d.ts +9 -0
  257. package/dist/core/events/v3/validate.d.ts.map +1 -0
  258. package/dist/core/events/v3/validate.js +281 -0
  259. package/dist/core/events/v3/writer.d.ts +56 -0
  260. package/dist/core/events/v3/writer.d.ts.map +1 -0
  261. package/dist/core/events/v3/writer.js +335 -0
  262. package/dist/core/governor/index.d.ts +1 -1
  263. package/dist/core/governor/index.d.ts.map +1 -1
  264. package/dist/core/governor/index.js +1 -1
  265. package/dist/core/governor/plan-api.d.ts +2 -1
  266. package/dist/core/governor/plan-api.d.ts.map +1 -1
  267. package/dist/core/governor/plan-api.js +6 -3
  268. package/dist/core/governor/read.d.ts +1 -1
  269. package/dist/core/governor/read.d.ts.map +1 -1
  270. package/dist/core/governor/read.js +1 -1
  271. package/dist/core/governor/state.d.ts +9 -0
  272. package/dist/core/governor/state.d.ts.map +1 -1
  273. package/dist/core/governor/state.js +40 -3
  274. package/dist/core/guard/config.d.ts +5 -0
  275. package/dist/core/guard/config.d.ts.map +1 -0
  276. package/dist/core/guard/config.js +157 -0
  277. package/dist/core/guard/coordinator.d.ts +14 -0
  278. package/dist/core/guard/coordinator.d.ts.map +1 -0
  279. package/dist/core/guard/coordinator.js +173 -0
  280. package/dist/core/guard/evaluator.d.ts +4 -0
  281. package/dist/core/guard/evaluator.d.ts.map +1 -0
  282. package/dist/core/guard/evaluator.js +242 -0
  283. package/dist/core/guard/evidence-v3.d.ts +15 -0
  284. package/dist/core/guard/evidence-v3.d.ts.map +1 -0
  285. package/dist/core/guard/evidence-v3.js +171 -0
  286. package/dist/core/guard/index.d.ts +10 -0
  287. package/dist/core/guard/index.d.ts.map +1 -0
  288. package/dist/core/guard/index.js +6 -0
  289. package/dist/core/guard/live-source-v3.d.ts +40 -0
  290. package/dist/core/guard/live-source-v3.d.ts.map +1 -0
  291. package/dist/core/guard/live-source-v3.js +128 -0
  292. package/dist/core/guard/storage.d.ts +27 -0
  293. package/dist/core/guard/storage.d.ts.map +1 -0
  294. package/dist/core/guard/storage.js +140 -0
  295. package/dist/core/guard/types.d.ts +119 -0
  296. package/dist/core/guard/types.d.ts.map +1 -0
  297. package/dist/core/guard/types.js +1 -0
  298. package/dist/core/hooks/adapter/detect.d.ts +18 -1
  299. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  300. package/dist/core/hooks/adapter/detect.js +29 -0
  301. package/dist/core/hooks/adapter/events.d.ts +7 -4
  302. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  303. package/dist/core/hooks/adapter/events.js +13 -4
  304. package/dist/core/hooks/adapter/output.d.ts +2 -2
  305. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  306. package/dist/core/hooks/adapter/parse.d.ts +7 -4
  307. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  308. package/dist/core/hooks/adapter/parse.js +64 -24
  309. package/dist/core/hooks/adapter/wiring.d.ts +30 -4
  310. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  311. package/dist/core/hooks/adapter/wiring.js +89 -11
  312. package/dist/core/hooks/cli.d.ts +1 -1
  313. package/dist/core/hooks/cli.js +345 -400
  314. package/dist/core/hooks/codex-authorization.d.ts +30 -0
  315. package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
  316. package/dist/core/hooks/codex-authorization.js +199 -0
  317. package/dist/core/hooks/effects/image-capture.d.ts +20 -34
  318. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  319. package/dist/core/hooks/effects/image-capture.js +212 -221
  320. package/dist/core/hooks/effects/index.d.ts +1 -9
  321. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  322. package/dist/core/hooks/effects/index.js +1 -24
  323. package/dist/core/hooks/events/input-hash.d.ts +7 -0
  324. package/dist/core/hooks/events/input-hash.d.ts.map +1 -0
  325. package/dist/core/hooks/events/input-hash.js +43 -0
  326. package/dist/core/hooks/index.d.ts +4 -3
  327. package/dist/core/hooks/index.d.ts.map +1 -1
  328. package/dist/core/hooks/index.js +2 -3
  329. package/dist/core/hooks/resolve/coord-root.d.ts +1 -1
  330. package/dist/core/hooks/resolve/coord-root.js +1 -1
  331. package/dist/core/hooks/resolve/owner.d.ts +16 -15
  332. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  333. package/dist/core/hooks/resolve/owner.js +51 -42
  334. package/dist/core/hooks/resolve/transcript.d.ts +33 -3
  335. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  336. package/dist/core/hooks/resolve/transcript.js +155 -14
  337. package/dist/core/hooks/session-name-presence.d.ts +9 -10
  338. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  339. package/dist/core/hooks/session-name-presence.js +15 -15
  340. package/dist/core/hooks/unsafe-cross-shell.d.ts +1 -1
  341. package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -1
  342. package/dist/core/journal/index.d.ts +2 -2
  343. package/dist/core/journal/index.d.ts.map +1 -1
  344. package/dist/core/journal/index.js +9 -22
  345. package/dist/core/presence/blob.d.ts +8 -3
  346. package/dist/core/presence/blob.d.ts.map +1 -1
  347. package/dist/core/presence/blob.js +54 -41
  348. package/dist/core/presence/codec-digest.d.ts +29 -0
  349. package/dist/core/presence/codec-digest.d.ts.map +1 -0
  350. package/dist/core/presence/codec-digest.js +170 -0
  351. package/dist/core/presence/index.js +15 -1
  352. package/dist/core/presence/relay-client.d.ts +1 -1
  353. package/dist/core/presence/relay-client.d.ts.map +1 -1
  354. package/dist/core/presence/relay-client.js +5 -5
  355. package/dist/core/work/index.d.ts +1 -1
  356. package/dist/core/work/index.d.ts.map +1 -1
  357. package/dist/core/work/index.js +1 -1
  358. package/dist/core/work/read.d.ts +1 -1
  359. package/dist/core/work/read.d.ts.map +1 -1
  360. package/dist/core/work/read.js +1 -1
  361. package/dist/core/work/runner.d.ts.map +1 -1
  362. package/dist/core/work/runner.js +6 -0
  363. package/dist/core/work/state.d.ts +9 -0
  364. package/dist/core/work/state.d.ts.map +1 -1
  365. package/dist/core/work/state.js +17 -3
  366. package/dist/core/workflow/engine.js +24 -25
  367. package/dist/core/workflow/live-session-v3.d.ts +19 -0
  368. package/dist/core/workflow/live-session-v3.d.ts.map +1 -0
  369. package/dist/core/workflow/live-session-v3.js +86 -0
  370. package/dist/core/workflow/scope-id.d.ts +3 -0
  371. package/dist/core/workflow/scope-id.d.ts.map +1 -0
  372. package/dist/core/workflow/scope-id.js +5 -0
  373. package/dist/core/workflow/workspaces/leases.d.ts +4 -0
  374. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  375. package/dist/core/workflow/workspaces/leases.js +53 -19
  376. package/dist/index.d.ts +1 -1
  377. package/dist/index.js +1 -1
  378. package/dist/lib/browser/client.d.ts +41 -0
  379. package/dist/lib/browser/client.d.ts.map +1 -1
  380. package/dist/lib/browser/client.js +203 -0
  381. package/dist/lib/browser/critique.d.ts +28 -2
  382. package/dist/lib/browser/critique.d.ts.map +1 -1
  383. package/dist/lib/browser/critique.js +74 -18
  384. package/dist/lib/browser/geometry.d.ts +1 -1
  385. package/dist/lib/browser/geometry.d.ts.map +1 -1
  386. package/dist/lib/browser/geometry.js +130 -6
  387. package/dist/lib/browser/index.d.ts +3 -0
  388. package/dist/lib/browser/index.d.ts.map +1 -1
  389. package/dist/lib/browser/index.js +3 -0
  390. package/dist/lib/browser/qa-plan.d.ts +174 -0
  391. package/dist/lib/browser/qa-plan.d.ts.map +1 -0
  392. package/dist/lib/browser/qa-plan.js +321 -0
  393. package/dist/lib/browser/qa-reuse.d.ts +99 -0
  394. package/dist/lib/browser/qa-reuse.d.ts.map +1 -0
  395. package/dist/lib/browser/qa-reuse.js +222 -0
  396. package/dist/lib/browser/qa-snapshot.d.ts +64 -0
  397. package/dist/lib/browser/qa-snapshot.d.ts.map +1 -0
  398. package/dist/lib/browser/qa-snapshot.js +171 -0
  399. package/dist/lib/browser/runts.d.ts.map +1 -1
  400. package/dist/lib/browser/runts.js +22 -1
  401. package/dist/lib/browser/tiling.d.ts +71 -0
  402. package/dist/lib/browser/tiling.d.ts.map +1 -0
  403. package/dist/lib/browser/tiling.js +118 -0
  404. package/dist/lib/context/index.d.ts +0 -3
  405. package/dist/lib/context/index.d.ts.map +1 -1
  406. package/dist/lib/context/index.js +20 -42
  407. package/dist/lib/council/index.d.ts +2 -2
  408. package/dist/lib/council/index.d.ts.map +1 -1
  409. package/dist/lib/council/index.js +9 -20
  410. package/dist/lib/docs-frontmatter.d.ts +1 -6
  411. package/dist/lib/docs-frontmatter.d.ts.map +1 -1
  412. package/dist/lib/docs-frontmatter.js +12 -78
  413. package/dist/lib/docs-index.d.ts +9 -0
  414. package/dist/lib/docs-index.d.ts.map +1 -1
  415. package/dist/lib/docs-index.js +32 -3
  416. package/dist/lib/docs-lint.d.ts.map +1 -1
  417. package/dist/lib/docs-lint.js +14 -30
  418. package/dist/lib/docs-metadata-audit.d.ts +19 -0
  419. package/dist/lib/docs-metadata-audit.d.ts.map +1 -0
  420. package/dist/lib/docs-metadata-audit.js +132 -0
  421. package/dist/lib/docs-metadata-managed.d.ts +23 -0
  422. package/dist/lib/docs-metadata-managed.d.ts.map +1 -0
  423. package/dist/lib/docs-metadata-managed.js +78 -0
  424. package/dist/lib/docs-metadata-sync.d.ts +20 -0
  425. package/dist/lib/docs-metadata-sync.d.ts.map +1 -0
  426. package/dist/lib/docs-metadata-sync.js +222 -0
  427. package/dist/lib/docs-metadata-v2-schema.d.ts +273 -0
  428. package/dist/lib/docs-metadata-v2-schema.d.ts.map +1 -0
  429. package/dist/lib/docs-metadata-v2-schema.js +134 -0
  430. package/dist/lib/docs-metadata-v2.d.ts +21 -0
  431. package/dist/lib/docs-metadata-v2.d.ts.map +1 -0
  432. package/dist/lib/docs-metadata-v2.js +424 -0
  433. package/dist/lib/docs-new.d.ts +11 -0
  434. package/dist/lib/docs-new.d.ts.map +1 -0
  435. package/dist/lib/docs-new.js +52 -0
  436. package/dist/lib/docs-sweep.d.ts.map +1 -1
  437. package/dist/lib/docs-sweep.js +45 -29
  438. package/dist/lib/headless/index.d.ts +98 -0
  439. package/dist/lib/headless/index.d.ts.map +1 -0
  440. package/dist/lib/headless/index.js +211 -0
  441. package/dist/lib/identities/assume.d.ts +4 -4
  442. package/dist/lib/identities/assume.d.ts.map +1 -1
  443. package/dist/lib/identities/assume.js +52 -68
  444. package/dist/lib/instructions/apply.d.ts +5 -4
  445. package/dist/lib/instructions/apply.d.ts.map +1 -1
  446. package/dist/lib/instructions/apply.js +32 -20
  447. package/dist/lib/instructions/bundle.d.ts +33 -0
  448. package/dist/lib/instructions/bundle.d.ts.map +1 -0
  449. package/dist/lib/instructions/bundle.js +149 -0
  450. package/dist/lib/instructions/splice.d.ts +0 -1
  451. package/dist/lib/instructions/splice.d.ts.map +1 -1
  452. package/dist/lib/instructions/splice.js +22 -6
  453. package/dist/lib/instructions/templates.d.ts +7 -5
  454. package/dist/lib/instructions/templates.d.ts.map +1 -1
  455. package/dist/lib/instructions/templates.js +79 -9
  456. package/package.json +27 -6
  457. package/schemas/config.schema.json +120 -2
  458. package/schemas/docs-metadata-v2.schema.json +441 -0
  459. package/schemas/event-v3.schema.json +24472 -0
  460. package/src/commander.ts +7 -1
  461. package/src/commands/adapter.ts +2 -2
  462. package/src/commands/agents.ts +1737 -570
  463. package/src/commands/artifacts.ts +7 -6
  464. package/src/commands/backup.ts +29 -21
  465. package/src/commands/browse.ts +349 -8
  466. package/src/commands/checkpoint.ts +4 -3
  467. package/src/commands/claude-desktop.ts +7 -5
  468. package/src/commands/context.ts +1 -6
  469. package/src/commands/decision.ts +27 -44
  470. package/src/commands/docs.ts +100 -33
  471. package/src/commands/doctor.ts +73 -8
  472. package/src/commands/events.ts +256 -0
  473. package/src/commands/governor.ts +18 -4
  474. package/src/commands/init.ts +264 -57
  475. package/src/commands/instructions.ts +129 -0
  476. package/src/commands/journal.ts +4 -13
  477. package/src/commands/ledger-v3.ts +111 -0
  478. package/src/commands/presence.ts +18 -11
  479. package/src/commands/sync.ts +1 -1
  480. package/src/commands/web.ts +67 -8
  481. package/src/commands/work.ts +28 -5
  482. package/src/core/adapter.ts +5 -0
  483. package/src/core/adapters/attest.ts +8 -3
  484. package/src/core/adapters/profiles.ts +4 -6
  485. package/src/core/agents/canonical-emit.ts +39 -28
  486. package/src/core/agents/cli.ts +242 -263
  487. package/src/core/agents/codex-archive-v3.ts +138 -0
  488. package/src/core/agents/coord-client.ts +75 -177
  489. package/src/core/agents/finalization.ts +120 -38
  490. package/src/core/agents/index.ts +5 -5
  491. package/src/core/agents/live-authority-v3.ts +375 -0
  492. package/src/core/agents/live-lifecycle-v3.ts +140 -0
  493. package/src/core/agents/live-observation-v3.ts +216 -0
  494. package/src/core/agents/render/prompt-context.ts +89 -55
  495. package/src/core/agents/render/session-context.ts +23 -32
  496. package/src/core/agents/rules/claim-conflict.ts +85 -126
  497. package/src/core/agents/rules/commit-conflict.ts +3 -14
  498. package/src/core/agents/rules/stop-hook.ts +239 -293
  499. package/src/core/agents/session-events.ts +137 -104
  500. package/src/core/agents/session-finalization-state-v3.ts +90 -0
  501. package/src/core/agents/session-finalizer-v3.ts +953 -0
  502. package/src/core/agents/session-name-display.ts +149 -0
  503. package/src/core/agents/state/activity-log.ts +6 -14
  504. package/src/core/agents/state/heartbeat-writer.ts +98 -242
  505. package/src/core/agents/state/live-coordination-view.ts +290 -0
  506. package/src/core/agents/state/session-state.ts +143 -0
  507. package/src/core/agents/state/stale-sweep.ts +40 -17
  508. package/src/core/artifacts/index.ts +4 -5
  509. package/src/core/config.ts +148 -3
  510. package/src/core/context/index.ts +9 -17
  511. package/src/core/events/span-parent.ts +46 -0
  512. package/src/core/events/v3/advance.ts +168 -0
  513. package/src/core/events/v3/authority-outbox.ts +548 -0
  514. package/src/core/events/v3/base-builder.ts +60 -0
  515. package/src/core/events/v3/base-contract.ts +882 -0
  516. package/src/core/events/v3/bootstrap.ts +226 -0
  517. package/src/core/events/v3/builder.ts +59 -0
  518. package/src/core/events/v3/canonical.ts +153 -0
  519. package/src/core/events/v3/capabilities.ts +171 -0
  520. package/src/core/events/v3/capability-drift.ts +177 -0
  521. package/src/core/events/v3/catalog.ts +202 -0
  522. package/src/core/events/v3/clock-order.ts +41 -0
  523. package/src/core/events/v3/contract.ts +365 -0
  524. package/src/core/events/v3/control.ts +576 -0
  525. package/src/core/events/v3/coordination-view.ts +193 -0
  526. package/src/core/events/v3/economics.ts +384 -0
  527. package/src/core/events/v3/finalization-view.ts +67 -0
  528. package/src/core/events/v3/fingerprint-keys.ts +260 -0
  529. package/src/core/events/v3/generated.ts +48 -0
  530. package/src/core/events/v3/ids.ts +33 -0
  531. package/src/core/events/v3/index.ts +322 -0
  532. package/src/core/events/v3/latency.ts +551 -0
  533. package/src/core/events/v3/live-feed.ts +319 -0
  534. package/src/core/events/v3/live-observation.ts +39 -0
  535. package/src/core/events/v3/live-routing.ts +135 -0
  536. package/src/core/events/v3/producers/command-recorder.ts +631 -0
  537. package/src/core/events/v3/producers/command.ts +201 -0
  538. package/src/core/events/v3/producers/coordination-recorder.ts +538 -0
  539. package/src/core/events/v3/producers/coordination.ts +355 -0
  540. package/src/core/events/v3/producers/hook-base.ts +578 -0
  541. package/src/core/events/v3/producers/hook.ts +119 -0
  542. package/src/core/events/v3/producers/intake.ts +319 -0
  543. package/src/core/events/v3/producers/recorder.ts +2805 -0
  544. package/src/core/events/v3/projection.ts +653 -0
  545. package/src/core/events/v3/reader.ts +1127 -0
  546. package/src/core/events/v3/recovery-record.ts +244 -0
  547. package/src/core/events/v3/recovery.ts +271 -0
  548. package/src/core/events/v3/span-state.ts +187 -0
  549. package/src/core/events/v3/targets.ts +182 -0
  550. package/src/core/events/v3/turn-telemetry.ts +259 -0
  551. package/src/core/events/v3/validate.ts +361 -0
  552. package/src/core/events/v3/writer.ts +419 -0
  553. package/src/core/governor/index.ts +3 -0
  554. package/src/core/governor/plan-api.ts +10 -2
  555. package/src/core/governor/read.ts +3 -0
  556. package/src/core/governor/state.ts +52 -5
  557. package/src/core/guard/config.ts +206 -0
  558. package/src/core/guard/coordinator.ts +227 -0
  559. package/src/core/guard/evaluator.ts +343 -0
  560. package/src/core/guard/evidence-v3.ts +227 -0
  561. package/src/core/guard/index.ts +35 -0
  562. package/src/core/guard/live-source-v3.ts +204 -0
  563. package/src/core/guard/storage.ts +188 -0
  564. package/src/core/guard/types.ts +152 -0
  565. package/src/core/hooks/adapter/detect.ts +28 -1
  566. package/src/core/hooks/adapter/events.ts +15 -6
  567. package/src/core/hooks/adapter/output.ts +7 -2
  568. package/src/core/hooks/adapter/parse.ts +81 -36
  569. package/src/core/hooks/adapter/wiring.ts +119 -13
  570. package/src/core/hooks/cli.ts +398 -422
  571. package/src/core/hooks/codex-authorization.ts +253 -0
  572. package/src/core/hooks/effects/image-capture.ts +228 -238
  573. package/src/core/hooks/effects/index.ts +1 -36
  574. package/src/core/hooks/events/input-hash.ts +47 -0
  575. package/src/core/hooks/index.ts +4 -11
  576. package/src/core/hooks/resolve/coord-root.ts +1 -1
  577. package/src/core/hooks/resolve/owner.ts +52 -45
  578. package/src/core/hooks/resolve/transcript.ts +165 -14
  579. package/src/core/hooks/session-name-presence.ts +14 -15
  580. package/src/core/hooks/unsafe-cross-shell.ts +1 -1
  581. package/src/core/journal/index.ts +15 -22
  582. package/src/core/presence/blob.ts +63 -38
  583. package/src/core/presence/codec-digest.ts +215 -0
  584. package/src/core/presence/index.ts +18 -1
  585. package/src/core/presence/relay-client.ts +5 -5
  586. package/src/core/work/index.ts +3 -0
  587. package/src/core/work/read.ts +3 -0
  588. package/src/core/work/runner.ts +6 -0
  589. package/src/core/work/state.ts +27 -3
  590. package/src/core/workflow/engine.ts +24 -24
  591. package/src/core/workflow/live-session-v3.ts +126 -0
  592. package/src/core/workflow/scope-id.ts +6 -0
  593. package/src/core/workflow/workspaces/leases.ts +56 -19
  594. package/src/index.ts +1 -1
  595. package/src/lib/browser/client.ts +230 -0
  596. package/src/lib/browser/critique.ts +104 -27
  597. package/src/lib/browser/geometry.ts +143 -14
  598. package/src/lib/browser/index.ts +34 -0
  599. package/src/lib/browser/qa-plan.ts +501 -0
  600. package/src/lib/browser/qa-reuse.ts +306 -0
  601. package/src/lib/browser/qa-snapshot.ts +223 -0
  602. package/src/lib/browser/runts.ts +27 -2
  603. package/src/lib/browser/tiling.ts +164 -0
  604. package/src/lib/context/index.ts +22 -46
  605. package/src/lib/council/index.ts +9 -21
  606. package/src/lib/docs-frontmatter.ts +8 -79
  607. package/src/lib/docs-index.ts +33 -3
  608. package/src/lib/docs-lint.ts +15 -29
  609. package/src/lib/docs-metadata-audit.ts +166 -0
  610. package/src/lib/docs-metadata-managed.ts +83 -0
  611. package/src/lib/docs-metadata-sync.ts +262 -0
  612. package/src/lib/docs-metadata-v2-schema.ts +138 -0
  613. package/src/lib/docs-metadata-v2.ts +646 -0
  614. package/src/lib/docs-new.ts +63 -0
  615. package/src/lib/docs-sweep.ts +42 -28
  616. package/src/lib/headless/index.ts +287 -0
  617. package/src/lib/identities/assume.ts +67 -86
  618. package/src/lib/instructions/apply.ts +33 -20
  619. package/src/lib/instructions/bundle.ts +199 -0
  620. package/src/lib/instructions/splice.ts +21 -7
  621. package/src/lib/instructions/templates.ts +85 -13
  622. package/dist/core/agents/cli-emit.d.ts +0 -27
  623. package/dist/core/agents/cli-emit.d.ts.map +0 -1
  624. package/dist/core/agents/cli-emit.js +0 -57
  625. package/dist/core/agents/codex-replay.d.ts +0 -29
  626. package/dist/core/agents/codex-replay.d.ts.map +0 -1
  627. package/dist/core/agents/codex-replay.js +0 -138
  628. package/dist/core/agents/events/consume.d.ts +0 -82
  629. package/dist/core/agents/events/consume.d.ts.map +0 -1
  630. package/dist/core/agents/events/consume.js +0 -195
  631. package/dist/core/agents/events/emit.d.ts +0 -42
  632. package/dist/core/agents/events/emit.d.ts.map +0 -1
  633. package/dist/core/agents/events/emit.js +0 -75
  634. package/dist/core/agents/events/ulid.d.ts +0 -11
  635. package/dist/core/agents/events/ulid.d.ts.map +0 -1
  636. package/dist/core/agents/events/ulid.js +0 -47
  637. package/dist/core/agents/state/heartbeat-projector.d.ts +0 -65
  638. package/dist/core/agents/state/heartbeat-projector.d.ts.map +0 -1
  639. package/dist/core/agents/state/heartbeat-projector.js +0 -527
  640. package/dist/core/hooks/events/emit.d.ts +0 -31
  641. package/dist/core/hooks/events/emit.d.ts.map +0 -1
  642. package/dist/core/hooks/events/emit.js +0 -93
  643. package/dist/core/hooks/events/rotate.d.ts +0 -43
  644. package/dist/core/hooks/events/rotate.d.ts.map +0 -1
  645. package/dist/core/hooks/events/rotate.js +0 -142
  646. package/dist/core/hooks/events/schema.d.ts +0 -312
  647. package/dist/core/hooks/events/schema.d.ts.map +0 -1
  648. package/dist/core/hooks/events/schema.js +0 -12
  649. package/dist/core/hooks/events/ulid.d.ts +0 -10
  650. package/dist/core/hooks/events/ulid.d.ts.map +0 -1
  651. package/dist/core/hooks/events/ulid.js +0 -47
  652. package/dist/lib/docs-frontmatter-migrate.d.ts +0 -33
  653. package/dist/lib/docs-frontmatter-migrate.d.ts.map +0 -1
  654. package/dist/lib/docs-frontmatter-migrate.js +0 -364
  655. package/src/core/agents/cli-emit.ts +0 -64
  656. package/src/core/agents/codex-replay.ts +0 -163
  657. package/src/core/agents/events/consume.ts +0 -254
  658. package/src/core/agents/events/emit.ts +0 -115
  659. package/src/core/agents/events/ulid.ts +0 -51
  660. package/src/core/agents/state/heartbeat-projector.ts +0 -582
  661. package/src/core/hooks/events/emit.ts +0 -125
  662. package/src/core/hooks/events/rotate.ts +0 -151
  663. package/src/core/hooks/events/schema.ts +0 -538
  664. package/src/core/hooks/events/ulid.ts +0 -51
  665. package/src/lib/docs-frontmatter-migrate.ts +0 -427
@@ -0,0 +1,306 @@
1
+ // Visual-result reuse for critique tiles (plan 3.3): avoid re-judging pixels
2
+ // that did not change.
3
+ //
4
+ // Two mechanisms share this module; which is primary was decided by the
5
+ // Stage 0 byte-stability experiment, not preference:
6
+ //
7
+ // Path B (primary): band-level pixel diff. Compare each candidate tile's
8
+ // document-space rect in the CURRENT full-page screenshot against the SAME
9
+ // rect in the persisted baseline screenshot (qa-snapshot.ts), with
10
+ // pixelmatch's anti-aliasing tolerance. At or below a strict mismatch
11
+ // ratio (default 0.001), the tile region provably didn't change and the
12
+ // provider call is skipped. No byte identity required, so font
13
+ // rasterization and raster-path drift don't defeat it.
14
+ //
15
+ // Path A (observe mode: key computation + logging only): the Stage 0
16
+ // byte-stability experiment PASSED its gate (100% cross-restart identical
17
+ // bands on real pages, same-day/same-machine, deterministic settle), so an
18
+ // exact-digest cache is viable — but Path B subsumes its win (a
19
+ // byte-identical band pixel-diffs at ratio 0) with zero extra storage, so
20
+ // only the versioned cache key ships until telemetry shows a payoff Path B
21
+ // misses (e.g. cross-target tile sharing).
22
+ //
23
+ // Hard rules, both paths: never convert a prior failure into a pass — any
24
+ // baseline finding overlapping a tile's region forces a fresh review; a
25
+ // missing/invalid baseline, changed rubric, or changed critique contract is
26
+ // a miss, never an error.
27
+
28
+ import { createHash } from "node:crypto";
29
+ import pixelmatch from "pixelmatch";
30
+ import { PNG } from "pngjs";
31
+ import type { CritiqueFinding, CritiqueTile } from "./critique.js";
32
+ import { fnv1a32 } from "./qa-plan.js";
33
+
34
+ /** Bump when the critique request/response contract changes in a way that
35
+ * invalidates persisted findings (rubric semantics, finding schema, tiling
36
+ * geometry meaning). Part of every cache key and persisted critique. */
37
+ export const QA_CRITIQUE_CONTRACT_VERSION = 1;
38
+
39
+ /** Default Path B mismatch-ratio threshold. Strict on purpose: reuse trades
40
+ * essentially nothing for robustness against rasterization noise, and a
41
+ * looser threshold could hide a sub-threshold real change. */
42
+ export const DEFAULT_REUSE_MISMATCH_RATIO = 0.001;
43
+
44
+ /** Critique results persisted beside a QA snapshot, enough to decide reuse
45
+ * later: findings plus the geometry they were judged under. */
46
+ export interface PersistedCritique {
47
+ contract_version: number;
48
+ /** fnv1a32 of the exact rubric text the findings were judged under. */
49
+ rubric_digest: string;
50
+ outcome: "pass" | "fail";
51
+ findings: CritiqueFinding[];
52
+ tiles: Array<{
53
+ index: number;
54
+ label: string;
55
+ x: number;
56
+ scrollY: number;
57
+ width: number;
58
+ height: number;
59
+ }>;
60
+ }
61
+
62
+ /** Versioned, canonicalized Path A cache key (observe mode). Field order is
63
+ * part of the contract — never reorder without bumping the version. */
64
+ export function qaTileCacheKey(parts: {
65
+ tilePngSha256: string;
66
+ rubricDigest: string;
67
+ provider: string;
68
+ model: string;
69
+ viewport: string;
70
+ deviceScaleFactor: number;
71
+ theme: string;
72
+ state: string;
73
+ tileLabel: string;
74
+ browserVersion?: string;
75
+ }): string {
76
+ const canonical = [
77
+ `v${QA_CRITIQUE_CONTRACT_VERSION}`,
78
+ `png:${parts.tilePngSha256}`,
79
+ `rubric:${parts.rubricDigest}`,
80
+ `provider:${parts.provider}`,
81
+ `model:${parts.model}`,
82
+ `viewport:${parts.viewport}`,
83
+ `dpr:${parts.deviceScaleFactor}`,
84
+ `theme:${parts.theme}`,
85
+ `state:${parts.state}`,
86
+ `tile:${parts.tileLabel}`,
87
+ `browser:${parts.browserVersion ?? "unknown"}`,
88
+ ].join("\x1f");
89
+ return `qa1-${createHash("sha256").update(canonical).digest("hex")}`;
90
+ }
91
+
92
+ export function sha256Hex(buf: Buffer | Uint8Array): string {
93
+ return createHash("sha256").update(buf).digest("hex");
94
+ }
95
+
96
+ export function rubricDigest(rubric: string): string {
97
+ // Whitespace-normalized so formatting-only edits don't invalidate reuse.
98
+ return fnv1a32(rubric.replace(/\s+/g, " ").trim());
99
+ }
100
+
101
+ function cropRegion(
102
+ png: PNG,
103
+ rect: { x: number; y: number; width: number; height: number },
104
+ ): PNG | null {
105
+ const sx = Math.max(0, Math.min(Math.round(rect.x), png.width - 1));
106
+ const sy = Math.max(0, Math.min(Math.round(rect.y), png.height - 1));
107
+ const w = Math.min(Math.round(rect.width), png.width - sx);
108
+ const h = Math.min(Math.round(rect.height), png.height - sy);
109
+ if (w <= 0 || h <= 0) return null;
110
+ const out = new PNG({ width: w, height: h });
111
+ for (let row = 0; row < h; row++) {
112
+ const srcStart = ((sy + row) * png.width + sx) * 4;
113
+ png.data.copy(out.data, row * w * 4, srcStart, srcStart + w * 4);
114
+ }
115
+ return out;
116
+ }
117
+
118
+ /**
119
+ * Pixel-compare one document-space rect across two full-page screenshots.
120
+ * `sizeMismatch` is true when the rect isn't fully comparable in both images
121
+ * (page grew/shrank past it) — the caller treats that as changed.
122
+ */
123
+ export function rectMismatch(
124
+ baselineFullPage: PNG,
125
+ currentFullPage: PNG,
126
+ rect: { x: number; y: number; width: number; height: number },
127
+ opts: { pixelThreshold?: number } = {},
128
+ ): { ratio: number; comparablePixels: number; sizeMismatch: boolean } {
129
+ const base = cropRegion(baselineFullPage, rect);
130
+ const cur = cropRegion(currentFullPage, rect);
131
+ if (!base || !cur || base.width !== cur.width || base.height !== cur.height) {
132
+ return { ratio: 1, comparablePixels: 0, sizeMismatch: true };
133
+ }
134
+ const mismatched = pixelmatch(base.data, cur.data, undefined, base.width, base.height, {
135
+ threshold: opts.pixelThreshold ?? 0.1,
136
+ includeAA: false,
137
+ });
138
+ const total = base.width * base.height;
139
+ return { ratio: mismatched / total, comparablePixels: total, sizeMismatch: false };
140
+ }
141
+
142
+ export interface TileReuseDecision {
143
+ index: number;
144
+ label: string;
145
+ reuse: boolean;
146
+ reason: string;
147
+ /** Rect mismatch ratio, present when a pixel comparison ran. */
148
+ mismatch_ratio?: number;
149
+ }
150
+
151
+ export interface CritiqueReusePlan {
152
+ mode: "band-diff";
153
+ /** Tiles that must be reviewed fresh, in original order. */
154
+ review: CritiqueTile[];
155
+ decisions: TileReuseDecision[];
156
+ tiles_total: number;
157
+ tiles_reused: number;
158
+ tiles_reviewed: number;
159
+ provider_calls_avoided: number;
160
+ mismatch_threshold: number;
161
+ /** Set when the whole baseline was unusable (everything reviews fresh). */
162
+ invalidation?: string;
163
+ }
164
+
165
+ function wholeRunMiss(
166
+ tiles: CritiqueTile[],
167
+ threshold: number,
168
+ invalidation: string,
169
+ ): CritiqueReusePlan {
170
+ return {
171
+ mode: "band-diff",
172
+ review: tiles,
173
+ decisions: tiles.map((t) => ({
174
+ index: t.index,
175
+ label: t.label,
176
+ reuse: false,
177
+ reason: invalidation,
178
+ })),
179
+ tiles_total: tiles.length,
180
+ tiles_reused: 0,
181
+ tiles_reviewed: tiles.length,
182
+ provider_calls_avoided: 0,
183
+ mismatch_threshold: threshold,
184
+ invalidation,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Path B reuse plan: decide per tile whether the baseline run's verdict still
190
+ * covers it. A tile is reused ONLY when (a) the baseline is valid for this
191
+ * rubric and contract, (b) no baseline finding overlaps the tile's document
192
+ * region (a prior failure is never converted into a pass), and (c) its rect
193
+ * pixel-matches the baseline screenshot at or below the strict threshold.
194
+ * Everything else reviews fresh — reuse can produce false REVIEWS, never
195
+ * false passes.
196
+ */
197
+ export function planCritiqueReuse(args: {
198
+ baselineScreenshot: Buffer;
199
+ baselineCritique: PersistedCritique;
200
+ currentScreenshot: Buffer;
201
+ tiles: CritiqueTile[];
202
+ rubric: string;
203
+ mismatchThreshold?: number;
204
+ }): CritiqueReusePlan {
205
+ const threshold = args.mismatchThreshold ?? DEFAULT_REUSE_MISMATCH_RATIO;
206
+ const { baselineCritique, tiles } = args;
207
+ if (baselineCritique.outcome !== "pass") {
208
+ return wholeRunMiss(tiles, threshold, "baseline critique did not pass");
209
+ }
210
+ if (baselineCritique.contract_version !== QA_CRITIQUE_CONTRACT_VERSION) {
211
+ return wholeRunMiss(
212
+ tiles,
213
+ threshold,
214
+ `baseline critique contract v${baselineCritique.contract_version} != v${QA_CRITIQUE_CONTRACT_VERSION}`,
215
+ );
216
+ }
217
+ if (baselineCritique.rubric_digest !== rubricDigest(args.rubric)) {
218
+ return wholeRunMiss(tiles, threshold, "rubric changed since the baseline run");
219
+ }
220
+ let baselinePng: PNG;
221
+ let currentPng: PNG;
222
+ try {
223
+ baselinePng = PNG.sync.read(args.baselineScreenshot);
224
+ currentPng = PNG.sync.read(args.currentScreenshot);
225
+ } catch {
226
+ return wholeRunMiss(tiles, threshold, "unreadable baseline or current screenshot");
227
+ }
228
+
229
+ // Document regions of baseline tiles that produced findings.
230
+ const findingTiles = new Set(baselineCritique.findings.map((f) => f.tile));
231
+ const dirtyRegions = baselineCritique.tiles
232
+ .filter((t) => findingTiles.has(t.index))
233
+ .map((t) => ({ top: t.scrollY, bottom: t.scrollY + t.height }));
234
+
235
+ const decisions: TileReuseDecision[] = [];
236
+ const review: CritiqueTile[] = [];
237
+ for (const tile of tiles) {
238
+ const top = tile.scrollY;
239
+ const bottom = tile.scrollY + tile.height;
240
+ const left = tile.x ?? 0;
241
+ const right = left + tile.width;
242
+ const coverage = baselineCritique.tiles
243
+ .filter((prior) => prior.x <= left && prior.x + prior.width >= right)
244
+ .map((prior) => ({ top: prior.scrollY, bottom: prior.scrollY + prior.height }))
245
+ .sort((a, b) => a.top - b.top);
246
+ let coveredTo = top;
247
+ for (const region of coverage) {
248
+ if (region.bottom <= coveredTo || region.top > coveredTo) continue;
249
+ coveredTo = Math.max(coveredTo, region.bottom);
250
+ if (coveredTo >= bottom) break;
251
+ }
252
+ if (coveredTo < bottom) {
253
+ decisions.push({
254
+ index: tile.index,
255
+ label: tile.label,
256
+ reuse: false,
257
+ reason: "baseline critique did not cover this entire region",
258
+ });
259
+ review.push(tile);
260
+ continue;
261
+ }
262
+ const overlapsFinding = dirtyRegions.some((r) => r.top < bottom && r.bottom > top);
263
+ if (overlapsFinding) {
264
+ decisions.push({
265
+ index: tile.index,
266
+ label: tile.label,
267
+ reuse: false,
268
+ reason: "baseline run had findings in this region — always re-reviewed",
269
+ });
270
+ review.push(tile);
271
+ continue;
272
+ }
273
+ const rect = { x: tile.x ?? 0, y: tile.scrollY, width: tile.width, height: tile.height };
274
+ const cmp = rectMismatch(baselinePng, currentPng, rect);
275
+ if (cmp.sizeMismatch || cmp.ratio > threshold) {
276
+ decisions.push({
277
+ index: tile.index,
278
+ label: tile.label,
279
+ reuse: false,
280
+ reason: cmp.sizeMismatch
281
+ ? "region not comparable across screenshots (layout size changed)"
282
+ : `mismatch ratio ${cmp.ratio.toFixed(6)} > ${threshold}`,
283
+ mismatch_ratio: cmp.sizeMismatch ? undefined : cmp.ratio,
284
+ });
285
+ review.push(tile);
286
+ continue;
287
+ }
288
+ decisions.push({
289
+ index: tile.index,
290
+ label: tile.label,
291
+ reuse: true,
292
+ reason: `clean region pixel-matched baseline (ratio ${cmp.ratio.toFixed(6)})`,
293
+ mismatch_ratio: cmp.ratio,
294
+ });
295
+ }
296
+ return {
297
+ mode: "band-diff",
298
+ review,
299
+ decisions,
300
+ tiles_total: tiles.length,
301
+ tiles_reused: tiles.length - review.length,
302
+ tiles_reviewed: review.length,
303
+ provider_calls_avoided: tiles.length - review.length,
304
+ mismatch_threshold: threshold,
305
+ };
306
+ }
@@ -0,0 +1,223 @@
1
+ // Persisted QA snapshots: the baseline source that makes repeat QA cheap.
2
+ //
3
+ // Every passing QA run persists its final page signature (and optionally the
4
+ // serialized DOM + full-page screenshot) per render context. The next run
5
+ // inherits its baseline for free instead of re-rendering production or a git
6
+ // revision. Resolution order (the plan's 2.6):
7
+ // 1. persisted QA snapshot (this store)
8
+ // 2. production render (caller-supplied fallback)
9
+ // 3. baseline-revision render (caller-supplied fallback, from a worktree)
10
+ // 4. none → classification is `unknown`, never text-only
11
+ //
12
+ // Storage is content-under-a-key, like visual-diff baselines: a directory per
13
+ // target, a subdirectory per context, atomic writes (tmp + rename) so a
14
+ // crashed writer never leaves a half-baseline the next run would trust.
15
+
16
+ import {
17
+ existsSync,
18
+ mkdirSync,
19
+ readdirSync,
20
+ readFileSync,
21
+ renameSync,
22
+ rmSync,
23
+ writeFileSync,
24
+ } from "node:fs";
25
+ import { homedir } from "node:os";
26
+ import { resolve } from "node:path";
27
+ import { fnv1a32, type QaContext, type QaSignature } from "./qa-plan.js";
28
+ import type { PersistedCritique } from "./qa-reuse.js";
29
+
30
+ const SNAPSHOT_DIR = resolve(homedir(), ".cache", "harnery", "qa-snapshots");
31
+
32
+ export interface QaSnapshotStoreOptions {
33
+ /** Override the store root (tests, host-managed cache locations). */
34
+ root?: string;
35
+ }
36
+
37
+ export interface StoredQaSnapshot {
38
+ signature: QaSignature;
39
+ /** Directory holding this snapshot's files. */
40
+ path: string;
41
+ domHtmlPath?: string;
42
+ screenshotPath?: string;
43
+ /** Critique results persisted with the snapshot (full-coverage runs only) —
44
+ * what the band-diff reuse layer replays for provably-unchanged regions. */
45
+ critique?: PersistedCritique;
46
+ }
47
+
48
+ /** Filesystem-safe key for a target URL/file + render context. The digest
49
+ * disambiguates targets whose sanitized slugs collide. */
50
+ export function qaSnapshotKey(target: string): string {
51
+ const source = target.replace(/^[a-z]+:\/\//i, "");
52
+ let slug = "";
53
+ let separatorPending = false;
54
+ for (let index = 0; index < source.length && slug.length < 80; index++) {
55
+ const code = source.charCodeAt(index);
56
+ const alpha = (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
57
+ const digit = code >= 48 && code <= 57;
58
+ if (alpha || digit) {
59
+ if (separatorPending && slug.length > 0 && slug.length < 80) slug += "-";
60
+ if (slug.length < 80) slug += source[index];
61
+ separatorPending = false;
62
+ } else if (slug.length > 0) {
63
+ separatorPending = true;
64
+ }
65
+ }
66
+ return `${slug || "target"}-${fnv1a32(target).slice(0, 8)}`;
67
+ }
68
+
69
+ function contextDirName(context: QaContext): string {
70
+ const clean = (part: string) => part.replace(/[^a-zA-Z0-9_.]+/g, "-");
71
+ return `${clean(context.viewport)}-${clean(context.theme)}-${clean(context.state)}`;
72
+ }
73
+
74
+ function snapshotDir(target: string, context: QaContext, opts: QaSnapshotStoreOptions): string {
75
+ return resolve(opts.root ?? SNAPSHOT_DIR, qaSnapshotKey(target), contextDirName(context));
76
+ }
77
+
78
+ /**
79
+ * Persist a QA snapshot atomically. The whole context directory is staged as
80
+ * `<dir>.tmp-<pid>` and renamed into place, replacing any prior snapshot for
81
+ * the same target + context — a reader never observes a partial write.
82
+ */
83
+ export function saveQaSnapshot(
84
+ target: string,
85
+ context: QaContext,
86
+ content: {
87
+ signature: QaSignature;
88
+ domHtml?: string;
89
+ screenshotPng?: Buffer;
90
+ critique?: PersistedCritique;
91
+ },
92
+ opts: QaSnapshotStoreOptions = {},
93
+ ): StoredQaSnapshot {
94
+ const dir = snapshotDir(target, context, opts);
95
+ const staging = `${dir}.tmp-${process.pid}`;
96
+ rmSync(staging, { recursive: true, force: true });
97
+ mkdirSync(staging, { recursive: true });
98
+ writeFileSync(resolve(staging, "signature.json"), `${JSON.stringify(content.signature)}\n`);
99
+ if (content.domHtml !== undefined) {
100
+ writeFileSync(resolve(staging, "dom.html"), content.domHtml);
101
+ }
102
+ if (content.screenshotPng !== undefined) {
103
+ writeFileSync(resolve(staging, "screenshot.png"), content.screenshotPng);
104
+ }
105
+ if (content.critique !== undefined) {
106
+ writeFileSync(resolve(staging, "critique.json"), `${JSON.stringify(content.critique)}\n`);
107
+ }
108
+ rmSync(dir, { recursive: true, force: true });
109
+ renameSync(staging, dir);
110
+ return {
111
+ signature: content.signature,
112
+ path: dir,
113
+ ...(content.domHtml !== undefined ? { domHtmlPath: resolve(dir, "dom.html") } : {}),
114
+ ...(content.screenshotPng !== undefined
115
+ ? { screenshotPath: resolve(dir, "screenshot.png") }
116
+ : {}),
117
+ };
118
+ }
119
+
120
+ /** Load the persisted snapshot for a target + context, or null. A corrupt
121
+ * signature file returns null (and the caller re-acquires a baseline) rather
122
+ * than throwing — a bad cache entry must never block QA. */
123
+ export function loadQaSnapshot(
124
+ target: string,
125
+ context: QaContext,
126
+ opts: QaSnapshotStoreOptions = {},
127
+ ): StoredQaSnapshot | null {
128
+ const dir = snapshotDir(target, context, opts);
129
+ const sigPath = resolve(dir, "signature.json");
130
+ if (!existsSync(sigPath)) return null;
131
+ try {
132
+ const signature = JSON.parse(readFileSync(sigPath, "utf8")) as QaSignature;
133
+ if (!signature || !Array.isArray(signature.nodes) || !Array.isArray(signature.stylesheets)) {
134
+ return null;
135
+ }
136
+ const domHtmlPath = resolve(dir, "dom.html");
137
+ const screenshotPath = resolve(dir, "screenshot.png");
138
+ const critiquePath = resolve(dir, "critique.json");
139
+ let critique: PersistedCritique | undefined;
140
+ if (existsSync(critiquePath)) {
141
+ try {
142
+ const parsed = JSON.parse(readFileSync(critiquePath, "utf8")) as PersistedCritique;
143
+ if (parsed && typeof parsed.contract_version === "number" && Array.isArray(parsed.tiles)) {
144
+ critique = parsed;
145
+ }
146
+ } catch {
147
+ // corrupt critique.json degrades to a reuse miss, never an error
148
+ }
149
+ }
150
+ return {
151
+ signature,
152
+ path: dir,
153
+ ...(existsSync(domHtmlPath) ? { domHtmlPath } : {}),
154
+ ...(existsSync(screenshotPath) ? { screenshotPath } : {}),
155
+ ...(critique ? { critique } : {}),
156
+ };
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
161
+
162
+ /** List targets with at least one persisted snapshot (store maintenance). */
163
+ export function listQaSnapshotTargets(opts: QaSnapshotStoreOptions = {}): string[] {
164
+ const root = opts.root ?? SNAPSHOT_DIR;
165
+ if (!existsSync(root)) return [];
166
+ try {
167
+ return readdirSync(root)
168
+ .filter((name) => !name.includes(".tmp-"))
169
+ .sort();
170
+ } catch {
171
+ return [];
172
+ }
173
+ }
174
+
175
+ export interface ResolvedQaBaseline {
176
+ /** `qa-snapshot:<capturedAt>` | `production-render:<capturedAt>` |
177
+ * `revision-render:<label>` | `none` — recorded in the manifest so a
178
+ * reviewer can judge staleness. */
179
+ source: string;
180
+ signature: QaSignature | null;
181
+ }
182
+
183
+ /**
184
+ * Walk the baseline resolution order with caller-supplied fallbacks. Pure
185
+ * orchestration: rendering lives in the caller (production URLs and git
186
+ * worktrees are its business), this just encodes the order and the source
187
+ * labeling. A fallback that throws or returns null falls through to the next
188
+ * rung; exhausting all rungs returns `{ source: "none", signature: null }`,
189
+ * which classifies as `unknown` — never silently text-only.
190
+ */
191
+ export async function resolveQaBaseline(opts: {
192
+ target: string;
193
+ context: QaContext;
194
+ store?: QaSnapshotStoreOptions;
195
+ renderProduction?: () => Promise<QaSignature | null>;
196
+ renderRevision?: { label: string; render: () => Promise<QaSignature | null> };
197
+ }): Promise<ResolvedQaBaseline> {
198
+ const stored = loadQaSnapshot(opts.target, opts.context, opts.store ?? {});
199
+ if (stored) {
200
+ return { source: `qa-snapshot:${stored.signature.capturedAt}`, signature: stored.signature };
201
+ }
202
+ if (opts.renderProduction) {
203
+ try {
204
+ const signature = await opts.renderProduction();
205
+ if (signature) return { source: `production-render:${signature.capturedAt}`, signature };
206
+ } catch {
207
+ // fall through — an unreachable production URL is not a verdict
208
+ }
209
+ }
210
+ if (opts.renderRevision) {
211
+ try {
212
+ const signature = await opts.renderRevision.render();
213
+ if (signature) {
214
+ return { source: `revision-render:${opts.renderRevision.label}`, signature };
215
+ }
216
+ } catch {
217
+ // fall through
218
+ }
219
+ }
220
+ return { source: "none", signature: null };
221
+ }
222
+
223
+ export { SNAPSHOT_DIR as QA_SNAPSHOT_DIR };
@@ -129,7 +129,7 @@ export function buildRuntsCheck(): (args: {
129
129
  scanned++;
130
130
 
131
131
  // Map every word to its visual line by rounding the Range rect top.
132
- const lines = new Map<number, { words: string[]; last: DOMRect }>();
132
+ const lines = new Map<number, { words: string[]; last: DOMRect; node: Node }>();
133
133
  const tw = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
134
134
  for (let n = tw.nextNode(); n; n = tw.nextNode()) {
135
135
  const value = n.nodeValue ?? "";
@@ -144,8 +144,9 @@ export function buildRuntsCheck(): (args: {
144
144
  if (entry) {
145
145
  entry.words.push(m[0]);
146
146
  entry.last = rect;
147
+ entry.node = n;
147
148
  } else {
148
- lines.set(top, { words: [m[0]], last: rect });
149
+ lines.set(top, { words: [m[0]], last: rect, node: n });
149
150
  }
150
151
  }
151
152
  }
@@ -156,6 +157,30 @@ export function buildRuntsCheck(): (args: {
156
157
  if (last?.words.length !== 1) continue;
157
158
  const word = last.words[0] ?? "";
158
159
  if (ATOMIC.test(word)) continue;
160
+ // A chip is not prose. A lone token whose glyphs live inside a padded,
161
+ // boxed inline element (a <code> chip, pill, or badge) is a UI control:
162
+ // it cannot be rebalanced by rewording, tag rows legitimately wrap to a
163
+ // single chip, and its shifted glyph box also buckets as a phantom
164
+ // "line" when it sits mid-line beside normal text. Walk the word's
165
+ // inline ancestry (tag-based, since flex/grid parents blockify computed
166
+ // display) and skip the finding when a chip hosts it.
167
+ const chipHosted = (() => {
168
+ for (
169
+ let e = last.node.parentElement;
170
+ e && e !== el && INLINE.has(e.tagName);
171
+ e = e.parentElement
172
+ ) {
173
+ const cs = getComputedStyle(e);
174
+ const padX =
175
+ (Number.parseFloat(cs.paddingLeft) || 0) + (Number.parseFloat(cs.paddingRight) || 0);
176
+ const boxed =
177
+ (cs.backgroundColor !== "rgba(0, 0, 0, 0)" && cs.backgroundColor !== "transparent") ||
178
+ (Number.parseFloat(cs.borderTopWidth) || 0) > 0;
179
+ if (padX > 0 && boxed) return true;
180
+ }
181
+ return false;
182
+ })();
183
+ if (chipHosted) continue;
159
184
 
160
185
  runts.push({
161
186
  block: label(el),