ultimate-pi 0.18.1 → 0.19.1

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 (325) hide show
  1. package/.agents/skills/harness-debate-plan/SKILL.md +1 -1
  2. package/.agents/skills/harness-decisions/SKILL.md +1 -2
  3. package/.agents/skills/harness-governor/SKILL.md +6 -5
  4. package/.agents/skills/web-retrieval/SKILL.md +163 -0
  5. package/.agents/skills/wiki-autoresearch/SKILL.md +6 -6
  6. package/.pi/PACKAGING.md +4 -4
  7. package/.pi/SYSTEM.md +75 -123
  8. package/.pi/agents/harness/incident-recorder.md +0 -1
  9. package/.pi/agents/harness/planning/decompose.md +0 -2
  10. package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
  11. package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
  12. package/.pi/agents/harness/planning/hypothesis.md +0 -2
  13. package/.pi/agents/harness/planning/implementation-researcher.md +1 -3
  14. package/.pi/agents/harness/planning/plan-adversary.md +0 -2
  15. package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
  16. package/.pi/agents/harness/planning/planning-context.md +0 -2
  17. package/.pi/agents/harness/planning/review-integrator.md +0 -2
  18. package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
  19. package/.pi/agents/harness/planning/stack-researcher.md +5 -3
  20. package/.pi/agents/harness/reviewing/adversary.md +0 -2
  21. package/.pi/agents/harness/reviewing/evaluator.md +0 -2
  22. package/.pi/agents/harness/reviewing/tie-breaker.md +0 -2
  23. package/.pi/agents/harness/running/executor.md +0 -2
  24. package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
  25. package/.pi/agents/harness/sentrux-steward.md +0 -2
  26. package/.pi/agents/harness/trace-librarian.md +0 -1
  27. package/.pi/agents/harness/web-retrieval/web-answerer.md +35 -0
  28. package/.pi/agents/harness/web-retrieval/web-criteria-verifier.md +28 -0
  29. package/.pi/agents/harness/web-retrieval/web-gap-analyzer.md +31 -0
  30. package/.pi/agents/harness/web-retrieval/web-query-expander-fast.md +34 -0
  31. package/.pi/agents/harness/web-retrieval/web-query-expander.md +60 -0
  32. package/.pi/agents/harness/web-retrieval/web-summarizer.md +18 -0
  33. package/.pi/extensions/agt-kill-switch.ts +57 -0
  34. package/.pi/extensions/agt-prompt-guard.ts +32 -0
  35. package/.pi/extensions/custom-footer.ts +46 -145
  36. package/.pi/extensions/custom-header.ts +1 -1
  37. package/.pi/extensions/custom-system-prompt.ts +1 -1
  38. package/.pi/extensions/debate-orchestrator.ts +6 -6
  39. package/.pi/extensions/harness-ask-user.ts +7 -7
  40. package/.pi/extensions/harness-debate-tools.ts +26 -42
  41. package/.pi/extensions/harness-lens.ts +94 -0
  42. package/.pi/extensions/harness-plan-approval.ts +11 -11
  43. package/.pi/extensions/harness-run-context.ts +1070 -876
  44. package/.pi/extensions/harness-subagent-governance.ts +8 -0
  45. package/.pi/extensions/harness-subagent-submit.ts +34 -163
  46. package/.pi/extensions/harness-subagents.ts +3 -3
  47. package/.pi/extensions/harness-telemetry.ts +2 -2
  48. package/.pi/extensions/harness-web-guard.ts +2 -1
  49. package/.pi/extensions/harness-web-tools.ts +691 -53
  50. package/.pi/extensions/policy-gate.ts +25 -5
  51. package/.pi/extensions/sentrux-rules-sync.ts +1 -1
  52. package/.pi/extensions/subagent-governance.ts +92 -0
  53. package/.pi/extensions/trace-recorder.ts +1 -1
  54. package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
  55. package/.pi/harness/README.md +6 -2
  56. package/.pi/harness/agents.manifest.json +46 -25
  57. package/.pi/harness/agents.policy.yaml +309 -0
  58. package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
  59. package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
  60. package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
  61. package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
  62. package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
  63. package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
  64. package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
  65. package/.pi/harness/docs/adrs/0050-agentic-web-retrieval-stack.md +46 -0
  66. package/.pi/harness/docs/adrs/README.md +5 -0
  67. package/.pi/harness/docs/harness-web-search.md +97 -0
  68. package/.pi/harness/env.harness.template +9 -1
  69. package/.pi/harness/evolution/README.md +1 -2
  70. package/.pi/harness/examples/agents.policy.project.yaml +19 -0
  71. package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
  72. package/.pi/harness/examples/web-heuristic-angles.project.yaml +22 -0
  73. package/.pi/harness/policies/bash-denylists.yaml +5 -0
  74. package/.pi/harness/policies/defaults.yaml +51 -0
  75. package/.pi/harness/policies/orchestrator.yaml +18 -0
  76. package/.pi/harness/policies/phases.yaml +10 -0
  77. package/.pi/harness/policies/roles.yaml +5 -0
  78. package/.pi/harness/policies/web-guard.yaml +5 -0
  79. package/.pi/harness/policies/workflow-sequences.yaml +9 -0
  80. package/.pi/harness/sentrux/architecture.manifest.json +26 -4
  81. package/.pi/harness/specs/observation.schema.json +2 -1
  82. package/.pi/harness/web-heuristic-angles.json +278 -0
  83. package/.pi/harness/web-heuristic-angles.yaml +182 -0
  84. package/.pi/lib/agents-policy.d.mts +70 -0
  85. package/.pi/lib/agents-policy.mjs +331 -0
  86. package/.pi/lib/agents-policy.ts +19 -0
  87. package/.pi/lib/agt/audit-run-sink.ts +52 -0
  88. package/.pi/lib/agt/build-evaluation-context.ts +285 -0
  89. package/.pi/lib/agt/config.ts +28 -0
  90. package/.pi/lib/agt/delegation.ts +69 -0
  91. package/.pi/lib/agt/evaluate-policy.ts +56 -0
  92. package/.pi/lib/agt/identity-registry.ts +41 -0
  93. package/.pi/lib/agt/index.ts +55 -0
  94. package/.pi/lib/agt/kill-switch-state.ts +11 -0
  95. package/.pi/lib/agt/legacy-evaluate.ts +101 -0
  96. package/.pi/lib/agt/policy-engine.ts +154 -0
  97. package/.pi/lib/agt/rings.ts +21 -0
  98. package/.pi/lib/agt/sre-hooks.ts +45 -0
  99. package/.pi/lib/agt/trust-run-store.ts +26 -0
  100. package/.pi/lib/agt/workflow-history.ts +29 -0
  101. package/.pi/lib/agt-governance-active.ts +14 -0
  102. package/.pi/lib/agt-tool-guard.ts +78 -0
  103. package/.pi/lib/ask-user/dialog.ts +314 -0
  104. package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
  105. package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
  106. package/.pi/{extensions/lib → lib}/extension-load-guard.ts +13 -2
  107. package/.pi/lib/harness-agt-tool-guard.ts +5 -0
  108. package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +1 -1
  109. package/.pi/lib/harness-debate-core-deps.ts +14 -0
  110. package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
  111. package/.pi/lib/harness-lens/.gitattributes +1 -0
  112. package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
  113. package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
  114. package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
  115. package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
  116. package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
  117. package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
  118. package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
  119. package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
  120. package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
  121. package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
  122. package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
  123. package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
  124. package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
  125. package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
  126. package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
  127. package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
  128. package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
  129. package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
  130. package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
  131. package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
  132. package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
  133. package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
  134. package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
  135. package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
  136. package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
  137. package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
  138. package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
  139. package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
  140. package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
  141. package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
  142. package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
  143. package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
  144. package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
  145. package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
  146. package/.pi/lib/harness-lens/clients/types.ts +59 -0
  147. package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
  148. package/.pi/lib/harness-lens/index.ts +532 -0
  149. package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
  150. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
  151. package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
  152. package/.pi/lib/harness-run-context-responses.ts +9 -0
  153. package/.pi/lib/harness-run-context.ts +0 -2
  154. package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +1 -0
  155. package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +1 -1
  156. package/.pi/lib/harness-subagent-auth.ts +81 -0
  157. package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +10 -7
  158. package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
  159. package/.pi/lib/harness-subagent-submit-register.ts +163 -0
  160. package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -37
  161. package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +74 -14
  162. package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
  163. package/.pi/lib/harness-web/artifacts.ts +200 -0
  164. package/.pi/lib/harness-web/cache.ts +369 -0
  165. package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +42 -2
  166. package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
  167. package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
  168. package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
  169. package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
  170. package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
  171. package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
  172. package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
  173. package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
  174. package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
  175. package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
  176. package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
  177. package/.pi/prompts/harness-plan.md +2 -1
  178. package/.pi/prompts/harness-setup.md +40 -65
  179. package/.pi/scripts/README.md +2 -5
  180. package/.pi/scripts/gen-web-heuristic-angles-json.mjs +24 -0
  181. package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
  182. package/.pi/scripts/harness-agents-manifest.mjs +60 -3
  183. package/.pi/scripts/harness-agt-doctor.ts +36 -0
  184. package/.pi/scripts/harness-cli-verify.sh +14 -2
  185. package/.pi/scripts/harness-verify.mjs +191 -39
  186. package/.pi/scripts/harness-web-policy-guard.mjs +3 -3
  187. package/.pi/scripts/harness-web.py +218 -15
  188. package/.pi/scripts/harness_web/deep_search.py +55 -0
  189. package/.pi/scripts/harness_web/evidence_bundle.py +47 -0
  190. package/.pi/scripts/harness_web/find_similar.py +88 -0
  191. package/.pi/scripts/harness_web/heuristic_angles_shipped.py +85 -0
  192. package/.pi/scripts/harness_web/heuristic_config.py +251 -0
  193. package/.pi/scripts/harness_web/highlights.py +47 -0
  194. package/.pi/scripts/harness_web/multi_search.py +59 -0
  195. package/.pi/scripts/harness_web/output.py +24 -0
  196. package/.pi/scripts/harness_web/query_angles.py +116 -0
  197. package/.pi/scripts/harness_web/rank.py +163 -0
  198. package/.pi/scripts/harness_web/scrape.py +30 -0
  199. package/.pi/scripts/tests/test_harness_web_heuristic_config.py +132 -0
  200. package/.pi/scripts/tests/test_harness_web_query_angles.py +45 -0
  201. package/.pi/scripts/tests/test_harness_web_rank.py +56 -0
  202. package/.pi/scripts/validate-plan-dag.mjs +65 -74
  203. package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
  204. package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
  205. package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
  206. package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
  207. package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
  208. package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
  209. package/.pi/skills/architecture/layered/SKILL.md +68 -0
  210. package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
  211. package/.pi/skills/architecture/microservices/SKILL.md +64 -0
  212. package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
  213. package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
  214. package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
  215. package/.pi/skills/architecture/service-based/SKILL.md +64 -0
  216. package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
  217. package/.pi/skills/architecture/space-based/SKILL.md +60 -0
  218. package/.pi/skills/ast-grep/SKILL.md +40 -321
  219. package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
  220. package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
  221. package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
  222. package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
  223. package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
  224. package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
  225. package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
  226. package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
  227. package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
  228. package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
  229. package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
  230. package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
  231. package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
  232. package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
  233. package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
  234. package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
  235. package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
  236. package/.pi/skills/lsp-navigation/SKILL.md +89 -0
  237. package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
  238. package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
  239. package/.pi/skills/quality/security-review/SKILL.md +34 -0
  240. package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
  241. package/.pi/skills/quality/testability-design/SKILL.md +33 -0
  242. package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
  243. package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
  244. package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
  245. package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
  246. package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
  247. package/.sentrux/rules.toml +20 -4
  248. package/AGENTS.md +7 -2
  249. package/CHANGELOG.md +20 -0
  250. package/README.md +3 -12
  251. package/THIRD_PARTY_NOTICES.md +12 -21
  252. package/package.json +17 -7
  253. package/vendor/pi-subagents/src/agents.ts +45 -1
  254. package/vendor/pi-subagents/src/subagents.ts +866 -811
  255. package/vendor/pi-vcc/src/core/brief.ts +68 -99
  256. package/vendor/pi-vcc/src/core/settings.ts +2 -2
  257. package/.agents/skills/caveman/SKILL.md +0 -67
  258. package/.agents/skills/scrapling-web/SKILL.md +0 -98
  259. package/.pi/agents/harness/meta-optimizer.md +0 -36
  260. package/.pi/extensions/00-posthog-network-bootstrap.ts +0 -11
  261. package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
  262. package/.pi/extensions/lib/harness-subagent-auth.ts +0 -207
  263. package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
  264. package/.pi/extensions/pi-model-router-harness.ts +0 -42
  265. package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
  266. package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
  267. package/.pi/model-router.example.json +0 -36
  268. package/.pi/prompts/harness-critic.md +0 -10
  269. package/.pi/prompts/harness-eval.md +0 -10
  270. package/.pi/prompts/harness-router-tune.md +0 -52
  271. package/.pi/scripts/harness-generate-model-router.mjs +0 -327
  272. package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
  273. package/.pi/scripts/harness-sync-model-router.mjs +0 -97
  274. package/.pi/scripts/harness_web/__pycache__/__init__.cpython-314.pyc +0 -0
  275. package/.pi/scripts/harness_web/__pycache__/config.cpython-314.pyc +0 -0
  276. package/.pi/scripts/harness_web/__pycache__/output.cpython-314.pyc +0 -0
  277. package/.pi/scripts/harness_web/__pycache__/scrape.cpython-314.pyc +0 -0
  278. package/.pi/scripts/harness_web/__pycache__/search.cpython-314.pyc +0 -0
  279. package/.pi/scripts/harness_web/__pycache__/search_ddg.cpython-314.pyc +0 -0
  280. package/.pi/scripts/harness_web/__pycache__/search_searxng.cpython-314.pyc +0 -0
  281. package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
  282. package/vendor/pi-model-router/.prettierignore +0 -4
  283. package/vendor/pi-model-router/.prettierrc +0 -5
  284. package/vendor/pi-model-router/AGENTS.md +0 -39
  285. package/vendor/pi-model-router/LICENSE +0 -21
  286. package/vendor/pi-model-router/README.md +0 -99
  287. package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
  288. package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
  289. package/vendor/pi-model-router/extensions/commands.ts +0 -720
  290. package/vendor/pi-model-router/extensions/config.ts +0 -348
  291. package/vendor/pi-model-router/extensions/constants.ts +0 -1
  292. package/vendor/pi-model-router/extensions/index.ts +0 -478
  293. package/vendor/pi-model-router/extensions/provider.ts +0 -580
  294. package/vendor/pi-model-router/extensions/routing.ts +0 -564
  295. package/vendor/pi-model-router/extensions/state.ts +0 -52
  296. package/vendor/pi-model-router/extensions/types.ts +0 -95
  297. package/vendor/pi-model-router/extensions/ui.ts +0 -144
  298. package/vendor/pi-model-router/model-router.example.json +0 -48
  299. package/vendor/pi-model-router/package.json +0 -48
  300. package/vendor/pi-model-router/tsconfig.json +0 -16
  301. /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
  302. /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
  303. /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
  304. /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
  305. /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
  306. /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
  307. /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
  308. /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
  309. /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
  310. /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
  311. /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
  312. /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
  313. /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
  314. /package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +0 -0
  315. /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
  316. /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
  317. /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
  318. /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
  319. /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
  320. /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
  321. /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
  322. /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
  323. /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
  324. /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
  325. /package/.pi/{extensions/lib → lib}/posthog-node.d.ts +0 -0
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { readFile, access } from "node:fs/promises";
7
7
  import { constants } from "node:fs";
