harnery 0.36.0 → 0.38.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 (432) hide show
  1. package/dist/commander.d.ts +9 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +10 -1
  4. package/dist/commands/admission.d.ts +21 -0
  5. package/dist/commands/admission.d.ts.map +1 -0
  6. package/dist/commands/admission.js +565 -0
  7. package/dist/commands/agents.d.ts +7 -0
  8. package/dist/commands/agents.d.ts.map +1 -1
  9. package/dist/commands/agents.js +148 -47
  10. package/dist/commands/artifacts.d.ts.map +1 -1
  11. package/dist/commands/artifacts.js +73 -2
  12. package/dist/commands/browse-ai.d.ts +2 -2
  13. package/dist/commands/browse-ai.d.ts.map +1 -1
  14. package/dist/commands/browse-ai.js +6 -4
  15. package/dist/commands/browse.d.ts.map +1 -1
  16. package/dist/commands/browse.js +360 -21
  17. package/dist/commands/diagnostics.d.ts +4 -0
  18. package/dist/commands/diagnostics.d.ts.map +1 -0
  19. package/dist/commands/diagnostics.js +229 -0
  20. package/dist/commands/fetch.js +1 -0
  21. package/dist/commands/files.d.ts +12 -0
  22. package/dist/commands/files.d.ts.map +1 -1
  23. package/dist/commands/files.js +54 -1
  24. package/dist/commands/governor.d.ts.map +1 -1
  25. package/dist/commands/governor.js +4 -0
  26. package/dist/commands/ledger-v3.d.ts.map +1 -1
  27. package/dist/commands/ledger-v3.js +71 -2
  28. package/dist/commands/qa-record.d.ts +144 -0
  29. package/dist/commands/qa-record.d.ts.map +1 -0
  30. package/dist/commands/qa-record.js +0 -0
  31. package/dist/commands/qa-run.d.ts +8 -0
  32. package/dist/commands/qa-run.d.ts.map +1 -0
  33. package/dist/commands/qa-run.js +396 -0
  34. package/dist/commands/qa-status.d.ts +71 -0
  35. package/dist/commands/qa-status.d.ts.map +1 -0
  36. package/dist/commands/qa-status.js +490 -0
  37. package/dist/commands/qa-verify.d.ts +40 -0
  38. package/dist/commands/qa-verify.d.ts.map +1 -0
  39. package/dist/commands/qa-verify.js +180 -0
  40. package/dist/commands/resources.d.ts +4 -0
  41. package/dist/commands/resources.d.ts.map +1 -0
  42. package/dist/commands/resources.js +29 -0
  43. package/dist/commands/review-pack.d.ts +4 -0
  44. package/dist/commands/review-pack.d.ts.map +1 -0
  45. package/dist/commands/review-pack.js +1001 -0
  46. package/dist/commands/supervisor.d.ts +4 -0
  47. package/dist/commands/supervisor.d.ts.map +1 -0
  48. package/dist/commands/supervisor.js +101 -0
  49. package/dist/commands/web.d.ts.map +1 -1
  50. package/dist/commands/web.js +17 -0
  51. package/dist/commands/workflow.d.ts.map +1 -1
  52. package/dist/commands/workflow.js +4 -0
  53. package/dist/core/agents/cli.js +7 -3
  54. package/dist/core/agents/health.d.ts +17 -0
  55. package/dist/core/agents/health.d.ts.map +1 -0
  56. package/dist/core/agents/health.js +109 -0
  57. package/dist/core/agents/mailbox.d.ts +92 -0
  58. package/dist/core/agents/mailbox.d.ts.map +1 -0
  59. package/dist/core/agents/mailbox.js +325 -0
  60. package/dist/core/agents/qa-signal.d.ts +111 -0
  61. package/dist/core/agents/qa-signal.d.ts.map +1 -0
  62. package/dist/core/agents/qa-signal.js +231 -0
  63. package/dist/core/agents/reconcile-coordination-v3.d.ts +44 -0
  64. package/dist/core/agents/reconcile-coordination-v3.d.ts.map +1 -0
  65. package/dist/core/agents/reconcile-coordination-v3.js +58 -0
  66. package/dist/core/agents/render/prompt-context.d.ts +5 -0
  67. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  68. package/dist/core/agents/render/prompt-context.js +34 -13
  69. package/dist/core/agents/render/session-context.d.ts +8 -0
  70. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  71. package/dist/core/agents/render/session-context.js +34 -3
  72. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  73. package/dist/core/agents/rules/commit-conflict.js +50 -1
  74. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  75. package/dist/core/agents/rules/stop-hook.js +6 -10
  76. package/dist/core/agents/session-finalizer-v3.js +4 -1
  77. package/dist/core/agents/session-name-display.d.ts +20 -5
  78. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  79. package/dist/core/agents/session-name-display.js +67 -7
  80. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  81. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  82. package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
  83. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  84. package/dist/core/agents/state/heartbeat-writer.js +17 -0
  85. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  86. package/dist/core/agents/state/live-coordination-view.js +1 -0
  87. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  88. package/dist/core/artifacts/constants.d.ts +1 -1
  89. package/dist/core/artifacts/constants.js +1 -1
  90. package/dist/core/artifacts/index.d.ts +82 -6
  91. package/dist/core/artifacts/index.d.ts.map +1 -1
  92. package/dist/core/artifacts/index.js +410 -15
  93. package/dist/core/config.d.ts +20 -0
  94. package/dist/core/config.d.ts.map +1 -1
  95. package/dist/core/config.js +59 -0
  96. package/dist/core/diagnostics/advice.d.ts +9 -0
  97. package/dist/core/diagnostics/advice.d.ts.map +1 -0
  98. package/dist/core/diagnostics/advice.js +112 -0
  99. package/dist/core/diagnostics/bundle.d.ts +51 -0
  100. package/dist/core/diagnostics/bundle.d.ts.map +1 -0
  101. package/dist/core/diagnostics/bundle.js +968 -0
  102. package/dist/core/diagnostics/comparison.d.ts +5 -0
  103. package/dist/core/diagnostics/comparison.d.ts.map +1 -0
  104. package/dist/core/diagnostics/comparison.js +444 -0
  105. package/dist/core/diagnostics/contract.d.ts +245 -0
  106. package/dist/core/diagnostics/contract.d.ts.map +1 -0
  107. package/dist/core/diagnostics/contract.js +18 -0
  108. package/dist/core/diagnostics/identity.d.ts +3 -0
  109. package/dist/core/diagnostics/identity.d.ts.map +1 -0
  110. package/dist/core/diagnostics/identity.js +10 -0
  111. package/dist/core/diagnostics/index.d.ts +8 -0
  112. package/dist/core/diagnostics/index.d.ts.map +1 -0
  113. package/dist/core/diagnostics/index.js +7 -0
  114. package/dist/core/diagnostics/replay.d.ts +8 -0
  115. package/dist/core/diagnostics/replay.d.ts.map +1 -0
  116. package/dist/core/diagnostics/replay.js +213 -0
  117. package/dist/core/diagnostics/sanitize.d.ts +9 -0
  118. package/dist/core/diagnostics/sanitize.d.ts.map +1 -0
  119. package/dist/core/diagnostics/sanitize.js +84 -0
  120. package/dist/core/events/legacy-storage/compression.d.ts +13 -0
  121. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -0
  122. package/dist/core/events/legacy-storage/compression.js +107 -0
  123. package/dist/core/events/legacy-storage/index.d.ts +1 -0
  124. package/dist/core/events/legacy-storage/index.d.ts.map +1 -1
  125. package/dist/core/events/legacy-storage/index.js +1 -0
  126. package/dist/core/events/v3/archive-retention.d.ts +33 -0
  127. package/dist/core/events/v3/archive-retention.d.ts.map +1 -0
  128. package/dist/core/events/v3/archive-retention.js +195 -0
  129. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  130. package/dist/core/events/v3/bootstrap.js +10 -0
  131. package/dist/core/events/v3/capabilities.d.ts +1 -1
  132. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  133. package/dist/core/events/v3/capabilities.js +1 -0
  134. package/dist/core/events/v3/coordination-view.d.ts +3 -0
  135. package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
  136. package/dist/core/events/v3/coordination-view.js +69 -8
  137. package/dist/core/events/v3/index.d.ts +1 -0
  138. package/dist/core/events/v3/index.d.ts.map +1 -1
  139. package/dist/core/events/v3/index.js +1 -0
  140. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  141. package/dist/core/events/v3/live-routing.js +1 -0
  142. package/dist/core/events/v3/producers/hook-base.d.ts +1 -1
  143. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  144. package/dist/core/events/v3/producers/hook-base.js +13 -2
  145. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  146. package/dist/core/events/v3/producers/intake.js +10 -2
  147. package/dist/core/events/v3/producers/recorder.d.ts +39 -0
  148. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  149. package/dist/core/events/v3/producers/recorder.js +350 -17
  150. package/dist/core/hooks/adapter/output.d.ts +8 -0
  151. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  152. package/dist/core/hooks/adapter/output.js +11 -1
  153. package/dist/core/hooks/cli.js +303 -219
  154. package/dist/core/hooks/health.d.ts +59 -0
  155. package/dist/core/hooks/health.d.ts.map +1 -0
  156. package/dist/core/hooks/health.js +84 -0
  157. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  158. package/dist/core/hooks/resolve/transcript.js +10 -3
  159. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  160. package/dist/core/hooks/session-name-presence.js +4 -1
  161. package/dist/core/qa-artifacts.d.ts +20 -0
  162. package/dist/core/qa-artifacts.d.ts.map +1 -0
  163. package/dist/core/qa-artifacts.js +110 -0
  164. package/dist/core/resources/contract.d.ts +96 -0
  165. package/dist/core/resources/contract.d.ts.map +1 -0
  166. package/dist/core/resources/contract.js +2 -0
  167. package/dist/core/resources/index.d.ts +6 -0
  168. package/dist/core/resources/index.d.ts.map +1 -0
  169. package/dist/core/resources/index.js +5 -0
  170. package/dist/core/resources/sampler.d.ts +34 -0
  171. package/dist/core/resources/sampler.d.ts.map +1 -0
  172. package/dist/core/resources/sampler.js +458 -0
  173. package/dist/core/resources/service-status.d.ts +3 -0
  174. package/dist/core/resources/service-status.d.ts.map +1 -0
  175. package/dist/core/resources/service-status.js +48 -0
  176. package/dist/core/resources/service.d.ts +26 -0
  177. package/dist/core/resources/service.d.ts.map +1 -0
  178. package/dist/core/resources/service.js +292 -0
  179. package/dist/core/resources/storage.d.ts +13 -0
  180. package/dist/core/resources/storage.d.ts.map +1 -0
  181. package/dist/core/resources/storage.js +30 -0
  182. package/dist/core/storage/atomic-json.d.ts +3 -0
  183. package/dist/core/storage/atomic-json.d.ts.map +1 -0
  184. package/dist/core/storage/atomic-json.js +15 -0
  185. package/dist/core/storage/builtins.d.ts.map +1 -1
  186. package/dist/core/storage/builtins.js +45 -6
  187. package/dist/core/storage/logger.d.ts +2 -0
  188. package/dist/core/storage/logger.d.ts.map +1 -1
  189. package/dist/core/storage/logger.js +2 -0
  190. package/dist/core/storage/query.d.ts +14 -0
  191. package/dist/core/storage/query.d.ts.map +1 -1
  192. package/dist/core/storage/query.js +51 -0
  193. package/dist/core/supervisor/activity.d.ts +4 -0
  194. package/dist/core/supervisor/activity.d.ts.map +1 -0
  195. package/dist/core/supervisor/activity.js +141 -0
  196. package/dist/core/supervisor/contract.d.ts +268 -0
  197. package/dist/core/supervisor/contract.d.ts.map +1 -0
  198. package/dist/core/supervisor/contract.js +35 -0
  199. package/dist/core/supervisor/explanations.d.ts +4 -0
  200. package/dist/core/supervisor/explanations.d.ts.map +1 -0
  201. package/dist/core/supervisor/explanations.js +71 -0
  202. package/dist/core/supervisor/findings.d.ts +20 -0
  203. package/dist/core/supervisor/findings.d.ts.map +1 -0
  204. package/dist/core/supervisor/findings.js +399 -0
  205. package/dist/core/supervisor/history.d.ts +9 -0
  206. package/dist/core/supervisor/history.d.ts.map +1 -0
  207. package/dist/core/supervisor/history.js +46 -0
  208. package/dist/core/supervisor/hook-health-alerts.d.ts +14 -0
  209. package/dist/core/supervisor/hook-health-alerts.d.ts.map +1 -0
  210. package/dist/core/supervisor/hook-health-alerts.js +70 -0
  211. package/dist/core/supervisor/hook-health-storage.d.ts +5 -0
  212. package/dist/core/supervisor/hook-health-storage.d.ts.map +1 -0
  213. package/dist/core/supervisor/hook-health-storage.js +24 -0
  214. package/dist/core/supervisor/hook-health.d.ts +72 -0
  215. package/dist/core/supervisor/hook-health.d.ts.map +1 -0
  216. package/dist/core/supervisor/hook-health.js +203 -0
  217. package/dist/core/supervisor/hooks.d.ts +10 -0
  218. package/dist/core/supervisor/hooks.d.ts.map +1 -0
  219. package/dist/core/supervisor/hooks.js +28 -0
  220. package/dist/core/supervisor/index.d.ts +15 -0
  221. package/dist/core/supervisor/index.d.ts.map +1 -0
  222. package/dist/core/supervisor/index.js +14 -0
  223. package/dist/core/supervisor/log-feed.d.ts +9 -0
  224. package/dist/core/supervisor/log-feed.d.ts.map +1 -0
  225. package/dist/core/supervisor/log-feed.js +103 -0
  226. package/dist/core/supervisor/service.d.ts +29 -0
  227. package/dist/core/supervisor/service.d.ts.map +1 -0
  228. package/dist/core/supervisor/service.js +450 -0
  229. package/dist/core/supervisor/services.d.ts +11 -0
  230. package/dist/core/supervisor/services.d.ts.map +1 -0
  231. package/dist/core/supervisor/services.js +155 -0
  232. package/dist/core/supervisor/status.d.ts +3 -0
  233. package/dist/core/supervisor/status.d.ts.map +1 -0
  234. package/dist/core/supervisor/status.js +48 -0
  235. package/dist/core/supervisor/storage.d.ts +30 -0
  236. package/dist/core/supervisor/storage.d.ts.map +1 -0
  237. package/dist/core/supervisor/storage.js +73 -0
  238. package/dist/core/supervisor/timeline.d.ts +3 -0
  239. package/dist/core/supervisor/timeline.d.ts.map +1 -0
  240. package/dist/core/supervisor/timeline.js +87 -0
  241. package/dist/core/workflow/admission.d.ts +23 -0
  242. package/dist/core/workflow/admission.d.ts.map +1 -0
  243. package/dist/core/workflow/admission.js +136 -0
  244. package/dist/core/workflow/engine.d.ts.map +1 -1
  245. package/dist/core/workflow/engine.js +77 -2
  246. package/dist/core/workflow/index.d.ts +3 -2
  247. package/dist/core/workflow/index.d.ts.map +1 -1
  248. package/dist/core/workflow/index.js +2 -1
  249. package/dist/core/workflow/proof.d.ts +2 -1
  250. package/dist/core/workflow/proof.d.ts.map +1 -1
  251. package/dist/core/workflow/proof.js +39 -1
  252. package/dist/core/workflow/run-state.d.ts +3 -2
  253. package/dist/core/workflow/run-state.d.ts.map +1 -1
  254. package/dist/core/workflow/run-state.js +8 -1
  255. package/dist/core/workflow/types.d.ts +33 -1
  256. package/dist/core/workflow/types.d.ts.map +1 -1
  257. package/dist/core/workflow/types.js +2 -1
  258. package/dist/lib/admission.d.ts +71 -0
  259. package/dist/lib/admission.d.ts.map +1 -0
  260. package/dist/lib/admission.js +264 -0
  261. package/dist/lib/agent-browser/client.d.ts +1 -1
  262. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  263. package/dist/lib/agent-browser/client.js +1 -5
  264. package/dist/lib/browser/capture-fidelity.d.ts +39 -0
  265. package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
  266. package/dist/lib/browser/capture-fidelity.js +84 -0
  267. package/dist/lib/browser/client.d.ts +46 -1
  268. package/dist/lib/browser/client.d.ts.map +1 -1
  269. package/dist/lib/browser/client.js +168 -9
  270. package/dist/lib/browser/critique.d.ts +38 -1
  271. package/dist/lib/browser/critique.d.ts.map +1 -1
  272. package/dist/lib/browser/critique.js +34 -6
  273. package/dist/lib/browser/index.d.ts +5 -1
  274. package/dist/lib/browser/index.d.ts.map +1 -1
  275. package/dist/lib/browser/index.js +4 -0
  276. package/dist/lib/browser/page-review-judge.d.ts +64 -0
  277. package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
  278. package/dist/lib/browser/page-review-judge.js +270 -0
  279. package/dist/lib/browser/page-review-pack.d.ts +613 -0
  280. package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
  281. package/dist/lib/browser/page-review-pack.js +1751 -0
  282. package/dist/lib/browser/qa-run-contracts.d.ts +377 -0
  283. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -0
  284. package/dist/lib/browser/qa-run-contracts.js +361 -0
  285. package/dist/lib/browser/qa-run.d.ts +141 -0
  286. package/dist/lib/browser/qa-run.d.ts.map +1 -0
  287. package/dist/lib/browser/qa-run.js +1283 -0
  288. package/dist/lib/browser/request-diagnostics.d.ts +13 -0
  289. package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
  290. package/dist/lib/browser/request-diagnostics.js +18 -0
  291. package/dist/lib/browser/tiling.d.ts +19 -0
  292. package/dist/lib/browser/tiling.d.ts.map +1 -1
  293. package/dist/lib/browser/tiling.js +28 -0
  294. package/dist/lib/cookies/client.d.ts +9 -0
  295. package/dist/lib/cookies/client.d.ts.map +1 -1
  296. package/dist/lib/cookies/client.js +197 -44
  297. package/dist/lib/cookies/extra.d.ts +18 -0
  298. package/dist/lib/cookies/extra.d.ts.map +1 -0
  299. package/dist/lib/cookies/extra.js +14 -0
  300. package/dist/lib/cookies/index.d.ts +2 -1
  301. package/dist/lib/cookies/index.d.ts.map +1 -1
  302. package/dist/lib/cookies/index.js +2 -1
  303. package/dist/lib/coord-root-id.d.ts +9 -0
  304. package/dist/lib/coord-root-id.d.ts.map +1 -0
  305. package/dist/lib/coord-root-id.js +17 -0
  306. package/dist/lib/durable-job.d.ts +124 -0
  307. package/dist/lib/durable-job.d.ts.map +1 -0
  308. package/dist/lib/durable-job.js +296 -0
  309. package/dist/lib/http/client.d.ts +7 -1
  310. package/dist/lib/http/client.d.ts.map +1 -1
  311. package/dist/lib/http/client.js +2 -0
  312. package/dist/lib/instructions/apply.d.ts +3 -1
  313. package/dist/lib/instructions/apply.d.ts.map +1 -1
  314. package/dist/lib/instructions/apply.js +74 -3
  315. package/dist/lib/instructions/templates.d.ts.map +1 -1
  316. package/dist/lib/instructions/templates.js +11 -2
  317. package/package.json +19 -2
  318. package/src/commander.ts +75 -1
  319. package/src/commands/admission.ts +699 -0
  320. package/src/commands/agents.ts +189 -50
  321. package/src/commands/artifacts.ts +132 -18
  322. package/src/commands/browse-ai.ts +10 -5
  323. package/src/commands/browse.ts +496 -20
  324. package/src/commands/diagnostics.ts +271 -0
  325. package/src/commands/fetch.ts +1 -0
  326. package/src/commands/files.ts +102 -2
  327. package/src/commands/governor.ts +8 -0
  328. package/src/commands/ledger-v3.ts +82 -0
  329. package/src/commands/qa-record.ts +682 -0
  330. package/src/commands/qa-run.ts +503 -0
  331. package/src/commands/qa-status.ts +608 -0
  332. package/src/commands/qa-verify.ts +238 -0
  333. package/src/commands/resources.ts +38 -0
  334. package/src/commands/review-pack.ts +1281 -0
  335. package/src/commands/supervisor.ts +141 -0
  336. package/src/commands/web.ts +19 -0
  337. package/src/commands/workflow.ts +8 -0
  338. package/src/core/agents/cli.ts +7 -3
  339. package/src/core/agents/health.ts +142 -0
  340. package/src/core/agents/mailbox.ts +373 -0
  341. package/src/core/agents/qa-signal.ts +261 -0
  342. package/src/core/agents/reconcile-coordination-v3.ts +77 -0
  343. package/src/core/agents/render/prompt-context.ts +42 -12
  344. package/src/core/agents/render/session-context.ts +37 -3
  345. package/src/core/agents/rules/commit-conflict.ts +58 -1
  346. package/src/core/agents/rules/stop-hook.ts +6 -12
  347. package/src/core/agents/session-finalizer-v3.ts +4 -1
  348. package/src/core/agents/session-name-display.ts +78 -7
  349. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  350. package/src/core/agents/state/heartbeat-writer.ts +23 -0
  351. package/src/core/agents/state/live-coordination-view.ts +1 -0
  352. package/src/core/agents/state/live-coordination-writer.ts +5 -0
  353. package/src/core/artifacts/constants.ts +1 -1
  354. package/src/core/artifacts/index.ts +565 -30
  355. package/src/core/config.ts +134 -2
  356. package/src/core/diagnostics/advice.ts +147 -0
  357. package/src/core/diagnostics/bundle.ts +1250 -0
  358. package/src/core/diagnostics/comparison.ts +565 -0
  359. package/src/core/diagnostics/contract.ts +311 -0
  360. package/src/core/diagnostics/identity.ts +11 -0
  361. package/src/core/diagnostics/index.ts +7 -0
  362. package/src/core/diagnostics/replay.ts +314 -0
  363. package/src/core/diagnostics/sanitize.ts +97 -0
  364. package/src/core/events/legacy-storage/compression.ts +130 -0
  365. package/src/core/events/legacy-storage/index.ts +1 -0
  366. package/src/core/events/v3/archive-retention.ts +251 -0
  367. package/src/core/events/v3/bootstrap.ts +10 -0
  368. package/src/core/events/v3/capabilities.ts +2 -0
  369. package/src/core/events/v3/coordination-view.ts +100 -11
  370. package/src/core/events/v3/index.ts +9 -0
  371. package/src/core/events/v3/live-routing.ts +1 -0
  372. package/src/core/events/v3/producers/hook-base.ts +14 -2
  373. package/src/core/events/v3/producers/intake.ts +10 -2
  374. package/src/core/events/v3/producers/recorder.ts +420 -17
  375. package/src/core/hooks/adapter/output.ts +12 -1
  376. package/src/core/hooks/cli.ts +332 -247
  377. package/src/core/hooks/health.ts +146 -0
  378. package/src/core/hooks/resolve/transcript.ts +10 -3
  379. package/src/core/hooks/session-name-presence.ts +6 -1
  380. package/src/core/qa-artifacts.ts +126 -0
  381. package/src/core/resources/contract.ts +105 -0
  382. package/src/core/resources/index.ts +5 -0
  383. package/src/core/resources/sampler.ts +590 -0
  384. package/src/core/resources/service-status.ts +62 -0
  385. package/src/core/resources/service.ts +337 -0
  386. package/src/core/resources/storage.ts +44 -0
  387. package/src/core/storage/atomic-json.ts +15 -0
  388. package/src/core/storage/builtins.ts +62 -6
  389. package/src/core/storage/logger.ts +2 -0
  390. package/src/core/storage/query.ts +58 -0
  391. package/src/core/supervisor/activity.ts +193 -0
  392. package/src/core/supervisor/contract.ts +312 -0
  393. package/src/core/supervisor/explanations.ts +104 -0
  394. package/src/core/supervisor/findings.ts +788 -0
  395. package/src/core/supervisor/history.ts +61 -0
  396. package/src/core/supervisor/hook-health-alerts.ts +108 -0
  397. package/src/core/supervisor/hook-health-storage.ts +25 -0
  398. package/src/core/supervisor/hook-health.ts +301 -0
  399. package/src/core/supervisor/hooks.ts +35 -0
  400. package/src/core/supervisor/index.ts +14 -0
  401. package/src/core/supervisor/log-feed.ts +131 -0
  402. package/src/core/supervisor/service.ts +511 -0
  403. package/src/core/supervisor/services.ts +203 -0
  404. package/src/core/supervisor/status.ts +59 -0
  405. package/src/core/supervisor/storage.ts +126 -0
  406. package/src/core/supervisor/timeline.ts +133 -0
  407. package/src/core/workflow/admission.ts +215 -0
  408. package/src/core/workflow/engine.ts +93 -1
  409. package/src/core/workflow/index.ts +10 -0
  410. package/src/core/workflow/proof.ts +54 -1
  411. package/src/core/workflow/run-state.ts +13 -1
  412. package/src/core/workflow/types.ts +39 -1
  413. package/src/lib/admission.ts +347 -0
  414. package/src/lib/agent-browser/client.ts +2 -10
  415. package/src/lib/browser/capture-fidelity.ts +98 -0
  416. package/src/lib/browser/client.ts +212 -10
  417. package/src/lib/browser/critique.ts +62 -7
  418. package/src/lib/browser/index.ts +65 -0
  419. package/src/lib/browser/page-review-judge.ts +360 -0
  420. package/src/lib/browser/page-review-pack.ts +2384 -0
  421. package/src/lib/browser/qa-run-contracts.ts +743 -0
  422. package/src/lib/browser/qa-run.ts +1462 -0
  423. package/src/lib/browser/request-diagnostics.ts +27 -0
  424. package/src/lib/browser/tiling.ts +32 -0
  425. package/src/lib/cookies/client.ts +228 -42
  426. package/src/lib/cookies/extra.ts +28 -0
  427. package/src/lib/cookies/index.ts +2 -0
  428. package/src/lib/coord-root-id.ts +23 -0
  429. package/src/lib/durable-job.ts +407 -0
  430. package/src/lib/http/client.ts +13 -1
  431. package/src/lib/instructions/apply.ts +81 -2
  432. package/src/lib/instructions/templates.ts +11 -2
