harnery 0.30.0 → 0.31.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 (501) hide show
  1. package/README.md +4 -4
  2. package/dist/commander.d.ts +8 -0
  3. package/dist/commander.d.ts.map +1 -1
  4. package/dist/commander.js +14 -13
  5. package/dist/commands/adapter.d.ts +12 -0
  6. package/dist/commands/adapter.d.ts.map +1 -0
  7. package/dist/commands/{harness.js → adapter.js} +35 -35
  8. package/dist/commands/agents.d.ts +3 -2
  9. package/dist/commands/agents.d.ts.map +1 -1
  10. package/dist/commands/agents.js +61 -60
  11. package/dist/commands/artifacts.d.ts +4 -0
  12. package/dist/commands/artifacts.d.ts.map +1 -0
  13. package/dist/commands/artifacts.js +143 -0
  14. package/dist/commands/backup.d.ts +1 -1
  15. package/dist/commands/backup.d.ts.map +1 -1
  16. package/dist/commands/backup.js +5 -2
  17. package/dist/commands/browse.d.ts.map +1 -1
  18. package/dist/commands/browse.js +310 -7
  19. package/dist/commands/checkpoint.d.ts +12 -0
  20. package/dist/commands/checkpoint.d.ts.map +1 -0
  21. package/dist/commands/checkpoint.js +162 -0
  22. package/dist/commands/context.d.ts +2 -2
  23. package/dist/commands/context.d.ts.map +1 -1
  24. package/dist/commands/context.js +3 -151
  25. package/dist/commands/decision.d.ts.map +1 -1
  26. package/dist/commands/decision.js +31 -7
  27. package/dist/commands/deinit.d.ts +2 -2
  28. package/dist/commands/deinit.js +15 -15
  29. package/dist/commands/doctor.js +23 -23
  30. package/dist/commands/governor.d.ts +4 -0
  31. package/dist/commands/governor.d.ts.map +1 -0
  32. package/dist/commands/{supervisor.js → governor.js} +133 -108
  33. package/dist/commands/init.d.ts +12 -12
  34. package/dist/commands/init.d.ts.map +1 -1
  35. package/dist/commands/init.js +44 -30
  36. package/dist/commands/{session.d.ts → journal.d.ts} +2 -2
  37. package/dist/commands/journal.d.ts.map +1 -0
  38. package/dist/commands/{scratch.js → journal.js} +47 -47
  39. package/dist/commands/policy.js +1 -1
  40. package/dist/commands/presence.js +2 -2
  41. package/dist/commands/sync.d.ts +1 -1
  42. package/dist/commands/sync.js +3 -3
  43. package/dist/commands/tunnel.d.ts +16 -0
  44. package/dist/commands/tunnel.d.ts.map +1 -1
  45. package/dist/commands/tunnel.js +193 -23
  46. package/dist/commands/web-fetch.js +1 -1
  47. package/dist/commands/web.d.ts +19 -0
  48. package/dist/commands/web.d.ts.map +1 -1
  49. package/dist/commands/web.js +51 -0
  50. package/dist/commands/work.d.ts.map +1 -1
  51. package/dist/commands/work.js +38 -22
  52. package/dist/commands/workflow.d.ts.map +1 -1
  53. package/dist/commands/workflow.js +35 -28
  54. package/dist/core/{harnesses → adapters}/attest-projection.d.ts +2 -2
  55. package/dist/core/adapters/attest-projection.d.ts.map +1 -0
  56. package/dist/core/{harnesses → adapters}/attest.d.ts +16 -16
  57. package/dist/core/adapters/attest.d.ts.map +1 -0
  58. package/dist/core/{harnesses → adapters}/attest.js +9 -9
  59. package/dist/core/{harnesses → adapters}/attestation.d.ts +16 -16
  60. package/dist/core/adapters/attestation.d.ts.map +1 -0
  61. package/dist/core/{harnesses → adapters}/attestation.js +20 -20
  62. package/dist/core/{harnesses → adapters}/bench.d.ts +12 -12
  63. package/dist/core/adapters/bench.d.ts.map +1 -0
  64. package/dist/core/{harnesses → adapters}/bench.js +12 -12
  65. package/dist/core/adapters/index.d.ts +13 -0
  66. package/dist/core/adapters/index.d.ts.map +1 -0
  67. package/dist/core/adapters/index.js +6 -0
  68. package/dist/core/{harnesses → adapters}/profiles.d.ts +11 -11
  69. package/dist/core/adapters/profiles.d.ts.map +1 -0
  70. package/dist/core/{harnesses → adapters}/profiles.js +11 -11
  71. package/dist/core/adapters/registry.d.ts +15 -0
  72. package/dist/core/adapters/registry.d.ts.map +1 -0
  73. package/dist/core/{harnesses → adapters}/registry.js +11 -11
  74. package/dist/core/{harnesses → adapters}/types.d.ts +23 -23
  75. package/dist/core/adapters/types.d.ts.map +1 -0
  76. package/dist/core/{harnesses → adapters}/types.js +1 -1
  77. package/dist/core/agents/canonical-emit.d.ts +4 -4
  78. package/dist/core/agents/canonical-emit.js +5 -5
  79. package/dist/core/agents/cli-emit.js +1 -1
  80. package/dist/core/agents/cli.js +30 -26
  81. package/dist/core/agents/codex-replay.d.ts +1 -1
  82. package/dist/core/agents/codex-replay.js +6 -6
  83. package/dist/core/agents/coord-client.d.ts +21 -2
  84. package/dist/core/agents/coord-client.d.ts.map +1 -1
  85. package/dist/core/agents/coord-client.js +122 -27
  86. package/dist/core/agents/events/consume.d.ts +1 -1
  87. package/dist/core/agents/events/emit.d.ts +5 -6
  88. package/dist/core/agents/events/emit.d.ts.map +1 -1
  89. package/dist/core/agents/events/emit.js +2 -2
  90. package/dist/core/agents/render/prompt-context.d.ts +21 -9
  91. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  92. package/dist/core/agents/render/prompt-context.js +82 -30
  93. package/dist/core/agents/render/session-context.d.ts +1 -1
  94. package/dist/core/agents/render/session-context.js +5 -5
  95. package/dist/core/agents/rules/claim-conflict.d.ts +2 -6
  96. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  97. package/dist/core/agents/rules/claim-conflict.js +1 -1
  98. package/dist/core/agents/rules/commit-conflict.js +98 -22
  99. package/dist/core/agents/rules/stop-hook.d.ts +9 -9
  100. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  101. package/dist/core/agents/rules/stop-hook.js +23 -5
  102. package/dist/core/agents/rules/verdict.d.ts +14 -0
  103. package/dist/core/agents/rules/verdict.d.ts.map +1 -0
  104. package/dist/core/agents/rules/verdict.js +1 -0
  105. package/dist/core/agents/session-events.js +3 -3
  106. package/dist/core/agents/state/council.d.ts +1 -1
  107. package/dist/core/agents/state/council.js +1 -1
  108. package/dist/core/agents/state/heartbeat-projector.d.ts +3 -2
  109. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  110. package/dist/core/agents/state/heartbeat-projector.js +14 -10
  111. package/dist/core/agents/state/heartbeat-writer.d.ts +1 -1
  112. package/dist/core/agents/state/heartbeat-writer.js +12 -12
  113. package/dist/core/agents/state/{scratch.d.ts → journal.d.ts} +7 -7
  114. package/dist/core/agents/state/{scratch.d.ts.map → journal.d.ts.map} +1 -1
  115. package/dist/core/agents/state/{scratch.js → journal.js} +12 -12
  116. package/dist/core/agents/state/pidmap.d.ts +28 -4
  117. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  118. package/dist/core/agents/state/pidmap.js +130 -15
  119. package/dist/core/agents/state/proc-start.d.ts +95 -0
  120. package/dist/core/agents/state/proc-start.d.ts.map +1 -0
  121. package/dist/core/agents/state/proc-start.js +199 -0
  122. package/dist/core/agents/state/shell-mutation.d.ts +1 -1
  123. package/dist/core/agents/state/shell-mutation.js +1 -1
  124. package/dist/core/agents/state/stale-sweep.js +7 -7
  125. package/dist/core/artifacts/index.d.ts +86 -0
  126. package/dist/core/artifacts/index.d.ts.map +1 -0
  127. package/dist/core/artifacts/index.js +421 -0
  128. package/dist/core/config.d.ts +9 -2
  129. package/dist/core/config.d.ts.map +1 -1
  130. package/dist/core/config.js +26 -2
  131. package/dist/core/context/index.d.ts +12 -12
  132. package/dist/core/context/index.js +10 -10
  133. package/dist/core/governor/adapter-spread.d.ts +52 -0
  134. package/dist/core/governor/adapter-spread.d.ts.map +1 -0
  135. package/dist/core/governor/adapter-spread.js +52 -0
  136. package/dist/core/governor/index.d.ts +7 -0
  137. package/dist/core/governor/index.d.ts.map +1 -0
  138. package/dist/core/governor/index.js +6 -0
  139. package/dist/core/governor/plan-api.d.ts +38 -0
  140. package/dist/core/governor/plan-api.d.ts.map +1 -0
  141. package/dist/core/governor/plan-api.js +83 -0
  142. package/dist/core/governor/plan-read.d.ts +5 -0
  143. package/dist/core/governor/plan-read.d.ts.map +1 -0
  144. package/dist/core/{supervisor → governor}/plan-read.js +66 -52
  145. package/dist/core/{supervisor → governor}/plan-types.d.ts +57 -56
  146. package/dist/core/governor/plan-types.d.ts.map +1 -0
  147. package/dist/core/{supervisor → governor}/plan-types.js +4 -4
  148. package/dist/core/governor/planning.d.ts +48 -0
  149. package/dist/core/governor/planning.d.ts.map +1 -0
  150. package/dist/core/{supervisor → governor}/planning.js +125 -94
  151. package/dist/core/governor/read.d.ts +6 -0
  152. package/dist/core/governor/read.d.ts.map +1 -0
  153. package/dist/core/governor/read.js +4 -0
  154. package/dist/core/governor/runner.d.ts +37 -0
  155. package/dist/core/governor/runner.d.ts.map +1 -0
  156. package/dist/core/{supervisor → governor}/runner.js +15 -15
  157. package/dist/core/governor/service-read.d.ts +6 -0
  158. package/dist/core/governor/service-read.d.ts.map +1 -0
  159. package/dist/core/{supervisor → governor}/service-read.js +12 -12
  160. package/dist/core/governor/service.d.ts +114 -0
  161. package/dist/core/governor/service.d.ts.map +1 -0
  162. package/dist/core/{supervisor → governor}/service.js +57 -57
  163. package/dist/core/governor/state.d.ts +116 -0
  164. package/dist/core/governor/state.d.ts.map +1 -0
  165. package/dist/core/{supervisor → governor}/state.js +228 -108
  166. package/dist/core/hooks/adapter/detect.d.ts +9 -0
  167. package/dist/core/hooks/{harness → adapter}/detect.d.ts.map +1 -1
  168. package/dist/core/hooks/{harness → adapter}/detect.js +8 -8
  169. package/dist/core/hooks/{harness → adapter}/events.d.ts +15 -14
  170. package/dist/core/hooks/adapter/events.d.ts.map +1 -0
  171. package/dist/core/hooks/{harness → adapter}/events.js +5 -5
  172. package/dist/core/hooks/{harness → adapter}/output.d.ts +20 -14
  173. package/dist/core/hooks/adapter/output.d.ts.map +1 -0
  174. package/dist/core/hooks/{harness → adapter}/output.js +30 -22
  175. package/dist/core/hooks/{harness → adapter}/parse.d.ts +5 -5
  176. package/dist/core/hooks/{harness → adapter}/parse.d.ts.map +1 -1
  177. package/dist/core/hooks/{harness → adapter}/parse.js +4 -4
  178. package/dist/core/hooks/{harness → adapter}/wiring.d.ts +15 -15
  179. package/dist/core/hooks/{harness → adapter}/wiring.d.ts.map +1 -1
  180. package/dist/core/hooks/{harness → adapter}/wiring.js +13 -13
  181. package/dist/core/hooks/cli.d.ts +3 -3
  182. package/dist/core/hooks/cli.js +175 -151
  183. package/dist/core/hooks/effects/image-capture.d.ts +5 -5
  184. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  185. package/dist/core/hooks/effects/image-capture.js +3 -3
  186. package/dist/core/hooks/effects/index.d.ts +12 -12
  187. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  188. package/dist/core/hooks/effects/index.js +17 -17
  189. package/dist/core/hooks/events/emit.d.ts +2 -2
  190. package/dist/core/hooks/events/emit.d.ts.map +1 -1
  191. package/dist/core/hooks/events/emit.js +1 -1
  192. package/dist/core/hooks/events/rotate.js +1 -1
  193. package/dist/core/hooks/events/schema.d.ts +13 -5
  194. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  195. package/dist/core/hooks/guard-path.d.ts +2 -2
  196. package/dist/core/hooks/guard-path.js +2 -2
  197. package/dist/core/hooks/index.d.ts +1 -1
  198. package/dist/core/hooks/index.d.ts.map +1 -1
  199. package/dist/core/hooks/resolve/anchor.d.ts +14 -5
  200. package/dist/core/hooks/resolve/anchor.d.ts.map +1 -1
  201. package/dist/core/hooks/resolve/anchor.js +64 -7
  202. package/dist/core/hooks/resolve/coord-root.d.ts +3 -3
  203. package/dist/core/hooks/resolve/coord-root.js +4 -4
  204. package/dist/core/hooks/resolve/intent.js +1 -1
  205. package/dist/core/hooks/resolve/owner.d.ts +1 -1
  206. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  207. package/dist/core/hooks/resolve/owner.js +11 -5
  208. package/dist/core/{scratch → journal}/index.d.ts +25 -25
  209. package/dist/core/{scratch → journal}/index.d.ts.map +1 -1
  210. package/dist/core/{scratch → journal}/index.js +38 -38
  211. package/dist/core/policy/evaluate.d.ts +1 -1
  212. package/dist/core/policy/evaluate.d.ts.map +1 -1
  213. package/dist/core/policy/evaluate.js +7 -7
  214. package/dist/core/policy/types.d.ts +3 -3
  215. package/dist/core/policy/types.d.ts.map +1 -1
  216. package/dist/core/work/state.d.ts +12 -4
  217. package/dist/core/work/state.d.ts.map +1 -1
  218. package/dist/core/work/state.js +50 -17
  219. package/dist/core/workflow/adapters.d.ts +17 -0
  220. package/dist/core/workflow/adapters.d.ts.map +1 -0
  221. package/dist/core/workflow/adapters.js +21 -0
  222. package/dist/core/workflow/approvals.js +6 -6
  223. package/dist/core/workflow/billing.d.ts +9 -9
  224. package/dist/core/workflow/billing.js +9 -9
  225. package/dist/core/workflow/child-env.d.ts +10 -2
  226. package/dist/core/workflow/child-env.d.ts.map +1 -1
  227. package/dist/core/workflow/child-env.js +12 -4
  228. package/dist/core/workflow/engine.d.ts +15 -5
  229. package/dist/core/workflow/engine.d.ts.map +1 -1
  230. package/dist/core/workflow/engine.js +138 -83
  231. package/dist/core/workflow/evidence-preflight.d.ts +24 -0
  232. package/dist/core/workflow/evidence-preflight.d.ts.map +1 -0
  233. package/dist/core/workflow/evidence-preflight.js +134 -0
  234. package/dist/core/workflow/index.d.ts +1 -1
  235. package/dist/core/workflow/index.d.ts.map +1 -1
  236. package/dist/core/workflow/proof.d.ts +11 -5
  237. package/dist/core/workflow/proof.d.ts.map +1 -1
  238. package/dist/core/workflow/proof.js +45 -36
  239. package/dist/core/workflow/run-state.d.ts +1 -1
  240. package/dist/core/workflow/run-state.js +7 -7
  241. package/dist/core/workflow/sandbox-projection.d.ts +6 -6
  242. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  243. package/dist/core/workflow/sandbox-projection.js +11 -11
  244. package/dist/core/workflow/spawn-claude.d.ts +3 -3
  245. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  246. package/dist/core/workflow/spawn-claude.js +9 -6
  247. package/dist/core/workflow/spawn-codex.d.ts +4 -4
  248. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  249. package/dist/core/workflow/spawn-codex.js +9 -6
  250. package/dist/core/workflow/spawn-cursor.d.ts +4 -4
  251. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  252. package/dist/core/workflow/spawn-cursor.js +9 -6
  253. package/dist/core/workflow/spawn-failure.d.ts +1 -1
  254. package/dist/core/workflow/spawn-failure.js +1 -1
  255. package/dist/core/workflow/specialists.js +2 -2
  256. package/dist/core/workflow/{journal.d.ts → transcript.d.ts} +8 -8
  257. package/dist/core/workflow/transcript.d.ts.map +1 -0
  258. package/dist/core/workflow/{journal.js → transcript.js} +14 -14
  259. package/dist/core/workflow/types.d.ts +98 -51
  260. package/dist/core/workflow/types.d.ts.map +1 -1
  261. package/dist/core/workflow/types.js +11 -1
  262. package/dist/core/workflow/validate.d.ts +1 -1
  263. package/dist/core/workflow/validate.js +1 -1
  264. package/dist/core/workflow/workspaces/cancellation.js +2 -2
  265. package/dist/core/workflow/workspaces/cleanup.js +2 -2
  266. package/dist/core/workflow/workspaces/execution.js +6 -6
  267. package/dist/core/workflow/workspaces/inspect.js +9 -9
  268. package/dist/core/workflow/workspaces/integration.js +14 -14
  269. package/dist/core/workflow/workspaces/lifecycle.d.ts +1 -1
  270. package/dist/core/workflow/workspaces/lifecycle.d.ts.map +1 -1
  271. package/dist/core/workflow/workspaces/lifecycle.js +9 -9
  272. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  273. package/dist/core/workflow/workspaces/local-git.js +39 -5
  274. package/dist/core/workflow/workspaces/recovery.js +3 -3
  275. package/dist/core/workflow/workspaces/state.d.ts +1 -1
  276. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  277. package/dist/core/workflow/workspaces/state.js +3 -3
  278. package/dist/core/workflow/workspaces/types.d.ts +1 -1
  279. package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
  280. package/dist/lib/browser/asserts.d.ts +28 -0
  281. package/dist/lib/browser/asserts.d.ts.map +1 -0
  282. package/dist/lib/browser/asserts.js +152 -0
  283. package/dist/lib/browser/client.d.ts +49 -0
  284. package/dist/lib/browser/client.d.ts.map +1 -1
  285. package/dist/lib/browser/client.js +155 -14
  286. package/dist/lib/browser/content-checks.d.ts +108 -0
  287. package/dist/lib/browser/content-checks.d.ts.map +1 -0
  288. package/dist/lib/browser/content-checks.js +474 -0
  289. package/dist/lib/browser/critique.d.ts +91 -0
  290. package/dist/lib/browser/critique.d.ts.map +1 -0
  291. package/dist/lib/browser/critique.js +167 -0
  292. package/dist/lib/browser/geometry.d.ts +32 -0
  293. package/dist/lib/browser/geometry.d.ts.map +1 -1
  294. package/dist/lib/browser/geometry.js +244 -1
  295. package/dist/lib/browser/index.d.ts +4 -1
  296. package/dist/lib/browser/index.d.ts.map +1 -1
  297. package/dist/lib/browser/index.js +2 -0
  298. package/dist/lib/claude-desktop.d.ts.map +1 -1
  299. package/dist/lib/claude-desktop.js +4 -1
  300. package/dist/lib/council/index.d.ts +6 -6
  301. package/dist/lib/council/index.d.ts.map +1 -1
  302. package/dist/lib/council/index.js +8 -8
  303. package/dist/lib/identities/assume.d.ts +2 -2
  304. package/dist/lib/identities/assume.js +7 -7
  305. package/dist/lib/instructions/apply.d.ts +3 -3
  306. package/dist/lib/instructions/apply.d.ts.map +1 -1
  307. package/dist/lib/instructions/apply.js +75 -27
  308. package/dist/lib/instructions/host-addendum.d.ts +54 -0
  309. package/dist/lib/instructions/host-addendum.d.ts.map +1 -0
  310. package/dist/lib/instructions/host-addendum.js +107 -0
  311. package/dist/lib/instructions/templates.d.ts +10 -3
  312. package/dist/lib/instructions/templates.d.ts.map +1 -1
  313. package/dist/lib/instructions/templates.js +41 -12
  314. package/dist/lib/machine.d.ts +2 -2
  315. package/dist/lib/machine.js +2 -2
  316. package/package.json +26 -21
  317. package/schemas/config.schema.json +26 -1
  318. package/src/commander.ts +22 -13
  319. package/src/commands/{harness.ts → adapter.ts} +46 -46
  320. package/src/commands/agents.ts +71 -64
  321. package/src/commands/artifacts.ts +168 -0
  322. package/src/commands/backup.ts +47 -33
  323. package/src/commands/browse.ts +438 -2
  324. package/src/commands/checkpoint.ts +196 -0
  325. package/src/commands/context.ts +3 -176
  326. package/src/commands/decision.ts +41 -7
  327. package/src/commands/deinit.ts +16 -16
  328. package/src/commands/doctor.ts +28 -28
  329. package/src/commands/{supervisor.ts → governor.ts} +184 -154
  330. package/src/commands/init.ts +50 -34
  331. package/src/commands/{scratch.ts → journal.ts} +60 -60
  332. package/src/commands/policy.ts +1 -1
  333. package/src/commands/presence.ts +2 -2
  334. package/src/commands/sync.ts +3 -3
  335. package/src/commands/tunnel.ts +237 -34
  336. package/src/commands/web-fetch.ts +1 -1
  337. package/src/commands/web.ts +56 -1
  338. package/src/commands/work.ts +50 -35
  339. package/src/commands/workflow.ts +43 -34
  340. package/src/core/{harnesses → adapters}/attest-projection.ts +2 -2
  341. package/src/core/{harnesses → adapters}/attest.ts +27 -27
  342. package/src/core/{harnesses → adapters}/attestation.ts +41 -41
  343. package/src/core/{harnesses → adapters}/bench.ts +34 -43
  344. package/src/core/adapters/index.ts +56 -0
  345. package/src/core/{harnesses → adapters}/profiles.ts +16 -16
  346. package/src/core/{harnesses → adapters}/registry.ts +23 -23
  347. package/src/core/{harnesses → adapters}/types.ts +23 -23
  348. package/src/core/agents/canonical-emit.ts +6 -6
  349. package/src/core/agents/cli-emit.ts +1 -1
  350. package/src/core/agents/cli.ts +30 -28
  351. package/src/core/agents/codex-replay.ts +6 -6
  352. package/src/core/agents/coord-client.ts +119 -28
  353. package/src/core/agents/events/consume.ts +1 -1
  354. package/src/core/agents/events/emit.ts +7 -6
  355. package/src/core/agents/render/prompt-context.ts +107 -31
  356. package/src/core/agents/render/session-context.ts +6 -6
  357. package/src/core/agents/rules/claim-conflict.ts +4 -7
  358. package/src/core/agents/rules/commit-conflict.ts +103 -22
  359. package/src/core/agents/rules/stop-hook.ts +33 -17
  360. package/src/core/agents/rules/verdict.ts +13 -0
  361. package/src/core/agents/session-events.ts +4 -4
  362. package/src/core/agents/state/council.ts +1 -1
  363. package/src/core/agents/state/heartbeat-projector.ts +16 -12
  364. package/src/core/agents/state/heartbeat-writer.ts +12 -12
  365. package/src/core/agents/state/{scratch.ts → journal.ts} +12 -12
  366. package/src/core/agents/state/pidmap.ts +130 -15
  367. package/src/core/agents/state/proc-start.ts +208 -0
  368. package/src/core/agents/state/shell-mutation.ts +1 -1
  369. package/src/core/agents/state/stale-sweep.ts +7 -7
  370. package/src/core/artifacts/index.ts +557 -0
  371. package/src/core/config.ts +30 -2
  372. package/src/core/context/index.ts +15 -15
  373. package/src/core/governor/adapter-spread.ts +82 -0
  374. package/src/core/governor/index.ts +91 -0
  375. package/src/core/governor/plan-api.ts +115 -0
  376. package/src/core/{supervisor → governor}/plan-read.ts +112 -97
  377. package/src/core/{supervisor → governor}/plan-types.ts +59 -56
  378. package/src/core/{supervisor → governor}/planning.ts +223 -192
  379. package/src/core/governor/read.ts +59 -0
  380. package/src/core/{supervisor → governor}/runner.ts +39 -39
  381. package/src/core/{supervisor → governor}/service-read.ts +22 -22
  382. package/src/core/{supervisor → governor}/service.ts +117 -119
  383. package/src/core/{supervisor → governor}/state.ts +325 -167
  384. package/src/core/hooks/adapter/detect.ts +30 -0
  385. package/src/core/hooks/{harness → adapter}/events.ts +16 -14
  386. package/src/core/hooks/{harness → adapter}/output.ts +31 -23
  387. package/src/core/hooks/{harness → adapter}/parse.ts +5 -5
  388. package/src/core/hooks/{harness → adapter}/wiring.ts +21 -21
  389. package/src/core/hooks/cli.ts +185 -162
  390. package/src/core/hooks/effects/image-capture.ts +6 -6
  391. package/src/core/hooks/effects/index.ts +17 -17
  392. package/src/core/hooks/events/emit.ts +3 -3
  393. package/src/core/hooks/events/rotate.ts +1 -1
  394. package/src/core/hooks/events/schema.ts +15 -7
  395. package/src/core/hooks/guard-path.ts +2 -2
  396. package/src/core/hooks/index.ts +1 -1
  397. package/src/core/hooks/resolve/anchor.ts +63 -8
  398. package/src/core/hooks/resolve/coord-root.ts +4 -4
  399. package/src/core/hooks/resolve/intent.ts +1 -1
  400. package/src/core/hooks/resolve/owner.ts +11 -5
  401. package/src/core/{scratch → journal}/index.ts +53 -53
  402. package/src/core/policy/evaluate.ts +8 -8
  403. package/src/core/policy/types.ts +3 -3
  404. package/src/core/work/state.ts +65 -23
  405. package/src/core/workflow/adapters.ts +33 -0
  406. package/src/core/workflow/approvals.ts +7 -7
  407. package/src/core/workflow/billing.ts +12 -12
  408. package/src/core/workflow/child-env.ts +13 -4
  409. package/src/core/workflow/engine.ts +155 -90
  410. package/src/core/workflow/evidence-preflight.ts +165 -0
  411. package/src/core/workflow/index.ts +3 -3
  412. package/src/core/workflow/proof.ts +60 -50
  413. package/src/core/workflow/run-state.ts +7 -7
  414. package/src/core/workflow/sandbox-projection.ts +16 -16
  415. package/src/core/workflow/spawn-claude.ts +13 -10
  416. package/src/core/workflow/spawn-codex.ts +13 -10
  417. package/src/core/workflow/spawn-cursor.ts +13 -10
  418. package/src/core/workflow/spawn-failure.ts +1 -1
  419. package/src/core/workflow/specialists.ts +2 -2
  420. package/src/core/workflow/{journal.ts → transcript.ts} +16 -16
  421. package/src/core/workflow/types.ts +108 -51
  422. package/src/core/workflow/validate.ts +1 -1
  423. package/src/core/workflow/workspaces/cancellation.ts +2 -2
  424. package/src/core/workflow/workspaces/cleanup.ts +2 -2
  425. package/src/core/workflow/workspaces/execution.ts +6 -6
  426. package/src/core/workflow/workspaces/inspect.ts +14 -11
  427. package/src/core/workflow/workspaces/integration.ts +18 -14
  428. package/src/core/workflow/workspaces/lifecycle.ts +10 -10
  429. package/src/core/workflow/workspaces/local-git.ts +47 -5
  430. package/src/core/workflow/workspaces/recovery.ts +3 -3
  431. package/src/core/workflow/workspaces/state.ts +4 -3
  432. package/src/core/workflow/workspaces/types.ts +1 -1
  433. package/src/lib/browser/asserts.ts +177 -0
  434. package/src/lib/browser/client.ts +189 -14
  435. package/src/lib/browser/content-checks.ts +571 -0
  436. package/src/lib/browser/critique.ts +231 -0
  437. package/src/lib/browser/geometry.ts +297 -1
  438. package/src/lib/browser/index.ts +34 -0
  439. package/src/lib/claude-desktop.ts +4 -1
  440. package/src/lib/council/index.ts +9 -9
  441. package/src/lib/identities/assume.ts +7 -7
  442. package/src/lib/instructions/apply.ts +87 -25
  443. package/src/lib/instructions/host-addendum.ts +126 -0
  444. package/src/lib/instructions/templates.ts +44 -15
  445. package/src/lib/machine.ts +2 -2
  446. package/dist/commands/harness.d.ts +0 -12
  447. package/dist/commands/harness.d.ts.map +0 -1
  448. package/dist/commands/scratch.d.ts +0 -4
  449. package/dist/commands/scratch.d.ts.map +0 -1
  450. package/dist/commands/session.d.ts.map +0 -1
  451. package/dist/commands/session.js +0 -162
  452. package/dist/commands/supervisor.d.ts +0 -4
  453. package/dist/commands/supervisor.d.ts.map +0 -1
  454. package/dist/core/harnesses/attest-projection.d.ts.map +0 -1
  455. package/dist/core/harnesses/attest.d.ts.map +0 -1
  456. package/dist/core/harnesses/attestation.d.ts.map +0 -1
  457. package/dist/core/harnesses/bench.d.ts.map +0 -1
  458. package/dist/core/harnesses/index.d.ts +0 -13
  459. package/dist/core/harnesses/index.d.ts.map +0 -1
  460. package/dist/core/harnesses/index.js +0 -6
  461. package/dist/core/harnesses/profiles.d.ts.map +0 -1
  462. package/dist/core/harnesses/registry.d.ts +0 -15
  463. package/dist/core/harnesses/registry.d.ts.map +0 -1
  464. package/dist/core/harnesses/types.d.ts.map +0 -1
  465. package/dist/core/hooks/harness/detect.d.ts +0 -9
  466. package/dist/core/hooks/harness/events.d.ts.map +0 -1
  467. package/dist/core/hooks/harness/output.d.ts.map +0 -1
  468. package/dist/core/supervisor/index.d.ts +0 -7
  469. package/dist/core/supervisor/index.d.ts.map +0 -1
  470. package/dist/core/supervisor/index.js +0 -6
  471. package/dist/core/supervisor/plan-api.d.ts +0 -23
  472. package/dist/core/supervisor/plan-api.d.ts.map +0 -1
  473. package/dist/core/supervisor/plan-api.js +0 -50
  474. package/dist/core/supervisor/plan-read.d.ts +0 -5
  475. package/dist/core/supervisor/plan-read.d.ts.map +0 -1
  476. package/dist/core/supervisor/plan-types.d.ts.map +0 -1
  477. package/dist/core/supervisor/planning.d.ts +0 -33
  478. package/dist/core/supervisor/planning.d.ts.map +0 -1
  479. package/dist/core/supervisor/read.d.ts +0 -6
  480. package/dist/core/supervisor/read.d.ts.map +0 -1
  481. package/dist/core/supervisor/read.js +0 -4
  482. package/dist/core/supervisor/runner.d.ts +0 -37
  483. package/dist/core/supervisor/runner.d.ts.map +0 -1
  484. package/dist/core/supervisor/service-read.d.ts +0 -6
  485. package/dist/core/supervisor/service-read.d.ts.map +0 -1
  486. package/dist/core/supervisor/service.d.ts +0 -114
  487. package/dist/core/supervisor/service.d.ts.map +0 -1
  488. package/dist/core/supervisor/state.d.ts +0 -95
  489. package/dist/core/supervisor/state.d.ts.map +0 -1
  490. package/dist/core/workflow/harnesses.d.ts +0 -17
  491. package/dist/core/workflow/harnesses.d.ts.map +0 -1
  492. package/dist/core/workflow/harnesses.js +0 -21
  493. package/dist/core/workflow/journal.d.ts.map +0 -1
  494. package/src/commands/session.ts +0 -187
  495. package/src/core/harnesses/index.ts +0 -56
  496. package/src/core/hooks/harness/detect.ts +0 -30
  497. package/src/core/supervisor/index.ts +0 -89
  498. package/src/core/supervisor/plan-api.ts +0 -73
  499. package/src/core/supervisor/read.ts +0 -59
  500. package/src/core/workflow/harnesses.ts +0 -33
  501. /package/dist/core/{harnesses → adapters}/attest-projection.js +0 -0
