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
@@ -0,0 +1,231 @@
1
+ // Vision-model page critic for `harn browse --check-critique`.
2
+ //
3
+ // Heuristic checks only catch what we can enumerate. The reason a human still
4
+ // eyeballs a page is the long tail — the odd thing that "looks off" without
5
+ // tripping a named rule. This hands a rendered page to a vision model and asks
6
+ // for that judgement, structured.
7
+ //
8
+ // Two things keep it honest:
9
+ // 1. Tiling. A tall page screenshotted whole and downscaled to a model's
10
+ // input budget loses the detail the critique depends on. So the page is
11
+ // cut into overlapping vertical bands (or one tile per selector match),
12
+ // each captured at full resolution and judged on its own, with findings
13
+ // tagged by tile + document scroll offset for locality.
14
+ // 2. Injection. harnery ships no model client and no API key. The host wires
15
+ // a `critiqueProvider` into the program context (same pattern as
16
+ // `extraHeaders`); without one, the check reports `skipped`, never a
17
+ // false pass. That keeps this portable — the tiling, prompt, and
18
+ // orchestration live here; the model call lives in the host.
19
+
20
+ import { PNG } from "pngjs";
21
+
22
+ export interface CritiqueTile {
23
+ index: number;
24
+ /** Section label or "band N", for locating a finding. */
25
+ label: string;
26
+ /** Document-space Y offset of the tile's top, so findings map to a place. */
27
+ scrollY: number;
28
+ width: number;
29
+ height: number;
30
+ /** Base64 PNG of the tile (no data: prefix). */
31
+ pngBase64: string;
32
+ }
33
+
34
+ export interface CritiqueFinding {
35
+ tile: number;
36
+ severity: "high" | "medium" | "low";
37
+ category: string;
38
+ description: string;
39
+ }
40
+
41
+ export interface CritiqueResult {
42
+ rule: "critique";
43
+ /** Number of tiles the page was cut into. */
44
+ tiles: number;
45
+ /** Whether a provider was available. False → outcome "skipped". */
46
+ provider: boolean;
47
+ findings: CritiqueFinding[];
48
+ outcome: "pass" | "fail" | "skipped";
49
+ /** Set when skipped or a provider call failed. */
50
+ error?: string;
51
+ }
52
+
53
+ /**
54
+ * The host-injected model call. Given one tile and the rubric, return the
55
+ * findings for that tile. Throwing is caught and surfaced as an error finding
56
+ * so one bad tile never sinks the run.
57
+ */
58
+ export type CritiqueProvider = (input: {
59
+ url: string;
60
+ rubric: string;
61
+ tile: CritiqueTile;
62
+ }) => Promise<CritiqueFinding[]>;
63
+
64
+ export const DEFAULT_CRITIQUE_RUBRIC = [
65
+ "You are reviewing one vertical slice of a rendered web page for visual defects.",
66
+ "Report ONLY concrete, visible problems a careful designer would flag:",
67
+ "- text cut off, overlapping, or colliding with other elements",
68
+ "- text that is hard to read against its background (low contrast)",
69
+ "- misaligned or unevenly spaced elements; two blocks touching with no gap",
70
+ "- broken, distorted, or missing images",
71
+ "- leaked template tokens, placeholder text, or obviously wrong values",
72
+ "- anything that looks broken, unfinished, or accidental",
73
+ "Do NOT comment on content quality, wording, tone, or subjective taste.",
74
+ "If the slice looks fine, return an empty list. Be precise and terse.",
75
+ "Return JSON only: an array of objects",
76
+ '{"severity":"high|medium|low","category":"<short-slug>","description":"<one sentence>"}.',
77
+ ].join("\n");
78
+
79
+ /**
80
+ * Cut a page of height `pageHeight` into overlapping vertical bands. Pure math
81
+ * so the caller (which owns the page) can screenshot each `clip` rect. The
82
+ * overlap keeps a finding that straddles a seam visible in at least one tile.
83
+ */
84
+ export function bandRects(
85
+ pageHeight: number,
86
+ pageWidth: number,
87
+ bandHeight: number,
88
+ overlap: number,
89
+ ): Array<{ index: number; x: number; y: number; width: number; height: number }> {
90
+ const bands: Array<{ index: number; x: number; y: number; width: number; height: number }> = [];
91
+ if (pageHeight <= 0 || pageWidth <= 0) return bands;
92
+ const step = Math.max(1, bandHeight - overlap);
93
+ let index = 0;
94
+ for (let y = 0; y < pageHeight; y += step) {
95
+ const height = Math.min(bandHeight, pageHeight - y);
96
+ bands.push({ index, x: 0, y, width: pageWidth, height });
97
+ index++;
98
+ if (y + height >= pageHeight) break;
99
+ }
100
+ return bands;
101
+ }
102
+
103
+ /**
104
+ * Cut tiles out of ONE full-page screenshot with pngjs. Playwright's own
105
+ * `clip` is viewport-relative, so a band below the fold errors with "clipped
106
+ * area outside the resulting image" — capturing the whole page once and
107
+ * cropping in pixel space sidesteps that and keeps every tile at full
108
+ * resolution. Pass `elementRects` for semantic (per-element) tiling; otherwise
109
+ * the page is banded from the actual image height. Rects are clamped to the
110
+ * image so an off-by-one never throws.
111
+ */
112
+ export function tilesFromFullPage(
113
+ fullPageBuffer: Buffer,
114
+ opts: {
115
+ elementRects?: Array<{ label: string; x: number; y: number; width: number; height: number }>;
116
+ bandHeight?: number;
117
+ overlap?: number;
118
+ maxTiles: number;
119
+ },
120
+ ): CritiqueTile[] {
121
+ const png = PNG.sync.read(fullPageBuffer);
122
+ const rects =
123
+ opts.elementRects && opts.elementRects.length > 0
124
+ ? opts.elementRects.map((r, index) => ({ index, ...r }))
125
+ : bandRects(png.height, png.width, opts.bandHeight ?? 1400, opts.overlap ?? 120).map((b) => ({
126
+ index: b.index,
127
+ label: `band ${b.index + 1}`,
128
+ x: b.x,
129
+ y: b.y,
130
+ width: b.width,
131
+ height: b.height,
132
+ }));
133
+ const tiles: CritiqueTile[] = [];
134
+ for (const rect of rects.slice(0, opts.maxTiles)) {
135
+ const sx = Math.max(0, Math.min(Math.round(rect.x), png.width - 1));
136
+ const sy = Math.max(0, Math.min(Math.round(rect.y), png.height - 1));
137
+ const w = Math.max(1, Math.min(Math.round(rect.width), png.width - sx));
138
+ const h = Math.max(1, Math.min(Math.round(rect.height), png.height - sy));
139
+ const dst = new PNG({ width: w, height: h });
140
+ // Manual RGBA row copy — pngjs's bitblt isn't available under every runtime
141
+ // (missing under Bun's build), so copy the crop window row by row instead.
142
+ for (let row = 0; row < h; row++) {
143
+ const srcStart = ((sy + row) * png.width + sx) * 4;
144
+ const dstStart = row * w * 4;
145
+ png.data.copy(dst.data, dstStart, srcStart, srcStart + w * 4);
146
+ }
147
+ tiles.push({
148
+ index: rect.index,
149
+ label: rect.label,
150
+ scrollY: sy,
151
+ width: w,
152
+ height: h,
153
+ pngBase64: PNG.sync.write(dst).toString("base64"),
154
+ });
155
+ }
156
+ return tiles;
157
+ }
158
+
159
+ /**
160
+ * Validate + normalize a provider's raw output into CritiqueFindings. Tolerant
161
+ * of a model returning a bare array or wrapping it in `{ findings: [...] }`,
162
+ * and drops entries that are not shaped like a finding.
163
+ */
164
+ export function normalizeFindings(raw: unknown, tile: number): CritiqueFinding[] {
165
+ const arr = Array.isArray(raw)
166
+ ? raw
167
+ : raw && typeof raw === "object" && Array.isArray((raw as { findings?: unknown }).findings)
168
+ ? (raw as { findings: unknown[] }).findings
169
+ : [];
170
+ const severities = new Set(["high", "medium", "low"]);
171
+ const out: CritiqueFinding[] = [];
172
+ for (const item of arr) {
173
+ if (!item || typeof item !== "object") continue;
174
+ const record = item as Record<string, unknown>;
175
+ const severity = String(record.severity ?? "low").toLowerCase();
176
+ const description = typeof record.description === "string" ? record.description.trim() : "";
177
+ if (!description) continue;
178
+ out.push({
179
+ tile,
180
+ severity: severities.has(severity) ? (severity as CritiqueFinding["severity"]) : "low",
181
+ category:
182
+ typeof record.category === "string" && record.category.trim()
183
+ ? record.category.trim()
184
+ : "visual",
185
+ description,
186
+ });
187
+ }
188
+ return out;
189
+ }
190
+
191
+ /**
192
+ * Run the critique over pre-captured tiles with an injected provider. Findings
193
+ * are collected across tiles; a provider throw becomes a `high` error finding
194
+ * for that tile rather than aborting. Outcome is `fail` when any `high` finding
195
+ * survives (the gate escalation lives in the caller).
196
+ */
197
+ export async function runCritique(args: {
198
+ url: string;
199
+ rubric: string;
200
+ tiles: CritiqueTile[];
201
+ provider: CritiqueProvider | undefined;
202
+ }): Promise<CritiqueResult> {
203
+ const { url, rubric, tiles, provider } = args;
204
+ if (!provider) {
205
+ return {
206
+ rule: "critique",
207
+ tiles: tiles.length,
208
+ provider: false,
209
+ findings: [],
210
+ outcome: "skipped",
211
+ error:
212
+ "no critiqueProvider injected by the host (see HarneryProgramContext.critiqueProvider)",
213
+ };
214
+ }
215
+ const findings: CritiqueFinding[] = [];
216
+ for (const tile of tiles) {
217
+ try {
218
+ const tileFindings = await provider({ url, rubric, tile });
219
+ findings.push(...tileFindings.map((f) => ({ ...f, tile: tile.index })));
220
+ } catch (err: unknown) {
221
+ findings.push({
222
+ tile: tile.index,
223
+ severity: "high",
224
+ category: "provider-error",
225
+ description: `critique provider failed on ${tile.label}: ${err instanceof Error ? err.message : String(err)}`,
226
+ });
227
+ }
228
+ }
229
+ const outcome = findings.some((f) => f.severity === "high") ? "fail" : "pass";
230
+ return { rule: "critique", tiles: tiles.length, provider: true, findings, outcome };
231
+ }
@@ -115,6 +115,35 @@ export interface OverlapResult {
115
115
  truncated: boolean;
116
116
  }