@@ -1,14 +1,17 @@
1
1
  import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, isAbsolute, resolve } from "node:path";
4
+ import { PNG } from "pngjs";
4
5
  import { resolveBinName } from "../core/config.js";
6
+ import { CAPTURE_FIDELITY_MISMATCH_THRESHOLD, compareBand, decideFidelity, pngDimensions, } from "../lib/browser/capture-fidelity.js";
5
7
  import { Browser, browserProxyFromEnv, browserProxyGateFromEnv, captureDevOverlay, DEFAULT_CRITIQUE_RUBRIC, extractObservedIp, parseAssertSpec, runCritique, tilesFromFullPage, WEBRTC_PROXY_ONLY_ARG, writeNetscapeCookieFile, wslHeadedLaunchArgs, } from "../lib/browser/index.js";
8
+ import { findPackTile, readPackContext, tileId, writePackContext, writePackExpandedTile, } from "../lib/browser/page-review-pack.js";
6
9
  import { buildQaManifest, classifySignatures, } from "../lib/browser/qa-plan.js";
7
10
  import { DEFAULT_REUSE_MISMATCH_RATIO, planCritiqueReuse, QA_CRITIQUE_CONTRACT_VERSION, rubricDigest, } from "../lib/browser/qa-reuse.js";
8
11
  import { loadQaSnapshot, resolveQaBaseline, saveQaSnapshot } from "../lib/browser/qa-snapshot.js";
