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
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
11
|
+
const actionGate = require(path.join(ROOT, 'hooks', 'scripts', 'action-gate.js'));
|
|
12
|
+
const canonicalScript = path.join(ROOT, 'hooks', 'scripts', 'action-gate.js');
|
|
13
|
+
const rulesPath = path.join(ROOT, 'hooks', 'scripts', 'action-gate-rules.json');
|
|
14
|
+
const pluginScript = path.join(ROOT, 'plugins', 'harness-everything', 'hooks', 'scripts', 'action-gate.js');
|
|
15
|
+
const pluginRules = path.join(ROOT, 'plugins', 'harness-everything', 'hooks', 'scripts', 'action-gate-rules.json');
|
|
16
|
+
const codexPost = require(path.join(ROOT, 'plugins', 'harness-everything', 'hooks', 'scripts', 'codex-action-gate-post.js'));
|
|
17
|
+
|
|
18
|
+
// The default Claude decision depends on this variable; a developer's shell must not leak it into the suite.
|
|
19
|
+
delete process.env.HARNESS_ACTION_GATE_POLICY;
|
|
20
|
+
|
|
21
|
+
let failed = 0;
|
|
22
|
+
function check(condition, message) {
|
|
23
|
+
if (condition) console.log(` PASS ${message}`);
|
|
24
|
+
else {
|
|
25
|
+
console.error(` FAIL ${message}`);
|
|
26
|
+
failed++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function payload(command, options = {}) {
|
|
31
|
+
return {
|
|
32
|
+
session_id: options.sessionId || 'phase5-test',
|
|
33
|
+
hook_event_name: options.event || 'PreToolUse',
|
|
34
|
+
tool_name: options.tool || 'Bash',
|
|
35
|
+
tool_use_id: options.toolUseId || `toolu_${Math.random().toString(16).slice(2)}`,
|
|
36
|
+
tool_input: options.tool === 'apply_patch' ? { patch: command } : { command },
|
|
37
|
+
permission_mode: options.permissionMode || 'default',
|
|
38
|
+
cwd: options.cwd || ROOT,
|
|
39
|
+
...(options.extra || {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readAudit(sessionDir, toolUseId) {
|
|
44
|
+
const file = path.join(sessionDir, 'action-gate', `${toolUseId}.json`);
|
|
45
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function findAudit(dir, toolUseId) {
|
|
49
|
+
if (!fs.existsSync(dir)) return null;
|
|
50
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
51
|
+
const full = path.join(dir, entry.name);
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
const found = findAudit(full, toolUseId);
|
|
54
|
+
if (found) return found;
|
|
55
|
+
} else if (entry.name === `${toolUseId}.json` && path.basename(dir) === 'action-gate') {
|
|
56
|
+
return JSON.parse(fs.readFileSync(full, 'utf8'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cleanTemp(dir) {
|
|
63
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_) {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log('=== Issue #85 Phase 5 — Pre-action gate ===');
|
|
67
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-action-gate-'));
|
|
68
|
+
const sessionDir = path.join(tempRoot, 'session');
|
|
69
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
70
|
+
const stateHome = path.join(tempRoot, 'state-home');
|
|
71
|
+
|
|
72
|
+
// Runs the hook as a separate process the way the host does, with state kept inside the temp dir.
|
|
73
|
+
function runHook(hookPayload, env = {}) {
|
|
74
|
+
const result = spawnSync(process.execPath, [canonicalScript], {
|
|
75
|
+
cwd: tempRoot,
|
|
76
|
+
input: JSON.stringify(hookPayload),
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
env: { ...process.env, HARNESS_STATE_HOME: stateHome, HARNESS_ACTION_GATE_HOST: 'claude', HARNESS_ACTION_GATE_POLICY: '', ...env },
|
|
79
|
+
});
|
|
80
|
+
return { code: result.status, stdout: String(result.stdout || '').trim(), stderr: String(result.stderr || '') };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const table = actionGate.loadRuleTable(rulesPath);
|
|
85
|
+
check(table.degraded === false, 'production action-gate rule table loads without fallback');
|
|
86
|
+
check(table.rules.length >= 9, 'production rule table is non-empty and keeps the destructive baseline');
|
|
87
|
+
const requiredIds = [
|
|
88
|
+
'git-force-push',
|
|
89
|
+
'git-reset-hard',
|
|
90
|
+
'git-clean-force-delete',
|
|
91
|
+
'recursive-delete',
|
|
92
|
+
'powershell-recursive-force-delete',
|
|
93
|
+
'sql-destructive-ddl',
|
|
94
|
+
'package-publish',
|
|
95
|
+
'github-release-create',
|
|
96
|
+
'production-deploy',
|
|
97
|
+
];
|
|
98
|
+
for (const id of requiredIds) check(table.rules.some(rule => rule.id === id), `rule table contains ${id}`);
|
|
99
|
+
|
|
100
|
+
const force = payload('git push --force origin main', { toolUseId: 'force-push' });
|
|
101
|
+
const forceDecision = actionGate.evaluatePreToolUse(force, { host: 'claude', sessionDir, ruleTable: table });
|
|
102
|
+
check(forceDecision.kind === 'defer' && forceDecision.exitCode === 0 && forceDecision.stdout === null, 'Claude force-push defers to the host permission flow without forcing a prompt');
|
|
103
|
+
check(forceDecision.rule && forceDecision.rule.id === 'git-force-push', 'deferred decision still names the matched rule');
|
|
104
|
+
const forceAudit = readAudit(sessionDir, 'force-push');
|
|
105
|
+
check(forceAudit.disposition === 'deferred-to-host', 'matched Claude action is audited as deferred-to-host');
|
|
106
|
+
check(forceAudit.matchedRule === 'git-force-push', 'audit records matched rule');
|
|
107
|
+
check(forceAudit.commandHash === actionGate.hashExact('git push --force origin main'), 'audit hashes the exact command payload');
|
|
108
|
+
|
|
109
|
+
const askForce = actionGate.evaluatePreToolUse(payload('git push --force origin main', { toolUseId: 'force-push-ask' }), { host: 'claude', sessionDir, ruleTable: table, policy: 'always-ask' });
|
|
110
|
+
check(askForce.kind === 'ask' && askForce.exitCode === 0, 'always-ask policy restores the forced Claude prompt');
|
|
111
|
+
check(askForce.stdout && askForce.stdout.hookSpecificOutput.permissionDecision === 'ask', 'ask decision uses Claude PreToolUse hookSpecificOutput');
|
|
112
|
+
check(askForce.stdout.hookSpecificOutput.permissionDecisionReason.includes('git-force-push'), 'ask reason names the matched rule');
|
|
113
|
+
check(readAudit(sessionDir, 'force-push-ask').disposition === 'pending-approval', 'always-ask action is audited as pending-approval');
|
|
114
|
+
|
|
115
|
+
const statusDecision = actionGate.evaluatePreToolUse(payload('git status', { toolUseId: 'status' }), { host: 'claude', sessionDir, ruleTable: table });
|
|
116
|
+
check(statusDecision.kind === 'allow' && statusDecision.stdout === null, 'git status passes untouched');
|
|
117
|
+
check(!fs.existsSync(path.join(sessionDir, 'action-gate', 'status.json')), 'safe unmatched command creates no gate audit record');
|
|
118
|
+
|
|
119
|
+
for (const mode of ['default', 'acceptEdits', 'plan', 'auto', 'dontAsk', 'bypassPermissions', null]) {
|
|
120
|
+
const label = mode || 'missing';
|
|
121
|
+
const modePayload = payload('git push --force-with-lease origin main', { permissionMode: mode, toolUseId: `mode-${label}` });
|
|
122
|
+
if (mode === null) delete modePayload.permission_mode;
|
|
123
|
+
const result = actionGate.evaluatePreToolUse(modePayload, { host: 'claude', sessionDir, ruleTable: table });
|
|
124
|
+
check(result.kind === 'defer' && result.exitCode === 0 && result.stdout === null, `permission_mode=${label} defers to the host without a forced prompt`);
|
|
125
|
+
check(readAudit(sessionDir, `mode-${label}`).permissionMode === mode, `audit records permission_mode=${label}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const routerFalse = payload('git reset --hard HEAD~1', {
|
|
129
|
+
toolUseId: 'router-false',
|
|
130
|
+
extra: { workflowPlan: { actionGate: { required: false, reasonCodes: [], disposition: null } } },
|
|
131
|
+
});
|
|
132
|
+
const routerFalseDecision = actionGate.evaluatePreToolUse(routerFalse, { host: 'claude', sessionDir, ruleTable: table });
|
|
133
|
+
check(routerFalseDecision.kind === 'defer' && routerFalseDecision.rule.id === 'git-reset-hard', 'command classification gates destructive action even when router hint says not required');
|
|
134
|
+
check(readAudit(sessionDir, 'router-false').routerActionGate.required === false, 'router actionGate hint is recorded without controlling enforcement');
|
|
135
|
+
|
|
136
|
+
const routerTrueSafe = payload('git status', {
|
|
137
|
+
toolUseId: 'router-true-safe',
|
|
138
|
+
extra: { workflowPlan: { actionGate: { required: true, reasonCodes: ['irreversible-action'], disposition: 'pending-approval' } } },
|
|
139
|
+
});
|
|
140
|
+
check(actionGate.evaluatePreToolUse(routerTrueSafe, { host: 'claude', sessionDir, ruleTable: table }).kind === 'allow', 'safe command is not gated solely because prompt router predicted risk');
|
|
141
|
+
|
|
142
|
+
const nonScratchTarget = process.platform === 'win32'
|
|
143
|
+
? 'C:\\workspace\\harness-project\\build'
|
|
144
|
+
: '/opt/harness-project/build';
|
|
145
|
+
const psDelete = payload(`Remove-Item -Recurse -Force "${nonScratchTarget}"`, { tool: 'PowerShell', toolUseId: 'ps-delete' });
|
|
146
|
+
const psDeleteDecision = actionGate.evaluatePreToolUse(psDelete, { host: 'claude', sessionDir, ruleTable: table });
|
|
147
|
+
check(psDeleteDecision.kind === 'defer' && psDeleteDecision.rule.id === 'powershell-recursive-force-delete', 'PowerShell recursive force delete outside scratch is gated');
|
|
148
|
+
|
|
149
|
+
const scratchTarget = path.join(sessionDir, 'scratch', 'build');
|
|
150
|
+
const psScratch = payload(`Remove-Item -Recurse -Force "${scratchTarget}"`, { tool: 'PowerShell', toolUseId: 'ps-scratch' });
|
|
151
|
+
check(actionGate.evaluatePreToolUse(psScratch, { host: 'claude', sessionDir, ruleTable: table }).kind === 'allow', 'PowerShell recursive force delete inside session scratch is exempt');
|
|
152
|
+
|
|
153
|
+
const rmDelete = actionGate.evaluatePreToolUse(payload(`rm -rf "${nonScratchTarget}"`, { toolUseId: 'rm-delete' }), { host: 'claude', sessionDir, ruleTable: table });
|
|
154
|
+
check(rmDelete.kind === 'defer' && rmDelete.rule.id === 'recursive-delete', 'rm -rf outside scratch is gated');
|
|
155
|
+
const rmScratch = actionGate.evaluatePreToolUse(payload(`rm -rf "${path.join(os.tmpdir(), 'harness-safe-scratch')}"`, { toolUseId: 'rm-scratch' }), { host: 'claude', sessionDir, ruleTable: table });
|
|
156
|
+
check(rmScratch.kind === 'allow', 'rm -rf inside OS temp is exempt');
|
|
157
|
+
|
|
158
|
+
const ruleCases = [
|
|
159
|
+
['git reset --hard HEAD', 'git-reset-hard'],
|
|
160
|
+
['git clean -fd', 'git-clean-force-delete'],
|
|
161
|
+
['psql -c "DROP TABLE users"', 'sql-destructive-ddl'],
|
|
162
|
+
['npm publish', 'package-publish'],
|
|
163
|
+
['gh release create v1.2.3', 'github-release-create'],
|
|
164
|
+
['npm run deploy', 'production-deploy'],
|
|
165
|
+
];
|
|
166
|
+
for (let i = 0; i < ruleCases.length; i++) {
|
|
167
|
+
const [command, expected] = ruleCases[i];
|
|
168
|
+
const result = actionGate.evaluatePreToolUse(payload(command, { toolUseId: `rule-${i}` }), { host: 'claude', sessionDir, ruleTable: table });
|
|
169
|
+
check(result.kind === 'defer' && result.rule.id === expected, `${command} matches ${expected}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const codexDecision = actionGate.evaluatePreToolUse(payload('git push -f origin main', { toolUseId: 'codex-block' }), { host: 'codex', sessionDir, ruleTable: table });
|
|
173
|
+
check(codexDecision.kind === 'block' && codexDecision.exitCode === 2, 'host without verified ask-style decision blocks with exit 2');
|
|
174
|
+
check(codexDecision.stderr.includes('git-force-push'), 'non-ask host block reason names the matched rule');
|
|
175
|
+
check(readAudit(sessionDir, 'codex-block').disposition === 'rejected', 'non-ask host block is audited as rejected');
|
|
176
|
+
|
|
177
|
+
const internalDefer = actionGate.internalErrorDecision(payload('git status', { toolUseId: 'internal-defer' }), new Error('synthetic failure'), { host: 'claude', sessionDir });
|
|
178
|
+
check(internalDefer.kind === 'defer' && internalDefer.exitCode === 0, 'internal action-gate error on Claude defers to the host instead of allowing or forcing a prompt');
|
|
179
|
+
check(Boolean(internalDefer.stdout) && !internalDefer.stdout.hookSpecificOutput && /internal-error/.test(internalDefer.stdout.systemMessage), 'internal error is shown to the user as a systemMessage without a permission decision');
|
|
180
|
+
check(readAudit(sessionDir, 'internal-defer').disposition === 'deferred-to-host', 'internal error on Claude is audited as deferred-to-host');
|
|
181
|
+
const internalAsk = actionGate.internalErrorDecision(payload('git status', { toolUseId: 'internal-ask' }), new Error('synthetic failure'), { host: 'claude', sessionDir, policy: 'always-ask' });
|
|
182
|
+
check(internalAsk.kind === 'ask' && internalAsk.stdout.hookSpecificOutput.permissionDecision === 'ask', 'internal action-gate error under always-ask fails closed to ask');
|
|
183
|
+
const internalBlock = actionGate.internalErrorDecision(payload('git status', { toolUseId: 'internal-block' }), new Error('synthetic failure'), { host: 'codex', sessionDir });
|
|
184
|
+
check(internalBlock.kind === 'block' && internalBlock.exitCode === 2, 'internal error on host without ask fails closed to block');
|
|
185
|
+
|
|
186
|
+
const invalidPath = path.join(tempRoot, 'invalid-rules.json');
|
|
187
|
+
fs.writeFileSync(invalidPath, '{ not-json', 'utf8');
|
|
188
|
+
const degraded = actionGate.loadRuleTable(invalidPath);
|
|
189
|
+
check(degraded.degraded === true && degraded.rules.length > 0, 'missing/invalid rule table visibly falls back to built-in defaults');
|
|
190
|
+
const degradedDecision = actionGate.evaluatePreToolUse(payload('git push --force origin main', { toolUseId: 'degraded' }), { host: 'claude', sessionDir, ruleTable: degraded });
|
|
191
|
+
check(degradedDecision.kind === 'defer' && degradedDecision.rule.id === 'git-force-push', 'built-in fallback still classifies and audits destructive action');
|
|
192
|
+
check(Boolean(degradedDecision.stdout) && /degraded/.test(degradedDecision.stdout.systemMessage), 'fallback degradation is shown to the user as a systemMessage');
|
|
193
|
+
|
|
194
|
+
const emptyPath = path.join(tempRoot, 'empty-rules.json');
|
|
195
|
+
fs.writeFileSync(emptyPath, JSON.stringify({ schemaVersion: 1, rules: [] }), 'utf8');
|
|
196
|
+
const empty = actionGate.loadRuleTable(emptyPath);
|
|
197
|
+
check(empty.degraded === false && empty.rules.length === 0, 'valid empty rule table does not silently activate built-in defaults');
|
|
198
|
+
check(actionGate.evaluatePreToolUse(payload('git push --force origin main', { toolUseId: 'empty' }), { host: 'claude', sessionDir, ruleTable: empty }).kind === 'allow', 'empty-table mutation control disables detection, proving production non-empty assertion is meaningful');
|
|
199
|
+
|
|
200
|
+
const execPayload = payload('npm publish', { toolUseId: 'exec-ok' });
|
|
201
|
+
actionGate.evaluatePreToolUse(execPayload, { host: 'claude', sessionDir, ruleTable: table });
|
|
202
|
+
const execResult = actionGate.processEvent({ ...execPayload, hook_event_name: 'PostToolUse' }, { sessionDir });
|
|
203
|
+
check(execResult.record && execResult.record.disposition === 'executed', 'PostToolUse closes a deferred payload as executed');
|
|
204
|
+
check(execResult.record.commandHash === actionGate.hashExact('npm publish'), 'executed audit keeps exact approved payload hash');
|
|
205
|
+
|
|
206
|
+
const failPayload = payload('gh release create v9', { toolUseId: 'exec-fail' });
|
|
207
|
+
actionGate.evaluatePreToolUse(failPayload, { host: 'claude', sessionDir, ruleTable: table });
|
|
208
|
+
const failResult = actionGate.processEvent({ ...failPayload, hook_event_name: 'PostToolUseFailure' }, { sessionDir });
|
|
209
|
+
check(failResult.record && failResult.record.disposition === 'executed' && failResult.record.executionFailure === true, 'PostToolUseFailure records attempted execution without pretending success');
|
|
210
|
+
|
|
211
|
+
const mismatchPayload = payload('git push --force origin main', { toolUseId: 'exec-mismatch' });
|
|
212
|
+
actionGate.evaluatePreToolUse(mismatchPayload, { host: 'claude', sessionDir, ruleTable: table });
|
|
213
|
+
const mismatch = actionGate.processEvent({ ...mismatchPayload, hook_event_name: 'PostToolUse', tool_input: { command: 'git push --force origin other' } }, { sessionDir });
|
|
214
|
+
check(mismatch.record && mismatch.record.disposition === 'integrity-violation', 'post-tool payload mismatch is never accepted as the approved command');
|
|
215
|
+
|
|
216
|
+
const pendingPayload = payload('terraform apply', { toolUseId: 'pending-stop' });
|
|
217
|
+
actionGate.evaluatePreToolUse(pendingPayload, { host: 'claude', sessionDir, ruleTable: table });
|
|
218
|
+
const stop = actionGate.processEvent({ session_id: 'phase5-test', hook_event_name: 'Stop', tool_name: 'Stop', tool_input: {} }, { sessionDir });
|
|
219
|
+
check(stop.rejected >= 1, 'Stop closes unexecuted deferred and pending payloads');
|
|
220
|
+
check(readAudit(sessionDir, 'pending-stop').disposition === 'rejected', 'unexecuted deferred payload is audited as rejected');
|
|
221
|
+
check(readAudit(sessionDir, 'force-push-ask').disposition === 'rejected', 'unexecuted always-ask approval is audited as rejected');
|
|
222
|
+
|
|
223
|
+
const hookDefer = runHook(payload('git push --force origin main', { toolUseId: 'e2e-defer', sessionId: 'phase5-e2e', permissionMode: 'auto', cwd: tempRoot }));
|
|
224
|
+
check(hookDefer.code === 0 && hookDefer.stdout === '', 'hook process returns no decision for a matched command under the default policy');
|
|
225
|
+
const hookDeferAudit = findAudit(stateHome, 'e2e-defer');
|
|
226
|
+
check(hookDeferAudit && hookDeferAudit.disposition === 'deferred-to-host' && hookDeferAudit.permissionMode === 'auto', 'hook process audits the deferred command with its permission mode');
|
|
227
|
+
const hookAsk = runHook(payload('git push --force origin main', { toolUseId: 'e2e-ask', sessionId: 'phase5-e2e', cwd: tempRoot }), { HARNESS_ACTION_GATE_POLICY: 'always-ask' });
|
|
228
|
+
let hookAskJson = null;
|
|
229
|
+
try { hookAskJson = JSON.parse(hookAsk.stdout); } catch (_) { hookAskJson = null; }
|
|
230
|
+
check(hookAsk.code === 0 && hookAskJson && hookAskJson.hookSpecificOutput.permissionDecision === 'ask', 'HARNESS_ACTION_GATE_POLICY=always-ask makes the hook process emit ask');
|
|
231
|
+
|
|
232
|
+
const canonicalHooks = JSON.parse(fs.readFileSync(path.join(ROOT, 'hooks', 'hooks.json'), 'utf8'));
|
|
233
|
+
const canonicalPre = canonicalHooks.hooks.PreToolUse.find(group => group.id === 'harness:pre:action-gate');
|
|
234
|
+
check(canonicalPre && canonicalPre.matcher === 'Bash|PowerShell', 'Claude hook manifest wires action-gate to Bash|PowerShell');
|
|
235
|
+
check(canonicalHooks.hooks.PostToolUse.some(group => group.id === 'harness:post:action-gate-audit'), 'Claude hook manifest audits successful tool execution');
|
|
236
|
+
check(canonicalHooks.hooks.PostToolUseFailure.some(group => group.id === 'harness:post-failure:action-gate-audit'), 'Claude hook manifest audits failed tool execution');
|
|
237
|
+
check(canonicalHooks.hooks.Stop.some(group => group.id === 'harness:stop:action-gate-audit'), 'Claude hook manifest closes unresolved approvals on Stop');
|
|
238
|
+
|
|
239
|
+
const pluginHooks = JSON.parse(fs.readFileSync(path.join(ROOT, 'plugins', 'harness-everything', 'hooks', 'hooks.json'), 'utf8'));
|
|
240
|
+
const pluginPre = pluginHooks.hooks.PreToolUse.find(group => group.matcher === 'Bash|apply_patch');
|
|
241
|
+
check(pluginPre && pluginPre.hooks.some(hook => /action-gate\.js/.test(hook.command)), 'OpenAI plugin wires action-gate to Bash|apply_patch');
|
|
242
|
+
check(!Object.prototype.hasOwnProperty.call(pluginHooks.hooks, 'PostToolUseFailure'), 'Codex plugin does not declare unsupported PostToolUseFailure lifecycle event');
|
|
243
|
+
const pluginPost = pluginHooks.hooks.PostToolUse.find(group => group.matcher === 'Bash|apply_patch');
|
|
244
|
+
check(pluginPost && pluginPost.hooks.some(hook => /codex-action-gate-post\.js/.test(hook.command)), 'Codex plugin infers action-gate outcome from supported PostToolUse');
|
|
245
|
+
check(codexPost.inferOutcome({ tool_response: { exitCode: 0 } }) === 'success', 'Codex PostToolUse maps zero exit code to success');
|
|
246
|
+
check(codexPost.inferOutcome({ tool_response: { exit_code: 2, stderr: 'failed' } }) === 'failure', 'Codex PostToolUse maps non-zero exit code to failure');
|
|
247
|
+
check(codexPost.inferOutcome({ tool_response: { stderr: 'fatal: denied' } }) === 'failure', 'Codex PostToolUse conservatively recognizes failure stderr without exit code');
|
|
248
|
+
check(pluginHooks.hooks.Stop.some(group => group.hooks.some(hook => /action-gate\.js/.test(hook.command))), 'OpenAI plugin closes pending approvals at Stop');
|
|
249
|
+
|
|
250
|
+
check(fs.readFileSync(rulesPath, 'utf8') === fs.readFileSync(pluginRules, 'utf8'), 'canonical and plugin action-gate rule tables are byte-identical');
|
|
251
|
+
check(fs.existsSync(pluginScript), 'OpenAI plugin packages action-gate runtime');
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.error(err.stack || err.message);
|
|
254
|
+
failed++;
|
|
255
|
+
} finally {
|
|
256
|
+
cleanTemp(tempRoot);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log(`\n${failed === 0 ? 'PASS' : 'FAIL'}: action-gate phase 5 (${failed} failure${failed === 1 ? '' : 's'})`);
|
|
260
|
+
process.exit(failed === 0 ? 0 : 1);
|
package/ci/cases/case2.json
CHANGED
package/ci/consistency-check.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const yaml = require('js-yaml');
|
|
19
|
+
const { checkDisclosure } = require('./disclosure-check');
|
|
19
20
|
|
|
20
21
|
const ROOT = path.resolve(__dirname, '..');
|
|
21
22
|
let failures = 0;
|
|
@@ -134,6 +135,15 @@ for (const s of skills) {
|
|
|
134
135
|
validateFrontmatterSyntax(s.body, s.dir);
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
// --- 2b. Progressive-disclosure surface -------------------------------
|
|
139
|
+
// Keep routed contracts, plain-language workflow docs, and lazy-loaded detail
|
|
140
|
+
// paired. The standalone module is also exercised by focused mechanism tests.
|
|
141
|
+
const disclosure = checkDisclosure(ROOT);
|
|
142
|
+
for (const failure of disclosure.failures) {
|
|
143
|
+
check(`disclosure: ${failure.check}`, false, failure.detail);
|
|
144
|
+
}
|
|
145
|
+
console.log(`Checked disclosure for ${disclosure.skills.length} skill(s) and ${disclosure.workflows.length} workflow doc(s).`);
|
|
146
|
+
|
|
137
147
|
// --- 3+4. Distribution manifests -----------------------------------------
|
|
138
148
|
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
139
149
|
const pluginJsonPath = path.join(ROOT, '.claude-plugin', 'plugin.json');
|
|
@@ -159,11 +169,10 @@ if (fs.existsSync(pluginJsonPath)) {
|
|
|
159
169
|
);
|
|
160
170
|
check('plugin.json hooks file exists', !plugin.hooks || fs.existsSync(path.join(ROOT, plugin.hooks)), plugin.hooks);
|
|
161
171
|
if (plugin.agents) {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
check('plugin.json agents path contains agent definitions', agentFiles.length > 0, plugin.agents);
|
|
172
|
+
const agentEntries = Array.isArray(plugin.agents) ? plugin.agents : [plugin.agents];
|
|
173
|
+
for (const rel of agentEntries) {
|
|
174
|
+
check(`plugin.json agents entry is a .md file: ${rel}`, rel.endsWith('.md'), rel);
|
|
175
|
+
check(`plugin.json agents path exists: ${rel}`, fs.existsSync(path.join(ROOT, rel)), rel);
|
|
167
176
|
}
|
|
168
177
|
}
|
|
169
178
|
for (const rel of plugin.skills || []) {
|
|
@@ -340,6 +349,43 @@ for (const s of skills) {
|
|
|
340
349
|
);
|
|
341
350
|
}
|
|
342
351
|
|
|
352
|
+
// 6. Changelog headings vs git tags.
|
|
353
|
+
// release.yml triggers on tag push, so a version with a changelog entry and no
|
|
354
|
+
// tag was never gated and never published - 0.3.4-beta and 0.3.5-beta both sat
|
|
355
|
+
// in that state (issue #39). A version deliberately left unreleased must say so
|
|
356
|
+
// in its heading rather than being silently absent from git.
|
|
357
|
+
const { execFileSync } = require('child_process');
|
|
358
|
+
let knownTags = null;
|
|
359
|
+
try {
|
|
360
|
+
knownTags = new Set(
|
|
361
|
+
execFileSync('git', ['tag', '--list'], { cwd: ROOT, encoding: 'utf8' })
|
|
362
|
+
.split(/\r?\n/)
|
|
363
|
+
.filter(Boolean)
|
|
364
|
+
);
|
|
365
|
+
} catch (err) {
|
|
366
|
+
knownTags = null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (knownTags === null || knownTags.size === 0) {
|
|
370
|
+
console.log('WARN changelog/tag check skipped: no git tags available (shallow clone? use fetch-depth: 0)');
|
|
371
|
+
} else {
|
|
372
|
+
const changelog = fs.readFileSync(path.join(ROOT, 'CHANGELOG.md'), 'utf8');
|
|
373
|
+
const headings = [...changelog.matchAll(/^## \[([^\]]+)\](.*)$/gm)];
|
|
374
|
+
check('CHANGELOG.md exposes version headings', headings.length > 0, 'no "## [version]" headings found');
|
|
375
|
+
for (const [, version, rest] of headings) {
|
|
376
|
+
if (/^unreleased$/i.test(version.trim())) continue;
|
|
377
|
+
if (/unreleased/i.test(rest)) {
|
|
378
|
+
console.log(`OK CHANGELOG ${version}: declared unreleased, no tag required`);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
check(
|
|
382
|
+
`CHANGELOG ${version}: released heading has tag v${version}`,
|
|
383
|
+
knownTags.has(`v${version}`),
|
|
384
|
+
`no tag v${version}; tag the release or mark the heading unreleased`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
343
389
|
if (failures > 0) {
|
|
344
390
|
console.error(`\n❌ CONSISTENCY CHECK FAILED: ${failures} problem(s).`);
|
|
345
391
|
process.exit(1);
|
|
@@ -56,29 +56,38 @@ function jaccard(a, b) {
|
|
|
56
56
|
|
|
57
57
|
function discoverSkills() {
|
|
58
58
|
const out = [];
|
|
59
|
+
const parseErrors = [];
|
|
59
60
|
for (const entry of fs.readdirSync(ROOT, { withFileTypes: true })) {
|
|
60
61
|
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
61
62
|
const skillMd = path.join(ROOT, entry.name, 'SKILL.md');
|
|
62
63
|
if (!fs.existsSync(skillMd)) continue;
|
|
63
64
|
const raw = fs.readFileSync(skillMd, 'utf8');
|
|
64
65
|
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
65
|
-
if (!fmMatch)
|
|
66
|
+
if (!fmMatch) {
|
|
67
|
+
parseErrors.push(`${entry.name}: missing YAML frontmatter delimiters`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
66
70
|
const fm = fmMatch[1];
|
|
67
71
|
// Parser parity: use js-yaml to parse frontmatter
|
|
68
72
|
let desc = '';
|
|
69
73
|
try {
|
|
70
74
|
const parsed = yaml.load(fm);
|
|
71
75
|
desc = (parsed.description || '').trim();
|
|
72
|
-
} catch {
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
} catch (error) {
|
|
77
|
+
parseErrors.push(`${entry.name}: invalid YAML frontmatter (${error.message})`);
|
|
78
|
+
continue;
|
|
75
79
|
}
|
|
76
80
|
out.push({ name: entry.name, desc });
|
|
77
81
|
}
|
|
78
|
-
return out;
|
|
82
|
+
return { skills: out, parseErrors };
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
const skills = discoverSkills();
|
|
85
|
+
const { skills, parseErrors } = discoverSkills();
|
|
86
|
+
if (parseErrors.length > 0) {
|
|
87
|
+
console.error('\n❌ DESCRIPTION COLLISION CHECK FAILED: malformed skill frontmatter.');
|
|
88
|
+
for (const error of parseErrors) console.error(` ${error}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
82
91
|
if (skills.length < 2) {
|
|
83
92
|
console.error('Not enough skills found to compare.');
|
|
84
93
|
process.exit(1);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Progressive-disclosure checks for the skill catalog.
|
|
4
|
+
*
|
|
5
|
+
* The top-level SKILL.md is the routed contract. Workflow docs explain the
|
|
6
|
+
* behavior in plain language, and references/guides hold detail that is
|
|
7
|
+
* loaded only when needed. Keeping these checks separate makes the policy
|
|
8
|
+
* usable from consistency-check.js and from focused mechanism tests.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const DEFAULT_ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
const DEFAULT_MIN_MERMAID = 1;
|
|
16
|
+
|
|
17
|
+
function discoverSkills(root) {
|
|
18
|
+
return fs.readdirSync(root, { withFileTypes: true })
|
|
19
|
+
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.') && fs.existsSync(path.join(root, entry.name, 'SKILL.md')))
|
|
20
|
+
.map(entry => entry.name)
|
|
21
|
+
.sort();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function discoverWorkflowDocs(root) {
|
|
25
|
+
const dir = path.join(root, 'docs', 'workflows');
|
|
26
|
+
if (!fs.existsSync(dir)) return [];
|
|
27
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
28
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
29
|
+
.map(entry => entry.name.slice(0, -3))
|
|
30
|
+
.sort();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function bodyWithoutFrontmatter(raw) {
|
|
34
|
+
return raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mermaidBlocks(text) {
|
|
38
|
+
const blocks = [];
|
|
39
|
+
const re = /```mermaid\s*\r?\n([\s\S]*?)\r?\n```/gi;
|
|
40
|
+
let match;
|
|
41
|
+
while ((match = re.exec(text)) !== null) blocks.push(match[1].trim());
|
|
42
|
+
return blocks;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isMeaningfulMermaid(block) {
|
|
46
|
+
if (!block) return false;
|
|
47
|
+
const lines = block.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
48
|
+
if (lines.length < 2) return false;
|
|
49
|
+
const edgeTokens = ['-.->', '-..->', '==>', '-->', '->>', '---', '-.-', '~~~', '-..-'];
|
|
50
|
+
for (const line of lines.slice(1)) {
|
|
51
|
+
let nesting = 0;
|
|
52
|
+
let quote = false;
|
|
53
|
+
for (let i = 0; i < line.length; i++) {
|
|
54
|
+
const char = line[i];
|
|
55
|
+
if (char === '"' && line[i - 1] !== '\\') {
|
|
56
|
+
quote = !quote;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (quote) continue;
|
|
60
|
+
if ('([{'.includes(char)) {
|
|
61
|
+
nesting++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (')]}'.includes(char)) {
|
|
65
|
+
nesting = Math.max(0, nesting - 1);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (nesting !== 0) continue;
|
|
69
|
+
if (edgeTokens.some(token => line.startsWith(token, i))) return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function deepDivePointers(body) {
|
|
76
|
+
const pointers = [];
|
|
77
|
+
const re = /^Deep dive:\s*(.+)$/gim;
|
|
78
|
+
let match;
|
|
79
|
+
while ((match = re.exec(body)) !== null) {
|
|
80
|
+
const line = match[1]
|
|
81
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$2')
|
|
82
|
+
.replace(/`/g, '');
|
|
83
|
+
pointers.push(...line.split(/\s+\+\s+/).map(value => value.trim()).filter(Boolean));
|
|
84
|
+
}
|
|
85
|
+
return pointers;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function cleanPointer(pointer) {
|
|
89
|
+
return pointer.replace(/[),.;:]+$/, '').trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isInside(root, target) {
|
|
93
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
94
|
+
return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function globRegExp(segment) {
|
|
98
|
+
const escaped = segment.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
99
|
+
return new RegExp(`^${escaped.replace(/\*/g, '.*').replace(/\?/g, '.')}$`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function globMatches(root, base, segments) {
|
|
103
|
+
if (!isInside(root, base)) return false;
|
|
104
|
+
if (!segments.length) return fs.existsSync(base);
|
|
105
|
+
if (!fs.existsSync(base) || !fs.statSync(base).isDirectory()) return false;
|
|
106
|
+
|
|
107
|
+
const [segment, ...rest] = segments;
|
|
108
|
+
if (!segment) return globMatches(root, base, rest);
|
|
109
|
+
if (!segment.includes('*') && !segment.includes('?')) {
|
|
110
|
+
const next = path.resolve(base, segment);
|
|
111
|
+
return isInside(root, next) && globMatches(root, next, rest);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const pattern = globRegExp(segment);
|
|
115
|
+
return fs.readdirSync(base).some(entry => pattern.test(entry) && globMatches(root, path.join(base, entry), rest));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function pointerTarget(root, skill, pointer, skillDirs) {
|
|
119
|
+
const clean = cleanPointer(pointer);
|
|
120
|
+
const match = clean.match(/^<([^>/]+)>\/(.*)$/);
|
|
121
|
+
if (match && match[1] === 'skills-repo-root') return path.resolve(root, match[2]);
|
|
122
|
+
if (match && match[1] === 'this-skill-dir') return path.resolve(root, skill, match[2]);
|
|
123
|
+
const first = clean.split('/')[0];
|
|
124
|
+
if (skillDirs.includes(first)) return path.resolve(root, clean);
|
|
125
|
+
return path.resolve(root, skill, clean);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function pointerResolves(root, skill, pointer, skillDirs) {
|
|
129
|
+
const clean = cleanPointer(pointer);
|
|
130
|
+
const wildcard = clean.search(/[?*]/);
|
|
131
|
+
if (wildcard === -1) {
|
|
132
|
+
const target = pointerTarget(root, skill, clean, skillDirs);
|
|
133
|
+
return isInside(root, target) && fs.existsSync(target);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const slash = clean.lastIndexOf('/', wildcard);
|
|
137
|
+
const basePointer = slash === -1 ? '.' : clean.slice(0, slash + 1);
|
|
138
|
+
const pattern = clean.slice(slash + 1);
|
|
139
|
+
const base = pointerTarget(root, skill, basePointer, skillDirs);
|
|
140
|
+
return globMatches(root, base, pattern.split('/'));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function checkDisclosure(root = DEFAULT_ROOT, options = {}) {
|
|
144
|
+
const minMermaid = options.minMermaid === undefined ? DEFAULT_MIN_MERMAID : options.minMermaid;
|
|
145
|
+
const skills = discoverSkills(root);
|
|
146
|
+
const workflows = discoverWorkflowDocs(root);
|
|
147
|
+
const failures = [];
|
|
148
|
+
const add = (check, detail) => failures.push({ check, detail });
|
|
149
|
+
|
|
150
|
+
const skillSet = new Set(skills);
|
|
151
|
+
for (const skill of skills) {
|
|
152
|
+
const skillPath = path.join(root, skill, 'SKILL.md');
|
|
153
|
+
const raw = fs.readFileSync(skillPath, 'utf8');
|
|
154
|
+
const body = bodyWithoutFrontmatter(raw);
|
|
155
|
+
const refsDir = path.join(root, skill, 'references');
|
|
156
|
+
if (!fs.existsSync(refsDir) || !fs.statSync(refsDir).isDirectory()) {
|
|
157
|
+
add(`${skill}: references directory`, 'missing references/ for lazy-loaded detail');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const pointers = deepDivePointers(body);
|
|
161
|
+
if (pointers.length === 0) {
|
|
162
|
+
add(`${skill}: Deep dive pointer`, 'SKILL.md must name one lazy-loaded entry point');
|
|
163
|
+
} else {
|
|
164
|
+
for (const pointer of pointers) {
|
|
165
|
+
if (!pointerResolves(root, skill, pointer, skills)) {
|
|
166
|
+
add(`${skill}: Deep dive pointer resolves`, `${pointer} does not resolve from ${skill}/SKILL.md`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (/```mermaid\b/i.test(body)) {
|
|
172
|
+
add(`${skill}: no Mermaid in SKILL.md`, 'put diagram source in docs/workflows/<skill>.md');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const skill of skills) {
|
|
177
|
+
const workflowPath = path.join(root, 'docs', 'workflows', `${skill}.md`);
|
|
178
|
+
if (!fs.existsSync(workflowPath)) {
|
|
179
|
+
add(`${skill}: workflow document`, `missing docs/workflows/${skill}.md`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const workflow = fs.readFileSync(workflowPath, 'utf8');
|
|
183
|
+
const blocks = mermaidBlocks(workflow);
|
|
184
|
+
if (blocks.length < minMermaid) {
|
|
185
|
+
add(`${skill}: Mermaid coverage`, `${blocks.length} fence(s); requires at least ${minMermaid}`);
|
|
186
|
+
}
|
|
187
|
+
if (blocks.some(block => !isMeaningfulMermaid(block))) {
|
|
188
|
+
add(`${skill}: meaningful Mermaid coverage`, 'each Mermaid fence needs nodes and at least one transition');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const workflow of workflows) {
|
|
193
|
+
if (!skillSet.has(workflow)) {
|
|
194
|
+
add(`${workflow}: workflow pairing`, `orphan docs/workflows/${workflow}.md`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { failures, skills, workflows, minMermaid };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function main() {
|
|
202
|
+
const rootArg = process.argv.indexOf('--root');
|
|
203
|
+
const root = rootArg === -1 ? DEFAULT_ROOT : path.resolve(process.argv[rootArg + 1]);
|
|
204
|
+
const result = checkDisclosure(root);
|
|
205
|
+
for (const failure of result.failures) console.error(`FAIL ${failure.check}: ${failure.detail}`);
|
|
206
|
+
console.log(`Checked disclosure for ${result.skills.length} skill(s) and ${result.workflows.length} workflow doc(s).`);
|
|
207
|
+
if (result.failures.length) {
|
|
208
|
+
console.error(`DISCLOSURE CHECK FAILED: ${result.failures.length} problem(s).`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
console.log('ALL DISCLOSURE CHECKS PASSED.');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (require.main === module) main();
|
|
215
|
+
|
|
216
|
+
module.exports = {
|
|
217
|
+
bodyWithoutFrontmatter,
|
|
218
|
+
checkDisclosure,
|
|
219
|
+
deepDivePointers,
|
|
220
|
+
discoverSkills,
|
|
221
|
+
discoverWorkflowDocs,
|
|
222
|
+
isMeaningfulMermaid,
|
|
223
|
+
mermaidBlocks,
|
|
224
|
+
pointerResolves,
|
|
225
|
+
};
|