117
117
 
118
+ export interface CrowdPair {
119
+ before: LayoutElementMeasurement;
120
+ after: LayoutElementMeasurement;
121
+ /** Edge separation along `axis` in CSS px; negative means the panels overlap. */
122
+ separationPx: number;
123
+ /** "y" = stacked, "x" = side by side, "overlap" = they intersect in 2D. */
124
+ axis: "x" | "y" | "overlap";
125
+ minGapPx: number;
126
+ /** DOM label of the shared parent, so the fix location is obvious. */
127
+ parentLabel: string;
128
+ /**
129
+ * Whether each consecutive sibling peer was a leaf panel or a wrapper that
130
+ * contains panels. `before`/`after` always measure the nearest face panels
131
+ * (a descendant when the peer is composite).
132
+ */
133
+ beforeKind: "panel" | "composite";
134
+ afterKind: "panel" | "composite";
135
+ }
136
+
137
+ export interface CrowdResult {
138
+ rule: "crowd";
139
+ selector: string;
140
+ found: boolean;
141
+ outcome: LayoutOutcome;
142
+ minGapPx: number;
143
+ issues: CrowdPair[];
144
+ truncated: boolean;
145
+ }
146
+
118
147
  export interface LayoutLintRequest {
119
148
  align: Array<{ selector: string; axis: LayoutAxis; tolerancePx: number }>;
120
149
  gap: Array<{
@@ -125,6 +154,7 @@ export interface LayoutLintRequest {
125
154
  }>;
126
155
  clip: Array<{ selector: string; tolerancePx: number }>;
127
156
  overlap: Array<{ selector: string; tolerancePx: number }>;
157
+ crowd: Array<{ selector: string; minGapPx: number }>;
128
158
  }
129
159
 
130
160
  export interface LayoutLintResult {
@@ -132,6 +162,7 @@ export interface LayoutLintResult {
132
162
  gap: GapResult[];
133
163
  clip: ClipResult[];
134
164
  overlap: OverlapResult[];
165
+ crowd: CrowdResult[];
135
166
  }
136
167
 
137
168
  export function median(values: number[]): number {
@@ -631,7 +662,241 @@ export function buildLayoutLintCheck(): (request: LayoutLintRequest) => LayoutLi
631
662
  };
632
663
  });