@@ -5,16 +5,25 @@ import type { Command } from "commander";
5
5
  import type { EmitContext, HarneryProgramContext } from "../commander.ts";
6
6
  import { resolveBinName } from "../core/config.ts";
7
7
  import {
8
+ type AssertResult,
8
9
  Browser,
10
+ type ContentAnnotationBox,
11
+ type ContentChecksResult,
12
+ type CritiqueResult,
13
+ type CritiqueTile,
9
14
  captureDevOverlay,
15
+ DEFAULT_CRITIQUE_RUBRIC,
10
16
  type DevOverlayResult,
11
17
  type Diagnostics,
12
18
  type LayoutAxis,
13
19
  type LayoutLintResult,
14
20
  type OverflowResult,
21
+ parseAssertSpec,
15
22
  type RuntsResult,
23
+ runCritique,
16
24
  type TargetSizeProfile,
17
25
  type TargetSizeResult,
26
+ tilesFromFullPage,
18
27
  type VisibilityResult,
19
28
  type WidthResult,
20
29
  wslHeadedLaunchArgs,
@@ -121,6 +130,35 @@ interface BrowseOpts {
121
130
  checkOverlapThreshold?: string;
122
131
  checkOverlapFail?: boolean;
123
132
  checkOverlapAnnotate?: boolean;
133
+ checkCrowd?: string[];
134
+ checkCrowdMin?: string;
135
+ checkCrowdFail?: boolean;
136
+ checkCrowdAnnotate?: boolean;
137
+ // Content checks (optional selector scope; true = whole body)
138
+ checkPlaceholder?: boolean | string;
139
+ checkPlaceholderFail?: boolean;
140
+ checkPlaceholderAnnotate?: boolean;
141
+ checkImages?: boolean | string;
142
+ checkImagesTolerance?: string;
143
+ checkImagesFail?: boolean;
144
+ checkImagesAnnotate?: boolean;
145
+ checkTruncation?: boolean | string;
146
+ checkTruncationTolerance?: string;
147
+ checkTruncationFail?: boolean;
148
+ checkTruncationAnnotate?: boolean;
149
+ checkContrast?: boolean | string;
150
+ checkContrastFail?: boolean;
151
+ checkContrastAnnotate?: boolean;
152
+ // Vision-model critique (optional selector = semantic per-element tiling)
153
+ checkCritique?: boolean | string;
154
+ checkCritiqueBand?: string;
155
+ checkCritiqueOverlap?: string;
156
+ checkCritiqueMaxTiles?: string;
157
+ checkCritiqueRubric?: string;
158
+ checkCritiqueFail?: boolean;
159
+ // Value assertions
160
+ assert?: string[];
161
+ assertFail?: boolean;
124
162
  // Optional selector; null means the full document. Repeatable.
125
163
  checkHit?: Array<string | null>;
126
164
  checkHitProfile?: string;
@@ -357,6 +395,105 @@ export function registerBrowseCommand(
357
395
  )
358
396
  .option("--check-overlap-fail", "Exit 2 when an overlap target fails or is missing.")
359
397
  .option("--no-check-overlap-annotate", "Skip overlap screenshot annotations.")
398
+ .option(
399
+ "--check-crowd <selector>",
400
+ "Flag adjacent card panels (full border, modest corner radius, or box-shadow) " +
401
+ "that touch or nearly touch. Also treats wrappers that contain panels as " +
402
+ "peers, measuring the nearest face panels inside them so a card-grid " +
403
+ "flush against the next card is caught. Walks the whole subtree under the " +
404
+ "selector, so one --check-crowd .wrap catches nested cases. Fills the gap " +
405
+ "between --check-overlap (needs a 2D intersection) and --check-gap (flags " +
406
+ "uneven spacing, so a uniformly-flush stack passes). Repeatable.",
407
+ (value: string, previous: string[] = []) => [...previous, value],
408
+ [] as string[],
409
+ )
410
+ .option(
411
+ "--check-crowd-min <px>",
412
+ "Minimum acceptable edge gap between adjacent panel faces in CSS px (default 6). " +
413
+ "Pairs closer than this are flagged; negative separation (overlap) always flags.",
414
+ "6",
415
+ )
416
+ .option("--check-crowd-fail", "Exit 2 when a crowd target fails or is missing.")
417
+ .option("--no-check-crowd-annotate", "Skip crowd screenshot annotations.")
418
+ .option(
419
+ "--check-placeholder [selector]",
420
+ "Scan rendered text for unrendered template tokens and bound-value tells: " +
421
+ "JS template literals, `{{x}}` handlebars, `[object Object]`, `Invalid Date`, " +
422
+ "`NaN`, or an element whose whole text is literally 'undefined' / 'null'. Optional selector " +
423
+ "scopes the sweep (default: whole body). Catches the most common generation " +
424
+ "bug on funnels and reports. Do not point it at a page that documents template " +
425
+ "syntax in prose.",
426
+ )
427
+ .option("--check-placeholder-fail", "Exit 2 if any placeholder tell is found.")
428
+ .option("--no-check-placeholder-annotate", "Skip placeholder screenshot annotations.")
429
+ .option(
430
+ "--check-images [selector]",
431
+ "Audit <img> elements for load + aspect health: a failed load (naturalWidth 0), " +
432
+ "a still-loading image, or a stretched image (rendered aspect ratio far from the " +
433
+ "intrinsic one, with an object-fit that does not correct it). Optional selector " +
434
+ "scopes the sweep (default: whole body).",
435
+ )
436
+ .option(
437
+ "--check-images-tolerance <ratio>",
438
+ "Aspect-ratio deviation above which an image counts as stretched (0–1, default 0.1 = 10%).",
439
+ "0.1",
440
+ )
441
+ .option("--check-images-fail", "Exit 2 if any image failed to load or is stretched.")
442
+ .option("--no-check-images-annotate", "Skip image screenshot annotations.")
443
+ .option(
444
+ "--check-truncation [selector]",
445
+ "Flag text actively cut off by an ellipsis or -webkit-line-clamp — the author asked " +
446
+ "to truncate AND the content overflows. High precision: it does not flag plain " +
447
+ "overflow:hidden clips. Optional selector scopes the sweep (default: whole body).",
448
+ )
449
+ .option(
450
+ "--check-truncation-tolerance <px>",
451
+ "Overflow past the clip box, in CSS px, above which truncation counts (default 2).",
452
+ "2",
453
+ )
454
+ .option("--check-truncation-fail", "Exit 2 if any text is actively truncated.")
455
+ .option("--no-check-truncation-annotate", "Skip truncation screenshot annotations.")
456
+ .option(
457
+ "--check-contrast [selector]",
458
+ "Flag rendered text below the WCAG AA contrast ratio (4.5:1 normal, 3:1 large) against " +
459
+ "its effective background. Runs at whatever theme the page is in, so toggle the theme " +
460
+ "with --batch to cover light and dark. Text over an image or gradient is reported as " +
461
+ "`unknown`, not failed. Optional selector scopes the sweep (default: whole body).",
462
+ )
463
+ .option("--check-contrast-fail", "Exit 2 if any text fails the contrast ratio.")
464
+ .option("--no-check-contrast-annotate", "Skip contrast screenshot annotations.")
465
+ .option(
466
+ "--check-critique [selector]",
467
+ "Hand the rendered page to a vision model for a visual-defect review, tile by " +
468
+ "tile. Catches the long tail heuristic checks can't enumerate. Default tiling " +
469
+ "cuts the page into overlapping vertical bands; pass a selector to tile one " +
470
+ "screenshot per matching element (semantic tiling). Requires the host to inject " +
471
+ "a critiqueProvider (harnery ships no model client) — without one the check " +
472
+ "reports `skipped`. Findings land under `critique` in the JSON envelope.",
473
+ )
474
+ .option("--check-critique-band <px>", "Band height for default tiling (default 1400).", "1400")
475
+ .option(
476
+ "--check-critique-overlap <px>",
477
+ "Vertical overlap between bands so a finding at a seam stays visible (default 120).",
478
+ "120",
479
+ )
480
+ .option(
481
+ "--check-critique-max-tiles <n>",
482
+ "Cap on tiles sent to the model, to bound cost (default 24).",
483
+ "24",
484
+ )
485
+ .option("--check-critique-rubric <text>", "Override the default critique rubric.")
486
+ .option("--check-critique-fail", "Exit 2 if the critique returns any high-severity finding.")
487
+ .option(
488
+ "--assert <expr>",
489
+ "Assert a page value (repeatable). Grammar: '<op> <selector> => <expected>', where op is " +
490
+ "text (trimmed text equals), contains (text includes), matches (text matches a regex), " +
491
+ "count (match count vs a number or >=/<=/>/< comparator), exists / absent (no => needed). " +
492
+ "e.g. --assert 'text h1 => Welcome' --assert 'count .card => >=3' --assert 'absent .error'.",
493
+ (value: string, previous: string[] = []) => [...previous, value],
494
+ [] as string[],
495
+ )
496
+ .option("--assert-fail", "Exit 2 if any --assert fails.")
360
497
  .option(
361
498
  "--check-hit [selector]",
362
499
  "Check pointer-target size and spacing in the document or optional scope (repeatable).",
@@ -437,6 +574,7 @@ async function runBrowse(
437
574
  opts.checkOverlapThreshold ?? "0",
438
575
  "--check-overlap-threshold",
439
576
  );
577
+ const crowdMin = parseNonNegativeNumber(opts.checkCrowdMin ?? "6", "--check-crowd-min");
440
578
  const hitProfile = parseTargetSizeProfile(opts.checkHitProfile ?? "wcag-aa");
441
579
 
442
580
  // Commander: `--no-cookies` turns `opts.cookies` into `false`. Default is `true`.
@@ -523,7 +661,8 @@ async function runBrowse(
523
661
  (opts.checkAlign?.length ?? 0) > 0 ||
524
662
  (opts.checkGap?.length ?? 0) > 0 ||
525
663
  (opts.checkClip?.length ?? 0) > 0 ||
526
- (opts.checkOverlap?.length ?? 0) > 0;
664
+ (opts.checkOverlap?.length ?? 0) > 0 ||
665
+ (opts.checkCrowd?.length ?? 0) > 0;
527
666
  let layoutLint: LayoutLintResult | undefined;
528
667
  if (hasLayoutLint) {
529
668
  layoutLint = await browser.checkLayoutLint({
@@ -546,12 +685,69 @@ async function runBrowse(
546
685
  selector,
547
686
  tolerancePx: overlapThreshold,
548
687
  })),
688
+ crowd: (opts.checkCrowd ?? []).map((selector) => ({
689
+ selector,
690
+ minGapPx: crowdMin,
691
+ })),
549
692
  });
550
693
  }
551
694
  let hit: TargetSizeResult[] | undefined;
552
695
  if (opts.checkHit && opts.checkHit.length > 0) {
553
696
  hit = await browser.checkTargetSize(opts.checkHit, hitProfile);
554
697
  }
698
+ const hasContentChecks =
699
+ opts.checkPlaceholder !== undefined ||
700
+ opts.checkImages !== undefined ||
701
+ opts.checkTruncation !== undefined ||
702
+ opts.checkContrast !== undefined;
703
+ let content: ContentChecksResult | undefined;
704
+ if (hasContentChecks) {
705
+ content = await browser.checkContent({
706
+ placeholder:
707
+ opts.checkPlaceholder !== undefined
708
+ ? { scope: contentScope(opts.checkPlaceholder) }
709
+ : null,
710
+ image:
711
+ opts.checkImages !== undefined
712
+ ? {
713
+ scope: contentScope(opts.checkImages),
714
+ tolerance: parseNonNegativeNumber(
715
+ opts.checkImagesTolerance ?? "0.1",
716
+ "--check-images-tolerance",
717
+ ),
718
+ }
719
+ : null,
720
+ truncation:
721
+ opts.checkTruncation !== undefined
722
+ ? {
723
+ scope: contentScope(opts.checkTruncation),
724
+ tolerance: parseNonNegativeNumber(
725
+ opts.checkTruncationTolerance ?? "2",
726
+ "--check-truncation-tolerance",
727
+ ),
728
+ }
729
+ : null,
730
+ contrast:
731
+ opts.checkContrast !== undefined ? { scope: contentScope(opts.checkContrast) } : null,
732
+ });
733
+ }
734
+ // Vision critique. Capture tiles BEFORE any annotation overlays are injected
735
+ // so the model sees the real page, not our boxes.
736
+ let critique: CritiqueResult | undefined;
737
+ if (opts.checkCritique !== undefined) {
738
+ const tiles = await captureCritiqueTiles(browser, opts);
739
+ critique = await runCritique({
740
+ url: navResult.url,
741
+ rubric: opts.checkCritiqueRubric ?? DEFAULT_CRITIQUE_RUBRIC,
742
+ tiles,
743
+ provider: context?.critiqueProvider,
744
+ });
745
+ }
746
+ let asserts: AssertResult[] | undefined;
747
+ if (opts.assert && opts.assert.length > 0) {
748
+ const specs = opts.assert.map(parseAssertSpec);
749
+ asserts = await browser.checkAsserts(specs);
750
+ }
555
751
  const widthThreshold = Number.parseFloat(opts.checkWidthThreshold ?? "0.9");
556
752
  const annotateWidth = widths && opts.checkWidthAnnotate !== false;
557
753
  const annotateOverflow = overflow && opts.checkOverflowAnnotate !== false;
@@ -572,6 +768,7 @@ async function runBrowse(
572
768
  gap: opts.checkGapAnnotate === false ? [] : layoutLint.gap,
573
769
  clip: opts.checkClipAnnotate === false ? [] : layoutLint.clip,
574
770
  overlap: opts.checkOverlapAnnotate === false ? [] : layoutLint.overlap,
771
+ crowd: opts.checkCrowdAnnotate === false ? [] : layoutLint.crowd,
575
772
  }
576
773
  : undefined;
577
774
  if (
@@ -579,7 +776,8 @@ async function runBrowse(
579
776
  (annotateLayoutLint.align.length > 0 ||
580
777
  annotateLayoutLint.gap.length > 0 ||
581
778
  annotateLayoutLint.clip.length > 0 ||
582
- annotateLayoutLint.overlap.length > 0)
779
+ annotateLayoutLint.overlap.length > 0 ||
780
+ annotateLayoutLint.crowd.length > 0)
583
781
  ) {
584
782
  await browser.annotateLayoutLint(annotateLayoutLint);
585
783
  }
@@ -587,6 +785,10 @@ async function runBrowse(
587
785
  if (annotateHit) {
588
786
  await browser.annotateTargetSize(hit!);
589
787
  }
788
+ const contentBoxes = content ? collectContentBoxes(content, opts) : [];
789
+ if (contentBoxes.length > 0) {
790
+ await browser.annotateContent(contentBoxes);
791
+ }
590
792
 
591
793
  if (opts.login) {
592
794
  await new Promise<void>((res) => {
@@ -627,6 +829,9 @@ async function runBrowse(
627
829
  runts,
628
830
  layoutLint,
629
831
  hit,
832
+ content,
833
+ critique,
834
+ asserts,
630
835
  devOverlay,
631
836
  batchResult,
632
837
  );
@@ -642,6 +847,9 @@ async function runBrowse(
642
847
  runts,
643
848
  layoutLint,
644
849
  hit,
850
+ content,
851
+ critique,
852
+ asserts,
645
853
  devOverlay,
646
854
  batchResult,
647
855
  );
@@ -662,6 +870,9 @@ async function runBrowse(
662
870
  if (annotateHit) {
663
871
  await browser.clearTargetSizeAnnotations();
664
872
  }
873
+ if (contentBoxes.length > 0) {
874
+ await browser.clearContentAnnotations();
875
+ }
665
876
 
666
877
  if (opts.checkVisibleFail && visibility) {
667
878
  const threshold = Number.parseFloat(opts.checkVisibleThreshold ?? "0.9");
@@ -723,6 +934,22 @@ async function runBrowse(
723
934
  }
724
935
 
725
936
  applyLayoutLintFailGates(opts, layoutLint, hit);
937
+ applyContentFailGates(opts, content);
938
+ if (opts.checkCritiqueFail && critique && critique.outcome === "fail") {
939
+ const high = critique.findings.filter((f) => f.severity === "high").length;
940
+ emit.log(`check-critique FAIL: ${high} high-severity finding(s)`, "warn");
941
+ process.exitCode = 2;
942
+ }
943
+ if (opts.assertFail && asserts) {
944
+ const failed = asserts.filter((a) => a.outcome === "fail");
945
+ for (const a of failed) {
946
+ emit.log(
947
+ `assert FAIL: ${a.op} ${a.selector} → "${a.actual}"${a.error ? ` (${a.error})` : ""}`,
948
+ "warn",
949
+ );
950
+ }
951
+ if (failed.length > 0) process.exitCode = 2;
952
+ }
726
953
  } finally {
727
954
  await browser.close();
728
955
  }
@@ -847,6 +1074,9 @@ async function runPrintMode(
847
1074
  runts: RuntsResult | undefined,
848
1075
  layoutLint: LayoutLintResult | undefined,
849
1076
  hit: TargetSizeResult[] | undefined,
1077
+ content: ContentChecksResult | undefined,
1078
+ critique: CritiqueResult | undefined,
1079
+ asserts: AssertResult[] | undefined,
850
1080
  devOverlay: DevOverlayResult | undefined,
851
1081
  batchResult: BatchResult | undefined,
852
1082
  ): Promise<void> {
@@ -876,8 +1106,12 @@ async function runPrintMode(
876
1106
  result.gap = layoutLint.gap;
877
1107
  result.clip = layoutLint.clip;
878
1108
  result.overlap = layoutLint.overlap;
1109
+ result.crowd = layoutLint.crowd;
879
1110
  }
880
1111
  if (hit) result.hit = hit;
1112
+ if (content) assignContent(result, content);
1113
+ if (critique) result.critique = critique;
1114
+ if (asserts) result.asserts = asserts;
881
1115
  if (devOverlay) result.devOverlay = devOverlay;
882
1116
  if (batchResult && batchResult.clipboardReads.length > 0) {
883
1117
  result.batchClipboardReads = batchResult.clipboardReads;
@@ -910,6 +1144,9 @@ async function runTrioMode(
910
1144
  runts: RuntsResult | undefined,
911
1145
  layoutLint: LayoutLintResult | undefined,
912
1146
  hit: TargetSizeResult[] | undefined,
1147
+ content: ContentChecksResult | undefined,
1148
+ critique: CritiqueResult | undefined,
1149
+ asserts: AssertResult[] | undefined,
913
1150
  devOverlay: DevOverlayResult | undefined,
914
1151
  batchResult: BatchResult | undefined,
915
1152
  ): Promise<void> {
@@ -979,8 +1216,12 @@ async function runTrioMode(
979
1216
  envelope.gap = layoutLint.gap;
980
1217
  envelope.clip = layoutLint.clip;
981
1218
  envelope.overlap = layoutLint.overlap;
1219
+ envelope.crowd = layoutLint.crowd;
982
1220
  }
983
1221
  if (hit) envelope.hit = hit;
1222
+ if (content) assignContent(envelope, content);
1223
+ if (critique) envelope.critique = critique;
1224
+ if (asserts) envelope.asserts = asserts;
984
1225
  if (devOverlay) envelope.devOverlay = devOverlay;
985
1226
  if (batchResult && batchResult.clipboardReads.length > 0) {
986
1227
  envelope.batchClipboardReads = batchResult.clipboardReads;
@@ -1098,6 +1339,23 @@ async function runTrioMode(
1098
1339
  });
1099
1340
  emit.log(`check-hit:\n${lines.join("\n")}`, "info");
1100
1341
  }
1342
+ if (content) {
1343
+ logContentSummary(content);
1344
+ }
1345
+ if (critique) {
1346
+ logCritiqueSummary(critique);
1347
+ }
1348
+ if (asserts && asserts.length > 0) {
1349
+ const lines = asserts.map((a) => {
1350
+ const detail =
1351
+ a.error ??
1352
+ (a.op === "count" || a.op === "exists" || a.op === "absent"
1353
+ ? `${a.selector} → ${a.actual}${a.expected ? ` (want ${a.expected})` : ""}`
1354
+ : `${a.selector} → "${a.actual.slice(0, 60)}"${a.expected ? ` (want ${a.op} "${a.expected}")` : ""}`);
1355
+ return ` [${a.outcome.toUpperCase()}] ${a.op} ${detail}`;
1356
+ });
1357
+ emit.log(`asserts:\n${lines.join("\n")}`, "info");
1358
+ }
1101
1359
 
1102
1360
  if (savedBaseline) {
1103
1361
  emit.log(
@@ -1185,6 +1443,15 @@ function logLayoutLintSummary(result: LayoutLintResult): void {
1185
1443
  ` [${check.outcome.toUpperCase()}] overlap ${check.selector}: ${check.issues.length} collisions, worst ${worst.toFixed(0)}px²`,
1186
1444
  );
1187
1445
  }
1446
+ for (const check of result.crowd) {
1447
+ const tightest =
1448
+ check.issues.length > 0
1449
+ ? Math.min(...check.issues.map((issue) => issue.separationPx))
1450
+ : check.minGapPx;
1451
+ lines.push(
1452
+ ` [${check.outcome.toUpperCase()}] crowd ${check.selector}: ${check.issues.length} tight pair${check.issues.length === 1 ? "" : "s"} (min gap ${check.minGapPx}px, tightest ${tightest.toFixed(1)}px)`,
1453
+ );
1454
+ }
1188
1455
  if (lines.length > 0) emit.log(`layout-lint:\n${lines.join("\n")}`, "info");
1189
1456
  }
1190
1457
 
@@ -1213,6 +1480,7 @@ function applyLayoutLintFailGates(
1213
1480
  gate(opts.checkGapFail, "gap", layoutLint?.gap);
1214
1481
  gate(opts.checkClipFail, "clip", layoutLint?.clip);
1215
1482
  gate(opts.checkOverlapFail, "overlap", layoutLint?.overlap);
1483
+ gate(opts.checkCrowdFail, "crowd", layoutLint?.crowd);
1216
1484
 
1217
1485
  if (opts.checkHitFail && hit) {
1218
1486
  const failed = hit.filter(
@@ -1228,6 +1496,174 @@ function applyLayoutLintFailGates(
1228
1496
  }
1229
1497
  }
1230
1498
 
1499
+ // ---------------------------------------------------------------------------
1500
+ // Content checks (placeholder / image / truncation / contrast)
1501
+ // ---------------------------------------------------------------------------
1502
+
1503
+ const CONTENT_COLORS = {
1504
+ placeholder: "#dc2626",
1505
+ image: "#f97316",
1506
+ truncation: "#a855f7",
1507
+ contrast: "#0891b2",
1508
+ } as const;
1509
+
1510
+ // Optional-selector flags resolve to a scope: a string selector, or null for
1511
+ // the whole body when the flag was passed bare.
1512
+ function contentScope(value: boolean | string): string | null {
1513
+ return typeof value === "string" ? value : null;
1514
+ }
1515
+
1516
+ function assignContent(target: Record<string, unknown>, content: ContentChecksResult): void {
1517
+ if (content.placeholder) target.placeholder = content.placeholder;
1518
+ if (content.image) target.image = content.image;
1519
+ if (content.truncation) target.truncation = content.truncation;
1520
+ if (content.contrast) target.contrast = content.contrast;
1521
+ }
1522
+
1523
+ function collectContentBoxes(
1524
+ content: ContentChecksResult,
1525
+ opts: BrowseOpts,
1526
+ ): ContentAnnotationBox[] {
1527
+ const boxes: ContentAnnotationBox[] = [];
1528
+ if (content.placeholder && opts.checkPlaceholderAnnotate !== false) {
1529
+ for (const hit of content.placeholder.hits) {
1530
+ boxes.push({
1531
+ rect: hit.rect,
1532
+ label: `placeholder ${hit.token}`,
1533
+ color: CONTENT_COLORS.placeholder,
1534
+ });
1535
+ }
1536
+ }
1537
+ if (content.image && opts.checkImagesAnnotate !== false) {
1538
+ for (const issue of content.image.issues) {
1539
+ boxes.push({ rect: issue.rect, label: `image ${issue.reason}`, color: CONTENT_COLORS.image });
1540
+ }
1541
+ }
1542
+ if (content.truncation && opts.checkTruncationAnnotate !== false) {
1543
+ for (const hit of content.truncation.hits) {
1544
+ boxes.push({
1545
+ rect: hit.rect,
1546
+ label: `truncated ${hit.how}`,
1547
+ color: CONTENT_COLORS.truncation,
1548
+ });
1549
+ }
1550
+ }
1551
+ if (content.contrast && opts.checkContrastAnnotate !== false) {
1552
+ for (const hit of content.contrast.hits) {
1553
+ if (hit.outcome !== "fail") continue;
1554
+ boxes.push({
1555
+ rect: hit.rect,
1556
+ label: `contrast ${hit.ratio}:1 < ${hit.required}`,
1557
+ color: CONTENT_COLORS.contrast,
1558
+ });
1559
+ }
1560
+ }
1561
+ return boxes;
1562
+ }
1563
+
1564
+ function logContentSummary(content: ContentChecksResult): void {
1565
+ const lines: string[] = [];
1566
+ if (content.placeholder) {
1567
+ const c = content.placeholder;
1568
+ lines.push(
1569
+ ` [${c.outcome.toUpperCase()}] placeholder: ${c.hits.length} tell(s) in ${c.scanned} node(s)`,
1570
+ );
1571
+ }
1572
+ if (content.image) {
1573
+ const c = content.image;
1574
+ const missing = c.issues.filter((i) => i.reason === "missing").length;
1575
+ const stretched = c.issues.filter((i) => i.reason === "stretched").length;
1576
+ lines.push(
1577
+ ` [${c.outcome.toUpperCase()}] image: ${missing} missing, ${stretched} stretched of ${c.scanned}`,
1578
+ );
1579
+ }
1580
+ if (content.truncation) {
1581
+ const c = content.truncation;
1582
+ lines.push(
1583
+ ` [${c.outcome.toUpperCase()}] truncation: ${c.hits.length} clipped of ${c.scanned}`,
1584
+ );
1585
+ }
1586
+ if (content.contrast) {
1587
+ const c = content.contrast;
1588
+ const fails = c.hits.filter((h) => h.outcome === "fail").length;
1589
+ const unknown = c.hits.filter((h) => h.outcome === "unknown").length;
1590
+ lines.push(
1591
+ ` [${c.outcome.toUpperCase()}] contrast: ${fails} fail, ${unknown} unknown of ${c.scanned}`,
1592
+ );
1593
+ }
1594
+ if (lines.length > 0) emit.log(`content-checks:\n${lines.join("\n")}`, "info");
1595
+ }
1596
+
1597
+ function applyContentFailGates(opts: BrowseOpts, content: ContentChecksResult | undefined): void {
1598
+ if (!content) return;
1599
+ const gate = (
1600
+ enabled: boolean | undefined,
1601
+ rule: string,
1602
+ result: { found: boolean; outcome: string } | undefined,
1603
+ ): void => {
1604
+ if (!enabled || !result) return;
1605
+ if (!result.found || result.outcome === "fail") {
1606
+ emit.log(`check-${rule} FAIL: ${result.found ? "issues found" : "scope not found"}`, "warn");
1607
+ process.exitCode = 2;
1608
+ }
1609
+ };
1610
+ gate(opts.checkPlaceholderFail, "placeholder", content.placeholder);
1611
+ gate(opts.checkImagesFail, "images", content.image);
1612
+ gate(opts.checkTruncationFail, "truncation", content.truncation);
1613
+ gate(opts.checkContrastFail, "contrast", content.contrast);
1614
+ }
1615
+
1616
+ // ---------------------------------------------------------------------------
1617
+ // Vision critique tiling
1618
+ // ---------------------------------------------------------------------------
1619
+
1620
+ async function captureCritiqueTiles(browser: Browser, opts: BrowseOpts): Promise<CritiqueTile[]> {
1621
+ const maxTiles = Math.max(1, Number.parseInt(opts.checkCritiqueMaxTiles ?? "24", 10));
1622
+ const band = Math.max(200, Number.parseInt(opts.checkCritiqueBand ?? "1400", 10));
1623
+ const overlap = Math.max(0, Number.parseInt(opts.checkCritiqueOverlap ?? "120", 10));
1624
+
1625
+ // Capture the whole page ONCE, then crop tiles from the pixels. Playwright's
1626
+ // clip is viewport-relative, so a below-fold band clip throws ("clipped area
1627
+ // outside the resulting image") — cropping the full-page image sidesteps that
1628
+ // and keeps every tile at full resolution.
1629
+ const buffer = await browser.fullPageScreenshotBuffer();
1630
+ const elementRects =
1631
+ typeof opts.checkCritique === "string"
1632
+ ? await browser.elementTiles(opts.checkCritique)
1633
+ : undefined;
1634
+ const tiles = tilesFromFullPage(buffer, { elementRects, bandHeight: band, overlap, maxTiles });
1635
+
1636
+ if (elementRects && elementRects.length > maxTiles) {
1637
+ emit.log(
1638
+ `check-critique: ${elementRects.length} elements matched, capped to ${maxTiles} tiles`,
1639
+ "warn",
1640
+ );
1641
+ } else if (!elementRects && tiles.length >= maxTiles) {
1642
+ emit.log(
1643
+ `check-critique: page banded to the ${maxTiles}-tile cap (raise --check-critique-max-tiles for full coverage)`,
1644
+ "warn",
1645
+ );
1646
+ }
1647
+ return tiles;
1648
+ }
1649
+ function logCritiqueSummary(critique: CritiqueResult): void {
1650
+ if (critique.outcome === "skipped") {
1651
+ emit.log(`check-critique: SKIPPED (${critique.error ?? "no provider"})`, "warn");
1652
+ return;
1653
+ }
1654
+ const high = critique.findings.filter((f) => f.severity === "high").length;
1655
+ const med = critique.findings.filter((f) => f.severity === "medium").length;
1656
+ const low = critique.findings.filter((f) => f.severity === "low").length;
1657
+ const lines = critique.findings
1658
+ .slice(0, 20)
1659
+ .map((f) => ` [${f.severity}] tile ${f.tile} ${f.category}: ${f.description}`);
1660
+ emit.log(
1661
+ `check-critique [${critique.outcome.toUpperCase()}] ${critique.tiles} tiles → ${high} high / ${med} med / ${low} low` +
1662
+ (lines.length ? `\n${lines.join("\n")}` : ""),
1663
+ critique.outcome === "fail" ? "warn" : "info",
1664
+ );
1665
+ }
1666
+
1231
1667
  function summarizeDiagnostics(diag: Diagnostics): Record<string, unknown> {
1232
1668
  return {
1233
1669
  viewport: diag.viewport,