8
- import { join, dirname, resolve } from "node:path";
8
+ import { join, dirname } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { spawn } from "node:child_process";
11
11
 
@@ -42,6 +42,10 @@ const REQUIRED_ADRS = [
42
42
  "0037-subagent-submit-tools.md",
43
43
  "0038-budget-telemetry-only.md",
44
44
  "0040-practice-grounded-orchestration.md",
45
+ "0045-harness-lens-minimal-contract.md",
46
+ "0046-agt-policy-engine.md",
47
+ "0047-agt-layered-security.md",
48
+ "0048-tool-call-hook-order.md",
45
49
  ];
46
50
 
47
51
  const REQUIRED_EXTENSIONS = [
@@ -148,32 +152,59 @@ async function checkSentruxRules() {
148
152
  ok(".sentrux/rules.toml present");
149
153
  }
150
154
 
151
- async function checkModelRouterThinkingOnly() {
152
- const path = join(ROOT, ".pi", "model-router.json");
153
- if (!(await fileExists(path))) {
154
- ok("model-router.json absent (skip thinking-only tier check)");
155
- return;
155
+ async function checkHarnessLens(pkgJson) {
156
+ if (!pkgJson.files?.includes(".pi/lib/harness-lens")) {
157
+ fail(
158
+ 'package.json "files" must include .pi/lib/harness-lens (npm publish ships harness lens extension)',
159
+ );
156
160
  }
157
- let raw;
158
- try {
159
- raw = JSON.parse(await readFile(path, "utf-8"));
160
- } catch {
161
- fail("invalid .pi/model-router.json");
162
- }
163
- const profiles = raw.profiles ?? {};
164
- for (const [name, profile] of Object.entries(profiles)) {
165
- const high = profile?.high?.model;
166
- const medium = profile?.medium?.model;
167
- const low = profile?.low?.model;
168
- if (
169
- !(high && medium && low && high === medium && medium === low)
170
- ) {
171
- fail(
172
- `model-router profile "${name}" must use the same model on high/medium/low (thinking-only tiers)`,
173
- );
174
- }
161
+ ok('package.json files includes .pi/lib/harness-lens');
162
+
163
+ const piExtensions = pkgJson.pi?.extensions ?? [];
164
+ if (!piExtensions.includes("./.pi/extensions")) {
165
+ fail('package.json pi.extensions must include ./.pi/extensions');
166
+ }
167
+ if (piExtensions.includes("./vendor/pi-lens/index.ts")) {
168
+ fail('package.json pi.extensions must not load vendor/pi-lens directly');
169
+ }
170
+ ok("package.json loads harness extension directory");
171
+
172
+ const harnessLens = join(ROOT, ".pi", "extensions", "harness-lens.ts");
173
+ if (!(await fileExists(harnessLens))) fail("missing .pi/extensions/harness-lens.ts");
174
+ ok("harness lens extension wrapper");
175
+
176
+ const lensIndex = join(ROOT, ".pi", "lib", "harness-lens", "index.ts");
177
+ if (!(await fileExists(lensIndex))) {
178
+ fail("missing .pi/lib/harness-lens/index.ts");
179
+ }
180
+ ok(".pi/lib/harness-lens/index.ts");
181
+
182
+ const legacyExtLib = join(ROOT, ".pi", "extensions", "lib");
183
+ if (await fileExists(legacyExtLib)) {
184
+ fail(".pi/extensions/lib must not exist (shared code lives in .pi/lib/)");
185
+ }
186
+ ok("no legacy .pi/extensions/lib directory");
187
+
188
+ const lensIndexSource = await readFile(lensIndex, "utf8");
189
+ if (lensIndexSource.includes("ast_grep_search")) {
190
+ fail("harness-lens index must not register ast_grep_search");
191
+ }
192
+ if (lensIndexSource.includes("lib/lens")) {
193
+ fail("harness-lens index must not import lib/lens");
175
194
  }
176
- ok("model-router.json thinking-only (same model per profile)");
195
+ ok("harness-lens index contract (no ast_grep, no lib/lens imports)");
196
+
197
+ const rulesDir = join(ROOT, ".pi", "lib", "harness-lens", "rules");
198
+ if (await fileExists(rulesDir)) {
199
+ fail("harness-lens bundled rules/ directory must not exist");
200
+ }
201
+ ok("no bundled harness-lens rules/ directory");
202
+
203
+ const upstreamPin = join(ROOT, ".pi", "lib", "harness-lens", "UPSTREAM_PIN.md");
204
+ if (await fileExists(upstreamPin)) {
205
+ fail("harness-lens UPSTREAM_PIN.md must not exist (harness-native, no upstream sync)");
206
+ }
207
+ ok("no harness-lens UPSTREAM_PIN.md");
177
208
  }
178
209
 
179
210
  async function checkSentruxGate() {
@@ -235,7 +266,7 @@ async function main() {
235
266
  ok(`extension ${name}`);
236
267
  }
237
268
 
238
- const libPath = join(ROOT, ".pi", "extensions", "lib", "harness-posthog.ts");
269
+ const libPath = join(ROOT, ".pi", "lib", "harness-posthog.ts");
239
270
  if (!(await fileExists(libPath))) fail("missing lib/harness-posthog.ts");
240
271
  ok("lib/harness-posthog.ts");
241
272
 
@@ -246,6 +277,8 @@ async function main() {
246
277
  const pkgJson = JSON.parse(
247
278
  await readFile(join(ROOT, "package.json"), "utf-8"),
248
279
  );
280
+ await checkHarnessLens(pkgJson);
281
+
249
282
  if (!pkgJson.files?.includes("vendor/pi-subagents")) {
250
283
  fail(
251
284
  'package.json "files" must include vendor/pi-subagents (npm publish ships subagents vendor)',
@@ -263,13 +296,7 @@ async function main() {
263
296
  if (!(await fileExists(subagentsVendor))) {
264
297
  fail("missing vendor/pi-subagents/src/subagents.ts");
265
298
  }
266
- const bridgePath = join(
267
- ROOT,
268
- ".pi",
269
- "extensions",
270
- "lib",
271
- "harness-subagents-bridge.ts",
272
- );
299
+ const bridgePath = join(ROOT, ".pi", "lib", "harness-subagents-bridge.ts");
273
300
  if (!(await fileExists(bridgePath))) {
274
301
  fail("missing harness-subagents-bridge.ts");
275
302
  }
@@ -280,6 +307,14 @@ async function main() {
280
307
  if (!bridgeSrc.includes("packageRoot")) {
281
308
  fail("harness-subagents-bridge must pass packageRoot for agent discovery");
282
309
  }
310
+ if (
311
+ !bridgeSrc.includes("subprocessGovernanceExtensionPath") &&
312
+ !bridgeSrc.includes("subagentGovernanceExtensionPath")
313
+ ) {
314
+ fail(
315
+ "harness-subagents-bridge must set subprocessGovernanceExtensionPath for all subagents",
316
+ );
317
+ }
283
318
  const subagentsSrc = await readFile(subagentsVendor, "utf-8");
284
319
  if (!subagentsSrc.includes("discoverAgents")) {
285
320
  fail("vendor subagents.ts must implement discoverAgents");
@@ -301,12 +336,46 @@ async function main() {
301
336
  if (!policyGateSrc.includes('pi.on("tool_call", async (event, ctx)')) {
302
337
  fail("policy-gate tool_call must receive ctx for run context");
303
338
  }
304
- if (!policyGateSrc.includes("evaluateContextModeMutation")) {
305
- fail(
306
- "policy-gate.ts must evaluate context-mode execute tools via evaluateContextModeMutation",
307
- );
339
+ if (!policyGateSrc.includes("evaluateAgtHarnessToolCall")) {
340
+ fail("policy-gate.ts must delegate tool_call to AGT evaluateAgtHarnessToolCall");
308
341
  }
309
- ok("policy-gate plan-phase writes");
342
+ const govPath = join(ROOT, ".pi", "extensions", "subagent-governance.ts");
343
+ const govAlias = join(
344
+ ROOT,
345
+ ".pi",
346
+ "extensions",
347
+ "harness-subagent-governance.ts",
348
+ );
349
+ if (!(await fileExists(govPath))) {
350
+ fail("missing subagent-governance.ts subprocess bundle");
351
+ }
352
+ if (!(await fileExists(govAlias))) {
353
+ fail("missing harness-subagent-governance.ts re-export alias");
354
+ }
355
+ ok("policy-gate + subprocess governance");
356
+
357
+ const agtDoctorPath = join(ROOT, ".pi", "scripts", "harness-agt-doctor.ts");
358
+ const { code: agtDoctorCode, out: agtDoctorOut } = await new Promise(
359
+ (resolve) => {
360
+ const child = spawn(
361
+ "npx",
362
+ ["-y", "tsx", agtDoctorPath],
363
+ { cwd: ROOT, stdio: ["ignore", "pipe", "pipe"], shell: true },
364
+ );
365
+ let out = "";
366
+ child.stdout?.on("data", (d) => {
367
+ out += d.toString();
368
+ });
369
+ child.stderr?.on("data", (d) => {
370
+ out += d.toString();
371
+ });
372
+ child.on("close", (code) => resolve({ code: code ?? 1, out }));
373
+ },
374
+ );
375
+ if (agtDoctorCode !== 0) {
376
+ fail(agtDoctorOut.trim() || "AGT policy doctor failed");
377
+ }
378
+ ok("AGT policy doctor");
310
379
 
311
380
  const runCtxFixture = join(SMOKE, "run-context.fixture.json");
312
381
  if (!(await fileExists(runCtxFixture))) {
@@ -332,7 +401,12 @@ async function main() {
332
401
  ok("test-diff-golden.json");
333
402
 
334
403
  await checkSentruxGate();
335
- await checkModelRouterThinkingOnly();
404
+
405
+ const AGENTS_POLICY = join(ROOT, ".pi", "harness", "agents.policy.yaml");
406
+ if (!(await fileExists(AGENTS_POLICY))) {
407
+ fail("missing .pi/harness/agents.policy.yaml");
408
+ }
409
+ ok("agents.policy.yaml present");
336
410
 
337
411
  if (!(await fileExists(AGENTS_MANIFEST))) {
338
412
  fail(
@@ -350,9 +424,87 @@ async function main() {
350
424
  }
351
425
  ok("agents.manifest.json in sync");
352
426
 
427
+ await checkWrsContracts();
428
+
353
429
  console.log("\nharness:verify PASS");
354
430
  }
355
431
 
432
+ async function checkWrsContracts() {
433
+ const systemMd = join(ROOT, ".pi", "SYSTEM.md");
434
+ const toolsTs = join(ROOT, ".pi", "extensions", "harness-web-tools.ts");
435
+ const runCli = join(ROOT, ".pi", "lib", "harness-web", "run-cli.ts");
436
+ const webRetrievalSkill = join(ROOT, ".agents", "skills", "web-retrieval", "SKILL.md");
437
+ const adr = join(
438
+ ROOT,
439
+ ".pi",
440
+ "harness",
441
+ "docs",
442
+ "adrs",
443
+ "0050-agentic-web-retrieval-stack.md",
444
+ );
445
+
446
+ for (const p of [systemMd, toolsTs, runCli, webRetrievalSkill, adr]) {
447
+ if (!(await fileExists(p))) fail(`WRS contract missing file: ${p}`);
448
+ }
449
+
450
+ const sys = await readFile(systemMd, "utf-8");
451
+ if (!sys.includes("tier=deep") && !sys.includes('tier: "deep"')) {
452
+ fail("SYSTEM.md must document deep tier default for WRS");
453
+ }
454
+ if (!sys.includes("web-retrieval")) {
455
+ fail("SYSTEM.md must reference web-retrieval skill");
456
+ }
457
+ if (!sys.includes(".web/cache") && !sys.includes("HARNESS_WEB_CACHE")) {
458
+ fail("SYSTEM.md must document pooled WRS cache under .web/cache/");
459
+ }
460
+
461
+ const tools = await readFile(toolsTs, "utf-8");
462
+ if (!tools.includes('Literal("deep")')) {
463
+ fail("harness-web-tools.ts must define tier enum including deep");
464
+ }
465
+ if (!tools.includes("anglesFile")) {
466
+ fail("harness-web-tools.ts must expose anglesFile on web_search");
467
+ }
468
+
469
+ const cli = await readFile(runCli, "utf-8");
470
+ if (!cli.includes("tier=deep")) {
471
+ fail("run-cli.ts harnessWebContextLine must mention tier=deep");
472
+ }
473
+
474
+ const artifactsTs = join(ROOT, ".pi", "lib", "harness-web", "artifacts.ts");
475
+ if (!(await fileExists(artifactsTs))) {
476
+ fail("missing harness-web/artifacts.ts for scoped .web paths");
477
+ }
478
+ const cacheTs = join(ROOT, ".pi", "lib", "harness-web", "cache.ts");
479
+ if (!(await fileExists(cacheTs))) {
480
+ fail("missing harness-web/cache.ts for pooled .web/cache/");
481
+ }
482
+ if (!tools.includes("refreshCache") || !tools.includes("lookupSearchCache")) {
483
+ fail("harness-web-tools.ts must implement pooled cache (refreshCache, lookupSearchCache)");
484
+ }
485
+ const heuristicYaml = join(ROOT, ".pi", "harness", "web-heuristic-angles.yaml");
486
+ if (!(await fileExists(heuristicYaml))) {
487
+ fail("missing .pi/harness/web-heuristic-angles.yaml");
488
+ }
489
+ const heuristicPy = join(ROOT, ".pi", "scripts", "harness_web", "heuristic_config.py");
490
+ if (!(await fileExists(heuristicPy))) {
491
+ fail("missing harness_web/heuristic_config.py");
492
+ }
493
+
494
+ const rankPy = join(ROOT, ".pi", "scripts", "harness_web", "rank.py");
495
+ const anglesPy = join(ROOT, ".pi", "scripts", "harness_web", "deep_search.py");
496
+ for (const p of [rankPy, anglesPy]) {
497
+ if (!(await fileExists(p))) fail(`WRS python module missing: ${p}`);
498
+ }
499
+
500
+ const expander = join(ROOT, ".pi", "agents", "harness", "web-retrieval", "web-query-expander.md");
501
+ if (!(await fileExists(expander))) {
502
+ fail("missing web-query-expander agent");
503
+ }
504
+
505
+ ok("WRS contracts (SYSTEM.md, tools, modules, web-retrieval skill, ADR)");
506
+ }
507
+
356
508
  main().catch((err) => {
357
509
  console.error(err);
358
510
  process.exit(1);
@@ -12,13 +12,13 @@ const ROOT = resolve(new URL("../..", import.meta.url).pathname);
12
12
  const ALLOWED_FILES = new Set([
13
13
  ".pi/extensions/harness-web-guard.ts",
14
14
  ".pi/extensions/harness-web-tools.ts",
15
- ".pi/extensions/lib/harness-web/run-cli.ts",
15
+ ".pi/lib/harness-web/run-cli.ts",
16
16
  ".pi/extensions/harness-run-context.ts",
17
- ".pi/extensions/lib/ask-user/schema.ts",
17
+ ".pi/lib/ask-user/schema.ts",
18
18
  ".pi/scripts/harness-web.py",
19
19
  ".pi/scripts/harness-web-search.md",
20
20
  ".pi/scripts/harness-web-policy-guard.mjs",
21
- ".agents/skills/scrapling-web/SKILL.md",
21
+ ".agents/skills/web-retrieval/SKILL.md",
22
22
  ".pi/scripts/harness-cli-verify.sh",
23
23
  ".pi/scripts/harness_web/output.py",
24
24
  "AGENTS.md",
@@ -9,6 +9,7 @@ import shutil
9
9
  import sys
10
10
  import time
11
11
  from pathlib import Path
12
+ from urllib.parse import urlparse
12
13
 
13
14
  # Re-exec with scrapling's uv-tool Python when the library is not on default python3.
14
15
  def _bootstrap_scrapling() -> None:
@@ -34,10 +35,28 @@ if str(SCRIPT_DIR) not in sys.path:
34
35
  sys.path.insert(0, str(SCRIPT_DIR))
35
36
 
36
37
  from harness_web.config import HarnessWebConfig, load_config # noqa: E402
37
- from harness_web.output import write_search_results # noqa: E402
38
- from harness_web.scrape import bulk_scrape, map_url, scrape_url # noqa: E402
38
+ from harness_web.deep_search import run_deep_search # noqa: E402
39
+ from harness_web.evidence_bundle import build_evidence_bundle, write_evidence_bundle # noqa: E402
40
+ from harness_web.find_similar import run_find_similar # noqa: E402
41
+ from harness_web.output import ( # noqa: E402
42
+ write_deep_search_results,
43
+ write_search_results,
44
+ )
45
+ from harness_web.scrape import ( # noqa: E402
46
+ bulk_scrape,
47
+ map_url,
48
+ scrape_url,
49
+ scrape_url_with_highlights,
50
+ )
39
51
  from harness_web.search import search # noqa: E402
40
52
 
53
+ TIER_LIMITS = {
54
+ "instant": 5,
55
+ "standard": 10,
56
+ "deep": 10,
57
+ "research": 15,
58
+ }
59
+
41
60
  DEFAULT_WEB_DIR = ".web"
42
61
 
43
62
 
@@ -45,26 +64,153 @@ def _default_out(sub: str) -> Path:
45
64
  return Path(DEFAULT_WEB_DIR) / sub
46
65
 
47
66
 
67
+ def _tier_limit(tier: str, cli_limit: int | None) -> int:
68
+ if cli_limit is not None:
69
+ return cli_limit
70
+ return TIER_LIMITS.get(tier, 10)
71
+
72
+
48
73
  def cmd_search(args: argparse.Namespace, config: HarnessWebConfig) -> int:
74
+ tier = getattr(args, "tier", None) or "standard"
75
+ limit = _tier_limit(tier, args.limit)
49
76
  out = Path(args.output or _default_out("search.json"))
50
- results = search(args.query, limit=args.limit, config=config)
51
- write_search_results(out, results, args.query, engine=config.search_engine)
52
- print(f"wrote {out} ({len(results)} results)")
77
+ results = search(args.query, limit=limit, config=config)
78
+ write_search_results(out, results, args.query, engine=config.search_engine, tier=tier)
79
+ print(f"wrote {out} ({len(results)} results, tier={tier})")
53
80
  return 0
54
81
 
55
82
 
56
- def cmd_scrape(args: argparse.Namespace, config: HarnessWebConfig) -> int:
57
- out = Path(args.output or _default_out("page.md"))
58
- fast = config.use_fast_for_url(args.url, args.fast)
59
- scrape_url(
83
+ def cmd_search_deep(args: argparse.Namespace, config: HarnessWebConfig) -> int:
84
+ out = Path(args.output or _default_out("search-deep.json"))
85
+ angles_path = Path(args.angles_file) if args.angles_file else None
86
+ plan, ranked = run_deep_search(
87
+ args.query,
88
+ config=config,
89
+ angles_file=angles_path,
90
+ expand_heuristic=args.expand_heuristic,
91
+ category=args.category,
92
+ per_angle_limit=args.per_angle_limit,
93
+ final_limit=args.limit,
94
+ )
95
+ angle_dicts = [
96
+ {"id": a.id, "query": a.query, "rationale": a.rationale} for a in plan.angles
97
+ ]
98
+ write_deep_search_results(
99
+ out,
100
+ query=args.query,
101
+ engine=config.search_engine,
102
+ tier="deep",
103
+ plan_angles=angle_dicts,
104
+ ranked_web=ranked,
105
+ )
106
+ print(f"wrote {out} ({len(ranked)} fused results, {len(plan.angles)} angles)")
107
+ return 0
108
+
109
+
110
+ def cmd_find_similar(args: argparse.Namespace, config: HarnessWebConfig) -> int:
111
+ out = Path(args.output or _default_out("search-deep.json"))
112
+ plan, ranked = run_find_similar(
60
113
  args.url,
61
- str(out),
62
114
  config=config,
63
- fast=fast,
64
- wait_ms=args.wait_for,
115
+ final_limit=args.limit,
116
+ per_angle_limit=args.per_angle_limit,
117
+ fast_fetch=args.fast,
118
+ )
119
+ angle_dicts = [
120
+ {"id": a.id, "query": a.query, "rationale": a.rationale} for a in plan.angles
121
+ ]
122
+ write_deep_search_results(
123
+ out,
124
+ query=plan.intent,
125
+ engine=config.search_engine,
126
+ tier="deep",
127
+ plan_angles=angle_dicts,
128
+ ranked_web=ranked,
65
129
  )
66
- mode = "fast" if fast else "stealth"
67
- print(f"wrote {out} ({mode})")
130
+ print(f"wrote {out} ({len(ranked)} similar results)")
131
+ return 0
132
+
133
+
134
+ def cmd_scrape(args: argparse.Namespace, config: HarnessWebConfig) -> int:
135
+ out = Path(args.output or _default_out("page.md"))
136
+ fast = config.use_fast_for_url(args.url, args.fast)
137
+ hl_out = args.highlights_output
138
+ hl_query = (args.highlight_query or "").strip()
139
+ if args.highlights and hl_query:
140
+ scrape_url_with_highlights(
141
+ args.url,
142
+ str(out),
143
+ hl_out or str(_default_out("highlights.json")),
144
+ config=config,
145
+ fast=fast,
146
+ wait_ms=args.wait_for,
147
+ highlight_query=hl_query,
148
+ )
149
+ print(f"wrote {out} (highlights)")
150
+ else:
151
+ scrape_url(
152
+ args.url,
153
+ str(out),
154
+ config=config,
155
+ fast=fast,
156
+ wait_ms=args.wait_for,
157
+ )
158
+ mode = "fast" if fast else "stealth"
159
+ print(f"wrote {out} ({mode})")
160
+ return 0
161
+
162
+
163
+ def cmd_contents_batch(args: argparse.Namespace, config: HarnessWebConfig) -> int:
164
+ import json
165
+
166
+ out_dir = Path(args.output or _default_out("contents"))
167
+ out_dir.mkdir(parents=True, exist_ok=True)
168
+ urls: list[str] = list(args.urls or [])
169
+ if args.from_search:
170
+ data = json.loads(Path(args.from_search).read_text(encoding="utf-8"))
171
+ for item in data.get("data", {}).get("web", []):
172
+ u = (item.get("url") or "").strip()
173
+ if u:
174
+ urls.append(u)
175
+ if not urls:
176
+ print("contents-batch: no URLs", file=sys.stderr)
177
+ return 1
178
+
179
+ hl_query = (args.highlight_query or "").strip()
180
+ manifest: list[dict] = []
181
+ sleep_sec = config.rate_limit_ms / 1000.0
182
+ for i, url in enumerate(urls[: args.limit]):
183
+ if i and sleep_sec > 0:
184
+ time.sleep(sleep_sec)
185
+ safe = urlparse(url).netloc.replace(".", "_")
186
+ md_path = out_dir / f"{safe}.md"
187
+ hl_path = out_dir / f"{safe}.highlights.json" if args.highlights and hl_query else None
188
+ fast = config.use_fast_for_url(url, args.fast)
189
+ try:
190
+ if hl_path:
191
+ scrape_url_with_highlights(
192
+ url,
193
+ str(md_path),
194
+ str(hl_path),
195
+ config=config,
196
+ fast=fast,
197
+ wait_ms=None,
198
+ highlight_query=hl_query,
199
+ )
200
+ else:
201
+ scrape_url(url, str(md_path), config=config, fast=fast, wait_ms=None)
202
+ manifest.append({"url": url, "markdown": str(md_path), "ok": True})
203
+ except Exception as err: # noqa: BLE001
204
+ manifest.append({"url": url, "ok": False, "error": str(err)})
205
+
206
+ manifest_path = out_dir / "manifest.json"
207
+ manifest_path.write_text(json.dumps({"urls": manifest}, indent=2) + "\n", encoding="utf-8")
208
+ if args.evidence_bundle and args.from_search:
209
+ eb_path = Path(args.evidence_bundle)
210
+ bundle = build_evidence_bundle(Path(args.from_search), query=hl_query)
211
+ write_evidence_bundle(eb_path, bundle)
212
+ print(f"wrote {eb_path}")
213
+ print(f"wrote {len(manifest)} entries to {out_dir}")
68
214
  return 0
69
215
 
70
216
 
@@ -132,9 +278,41 @@ def build_parser() -> argparse.ArgumentParser:
132
278
  ps = sub.add_parser("search", help="Search via configured SERP (HARNESS_WEB_SEARCH_ENGINE)")
133
279
  ps.add_argument("query", help="Search query")
134
280
  ps.add_argument("-o", "--output", help="JSON output path (default: .web/search.json)")
135
- ps.add_argument("--limit", type=int, default=5)
281
+ ps.add_argument("--limit", type=int, default=None)
282
+ ps.add_argument(
283
+ "--tier",
284
+ choices=("instant", "standard", "deep", "research"),
285
+ default="standard",
286
+ help="WRS tier (instant=5, standard=10 results)",
287
+ )
136
288
  ps.set_defaults(func=cmd_search)
137
289
 
290
+ pd = sub.add_parser("search-deep", help="Multi-angle SERP fusion (WRS deep)")
291
+ pd.add_argument("query", help="Original research intent")
292
+ pd.add_argument("-o", "--output", help="JSON output (default: .web/search-deep.json)")
293
+ pd.add_argument("--limit", type=int, default=10, help="Final fused result count")
294
+ pd.add_argument("--per-angle-limit", type=int, default=8, help="SERP hits per angle")
295
+ pd.add_argument(
296
+ "--angles-file",
297
+ metavar="YAML",
298
+ help="Angles from web-query-expander (.web/angles.yaml)",
299
+ )
300
+ pd.add_argument(
301
+ "--expand-heuristic",
302
+ action="store_true",
303
+ help="Emergency angle templates without expander subagent",
304
+ )
305
+ pd.add_argument("--category", help="Hint: code|company|people|paper|news")
306
+ pd.set_defaults(func=cmd_search_deep)
307
+
308
+ pf = sub.add_parser("find-similar", help="Pages similar to a seed URL")
309
+ pf.add_argument("url", help="Seed URL")
310
+ pf.add_argument("-o", "--output", help="JSON output (default: .web/search-deep.json)")
311
+ pf.add_argument("--limit", type=int, default=10)
312
+ pf.add_argument("--per-angle-limit", type=int, default=6)
313
+ pf.add_argument("--fast", action="store_true", help="Fast HTTP for seed fetch")
314
+ pf.set_defaults(func=cmd_find_similar)
315
+
138
316
  pc = sub.add_parser("scrape", help="Scrape a URL to markdown")
139
317
  pc.add_argument("url")
140
318
  pc.add_argument("-o", "--output", help="Markdown output (default: .web/page.md)")
@@ -150,8 +328,33 @@ def build_parser() -> argparse.ArgumentParser:
150
328
  metavar="MS",
151
329
  help="Extra wait after load (stealth mode, milliseconds)",
152
330
  )
331
+ pc.add_argument("--highlights", action="store_true", help="Extract query-aligned excerpts")
332
+ pc.add_argument("--highlight-query", help="Query for highlight scoring")
333
+ pc.add_argument(
334
+ "--highlights-output",
335
+ help="Highlights JSON path (default: .web/highlights.json)",
336
+ )
153
337
  pc.set_defaults(func=cmd_scrape)
154
338
 
339
+ pbatch = sub.add_parser("contents-batch", help="Batch scrape URLs to markdown manifest")
340
+ pbatch.add_argument("urls", nargs="*", help="URLs to fetch")
341
+ pbatch.add_argument("-o", "--output", help="Output directory (default: .web/contents)")
342
+ pbatch.add_argument("--limit", type=int, default=5)
343
+ pbatch.add_argument(
344
+ "--from-search",
345
+ metavar="JSON",
346
+ help="URLs from search.json or search-deep.json",
347
+ )
348
+ pbatch.add_argument("--fast", action="store_true")
349
+ pbatch.add_argument("--highlights", action="store_true")
350
+ pbatch.add_argument("--highlight-query", default="")
351
+ pbatch.add_argument(
352
+ "--evidence-bundle",
353
+ metavar="JSON",
354
+ help="Write evidence-bundle.json from --from-search",
355
+ )
356
+ pbatch.set_defaults(func=cmd_contents_batch)
357
+
155
358
  pb = sub.add_parser("bulk-scrape", help="Search then scrape multiple URLs")
156
359
  pb.add_argument("query", nargs="?", help="Search query when not using --from-search")
157
360
  pb.add_argument("-o", "--output", help="Output directory (default: .web/bulk)")
@@ -0,0 +1,55 @@
1
+ """WRS deep search orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+ from .config import HarnessWebConfig
9
+ from .multi_search import multi_search
10
+ from .query_angles import AnglesPlan, resolve_angles
11
+ from .rank import fuse_angle_results
12
+
13
+
14
+ def _rerank_mode() -> str:
15
+ mode = os.environ.get("HARNESS_WEB_RERANK", "off").strip().lower()
16
+ if mode in ("off", "lexical", "embed"):
17
+ return mode
18
+ return "off"
19
+
20
+
21
+ def run_deep_search(
22
+ query: str,
23
+ *,
24
+ config: HarnessWebConfig,
25
+ angles_file: Path | None = None,
26
+ expand_heuristic: bool = False,
27
+ category: str | None = None,
28
+ per_angle_limit: int = 8,
29
+ final_limit: int = 10,
30
+ ) -> tuple[AnglesPlan, list[dict]]:
31
+ plan = resolve_angles(
32
+ query,
33
+ angles_file=angles_file,
34
+ expand_heuristic=expand_heuristic,
35
+ category=category,
36
+ )
37
+ per_angle = multi_search(plan, per_angle_limit=per_angle_limit, config=config)
38
+ # Strip internal tags before fusion
39
+ clean: dict[str, list[dict[str, str]]] = {}
40
+ for aid, rows in per_angle.items():
41
+ clean[aid] = [
42
+ {
43
+ "url": r.get("url", ""),
44
+ "title": r.get("title", ""),
45
+ "description": r.get("description", ""),
46
+ }
47
+ for r in rows
48
+ ]
49
+ ranked = fuse_angle_results(
50
+ clean,
51
+ final_limit=final_limit,
52
+ intent=plan.intent,
53
+ rerank_mode=_rerank_mode(),
54
+ )
55
+ return plan, [h.to_web_dict() for h in ranked]