633
664
 
634
- return { align, gap, clip, overlap };
665
+ // A "panel" here means a card-like callout — a block that reads as its own
666
+ // standalone box: a full (4-side) border, a modest corner radius, or a
667
+ // box-shadow. Two flush cards read as one merged block, which `overlap`
668
+ // (needs a 2D intersection) and `gap` (flags uneven spacing, so a
669
+ // uniformly-flush stack passes) both miss.
670
+ //
671
+ // Crowd peers are also *composites*: in-flow siblings that are not panels
672
+ // themselves but contain at least one panel descendant (a card grid, a
673
+ // flow of cards, etc.). Comparing only leaf panels missed the common case
674
+ // where a wrapper-of-cards sits flush against the next card. Separation is
675
+ // measured between the nearest face panels inside each peer, not the
676
+ // wrapper boxes, so a tall section with a card near the top and prose
677
+ // below does not false-fail against the next sibling.
678
+ //
679
+ // Deliberately NOT flagged: structural elements that are flush BY DESIGN —
680
+ // table cells, list/definition rows, and divided segments (a bg-only cell
681
+ // in a `gap:1px` strip). Those have no card boundary of their own, so the
682
+ // test below excludes them without a special-case allowlist. Pills, chips,
683
+ // and badges (radius ≥ half their smaller side) are inline controls, not
684
+ // layout cards, and are excluded too. Plain blocks with no panel chrome
685
+ // and no panel descendants stay out of the peer list.
686
+ const PARENT_LIMIT = 2000;
687
+ const PANEL_DESCENDANT_LIMIT = 100;
688
+ const OVERLAP_EPS = 1;
689
+ const STRUCTURAL_TAGS = new Set([
690
+ "TD",
691
+ "TH",
692
+ "TR",
693
+ "THEAD",
694
+ "TBODY",
695
+ "TFOOT",
696
+ "COL",
697
+ "COLGROUP",
698
+ ]);
699
+
700
+ const maxCornerRadiusPx = (style: CSSStyleDeclaration, minDim: number): number => {
701
+ const corners = [
702
+ "border-top-left-radius",
703
+ "border-top-right-radius",
704
+ "border-bottom-right-radius",
705
+ "border-bottom-left-radius",
706
+ ];
707
+ let max = 0;
708
+ for (const corner of corners) {
709
+ const token = (style.getPropertyValue(corner) || "").trim().split(/\s+/)[0] ?? "";
710
+ const value = token.endsWith("%")
711
+ ? (Number.parseFloat(token) / 100) * minDim
712
+ : Number.parseFloat(token) || 0;
713
+ if (value > max) max = value;
714
+ }
715
+ return max;
716
+ };
717
+
718
+ const isPanel = (element: Element): boolean => {
719
+ if (STRUCTURAL_TAGS.has(element.tagName)) return false;
720
+ const style = getComputedStyle(element);
721
+ if (style.display.startsWith("table") || style.display.startsWith("inline")) return false;
722
+ if (style.boxShadow && style.boxShadow !== "none") return true;
723
+ const sides = ["top", "right", "bottom", "left"];
724
+ const fullBorder = sides.every((side) => {
725
+ const width = Number.parseFloat(style.getPropertyValue(`border-${side}-width`)) || 0;
726
+ const lineStyle = style.getPropertyValue(`border-${side}-style`);
727
+ return width > 0 && lineStyle !== "none" && lineStyle !== "";
728
+ });
729
+ if (fullBorder) return true;
730
+ const rect = element.getBoundingClientRect();
731
+ const minDim = Math.min(rect.width, rect.height);
732
+ const radius = maxCornerRadiusPx(style, minDim);
733
+ // A card has a modest radius; a pill/circle (radius ≥ half its short side)
734
+ // is a control, not a card.
735
+ return radius >= 3 && radius < minDim / 2 - 1;
736
+ };
737
+
738
+ const isInFlowBox = (element: Element): boolean => {
739
+ if (isHidden(element)) return false;
740
+ const style = getComputedStyle(element);
741
+ if (style.position === "absolute" || style.position === "fixed") return false;
742
+ const rect = element.getBoundingClientRect();
743
+ return rect.width > 0 && rect.height > 0;
744
+ };
745
+
746
+ /** Leaf panel faces that represent a crowd peer (self, or descendants). */
747
+ const collectPanelFaces = (
748
+ element: Element,
749
+ selfIsPanel: boolean,
750
+ ): LayoutElementMeasurement[] => {
751
+ if (selfIsPanel) return [measure(element, 0, false)];
752
+ const faces: LayoutElementMeasurement[] = [];
753
+ const descendants = element.querySelectorAll("*");
754
+ for (let i = 0; i < descendants.length && faces.length < PANEL_DESCENDANT_LIMIT; i++) {
755
+ const child = descendants[i];
756
+ if (!child || !isInFlowBox(child) || !isPanel(child)) continue;
757
+ faces.push(measure(child, faces.length, false));
758
+ }
759
+ return faces;
760
+ };
761
+
762
+ // In-flow, visible, non-zero-area direct children that are crowd peers:
763
+ // leaf panels, or composites that wrap at least one panel. Non-peer
764
+ // siblings (plain prose, arrows, spacers) drop out of the list so two
765
+ // panels separated only by chrome still get a face-to-face check.
766
+ const collectCrowdPeers = (
767
+ parent: Element,
768
+ ): Array<{
769
+ kind: "panel" | "composite";
770
+ faces: LayoutElementMeasurement[];
771
+ }> => {
772
+ const out: Array<{
773
+ kind: "panel" | "composite";
774
+ faces: LayoutElementMeasurement[];
775
+ }> = [];
776
+ const children = [...parent.children];
777
+ for (let index = 0; index < children.length && index < CHILD_LIMIT; index++) {
778
+ const element = children[index];
779
+ if (!element || !isInFlowBox(element)) continue;
780
+ const selfIsPanel = isPanel(element);
781
+ const faces = collectPanelFaces(element, selfIsPanel);
782
+ if (faces.length === 0) continue;
783
+ out.push({
784
+ kind: selfIsPanel ? "panel" : "composite",
785
+ faces,
786
+ });
787
+ }
788
+ return out;
789
+ };
790
+
791
+ const separationOf = (
792
+ a: LayoutElementMeasurement,
793
+ b: LayoutElementMeasurement,
794
+ ): { axis: "x" | "y" | "overlap" | "none"; sep: number } => {
795
+ const ra = a.rect;
796
+ const rb = b.rect;
797
+ const yOverlap = Math.min(ra.bottom, rb.bottom) - Math.max(ra.top, rb.top);
798
+ const xOverlap = Math.min(ra.right, rb.right) - Math.max(ra.left, rb.left);
799
+ const sharesY = yOverlap > OVERLAP_EPS;
800
+ const sharesX = xOverlap > OVERLAP_EPS;
801
+ if (sharesX && sharesY) return { axis: "overlap", sep: -Math.min(xOverlap, yOverlap) };
802
+ if (sharesX) {
803
+ const [upper, lower] = ra.top <= rb.top ? [ra, rb] : [rb, ra];
804
+ return { axis: "y", sep: lower.top - upper.bottom };
805
+ }
806
+ if (sharesY) {
807
+ const [left, right] = ra.left <= rb.left ? [ra, rb] : [rb, ra];
808
+ return { axis: "x", sep: right.left - left.right };
809
+ }
810
+ // Diagonal (e.g. a grid row wrap): not edge-adjacent, so never crowded.
811
+ const vGap = Math.max(ra.top, rb.top) - Math.min(ra.bottom, rb.bottom);
812
+ const hGap = Math.max(ra.left, rb.left) - Math.min(ra.right, rb.right);
813
+ return { axis: "none", sep: Math.max(vGap, hGap) };
814
+ };
815
+
816
+ /** Tightest edge-adjacent face pair across two peers' panel faces. */
817
+ const nearestCrowdPair = (
818
+ beforeFaces: LayoutElementMeasurement[],
819
+ afterFaces: LayoutElementMeasurement[],
820
+ ): {
821
+ before: LayoutElementMeasurement;
822
+ after: LayoutElementMeasurement;
823
+ axis: "x" | "y" | "overlap";
824
+ sep: number;
825
+ } | null => {
826
+ let best: {
827
+ before: LayoutElementMeasurement;
828
+ after: LayoutElementMeasurement;
829
+ axis: "x" | "y" | "overlap";
830
+ sep: number;
831
+ } | null = null;
832
+ for (const before of beforeFaces) {
833
+ for (const after of afterFaces) {
834
+ const { axis, sep } = separationOf(before, after);
835
+ if (axis === "none") continue;
836
+ if (best === null || sep < best.sep) {
837
+ best = { before, after, axis, sep };
838
+ }
839
+ }
840
+ }
841
+ return best;
842
+ };
843
+
844
+ const crowd = request.crowd.map(({ selector, minGapPx }): CrowdResult => {
845
+ const container = document.querySelector(selector);
846
+ if (!(container instanceof Element)) {
847
+ return {
848
+ rule: "crowd" as const,
849
+ selector,
850
+ found: false,
851
+ outcome: "fail" as const,
852
+ minGapPx,
853
+ issues: [],
854
+ truncated: false,
855
+ };
856
+ }
857
+ const parents: Element[] = [container, ...container.querySelectorAll("*")];
858
+ const issues: CrowdPair[] = [];
859
+ let truncated = parents.length > PARENT_LIMIT;
860
+ const limit = Math.min(parents.length, PARENT_LIMIT);
861
+ for (let p = 0; p < limit; p++) {
862
+ const parent = parents[p]!;
863
+ const peers = collectCrowdPeers(parent);
864
+ for (let i = 1; i < peers.length; i++) {
865
+ const beforePeer = peers[i - 1]!;
866
+ const afterPeer = peers[i]!;
867
+ const best = nearestCrowdPair(beforePeer.faces, afterPeer.faces);
868
+ if (!best) continue;
869
+ if (best.sep < minGapPx) {
870
+ issues.push({
871
+ before: best.before,
872
+ after: best.after,
873
+ separationPx: best.sep,
874
+ axis: best.axis,
875
+ minGapPx,
876
+ parentLabel: labelOf(parent),
877
+ beforeKind: beforePeer.kind,
878
+ afterKind: afterPeer.kind,
879
+ });
880
+ if (issues.length >= ISSUE_LIMIT) {
881
+ truncated = true;
882
+ break;
883
+ }
884
+ }
885
+ }
886
+ if (issues.length >= ISSUE_LIMIT) break;
887
+ }
888
+ return {
889
+ rule: "crowd" as const,
890
+ selector,
891
+ found: true,
892
+ outcome: issues.length > 0 ? ("fail" as const) : ("pass" as const),
893
+ minGapPx,
894
+ issues,
895
+ truncated,
896
+ };
897
+ });
898
+
899
+ return { align, gap, clip, overlap, crowd };
635
900
  };
