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.
- package/.agents/skills/harness-debate-plan/SKILL.md +1 -1
- package/.agents/skills/harness-decisions/SKILL.md +1 -2
- package/.agents/skills/harness-governor/SKILL.md +6 -5
- package/.agents/skills/web-retrieval/SKILL.md +163 -0
- package/.agents/skills/wiki-autoresearch/SKILL.md +6 -6
- package/.pi/PACKAGING.md +4 -4
- package/.pi/SYSTEM.md +75 -123
- package/.pi/agents/harness/incident-recorder.md +0 -1
- package/.pi/agents/harness/planning/decompose.md +0 -2
- package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
- package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
- package/.pi/agents/harness/planning/hypothesis.md +0 -2
- package/.pi/agents/harness/planning/implementation-researcher.md +1 -3
- package/.pi/agents/harness/planning/plan-adversary.md +0 -2
- package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
- package/.pi/agents/harness/planning/planning-context.md +0 -2
- package/.pi/agents/harness/planning/review-integrator.md +0 -2
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
- package/.pi/agents/harness/planning/stack-researcher.md +5 -3
- package/.pi/agents/harness/reviewing/adversary.md +0 -2
- package/.pi/agents/harness/reviewing/evaluator.md +0 -2
- package/.pi/agents/harness/reviewing/tie-breaker.md +0 -2
- package/.pi/agents/harness/running/executor.md +0 -2
- package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
- package/.pi/agents/harness/sentrux-steward.md +0 -2
- package/.pi/agents/harness/trace-librarian.md +0 -1
- package/.pi/agents/harness/web-retrieval/web-answerer.md +35 -0
- package/.pi/agents/harness/web-retrieval/web-criteria-verifier.md +28 -0
- package/.pi/agents/harness/web-retrieval/web-gap-analyzer.md +31 -0
- package/.pi/agents/harness/web-retrieval/web-query-expander-fast.md +34 -0
- package/.pi/agents/harness/web-retrieval/web-query-expander.md +60 -0
- package/.pi/agents/harness/web-retrieval/web-summarizer.md +18 -0
- package/.pi/extensions/agt-kill-switch.ts +57 -0
- package/.pi/extensions/agt-prompt-guard.ts +32 -0
- package/.pi/extensions/custom-footer.ts +46 -145
- package/.pi/extensions/custom-header.ts +1 -1
- package/.pi/extensions/custom-system-prompt.ts +1 -1
- package/.pi/extensions/debate-orchestrator.ts +6 -6
- package/.pi/extensions/harness-ask-user.ts +7 -7
- package/.pi/extensions/harness-debate-tools.ts +26 -42
- package/.pi/extensions/harness-lens.ts +94 -0
- package/.pi/extensions/harness-plan-approval.ts +11 -11
- package/.pi/extensions/harness-run-context.ts +1070 -876
- package/.pi/extensions/harness-subagent-governance.ts +8 -0
- package/.pi/extensions/harness-subagent-submit.ts +34 -163
- package/.pi/extensions/harness-subagents.ts +3 -3
- package/.pi/extensions/harness-telemetry.ts +2 -2
- package/.pi/extensions/harness-web-guard.ts +2 -1
- package/.pi/extensions/harness-web-tools.ts +691 -53
- package/.pi/extensions/policy-gate.ts +25 -5
- package/.pi/extensions/sentrux-rules-sync.ts +1 -1
- package/.pi/extensions/subagent-governance.ts +92 -0
- package/.pi/extensions/trace-recorder.ts +1 -1
- package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
- package/.pi/harness/README.md +6 -2
- package/.pi/harness/agents.manifest.json +46 -25
- package/.pi/harness/agents.policy.yaml +309 -0
- package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
- package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
- package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
- package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
- package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
- package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
- package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
- package/.pi/harness/docs/adrs/0050-agentic-web-retrieval-stack.md +46 -0
- package/.pi/harness/docs/adrs/README.md +5 -0
- package/.pi/harness/docs/harness-web-search.md +97 -0
- package/.pi/harness/env.harness.template +9 -1
- package/.pi/harness/evolution/README.md +1 -2
- package/.pi/harness/examples/agents.policy.project.yaml +19 -0
- package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
- package/.pi/harness/examples/web-heuristic-angles.project.yaml +22 -0
- package/.pi/harness/policies/bash-denylists.yaml +5 -0
- package/.pi/harness/policies/defaults.yaml +51 -0
- package/.pi/harness/policies/orchestrator.yaml +18 -0
- package/.pi/harness/policies/phases.yaml +10 -0
- package/.pi/harness/policies/roles.yaml +5 -0
- package/.pi/harness/policies/web-guard.yaml +5 -0
- package/.pi/harness/policies/workflow-sequences.yaml +9 -0
- package/.pi/harness/sentrux/architecture.manifest.json +26 -4
- package/.pi/harness/specs/observation.schema.json +2 -1
- package/.pi/harness/web-heuristic-angles.json +278 -0
- package/.pi/harness/web-heuristic-angles.yaml +182 -0
- package/.pi/lib/agents-policy.d.mts +70 -0
- package/.pi/lib/agents-policy.mjs +331 -0
- package/.pi/lib/agents-policy.ts +19 -0
- package/.pi/lib/agt/audit-run-sink.ts +52 -0
- package/.pi/lib/agt/build-evaluation-context.ts +285 -0
- package/.pi/lib/agt/config.ts +28 -0
- package/.pi/lib/agt/delegation.ts +69 -0
- package/.pi/lib/agt/evaluate-policy.ts +56 -0
- package/.pi/lib/agt/identity-registry.ts +41 -0
- package/.pi/lib/agt/index.ts +55 -0
- package/.pi/lib/agt/kill-switch-state.ts +11 -0
- package/.pi/lib/agt/legacy-evaluate.ts +101 -0
- package/.pi/lib/agt/policy-engine.ts +154 -0
- package/.pi/lib/agt/rings.ts +21 -0
- package/.pi/lib/agt/sre-hooks.ts +45 -0
- package/.pi/lib/agt/trust-run-store.ts +26 -0
- package/.pi/lib/agt/workflow-history.ts +29 -0
- package/.pi/lib/agt-governance-active.ts +14 -0
- package/.pi/lib/agt-tool-guard.ts +78 -0
- package/.pi/lib/ask-user/dialog.ts +314 -0
- package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
- package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
- package/.pi/{extensions/lib → lib}/extension-load-guard.ts +13 -2
- package/.pi/lib/harness-agt-tool-guard.ts +5 -0
- package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +1 -1
- package/.pi/lib/harness-debate-core-deps.ts +14 -0
- package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
- package/.pi/lib/harness-lens/.gitattributes +1 -0
- package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
- package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
- package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
- package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
- package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
- package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
- package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
- package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
- package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
- package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
- package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
- package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
- package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
- package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
- package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
- package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
- package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
- package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
- package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
- package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
- package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
- package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
- package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
- package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
- package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
- package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
- package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
- package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
- package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
- package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
- package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
- package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
- package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
- package/.pi/lib/harness-lens/clients/types.ts +59 -0
- package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
- package/.pi/lib/harness-lens/index.ts +532 -0
- package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
- package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
- package/.pi/lib/harness-run-context-responses.ts +9 -0
- package/.pi/lib/harness-run-context.ts +0 -2
- package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +1 -0
- package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +1 -1
- package/.pi/lib/harness-subagent-auth.ts +81 -0
- package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +10 -7
- package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
- package/.pi/lib/harness-subagent-submit-register.ts +163 -0
- package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -37
- package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +74 -14
- package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
- package/.pi/lib/harness-web/artifacts.ts +200 -0
- package/.pi/lib/harness-web/cache.ts +369 -0
- package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +42 -2
- package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
- package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
- package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
- package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
- package/.pi/prompts/harness-plan.md +2 -1
- package/.pi/prompts/harness-setup.md +40 -65
- package/.pi/scripts/README.md +2 -5
- package/.pi/scripts/gen-web-heuristic-angles-json.mjs +24 -0
- package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
- package/.pi/scripts/harness-agents-manifest.mjs +60 -3
- package/.pi/scripts/harness-agt-doctor.ts +36 -0
- package/.pi/scripts/harness-cli-verify.sh +14 -2
- package/.pi/scripts/harness-verify.mjs +191 -39
- package/.pi/scripts/harness-web-policy-guard.mjs +3 -3
- package/.pi/scripts/harness-web.py +218 -15
- package/.pi/scripts/harness_web/deep_search.py +55 -0
- package/.pi/scripts/harness_web/evidence_bundle.py +47 -0
- package/.pi/scripts/harness_web/find_similar.py +88 -0
- package/.pi/scripts/harness_web/heuristic_angles_shipped.py +85 -0
- package/.pi/scripts/harness_web/heuristic_config.py +251 -0
- package/.pi/scripts/harness_web/highlights.py +47 -0
- package/.pi/scripts/harness_web/multi_search.py +59 -0
- package/.pi/scripts/harness_web/output.py +24 -0
- package/.pi/scripts/harness_web/query_angles.py +116 -0
- package/.pi/scripts/harness_web/rank.py +163 -0
- package/.pi/scripts/harness_web/scrape.py +30 -0
- package/.pi/scripts/tests/test_harness_web_heuristic_config.py +132 -0
- package/.pi/scripts/tests/test_harness_web_query_angles.py +45 -0
- package/.pi/scripts/tests/test_harness_web_rank.py +56 -0
- package/.pi/scripts/validate-plan-dag.mjs +65 -74
- package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
- package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
- package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
- package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
- package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
- package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
- package/.pi/skills/architecture/layered/SKILL.md +68 -0
- package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
- package/.pi/skills/architecture/microservices/SKILL.md +64 -0
- package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
- package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
- package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
- package/.pi/skills/architecture/service-based/SKILL.md +64 -0
- package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
- package/.pi/skills/architecture/space-based/SKILL.md +60 -0
- package/.pi/skills/ast-grep/SKILL.md +40 -321
- package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
- package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
- package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
- package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
- package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
- package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
- package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
- package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
- package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
- package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
- package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
- package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
- package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
- package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
- package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
- package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
- package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
- package/.pi/skills/lsp-navigation/SKILL.md +89 -0
- package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
- package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
- package/.pi/skills/quality/security-review/SKILL.md +34 -0
- package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
- package/.pi/skills/quality/testability-design/SKILL.md +33 -0
- package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
- package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
- package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
- package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
- package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
- package/.sentrux/rules.toml +20 -4
- package/AGENTS.md +7 -2
- package/CHANGELOG.md +20 -0
- package/README.md +3 -12
- package/THIRD_PARTY_NOTICES.md +12 -21
- package/package.json +17 -7
- package/vendor/pi-subagents/src/agents.ts +45 -1
- package/vendor/pi-subagents/src/subagents.ts +866 -811
- package/vendor/pi-vcc/src/core/brief.ts +68 -99
- package/vendor/pi-vcc/src/core/settings.ts +2 -2
- package/.agents/skills/caveman/SKILL.md +0 -67
- package/.agents/skills/scrapling-web/SKILL.md +0 -98
- package/.pi/agents/harness/meta-optimizer.md +0 -36
- package/.pi/extensions/00-posthog-network-bootstrap.ts +0 -11
- package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
- package/.pi/extensions/lib/harness-subagent-auth.ts +0 -207
- package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
- package/.pi/extensions/pi-model-router-harness.ts +0 -42
- package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
- package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
- package/.pi/model-router.example.json +0 -36
- package/.pi/prompts/harness-critic.md +0 -10
- package/.pi/prompts/harness-eval.md +0 -10
- package/.pi/prompts/harness-router-tune.md +0 -52
- package/.pi/scripts/harness-generate-model-router.mjs +0 -327
- package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
- package/.pi/scripts/harness-sync-model-router.mjs +0 -97
- package/.pi/scripts/harness_web/__pycache__/__init__.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/config.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/output.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/scrape.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/search.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/search_ddg.cpython-314.pyc +0 -0
- package/.pi/scripts/harness_web/__pycache__/search_searxng.cpython-314.pyc +0 -0
- package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
- package/vendor/pi-model-router/.prettierignore +0 -4
- package/vendor/pi-model-router/.prettierrc +0 -5
- package/vendor/pi-model-router/AGENTS.md +0 -39
- package/vendor/pi-model-router/LICENSE +0 -21
- package/vendor/pi-model-router/README.md +0 -99
- package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
- package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
- package/vendor/pi-model-router/extensions/commands.ts +0 -720
- package/vendor/pi-model-router/extensions/config.ts +0 -348
- package/vendor/pi-model-router/extensions/constants.ts +0 -1
- package/vendor/pi-model-router/extensions/index.ts +0 -478
- package/vendor/pi-model-router/extensions/provider.ts +0 -580
- package/vendor/pi-model-router/extensions/routing.ts +0 -564
- package/vendor/pi-model-router/extensions/state.ts +0 -52
- package/vendor/pi-model-router/extensions/types.ts +0 -95
- package/vendor/pi-model-router/extensions/ui.ts +0 -144
- package/vendor/pi-model-router/model-router.example.json +0 -48
- package/vendor/pi-model-router/package.json +0 -48
- package/vendor/pi-model-router/tsconfig.json +0 -16
- /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
- /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
- /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
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
fail(
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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("
|
|
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", "
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
15
|
+
".pi/lib/harness-web/run-cli.ts",
|
|
16
16
|
".pi/extensions/harness-run-context.ts",
|
|
17
|
-
".pi/
|
|
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/
|
|
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.
|
|
38
|
-
from harness_web.
|
|
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=
|
|
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
|
|
57
|
-
out = Path(args.output or _default_out("
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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=
|
|
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]
|