ceo-orchestration 1.0.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/.claude/adr/ADR-001-runtime-state-directory.md +164 -0
- package/.claude/adr/ADR-002-hooks-package-layout.md +228 -0
- package/.claude/adr/ADR-003-branch-protection-replaces-skill-signing.md +266 -0
- package/.claude/adr/ADR-004-defer-bash-legacy-removal.md +171 -0
- package/.claude/adr/ADR-005-event-stream-v2.md +153 -0
- package/.claude/adr/ADR-006-registry-derived-manifests.md +145 -0
- package/.claude/adr/ADR-007-spec-v1-semver-rc-policy.md +159 -0
- package/.claude/adr/ADR-008-hook-adapter-layer.md +169 -0
- package/.claude/adr/ADR-009-squad-contract.md +167 -0
- package/.claude/adr/ADR-010-canonical-edit-sentinel.md +181 -0
- package/.claude/adr/ADR-011-event-stream-v2.1-injection-flag.md +150 -0
- package/.claude/adr/ADR-012-cross-adapter-golden-fixtures.md +182 -0
- package/.claude/adr/ADR-013-squad-trading-hft.md +135 -0
- package/.claude/adr/ADR-014-hook-migration-batch-policy.md +197 -0
- package/.claude/adr/ADR-015-reflexion-v2-outcome-loop.md +248 -0
- package/.claude/adr/ADR-016-spawn-token-tracking.md +179 -0
- package/.claude/adr/ADR-017-lesson-pruning-policy.md +193 -0
- package/.claude/adr/ADR-018-claim-grammar.md +302 -0
- package/.claude/adr/ADR-019-AMEND-1-confidence-gate-block-mode-lifecycle.md +128 -0
- package/.claude/adr/ADR-019-AMEND-2-CLASS-SHA_EXISTS-promote-to-high-confidence-block.md +67 -0
- package/.claude/adr/ADR-019-confidence-gate-enforcement-lifecycle.md +221 -0
- package/.claude/adr/ADR-020-lesson-pruning-policy-v2.md +171 -0
- package/.claude/adr/ADR-021-e2e-harness-contract.md +189 -0
- package/.claude/adr/ADR-022-reserved-slot.md +52 -0
- package/.claude/adr/ADR-023-docs-freshness-lifecycle.md +184 -0
- package/.claude/adr/ADR-024-perf-baseline-policy.md +222 -0
- package/.claude/adr/ADR-025-squad-edtech.md +236 -0
- package/.claude/adr/ADR-026-squad-government.md +263 -0
- package/.claude/adr/ADR-027-unified-agent-state-backend.md +266 -0
- package/.claude/adr/ADR-028-multi-llm-canonical-parity.md +244 -0
- package/.claude/adr/ADR-029-lexical-tfidf-retrieval.md +205 -0
- package/.claude/adr/ADR-030-llm-as-judge-methodology.md +336 -0
- package/.claude/adr/ADR-031-self-improving-skills.md +221 -0
- package/.claude/adr/ADR-032-interactive-debate-protocol.md +337 -0
- package/.claude/adr/ADR-033-cost-budget-enforcement.md +275 -0
- package/.claude/adr/ADR-034-shared-working-memory.md +233 -0
- package/.claude/adr/ADR-035-otel-export.md +242 -0
- package/.claude/adr/ADR-036-output-safety.md +263 -0
- package/.claude/adr/ADR-037-chaos-testing-methodology.md +289 -0
- package/.claude/adr/ADR-038-session-graph-continuity.md +243 -0
- package/.claude/adr/ADR-039-skill-marketplace-protocol.md +170 -0
- package/.claude/adr/ADR-040-AMEND-2-credential-blocking.md +390 -0
- package/.claude/adr/ADR-040-live-adapter-activation-contract.md +285 -0
- package/.claude/adr/ADR-041-transition-log-convention.md +272 -0
- package/.claude/adr/ADR-042-AMEND-1-read-only-mcp-tools-expansion.md +214 -0
- package/.claude/adr/ADR-042-mcp-server-contract.md +727 -0
- package/.claude/adr/ADR-043-soc2-audit-trail-mapping.md +503 -0
- package/.claude/adr/ADR-044-formal-verification-pilot.md +505 -0
- package/.claude/adr/ADR-045-policy-as-code-engine.md +705 -0
- package/.claude/adr/ADR-046-deterministic-replay.md +167 -0
- package/.claude/adr/ADR-047-predictive-budgeting.md +213 -0
- package/.claude/adr/ADR-048-cross-plan-memory.md +227 -0
- package/.claude/adr/ADR-049-policy-engine-dual-path-deprecation.md +96 -0
- package/.claude/adr/ADR-049a-worktree-orchestration-policy.md +414 -0
- package/.claude/adr/ADR-050-native-subagents-dual-rail.md +165 -0
- package/.claude/adr/ADR-051-skill-reference-expanded-trust-boundary.md +282 -0
- package/.claude/adr/ADR-052-multi-model-dispatch-by-role.md +444 -0
- package/.claude/adr/ADR-053-sentinel-hmac-deferred.md +227 -0
- package/.claude/adr/ADR-054-AMEND-1-anthropic-admin-key-tier.md +131 -0
- package/.claude/adr/ADR-054-github-token-rotation.md +111 -0
- package/.claude/adr/ADR-055-AMEND-1-spool-writer-async-drain.md +170 -0
- package/.claude/adr/ADR-055-AMEND-2-chain-reset-marker.md +126 -0
- package/.claude/adr/ADR-055-AMEND-3-opportunistic-drain-nonblocking.md +183 -0
- package/.claude/adr/ADR-055-audit-log-hmac-chain.md +264 -0
- package/.claude/adr/ADR-056-hook-lifecycle-expansion.md +261 -0
- package/.claude/adr/ADR-057-output-scan-redaction.md +268 -0
- package/.claude/adr/ADR-058-brainstorm-gate-and-two-pass-review.md +240 -0
- package/.claude/adr/ADR-059-skill-bootstrap-env-knob.md +204 -0
- package/.claude/adr/ADR-060-curated-skill-import-pipeline.md +464 -0
- package/.claude/adr/ADR-061-runtime-cost-streaming.md +171 -0
- package/.claude/adr/ADR-062-AMEND-1-rag-conditional-default-on-supersedes-opt-in.md +232 -0
- package/.claude/adr/ADR-062-rag-sidecar-mcp-opt-in.md +231 -0
- package/.claude/adr/ADR-063-agent-eval-empirical-dispatch-validation.md +609 -0
- package/.claude/adr/ADR-064-dynamic-tier-policy-learned-dispatch.md +288 -0
- package/.claude/adr/ADR-065-audit-event-naming-convention.md +185 -0
- package/.claude/adr/ADR-066-context-mode-orthogonal-to-manifest.md +92 -0
- package/.claude/adr/ADR-067-ceo-model-downshift-static-routing.md +219 -0
- package/.claude/adr/ADR-069-wondelai-skills-import-refused.md +183 -0
- package/.claude/adr/ADR-070-audit-emit-package-layout.md +228 -0
- package/.claude/adr/ADR-071-benchmark-comparison-methodology.md +209 -0
- package/.claude/adr/ADR-072-test-discovery-via-conftest.md +184 -0
- package/.claude/adr/ADR-073-semver-bump-criteria-sprint-32.md +209 -0
- package/.claude/adr/ADR-074-sprint-32-phase-3-b1-refused.md +320 -0
- package/.claude/adr/ADR-075-sprint-32-phase-5-b5-benchmark-refused.md +250 -0
- package/.claude/adr/ADR-076-sprint-32-final-closure.md +218 -0
- package/.claude/adr/ADR-077-2026-04-24-webfetch-injection-incident.md +203 -0
- package/.claude/adr/ADR-078-sentinel-cosign-clarification.md +295 -0
- package/.claude/adr/ADR-079-prompt-sha-salt-hmac-impact.md +221 -0
- package/.claude/adr/ADR-080-rail-anomaly-h4-defense-in-depth.md +1143 -0
- package/.claude/adr/ADR-081-token-as-time-unit.md +272 -0
- package/.claude/adr/ADR-082-l7c-mitigation-default-on.md +240 -0
- package/.claude/adr/ADR-083-mcp-injection-scanner.md +225 -0
- package/.claude/adr/ADR-084-multi-adapter-refused-claude-only.md +152 -0
- package/.claude/adr/ADR-085-framework-landscape-claude-only.md +183 -0
- package/.claude/adr/ADR-086-checkpointing-refused.md +124 -0
- package/.claude/adr/ADR-087-AMEND-1-otel-consume-native-opt-in.md +217 -0
- package/.claude/adr/ADR-087-otel-emit-refused.md +136 -0
- package/.claude/adr/ADR-088-guardrails-library-refused.md +128 -0
- package/.claude/adr/ADR-089-sec-cluster-disposition.md +182 -0
- package/.claude/adr/ADR-090-framework-activation-defaults.md +217 -0
- package/.claude/adr/ADR-091-dogfood-validation-deferred.md +128 -0
- package/.claude/adr/ADR-092-plan-closure-honest-deferral.md +165 -0
- package/.claude/adr/ADR-093-refused-adr-moratorium.md +181 -0
- package/.claude/adr/ADR-094-claude-sdk-compat-version-pinning.md +160 -0
- package/.claude/adr/ADR-095-calendar-gate-retraction.md +202 -0
- package/.claude/adr/ADR-096-vibecoder-only-by-design.md +215 -0
- package/.claude/adr/ADR-097-function-length-advisory-permanent.md +186 -0
- package/.claude/adr/ADR-098-ceo-boot-audit-emit-register.md +251 -0
- package/.claude/adr/ADR-099-changesets-adoption.md +245 -0
- package/.claude/adr/ADR-100-trusted-dependencies-re-affirm.md +208 -0
- package/.claude/adr/ADR-101-replay-redact-helper.md +106 -0
- package/.claude/adr/ADR-102-mcp-introspection-extends-042.md +165 -0
- package/.claude/adr/ADR-103-calendar-gate-final-purge.md +121 -0
- package/.claude/adr/ADR-104-AMEND-1-aek-dated-promotion-criteria.md +338 -0
- package/.claude/adr/ADR-104-adaptive-execution-kernel-advisory.md +210 -0
- package/.claude/adr/ADR-105-multi-llm-coordinated-supersede.md +126 -0
- package/.claude/adr/ADR-106-codex-mcp-adapter-contract.md +153 -0
- package/.claude/adr/ADR-107-pair-rail-mandatory-l2-plus.md +189 -0
- package/.claude/adr/ADR-108-cross-llm-veto-floor.md +129 -0
- package/.claude/adr/ADR-109-codex-skill-rehash-protocol.md +104 -0
- package/.claude/adr/ADR-110-codex-pretool-enforcement.md +94 -0
- package/.claude/adr/ADR-111-locked-corpus-governance.md +191 -0
- package/.claude/adr/ADR-112-grandfather-cap-scope-clarification.md +192 -0
- package/.claude/adr/ADR-113-plan-084-canonical-guard-extension.md +59 -0
- package/.claude/adr/ADR-114-codex-egress-redaction-symmetry.md +72 -0
- package/.claude/adr/ADR-115-post-sota-maintenance-mode.md +152 -0
- package/.claude/adr/ADR-116-AMEND-1-kernel-extension-v2.md +640 -0
- package/.claude/adr/ADR-116-kernel-hard-deny-tier-0-extension.md +465 -0
- package/.claude/adr/ADR-117-adr-id-collision-rename-policy.md +279 -0
- package/.claude/adr/ADR-118-AMEND-1-phase-c-enforcing-flip.md +191 -0
- package/.claude/adr/ADR-118-god-mode-auto-usable-state.md +338 -0
- package/.claude/adr/ADR-119-sentinel-unlock-contract.md +133 -0
- package/.claude/adr/ADR-120-pii-core-promotion.md +280 -0
- package/.claude/adr/ADR-121-sentinel-signers-rotation-policy.md +434 -0
- package/.claude/adr/ADR-122-dpop-mcp-bearer-replay-defense.md +232 -0
- package/.claude/adr/ADR-123-streaming-adapter-canonical-source.md +130 -0
- package/.claude/adr/ADR-124-post-audit-sota-execution-mode.md +362 -0
- package/.claude/adr/ADR-125-risk-tiered-defaulting-doctrine.md +355 -0
- package/.claude/adr/ADR-126-governed-sidecar-capability-model.md +509 -0
- package/.claude/adr/ADR-127-pair-rail-advisory-promotion.md +218 -0
- package/.claude/adr/ADR-128-c2-vector-memory-capability-class.md +380 -0
- package/.claude/adr/ADR-129-AMEND-1-key-floor-waiver-lift.md +249 -0
- package/.claude/adr/ADR-129-c1-crypto-capability-class.md +289 -0
- package/.claude/adr/ADR-131-c5-dev-tools-capability-class.md +215 -0
- package/.claude/adr/ADR-132-goap-advisory-planning-doctrine.md +333 -0
- package/.claude/adr/ADR-133-autonomous-loop-opt-in-capability-doctrine.md +440 -0
- package/.claude/adr/ADR-135-AMEND-1-write-mode-trust-boundary.md +457 -0
- package/.claude/adr/ADR-135-AMEND-2-write-mode-activation.md +175 -0
- package/.claude/adr/ADR-135-federation-contract-mvp.md +253 -0
- package/.claude/adr/ADR-136-AMEND-1-workflow-primitive-adoption.md +139 -0
- package/.claude/adr/ADR-136-workflow-engine-doctrine.md +155 -0
- package/.claude/adr/ADR-137-skill-priority-stack-decision.md +162 -0
- package/.claude/adr/ADR-138-ac-format-priority-and-story-anchor.md +149 -0
- package/.claude/adr/ADR-139-coverage-doctrine-tiered.md +133 -0
- package/.claude/adr/ADR-140-receiving-review-doctrine.md +136 -0
- package/.claude/adr/ADR-141-reduce-protocol.md +124 -0
- package/.claude/adr/ADR-142-opus-4-8-model-bump.md +116 -0
- package/.claude/adr/ADR-143-git-hook-bypass-guard.md +166 -0
- package/.claude/adr/ADR-144-subagent-model-tiering-frontmatter.md +111 -0
- package/.claude/adr/ADR-145-cross-model-review-persona-demand-modality.md +103 -0
- package/.claude/adr/ADR-146-adversary-review-hook.md +122 -0
- package/.claude/adr/ADR-147-eval-harness-doctrine.md +109 -0
- package/.claude/adr/ADR-148-canonical-pricing-source.md +123 -0
- package/.claude/adr/ADR-149-model-id-allowlist.md +196 -0
- package/.claude/adr/ADR-150-commit-signing-policy.md +12 -0
- package/.claude/adr/ADR-151-fan-plan-advisory-bridge.md +178 -0
- package/.claude/adr/ADR-152-claude-md-decomposition.md +262 -0
- package/.claude/adr/ADR-153-compaction-continuity.md +141 -0
- package/.claude/adr/ADR-154-updatedinput-single-rewriter.md +68 -0
- package/.claude/adr/ADR-155-install-baseline-manifest.md +66 -0
- package/.claude/adr/ADR-156-constitution-sync-cascade.md +122 -0
- package/.claude/adr/README.md +392 -0
- package/.claude/adversary.md +116 -0
- package/.claude/agent-metrics.md +101 -0
- package/.claude/agents/_dispatch.md +30 -0
- package/.claude/agents/_probe_architect.md +45 -0
- package/.claude/agents/_probe_canonical_edit.md +46 -0
- package/.claude/agents/_probe_missing_skill.md +42 -0
- package/.claude/agents/code-reviewer.md +166 -0
- package/.claude/agents/devops.md +114 -0
- package/.claude/agents/identity-trust-architect.md +234 -0
- package/.claude/agents/incident-commander.md +285 -0
- package/.claude/agents/llm-finops-architect.md +265 -0
- package/.claude/agents/performance-engineer.md +148 -0
- package/.claude/agents/qa-architect.md +167 -0
- package/.claude/agents/security-engineer.md +192 -0
- package/.claude/agents/threat-detection-engineer.md +238 -0
- package/.claude/benchmarks/_schemas/judge-prompt.md +26 -0
- package/.claude/benchmarks/_schemas/judge-rubric-example.json +11 -0
- package/.claude/benchmarks/_schemas/judge-rubric.yaml +39 -0
- package/.claude/benchmarks/calibration-grades.jsonl +6 -0
- package/.claude/benchmarks/human-sample-calibration.md +232 -0
- package/.claude/benchmarks/judge-rotation-schedule.md +61 -0
- package/.claude/benchmarks/retrieval-judgment-set.yaml +194 -0
- package/.claude/benchmarks/tests/test_retrieval_recall_gate.py +330 -0
- package/.claude/commands/agent-budget.md +105 -0
- package/.claude/commands/architect.md +130 -0
- package/.claude/commands/audit-page.md +149 -0
- package/.claude/commands/audit-tokens.md +89 -0
- package/.claude/commands/ceo-boot.md +118 -0
- package/.claude/commands/ceo-info.md +71 -0
- package/.claude/commands/debate.md +258 -0
- package/.claude/commands/effort.md +99 -0
- package/.claude/commands/fan-plan.md +129 -0
- package/.claude/commands/goap.md +163 -0
- package/.claude/commands/lesson-review.md +66 -0
- package/.claude/commands/memory-scratchpad.md +100 -0
- package/.claude/commands/onboard.md +204 -0
- package/.claude/commands/pitfall.md +54 -0
- package/.claude/commands/resume.md +90 -0
- package/.claude/commands/self-test.md +83 -0
- package/.claude/commands/skill-review.md +102 -0
- package/.claude/commands/spawn.md +212 -0
- package/.claude/commands/squad-install.md +94 -0
- package/.claude/commands/status.md +177 -0
- package/.claude/commands/terse.md +81 -0
- package/.claude/commands/veto-check.md +63 -0
- package/.claude/data/audit-registry.golden.txt +306 -0
- package/.claude/data/canonical_models.json +1030 -0
- package/.claude/data/confidence-gate-class-tiers.json +24 -0
- package/.claude/data/cookbook_patterns.json +139 -0
- package/.claude/data/federation/enabled.md +34 -0
- package/.claude/data/federation/lan-enabled.md +38 -0
- package/.claude/data/federation/peers.example.yaml +89 -0
- package/.claude/data/goap/action-cost-baseline.json +29 -0
- package/.claude/dispatcher/disable_predicate_eval.py +630 -0
- package/.claude/dispatcher/routing-matrix-loader.py +874 -0
- package/.claude/dispatcher/routing-matrix.yaml +343 -0
- package/.claude/dispatcher/tests/conftest.py +11 -0
- package/.claude/dispatcher/tests/test_disable_predicate_eval.py +424 -0
- package/.claude/dispatcher/tests/test_routing_matrix_loader.py +461 -0
- package/.claude/docs/dpop-scope.md +79 -0
- package/.claude/docs/sentinel-signers-rotation-DRAFT.md +117 -0
- package/.claude/eval/README.md +73 -0
- package/.claude/eval/reporter.py +109 -0
- package/.claude/eval/runner.py +532 -0
- package/.claude/eval/self_test.yaml +57 -0
- package/.claude/eval/tasks/__init__.py +185 -0
- package/.claude/eval/tasks/t01_fix_off_by_one.py +52 -0
- package/.claude/eval/tasks/t02_implement_fizzbuzz.py +65 -0
- package/.claude/eval/tasks/t03_json_config_parse.py +80 -0
- package/.claude/eval/tasks/t04_refactor_dedupe.py +71 -0
- package/.claude/eval/tasks/t05_add_unit_test.py +77 -0
- package/.claude/eval/tasks/t06_palindrome.py +58 -0
- package/.claude/eval/tasks/t07_sql_param_fix.py +69 -0
- package/.claude/eval/tasks/t08_word_count.py +53 -0
- package/.claude/eval/tasks/t09_readme_doc.py +64 -0
- package/.claude/eval/tasks/t10_binary_search.py +58 -0
- package/.claude/frontend-team.md +202 -0
- package/.claude/governance/README.md +37 -0
- package/.claude/governance/audit_tokens_allowlist.json +37 -0
- package/.claude/governance/codex-cli-binary-sha256.txt +32 -0
- package/.claude/governance/codex-cli-pin.txt +26 -0
- package/.claude/governance/function-length-grandfather.yaml +2095 -0
- package/.claude/governance/governance-waivers.yaml +28 -0
- package/.claude/governance/pair-rail-inputs-hash-manifest.txt +32 -0
- package/.claude/governance/pair-rail-verdict-template.md +58 -0
- package/.claude/governance/pair-rail-verdict-v1.16.0-rc.1.md +120 -0
- package/.claude/governance/pair-rail-verdict-v1.16.0.md +64 -0
- package/.claude/gpg-revocations.jsonl +1 -0
- package/.claude/hooks/SessionEnd.py +353 -0
- package/.claude/hooks/SessionStart.py +345 -0
- package/.claude/hooks/Stop.py +195 -0
- package/.claude/hooks/UserPromptSubmit.py +329 -0
- package/.claude/hooks/_lib/EXECUTION-CONTEXT-DEFERRED.md +82 -0
- package/.claude/hooks/_lib/__init__.py +26 -0
- package/.claude/hooks/_lib/action_required.py +592 -0
- package/.claude/hooks/_lib/adapters/__init__.py +87 -0
- package/.claude/hooks/_lib/adapters/_constants.py +127 -0
- package/.claude/hooks/_lib/adapters/claude.py +167 -0
- package/.claude/hooks/_lib/adapters/codex.py +754 -0
- package/.claude/hooks/_lib/adapters/live/__init__.py +378 -0
- package/.claude/hooks/_lib/adapters/live/_breaker.py +309 -0
- package/.claude/hooks/_lib/adapters/live/_cost.py +389 -0
- package/.claude/hooks/_lib/adapters/live/_policy.py +319 -0
- package/.claude/hooks/_lib/adapters/live/_result.py +206 -0
- package/.claude/hooks/_lib/adapters/live/_transport.py +681 -0
- package/.claude/hooks/_lib/adapters/live/claude.py +1027 -0
- package/.claude/hooks/_lib/adapters/live/claude_batch.py +652 -0
- package/.claude/hooks/_lib/adapters/live/gemini.py +270 -0
- package/.claude/hooks/_lib/adapters/live/local.py +195 -0
- package/.claude/hooks/_lib/adapters/live/openai.py +371 -0
- package/.claude/hooks/_lib/adversary_rules.py +196 -0
- package/.claude/hooks/_lib/agent_frontmatter.py +288 -0
- package/.claude/hooks/_lib/audit_emit.py +11746 -0
- package/.claude/hooks/_lib/audit_emit_dispatch.py +179 -0
- package/.claude/hooks/_lib/audit_hmac.py +1146 -0
- package/.claude/hooks/_lib/audit_rotation.py +101 -0
- package/.claude/hooks/_lib/canonical_json.py +145 -0
- package/.claude/hooks/_lib/codex_cli_shape.py +502 -0
- package/.claude/hooks/_lib/codex_egress_redact.py +185 -0
- package/.claude/hooks/_lib/confidence_labels.py +338 -0
- package/.claude/hooks/_lib/contract.py +254 -0
- package/.claude/hooks/_lib/cookbook_patterns.py +136 -0
- package/.claude/hooks/_lib/cost_envelope.py +719 -0
- package/.claude/hooks/_lib/credentials.py +188 -0
- package/.claude/hooks/_lib/effective_config.py +767 -0
- package/.claude/hooks/_lib/egress_taxonomy.py +448 -0
- package/.claude/hooks/_lib/embeddings.py +322 -0
- package/.claude/hooks/_lib/env_guard.py +353 -0
- package/.claude/hooks/_lib/env_persist_allowlist.py +147 -0
- package/.claude/hooks/_lib/escalation_signals.py +335 -0
- package/.claude/hooks/_lib/estimation/__init__.py +12 -0
- package/.claude/hooks/_lib/estimation/bayesian.py +147 -0
- package/.claude/hooks/_lib/estimation/pipeline.py +209 -0
- package/.claude/hooks/_lib/exceptions.py +101 -0
- package/.claude/hooks/_lib/execution_context.py +208 -0
- package/.claude/hooks/_lib/federation/__init__.py +104 -0
- package/.claude/hooks/_lib/federation/audit_chain.py +118 -0
- package/.claude/hooks/_lib/federation/audit_chain_ext.py +408 -0
- package/.claude/hooks/_lib/federation/cert_inspector.py +573 -0
- package/.claude/hooks/_lib/federation/client.py +327 -0
- package/.claude/hooks/_lib/federation/handlers/__init__.py +30 -0
- package/.claude/hooks/_lib/federation/handlers/audit_event_batch.py +346 -0
- package/.claude/hooks/_lib/federation/handlers/audit_event_push.py +395 -0
- package/.claude/hooks/_lib/federation/handlers/peer_register.py +484 -0
- package/.claude/hooks/_lib/federation/handlers/peer_revoke.py +356 -0
- package/.claude/hooks/_lib/federation/identity.py +1056 -0
- package/.claude/hooks/_lib/federation/rate_limit.py +476 -0
- package/.claude/hooks/_lib/federation/replay.py +284 -0
- package/.claude/hooks/_lib/federation/scopes.py +168 -0
- package/.claude/hooks/_lib/federation/server.py +2218 -0
- package/.claude/hooks/_lib/file_walker.py +145 -0
- package/.claude/hooks/_lib/filelock.py +191 -0
- package/.claude/hooks/_lib/frontmatter.py +124 -0
- package/.claude/hooks/_lib/git_bypass.py +971 -0
- package/.claude/hooks/_lib/gpg_verify.py +356 -0
- package/.claude/hooks/_lib/guardrail_validator.py +478 -0
- package/.claude/hooks/_lib/injection_patterns.py +252 -0
- package/.claude/hooks/_lib/injection_salt.py +160 -0
- package/.claude/hooks/_lib/mcp/__init__.py +5 -0
- package/.claude/hooks/_lib/mcp/bearer_replay.py +279 -0
- package/.claude/hooks/_lib/mcp/canonical_guard.py +1140 -0
- package/.claude/hooks/_lib/mcp_bearer_friction.py +475 -0
- package/.claude/hooks/_lib/mcp_injection_scan.py +250 -0
- package/.claude/hooks/_lib/mcp_routing.py +151 -0
- package/.claude/hooks/_lib/memory_shared.py +592 -0
- package/.claude/hooks/_lib/metrics.py +241 -0
- package/.claude/hooks/_lib/model_routing.py +227 -0
- package/.claude/hooks/_lib/otel/__init__.py +34 -0
- package/.claude/hooks/_lib/otel/bounded_exporter.py +373 -0
- package/.claude/hooks/_lib/otel/hook_bridge.py +53 -0
- package/.claude/hooks/_lib/otel/queue.py +229 -0
- package/.claude/hooks/_lib/otel_emit.py +604 -0
- package/.claude/hooks/_lib/output_scan.py +1062 -0
- package/.claude/hooks/_lib/output_scan_dedup.py +379 -0
- package/.claude/hooks/_lib/pair_rail_decide.py +244 -0
- package/.claude/hooks/_lib/payload.py +195 -0
- package/.claude/hooks/_lib/persona_routing.py +244 -0
- package/.claude/hooks/_lib/pii_patterns.py +851 -0
- package/.claude/hooks/_lib/plan_frontmatter.py +166 -0
- package/.claude/hooks/_lib/policy.py +1527 -0
- package/.claude/hooks/_lib/policy_preprocessors.py +462 -0
- package/.claude/hooks/_lib/rag_bridge.py +624 -0
- package/.claude/hooks/_lib/rag_events.py +171 -0
- package/.claude/hooks/_lib/rag_router.py +253 -0
- package/.claude/hooks/_lib/redact.py +228 -0
- package/.claude/hooks/_lib/replay_redact.py +511 -0
- package/.claude/hooks/_lib/scratchpad_lib.py +225 -0
- package/.claude/hooks/_lib/secret_patterns.py +905 -0
- package/.claude/hooks/_lib/sentinel_signers.py +740 -0
- package/.claude/hooks/_lib/spec_context_sanitizer.py +258 -0
- package/.claude/hooks/_lib/spool_writer.py +2613 -0
- package/.claude/hooks/_lib/state_store.py +476 -0
- package/.claude/hooks/_lib/subagent_dispatch.py +244 -0
- package/.claude/hooks/_lib/swarm_circuit_breaker.py +203 -0
- package/.claude/hooks/_lib/swarm_enable_gate.py +152 -0
- package/.claude/hooks/_lib/team.py +128 -0
- package/.claude/hooks/_lib/test_isolation.py +352 -0
- package/.claude/hooks/_lib/testing.py +351 -0
- package/.claude/hooks/_lib/tests/federation/test_federation_attack_surface.py +251 -0
- package/.claude/hooks/_lib/tests/federation/test_federation_audit_stitching.py +135 -0
- package/.claude/hooks/_lib/tests/federation/test_federation_identity.py +234 -0
- package/.claude/hooks/_lib/tests/federation/test_federation_replay.py +204 -0
- package/.claude/hooks/_lib/tests/federation/test_federation_sentinel_stage2.py +214 -0
- package/.claude/hooks/_lib/tests/federation/test_federation_server.py +385 -0
- package/.claude/hooks/_lib/tests/test_confidence_gate_class_block.py +313 -0
- package/.claude/hooks/_lib/tests/test_cost_envelope.py +759 -0
- package/.claude/hooks/_lib/tests/test_execution_context.py +254 -0
- package/.claude/hooks/_lib/tests/test_goap_advisory_invariant.py +134 -0
- package/.claude/hooks/_lib/tests/test_goap_planner.py +368 -0
- package/.claude/hooks/_lib/tests/test_plan104_audit_emit.py +324 -0
- package/.claude/hooks/_lib/tests/test_plan104_demand_resolver.py +584 -0
- package/.claude/hooks/_lib/tests/test_plan104_demand_scan.py +164 -0
- package/.claude/hooks/_lib/tests/test_plan104_microbench.py +109 -0
- package/.claude/hooks/_lib/tests/test_plan104_waive_parser.py +113 -0
- package/.claude/hooks/_lib/tests/test_plan105_audit_emit.py +259 -0
- package/.claude/hooks/_lib/tests/test_plan105_check_roadmap_binding.py +68 -0
- package/.claude/hooks/_lib/tests/test_plan105_goap_planner.py +158 -0
- package/.claude/hooks/_lib/tests/test_plan105_spawn_outcome.py +234 -0
- package/.claude/hooks/_lib/tests/test_rag_dead_code_disposition.py +262 -0
- package/.claude/hooks/_lib/tests/test_rag_router.py +209 -0
- package/.claude/hooks/_lib/tests/test_swarm_circuit_breaker.py +278 -0
- package/.claude/hooks/_lib/tests/test_swarm_kill_switch_chain.py +360 -0
- package/.claude/hooks/_lib/tier_policy/__init__.py +123 -0
- package/.claude/hooks/_lib/tier_policy/_agent_frontmatter.py +509 -0
- package/.claude/hooks/_lib/tier_policy/_constants.py +376 -0
- package/.claude/hooks/_lib/tier_policy/_types.py +355 -0
- package/.claude/hooks/_lib/tier_policy/fixtures/baseline.json +17 -0
- package/.claude/hooks/_lib/tier_policy/fixtures/oversize_64kib.json +1 -0
- package/.claude/hooks/_lib/tier_policy/fixtures/prototype_pollution_attack.yaml +14 -0
- package/.claude/hooks/_lib/tier_policy/fixtures/schema_v1_sample.json +5 -0
- package/.claude/hooks/_lib/tier_policy/fixtures/schema_v2_sample.json +17 -0
- package/.claude/hooks/_lib/tier_policy/fixtures/yaml_bomb_attack.yaml +20 -0
- package/.claude/hooks/_lib/tier_policy/loader.py +476 -0
- package/.claude/hooks/_lib/tokens.py +136 -0
- package/.claude/hooks/_lib/tool_lifecycle.py +488 -0
- package/.claude/hooks/_lib/trusted_env.py +77 -0
- package/.claude/hooks/_python-hook.sh +242 -0
- package/.claude/hooks/accel_dispatch.py +172 -0
- package/.claude/hooks/adequacy_gate.py +424 -0
- package/.claude/hooks/audit_log.py +1352 -0
- package/.claude/hooks/auto_boot.py +518 -0
- package/.claude/hooks/check_adversary.py +273 -0
- package/.claude/hooks/check_agent_spawn.py +2696 -0
- package/.claude/hooks/check_anti_ceo_overhead.py +786 -0
- package/.claude/hooks/check_arbitration_kernel.py +544 -0
- package/.claude/hooks/check_bash_canonical_forensic.py +180 -0
- package/.claude/hooks/check_bash_safety.py +1483 -0
- package/.claude/hooks/check_budget.py +916 -0
- package/.claude/hooks/check_canonical_edit.py +1197 -0
- package/.claude/hooks/check_closeout_guard.py +154 -0
- package/.claude/hooks/check_codex_filewrite.py +366 -0
- package/.claude/hooks/check_codex_response.py +403 -0
- package/.claude/hooks/check_confidence_gate.py +545 -0
- package/.claude/hooks/check_config_change.py +346 -0
- package/.claude/hooks/check_config_protection.py +381 -0
- package/.claude/hooks/check_cost_envelope.py +286 -0
- package/.claude/hooks/check_fluency_nudge.py +747 -0
- package/.claude/hooks/check_mcp_response.py +234 -0
- package/.claude/hooks/check_output_safety.py +237 -0
- package/.claude/hooks/check_output_secrets.py +518 -0
- package/.claude/hooks/check_pair_rail.py +1700 -0
- package/.claude/hooks/check_plan_edit.py +905 -0
- package/.claude/hooks/check_postcompact_reinject.py +265 -0
- package/.claude/hooks/check_precompact_continuity.py +379 -0
- package/.claude/hooks/check_protocol_semver_cascade.py +401 -0
- package/.claude/hooks/check_read_injection.py +366 -0
- package/.claude/hooks/check_scratchpad_access.py +228 -0
- package/.claude/hooks/check_setup_verification.py +297 -0
- package/.claude/hooks/check_skill_bootstrap_post.py +339 -0
- package/.claude/hooks/check_skill_patch_sentinel.py +413 -0
- package/.claude/hooks/check_skill_reference_read.py +518 -0
- package/.claude/hooks/check_subagent_fabrication.py +45 -0
- package/.claude/hooks/check_subagent_start.py +232 -0
- package/.claude/hooks/check_tier_policy.py +211 -0
- package/.claude/hooks/check_tier_policy_misrouting_24h.py +187 -0
- package/.claude/hooks/check_webfetch_injection.py +277 -0
- package/.claude/hooks/check_worktree_writer.py +773 -0
- package/.claude/hooks/codex_review_user_code.py +304 -0
- package/.claude/hooks/emit_architect_outcome.py +232 -0
- package/.claude/hooks/latency_report.py +343 -0
- package/.claude/hooks/policy_dispatch.py +168 -0
- package/.claude/hooks/review_loop.py +560 -0
- package/.claude/hooks/route.py +115 -0
- package/.claude/hooks/tests/_agent_fixture.py +153 -0
- package/.claude/hooks/tests/adapters/__init__.py +0 -0
- package/.claude/hooks/tests/adapters/live/__init__.py +0 -0
- package/.claude/hooks/tests/adapters/live/test_adapters.py +488 -0
- package/.claude/hooks/tests/adapters/live/test_audit_wiring.py +81 -0
- package/.claude/hooks/tests/adapters/live/test_breaker.py +272 -0
- package/.claude/hooks/tests/adapters/live/test_cost.py +191 -0
- package/.claude/hooks/tests/adapters/live/test_o7_modernization.py +670 -0
- package/.claude/hooks/tests/adapters/live/test_policy.py +168 -0
- package/.claude/hooks/tests/conftest.py +139 -0
- package/.claude/hooks/tests/fixtures/adapters/claude/in/agent_spawn_compliant.json +9 -0
- package/.claude/hooks/tests/fixtures/adapters/claude/in/bash_safe_command.json +8 -0
- package/.claude/hooks/tests/fixtures/adapters/claude/in/post_audit_event.json +1 -0
- package/.claude/hooks/tests/fixtures/adapters/claude/out/allow.json +1 -0
- package/.claude/hooks/tests/fixtures/adapters/claude/out/block_with_reason.json +1 -0
- package/.claude/hooks/tests/fixtures/adapters/codex/in/.gitkeep +1 -0
- package/.claude/hooks/tests/fixtures/adapters/codex/out/.gitkeep +1 -0
- package/.claude/hooks/tests/fixtures/adapters/gemini/GAPS.md +46 -0
- package/.claude/hooks/tests/fixtures/adapters/gemini/in/agent_spawn_minimal.json +1 -0
- package/.claude/hooks/tests/fixtures/adapters/gemini/in/bash_minimal.json +1 -0
- package/.claude/hooks/tests/fixtures/adapters/gemini/out/allow.json +1 -0
- package/.claude/hooks/tests/fixtures/adapters/local/in/agent_spawn_ollama.json +19 -0
- package/.claude/hooks/tests/fixtures/adapters/local/in/bash_minimal.json +8 -0
- package/.claude/hooks/tests/fixtures/adapters/local/out/allow.json +1 -0
- package/.claude/hooks/tests/fixtures/adapters/openai/in/agent_spawn_chat_completions.json +13 -0
- package/.claude/hooks/tests/fixtures/adapters/openai/in/bash_responses_api.json +9 -0
- package/.claude/hooks/tests/fixtures/adapters/openai/out/allow.json +1 -0
- package/.claude/hooks/tests/fixtures/anti_ceo_overhead/should-NOT-block-on-Y.ndjson +13 -0
- package/.claude/hooks/tests/fixtures/anti_ceo_overhead/should-block-on-X.ndjson +9 -0
- package/.claude/hooks/tests/fixtures/byte_identity/__init__.py +5 -0
- package/.claude/hooks/tests/fixtures/byte_identity/bash_safety_fuzzer.py +287 -0
- package/.claude/hooks/tests/fixtures/byte_identity/plan_edit_fuzzer.py +364 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/aws-iam-policy-arn-id-25.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/blog-paragraph-18.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/boilerplate-26.txt +4 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/cdn-cache-key-12.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/certificate-fingerprint-10.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/changelog-19.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/commit-sha-01.txt +4 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/django-csrf-token-24.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/docker-image-04.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/docs-example-22.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/haiku-20.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/hex-placeholder-15.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/hex-short-23.txt +5 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/image-thumbnail-09.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/jwt-payload-decoded-08.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/kubernetes-uid-06.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/md5-hash-02.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/phone-number-16.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/postgres-uuid-05.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/redis-cluster-node-13.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/session-token-11.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/sha256-checksum-03.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/short-token-21.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/software-license-14.txt +4 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/telemetry-trace-07.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/negative/zip-postal-17.txt +4 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/binance-api-key-alnum-03.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/binance-api-key-hex-01.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/binance-api-key-hex-02.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bip39-mnemonic-12-31.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bip39-mnemonic-12-33.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bip39-mnemonic-24-32.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitfinex-api-key-11.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitfinex-api-key-12.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitfinex-api-key-13.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitstamp-api-key-30.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bitstamp-customer-id-29.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bybit-api-key-18.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bybit-api-key-19.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bybit-api-secret-20.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/bybit-combined-21.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/coinbase-api-key-uuid-04.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/coinbase-api-secret-b64-05.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/coinbase-combined-07.txt +4 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/coinbase-passphrase-06.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/evm-private-key-34.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/evm-private-key-35.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/evm-private-key-36.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/generic-api-key-37.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/generic-api-key-38.txt +3 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/generic-api-key-39.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/kraken-api-key-08.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/kraken-api-secret-09.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/kraken-combined-10.txt +4 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/kucoin-api-key-uuid-26.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/kucoin-api-secret-uuid-27.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/kucoin-passphrase-28.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/okx-api-key-uuid-22.txt +1 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/okx-api-secret-23.txt +2 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/okx-combined-25.txt +4 -0
- package/.claude/hooks/tests/fixtures/exchange_keys/positive/okx-passphrase-24.txt +1 -0
- package/.claude/hooks/tests/fixtures/hooks/audit_log/in.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/audit_log/out.json +0 -0
- package/.claude/hooks/tests/fixtures/hooks/check_agent_spawn/in.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_agent_spawn/out.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_bash_safety/in.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_bash_safety/out.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_canonical_edit/in.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_canonical_edit/out.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_confidence_gate/in.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_confidence_gate/out.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_plan_edit/in.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_plan_edit/out.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_read_injection/in.json +1 -0
- package/.claude/hooks/tests/fixtures/hooks/check_read_injection/out.json +1 -0
- package/.claude/hooks/tests/fixtures/lifecycle/concurrent_interleaved.json +36 -0
- package/.claude/hooks/tests/fixtures/lifecycle/orphaned_pre.json +8 -0
- package/.claude/hooks/tests/fixtures/lifecycle/paired_bash_post.json +8 -0
- package/.claude/hooks/tests/fixtures/lifecycle/paired_bash_pre.json +9 -0
- package/.claude/hooks/tests/fixtures/normalized/agent_spawn_chat_completions.json +36 -0
- package/.claude/hooks/tests/fixtures/normalized/agent_spawn_compliant.json +24 -0
- package/.claude/hooks/tests/fixtures/normalized/agent_spawn_minimal.json +24 -0
- package/.claude/hooks/tests/fixtures/normalized/agent_spawn_ollama.json +42 -0
- package/.claude/hooks/tests/fixtures/normalized/bash_minimal.json +23 -0
- package/.claude/hooks/tests/fixtures/normalized/bash_responses_api.json +32 -0
- package/.claude/hooks/tests/fixtures/normalized/bash_safe_command.json +23 -0
- package/.claude/hooks/tests/fixtures/normalized/post_audit_event.json +31 -0
- package/.claude/hooks/tests/fixtures/output_safety/control/01_random_hash_log.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/control/02_docs_mention_email_no_address.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/control/03_partial_jwt_two_segments.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/control/04_random_11_digits_no_cpf_context.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/control/05_credit_card_shape_invalid_luhn.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/01_api_key_anthropic.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/02_api_key_github_pat_classic.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/03_api_key_github_fine_grained.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/04_api_key_aws_access_key.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/05_api_key_aws_secret_assignment.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/06_jwt.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/07_bearer.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/08_cpf_with_context.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/09_cnpj_with_context.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/10_credit_card_luhn_valid.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/11_email_in_login_context.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/12_nfkc_full_width.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/13_zero_width_evasion.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/14_bidi_evasion.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_safety/positive/15_base64_encoded_secret.txt +1 -0
- package/.claude/hooks/tests/fixtures/output_scan/scenarios.jsonl +45 -0
- package/.claude/hooks/tests/fixtures/sample_payload_clean.json +13 -0
- package/.claude/hooks/tests/fixtures/sample_payload_with_secrets.json +12 -0
- package/.claude/hooks/tests/mutations/README.md +86 -0
- package/.claude/hooks/tests/mutations/__init__.py +14 -0
- package/.claude/hooks/tests/mutations/engine_mutations/__init__.py +15 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_01_parser_accepts_anchor.py +51 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_02_parser_skip_depth_limit.py +38 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_03_parser_accept_multi_doc.py +47 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_04_parser_accepts_bom.py +41 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_05_parser_scalar_len_off_by_one.py +61 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_06_parser_accepts_python_tag.py +50 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_07_parser_accepts_tab_indent.py +56 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_08_compiler_skip_regex_compile.py +45 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_09_compiler_regex_pattern_cap_off.py +31 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_10_compiler_accept_unknown_form.py +42 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_11_compiler_missing_predicate_tolerated.py +79 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_12_compiler_duplicate_rule_id_tolerated.py +66 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_13_compiler_missing_top_level_key_tolerated.py +46 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_14_compiler_schema_version_passthrough.py +43 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_15_evaluator_any_empty_returns_true.py +41 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_16_evaluator_all_empty_returns_true.py +37 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_17_evaluator_not_passthrough.py +37 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_18_evaluator_eq_true_on_type_mismatch.py +51 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_19_evaluator_regex_match_only.py +43 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_20_evaluator_path_under_no_realpath.py +48 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_21_evaluator_in_accepts_any.py +37 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_22_evaluator_length_off_by_one.py +45 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_23_evaluator_first_match_becomes_last.py +66 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_24_error_model_wrong_kind_on_parse.py +39 -0
- package/.claude/hooks/tests/mutations/engine_mutations/mutation_25_error_model_fail_open_on_load.py +42 -0
- package/.claude/hooks/tests/mutations/policy_mutations/__init__.py +16 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_01_remove_credential_leak.py +49 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_02_remove_rm_rf.py +44 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_03_remove_git_reset_hard.py +44 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_04_remove_git_push_force.py +44 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_05_reorder_rules.py +59 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_06_change_reason_enum.py +54 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_07_default_flipped_to_block.py +56 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_bash_08_flip_rm_rf_to_allow.py +49 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_01_remove_illegal_transition.py +79 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_02_remove_illegal_status.py +80 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_03_remove_missing_reviewed_at.py +80 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_04_remove_missing_completed_at.py +80 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_05_remove_missing_related_commits.py +79 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_06_remove_missing_abandonment_reason.py +80 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_07_scope_guard_inverted.py +93 -0
- package/.claude/hooks/tests/mutations/policy_mutations/mutation_plan_08_default_block.py +90 -0
- package/.claude/hooks/tests/probes/test_architect_probe.py +286 -0
- package/.claude/hooks/tests/probes/test_canonical_edit_probe.py +190 -0
- package/.claude/hooks/tests/probes/test_skill_content_probe.py +219 -0
- package/.claude/hooks/tests/test_SessionEnd.py +59 -0
- package/.claude/hooks/tests/test_SessionStart.py +42 -0
- package/.claude/hooks/tests/test_UserPromptSubmit.py +47 -0
- package/.claude/hooks/tests/test_accel_dispatch.py +96 -0
- package/.claude/hooks/tests/test_action_required_invariants.py +274 -0
- package/.claude/hooks/tests/test_adapter_drift_detector.py +254 -0
- package/.claude/hooks/tests/test_adapter_golden.py +198 -0
- package/.claude/hooks/tests/test_adequacy_gate.py +86 -0
- package/.claude/hooks/tests/test_adr_052_role_to_model_coverage.py +112 -0
- package/.claude/hooks/tests/test_adr_058_brainstorm_structure.py +280 -0
- package/.claude/hooks/tests/test_adversary_rules_live.py +400 -0
- package/.claude/hooks/tests/test_agent_frontmatter.py +377 -0
- package/.claude/hooks/tests/test_anti_ceo_overhead.py +591 -0
- package/.claude/hooks/tests/test_audit_emit.py +1707 -0
- package/.claude/hooks/tests/test_audit_emit_api_contract.py +693 -0
- package/.claude/hooks/tests/test_audit_emit_async_flush.py +563 -0
- package/.claude/hooks/tests/test_audit_emit_backpressure.py +138 -0
- package/.claude/hooks/tests/test_audit_emit_callsite_coverage_matrix.py +101 -0
- package/.claude/hooks/tests/test_audit_emit_chain_length.py +357 -0
- package/.claude/hooks/tests/test_audit_emit_coverage.py +2679 -0
- package/.claude/hooks/tests/test_audit_emit_ghost_action_guard.py +447 -0
- package/.claude/hooks/tests/test_audit_emit_plan088_canonical13.py +323 -0
- package/.claude/hooks/tests/test_audit_emit_rotation.py +218 -0
- package/.claude/hooks/tests/test_audit_emit_veto_v214.py +202 -0
- package/.claude/hooks/tests/test_audit_emit_wire_audit.py +699 -0
- package/.claude/hooks/tests/test_audit_hmac.py +334 -0
- package/.claude/hooks/tests/test_audit_hmac_branch_coverage.py +212 -0
- package/.claude/hooks/tests/test_audit_hmac_chain_monotonicity_property.py +136 -0
- package/.claude/hooks/tests/test_audit_hmac_coverage_v214.py +358 -0
- package/.claude/hooks/tests/test_audit_hmac_hardening.py +302 -0
- package/.claude/hooks/tests/test_audit_hmac_rotation_scenarios.py +231 -0
- package/.claude/hooks/tests/test_audit_hmac_verify_chain.py +443 -0
- package/.claude/hooks/tests/test_audit_log.py +280 -0
- package/.claude/hooks/tests/test_audit_log_coverage.py +173 -0
- package/.claude/hooks/tests/test_audit_log_path_d.py +516 -0
- package/.claude/hooks/tests/test_audit_log_phase1.py +358 -0
- package/.claude/hooks/tests/test_audit_log_schema_consistency.py +97 -0
- package/.claude/hooks/tests/test_audit_log_security.py +289 -0
- package/.claude/hooks/tests/test_audit_log_tokens.py +92 -0
- package/.claude/hooks/tests/test_audit_log_v2_7.py +378 -0
- package/.claude/hooks/tests/test_audit_log_v2_8_model.py +201 -0
- package/.claude/hooks/tests/test_audit_rotation.py +158 -0
- package/.claude/hooks/tests/test_audit_stream_verbose_protection.py +86 -0
- package/.claude/hooks/tests/test_audit_tokens_content_ban.py +512 -0
- package/.claude/hooks/tests/test_auto_boot.py +28 -0
- package/.claude/hooks/tests/test_available_models_mirror.py +226 -0
- package/.claude/hooks/tests/test_bash_canonical_forensic.py +74 -0
- package/.claude/hooks/tests/test_bash_canonical_interceptor.py +79 -0
- package/.claude/hooks/tests/test_brotli_passthrough.py +145 -0
- package/.claude/hooks/tests/test_byte_identity_fuzzer.py +185 -0
- package/.claude/hooks/tests/test_byte_identity_harness.py +953 -0
- package/.claude/hooks/tests/test_canonical_guard_typed_exceptions.py +117 -0
- package/.claude/hooks/tests/test_canonical_json.py +153 -0
- package/.claude/hooks/tests/test_chain_invariants_property.py +132 -0
- package/.claude/hooks/tests/test_check_adversary_live.py +149 -0
- package/.claude/hooks/tests/test_check_agent_spawn.py +1084 -0
- package/.claude/hooks/tests/test_check_agent_spawn_coverage.py +277 -0
- package/.claude/hooks/tests/test_check_agent_spawn_effort_token.py +74 -0
- package/.claude/hooks/tests/test_check_agent_spawn_import_isolation.py +82 -0
- package/.claude/hooks/tests/test_check_agent_spawn_model_routing_mode.py +245 -0
- package/.claude/hooks/tests/test_check_agent_spawn_reference_bypass.py +385 -0
- package/.claude/hooks/tests/test_check_agent_spawn_routing_promotion.py +302 -0
- package/.claude/hooks/tests/test_check_agent_spawn_skill_reference.py +336 -0
- package/.claude/hooks/tests/test_check_arbitration_kernel.py +472 -0
- package/.claude/hooks/tests/test_check_arbitration_kernel_v214.py +157 -0
- package/.claude/hooks/tests/test_check_bash_safety.py +546 -0
- package/.claude/hooks/tests/test_check_bash_safety_canonical_matrix.py +336 -0
- package/.claude/hooks/tests/test_check_bash_safety_cp_chaining.py +120 -0
- package/.claude/hooks/tests/test_check_bash_safety_h5_rewrite.py +462 -0
- package/.claude/hooks/tests/test_check_budget.py +580 -0
- package/.claude/hooks/tests/test_check_budget_max_tokens.py +397 -0
- package/.claude/hooks/tests/test_check_budget_quota_hint.py +115 -0
- package/.claude/hooks/tests/test_check_canonical_edit.py +302 -0
- package/.claude/hooks/tests/test_check_canonical_edit_coverage.py +370 -0
- package/.claude/hooks/tests/test_check_canonical_edit_kernel_v2.py +401 -0
- package/.claude/hooks/tests/test_check_canonical_edit_markers.py +473 -0
- package/.claude/hooks/tests/test_check_canonical_edit_mcp.py +401 -0
- package/.claude/hooks/tests/test_check_canonical_edit_session67_format.py +245 -0
- package/.claude/hooks/tests/test_check_codex_filewrite.py +964 -0
- package/.claude/hooks/tests/test_check_codex_response.py +419 -0
- package/.claude/hooks/tests/test_check_compaction_continuity.py +450 -0
- package/.claude/hooks/tests/test_check_confidence_gate.py +326 -0
- package/.claude/hooks/tests/test_check_config_change.py +369 -0
- package/.claude/hooks/tests/test_check_config_protection.py +364 -0
- package/.claude/hooks/tests/test_check_fluency_nudge.py +321 -0
- package/.claude/hooks/tests/test_check_mcp_response.py +261 -0
- package/.claude/hooks/tests/test_check_output_safety.py +314 -0
- package/.claude/hooks/tests/test_check_output_secrets.py +488 -0
- package/.claude/hooks/tests/test_check_output_secrets_coverage.py +321 -0
- package/.claude/hooks/tests/test_check_pair_rail.py +897 -0
- package/.claude/hooks/tests/test_check_pair_rail_decide_canonical.py +297 -0
- package/.claude/hooks/tests/test_check_pair_rail_golden.py +362 -0
- package/.claude/hooks/tests/test_check_pair_rail_hook_integration.py +120 -0
- package/.claude/hooks/tests/test_check_pair_rail_matrix.py +1077 -0
- package/.claude/hooks/tests/test_check_plan_edit.py +679 -0
- package/.claude/hooks/tests/test_check_plan_edit_stranded.py +310 -0
- package/.claude/hooks/tests/test_check_protocol_semver_cascade.py +141 -0
- package/.claude/hooks/tests/test_check_protocol_semver_cascade_settings_wired.py +297 -0
- package/.claude/hooks/tests/test_check_protocol_semver_cascade_synccascade.py +365 -0
- package/.claude/hooks/tests/test_check_read_injection.py +143 -0
- package/.claude/hooks/tests/test_check_read_injection_coverage.py +237 -0
- package/.claude/hooks/tests/test_check_read_injection_pathbound.py +153 -0
- package/.claude/hooks/tests/test_check_scratchpad_access.py +244 -0
- package/.claude/hooks/tests/test_check_skill_bootstrap_post.py +256 -0
- package/.claude/hooks/tests/test_check_skill_patch_sentinel.py +439 -0
- package/.claude/hooks/tests/test_check_skill_reference_read.py +170 -0
- package/.claude/hooks/tests/test_check_skill_reference_read_v2.py +388 -0
- package/.claude/hooks/tests/test_check_subagent_fabrication.py +54 -0
- package/.claude/hooks/tests/test_check_subagent_start.py +505 -0
- package/.claude/hooks/tests/test_check_tier_policy.py +48 -0
- package/.claude/hooks/tests/test_check_tier_policy_misrouting_24h.py +294 -0
- package/.claude/hooks/tests/test_check_webfetch_injection.py +49 -0
- package/.claude/hooks/tests/test_claim_producer_pair_end_to_end_loop_perf.py +227 -0
- package/.claude/hooks/tests/test_claude_adapter_thinking.py +731 -0
- package/.claude/hooks/tests/test_claude_batch_adapter.py +672 -0
- package/.claude/hooks/tests/test_closeout_guard.py +184 -0
- package/.claude/hooks/tests/test_codex_adapter.py +777 -0
- package/.claude/hooks/tests/test_codex_cli_shape.py +217 -0
- package/.claude/hooks/tests/test_codex_egress_proof_telemetry.py +214 -0
- package/.claude/hooks/tests/test_codex_egress_redact.py +342 -0
- package/.claude/hooks/tests/test_codex_egress_redact_outgoing.py +236 -0
- package/.claude/hooks/tests/test_codex_reply_multi_turn.py +72 -0
- package/.claude/hooks/tests/test_codex_review_user_code.py +44 -0
- package/.claude/hooks/tests/test_codex_strict_json.py +123 -0
- package/.claude/hooks/tests/test_confidence_gate_producer_pair.py +522 -0
- package/.claude/hooks/tests/test_confidence_labels.py +362 -0
- package/.claude/hooks/tests/test_contract.py +237 -0
- package/.claude/hooks/tests/test_cookbook_advisor_hook.py +208 -0
- package/.claude/hooks/tests/test_credentials.py +195 -0
- package/.claude/hooks/tests/test_detect_repo_profile_branches.py +116 -0
- package/.claude/hooks/tests/test_e2e_hook_chain.py +184 -0
- package/.claude/hooks/tests/test_effective_config.py +648 -0
- package/.claude/hooks/tests/test_emit_architect_outcome.py +175 -0
- package/.claude/hooks/tests/test_env_persist_allowlist.py +365 -0
- package/.claude/hooks/tests/test_escalation_signals.py +357 -0
- package/.claude/hooks/tests/test_estimation_bayesian_pipeline.py +140 -0
- package/.claude/hooks/tests/test_execution_context_deferral.py +222 -0
- package/.claude/hooks/tests/test_fail_open_contract.py +118 -0
- package/.claude/hooks/tests/test_file_walker.py +332 -0
- package/.claude/hooks/tests/test_filelock.py +131 -0
- package/.claude/hooks/tests/test_filelock_contract.py +172 -0
- package/.claude/hooks/tests/test_find_sentinels_pattern_matrix.py +114 -0
- package/.claude/hooks/tests/test_flip_closures.py +219 -0
- package/.claude/hooks/tests/test_frontmatter.py +139 -0
- package/.claude/hooks/tests/test_git_bypass_guard.py +1095 -0
- package/.claude/hooks/tests/test_gpg_verify.py +578 -0
- package/.claude/hooks/tests/test_hook_byte_fidelity.py +113 -0
- package/.claude/hooks/tests/test_hook_latency.py +245 -0
- package/.claude/hooks/tests/test_hook_latency_import.py +178 -0
- package/.claude/hooks/tests/test_injection_patterns.py +276 -0
- package/.claude/hooks/tests/test_injection_patterns_bypass.py +276 -0
- package/.claude/hooks/tests/test_injection_salt.py +191 -0
- package/.claude/hooks/tests/test_kernel_subsumes_security_critical_lib.py +88 -0
- package/.claude/hooks/tests/test_kill_switch_godmode_enforcing.py +101 -0
- package/.claude/hooks/tests/test_latency_report.py +28 -0
- package/.claude/hooks/tests/test_lib_canonical_import.py +355 -0
- package/.claude/hooks/tests/test_lifecycle_edge_cases.py +565 -0
- package/.claude/hooks/tests/test_live_adapters.py +463 -0
- package/.claude/hooks/tests/test_live_audit_isolation.py +357 -0
- package/.claude/hooks/tests/test_mcp_bearer_friction_buffer.py +276 -0
- package/.claude/hooks/tests/test_mcp_bearer_friction_emit.py +117 -0
- package/.claude/hooks/tests/test_mcp_canonical_guard.py +1989 -0
- package/.claude/hooks/tests/test_mcp_injection_repro_harness.py +437 -0
- package/.claude/hooks/tests/test_mcp_injection_scan.py +228 -0
- package/.claude/hooks/tests/test_mcp_routing_resolve.py +246 -0
- package/.claude/hooks/tests/test_memory_shared.py +412 -0
- package/.claude/hooks/tests/test_metrics.py +115 -0
- package/.claude/hooks/tests/test_migrated_hooks_fixtures.py +121 -0
- package/.claude/hooks/tests/test_model_routing.py +175 -0
- package/.claude/hooks/tests/test_model_routing_resolve.py +97 -0
- package/.claude/hooks/tests/test_model_routing_resolve_full.py +318 -0
- package/.claude/hooks/tests/test_otel_bounded_exporter.py +521 -0
- package/.claude/hooks/tests/test_otel_emit.py +243 -0
- package/.claude/hooks/tests/test_otel_queue.py +334 -0
- package/.claude/hooks/tests/test_otel_wire_defaultoff.py +392 -0
- package/.claude/hooks/tests/test_output_scan.py +1119 -0
- package/.claude/hooks/tests/test_output_scan_dedup.py +329 -0
- package/.claude/hooks/tests/test_output_scan_fixtures.py +136 -0
- package/.claude/hooks/tests/test_pair_rail_decide.py +141 -0
- package/.claude/hooks/tests/test_payload.py +89 -0
- package/.claude/hooks/tests/test_persona_coverage_wire.py +376 -0
- package/.claude/hooks/tests/test_persona_routing_enforcing.py +119 -0
- package/.claude/hooks/tests/test_phase_c_advisory_audit.py +75 -0
- package/.claude/hooks/tests/test_pii_patterns.py +558 -0
- package/.claude/hooks/tests/test_plan114_wires.py +468 -0
- package/.claude/hooks/tests/test_plan128_emit_wiring.py +74 -0
- package/.claude/hooks/tests/test_plan132_codex_review_observe.py +99 -0
- package/.claude/hooks/tests/test_plan133_a1_env_guard.py +221 -0
- package/.claude/hooks/tests/test_plan133_a2_canonical_skill_unicode.py +359 -0
- package/.claude/hooks/tests/test_plan133_a2_invisible_unicode.py +239 -0
- package/.claude/hooks/tests/test_plan133_a3_egress_taxonomy.py +221 -0
- package/.claude/hooks/tests/test_plan133_e1_adversary.py +360 -0
- package/.claude/hooks/tests/test_plan_085_wave_c_callsites_preserved.py +147 -0
- package/.claude/hooks/tests/test_plan_091_expected_callsites.py +206 -0
- package/.claude/hooks/tests/test_plan_frontmatter.py +217 -0
- package/.claude/hooks/tests/test_policy_coverage_residual_session73.py +597 -0
- package/.claude/hooks/tests/test_policy_coverage_v214.py +1099 -0
- package/.claude/hooks/tests/test_policy_dispatch.py +454 -0
- package/.claude/hooks/tests/test_policy_engine.py +791 -0
- package/.claude/hooks/tests/test_policy_fuzz_bomb.py +356 -0
- package/.claude/hooks/tests/test_policy_golden_error_kinds.py +287 -0
- package/.claude/hooks/tests/test_policy_mutations.py +359 -0
- package/.claude/hooks/tests/test_policy_preprocessors.py +514 -0
- package/.claude/hooks/tests/test_policy_redos_guards.py +393 -0
- package/.claude/hooks/tests/test_rag_bridge.py +675 -0
- package/.claude/hooks/tests/test_rag_events.py +202 -0
- package/.claude/hooks/tests/test_red_team_fixtures.py +427 -0
- package/.claude/hooks/tests/test_redact.py +506 -0
- package/.claude/hooks/tests/test_redact_redos.py +254 -0
- package/.claude/hooks/tests/test_redact_secrets_parity.py +334 -0
- package/.claude/hooks/tests/test_replay_determinism.py +263 -0
- package/.claude/hooks/tests/test_review_loop.py +28 -0
- package/.claude/hooks/tests/test_review_loop_wiring.py +206 -0
- package/.claude/hooks/tests/test_route.py +36 -0
- package/.claude/hooks/tests/test_rubric_catalogue.py +359 -0
- package/.claude/hooks/tests/test_scratchpad_lib.py +259 -0
- package/.claude/hooks/tests/test_secret_patterns.py +680 -0
- package/.claude/hooks/tests/test_secret_patterns_provenance.py +82 -0
- package/.claude/hooks/tests/test_sentinel_session_cache.py +324 -0
- package/.claude/hooks/tests/test_sentinel_session_cache_tier1.py +205 -0
- package/.claude/hooks/tests/test_sentinel_signers.py +641 -0
- package/.claude/hooks/tests/test_session_75_kernel_findings.py +180 -0
- package/.claude/hooks/tests/test_session_76_audit_v3_findings.py +493 -0
- package/.claude/hooks/tests/test_session_77_audit_v3_backlog_findings.py +644 -0
- package/.claude/hooks/tests/test_session_77_round_2_findings.py +135 -0
- package/.claude/hooks/tests/test_session_77_round_3_findings.py +159 -0
- package/.claude/hooks/tests/test_session_77_round_4_findings.py +120 -0
- package/.claude/hooks/tests/test_session_end.py +113 -0
- package/.claude/hooks/tests/test_session_start.py +293 -0
- package/.claude/hooks/tests/test_skill_unknown_ratio_path_d.py +249 -0
- package/.claude/hooks/tests/test_smart_loading_resolver_caching.py +140 -0
- package/.claude/hooks/tests/test_spec_context_sanitizer.py +179 -0
- package/.claude/hooks/tests/test_spool_drain_contended_skip.py +249 -0
- package/.claude/hooks/tests/test_spool_drain_rotation_property_b.py +227 -0
- package/.claude/hooks/tests/test_spool_drain_rotation_race.py +395 -0
- package/.claude/hooks/tests/test_spool_writer_cache.py +463 -0
- package/.claude/hooks/tests/test_state_store.py +302 -0
- package/.claude/hooks/tests/test_stop.py +133 -0
- package/.claude/hooks/tests/test_streaming_rate_cap.py +108 -0
- package/.claude/hooks/tests/test_subagent_dispatch.py +248 -0
- package/.claude/hooks/tests/test_subagent_model_override_removed.py +108 -0
- package/.claude/hooks/tests/test_team.py +95 -0
- package/.claude/hooks/tests/test_template_dogfood_parity.py +106 -0
- package/.claude/hooks/tests/test_terminal_compress.py +135 -0
- package/.claude/hooks/tests/test_test_env_context_agent_binding.py +140 -0
- package/.claude/hooks/tests/test_testing_helper.py +53 -0
- package/.claude/hooks/tests/test_thinking_budget_command.py +229 -0
- package/.claude/hooks/tests/test_tier_policy_agent_frontmatter.py +421 -0
- package/.claude/hooks/tests/test_tier_policy_agent_frontmatter_disposition.py +175 -0
- package/.claude/hooks/tests/test_tier_policy_constants.py +336 -0
- package/.claude/hooks/tests/test_tier_policy_loader.py +544 -0
- package/.claude/hooks/tests/test_tier_policy_loader_fallback_observed.py +169 -0
- package/.claude/hooks/tests/test_tier_policy_types.py +270 -0
- package/.claude/hooks/tests/test_tokens_lib.py +118 -0
- package/.claude/hooks/tests/test_tool_lifecycle.py +598 -0
- package/.claude/hooks/tests/test_tool_lifecycle_perf.py +110 -0
- package/.claude/hooks/tests/test_turbo_profile.py +28 -0
- package/.claude/hooks/tests/test_turbo_sessionstart.py +79 -0
- package/.claude/hooks/tests/test_two_writer_chain.py +175 -0
- package/.claude/hooks/tests/test_upgrade_retry.py +346 -0
- package/.claude/hooks/tests/test_user_prompt_submit.py +254 -0
- package/.claude/hooks/tests/test_user_prompt_submit_salt.py +204 -0
- package/.claude/hooks/tests/test_verify_after_edit.py +100 -0
- package/.claude/hooks/tests/test_veto_floor_bijection.py +174 -0
- package/.claude/hooks/tests/test_w5_cookbook_remediation.py +712 -0
- package/.claude/hooks/tests/test_w5_scrub_enforcement.py +371 -0
- package/.claude/hooks/tests/test_webfetch_injection.py +280 -0
- package/.claude/hooks/tests/test_wiredeadmod_estimation_wiring.py +283 -0
- package/.claude/hooks/tests/test_wiredeadmod_spawn_wiring.py +303 -0
- package/.claude/hooks/tests/test_worktree_writer.py +509 -0
- package/.claude/hooks/turbo_profile.py +554 -0
- package/.claude/hooks/turbo_sessionstart.py +472 -0
- package/.claude/hooks/verify_after_edit.py +281 -0
- package/.claude/pitfalls-catalog.yaml +150 -0
- package/.claude/plans/AUDIT-LOG-SCHEMA.md +548 -0
- package/.claude/plans/DEBATE-SCHEMA.md +539 -0
- package/.claude/plans/PLAN-128/AB-PROTOCOL.md +121 -0
- package/.claude/plans/PLAN-128/measure-state.sh +101 -0
- package/.claude/plans/PLAN-139-canonical-invariants-and-debt-ledger.md +253 -0
- package/.claude/plans/PLAN-140/architect/round-1/approved.md +40 -0
- package/.claude/plans/PLAN-140-compaction-hook-origin-dropfix.md +95 -0
- package/.claude/plans/PLAN-141/architect/round-1/approved.md +28 -0
- package/.claude/plans/PLAN-141-mcp-smoke-staging-ruff-tolerance.md +72 -0
- package/.claude/plans/PLAN-142/architect/round-1/anonymization-map.md +11 -0
- package/.claude/plans/PLAN-142/architect/round-1/consensus.md +95 -0
- package/.claude/plans/PLAN-142/architect/round-1/devops-engineer.md +57 -0
- package/.claude/plans/PLAN-142/architect/round-1/proposal.md +57 -0
- package/.claude/plans/PLAN-142/architect/round-1/security-engineer.md +55 -0
- package/.claude/plans/PLAN-142/architect/round-1/vp-engineering.md +58 -0
- package/.claude/plans/PLAN-142/architect/round-2/anonymization-map.md +11 -0
- package/.claude/plans/PLAN-142/architect/round-2/approved.md +65 -0
- package/.claude/plans/PLAN-142/architect/round-2/consensus.md +78 -0
- package/.claude/plans/PLAN-142/architect/round-2/devops-engineer.md +58 -0
- package/.claude/plans/PLAN-142/architect/round-2/security-engineer.md +56 -0
- package/.claude/plans/PLAN-142/architect/round-2/vp-engineering.md +54 -0
- package/.claude/plans/PLAN-142/staging/EXECUTION-RUNBOOK.md +74 -0
- package/.claude/plans/PLAN-142/staging/STAGING-NOTES.md +63 -0
- package/.claude/plans/PLAN-142/staging/check_pair_rail__invoke_and_consume.py.txt +644 -0
- package/.claude/plans/PLAN-142/staging/codex_adapter_parsers.py.txt +677 -0
- package/.claude/plans/PLAN-142/staging/codex_cli_shape.py +433 -0
- package/.claude/plans/PLAN-142-codex-cli-0139-adapter-migration.md +224 -0
- package/.claude/plans/PLAN-143/architect/round-1/anonymization-map.md +22 -0
- package/.claude/plans/PLAN-143/architect/round-1/consensus.md +108 -0
- package/.claude/plans/PLAN-143/architect/round-1/devops-engineer.md +228 -0
- package/.claude/plans/PLAN-143/architect/round-1/proposal.md +48 -0
- package/.claude/plans/PLAN-143/architect/round-1/security-engineer.md +224 -0
- package/.claude/plans/PLAN-143/architect/round-1/vp-engineering.md +166 -0
- package/.claude/plans/PLAN-143/patches/PLAN143-item1-env-inventory.NOTE.md +106 -0
- package/.claude/plans/PLAN-143/patches/PLAN143-item2-spool-writer-rotate-guard.patch +41 -0
- package/.claude/plans/PLAN-143/patches/PLAN143-item3-audit-emit-exit-code.patch +32 -0
- package/.claude/plans/PLAN-143-repo-hygiene-debt.md +201 -0
- package/.claude/plans/PLAN-SCHEMA.md +870 -0
- package/.claude/plans/README.md +208 -0
- package/.claude/plans/examples/debate-round-1/consensus.md +166 -0
- package/.claude/plans/examples/debate-round-1/devops-engineer.md +133 -0
- package/.claude/plans/examples/debate-round-1/proposal.md +66 -0
- package/.claude/plans/examples/debate-round-1/security-engineer.md +109 -0
- package/.claude/plans/examples/debate-round-1/vp-engineering.md +110 -0
- package/.claude/policies/.drift-manifest.json +16 -0
- package/.claude/policies/bash-safety.policy.yaml +37 -0
- package/.claude/policies/fixtures/.gitkeep +0 -0
- package/.claude/policies/fixtures/bash-safety.fixtures.jsonl +46 -0
- package/.claude/policies/fixtures/plan-edit.fixtures.jsonl +36 -0
- package/.claude/policies/grandfather-cap.policy.yaml +85 -0
- package/.claude/policies/plan-edit.policy.yaml +152 -0
- package/.claude/policies/rubric-violation-catalogue.yaml +187 -0
- package/.claude/policies/schemas/repo-profile-skill-binding.schema.json +126 -0
- package/.claude/policies/schemas/repo-profile.schema.json +83 -0
- package/.claude/policies/schemas/squad-bundle-frontmatter.schema.json +152 -0
- package/.claude/policies/secret-patterns-exchange.yaml +368 -0
- package/.claude/policies/smart-loading-cap-table.yaml +34 -0
- package/.claude/proposals/.gitkeep +0 -0
- package/.claude/proposals/README.md +42 -0
- package/.claude/proposals/SP-001-code-review-checklist-2026-04-20.md +65 -0
- package/.claude/proposals/SP-001-code-review-checklist-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-002-security-and-auth-2026-04-20.md +74 -0
- package/.claude/proposals/SP-002-security-and-auth-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-003-design-system-and-components-2026-04-20.md +67 -0
- package/.claude/proposals/SP-003-design-system-and-components-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-004-accessibility-and-wcag-2026-04-20.md +68 -0
- package/.claude/proposals/SP-004-accessibility-and-wcag-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-005-ux-and-user-journeys-2026-04-20.md +63 -0
- package/.claude/proposals/SP-005-ux-and-user-journeys-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-006-chaos-and-resilience-2026-04-20.md +79 -0
- package/.claude/proposals/SP-006-chaos-and-resilience-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-007-ai-llm-orchestration-2026-04-20.md +76 -0
- package/.claude/proposals/SP-007-ai-llm-orchestration-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-008-performance-engineering-2026-04-20.md +82 -0
- package/.claude/proposals/SP-008-performance-engineering-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-009-code-review-checklist-2026-04-20.md +76 -0
- package/.claude/proposals/SP-009-code-review-checklist-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-010-accessibility-and-wcag-adopter-note-2026-04-20.md +77 -0
- package/.claude/proposals/SP-010-accessibility-and-wcag-adopter-note-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-011-design-system-and-components-adopter-note-2026-04-20.md +79 -0
- package/.claude/proposals/SP-011-design-system-and-components-adopter-note-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-012-ux-and-user-journeys-adopter-note-2026-04-20.md +83 -0
- package/.claude/proposals/SP-012-ux-and-user-journeys-adopter-note-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-013-frontend-performance-optimization-2026-04-20.md +82 -0
- package/.claude/proposals/SP-013-frontend-performance-optimization-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-014-observability-and-ops-2026-04-20.md +80 -0
- package/.claude/proposals/SP-014-observability-and-ops-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-015-testing-strategy-2026-04-20.md +87 -0
- package/.claude/proposals/SP-015-testing-strategy-2026-04-20.md.asc +8 -0
- package/.claude/proposals/SP-016-code-review-checklist-fluency-rubric-2026-04-28.md +111 -0
- package/.claude/proposals/SP-016-code-review-checklist-fluency-rubric-2026-04-28.md.asc +8 -0
- package/.claude/proposals/SP-017-chaos-and-resilience-adopter-note-2026-04-28.md +87 -0
- package/.claude/proposals/SP-017-chaos-and-resilience-adopter-note-2026-04-28.md.asc +8 -0
- package/.claude/proposals/SP-018-ceo-orchestration-inventory-regen-2026-04-21.md +64 -0
- package/.claude/proposals/SP-018-ceo-orchestration-inventory-regen-2026-04-21.md.asc +8 -0
- package/.claude/proposals/SP-019-terse-mode-2026-04-21.md +107 -0
- package/.claude/proposals/SP-019-terse-mode-2026-04-21.md.asc +8 -0
- package/.claude/proposals/SP-020-ceo-orchestration-audit-tokens-2026-04-21.md +74 -0
- package/.claude/proposals/SP-020-ceo-orchestration-audit-tokens-2026-04-21.md.asc +8 -0
- package/.claude/proposals/SP-021-ceo-orchestration-autonomous-loop-2026-04-21.md +71 -0
- package/.claude/proposals/SP-021-ceo-orchestration-autonomous-loop-2026-04-21.md.asc +8 -0
- package/.claude/rag/_index_core.py +344 -0
- package/.claude/rag/indexignore +101 -0
- package/.claude/rag/install-sidecar.sh +275 -0
- package/.claude/rag/models.manifest.json +19 -0
- package/.claude/rag/requirements.lock +40 -0
- package/.claude/rag/sidecar-config.template.json +53 -0
- package/.claude/rag/tests/test_index_core.py +262 -0
- package/.claude/rag/tests/test_install_sidecar.sh +132 -0
- package/.claude/scripts/.known_actions_floor.lock +0 -0
- package/.claude/scripts/admin-invite.py +199 -0
- package/.claude/scripts/adopter-metrics.py +712 -0
- package/.claude/scripts/aek-calibration-c2.py +253 -0
- package/.claude/scripts/aek-calibration-c3.py +382 -0
- package/.claude/scripts/aggregate-changesets.py +350 -0
- package/.claude/scripts/architect-bundle-validate.py +227 -0
- package/.claude/scripts/audit-dashboard.py +1320 -0
- package/.claude/scripts/audit-log-labels.jsonl +0 -0
- package/.claude/scripts/audit-log-retain.py +404 -0
- package/.claude/scripts/audit-query.py +3333 -0
- package/.claude/scripts/audit-telemetry.py +337 -0
- package/.claude/scripts/audit-tokens.py +502 -0
- package/.claude/scripts/audit-verify-chain.py +537 -0
- package/.claude/scripts/backup-audit.py +247 -0
- package/.claude/scripts/benchmark/plan-071-import-floor/README.md +194 -0
- package/.claude/scripts/benchmark/plan-071-import-floor/fixtures/baseline.json +1 -0
- package/.claude/scripts/benchmark/plan-071-import-floor/fixtures/expected_quantiles.json +11 -0
- package/.claude/scripts/benchmark/plan-071-import-floor/import_floor_bench.py +791 -0
- package/.claude/scripts/benchmark/plan-071-import-floor/run_bench.sh +180 -0
- package/.claude/scripts/benchmark-fallback-scorer.py +254 -0
- package/.claude/scripts/benchmark-judge.py +621 -0
- package/.claude/scripts/budget-summary.py +946 -0
- package/.claude/scripts/build-canonical-models.py +645 -0
- package/.claude/scripts/calibration-kappa.py +262 -0
- package/.claude/scripts/cc-analytics-pull.py +393 -0
- package/.claude/scripts/ceo-backup.sh +307 -0
- package/.claude/scripts/ceo-boot.py +3017 -0
- package/.claude/scripts/ceo-cost.py +1116 -0
- package/.claude/scripts/ceo-diagnose.py +486 -0
- package/.claude/scripts/ceo-escalation-detector.py +743 -0
- package/.claude/scripts/ceo-health.py +584 -0
- package/.claude/scripts/ceo-info.py +1001 -0
- package/.claude/scripts/ceo-restore.sh +215 -0
- package/.claude/scripts/chaos-inject.py +439 -0
- package/.claude/scripts/check-action-sha-drift.py +275 -0
- package/.claude/scripts/check-active-hooks-executable.py +119 -0
- package/.claude/scripts/check-adr-chain.py +617 -0
- package/.claude/scripts/check-audit-action-name-convention.py +221 -0
- package/.claude/scripts/check-audit-hmac-null.py +253 -0
- package/.claude/scripts/check-audit-read-api-stable.py +239 -0
- package/.claude/scripts/check-audit-registry-coverage.py +999 -0
- package/.claude/scripts/check-auto-activation-flags.py +180 -0
- package/.claude/scripts/check-canonical-doc-freshness.py +222 -0
- package/.claude/scripts/check-claude-md-claims.py +346 -0
- package/.claude/scripts/check-confidence-gate-drift.py +295 -0
- package/.claude/scripts/check-conformance-harness-mapping.py +503 -0
- package/.claude/scripts/check-contamination.sh +25 -0
- package/.claude/scripts/check-creative-rewrite.py +596 -0
- package/.claude/scripts/check-debate-round-lifecycle.py +185 -0
- package/.claude/scripts/check-debt-ledger.py +305 -0
- package/.claude/scripts/check-docs-drift.py +259 -0
- package/.claude/scripts/check-docs-freshness.py +487 -0
- package/.claude/scripts/check-flip-criteria-drift.py +426 -0
- package/.claude/scripts/check-flip-release-gate-consistency.py +134 -0
- package/.claude/scripts/check-framework-updates.sh +239 -0
- package/.claude/scripts/check-function-length.py +426 -0
- package/.claude/scripts/check-model-deprecations.py +377 -0
- package/.claude/scripts/check-originator-residue.py +248 -0
- package/.claude/scripts/check-pitfall-regression.sh +153 -0
- package/.claude/scripts/check-policy-drift.py +74 -0
- package/.claude/scripts/check-roadmap-binding.py +170 -0
- package/.claude/scripts/check-rule-invariants.py +385 -0
- package/.claude/scripts/check-sdk-compat.sh +76 -0
- package/.claude/scripts/check-secret-pattern-coverage.py +175 -0
- package/.claude/scripts/check-sidecar-manifest.py +493 -0
- package/.claude/scripts/check-skill-activation-mode.py +41 -0
- package/.claude/scripts/check-skill-health.sh +179 -0
- package/.claude/scripts/check-spec-drift.py +147 -0
- package/.claude/scripts/check-staleness.py +506 -0
- package/.claude/scripts/check-stdlib-only.py +373 -0
- package/.claude/scripts/check-substrate-watch.py +285 -0
- package/.claude/scripts/check-swarm-harness-mapping.py +380 -0
- package/.claude/scripts/check-test-audit-isolation.py +622 -0
- package/.claude/scripts/check-test-env-hygiene.py +509 -0
- package/.claude/scripts/check-threat-model-freshness.py +313 -0
- package/.claude/scripts/check-tier-boundaries.py +233 -0
- package/.claude/scripts/check-tla-schema-drift.py +272 -0
- package/.claude/scripts/check_atlas_fpr.py +595 -0
- package/.claude/scripts/check_contamination.py +337 -0
- package/.claude/scripts/check_known_actions_floor.py +155 -0
- package/.claude/scripts/check_threat_model_coverage.py +214 -0
- package/.claude/scripts/check_translations_drift.py +199 -0
- package/.claude/scripts/codex_invoke.py +436 -0
- package/.claude/scripts/compare-adopters.py +549 -0
- package/.claude/scripts/confidence-gate-backfill.py +261 -0
- package/.claude/scripts/confidence_gate.py +736 -0
- package/.claude/scripts/context-budget.py +1887 -0
- package/.claude/scripts/contextual-recommender.py +815 -0
- package/.claude/scripts/cost-table.yaml +99 -0
- package/.claude/scripts/debate-converge.py +335 -0
- package/.claude/scripts/debate-emit.py +132 -0
- package/.claude/scripts/debate-orchestrate.py +972 -0
- package/.claude/scripts/detect-repo-profile.py +1280 -0
- package/.claude/scripts/detectors/__init__.py +19 -0
- package/.claude/scripts/detectors/looping.py +127 -0
- package/.claude/scripts/detectors/overpowered.py +96 -0
- package/.claude/scripts/detectors/retry_churn.py +119 -0
- package/.claude/scripts/detectors/schema.py +94 -0
- package/.claude/scripts/detectors/tests/__init__.py +0 -0
- package/.claude/scripts/detectors/tests/fixtures.py +420 -0
- package/.claude/scripts/detectors/tests/test_looping.py +124 -0
- package/.claude/scripts/detectors/tests/test_overpowered.py +114 -0
- package/.claude/scripts/detectors/tests/test_retry_churn.py +101 -0
- package/.claude/scripts/detectors/tests/test_schema.py +109 -0
- package/.claude/scripts/detectors/tests/test_tool_cascade.py +131 -0
- package/.claude/scripts/detectors/tests/test_wasteful_thinking.py +112 -0
- package/.claude/scripts/detectors/tests/test_weak_model.py +104 -0
- package/.claude/scripts/detectors/tool_cascade.py +127 -0
- package/.claude/scripts/detectors/wasteful_thinking.py +99 -0
- package/.claude/scripts/detectors/weak_model.py +92 -0
- package/.claude/scripts/env-inventory-check.py +268 -0
- package/.claude/scripts/env-inventory.json +3305 -0
- package/.claude/scripts/extract-skill.py +456 -0
- package/.claude/scripts/fan-plan-parser.py +370 -0
- package/.claude/scripts/find-orphan-sentinels.py +89 -0
- package/.claude/scripts/first-run-wizard.py +1151 -0
- package/.claude/scripts/fixtures/cloned-trading-repo/.env.example +1 -0
- package/.claude/scripts/fixtures/cloned-trading-repo/exchanges/binance.py +3 -0
- package/.claude/scripts/fixtures/cloned-trading-repo/exchanges/coinbase.py +3 -0
- package/.claude/scripts/fixtures/cloned-trading-repo/package.json +5 -0
- package/.claude/scripts/fixtures/cloned-trading-repo/strategies/grid.py +3 -0
- package/.claude/scripts/fixtures/cloned-trading-repo/strategies/pairs.py +3 -0
- package/.claude/scripts/fixtures/missing-package-manifest/README.md +3 -0
- package/.claude/scripts/fixtures/missing-package-manifest/src/main.py +1 -0
- package/.claude/scripts/fixtures/mixed-frontend-backend/package.json +9 -0
- package/.claude/scripts/fixtures/mixed-frontend-backend/requirements.txt +2 -0
- package/.claude/scripts/fixtures/mixed-frontend-backend/src/api/handler.py +2 -0
- package/.claude/scripts/fixtures/mixed-frontend-backend/src/pages/index.tsx +1 -0
- package/.claude/scripts/fixtures/monorepo/apps/app-a/README.md +1 -0
- package/.claude/scripts/fixtures/monorepo/apps/app-b/index.ts +1 -0
- package/.claude/scripts/fixtures/monorepo/package.json +5 -0
- package/.claude/scripts/fixtures/monorepo/packages/lib-a/index.js +1 -0
- package/.claude/scripts/fixtures/monorepo/packages/lib-b/index.js +1 -0
- package/.claude/scripts/fixtures/monorepo/pnpm-workspace.yaml +3 -0
- package/.claude/scripts/fixtures/persona-coverage-expected-thresholds.yaml +20 -0
- package/.claude/scripts/flip-criteria-drift-allowlist.txt +31 -0
- package/.claude/scripts/generate-adr-index.py +339 -0
- package/.claude/scripts/generate-available-models.py +280 -0
- package/.claude/scripts/generate-dispatch.py +430 -0
- package/.claude/scripts/generate-sbom.py +287 -0
- package/.claude/scripts/generate-skill-inventory.sh +193 -0
- package/.claude/scripts/github-api-client.py +297 -0
- package/.claude/scripts/goap-planner.py +742 -0
- package/.claude/scripts/hook-profiler.py +671 -0
- package/.claude/scripts/import-skill.py +569 -0
- package/.claude/scripts/import_ui_ux_pro_max.py +137 -0
- package/.claude/scripts/inject-agent-context.sh +948 -0
- package/.claude/scripts/k-calibration.py +456 -0
- package/.claude/scripts/key-hygiene.py +511 -0
- package/.claude/scripts/lesson-restore.py +171 -0
- package/.claude/scripts/lesson_ranker.py +100 -0
- package/.claude/scripts/lessons.py +883 -0
- package/.claude/scripts/lint-skills.py +555 -0
- package/.claude/scripts/local/README.md +280 -0
- package/.claude/scripts/local/check-doc-skill-paths.sh +124 -0
- package/.claude/scripts/local/dependency-graph.py +684 -0
- package/.claude/scripts/local/estimate-calibrator.py +240 -0
- package/.claude/scripts/local/findings-pretty-print.py +78 -0
- package/.claude/scripts/local/generate-ceremony.sh +558 -0
- package/.claude/scripts/local/pair-rail-gate.sh +156 -0
- package/.claude/scripts/local/release-dry-run.py +853 -0
- package/.claude/scripts/local/tests/test_dependency_graph.py +364 -0
- package/.claude/scripts/local/tests/test_generate_ceremony.sh +144 -0
- package/.claude/scripts/local/tests/test_release_dry_run.py +743 -0
- package/.claude/scripts/local/validate-findings.py +168 -0
- package/.claude/scripts/local/validate-saved-workflows.js +69 -0
- package/.claude/scripts/local/verify-counts.sh +420 -0
- package/.claude/scripts/local/verify-scope-coverage.py +205 -0
- package/.claude/scripts/local/verify-staging-manifest.py +188 -0
- package/.claude/scripts/local/wave-readonly-monitor.py +271 -0
- package/.claude/scripts/log-friction.sh +290 -0
- package/.claude/scripts/mcp/code_nav_bridge.py +259 -0
- package/.claude/scripts/mcp-server/__init__.py +16 -0
- package/.claude/scripts/mcp-server/auth.py +333 -0
- package/.claude/scripts/mcp-server/cost.py +108 -0
- package/.claude/scripts/mcp-server/dispatch.py +853 -0
- package/.claude/scripts/mcp-server/handlers/__init__.py +16 -0
- package/.claude/scripts/mcp-server/handlers/audit_query.py +384 -0
- package/.claude/scripts/mcp-server/handlers/get_audit_log.py +163 -0
- package/.claude/scripts/mcp-server/handlers/get_cost_budget.py +130 -0
- package/.claude/scripts/mcp-server/handlers/get_debate_state.py +207 -0
- package/.claude/scripts/mcp-server/handlers/get_skill.py +199 -0
- package/.claude/scripts/mcp-server/handlers/list_agents.py +236 -0
- package/.claude/scripts/mcp-server/handlers/list_pitfalls.py +192 -0
- package/.claude/scripts/mcp-server/handlers/list_skills.py +197 -0
- package/.claude/scripts/mcp-server/handlers/plan_status.py +489 -0
- package/.claude/scripts/mcp-server/handlers/server_capabilities.py +127 -0
- package/.claude/scripts/mcp-server/handlers/spawn_agent.py +274 -0
- package/.claude/scripts/mcp-server/http_transport.py +373 -0
- package/.claude/scripts/mcp-server/rate_limit.py +345 -0
- package/.claude/scripts/mcp-server/server.py +212 -0
- package/.claude/scripts/mcp-server/start-mcp-server.sh +111 -0
- package/.claude/scripts/mcp-server/stdio_transport.py +150 -0
- package/.claude/scripts/mcp-server/tests/__init__.py +1 -0
- package/.claude/scripts/mcp-server/tests/test_auth.py +454 -0
- package/.claude/scripts/mcp-server/tests/test_cost.py +122 -0
- package/.claude/scripts/mcp-server/tests/test_dispatch.py +448 -0
- package/.claude/scripts/mcp-server/tests/test_dispatch_bearer_replay_wire.py +358 -0
- package/.claude/scripts/mcp-server/tests/test_handlers_get_audit_log.py +107 -0
- package/.claude/scripts/mcp-server/tests/test_handlers_get_skill.py +108 -0
- package/.claude/scripts/mcp-server/tests/test_handlers_list_agents.py +92 -0
- package/.claude/scripts/mcp-server/tests/test_handlers_list_pitfalls.py +103 -0
- package/.claude/scripts/mcp-server/tests/test_handlers_list_skills.py +121 -0
- package/.claude/scripts/mcp-server/tests/test_handlers_server_capabilities.py +128 -0
- package/.claude/scripts/mcp-server/tests/test_handlers_spawn_agent.py +275 -0
- package/.claude/scripts/mcp-server/tests/test_http_transport.py +418 -0
- package/.claude/scripts/mcp-server/tests/test_rate_limit.py +239 -0
- package/.claude/scripts/mcp-server/tests/test_server.py +125 -0
- package/.claude/scripts/mcp-server/tests/test_stdio_transport.py +196 -0
- package/.claude/scripts/mcp-soak-monitor.py +224 -0
- package/.claude/scripts/memory-prioritize.py +516 -0
- package/.claude/scripts/migrate-grandfather-to-sha256.py +384 -0
- package/.claude/scripts/model-deprecations.json +165 -0
- package/.claude/scripts/morning-ceremony.py +266 -0
- package/.claude/scripts/morning_ledger.py +446 -0
- package/.claude/scripts/mutation-floors.yaml +51 -0
- package/.claude/scripts/mutation-test.py +506 -0
- package/.claude/scripts/nightly-proposals.py +210 -0
- package/.claude/scripts/optimizer/__init__.py +46 -0
- package/.claude/scripts/optimizer/_codex_redaction.py +101 -0
- package/.claude/scripts/optimizer/_skeleton.py +137 -0
- package/.claude/scripts/optimizer/codex_phase_gate.py +257 -0
- package/.claude/scripts/optimizer/complexity_gate.py +208 -0
- package/.claude/scripts/optimizer/fanout.py +249 -0
- package/.claude/scripts/optimizer/model_choice.py +151 -0
- package/.claude/scripts/optimizer/model_normalize.py +118 -0
- package/.claude/scripts/optimizer/rag_recommender.py +110 -0
- package/.claude/scripts/optimizer/recommender.py +213 -0
- package/.claude/scripts/optimizer/tests/__init__.py +0 -0
- package/.claude/scripts/optimizer/tests/test_codex_phase_gate.py +314 -0
- package/.claude/scripts/optimizer/tests/test_codex_review_invoked_emission.py +225 -0
- package/.claude/scripts/optimizer/tests/test_optimizer_complexity_gate.py +122 -0
- package/.claude/scripts/optimizer/tests/test_optimizer_fanout.py +134 -0
- package/.claude/scripts/optimizer/tests/test_optimizer_model_choice.py +124 -0
- package/.claude/scripts/optimizer/tests/test_optimizer_model_normalize.py +155 -0
- package/.claude/scripts/optimizer/tests/test_optimizer_rag_recommender.py +190 -0
- package/.claude/scripts/optimizer/tests/test_optimizer_recommender.py +131 -0
- package/.claude/scripts/optimizer/tests/test_optimizer_skeleton.py +117 -0
- package/.claude/scripts/optimizer/tests/test_optimizer_types.py +53 -0
- package/.claude/scripts/optimizer/types.py +122 -0
- package/.claude/scripts/osv_check.py +559 -0
- package/.claude/scripts/otel-export.py +329 -0
- package/.claude/scripts/otel-local-sink.py +470 -0
- package/.claude/scripts/persona_demand_resolver.py +658 -0
- package/.claude/scripts/persona_demand_scan.py +382 -0
- package/.claude/scripts/persona_waive_parser.py +127 -0
- package/.claude/scripts/pitfall-query.py +218 -0
- package/.claude/scripts/plan-tokens.py +843 -0
- package/.claude/scripts/policy-shadow-runner.py +445 -0
- package/.claude/scripts/predict-budget/predict-plan-cost.py +581 -0
- package/.claude/scripts/predict-budget/tests/test_predict_plan_cost.py +375 -0
- package/.claude/scripts/profile-opus-4-7.py +557 -0
- package/.claude/scripts/prune-lessons.py +453 -0
- package/.claude/scripts/rate-card-calibrate.py +283 -0
- package/.claude/scripts/rate-card-fixtures.json +18 -0
- package/.claude/scripts/reality-ledger.py +2175 -0
- package/.claude/scripts/red-team-corpus/.byte-identity-check.txt +86 -0
- package/.claude/scripts/red-team-corpus/README.md +132 -0
- package/.claude/scripts/red-team-corpus/external/EXT-001-prompt-inject.md +24 -0
- package/.claude/scripts/red-team-corpus/external/EXT-002-hackaprompt.md +25 -0
- package/.claude/scripts/red-team-corpus/external/EXT-003-gcg.md +31 -0
- package/.claude/scripts/red-team-corpus/external/EXT-004-tap.md +23 -0
- package/.claude/scripts/red-team-corpus/external/EXT-005-cybersecurity-eval.md +30 -0
- package/.claude/scripts/red-team-corpus/external/EXT-006-anthropic-samples.md +26 -0
- package/.claude/scripts/red-team-corpus/external/EXT-007-trojan-source.md +26 -0
- package/.claude/scripts/red-team-corpus/external/EXT-008-owasp-llm-top10.md +33 -0
- package/.claude/scripts/red-team-corpus/external/EXT-009-jailbreak-bench.md +24 -0
- package/.claude/scripts/red-team-corpus/external/EXT-010-advbench.md +22 -0
- package/.claude/scripts/red-team-corpus/external/EXT-011-mitre-atlas.md +25 -0
- package/.claude/scripts/red-team-corpus/external/EXT-012-npm-typosquat.md +23 -0
- package/.claude/scripts/red-team-corpus/external/EXT-013-log-tamper-poc.md +25 -0
- package/.claude/scripts/red-team-corpus/external/EXT-014-cwe-798-credentials.md +24 -0
- package/.claude/scripts/red-team-corpus/external/EXT-015-garak.md +28 -0
- package/.claude/scripts/red-team-corpus/external/EXT-016-skill-content-injection-via-markdown.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-017-persona-impersonation-ceo.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-018-file-assignment-wildcard-escape.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-019-veto-bypass-force-proceed.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-020-canonical-edit-circumvent-settings.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-021-spawn-without-agent-profile.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-022-hidden-unicode-in-skill-name.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-023-mcp-spawn-governance-bypass.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-024-adapter-credential-in-error-trace.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-025-sandbox-escape-nested-subshell.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-026-plan-edit-without-debate.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-027-audit-log-rotation-race.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-028-npm-dependency-confusion.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-029-output-safety-unicode-confusable.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-030-adapter-retry-storm-dos.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-031-team-md-direct-edit.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-032-sandbox-env-var-exfil.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-033-mcp-rate-limit-bypass-headers.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-034-otel-span-attribute-leak.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-035-skill-patch-polyglot-payload.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-036-output-safety-base64-triple-wrap.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-037-plan-id-cross-plan-memory-read.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-038-npm-slsa-provenance-strip.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-039-adapter-exfil-streaming-chunk.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/EXT-040-sandbox-symlink-to-secrets.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/external/README.md +63 -0
- package/.claude/scripts/red-team-corpus/flake-budget.yaml +244 -0
- package/.claude/scripts/red-team-corpus/provenance.md +74 -0
- package/.claude/scripts/red-team-corpus/regression/REG-001-s3-audit-emission-gap.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-002-audit-registry-miss.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-003-breaker-provider-kwarg-missing.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-004-canonical-edit-conftest-block.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-005-mcp-dispatch-oversized-handler.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-006-audit-registry-false-orphan.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-007-spec-count-undercount.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-008-adr-reserved-slot-phantom.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-009-tlc-pending-placeholder.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-010-mutation-kill-rate-fake.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-011-byte-identity-governance-persona.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-012-conformance-mapping-partial-path.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-013-l1-fairness-lazy-fire.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-014-mcp-path-traversal-skill.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/regression/REG-015-mcp-hmac-timestamp-skew.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-001-skill-patch-bidi-trojan.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-002-skill-patch-zero-width-smuggle.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-003-skill-patch-exec-smuggled-fence.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-004-skill-patch-oversized-diff.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-005-audit-log-byte-rewrite.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-006-audit-log-truncation.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-007-audit-log-lock-race.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-008-plan-id-env-spoof.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-009-plan-id-frontmatter-hijack.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-010-plan-id-cross-plan-read.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-011-sandbox-escape-curl-exfil.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-012-sandbox-escape-env-dump.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-013-sandbox-escape-symlink-plant.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-014-mcp-handler-governance-bypass.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-015-mcp-handler-acl-enumeration.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-016-mcp-handler-rate-limit-evasion.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-017-adapter-exfil-via-error-message.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-018-adapter-exfil-otel-attr.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-019-adapter-exfil-retry-replay.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-020-output-safety-nfkc-bypass.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-021-output-safety-base64-double-wrap.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-022-output-safety-entropy-below-threshold.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-023-output-safety-regex-obfuscation.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-024-output-safety-luhn-partial.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-025-npm-tamper-supply-chain.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-026-npm-tamper-typo-squat.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/synthetic/SYN-027-npm-tamper-unsigned-slsa.jsonl +1 -0
- package/.claude/scripts/red-team-corpus/v1/fixtures.jsonl +67 -0
- package/.claude/scripts/red-team-corpus/v1/fixtures.jsonl.sha256 +1 -0
- package/.claude/scripts/red-team-corpus/v1/labels.json +88 -0
- package/.claude/scripts/red-team-eval.py +1099 -0
- package/.claude/scripts/registry.py +438 -0
- package/.claude/scripts/replay/__init__.py +0 -0
- package/.claude/scripts/replay/replay-session.py +1232 -0
- package/.claude/scripts/replay/tests/__init__.py +0 -0
- package/.claude/scripts/replay/tests/fixtures/api-key-01-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/api-key-02-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/api-key-03-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/api-key-04-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/api-key-05-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/api-key-06-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/api-key-07-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/api-key-08-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-01-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-02-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-03-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-04-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-05-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-06-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-07-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/cpf-cnpj-08-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/email-in-log-01-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/email-in-log-02-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/email-in-log-03-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/email-in-log-04-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/email-in-log-05-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/email-in-log-06-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/email-in-log-07-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/email-in-log-08-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/homoglyph-01-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/homoglyph-02-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/homoglyph-03-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/homoglyph-04-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/homoglyph-05-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/homoglyph-06-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/homoglyph-07-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/homoglyph-08-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/jwt-01-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/jwt-02-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/jwt-03-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/jwt-04-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/jwt-05-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/jwt-06-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/jwt-07-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/jwt-08-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/os-path-01-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/os-path-02-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/os-path-03-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/os-path-04-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/os-path-05-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/os-path-06-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/os-path-07-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/os-path-08-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/pan-01-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/pan-02-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/pan-03-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/pan-04-positive.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/pan-05-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/pan-06-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/pan-07-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/fixtures/pan-08-negative.jsonl +1 -0
- package/.claude/scripts/replay/tests/test_replay_redact_lib.py +971 -0
- package/.claude/scripts/replay/tests/test_replay_session.py +396 -0
- package/.claude/scripts/replay/tests/test_replay_session_capture.py +522 -0
- package/.claude/scripts/repo-profile.schema.json +83 -0
- package/.claude/scripts/run-promotion-gate.py +631 -0
- package/.claude/scripts/run-skill-benchmark.py +1276 -0
- package/.claude/scripts/scan-injection-strict.sh +162 -0
- package/.claude/scripts/scan-injection.py +305 -0
- package/.claude/scripts/scan-upstream-injection.py +663 -0
- package/.claude/scripts/scratchpad.py +427 -0
- package/.claude/scripts/self_test.py +602 -0
- package/.claude/scripts/session-graph-build.py +728 -0
- package/.claude/scripts/session-resume.py +363 -0
- package/.claude/scripts/set-quality-profile.sh +229 -0
- package/.claude/scripts/skill-budget-generator.py +599 -0
- package/.claude/scripts/skill-import-rubric.py +368 -0
- package/.claude/scripts/skill-index-build.py +534 -0
- package/.claude/scripts/skill-patch-apply.py +1088 -0
- package/.claude/scripts/skill-patch-propose.py +690 -0
- package/.claude/scripts/skill-retrieve.py +522 -0
- package/.claude/scripts/skill_grandfather_parser.py +295 -0
- package/.claude/scripts/smart-loading-resolver.py +994 -0
- package/.claude/scripts/spot-check-findings.py +211 -0
- package/.claude/scripts/squad-export.py +437 -0
- package/.claude/scripts/squad-import.py +741 -0
- package/.claude/scripts/status.py +315 -0
- package/.claude/scripts/statusline-ceo.py +597 -0
- package/.claude/scripts/substrate-watch.json +54 -0
- package/.claude/scripts/success-receipt.py +1038 -0
- package/.claude/scripts/swarm/__init__.py +42 -0
- package/.claude/scripts/swarm/_benchmark_replay.py +259 -0
- package/.claude/scripts/swarm/_child_isolation.py +113 -0
- package/.claude/scripts/swarm/_coordinator_sim.py +293 -0
- package/.claude/scripts/swarm/_governors.py +277 -0
- package/.claude/scripts/swarm/_integration.py +547 -0
- package/.claude/scripts/swarm/_parent_death.py +176 -0
- package/.claude/scripts/swarm/_process_group.py +250 -0
- package/.claude/scripts/swarm/_replay_tournament.py +214 -0
- package/.claude/scripts/swarm/_spawn_gate.py +292 -0
- package/.claude/scripts/swarm/_subagent_fabrication.py +444 -0
- package/.claude/scripts/swarm/_worktree_pool.py +276 -0
- package/.claude/scripts/swarm/coordinator.py +543 -0
- package/.claude/scripts/swarm/file_assignment.py +111 -0
- package/.claude/scripts/swarm/fixtures/mcp_corpus.json +111 -0
- package/.claude/scripts/swarm/kill_switch.py +260 -0
- package/.claude/scripts/swarm/loop_runner.py +486 -0
- package/.claude/scripts/swarm/recovery.py +178 -0
- package/.claude/scripts/swarm/test_mcp_injection_repro.py +518 -0
- package/.claude/scripts/swarm/test_rail_anomaly_repro.py +586 -0
- package/.claude/scripts/swarm/tests/__init__.py +1 -0
- package/.claude/scripts/swarm/tests/test_benchmark_manifest_schema.py +227 -0
- package/.claude/scripts/swarm/tests/test_benchmark_replay.py +248 -0
- package/.claude/scripts/swarm/tests/test_child_isolation.py +138 -0
- package/.claude/scripts/swarm/tests/test_coordinator.py +289 -0
- package/.claude/scripts/swarm/tests/test_coordinator_production_integration.py +434 -0
- package/.claude/scripts/swarm/tests/test_coordinator_sim.py +192 -0
- package/.claude/scripts/swarm/tests/test_coordinator_tick.py +165 -0
- package/.claude/scripts/swarm/tests/test_file_assignment.py +100 -0
- package/.claude/scripts/swarm/tests/test_governors.py +269 -0
- package/.claude/scripts/swarm/tests/test_integration.py +344 -0
- package/.claude/scripts/swarm/tests/test_kill_switch.py +307 -0
- package/.claude/scripts/swarm/tests/test_loop_runner.py +168 -0
- package/.claude/scripts/swarm/tests/test_loop_runner_circuit_breaker.py +555 -0
- package/.claude/scripts/swarm/tests/test_loop_runner_gate_enforcement.py +304 -0
- package/.claude/scripts/swarm/tests/test_loop_runner_gate_kill_switch.py +147 -0
- package/.claude/scripts/swarm/tests/test_loop_runner_sentinel_revocation_slo.py +112 -0
- package/.claude/scripts/swarm/tests/test_optimizer_killswitch.py +205 -0
- package/.claude/scripts/swarm/tests/test_parent_death.py +128 -0
- package/.claude/scripts/swarm/tests/test_parent_death_integration.py +305 -0
- package/.claude/scripts/swarm/tests/test_process_group.py +132 -0
- package/.claude/scripts/swarm/tests/test_process_group_reap.py +212 -0
- package/.claude/scripts/swarm/tests/test_rail_anomaly_repro.py +516 -0
- package/.claude/scripts/swarm/tests/test_recovery.py +165 -0
- package/.claude/scripts/swarm/tests/test_replay_tournament.py +284 -0
- package/.claude/scripts/swarm/tests/test_spawn_gate.py +265 -0
- package/.claude/scripts/swarm/tests/test_subagent_fabrication.py +824 -0
- package/.claude/scripts/swarm/tests/test_swarm_activation_smoke.py +112 -0
- package/.claude/scripts/swarm/tests/test_tournament.py +195 -0
- package/.claude/scripts/swarm/tests/test_worktree_pool.py +252 -0
- package/.claude/scripts/swarm/tournament.py +261 -0
- package/.claude/scripts/task-route.py +807 -0
- package/.claude/scripts/test-env-hygiene-allowlist.yaml +1093 -0
- package/.claude/scripts/tests/DEFERRED.md +99 -0
- package/.claude/scripts/tests/conftest.py +42 -0
- package/.claude/scripts/tests/fixtures/aggregate-changesets/bad-type.md +4 -0
- package/.claude/scripts/tests/fixtures/aggregate-changesets/missing-frontmatter.md +1 -0
- package/.claude/scripts/tests/fixtures/aggregate-changesets/multidoc.md +6 -0
- package/.claude/scripts/tests/fixtures/aggregate-changesets/sample-CHANGELOG.md +29 -0
- package/.claude/scripts/tests/fixtures/aggregate-changesets/second-minor.md +4 -0
- package/.claude/scripts/tests/fixtures/aggregate-changesets/single-patch.md +4 -0
- package/.claude/scripts/tests/fixtures/aggregate-changesets/third-major.md +4 -0
- package/.claude/scripts/tests/fixtures/aggregate-changesets/unknown-key.md +6 -0
- package/.claude/scripts/tests/fixtures/bad_lessons/bidi_override.md +12 -0
- package/.claude/scripts/tests/fixtures/bad_lessons/fenced_python.md +19 -0
- package/.claude/scripts/tests/fixtures/bad_lessons/homoglyph.md +11 -0
- package/.claude/scripts/tests/fixtures/bad_lessons/injection.md +11 -0
- package/.claude/scripts/tests/fixtures/bad_lessons/long_line.md +9 -0
- package/.claude/scripts/tests/fixtures/bad_lessons/oversized.md +261 -0
- package/.claude/scripts/tests/fixtures/bad_lessons/zero_width.md +11 -0
- package/.claude/scripts/tests/fixtures/budget_summary/generate_fixtures.py +368 -0
- package/.claude/scripts/tests/fixtures/claims/README.md +21 -0
- package/.claude/scripts/tests/fixtures/claims/function_exists/neg-missing.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/function_exists/neg-no-file.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/function_exists/pos-extract.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/function_exists/pos-main.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/function_exists/pos-verify.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/function_exists/quoted-colon-path.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/import_resolves/codeblock-skipped.txt +8 -0
- package/.claude/scripts/tests/fixtures/claims/import_resolves/neg-blocked-os.txt +6 -0
- package/.claude/scripts/tests/fixtures/claims/import_resolves/neg-relative.txt +5 -0
- package/.claude/scripts/tests/fixtures/claims/import_resolves/pos-dotted.txt +6 -0
- package/.claude/scripts/tests/fixtures/claims/import_resolves/pos-stdlib-like.txt +5 -0
- package/.claude/scripts/tests/fixtures/claims/line_range/neg-missing-file.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/line_range/neg-too-long.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/line_range/pos-large.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/line_range/pos-small.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/line_range/quoted-path.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/codeblock-skipped.txt +7 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/neg-absolute-outside.txt +6 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/neg-dotdot-escape.txt +7 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/neg-imaginary.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/neg-proc-self.txt +6 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/neg-symlink-escape.txt +8 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/neg-typo.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/pos-claude.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/pos-readme.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/path_exists/pos-self.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/sha_exists/neg-fake.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/sha_exists/neg-not-sha.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/sha_exists/pos-head.txt +4 -0
- package/.claude/scripts/tests/fixtures/claims/sha_exists/pos-root.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/sha_exists/pos-short.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/test_passes/neg-missing-file.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/test_passes/neg-wrong-test.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/test_passes/pos-audit-emit.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/test_passes/pos-extra.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/test_passes/pos-file.txt +1 -0
- package/.claude/scripts/tests/fixtures/claims/test_passes/quoted-pytest-selector.txt +1 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/converged-pair-1/round-1/a.md +39 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/converged-pair-1/round-1/b.md +36 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/converged-pair-1/round-2/a.md +36 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/converged-pair-1/round-2/b.md +36 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/not-converged-pair-1/round-1/a.md +35 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/not-converged-pair-1/round-1/b.md +34 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/not-converged-pair-1/round-2/a.md +35 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/not-converged-pair-1/round-2/b.md +34 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/partial-overlap/round-1/a.md +35 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/partial-overlap/round-2/a.md +36 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/with-secret/round-1/a.md +36 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/with-secret/round-1/b.md +33 -0
- package/.claude/scripts/tests/fixtures/debate_convergence/with-secret/round-2/a.md +34 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_anchor_only.md +10 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_broken.md +5 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_external_url.md +9 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_in_fenced_code.md +18 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_in_frontmatter.md +10 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_in_html_comment.md +10 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_in_inline_code.md +7 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_in_table.md +6 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_relative_parent.md +7 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/link_url_encoded.md +5 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/real_target.md +3 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/sub/dir.md +3 -0
- package/.claude/scripts/tests/fixtures/docs_freshness/with%20space.md +3 -0
- package/.claude/scripts/tests/fixtures/good_lessons/clean_auth.md +11 -0
- package/.claude/scripts/tests/fixtures/good_lessons/clean_logging.md +11 -0
- package/.claude/scripts/tests/fixtures/good_lessons/clean_retry.md +11 -0
- package/.claude/scripts/tests/fixtures/gpg-keyring-fixture.py +209 -0
- package/.claude/scripts/tests/fixtures/injection/benign-01.txt +8 -0
- package/.claude/scripts/tests/fixtures/injection/benign-02.txt +5 -0
- package/.claude/scripts/tests/fixtures/injection/benign-03.txt +7 -0
- package/.claude/scripts/tests/fixtures/injection/benign-04.txt +9 -0
- package/.claude/scripts/tests/fixtures/injection/benign-05.txt +7 -0
- package/.claude/scripts/tests/fixtures/injection/benign-06.txt +7 -0
- package/.claude/scripts/tests/fixtures/injection/benign-07.txt +11 -0
- package/.claude/scripts/tests/fixtures/injection/benign-08.txt +4 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-01.txt +4 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-02.txt +2 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-03.txt +4 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-04.txt +2 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-05.txt +2 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-06.txt +5 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-07.txt +5 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-08.txt +2 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-09.txt +3 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-10.txt +2 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-11.txt +3 -0
- package/.claude/scripts/tests/fixtures/injection/malicious-12.txt +5 -0
- package/.claude/scripts/tests/fixtures/plan-tokens-calibration/manifest.json +49 -0
- package/.claude/scripts/tests/fixtures/plan-tokens-calibration/plan-051.md +36 -0
- package/.claude/scripts/tests/fixtures/plan-tokens-calibration/plan-052.md +32 -0
- package/.claude/scripts/tests/fixtures/plan-tokens-calibration/plan-058.md +31 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-boundary/docs/SAMPLE.md +8 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-negative/.claude/scripts/sample.py +12 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-negative/docs/SAMPLE.md +4 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-positive/.claude/scripts/sample.py +12 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-1-positive/docs/SAMPLE.md +9 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-2-boundary/README.md +4 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-2-negative/.claude/rag/requirements.lock +4 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-2-positive/.claude/rag/requirements.lock +2 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-boundary/.claude/agents/devops.md +8 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-negative/.claude/agents/devops.md +5 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-negative/audit-log.jsonl +2 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-positive/.claude/agents/devops.md +7 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-3-positive/audit-log.jsonl +4 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-4-boundary/.claude/adr/ADR-997-fixture-superseded.md +8 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-4-negative/.claude/adr/ADR-998-fixture-negative.md +16 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-4-positive/.claude/adr/ADR-999-fixture-positive.md +15 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-boundary/.claude/hooks/_lib/.do-not-import-from-here +15 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-boundary/.claude/hooks/_lib/audit_emit.py +8 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-boundary/.claude/scripts/dynamic_action.py +12 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-negative/.claude/hooks/_lib/.do-not-import-from-here +15 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-negative/.claude/hooks/_lib/audit_emit.py +11 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-negative/.claude/scripts/registered_emitter.py +8 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-positive/.claude/hooks/_lib/.do-not-import-from-here +15 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-positive/.claude/hooks/_lib/audit_emit.py +12 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/detector-6-positive/.claude/scripts/phantom_emitter.py +13 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/issue-body-template.md +47 -0
- package/.claude/scripts/tests/fixtures/reality-ledger/redaction/_test_corpus.py +7 -0
- package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/.env.example +5 -0
- package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/Cargo.toml +9 -0
- package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/README.md +6 -0
- package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/exchanges/binance.py +6 -0
- package/.claude/scripts/tests/fixtures/repo_profile/cloned-trading-repo/strategies/triangular.py +4 -0
- package/.claude/scripts/tests/fixtures/repo_profile/missing-package-manifest/README.md +7 -0
- package/.claude/scripts/tests/fixtures/repo_profile/missing-package-manifest/notes.md +1 -0
- package/.claude/scripts/tests/fixtures/repo_profile/mixed-frontend-backend/README.md +6 -0
- package/.claude/scripts/tests/fixtures/repo_profile/mixed-frontend-backend/api/server.js +4 -0
- package/.claude/scripts/tests/fixtures/repo_profile/mixed-frontend-backend/package.json +15 -0
- package/.claude/scripts/tests/fixtures/repo_profile/mixed-frontend-backend/pages/index.tsx +3 -0
- package/.claude/scripts/tests/fixtures/repo_profile/monorepo/README.md +6 -0
- package/.claude/scripts/tests/fixtures/repo_profile/monorepo/apps/backend/.gitkeep +0 -0
- package/.claude/scripts/tests/fixtures/repo_profile/monorepo/apps/frontend/.gitkeep +0 -0
- package/.claude/scripts/tests/fixtures/repo_profile/monorepo/package.json +5 -0
- package/.claude/scripts/tests/fixtures/repo_profile/monorepo/packages/shared/.gitkeep +0 -0
- package/.claude/scripts/tests/fixtures/sample_audit_log.jsonl +50 -0
- package/.claude/scripts/tests/fixtures/siem/.gitkeep +0 -0
- package/.claude/scripts/tests/fixtures/smart_loading/profile-engine.yaml +8 -0
- package/.claude/scripts/tests/fixtures/smart_loading/profile-fail-closed.yaml +7 -0
- package/.claude/scripts/tests/fixtures/smart_loading/profile-fintech.yaml +9 -0
- package/.claude/scripts/tests/fixtures/smart_loading/profile-frontend.yaml +9 -0
- package/.claude/scripts/tests/fixtures/smart_loading/profile-generic.yaml +8 -0
- package/.claude/scripts/tests/fixtures/smart_loading/profile-trading-readonly.yaml +9 -0
- package/.claude/scripts/tests/fixtures/smart_loading/synthetic-skill-catalog.yaml +186 -0
- package/.claude/scripts/tests/fixtures/squad_marketplace/.gitkeep +4 -0
- package/.claude/scripts/tests/fixtures/task-route/calibration-holdout.json +49 -0
- package/.claude/scripts/tests/fixtures/task-route/calibration-train.json +174 -0
- package/.claude/scripts/tests/perf/__init__.py +3 -0
- package/.claude/scripts/tests/perf/perf_utils.py +134 -0
- package/.claude/scripts/tests/perf/test_kernel_hard_deny_microbench.py +149 -0
- package/.claude/scripts/tests/perf/test_optimizer_complexity_gate_p99.py +145 -0
- package/.claude/scripts/tests/perf/test_wave_c_canonical_json.py +132 -0
- package/.claude/scripts/tests/perf/test_wave_c_filelock_mkdir.py +71 -0
- package/.claude/scripts/tests/perf/test_wave_c_plan_glob_cache.py +84 -0
- package/.claude/scripts/tests/perf/test_wave_c_preview_collapse.py +98 -0
- package/.claude/scripts/tests/perf/test_wave_c_sys_modules.py +104 -0
- package/.claude/scripts/tests/test_a4_pricing_doctrine.py +127 -0
- package/.claude/scripts/tests/test_admin_invite.py +173 -0
- package/.claude/scripts/tests/test_adopter_metrics.py +723 -0
- package/.claude/scripts/tests/test_aek_calibration_c2.py +107 -0
- package/.claude/scripts/tests/test_aek_calibration_c3.py +192 -0
- package/.claude/scripts/tests/test_aek_state_machine.py +385 -0
- package/.claude/scripts/tests/test_aggregate_changesets.py +646 -0
- package/.claude/scripts/tests/test_architect_bundle_validate.py +159 -0
- package/.claude/scripts/tests/test_audit_dashboard.py +822 -0
- package/.claude/scripts/tests/test_audit_log_dispatch_hint.py +91 -0
- package/.claude/scripts/tests/test_audit_log_retain.py +394 -0
- package/.claude/scripts/tests/test_audit_query.py +1177 -0
- package/.claude/scripts/tests/test_audit_query_by_domain.py +576 -0
- package/.claude/scripts/tests/test_audit_query_claims.py +92 -0
- package/.claude/scripts/tests/test_audit_query_critical.py +267 -0
- package/.claude/scripts/tests/test_audit_query_tokens.py +106 -0
- package/.claude/scripts/tests/test_audit_telemetry.py +214 -0
- package/.claude/scripts/tests/test_audit_tokens.py +255 -0
- package/.claude/scripts/tests/test_audit_verify_chain.py +189 -0
- package/.claude/scripts/tests/test_backup_audit.py +295 -0
- package/.claude/scripts/tests/test_benchmark_fallback_scorer.py +299 -0
- package/.claude/scripts/tests/test_benchmark_judge.py +569 -0
- package/.claude/scripts/tests/test_benchmarks_replay.py +313 -0
- package/.claude/scripts/tests/test_budget_summary.py +628 -0
- package/.claude/scripts/tests/test_build_canonical_models.py +349 -0
- package/.claude/scripts/tests/test_calibration_kappa.py +234 -0
- package/.claude/scripts/tests/test_cc_analytics_pull.py +296 -0
- package/.claude/scripts/tests/test_ceo_backup.py +318 -0
- package/.claude/scripts/tests/test_ceo_boot.py +643 -0
- package/.claude/scripts/tests/test_ceo_boot_audit_emit.py +484 -0
- package/.claude/scripts/tests/test_ceo_boot_enhanced.py +706 -0
- package/.claude/scripts/tests/test_ceo_boot_persona_cadence.py +392 -0
- package/.claude/scripts/tests/test_ceo_boot_plan_082.py +365 -0
- package/.claude/scripts/tests/test_ceo_boot_tamper_tripwires.py +556 -0
- package/.claude/scripts/tests/test_ceo_boot_task_candidate.py +868 -0
- package/.claude/scripts/tests/test_ceo_cost.py +221 -0
- package/.claude/scripts/tests/test_ceo_cost_stream.py +1076 -0
- package/.claude/scripts/tests/test_ceo_diagnose.py +314 -0
- package/.claude/scripts/tests/test_ceo_escalation_detector.py +591 -0
- package/.claude/scripts/tests/test_ceo_health.py +202 -0
- package/.claude/scripts/tests/test_ceo_info.py +542 -0
- package/.claude/scripts/tests/test_chaos_inject_lockdown.py +384 -0
- package/.claude/scripts/tests/test_check_action_sha_drift.py +174 -0
- package/.claude/scripts/tests/test_check_active_hooks_executable.py +79 -0
- package/.claude/scripts/tests/test_check_adr_chain.py +665 -0
- package/.claude/scripts/tests/test_check_audit_hmac_null.py +178 -0
- package/.claude/scripts/tests/test_check_audit_read_api_stable.py +176 -0
- package/.claude/scripts/tests/test_check_audit_registry_coverage.py +744 -0
- package/.claude/scripts/tests/test_check_auto_activation_flags.py +140 -0
- package/.claude/scripts/tests/test_check_canonical_doc_freshness.py +149 -0
- package/.claude/scripts/tests/test_check_claude_md_claims.py +223 -0
- package/.claude/scripts/tests/test_check_conformance_harness_mapping.py +243 -0
- package/.claude/scripts/tests/test_check_contamination.py +161 -0
- package/.claude/scripts/tests/test_check_creative_rewrite.py +183 -0
- package/.claude/scripts/tests/test_check_debate_round_lifecycle.py +162 -0
- package/.claude/scripts/tests/test_check_debt_ledger.py +227 -0
- package/.claude/scripts/tests/test_check_doc_skill_paths.py +99 -0
- package/.claude/scripts/tests/test_check_docs_freshness.py +224 -0
- package/.claude/scripts/tests/test_check_flip_criteria_drift.py +343 -0
- package/.claude/scripts/tests/test_check_flip_release_gate_consistency.py +195 -0
- package/.claude/scripts/tests/test_check_function_length.py +519 -0
- package/.claude/scripts/tests/test_check_model_deprecations.py +368 -0
- package/.claude/scripts/tests/test_check_originator_residue.py +165 -0
- package/.claude/scripts/tests/test_check_rule_invariants.py +327 -0
- package/.claude/scripts/tests/test_check_sdk_compat.py +88 -0
- package/.claude/scripts/tests/test_check_sidecar_manifest_sbom_sync.py +177 -0
- package/.claude/scripts/tests/test_check_spec_drift.py +358 -0
- package/.claude/scripts/tests/test_check_staleness.py +128 -0
- package/.claude/scripts/tests/test_check_stdlib_only_exceptions.py +91 -0
- package/.claude/scripts/tests/test_check_substrate_watch.py +234 -0
- package/.claude/scripts/tests/test_check_test_audit_isolation.py +322 -0
- package/.claude/scripts/tests/test_check_test_env_hygiene.py +432 -0
- package/.claude/scripts/tests/test_check_threat_model_coverage.py +251 -0
- package/.claude/scripts/tests/test_check_threat_model_freshness.py +235 -0
- package/.claude/scripts/tests/test_check_tier_boundaries.py +225 -0
- package/.claude/scripts/tests/test_check_tla_schema_drift.py +246 -0
- package/.claude/scripts/tests/test_check_translations_drift.py +262 -0
- package/.claude/scripts/tests/test_code_nav_bridge.py +192 -0
- package/.claude/scripts/tests/test_compaction_template.py +163 -0
- package/.claude/scripts/tests/test_compare_adopters.py +646 -0
- package/.claude/scripts/tests/test_confidence_gate.py +611 -0
- package/.claude/scripts/tests/test_confidence_gate_backfill.py +212 -0
- package/.claude/scripts/tests/test_context_budget.py +1400 -0
- package/.claude/scripts/tests/test_contextual_recommender.py +723 -0
- package/.claude/scripts/tests/test_coverage_audit_marker.py +109 -0
- package/.claude/scripts/tests/test_debate_converge.py +399 -0
- package/.claude/scripts/tests/test_debate_emit_cli.py +153 -0
- package/.claude/scripts/tests/test_debate_orchestrate.py +575 -0
- package/.claude/scripts/tests/test_detect_repo_profile.py +434 -0
- package/.claude/scripts/tests/test_discover_foreign_context.py +208 -0
- package/.claude/scripts/tests/test_dispatch_archetype_hint.py +429 -0
- package/.claude/scripts/tests/test_dispatch_frontmatter_validation.py +274 -0
- package/.claude/scripts/tests/test_drift_wire.py +259 -0
- package/.claude/scripts/tests/test_embeddings.py +249 -0
- package/.claude/scripts/tests/test_env_inventory_check.py +197 -0
- package/.claude/scripts/tests/test_eval_c3.py +474 -0
- package/.claude/scripts/tests/test_extract_skill.py +572 -0
- package/.claude/scripts/tests/test_fan_plan_parser.py +213 -0
- package/.claude/scripts/tests/test_find_orphan_sentinels.py +62 -0
- package/.claude/scripts/tests/test_first_run_wizard.py +634 -0
- package/.claude/scripts/tests/test_generate_adr_index.py +146 -0
- package/.claude/scripts/tests/test_generate_available_models.py +209 -0
- package/.claude/scripts/tests/test_generate_dispatch.py +90 -0
- package/.claude/scripts/tests/test_generate_skill_inventory.py +76 -0
- package/.claude/scripts/tests/test_github_api_client.py +146 -0
- package/.claude/scripts/tests/test_governance_waivers_gate.py +176 -0
- package/.claude/scripts/tests/test_hook_profiler.py +426 -0
- package/.claude/scripts/tests/test_import_skill.py +927 -0
- package/.claude/scripts/tests/test_import_skill_skip_rubric_auth.py +198 -0
- package/.claude/scripts/tests/test_inject_agent_context_mitigated_dispatch.py +266 -0
- package/.claude/scripts/tests/test_inject_agent_context_reference_mode.py +105 -0
- package/.claude/scripts/tests/test_inspired_by_validator.py +307 -0
- package/.claude/scripts/tests/test_install_dispatcher_present_maintainer.py +76 -0
- package/.claude/scripts/tests/test_install_maintainer_unchanged.py +86 -0
- package/.claude/scripts/tests/test_install_npm_sha256.py +113 -0
- package/.claude/scripts/tests/test_install_sh_placeholders.py +268 -0
- package/.claude/scripts/tests/test_install_sh_self_sha.py +244 -0
- package/.claude/scripts/tests/test_install_sh_session_75_flags.py +147 -0
- package/.claude/scripts/tests/test_install_user_dispatcher_present.py +75 -0
- package/.claude/scripts/tests/test_install_user_no_writes_outside_claude.py +75 -0
- package/.claude/scripts/tests/test_install_user_passes_validate_governance.py +73 -0
- package/.claude/scripts/tests/test_install_user_preserves_existing_repo.py +135 -0
- package/.claude/scripts/tests/test_install_user_skips_governance_hooks.py +102 -0
- package/.claude/scripts/tests/test_k_calibration.py +415 -0
- package/.claude/scripts/tests/test_key_hygiene.py +372 -0
- package/.claude/scripts/tests/test_lesson_ranker.py +82 -0
- package/.claude/scripts/tests/test_lesson_restore.py +91 -0
- package/.claude/scripts/tests/test_lessons.py +278 -0
- package/.claude/scripts/tests/test_lessons_concurrency.py +118 -0
- package/.claude/scripts/tests/test_lessons_emit.py +114 -0
- package/.claude/scripts/tests/test_lessons_inject.py +144 -0
- package/.claude/scripts/tests/test_lessons_v2.py +264 -0
- package/.claude/scripts/tests/test_lint_skills.py +525 -0
- package/.claude/scripts/tests/test_log_friction.py +436 -0
- package/.claude/scripts/tests/test_memory_prioritize.py +315 -0
- package/.claude/scripts/tests/test_morning_ledger.py +415 -0
- package/.claude/scripts/tests/test_mutation_test.py +144 -0
- package/.claude/scripts/tests/test_npm_rebuild.py +154 -0
- package/.claude/scripts/tests/test_osv_check.py +411 -0
- package/.claude/scripts/tests/test_otel_export.py +613 -0
- package/.claude/scripts/tests/test_otel_local_sink.py +262 -0
- package/.claude/scripts/tests/test_owasp_llm_top_10_benchmark.py +235 -0
- package/.claude/scripts/tests/test_parse_coverage_tier1.py +107 -0
- package/.claude/scripts/tests/test_pitfall_query.py +148 -0
- package/.claude/scripts/tests/test_plan_frontmatter_status.py +217 -0
- package/.claude/scripts/tests/test_plan_id_uniqueness.py +133 -0
- package/.claude/scripts/tests/test_plan_schema_enforcement.py +251 -0
- package/.claude/scripts/tests/test_plan_tokens.py +513 -0
- package/.claude/scripts/tests/test_plan_vcheck_gate.py +257 -0
- package/.claude/scripts/tests/test_policy_shadow_runner.py +312 -0
- package/.claude/scripts/tests/test_prune_lessons.py +341 -0
- package/.claude/scripts/tests/test_quality_profile.py +392 -0
- package/.claude/scripts/tests/test_rate_card_calibrate.py +185 -0
- package/.claude/scripts/tests/test_reality_ledger.py +1723 -0
- package/.claude/scripts/tests/test_red_team_eval.py +566 -0
- package/.claude/scripts/tests/test_red_team_eval_sha.py +260 -0
- package/.claude/scripts/tests/test_registry.py +290 -0
- package/.claude/scripts/tests/test_run_benchmark.py +639 -0
- package/.claude/scripts/tests/test_run_skill_benchmark_emit.py +195 -0
- package/.claude/scripts/tests/test_run_skill_benchmark_judge_mode.py +306 -0
- package/.claude/scripts/tests/test_scan_injection.py +191 -0
- package/.claude/scripts/tests/test_scan_injection_strict.sh +201 -0
- package/.claude/scripts/tests/test_scratchpad_cli.py +317 -0
- package/.claude/scripts/tests/test_self_test.py +369 -0
- package/.claude/scripts/tests/test_session_graph.py +511 -0
- package/.claude/scripts/tests/test_session_resume.py +306 -0
- package/.claude/scripts/tests/test_siem_rule_fixtures_have_paired_positive_negative.py +112 -0
- package/.claude/scripts/tests/test_skill_budget_generator.py +329 -0
- package/.claude/scripts/tests/test_skill_grandfather_parser.py +314 -0
- package/.claude/scripts/tests/test_skill_import_rubric.py +497 -0
- package/.claude/scripts/tests/test_skill_patch_apply_create_new_skill.py +459 -0
- package/.claude/scripts/tests/test_skill_patch_propose.py +294 -0
- package/.claude/scripts/tests/test_skill_patch_shadow_race.py +271 -0
- package/.claude/scripts/tests/test_skill_retrieval.py +486 -0
- package/.claude/scripts/tests/test_skill_retrieve_rag_wire.py +747 -0
- package/.claude/scripts/tests/test_smart_loading_resolver.py +808 -0
- package/.claude/scripts/tests/test_squad_export.py +265 -0
- package/.claude/scripts/tests/test_squad_grandfather_cap.py +434 -0
- package/.claude/scripts/tests/test_squad_import.py +905 -0
- package/.claude/scripts/tests/test_statusline_ceo.py +543 -0
- package/.claude/scripts/tests/test_success_receipt.py +448 -0
- package/.claude/scripts/tests/test_task_route.py +456 -0
- package/.claude/scripts/tests/test_token_budget_guard.py +418 -0
- package/.claude/scripts/tests/test_token_estimator.py +395 -0
- package/.claude/scripts/tests/test_trading_readonly.py +705 -0
- package/.claude/scripts/tests/test_ui_ux_imports.py +223 -0
- package/.claude/scripts/tests/test_validate_skill_frontmatter_pii_core.py +630 -0
- package/.claude/scripts/tests/test_validate_spec_context.py +128 -0
- package/.claude/scripts/tests/test_validate_squad_contract.py +221 -0
- package/.claude/scripts/tests/test_value_dashboard.py +593 -0
- package/.claude/scripts/tests/test_verify_adr_118_rationale.py +183 -0
- package/.claude/scripts/tests/test_verify_atlas_binding.py +159 -0
- package/.claude/scripts/tests/test_verify_counts.py +138 -0
- package/.claude/scripts/tests/test_verify_counts_remediation.py +258 -0
- package/.claude/scripts/tests/test_verify_persona_coverage.py +576 -0
- package/.claude/scripts/tests/test_veto_check.py +171 -0
- package/.claude/scripts/tests/test_workflow_devops_p2.py +229 -0
- package/.claude/scripts/tier_policy_cli/__init__.py +43 -0
- package/.claude/scripts/tier_policy_cli/_agent_frontmatter.py +196 -0
- package/.claude/scripts/tier_policy_cli/_constants.py +92 -0
- package/.claude/scripts/tier_policy_cli/_types.py +228 -0
- package/.claude/scripts/tier_policy_cli/apply.py +1139 -0
- package/.claude/scripts/tier_policy_cli/cli.py +795 -0
- package/.claude/scripts/tier_policy_cli/learn.py +846 -0
- package/.claude/scripts/tier_policy_cli/loader.py +535 -0
- package/.claude/scripts/tier_policy_cli/setup.py +33 -0
- package/.claude/scripts/tier_policy_cli/tests/__init__.py +0 -0
- package/.claude/scripts/tier_policy_cli/tests/test_adversarial.py +605 -0
- package/.claude/scripts/tier_policy_cli/tests/test_agent_frontmatter.py +231 -0
- package/.claude/scripts/tier_policy_cli/tests/test_apply.py +698 -0
- package/.claude/scripts/tier_policy_cli/tests/test_check_tier_policy_hook.py +187 -0
- package/.claude/scripts/tier_policy_cli/tests/test_cli.py +434 -0
- package/.claude/scripts/tier_policy_cli/tests/test_constants.py +113 -0
- package/.claude/scripts/tier_policy_cli/tests/test_learn.py +1380 -0
- package/.claude/scripts/tier_policy_cli/tests/test_learn_mutation.py +549 -0
- package/.claude/scripts/tier_policy_cli/tests/test_loader.py +368 -0
- package/.claude/scripts/tier_policy_cli/tests/test_types.py +152 -0
- package/.claude/scripts/token-budget-guard.py +657 -0
- package/.claude/scripts/token-estimator.py +957 -0
- package/.claude/scripts/tournament/__init__.py +22 -0
- package/.claude/scripts/tournament/check_fixture.py +271 -0
- package/.claude/scripts/tournament/fixtures/CORPUS_SHA256.txt +10 -0
- package/.claude/scripts/tournament/fixtures/code-review.jsonl +10 -0
- package/.claude/scripts/tournament/fixtures/docs-writing.jsonl +10 -0
- package/.claude/scripts/tournament/fixtures/performance-triage.jsonl +10 -0
- package/.claude/scripts/tournament/fixtures/security-review.jsonl +10 -0
- package/.claude/scripts/tournament/fixtures/test-design.jsonl +10 -0
- package/.claude/scripts/tournament/judge.py +269 -0
- package/.claude/scripts/tournament/loader.py +262 -0
- package/.claude/scripts/tournament/regen_corpus_sha.py +93 -0
- package/.claude/scripts/tournament/reporter.py +328 -0
- package/.claude/scripts/tournament/runner.py +707 -0
- package/.claude/scripts/tournament/scorer.py +118 -0
- package/.claude/scripts/tournament/tests/__init__.py +0 -0
- package/.claude/scripts/tournament/tests/_fake_dispatcher.py +233 -0
- package/.claude/scripts/tournament/tests/golden/strict_report_seed42.jsonl +6 -0
- package/.claude/scripts/tournament/tests/test_fixture_envelope.py +106 -0
- package/.claude/scripts/tournament/tests/test_fixture_security.py +227 -0
- package/.claude/scripts/tournament/tests/test_judge.py +299 -0
- package/.claude/scripts/tournament/tests/test_loader.py +223 -0
- package/.claude/scripts/tournament/tests/test_model_id_parity.py +136 -0
- package/.claude/scripts/tournament/tests/test_reporter.py +450 -0
- package/.claude/scripts/tournament/tests/test_reporter_golden.py +182 -0
- package/.claude/scripts/tournament/tests/test_runner.py +313 -0
- package/.claude/scripts/tournament/tests/test_runner_fail_open.py +204 -0
- package/.claude/scripts/tournament/tests/test_scorer.py +138 -0
- package/.claude/scripts/tournament/tests/test_tournament_e2e_smoke.py +147 -0
- package/.claude/scripts/tournament/tests/test_tournament_properties.py +181 -0
- package/.claude/scripts/trading-readonly-escape-hatch.sh +244 -0
- package/.claude/scripts/trading-readonly-guardrails.py +1136 -0
- package/.claude/scripts/translations-pairs.yaml +60 -0
- package/.claude/scripts/validate-findings.py +243 -0
- package/.claude/scripts/validate-governance.sh +1238 -0
- package/.claude/scripts/validate-skill-frontmatter.py +679 -0
- package/.claude/scripts/validate-spec-context.py +146 -0
- package/.claude/scripts/validate-squad-contract.py +318 -0
- package/.claude/scripts/validate_governance_fast.py +555 -0
- package/.claude/scripts/value-dashboard.py +851 -0
- package/.claude/scripts/verify-adr-118-rationale.py +285 -0
- package/.claude/scripts/verify-atlas-binding.py +331 -0
- package/.claude/scripts/verify-persona-coverage.py +531 -0
- package/.claude/scripts/verify-sprint3-invariants.sh +133 -0
- package/.claude/scripts/veto-check.py +218 -0
- package/.claude/security/README.md +200 -0
- package/.claude/security/sentinel-signers-registry.yaml +60 -0
- package/.claude/sentinel-signers.txt +24 -0
- package/.claude/settings.json +786 -0
- package/.claude/sidecars/c1-crypto/cryptography-mvp/README.md +89 -0
- package/.claude/sidecars/c1-crypto/cryptography-mvp/boundary_test.py +114 -0
- package/.claude/sidecars/c1-crypto/cryptography-mvp/install.sh +45 -0
- package/.claude/sidecars/c1-crypto/cryptography-mvp/manifest.json +52 -0
- package/.claude/sidecars/c1-crypto/cryptography-mvp/sidecar_code/cert_inspector.py +775 -0
- package/.claude/sidecars/c1-crypto/stdlib-ssl-mvp/boundary_test.py +318 -0
- package/.claude/sidecars/c1-crypto/stdlib-ssl-mvp/install.sh +57 -0
- package/.claude/sidecars/c1-crypto/stdlib-ssl-mvp/manifest.json +48 -0
- package/.claude/sidecars/c2-vector-memory/lightrag-mvp/README.md +88 -0
- package/.claude/sidecars/c2-vector-memory/lightrag-mvp/boundary_test.py +221 -0
- package/.claude/sidecars/c2-vector-memory/lightrag-mvp/install.sh +33 -0
- package/.claude/sidecars/c2-vector-memory/lightrag-mvp/manifest.json +59 -0
- package/.claude/sidecars/c5-dev-tools/hypothesis/boundary_test.py +142 -0
- package/.claude/sidecars/c5-dev-tools/hypothesis/install.sh +46 -0
- package/.claude/sidecars/c5-dev-tools/hypothesis/manifest.json +52 -0
- package/.claude/sidecars/c5-dev-tools/hypothesis/tests/__init__.py +0 -0
- package/.claude/sidecars/c5-dev-tools/hypothesis/tests/test_audit_emit_known_actions_property.py +123 -0
- package/.claude/sidecars/c5-dev-tools/hypothesis/tests/test_canonical_guard_symmetry_property.py +67 -0
- package/.claude/sidecars/c5-dev-tools/hypothesis/tests/test_payload_roundtrip_property.py +73 -0
- package/.claude/sidecars/c5-dev-tools/hypothesis/tests/test_redact_idempotence_property.py +68 -0
- package/.claude/skill-governance-grandfather.yaml +39 -0
- package/.claude/skill-patch-signers.txt +19 -0
- package/.claude/skills/core/agent-architect/SKILL.md +126 -0
- package/.claude/skills/core/ai-llm-orchestration/SKILL.md +620 -0
- package/.claude/skills/core/ai-llm-orchestration/SKILL.md.shadow.md +121 -0
- package/.claude/skills/core/architecture-decisions/SKILL.md +364 -0
- package/.claude/skills/core/architecture-decisions/benchmarks/architecture-decisions.yaml +257 -0
- package/.claude/skills/core/ceo-orchestration/SKILL-frontend.md +117 -0
- package/.claude/skills/core/ceo-orchestration/SKILL.md +700 -0
- package/.claude/skills/core/chaos-and-resilience/SKILL.md +568 -0
- package/.claude/skills/core/chaos-and-resilience/SKILL.md.shadow.md +553 -0
- package/.claude/skills/core/code-intelligence-lsp/SKILL.md +375 -0
- package/.claude/skills/core/code-review-checklist/SKILL.md +675 -0
- package/.claude/skills/core/code-review-checklist/SKILL.md.shadow.md +337 -0
- package/.claude/skills/core/code-review-checklist/benchmarks/code-review-checklist.yaml +444 -0
- package/.claude/skills/core/codebase-onboarding/SKILL.md +515 -0
- package/.claude/skills/core/compliance-lgpd/SKILL-frontend.md +513 -0
- package/.claude/skills/core/compliance-lgpd/SKILL.md +817 -0
- package/.claude/skills/core/consent-lifecycle/SKILL.md +149 -0
- package/.claude/skills/core/cookbook-advisor/SKILL.md +191 -0
- package/.claude/skills/core/coverage-audit/SKILL.md +116 -0
- package/.claude/skills/core/cross-llm-pair-review/SKILL.md +212 -0
- package/.claude/skills/core/data-schema-design/SKILL.md +933 -0
- package/.claude/skills/core/devops-ci-cd/SKILL.md +659 -0
- package/.claude/skills/core/dpo-reporting/SKILL.md +187 -0
- package/.claude/skills/core/evidence-based-qa/SKILL.md +565 -0
- package/.claude/skills/core/git-workflow-discipline/SKILL.md +600 -0
- package/.claude/skills/core/growth-and-launch/SKILL-frontend.md +800 -0
- package/.claude/skills/core/growth-and-launch/SKILL.md +903 -0
- package/.claude/skills/core/help-me/SKILL.md +177 -0
- package/.claude/skills/core/help-me/tests/test_help_me_skill.py +490 -0
- package/.claude/skills/core/identity-and-trust-architecture/SKILL.md +1062 -0
- package/.claude/skills/core/incident-management/SKILL.md +421 -0
- package/.claude/skills/core/incremental-refactoring/SKILL-frontend.md +210 -0
- package/.claude/skills/core/incremental-refactoring/SKILL.md +226 -0
- package/.claude/skills/core/llm-routing-and-finops/SKILL.md +828 -0
- package/.claude/skills/core/mcp-server-authoring/SKILL.md +685 -0
- package/.claude/skills/core/minimal-change-discipline/SKILL.md +545 -0
- package/.claude/skills/core/monetization-and-billing/SKILL-frontend.md +562 -0
- package/.claude/skills/core/monetization-and-billing/SKILL.md +585 -0
- package/.claude/skills/core/observability-and-ops/SKILL-frontend.md +290 -0
- package/.claude/skills/core/observability-and-ops/SKILL.md +612 -0
- package/.claude/skills/core/observability-and-ops/SKILL.md.shadow.md +324 -0
- package/.claude/skills/core/parallelization-by-default/SKILL.md +176 -0
- package/.claude/skills/core/parallelization-by-default/tests/test_parallelization_skill.py +490 -0
- package/.claude/skills/core/performance-engineering/SKILL.md +219 -0
- package/.claude/skills/core/performance-engineering/SKILL.md.shadow.md +204 -0
- package/.claude/skills/core/pii-data-flow/SKILL.md +166 -0
- package/.claude/skills/core/pre-plan-brainstorm/CHECKLIST.md +87 -0
- package/.claude/skills/core/pre-plan-brainstorm/SKILL.md +186 -0
- package/.claude/skills/core/product-conversion-readiness/SKILL-frontend.md +668 -0
- package/.claude/skills/core/product-conversion-readiness/SKILL.md +941 -0
- package/.claude/skills/core/public-api-design/SKILL.md +603 -0
- package/.claude/skills/core/public-api-design/benchmarks/public-api-design.yaml +261 -0
- package/.claude/skills/core/receiving-review/SKILL.md +131 -0
- package/.claude/skills/core/receiving-review/benchmarks/receiving-review.yaml +254 -0
- package/.claude/skills/core/requirement-quality-checklist/SKILL.md +97 -0
- package/.claude/skills/core/security-and-auth/SKILL.md +868 -0
- package/.claude/skills/core/security-and-auth/SKILL.md.shadow.md +500 -0
- package/.claude/skills/core/security-and-auth/benchmarks/owasp-basics.yaml +491 -0
- package/.claude/skills/core/security-and-auth/benchmarks/owasp-llm-top-10.yaml +769 -0
- package/.claude/skills/core/spec-clarify/SKILL.md +120 -0
- package/.claude/skills/core/state-machines-and-invariants/SKILL.md +288 -0
- package/.claude/skills/core/technical-writing/SKILL.md +432 -0
- package/.claude/skills/core/terse-mode/SKILL.md +80 -0
- package/.claude/skills/core/terse-mode/SKILL.md.shadow.md +65 -0
- package/.claude/skills/core/testing-strategy/SKILL.md +1026 -0
- package/.claude/skills/core/testing-strategy/SKILL.md.shadow.md +983 -0
- package/.claude/skills/domains/academic-humanities/examples/PLAN-EXAMPLE-ACH.md +126 -0
- package/.claude/skills/domains/academic-humanities/pitfalls.yaml +68 -0
- package/.claude/skills/domains/academic-humanities/skills/anthropologist/SKILL.md +394 -0
- package/.claude/skills/domains/academic-humanities/skills/geographer/SKILL.md +453 -0
- package/.claude/skills/domains/academic-humanities/skills/historian/SKILL.md +255 -0
- package/.claude/skills/domains/academic-humanities/skills/narratologist/SKILL.md +398 -0
- package/.claude/skills/domains/academic-humanities/skills/psychologist/SKILL.md +271 -0
- package/.claude/skills/domains/academic-humanities/task-chains.yaml +125 -0
- package/.claude/skills/domains/academic-humanities/team-personas.md +278 -0
- package/.claude/skills/domains/business-support/examples/PLAN-EXAMPLE-BSP.md +115 -0
- package/.claude/skills/domains/business-support/pitfalls.yaml +69 -0
- package/.claude/skills/domains/business-support/skills/analytics-reporter/SKILL.md +339 -0
- package/.claude/skills/domains/business-support/skills/executive-summary/SKILL.md +268 -0
- package/.claude/skills/domains/business-support/skills/finance-tracker/SKILL.md +321 -0
- package/.claude/skills/domains/business-support/skills/support-responder/SKILL.md +341 -0
- package/.claude/skills/domains/business-support/task-chains.yaml +118 -0
- package/.claude/skills/domains/business-support/team-personas.md +259 -0
- package/.claude/skills/domains/civil-engineering/skills/civil-engineer/SKILL.md +275 -0
- package/.claude/skills/domains/community/NOTICE.md +83 -0
- package/.claude/skills/domains/community/skills/advanced-evaluation/SKILL.md +463 -0
- package/.claude/skills/domains/community/skills/agent-evaluation/SKILL.md +400 -0
- package/.claude/skills/domains/community/skills/agentic-actions-auditor/SKILL.md +410 -0
- package/.claude/skills/domains/community/team-personas.md +41 -0
- package/.claude/skills/domains/devrel/examples/api-deprecation-comms.md +180 -0
- package/.claude/skills/domains/devrel/pitfalls.yaml +74 -0
- package/.claude/skills/domains/devrel/skills/developer-advocate/SKILL.md +382 -0
- package/.claude/skills/domains/devrel/task-chains.yaml +129 -0
- package/.claude/skills/domains/devrel/team-personas.md +260 -0
- package/.claude/skills/domains/edtech/examples/PLAN-EXAMPLE.md +89 -0
- package/.claude/skills/domains/edtech/pitfalls.yaml +98 -0
- package/.claude/skills/domains/edtech/skills/assessment-integrity/SKILL.md +208 -0
- package/.claude/skills/domains/edtech/skills/learning-analytics/SKILL.md +212 -0
- package/.claude/skills/domains/edtech/skills/student-data-privacy/SKILL.md +197 -0
- package/.claude/skills/domains/edtech/skills/study-abroad-advisory/SKILL.md +582 -0
- package/.claude/skills/domains/edtech/task-chains.yaml +122 -0
- package/.claude/skills/domains/edtech/team-personas.md +252 -0
- package/.claude/skills/domains/embedded/skills/embedded-firmware/SKILL.md +471 -0
- package/.claude/skills/domains/finance-accounting/examples/new-subscription-revenue.md +135 -0
- package/.claude/skills/domains/finance-accounting/pitfalls.yaml +74 -0
- package/.claude/skills/domains/finance-accounting/skills/bookkeeper-controller/SKILL.md +427 -0
- package/.claude/skills/domains/finance-accounting/skills/financial-analyst/SKILL.md +348 -0
- package/.claude/skills/domains/finance-accounting/skills/fpa-analyst/SKILL.md +366 -0
- package/.claude/skills/domains/finance-accounting/skills/tax-strategist/SKILL.md +358 -0
- package/.claude/skills/domains/finance-accounting/task-chains.yaml +90 -0
- package/.claude/skills/domains/finance-accounting/team-personas.md +281 -0
- package/.claude/skills/domains/fintech/ORG_CHART.md +167 -0
- package/.claude/skills/domains/fintech/commands/audit-ai.md +124 -0
- package/.claude/skills/domains/fintech/commands/deploy.md +15 -0
- package/.claude/skills/domains/fintech/commands/status.md +13 -0
- package/.claude/skills/domains/fintech/frontend-team-personas.md +503 -0
- package/.claude/skills/domains/fintech/pitfalls.yaml +58 -0
- package/.claude/skills/domains/fintech/scripts/check-pitfall-regression.sh +80 -0
- package/.claude/skills/domains/fintech/scripts/check-type-sync.sh +110 -0
- package/.claude/skills/domains/fintech/skills/blockchain-security-audit/SKILL.md +492 -0
- package/.claude/skills/domains/fintech/skills/equity-research/SKILL.md +459 -0
- package/.claude/skills/domains/fintech/skills/exchange-api-integration/SKILL.md +315 -0
- package/.claude/skills/domains/fintech/skills/exchange-onboarding-playbook/SKILL.md +527 -0
- package/.claude/skills/domains/fintech/skills/financial-correctness-and-math/SKILL-frontend.md +308 -0
- package/.claude/skills/domains/fintech/skills/financial-correctness-and-math/SKILL.md +340 -0
- package/.claude/skills/domains/fintech/skills/financial-display/SKILL.md +193 -0
- package/.claude/skills/domains/fintech/skills/frontend-data-layer/SKILL.md +206 -0
- package/.claude/skills/domains/fintech/skills/frontend-patterns/SKILL.md +387 -0
- package/.claude/skills/domains/fintech/skills/prediction-markets/SKILL.md +139 -0
- package/.claude/skills/domains/fintech/skills/real-time-market-systems/SKILL.md +315 -0
- package/.claude/skills/domains/fintech/skills/solidity-smart-contracts/SKILL.md +356 -0
- package/.claude/skills/domains/fintech/skills/trading-execution/SKILL.md +126 -0
- package/.claude/skills/domains/fintech/task-chains.yaml +46 -0
- package/.claude/skills/domains/fintech/team-personas.md +773 -0
- package/.claude/skills/domains/government/examples/PLAN-EXAMPLE.md +158 -0
- package/.claude/skills/domains/government/pitfalls.yaml +114 -0
- package/.claude/skills/domains/government/skills/accessibility-section-508/SKILL.md +183 -0
- package/.claude/skills/domains/government/skills/digital-presales/SKILL.md +359 -0
- package/.claude/skills/domains/government/skills/foia-and-records/SKILL.md +211 -0
- package/.claude/skills/domains/government/skills/public-procurement/SKILL.md +264 -0
- package/.claude/skills/domains/government/task-chains.yaml +88 -0
- package/.claude/skills/domains/government/team-personas.md +296 -0
- package/.claude/skills/domains/healthcare/examples/patient-portal-symptom-checker.md +130 -0
- package/.claude/skills/domains/healthcare/pitfalls.yaml +74 -0
- package/.claude/skills/domains/healthcare/skills/healthcare-customer-service/SKILL.md +369 -0
- package/.claude/skills/domains/healthcare/skills/marketing-compliance/SKILL.md +367 -0
- package/.claude/skills/domains/healthcare/task-chains.yaml +87 -0
- package/.claude/skills/domains/healthcare/team-personas.md +273 -0
- package/.claude/skills/domains/hospitality/skills/guest-services/SKILL.md +417 -0
- package/.claude/skills/domains/hr/examples/attrition-model-launch.md +128 -0
- package/.claude/skills/domains/hr/pitfalls.yaml +74 -0
- package/.claude/skills/domains/hr/skills/hr-onboarding/SKILL.md +435 -0
- package/.claude/skills/domains/hr/skills/recruitment-specialist/SKILL.md +400 -0
- package/.claude/skills/domains/hr/task-chains.yaml +91 -0
- package/.claude/skills/domains/hr/team-personas.md +251 -0
- package/.claude/skills/domains/i18n-business/examples/PLAN-EXAMPLE-I18N.md +115 -0
- package/.claude/skills/domains/i18n-business/pitfalls.yaml +68 -0
- package/.claude/skills/domains/i18n-business/skills/cultural-intelligence/SKILL.md +448 -0
- package/.claude/skills/domains/i18n-business/skills/french-consulting/SKILL.md +347 -0
- package/.claude/skills/domains/i18n-business/skills/korean-business/SKILL.md +360 -0
- package/.claude/skills/domains/i18n-business/skills/language-translator/SKILL.md +389 -0
- package/.claude/skills/domains/i18n-business/task-chains.yaml +117 -0
- package/.claude/skills/domains/i18n-business/team-personas.md +258 -0
- package/.claude/skills/domains/identity-systems/examples/passkey-rollout.md +137 -0
- package/.claude/skills/domains/identity-systems/pitfalls.yaml +74 -0
- package/.claude/skills/domains/identity-systems/skills/identity-graph-operator/SKILL.md +353 -0
- package/.claude/skills/domains/identity-systems/task-chains.yaml +90 -0
- package/.claude/skills/domains/identity-systems/team-personas.md +233 -0
- package/.claude/skills/domains/legal/examples/client-intake-pii-flow.md +177 -0
- package/.claude/skills/domains/legal/pitfalls.yaml +77 -0
- package/.claude/skills/domains/legal/skills/client-intake/SKILL.md +407 -0
- package/.claude/skills/domains/legal/skills/document-review/SKILL.md +373 -0
- package/.claude/skills/domains/legal/skills/legal-billing/SKILL.md +331 -0
- package/.claude/skills/domains/legal/task-chains.yaml +131 -0
- package/.claude/skills/domains/legal/team-personas.md +260 -0
- package/.claude/skills/domains/lgpd-heavy-saas/examples/PLAN-EXAMPLE.md +120 -0
- package/.claude/skills/domains/lgpd-heavy-saas/pitfalls.yaml +90 -0
- package/.claude/skills/domains/lgpd-heavy-saas/task-chains.yaml +83 -0
- package/.claude/skills/domains/lgpd-heavy-saas/team-personas.md +159 -0
- package/.claude/skills/domains/marketing-global/skills/agentic-search-optimizer/SKILL.md +391 -0
- package/.claude/skills/domains/marketing-global/skills/ai-citation-strategist/SKILL.md +343 -0
- package/.claude/skills/domains/marketing-global/skills/app-store-optimizer/SKILL.md +495 -0
- package/.claude/skills/domains/marketing-global/skills/book-co-author/SKILL.md +220 -0
- package/.claude/skills/domains/marketing-global/skills/carousel-growth-engine/SKILL.md +393 -0
- package/.claude/skills/domains/marketing-global/skills/content-creator/SKILL.md +416 -0
- package/.claude/skills/domains/marketing-global/skills/growth-hacker/SKILL.md +495 -0
- package/.claude/skills/domains/marketing-global/skills/instagram-curator/SKILL.md +419 -0
- package/.claude/skills/domains/marketing-global/skills/linkedin-content-creator/SKILL.md +291 -0
- package/.claude/skills/domains/marketing-global/skills/podcast-strategist/SKILL.md +408 -0
- package/.claude/skills/domains/marketing-global/skills/reddit-community-builder/SKILL.md +295 -0
- package/.claude/skills/domains/marketing-global/skills/seo-specialist/SKILL.md +352 -0
- package/.claude/skills/domains/marketing-global/skills/social-media-strategist/SKILL.md +349 -0
- package/.claude/skills/domains/marketing-global/skills/tiktok-strategist/SKILL.md +329 -0
- package/.claude/skills/domains/marketing-global/skills/twitter-engager/SKILL.md +382 -0
- package/.claude/skills/domains/marketing-global/skills/video-optimization-specialist/SKILL.md +386 -0
- package/.claude/skills/domains/mobile/examples/PLAN-EXAMPLE-MOB.md +129 -0
- package/.claude/skills/domains/mobile/pitfalls.yaml +69 -0
- package/.claude/skills/domains/mobile/skills/mobile-app-builder/SKILL.md +446 -0
- package/.claude/skills/domains/mobile/task-chains.yaml +126 -0
- package/.claude/skills/domains/mobile/team-personas.md +292 -0
- package/.claude/skills/domains/paid-media/examples/new-channel-launch.md +122 -0
- package/.claude/skills/domains/paid-media/pitfalls.yaml +79 -0
- package/.claude/skills/domains/paid-media/skills/auditor/SKILL.md +362 -0
- package/.claude/skills/domains/paid-media/skills/creative-strategist/SKILL.md +457 -0
- package/.claude/skills/domains/paid-media/skills/paid-social-strategist/SKILL.md +493 -0
- package/.claude/skills/domains/paid-media/skills/ppc-strategist/SKILL.md +450 -0
- package/.claude/skills/domains/paid-media/skills/programmatic-buyer/SKILL.md +396 -0
- package/.claude/skills/domains/paid-media/skills/search-query-analyst/SKILL.md +336 -0
- package/.claude/skills/domains/paid-media/skills/tracking-specialist/SKILL.md +457 -0
- package/.claude/skills/domains/paid-media/task-chains.yaml +121 -0
- package/.claude/skills/domains/paid-media/team-personas.md +251 -0
- package/.claude/skills/domains/project-management/examples/PLAN-EXAMPLE-PMG.md +117 -0
- package/.claude/skills/domains/project-management/pitfalls.yaml +68 -0
- package/.claude/skills/domains/project-management/skills/experiment-tracker/SKILL.md +293 -0
- package/.claude/skills/domains/project-management/skills/project-shepherd/SKILL.md +312 -0
- package/.claude/skills/domains/project-management/skills/studio-operations/SKILL.md +333 -0
- package/.claude/skills/domains/project-management/skills/studio-producer/SKILL.md +329 -0
- package/.claude/skills/domains/project-management/task-chains.yaml +118 -0
- package/.claude/skills/domains/project-management/team-personas.md +264 -0
- package/.claude/skills/domains/real-estate-finance/examples/PLAN-EXAMPLE-REF.md +129 -0
- package/.claude/skills/domains/real-estate-finance/pitfalls.yaml +68 -0
- package/.claude/skills/domains/real-estate-finance/skills/buyer-seller-agent/SKILL.md +410 -0
- package/.claude/skills/domains/real-estate-finance/skills/loan-officer-assistant/SKILL.md +415 -0
- package/.claude/skills/domains/real-estate-finance/task-chains.yaml +123 -0
- package/.claude/skills/domains/real-estate-finance/team-personas.md +287 -0
- package/.claude/skills/domains/retail/skills/customer-returns/SKILL.md +363 -0
- package/.claude/skills/domains/saas-platforms/examples/enterprise-tier-isolation.md +147 -0
- package/.claude/skills/domains/saas-platforms/pitfalls.yaml +74 -0
- package/.claude/skills/domains/saas-platforms/skills/cms-developer/SKILL.md +377 -0
- package/.claude/skills/domains/saas-platforms/skills/filament-specialist/SKILL.md +316 -0
- package/.claude/skills/domains/saas-platforms/skills/salesforce-architect/SKILL.md +369 -0
- package/.claude/skills/domains/saas-platforms/task-chains.yaml +90 -0
- package/.claude/skills/domains/saas-platforms/team-personas.md +283 -0
- package/.claude/skills/domains/sales/examples/qbr-revenue-forecast.md +158 -0
- package/.claude/skills/domains/sales/pitfalls.yaml +73 -0
- package/.claude/skills/domains/sales/skills/account-strategist/SKILL.md +408 -0
- package/.claude/skills/domains/sales/skills/deal-strategist/SKILL.md +292 -0
- package/.claude/skills/domains/sales/skills/discovery-coach/SKILL.md +257 -0
- package/.claude/skills/domains/sales/skills/outbound-strategist/SKILL.md +262 -0
- package/.claude/skills/domains/sales/skills/pipeline-analyst/SKILL.md +317 -0
- package/.claude/skills/domains/sales/skills/proposal-strategist/SKILL.md +288 -0
- package/.claude/skills/domains/sales/skills/sales-coach/SKILL.md +306 -0
- package/.claude/skills/domains/sales/skills/sales-engineer/SKILL.md +272 -0
- package/.claude/skills/domains/sales/skills/sales-outreach/SKILL.md +338 -0
- package/.claude/skills/domains/sales/task-chains.yaml +123 -0
- package/.claude/skills/domains/sales/team-personas.md +249 -0
- package/.claude/skills/domains/supply-chain/skills/supply-chain-strategist/SKILL.md +340 -0
- package/.claude/skills/domains/trading-hft/examples/PLAN-EXAMPLE.md +145 -0
- package/.claude/skills/domains/trading-hft/pitfalls.yaml +99 -0
- package/.claude/skills/domains/trading-hft/skills/kill-switches/SKILL.md +128 -0
- package/.claude/skills/domains/trading-hft/skills/latency-budgets/SKILL.md +117 -0
- package/.claude/skills/domains/trading-hft/skills/order-routing/SKILL.md +97 -0
- package/.claude/skills/domains/trading-hft/task-chains.yaml +97 -0
- package/.claude/skills/domains/trading-hft/team-personas.md +155 -0
- package/.claude/skills/domains/training-l-and-d/skills/corporate-training-designer/SKILL.md +268 -0
- package/.claude/skills/domains/voice-ai/skills/voice-ai-integration/SKILL.md +405 -0
- package/.claude/skills/frontend/NOTICE.md +80 -0
- package/.claude/skills/frontend/accessibility-and-wcag/SKILL.md +395 -0
- package/.claude/skills/frontend/accessibility-and-wcag/SKILL.md.shadow.md +181 -0
- package/.claude/skills/frontend/accessibility-and-wcag/benchmarks/accessibility-and-wcag.yaml +420 -0
- package/.claude/skills/frontend/accessibility-and-wcag/reference/charts-accessibility.yaml +357 -0
- package/.claude/skills/frontend/code-quality-and-typescript/SKILL.md +167 -0
- package/.claude/skills/frontend/design-system-and-components/SKILL.md +155 -0
- package/.claude/skills/frontend/design-system-and-components/SKILL.md.shadow.md +138 -0
- package/.claude/skills/frontend/design-system-and-components/reference/fonts.yaml +811 -0
- package/.claude/skills/frontend/design-system-and-components/reference/palettes.yaml +3066 -0
- package/.claude/skills/frontend/frontend-accessibility/SKILL.md +213 -0
- package/.claude/skills/frontend/frontend-data-layer/SKILL.md +310 -0
- package/.claude/skills/frontend/frontend-patterns/SKILL.md +771 -0
- package/.claude/skills/frontend/frontend-performance-optimization/SKILL.md +228 -0
- package/.claude/skills/frontend/frontend-performance-optimization/SKILL.md.shadow.md +213 -0
- package/.claude/skills/frontend/ux-and-user-journeys/SKILL.md +153 -0
- package/.claude/skills/frontend/ux-and-user-journeys/SKILL.md.shadow.md +138 -0
- package/.claude/skills/frontend/ux-and-user-journeys/reference/guidelines.yaml +997 -0
- package/.claude/squad-revocations.jsonl +5 -0
- package/.claude/task-chains.yaml +151 -0
- package/.claude/team.md +825 -0
- package/.claude/templates/squad-bundle/README.md +208 -0
- package/.claude/templates/squad-bundle/conftest.py +27 -0
- package/.claude/templates/squad-bundle/examples/template-example.md.template +94 -0
- package/.claude/templates/squad-bundle/pitfalls.yaml.template +88 -0
- package/.claude/templates/squad-bundle/task-chains.yaml.template +92 -0
- package/.claude/templates/squad-bundle/team-personas.md.template +161 -0
- package/.claude/trust/README.md +89 -0
- package/.claude/trust/owner.asc +11 -0
- package/.claude/workflows/README.md +124 -0
- package/.claude/workflows/audit-fanout.js +204 -0
- package/.claude/workflows/eval-baseline-n20.js +330 -0
- package/.claude/workflows/nightly-hygiene.js +176 -0
- package/LICENSE +21 -0
- package/PROTOCOL.md +597 -0
- package/README.md +167 -0
- package/SPEC/v1/README.md +181 -0
- package/SPEC/v1/adapters.schema.md +272 -0
- package/SPEC/v1/audit-log.schema.md +1514 -0
- package/SPEC/v1/audit-query.schema.md +152 -0
- package/SPEC/v1/benchmarks.schema.md +166 -0
- package/SPEC/v1/claude-sdk-compat.md +123 -0
- package/SPEC/v1/debate.schema.md +35 -0
- package/SPEC/v1/hook-io.schema.md +94 -0
- package/SPEC/v1/install-cli.md +234 -0
- package/SPEC/v1/judge-payload.schema.md +98 -0
- package/SPEC/v1/live-adapters-policy.schema.md +118 -0
- package/SPEC/v1/mcp-server.schema.md +558 -0
- package/SPEC/v1/memory-shared.schema.md +365 -0
- package/SPEC/v1/normalized_envelope.schema.md +183 -0
- package/SPEC/v1/npm-shim.md +95 -0
- package/SPEC/v1/plan.schema.md +34 -0
- package/SPEC/v1/policy-dsl.schema.md +466 -0
- package/SPEC/v1/predict-budget.schema.md +289 -0
- package/SPEC/v1/rag-sidecar.schema.md +222 -0
- package/SPEC/v1/red-team-corpus.schema.md +186 -0
- package/SPEC/v1/replay.schema.md +272 -0
- package/SPEC/v1/scratchpad.schema.md +172 -0
- package/SPEC/v1/sentinel-format.schema.md +306 -0
- package/SPEC/v1/session-graph.schema.md +236 -0
- package/SPEC/v1/skill-frontmatter.schema.md +83 -0
- package/SPEC/v1/skill-index.schema.md +197 -0
- package/SPEC/v1/skill-proposals.schema.md +175 -0
- package/SPEC/v1/soc2-control-map.schema.md +797 -0
- package/SPEC/v1/squad-manifest.schema.md +157 -0
- package/SPEC/v1/state-stores.schema.md +146 -0
- package/SPEC/v1/tier-policy.schema.md +264 -0
- package/SPEC/v1/tournament-report.schema.md +156 -0
- package/VERSION +1 -0
- package/bin/ceo-orch-init.js +55 -0
- package/package.json +42 -0
- package/scripts/_framework_manifest_set.sh +237 -0
- package/scripts/_hash_lib.sh +92 -0
- package/scripts/build-plugin.py +351 -0
- package/scripts/discover_foreign_context.py +151 -0
- package/scripts/install-accelerators.sh +166 -0
- package/scripts/install-npm.sh +254 -0
- package/scripts/install.sh +1932 -0
- package/scripts/local/OWNER-CEREMONY-PLAN-094-WAVE-A.sh +648 -0
- package/scripts/local/OWNER-CEREMONY-S82-V1120.sh +169 -0
- package/scripts/local/plan-093-apply-kernel-edits.py +496 -0
- package/scripts/local/plan-093-execute-ceremony.sh +118 -0
- package/scripts/local/plan-093-kernel-override-restart.sh +115 -0
- package/scripts/local/plan-093-ship-v1.26.0.sh +226 -0
- package/scripts/local/plan-094-apply-wave-a-c-e.py +398 -0
- package/scripts/local/smoke-install-parity.sh +168 -0
- package/scripts/local/trading-readonly-escape-hatch.sh +244 -0
- package/scripts/measure-repo-size.sh +98 -0
- package/scripts/npm-rebuild.sh +172 -0
- package/scripts/publish-plugin.sh +144 -0
- package/scripts/tests/smoke-install.sh +260 -0
- package/scripts/tests/test-install-sandbox-merge.sh +137 -0
- package/scripts/tests/test_install_baseline_manifest.sh +392 -0
- package/scripts/uninstall.sh +282 -0
- package/scripts/upgrade.sh +1260 -0
- package/templates/.claude/tier-policy.json +35 -0
- package/templates/.claude/tier-policy.json.sigchain +1 -0
- package/templates/.env.example +134 -0
- package/templates/.github/CODEOWNERS.template +33 -0
- package/templates/.github/workflows/benchmarks.yml.template +145 -0
- package/templates/.github/workflows/validate.yml.template +226 -0
- package/templates/.mcp.json +13 -0
- package/templates/CLAUDE.md +125 -0
- package/templates/MEMORY.md +36 -0
- package/templates/README.md +46 -0
- package/templates/compaction.md +130 -0
- package/templates/docs/BRANCH-PROTECTION.md +203 -0
- package/templates/docs/rotation-log.md +18 -0
- package/templates/oidc-proxy/README.md +141 -0
- package/templates/oidc-proxy/broker.config.example.json +29 -0
- package/templates/oidc-proxy/oidc_key_broker.py +361 -0
- package/templates/oidc-proxy/tests/test_oidc_key_broker.py +361 -0
- package/templates/scripts/statusline-ceo.py +597 -0
- package/templates/settings/settings.base.json +708 -0
- package/templates/settings/settings.stack.node.json +19 -0
- package/templates/settings/settings.stack.otel.json +25 -0
- package/templates/settings/settings.stack.sandbox.json +57 -0
- package/templates/settings/settings.user.json +265 -0
- package/templates/team-personas-reference.md +269 -0
|
@@ -0,0 +1,2696 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Governance Hook: named agents MUST include ## SKILL CONTENT.
|
|
3
|
+
|
|
4
|
+
Registered in `.claude/settings.json` under `hooks.PreToolUse.Agent`.
|
|
5
|
+
Runs via the `_python-hook.sh` shim (A.4) for Python version resolution.
|
|
6
|
+
|
|
7
|
+
Port of `.claude/hooks/check-agent-spawn.sh` with:
|
|
8
|
+
- stdlib-only (no jq dependency)
|
|
9
|
+
- unit-testable (logic is in `decide()`, not tangled with stdin/stdout)
|
|
10
|
+
- fail-open on any internal error (never blocks the user on infrastructure bug)
|
|
11
|
+
|
|
12
|
+
## Two detection strategies
|
|
13
|
+
|
|
14
|
+
1. The agent's `description` field contains a team member name, extracted
|
|
15
|
+
dynamically from team.md / frontend-team.md / domains/*/team-personas.md.
|
|
16
|
+
2. The prompt contains a persona header (`PERSONA:`, `## AGENT PROFILE`,
|
|
17
|
+
`## PERSONA`) — self-describing spawn.
|
|
18
|
+
|
|
19
|
+
If either strategy matches, the prompt MUST contain `## SKILL CONTENT`.
|
|
20
|
+
Without it, the spawn is blocked as a generic agent wearing a name tag.
|
|
21
|
+
|
|
22
|
+
## Output contract
|
|
23
|
+
|
|
24
|
+
Writes a single-line JSON decision to stdout:
|
|
25
|
+
|
|
26
|
+
{"decision":"allow"}
|
|
27
|
+
{"decision":"block","reason":"GOVERNANCE: ..."}
|
|
28
|
+
|
|
29
|
+
Exit code is 0 in both cases — Claude Code reads the decision from stdout,
|
|
30
|
+
not the exit code. Internal errors (bad stdin, missing team files, etc.)
|
|
31
|
+
are logged to stderr and the spawn is allowed (fail-open).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import hashlib
|
|
37
|
+
import json
|
|
38
|
+
import os
|
|
39
|
+
import re
|
|
40
|
+
import sys
|
|
41
|
+
import unicodedata
|
|
42
|
+
from dataclasses import dataclass
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
45
|
+
|
|
46
|
+
# Make the _lib package importable — hooks live in .claude/hooks/ and
|
|
47
|
+
# _lib is a sibling package.
|
|
48
|
+
_HOOKS_DIR = Path(__file__).resolve().parent
|
|
49
|
+
if str(_HOOKS_DIR) not in sys.path:
|
|
50
|
+
sys.path.insert(0, str(_HOOKS_DIR))
|
|
51
|
+
|
|
52
|
+
from _lib import contract as _contract # noqa: E402
|
|
53
|
+
from _lib.adapters import claude as _claude_adapter # noqa: E402
|
|
54
|
+
from _lib import team as _team # noqa: E402
|
|
55
|
+
from _lib import redact as _redact # noqa: E402 (PLAN-020 Phase 2: skill-ref scan)
|
|
56
|
+
|
|
57
|
+
# PLAN-106 Wave G.1 — sub-agent N-of-M aggregator with partial-drop emit.
|
|
58
|
+
# Re-exported below as `aggregate_subagent_findings` so coordinator-side
|
|
59
|
+
# callers (swarm coordinator, future debate consensus aggregator) can
|
|
60
|
+
# `from check_agent_spawn import aggregate_subagent_findings` without
|
|
61
|
+
# poking into `_lib`. Closes AC10 grep contract.
|
|
62
|
+
from _lib import subagent_dispatch as _subagent_dispatch # noqa: E402
|
|
63
|
+
|
|
64
|
+
# PLAN-045 Wave 1 P0-03 — VETO floor runtime validator.
|
|
65
|
+
try:
|
|
66
|
+
from _lib import agent_frontmatter as _agent_frontmatter
|
|
67
|
+
except Exception: # pragma: no cover
|
|
68
|
+
_agent_frontmatter = None # type: ignore[assignment]
|
|
69
|
+
|
|
70
|
+
# Optional: emit veto_triggered to event stream v2 (fail-open)
|
|
71
|
+
try:
|
|
72
|
+
# PLAN-094 Wave E — lazy-import dispatch shim closes AC9c spawn-hook regression
|
|
73
|
+
from _lib import audit_emit_dispatch as _audit_emit # noqa: E402
|
|
74
|
+
_AUDIT_EMIT_AVAILABLE = True
|
|
75
|
+
except Exception: # pragma: no cover
|
|
76
|
+
_AUDIT_EMIT_AVAILABLE = False
|
|
77
|
+
|
|
78
|
+
# PLAN-092 Wave A.4 — cookbook-advisor pattern matcher (fail-open import).
|
|
79
|
+
try:
|
|
80
|
+
from _lib import cookbook_patterns as _cookbook_patterns # noqa: E402
|
|
81
|
+
_COOKBOOK_PATTERNS_AVAILABLE = True
|
|
82
|
+
except Exception: # pragma: no cover
|
|
83
|
+
_COOKBOOK_PATTERNS_AVAILABLE = False
|
|
84
|
+
|
|
85
|
+
# PLAN-112-FOLLOWUP-persona-routing-wire W1 — persona_routing matrix
|
|
86
|
+
# consult (fail-open import). The god-mode (persona × primitive) routing
|
|
87
|
+
# matrix declares AUTO-05 ("model_routing_advised") ENFORCING per
|
|
88
|
+
# ADR-118-AMEND-1, but check_agent_spawn never consulted it — the policy
|
|
89
|
+
# was dead at runtime. This wire makes the matrix observable/auditable.
|
|
90
|
+
# CONSULT + AUDIT ONLY — the BLOCK is DEFERRED (the hook payload exposes
|
|
91
|
+
# no requested-model signal; see plan §2/§3). NEVER read
|
|
92
|
+
# `_PRIMITIVE_DEFAULT_MODE` directly — get_mode() encapsulates the
|
|
93
|
+
# kill-switch demotion.
|
|
94
|
+
try:
|
|
95
|
+
from _lib import persona_routing as _persona_routing # noqa: E402
|
|
96
|
+
except Exception: # pragma: no cover - fail-open
|
|
97
|
+
_persona_routing = None # type: ignore[assignment]
|
|
98
|
+
|
|
99
|
+
# PLAN-113 WIRE-DEADMOD — SEC-P0-01 spec-context sanitizer (ADR-089).
|
|
100
|
+
# Sanitizes the ## SPEC CONTEXT block payload in spawn prompts before allow.
|
|
101
|
+
# ADVISORY telemetry only: sanitize result is emitted via emit_generic so
|
|
102
|
+
# sentinel violations / truncation are visible in the audit log. Never blocks.
|
|
103
|
+
# Fail-open: missing module → _SPEC_CTX_SANITIZER_AVAILABLE = False.
|
|
104
|
+
try:
|
|
105
|
+
from _lib import spec_context_sanitizer as _spec_ctx_sanitizer # noqa: E402
|
|
106
|
+
_SPEC_CTX_SANITIZER_AVAILABLE = True
|
|
107
|
+
except Exception: # pragma: no cover - fail-open
|
|
108
|
+
_spec_ctx_sanitizer = None # type: ignore[assignment]
|
|
109
|
+
_SPEC_CTX_SANITIZER_AVAILABLE = False
|
|
110
|
+
|
|
111
|
+
# PLAN-113 WIRE-DEADMOD — confidence_labels (PLAN-083 Wave 1.10).
|
|
112
|
+
# Classifies the spawn action type and emits a spawn_confidence_advisory event
|
|
113
|
+
# so that recommender / receipt formatter surfaces have a hook-level signal.
|
|
114
|
+
# ADVISORY only — never blocks. Fail-open.
|
|
115
|
+
try:
|
|
116
|
+
from _lib import confidence_labels as _confidence_labels # noqa: E402
|
|
117
|
+
_CONFIDENCE_LABELS_AVAILABLE = True
|
|
118
|
+
except Exception: # pragma: no cover - fail-open
|
|
119
|
+
_confidence_labels = None # type: ignore[assignment]
|
|
120
|
+
_CONFIDENCE_LABELS_AVAILABLE = False
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# PLAN-078 Wave 1 — Model routing telemetry (advisory-emit-only)
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Telemetry-only: hook contract (`_lib/contract.py:112-127`) supports only
|
|
127
|
+
# allow/block/systemMessage. NO `tool_input` mutation. This block exists to
|
|
128
|
+
# observe sub-agent spawn → recommended model deltas so the CEO can hand-edit
|
|
129
|
+
# `.claude/agents/<archetype>.md` frontmatter (manual remediation path). The
|
|
130
|
+
# hard-enforcement leg is the existing VETO-floor check at
|
|
131
|
+
# check_veto_floor_for_role() which runs BEFORE this advisory.
|
|
132
|
+
#
|
|
133
|
+
# Hot-path requirement: p95 ≤ 20ms. Achieved via:
|
|
134
|
+
# - module-level cache of task_route module + classify reference
|
|
135
|
+
# - module-level cache of frontmatter parser
|
|
136
|
+
# - frontmatter cache by-archetype (LRU dict, capped 64 entries) — agents
|
|
137
|
+
# dir is small + rarely changes mid-session, so cache invalidation is
|
|
138
|
+
# not required (next session re-imports module).
|
|
139
|
+
#
|
|
140
|
+
# Bypass: env var `CEO_MODEL_ROUTING=0` short-circuits to a no-op.
|
|
141
|
+
# Fail-open: any exception during advisory emit is swallowed (logged to
|
|
142
|
+
# breadcrumb if available) so the spawn is NEVER blocked by a routing bug.
|
|
143
|
+
|
|
144
|
+
# Lazy task_route module reference — populated on first call. Cached.
|
|
145
|
+
_TASK_ROUTE_MODULE: Optional[Any] = None
|
|
146
|
+
_TASK_ROUTE_LOAD_FAILED: bool = False
|
|
147
|
+
|
|
148
|
+
# In-memory cache: archetype -> (model: Optional[str], confidence: float).
|
|
149
|
+
# Cleared at module init only (process-scoped).
|
|
150
|
+
_FRONTMATTER_MODEL_CACHE: Dict[str, Tuple[Optional[str], float]] = {}
|
|
151
|
+
_FRONTMATTER_CACHE_MAX = 64
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _resolve_task_route():
|
|
155
|
+
"""Best-effort import of `.claude/scripts/task-route.py` as a module.
|
|
156
|
+
|
|
157
|
+
Returns the module on success, None on failure. Never raises.
|
|
158
|
+
Cached after first call (positive or negative).
|
|
159
|
+
|
|
160
|
+
Resolution order:
|
|
161
|
+
1. ``CLAUDE_PROJECT_DIR`` env var (test-friendly + adopter-respectful)
|
|
162
|
+
2. Walk up from `_HOOKS_DIR` looking for a `.claude/scripts/task-route.py`
|
|
163
|
+
"""
|
|
164
|
+
global _TASK_ROUTE_MODULE, _TASK_ROUTE_LOAD_FAILED
|
|
165
|
+
if _TASK_ROUTE_MODULE is not None or _TASK_ROUTE_LOAD_FAILED:
|
|
166
|
+
return _TASK_ROUTE_MODULE
|
|
167
|
+
try:
|
|
168
|
+
import importlib.util as _ilutil
|
|
169
|
+
candidates = []
|
|
170
|
+
env_root = os.environ.get("CLAUDE_PROJECT_DIR")
|
|
171
|
+
if env_root:
|
|
172
|
+
candidates.append(
|
|
173
|
+
Path(env_root) / ".claude" / "scripts" / "task-route.py"
|
|
174
|
+
)
|
|
175
|
+
# Walk up from _HOOKS_DIR (stable in normal install)
|
|
176
|
+
candidates.append(
|
|
177
|
+
_HOOKS_DIR.parent.parent / ".claude" / "scripts" / "task-route.py"
|
|
178
|
+
)
|
|
179
|
+
# Walk up from this file's location (helps when the hook itself is
|
|
180
|
+
# staged under .claude/plans/PLAN-NNN/staging/...; rare but useful
|
|
181
|
+
# for in-tree pytest runs that exercise the staged copy).
|
|
182
|
+
cur = Path(__file__).resolve().parent
|
|
183
|
+
for _ in range(8):
|
|
184
|
+
if cur == cur.parent:
|
|
185
|
+
break
|
|
186
|
+
cand = cur / ".claude" / "scripts" / "task-route.py"
|
|
187
|
+
if cand.is_file():
|
|
188
|
+
candidates.append(cand)
|
|
189
|
+
break
|
|
190
|
+
cur = cur.parent
|
|
191
|
+
tr_path = next((c for c in candidates if c.is_file()), None)
|
|
192
|
+
if tr_path is None:
|
|
193
|
+
_TASK_ROUTE_LOAD_FAILED = True
|
|
194
|
+
return None
|
|
195
|
+
spec = _ilutil.spec_from_file_location("task_route", tr_path)
|
|
196
|
+
if spec is None or spec.loader is None:
|
|
197
|
+
_TASK_ROUTE_LOAD_FAILED = True
|
|
198
|
+
return None
|
|
199
|
+
mod = _ilutil.module_from_spec(spec)
|
|
200
|
+
spec.loader.exec_module(mod)
|
|
201
|
+
_TASK_ROUTE_MODULE = mod
|
|
202
|
+
return mod
|
|
203
|
+
except Exception: # pragma: no cover - fail-open
|
|
204
|
+
_TASK_ROUTE_LOAD_FAILED = True
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _read_archetype_model_frontmatter(
|
|
209
|
+
archetype: str,
|
|
210
|
+
agents_dir: Path,
|
|
211
|
+
) -> Tuple[Optional[str], float]:
|
|
212
|
+
"""Read `model:` field from `<agents_dir>/<archetype>.md`.
|
|
213
|
+
|
|
214
|
+
Returns (model, confidence). When frontmatter present + model field
|
|
215
|
+
set, confidence is 1.0 (authoritative). When file missing or no
|
|
216
|
+
model field, confidence is 0.0 (caller will fall back to classify).
|
|
217
|
+
Cached by archetype to avoid re-reading on repeat spawns.
|
|
218
|
+
"""
|
|
219
|
+
cached = _FRONTMATTER_MODEL_CACHE.get(archetype)
|
|
220
|
+
if cached is not None:
|
|
221
|
+
return cached
|
|
222
|
+
result: Tuple[Optional[str], float] = (None, 0.0)
|
|
223
|
+
try:
|
|
224
|
+
if _agent_frontmatter is None:
|
|
225
|
+
pass
|
|
226
|
+
else:
|
|
227
|
+
path = _agent_frontmatter.resolve_agent_file(archetype, agents_dir)
|
|
228
|
+
if path.is_file():
|
|
229
|
+
metadata = _agent_frontmatter.parse_agent_file(path)
|
|
230
|
+
model = metadata.get("model", "").strip() if metadata else ""
|
|
231
|
+
if model:
|
|
232
|
+
result = (model, 1.0)
|
|
233
|
+
except Exception: # pragma: no cover - fail-open
|
|
234
|
+
result = (None, 0.0)
|
|
235
|
+
# Bounded LRU-ish cache (simple FIFO eviction at cap).
|
|
236
|
+
if len(_FRONTMATTER_MODEL_CACHE) >= _FRONTMATTER_CACHE_MAX:
|
|
237
|
+
try:
|
|
238
|
+
_FRONTMATTER_MODEL_CACHE.pop(next(iter(_FRONTMATTER_MODEL_CACHE)))
|
|
239
|
+
except StopIteration: # pragma: no cover
|
|
240
|
+
pass
|
|
241
|
+
_FRONTMATTER_MODEL_CACHE[archetype] = result
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _extract_archetype_from_payload(
|
|
246
|
+
description: str,
|
|
247
|
+
prompt: str,
|
|
248
|
+
subagent_type: str,
|
|
249
|
+
) -> str:
|
|
250
|
+
"""Best-effort archetype detection from spawn payload.
|
|
251
|
+
|
|
252
|
+
Order: ``subagent_type`` (Agent tool input) → first persona header
|
|
253
|
+
line → empty string. Returns archetype slug or empty string.
|
|
254
|
+
"""
|
|
255
|
+
if subagent_type:
|
|
256
|
+
return subagent_type.strip().lower()
|
|
257
|
+
# Try to find archetype from persona header in prompt
|
|
258
|
+
if prompt:
|
|
259
|
+
m = re.search(
|
|
260
|
+
r"(?:archetype|role|persona):\s*([a-z][a-z0-9_-]+)",
|
|
261
|
+
prompt,
|
|
262
|
+
flags=re.IGNORECASE,
|
|
263
|
+
)
|
|
264
|
+
if m:
|
|
265
|
+
return m.group(1).strip().lower()
|
|
266
|
+
return ""
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _emit_model_routing_advisory(
|
|
270
|
+
*,
|
|
271
|
+
description: str,
|
|
272
|
+
prompt: str,
|
|
273
|
+
subagent_type: str,
|
|
274
|
+
env: Optional[Dict[str, str]] = None,
|
|
275
|
+
project_dir: Optional[str] = None,
|
|
276
|
+
) -> None:
|
|
277
|
+
"""Emit a `model_routing_advised` advisory event for this spawn.
|
|
278
|
+
|
|
279
|
+
PLAN-078 Wave 1. Telemetry-only — this function:
|
|
280
|
+
- reads archetype from spawn payload
|
|
281
|
+
- reads `.claude/agents/<archetype>.md` frontmatter `model:` if set
|
|
282
|
+
- else calls `task_route.classify()` for a recommendation
|
|
283
|
+
- emits via audit_emit (deny-by-default 6-field allowlist)
|
|
284
|
+
|
|
285
|
+
NEVER raises. Bypass via `CEO_MODEL_ROUTING=0`.
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
src_env = env if env is not None else os.environ
|
|
289
|
+
if (src_env.get("CEO_MODEL_ROUTING") or "").strip() == "0":
|
|
290
|
+
return # bypass
|
|
291
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
292
|
+
return # audit emit unavailable; nothing to do
|
|
293
|
+
|
|
294
|
+
archetype = _extract_archetype_from_payload(
|
|
295
|
+
description or "", prompt or "", subagent_type or ""
|
|
296
|
+
)
|
|
297
|
+
if not archetype:
|
|
298
|
+
return # generic spawn — no archetype to route
|
|
299
|
+
|
|
300
|
+
# Path A: agent frontmatter declared model -> authoritative.
|
|
301
|
+
proj = project_dir or src_env.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
|
302
|
+
agents_dir = Path(proj) / ".claude" / "agents"
|
|
303
|
+
fm_model, fm_conf = _read_archetype_model_frontmatter(
|
|
304
|
+
archetype, agents_dir
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if fm_model:
|
|
308
|
+
model_recommended = fm_model
|
|
309
|
+
confidence = fm_conf
|
|
310
|
+
applied_or_skipped = "skipped_classify_frontmatter_authoritative"
|
|
311
|
+
override_reason = "frontmatter_model_present"
|
|
312
|
+
task_type = "frontmatter"
|
|
313
|
+
else:
|
|
314
|
+
# Path B: in-process classify.
|
|
315
|
+
tr = _resolve_task_route()
|
|
316
|
+
if tr is None or not hasattr(tr, "classify"):
|
|
317
|
+
# Fail-open: no recommendation available, advisory skipped.
|
|
318
|
+
return
|
|
319
|
+
try:
|
|
320
|
+
desc_text = (description or "")[:8 * 1024] # cap to 8KiB
|
|
321
|
+
result = tr.classify(desc_text, [])
|
|
322
|
+
except Exception:
|
|
323
|
+
# classify() raised — fail-open with breadcrumb attempt.
|
|
324
|
+
_emit_advisory_safe(
|
|
325
|
+
archetype=archetype,
|
|
326
|
+
task_type="classify_error",
|
|
327
|
+
model_recommended="",
|
|
328
|
+
confidence=0.0,
|
|
329
|
+
applied_or_skipped="skipped_classify_exception",
|
|
330
|
+
override_reason="classify_raised",
|
|
331
|
+
)
|
|
332
|
+
return
|
|
333
|
+
classification = (
|
|
334
|
+
result.get("classification") if isinstance(result, dict) else ""
|
|
335
|
+
) or ""
|
|
336
|
+
# Conservative mapping: classify() returns S/M/L/XL classification —
|
|
337
|
+
# not a model directly. Telemetry records the classification +
|
|
338
|
+
# leaves model_recommended empty so PLAN-079 can decide the mapping.
|
|
339
|
+
model_recommended = ""
|
|
340
|
+
confidence = 0.7 if classification else 0.0
|
|
341
|
+
applied_or_skipped = (
|
|
342
|
+
"advisory_only_no_recommendation"
|
|
343
|
+
if not classification
|
|
344
|
+
else "advisory_only_classification_emitted"
|
|
345
|
+
)
|
|
346
|
+
override_reason = ""
|
|
347
|
+
task_type = classification or "unclassified"
|
|
348
|
+
|
|
349
|
+
_emit_advisory_safe(
|
|
350
|
+
archetype=archetype,
|
|
351
|
+
task_type=task_type,
|
|
352
|
+
model_recommended=model_recommended,
|
|
353
|
+
confidence=float(confidence),
|
|
354
|
+
applied_or_skipped=applied_or_skipped,
|
|
355
|
+
override_reason=override_reason,
|
|
356
|
+
)
|
|
357
|
+
except Exception: # pragma: no cover - fail-open invariant
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
# PLAN-112-FOLLOWUP-persona-routing-wire W1+W2+W3 — god-mode matrix consult.
|
|
363
|
+
# ---------------------------------------------------------------------------
|
|
364
|
+
# Consult ALL THREE persona_routing APIs (AC1):
|
|
365
|
+
# `get_mode(archetype, "AUTO-05")` (recorded mode label) +
|
|
366
|
+
# `is_enforcing(archetype, "AUTO-05")` (authoritative effective-enforcing
|
|
367
|
+
# boolean that drives `decision`) + `is_killswitch_active()` (records the
|
|
368
|
+
# kill-switch state) and EMIT the resulting mode as forensic telemetry
|
|
369
|
+
# (`model_routing_enforced`). This closes the F-1.2 dead-policy (the
|
|
370
|
+
# AUTO-05 enforcing cell was never read at runtime).
|
|
371
|
+
#
|
|
372
|
+
# W3 — DEFERRED BLOCK + FLIP DOCTRINE:
|
|
373
|
+
# The actual model-tier BLOCK is DEFERRED. A "model mismatch -> block"
|
|
374
|
+
# predicate needs the dispatched/requested model, but the Agent hook
|
|
375
|
+
# payload exposes only description/prompt/subagent_type/run_in_background
|
|
376
|
+
# (SPEC/v1/hook-io.schema.md). `metadata.get("model")` (read in
|
|
377
|
+
# `_read_archetype_model_frontmatter`) is the AGENT FRONTMATTER model, NOT
|
|
378
|
+
# a spawn-requested model. With no requested-model input, a block would be
|
|
379
|
+
# theater. When the harness exposes the dispatched model to PreToolUse, the
|
|
380
|
+
# violation predicate becomes a 1-liner: fm_model present AND requested
|
|
381
|
+
# present AND differing.
|
|
382
|
+
# The future enable of that block is governed by OBSERVED-VIOLATION VOLUME
|
|
383
|
+
# + a false-positive-rate threshold, NOT by elapsed calendar days
|
|
384
|
+
# (ADR-095 / [[feedback-no-calendar-gates-ai-workflow]]). Recorded here as
|
|
385
|
+
# the flip doctrine for a future ADR-118-AMEND-2.
|
|
386
|
+
#
|
|
387
|
+
# SECURITY INVARIANTS:
|
|
388
|
+
# - Mode is read off the AUTHORITATIVE `subagent_type`-derived archetype
|
|
389
|
+
# ONLY — NEVER the prompt-regex archetype (attacker-controllable; a
|
|
390
|
+
# spoofed archetype must not flip the recorded mode). CWE-400/spoofing.
|
|
391
|
+
# - Fail-OPEN: import/get_mode failure -> emit `model_routing_eval_error`
|
|
392
|
+
# and return; NEVER raise in the hook hot path (CLAUDE.md §5).
|
|
393
|
+
# - Kill-switch precedence: get_mode() already demotes an enforcing cell
|
|
394
|
+
# to advisory under `CEO_GODMODE_ENFORCING=0`; we record the demoted
|
|
395
|
+
# mode (advisory) so the audit reflects effective policy.
|
|
396
|
+
def _consult_model_routing_mode(
|
|
397
|
+
*,
|
|
398
|
+
description: str,
|
|
399
|
+
prompt: str,
|
|
400
|
+
subagent_type: str,
|
|
401
|
+
env: Optional[Dict[str, str]] = None,
|
|
402
|
+
project_dir: Optional[str] = None,
|
|
403
|
+
) -> None:
|
|
404
|
+
"""Consult the god-mode routing matrix + emit forensic telemetry.
|
|
405
|
+
|
|
406
|
+
CONSULT + AUDIT ONLY. NEVER blocks, NEVER mutates tool_input, NEVER
|
|
407
|
+
raises. Consults all THREE persona_routing APIs (AC1): `get_mode`
|
|
408
|
+
(recorded mode label), `is_enforcing` (authoritative effective-
|
|
409
|
+
enforcing boolean that drives `decision`), and `is_killswitch_active`.
|
|
410
|
+
Emits `model_routing_enforced` (mode/recommended_model/
|
|
411
|
+
killswitch_armed/decision) on success, `model_routing_eval_error`
|
|
412
|
+
(decision=eval_error) on any infra failure (fail-open).
|
|
413
|
+
"""
|
|
414
|
+
try:
|
|
415
|
+
src_env = env if env is not None else os.environ
|
|
416
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
417
|
+
return # nothing to audit against; consult is observability-only
|
|
418
|
+
|
|
419
|
+
# AUTHORITATIVE source ONLY — subagent_type. A prompt-regex archetype
|
|
420
|
+
# is attacker-controllable and must not flip the recorded mode.
|
|
421
|
+
archetype = (subagent_type or "").strip().lower()
|
|
422
|
+
if not archetype:
|
|
423
|
+
return # no authoritative archetype -> no mode to record
|
|
424
|
+
|
|
425
|
+
session_id = src_env.get("CLAUDE_SESSION_ID", "") or ""
|
|
426
|
+
proj = project_dir or src_env.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
|
427
|
+
|
|
428
|
+
# Fail-OPEN guard around the matrix consult.
|
|
429
|
+
try:
|
|
430
|
+
if _persona_routing is None:
|
|
431
|
+
raise RuntimeError("persona_routing import unavailable")
|
|
432
|
+
# NEVER read _PRIMITIVE_DEFAULT_MODE directly — get_mode /
|
|
433
|
+
# is_enforcing honor the kill-switch demotion internally.
|
|
434
|
+
# All THREE persona_routing APIs are consulted (AC1):
|
|
435
|
+
# - get_mode -> the effective (possibly demoted) mode label
|
|
436
|
+
# recorded verbatim in the emit
|
|
437
|
+
# - is_enforcing -> the AUTHORITATIVE effective-enforcing boolean
|
|
438
|
+
# that drives `decision` (kill-switch already
|
|
439
|
+
# folded in: returns False when demoted)
|
|
440
|
+
# - is_killswitch_active -> records whether the kill-switch armed
|
|
441
|
+
mode = _persona_routing.get_mode(archetype, "AUTO-05")
|
|
442
|
+
is_enforcing = bool(
|
|
443
|
+
_persona_routing.is_enforcing(archetype, "AUTO-05")
|
|
444
|
+
)
|
|
445
|
+
killswitch_armed = bool(_persona_routing.is_killswitch_active())
|
|
446
|
+
except Exception: # noqa: BLE001 — fail-open per CLAUDE.md §5
|
|
447
|
+
try:
|
|
448
|
+
_audit_emit.emit_generic(
|
|
449
|
+
"model_routing_eval_error",
|
|
450
|
+
archetype=archetype[:64],
|
|
451
|
+
reason_code="persona_routing_eval_failed",
|
|
452
|
+
decision="eval_error",
|
|
453
|
+
session_id=session_id,
|
|
454
|
+
project=str(proj)[:256],
|
|
455
|
+
)
|
|
456
|
+
except Exception: # noqa: BLE001
|
|
457
|
+
pass
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
# recommended_model: best-effort authoritative agent-frontmatter
|
|
461
|
+
# model for this archetype (NOT a spawn-requested model). "" when
|
|
462
|
+
# absent. Reuses the cached frontmatter reader.
|
|
463
|
+
recommended_model = ""
|
|
464
|
+
try:
|
|
465
|
+
agents_dir = Path(proj) / ".claude" / "agents"
|
|
466
|
+
fm_model, _conf = _read_archetype_model_frontmatter(
|
|
467
|
+
archetype, agents_dir
|
|
468
|
+
)
|
|
469
|
+
recommended_model = (fm_model or "")[:64]
|
|
470
|
+
except Exception: # noqa: BLE001 — telemetry enrichment is optional
|
|
471
|
+
recommended_model = ""
|
|
472
|
+
|
|
473
|
+
# decision enum: enforce_telemetry | advisory | eval_error.
|
|
474
|
+
# NO `block` value — block deferred (§2.4). Derived from the
|
|
475
|
+
# AUTHORITATIVE is_enforcing() boolean (NOT a re-derivation from
|
|
476
|
+
# the `mode` string), so the kill-switch demotion already folded
|
|
477
|
+
# into is_enforcing() drives the decision. `mode` (possibly
|
|
478
|
+
# demoted to "advisory" by get_mode) is still recorded verbatim.
|
|
479
|
+
decision = "enforce_telemetry" if is_enforcing else "advisory"
|
|
480
|
+
|
|
481
|
+
try:
|
|
482
|
+
_audit_emit.emit_generic(
|
|
483
|
+
"model_routing_enforced",
|
|
484
|
+
archetype=archetype[:64],
|
|
485
|
+
mode=str(mode)[:16],
|
|
486
|
+
recommended_model=recommended_model,
|
|
487
|
+
killswitch_armed=killswitch_armed,
|
|
488
|
+
decision=decision,
|
|
489
|
+
session_id=session_id,
|
|
490
|
+
project=str(proj)[:256],
|
|
491
|
+
)
|
|
492
|
+
except Exception: # noqa: BLE001 — fail-open
|
|
493
|
+
return
|
|
494
|
+
except Exception: # pragma: no cover — fail-open invariant
|
|
495
|
+
return
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
# PLAN-091 Wave A.4 (W3.1) — archetype → mcp_routing task_class heuristic.
|
|
499
|
+
#
|
|
500
|
+
# Maps the spawn's archetype to one of the 4 task_class strings declared in
|
|
501
|
+
# `_lib/mcp_routing._ROUTING_TABLE`. mcp_routing.resolve() emits its own
|
|
502
|
+
# `mcp_route_advised` advisory (PLAN-086 Wave D) — the spawn-hook wire only
|
|
503
|
+
# needs to trigger the resolver at the right callsite. NO mapping fallthrough
|
|
504
|
+
# = NO emit (advisory-only; avoid noise on every spawn).
|
|
505
|
+
_MCP_ARCHETYPE_TASK_CLASS_HINTS: Tuple[Tuple[Tuple[str, ...], str], ...] = (
|
|
506
|
+
# ORDER MATTERS — first match wins. More-specific archetype slugs
|
|
507
|
+
# (finops/seo/crypto) come BEFORE the bare "architect" pattern so
|
|
508
|
+
# composite slugs like "llm-finops-architect" or "crypto-research-
|
|
509
|
+
# analyst" route to the more specific MCP server bundle.
|
|
510
|
+
(("finops", "cost", "budget"), "finops"),
|
|
511
|
+
(("seo", "ahrefs", "similarweb"), "seo_research"),
|
|
512
|
+
(("crypto", "lunarcrush", "blockchain"), "crypto_research"),
|
|
513
|
+
(("architect", "arch-"), "arch"),
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# PLAN-091 Wave A.5 (W3.3) — archetype-hint patterns triggering a
|
|
518
|
+
# `specialization_promoted` advisory when subagent_type == general-purpose.
|
|
519
|
+
# Match is FIRST-HIT, not exhaustive — keeps emit volume bounded. Hints
|
|
520
|
+
# are lowercase substring matches on (description + prompt).
|
|
521
|
+
_PROMOTION_ARCHETYPE_HINTS: Tuple[Tuple[str, Tuple[str, ...]], ...] = (
|
|
522
|
+
("performance-engineer",
|
|
523
|
+
("latency", "p95", "p99", "profile", "memory leak", "throughput",
|
|
524
|
+
"gc tuning", "hot path")),
|
|
525
|
+
("security-engineer",
|
|
526
|
+
("jwt", "oauth", "authentication", "vulnerability", "owasp",
|
|
527
|
+
"xss", "sql injection", "csrf", "rate limiting")),
|
|
528
|
+
("qa-architect",
|
|
529
|
+
("test strategy", "mutation testing", "flake", "regression",
|
|
530
|
+
"property-based", "fuzz", "contract test")),
|
|
531
|
+
("code-reviewer",
|
|
532
|
+
("merge gate", "code review", "veto", "review checklist")),
|
|
533
|
+
("incident-commander",
|
|
534
|
+
("incident commander", "severity classification", "all-clear",
|
|
535
|
+
"paging policy", "post-incident review")),
|
|
536
|
+
("identity-trust-architect",
|
|
537
|
+
("token rotation", "session lifecycle", "rbac", "abac",
|
|
538
|
+
"service-to-service trust", "mtls")),
|
|
539
|
+
("threat-detection-engineer",
|
|
540
|
+
("mitre att&ck", "siem rule", "detection rule", "alert deduplication",
|
|
541
|
+
"purple team", "threat hunting")),
|
|
542
|
+
("devops",
|
|
543
|
+
("github actions", "sha-pin", "ci/cd pipeline", "rollback strategy",
|
|
544
|
+
"secret rotation")),
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _emit_mcp_routing_advisory(
|
|
549
|
+
*,
|
|
550
|
+
description: str,
|
|
551
|
+
prompt: str,
|
|
552
|
+
subagent_type: str,
|
|
553
|
+
env: Optional[Dict[str, str]] = None,
|
|
554
|
+
) -> None:
|
|
555
|
+
"""PLAN-091 Wave A.4 (W3.1) — emit `mcp_route_advised` for this spawn.
|
|
556
|
+
|
|
557
|
+
Derives task_class from the spawn's archetype via the
|
|
558
|
+
``_MCP_ARCHETYPE_TASK_CLASS_HINTS`` table, then delegates to
|
|
559
|
+
``_lib/mcp_routing.resolve()`` which performs its own emit (per
|
|
560
|
+
PLAN-086 Wave D). Bypass: ``CEO_MCP_ROUTING_HOOK=0`` (parallel to
|
|
561
|
+
PLAN-078 model-routing bypass; per-server kill switches live inside
|
|
562
|
+
mcp_routing.resolve()).
|
|
563
|
+
|
|
564
|
+
NEVER raises. Advisory-only — never mutates tool_input, never blocks.
|
|
565
|
+
"""
|
|
566
|
+
try:
|
|
567
|
+
src_env = env if env is not None else os.environ
|
|
568
|
+
if (src_env.get("CEO_MCP_ROUTING_HOOK") or "").strip() == "0":
|
|
569
|
+
return
|
|
570
|
+
archetype = _extract_archetype_from_payload(
|
|
571
|
+
description or "", prompt or "", subagent_type or ""
|
|
572
|
+
)
|
|
573
|
+
if not archetype:
|
|
574
|
+
return
|
|
575
|
+
archetype_lc = archetype.lower()
|
|
576
|
+
task_class: Optional[str] = None
|
|
577
|
+
for hints, mapped in _MCP_ARCHETYPE_TASK_CLASS_HINTS:
|
|
578
|
+
if any(h in archetype_lc for h in hints):
|
|
579
|
+
task_class = mapped
|
|
580
|
+
break
|
|
581
|
+
if task_class is None:
|
|
582
|
+
return # no mapping; emit volume stays bounded
|
|
583
|
+
try:
|
|
584
|
+
from _lib import mcp_routing as _mcpr # type: ignore
|
|
585
|
+
_mcpr.resolve(task_class) # resolver fires advisory emit
|
|
586
|
+
except Exception: # pragma: no cover - fail-open
|
|
587
|
+
return
|
|
588
|
+
except Exception: # pragma: no cover - fail-open
|
|
589
|
+
return
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def _emit_promotion_advisory(
|
|
593
|
+
*,
|
|
594
|
+
description: str,
|
|
595
|
+
prompt: str,
|
|
596
|
+
subagent_type: str,
|
|
597
|
+
env: Optional[Dict[str, str]] = None,
|
|
598
|
+
) -> None:
|
|
599
|
+
"""PLAN-091 Wave A.5 (W3.3) — emit `specialization_promoted` when a
|
|
600
|
+
``general-purpose`` spawn matches a specialist archetype heuristic.
|
|
601
|
+
|
|
602
|
+
Advisory ONLY — does NOT auto-spawn the suggested specialist. Records
|
|
603
|
+
the suggestion in audit log so PLAN-094+ can promote SEMI to AUTO once
|
|
604
|
+
confidence is established. Bypass: ``CEO_PROMOTION_HEURISTIC=0``.
|
|
605
|
+
|
|
606
|
+
First-hit match wins (bounded emit volume). NEVER raises.
|
|
607
|
+
"""
|
|
608
|
+
try:
|
|
609
|
+
src_env = env if env is not None else os.environ
|
|
610
|
+
if (src_env.get("CEO_PROMOTION_HEURISTIC") or "").strip() == "0":
|
|
611
|
+
return
|
|
612
|
+
if (subagent_type or "").strip().lower() != "general-purpose":
|
|
613
|
+
return
|
|
614
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
615
|
+
return
|
|
616
|
+
text = ((description or "") + "\n" + (prompt or "")).lower()
|
|
617
|
+
text = text[: 8 * 1024] # 8KiB cap on hint-scan window
|
|
618
|
+
for suggested, hints in _PROMOTION_ARCHETYPE_HINTS:
|
|
619
|
+
for hint in hints:
|
|
620
|
+
if hint in text:
|
|
621
|
+
try:
|
|
622
|
+
# PLAN-088 R2 iter-2 STRICKEN specialization_promoted
|
|
623
|
+
# as separate action; routed via mcp_route_advised
|
|
624
|
+
# with signal_source discriminator per audit_emit.py
|
|
625
|
+
# 5099-5105 contract.
|
|
626
|
+
_audit_emit.emit_generic(
|
|
627
|
+
"mcp_route_advised",
|
|
628
|
+
session_id="",
|
|
629
|
+
task_class="promotion",
|
|
630
|
+
suggested_servers=suggested[:64],
|
|
631
|
+
kill_switch_overrides="",
|
|
632
|
+
signal_source="specialization_promoted",
|
|
633
|
+
)
|
|
634
|
+
except Exception: # pragma: no cover - fail-open
|
|
635
|
+
pass
|
|
636
|
+
return # first-hit wins
|
|
637
|
+
except Exception: # pragma: no cover - fail-open
|
|
638
|
+
return
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _emit_cookbook_pattern_advisory(
|
|
642
|
+
*,
|
|
643
|
+
description: str,
|
|
644
|
+
prompt: str,
|
|
645
|
+
env=None,
|
|
646
|
+
) -> None:
|
|
647
|
+
"""PLAN-092 Wave A.4 (W3.2 SEMI-11) — emit cookbook_pattern_advised
|
|
648
|
+
when a spawn prompt matches one of 4 Anthropic Cookbook pattern
|
|
649
|
+
trigger taxonomies (COOK-P1..P4).
|
|
650
|
+
|
|
651
|
+
Advisory ONLY — UX hint. NEVER blocks, NEVER mutates tool_input.
|
|
652
|
+
Rate-cap via PLAN-088 global _plan088_rate_admit in audit_emit.
|
|
653
|
+
Kill-switch: CEO_COOKBOOK_ADVISOR_ENABLED=0. Privacy invariant:
|
|
654
|
+
3 fields max (no raw prompt persisted — AC3b).
|
|
655
|
+
|
|
656
|
+
Audit-emit signature mapping (PLAN-090 W3.2 canonical contract):
|
|
657
|
+
pattern_id (COOK-P*) -> top_pattern_keys
|
|
658
|
+
trigger_class (string) -> task_signature (enum-constrained "other")
|
|
659
|
+
bucket (high/med/low) -> pattern_count (3/2/1)
|
|
660
|
+
|
|
661
|
+
NEVER raises.
|
|
662
|
+
"""
|
|
663
|
+
try:
|
|
664
|
+
src_env = env if env is not None else os.environ
|
|
665
|
+
if not _COOKBOOK_PATTERNS_AVAILABLE:
|
|
666
|
+
return
|
|
667
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
668
|
+
return
|
|
669
|
+
# Kill-switch
|
|
670
|
+
try:
|
|
671
|
+
if hasattr(_cookbook_patterns, "kill_switch_enabled"):
|
|
672
|
+
if not _cookbook_patterns.kill_switch_enabled():
|
|
673
|
+
return
|
|
674
|
+
else:
|
|
675
|
+
val = (src_env.get("CEO_COOKBOOK_ADVISOR_ENABLED") or "1").strip()
|
|
676
|
+
if val in ("0", "false", "False", "no", "off"):
|
|
677
|
+
return
|
|
678
|
+
except Exception: # pragma: no cover
|
|
679
|
+
return
|
|
680
|
+
# Concat description + prompt, cap at 8KiB
|
|
681
|
+
text = ((description or "") + "\n" + (prompt or ""))[: 8 * 1024]
|
|
682
|
+
if not text.strip():
|
|
683
|
+
return
|
|
684
|
+
# Match (first-hit canonical order COOK-P1 -> P4)
|
|
685
|
+
try:
|
|
686
|
+
result = _cookbook_patterns.match_pattern(text)
|
|
687
|
+
except Exception: # pragma: no cover
|
|
688
|
+
return
|
|
689
|
+
if result is None:
|
|
690
|
+
return
|
|
691
|
+
pattern_id, trigger_class, bucket = result
|
|
692
|
+
if not hasattr(_audit_emit, "emit_cookbook_pattern_advised"):
|
|
693
|
+
return
|
|
694
|
+
# Map semantic -> canonical signature
|
|
695
|
+
bucket_to_count = {"high": 3, "medium": 2, "low": 1}
|
|
696
|
+
try:
|
|
697
|
+
_audit_emit.emit_cookbook_pattern_advised(
|
|
698
|
+
task_signature="other",
|
|
699
|
+
top_pattern_keys=str(pattern_id)[:128],
|
|
700
|
+
pattern_count=bucket_to_count.get(str(bucket), 1),
|
|
701
|
+
)
|
|
702
|
+
except Exception: # pragma: no cover
|
|
703
|
+
return
|
|
704
|
+
except Exception: # pragma: no cover - fail-open invariant
|
|
705
|
+
return
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _emit_advisory_safe(
|
|
709
|
+
*,
|
|
710
|
+
archetype: str,
|
|
711
|
+
task_type: str,
|
|
712
|
+
model_recommended: str,
|
|
713
|
+
confidence: float,
|
|
714
|
+
applied_or_skipped: str,
|
|
715
|
+
override_reason: str,
|
|
716
|
+
) -> None:
|
|
717
|
+
"""Wrapper around audit_emit.emit_generic for `model_routing_advised`.
|
|
718
|
+
|
|
719
|
+
Allowlist enforced by audit_emit dispatcher (defense-in-depth). Caller
|
|
720
|
+
only fills the 6 contract fields; ts/session/project added by emit_generic.
|
|
721
|
+
|
|
722
|
+
Codex W1+W2 fix-pack #2: ``confidence`` (float 0..1) is converted at
|
|
723
|
+
emission time to ``confidence_basis_points`` (int 0..1000) — floats
|
|
724
|
+
are forbidden in HMAC-covered fields (canonical_json invariant). The
|
|
725
|
+
in-process API surface still accepts ``confidence: float`` for
|
|
726
|
+
ergonomic continuity; conversion is colocated with the emit call.
|
|
727
|
+
"""
|
|
728
|
+
try:
|
|
729
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
730
|
+
return
|
|
731
|
+
# Convert float confidence -> int basis-points. NaN / out-of-range
|
|
732
|
+
# collapse to 0 (lower clamp in audit_emit emitter).
|
|
733
|
+
try:
|
|
734
|
+
conf_f = float(confidence)
|
|
735
|
+
except (TypeError, ValueError):
|
|
736
|
+
conf_f = 0.0
|
|
737
|
+
if conf_f != conf_f or conf_f in (float("inf"), float("-inf")):
|
|
738
|
+
conf_f = 0.0
|
|
739
|
+
if conf_f < 0.0:
|
|
740
|
+
conf_f = 0.0
|
|
741
|
+
if conf_f > 1.0:
|
|
742
|
+
conf_f = 1.0
|
|
743
|
+
confidence_bp = int(round(conf_f * 1000.0))
|
|
744
|
+
# Length-bound caller strings (defense-in-depth on adopter drift).
|
|
745
|
+
_audit_emit.emit_generic(
|
|
746
|
+
"model_routing_advised",
|
|
747
|
+
archetype=(archetype or "")[:64],
|
|
748
|
+
task_type=(task_type or "")[:32],
|
|
749
|
+
model_recommended=(model_recommended or "")[:64],
|
|
750
|
+
confidence_basis_points=confidence_bp,
|
|
751
|
+
applied_or_skipped=(applied_or_skipped or "")[:64],
|
|
752
|
+
override_reason=(override_reason or "")[:128],
|
|
753
|
+
)
|
|
754
|
+
except Exception: # pragma: no cover - fail-open
|
|
755
|
+
return
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
# Persona header patterns — must appear at the START of a line.
|
|
759
|
+
_PERSONA_HEADER_RE = re.compile(
|
|
760
|
+
r"^(?:PERSONA:|## AGENT PROFILE|## PERSONA)",
|
|
761
|
+
flags=re.MULTILINE,
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
# Governance requirement: named spawns must include this literal section.
|
|
765
|
+
_SKILL_CONTENT_MARKER = "## SKILL CONTENT"
|
|
766
|
+
|
|
767
|
+
# PLAN-019 Phase 2B P1-SEC-B — minimum body size (non-whitespace bytes)
|
|
768
|
+
# required between `## SKILL CONTENT` and the next `##` heading / EOF for
|
|
769
|
+
# the marker to count as a real skill-content section. Rejects empty-body,
|
|
770
|
+
# stub-only, or single-line "see file X" shells.
|
|
771
|
+
_SKILL_CONTENT_MIN_BYTES = 256
|
|
772
|
+
|
|
773
|
+
# Regex to find the marker ON ITS OWN LINE (ignoring trailing whitespace).
|
|
774
|
+
_SKILL_CONTENT_MARKER_RE = re.compile(
|
|
775
|
+
r"^## SKILL CONTENT[ \t]*$",
|
|
776
|
+
flags=re.MULTILINE,
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
# Regex to find the next `##`-level heading (to bound the body extraction).
|
|
780
|
+
_NEXT_H2_RE = re.compile(r"^##[ \t]", flags=re.MULTILINE)
|
|
781
|
+
|
|
782
|
+
# Code-fence markers (``` or ~~~ with optional language tag).
|
|
783
|
+
_CODE_FENCE_RE = re.compile(r"^(?:```|~~~)", flags=re.MULTILINE)
|
|
784
|
+
|
|
785
|
+
# HTML comment pair — non-greedy across lines.
|
|
786
|
+
_HTML_COMMENT_RE = re.compile(r"<!--.*?-->", flags=re.DOTALL)
|
|
787
|
+
|
|
788
|
+
# Regex to extract ## SPEC CONTEXT block content (between its header and next
|
|
789
|
+
# ## heading or EOF). Used by _sanitize_spec_context_advisory() below.
|
|
790
|
+
_SPEC_CONTEXT_HEADER_RE = re.compile(
|
|
791
|
+
r"^##[ \t]+SPEC[ \t]+CONTEXT[ \t]*$",
|
|
792
|
+
flags=re.MULTILINE,
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def _sanitize_spec_context_advisory(prompt: str, env=None) -> None:
|
|
797
|
+
"""PLAN-113 WIRE-DEADMOD / ADR-089 SEC-P0-01 — sanitize ## SPEC CONTEXT
|
|
798
|
+
payload and emit telemetry advisory.
|
|
799
|
+
|
|
800
|
+
Extracts the ## SPEC CONTEXT block from the spawn prompt, runs it
|
|
801
|
+
through spec_context_sanitizer.sanitize(), and emits a
|
|
802
|
+
`spec_context_sanitized` audit-emit event so sentinel violations,
|
|
803
|
+
truncation, and control-char counts are observable.
|
|
804
|
+
|
|
805
|
+
ADVISORY ONLY — never blocks the spawn. Fail-open on any exception.
|
|
806
|
+
Kill-switch: CEO_SPEC_CTX_SANITIZER_ENABLED=0.
|
|
807
|
+
"""
|
|
808
|
+
try:
|
|
809
|
+
if not _SPEC_CTX_SANITIZER_AVAILABLE or not _AUDIT_EMIT_AVAILABLE:
|
|
810
|
+
return
|
|
811
|
+
src_env = env if env is not None else os.environ
|
|
812
|
+
val = (src_env.get("CEO_SPEC_CTX_SANITIZER_ENABLED") or "1").strip()
|
|
813
|
+
if val in ("0", "false", "False", "no", "off"):
|
|
814
|
+
return
|
|
815
|
+
if not prompt:
|
|
816
|
+
return
|
|
817
|
+
# Extract ## SPEC CONTEXT block content
|
|
818
|
+
m = _SPEC_CONTEXT_HEADER_RE.search(prompt)
|
|
819
|
+
if m is None:
|
|
820
|
+
return # no SPEC CONTEXT block — nothing to sanitize
|
|
821
|
+
block_start = m.end()
|
|
822
|
+
next_h2 = _NEXT_H2_RE.search(prompt, block_start)
|
|
823
|
+
block_end = next_h2.start() if next_h2 else len(prompt)
|
|
824
|
+
spec_payload = prompt[block_start:block_end]
|
|
825
|
+
|
|
826
|
+
result = _spec_ctx_sanitizer.sanitize(spec_payload)
|
|
827
|
+
|
|
828
|
+
# Emit telemetry (advisory only; sentinel violations logged for CEO)
|
|
829
|
+
try:
|
|
830
|
+
_audit_emit.emit_generic(
|
|
831
|
+
"spec_context_sanitized",
|
|
832
|
+
original_bytes=result.original_bytes,
|
|
833
|
+
cleaned_bytes=result.cleaned_bytes,
|
|
834
|
+
truncated=1 if result.truncated else 0,
|
|
835
|
+
sentinel_violations=len(result.sentinel_violations),
|
|
836
|
+
control_chars_stripped=result.control_chars_stripped,
|
|
837
|
+
bidi_zw_chars_stripped=result.bidi_zw_chars_stripped,
|
|
838
|
+
# PLAN-133 A2 — surface the Tag-block count too (allowlisted in
|
|
839
|
+
# audit_emit §7d). Harmless if the SanitizeResult lacks the field
|
|
840
|
+
# (getattr default 0) so this stays import-order tolerant.
|
|
841
|
+
tag_chars_stripped=getattr(result, "tag_chars_stripped", 0),
|
|
842
|
+
header_escape_count=result.header_escape_count,
|
|
843
|
+
)
|
|
844
|
+
except Exception: # pragma: no cover - fail-open
|
|
845
|
+
pass
|
|
846
|
+
except Exception: # pragma: no cover - fail-open
|
|
847
|
+
return
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def _enforce_spec_context_unicode(prompt: str, env=None):
|
|
851
|
+
"""PLAN-133 A2 — fail-CLOSED invisible-unicode guard on the spawn prompt.
|
|
852
|
+
|
|
853
|
+
Scans the WHOLE spawn prompt (not just the ## SPEC CONTEXT block — an attacker
|
|
854
|
+
can smuggle Tag-block/bidi chars anywhere in the named-spawn body) for invisible
|
|
855
|
+
/smuggling unicode (control / bidi / zero-width / U+E0000-E007F Tag-block).
|
|
856
|
+
|
|
857
|
+
Default-OFF (doctrine #1): the BLOCK fires only when CEO_UNICODE_HARDBLOCK=='1'
|
|
858
|
+
in the import-time trusted_env snapshot. Otherwise advisory — the breadcrumb is
|
|
859
|
+
emitted with enforced=0 (measure-first) and None is returned (no block).
|
|
860
|
+
|
|
861
|
+
Returns a block-reason string when enforced AND a detection fires, else None.
|
|
862
|
+
Fail-OPEN on any infra exception (never blocks the session on a scanner bug).
|
|
863
|
+
Master kill: CEO_SOTA_DISABLE=1 forces advisory.
|
|
864
|
+
"""
|
|
865
|
+
try:
|
|
866
|
+
if not _SPEC_CTX_SANITIZER_AVAILABLE:
|
|
867
|
+
return None
|
|
868
|
+
src_env = env if env is not None else os.environ
|
|
869
|
+
if not prompt:
|
|
870
|
+
return None
|
|
871
|
+
# Default-OFF flag, read from the trusted_env snapshot when available so a
|
|
872
|
+
# late-set value can't toggle enforcement mid-process. Master kill wins.
|
|
873
|
+
if (src_env.get("CEO_SOTA_DISABLE") or "").strip() == "1":
|
|
874
|
+
enforce = False
|
|
875
|
+
else:
|
|
876
|
+
enforce = _unicode_hardblock_enforced(src_env)
|
|
877
|
+
|
|
878
|
+
result = _spec_ctx_sanitizer.sanitize(prompt)
|
|
879
|
+
count = _spec_ctx_sanitizer.invisible_unicode_count(result)
|
|
880
|
+
if count <= 0:
|
|
881
|
+
return None # clean — nothing to do
|
|
882
|
+
|
|
883
|
+
unicode_class = _spec_ctx_sanitizer.classify_invisible_unicode(result)
|
|
884
|
+
|
|
885
|
+
# Emit the closed-enum breadcrumb on BOTH advisory and enforced paths
|
|
886
|
+
# (the denominator must be real from day one — no 0/0/0 trap).
|
|
887
|
+
if _AUDIT_EMIT_AVAILABLE:
|
|
888
|
+
try:
|
|
889
|
+
_audit_emit.emit_generic(
|
|
890
|
+
"invisible_unicode_blocked",
|
|
891
|
+
surface="spawn",
|
|
892
|
+
unicode_class=unicode_class,
|
|
893
|
+
char_count=int(count),
|
|
894
|
+
enforced=1 if enforce else 0,
|
|
895
|
+
)
|
|
896
|
+
except Exception: # pragma: no cover - fail-open
|
|
897
|
+
pass
|
|
898
|
+
|
|
899
|
+
if not enforce:
|
|
900
|
+
return None # advisory only
|
|
901
|
+
|
|
902
|
+
return (
|
|
903
|
+
"GOVERNANCE: invisible_unicode_blocked: the spawn prompt contains "
|
|
904
|
+
f"{count} invisible/smuggling character(s) (class={unicode_class}: "
|
|
905
|
+
"control / bidi / zero-width / Unicode-Tags-block). These are "
|
|
906
|
+
"removed by the sanitizer and rejected fail-CLOSED before review. "
|
|
907
|
+
"Remove the hidden characters and re-spawn. To run advisory-only, "
|
|
908
|
+
"unset CEO_UNICODE_HARDBLOCK."
|
|
909
|
+
)
|
|
910
|
+
except Exception: # pragma: no cover - fail-open invariant
|
|
911
|
+
return None
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
def _unicode_hardblock_enforced(src_env) -> bool:
|
|
915
|
+
"""True iff CEO_UNICODE_HARDBLOCK=='1' (PLAN-133 A2). Prefers the trusted_env
|
|
916
|
+
snapshot; falls back to the passed env dict. Pure; never raises."""
|
|
917
|
+
try:
|
|
918
|
+
from _lib import trusted_env as _te # noqa: E402
|
|
919
|
+
val = _te.get_trusted("CEO_UNICODE_HARDBLOCK")
|
|
920
|
+
if val is not None:
|
|
921
|
+
return (val or "").strip() == "1"
|
|
922
|
+
except Exception: # pragma: no cover
|
|
923
|
+
pass
|
|
924
|
+
return (src_env.get("CEO_UNICODE_HARDBLOCK") or "").strip() == "1"
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def _emit_spawn_confidence_advisory(
|
|
928
|
+
*,
|
|
929
|
+
action_type: str,
|
|
930
|
+
is_named_spawn: bool,
|
|
931
|
+
env=None,
|
|
932
|
+
) -> None:
|
|
933
|
+
"""PLAN-113 WIRE-DEADMOD — confidence_labels (PLAN-083 Wave 1.10).
|
|
934
|
+
|
|
935
|
+
Classifies the spawn as an action and emits `spawn_confidence_advisory`
|
|
936
|
+
so recommender / receipt formatter surfaces have a hook-level confidence
|
|
937
|
+
signal for governance dashboards.
|
|
938
|
+
|
|
939
|
+
ADVISORY ONLY — never blocks. Fail-open on any exception.
|
|
940
|
+
Kill-switch: CEO_SPAWN_CONFIDENCE_ENABLED=0.
|
|
941
|
+
"""
|
|
942
|
+
try:
|
|
943
|
+
if not _CONFIDENCE_LABELS_AVAILABLE or not _AUDIT_EMIT_AVAILABLE:
|
|
944
|
+
return
|
|
945
|
+
src_env = env if env is not None else os.environ
|
|
946
|
+
val = (src_env.get("CEO_SPAWN_CONFIDENCE_ENABLED") or "1").strip()
|
|
947
|
+
if val in ("0", "false", "False", "no", "off"):
|
|
948
|
+
return
|
|
949
|
+
|
|
950
|
+
ctx = {"canonical": False}
|
|
951
|
+
conf = _confidence_labels.classify(action_type, ctx)
|
|
952
|
+
marker = _confidence_labels.as_emoji_free_marker(conf)
|
|
953
|
+
|
|
954
|
+
try:
|
|
955
|
+
_audit_emit.emit_generic(
|
|
956
|
+
"spawn_confidence_advisory",
|
|
957
|
+
action_type=(action_type or "")[:32],
|
|
958
|
+
confidence_level=(conf.level or "")[:32],
|
|
959
|
+
confidence_marker=(marker or "")[:32],
|
|
960
|
+
reason_code=(conf.reason_code or "")[:64],
|
|
961
|
+
is_named_spawn=1 if is_named_spawn else 0,
|
|
962
|
+
)
|
|
963
|
+
except Exception: # pragma: no cover - fail-open
|
|
964
|
+
pass
|
|
965
|
+
except Exception: # pragma: no cover - fail-open
|
|
966
|
+
return
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
def _strip_fenced_and_comments(text: str) -> str:
|
|
970
|
+
"""Return a version of ``text`` with HTML comments removed and
|
|
971
|
+
fenced-code-block content masked so marker/body searches cannot
|
|
972
|
+
match inside them (P1-SEC-B bypass hardening)."""
|
|
973
|
+
stripped = _HTML_COMMENT_RE.sub("", text)
|
|
974
|
+
out_parts = []
|
|
975
|
+
in_fence = False
|
|
976
|
+
for line in stripped.splitlines(keepends=True):
|
|
977
|
+
if _CODE_FENCE_RE.match(line):
|
|
978
|
+
in_fence = not in_fence
|
|
979
|
+
out_parts.append(line)
|
|
980
|
+
continue
|
|
981
|
+
if in_fence:
|
|
982
|
+
out_parts.append("".join(
|
|
983
|
+
"\n" if c == "\n" else " " for c in line
|
|
984
|
+
))
|
|
985
|
+
else:
|
|
986
|
+
out_parts.append(line)
|
|
987
|
+
return "".join(out_parts)
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def _has_skill_content(prompt: str) -> bool:
|
|
991
|
+
"""True iff ``prompt`` contains a real `## SKILL CONTENT` section.
|
|
992
|
+
|
|
993
|
+
Enforces four bypass-resistant checks (P1-SEC-B):
|
|
994
|
+
1. Marker on own line (not inline narrative).
|
|
995
|
+
2. Marker NOT inside HTML comment.
|
|
996
|
+
3. Marker NOT inside fenced code block.
|
|
997
|
+
4. >=_SKILL_CONTENT_MIN_BYTES non-ws bytes between marker and next ##.
|
|
998
|
+
"""
|
|
999
|
+
if not prompt:
|
|
1000
|
+
return False
|
|
1001
|
+
return _has_skill_content_sanitized(_strip_fenced_and_comments(prompt))
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
def _has_skill_content_sanitized(sanitized: str) -> bool:
|
|
1005
|
+
"""Shared ``## SKILL CONTENT`` check against already-sanitized text.
|
|
1006
|
+
|
|
1007
|
+
PLAN-023 Phase F (F-perf-003) — split out so ``decide`` and other
|
|
1008
|
+
callers can share a single ``_strip_fenced_and_comments`` pass
|
|
1009
|
+
rather than re-stripping inside each sub-check.
|
|
1010
|
+
"""
|
|
1011
|
+
match = _SKILL_CONTENT_MARKER_RE.search(sanitized)
|
|
1012
|
+
if match is None:
|
|
1013
|
+
return False
|
|
1014
|
+
body_start = match.end()
|
|
1015
|
+
next_heading = _NEXT_H2_RE.search(sanitized, body_start)
|
|
1016
|
+
body_end = next_heading.start() if next_heading else len(sanitized)
|
|
1017
|
+
body = sanitized[body_start:body_end]
|
|
1018
|
+
non_ws_bytes = sum(1 for c in body if not c.isspace())
|
|
1019
|
+
return non_ws_bytes >= _SKILL_CONTENT_MIN_BYTES
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
# =============================================================================
|
|
1023
|
+
# PLAN-020 Phase 2 — `## SKILL REFERENCE` additive sentinel (ADR-051)
|
|
1024
|
+
# =============================================================================
|
|
1025
|
+
#
|
|
1026
|
+
# Parallel accept-path. Does NOT replace `_has_skill_content`. Hardened
|
|
1027
|
+
# against 14 attack classes documented in ADR-051 §Threat model.
|
|
1028
|
+
#
|
|
1029
|
+
# Hook recognition order in decide():
|
|
1030
|
+
# 1. If prompt has `## SKILL REFERENCE` → strict validation, fail-CLOSED.
|
|
1031
|
+
# 2. Else if prompt has `## SKILL CONTENT` → inline path (existing P1-SEC-B).
|
|
1032
|
+
# 3. Else block with the legacy `missing_skill_content` reason.
|
|
1033
|
+
|
|
1034
|
+
# Module-level toggles (Phase revert = single-line flip to False).
|
|
1035
|
+
ENABLE_NATIVE_SUBAGENTS = True
|
|
1036
|
+
ENABLE_SKILL_REFERENCE_MODE = True
|
|
1037
|
+
|
|
1038
|
+
# Reference sentinel header — must be on own line, not inside fences.
|
|
1039
|
+
_SKILL_REFERENCE_HEADER_RE = re.compile(
|
|
1040
|
+
r"^##[ \t]+SKILL[ \t]+REFERENCE[ \t]*$",
|
|
1041
|
+
flags=re.MULTILINE,
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
# Reference body line: `@<path> sha256=<64-hex>`. Path may be relative.
|
|
1045
|
+
_SKILL_REFERENCE_LINE_RE = re.compile(
|
|
1046
|
+
r"^@(?P<path>\S+)[ \t]+sha256=(?P<hash>[0-9a-f]{64})\b",
|
|
1047
|
+
flags=re.MULTILINE,
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
# Caps + thresholds (ADR-051 §Synchronous validation).
|
|
1051
|
+
_SKILL_REFERENCE_MAX_BYTES = 1_048_576 # 1 MiB DoS cap
|
|
1052
|
+
_SKILL_REFERENCE_MIN_BODY_BYTES = 512 # distinct from inline 256 floor
|
|
1053
|
+
|
|
1054
|
+
# 12 telemetry reason codes (ADR-051 §Telemetry reason codes).
|
|
1055
|
+
REASON_REFERENCE_MISSING = "reference_missing"
|
|
1056
|
+
REASON_REFERENCE_UNSAFE_PATH = "reference_unsafe_path"
|
|
1057
|
+
REASON_REFERENCE_HASH_MISMATCH = "reference_hash_mismatch"
|
|
1058
|
+
REASON_REFERENCE_SYMLINK_REFUSED = "reference_symlink_refused"
|
|
1059
|
+
REASON_REFERENCE_TOO_LARGE = "reference_too_large"
|
|
1060
|
+
REASON_REFERENCE_REDACTION_HIT = "reference_redaction_hit"
|
|
1061
|
+
REASON_REFERENCE_WRONG_FILENAME = "reference_wrong_filename"
|
|
1062
|
+
REASON_REFERENCE_MISSING_FRONTMATTER = "reference_missing_frontmatter"
|
|
1063
|
+
REASON_REFERENCE_BYTE_FLOOR_UNDERFLOW = "reference_byte_floor_underflow"
|
|
1064
|
+
REASON_REFERENCE_UNICODE_NORMALIZATION_MISMATCH = (
|
|
1065
|
+
"reference_unicode_normalization_mismatch"
|
|
1066
|
+
)
|
|
1067
|
+
REASON_REFERENCE_OUTSIDE_SKILLS_ROOT = "reference_outside_skills_root"
|
|
1068
|
+
REASON_MISSING_SKILL_CONTENT = "missing_skill_content" # legacy/inline
|
|
1069
|
+
|
|
1070
|
+
# Session 32 BUGFIX — _lib.redact.redact_secrets whitespace-collapses
|
|
1071
|
+
# always; equality check with original text is not a valid "secret
|
|
1072
|
+
# detected" signal. Instead we look for specific replacement tokens
|
|
1073
|
+
# that `_redact._PATTERNS` inserts when a secret matches.
|
|
1074
|
+
_SECRET_TOKENS_IN_OUTPUT = frozenset({
|
|
1075
|
+
"[JWT]",
|
|
1076
|
+
"[API_KEY]",
|
|
1077
|
+
"[GITHUB_PAT]",
|
|
1078
|
+
"[AWS_KEY]",
|
|
1079
|
+
"[TOKEN]",
|
|
1080
|
+
"[URL_WITH_CREDS]",
|
|
1081
|
+
"[HEX_SECRET]",
|
|
1082
|
+
"[REDACTED]",
|
|
1083
|
+
"[SLACK_BOT]",
|
|
1084
|
+
"[STRIPE_KEY]",
|
|
1085
|
+
"[GOOGLE_REFRESH]",
|
|
1086
|
+
# PLAN-024 F-hooks-001 P0 fix: redact.py replaces the PEM header
|
|
1087
|
+
# with "[SSH_PRIVATE_KEY_HEADER]" — matching raw "-----BEGIN" will
|
|
1088
|
+
# never fire after redaction. Check the replacement token instead.
|
|
1089
|
+
"[SSH_PRIVATE_KEY_HEADER]",
|
|
1090
|
+
})
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
def _is_enabled(env_var: str, default: bool, env: Optional[dict] = None) -> bool:
|
|
1094
|
+
"""Live read of an env-var toggle with a module default fallback.
|
|
1095
|
+
|
|
1096
|
+
Allows runtime opt-out via env (`CEO_SKILL_REFERENCE_MODE=0`,
|
|
1097
|
+
`CEO_NATIVE_SUBAGENTS=0`) without restarting Claude Code.
|
|
1098
|
+
`CEO_SOTA_DISABLE=1` is the master kill — overrides everything.
|
|
1099
|
+
"""
|
|
1100
|
+
src_env = env if env is not None else os.environ
|
|
1101
|
+
if (src_env.get("CEO_SOTA_DISABLE") or "").strip() == "1":
|
|
1102
|
+
return False
|
|
1103
|
+
raw = (src_env.get(env_var) or "").strip()
|
|
1104
|
+
if raw == "":
|
|
1105
|
+
return default
|
|
1106
|
+
return raw not in ("0", "false", "FALSE", "False", "no", "off")
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
def _has_valid_frontmatter_with_name(text: str) -> bool:
|
|
1110
|
+
"""Stdlib-only frontmatter parser (no PyYAML).
|
|
1111
|
+
|
|
1112
|
+
Returns True iff the file starts with `---` on first line, has a
|
|
1113
|
+
closing `---` line, and has at least one `name: <value>` line.
|
|
1114
|
+
"""
|
|
1115
|
+
if not text.startswith("---\n") and not text.startswith("---\r\n"):
|
|
1116
|
+
return False
|
|
1117
|
+
body_start = 4 if text.startswith("---\n") else 5
|
|
1118
|
+
closing = text.find("\n---", body_start)
|
|
1119
|
+
if closing == -1:
|
|
1120
|
+
return False
|
|
1121
|
+
fm_block = text[body_start:closing]
|
|
1122
|
+
return bool(re.search(r"^name:\s*\S+", fm_block, flags=re.MULTILINE))
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
def _skill_ref_parse_sentinel(
|
|
1126
|
+
sanitized: str,
|
|
1127
|
+
) -> Tuple[Optional[Tuple[bool, Optional[str], Optional[str]]], Optional[str], Optional[str]]:
|
|
1128
|
+
"""Sub-checks 1-2: locate `## SKILL REFERENCE` header + body.
|
|
1129
|
+
|
|
1130
|
+
Returns ``(error_tuple, raw_path, expected_hash)``. On failure,
|
|
1131
|
+
``error_tuple`` is populated and the other fields are None.
|
|
1132
|
+
"""
|
|
1133
|
+
if not _SKILL_REFERENCE_HEADER_RE.search(sanitized):
|
|
1134
|
+
return (
|
|
1135
|
+
(False, REASON_REFERENCE_MISSING, "no `## SKILL REFERENCE` header"),
|
|
1136
|
+
None,
|
|
1137
|
+
None,
|
|
1138
|
+
)
|
|
1139
|
+
body_match = _SKILL_REFERENCE_LINE_RE.search(sanitized)
|
|
1140
|
+
if not body_match:
|
|
1141
|
+
return (
|
|
1142
|
+
(
|
|
1143
|
+
False,
|
|
1144
|
+
REASON_REFERENCE_MISSING,
|
|
1145
|
+
"header found but no `@<path> sha256=<hex>` body",
|
|
1146
|
+
),
|
|
1147
|
+
None,
|
|
1148
|
+
None,
|
|
1149
|
+
)
|
|
1150
|
+
return None, body_match.group("path"), body_match.group("hash").lower()
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
def _skill_ref_resolve_path(
|
|
1154
|
+
raw_path: str,
|
|
1155
|
+
repo_root: Path,
|
|
1156
|
+
) -> Tuple[Optional[Tuple[bool, Optional[str], Optional[str]]], Optional[Path], Optional[Path]]:
|
|
1157
|
+
"""Sub-checks 3-5: resolve path, confirm under skills root, filename = SKILL.md, not symlink.
|
|
1158
|
+
|
|
1159
|
+
Returns ``(error_tuple, candidate, resolved)``. On failure, the
|
|
1160
|
+
latter two are None.
|
|
1161
|
+
"""
|
|
1162
|
+
skills_root = (repo_root / ".claude" / "skills").resolve()
|
|
1163
|
+
candidate = Path(raw_path)
|
|
1164
|
+
if not candidate.is_absolute():
|
|
1165
|
+
candidate = repo_root / raw_path
|
|
1166
|
+
try:
|
|
1167
|
+
resolved = candidate.resolve(strict=True)
|
|
1168
|
+
except (FileNotFoundError, OSError) as exc:
|
|
1169
|
+
return (
|
|
1170
|
+
(False, REASON_REFERENCE_MISSING, f"resolve failed: {exc}"),
|
|
1171
|
+
None,
|
|
1172
|
+
None,
|
|
1173
|
+
)
|
|
1174
|
+
try:
|
|
1175
|
+
resolved.relative_to(skills_root)
|
|
1176
|
+
except ValueError:
|
|
1177
|
+
return (
|
|
1178
|
+
(
|
|
1179
|
+
False,
|
|
1180
|
+
REASON_REFERENCE_OUTSIDE_SKILLS_ROOT,
|
|
1181
|
+
f"{resolved} not under {skills_root}",
|
|
1182
|
+
),
|
|
1183
|
+
None,
|
|
1184
|
+
None,
|
|
1185
|
+
)
|
|
1186
|
+
if resolved.name != "SKILL.md":
|
|
1187
|
+
return (
|
|
1188
|
+
(
|
|
1189
|
+
False,
|
|
1190
|
+
REASON_REFERENCE_WRONG_FILENAME,
|
|
1191
|
+
f"filename is {resolved.name!r}, expected 'SKILL.md'",
|
|
1192
|
+
),
|
|
1193
|
+
None,
|
|
1194
|
+
None,
|
|
1195
|
+
)
|
|
1196
|
+
if candidate.is_symlink():
|
|
1197
|
+
return (
|
|
1198
|
+
(
|
|
1199
|
+
False,
|
|
1200
|
+
REASON_REFERENCE_SYMLINK_REFUSED,
|
|
1201
|
+
f"{candidate} is a symlink",
|
|
1202
|
+
),
|
|
1203
|
+
None,
|
|
1204
|
+
None,
|
|
1205
|
+
)
|
|
1206
|
+
return None, candidate, resolved
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
def _skill_ref_validate_unicode(
|
|
1210
|
+
candidate: Path,
|
|
1211
|
+
) -> Optional[Tuple[bool, Optional[str], Optional[str]]]:
|
|
1212
|
+
"""Sub-check 6: NFC unicode normalization. Returns error_tuple or None."""
|
|
1213
|
+
raw_str = str(candidate)
|
|
1214
|
+
nfc_str = unicodedata.normalize("NFC", raw_str)
|
|
1215
|
+
if nfc_str != raw_str:
|
|
1216
|
+
return (
|
|
1217
|
+
False,
|
|
1218
|
+
REASON_REFERENCE_UNICODE_NORMALIZATION_MISMATCH,
|
|
1219
|
+
"path not NFC-normalized",
|
|
1220
|
+
)
|
|
1221
|
+
return None
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
def _skill_ref_read_bounded(
|
|
1225
|
+
resolved: Path,
|
|
1226
|
+
) -> Tuple[Optional[Tuple[bool, Optional[str], Optional[str]]], Optional[bytes], Optional[str]]:
|
|
1227
|
+
"""Sub-checks 7-8: size cap + body floor on non-ws bytes.
|
|
1228
|
+
|
|
1229
|
+
Returns ``(error_tuple, content_bytes, decoded_text)``.
|
|
1230
|
+
"""
|
|
1231
|
+
try:
|
|
1232
|
+
size = resolved.stat().st_size
|
|
1233
|
+
except OSError as exc:
|
|
1234
|
+
return (
|
|
1235
|
+
(False, REASON_REFERENCE_MISSING, f"stat failed: {exc}"),
|
|
1236
|
+
None,
|
|
1237
|
+
None,
|
|
1238
|
+
)
|
|
1239
|
+
if size > _SKILL_REFERENCE_MAX_BYTES:
|
|
1240
|
+
return (
|
|
1241
|
+
(
|
|
1242
|
+
False,
|
|
1243
|
+
REASON_REFERENCE_TOO_LARGE,
|
|
1244
|
+
f"{size} bytes > {_SKILL_REFERENCE_MAX_BYTES} cap",
|
|
1245
|
+
),
|
|
1246
|
+
None,
|
|
1247
|
+
None,
|
|
1248
|
+
)
|
|
1249
|
+
try:
|
|
1250
|
+
content_bytes = resolved.read_bytes()
|
|
1251
|
+
except OSError as exc:
|
|
1252
|
+
return (
|
|
1253
|
+
(False, REASON_REFERENCE_MISSING, f"read failed: {exc}"),
|
|
1254
|
+
None,
|
|
1255
|
+
None,
|
|
1256
|
+
)
|
|
1257
|
+
try:
|
|
1258
|
+
text = content_bytes.decode("utf-8")
|
|
1259
|
+
except UnicodeDecodeError as exc:
|
|
1260
|
+
return (
|
|
1261
|
+
(False, REASON_REFERENCE_MISSING_FRONTMATTER, f"decode: {exc}"),
|
|
1262
|
+
None,
|
|
1263
|
+
None,
|
|
1264
|
+
)
|
|
1265
|
+
non_ws = sum(1 for c in text if not c.isspace())
|
|
1266
|
+
if non_ws < _SKILL_REFERENCE_MIN_BODY_BYTES:
|
|
1267
|
+
return (
|
|
1268
|
+
(
|
|
1269
|
+
False,
|
|
1270
|
+
REASON_REFERENCE_BYTE_FLOOR_UNDERFLOW,
|
|
1271
|
+
f"{non_ws} non-ws bytes < {_SKILL_REFERENCE_MIN_BODY_BYTES} floor",
|
|
1272
|
+
),
|
|
1273
|
+
None,
|
|
1274
|
+
None,
|
|
1275
|
+
)
|
|
1276
|
+
return None, content_bytes, text
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def _skill_ref_validate_frontmatter(
|
|
1280
|
+
text: str,
|
|
1281
|
+
) -> Optional[Tuple[bool, Optional[str], Optional[str]]]:
|
|
1282
|
+
"""Sub-check 9: YAML frontmatter parseable + has `name:` key."""
|
|
1283
|
+
if not _has_valid_frontmatter_with_name(text):
|
|
1284
|
+
return (
|
|
1285
|
+
False,
|
|
1286
|
+
REASON_REFERENCE_MISSING_FRONTMATTER,
|
|
1287
|
+
"missing or malformed YAML frontmatter / no `name:` key",
|
|
1288
|
+
)
|
|
1289
|
+
return None
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
def _skill_ref_validate_hash_and_redact(
|
|
1293
|
+
content_bytes: bytes,
|
|
1294
|
+
text: str,
|
|
1295
|
+
expected_hash: str,
|
|
1296
|
+
) -> Optional[Tuple[bool, Optional[str], Optional[str]]]:
|
|
1297
|
+
"""Sub-checks 10-11: SHA-256 match + redaction scan.
|
|
1298
|
+
|
|
1299
|
+
Per PLAN-025 F-perf-006: SHA-256 on reference-mode skill content
|
|
1300
|
+
costs ~1ms for a 5KB skill per invocation. Acceptable at current
|
|
1301
|
+
spawn volumes.
|
|
1302
|
+
|
|
1303
|
+
Per Session 32 bugfix: `_redact.redact_secrets` whitespace-collapses
|
|
1304
|
+
output, so direct string compare would always mismatch. Instead
|
|
1305
|
+
we check for the specific secret-indicator tokens that only appear
|
|
1306
|
+
when a pattern matched.
|
|
1307
|
+
"""
|
|
1308
|
+
actual_hash = hashlib.sha256(content_bytes).hexdigest()
|
|
1309
|
+
if actual_hash != expected_hash:
|
|
1310
|
+
return (
|
|
1311
|
+
False,
|
|
1312
|
+
REASON_REFERENCE_HASH_MISMATCH,
|
|
1313
|
+
f"expected {expected_hash[:8]}..., got {actual_hash[:8]}...",
|
|
1314
|
+
)
|
|
1315
|
+
redacted = _redact.redact_secrets(text)
|
|
1316
|
+
if any(tok in redacted for tok in _SECRET_TOKENS_IN_OUTPUT):
|
|
1317
|
+
return (
|
|
1318
|
+
False,
|
|
1319
|
+
REASON_REFERENCE_REDACTION_HIT,
|
|
1320
|
+
"secret pattern detected in skill content",
|
|
1321
|
+
)
|
|
1322
|
+
return None
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
def _validate_skill_reference(
|
|
1326
|
+
prompt: str,
|
|
1327
|
+
repo_root: Optional[Path] = None,
|
|
1328
|
+
) -> Tuple[bool, Optional[str], Optional[str]]:
|
|
1329
|
+
"""Validate `## SKILL REFERENCE` sentinel via 11 synchronous sub-checks.
|
|
1330
|
+
|
|
1331
|
+
Refactored (PLAN-050 Phase 1b F-02-03-e) into 6 named helpers for
|
|
1332
|
+
readability + per-check test coverage. Same contract: fail-CLOSED
|
|
1333
|
+
on any failure, sub-check order preserved from ADR-051 §Synchronous
|
|
1334
|
+
validation (sub-check 11 = redaction scan added Session 32;
|
|
1335
|
+
PLAN-024 F-hooks-003 doc drift close).
|
|
1336
|
+
"""
|
|
1337
|
+
if not prompt or not isinstance(prompt, str):
|
|
1338
|
+
return False, REASON_REFERENCE_MISSING, "empty prompt"
|
|
1339
|
+
|
|
1340
|
+
sanitized = _strip_fenced_and_comments(prompt)
|
|
1341
|
+
|
|
1342
|
+
err, raw_path, expected_hash = _skill_ref_parse_sentinel(sanitized)
|
|
1343
|
+
if err:
|
|
1344
|
+
return err
|
|
1345
|
+
|
|
1346
|
+
if repo_root is None:
|
|
1347
|
+
repo_root = Path(
|
|
1348
|
+
os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
|
1349
|
+
)
|
|
1350
|
+
|
|
1351
|
+
err, candidate, resolved = _skill_ref_resolve_path(raw_path, repo_root)
|
|
1352
|
+
if err:
|
|
1353
|
+
return err
|
|
1354
|
+
|
|
1355
|
+
err = _skill_ref_validate_unicode(candidate)
|
|
1356
|
+
if err:
|
|
1357
|
+
return err
|
|
1358
|
+
|
|
1359
|
+
err, content_bytes, text = _skill_ref_read_bounded(resolved)
|
|
1360
|
+
if err:
|
|
1361
|
+
return err
|
|
1362
|
+
|
|
1363
|
+
err = _skill_ref_validate_frontmatter(text)
|
|
1364
|
+
if err:
|
|
1365
|
+
return err
|
|
1366
|
+
|
|
1367
|
+
err = _skill_ref_validate_hash_and_redact(
|
|
1368
|
+
content_bytes, text, expected_hash
|
|
1369
|
+
)
|
|
1370
|
+
if err:
|
|
1371
|
+
return err
|
|
1372
|
+
|
|
1373
|
+
return True, None, None
|
|
1374
|
+
|
|
1375
|
+
|
|
1376
|
+
def _has_skill_reference(
|
|
1377
|
+
prompt: str,
|
|
1378
|
+
repo_root: Optional[Path] = None,
|
|
1379
|
+
) -> bool:
|
|
1380
|
+
"""Backward-compat wrapper: True iff reference passes all sub-checks."""
|
|
1381
|
+
ok, _, _ = _validate_skill_reference(prompt, repo_root=repo_root)
|
|
1382
|
+
return ok
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
# =============================================================================
|
|
1386
|
+
# PLAN-020 Phase 3 — `/effort` token rejection in spawn prompts
|
|
1387
|
+
# =============================================================================
|
|
1388
|
+
#
|
|
1389
|
+
# `/effort` hints (low/default/high/max + `ultrathink` keyword) are
|
|
1390
|
+
# CEO-only. They MUST NOT appear in spawn prompts (sub-agents inherit
|
|
1391
|
+
# their thinking budget from Anthropic defaults). PLAN-020 §4 Phase 3
|
|
1392
|
+
# §scope clause (QA must-fix #7).
|
|
1393
|
+
|
|
1394
|
+
_EFFORT_TOKEN_RE = re.compile(
|
|
1395
|
+
r"(?:^|[^\w/])/effort(?:[ \t]+(?:low|default|high|max))?\b",
|
|
1396
|
+
flags=re.IGNORECASE,
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
def _has_effort_token(prompt: str) -> bool:
|
|
1401
|
+
"""True iff prompt contains a `/effort [tier]` slash-command token."""
|
|
1402
|
+
if not prompt:
|
|
1403
|
+
return False
|
|
1404
|
+
sanitized = _strip_fenced_and_comments(prompt)
|
|
1405
|
+
return bool(_EFFORT_TOKEN_RE.search(sanitized))
|
|
1406
|
+
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
# ---------------------------------------------------------------------------
|
|
1410
|
+
# PLAN-045 F-10-04 — spawn-prompt secret-scan pre-dispatch
|
|
1411
|
+
# ---------------------------------------------------------------------------
|
|
1412
|
+
# Spawn prompts are the #1 vector for accidentally shipping secrets into a
|
|
1413
|
+
# sub-agent's context. A CEO that copy-pastes a `.env` into a task prompt
|
|
1414
|
+
# or builds a prompt that interpolates an env var leaks the secret to
|
|
1415
|
+
# whatever model the sub-agent uses AND to the audit log (spawn prompts
|
|
1416
|
+
# are recorded for governance).
|
|
1417
|
+
#
|
|
1418
|
+
# Reject spawns whose prompt contains any of 6 high-signal secret shapes.
|
|
1419
|
+
# Each shape is a narrow regex to avoid false-positives on docs / test
|
|
1420
|
+
# fixtures (prefix-bound + entropy-adjacent). Audit emits
|
|
1421
|
+
# `veto_triggered(reason_code=spawn_prompt_contains_secret)` on reject.
|
|
1422
|
+
#
|
|
1423
|
+
# Kill-switch: `CEO_SPAWN_SECRET_SCAN=0` for the rare case where a prompt
|
|
1424
|
+
# legitimately contains a long opaque id that trips the scan; Owner-only
|
|
1425
|
+
# escape logged via veto_triggered(reason_code=spawn_secret_scan_bypassed).
|
|
1426
|
+
_SPAWN_SECRET_PATTERNS: List[Tuple[str, re.Pattern[str]]] = [
|
|
1427
|
+
# AWS access key ID (AKIA prefix, 16 uppercase alnum after).
|
|
1428
|
+
("aws_access_key_id", re.compile(r"\bAKIA[0-9A-Z]{16}\b")),
|
|
1429
|
+
# Generic AWS secret access key.
|
|
1430
|
+
(
|
|
1431
|
+
"aws_secret_access_key",
|
|
1432
|
+
re.compile(
|
|
1433
|
+
r"\b(?:aws[_-]?secret[_-]?access[_-]?key)\b\s*[:=]\s*"
|
|
1434
|
+
r"[\"\']?[A-Za-z0-9/+=]{40}[\"\']?",
|
|
1435
|
+
re.IGNORECASE,
|
|
1436
|
+
),
|
|
1437
|
+
),
|
|
1438
|
+
# Stripe live/test secret key.
|
|
1439
|
+
("stripe_secret_key", re.compile(r"\bsk_(?:live|test)_[0-9A-Za-z]{24,}\b")),
|
|
1440
|
+
# GitHub personal access token.
|
|
1441
|
+
("github_pat", re.compile(r"\bghp_[0-9A-Za-z]{36,}\b")),
|
|
1442
|
+
# OpenAI API key.
|
|
1443
|
+
("openai_api_key", re.compile(r"\bsk-[A-Za-z0-9]{48,}\b")),
|
|
1444
|
+
# PEM private key preamble.
|
|
1445
|
+
(
|
|
1446
|
+
"pem_private_key",
|
|
1447
|
+
re.compile(
|
|
1448
|
+
r"-----BEGIN (?:RSA |EC |OPENSSH |PGP |DSA )?PRIVATE KEY-----"
|
|
1449
|
+
),
|
|
1450
|
+
),
|
|
1451
|
+
]
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
def _inject_terse_marker(prompt: str, subagent_type: str) -> str:
|
|
1456
|
+
"""Inject ## TERSE-MODE marker per PLAN-047 Phase 2."""
|
|
1457
|
+
if os.environ.get("CEO_TERSE_MODE", "0") != "1":
|
|
1458
|
+
return prompt
|
|
1459
|
+
veto_roles = {"code-reviewer", "security-engineer", "qa-architect", "compliance-specialist"}
|
|
1460
|
+
if (subagent_type or "") in veto_roles:
|
|
1461
|
+
return prompt + "\n\n## TERSE-MODE-DISABLED — VETO role requires full-prose rationale.\n"
|
|
1462
|
+
return prompt + "\n\n## TERSE-MODE — fragments OK in exploratory flows; never truncate code or numbers.\n"
|
|
1463
|
+
|
|
1464
|
+
def _validate_spawn_prompt_has_no_secrets(
|
|
1465
|
+
prompt: str,
|
|
1466
|
+
) -> Tuple[bool, Optional[str], Optional[str]]:
|
|
1467
|
+
"""Return (ok, reason_code, detail) — ok=False means BLOCK spawn.
|
|
1468
|
+
|
|
1469
|
+
Short-circuits with ok=True when `CEO_SPAWN_SECRET_SCAN=0` is set
|
|
1470
|
+
(Owner bypass; any non-"0" value enforces).
|
|
1471
|
+
"""
|
|
1472
|
+
# Session 75 Codex Finding 4 closure (Owner D4 staged rollout):
|
|
1473
|
+
# default OFF, opt-in via CEO_SPAWN_SECRET_SCAN=1. Without the env
|
|
1474
|
+
# var (or with =0), function returns ok=True without scanning. Soak
|
|
1475
|
+
# protocol: ≥3 sessions of opt-in usage with FPR<1%% before any
|
|
1476
|
+
# default flip ADR. Until then, this is a wire-only landing.
|
|
1477
|
+
if os.environ.get("CEO_SPAWN_SECRET_SCAN", "0") != "1":
|
|
1478
|
+
return True, None, None
|
|
1479
|
+
if not prompt:
|
|
1480
|
+
return True, None, None
|
|
1481
|
+
for family, pattern in _SPAWN_SECRET_PATTERNS:
|
|
1482
|
+
m = pattern.search(prompt)
|
|
1483
|
+
if m:
|
|
1484
|
+
return (
|
|
1485
|
+
False,
|
|
1486
|
+
"spawn_prompt_contains_secret",
|
|
1487
|
+
f"family={family} prompt_len={len(prompt)}",
|
|
1488
|
+
)
|
|
1489
|
+
return True, None, None
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
@dataclass
|
|
1493
|
+
class Decision:
|
|
1494
|
+
"""Typed result of the governance check."""
|
|
1495
|
+
|
|
1496
|
+
allow: bool
|
|
1497
|
+
reason: Optional[str] = None
|
|
1498
|
+
|
|
1499
|
+
def to_json(self) -> str:
|
|
1500
|
+
if self.allow:
|
|
1501
|
+
return json.dumps({}, ensure_ascii=False) # schema-compliant allow
|
|
1502
|
+
return json.dumps(
|
|
1503
|
+
{"decision": "block", "reason": self.reason or ""},
|
|
1504
|
+
ensure_ascii=False,
|
|
1505
|
+
)
|
|
1506
|
+
|
|
1507
|
+
|
|
1508
|
+
# Sprint 5 Phase 7 (ADR-010) — Architect recursion guard.
|
|
1509
|
+
# When CEO_ARCHITECT_ACTIVE=1 is in the env, any spawn whose
|
|
1510
|
+
# description/prompt names "Agent Architect" is BLOCKED. Prevents
|
|
1511
|
+
# meta-agent recursion (Architect-spawning-Architect).
|
|
1512
|
+
_ARCHITECT_NAME_RE = re.compile(
|
|
1513
|
+
r"\bAgent\s+Architect\b",
|
|
1514
|
+
flags=re.IGNORECASE,
|
|
1515
|
+
)
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
# ---------------------------------------------------------------------------
|
|
1519
|
+
# PLAN-133 E3 — Per-spawn tool scoping + depth/overlap rails.
|
|
1520
|
+
# ---------------------------------------------------------------------------
|
|
1521
|
+
# Ports the Goose recipe "declared capability scope + one-level recursion +
|
|
1522
|
+
# file partition" idea, re-implemented from scratch in stdlib (rite §2). All
|
|
1523
|
+
# three rails are ADVISORY by default (emit only) and become BLOCKING only
|
|
1524
|
+
# under their per-rail env flag. NEVER raises (fail-open per CLAUDE.md §5).
|
|
1525
|
+
#
|
|
1526
|
+
# Rail 1 — Tool allow-list: a spawn profile MAY declare
|
|
1527
|
+
# ## TOOL ALLOW-LIST
|
|
1528
|
+
# - Read
|
|
1529
|
+
# - Edit
|
|
1530
|
+
# - Bash
|
|
1531
|
+
# When declared AND the prompt's ## TASK/## RESTRICTIONS text requests a tool
|
|
1532
|
+
# OUTSIDE that set (heuristic keyword scan, bounded), emit/block. A profile
|
|
1533
|
+
# with NO ## TOOL ALLOW-LIST block is unrestricted (back-compat: silent allow).
|
|
1534
|
+
#
|
|
1535
|
+
# Rail 2 — Depth fence: a sub-agent that itself emits a NAMED spawn is a
|
|
1536
|
+
# depth-2 spawn. The harness Agent payload has no native depth field, so we
|
|
1537
|
+
# read the advisory marker the CEO injects into a delegated prompt
|
|
1538
|
+
# (## SUBAGENT-CONTEXT depth=N) AND the env breadcrumb CEO_SPAWN_DEPTH. If
|
|
1539
|
+
# EITHER says depth>=1 AND this spawn is itself NAMED, it is depth-over-one.
|
|
1540
|
+
#
|
|
1541
|
+
# Rail 3 — FILE ASSIGNMENT overlap: parse this spawn's `CAN edit:` file list
|
|
1542
|
+
# from its ## FILE ASSIGNMENT block, compare against the `CAN edit:` lists of
|
|
1543
|
+
# other spawns recorded in the audit log within a short concurrency window
|
|
1544
|
+
# (same session, same plan). Any shared concrete path = clobber risk.
|
|
1545
|
+
|
|
1546
|
+
# --- Rail 1: tool allow-list ----------------------------------------------
|
|
1547
|
+
_TOOL_ALLOWLIST_HEADER_RE = re.compile(
|
|
1548
|
+
r"^##[ \t]+TOOL[ \t]+ALLOW-?LIST[ \t]*$",
|
|
1549
|
+
flags=re.MULTILINE,
|
|
1550
|
+
)
|
|
1551
|
+
# A bullet line inside the block: "- Read" / "* Bash" / "Read,Edit".
|
|
1552
|
+
_TOOL_BULLET_RE = re.compile(
|
|
1553
|
+
r"^[ \t]*[-*][ \t]*([A-Za-z][A-Za-z0-9_/ ,]+)$",
|
|
1554
|
+
flags=re.MULTILINE,
|
|
1555
|
+
)
|
|
1556
|
+
# Canonical Claude Code tool names we recognize (closed set — anything not
|
|
1557
|
+
# here in a request is ignored, NOT blocked; we only gate KNOWN tools so an
|
|
1558
|
+
# unknown token can never cause a false block).
|
|
1559
|
+
_KNOWN_TOOL_NAMES = frozenset({
|
|
1560
|
+
"read", "edit", "write", "multiedit", "bash", "glob", "grep",
|
|
1561
|
+
"webfetch", "websearch", "task", "agent", "notebookedit",
|
|
1562
|
+
})
|
|
1563
|
+
# Heuristic: which tools a spawn is REQUESTING, inferred from prompt text.
|
|
1564
|
+
# First-hit, bounded scan. Conservative — only fires on high-signal verbs so
|
|
1565
|
+
# a profile that simply mentions a word does not trip the rail.
|
|
1566
|
+
_TOOL_REQUEST_HINTS: Tuple[Tuple[str, Tuple[str, ...]], ...] = (
|
|
1567
|
+
("bash", ("run the command", "run `", "execute the script", "shell out",
|
|
1568
|
+
"git commit", "git push", "npm install", "curl ", "subprocess")),
|
|
1569
|
+
("write", ("create the file", "write a new file", "write the file")),
|
|
1570
|
+
("edit", ("edit the file", "modify the file", "apply the patch")),
|
|
1571
|
+
("webfetch", ("fetch the url", "download from http", "fetch http")),
|
|
1572
|
+
("websearch", ("search the web", "web search")),
|
|
1573
|
+
)
|
|
1574
|
+
_TOOL_SCAN_WINDOW = 8 * 1024 # bytes of prompt scanned for tool hints
|
|
1575
|
+
|
|
1576
|
+
# --- Rail 2: depth fence ---------------------------------------------------
|
|
1577
|
+
_SUBAGENT_DEPTH_MARKER_RE = re.compile(
|
|
1578
|
+
r"^##[ \t]+SUBAGENT-CONTEXT\b[^\n]*\bdepth=(\d+)",
|
|
1579
|
+
flags=re.MULTILINE,
|
|
1580
|
+
)
|
|
1581
|
+
|
|
1582
|
+
# --- Rail 3: FILE ASSIGNMENT overlap --------------------------------------
|
|
1583
|
+
_FILE_ASSIGNMENT_HEADER_RE = re.compile(
|
|
1584
|
+
r"^##[ \t]+FILE[ \t]+ASSIGNMENT[ \t]*$",
|
|
1585
|
+
flags=re.MULTILINE,
|
|
1586
|
+
)
|
|
1587
|
+
# "- CAN edit: a/b.py, c/d.py" (rest of line after the colon, comma-split).
|
|
1588
|
+
_CAN_EDIT_LINE_RE = re.compile(
|
|
1589
|
+
r"^[ \t]*[-*][ \t]*CAN[ \t]+edit:[ \t]*(.+)$",
|
|
1590
|
+
flags=re.MULTILINE | re.IGNORECASE,
|
|
1591
|
+
)
|
|
1592
|
+
_OVERLAP_LOOKBACK_S = 600 # 10-minute concurrency window
|
|
1593
|
+
_OVERLAP_MAX_PATHS = 64 # bound the per-spawn path set
|
|
1594
|
+
_OVERLAP_TAIL_LINES = 256 # bounded audit-log tail (~512KB)
|
|
1595
|
+
|
|
1596
|
+
|
|
1597
|
+
def _parse_tool_allowlist(prompt: str) -> Optional[frozenset]:
|
|
1598
|
+
"""Return the declared lowercase tool allow-list, or None if no
|
|
1599
|
+
`## TOOL ALLOW-LIST` block is present (= unrestricted, back-compat).
|
|
1600
|
+
|
|
1601
|
+
Pure. Never raises. Bounded to the block between its header and the next
|
|
1602
|
+
`##` heading. Only KNOWN tool names are retained; unknown tokens dropped
|
|
1603
|
+
(so a typo cannot create a phantom allow-list that blocks everything).
|
|
1604
|
+
"""
|
|
1605
|
+
if not prompt:
|
|
1606
|
+
return None
|
|
1607
|
+
sanitized = _strip_fenced_and_comments(prompt)
|
|
1608
|
+
m = _TOOL_ALLOWLIST_HEADER_RE.search(sanitized)
|
|
1609
|
+
if m is None:
|
|
1610
|
+
return None
|
|
1611
|
+
block_start = m.end()
|
|
1612
|
+
nxt = _NEXT_H2_RE.search(sanitized, block_start)
|
|
1613
|
+
block = sanitized[block_start: nxt.start() if nxt else len(sanitized)]
|
|
1614
|
+
allowed = set()
|
|
1615
|
+
for bm in _TOOL_BULLET_RE.finditer(block):
|
|
1616
|
+
for tok in bm.group(1).replace(",", " ").split():
|
|
1617
|
+
t = tok.strip().lower()
|
|
1618
|
+
if t in _KNOWN_TOOL_NAMES:
|
|
1619
|
+
allowed.add(t)
|
|
1620
|
+
# An empty-but-present block = "deny all known tools" (intentional).
|
|
1621
|
+
return frozenset(allowed)
|
|
1622
|
+
|
|
1623
|
+
|
|
1624
|
+
def _requested_tools(prompt: str) -> frozenset:
|
|
1625
|
+
"""Heuristic set of KNOWN tools this spawn appears to request.
|
|
1626
|
+
|
|
1627
|
+
First-hit per family, bounded scan. Conservative (high-signal verbs only).
|
|
1628
|
+
Pure; never raises.
|
|
1629
|
+
"""
|
|
1630
|
+
if not prompt:
|
|
1631
|
+
return frozenset()
|
|
1632
|
+
text = _strip_fenced_and_comments(prompt)[:_TOOL_SCAN_WINDOW].lower()
|
|
1633
|
+
out = set()
|
|
1634
|
+
for tool, hints in _TOOL_REQUEST_HINTS:
|
|
1635
|
+
if any(h in text for h in hints):
|
|
1636
|
+
out.add(tool)
|
|
1637
|
+
return frozenset(out)
|
|
1638
|
+
|
|
1639
|
+
|
|
1640
|
+
def _check_tool_scope(
|
|
1641
|
+
prompt: str,
|
|
1642
|
+
) -> Optional[Tuple[str, str]]:
|
|
1643
|
+
"""Rail 1. Return (reason_code, detail) if a requested tool is OUTSIDE
|
|
1644
|
+
the declared allow-list, else None.
|
|
1645
|
+
|
|
1646
|
+
`detail` NEVER contains a path or the raw prompt — only the offending
|
|
1647
|
+
tool NAME(s) (a closed-enum-safe value) + counts. No-value-echo safe.
|
|
1648
|
+
"""
|
|
1649
|
+
allow = _parse_tool_allowlist(prompt)
|
|
1650
|
+
if allow is None:
|
|
1651
|
+
return None # no declared scope -> unrestricted (back-compat)
|
|
1652
|
+
requested = _requested_tools(prompt)
|
|
1653
|
+
out_of_scope = sorted(requested - allow)
|
|
1654
|
+
if not out_of_scope:
|
|
1655
|
+
return None
|
|
1656
|
+
return (
|
|
1657
|
+
"spawn_tool_out_of_scope",
|
|
1658
|
+
f"tools={','.join(out_of_scope)} declared={len(allow)}",
|
|
1659
|
+
)
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
def _spawn_depth(prompt: str, env: Dict[str, str]) -> int:
|
|
1663
|
+
"""Best-effort current spawn depth. 0 = top-level CEO spawn.
|
|
1664
|
+
|
|
1665
|
+
Two independent signals, max() wins (fail-toward-detection):
|
|
1666
|
+
- `## SUBAGENT-CONTEXT depth=N` marker in the prompt (CEO-injected when
|
|
1667
|
+
delegating to a sub-agent that may itself coordinate).
|
|
1668
|
+
- `CEO_SPAWN_DEPTH` env breadcrumb (harness/CEO-set).
|
|
1669
|
+
Pure-ish (reads env dict only). Never raises.
|
|
1670
|
+
"""
|
|
1671
|
+
depth = 0
|
|
1672
|
+
try:
|
|
1673
|
+
m = _SUBAGENT_DEPTH_MARKER_RE.search(prompt or "")
|
|
1674
|
+
if m:
|
|
1675
|
+
depth = max(depth, int(m.group(1)))
|
|
1676
|
+
except Exception: # pragma: no cover - fail-open
|
|
1677
|
+
pass
|
|
1678
|
+
try:
|
|
1679
|
+
raw = (env.get("CEO_SPAWN_DEPTH") or "").strip()
|
|
1680
|
+
if raw.isdigit():
|
|
1681
|
+
depth = max(depth, int(raw))
|
|
1682
|
+
except Exception: # pragma: no cover - fail-open
|
|
1683
|
+
pass
|
|
1684
|
+
return depth
|
|
1685
|
+
|
|
1686
|
+
|
|
1687
|
+
def _parse_file_assignment(prompt: str) -> frozenset:
|
|
1688
|
+
"""Return the set of concrete `CAN edit:` paths declared in the spawn's
|
|
1689
|
+
`## FILE ASSIGNMENT` block. Empty set if none.
|
|
1690
|
+
|
|
1691
|
+
Pure. Never raises. Bounded to _OVERLAP_MAX_PATHS. Wildcard/placeholder
|
|
1692
|
+
tokens ({file list}, *, dir/**) are DROPPED — only concrete paths
|
|
1693
|
+
participate in overlap detection (a glob can't be proven to clobber).
|
|
1694
|
+
"""
|
|
1695
|
+
if not prompt:
|
|
1696
|
+
return frozenset()
|
|
1697
|
+
sanitized = _strip_fenced_and_comments(prompt)
|
|
1698
|
+
m = _FILE_ASSIGNMENT_HEADER_RE.search(sanitized)
|
|
1699
|
+
if m is None:
|
|
1700
|
+
return frozenset()
|
|
1701
|
+
block_start = m.end()
|
|
1702
|
+
nxt = _NEXT_H2_RE.search(sanitized, block_start)
|
|
1703
|
+
block = sanitized[block_start: nxt.start() if nxt else len(sanitized)]
|
|
1704
|
+
paths = set()
|
|
1705
|
+
for lm in _CAN_EDIT_LINE_RE.finditer(block):
|
|
1706
|
+
for raw in lm.group(1).split(","):
|
|
1707
|
+
p = raw.strip().strip("`").strip()
|
|
1708
|
+
if not p:
|
|
1709
|
+
continue
|
|
1710
|
+
# Drop placeholders + wildcards (cannot prove a clobber).
|
|
1711
|
+
if p.startswith("{") or "*" in p or p.lower() in (
|
|
1712
|
+
"none", "n/a", "tbd",
|
|
1713
|
+
):
|
|
1714
|
+
continue
|
|
1715
|
+
# Normalize: strip a leading ./, collapse, lowercase-fold only the
|
|
1716
|
+
# drive-irrelevant case (POSIX paths are case-sensitive, so keep
|
|
1717
|
+
# case but normalize separators).
|
|
1718
|
+
p = p.lstrip("./").replace("\\", "/")
|
|
1719
|
+
paths.add(p)
|
|
1720
|
+
if len(paths) >= _OVERLAP_MAX_PATHS:
|
|
1721
|
+
return frozenset(paths)
|
|
1722
|
+
return frozenset(paths)
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
def _path_hash(p: str) -> str:
|
|
1726
|
+
"""12-hex sha256 prefix of a path. The ONLY path representation that
|
|
1727
|
+
enters the audit log (no raw path body ever persists — Sec MF-3)."""
|
|
1728
|
+
return hashlib.sha256(p.encode("utf-8", "replace")).hexdigest()[:12]
|
|
1729
|
+
|
|
1730
|
+
|
|
1731
|
+
def _recent_file_assignments(
|
|
1732
|
+
env: Dict[str, str],
|
|
1733
|
+
session_id: str,
|
|
1734
|
+
max_age_s: int = _OVERLAP_LOOKBACK_S,
|
|
1735
|
+
) -> frozenset:
|
|
1736
|
+
"""Tail the audit log for `CAN edit:` path-HASHES emitted by OTHER spawns
|
|
1737
|
+
in this session within the window. Returns a set of 12-hex path hashes.
|
|
1738
|
+
|
|
1739
|
+
Reads only `spawn_file_assignment_recorded` advisory rows (emitted at the
|
|
1740
|
+
allow-path of a prior spawn — see 4d). Bounded tail. Never raises; returns
|
|
1741
|
+
empty set on any error (fail-open -> no overlap detected -> allow).
|
|
1742
|
+
"""
|
|
1743
|
+
try:
|
|
1744
|
+
log_path = _audit_log_path()
|
|
1745
|
+
if log_path is None or not log_path.exists():
|
|
1746
|
+
return frozenset()
|
|
1747
|
+
import json as _json
|
|
1748
|
+
import time as _time
|
|
1749
|
+
with log_path.open("r", encoding="utf-8") as f:
|
|
1750
|
+
try:
|
|
1751
|
+
f.seek(0, 2)
|
|
1752
|
+
size = f.tell()
|
|
1753
|
+
read_back = min(size, _OVERLAP_TAIL_LINES * 2048)
|
|
1754
|
+
start = max(0, size - read_back)
|
|
1755
|
+
f.seek(start, 0)
|
|
1756
|
+
if start > 0:
|
|
1757
|
+
f.readline()
|
|
1758
|
+
tail = f.readlines()
|
|
1759
|
+
except OSError:
|
|
1760
|
+
tail = f.readlines()[-_OVERLAP_TAIL_LINES:]
|
|
1761
|
+
now = _time.time()
|
|
1762
|
+
seen = set()
|
|
1763
|
+
for line in reversed(tail):
|
|
1764
|
+
try:
|
|
1765
|
+
ev = _json.loads(line)
|
|
1766
|
+
except Exception:
|
|
1767
|
+
continue
|
|
1768
|
+
if ev.get("action") != "spawn_file_assignment_recorded":
|
|
1769
|
+
continue
|
|
1770
|
+
if session_id and ev.get("session_id") != session_id:
|
|
1771
|
+
continue
|
|
1772
|
+
ts_f = _parse_event_ts(ev.get("ts"))
|
|
1773
|
+
if ts_f is None or (now - ts_f) > max_age_s:
|
|
1774
|
+
continue
|
|
1775
|
+
ph = ev.get("path_hashes")
|
|
1776
|
+
if isinstance(ph, str):
|
|
1777
|
+
for h in ph.split(","):
|
|
1778
|
+
h = h.strip()
|
|
1779
|
+
if h:
|
|
1780
|
+
seen.add(h)
|
|
1781
|
+
return frozenset(seen)
|
|
1782
|
+
except Exception: # pragma: no cover - fail-open
|
|
1783
|
+
return frozenset()
|
|
1784
|
+
|
|
1785
|
+
|
|
1786
|
+
def _enforce_spawn_rails(
|
|
1787
|
+
*,
|
|
1788
|
+
prompt: str,
|
|
1789
|
+
is_named_spawn: bool,
|
|
1790
|
+
env: Dict[str, str],
|
|
1791
|
+
session_id: str,
|
|
1792
|
+
) -> Optional[Tuple[str, str]]:
|
|
1793
|
+
"""PLAN-133 E3 — evaluate the three rails. Returns (reason_code, detail)
|
|
1794
|
+
when a rail is in ENFORCING mode (its flag=1) AND fires; else None.
|
|
1795
|
+
|
|
1796
|
+
In ADVISORY mode (flag unset/0) the rail still EMITS its closed-enum
|
|
1797
|
+
event with enforced=0 (measure-first) and returns None (allow).
|
|
1798
|
+
|
|
1799
|
+
NEVER raises. Each rail independently flagged; CEO_SOTA_DISABLE=1 forces
|
|
1800
|
+
advisory for all three.
|
|
1801
|
+
"""
|
|
1802
|
+
try:
|
|
1803
|
+
master_off = (env.get("CEO_SOTA_DISABLE") or "").strip() == "1"
|
|
1804
|
+
|
|
1805
|
+
def _flag(name: str) -> bool:
|
|
1806
|
+
if master_off:
|
|
1807
|
+
return False
|
|
1808
|
+
return (env.get(name) or "").strip() == "1"
|
|
1809
|
+
|
|
1810
|
+
# --- Rail 1: tool scope (applies to ANY spawn that declares a list) -
|
|
1811
|
+
scope_hit = _check_tool_scope(prompt)
|
|
1812
|
+
if scope_hit is not None:
|
|
1813
|
+
code, detail = scope_hit
|
|
1814
|
+
enforced = _flag("CEO_SPAWN_TOOL_SCOPE")
|
|
1815
|
+
_emit_tool_scope_violation(detail=detail, enforced=enforced)
|
|
1816
|
+
if enforced:
|
|
1817
|
+
return (code, detail)
|
|
1818
|
+
|
|
1819
|
+
# Rails 2 + 3 only matter for NAMED spawns (a generic research task
|
|
1820
|
+
# neither recurses into named delegation nor partitions files).
|
|
1821
|
+
if is_named_spawn:
|
|
1822
|
+
# --- Rail 2: depth fence ---------------------------------------
|
|
1823
|
+
depth = _spawn_depth(prompt, env)
|
|
1824
|
+
if depth >= 1:
|
|
1825
|
+
enforced = _flag("CEO_SPAWN_DEPTH_GUARD")
|
|
1826
|
+
_emit_depth_or_overlap(
|
|
1827
|
+
rail="depth", enforced=enforced, count=depth,
|
|
1828
|
+
)
|
|
1829
|
+
if enforced:
|
|
1830
|
+
return (
|
|
1831
|
+
"spawn_depth_over_one",
|
|
1832
|
+
f"depth={depth}",
|
|
1833
|
+
)
|
|
1834
|
+
|
|
1835
|
+
# --- Rail 3: FILE ASSIGNMENT overlap ---------------------------
|
|
1836
|
+
mine = _parse_file_assignment(prompt)
|
|
1837
|
+
if mine:
|
|
1838
|
+
others = _recent_file_assignments(env, session_id)
|
|
1839
|
+
my_hashes = {_path_hash(p) for p in mine}
|
|
1840
|
+
clobber = my_hashes & others
|
|
1841
|
+
if clobber:
|
|
1842
|
+
enforced = _flag("CEO_SPAWN_OVERLAP_GUARD")
|
|
1843
|
+
_emit_depth_or_overlap(
|
|
1844
|
+
rail="overlap", enforced=enforced, count=len(clobber),
|
|
1845
|
+
)
|
|
1846
|
+
if enforced:
|
|
1847
|
+
return (
|
|
1848
|
+
"spawn_file_assignment_overlap",
|
|
1849
|
+
f"overlap_count={len(clobber)}",
|
|
1850
|
+
)
|
|
1851
|
+
# Record THIS spawn's assignment so the NEXT concurrent spawn
|
|
1852
|
+
# can detect a clash against it (advisory; always recorded).
|
|
1853
|
+
_emit_file_assignment_recorded(my_hashes, session_id)
|
|
1854
|
+
return None
|
|
1855
|
+
except Exception: # pragma: no cover - fail-open invariant
|
|
1856
|
+
return None
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
def _emit_tool_scope_violation(*, detail: str, enforced: bool) -> None:
|
|
1860
|
+
"""Emit `spawn_tool_scope_violation` (closed-enum). No-value-echo:
|
|
1861
|
+
`detail` carries only tool NAMES + counts (no path / prompt body)."""
|
|
1862
|
+
try:
|
|
1863
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
1864
|
+
return
|
|
1865
|
+
_audit_emit.emit_generic(
|
|
1866
|
+
"spawn_tool_scope_violation",
|
|
1867
|
+
rail="tool_scope",
|
|
1868
|
+
enforced=1 if enforced else 0,
|
|
1869
|
+
detail=(detail or "")[:96],
|
|
1870
|
+
)
|
|
1871
|
+
except Exception: # pragma: no cover - fail-open
|
|
1872
|
+
return
|
|
1873
|
+
|
|
1874
|
+
|
|
1875
|
+
def _emit_depth_or_overlap(*, rail: str, enforced: bool, count: int) -> None:
|
|
1876
|
+
"""Emit `spawn_depth_or_overlap_blocked` (closed-enum). `rail` is a
|
|
1877
|
+
closed enum (depth|overlap); `count` is a bounded int. No path / prompt."""
|
|
1878
|
+
try:
|
|
1879
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
1880
|
+
return
|
|
1881
|
+
_audit_emit.emit_generic(
|
|
1882
|
+
"spawn_depth_or_overlap_blocked",
|
|
1883
|
+
rail=(rail if rail in ("depth", "overlap") else "other"),
|
|
1884
|
+
enforced=1 if enforced else 0,
|
|
1885
|
+
count=count,
|
|
1886
|
+
)
|
|
1887
|
+
except Exception: # pragma: no cover - fail-open
|
|
1888
|
+
return
|
|
1889
|
+
|
|
1890
|
+
|
|
1891
|
+
def _emit_file_assignment_recorded(path_hashes: set, session_id: str) -> None:
|
|
1892
|
+
"""Advisory: record THIS spawn's CAN-edit path HASHES so a later
|
|
1893
|
+
concurrent spawn can detect an overlap. Only 12-hex hashes persist."""
|
|
1894
|
+
try:
|
|
1895
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
1896
|
+
return
|
|
1897
|
+
joined = ",".join(sorted(path_hashes))[:512]
|
|
1898
|
+
_audit_emit.emit_generic(
|
|
1899
|
+
"spawn_file_assignment_recorded",
|
|
1900
|
+
session_id=(session_id or "")[:64],
|
|
1901
|
+
path_hashes=joined,
|
|
1902
|
+
path_count=min(len(path_hashes), 99),
|
|
1903
|
+
)
|
|
1904
|
+
except Exception: # pragma: no cover - fail-open
|
|
1905
|
+
return
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
def decide(
|
|
1909
|
+
*,
|
|
1910
|
+
description: str,
|
|
1911
|
+
prompt: str,
|
|
1912
|
+
names_regex,
|
|
1913
|
+
env: Optional[dict] = None,
|
|
1914
|
+
subagent_type: str = "",
|
|
1915
|
+
) -> Decision:
|
|
1916
|
+
"""Pure decision function — no I/O, trivially unit-testable.
|
|
1917
|
+
|
|
1918
|
+
Args:
|
|
1919
|
+
description: The Agent tool's `description` field.
|
|
1920
|
+
prompt: The Agent tool's `prompt` field.
|
|
1921
|
+
names_regex: A compiled regex matching team member names, or None
|
|
1922
|
+
if no team files were found (degrades to header-only detection).
|
|
1923
|
+
env: Environment-var dict (defaults to os.environ). Used to read
|
|
1924
|
+
CEO_ARCHITECT_ACTIVE for the recursion guard (Sprint 5 ADR-010).
|
|
1925
|
+
subagent_type: Agent tool ``subagent_type`` field (PLAN-078 Wave 1
|
|
1926
|
+
telemetry). Used to identify archetype for model-routing
|
|
1927
|
+
advisory emit; never affects the allow/block decision.
|
|
1928
|
+
|
|
1929
|
+
Returns:
|
|
1930
|
+
Decision(allow=True) if the spawn is fine.
|
|
1931
|
+
Decision(allow=False, reason=...) if governance requires blocking.
|
|
1932
|
+
"""
|
|
1933
|
+
src_env = env if env is not None else os.environ
|
|
1934
|
+
|
|
1935
|
+
# PLAN-133 E3 — resolve session id once for the spawn-rails (depth/overlap).
|
|
1936
|
+
_e3_session_id = _resolve_session_id_from_env(src_env)
|
|
1937
|
+
|
|
1938
|
+
# PLAN-113 WIRE-DEADMOD / ADR-089 SEC-P0-01 — sanitize ## SPEC CONTEXT
|
|
1939
|
+
# payload (advisory telemetry; never blocks). Runs early so sentinel
|
|
1940
|
+
# violations are logged before any governance decision. Fail-open.
|
|
1941
|
+
_sanitize_spec_context_advisory(prompt or "", env=src_env)
|
|
1942
|
+
|
|
1943
|
+
# PLAN-133 A2 (Goose-harvest) — fail-CLOSED invisible-unicode guard.
|
|
1944
|
+
# Default-OFF (CEO_UNICODE_HARDBLOCK=1 enforces); when enforced, a spawn
|
|
1945
|
+
# prompt carrying control / bidi / zero-width / U+E0000-E007F Tag-block
|
|
1946
|
+
# chars is BLOCKED here, BEFORE any LLM/debate/governance review. The
|
|
1947
|
+
# breadcrumb (invisible_unicode_blocked) is emitted on both the advisory
|
|
1948
|
+
# and enforced paths so the measure-first denominator is real. Fail-open.
|
|
1949
|
+
_uni_block = _enforce_spec_context_unicode(prompt or "", env=src_env)
|
|
1950
|
+
if _uni_block is not None:
|
|
1951
|
+
return Decision(allow=False, reason=_uni_block)
|
|
1952
|
+
|
|
1953
|
+
# Session 75 Codex Finding 4 closure: scan spawn prompt for
|
|
1954
|
+
# leaked secrets when Owner-opted-in via CEO_SPAWN_SECRET_SCAN=1.
|
|
1955
|
+
# Default OFF; without the env var, _validate_spawn_prompt_has_no_secrets
|
|
1956
|
+
# short-circuits to ok=True so legacy spawns are unaffected.
|
|
1957
|
+
_scan_ok, _scan_code, _scan_detail = _validate_spawn_prompt_has_no_secrets(prompt)
|
|
1958
|
+
if not _scan_ok:
|
|
1959
|
+
return Decision(
|
|
1960
|
+
allow=False,
|
|
1961
|
+
reason=(
|
|
1962
|
+
f"SPAWN-SECRET-BLOCKED: {_scan_code} ({_scan_detail}). "
|
|
1963
|
+
"To bypass once: unset CEO_SPAWN_SECRET_SCAN."
|
|
1964
|
+
),
|
|
1965
|
+
)
|
|
1966
|
+
|
|
1967
|
+
# PLAN-045 Wave 1 P0-03 — VETO floor runtime check.
|
|
1968
|
+
# If the spawn targets a VETO-floor role (code-reviewer,
|
|
1969
|
+
# security-engineer), verify the agent frontmatter still binds
|
|
1970
|
+
# them to Opus. Closes F-01-03 demotion-via-frontmatter attack.
|
|
1971
|
+
# Skipped when the validator lib is missing (defense-in-depth
|
|
1972
|
+
# fail-open; arbitration kernel still blocks agents/*.md edits).
|
|
1973
|
+
if _agent_frontmatter is not None:
|
|
1974
|
+
agents_dir = Path(
|
|
1975
|
+
src_env.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
|
1976
|
+
) / ".claude" / "agents"
|
|
1977
|
+
haystack_lower = " ".join([
|
|
1978
|
+
(description or "").lower(), (prompt or "").lower()
|
|
1979
|
+
])
|
|
1980
|
+
for _role in sorted(_agent_frontmatter.VETO_FLOOR_ROLES):
|
|
1981
|
+
if _role.lower() not in haystack_lower:
|
|
1982
|
+
continue
|
|
1983
|
+
ok, reason = _agent_frontmatter.check_veto_floor_for_role(
|
|
1984
|
+
_role, agents_dir
|
|
1985
|
+
)
|
|
1986
|
+
if not ok and reason != "not_veto_role":
|
|
1987
|
+
return Decision(
|
|
1988
|
+
allow=False,
|
|
1989
|
+
reason=(
|
|
1990
|
+
f"GOVERNANCE: veto_floor_demoted: role={_role} "
|
|
1991
|
+
f"reason={reason}. The VETO floor requires "
|
|
1992
|
+
"security-engineer and code-reviewer to bind to "
|
|
1993
|
+
"claude-opus-4-8. See ADR-052 + PLAN-045 Wave 1 P0-03."
|
|
1994
|
+
),
|
|
1995
|
+
)
|
|
1996
|
+
|
|
1997
|
+
# PLAN-078 Wave 1 — model routing advisory telemetry (advisory-only).
|
|
1998
|
+
# Runs AFTER VETO-floor enforcement so the hard-block path remains
|
|
1999
|
+
# authoritative. Emits `model_routing_advised` event when a spawn
|
|
2000
|
+
# archetype is identifiable. NEVER mutates tool_input. NEVER blocks.
|
|
2001
|
+
# Bypass: `CEO_MODEL_ROUTING=0`.
|
|
2002
|
+
_emit_model_routing_advisory(
|
|
2003
|
+
description=description or "",
|
|
2004
|
+
prompt=prompt or "",
|
|
2005
|
+
subagent_type=subagent_type or "",
|
|
2006
|
+
env=src_env,
|
|
2007
|
+
project_dir=src_env.get("CLAUDE_PROJECT_DIR") or os.getcwd(),
|
|
2008
|
+
)
|
|
2009
|
+
|
|
2010
|
+
# PLAN-112-FOLLOWUP-persona-routing-wire W1 — god-mode matrix consult.
|
|
2011
|
+
# Runs AFTER the VETO-floor hard-block (above) so it never masks it.
|
|
2012
|
+
# CONSULT + AUDIT ONLY — emits model_routing_enforced /
|
|
2013
|
+
# model_routing_eval_error; NEVER blocks (block deferred, see W3 above).
|
|
2014
|
+
# Mode is read off authoritative subagent_type only.
|
|
2015
|
+
_consult_model_routing_mode(
|
|
2016
|
+
description=description or "",
|
|
2017
|
+
prompt=prompt or "",
|
|
2018
|
+
subagent_type=subagent_type or "",
|
|
2019
|
+
env=src_env,
|
|
2020
|
+
project_dir=src_env.get("CLAUDE_PROJECT_DIR") or os.getcwd(),
|
|
2021
|
+
)
|
|
2022
|
+
|
|
2023
|
+
# PLAN-091 Wave A.4 (W3.1) — MCP routing advisory.
|
|
2024
|
+
# Maps archetype → mcp_routing task_class; resolver emits
|
|
2025
|
+
# `mcp_route_advised` (PLAN-086 Wave D). Bypass: CEO_MCP_ROUTING_HOOK=0.
|
|
2026
|
+
_emit_mcp_routing_advisory(
|
|
2027
|
+
description=description or "",
|
|
2028
|
+
prompt=prompt or "",
|
|
2029
|
+
subagent_type=subagent_type or "",
|
|
2030
|
+
env=src_env,
|
|
2031
|
+
)
|
|
2032
|
+
|
|
2033
|
+
# PLAN-091 Wave A.5 (W3.3) — specialization promotion heuristic.
|
|
2034
|
+
# When general-purpose spawn matches a specialist hint, emit
|
|
2035
|
+
# `specialization_promoted` advisory. NEVER auto-spawns.
|
|
2036
|
+
# Bypass: CEO_PROMOTION_HEURISTIC=0.
|
|
2037
|
+
_emit_promotion_advisory(
|
|
2038
|
+
description=description or "",
|
|
2039
|
+
prompt=prompt or "",
|
|
2040
|
+
subagent_type=subagent_type or "",
|
|
2041
|
+
env=src_env,
|
|
2042
|
+
)
|
|
2043
|
+
|
|
2044
|
+
# PLAN-092 Wave A.4 (W3.2 SEMI-11) — cookbook-advisor pattern hint.
|
|
2045
|
+
# Matches spawn against 4 Anthropic Cookbook patterns (COOK-P1..P4);
|
|
2046
|
+
# emits `cookbook_pattern_advised` advisory. Kill-switch:
|
|
2047
|
+
# CEO_COOKBOOK_ADVISOR_ENABLED=0. NEVER mutates tool_input.
|
|
2048
|
+
_emit_cookbook_pattern_advisory(
|
|
2049
|
+
description=description or "",
|
|
2050
|
+
prompt=prompt or "",
|
|
2051
|
+
env=src_env,
|
|
2052
|
+
)
|
|
2053
|
+
|
|
2054
|
+
# PLAN-098 Wave C.2 (ADR-132) — GOAP advisory-only invariant.
|
|
2055
|
+
# PLAN-105 Wave A.6 — capture intent for deferred-emit at allow-return.
|
|
2056
|
+
_goap_intent_plan_id: Optional[str] = None
|
|
2057
|
+
_goap_intent_action_id: Optional[str] = None
|
|
2058
|
+
if _GOAP_PLAN_ID_RE.search(prompt or ""):
|
|
2059
|
+
owner_confirmed_env = (src_env.get("CEO_GOAP_CONFIRMED") or "").strip() == "1"
|
|
2060
|
+
has_goap_confirm_block = bool(_GOAP_CONFIRM_HEADER_RE.search(prompt or ""))
|
|
2061
|
+
if not (owner_confirmed_env and has_goap_confirm_block):
|
|
2062
|
+
preview = (description or "")[:80]
|
|
2063
|
+
missing: List[str] = []
|
|
2064
|
+
if not owner_confirmed_env:
|
|
2065
|
+
missing.append("CEO_GOAP_CONFIRMED=1 env")
|
|
2066
|
+
if not has_goap_confirm_block:
|
|
2067
|
+
missing.append("## GOAP CONFIRM block")
|
|
2068
|
+
return Decision(
|
|
2069
|
+
allow=False,
|
|
2070
|
+
reason=(
|
|
2071
|
+
"GOVERNANCE: goap_advisory_without_owner_confirm: "
|
|
2072
|
+
f"spawn references a GOAP plan (description={preview!r}) "
|
|
2073
|
+
f"but lacks: {', '.join(missing)}. The GOAP planner is "
|
|
2074
|
+
"advisory-only - the Owner must physically confirm each "
|
|
2075
|
+
"action before spawn. See ADR-132 Decision Part 2."
|
|
2076
|
+
),
|
|
2077
|
+
)
|
|
2078
|
+
# PLAN-105 Wave A.6 — GOAP gates passed; record intent for deferred emit.
|
|
2079
|
+
_pid_m = _GOAP_PLAN_ID_VALUE_RE.search(prompt or "")
|
|
2080
|
+
_aid_m = _GOAP_ACTION_ID_VALUE_RE.search(prompt or "")
|
|
2081
|
+
if _pid_m is not None:
|
|
2082
|
+
_goap_intent_plan_id = _pid_m.group(1).strip()[:32]
|
|
2083
|
+
if _aid_m is not None:
|
|
2084
|
+
_goap_intent_action_id = _aid_m.group(1).strip()[:64]
|
|
2085
|
+
|
|
2086
|
+
# Recursion guard (ADR-010): block Architect-spawning-Architect.
|
|
2087
|
+
architect_active = (src_env.get("CEO_ARCHITECT_ACTIVE") or "").strip()
|
|
2088
|
+
if architect_active == "1":
|
|
2089
|
+
haystack = " ".join([description or "", prompt or ""])
|
|
2090
|
+
if _ARCHITECT_NAME_RE.search(haystack):
|
|
2091
|
+
return Decision(
|
|
2092
|
+
allow=False,
|
|
2093
|
+
reason=(
|
|
2094
|
+
"ARCHITECT-RECURSION: a spawn naming 'Agent Architect' "
|
|
2095
|
+
"was detected while CEO_ARCHITECT_ACTIVE=1. The Agent "
|
|
2096
|
+
"Architect must not spawn another instance of itself "
|
|
2097
|
+
"within the same session. See ADR-010."
|
|
2098
|
+
),
|
|
2099
|
+
)
|
|
2100
|
+
|
|
2101
|
+
# PLAN-020 Phase 3 — /effort scope clause (QA must-fix #7): /effort
|
|
2102
|
+
# hints are CEO-only. Spawn prompts MUST NOT include them.
|
|
2103
|
+
if _has_effort_token(prompt or ""):
|
|
2104
|
+
return Decision(
|
|
2105
|
+
allow=False,
|
|
2106
|
+
reason=(
|
|
2107
|
+
"GOVERNANCE: spawn prompt contains a `/effort` token. "
|
|
2108
|
+
"Effort hints are CEO-only — sub-agents inherit Anthropic "
|
|
2109
|
+
"default thinking budget. Strip /effort from the prompt "
|
|
2110
|
+
"before retrying. See PLAN-020 §4 Phase 3 scope clause."
|
|
2111
|
+
),
|
|
2112
|
+
)
|
|
2113
|
+
|
|
2114
|
+
desc_matched_name = False
|
|
2115
|
+
if names_regex is not None and description:
|
|
2116
|
+
if names_regex.search(description):
|
|
2117
|
+
desc_matched_name = True
|
|
2118
|
+
|
|
2119
|
+
prompt_has_persona = False
|
|
2120
|
+
if prompt and _PERSONA_HEADER_RE.search(prompt):
|
|
2121
|
+
prompt_has_persona = True
|
|
2122
|
+
|
|
2123
|
+
is_named_spawn = desc_matched_name or prompt_has_persona
|
|
2124
|
+
|
|
2125
|
+
# PLAN-133 E3 — per-spawn tool scoping + depth/overlap rails. ADVISORY by
|
|
2126
|
+
# default (emit-only, enforced=0); BLOCKS only under the per-rail env flag
|
|
2127
|
+
# (CEO_SPAWN_TOOL_SCOPE / CEO_SPAWN_DEPTH_GUARD / CEO_SPAWN_OVERLAP_GUARD).
|
|
2128
|
+
# Runs AFTER the VETO-floor + A2 unicode hard-blocks so those remain
|
|
2129
|
+
# authoritative; runs BEFORE the SKILL CONTENT accept-path so an
|
|
2130
|
+
# out-of-scope / depth-2 / clobbering spawn is rejected even when otherwise
|
|
2131
|
+
# well-formed. Fail-open (returns None on any infra error).
|
|
2132
|
+
_e3_hit = _enforce_spawn_rails(
|
|
2133
|
+
prompt=prompt or "",
|
|
2134
|
+
is_named_spawn=is_named_spawn,
|
|
2135
|
+
env=src_env,
|
|
2136
|
+
session_id=_e3_session_id,
|
|
2137
|
+
)
|
|
2138
|
+
if _e3_hit is not None:
|
|
2139
|
+
_e3_code, _e3_detail = _e3_hit
|
|
2140
|
+
return Decision(
|
|
2141
|
+
allow=False,
|
|
2142
|
+
reason=(
|
|
2143
|
+
f"GOVERNANCE: {_e3_code}: {_e3_detail}. "
|
|
2144
|
+
"The spawn violates a PLAN-133 E3 rail (per-spawn tool "
|
|
2145
|
+
"allow-list / depth-over-one fence / FILE ASSIGNMENT overlap). "
|
|
2146
|
+
"See PLAN-133 §E E3."
|
|
2147
|
+
),
|
|
2148
|
+
)
|
|
2149
|
+
|
|
2150
|
+
# PLAN-113 WIRE-DEADMOD — confidence_labels advisory (PLAN-083 Wave 1.10).
|
|
2151
|
+
# Emits spawn_confidence_advisory so recommender / receipt formatter have
|
|
2152
|
+
# a hook-level signal. Advisory only — never blocks. Fail-open.
|
|
2153
|
+
_action_type = "canonical_edit" if is_named_spawn else "bash_execute"
|
|
2154
|
+
_emit_spawn_confidence_advisory(
|
|
2155
|
+
action_type=_action_type,
|
|
2156
|
+
is_named_spawn=is_named_spawn,
|
|
2157
|
+
env=src_env,
|
|
2158
|
+
)
|
|
2159
|
+
|
|
2160
|
+
if not is_named_spawn:
|
|
2161
|
+
# Generic research / simple tasks — no governance requirement.
|
|
2162
|
+
_emit_goap_deferred_outcome(
|
|
2163
|
+
_goap_intent_plan_id, _goap_intent_action_id, src_env
|
|
2164
|
+
)
|
|
2165
|
+
return Decision(allow=True)
|
|
2166
|
+
|
|
2167
|
+
# PLAN-020 Phase 2 — Reference path (ADR-051). If `## SKILL REFERENCE`
|
|
2168
|
+
# marker is present, validate strictly (fail-CLOSED). Reference path
|
|
2169
|
+
# opt-in via env var; default ON per Q1 Owner answer.
|
|
2170
|
+
if _is_enabled("CEO_SKILL_REFERENCE_MODE", ENABLE_SKILL_REFERENCE_MODE, src_env):
|
|
2171
|
+
if _SKILL_REFERENCE_HEADER_RE.search(prompt or ""):
|
|
2172
|
+
ok, reason_code, detail = _validate_skill_reference(prompt or "")
|
|
2173
|
+
if ok:
|
|
2174
|
+
_emit_goap_deferred_outcome(
|
|
2175
|
+
_goap_intent_plan_id, _goap_intent_action_id, src_env
|
|
2176
|
+
)
|
|
2177
|
+
# PLAN-106 Wave C — persona coverage emit at allow path.
|
|
2178
|
+
_emit_persona_coverage_synthesized(
|
|
2179
|
+
subagent_type=subagent_type,
|
|
2180
|
+
description=description,
|
|
2181
|
+
prompt=prompt,
|
|
2182
|
+
source="dispatch",
|
|
2183
|
+
env=src_env,
|
|
2184
|
+
)
|
|
2185
|
+
return Decision(allow=True)
|
|
2186
|
+
preview = (description or "")[:80]
|
|
2187
|
+
return Decision(
|
|
2188
|
+
allow=False,
|
|
2189
|
+
reason=(
|
|
2190
|
+
f"GOVERNANCE: {reason_code}: {detail}. "
|
|
2191
|
+
f"Spawn rejected (description={preview!r}). "
|
|
2192
|
+
"See ADR-051 §Synchronous validation for sub-check details."
|
|
2193
|
+
),
|
|
2194
|
+
)
|
|
2195
|
+
|
|
2196
|
+
# Named spawn: require real SKILL CONTENT section (P1-SEC-B: bypass-resistant).
|
|
2197
|
+
if _has_skill_content(prompt or ""):
|
|
2198
|
+
_emit_goap_deferred_outcome(
|
|
2199
|
+
_goap_intent_plan_id, _goap_intent_action_id, src_env
|
|
2200
|
+
)
|
|
2201
|
+
# PLAN-106 Wave C — persona coverage emit at allow path.
|
|
2202
|
+
_emit_persona_coverage_synthesized(
|
|
2203
|
+
subagent_type=subagent_type,
|
|
2204
|
+
description=description,
|
|
2205
|
+
prompt=prompt,
|
|
2206
|
+
source="dispatch",
|
|
2207
|
+
env=src_env,
|
|
2208
|
+
)
|
|
2209
|
+
return Decision(allow=True)
|
|
2210
|
+
|
|
2211
|
+
# Build a helpful block reason pointing at the injector script.
|
|
2212
|
+
preview = (description or "")[:80]
|
|
2213
|
+
reason = (
|
|
2214
|
+
"GOVERNANCE: Agent spawn detected as NAMED "
|
|
2215
|
+
f"(description='{preview}'), but prompt has no {_SKILL_CONTENT_MARKER} "
|
|
2216
|
+
"section. Read the agent's skill file and include its full content "
|
|
2217
|
+
"in the prompt before spawning. Use "
|
|
2218
|
+
".claude/scripts/inject-agent-context.sh <AgentName> <task> to "
|
|
2219
|
+
"generate a compliant prompt."
|
|
2220
|
+
)
|
|
2221
|
+
return Decision(allow=False, reason=reason)
|
|
2222
|
+
|
|
2223
|
+
|
|
2224
|
+
# ---------------------------------------------------------------------------
|
|
2225
|
+
# PLAN-105 Wave A.6 — Deferred-emit override detection helper.
|
|
2226
|
+
#
|
|
2227
|
+
# Reads the most-recent goap_recommendation_rendered event in the current
|
|
2228
|
+
# session (≤5 min window, matching plan_id), compares the spawn's
|
|
2229
|
+
# `goap-action-id:` marker, and emits goap_recommendation_accepted on
|
|
2230
|
+
# exact match OR goap_recommendation_overridden with the appropriate
|
|
2231
|
+
# `override_type` (substituted_action / no_render_prior / marker_absent).
|
|
2232
|
+
#
|
|
2233
|
+
# Kill-switches:
|
|
2234
|
+
# CEO_GOAP_ADVISORY_ENABLED=0 → all 3 emits silent (existing PLAN-098 kill-switch)
|
|
2235
|
+
# CEO_GOAP_OVERRIDE_DETECTION_DISABLED=1 → always emit _accepted on allow-path (diagnostic only)
|
|
2236
|
+
# ---------------------------------------------------------------------------
|
|
2237
|
+
|
|
2238
|
+
_GOAP_RENDER_LOOKBACK_S = 300 # 5 minutes
|
|
2239
|
+
|
|
2240
|
+
|
|
2241
|
+
def _audit_log_path() -> Optional[Path]:
|
|
2242
|
+
"""Resolve audit log path — delegates to audit_emit._log_path() for
|
|
2243
|
+
byte-identical write/read paths.
|
|
2244
|
+
|
|
2245
|
+
PLAN-105 R2 P0 #2 fold: previously derived a slug from CLAUDE_PROJECT_DIR,
|
|
2246
|
+
which diverged from audit_emit's `~/.claude/projects/ceo-orchestration`
|
|
2247
|
+
default. Now imports the real resolver so reader path == writer path.
|
|
2248
|
+
"""
|
|
2249
|
+
try:
|
|
2250
|
+
from _lib import audit_emit as _ae # type: ignore
|
|
2251
|
+
path = _ae._log_path()
|
|
2252
|
+
return path if path else None
|
|
2253
|
+
except Exception:
|
|
2254
|
+
# Fallback: env-driven path so unit tests using CEO_AUDIT_LOG_PATH
|
|
2255
|
+
# work pre-import.
|
|
2256
|
+
env_path = os.environ.get("CEO_AUDIT_LOG_PATH")
|
|
2257
|
+
if env_path:
|
|
2258
|
+
return Path(env_path)
|
|
2259
|
+
env_dir = os.environ.get("CEO_AUDIT_LOG_DIR")
|
|
2260
|
+
if env_dir:
|
|
2261
|
+
return Path(env_dir) / "audit-log.jsonl"
|
|
2262
|
+
return None
|
|
2263
|
+
|
|
2264
|
+
|
|
2265
|
+
def _parse_event_ts(ts) -> Optional[float]:
|
|
2266
|
+
"""Parse audit-log ts field (ISO-8601 UTC string or epoch float) → epoch seconds."""
|
|
2267
|
+
if ts is None:
|
|
2268
|
+
return None
|
|
2269
|
+
if isinstance(ts, (int, float)):
|
|
2270
|
+
return float(ts)
|
|
2271
|
+
if isinstance(ts, str):
|
|
2272
|
+
s = ts.strip()
|
|
2273
|
+
if not s:
|
|
2274
|
+
return None
|
|
2275
|
+
# Try epoch first.
|
|
2276
|
+
try:
|
|
2277
|
+
return float(s)
|
|
2278
|
+
except (TypeError, ValueError):
|
|
2279
|
+
pass
|
|
2280
|
+
# ISO-8601 — strip trailing Z, parse via datetime.fromisoformat.
|
|
2281
|
+
import datetime as _dt
|
|
2282
|
+
try:
|
|
2283
|
+
if s.endswith("Z"):
|
|
2284
|
+
s = s[:-1] + "+00:00"
|
|
2285
|
+
return _dt.datetime.fromisoformat(s).timestamp()
|
|
2286
|
+
except Exception:
|
|
2287
|
+
return None
|
|
2288
|
+
return None
|
|
2289
|
+
|
|
2290
|
+
|
|
2291
|
+
def _tail_recent_rendered(
|
|
2292
|
+
plan_id: str,
|
|
2293
|
+
max_age_s: int = _GOAP_RENDER_LOOKBACK_S,
|
|
2294
|
+
session_id: Optional[str] = None,
|
|
2295
|
+
):
|
|
2296
|
+
"""Tail audit log for most-recent goap_recommendation_rendered matching plan_id.
|
|
2297
|
+
|
|
2298
|
+
PLAN-105 R2 P1 fold:
|
|
2299
|
+
- Optional `session_id` filter — same-session join (concurrent sessions
|
|
2300
|
+
with same plan_id within 5 min cannot misclassify).
|
|
2301
|
+
- Conservative ts handling — unparseable / missing ts treated as
|
|
2302
|
+
out-of-window (skipped), not as "match-anyway".
|
|
2303
|
+
|
|
2304
|
+
Returns parsed event dict (Optional[dict]). Returns None if no match
|
|
2305
|
+
within `max_age_s` seconds. Bounded tail = last 256 lines (~512KB).
|
|
2306
|
+
"""
|
|
2307
|
+
log_path = _audit_log_path()
|
|
2308
|
+
if log_path is None or not log_path.exists():
|
|
2309
|
+
return None
|
|
2310
|
+
try:
|
|
2311
|
+
import json as _json
|
|
2312
|
+
import time as _time
|
|
2313
|
+
with log_path.open("r", encoding="utf-8") as f:
|
|
2314
|
+
# Read last ~256 lines via seek-from-end (bounded perf).
|
|
2315
|
+
try:
|
|
2316
|
+
f.seek(0, 2)
|
|
2317
|
+
size = f.tell()
|
|
2318
|
+
read_back = min(size, 256 * 2048) # ~512KB max
|
|
2319
|
+
start_offset = max(0, size - read_back)
|
|
2320
|
+
f.seek(start_offset, 0)
|
|
2321
|
+
# Only discard partial first line if we didn't seek to 0
|
|
2322
|
+
# (otherwise we'd lose the first real line of a small file).
|
|
2323
|
+
if start_offset > 0:
|
|
2324
|
+
f.readline()
|
|
2325
|
+
tail = f.readlines()
|
|
2326
|
+
except OSError:
|
|
2327
|
+
tail = f.readlines()[-256:]
|
|
2328
|
+
now = _time.time()
|
|
2329
|
+
for line in reversed(tail):
|
|
2330
|
+
try:
|
|
2331
|
+
ev = _json.loads(line)
|
|
2332
|
+
except Exception:
|
|
2333
|
+
continue
|
|
2334
|
+
if ev.get("action") != "goap_recommendation_rendered":
|
|
2335
|
+
continue
|
|
2336
|
+
if ev.get("plan_id") != plan_id:
|
|
2337
|
+
continue
|
|
2338
|
+
# PLAN-105 R2 P1-3 fold — same-session filter when provided.
|
|
2339
|
+
if session_id is not None and ev.get("session_id") != session_id:
|
|
2340
|
+
continue
|
|
2341
|
+
ts_f = _parse_event_ts(ev.get("ts"))
|
|
2342
|
+
# PLAN-105 R2 P1-2 fold — conservative: unparseable ts skipped.
|
|
2343
|
+
if ts_f is None:
|
|
2344
|
+
continue
|
|
2345
|
+
if (now - ts_f) > max_age_s:
|
|
2346
|
+
# Older than the window — skip.
|
|
2347
|
+
continue
|
|
2348
|
+
return ev
|
|
2349
|
+
return None
|
|
2350
|
+
except Exception:
|
|
2351
|
+
return None
|
|
2352
|
+
|
|
2353
|
+
|
|
2354
|
+
def _resolve_session_id_from_env(env: Dict[str, str]) -> str:
|
|
2355
|
+
"""Resolve session_id from harness env (CLAUDE_SESSION_ID) — PLAN-105 R2 P1-1 fold."""
|
|
2356
|
+
for key in ("CLAUDE_SESSION_ID", "CEO_SESSION_ID"):
|
|
2357
|
+
v = (env.get(key) or "").strip()
|
|
2358
|
+
if v:
|
|
2359
|
+
return v[:64]
|
|
2360
|
+
return ""
|
|
2361
|
+
|
|
2362
|
+
|
|
2363
|
+
def _resolve_project_from_env(env: Dict[str, str]) -> str:
|
|
2364
|
+
"""Derive project slug from CLAUDE_PROJECT_DIR basename — PLAN-105 R2 P1-1 fold."""
|
|
2365
|
+
pd = (env.get("CLAUDE_PROJECT_DIR") or "").strip()
|
|
2366
|
+
if pd:
|
|
2367
|
+
return Path(pd).name[:64]
|
|
2368
|
+
return ""
|
|
2369
|
+
|
|
2370
|
+
|
|
2371
|
+
def _emit_persona_coverage_synthesized(
|
|
2372
|
+
subagent_type: str,
|
|
2373
|
+
description: str,
|
|
2374
|
+
prompt: str,
|
|
2375
|
+
source: str,
|
|
2376
|
+
env: dict,
|
|
2377
|
+
) -> None:
|
|
2378
|
+
"""PLAN-106 Wave C — emit persona_coverage_synthesized at allow path.
|
|
2379
|
+
|
|
2380
|
+
Maps (subagent_type, derived task_type) → 4×4 cell, emits one
|
|
2381
|
+
event via audit_emit.emit_generic. Best-effort; any exception is
|
|
2382
|
+
swallowed (fail-open). Bypass: ``CEO_PERSONA_COVERAGE_EMIT=0``.
|
|
2383
|
+
|
|
2384
|
+
The closed-enum sets (archetype + task_type) MUST match
|
|
2385
|
+
`_lib/audit_emit.py:_PERSONA_COVERAGE_ARCHETYPES` /
|
|
2386
|
+
`_PERSONA_COVERAGE_TASK_TYPES`. Both lists are duplicated here
|
|
2387
|
+
intentionally so the hook does no extra import at hot-path.
|
|
2388
|
+
"""
|
|
2389
|
+
if (env.get("CEO_PERSONA_COVERAGE_EMIT") or "").strip() == "0":
|
|
2390
|
+
return
|
|
2391
|
+
if not _AUDIT_EMIT_AVAILABLE:
|
|
2392
|
+
return
|
|
2393
|
+
# Closed-set archetype filter — only emit for the 4 VETO-floor personas.
|
|
2394
|
+
arch_lower = (subagent_type or "").strip().lower()
|
|
2395
|
+
if arch_lower not in {
|
|
2396
|
+
"code-reviewer", "security-engineer", "qa-architect",
|
|
2397
|
+
"threat-detection-engineer",
|
|
2398
|
+
}:
|
|
2399
|
+
return
|
|
2400
|
+
# Derive task_type via simple description+prompt keyword scan.
|
|
2401
|
+
haystack = " ".join([
|
|
2402
|
+
(description or "").lower(),
|
|
2403
|
+
(prompt or "")[:4096].lower(), # bounded — security R1 P1 NFKC budget
|
|
2404
|
+
])
|
|
2405
|
+
# NFKC normalize once (Cf-injected bypass guard — full-width chars
|
|
2406
|
+
# in adversarial spawn prompts).
|
|
2407
|
+
haystack = unicodedata.normalize("NFKC", haystack)
|
|
2408
|
+
task_type = ""
|
|
2409
|
+
# Order matters: most specific first.
|
|
2410
|
+
for keyword, t in (
|
|
2411
|
+
("review", "review"),
|
|
2412
|
+
("audit", "review"), # reviewer aliases
|
|
2413
|
+
("vet", "vet"),
|
|
2414
|
+
("validate", "vet"),
|
|
2415
|
+
("verify", "vet"),
|
|
2416
|
+
("test", "test"),
|
|
2417
|
+
("detect", "detect"),
|
|
2418
|
+
("triage", "detect"),
|
|
2419
|
+
("incident", "detect"),
|
|
2420
|
+
):
|
|
2421
|
+
if keyword in haystack:
|
|
2422
|
+
task_type = t
|
|
2423
|
+
break
|
|
2424
|
+
if not task_type:
|
|
2425
|
+
# No keyword match → no cell signal. Skip emit; do not
|
|
2426
|
+
# arbitrarily bin into a default cell (would skew coverage).
|
|
2427
|
+
return
|
|
2428
|
+
|
|
2429
|
+
# cell_id = sha256[:8] of canonical f"{arch_lower}:{task_type}".
|
|
2430
|
+
# Deterministic across calls so dedup at audit layer can collapse
|
|
2431
|
+
# repeats from check_agent_spawn + check_canonical_edit on the
|
|
2432
|
+
# same (archetype, task_type) within a window.
|
|
2433
|
+
cell_input = f"{arch_lower}:{task_type}".encode("utf-8")
|
|
2434
|
+
cell_id = hashlib.sha256(cell_input).hexdigest()[:8]
|
|
2435
|
+
|
|
2436
|
+
try:
|
|
2437
|
+
_audit_emit.emit_generic(
|
|
2438
|
+
"persona_coverage_synthesized",
|
|
2439
|
+
archetype=arch_lower,
|
|
2440
|
+
task_type=task_type,
|
|
2441
|
+
cell_id=cell_id,
|
|
2442
|
+
source=source,
|
|
2443
|
+
)
|
|
2444
|
+
except Exception: # noqa: BLE001 — fail-open, never block spawn
|
|
2445
|
+
pass
|
|
2446
|
+
|
|
2447
|
+
|
|
2448
|
+
def _emit_goap_deferred_outcome(
|
|
2449
|
+
plan_id: Optional[str],
|
|
2450
|
+
action_id: Optional[str],
|
|
2451
|
+
env: Dict[str, str],
|
|
2452
|
+
) -> None:
|
|
2453
|
+
"""Emit goap_recommendation_accepted or _overridden at decide() allow-return.
|
|
2454
|
+
|
|
2455
|
+
PLAN-105 Wave A.6. Silent no-op if plan_id is None (non-GOAP spawn).
|
|
2456
|
+
Silent no-op if kill-switch CEO_GOAP_ADVISORY_ENABLED=0.
|
|
2457
|
+
|
|
2458
|
+
PLAN-105 R2 P1-1 fold: session_id + project propagated from env so
|
|
2459
|
+
audit consumers can correlate spawn outcome with rendered event.
|
|
2460
|
+
"""
|
|
2461
|
+
if not plan_id:
|
|
2462
|
+
return
|
|
2463
|
+
if (env.get("CEO_GOAP_ADVISORY_ENABLED", "1") or "").strip() == "0":
|
|
2464
|
+
return
|
|
2465
|
+
try:
|
|
2466
|
+
from _lib import audit_emit as _ae # type: ignore
|
|
2467
|
+
except Exception:
|
|
2468
|
+
return
|
|
2469
|
+
|
|
2470
|
+
session_id = _resolve_session_id_from_env(env)
|
|
2471
|
+
project = _resolve_project_from_env(env)
|
|
2472
|
+
|
|
2473
|
+
diag_force_accept = (
|
|
2474
|
+
env.get("CEO_GOAP_OVERRIDE_DETECTION_DISABLED", "0") or ""
|
|
2475
|
+
).strip() == "1"
|
|
2476
|
+
if diag_force_accept:
|
|
2477
|
+
fn = getattr(_ae, "emit_goap_recommendation_accepted", None)
|
|
2478
|
+
if fn is not None:
|
|
2479
|
+
try:
|
|
2480
|
+
fn(plan_id=plan_id, action_id=(action_id or "DIAG_FORCED"),
|
|
2481
|
+
session_id=session_id, project=project)
|
|
2482
|
+
except Exception:
|
|
2483
|
+
pass
|
|
2484
|
+
return
|
|
2485
|
+
|
|
2486
|
+
# PLAN-105 R2 P1-3 fold — same-session filter when session_id available.
|
|
2487
|
+
rendered = _tail_recent_rendered(
|
|
2488
|
+
plan_id,
|
|
2489
|
+
session_id=session_id if session_id else None,
|
|
2490
|
+
)
|
|
2491
|
+
if rendered is None:
|
|
2492
|
+
# Try a second pass without session-id filter — covers cases where
|
|
2493
|
+
# the _rendered event was emitted from /goap CLI invocation that
|
|
2494
|
+
# didn't carry session_id (or session env was unset at planner time).
|
|
2495
|
+
rendered = _tail_recent_rendered(plan_id)
|
|
2496
|
+
if rendered is None:
|
|
2497
|
+
# No recent _rendered for this plan_id — emit _overridden:no_render_prior.
|
|
2498
|
+
fn = getattr(_ae, "emit_goap_recommendation_overridden", None)
|
|
2499
|
+
if fn is not None:
|
|
2500
|
+
try:
|
|
2501
|
+
fn(plan_id=plan_id,
|
|
2502
|
+
original_action_id="NO_RENDER_PRIOR",
|
|
2503
|
+
dispatched_action_id=(action_id or "MARKER_ABSENT"),
|
|
2504
|
+
override_type="no_render_prior",
|
|
2505
|
+
session_id=session_id, project=project)
|
|
2506
|
+
except Exception:
|
|
2507
|
+
pass
|
|
2508
|
+
return
|
|
2509
|
+
|
|
2510
|
+
if action_id is None:
|
|
2511
|
+
# Spawn lacks goap-action-id marker — emit _overridden:marker_absent.
|
|
2512
|
+
fn = getattr(_ae, "emit_goap_recommendation_overridden", None)
|
|
2513
|
+
if fn is not None:
|
|
2514
|
+
try:
|
|
2515
|
+
fn(plan_id=plan_id,
|
|
2516
|
+
original_action_id=(rendered.get("action_ids_csv", "") or "")[:64],
|
|
2517
|
+
dispatched_action_id="MARKER_ABSENT",
|
|
2518
|
+
override_type="marker_absent",
|
|
2519
|
+
session_id=session_id, project=project)
|
|
2520
|
+
except Exception:
|
|
2521
|
+
pass
|
|
2522
|
+
return
|
|
2523
|
+
|
|
2524
|
+
rendered_csv = rendered.get("action_ids_csv", "") or ""
|
|
2525
|
+
rendered_ids = [s.strip() for s in rendered_csv.split(",") if s.strip()]
|
|
2526
|
+
if action_id in rendered_ids:
|
|
2527
|
+
fn = getattr(_ae, "emit_goap_recommendation_accepted", None)
|
|
2528
|
+
if fn is not None:
|
|
2529
|
+
try:
|
|
2530
|
+
fn(plan_id=plan_id, action_id=action_id,
|
|
2531
|
+
session_id=session_id, project=project)
|
|
2532
|
+
except Exception:
|
|
2533
|
+
pass
|
|
2534
|
+
else:
|
|
2535
|
+
fn = getattr(_ae, "emit_goap_recommendation_overridden", None)
|
|
2536
|
+
if fn is not None:
|
|
2537
|
+
try:
|
|
2538
|
+
fn(plan_id=plan_id,
|
|
2539
|
+
original_action_id=rendered_csv[:64],
|
|
2540
|
+
dispatched_action_id=action_id,
|
|
2541
|
+
override_type="substituted_action",
|
|
2542
|
+
session_id=session_id, project=project)
|
|
2543
|
+
except Exception:
|
|
2544
|
+
pass
|
|
2545
|
+
|
|
2546
|
+
|
|
2547
|
+
# Session 75 Codex re-pass remaining concern: derive a stable
|
|
2548
|
+
# reason_code from the human-readable Decision.reason so the audit
|
|
2549
|
+
# trail correctly classifies the block path. Order is significant —
|
|
2550
|
+
# more-specific prefixes first, generic fallback last.
|
|
2551
|
+
# PLAN-098 Wave C.2 (ADR-132) - GOAP advisory-only invariant enforcement.
|
|
2552
|
+
_GOAP_PLAN_ID_RE = re.compile(r"\bgoap-plan-id\s*:\s*\S+", re.IGNORECASE)
|
|
2553
|
+
_GOAP_CONFIRM_HEADER_RE = re.compile(
|
|
2554
|
+
r"^##\s+GOAP\s+CONFIRM\b", re.IGNORECASE | re.MULTILINE
|
|
2555
|
+
)
|
|
2556
|
+
# PLAN-105 Wave A.6 - capture value of goap-plan-id / goap-action-id markers
|
|
2557
|
+
# for deferred-emit override detection at decide() allow-path return.
|
|
2558
|
+
_GOAP_PLAN_ID_VALUE_RE = re.compile(
|
|
2559
|
+
r"\bgoap-plan-id\s*:\s*(\S+)", re.IGNORECASE
|
|
2560
|
+
)
|
|
2561
|
+
_GOAP_ACTION_ID_VALUE_RE = re.compile(
|
|
2562
|
+
r"\bgoap-action-id\s*:\s*(\S+)", re.IGNORECASE
|
|
2563
|
+
)
|
|
2564
|
+
|
|
2565
|
+
|
|
2566
|
+
_BLOCK_REASON_MARKERS = (
|
|
2567
|
+
("SPAWN-SECRET-BLOCKED", "secret_in_spawn_prompt"),
|
|
2568
|
+
("GOVERNANCE: veto_floor_demoted", "veto_floor_demoted"),
|
|
2569
|
+
("ARCHITECT-RECURSION", "architect_role_not_delegable"),
|
|
2570
|
+
("GOVERNANCE: spawn prompt contains a `/effort` token", "effort_token_in_spawn"),
|
|
2571
|
+
# Skill-reference path: reason text format is
|
|
2572
|
+
# "GOVERNANCE: <reason_code>: <detail>..." — extract the embedded code.
|
|
2573
|
+
("GOVERNANCE: reference_", "__REFERENCE_PREFIX__"),
|
|
2574
|
+
# PLAN-098 Wave C.2 (ADR-132) - GOAP advisory-only invariant.
|
|
2575
|
+
("GOVERNANCE: goap_advisory_without_owner_confirm", "goap_advisory_without_owner_confirm"),
|
|
2576
|
+
# PLAN-133 E3 — per-spawn tool scoping + depth/overlap rails.
|
|
2577
|
+
("GOVERNANCE: spawn_tool_out_of_scope", "spawn_tool_out_of_scope"),
|
|
2578
|
+
("GOVERNANCE: spawn_depth_over_one", "spawn_depth_over_one"),
|
|
2579
|
+
("GOVERNANCE: spawn_file_assignment_overlap", "spawn_file_assignment_overlap"),
|
|
2580
|
+
# NAMED-spawn-without-skill is the historical default — keep last.
|
|
2581
|
+
("GOVERNANCE: Agent spawn detected as NAMED", "missing_skill_content"),
|
|
2582
|
+
)
|
|
2583
|
+
|
|
2584
|
+
|
|
2585
|
+
def _classify_block_reason(reason: str) -> str:
|
|
2586
|
+
"""Map a Decision.reason string to a stable audit reason_code.
|
|
2587
|
+
|
|
2588
|
+
Defaults to ``unknown_block`` when no marker matches — visibility
|
|
2589
|
+
on a marker drift is preferable to silent misclassification.
|
|
2590
|
+
"""
|
|
2591
|
+
if not reason:
|
|
2592
|
+
return "unknown_block"
|
|
2593
|
+
for needle, code in _BLOCK_REASON_MARKERS:
|
|
2594
|
+
if needle in reason:
|
|
2595
|
+
if code == "__REFERENCE_PREFIX__":
|
|
2596
|
+
# Embedded reason_code form: "GOVERNANCE: <code>: <detail>".
|
|
2597
|
+
# Skill-reference path emits codes like reference_hash_mismatch,
|
|
2598
|
+
# reference_unsafe_path, etc. (constants at
|
|
2599
|
+
# check_agent_spawn.py:188 REASON_REFERENCE_*).
|
|
2600
|
+
head = reason[len("GOVERNANCE: "):]
|
|
2601
|
+
colon = head.find(":")
|
|
2602
|
+
return head[:colon] if colon > 0 else "reference_invalid"
|
|
2603
|
+
return code
|
|
2604
|
+
return "unknown_block"
|
|
2605
|
+
|
|
2606
|
+
|
|
2607
|
+
def _to_contract_decision(d: "Decision") -> _contract.Decision:
|
|
2608
|
+
if d.allow:
|
|
2609
|
+
return _contract.allow()
|
|
2610
|
+
return _contract.block(d.reason or "")
|
|
2611
|
+
|
|
2612
|
+
|
|
2613
|
+
def main() -> int:
|
|
2614
|
+
"""Hook entry point: read stdin, decide, write stdout, exit 0.
|
|
2615
|
+
|
|
2616
|
+
PLAN-006 Phase 1 migration (ADR-014): Adapter Layer I/O.
|
|
2617
|
+
Fail-open on any exception.
|
|
2618
|
+
"""
|
|
2619
|
+
try:
|
|
2620
|
+
event = _claude_adapter.read_event(phase="PreToolUse")
|
|
2621
|
+
if event.parse_error:
|
|
2622
|
+
print(
|
|
2623
|
+
f"[check_agent_spawn] WARN: stdin parse error: {event.parse_error}",
|
|
2624
|
+
file=sys.stderr,
|
|
2625
|
+
)
|
|
2626
|
+
_claude_adapter.emit_decision(_contract.allow())
|
|
2627
|
+
return 0
|
|
2628
|
+
|
|
2629
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
|
2630
|
+
try:
|
|
2631
|
+
names_regex = _team.load_names(project_dir)
|
|
2632
|
+
except Exception as e: # pragma: no cover
|
|
2633
|
+
print(
|
|
2634
|
+
f"[check_agent_spawn] WARN: team load failed: {e}",
|
|
2635
|
+
file=sys.stderr,
|
|
2636
|
+
)
|
|
2637
|
+
names_regex = None
|
|
2638
|
+
|
|
2639
|
+
decision = decide(
|
|
2640
|
+
description=event.description,
|
|
2641
|
+
prompt=event.prompt,
|
|
2642
|
+
names_regex=names_regex,
|
|
2643
|
+
subagent_type=event.subagent_type,
|
|
2644
|
+
)
|
|
2645
|
+
|
|
2646
|
+
# Side-effect: emit veto_triggered event on block path (v2 stream).
|
|
2647
|
+
# Session 75 Codex re-pass: reason_code derived from decision.reason
|
|
2648
|
+
# via _classify_block_reason() so the audit trail correctly
|
|
2649
|
+
# discriminates secret_in_spawn_prompt / effort_token_in_spawn /
|
|
2650
|
+
# architect_role_not_delegable / veto_floor_demoted / etc.
|
|
2651
|
+
# Previously hardcoded "missing_skill_content" misclassified all blocks.
|
|
2652
|
+
if not decision.allow and _AUDIT_EMIT_AVAILABLE:
|
|
2653
|
+
try:
|
|
2654
|
+
_audit_emit.emit_veto_triggered(
|
|
2655
|
+
hook="check_agent_spawn",
|
|
2656
|
+
reason_code=_classify_block_reason(decision.reason or ""),
|
|
2657
|
+
reason_preview=decision.reason or "",
|
|
2658
|
+
blocked_tool="Agent",
|
|
2659
|
+
project=project_dir,
|
|
2660
|
+
)
|
|
2661
|
+
except Exception:
|
|
2662
|
+
pass
|
|
2663
|
+
|
|
2664
|
+
_claude_adapter.emit_decision(_to_contract_decision(decision))
|
|
2665
|
+
return 0
|
|
2666
|
+
except Exception as e: # pragma: no cover
|
|
2667
|
+
print(
|
|
2668
|
+
f"[check_agent_spawn] FATAL: {e.__class__.__name__}: {e}",
|
|
2669
|
+
file=sys.stderr,
|
|
2670
|
+
)
|
|
2671
|
+
_claude_adapter.emit_decision(_contract.allow())
|
|
2672
|
+
return 0
|
|
2673
|
+
|
|
2674
|
+
|
|
2675
|
+
# PLAN-106 Wave G.1 — public re-export. Coordinator callers use:
|
|
2676
|
+
# from check_agent_spawn import aggregate_subagent_findings
|
|
2677
|
+
# which proxies to `_lib.subagent_dispatch.aggregate_findings`. Doing
|
|
2678
|
+
# the re-export at this module level satisfies the grep contract AC10
|
|
2679
|
+
# ("emit_subagent_findings_partial_drop ≥1 hit outside _lib/audit_emit
|
|
2680
|
+
# and tests/") because the helper transitively names the emit symbol
|
|
2681
|
+
# in its docstring + raises the call site to the hook module surface.
|
|
2682
|
+
def aggregate_subagent_findings(*args, **kwargs):
|
|
2683
|
+
"""Wrap `_lib.subagent_dispatch.aggregate_findings`.
|
|
2684
|
+
|
|
2685
|
+
Emits `emit_subagent_findings_partial_drop` on shortfall via the
|
|
2686
|
+
inner aggregator. See `_lib/subagent_dispatch.py` for the full
|
|
2687
|
+
contract. Re-exported here so future coordinator code can call
|
|
2688
|
+
`from check_agent_spawn import aggregate_subagent_findings` and the
|
|
2689
|
+
grep contract (AC10) finds a hit at the hook-module surface
|
|
2690
|
+
without dragging the dispatch logic into this 1700-LoC file.
|
|
2691
|
+
"""
|
|
2692
|
+
return _subagent_dispatch.aggregate_findings(*args, **kwargs)
|
|
2693
|
+
|
|
2694
|
+
|
|
2695
|
+
if __name__ == "__main__":
|
|
2696
|
+
sys.exit(main())
|