9
12
  import { startBrowserSessionServer, } from "../lib/browser/session-control.js";
10
13
  import { diffAgainstBaseline, saveBaseline, } from "../lib/browser/visual-diff.js";
11
- import { CookieJar } from "../lib/cookies/index.js";
14
+ import { applyExtraCookies, CookieJar } from "../lib/cookies/index.js";
12
15
  /**
13
16
  * `harn browse <url>`: Playwright-backed page navigation with shared
14
17
  * cookie jar + persistent profile + diagnostics capture for LLM
@@ -64,6 +67,9 @@ export function registerBrowseCommand(program, emitParam, context, binName = res
64
67
  'Example: `--batch "click button; wait 1500; reload; wait 3000"`. `reload` preserves sessionStorage + cookies, which is how to repro sessionStorage-restored UI state.')
65
68
  .option("--network-har <path>", "Record network traffic to a HAR file (finalized on close)")
66
69
  .option("--viewport <preset|WxH>", "Viewport: mobile (390x844), tablet (820x1180), desktop (1280x800), hd (1920x1080), or explicit '1920x1080'", "desktop")
70
+ .option("--color-scheme <scheme>", "Emulate prefers-color-scheme for the whole session: light | dark. " +
71
+ "Renders theme-aware pages in that scheme without page-specific toggles. " +
72
+ "Absent = browser default (unchanged behavior).")
67
73
  .option("--login", "Headed mode for one-time auth flow (cookies persist in profile)")
68
74
  .option("--login-close-file <path>", "With --login, wait for this file instead of terminal Enter, then remove it and close cleanly")
69
75
  .option("--control-file <path>", "With --login, publish an owner-only descriptor for repeated browse-session control")
@@ -234,6 +240,23 @@ export function registerBrowseCommand(program, emitParam, context, binName = res
234
240
  "full fresh review, never an error. Stats land under `qaReuse` in the JSON envelope.")
235
241
  .option("--qa-reuse-threshold <ratio>", "Band-diff mismatch ratio at or below which a clean tile region is reused " +
236
242
  `(default ${DEFAULT_REUSE_MISMATCH_RATIO}; lower it toward 0 for stricter reuse).`)
243
+ .option("--review-pack <dir>", "Capture this render into a page review pack at <dir> (contexts/<id>/: full-page " +
244
+ "screenshot, critique tiles as PNG files, serialized DOM, QA signature) and make no " +
245
+ "vision call. A judge stage (`review-pack judge`, or qa-run) reviews the tiles from disk " +
246
+ "after every browser has closed. Tiling knobs are the --check-critique-* flags.")
247
+ .option("--review-pack-context <id>", "Context id inside the pack (default <viewport>-<theme>-<state> from --viewport, " +
248
+ "--qa-theme, --qa-state).")
249
+ .option("--review-pack-scope <selector>", "Also tile one screenshot per element matching <selector> into the pack (repeatable).", (value, previous = []) => [...previous, value], [])
250
+ .option("--no-review-pack-bands", "Skip full-page bands in the pack and keep only --review-pack-scope tiles (a scoped " +
251
+ "visual pass).")
252
+ .option("--review-pack-expand <tile-id>", "Instead of capturing a context, re-render ONE existing tile of --review-pack-context " +
253
+ "(e.g. T012) at --device-scale-factor and write it beside the original as " +
254
+ "tiles/<tile>@<dpr>x.png. Existing tiles are untouched.")
255
+ .option("--review-pack-hit-rect <x,y,w,h>", "Document-space rectangle (integer px) a deterministic gate already flagged. Every " +
256
+ "full-page band that contains it is captured even past --check-critique-max-tiles, " +
257
+ `labelled \`hit band N\` (repeatable, at most ${REVIEW_PACK_HIT_RECT_MAX}).`, (value, previous = []) => [...previous, value], [])
258
+ .option("--device-scale-factor <n>", "Device scale factor for the browser context (default 1). 2 renders every screenshot " +
259
+ "at twice the pixels; pair with --review-pack-expand for a sharper look at one tile.")
237
260
  .option("--no-dev-overlay", "Skip auto-capture of Next.js dev-overlay issues. Default: capture every queued error (kind/code/message/stack) when a <nextjs-portal> shadow root is present. Necessary because Next.js 16 + React 19 route hydration errors + most React warnings through onCaughtError → next-devtools' errorQueue, NOT through console.error, so Playwright's standard listener doesn't see them. Surfaces them in the JSON envelope under `devOverlay`.")
238
261
  .action(async (url, opts) => {
239
262
  try {
@@ -263,8 +286,25 @@ async function runBrowse(url, opts, context) {
263
286
  const jar = opts.cookies === false
264
287
  ? null
265
288
  : new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse" });
289
+ if (jar)
290
+ applyExtraCookies(url, jar, context?.extraCookies);
266
291
  const headed = opts.login || opts.headed;
267
292
  const viewport = parseViewport(opts.viewport ?? "desktop");
293
+ const deviceScaleFactor = parseDeviceScaleFactor(opts.deviceScaleFactor);
294
+ if (opts.reviewPackExpand !== undefined) {
295
+ if (opts.reviewPack === undefined || opts.reviewPackContext === undefined) {
296
+ throw new Error("--review-pack-expand requires --review-pack <dir> and --review-pack-context <id>.");
297
+ }
298
+ }
299
+ // Malformed hit rects fail here, before a browser launches.
300
+ parseReviewPackHitRects(opts.reviewPackHitRect);
301
+ let colorScheme;
302
+ if (opts.colorScheme !== undefined) {
303
+ if (opts.colorScheme !== "light" && opts.colorScheme !== "dark") {
304
+ throw new Error(`--color-scheme must be light or dark (got: ${opts.colorScheme}).`);
305
+ }
306
+ colorScheme = opts.colorScheme;
307
+ }
268
308
  const proxy = opts.proxyFromEnv ? browserProxyFromEnv() : undefined;
269
309
  const proxyGate = opts.proxyFromEnv ? browserProxyGateFromEnv() : null;
270
310
  if (opts.loginCloseFile && !opts.login) {
@@ -282,6 +322,8 @@ async function runBrowse(url, opts, context) {
282
322
  headed,
283
323
  jar,
284
324
  viewport,
325
+ ...(deviceScaleFactor !== undefined ? { deviceScaleFactor } : {}),
326
+ ...(colorScheme ? { colorScheme } : {}),
285
327
  navigationTimeout: Number.parseInt(opts.timeout, 10),
286
328
  waitUntil: opts.waitUntil,
287
329
  recordHarPath: opts.networkHar ? resolve(opts.networkHar) : undefined,
@@ -482,12 +524,26 @@ async function runBrowse(url, opts, context) {
482
524
  emit.log(`qa-reuse: ${qaReuse.tiles_reused}/${qaReuse.tiles_total} tiles reused (band-diff), ` +
483
525
  `${qaReuse.tiles_reviewed} reviewed fresh${qaReuse.invalidation ? ` — ${qaReuse.invalidation}` : ""}`, "info");
484
526
  }
485
- critique = await runCritique({
486
- url: navResult.url,
487
- rubric,
488
- tiles: tilesToReview,
489
- provider: critiqueProvider,
490
- });
527
+ critique = {
528
+ ...(await runCritique({
529
+ url: navResult.url,
530
+ rubric,
531
+ tiles: tilesToReview,
532
+ provider: critiqueProvider,
533
+ })),
534
+ // Coverage rides on every envelope, skipped or not, so a reader can
535
+ // tell a pass over the whole page from a pass over its first N bands.
536
+ coverage: captured.coverage,
537
+ };
538
+ }
539
+ // Page review pack capture: tiles, DOM, and signature to disk, no vision
540
+ // call. Also before annotations, for the same reason as the critique.
541
+ let reviewPack;
542
+ if (opts.reviewPack !== undefined && opts.reviewPackExpand !== undefined) {
543
+ reviewPack = await expandReviewPackTile(browser, opts, deviceScaleFactor ?? 1);
544
+ }
545
+ else if (opts.reviewPack !== undefined) {
546
+ reviewPack = await captureReviewPackContext(browser, url, navResult, opts, qaCapture);
491
547
  }
492
548
  // Persist the QA baseline AFTER critique so a passing run's verdicts ride
493
549
  // along with the snapshot (still before annotations mutate the page).
@@ -499,6 +555,7 @@ async function runBrowse(url, opts, context) {
499
555
  const fullCoverage = critique?.provider &&
500
556
  critique.outcome !== "skipped" &&
501
557
  critiqueTiles !== undefined &&
558
+ !critique.coverage?.capped &&
502
559
  (!qaReuse || qaReuse.tiles_reused === 0);
503
560
  const persistedCritique = fullCoverage && critique && critiqueTiles
504
561
  ? {
@@ -620,10 +677,10 @@ async function runBrowse(url, opts, context) {
620
677
  if (opts.baseline || opts.diff) {
621
678
  throw new Error("--baseline / --diff require trio mode (screenshot file). Remove --snapshot/--html/--json or capture the screenshot first.");
622
679
  }
623
- await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse ? summarizeReuse(qaReuse) : undefined);
680
+ await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse ? summarizeReuse(qaReuse) : undefined, reviewPack);
624
681
  }
625
682
  else {
626
- await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse ? summarizeReuse(qaReuse) : undefined);
683
+ await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse ? summarizeReuse(qaReuse) : undefined, reviewPack);
627
684
  }
628
685
  if (visibility && opts.checkVisibleAnnotate !== false) {
629
686
  await browser.clearVisibilityAnnotations();
@@ -821,7 +878,7 @@ function splitBatchSteps(input) {
821
878
  // ---------------------------------------------------------------------------
822
879
  // Print mode (--snapshot / --html / --json): stdout, no file writes
823
880
  // ---------------------------------------------------------------------------
824
- async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse) {
881
+ async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse, reviewPack) {
825
882
  let body = null;
826
883
  if (opts.html) {
827
884
  body = await browser.htmlContent(opts.selector);
@@ -871,6 +928,8 @@ async function runPrintMode(browser, navResult, opts, evalResult, visibility, wi
871
928
  result.qaPlan = qaPlan;
872
929
  if (qaReuse)
873
930
  result.qaReuse = qaReuse;
931
+ if (reviewPack)
932
+ result.reviewPack = reviewPack;
874
933
  if (batchResult && batchResult.clipboardReads.length > 0) {
875
934
  result.batchClipboardReads = batchResult.clipboardReads;
876
935
  }
@@ -892,7 +951,7 @@ async function runPrintMode(browser, navResult, opts, evalResult, visibility, wi
892
951
  // ---------------------------------------------------------------------------
893
952
  // Trio mode (default): write <prefix>.{png,html,json}
894
953
  // ---------------------------------------------------------------------------
895
- async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse) {
954
+ async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, runts, layoutLint, hit, content, critique, asserts, devOverlay, batchResult, qaPlan, qaReuse, reviewPack) {
896
955
  const prefix = resolveOutPrefix(opts.out);
897
956
  mkdirSync(dirname(prefix), { recursive: true });
898
957
  const written = [];
@@ -1002,6 +1061,8 @@ async function runTrioMode(browser, navResult, opts, evalResult, visibility, wid
1002
1061
  envelope.qaPlan = qaPlan;
1003
1062
  if (qaReuse)
1004
1063
  envelope.qaReuse = qaReuse;
1064
+ if (reviewPack)
1065
+ envelope.reviewPack = reviewPack;
1005
1066
  if (batchResult && batchResult.clipboardReads.length > 0) {
1006
1067
  envelope.batchClipboardReads = batchResult.clipboardReads;
1007
1068
  }
@@ -1377,7 +1438,9 @@ function summarizeReuse(plan) {
1377
1438
  * supplies the live-page inputs (signature, page height, explicit scope match
1378
1439
  * counts). Classification and manifest math live in lib/browser/qa-plan.ts.
1379
1440
  */