636
901
  }
637
902
 
@@ -685,6 +950,37 @@ export function buildLayoutLintAnnotateScript(): (result: LayoutLintResult) => v
685
950
  box(issue.intersection, "#e11d48", `overlap ${issue.areaPx.toFixed(0)}px²`);
686
951
  }
687
952
  }
953
+ for (const check of result.crowd) {
954
+ for (const issue of check.issues) {
955
+ // Draw the touching seam: a thin strip along the edge the two panels
956
+ // share, so "no space here" is visible even at zero separation.
957
+ const a = issue.before.rect;
958
+ const b = issue.after.rect;
959
+ const seam: LayoutRect =
960
+ issue.axis === "x"
961
+ ? {
962
+ x: Math.min(a.right, b.right),
963
+ y: Math.max(a.top, b.top),
964
+ width: Math.max(2, Math.abs(issue.separationPx)),
965
+ height: Math.max(1, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top)),
966
+ top: Math.max(a.top, b.top),
967
+ right: 0,
968
+ bottom: 0,
969
+ left: Math.min(a.right, b.right),
970
+ }
971
+ : {
972
+ x: Math.max(a.left, b.left),
973
+ y: Math.min(a.bottom, b.bottom),
974
+ width: Math.max(1, Math.min(a.right, b.right) - Math.max(a.left, b.left)),
975
+ height: Math.max(2, Math.abs(issue.separationPx)),
976
+ top: Math.min(a.bottom, b.bottom),
977
+ right: 0,
978
+ bottom: 0,
979
+ left: Math.max(a.left, b.left),
980
+ };
981
+ box(seam, "#14b8a6", `crowd ${issue.separationPx.toFixed(1)}px`);
982
+ }
983
+ }
688
984
  };
