harness-everything 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +8 -7
- package/.github/workflows/ci.yml +23 -18
- package/.github/workflows/release.yml +53 -27
- package/.releaserc.json +113 -0
- package/AGENTS.md +34 -23
- package/BENCHMARK_SOP.md +38 -18
- package/CHANGELOG.md +194 -3
- package/CONTRIBUTING.md +17 -4
- package/PRIVACY.md +25 -0
- package/README.md +118 -87
- package/RELEASING.md +82 -0
- package/SUPPORT.md +27 -0
- package/TERMS.md +21 -0
- package/VERIFICATION.md +175 -286
- package/behavioral-evals/README.md +21 -0
- package/behavioral-evals/case-validator.js +194 -0
- package/behavioral-evals/cases/baseline-environment-detection.yaml +21 -0
- package/behavioral-evals/cases/baseline-eval-harness.yaml +27 -0
- package/behavioral-evals/cases/baseline-fable-discipline.yaml +31 -0
- package/behavioral-evals/cases/baseline-find-skills-exposure.yaml +49 -0
- package/behavioral-evals/cases/baseline-find-skills.yaml +21 -0
- package/behavioral-evals/cases/baseline-git-commit.yaml +2 -2
- package/behavioral-evals/cases/baseline-install-cognitive-os.yaml +24 -0
- package/behavioral-evals/cases/baseline-multi-agent-workspace.yaml +28 -0
- package/behavioral-evals/cases/baseline-rewrite-commits.yaml +34 -0
- package/behavioral-evals/cases/baseline-self-evolve.yaml +31 -0
- package/behavioral-evals/cases/baseline-skill-creator.yaml +20 -0
- package/behavioral-evals/cases/baseline-skill-style.yaml +21 -0
- package/behavioral-evals/cases/baseline-to-spec.yaml +34 -0
- package/behavioral-evals/cases/baseline-to-tickets.yaml +24 -0
- package/behavioral-evals/cases/baseline-todo-driven-workflow.yaml +32 -0
- package/behavioral-evals/cases/baseline-using-git-worktrees.yaml +15 -0
- package/behavioral-evals/cases/breaker-zoom-out-after-3.yaml +2 -2
- package/behavioral-evals/cases/pressure-complexity-skip.yaml +2 -2
- package/behavioral-evals/cases/pressure-scope-bypass.yaml +7 -2
- package/behavioral-evals/cases/pressure-sunk-cost-retry.yaml +7 -4
- package/behavioral-evals/cases/scope-discipline.yaml +2 -1
- package/behavioral-evals/cases/verify-before-done.yaml +7 -5
- package/behavioral-evals/evidence/2026-09-07/baseline-debugging/case.yaml +31 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-debugging/historical-case.yaml +31 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-debugging/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-debugging/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-performance/case.yaml +34 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-performance/historical-case.yaml +34 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-performance/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-performance/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-security-review/case.yaml +28 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-security-review/historical-case.yaml +28 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-security-review/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-security-review/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-simple-bugfix/case.yaml +33 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-simple-bugfix/historical-case.yaml +33 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-simple-bugfix/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/baseline-simple-bugfix/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/grill-me-adversarial/case.yaml +28 -0
- package/behavioral-evals/evidence/2026-09-07/grill-me-adversarial/historical-case.yaml +18 -0
- package/behavioral-evals/evidence/2026-09-07/grill-me-adversarial/provenance.json +30 -0
- package/behavioral-evals/evidence/2026-09-07/live-run-blocker.md +16 -0
- package/behavioral-evals/evidence/2026-09-07/manifest.json +88 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-scope-bypass/case.yaml +33 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-scope-bypass/historical-case.yaml +28 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-scope-bypass/provenance.json +30 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-docs/case.yaml +50 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-docs/historical-case.yaml +50 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-docs/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-docs/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-error-handling/case.yaml +35 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-error-handling/historical-case.yaml +35 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-error-handling/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-error-handling/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification/case.yaml +43 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification/historical-case.yaml +43 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification/result.sanitized.json +24 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-23-pressure-skip-verification/case.yaml +43 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-23-pressure-skip-verification/historical-case.yaml +43 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-23-pressure-skip-verification/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-23-pressure-skip-verification/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-27-pressure-skip-verification/case.yaml +43 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-27-pressure-skip-verification/historical-case.yaml +43 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-27-pressure-skip-verification/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-27-pressure-skip-verification/result.sanitized.json +24 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry/case.yaml +50 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry/historical-case.yaml +47 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry/result.sanitized.json +24 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-23-pressure-sunk-cost-retry/case.yaml +50 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-23-pressure-sunk-cost-retry/historical-case.yaml +47 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-23-pressure-sunk-cost-retry/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-23-pressure-sunk-cost-retry/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-27-pressure-sunk-cost-retry/case.yaml +50 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-27-pressure-sunk-cost-retry/historical-case.yaml +47 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-27-pressure-sunk-cost-retry/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-27-pressure-sunk-cost-retry/result.sanitized.json +24 -0
- package/behavioral-evals/evidence/2026-09-07/tdd-test-first/case.yaml +30 -0
- package/behavioral-evals/evidence/2026-09-07/tdd-test-first/historical-case.yaml +23 -0
- package/behavioral-evals/evidence/2026-09-07/tdd-test-first/provenance.json +30 -0
- package/behavioral-evals/evidence/2026-09-07/triage-matrix.json +426 -0
- package/behavioral-evals/evidence/2026-09-07/triage-matrix.md +24 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-claim-cites/case.yaml +19 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-claim-cites/historical-case.yaml +18 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-claim-cites/provenance.json +30 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done/case.yaml +33 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done/historical-case.yaml +31 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-23-verify-before-done/case.yaml +33 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-23-verify-before-done/historical-case.yaml +31 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-23-verify-before-done/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-23-verify-before-done/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-27-verify-before-done/case.yaml +33 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-27-verify-before-done/historical-case.yaml +31 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-27-verify-before-done/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-27-verify-before-done/result.sanitized.json +20 -0
- package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/case.yaml +33 -0
- package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/historical-case.yaml +33 -0
- package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/provenance.json +42 -0
- package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/result.sanitized.json +480 -0
- package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/transcript.sanitized.jsonl +95 -0
- package/behavioral-evals/evidence-tool.js +443 -0
- package/behavioral-evals/fixtures/claude-denied.jsonl +3 -0
- package/behavioral-evals/fixtures/claude-legacy.json +1 -0
- package/behavioral-evals/fixtures/claude-oral-only.jsonl +2 -0
- package/behavioral-evals/fixtures/claude-success.jsonl +5 -0
- package/behavioral-evals/fixtures/claude-truncated.jsonl +3 -0
- package/behavioral-evals/fixtures/opencode-success.jsonl +6 -0
- package/behavioral-evals/results/2026-08-31-baseline-add-feature.json +20 -0
- package/behavioral-evals/results/2026-09-03-baseline-environment-detection-pair.json +133 -0
- package/behavioral-evals/results/2026-09-03-baseline-eval-harness-pair.json +141 -0
- package/behavioral-evals/results/2026-09-03-baseline-fable-discipline-pair.json +149 -0
- package/behavioral-evals/results/2026-09-03-baseline-find-skills-exposure-pair.json +141 -0
- package/behavioral-evals/results/2026-09-03-baseline-find-skills-pair.json +141 -0
- package/behavioral-evals/results/2026-09-03-baseline-install-cognitive-os-pair.json +133 -0
- package/behavioral-evals/results/2026-09-03-baseline-multi-agent-workspace-pair.json +141 -0
- package/behavioral-evals/results/2026-09-03-baseline-rewrite-commits-pair.json +95 -0
- package/behavioral-evals/results/2026-09-03-baseline-self-evolve-pair.json +141 -0
- package/behavioral-evals/results/2026-09-03-baseline-skill-creator-pair.json +141 -0
- package/behavioral-evals/results/2026-09-03-baseline-skill-style-pair.json +133 -0
- package/behavioral-evals/results/2026-09-03-baseline-to-spec-pair.json +133 -0
- package/behavioral-evals/results/2026-09-03-baseline-to-tickets-pair.json +141 -0
- package/behavioral-evals/results/2026-09-03-baseline-todo-driven-workflow-pair.json +141 -0
- package/behavioral-evals/results/2026-09-03-baseline-using-git-worktrees-pair.json +133 -0
- package/behavioral-evals/results/2026-09-03-summary-both.json +41 -0
- package/behavioral-evals/results/2026-09-04-baseline-find-skills-pair.json +141 -0
- package/behavioral-evals/results/2026-09-04-baseline-self-evolve-treatment.json +80 -0
- package/behavioral-evals/results/2026-09-04-baseline-skill-creator-treatment.json +80 -0
- package/behavioral-evals/results/2026-09-04-summary-both.json +41 -0
- package/behavioral-evals/run-with-plugin.js +15 -31
- package/behavioral-evals/run.js +446 -105
- package/behavioral-evals/transcript-parser.js +591 -0
- package/bin/cli.js +24 -12
- package/ci/ab-test-cases/grill-me-adversarial.yaml +14 -4
- package/ci/ab-test-cases/tdd-test-first.yaml +13 -6
- package/ci/ab-test-cases/verification-loop-gates.yaml +20 -7
- package/ci/ab-test-cases/verify-before-claim-cites.yaml +7 -6
- package/ci/ab-test-harness.js +113 -38
- package/ci/action-gate-platform-doc.test.js +31 -0
- package/ci/action-gate.test.js +260 -0
- package/ci/cases/case2.json +2 -2
- package/ci/consistency-check.js +51 -5
- package/ci/description-collision.js +15 -6
- package/ci/disclosure-check.js +225 -0
- package/ci/doc-capability-consistency.test.js +180 -0
- package/ci/fixtures/phase4/parallel-disjoint.stages.json +32 -0
- package/ci/fixtures/phase4/parallel-read-only.stages.json +32 -0
- package/ci/installer-roundtrip.test.js +159 -0
- package/ci/invariant-routing.test.js +74 -0
- package/ci/issue99-runtime.test.js +218 -0
- package/ci/mechanism-2aa-codex-action-gate-policy.test.js +117 -0
- package/ci/mechanism-2ab-openai-plugin-sync-integrity.test.js +80 -0
- package/ci/mechanism-2j-behavioral-runner.test.js +43 -1
- package/ci/mechanism-2k-reference-check.test.js +57 -2
- package/ci/mechanism-2l-multi-agent-workspace.test.js +202 -6
- package/ci/mechanism-2m-self-repo-guard.test.js +65 -0
- package/ci/mechanism-2n-opencode-plugin.test.js +461 -0
- package/ci/mechanism-2o-state-home-isolation.test.js +183 -0
- package/ci/mechanism-2p-installer-links.test.js +183 -0
- package/ci/mechanism-2q-installer-global-shared-store.test.js +132 -0
- package/ci/mechanism-2r-uninstall-flags.test.js +148 -0
- package/ci/mechanism-2s-tdd-quality.test.js +203 -0
- package/ci/mechanism-2t-disclosure.test.js +90 -0
- package/ci/mechanism-2t-evidence-triage.test.js +200 -0
- package/ci/mechanism-2t-issue-20.test.js +75 -0
- package/ci/mechanism-2t-platform-skill-targets.test.js +164 -0
- package/ci/mechanism-2t-transcript-parser.test.js +179 -0
- package/ci/mechanism-2t-workspace-identity.test.js +200 -0
- package/ci/mechanism-2u-issue-42-regressions.test.js +239 -0
- package/ci/mechanism-2u-issue-44.test.js +35 -0
- package/ci/mechanism-2v-verifier-boundaries.test.js +138 -0
- package/ci/mechanism-2w-platform-compatibility.test.js +148 -0
- package/ci/mechanism-2x-release-automation.test.js +157 -0
- package/ci/mechanism-2x-release-commit-message.test.js +66 -0
- package/ci/mechanism-2y-release-tooling-compat.test.js +47 -0
- package/ci/mechanism-2z-claude-plugin-manifest.test.js +29 -0
- package/ci/mutation-check.js +53 -0
- package/ci/mutation-loader.js +57 -0
- package/ci/negative-controls.js +76 -46
- package/ci/openai-plugin-package.test.js +182 -0
- package/ci/openai-plugin-runtime.test.js +175 -0
- package/ci/openai-plugin-submission.test.js +101 -0
- package/ci/phase4-ensemble-memory.test.js +307 -0
- package/ci/reference-check.js +73 -25
- package/ci/release-consistency-check.js +24 -1
- package/ci/router-phase2-safety.test.js +81 -0
- package/ci/router-workflow-plan.test.js +264 -0
- package/ci/routing-check.js +6 -5
- package/ci/runner.js +6 -5
- package/ci/skill-routing-check.js +48 -11
- package/ci/test-behavioral-case-validation.js +62 -0
- package/ci/test-helper.js +10 -0
- package/ci/workflow-plan-consumers-edge.test.js +72 -0
- package/ci/workflow-plan-consumers.test.js +276 -0
- package/docs/architecture.md +137 -57
- package/docs/audit.md +22 -20
- package/docs/codex-action-gate.md +90 -0
- package/docs/issue-20-rollup.md +47 -0
- package/docs/mechanism-first-skill-mesh.md +22 -20
- package/docs/openai-plugin.md +118 -0
- package/docs/philosophy.md +28 -17
- package/docs/platform-capabilities.md +73 -0
- package/docs/platform-compatibility.json +270 -0
- package/docs/reflection.md +37 -22
- package/docs/release-evidence.md +25 -0
- package/docs/routing.md +157 -44
- package/docs/security-audit.md +38 -0
- package/docs/troubleshooting.md +82 -46
- package/docs/verifier-boundaries.md +45 -0
- package/docs/workflows/fable-mode.md +77 -21
- package/docs/workflows/find-skills.md +40 -0
- package/docs/workflows/grill-with-docs.md +2 -2
- package/docs/workflows/multi-agent-workspace.md +22 -10
- package/docs/workflows/self-evolve.md +33 -0
- package/docs/workflows/tdd.md +36 -0
- package/docs/workflows/todo-driven-workflow.md +33 -0
- package/environment-detection/SKILL.md +3 -3
- package/eval-harness/SKILL.md +7 -7
- package/eval-harness/scripts/evaluate.js +12 -10
- package/evals/evaluation-reports.md +14 -0
- package/evals/harness-everything/tasks/positive-tier1.yaml +1 -1
- package/evals/harness-everything/tasks/positive-tier2.yaml +1 -1
- package/evals/harness-everything/tasks/positive-tier3.yaml +1 -1
- package/evals/install-cognitive-os/tasks/positive-trigger.yaml +1 -1
- package/fable-discipline/SKILL.md +2 -2
- package/fable-mode/CONTRACT-FORMAT.md +147 -33
- package/fable-mode/SKILL.md +3 -3
- package/fable-mode/agents/fable-orchestrator.md +95 -35
- package/fable-mode/execution-guardrails/SKILL.md +2 -2
- package/fable-mode/fable-haiku/SKILL.md +3 -3
- package/fable-mode/fable-opus/SKILL.md +3 -3
- package/fable-mode/fable-sonnet/SKILL.md +3 -3
- package/fable-mode/scripts/ensemble-review.js +371 -0
- package/fable-mode/scripts/model-selector.js +134 -3
- package/fable-mode/scripts/workflow-plan-consumer.js +337 -0
- package/find-skills/SKILL.md +5 -5
- package/find-skills/references/discovery-flow.md +1 -1
- package/find-skills/scripts/use-skill.js +7 -1
- package/git-commit/SKILL.md +4 -11
- package/grill-me/SKILL.md +5 -5
- package/grill-with-docs/SKILL.md +7 -7
- package/grill-with-docs/references/session-playbook.md +6 -4
- package/harness-everything/SKILL.md +23 -22
- package/harness-everything/references/router-workflow-plan.md +128 -0
- package/harness-everything/references/triage-and-tiers.md +173 -96
- package/harness-everything/schemas/router-task-shape.schema.json +126 -0
- package/harness-everything/schemas/router-workflow-plan.schema.json +149 -0
- package/harness-everything/scripts/bootstrap.js +13 -4
- package/harness-everything/scripts/ensemble-policy.js +102 -0
- package/harness-everything/scripts/kernel-router.js +212 -0
- package/harness-everything/scripts/router-contract.js +612 -0
- package/harness-everything/scripts/self-heal.js +29 -20
- package/harness-everything/scripts/tier-router.js +346 -221
- package/hooks/hooks.json +60 -15
- package/hooks/scripts/action-gate-rules.json +77 -0
- package/hooks/scripts/action-gate.js +528 -0
- package/hooks/scripts/atomic-commit-check.js +5 -5
- package/hooks/scripts/codex-action-gate-post.js +46 -0
- package/hooks/scripts/codex-permission-request.js +140 -0
- package/hooks/scripts/context-compact.js +4 -3
- package/hooks/scripts/contract-test.js +118 -51
- package/hooks/scripts/lib/fable-contracts.js +121 -0
- package/hooks/scripts/lib/harness-state.js +164 -28
- package/hooks/scripts/lib/platforms/claude.js +45 -62
- package/hooks/scripts/lib/platforms/codex.js +35 -61
- package/hooks/scripts/lib/platforms/continue.js +7 -7
- package/hooks/scripts/lib/platforms/copilot.js +29 -53
- package/hooks/scripts/lib/platforms/hermes.js +71 -20
- package/hooks/scripts/openai-session-start.js +81 -0
- package/hooks/scripts/rule-of-3-tracker.js +9 -8
- package/hooks/scripts/rule-of-3.js +29 -21
- package/hooks/scripts/state-persist.js +5 -4
- package/hooks/scripts/stop-gate.js +2 -2
- package/hooks/scripts/subagent-scope-guard.js +144 -38
- package/hooks/scripts/subagent-start.js +19 -0
- package/hooks/scripts/subagent-stop.js +6 -0
- package/improve-codebase-architecture/SKILL.md +2 -4
- package/improve-codebase-architecture/references/README.md +16 -0
- package/install-cognitive-os/SKILL.md +28 -22
- package/multi-agent-workspace/SKILL.md +16 -15
- package/multi-agent-workspace/references/architecture-guide.md +13 -9
- package/multi-agent-workspace/references/orchestration.md +39 -22
- package/multi-agent-workspace/scripts/consume-workflow-plan.js +129 -0
- package/multi-agent-workspace/scripts/index_memory.js +79 -26
- package/multi-agent-workspace/scripts/path-boundary.js +59 -0
- package/multi-agent-workspace/scripts/project-docs-resolver.js +357 -0
- package/multi-agent-workspace/scripts/scaffold.js +258 -103
- package/multi-agent-workspace/templates/AGENTS.md +20 -0
- package/multi-agent-workspace/workflows/03-generation.md +8 -10
- package/multi-agent-workspace/workflows/04-launcher.md +5 -5
- package/opencode-plugin/README.md +114 -71
- package/opencode-plugin/hooks/verify.js +62 -12
- package/opencode-plugin/index.mjs +547 -0
- package/opencode-plugin/plugin.json +4 -12
- package/package.json +16 -3
- package/plugins/harness-everything/.codex-plugin/plugin.json +39 -0
- package/plugins/harness-everything/hooks/hooks.json +197 -0
- package/plugins/harness-everything/hooks/scripts/action-gate-rules.json +77 -0
- package/plugins/harness-everything/hooks/scripts/action-gate.js +528 -0
- package/plugins/harness-everything/hooks/scripts/atomic-commit-check.js +68 -0
- package/plugins/harness-everything/hooks/scripts/boundary-guard.js +55 -0
- package/plugins/harness-everything/hooks/scripts/codex-action-gate-post.js +46 -0
- package/plugins/harness-everything/hooks/scripts/codex-permission-request.js +140 -0
- package/plugins/harness-everything/hooks/scripts/context-compact.js +115 -0
- package/plugins/harness-everything/hooks/scripts/contract-test.js +146 -0
- package/plugins/harness-everything/hooks/scripts/depth-guard.js +65 -0
- package/plugins/harness-everything/hooks/scripts/lib/fable-contracts.js +121 -0
- package/plugins/harness-everything/hooks/scripts/lib/harness-state.js +275 -0
- package/plugins/harness-everything/hooks/scripts/lib/platforms/claude.js +168 -0
- package/plugins/harness-everything/hooks/scripts/lib/platforms/codex.js +88 -0
- package/plugins/harness-everything/hooks/scripts/lib/platforms/continue.js +91 -0
- package/plugins/harness-everything/hooks/scripts/lib/platforms/copilot.js +85 -0
- package/plugins/harness-everything/hooks/scripts/lib/platforms/cursor.js +89 -0
- package/plugins/harness-everything/hooks/scripts/lib/platforms/hermes.js +108 -0
- package/plugins/harness-everything/hooks/scripts/lib/platforms/index.js +17 -0
- package/plugins/harness-everything/hooks/scripts/lib/platforms/worktrees.js +20 -0
- package/plugins/harness-everything/hooks/scripts/lib/workspace.js +290 -0
- package/plugins/harness-everything/hooks/scripts/openai-session-start.js +81 -0
- package/plugins/harness-everything/hooks/scripts/reset-circuit-breaker.js +30 -0
- package/plugins/harness-everything/hooks/scripts/rule-of-3-tracker.js +125 -0
- package/plugins/harness-everything/hooks/scripts/rule-of-3.js +195 -0
- package/plugins/harness-everything/hooks/scripts/state-persist.js +100 -0
- package/plugins/harness-everything/hooks/scripts/stop-gate.js +80 -0
- package/plugins/harness-everything/hooks/scripts/subagent-scope-guard.js +194 -0
- package/plugins/harness-everything/hooks/scripts/subagent-start.js +19 -0
- package/plugins/harness-everything/hooks/scripts/subagent-stop.js +6 -0
- package/plugins/harness-everything/hooks/session-start.js +81 -0
- package/plugins/harness-everything/plugin.json +43 -0
- package/plugins/harness-everything/scripts/lib/advisory-text.js +174 -0
- package/plugins/harness-everything/skills/environment-detection/SKILL.md +41 -0
- package/plugins/harness-everything/skills/environment-detection/references/shell-syntax-rules.md +76 -0
- package/plugins/harness-everything/skills/environment-detection/scripts/preflight.js +103 -0
- package/plugins/harness-everything/skills/eval-harness/SKILL.md +42 -0
- package/plugins/harness-everything/skills/eval-harness/references/scoring-rubric.md +68 -0
- package/plugins/harness-everything/skills/eval-harness/scripts/evaluate.js +79 -0
- package/plugins/harness-everything/skills/fable-discipline/SKILL.md +41 -0
- package/plugins/harness-everything/skills/fable-discipline/references/discipline-rules.md +22 -0
- package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +165 -0
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +39 -0
- package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +159 -0
- package/plugins/harness-everything/skills/fable-mode/agents/fable-verifier.md +32 -0
- package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-haiku.md +52 -0
- package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-sonnet.md +53 -0
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +43 -0
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/references/guardrail-rules.md +56 -0
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +36 -0
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/references/routing-notes.md +26 -0
- package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +36 -0
- package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +36 -0
- package/plugins/harness-everything/skills/fable-mode/model-matrix.json +32 -0
- package/plugins/harness-everything/skills/fable-mode/references/execution-phases.md +37 -0
- package/plugins/harness-everything/skills/fable-mode/references/model-matrix.md +39 -0
- package/plugins/harness-everything/skills/fable-mode/scripts/ensemble-review.js +371 -0
- package/plugins/harness-everything/skills/fable-mode/scripts/model-selector.js +260 -0
- package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +337 -0
- package/plugins/harness-everything/skills/find-skills/SKILL.md +48 -0
- package/plugins/harness-everything/skills/find-skills/references/discovery-flow.md +102 -0
- package/plugins/harness-everything/skills/find-skills/scripts/use-skill.js +112 -0
- package/plugins/harness-everything/skills/git-commit/SKILL.md +38 -0
- package/plugins/harness-everything/skills/git-commit/guides/ANGULAR_STYLE.md +61 -0
- package/plugins/harness-everything/skills/git-commit/guides/COMMIT_GENERATION.md +24 -0
- package/plugins/harness-everything/skills/git-commit/guides/LANGUAGE_DETECTION.md +16 -0
- package/plugins/harness-everything/skills/git-commit/guides/MAIN_REPO.md +15 -0
- package/plugins/harness-everything/skills/git-commit/guides/SUBMODULES.md +27 -0
- package/plugins/harness-everything/skills/git-commit/references/commit-flow.md +30 -0
- package/plugins/harness-everything/skills/grill-me/SKILL.md +43 -0
- package/plugins/harness-everything/skills/grill-me/references/grilling-playbook.md +38 -0
- package/plugins/harness-everything/skills/grill-with-docs/ADR-FORMAT.md +47 -0
- package/plugins/harness-everything/skills/grill-with-docs/CONTEXT-FORMAT.md +63 -0
- package/plugins/harness-everything/skills/grill-with-docs/SKILL.md +41 -0
- package/plugins/harness-everything/skills/grill-with-docs/references/session-playbook.md +99 -0
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +43 -0
- package/plugins/harness-everything/skills/harness-everything/examples/README.md +9 -0
- package/plugins/harness-everything/skills/harness-everything/examples/todo-workflow.md +22 -0
- package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +128 -0
- package/plugins/harness-everything/skills/harness-everything/references/skill-registry.md +39 -0
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +193 -0
- package/plugins/harness-everything/skills/harness-everything/schemas/router-task-shape.schema.json +126 -0
- package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +149 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/bootstrap.js +212 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/ensemble-policy.js +102 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +212 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +612 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +320 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/self-heal.js +250 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +459 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/verify-gate.js +100 -0
- package/plugins/harness-everything/skills/improve-codebase-architecture/SKILL.md +38 -0
- package/plugins/harness-everything/skills/improve-codebase-architecture/guides/DEEPENING.md +37 -0
- package/plugins/harness-everything/skills/improve-codebase-architecture/guides/HTML-REPORT.md +123 -0
- package/plugins/harness-everything/skills/improve-codebase-architecture/guides/INTERFACE-DESIGN.md +44 -0
- package/plugins/harness-everything/skills/improve-codebase-architecture/guides/LANGUAGE.md +53 -0
- package/plugins/harness-everything/skills/improve-codebase-architecture/guides/refactoring-workflow.md +59 -0
- package/plugins/harness-everything/skills/improve-codebase-architecture/references/README.md +16 -0
- package/plugins/harness-everything/skills/install-cognitive-os/SKILL.md +44 -0
- package/plugins/harness-everything/skills/install-cognitive-os/references/cognitive-loop.md +27 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/NOTICE-agency-agents.md +27 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +38 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/examples/csharp-monorepo.md +123 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/examples/typescript-cli.md +126 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-claude.md +77 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-codex.md +146 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-continue.md +38 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-copilot.md +74 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-cursor.md +40 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-hermes.md +38 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/role-capabilities.md +108 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/references/agency-agents.md +31 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/references/architecture-guide.md +36 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/references/orchestration.md +65 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/agency-catalog.js +190 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/consume-workflow-plan.js +129 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/index_memory.js +104 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/path-boundary.js +59 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/project-docs-resolver.js +357 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/scaffold.js +408 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/AGENTS.md +20 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/README.md +17 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/agent-launcher.md +68 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/backend-developer-csharp.md +56 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/backend-developer-polyglot.md +62 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/backend-developer-ts.md +57 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/memory-keeper.md +37 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/requirement-analyzer.md +66 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/specialist-agent.md +34 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/task-verifier.md +56 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/templates/tool-maker.md +33 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/workflows/01-init.md +22 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/workflows/02-analysis.md +23 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/workflows/03-generation.md +25 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/workflows/04-launcher.md +16 -0
- package/plugins/harness-everything/skills/repo-docs/SKILL.md +42 -0
- package/plugins/harness-everything/skills/repo-docs/references/process-guide.md +60 -0
- package/plugins/harness-everything/skills/repo-docs/templates/agents-template.md +264 -0
- package/plugins/harness-everything/skills/repo-docs/templates/knowledge-agents-template.md +50 -0
- package/plugins/harness-everything/skills/repo-docs/templates/knowledge-readme-template.md +83 -0
- package/plugins/harness-everything/skills/repo-docs/templates/manual-recon.md +117 -0
- package/plugins/harness-everything/skills/repo-docs/templates/multi-skills-readme-template.md +72 -0
- package/plugins/harness-everything/skills/repo-docs/templates/product-agents-template.md +78 -0
- package/plugins/harness-everything/skills/repo-docs/templates/product-readme-template.md +95 -0
- package/plugins/harness-everything/skills/repo-docs/templates/readme-template.md +136 -0
- package/plugins/harness-everything/skills/repo-docs/templates/single-skill-readme-template.md +70 -0
- package/plugins/harness-everything/skills/repo-docs/templates/skills-agents-template.md +60 -0
- package/plugins/harness-everything/skills/rewrite-commits/SKILL.md +37 -0
- package/plugins/harness-everything/skills/rewrite-commits/references/workflow.md +18 -0
- package/plugins/harness-everything/skills/security-review/SKILL.md +47 -0
- package/plugins/harness-everything/skills/security-review/guides/OWASP-PATTERNS.md +31 -0
- package/plugins/harness-everything/skills/security-review/guides/STRIDE-THREAT-MODEL.md +21 -0
- package/plugins/harness-everything/skills/security-review/references/security-checklist.md +478 -0
- package/plugins/harness-everything/skills/security-review/scripts/audit-secrets.js +85 -0
- package/plugins/harness-everything/skills/self-evolve/SKILL.md +45 -0
- package/plugins/harness-everything/skills/self-evolve/references/memory-resolution.md +61 -0
- package/plugins/harness-everything/skills/self-evolve/scripts/persist-memory.js +272 -0
- package/plugins/harness-everything/skills/self-evolve/scripts/register-dynamic-skill.js +253 -0
- package/plugins/harness-everything/skills/self-evolve/scripts/self-regression.js +125 -0
- package/plugins/harness-everything/skills/skill-creator/SKILL.md +47 -0
- package/plugins/harness-everything/skills/skill-creator/references/authoring-workflow.md +14 -0
- package/plugins/harness-everything/skills/skill-creator/references/dynamic-generation-contract.md +21 -0
- package/plugins/harness-everything/skills/skill-creator/references/quality-checklist.md +16 -0
- package/plugins/harness-everything/skills/skill-creator/references/quality-principles.md +81 -0
- package/plugins/harness-everything/skills/skill-creator/references/testing-workflow.md +27 -0
- package/plugins/harness-everything/skills/skill-style/SKILL.md +42 -0
- package/plugins/harness-everything/skills/skill-style/references/style-guide.md +62 -0
- package/plugins/harness-everything/skills/tdd/SKILL.md +38 -0
- package/plugins/harness-everything/skills/tdd/fixtures/pass-evidence.json +100 -0
- package/plugins/harness-everything/skills/tdd/fixtures/quality-scenarios.json +20 -0
- package/plugins/harness-everything/skills/tdd/guides/deep-modules.md +33 -0
- package/plugins/harness-everything/skills/tdd/guides/interface-design.md +31 -0
- package/plugins/harness-everything/skills/tdd/guides/mocking.md +59 -0
- package/plugins/harness-everything/skills/tdd/guides/refactoring.md +10 -0
- package/plugins/harness-everything/skills/tdd/guides/tests.md +61 -0
- package/plugins/harness-everything/skills/tdd/references/core-discipline.md +51 -0
- package/plugins/harness-everything/skills/tdd/references/integration-testing.md +13 -0
- package/plugins/harness-everything/skills/tdd/references/quality-model.md +68 -0
- package/plugins/harness-everything/skills/tdd/references/unit-testing.md +15 -0
- package/plugins/harness-everything/skills/tdd/schemas/quality-evidence.schema.json +211 -0
- package/plugins/harness-everything/skills/tdd/schemas/quality-report.schema.json +110 -0
- package/plugins/harness-everything/skills/tdd/scripts/quality-gate.js +475 -0
- package/plugins/harness-everything/skills/to-spec/SKILL.md +40 -0
- package/plugins/harness-everything/skills/to-spec/references/process.md +77 -0
- package/plugins/harness-everything/skills/to-spec/scripts/check-project-docs.js +239 -0
- package/plugins/harness-everything/skills/to-spec/scripts/project-docs-resolver.js +111 -0
- package/plugins/harness-everything/skills/to-spec/templates/cli-reference.md +31 -0
- package/plugins/harness-everything/skills/to-spec/templates/dev-doc.md +27 -0
- package/plugins/harness-everything/skills/to-spec/templates/feature-spec.md +59 -0
- package/plugins/harness-everything/skills/to-spec/templates/schema-doc.md +31 -0
- package/plugins/harness-everything/skills/to-tickets/SKILL.md +42 -0
- package/plugins/harness-everything/skills/to-tickets/references/publishing-and-templates.md +97 -0
- package/plugins/harness-everything/skills/todo-driven-workflow/SKILL.md +42 -0
- package/plugins/harness-everything/skills/todo-driven-workflow/references/execution-guide.md +27 -0
- package/plugins/harness-everything/skills/using-git-worktrees/SKILL.md +49 -0
- package/plugins/harness-everything/skills/using-git-worktrees/references/workflow-details.md +108 -0
- package/plugins/harness-everything/skills/verification-loop/SKILL.md +46 -0
- package/plugins/harness-everything/skills/verification-loop/references/verification-phases.md +116 -0
- package/plugins/harness-everything/skills/verification-loop/templates/verification-report.template.md +15 -0
- package/plugins/harness-everything/skills/verify-before-claim/SKILL.md +43 -0
- package/plugins/harness-everything/skills/verify-before-claim/references/verification-guide.md +73 -0
- package/plugins/harness-everything/skills/zoom-out/SKILL.md +42 -0
- package/plugins/harness-everything/skills/zoom-out/references/circuit-breaker.md +66 -0
- package/plugins/harness-everything/skills/zoom-out/templates/zoom-out-report.template.md +20 -0
- package/repo-docs/SKILL.md +3 -7
- package/rewrite-commits/SKILL.md +2 -2
- package/scripts/build-openai-submission.js +158 -0
- package/scripts/installer.js +86 -11
- package/scripts/lib/advisory-text.js +25 -18
- package/scripts/lib/execution-contract.js +70 -0
- package/scripts/lib/manifest.js +34 -38
- package/scripts/lib/path-boundary.js +90 -0
- package/scripts/lib/skills.js +124 -70
- package/scripts/lib/workspace.js +269 -7
- package/scripts/sync-openai-plugin.js +91 -0
- package/scripts/sync-release-version.js +212 -0
- package/security-review/SKILL.md +4 -7
- package/security-review/scripts/audit-secrets.js +9 -8
- package/self-evolve/SKILL.md +9 -11
- package/self-evolve/references/memory-resolution.md +4 -1
- package/self-evolve/scripts/persist-memory.js +221 -88
- package/self-evolve/scripts/register-dynamic-skill.js +12 -10
- package/self-evolve/scripts/self-regression.js +23 -8
- package/skill-creator/SKILL.md +12 -14
- package/skill-creator/references/quality-checklist.md +1 -0
- package/skill-style/SKILL.md +4 -4
- package/skill-style/references/style-guide.md +29 -3
- package/submission/openai/README.md +67 -0
- package/submission/openai/listing.json +31 -0
- package/submission/openai/test-cases.json +61 -0
- package/tdd/SKILL.md +20 -26
- package/tdd/fixtures/pass-evidence.json +100 -0
- package/tdd/fixtures/quality-scenarios.json +20 -0
- package/tdd/references/core-discipline.md +2 -0
- package/tdd/references/integration-testing.md +13 -0
- package/tdd/references/quality-model.md +68 -0
- package/tdd/references/unit-testing.md +15 -0
- package/tdd/schemas/quality-evidence.schema.json +211 -0
- package/tdd/schemas/quality-report.schema.json +110 -0
- package/tdd/scripts/quality-gate.js +475 -0
- package/to-spec/SKILL.md +3 -5
- package/to-spec/scripts/check-project-docs.js +42 -62
- package/to-spec/scripts/project-docs-resolver.js +111 -0
- package/to-tickets/SKILL.md +4 -4
- package/todo-driven-workflow/SKILL.md +6 -4
- package/using-git-worktrees/SKILL.md +3 -3
- package/verification-loop/SKILL.md +4 -4
- package/verify-before-claim/SKILL.md +2 -2
- package/zoom-out/SKILL.md +4 -4
- package/ab-test-deep-analysis-2026-08-27.md +0 -383
- package/ab-test-results-2026-08-27.md +0 -152
- package/ab-test-statistical-analysis-31cases.md +0 -193
- package/opencode-plugin/hooks/circuit-breaker.js +0 -121
- package/opencode-plugin/hooks/compliance.js +0 -128
- package/opencode-plugin/hooks/post-edit.js +0 -62
- package/opencode-plugin/hooks/pre-complete.js +0 -65
- package/opencode-plugin/test-hook-firing.js +0 -198
package/behavioral-evals/run.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 1. Build identical case fixtures in OS temp workspaces.
|
|
14
14
|
* 2. Run a control with no Harness files and a treatment with only the
|
|
15
15
|
* case's named skill loaded; `--arm both` randomizes this order.
|
|
16
|
-
* 3. Run: claude -p "<prompt>" --output-format json --max-turns N
|
|
16
|
+
* 3. Run: claude -p "<prompt>" --output-format stream-json --verbose --max-turns N
|
|
17
17
|
* 4. Grade expectations[] against transcript text and workspace state.
|
|
18
18
|
* 5. Write paired results with fixture/prompt hashes and attribution so any
|
|
19
19
|
* grader verdict can be audited by a human.
|
|
@@ -23,6 +23,11 @@ const os = require('os');
|
|
|
23
23
|
const path = require('path');
|
|
24
24
|
const crypto = require('crypto');
|
|
25
25
|
const { execFileSync } = require('child_process');
|
|
26
|
+
const {
|
|
27
|
+
gradeExecutionEvidence,
|
|
28
|
+
parseTranscriptFile,
|
|
29
|
+
} = require('./transcript-parser');
|
|
30
|
+
const { commandMatches, normalizeRepoPath } = require('../scripts/lib/execution-contract');
|
|
26
31
|
|
|
27
32
|
const ROOT = path.resolve(__dirname, '..');
|
|
28
33
|
const CASES_DIR = path.join(__dirname, 'cases');
|
|
@@ -32,7 +37,7 @@ const RESULTS_DIR = path.join(__dirname, 'results');
|
|
|
32
37
|
// Minimal YAML subset parser for our case files (flat maps, lists of maps,
|
|
33
38
|
// block scalars via |). Avoids adding dependencies to this package.
|
|
34
39
|
function parseSimpleYaml(text) {
|
|
35
|
-
const lines = text.split('\n');
|
|
40
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n');
|
|
36
41
|
let i = 0;
|
|
37
42
|
function parseBlock(indent) {
|
|
38
43
|
const obj = {};
|
|
@@ -141,7 +146,10 @@ const PRESSURE_RULES = {
|
|
|
141
146
|
// --- validate mode ----------------------------------------------------------
|
|
142
147
|
function validate(cases) {
|
|
143
148
|
let failures = 0;
|
|
144
|
-
const EXPECT_TYPES = new Set([
|
|
149
|
+
const EXPECT_TYPES = new Set([
|
|
150
|
+
'trace_contains', 'trace_not_contains', 'file_contains', 'file_not_exists', 'command_exit_0',
|
|
151
|
+
'tool_call', 'tool_attempted', 'tool_completed', 'tool_executed', 'tool_denied', 'execution_evidence',
|
|
152
|
+
]);
|
|
145
153
|
for (const c of cases) {
|
|
146
154
|
const problems = [];
|
|
147
155
|
if (!c.id) problems.push('missing id');
|
|
@@ -155,7 +163,9 @@ function validate(cases) {
|
|
|
155
163
|
if (!resolveTreatmentSkills(c).length) problems.push('treatment must load at least one named Harness skill');
|
|
156
164
|
for (const e of c.expectations || []) {
|
|
157
165
|
if (!EXPECT_TYPES.has(e.type)) problems.push(`unknown expectation type "${e.type}"`);
|
|
158
|
-
if (e.value === undefined && e.command === undefined
|
|
166
|
+
if (e.value === undefined && e.command === undefined && e.tool === undefined && e.name === undefined && e.count === undefined) {
|
|
167
|
+
problems.push(`expectation ${e.type} needs value, tool, name, count, or command`);
|
|
168
|
+
}
|
|
159
169
|
}
|
|
160
170
|
if (c.pressure && !/skip|don't|not|quick|minutes/i.test(c.prompt)) {
|
|
161
171
|
problems.push('pressure case prompt does not read as pressure');
|
|
@@ -180,8 +190,16 @@ function validate(cases) {
|
|
|
180
190
|
|
|
181
191
|
// --- live run mode -----------------------------------------------------------
|
|
182
192
|
function buildWorkspace(c) {
|
|
193
|
+
if (!c || !c.fixture || !Array.isArray(c.fixture.files)) throw new Error('fixture.files must be a non-empty array');
|
|
194
|
+
const fixtureFiles = c.fixture.files.map((file, index) => {
|
|
195
|
+
const relative = normalizeRepoPath(file && file.path);
|
|
196
|
+
if (!relative || relative === '.git' || relative.startsWith('.git/')) {
|
|
197
|
+
throw new Error(`fixture.files[${index}] path must stay inside the generated workspace`);
|
|
198
|
+
}
|
|
199
|
+
return { ...file, path: relative };
|
|
200
|
+
});
|
|
183
201
|
const ws = fs.mkdtempSync(path.join(os.tmpdir(), `harness-behavioral-${c.id}-`));
|
|
184
|
-
for (const f of
|
|
202
|
+
for (const f of fixtureFiles) {
|
|
185
203
|
const target = path.join(ws, f.path);
|
|
186
204
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
187
205
|
fs.writeFileSync(target, typeof f.content === 'string' ? f.content.replace(/\n$/, '') + '\n' : String(f.content));
|
|
@@ -260,21 +278,74 @@ function buildEngineInvocation(engine, prompt, ws, maxTurns, arm = 'treatment')
|
|
|
260
278
|
args.push(prompt);
|
|
261
279
|
return { command: 'opencode', args };
|
|
262
280
|
}
|
|
263
|
-
|
|
281
|
+
// acceptEdits only auto-approves file edits, not Bash/PowerShell tool calls -
|
|
282
|
+
// a headless -p session has no human to answer those prompts, so any case
|
|
283
|
+
// needing a shell command (git, npm, node scripts) stalled with every tool
|
|
284
|
+
// call denied and the model just describing what it would do. This harness
|
|
285
|
+
// needs the same full autonomy opencode's --auto gives it, in the same kind
|
|
286
|
+
// of disposable os.tmpdir() fixture workspace.
|
|
287
|
+
const args = ['-p', prompt, '--output-format', 'stream-json', '--verbose', '--max-turns', String(maxTurns), '--dangerously-skip-permissions', '--setting-sources', 'project,local'];
|
|
288
|
+
const model = process.env.BEHAVIORAL_MODEL;
|
|
289
|
+
if (model) args.push('--model', model);
|
|
264
290
|
if (arm === 'baseline') args.push('--safe-mode');
|
|
265
291
|
return { command: 'claude', args };
|
|
266
292
|
}
|
|
267
293
|
|
|
294
|
+
function expandWindowsBatchPath(value, batchPath) {
|
|
295
|
+
const base = path.dirname(batchPath) + path.sep;
|
|
296
|
+
return value
|
|
297
|
+
.replace(/%~dp0/gi, base)
|
|
298
|
+
.replace(/%dp0%/gi, base)
|
|
299
|
+
.replace(/^"|"$/g, '')
|
|
300
|
+
.trim();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function windowsCommandCandidates(command) {
|
|
304
|
+
try {
|
|
305
|
+
return execFileSync('where.exe', [command], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
306
|
+
.split(/\r?\n/)
|
|
307
|
+
.map(line => line.trim())
|
|
308
|
+
.filter(line => line && !/^INFO:/i.test(line));
|
|
309
|
+
} catch {
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function resolveCliInvocation(invocation) {
|
|
315
|
+
if (process.platform !== 'win32') return invocation;
|
|
316
|
+
const candidates = windowsCommandCandidates(invocation.command);
|
|
317
|
+
const native = candidates.find(candidate => /\.exe$/i.test(candidate));
|
|
318
|
+
if (native) return { command: native, args: invocation.args.slice() };
|
|
319
|
+
const shim = candidates.find(candidate => /\.(?:cmd|bat)$/i.test(candidate));
|
|
320
|
+
if (shim) {
|
|
321
|
+
let source = '';
|
|
322
|
+
try { source = fs.readFileSync(shim, 'utf8'); } catch { source = ''; }
|
|
323
|
+
const nodeEntry = source.match(/(?:^|\s)(?:node(?:\.exe)?)(?:\s+)(?:"([^"]+\.js)"|([^\s]+\.js))/im);
|
|
324
|
+
if (nodeEntry) {
|
|
325
|
+
const entry = expandWindowsBatchPath(nodeEntry[1] || nodeEntry[2], shim);
|
|
326
|
+
return { command: process.execPath, args: [entry, ...invocation.args] };
|
|
327
|
+
}
|
|
328
|
+
const binaryEntry = source.match(/"?([^"\r\n]+\.exe)"?\s+%\*/i);
|
|
329
|
+
if (binaryEntry) {
|
|
330
|
+
const entry = expandWindowsBatchPath(binaryEntry[1], shim);
|
|
331
|
+
return { command: entry, args: invocation.args.slice() };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const direct = candidates.find(candidate => !/\.(?:cmd|bat|ps1)$/i.test(candidate));
|
|
335
|
+
return direct ? { command: direct, args: invocation.args.slice() } : invocation;
|
|
336
|
+
}
|
|
337
|
+
|
|
268
338
|
function runHeadless(prompt, ws, maxTurns, engine, arm) {
|
|
269
339
|
// Keep the transcript OUTSIDE the workspace: grader artifacts must never
|
|
270
340
|
// show up in git-status-based scope checks.
|
|
271
|
-
const outPath = path.join(path.dirname(ws), path.basename(ws) + '.transcript.
|
|
341
|
+
const outPath = path.join(path.dirname(ws), path.basename(ws) + '.transcript.jsonl');
|
|
272
342
|
const invocation = buildEngineInvocation(engine, prompt, ws, maxTurns, arm);
|
|
343
|
+
const executable = resolveCliInvocation(invocation);
|
|
273
344
|
const outputFd = fs.openSync(outPath, 'w');
|
|
274
345
|
try {
|
|
275
346
|
// Pass the prompt as one argv element. A shell wrapper on Windows can
|
|
276
347
|
// reinterpret spaces and punctuation, truncating prompts to `Add.`.
|
|
277
|
-
execFileSync(
|
|
348
|
+
execFileSync(executable.command, executable.args, {
|
|
278
349
|
cwd: ws,
|
|
279
350
|
stdio: ['ignore', outputFd, 'inherit'],
|
|
280
351
|
timeout: engine === 'opencode' ? 20 * 60 * 1000 : 15 * 60 * 1000,
|
|
@@ -290,92 +361,299 @@ function runHeadless(prompt, ws, maxTurns, engine, arm) {
|
|
|
290
361
|
// Tool outputs embed installed skill text, so keyword graders over raw
|
|
291
362
|
// transcripts produce false positives (a read of zoom-out/SKILL.md counts as
|
|
292
363
|
// "invoked zoom-out"). Trace = assistant text + tool inputs only.
|
|
293
|
-
|
|
364
|
+
const COMPLETED_TOOL_STATUSES = new Set(['completed', 'complete', 'success', 'succeeded', 'done', 'finished']);
|
|
365
|
+
const DENIED_TOOL_STATUSES = new Set(['denied', 'rejected', 'blocked', 'cancelled', 'canceled', 'error', 'failed']);
|
|
366
|
+
|
|
367
|
+
function firstDefined(...values) {
|
|
368
|
+
return values.find(value => value !== undefined && value !== null);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function eventPart(event) {
|
|
372
|
+
return event && event.part && typeof event.part === 'object' ? event.part : event;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function eventType(event) {
|
|
376
|
+
return String(event && event.type || '').toLowerCase();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function eventStatus(event, part) {
|
|
380
|
+
const state = part && part.state && typeof part.state === 'object' ? part.state : {};
|
|
381
|
+
return String(firstDefined(state.status, part && part.status, event && event.status, '')).toLowerCase();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function eventToolName(event, part) {
|
|
385
|
+
const state = part && part.state && typeof part.state === 'object' ? part.state : {};
|
|
386
|
+
return String(firstDefined(part && part.tool, part && part.name, state.tool, event && event.tool, event && event.name, 'tool'));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function eventInput(event, part) {
|
|
390
|
+
const state = part && part.state && typeof part.state === 'object' ? part.state : {};
|
|
391
|
+
return firstDefined(state.input, part && part.input, part && part.arguments, event && event.input, event && event.arguments);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function eventId(event, part) {
|
|
395
|
+
const state = part && part.state && typeof part.state === 'object' ? part.state : {};
|
|
396
|
+
return firstDefined(part && (part.callID || part.callId || part.id || part.tool_use_id), state.callID, state.callId, event && (event.callID || event.callId || event.id || event.tool_use_id));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function isToolEvent(event, part) {
|
|
400
|
+
const type = eventType(event);
|
|
401
|
+
const partType = String(part && part.type || '').toLowerCase();
|
|
402
|
+
return type.includes('tool') || partType === 'tool' || partType === 'tool_use' ||
|
|
403
|
+
eventToolName(event, part) !== 'tool' || eventInput(event, part) !== undefined;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function isEditOperation(tool, input) {
|
|
407
|
+
const name = String(tool || '').toLowerCase();
|
|
408
|
+
if (/(^|[-_])(edit|write|patch|create|delete|move|rename)([-_]|$)/.test(name) ||
|
|
409
|
+
/^(edit|write|apply_patch|create_file|delete_file|move_file|rename_file)$/.test(name)) return true;
|
|
410
|
+
const command = input && typeof input === 'object' ? firstDefined(input.command, input.cmd, input.script) : input;
|
|
411
|
+
if (typeof command === 'string' && /(?:>>?|\btee\b|\bset-content\b|\bout-file\b|\bwritefile\b|\brename(?:-item)?\b|\bremove(?:-item)?\b|\b(?:sed|perl)\b[^\r\n]*\s-i\b)/i.test(command)) return true;
|
|
412
|
+
const target = input && typeof input === 'object'
|
|
413
|
+
? firstDefined(input.file_path, input.filePath, input.path, input.filename)
|
|
414
|
+
: null;
|
|
415
|
+
return typeof target === 'string' && /\.(?:patch|diff)$/i.test(target);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function executionState(status, type) {
|
|
419
|
+
const normalizedType = String(type || '').toLowerCase();
|
|
420
|
+
if (DENIED_TOOL_STATUSES.has(status) || normalizedType.includes('denied') || normalizedType.includes('rejected')) return 'denied';
|
|
421
|
+
if (status && !COMPLETED_TOOL_STATUSES.has(status)) return 'attempted';
|
|
422
|
+
if (COMPLETED_TOOL_STATUSES.has(status) || (!status && (normalizedType.includes('result') || normalizedType.includes('completed')))) return 'executed';
|
|
423
|
+
return 'attempted';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Keep a second, event-oriented view for ordered execution and edit evidence.
|
|
427
|
+
// transcript-parser.js remains the authoritative command-aware grader.
|
|
428
|
+
function parseTranscriptEvents(transcriptPath) {
|
|
294
429
|
const raw = fs.readFileSync(transcriptPath, 'utf8');
|
|
295
|
-
const
|
|
430
|
+
const events = [];
|
|
431
|
+
const pendingById = new Map();
|
|
432
|
+
let sequence = 0;
|
|
433
|
+
const addText = text => {
|
|
434
|
+
if (typeof text === 'string' && text) events.push({ kind: 'text', text, sequence: sequence++ });
|
|
435
|
+
};
|
|
436
|
+
const addTool = (event, part) => {
|
|
437
|
+
const type = eventType(event);
|
|
438
|
+
const input = eventInput(event, part) ?? {};
|
|
439
|
+
const tool = eventToolName(event, part);
|
|
440
|
+
const id = eventId(event, part);
|
|
441
|
+
const state = executionState(eventStatus(event, part), type);
|
|
442
|
+
const existing = id && pendingById.get(String(id));
|
|
443
|
+
if (existing) {
|
|
444
|
+
if (input && Object.keys(input).length) existing.input = input;
|
|
445
|
+
existing.tool = tool !== 'tool' ? tool : existing.tool;
|
|
446
|
+
if (state === 'executed' || state === 'denied') existing.state = state;
|
|
447
|
+
existing.executed = existing.state === 'executed';
|
|
448
|
+
existing.denied = existing.state === 'denied';
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const item = {
|
|
452
|
+
kind: 'tool', tool, input, id: id === undefined ? null : String(id), state,
|
|
453
|
+
attempted: true, executed: state === 'executed', denied: state === 'denied',
|
|
454
|
+
edit: isEditOperation(tool, input), sequence: sequence++, raw_type: type,
|
|
455
|
+
};
|
|
456
|
+
events.push(item);
|
|
457
|
+
if (id !== undefined) pendingById.set(String(id), item);
|
|
458
|
+
};
|
|
459
|
+
const visit = (value, includeText = true) => {
|
|
460
|
+
if (!value || typeof value !== 'object') return;
|
|
461
|
+
if (Array.isArray(value)) { value.forEach(item => visit(item, includeText)); return; }
|
|
462
|
+
const part = eventPart(value);
|
|
463
|
+
const type = eventType(value);
|
|
464
|
+
if (type === 'text' || String(part && part.type || '').toLowerCase() === 'text') {
|
|
465
|
+
if (includeText) addText(part && part.text !== undefined ? part.text : value.text);
|
|
466
|
+
if (value.part || !value.message) return;
|
|
467
|
+
}
|
|
468
|
+
if (value.part || isToolEvent(value, part)) {
|
|
469
|
+
if (isToolEvent(value, part)) addTool(value, part);
|
|
470
|
+
if (value.part) return;
|
|
471
|
+
}
|
|
472
|
+
if (value.message && value.message.content) visit(value.message.content, value.type !== 'user');
|
|
473
|
+
if (value.content && value.content !== value.message) visit(value.content, includeText);
|
|
474
|
+
if (!value.part && typeof value.result === 'string' && includeText) addText(value.result);
|
|
475
|
+
};
|
|
296
476
|
let structured = false;
|
|
297
477
|
for (const line of raw.split('\n')) {
|
|
298
478
|
if (!line.trim()) continue;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
structured = true;
|
|
306
|
-
} else if (String(e.type).includes('tool')) {
|
|
307
|
-
const input = p.state && p.state.input !== undefined ? p.state.input : p.input;
|
|
308
|
-
chunks.push(`[${p.tool || 'tool'}] ` + JSON.stringify(input ?? {}));
|
|
309
|
-
structured = true;
|
|
310
|
-
}
|
|
479
|
+
try {
|
|
480
|
+
const value = JSON.parse(line);
|
|
481
|
+
const before = events.length;
|
|
482
|
+
visit(value);
|
|
483
|
+
if (events.length > before) structured = true;
|
|
484
|
+
} catch { /* malformed/non-JSON lines are retained only by the raw fallback */ }
|
|
311
485
|
}
|
|
312
|
-
// claude -p --output-format json emits one object; grade its result text.
|
|
313
486
|
if (!structured) {
|
|
314
487
|
try {
|
|
315
|
-
const
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
488
|
+
const value = JSON.parse(raw);
|
|
489
|
+
if (value && typeof value.result === 'string') addText(value.result);
|
|
490
|
+
else visit(value);
|
|
491
|
+
} catch { /* raw fallback handled by renderAgentTrace */ }
|
|
319
492
|
}
|
|
320
|
-
return
|
|
493
|
+
return { raw, structured: events.length > 0, events };
|
|
321
494
|
}
|
|
322
495
|
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
496
|
+
function renderAgentTrace(parsed) {
|
|
497
|
+
if (!parsed.structured) return parsed.raw;
|
|
498
|
+
return parsed.events.map(event => event.kind === 'text'
|
|
499
|
+
? event.text
|
|
500
|
+
: `[${event.tool}] ${JSON.stringify(event.input ?? {})}`).join('\n');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function extractAgentEvents(transcriptPath) {
|
|
504
|
+
return parseTranscriptEvents(transcriptPath).events;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function extractAgentTrace(transcriptPath) {
|
|
508
|
+
return renderAgentTrace(parseTranscriptEvents(transcriptPath));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function toolTarget(expectation) {
|
|
512
|
+
if (expectation.value && typeof expectation.value === 'object') return expectation.value;
|
|
513
|
+
if (expectation.command !== undefined) return { command: expectation.command };
|
|
514
|
+
if (expectation.tool !== undefined) return { tool: expectation.tool };
|
|
515
|
+
if (expectation.name !== undefined) return { tool: expectation.name };
|
|
516
|
+
if (typeof expectation.value === 'string') return { command: expectation.value };
|
|
517
|
+
if (typeof expectation.value === 'number') return { tool: String(expectation.value) };
|
|
518
|
+
return {};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function toolMatches(event, target) {
|
|
522
|
+
if (!event || event.kind !== 'tool') return false;
|
|
523
|
+
const expectedTool = target.tool || target.name;
|
|
524
|
+
if (expectedTool && String(event.tool).toLowerCase() !== String(expectedTool).toLowerCase()) return false;
|
|
525
|
+
if (target.command !== undefined) {
|
|
526
|
+
const actual = event.input && typeof event.input === 'object'
|
|
527
|
+
? firstDefined(event.input.command, event.input.cmd, event.input.script)
|
|
528
|
+
: event.input;
|
|
529
|
+
if (!commandMatches(actual, target.command)) return false;
|
|
336
530
|
}
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function gradeToolExpectation(events, expectation) {
|
|
535
|
+
const target = toolTarget(expectation);
|
|
536
|
+
const type = expectation.type === 'execution_evidence' ? 'tool_executed' : expectation.type;
|
|
537
|
+
return events.some(event => {
|
|
538
|
+
if (!toolMatches(event, target)) return false;
|
|
539
|
+
if (type === 'tool_executed' || type === 'tool_completed') {
|
|
540
|
+
if (!event.executed) return false;
|
|
541
|
+
if (expectation.after_edit && !events.some(previous => previous.kind === 'tool' && previous.edit && previous.executed && previous.sequence < event.sequence)) return false;
|
|
542
|
+
} else if (type === 'tool_denied') {
|
|
543
|
+
if (!event.denied) return false;
|
|
544
|
+
} else if (type === 'tool_attempted' || type === 'tool_call') {
|
|
545
|
+
if (!event.attempted) return false;
|
|
546
|
+
}
|
|
547
|
+
return true;
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function extractSessionMetadata(transcriptPath, engine = 'auto') {
|
|
552
|
+
const parsed = parseTranscriptFile(transcriptPath, engine);
|
|
553
|
+
return {
|
|
554
|
+
...parsed.metadata,
|
|
555
|
+
parse_status: parsed.parseStatus,
|
|
556
|
+
parse_error: parsed.parseError,
|
|
557
|
+
format: parsed.format,
|
|
558
|
+
};
|
|
337
559
|
}
|
|
338
560
|
|
|
339
561
|
function runShellCommand(command, cwd) {
|
|
562
|
+
if (typeof command !== 'string' || !command.trim()) throw new Error('command must be non-empty');
|
|
340
563
|
if (process.platform === 'win32') {
|
|
341
|
-
|
|
564
|
+
const scriptDir = fs.mkdtempSync(path.join(os.tmpdir(), 'behavioral-shell-'));
|
|
565
|
+
const scriptPath = path.join(scriptDir, 'command.cmd');
|
|
566
|
+
try {
|
|
567
|
+
fs.writeFileSync(scriptPath, `@echo off\r\n${command}\r\n`, 'utf8');
|
|
568
|
+
return execFileSync(process.env.ComSpec || 'cmd.exe', ['/d', '/c', scriptPath], { cwd, stdio: 'ignore' });
|
|
569
|
+
} finally {
|
|
570
|
+
fs.rmSync(scriptDir, { recursive: true, force: true });
|
|
571
|
+
}
|
|
342
572
|
}
|
|
343
573
|
return execFileSync('/bin/sh', ['-c', command], { cwd, stdio: 'ignore' });
|
|
344
574
|
}
|
|
345
575
|
|
|
346
|
-
|
|
347
|
-
|
|
576
|
+
const EXECUTION_COMMAND_RE = /^(?:npm|npx|node|git|pnpm|yarn|bun|cargo|python(?:3)?|pytest|go|ruby|java|make|rm|mv|cp|mkdir|chmod|powershell|pwsh|bash|sh|cmd)(?:\s|$)/i;
|
|
577
|
+
|
|
578
|
+
function isExecutionLikeTraceValue(value) {
|
|
579
|
+
if (typeof value !== 'string') return false;
|
|
580
|
+
const text = value.trim();
|
|
581
|
+
return (EXECUTION_COMMAND_RE.test(text) && /\s+\S/.test(text))
|
|
582
|
+
|| /(?:^|[/\\])[\w.-]+\.(?:c?js|mjs|py|sh|ps1|bat|cmd)\b/i.test(text);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function grade(c, ws, transcriptPath, engine = 'auto') {
|
|
586
|
+
const parsed = parseTranscriptFile(transcriptPath, engine);
|
|
587
|
+
const ordered = parseTranscriptEvents(transcriptPath);
|
|
588
|
+
const trace = renderAgentTrace(ordered);
|
|
348
589
|
const results = [];
|
|
349
590
|
for (const e of c.expectations) {
|
|
350
591
|
let pass = false;
|
|
592
|
+
let status = 'fail';
|
|
593
|
+
let reason = null;
|
|
351
594
|
try {
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
595
|
+
if (['tool_call', 'tool_attempted', 'tool_completed', 'tool_executed', 'tool_denied', 'execution_evidence'].includes(e.type)) {
|
|
596
|
+
const evidenceResult = gradeExecutionEvidence(e, parsed);
|
|
597
|
+
pass = evidenceResult.pass;
|
|
598
|
+
status = evidenceResult.status;
|
|
599
|
+
reason = evidenceResult.reason;
|
|
600
|
+
if (pass && e.after_edit && !gradeToolExpectation(ordered.events, e)) {
|
|
601
|
+
pass = false;
|
|
602
|
+
status = 'fail';
|
|
603
|
+
reason = 'expected a completed edit before the matching tool execution';
|
|
604
|
+
}
|
|
605
|
+
} else if (e.type === 'trace_contains' || e.type === 'trace_not_contains') {
|
|
606
|
+
if (e.type === 'trace_contains' && isExecutionLikeTraceValue(e.value)) {
|
|
607
|
+
status = 'inconclusive';
|
|
608
|
+
reason = 'execution-like trace assertions require structured tool evidence';
|
|
609
|
+
} else if (parsed.parseStatus !== 'parsed') {
|
|
610
|
+
status = 'inconclusive';
|
|
611
|
+
reason = parsed.parseError || 'transcript parser did not produce a complete structured trace';
|
|
612
|
+
} else {
|
|
613
|
+
pass = e.type === 'trace_contains' ? trace.includes(e.value) : !trace.includes(e.value);
|
|
614
|
+
status = pass ? 'pass' : 'fail';
|
|
615
|
+
}
|
|
616
|
+
} else if (e.type === 'file_contains') {
|
|
617
|
+
pass = fs.readFileSync(path.join(ws, e.path), 'utf8').includes(e.value);
|
|
618
|
+
status = pass ? 'pass' : 'fail';
|
|
619
|
+
} else if (e.type === 'file_not_exists') {
|
|
620
|
+
pass = !fs.existsSync(path.join(ws, e.path));
|
|
621
|
+
status = pass ? 'pass' : 'fail';
|
|
622
|
+
} else if (e.type === 'command_exit_0') {
|
|
357
623
|
runShellCommand(e.command, ws);
|
|
358
624
|
pass = true;
|
|
625
|
+
status = 'pass';
|
|
359
626
|
}
|
|
360
627
|
} catch (err) {
|
|
361
628
|
pass = false;
|
|
629
|
+
status = status === 'inconclusive' ? status : 'fail';
|
|
630
|
+
reason = err.message;
|
|
362
631
|
}
|
|
363
|
-
results.push({ ...e, pass });
|
|
632
|
+
results.push({ ...e, pass, status, reason });
|
|
364
633
|
}
|
|
365
634
|
// Informational expectations are reported but never gate the outcome:
|
|
366
635
|
// they capture context-dependent behavior (e.g. zoom-out only becomes
|
|
367
636
|
// obligatory once the breaker actually trips) that would otherwise
|
|
368
637
|
// produce meaningless failures.
|
|
369
638
|
const gating = results.filter((r) => !r.informational);
|
|
370
|
-
|
|
639
|
+
const inconclusive = gating.some(result => result.status === 'inconclusive');
|
|
640
|
+
const passed = gating.length > 0 && !inconclusive && gating.every((g) => g.pass);
|
|
641
|
+
return {
|
|
642
|
+
results,
|
|
643
|
+
passed,
|
|
644
|
+
status: inconclusive ? 'inconclusive' : passed ? 'pass' : 'fail',
|
|
645
|
+
parsed,
|
|
646
|
+
trace,
|
|
647
|
+
executionEvidence: parsed.executionEvidence,
|
|
648
|
+
};
|
|
371
649
|
}
|
|
372
650
|
|
|
373
651
|
function resolveEngine(requested) {
|
|
374
|
-
const has = (cmd) => {
|
|
375
|
-
try {
|
|
376
|
-
|
|
377
|
-
execFileSync(
|
|
378
|
-
return true;
|
|
652
|
+
const has = (cmd) => {
|
|
653
|
+
try {
|
|
654
|
+
const invocation = resolveCliInvocation({ command: cmd, args: [] });
|
|
655
|
+
execFileSync(invocation.command, [...invocation.args, '--version'], { stdio: 'pipe' });
|
|
656
|
+
return true;
|
|
379
657
|
} catch {
|
|
380
658
|
return false;
|
|
381
659
|
}
|
|
@@ -394,7 +672,10 @@ function sha256(value) {
|
|
|
394
672
|
}
|
|
395
673
|
|
|
396
674
|
function countToolCalls(trace) {
|
|
397
|
-
|
|
675
|
+
const evidence = trace && (trace.executionEvidence || (trace.parsed && trace.parsed.executionEvidence));
|
|
676
|
+
if (evidence) return evidence.counts.attempted;
|
|
677
|
+
const text = typeof trace === 'string' ? trace : trace && trace.trace;
|
|
678
|
+
return typeof text === 'string' ? (text.match(/^\[[^\]]+\]/gm) || []).length : null;
|
|
398
679
|
}
|
|
399
680
|
|
|
400
681
|
function treatmentSkills(c) {
|
|
@@ -421,9 +702,11 @@ function runArm(c, engine, arm, pairId) {
|
|
|
421
702
|
};
|
|
422
703
|
try {
|
|
423
704
|
const transcriptPath = runHeadless(c.prompt, ws, c.max_turns, engine, arm);
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
const
|
|
705
|
+
const graded = grade(c, ws, transcriptPath, engine);
|
|
706
|
+
const { results, passed } = graded;
|
|
707
|
+
const trace = graded.trace;
|
|
708
|
+
const metadata = graded.parsed.metadata;
|
|
709
|
+
const evidence = graded.executionEvidence;
|
|
427
710
|
if (attributionRequired && loadedSkills.length === 0) {
|
|
428
711
|
throw new Error('treatment attribution is required but no skill was loaded');
|
|
429
712
|
}
|
|
@@ -434,9 +717,18 @@ function runArm(c, engine, arm, pairId) {
|
|
|
434
717
|
usage: metadata.usage,
|
|
435
718
|
duration_ms: metadata.duration_ms,
|
|
436
719
|
num_turns: metadata.num_turns,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
720
|
+
model_name: metadata.model,
|
|
721
|
+
expectations: results.map(({ type, value, command, after_edit, description, pass, status, reason }) => ({
|
|
722
|
+
type, value, command, after_edit, description, pass, status, reason,
|
|
723
|
+
})),
|
|
724
|
+
outcome: graded.status,
|
|
725
|
+
parse_status: graded.parsed.parseStatus,
|
|
726
|
+
parse_error: graded.parsed.parseError,
|
|
727
|
+
transcript_format: graded.parsed.format,
|
|
728
|
+
cli_version: metadata.cli_version,
|
|
729
|
+
tool_call_count: countToolCalls(graded),
|
|
730
|
+
tool_call_counts: evidence.counts,
|
|
731
|
+
execution_evidence: evidence,
|
|
440
732
|
trace_preview: trace.slice(0, 500),
|
|
441
733
|
};
|
|
442
734
|
} catch (err) {
|
|
@@ -444,18 +736,84 @@ function runArm(c, engine, arm, pairId) {
|
|
|
444
736
|
...record,
|
|
445
737
|
outcome: 'session-error',
|
|
446
738
|
error: err.message.slice(0, 500),
|
|
447
|
-
|
|
739
|
+
parse_status: 'unavailable',
|
|
740
|
+
tool_call_count: null,
|
|
741
|
+
tool_call_counts: { attempted: null, completed: null, denied: null, unresolved: null },
|
|
448
742
|
};
|
|
449
743
|
}
|
|
450
744
|
}
|
|
451
745
|
|
|
746
|
+
function isDefinitiveArm(arm) {
|
|
747
|
+
return !!arm && (arm.outcome === 'pass' || arm.outcome === 'fail');
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function isCompletedPair(record) {
|
|
751
|
+
return !!record && isDefinitiveArm(record.arms && record.arms.baseline)
|
|
752
|
+
&& isDefinitiveArm(record.arms && record.arms.treatment);
|
|
753
|
+
}
|
|
754
|
+
|
|
452
755
|
function pairVerdict(baseline, treatment) {
|
|
756
|
+
if (!isDefinitiveArm(baseline) || !isDefinitiveArm(treatment)) return 'INCONCLUSIVE';
|
|
453
757
|
if (baseline.outcome === 'pass' && treatment.outcome === 'pass') return 'INCONCLUSIVE';
|
|
454
758
|
if (baseline.outcome !== 'pass' && treatment.outcome === 'pass') return 'EFFECTIVE';
|
|
455
759
|
if (baseline.outcome === 'pass' && treatment.outcome !== 'pass') return 'HARMFUL';
|
|
456
760
|
return 'INEFFECTIVE';
|
|
457
761
|
}
|
|
458
762
|
|
|
763
|
+
function summarizePairResults(pairResults) {
|
|
764
|
+
const completed = pairResults.filter(isCompletedPair);
|
|
765
|
+
const sessionFailures = pairResults.filter(record => Object.values(record.arms || {})
|
|
766
|
+
.some(arm => arm.outcome === 'session-error')).length;
|
|
767
|
+
const effective = completed.filter(record => record.verdict === 'EFFECTIVE').length;
|
|
768
|
+
const categorySummary = {};
|
|
769
|
+
for (const record of pairResults) {
|
|
770
|
+
const category = record.pressure_category || 'unclassified';
|
|
771
|
+
if (!categorySummary[category]) {
|
|
772
|
+
categorySummary[category] = { requested: 0, completed: 0, pass: 0, fail: 0 };
|
|
773
|
+
}
|
|
774
|
+
categorySummary[category].requested++;
|
|
775
|
+
}
|
|
776
|
+
for (const record of completed) {
|
|
777
|
+
const category = record.pressure_category || 'unclassified';
|
|
778
|
+
const bucket = categorySummary[category];
|
|
779
|
+
bucket.completed++;
|
|
780
|
+
if (record.arms.treatment.outcome === 'pass') bucket.pass++;
|
|
781
|
+
else bucket.fail++;
|
|
782
|
+
}
|
|
783
|
+
for (const bucket of Object.values(categorySummary)) {
|
|
784
|
+
bucket.pass_rate = bucket.completed
|
|
785
|
+
? Number((bucket.pass / bucket.completed).toFixed(4))
|
|
786
|
+
: null;
|
|
787
|
+
bucket.pass_rate_ci95 = wilsonInterval(bucket.pass, bucket.completed);
|
|
788
|
+
}
|
|
789
|
+
const costs = completed.flatMap(record => Object.values(record.arms || {}).map(arm => arm.cost));
|
|
790
|
+
const cost = costs.length === completed.length * 2 && costs.every(value => typeof value === 'number')
|
|
791
|
+
? Number(costs.reduce((sum, value) => sum + value, 0).toFixed(6))
|
|
792
|
+
: null;
|
|
793
|
+
const toolCallDelta = completed.map(record => {
|
|
794
|
+
const baseline = record.arms.baseline.tool_call_count;
|
|
795
|
+
const treatment = record.arms.treatment.tool_call_count;
|
|
796
|
+
return {
|
|
797
|
+
id: record.id,
|
|
798
|
+
baseline,
|
|
799
|
+
treatment,
|
|
800
|
+
treatment_minus_baseline: typeof baseline === 'number' && typeof treatment === 'number'
|
|
801
|
+
? treatment - baseline
|
|
802
|
+
: null,
|
|
803
|
+
};
|
|
804
|
+
});
|
|
805
|
+
return {
|
|
806
|
+
cost,
|
|
807
|
+
completed_pairs: completed.length,
|
|
808
|
+
session_failures: sessionFailures,
|
|
809
|
+
tool_call_delta: toolCallDelta,
|
|
810
|
+
verdicts: Object.fromEntries(['EFFECTIVE', 'INEFFECTIVE', 'INCONCLUSIVE', 'HARMFUL']
|
|
811
|
+
.map(verdict => [verdict, completed.filter(record => record.verdict === verdict).length])),
|
|
812
|
+
effective_rate_ci95: wilsonInterval(effective, completed.length),
|
|
813
|
+
pressure_categories: categorySummary,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
459
817
|
function wilsonInterval(successes, total) {
|
|
460
818
|
if (!total) return null;
|
|
461
819
|
const z = 1.96;
|
|
@@ -491,7 +849,7 @@ function runLive(filter, engineArg, armArg = 'treatment') {
|
|
|
491
849
|
id: c.id,
|
|
492
850
|
pair_id: pairId,
|
|
493
851
|
engine,
|
|
494
|
-
model: engine === 'opencode' ? (process.env.BEHAVIORAL_MODEL || 'openai/gpt-5-mini') : 'claude-default',
|
|
852
|
+
model: engine === 'opencode' ? (process.env.BEHAVIORAL_MODEL || 'openai/gpt-5-mini') : (process.env.BEHAVIORAL_MODEL || 'claude-default'),
|
|
495
853
|
pressure: !!c.pressure,
|
|
496
854
|
pressure_category: c.pressure_category || null,
|
|
497
855
|
sample_unit: 'paired case',
|
|
@@ -503,57 +861,26 @@ function runLive(filter, engineArg, armArg = 'treatment') {
|
|
|
503
861
|
date: new Date().toISOString(),
|
|
504
862
|
};
|
|
505
863
|
pairResults.push(record);
|
|
506
|
-
if (Object.values(arms).some(arm => arm.outcome === 'session-error')) sessionFailures++;
|
|
507
864
|
for (const arm of Object.values(arms)) {
|
|
508
865
|
console.log(` ${arm.arm}: ${arm.outcome} (${arm.loaded_skills.length ? arm.loaded_skills.join(', ') : 'no Harness skills'})`);
|
|
509
866
|
}
|
|
867
|
+
if (Object.values(arms).some(arm => arm.outcome === 'session-error')) sessionFailures++;
|
|
510
868
|
if (armArg === 'both') console.log(` Verdict: ${record.verdict}`);
|
|
511
869
|
const suffix = armArg === 'both' ? 'pair' : armArg;
|
|
512
870
|
fs.writeFileSync(path.join(RESULTS_DIR, `${record.date.slice(0, 10)}-${c.id}-${suffix}.json`), JSON.stringify(record, null, 2));
|
|
513
871
|
}
|
|
514
872
|
|
|
873
|
+
let exitCode = sessionFailures ? 1 : 0;
|
|
515
874
|
if (armArg === 'both') {
|
|
516
|
-
const
|
|
517
|
-
const effective = completed.filter(r => r.verdict === 'EFFECTIVE').length;
|
|
518
|
-
const categorySummary = {};
|
|
519
|
-
for (const record of pairResults) {
|
|
520
|
-
const category = record.pressure_category || 'unclassified';
|
|
521
|
-
if (!categorySummary[category]) categorySummary[category] = { requested: 0, completed: 0, pass: 0, fail: 0 };
|
|
522
|
-
categorySummary[category].requested++;
|
|
523
|
-
}
|
|
524
|
-
for (const record of completed) {
|
|
525
|
-
const category = record.pressure_category || 'unclassified';
|
|
526
|
-
const bucket = categorySummary[category];
|
|
527
|
-
bucket.completed++;
|
|
528
|
-
if (record.arms.treatment.outcome === 'pass') bucket.pass++;
|
|
529
|
-
else bucket.fail++;
|
|
530
|
-
}
|
|
531
|
-
for (const bucket of Object.values(categorySummary)) {
|
|
532
|
-
bucket.pass_rate = Number((bucket.pass / bucket.completed).toFixed(4));
|
|
533
|
-
bucket.pass_rate_ci95 = wilsonInterval(bucket.pass, bucket.completed);
|
|
534
|
-
}
|
|
535
|
-
const costs = completed.flatMap(record => Object.values(record.arms).map(arm => arm.cost));
|
|
536
|
-
const cost = costs.length === completed.length * 2 && costs.every(value => typeof value === 'number')
|
|
537
|
-
? Number(costs.reduce((sum, value) => sum + value, 0).toFixed(6))
|
|
538
|
-
: null;
|
|
875
|
+
const aggregate = summarizePairResults(pairResults);
|
|
539
876
|
const summary = {
|
|
540
877
|
date: new Date().toISOString(),
|
|
541
878
|
protocol: 'paired-randomized-v1',
|
|
542
879
|
requested_sample_size: cases.length,
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
tool_call_delta: completed.map(r => ({
|
|
548
|
-
id: r.id,
|
|
549
|
-
baseline: r.arms.baseline.tool_call_count,
|
|
550
|
-
treatment: r.arms.treatment.tool_call_count,
|
|
551
|
-
treatment_minus_baseline: r.arms.treatment.tool_call_count - r.arms.baseline.tool_call_count,
|
|
552
|
-
})),
|
|
553
|
-
verdicts: Object.fromEntries(['EFFECTIVE', 'INEFFECTIVE', 'INCONCLUSIVE', 'HARMFUL'].map(v => [v, completed.filter(r => r.verdict === v).length])),
|
|
554
|
-
effective_rate_ci95: wilsonInterval(effective, completed.length),
|
|
555
|
-
pressure_categories: categorySummary,
|
|
556
|
-
interpretation: completed.length < 2
|
|
880
|
+
...aggregate,
|
|
881
|
+
boundary_compliance: aggregate.completed_pairs === cases.length ? 'complete' : 'incomplete',
|
|
882
|
+
cost: aggregate.cost,
|
|
883
|
+
interpretation: aggregate.completed_pairs < 2
|
|
557
884
|
? 'insufficient paired samples for an effectiveness claim'
|
|
558
885
|
: 'report paired outcomes; do not treat INCONCLUSIVE pairs as skill lift',
|
|
559
886
|
};
|
|
@@ -561,8 +888,9 @@ function runLive(filter, engineArg, armArg = 'treatment') {
|
|
|
561
888
|
fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
|
|
562
889
|
console.log(`\nPaired summary: ${summaryPath}`);
|
|
563
890
|
console.log(`Completed pairs: ${summary.completed_pairs}/${summary.requested_sample_size}`);
|
|
891
|
+
exitCode = aggregate.session_failures ? 1 : 0;
|
|
564
892
|
}
|
|
565
|
-
process.exit(
|
|
893
|
+
process.exit(exitCode);
|
|
566
894
|
}
|
|
567
895
|
|
|
568
896
|
// ----------------------------------------------------------------------------
|
|
@@ -584,4 +912,17 @@ function main() {
|
|
|
584
912
|
|
|
585
913
|
if (require.main === module) main();
|
|
586
914
|
|
|
587
|
-
module.exports = {
|
|
915
|
+
module.exports = {
|
|
916
|
+
buildEngineInvocation,
|
|
917
|
+
buildWorkspace,
|
|
918
|
+
countToolCalls,
|
|
919
|
+
extractAgentEvents,
|
|
920
|
+
extractAgentTrace,
|
|
921
|
+
extractSessionMetadata,
|
|
922
|
+
grade,
|
|
923
|
+
parseTranscriptEvents,
|
|
924
|
+
pairVerdict,
|
|
925
|
+
parseSimpleYaml,
|
|
926
|
+
summarizePairResults,
|
|
927
|
+
validate,
|
|
928
|
+
};
|