1380
- async function runQaPlanning(browser, targetArg, renderedUrl, opts) {
1441
+ /** Capture the live page's QA signature and serialized DOM for the store key
1442
+ * `--qa-target` (default: the url argument) and the labelled context. */
1443
+ async function captureQaState(browser, targetArg, renderedUrl, opts) {
1381
1444
  const context = {
1382
1445
  viewport: opts.viewport ?? "desktop",
1383
1446
  theme: opts.qaTheme === "dark" ? "dark" : "light",
@@ -1392,11 +1455,16 @@ async function runQaPlanning(browser, targetArg, renderedUrl, opts) {
1392
1455
  stylesheets: captured.stylesheets,
1393
1456
  ...(captured.truncated ? { truncated: true } : {}),
1394
1457
  };
1458
+ return { target, context, signature, domHtml: captured.domHtml };
1459
+ }
1460
+ async function runQaPlanning(browser, targetArg, renderedUrl, opts) {
1461
+ const capture = await captureQaState(browser, targetArg, renderedUrl, opts);
1462
+ const { target, context, signature } = capture;
1395
1463
  const report = {
1396
1464
  signature: {
1397
- nodes: captured.nodes.length,
1398
- stylesheets: captured.stylesheets.length,
1399
- truncated: captured.truncated,
1465
+ nodes: signature.nodes.length,
1466
+ stylesheets: signature.stylesheets.length,
1467
+ truncated: Boolean(signature.truncated),
1400
1468
  },
1401
1469
  };
1402
1470
  if (opts.qaPlan) {
@@ -1424,15 +1492,256 @@ async function runQaPlanning(browser, targetArg, renderedUrl, opts) {
1424
1492
  estimatedFullPageTiles,
1425
1493
  });
1426
1494
  }
1427
- return { report, capture: { target, context, signature, domHtml: captured.domHtml } };
1495
+ return { report, capture };
1428
1496
  }
1429
- async function captureCritiqueTiles(browser, opts, tileBudgetPx) {
1497
+ /** Tiling knobs shared by the critique and the review-pack capture. The
1498
+ * routed provider's vision long-edge budget caps band height so tiles are
1499
+ * never silently downscaled by the model. */
1500
+ function critiqueTilingKnobs(opts, tileBudgetPx) {
1430
1501
  const maxTiles = Math.max(1, Number.parseInt(opts.checkCritiqueMaxTiles ?? "24", 10));
1431
- // The routed provider's vision long-edge budget caps band height so tiles
1432
- // are never silently downscaled by the model.
1433
1502
  const bandFlag = Math.max(200, Number.parseInt(opts.checkCritiqueBand ?? "1400", 10));
1434
1503
  const band = tileBudgetPx ? Math.max(200, Math.min(bandFlag, tileBudgetPx)) : bandFlag;
1435
1504
  const overlap = Math.max(0, Number.parseInt(opts.checkCritiqueOverlap ?? "120", 10));
1505
+ return { maxTiles, band, overlap };
1506
+ }
1507
+ /**
1508
+ * `--review-pack-expand`: re-render one existing tile of a pack context at
1509
+ * the context's device scale factor. The tile's CSS-pixel rect is captured by
1510
+ * scrolling the viewport to it and clipping (`captureRegionByScroll`), which
1511
+ * renders the region the way a reader sees it and yields tile size × DPR
1512
+ * pixels. A second full-page screenshot is not taken: it would reproduce the
1513
+ * very raster artifact the expand is meant to check. No other pack file
1514
+ * changes.
1515
+ */
1516
+ async function expandReviewPackTile(browser, opts, dpr) {
1517
+ const packDir = opts.reviewPack;
1518
+ const contextId = opts.reviewPackContext;
1519
+ const tileId = opts.reviewPackExpand;
1520
+ const { tile } = findPackTile([readPackContext(packDir, contextId)], tileId, contextId);
1521
+ const region = await browser.captureRegionByScroll({
1522
+ x: tile.x,
1523
+ y: tile.scrollY,
1524
+ width: tile.width,
1525
+ height: tile.height,
1526
+ });
1527
+ const { record, expanded } = writePackExpandedTile(packDir, contextId, {
1528
+ tileId,
1529
+ dpr,
1530
+ region,
1531
+ });
1532
+ emit.log(`review-pack: ${record.id}/${tileId} expanded at ${dpr}× → ${expanded.width}×${expanded.height} px, ${expanded.file}`, "info");
1533
+ return {
1534
+ dir: packDir,
1535
+ context_id: record.id,
1536
+ tiles: record.tiles.length,
1537
+ scopes: record.scopes,
1538
+ coverage: record.coverage,
1539
+ files: record.files,
1540
+ expanded,
1541
+ };
1542
+ }
1543
+ /**
1544
+ * Capture-fidelity probe (capture-fidelity.ts). Every band is re-shot by
1545
+ * scrolling the viewport (`Browser.captureRegionByScroll`) and pixel compared
1546
+ * with the crop the tiler took from the full-page screenshot. When they all
1547
+ * agree the tiles stand and the record says `full-page`; when any disagrees,
1548
+ * every band is re-cut from its scrolled capture (same rects, same ids, only
1549
+ * the pixels change) and the record says `scrolled-bands`. A probe that
1550
+ * cannot run, or a re-capture that comes back the wrong size (a page wider
1551
+ * than the viewport), keeps the full-page crop for that band and says so in a
1552
+ * warning. Runs while the capture browser is open; the judge never opens one.
1553
+ */
1554
+ async function reconcileCaptureFidelity(browser, tiles) {
1555
+ const threshold = CAPTURE_FIDELITY_MISMATCH_THRESHOLD;
1556
+ const warnings = [];
1557
+ const dpr = await browser.currentPage.evaluate(() => window.devicePixelRatio || 1);
1558
+ // Tile rects live in the full-page PNG's pixel space; the scroll capture
1559
+ // takes CSS px and renders at the page's DPR, so the two line up again.
1560
+ const cssRect = (tile) => ({
1561
+ x: (tile.x ?? 0) / dpr,
1562
+ y: tile.scrollY / dpr,
1563
+ width: tile.width / dpr,
1564
+ height: tile.height / dpr,
1565
+ });
1566
+ const shots = new Map();
1567
+ const shoot = async (position) => {
1568
+ if (shots.has(position))
1569
+ return shots.get(position);
1570
+ const tile = tiles[position];
1571
+ let shot;
1572
+ try {
1573
+ const captured = await browser.captureRegionByScroll(cssRect(tile));
1574
+ const size = pngDimensions(captured);
1575
+ if (Math.abs(size.width - tile.width) > 1 || Math.abs(size.height - tile.height) > 1) {
1576
+ warnings.push(`review-pack: scrolled capture of ${tileId(position)} came back ` +
1577
+ `${size.width}×${size.height} px for a ${tile.width}×${tile.height} px band; ` +
1578
+ "its full-page crop was kept");
1579
+ }
1580
+ else {
1581
+ shot = captured;
1582
+ }
1583
+ }
1584
+ catch (err) {
1585
+ warnings.push(`review-pack: scrolled capture of ${tileId(position)} failed ` +
1586
+ `(${err instanceof Error ? err.message : String(err)}); its full-page crop was kept`);
1587
+ }
1588
+ shots.set(position, shot);
1589
+ return shot;
1590
+ };
1591
+ // Every band is probed. A two-band sample (top and middle) missed a raster
1592
+ // artifact that sat elsewhere on the page, and once every band has been
1593
+ // shot by scrolling, the fallback below costs nothing extra: the same shots
1594
+ // become the tiles.
1595
+ const probes = [];
1596
+ for (let position = 0; position < tiles.length; position++) {
1597
+ const tile = tiles[position];
1598
+ const shot = await shoot(position);
1599
+ if (!shot)
1600
+ continue;
1601
+ const cmp = compareBand(PNG.sync.read(Buffer.from(tile.pngBase64, "base64")), PNG.sync.read(shot), threshold);
1602
+ probes.push({
1603
+ tile_id: tileId(position),
1604
+ scrollY: tile.scrollY,
1605
+ height: tile.height,
1606
+ mismatch_ratio: cmp.mismatch_ratio,
1607
+ });
1608
+ }
1609
+ const fidelity = decideFidelity(probes, threshold);
1610
+ if (fidelity.source === "full-page")
1611
+ return { tiles, fidelity, warnings };
1612
+ const recut = [];
1613
+ for (let position = 0; position < tiles.length; position++) {
1614
+ const tile = tiles[position];
1615
+ const shot = await shoot(position);
1616
+ recut.push(shot ? { ...tile, pngBase64: shot.toString("base64") } : tile);
1617
+ }
1618
+ warnings.unshift(`review-pack: the full-page screenshot disagreed with the viewport render on ` +
1619
+ `${fidelity.mismatched.join(", ")} (mismatch ratio above ${threshold}); every band was ` +
1620
+ "re-captured by scrolling the viewport");
1621
+ return { tiles: recut, fidelity, warnings };
1622
+ }
1623
+ /**
1624
+ * Capture this render into a page review pack: one full-page screenshot,
1625
+ * banded tiles (unless --no-review-pack-bands), one tile set per
1626
+ * --review-pack-scope selector, the serialized DOM, and the QA signature —
1627
+ * all written to disk, with no vision call. The judge stage reads them back
1628
+ * once the browser is gone.
1629
+ */
1630
+ async function captureReviewPackContext(browser, targetArg, navResult, opts, qaCapture) {
1631
+ const packDir = opts.reviewPack;
1632
+ const { maxTiles, band, overlap } = critiqueTilingKnobs(opts);
1633
+ const buffer = await browser.fullPageScreenshotBuffer();
1634
+ let atoms;
1635
+ try {
1636
+ atoms = await browser.visualAtoms();
1637
+ }
1638
+ catch {
1639
+ atoms = undefined;
1640
+ }
1641
+ const tiling = { bandHeight: band, overlap, maxTiles, atoms };
1642
+ const withBands = opts.reviewPackBands !== false;
1643
+ // Gate-hit rectangles (qa-run lifts them from the gate stage, which runs
1644
+ // first) pull the bands they land in past the cap; those tiles follow the
1645
+ // kept bands as `hit band N`. Coverage still describes the kept prefix.
1646
+ const hitRects = parseReviewPackHitRects(opts.reviewPackHitRect);
1647
+ const banded = withBands
1648
+ ? tilesFromFullPage(buffer, { ...tiling, hitRects, maxHitBands: REVIEW_PACK_HIT_RECT_MAX })
1649
+ : undefined;
1650
+ if (banded?.coverage.capped) {
1651
+ emit.log(`review-pack: page banded to the ${maxTiles}-tile cap (raise --check-critique-max-tiles for full coverage)`, "warn");
1652
+ }
1653
+ if (banded && banded.hitBands > 0) {
1654
+ emit.log(`review-pack: ${banded.hitBands} band(s) below the tile cap captured for ${hitRects.length} gate-hit rect(s)`, "info");
1655
+ }
1656
+ // Fidelity: prove the full-page screenshot against scrolled viewport
1657
+ // renders while the browser is still open; on disagreement the bands are
1658
+ // re-cut from scrolled captures (see reconcileCaptureFidelity).
1659
+ let bandTiles = banded?.tiles ?? [];
1660
+ let captureFidelity;
1661
+ const fidelityWarnings = [];
1662
+ if (bandTiles.length > 0) {
1663
+ const reconciled = await reconcileCaptureFidelity(browser, bandTiles);
1664
+ bandTiles = reconciled.tiles;
1665
+ captureFidelity = reconciled.fidelity;
1666
+ fidelityWarnings.push(...reconciled.warnings);
1667
+ for (const warning of reconciled.warnings)
1668
+ emit.log(warning, "warn");
1669
+ emit.log(`review-pack: capture fidelity ${captureFidelity.source} ` +
1670
+ `(${captureFidelity.probed.length} band(s) probed, ${captureFidelity.mismatched.length} mismatched)`, "info");
1671
+ }
1672
+ const scopeTiles = [];
1673
+ for (const selector of opts.reviewPackScope ?? []) {
1674
+ const elementRects = await browser.elementTiles(selector);
1675
+ const scoped = tilesFromFullPage(buffer, { ...tiling, elementRects });
1676
+ if (elementRects.length > maxTiles) {
1677
+ emit.log(`review-pack: ${elementRects.length} elements matched ${selector}, capped to ${maxTiles} tiles`, "warn");
1678
+ }
1679
+ scopeTiles.push({ selector, tiles: scoped.tiles, coverage: scoped.coverage });
1680
+ }
1681
+ // Coverage: the bands when present, else the scopes folded together
1682
+ // (heights worst-case, band counts add, capped sticky).
1683
+ let coverage = banded?.coverage;
1684
+ if (!coverage) {
1685
+ for (const scope of scopeTiles) {
1686
+ coverage = coverage
1687
+ ? {
1688
+ page_height_px: Math.max(coverage.page_height_px, scope.coverage.page_height_px),
1689
+ reviewed_height_px: Math.min(coverage.reviewed_height_px, scope.coverage.reviewed_height_px),
1690
+ bands_total: coverage.bands_total + scope.coverage.bands_total,
1691
+ bands_reviewed: coverage.bands_reviewed + scope.coverage.bands_reviewed,
1692
+ capped: coverage.capped || scope.coverage.capped,
1693
+ }
1694
+ : scope.coverage;
1695
+ }
1696
+ }
1697
+ const page = await browser.currentPage.evaluate(() => ({
1698
+ width: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
1699
+ height: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0),
1700
+ }));
1701
+ const capture = qaCapture ?? (await captureQaState(browser, targetArg, navResult.url, opts));
1702
+ const record = writePackContext(packDir, {
1703
+ context: {
1704
+ ...capture.context,
1705
+ ...(opts.reviewPackContext ? { id: opts.reviewPackContext } : {}),
1706
+ },
1707
+ url: navResult.url,
1708
+ title: navResult.title,
1709
+ fullPage: buffer,
1710
+ pageWidth: page.width,
1711
+ pageHeight: page.height,
1712
+ tiles: bandTiles,
1713
+ coverage: coverage ?? {
1714
+ page_height_px: page.height,
1715
+ reviewed_height_px: 0,
1716
+ bands_total: 0,
1717
+ bands_reviewed: 0,
1718
+ capped: false,
1719
+ },
1720
+ scopeTiles: scopeTiles.map(({ selector, tiles }) => ({ selector, tiles })),
1721
+ signature: capture.signature,
1722
+ domHtml: capture.domHtml,
1723
+ ...(captureFidelity ? { captureFidelity } : {}),
1724
+ hitBands: banded?.hitBands ?? 0,
1725
+ });
1726
+ emit.log(`review-pack: ${record.id} → ${record.tiles.length} tile(s)` +
1727
+ (record.scopes.length > 0
1728
+ ? ` (${record.scopes.map((s) => `${s.selector}: ${s.tiles}`).join(", ")})`
1729
+ : "") +
1730
+ ` written to ${packDir}`, "info");
1731
+ return {
1732
+ dir: packDir,
1733
+ context_id: record.id,
1734
+ tiles: record.tiles.length,
1735
+ scopes: record.scopes,
1736
+ coverage: record.coverage,
1737
+ files: record.files,
1738
+ ...(record.hit_bands !== undefined ? { hit_bands: record.hit_bands } : {}),
1739
+ ...(record.capture_fidelity ? { capture_fidelity: record.capture_fidelity } : {}),
1740
+ ...(fidelityWarnings.length > 0 ? { warnings: fidelityWarnings } : {}),
1741
+ };
1742
+ }
1743
+ async function captureCritiqueTiles(browser, opts, tileBudgetPx) {
1744
+ const { maxTiles, band, overlap } = critiqueTilingKnobs(opts, tileBudgetPx);
1436
1745
  // Capture the whole page ONCE, then crop tiles from the pixels. Playwright's
1437
1746
  // clip is viewport-relative, so a below-fold band clip throws ("clipped area
1438
1747
  // outside the resulting image") — cropping the full-page image sidesteps that
@@ -1451,7 +1760,7 @@ async function captureCritiqueTiles(browser, opts, tileBudgetPx) {
1451
1760
  catch {
1452
1761
  atoms = undefined;
1453
1762
  }
1454
- const tiles = tilesFromFullPage(buffer, {
1763
+ const { tiles, coverage } = tilesFromFullPage(buffer, {
1455
1764
  elementRects,
1456
1765
  bandHeight: band,
1457
1766
  overlap,
@@ -1464,7 +1773,7 @@ async function captureCritiqueTiles(browser, opts, tileBudgetPx) {
1464
1773
  else if (!elementRects && tiles.length >= maxTiles) {
1465
1774
  emit.log(`check-critique: page banded to the ${maxTiles}-tile cap (raise --check-critique-max-tiles for full coverage)`, "warn");
1466
1775
  }
1467
- return { tiles, fullPage: buffer };
1776
+ return { tiles, fullPage: buffer, coverage };
1468
1777
  }
1469
1778
  function logCritiqueSummary(critique) {
1470
1779
  if (critique.outcome === "skipped") {
@@ -1549,6 +1858,36 @@ async function waitForTerminalEnter(signal) {
1549
1858
  signal.addEventListener("abort", onAbort, { once: true });
1550
1859
  });
1551
1860
  }
1861
+ /** Upper bound on --review-pack-hit-rect values (and on hit bands cut past the cap). */
1862
+ const REVIEW_PACK_HIT_RECT_MAX = 50;
1863
+ /** `x,y,w,h` integer rectangles from repeated --review-pack-hit-rect flags. */
1864
+ function parseReviewPackHitRects(specs) {
1865
+ const list = specs ?? [];
1866
+ if (list.length > REVIEW_PACK_HIT_RECT_MAX) {
1867
+ throw new Error(`--review-pack-hit-rect accepts at most ${REVIEW_PACK_HIT_RECT_MAX} rectangles (got ${list.length}).`);
1868
+ }
1869
+ return list.map((spec) => {
1870
+ const parts = spec.split(",").map((part) => part.trim());
1871
+ const nums = parts.map((part) => (/^-?\d+$/.test(part) ? Number.parseInt(part, 10) : NaN));
1872
+ if (parts.length !== 4 || nums.some((n) => !Number.isFinite(n))) {
1873
+ throw new Error(`--review-pack-hit-rect must be four integers x,y,w,h in document px (got: ${spec}).`);
1874
+ }
1875
+ const [x, y, width, height] = nums;
1876
+ if (width < 0 || height < 0) {
1877
+ throw new Error(`--review-pack-hit-rect width and height must not be negative (got: ${spec}).`);
1878
+ }
1879
+ return { x, y, width, height };
1880
+ });
1881
+ }
1882
+ function parseDeviceScaleFactor(spec) {
1883
+ if (spec === undefined)
1884
+ return undefined;
1885
+ const value = Number.parseFloat(spec);
1886
+ if (!Number.isFinite(value) || value < 1 || value > 4) {
1887
+ throw new Error(`--device-scale-factor must be a number between 1 and 4 (got: ${spec})`);
1888
+ }
1889
+ return value;
1890
+ }
1552
1891
  function parseViewport(spec) {
1553
1892
  const preset = VIEWPORT_PRESETS[spec.toLowerCase()];
1554
1893
  if (preset)
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext, HarneryProgramContext } from "../commander.js";
3
+ export declare function registerDiagnosticsCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
4
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/commands/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAyB1E,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAiKN"}