689
985
  }
690
986
 
@@ -1,3 +1,10 @@
1
+ export {
2
+ type AssertOp,
3
+ type AssertResult,
4
+ type AssertSpec,
5
+ buildAssertCheck,
6
+ parseAssertSpec,
7
+ } from "./asserts.js";
1
8
  export {
2
9
  Browser,
3
10
  type BrowserOptions,
@@ -7,6 +14,31 @@ export {
7
14
  type NavigateResult,
8
15
  type PageErrorEvent,
9
16
  } from "./client.js";
17
+ export type {
18
+ CheckRect,
19
+ ContentAnnotationBox,
20
+ ContentChecksRequest,
21
+ ContentChecksResult,
22
+ ContrastHit,
23
+ ContrastResult,
24
+ ImageHealthResult,
25
+ ImageHit,
26
+ PlaceholderHit,
27
+ PlaceholderResult,
28
+ TruncationHit,
29
+ TruncationResult,
30
+ } from "./content-checks.js";
31
+ export {
32
+ bandRects,
33
+ type CritiqueFinding,
34
+ type CritiqueProvider,
35
+ type CritiqueResult,
36
+ type CritiqueTile,
37
+ DEFAULT_CRITIQUE_RUBRIC,
38
+ normalizeFindings,
39
+ runCritique,
40
+ tilesFromFullPage,
41
+ } from "./critique.js";
10
42
  export {
11
43
  captureDevOverlay,
12
44
  type DevOverlayError,
@@ -18,6 +50,8 @@ export type {
18
50
  AlignResult,
19
51
  ClipIssue,
20
52
  ClipResult,
53
+ CrowdPair,
54
+ CrowdResult,
21
55
  GapCluster,
22
56
  GapPair,
23
57
  GapResult,
@@ -226,7 +226,10 @@ export interface MirrorOptions {
226
226
 
227
227
  export function entryMatchesSelector(entry: DesktopSessionEntry, selector: string): boolean {
228
228
  if (entry.cliSessionId === selector || entry.sessionId === selector) return true;
229
- return entry.title !== null && entry.title.toLowerCase().includes(selector.toLowerCase());
229
+ // Optional chain rather than a null guard: the reader normalises a missing
230
+ // title to null, but this is exported, and a hand-built entry with an absent
231
+ // title would slip past `!== null` and then throw on the method call.
232
+ return entry.title?.toLowerCase().includes(selector.toLowerCase()) ?? false;
230
233
  }
231
234
 
232
235
  /**