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,3017 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""ceo-boot.py — PLAN-065 Phase 3 production session-boot autopilot.
|
|
3
|
+
|
|
4
|
+
Single command at session start that consolidates governance reads + state
|
|
5
|
+
digest + recommendations. Per PLAN-065 §4.3 acceptance:
|
|
6
|
+
|
|
7
|
+
- 15 Tier-S checks dispatched parallel via ThreadPoolExecutor (stdlib)
|
|
8
|
+
- Per-check timeout 500 ms; aggregate wall-clock budget 5 s
|
|
9
|
+
- ``--short`` defaults to cached mode (≤2 s budget; cache-hit ≤200 ms)
|
|
10
|
+
- ``--json`` emits machine-readable digest
|
|
11
|
+
- Idempotent (back-to-back identical mod timestamps + transient failures)
|
|
12
|
+
- Recommendations engine (rule-based; ≤5 items)
|
|
13
|
+
- Audit emit hasattr-guarded — works pre + post canonical ceremony
|
|
14
|
+
|
|
15
|
+
Stdlib only. Python 3.9+.
|
|
16
|
+
|
|
17
|
+
Run from repo root:
|
|
18
|
+
|
|
19
|
+
python3 .claude/scripts/ceo-boot.py # full digest
|
|
20
|
+
python3 .claude/scripts/ceo-boot.py --short # cached top-line
|
|
21
|
+
python3 .claude/scripts/ceo-boot.py --json # machine output
|
|
22
|
+
python3 .claude/scripts/ceo-boot.py --bench # bench harness
|
|
23
|
+
|
|
24
|
+
Slash command: ``/ceo-boot`` (see ``.claude/commands/ceo-boot.md``).
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import hashlib
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import re
|
|
33
|
+
import resource
|
|
34
|
+
import subprocess
|
|
35
|
+
import sys
|
|
36
|
+
import time
|
|
37
|
+
import tracemalloc
|
|
38
|
+
import unicodedata
|
|
39
|
+
from concurrent.futures import (
|
|
40
|
+
ThreadPoolExecutor,
|
|
41
|
+
TimeoutError as FuturesTimeout,
|
|
42
|
+
as_completed,
|
|
43
|
+
)
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
46
|
+
|
|
47
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# PLAN-087 Wave C.4 — module-level plan-glob cache.
|
|
51
|
+
# Populated lazily on first call to _get_plan_paths(); subsequent calls
|
|
52
|
+
# within the same /ceo-boot subprocess return the cached sorted list.
|
|
53
|
+
# Process-scoped (no TTL); each /ceo-boot invocation is a fresh subprocess
|
|
54
|
+
# so the cache cannot go stale within a single invocation.
|
|
55
|
+
_PLAN_GLOB_CACHE: Optional[List[Path]] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _get_plan_paths() -> List[Path]:
|
|
59
|
+
"""Return sorted PLAN-*.md paths, using a module-level cache."""
|
|
60
|
+
global _PLAN_GLOB_CACHE
|
|
61
|
+
if _PLAN_GLOB_CACHE is None:
|
|
62
|
+
_PLAN_GLOB_CACHE = sorted(
|
|
63
|
+
(REPO_ROOT / ".claude" / "plans").glob("PLAN-*.md")
|
|
64
|
+
)
|
|
65
|
+
return _PLAN_GLOB_CACHE
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _reset_plan_glob_cache() -> None:
|
|
69
|
+
"""Test helper: clear the cache so a subsequent _get_plan_paths re-globs."""
|
|
70
|
+
global _PLAN_GLOB_CACHE
|
|
71
|
+
_PLAN_GLOB_CACHE = None
|
|
72
|
+
AUDIT_LOG_DEFAULT = (
|
|
73
|
+
Path.home() / ".claude" / "projects" / "ceo-orchestration" / "audit-log.jsonl"
|
|
74
|
+
)
|
|
75
|
+
# Legacy single-file cache (kept for backward compat with S82 MVP).
|
|
76
|
+
CACHE_FILE_DEFAULT = (
|
|
77
|
+
Path.home() / ".claude" / "projects" / "ceo-orchestration" / "cache" / "ceo-boot-digest.json"
|
|
78
|
+
)
|
|
79
|
+
# PLAN-065 §4.3.2 real cache directory — keyed by (HEAD + audit-log mtime + size).
|
|
80
|
+
# Default lives under project state dir so it is excluded from git via
|
|
81
|
+
# ~/.claude/projects layout (parity with audit-log.jsonl). Override via env
|
|
82
|
+
# CEO_BOOT_CACHE_DIR for tests.
|
|
83
|
+
CACHE_DIR_DEFAULT = (
|
|
84
|
+
Path.home() / ".claude" / "projects" / "ceo-orchestration" / "state" / "ceo-boot-cache"
|
|
85
|
+
)
|
|
86
|
+
CACHE_TTL_S = 3600.0 # 1 hour
|
|
87
|
+
CACHE_FILE_SIZE_CAP_BYTES = 100 * 1024 # 100 KB per cache file
|
|
88
|
+
CACHE_DIR_SIZE_CAP_BYTES = 10 * 1024 * 1024 # 10 MB total → LRU eviction
|
|
89
|
+
CACHE_HIT_BUDGET_MS = 200.0 # ≤200 ms wall-clock budget for cache hit
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _cache_dir() -> Path:
|
|
93
|
+
"""Resolve cache dir at call time so test env overrides are honored."""
|
|
94
|
+
override = os.environ.get("CEO_BOOT_CACHE_DIR")
|
|
95
|
+
if override:
|
|
96
|
+
return Path(override)
|
|
97
|
+
return CACHE_DIR_DEFAULT
|
|
98
|
+
|
|
99
|
+
# ---- Per-check + aggregate budgets ------------------------------------------
|
|
100
|
+
# Default per-check budget. Most checks are file-walks completing in <300ms;
|
|
101
|
+
# subprocess-bound checks need longer (overrides below). Aggregate is the hard
|
|
102
|
+
# cap for the whole boot.
|
|
103
|
+
PER_CHECK_TIMEOUT_S = 1.0
|
|
104
|
+
AGGREGATE_TIMEOUT_S = 5.0
|
|
105
|
+
MAX_WORKERS = 8
|
|
106
|
+
|
|
107
|
+
# Per-check overrides — PLAN-082 Codex Item A: governance_validate now
|
|
108
|
+
# dispatches `validate-governance.sh --fast --json` (~40 ms typical).
|
|
109
|
+
# Previous full-walk path required 2.5 s ceiling; fast profile fits the
|
|
110
|
+
# default 1.0 s easily, but we keep a small explicit ceiling for cold-start
|
|
111
|
+
# bash + python3 spawn variance on adopter machines.
|
|
112
|
+
PER_CHECK_TIMEOUT_OVERRIDES_S: Dict[str, float] = {
|
|
113
|
+
"governance_validate": 2.0, # fast --json profile (~40-200 ms warm)
|
|
114
|
+
"plans_executing": 1.5, # full plan tree walk
|
|
115
|
+
"plans_reviewed_pending": 1.5,
|
|
116
|
+
"plans_stranded_executing": 2.0, # plan walk + git log subprocess
|
|
117
|
+
"plans_draft": 1.5,
|
|
118
|
+
"audit_v3_backlog": 1.5,
|
|
119
|
+
"dispatch_count_24h": 1.5, # streaming audit-log read
|
|
120
|
+
"skill_unknown_ratio": 1.5, # streaming audit-log read
|
|
121
|
+
"cost_24h_usd": 1.5,
|
|
122
|
+
"sentinels_pending_gpg": 1.0,
|
|
123
|
+
# PLAN-106 Wave F.3 — EXPLICIT 200 ms override per perf R1 P1 fold.
|
|
124
|
+
# Empirical scan time on 2.3 MB log is ~9-54 ms (200-300k json.loads/sec
|
|
125
|
+
# at ~11.5k events); 200 ms gives ~4-9× headroom. Without explicit
|
|
126
|
+
# override the check would inherit the 1.0 s default and a future
|
|
127
|
+
# log-growth regression wouldn't trip an alarm until 1000 ms.
|
|
128
|
+
"confidence_gate_drift_7d": 0.2,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# ---- Sentinel mtime cutoff (Codex S82 P2 fix) ------------------------------
|
|
132
|
+
# Sentinels signed before this date are pre-enforcement-era legacy and don't
|
|
133
|
+
# require GPG sign now. Without cutoff, scanning all historical produces
|
|
134
|
+
# eternal noise (30+ pending every boot).
|
|
135
|
+
# 2026-04-22 = first ceremony with mandatory GPG enforcement (S81 ceremony-generator).
|
|
136
|
+
SENTINEL_CUTOFF_EPOCH = 1776816000 # 2026-04-22 00:00:00 UTC (Codex S82 P2 fix: was 1776297600 = 2026-04-16, off by 6d)
|
|
137
|
+
|
|
138
|
+
# ---- Sanitization for recommendations engine inputs (Sec MF-4) ------------
|
|
139
|
+
# audit_emit telemetry (`ceo_boot_emitted` / `ceo_boot_check_skipped` actions)
|
|
140
|
+
# was DEFERRED to PLAN-065 Phase 7.A v1.12.0 ceremony pre-S82. Phase 2 wire
|
|
141
|
+
# (this file): we now CALL the typed wrappers but guard with hasattr() so
|
|
142
|
+
# the script keeps working pre-canonical-merge. After ceremony lands the
|
|
143
|
+
# kernel ceremony for `_KNOWN_ACTIONS` add + 2 emit functions, this guard
|
|
144
|
+
# becomes a no-op false-branch. Field allowlist (Sec MF-3) is enforced
|
|
145
|
+
# ON THE EMIT SIDE in `_lib/audit_emit.py` — this caller passes only the
|
|
146
|
+
# allowlisted fields and never raises on emit failure.
|
|
147
|
+
_HOOKS_DIR = REPO_ROOT / ".claude" / "hooks"
|
|
148
|
+
if str(_HOOKS_DIR) not in sys.path:
|
|
149
|
+
sys.path.insert(0, str(_HOOKS_DIR))
|
|
150
|
+
try:
|
|
151
|
+
from _lib import injection_patterns as _injection_patterns # type: ignore
|
|
152
|
+
except Exception: # noqa: BLE001
|
|
153
|
+
_injection_patterns = None
|
|
154
|
+
|
|
155
|
+
# Fail-soft import: pre-canonical-ceremony, audit_emit module loads but
|
|
156
|
+
# the new symbols may not exist yet. Use hasattr() at call site.
|
|
157
|
+
try:
|
|
158
|
+
from _lib import audit_emit as _audit_emit # type: ignore
|
|
159
|
+
except Exception: # noqa: BLE001
|
|
160
|
+
_audit_emit = None # type: ignore[assignment]
|
|
161
|
+
|
|
162
|
+
# PLAN-135 W1 S3 — settings/env tamper tripwires. The shared resolver
|
|
163
|
+
# `_lib/effective_config.py` (built ONCE for the three consumers S3 / W2 H2
|
|
164
|
+
# / W5 O11 per the debate round-1 shared-module rule) captures its trusted
|
|
165
|
+
# env surface (`IMPORT_TIME_ENV_SNAPSHOT`: ANTHROPIC_* + *DANGEROUSLY*
|
|
166
|
+
# keys) at ITS import time. Importing it HERE — at the top of ceo-boot,
|
|
167
|
+
# before any check dispatch — anchors that snapshot as early as the
|
|
168
|
+
# `trusted_env` import-time pattern allows for this script
|
|
169
|
+
# (check_bash_safety.py precedent: a late-set value injected by a
|
|
170
|
+
# sub-agent/subprocess after this anchor cannot dodge the scan).
|
|
171
|
+
# Fail-soft: a missing module (pre-W1 ceremony / partial install) degrades
|
|
172
|
+
# the `settings_tamper_tripwires` Tier-S check to yellow, never crashes boot.
|
|
173
|
+
try:
|
|
174
|
+
from _lib import effective_config as _effective_config # type: ignore
|
|
175
|
+
except Exception: # noqa: BLE001
|
|
176
|
+
_effective_config = None # type: ignore[assignment]
|
|
177
|
+
|
|
178
|
+
# Frozen copy of the import-time env snapshot (defense-in-depth: a later
|
|
179
|
+
# mutation of the module attribute cannot alter what the check scans).
|
|
180
|
+
try:
|
|
181
|
+
_TAMPER_ENV_SNAPSHOT: Dict[str, str] = (
|
|
182
|
+
dict(_effective_config.IMPORT_TIME_ENV_SNAPSHOT)
|
|
183
|
+
if _effective_config is not None
|
|
184
|
+
else {}
|
|
185
|
+
)
|
|
186
|
+
except Exception: # noqa: BLE001
|
|
187
|
+
_TAMPER_ENV_SNAPSHOT = {}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _sanitize_for_recs(s: str) -> str:
|
|
191
|
+
"""Sanitize a disk-sourced string before recommendation rendering (Sec MF-4).
|
|
192
|
+
|
|
193
|
+
Pipeline (deterministic, applied in order):
|
|
194
|
+
|
|
195
|
+
1. Coerce non-str → str.
|
|
196
|
+
2. Strip NUL bytes (defense vs. accidental binary in audit-log).
|
|
197
|
+
3. NFKC normalize (PLAN-065 Sec MF-4 — collapse homoglyph escapes:
|
|
198
|
+
fullwidth, ligatures, mathematical alphanumerics).
|
|
199
|
+
4. Length-bound to 200 chars (post-NFKC; NFKC may expand a few code
|
|
200
|
+
points but bound applies to final rendered string).
|
|
201
|
+
5. injection_patterns scan; substitute [REDACTED-INJECTION-PATTERN] on hit.
|
|
202
|
+
6. Strip HTML angle brackets + markdown link URL + backticks (defensive
|
|
203
|
+
belt-and-suspenders if patterns library missed a variant).
|
|
204
|
+
"""
|
|
205
|
+
if not isinstance(s, str):
|
|
206
|
+
s = str(s)
|
|
207
|
+
# NUL strip pre-NFKC (NFKC preserves NUL otherwise)
|
|
208
|
+
s = s.replace("\x00", "")
|
|
209
|
+
# NFKC homoglyph collapse — must run BEFORE length bound + scan so that
|
|
210
|
+
# fullwidth/ligature variants are normalized to their ASCII canonicals
|
|
211
|
+
# before the pattern scan (otherwise scanner misses them).
|
|
212
|
+
try:
|
|
213
|
+
s = unicodedata.normalize("NFKC", s)
|
|
214
|
+
except (TypeError, ValueError):
|
|
215
|
+
pass
|
|
216
|
+
s = s[:200]
|
|
217
|
+
if _injection_patterns is not None:
|
|
218
|
+
try:
|
|
219
|
+
# Codex S82 P0 #3 (post-patch v2): scan_harness_mimicry returns
|
|
220
|
+
# ScanResult dataclass (.matched bool), NOT iterable. Previous
|
|
221
|
+
# `if hits:` was always truthy → over-redaction of clean strings.
|
|
222
|
+
# Now check .matched attr; fall back to scan_text alias signature.
|
|
223
|
+
scan_fn = (
|
|
224
|
+
getattr(_injection_patterns, "scan_harness_mimicry", None)
|
|
225
|
+
or getattr(_injection_patterns, "scan_text", None)
|
|
226
|
+
)
|
|
227
|
+
if callable(scan_fn):
|
|
228
|
+
result = scan_fn(s)
|
|
229
|
+
# ScanResult has .matched (bool); legacy iterable returns truthy non-empty
|
|
230
|
+
matched = getattr(result, "matched", None)
|
|
231
|
+
if matched is None:
|
|
232
|
+
matched = bool(result) # legacy iterable contract
|
|
233
|
+
if matched:
|
|
234
|
+
return "[REDACTED-INJECTION-PATTERN]"
|
|
235
|
+
except Exception: # noqa: BLE001
|
|
236
|
+
pass
|
|
237
|
+
# Strip HTML angle brackets + markdown link syntax + backticks (defensive)
|
|
238
|
+
s = re.sub(r"[<>`]", "", s)
|
|
239
|
+
s = re.sub(r"\[([^\]]*)\]\([^)]*\)", r"\1", s)
|
|
240
|
+
return s
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ---- Result dataclass-lite -------------------------------------------------
|
|
244
|
+
class CheckResult:
|
|
245
|
+
__slots__ = ("name", "status", "summary", "duration_ms", "detail")
|
|
246
|
+
|
|
247
|
+
def __init__(self, name: str, status: str, summary: str, duration_ms: float, detail: Any = None):
|
|
248
|
+
self.name = name
|
|
249
|
+
self.status = status # green/yellow/red/timeout/error
|
|
250
|
+
self.summary = summary
|
|
251
|
+
self.duration_ms = duration_ms
|
|
252
|
+
self.detail = detail
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ---- 15 Tier-S checks (PoC implementations) -------------------------------
|
|
256
|
+
|
|
257
|
+
def check_plans_executing() -> Tuple[str, str, Any]:
|
|
258
|
+
plans = _get_plan_paths()
|
|
259
|
+
executing: List[str] = []
|
|
260
|
+
for p in plans:
|
|
261
|
+
try:
|
|
262
|
+
text = p.read_text(encoding="utf-8", errors="replace")
|
|
263
|
+
except OSError:
|
|
264
|
+
continue
|
|
265
|
+
m = re.match(r"^---\s*\n(.*?)\n---\s*\n", text, re.DOTALL)
|
|
266
|
+
if not m:
|
|
267
|
+
continue
|
|
268
|
+
if re.search(r"^status:\s*executing\s*$", m.group(1), re.MULTILINE):
|
|
269
|
+
executing.append(p.stem)
|
|
270
|
+
status = "yellow" if executing else "green"
|
|
271
|
+
return status, f"{len(executing)} executing", executing
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def check_plans_reviewed_pending() -> Tuple[str, str, Any]:
|
|
275
|
+
plans = _get_plan_paths()
|
|
276
|
+
reviewed: List[str] = []
|
|
277
|
+
for p in plans:
|
|
278
|
+
try:
|
|
279
|
+
text = p.read_text(encoding="utf-8", errors="replace")
|
|
280
|
+
except OSError:
|
|
281
|
+
continue
|
|
282
|
+
m = re.match(r"^---\s*\n(.*?)\n---\s*\n", text, re.DOTALL)
|
|
283
|
+
if not m:
|
|
284
|
+
continue
|
|
285
|
+
if re.search(r"^status:\s*reviewed\s*$", m.group(1), re.MULTILINE):
|
|
286
|
+
reviewed.append(p.stem)
|
|
287
|
+
return ("yellow" if reviewed else "green", f"{len(reviewed)} reviewed", reviewed)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def check_plans_stranded_executing() -> Tuple[str, str, Any]:
|
|
291
|
+
# Subprocess: git log --since=24h --name-only
|
|
292
|
+
try:
|
|
293
|
+
proc = subprocess.run(
|
|
294
|
+
["git", "-C", str(REPO_ROOT), "log", "--since=24 hours ago", "--name-only", "--pretty=format:"],
|
|
295
|
+
capture_output=True, text=True, timeout=2.0,
|
|
296
|
+
)
|
|
297
|
+
touched = {line.strip() for line in proc.stdout.splitlines() if line.strip()}
|
|
298
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
299
|
+
return "yellow", "git unavailable", None
|
|
300
|
+
# Cross-ref against executing plans
|
|
301
|
+
executing_status, _, executing_list = check_plans_executing()
|
|
302
|
+
stranded = [
|
|
303
|
+
plan for plan in executing_list
|
|
304
|
+
if not any(plan in t for t in touched)
|
|
305
|
+
]
|
|
306
|
+
return ("red" if stranded else "green", f"{len(stranded)} stranded", stranded)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def check_plans_draft() -> Tuple[str, str, Any]:
|
|
310
|
+
plans = _get_plan_paths()
|
|
311
|
+
draft: List[str] = []
|
|
312
|
+
for p in plans:
|
|
313
|
+
try:
|
|
314
|
+
text = p.read_text(encoding="utf-8", errors="replace")
|
|
315
|
+
except OSError:
|
|
316
|
+
continue
|
|
317
|
+
m = re.match(r"^---\s*\n(.*?)\n---\s*\n", text, re.DOTALL)
|
|
318
|
+
if not m:
|
|
319
|
+
continue
|
|
320
|
+
if re.search(r"^status:\s*draft\s*$", m.group(1), re.MULTILINE):
|
|
321
|
+
draft.append(p.stem)
|
|
322
|
+
return "green", f"{len(draft)} draft", draft
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def check_audit_log_freshness() -> Tuple[str, str, Any]:
|
|
326
|
+
"""Check audit-log freshness + surface errors sidecar signals.
|
|
327
|
+
|
|
328
|
+
F-6.3 (PLAN-113 W7-OPS): also inspects audit-log.errors sidecar so
|
|
329
|
+
that spool_writer FAIL-CLOSED floods become visible at boot time.
|
|
330
|
+
The errors sidecar is resolved via CEO_AUDIT_LOG_ERR env var if set,
|
|
331
|
+
otherwise defaults to audit-log.errors sibling of AUDIT_LOG_DEFAULT.
|
|
332
|
+
Fail-open on any OSError — never blocks boot.
|
|
333
|
+
"""
|
|
334
|
+
try:
|
|
335
|
+
st = AUDIT_LOG_DEFAULT.stat()
|
|
336
|
+
except OSError:
|
|
337
|
+
return "yellow", "audit-log missing", None
|
|
338
|
+
age_s = time.time() - st.st_mtime
|
|
339
|
+
age_h = age_s / 3600.0
|
|
340
|
+
size_mb = st.st_size / (1024 * 1024)
|
|
341
|
+
|
|
342
|
+
# F-6.3: inspect audit-log.errors sidecar for write failures.
|
|
343
|
+
errors_path_raw = os.environ.get("CEO_AUDIT_LOG_ERR", "")
|
|
344
|
+
if errors_path_raw:
|
|
345
|
+
errors_path = Path(errors_path_raw)
|
|
346
|
+
else:
|
|
347
|
+
errors_path = AUDIT_LOG_DEFAULT.parent / "audit-log.errors"
|
|
348
|
+
|
|
349
|
+
errors_present = False
|
|
350
|
+
errors_line_count = 0
|
|
351
|
+
try:
|
|
352
|
+
if errors_path.is_file():
|
|
353
|
+
errors_st = errors_path.stat()
|
|
354
|
+
if errors_st.st_size > 0:
|
|
355
|
+
errors_present = True
|
|
356
|
+
# Count lines without reading the full file into memory.
|
|
357
|
+
with errors_path.open("rb") as ef:
|
|
358
|
+
errors_line_count = sum(1 for _ in ef)
|
|
359
|
+
except OSError:
|
|
360
|
+
pass # fail-open
|
|
361
|
+
|
|
362
|
+
detail: Dict[str, Any] = {
|
|
363
|
+
"age_hours": age_h,
|
|
364
|
+
"size_mb": size_mb,
|
|
365
|
+
"errors_present": errors_present,
|
|
366
|
+
"errors_line_count": errors_line_count,
|
|
367
|
+
}
|
|
368
|
+
if errors_present:
|
|
369
|
+
status = "yellow"
|
|
370
|
+
summary = (
|
|
371
|
+
f"{age_h:.1f}h old, {size_mb:.1f} MB "
|
|
372
|
+
f"[audit-log.errors: {errors_line_count} lines]"
|
|
373
|
+
)
|
|
374
|
+
else:
|
|
375
|
+
status = "green" if age_h < 24 else "yellow"
|
|
376
|
+
summary = f"{age_h:.1f}h old, {size_mb:.1f} MB"
|
|
377
|
+
return status, summary, detail
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _iter_audit_events_since(hours: float = 24.0):
|
|
381
|
+
"""PoC streaming iterator — single-pass discipline."""
|
|
382
|
+
if not AUDIT_LOG_DEFAULT.exists():
|
|
383
|
+
return
|
|
384
|
+
cutoff = time.time() - hours * 3600
|
|
385
|
+
with AUDIT_LOG_DEFAULT.open("r", encoding="utf-8", errors="replace") as f:
|
|
386
|
+
for line in f:
|
|
387
|
+
line = line.strip()
|
|
388
|
+
if not line:
|
|
389
|
+
continue
|
|
390
|
+
try:
|
|
391
|
+
ev = json.loads(line)
|
|
392
|
+
except json.JSONDecodeError:
|
|
393
|
+
continue
|
|
394
|
+
ts = ev.get("ts") or ev.get("timestamp")
|
|
395
|
+
if not ts:
|
|
396
|
+
continue
|
|
397
|
+
# Best-effort epoch parse
|
|
398
|
+
try:
|
|
399
|
+
from datetime import datetime
|
|
400
|
+
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
|
401
|
+
if dt.timestamp() < cutoff:
|
|
402
|
+
continue
|
|
403
|
+
except (ValueError, TypeError):
|
|
404
|
+
continue
|
|
405
|
+
yield ev
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def check_dispatch_count_24h() -> Tuple[str, str, Any]:
|
|
409
|
+
n = sum(
|
|
410
|
+
1 for ev in _iter_audit_events_since(24)
|
|
411
|
+
if ev.get("action") == "agent_spawn" and not _is_test_pollution_event(ev)
|
|
412
|
+
)
|
|
413
|
+
return "green", f"{n} dispatches/24h", n
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# SHA256 of empty string — fingerprint for harness ghost-events that fire
|
|
417
|
+
# PostToolUse on Agent calls with no real payload (ToolSearch probes, canceled
|
|
418
|
+
# spawns, harness-internal invocations). S86 follow-up: these polluted the
|
|
419
|
+
# skill_unknown_ratio detector by inflating denominator with non-dispatches.
|
|
420
|
+
_EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
421
|
+
|
|
422
|
+
# S127 follow-up: PLAN-094 / PLAN-094-FOLLOWUP perf benchmarks + drain warmup
|
|
423
|
+
# fixtures emit synthetic `action=agent_spawn` events into the canonical
|
|
424
|
+
# audit-log via `audit_emit_dispatch.emit_generic` (e.g. wave-d-compound-
|
|
425
|
+
# benchmark-full.py:73). They carry a literal `test` discriminant — filter
|
|
426
|
+
# them from spawn-attribution detectors so the ratio reflects real CEO
|
|
427
|
+
# dispatches. Patch B (test redirection via TestEnvContext) is the proper
|
|
428
|
+
# fix; this hygiene patch stops the detector from mis-classifying.
|
|
429
|
+
_TEST_DISCRIMINANTS = ("bench", "warmup", "probe")
|
|
430
|
+
|
|
431
|
+
# S239: governance self-test probes — the `_probe_*` archetypes in the agent
|
|
432
|
+
# registry (`_probe_missing_skill`, `_probe_canonical_edit`, `_probe_architect`)
|
|
433
|
+
# are synthetic spawns whose entire purpose is to exercise the spawn / canonical
|
|
434
|
+
# hooks. They are skill-less by design and do zero real LLM work, yet they emit a
|
|
435
|
+
# genuine `action=agent_spawn` row that carries NO `test` discriminant — so the
|
|
436
|
+
# `test in _TEST_DISCRIMINANTS` line below misses them. Counting such a probe as a
|
|
437
|
+
# governance gap (skill=unknown) or a cache-coverage failure (cache_coverage_bps=0)
|
|
438
|
+
# is the exact false-positive class these filters exist to prevent: a single S237
|
|
439
|
+
# A3 hook-parity probe pinned BOTH skill_unknown_ratio and cache_discipline_alerted
|
|
440
|
+
# to red on an otherwise-idle window. A CLOSED SET of the three registered probe
|
|
441
|
+
# archetypes — not a `_probe_` PREFIX match — keeps this advisory detector from
|
|
442
|
+
# being side-stepped by a real skill-less dispatch that merely names itself
|
|
443
|
+
# `_probe_*` (the prefix would have excluded `_probe_anything`). These are the
|
|
444
|
+
# only `_probe_*` archetypes in the agent registry (Codex S239 review, P2).
|
|
445
|
+
_PROBE_ARCHETYPES = frozenset({
|
|
446
|
+
"_probe_missing_skill",
|
|
447
|
+
"_probe_canonical_edit",
|
|
448
|
+
"_probe_architect",
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _is_test_pollution_event(ev: Dict[str, Any]) -> bool:
|
|
453
|
+
if ev.get("test") in _TEST_DISCRIMINANTS:
|
|
454
|
+
return True
|
|
455
|
+
for key in ("archetype", "subagent_type"):
|
|
456
|
+
if ev.get(key) in _PROBE_ARCHETYPES:
|
|
457
|
+
return True
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _is_ghost_spawn_event(ev: Dict[str, Any]) -> bool:
|
|
462
|
+
"""True iff the agent_spawn event is a harness ghost-event (no real payload).
|
|
463
|
+
|
|
464
|
+
All four conditions must hold simultaneously to avoid false-positives on
|
|
465
|
+
legitimate near-empty dispatches: empty desc, no rail attribution, no
|
|
466
|
+
profile marker, and SHA-of-empty-string desc_hash.
|
|
467
|
+
"""
|
|
468
|
+
return (
|
|
469
|
+
ev.get("desc_preview") == ""
|
|
470
|
+
and ev.get("rail") is None
|
|
471
|
+
and ev.get("has_profile") is False
|
|
472
|
+
and ev.get("desc_hash") == _EMPTY_SHA256
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def check_skill_unknown_ratio() -> Tuple[str, str, Any]:
|
|
477
|
+
"""Detect spawns that should have SKILL injection but didn't.
|
|
478
|
+
|
|
479
|
+
S94 follow-up: excludes intentionally skill-less archetypes —
|
|
480
|
+
`general-purpose` subagent dispatches via the mitigated rail
|
|
481
|
+
(ADR-082) by design have no SKILL.md anchor. They broker cross-
|
|
482
|
+
LLM gate calls and similar utility work; counting them as FPs
|
|
483
|
+
inflates the ratio to 100% during healthy Codex MCP sessions
|
|
484
|
+
and trains the operator to ignore the channel.
|
|
485
|
+
|
|
486
|
+
A spawn counts as "skill missing" ONLY when its `archetype` is
|
|
487
|
+
a custom (non-general-purpose) one AND `skill` is unknown/empty.
|
|
488
|
+
That is the original PLAN-020 ADR-051 governance gap the
|
|
489
|
+
detector was built for.
|
|
490
|
+
"""
|
|
491
|
+
total = 0
|
|
492
|
+
unknown = 0
|
|
493
|
+
ghosts_skipped = 0
|
|
494
|
+
skill_less_by_design = 0
|
|
495
|
+
test_pollution_skipped = 0
|
|
496
|
+
for ev in _iter_audit_events_since(24):
|
|
497
|
+
if ev.get("action") != "agent_spawn":
|
|
498
|
+
continue
|
|
499
|
+
if _is_test_pollution_event(ev):
|
|
500
|
+
test_pollution_skipped += 1
|
|
501
|
+
continue
|
|
502
|
+
if _is_ghost_spawn_event(ev):
|
|
503
|
+
ghosts_skipped += 1
|
|
504
|
+
continue
|
|
505
|
+
# Skill-less by design: general-purpose archetype dispatches
|
|
506
|
+
# (mitigated rail per ADR-082) AND built-in subagent types like
|
|
507
|
+
# Explore/Plan/claude-code-guide that have no .claude/agents/<name>.md
|
|
508
|
+
# and so cannot carry a `Loads <skill> skill via reference` phrase
|
|
509
|
+
# (drift-detector contract per S143 lesson). Adding them to
|
|
510
|
+
# _ARCHETYPE_TO_SKILL would violate the contract — exclude here instead.
|
|
511
|
+
# S200: claude/claude-code-guide/statusline-setup are first-party
|
|
512
|
+
# Claude Code built-ins (no .claude/agents anchor); counting them as a
|
|
513
|
+
# governance gap is a false positive — exactly the FP class this filter
|
|
514
|
+
# exists to prevent (else healthy claude-code-guide use trains the
|
|
515
|
+
# operator to ignore the channel).
|
|
516
|
+
_SKILL_LESS_BUILTINS = {
|
|
517
|
+
"general-purpose", "Explore", "Plan",
|
|
518
|
+
"claude", "claude-code-guide", "statusline-setup",
|
|
519
|
+
}
|
|
520
|
+
if (
|
|
521
|
+
ev.get("subagent_type") in _SKILL_LESS_BUILTINS
|
|
522
|
+
and ev.get("archetype") in _SKILL_LESS_BUILTINS
|
|
523
|
+
):
|
|
524
|
+
skill_less_by_design += 1
|
|
525
|
+
continue
|
|
526
|
+
total += 1
|
|
527
|
+
if ev.get("skill") in (None, "unknown", ""):
|
|
528
|
+
unknown += 1
|
|
529
|
+
if total == 0:
|
|
530
|
+
msg = (
|
|
531
|
+
"no custom-archetype spawns "
|
|
532
|
+
"({s} general-purpose, {g} ghosts, {t} test-pollution)".format(
|
|
533
|
+
s=skill_less_by_design, g=ghosts_skipped, t=test_pollution_skipped,
|
|
534
|
+
)
|
|
535
|
+
)
|
|
536
|
+
return "green", msg, {
|
|
537
|
+
"unknown": 0, "total": 0,
|
|
538
|
+
"ghosts_skipped": ghosts_skipped,
|
|
539
|
+
"skill_less_by_design": skill_less_by_design,
|
|
540
|
+
"test_pollution_skipped": test_pollution_skipped,
|
|
541
|
+
}
|
|
542
|
+
ratio = unknown / total
|
|
543
|
+
status = "red" if ratio > 0.10 else "yellow" if ratio > 0 else "green"
|
|
544
|
+
return status, f"{unknown}/{total} = {ratio:.0%}", {
|
|
545
|
+
"unknown": unknown, "total": total,
|
|
546
|
+
"ghosts_skipped": ghosts_skipped,
|
|
547
|
+
"skill_less_by_design": skill_less_by_design,
|
|
548
|
+
"test_pollution_skipped": test_pollution_skipped,
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def check_governance_validate() -> Tuple[str, str, Any]:
|
|
553
|
+
"""PLAN-082 Codex Item A: dispatch fast-profile validator.
|
|
554
|
+
|
|
555
|
+
Calls `validate-governance.sh --fast --json` (delegates to
|
|
556
|
+
`validate_governance_fast.py`) with a 1.8 s timeout. Parses JSON
|
|
557
|
+
output; `rc != 0` is the red truth signal (NOT `stdout.count("ERROR")`
|
|
558
|
+
— Codex 6th-option catch: the full validator emits `FAIL:` in some
|
|
559
|
+
sections without printing literal "ERROR", which underclassified
|
|
560
|
+
failures as yellow).
|
|
561
|
+
"""
|
|
562
|
+
script = REPO_ROOT / ".claude" / "scripts" / "validate-governance.sh"
|
|
563
|
+
if not script.exists():
|
|
564
|
+
return "yellow", "validate-governance missing", None
|
|
565
|
+
try:
|
|
566
|
+
proc = subprocess.run(
|
|
567
|
+
["bash", str(script), "--fast", "--json"],
|
|
568
|
+
capture_output=True, text=True, timeout=1.8, cwd=str(REPO_ROOT),
|
|
569
|
+
)
|
|
570
|
+
except subprocess.TimeoutExpired:
|
|
571
|
+
return "red", "validate timeout (fast)", None
|
|
572
|
+
rc = proc.returncode
|
|
573
|
+
try:
|
|
574
|
+
payload = json.loads(proc.stdout) if proc.stdout else {}
|
|
575
|
+
except (json.JSONDecodeError, ValueError):
|
|
576
|
+
payload = {}
|
|
577
|
+
errors = payload.get("errors", []) if isinstance(payload, dict) else []
|
|
578
|
+
warnings = payload.get("warnings", []) if isinstance(payload, dict) else []
|
|
579
|
+
n_err = len(errors) if isinstance(errors, list) else 0
|
|
580
|
+
n_warn = len(warnings) if isinstance(warnings, list) else 0
|
|
581
|
+
# rc != 0 is the red truth (Codex 6th-option catch).
|
|
582
|
+
if rc != 0:
|
|
583
|
+
status = "red"
|
|
584
|
+
summary = f"fast fail: {n_err} error(s)"
|
|
585
|
+
elif n_warn:
|
|
586
|
+
status = "yellow"
|
|
587
|
+
summary = f"fast pass, {n_warn} warn(s)"
|
|
588
|
+
else:
|
|
589
|
+
status = "green"
|
|
590
|
+
summary = "fast pass"
|
|
591
|
+
return status, summary, {"rc": rc, "errors": n_err, "warnings": n_warn, "profile": "fast"}
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def check_hook_live_smoke() -> Tuple[str, str, Any]:
|
|
595
|
+
"""PLAN-082 Codex Item D replacement for hook_test_baseline.
|
|
596
|
+
|
|
597
|
+
Drops the broken `.claude/cache/hook-tests.json` baseline (never
|
|
598
|
+
populated; required pytest 12 s — not boot-budget feasible). Per
|
|
599
|
+
Codex 6th-option: replace with a cheap live hook smoke — parse
|
|
600
|
+
settings.json + verify referenced hook files exist + executable +
|
|
601
|
+
`py_compile` cleanly. Stdlib only, no pytest.
|
|
602
|
+
|
|
603
|
+
Test provenance (last full pytest pass) moves to Tier-A
|
|
604
|
+
(`tier_a_hook_test_baseline_age` — separate check).
|
|
605
|
+
"""
|
|
606
|
+
import py_compile
|
|
607
|
+
|
|
608
|
+
settings = REPO_ROOT / ".claude" / "settings.json"
|
|
609
|
+
if not settings.exists():
|
|
610
|
+
return "yellow", "settings.json missing", None
|
|
611
|
+
try:
|
|
612
|
+
data = json.loads(settings.read_text(encoding="utf-8"))
|
|
613
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
614
|
+
return "red", f"settings.json parse: {exc.__class__.__name__}", None
|
|
615
|
+
|
|
616
|
+
hooks_table = data.get("hooks") if isinstance(data, dict) else None
|
|
617
|
+
if not isinstance(hooks_table, dict):
|
|
618
|
+
return "yellow", "no hooks table", None
|
|
619
|
+
|
|
620
|
+
shim_re = re.compile(r"_python-hook\.sh[\"']?\s+[\"']?([A-Za-z0-9_./-]+\.py)")
|
|
621
|
+
direct_re = re.compile(r"\.claude/hooks/[A-Za-z0-9_./-]+\.py")
|
|
622
|
+
seen: List[str] = []
|
|
623
|
+
seen_set: set = set()
|
|
624
|
+
for hook_list in hooks_table.values():
|
|
625
|
+
if not isinstance(hook_list, list):
|
|
626
|
+
continue
|
|
627
|
+
for entry in hook_list:
|
|
628
|
+
if not isinstance(entry, dict):
|
|
629
|
+
continue
|
|
630
|
+
inner = entry.get("hooks") if isinstance(entry.get("hooks"), list) else []
|
|
631
|
+
for cmd_obj in inner:
|
|
632
|
+
if not isinstance(cmd_obj, dict):
|
|
633
|
+
continue
|
|
634
|
+
cmd_str = cmd_obj.get("command", "")
|
|
635
|
+
if not isinstance(cmd_str, str):
|
|
636
|
+
continue
|
|
637
|
+
refs: List[str] = []
|
|
638
|
+
refs.extend(direct_re.findall(cmd_str))
|
|
639
|
+
refs.extend(shim_re.findall(cmd_str))
|
|
640
|
+
for raw in refs:
|
|
641
|
+
s = re.sub(r"^\$\{?CLAUDE_PROJECT_DIR\}?/", "", raw)
|
|
642
|
+
if "/" not in s:
|
|
643
|
+
s = f".claude/hooks/{s}"
|
|
644
|
+
if s not in seen_set:
|
|
645
|
+
seen_set.add(s)
|
|
646
|
+
seen.append(s)
|
|
647
|
+
|
|
648
|
+
failures: List[str] = []
|
|
649
|
+
checked = 0
|
|
650
|
+
for rel in seen:
|
|
651
|
+
path = REPO_ROOT / rel
|
|
652
|
+
if not path.is_file():
|
|
653
|
+
failures.append(f"missing:{rel}")
|
|
654
|
+
continue
|
|
655
|
+
try:
|
|
656
|
+
py_compile.compile(str(path), doraise=True)
|
|
657
|
+
except (py_compile.PyCompileError, OSError):
|
|
658
|
+
failures.append(f"compile_fail:{rel}")
|
|
659
|
+
continue
|
|
660
|
+
checked += 1
|
|
661
|
+
|
|
662
|
+
if failures:
|
|
663
|
+
return "red", f"{len(failures)}/{len(seen)} hook(s) broken", {
|
|
664
|
+
"failures": failures[:10], # bound for sanitization
|
|
665
|
+
"checked": checked,
|
|
666
|
+
"total": len(seen),
|
|
667
|
+
}
|
|
668
|
+
if checked == 0:
|
|
669
|
+
return "yellow", "no hooks discovered", {"checked": 0, "total": 0}
|
|
670
|
+
return "green", f"{checked} hook(s) smoke-pass", {"checked": checked, "total": len(seen)}
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
# Backward-compat alias — some external callers / tests may still import the
|
|
674
|
+
# old name. Live smoke is a strict improvement (no cache dependency, faster).
|
|
675
|
+
check_hook_test_baseline = check_hook_live_smoke
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def check_audit_v3_backlog() -> Tuple[str, str, Any]:
|
|
679
|
+
# PoC: count plans with audit_v3_* tags still open
|
|
680
|
+
plans = _get_plan_paths()
|
|
681
|
+
backlog: List[str] = []
|
|
682
|
+
for p in plans:
|
|
683
|
+
try:
|
|
684
|
+
text = p.read_text(encoding="utf-8", errors="replace")
|
|
685
|
+
except OSError:
|
|
686
|
+
continue
|
|
687
|
+
m = re.match(r"^---\s*\n(.*?)\n---\s*\n", text, re.DOTALL)
|
|
688
|
+
if not m:
|
|
689
|
+
continue
|
|
690
|
+
front = m.group(1)
|
|
691
|
+
if "audit_v3" in front and not re.search(r"^status:\s*done\s*$", front, re.MULTILINE):
|
|
692
|
+
backlog.append(p.stem)
|
|
693
|
+
return ("yellow" if backlog else "green", f"{len(backlog)} open", backlog)
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def check_sentinels_pending_gpg() -> Tuple[str, str, Any]:
|
|
697
|
+
"""Count GPG-pending sentinels post-cutoff.
|
|
698
|
+
|
|
699
|
+
Codex S82 P2 fix: previous impl scanned ALL historical sentinels with no
|
|
700
|
+
date cutoff → 30+ pending entries every boot from PLAN-030/031/039 round-1
|
|
701
|
+
pre-enforcement era. Now applies SENTINEL_CUTOFF_EPOCH (2026-04-22) to
|
|
702
|
+
skip legacy. Also Codex S82 P2 sorted glob for CR-N7 stable ordering.
|
|
703
|
+
"""
|
|
704
|
+
pending: List[str] = []
|
|
705
|
+
plans_dir = REPO_ROOT / ".claude" / "plans"
|
|
706
|
+
for approved in sorted(plans_dir.glob("PLAN-*/architect/round-*/approved.md")):
|
|
707
|
+
try:
|
|
708
|
+
mtime = approved.stat().st_mtime
|
|
709
|
+
except OSError:
|
|
710
|
+
continue
|
|
711
|
+
if mtime < SENTINEL_CUTOFF_EPOCH:
|
|
712
|
+
continue # pre-enforcement legacy
|
|
713
|
+
if not (approved.parent / "approved.md.asc").exists():
|
|
714
|
+
pending.append(str(approved.relative_to(REPO_ROOT)))
|
|
715
|
+
return ("yellow" if pending else "green", f"{len(pending)} pending", pending)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def check_rc_hold_aged() -> Tuple[str, str, Any]:
|
|
719
|
+
release_md = REPO_ROOT / "RELEASE.md"
|
|
720
|
+
rc_hold = REPO_ROOT / "RC-HOLD.md"
|
|
721
|
+
target = release_md if release_md.exists() else rc_hold if rc_hold.exists() else None
|
|
722
|
+
if target is None:
|
|
723
|
+
return "green", "no rc-hold doc", None
|
|
724
|
+
text = target.read_text(encoding="utf-8", errors="replace")
|
|
725
|
+
# PoC: count rc-hold-waiver entries (real impl would parse dates)
|
|
726
|
+
n = len(re.findall(r"rc-hold-waiver", text))
|
|
727
|
+
return ("yellow" if n else "green", f"{n} rc-hold-waiver entries", n)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
# E7-F5 (PLAN-120-FOLLOWUP): real daily-USD burn-rate thresholds.
|
|
731
|
+
# Previously this check returned "green" unconditionally (Potemkin stub) — it
|
|
732
|
+
# tallied cost_usd over 24h but never compared it against a budget, so a
|
|
733
|
+
# runaway burn never surfaced at boot. We now apply a yellow/red ceiling.
|
|
734
|
+
# Both bounds are env-overridable (adopter sessions differ wildly in cost);
|
|
735
|
+
# defaults are calibrated to a typical heavy CEO session. Fail-OPEN: any
|
|
736
|
+
# parse/lookup error degrades to the default ceiling, never raises, and an
|
|
737
|
+
# empty/zero-cost log stays green (the steady-state).
|
|
738
|
+
_COST_YELLOW_USD_DEFAULT = 50.0
|
|
739
|
+
_COST_RED_USD_DEFAULT = 150.0
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def _cost_threshold(env_var: str, default: float) -> float:
|
|
743
|
+
"""Read a positive float ceiling from env; fall back to default fail-open."""
|
|
744
|
+
raw = os.environ.get(env_var, "")
|
|
745
|
+
if not raw:
|
|
746
|
+
return default
|
|
747
|
+
try:
|
|
748
|
+
val = float(raw.strip())
|
|
749
|
+
except (TypeError, ValueError):
|
|
750
|
+
return default
|
|
751
|
+
# Reject non-finite / non-positive overrides (would defeat the gate).
|
|
752
|
+
if val != val or val == float("inf") or val <= 0:
|
|
753
|
+
return default
|
|
754
|
+
return val
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def check_cost_24h_usd() -> Tuple[str, str, Any]:
|
|
758
|
+
"""Sum cost_usd over 24h and gate against env-overridable USD ceilings.
|
|
759
|
+
|
|
760
|
+
yellow at CEO_BOOT_COST_YELLOW_USD (default $50/24h); red at
|
|
761
|
+
CEO_BOOT_COST_RED_USD (default $150/24h). Fail-open: no cost datapoints
|
|
762
|
+
or unreadable log => green. Thresholds are advisory burn-rate alerts
|
|
763
|
+
(ADR-064 50/80/95% doctrine), never a hard block.
|
|
764
|
+
"""
|
|
765
|
+
total = 0.0
|
|
766
|
+
samples = 0
|
|
767
|
+
for ev in _iter_audit_events_since(24):
|
|
768
|
+
c = ev.get("cost_usd")
|
|
769
|
+
if isinstance(c, (int, float)) and not isinstance(c, bool):
|
|
770
|
+
total += float(c)
|
|
771
|
+
samples += 1
|
|
772
|
+
yellow = _cost_threshold("CEO_BOOT_COST_YELLOW_USD", _COST_YELLOW_USD_DEFAULT)
|
|
773
|
+
red = _cost_threshold("CEO_BOOT_COST_RED_USD", _COST_RED_USD_DEFAULT)
|
|
774
|
+
# Guard against an inverted override (yellow >= red): keep red as the
|
|
775
|
+
# higher bound so the status ladder stays monotonic.
|
|
776
|
+
if yellow >= red:
|
|
777
|
+
yellow = min(yellow, red)
|
|
778
|
+
if samples == 0:
|
|
779
|
+
status = "green"
|
|
780
|
+
elif total >= red:
|
|
781
|
+
status = "red"
|
|
782
|
+
elif total >= yellow:
|
|
783
|
+
status = "yellow"
|
|
784
|
+
else:
|
|
785
|
+
status = "green"
|
|
786
|
+
return status, f"${total:.2f}/24h", {
|
|
787
|
+
"total_usd": round(total, 4),
|
|
788
|
+
"samples": samples,
|
|
789
|
+
"yellow_usd": yellow,
|
|
790
|
+
"red_usd": red,
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def check_active_plan_burn_ratio() -> Tuple[str, str, Any]:
|
|
795
|
+
# PoC: find first executing plan, parse budget_tokens, sum tokens from log
|
|
796
|
+
_, _, executing = check_plans_executing()
|
|
797
|
+
if not executing:
|
|
798
|
+
return "green", "no active plan", None
|
|
799
|
+
plan_id = executing[0].split("-")[0] + "-" + executing[0].split("-")[1] if executing else None
|
|
800
|
+
if not plan_id:
|
|
801
|
+
return "green", "no plan id", None
|
|
802
|
+
plan_path = REPO_ROOT / ".claude" / "plans" / f"{executing[0]}.md"
|
|
803
|
+
try:
|
|
804
|
+
text = plan_path.read_text(encoding="utf-8", errors="replace")
|
|
805
|
+
except OSError:
|
|
806
|
+
return "yellow", "plan unreadable", None
|
|
807
|
+
m = re.search(r"^budget_tokens:\s*(.+?)$", text, re.MULTILINE)
|
|
808
|
+
if not m:
|
|
809
|
+
return "yellow", "no budget_tokens", None
|
|
810
|
+
return "green", f"budget {m.group(1).strip()}", {"plan": plan_id, "budget_raw": m.group(1).strip()}
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def check_adrs_stale_proposed() -> Tuple[str, str, Any]:
|
|
814
|
+
adrs = sorted((REPO_ROOT / ".claude" / "adr").glob("ADR-*.md"))
|
|
815
|
+
proposed_old: List[str] = []
|
|
816
|
+
now = time.time()
|
|
817
|
+
for a in adrs:
|
|
818
|
+
try:
|
|
819
|
+
text = a.read_text(encoding="utf-8", errors="replace")
|
|
820
|
+
mtime = a.stat().st_mtime
|
|
821
|
+
except OSError:
|
|
822
|
+
continue
|
|
823
|
+
m = re.match(r"^---\s*\n(.*?)\n---\s*\n", text, re.DOTALL)
|
|
824
|
+
if not m:
|
|
825
|
+
continue
|
|
826
|
+
if re.search(r"^status:\s*proposed\s*$", m.group(1), re.MULTILINE):
|
|
827
|
+
age_d = (now - mtime) / 86400
|
|
828
|
+
if age_d > 30:
|
|
829
|
+
proposed_old.append(a.stem)
|
|
830
|
+
return ("yellow" if proposed_old else "green", f"{len(proposed_old)} proposed >30d", proposed_old)
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
# ---- 10 Tier-A checks (--verbose mode; PLAN-065 §4.3.3) ------------------
|
|
834
|
+
# Selection rationale (each picked for high signal-to-cost ratio + non-overlap
|
|
835
|
+
# with Tier-S; see PLAN-065 §4.3.3):
|
|
836
|
+
# tier_a_debate_transcripts — debate hygiene (Round-1 archetype output)
|
|
837
|
+
# tier_a_lessons_30d — lesson velocity (memory growth pulse)
|
|
838
|
+
# tier_a_spec_version_drift — VERSION ↔ SPEC/v*/VERSION mismatch
|
|
839
|
+
# tier_a_npm_version_match — package.json vs VERSION mismatch
|
|
840
|
+
# tier_a_waivers_count — waivers/*.md aggregate (rc-hold + cosmetic)
|
|
841
|
+
# tier_a_adrs_recent_status — ADR-098..104 reservation slots tracking
|
|
842
|
+
# tier_a_cache_hit_rate_24h — ceo-boot-emitted cache_hit ratio (self-loop)
|
|
843
|
+
# tier_a_hook_test_baseline_age — last-cached hook-test baseline age (S81 cache)
|
|
844
|
+
# tier_a_sentinel_signers_tracked — .claude/state/sentinel-signers.txt git-tracked?
|
|
845
|
+
# tier_a_gitignore_state_excluded — .gitignore covers state/ dir?
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
def check_tier_a_debate_transcripts() -> Tuple[str, str, Any]:
|
|
849
|
+
"""Count debate transcripts produced in last 24h (forensic hygiene)."""
|
|
850
|
+
debate_root = REPO_ROOT / ".claude" / "plans"
|
|
851
|
+
n = 0
|
|
852
|
+
cutoff = time.time() - 86400
|
|
853
|
+
for transcript in debate_root.rglob("debate/*/round-*.md"):
|
|
854
|
+
try:
|
|
855
|
+
if transcript.stat().st_mtime >= cutoff:
|
|
856
|
+
n += 1
|
|
857
|
+
except OSError:
|
|
858
|
+
continue
|
|
859
|
+
# Status green regardless — informational. Yellow if zero AND there is an
|
|
860
|
+
# executing plan (suggests work without debate trail).
|
|
861
|
+
_, _, executing = check_plans_executing()
|
|
862
|
+
if n == 0 and executing:
|
|
863
|
+
return "yellow", f"0 transcripts/24h ({len(executing)} executing)", n
|
|
864
|
+
return "green", f"{n} transcripts/24h", n
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
def check_tier_a_lessons_30d() -> Tuple[str, str, Any]:
|
|
868
|
+
"""Count lessons added in memory dir over 30d (informational)."""
|
|
869
|
+
# Derive the Claude Code project slug from the project dir (absolute path
|
|
870
|
+
# with "/" -> "-") instead of hard-coding the meta-repo's slug, so this
|
|
871
|
+
# resolves correctly in any install. (Previously hard-coded the Owner's
|
|
872
|
+
# absolute home path, which broke for every other install and tripped the
|
|
873
|
+
# contamination guard.)
|
|
874
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
|
875
|
+
slug = str(Path(project_dir).resolve()).replace("/", "-")
|
|
876
|
+
mem_dir = Path.home() / ".claude" / "projects" / slug / "memory"
|
|
877
|
+
if not mem_dir.exists():
|
|
878
|
+
return "green", "memory dir absent", 0
|
|
879
|
+
cutoff = time.time() - 30 * 86400
|
|
880
|
+
n = 0
|
|
881
|
+
for f in mem_dir.glob("*.md"):
|
|
882
|
+
try:
|
|
883
|
+
if f.stat().st_mtime >= cutoff:
|
|
884
|
+
n += 1
|
|
885
|
+
except OSError:
|
|
886
|
+
continue
|
|
887
|
+
return "green", f"{n} lessons/30d", n
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def check_tier_a_spec_version_drift() -> Tuple[str, str, Any]:
|
|
891
|
+
"""VERSION file vs latest SPEC/v*/VERSION agreement (informational)."""
|
|
892
|
+
version_file = REPO_ROOT / "VERSION"
|
|
893
|
+
if not version_file.exists():
|
|
894
|
+
return "yellow", "VERSION missing", None
|
|
895
|
+
try:
|
|
896
|
+
repo_v = version_file.read_text(encoding="utf-8", errors="replace").strip()
|
|
897
|
+
except OSError:
|
|
898
|
+
return "yellow", "VERSION unreadable", None
|
|
899
|
+
spec_root = REPO_ROOT / "SPEC"
|
|
900
|
+
if not spec_root.exists():
|
|
901
|
+
return "green", f"repo {repo_v}, no SPEC dir", repo_v
|
|
902
|
+
spec_versions = sorted(p.name for p in spec_root.iterdir() if p.is_dir() and p.name.startswith("v"))
|
|
903
|
+
if not spec_versions:
|
|
904
|
+
return "green", f"repo {repo_v}, no SPEC versions", repo_v
|
|
905
|
+
return "green", f"repo {repo_v}, spec {','.join(spec_versions[-2:])}", {
|
|
906
|
+
"repo_version": repo_v, "spec_versions": spec_versions,
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def check_tier_a_npm_version_match() -> Tuple[str, str, Any]:
|
|
911
|
+
"""package.json version vs VERSION file (Codex S79 P1 finding)."""
|
|
912
|
+
version_file = REPO_ROOT / "VERSION"
|
|
913
|
+
pkg_file = REPO_ROOT / "package.json"
|
|
914
|
+
if not version_file.exists() or not pkg_file.exists():
|
|
915
|
+
return "green", "no npm artifacts", None
|
|
916
|
+
try:
|
|
917
|
+
repo_v = version_file.read_text(encoding="utf-8", errors="replace").strip()
|
|
918
|
+
pkg = json.loads(pkg_file.read_text(encoding="utf-8", errors="replace"))
|
|
919
|
+
except (OSError, json.JSONDecodeError):
|
|
920
|
+
return "yellow", "parse error", None
|
|
921
|
+
pkg_v = pkg.get("version", "")
|
|
922
|
+
if pkg_v == repo_v:
|
|
923
|
+
return "green", f"match {repo_v}", {"version": repo_v}
|
|
924
|
+
return "red", f"drift: VERSION={repo_v} package.json={pkg_v}", {
|
|
925
|
+
"repo_version": repo_v, "pkg_version": pkg_v,
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def check_tier_a_waivers_count() -> Tuple[str, str, Any]:
|
|
930
|
+
"""Aggregate waivers/*.md count (rc-hold + cosmetic + audit)."""
|
|
931
|
+
waivers_dir = REPO_ROOT / "waivers"
|
|
932
|
+
if not waivers_dir.exists():
|
|
933
|
+
return "green", "no waivers dir", 0
|
|
934
|
+
waivers = list(waivers_dir.glob("*.md"))
|
|
935
|
+
n = len(waivers)
|
|
936
|
+
status = "yellow" if n > 5 else "green"
|
|
937
|
+
return status, f"{n} waivers", n
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def check_tier_a_adrs_recent_status() -> Tuple[str, str, Any]:
|
|
941
|
+
"""ADR-098..104 status tracker (PLAN-065 reserved slots — drift detector)."""
|
|
942
|
+
adr_dir = REPO_ROOT / ".claude" / "adr"
|
|
943
|
+
statuses: Dict[str, str] = {}
|
|
944
|
+
for adr_num in range(98, 105):
|
|
945
|
+
matches = list(adr_dir.glob(f"ADR-{adr_num:03d}-*.md"))
|
|
946
|
+
if not matches:
|
|
947
|
+
statuses[f"ADR-{adr_num:03d}"] = "missing"
|
|
948
|
+
continue
|
|
949
|
+
try:
|
|
950
|
+
text = matches[0].read_text(encoding="utf-8", errors="replace")
|
|
951
|
+
except OSError:
|
|
952
|
+
statuses[f"ADR-{adr_num:03d}"] = "unreadable"
|
|
953
|
+
continue
|
|
954
|
+
m = re.search(r"^status:\s*([a-zA-Z\-]+)\s*$", text, re.MULTILINE)
|
|
955
|
+
statuses[f"ADR-{adr_num:03d}"] = m.group(1).lower() if m else "unknown"
|
|
956
|
+
accepted = sum(1 for v in statuses.values() if v == "accepted")
|
|
957
|
+
return "green", f"{accepted}/{len(statuses)} accepted", statuses
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def check_tier_a_cache_hit_rate_24h() -> Tuple[str, str, Any]:
|
|
961
|
+
"""ceo_boot_emitted cache_hit ratio over 24h (self-observation)."""
|
|
962
|
+
total = 0
|
|
963
|
+
hits = 0
|
|
964
|
+
for ev in _iter_audit_events_since(24):
|
|
965
|
+
if ev.get("action") != "ceo_boot_emitted":
|
|
966
|
+
continue
|
|
967
|
+
total += 1
|
|
968
|
+
if ev.get("cache_hit"):
|
|
969
|
+
hits += 1
|
|
970
|
+
if total == 0:
|
|
971
|
+
return "green", "no boots/24h", 0
|
|
972
|
+
ratio = hits / total
|
|
973
|
+
return "green", f"{hits}/{total} = {ratio:.0%} cache-hit", {
|
|
974
|
+
"hits": hits, "total": total, "ratio": ratio,
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def check_tier_a_hook_test_baseline_age() -> Tuple[str, str, Any]:
|
|
979
|
+
"""Age of cached hook-test baseline file (S81 cache convention)."""
|
|
980
|
+
cache = REPO_ROOT / ".claude" / "cache" / "hook-tests.json"
|
|
981
|
+
if not cache.exists():
|
|
982
|
+
return "yellow", "no cached baseline", None
|
|
983
|
+
try:
|
|
984
|
+
st = cache.stat()
|
|
985
|
+
except OSError:
|
|
986
|
+
return "yellow", "stat failed", None
|
|
987
|
+
age_h = (time.time() - st.st_mtime) / 3600.0
|
|
988
|
+
status = "green" if age_h < 168 else "yellow" # 7d window
|
|
989
|
+
return status, f"{age_h:.1f}h old", {"age_hours": age_h}
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def check_tier_a_sentinel_signers_tracked() -> Tuple[str, str, Any]:
|
|
993
|
+
"""sentinel-signers.txt presence + git-tracked status."""
|
|
994
|
+
candidates = [
|
|
995
|
+
REPO_ROOT / ".claude" / "state" / "sentinel-signers.txt",
|
|
996
|
+
REPO_ROOT / "sentinel-signers.txt",
|
|
997
|
+
]
|
|
998
|
+
found: Optional[Path] = None
|
|
999
|
+
for c in candidates:
|
|
1000
|
+
if c.exists():
|
|
1001
|
+
found = c
|
|
1002
|
+
break
|
|
1003
|
+
if found is None:
|
|
1004
|
+
return "yellow", "sentinel-signers.txt missing", None
|
|
1005
|
+
try:
|
|
1006
|
+
proc = subprocess.run(
|
|
1007
|
+
["git", "-C", str(REPO_ROOT), "ls-files", "--error-unmatch", str(found.relative_to(REPO_ROOT))],
|
|
1008
|
+
capture_output=True, text=True, timeout=1.5,
|
|
1009
|
+
)
|
|
1010
|
+
tracked = (proc.returncode == 0)
|
|
1011
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, ValueError):
|
|
1012
|
+
return "yellow", "git unavailable", None
|
|
1013
|
+
return ("green" if tracked else "yellow",
|
|
1014
|
+
f"present, tracked={tracked}",
|
|
1015
|
+
{"path": str(found.relative_to(REPO_ROOT)), "tracked": tracked})
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def check_tier_a_gitignore_state_excluded() -> Tuple[str, str, Any]:
|
|
1019
|
+
""".gitignore covers state/ dir (LRU cache + sentinels safety)."""
|
|
1020
|
+
gi = REPO_ROOT / ".gitignore"
|
|
1021
|
+
if not gi.exists():
|
|
1022
|
+
return "yellow", ".gitignore missing", None
|
|
1023
|
+
try:
|
|
1024
|
+
text = gi.read_text(encoding="utf-8", errors="replace")
|
|
1025
|
+
except OSError:
|
|
1026
|
+
return "yellow", ".gitignore unreadable", None
|
|
1027
|
+
# Match leading patterns: state/, .claude/state/, /state, etc.
|
|
1028
|
+
has_state = bool(re.search(r"(?m)^\s*\.?/?(?:\.claude/)?state/", text))
|
|
1029
|
+
return ("green" if has_state else "yellow",
|
|
1030
|
+
f"state/ excluded={has_state}",
|
|
1031
|
+
{"covered": has_state})
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
# ---- Registry --------------------------------------------------------------
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
def check_tier_policy_misrouting_24h() -> Tuple[str, str, Any]:
|
|
1038
|
+
"""16th Tier-S check — delegates to standalone hook module (PLAN-091 W2.1).
|
|
1039
|
+
|
|
1040
|
+
Lazy-imports ``.claude/hooks/check_tier_policy_misrouting_24h.py`` so
|
|
1041
|
+
that the standalone module can also be invoked as a CLI smoke-test
|
|
1042
|
+
(``python3 .claude/hooks/check_tier_policy_misrouting_24h.py``).
|
|
1043
|
+
Any import-time failure surfaces as a `yellow` status (fail-soft
|
|
1044
|
+
Tier-S contract); the dispatcher's outer try/except still wraps the
|
|
1045
|
+
inner call for additional defense-in-depth.
|
|
1046
|
+
"""
|
|
1047
|
+
try:
|
|
1048
|
+
hooks_dir = REPO_ROOT / ".claude" / "hooks"
|
|
1049
|
+
if str(hooks_dir) not in sys.path:
|
|
1050
|
+
sys.path.insert(0, str(hooks_dir))
|
|
1051
|
+
from check_tier_policy_misrouting_24h import ( # type: ignore
|
|
1052
|
+
check_tier_policy_misrouting_24h as _impl,
|
|
1053
|
+
)
|
|
1054
|
+
return _impl()
|
|
1055
|
+
except Exception as exc: # noqa: BLE001 (Tier-S fail-soft floor)
|
|
1056
|
+
return "yellow", f"tier_policy_misrouting import error: {exc}", None
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
def check_cache_discipline_alerted() -> Tuple[str, str, Any]:
|
|
1060
|
+
"""17th Tier-S check — prompt-cache coverage detection (PLAN-093 Wave C.2).
|
|
1061
|
+
|
|
1062
|
+
Surfaces ``cache_discipline_alerted`` when prompt-cache coverage falls
|
|
1063
|
+
below 0.7 over the last 24h of audit-log events. Detection is heuristic:
|
|
1064
|
+
any audit row (``agent_spawn`` action) carrying a ``cache_coverage_bps``
|
|
1065
|
+
numeric field is averaged; rows without the field are ignored. Absent
|
|
1066
|
+
any datapoints, status is green with a "no data" summary (fail-soft
|
|
1067
|
+
Tier-S contract).
|
|
1068
|
+
|
|
1069
|
+
Field alignment (F-5-5.1-0624274e fix): audit_log.py emits the
|
|
1070
|
+
cache-coverage metric derived from usage_metadata cache_read /
|
|
1071
|
+
(cache_read + cache_creation + uncached); older code read the
|
|
1072
|
+
non-existent ``cache_hit_rate`` field, causing the gate to always
|
|
1073
|
+
return green/"no data". This fix aligns the reader to the emitted field.
|
|
1074
|
+
PLAN-118 WS-E (S181): the emitted field is now ``cache_coverage_bps``
|
|
1075
|
+
(integer basis-points); this reader reads it (÷10000) and falls back to
|
|
1076
|
+
the legacy ``cache_coverage`` float for events emitted before the fix.
|
|
1077
|
+
|
|
1078
|
+
Emits ``cache_discipline_alerted`` via ``emit_generic`` on yellow/red
|
|
1079
|
+
so downstream analytics can correlate with /ceo-boot runs. Action
|
|
1080
|
+
name is registered at ``audit_emit.py:440`` (PLAN-088 canonical).
|
|
1081
|
+
"""
|
|
1082
|
+
threshold = 0.70
|
|
1083
|
+
try:
|
|
1084
|
+
rates: List[float] = []
|
|
1085
|
+
for ev in _iter_audit_events_since(hours=24.0):
|
|
1086
|
+
# S239: skip synthetic governance-probe / benchmark spawns — they
|
|
1087
|
+
# carry cache_coverage_bps=0 by construction (no real cached LLM
|
|
1088
|
+
# call), so a probe-only 24h window would pin this gate to red (FP).
|
|
1089
|
+
if _is_test_pollution_event(ev):
|
|
1090
|
+
continue
|
|
1091
|
+
# PLAN-118 WS-E (S181): primary field is now ``cache_coverage_bps``
|
|
1092
|
+
# (integer basis-points, ratio × 10000) — the legacy float
|
|
1093
|
+
# ``cache_coverage`` was dropped because it broke the HMAC chain.
|
|
1094
|
+
# Read bps first, fall back to the legacy float for events emitted
|
|
1095
|
+
# before the fix (the 24h window straddles the transition; without
|
|
1096
|
+
# the fallback this Tier-S gate would go silently dead again —
|
|
1097
|
+
# the exact F-5-5.1-0624274e failure mode this check exists to avoid).
|
|
1098
|
+
v_bps = ev.get("cache_coverage_bps")
|
|
1099
|
+
if isinstance(v_bps, int) and not isinstance(v_bps, bool) and 0 <= v_bps <= 10000:
|
|
1100
|
+
rates.append(v_bps / 10000.0)
|
|
1101
|
+
continue
|
|
1102
|
+
# F-5-5.1-0624274e: legacy float field (pre-PLAN-118 events).
|
|
1103
|
+
v = ev.get("cache_coverage")
|
|
1104
|
+
if isinstance(v, (int, float)) and not isinstance(v, bool) and 0.0 <= float(v) <= 1.0:
|
|
1105
|
+
rates.append(float(v))
|
|
1106
|
+
if not rates:
|
|
1107
|
+
return "green", "no cache_coverage datapoints", {"samples": 0}
|
|
1108
|
+
avg = sum(rates) / len(rates)
|
|
1109
|
+
if avg < threshold:
|
|
1110
|
+
try:
|
|
1111
|
+
if _audit_emit is not None and hasattr(
|
|
1112
|
+
_audit_emit, "emit_cache_discipline_alerted"
|
|
1113
|
+
):
|
|
1114
|
+
_audit_emit.emit_cache_discipline_alerted(
|
|
1115
|
+
hit_rate_basis_points=max(
|
|
1116
|
+
0, min(1000, int(round(avg * 1000)))
|
|
1117
|
+
),
|
|
1118
|
+
floor_basis_points=max(
|
|
1119
|
+
0, min(1000, int(round(threshold * 1000)))
|
|
1120
|
+
),
|
|
1121
|
+
session_count_24h=len(rates),
|
|
1122
|
+
below_floor=True,
|
|
1123
|
+
opted_out=False,
|
|
1124
|
+
)
|
|
1125
|
+
except Exception: # noqa: BLE001 (Tier-S fail-soft)
|
|
1126
|
+
pass
|
|
1127
|
+
return "red", f"cache_coverage {avg:.2f} < {threshold}", {
|
|
1128
|
+
"avg_rate": avg,
|
|
1129
|
+
"samples": len(rates),
|
|
1130
|
+
}
|
|
1131
|
+
return "green", f"cache_coverage {avg:.2f} ok", {
|
|
1132
|
+
"avg_rate": avg,
|
|
1133
|
+
"samples": len(rates),
|
|
1134
|
+
}
|
|
1135
|
+
except Exception as exc: # noqa: BLE001 (Tier-S fail-soft)
|
|
1136
|
+
return "yellow", f"cache_discipline_alerted error: {exc}", None
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
# PLAN-093 Wave C.5/C.6 canonical persona × task matrix.
|
|
1140
|
+
#
|
|
1141
|
+
# S127 cadence-amendment (Codex R2 thread `019e33a3` AMEND verdict
|
|
1142
|
+
# `PHASE-1+2-WITH-(c)`): the 4×4 matrix is demoted from gate-eligible to
|
|
1143
|
+
# permanent observability. RED authority moves to a future event-driven
|
|
1144
|
+
# demand ledger (`PLAN-104-persona-demand-ledger`); see
|
|
1145
|
+
# `PLAN-093-FOLLOWUP-cadence-amendment.md` for the full doctrine record.
|
|
1146
|
+
_VETO_FLOOR_PERSONAS = (
|
|
1147
|
+
"code-reviewer", "security-engineer", "qa-architect",
|
|
1148
|
+
"threat-detection-engineer",
|
|
1149
|
+
)
|
|
1150
|
+
_PERSONA_TASK_TYPES = ("review", "vet", "test", "detect")
|
|
1151
|
+
_VETO_FLOOR_PERSONAS_LOWER = frozenset(p.lower() for p in _VETO_FLOOR_PERSONAS)
|
|
1152
|
+
|
|
1153
|
+
# PLAN-112-FOLLOWUP-persona-routing-wire W4 — F-5.4-tasktype-pollution.
|
|
1154
|
+
# `_score_persona_coverage` previously counted task_type from ANY audit
|
|
1155
|
+
# event whose archetype matched a VETO-floor persona. Unrelated emitters
|
|
1156
|
+
# (notably `model_routing_advised`, which carries archetype + a bogus
|
|
1157
|
+
# task_type like `frontmatter`/`M`) inflated the denominator + skewed cells.
|
|
1158
|
+
# Restrict contributing events to the GENUINE persona-dispatch actions:
|
|
1159
|
+
# - `persona_coverage_synthesized` (SPEC/v1/audit-log.schema.md:319 —
|
|
1160
|
+
# carries `archetype` + `task_type`, the only fields the scorer reads)
|
|
1161
|
+
# - `persona_demand_*` (the persona-demand ledger family; PLAN-104)
|
|
1162
|
+
# NOTE: `persona_dispatch` does NOT exist and must not be referenced.
|
|
1163
|
+
_PERSONA_DISPATCH_ACTION_PREFIXES = ("persona_demand_",)
|
|
1164
|
+
_PERSONA_DISPATCH_ACTIONS = frozenset({"persona_coverage_synthesized"})
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
def _is_persona_dispatch_event(ev: Dict[str, Any]) -> bool:
|
|
1168
|
+
"""True iff `ev` is a genuine persona-dispatch event (F-5.4 filter).
|
|
1169
|
+
|
|
1170
|
+
Defensive: a missing/non-str `action` -> False (excluded).
|
|
1171
|
+
"""
|
|
1172
|
+
action = ev.get("action")
|
|
1173
|
+
if not isinstance(action, str):
|
|
1174
|
+
return False
|
|
1175
|
+
if action in _PERSONA_DISPATCH_ACTIONS:
|
|
1176
|
+
return True
|
|
1177
|
+
return any(action.startswith(p) for p in _PERSONA_DISPATCH_ACTION_PREFIXES)
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
def _normalize_persona_role(ev: Dict[str, Any]) -> str:
|
|
1181
|
+
"""Case-folded canonical role extracted from any audit_log emission surface.
|
|
1182
|
+
|
|
1183
|
+
PLAN-093 Wave C.5 originally read only ``archetype`` / ``persona``. Codex
|
|
1184
|
+
R2 thread `019e33a3` AMEND #4: audit-log events emitted from
|
|
1185
|
+
``audit_log.py`` carry the role on ``subagent_type`` (canonical) and
|
|
1186
|
+
``dispatch_archetype_hint`` (resolved-from-prompt) — narrow read missed
|
|
1187
|
+
those surfaces. First non-empty match wins, case-folded.
|
|
1188
|
+
"""
|
|
1189
|
+
for field in ("archetype", "persona", "subagent_type",
|
|
1190
|
+
"dispatch_archetype_hint"):
|
|
1191
|
+
val = ev.get(field)
|
|
1192
|
+
if isinstance(val, str) and val.strip():
|
|
1193
|
+
return val.strip().lower()
|
|
1194
|
+
return ""
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
def _score_persona_coverage(hours: float) -> Dict[str, int]:
|
|
1198
|
+
"""Compute 4×4 persona × task coverage over a rolling audit-log window.
|
|
1199
|
+
|
|
1200
|
+
Returns canonical metrics dict suitable for both the 24h session-smoke
|
|
1201
|
+
check and the 7d trend check. Phase 1 (S127): `eligible_demand_events`
|
|
1202
|
+
is unconditionally 0 — that signal is produced by the demand ledger
|
|
1203
|
+
scheduled for Phase 2 (`PLAN-104-persona-demand-ledger`).
|
|
1204
|
+
"""
|
|
1205
|
+
canonical_by_lower = {p.lower(): p for p in _VETO_FLOOR_PERSONAS}
|
|
1206
|
+
seen: Dict[str, set] = {p: set() for p in _VETO_FLOOR_PERSONAS}
|
|
1207
|
+
events_with_target = 0
|
|
1208
|
+
for ev in _iter_audit_events_since(hours=hours):
|
|
1209
|
+
# PLAN-112-FOLLOWUP-persona-routing-wire W4 — F-5.4 task_type
|
|
1210
|
+
# pollution fix. Only genuine persona-dispatch events contribute to
|
|
1211
|
+
# coverage; unrelated emitters carrying a VETO-floor archetype +
|
|
1212
|
+
# a bogus task_type are excluded entirely.
|
|
1213
|
+
if not _is_persona_dispatch_event(ev):
|
|
1214
|
+
continue
|
|
1215
|
+
role = _normalize_persona_role(ev)
|
|
1216
|
+
if role not in _VETO_FLOOR_PERSONAS_LOWER:
|
|
1217
|
+
continue
|
|
1218
|
+
events_with_target += 1
|
|
1219
|
+
task_type = ev.get("task_type") or ev.get("phase") or ""
|
|
1220
|
+
if not isinstance(task_type, str) or not task_type:
|
|
1221
|
+
continue
|
|
1222
|
+
task_lower = task_type.lower()
|
|
1223
|
+
for t in _PERSONA_TASK_TYPES:
|
|
1224
|
+
if t in task_lower:
|
|
1225
|
+
seen[canonical_by_lower[role]].add(t)
|
|
1226
|
+
break
|
|
1227
|
+
cells_covered = sum(len(v) for v in seen.values())
|
|
1228
|
+
total_cells = len(_VETO_FLOOR_PERSONAS) * len(_PERSONA_TASK_TYPES)
|
|
1229
|
+
score_pct = (cells_covered / total_cells * 100.0) if total_cells else 0.0
|
|
1230
|
+
return {
|
|
1231
|
+
"cells_covered": cells_covered,
|
|
1232
|
+
"total_cells": total_cells,
|
|
1233
|
+
"events_with_target_archetype": events_with_target,
|
|
1234
|
+
"score_x100": int(round(score_pct * 100)),
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
def _persona_coverage_status(
|
|
1239
|
+
metrics: Dict[str, int],
|
|
1240
|
+
*,
|
|
1241
|
+
window_hours: int,
|
|
1242
|
+
) -> Tuple[str, str]:
|
|
1243
|
+
"""Decide status + summary for a persona-coverage check window.
|
|
1244
|
+
|
|
1245
|
+
Phase 1 (S127) semantic — pure observability, never red:
|
|
1246
|
+
|
|
1247
|
+
``events_with_target_archetype == 0`` → green "no VETO-floor
|
|
1248
|
+
dispatches in <h>h" (mirrors demand-driven empty-green pattern of
|
|
1249
|
+
the other 17 Tier-S checks — Codex R2 AMEND #1).
|
|
1250
|
+
|
|
1251
|
+
``events_with_target_archetype > 0`` → yellow "M/16 cells covered
|
|
1252
|
+
in <h>h" (matrix demoted to max-yellow forever per Codex R2 AMEND
|
|
1253
|
+
#2 — RED authority reserved for Phase 2 demand-driven gate).
|
|
1254
|
+
"""
|
|
1255
|
+
events_target = metrics["events_with_target_archetype"]
|
|
1256
|
+
cells = metrics["cells_covered"]
|
|
1257
|
+
total = metrics["total_cells"]
|
|
1258
|
+
if events_target == 0:
|
|
1259
|
+
return "green", f"no VETO-floor dispatches in {window_hours}h"
|
|
1260
|
+
return "yellow", f"{cells}/{total} cells covered in {window_hours}h"
|
|
1261
|
+
|
|
1262
|
+
|
|
1263
|
+
def _emit_persona_coverage(
|
|
1264
|
+
metrics: Dict[str, int],
|
|
1265
|
+
*,
|
|
1266
|
+
window_hours: int,
|
|
1267
|
+
) -> None:
|
|
1268
|
+
"""Emit ``ceo_boot_persona_coverage_score`` audit event (shared by 24h+7d).
|
|
1269
|
+
|
|
1270
|
+
S127 Phase 1 scope-(b) — emits only the 3 fields already in the kernel
|
|
1271
|
+
allowlist (`score_x100`, `cells_covered`, `total_cells`). The new fields
|
|
1272
|
+
(`window_hours`, `events_with_target_archetype`, `eligible_demand_events`)
|
|
1273
|
+
surface in the /ceo-boot result dict + summary text but are NOT persisted
|
|
1274
|
+
in the audit-log under Phase 1. Deferring the
|
|
1275
|
+
``_CEO_BOOT_PERSONA_COVERAGE_ALLOWLIST`` kernel amendment to an Owner
|
|
1276
|
+
ceremony — bundle with the Phase 2 demand-ledger ship (which needs more
|
|
1277
|
+
kernel surface anyway), avoiding two separate kernel-override events.
|
|
1278
|
+
|
|
1279
|
+
The `window_hours` value is consumed by `_persona_coverage_status` for
|
|
1280
|
+
the summary string; downstream audit-log consumers reconstructing
|
|
1281
|
+
cadence from emitted events would need to infer it (or wait for
|
|
1282
|
+
Phase 2 ship).
|
|
1283
|
+
"""
|
|
1284
|
+
del window_hours # See docstring; not emitted under Phase 1 scope-(b).
|
|
1285
|
+
try:
|
|
1286
|
+
if _audit_emit is not None and hasattr(_audit_emit, "emit_generic"):
|
|
1287
|
+
# score_x100 is integer basis-points (0-10000); floats break
|
|
1288
|
+
# canonical JSON HMAC chain — Codex S123 iter-2 P1.
|
|
1289
|
+
_audit_emit.emit_generic(
|
|
1290
|
+
"ceo_boot_persona_coverage_score",
|
|
1291
|
+
score_x100=metrics["score_x100"],
|
|
1292
|
+
cells_covered=metrics["cells_covered"],
|
|
1293
|
+
total_cells=metrics["total_cells"],
|
|
1294
|
+
)
|
|
1295
|
+
except Exception: # noqa: BLE001 (Tier-S fail-soft)
|
|
1296
|
+
pass
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
def check_ceo_boot_persona_coverage_score() -> Tuple[str, str, Any]:
|
|
1300
|
+
"""18th Tier-S check — persona × task coverage at 24h cadence (session-smoke).
|
|
1301
|
+
|
|
1302
|
+
Originally PLAN-093 Wave C.5/C.6 AC10 — sourced 24h of audit-log and
|
|
1303
|
+
scored a 4×4 matrix with `<50% red` thresholds. S127 cadence-amendment
|
|
1304
|
+
(Codex R2 thread `019e33a3` AMEND `PHASE-1+2-WITH-(c)`): demoted to
|
|
1305
|
+
permanent observability. Never red, never gate-failing. RED authority
|
|
1306
|
+
moves to `PLAN-104-persona-demand-ledger` (Phase 2 event-driven gate).
|
|
1307
|
+
|
|
1308
|
+
Companion: `check_persona_atrophy_7d` at 168h cadence for trend signal.
|
|
1309
|
+
|
|
1310
|
+
Emits ``ceo_boot_persona_coverage_score`` with `window_hours=24` and
|
|
1311
|
+
`eligible_demand_events=0` (PLAN-104 demand ledger live; observability-only here per AC4).
|
|
1312
|
+
"""
|
|
1313
|
+
try:
|
|
1314
|
+
metrics = _score_persona_coverage(hours=24.0)
|
|
1315
|
+
except Exception as exc: # noqa: BLE001 (Tier-S fail-soft)
|
|
1316
|
+
return "yellow", f"persona_coverage error: {exc}", None
|
|
1317
|
+
status, summary = _persona_coverage_status(metrics, window_hours=24)
|
|
1318
|
+
_emit_persona_coverage(metrics, window_hours=24)
|
|
1319
|
+
return status, summary, {
|
|
1320
|
+
**metrics,
|
|
1321
|
+
"window_hours": 24,
|
|
1322
|
+
"eligible_demand_events": 0,
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
|
+
def check_persona_atrophy_7d() -> Tuple[str, str, Any]:
|
|
1327
|
+
"""19th Tier-S check — demand-normalized persona-atrophy at 168h.
|
|
1328
|
+
|
|
1329
|
+
PLAN-104 Wave D activated the demand-driven RED branch (S134 Codex R2
|
|
1330
|
+
thread `019e37e3` ACCEPT). Set-algebra in
|
|
1331
|
+
`persona_demand_resolver.atrophy_7d_status` (Codex iter-1 P0 #2 +
|
|
1332
|
+
iter-2 P1 #1 folds — adds defense-in-depth effective_unmet
|
|
1333
|
+
computation inline):
|
|
1334
|
+
|
|
1335
|
+
satisfied = opened & matched
|
|
1336
|
+
unmet_recorded = (opened & unmet) - matched - waived
|
|
1337
|
+
effective_unmet = opened where opened_ts + 24h < now AND
|
|
1338
|
+
no terminal AND no in-window dispatch-match
|
|
1339
|
+
unmet_total = unmet_recorded | (effective_unmet - waived)
|
|
1340
|
+
waived = (opened & waived) - matched
|
|
1341
|
+
still_open = opened where window NOT expired and no terminal
|
|
1342
|
+
eligible_settled = satisfied | unmet_total | waived
|
|
1343
|
+
|
|
1344
|
+
not opened -> green "no eligible persona demand in 168h"
|
|
1345
|
+
not eligible_settled -> green "<N> demand(s) still inside window"
|
|
1346
|
+
not unmet_total -> green "<S>/<E> demands matched (<W> waived)"
|
|
1347
|
+
else -> red "<U> persona demand(s) unmet in 168h ..."
|
|
1348
|
+
|
|
1349
|
+
Side-effect: this check runs scan + waive-emit + resolve before
|
|
1350
|
+
computing status (Codex iter-1 P0 #1). Ordering per Codex iter-2
|
|
1351
|
+
P1 #2: scan -> emit_opened -> emit_waives_for_scanned -> resolve
|
|
1352
|
+
-> emit_resolutions (waive precedes unmet emit).
|
|
1353
|
+
|
|
1354
|
+
Kill-switch CEO_PERSONA_DEMAND_LEDGER_DISABLED=1 reverts to
|
|
1355
|
+
pre-PLAN-104 observability-only semantic (max-yellow). The 18th check
|
|
1356
|
+
`check_ceo_boot_persona_coverage_score` stays observability-only
|
|
1357
|
+
forever per S127 AMEND option-(c).
|
|
1358
|
+
"""
|
|
1359
|
+
if os.environ.get("CEO_PERSONA_DEMAND_LEDGER_DISABLED") == "1":
|
|
1360
|
+
try:
|
|
1361
|
+
metrics = _score_persona_coverage(hours=168.0)
|
|
1362
|
+
except Exception as exc: # noqa: BLE001
|
|
1363
|
+
return "yellow", f"persona_atrophy_7d error: {exc}", None
|
|
1364
|
+
status, summary = _persona_coverage_status(metrics, window_hours=168)
|
|
1365
|
+
_emit_persona_coverage(metrics, window_hours=168)
|
|
1366
|
+
return status, summary, {
|
|
1367
|
+
**metrics,
|
|
1368
|
+
"window_hours": 168,
|
|
1369
|
+
"eligible_demand_events": 0,
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
try:
|
|
1373
|
+
import importlib.util
|
|
1374
|
+
scripts_dir = Path(__file__).resolve().parent
|
|
1375
|
+
spec_path = scripts_dir / "persona_demand_resolver.py"
|
|
1376
|
+
spec = importlib.util.spec_from_file_location(
|
|
1377
|
+
"persona_demand_resolver", spec_path,
|
|
1378
|
+
)
|
|
1379
|
+
if spec is None or spec.loader is None:
|
|
1380
|
+
raise ImportError("resolver spec load failed")
|
|
1381
|
+
resolver = importlib.util.module_from_spec(spec)
|
|
1382
|
+
spec.loader.exec_module(resolver)
|
|
1383
|
+
scan_spec = importlib.util.spec_from_file_location(
|
|
1384
|
+
"persona_demand_scan", scripts_dir / "persona_demand_scan.py",
|
|
1385
|
+
)
|
|
1386
|
+
if scan_spec is None or scan_spec.loader is None:
|
|
1387
|
+
raise ImportError("scan spec load failed")
|
|
1388
|
+
scanner = importlib.util.module_from_spec(scan_spec)
|
|
1389
|
+
scan_spec.loader.exec_module(scanner)
|
|
1390
|
+
except Exception as exc: # noqa: BLE001
|
|
1391
|
+
return "yellow", f"persona_atrophy_7d module import error: {exc}", None
|
|
1392
|
+
|
|
1393
|
+
# Codex iter-1 P0 #1 fold: actually run scan + waive-emit + resolve
|
|
1394
|
+
# before computing status. Without this the 19th check only reads
|
|
1395
|
+
# ledger state that nothing ever populates -> never reaches RED in
|
|
1396
|
+
# real use. Each step is best-effort; any IO error is swallowed and
|
|
1397
|
+
# the status path's defense-in-depth expiry computation still works.
|
|
1398
|
+
#
|
|
1399
|
+
# Codex iter-2 P1 #2 fold: waives MUST fire BEFORE emit_resolutions.
|
|
1400
|
+
# Codex iter-4 P1 #1 fold: detect_all() is called ONCE here and
|
|
1401
|
+
# both scoped operations re-use it (avoids git-subprocess duplication).
|
|
1402
|
+
# Order:
|
|
1403
|
+
# 1. detect_all() -> full candidate set with target_ref cleartext
|
|
1404
|
+
# 2. emit_opened() dedups via audit-log
|
|
1405
|
+
# 3. emit_waives_for_scanned(all_candidates) scoped to commits
|
|
1406
|
+
# 4. resolve() / emit_resolutions() catches non-waived expired demands
|
|
1407
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
1408
|
+
all_candidates: List = []
|
|
1409
|
+
try:
|
|
1410
|
+
all_candidates = scanner.detect_all(repo_root)
|
|
1411
|
+
# Local dedup against audit-log (avoids 2nd git subprocess pass).
|
|
1412
|
+
already = scanner._existing_demand_ids(
|
|
1413
|
+
AUDIT_LOG_DEFAULT, scanner.SCAN_HORIZON_HOURS,
|
|
1414
|
+
)
|
|
1415
|
+
new_only = [ev for ev in all_candidates if ev.demand_id not in already]
|
|
1416
|
+
scanner.emit_opened(new_only)
|
|
1417
|
+
except Exception: # noqa: BLE001 (Tier-S fail-soft)
|
|
1418
|
+
pass
|
|
1419
|
+
try:
|
|
1420
|
+
resolver.emit_waives_for_scanned(all_candidates, AUDIT_LOG_DEFAULT, repo_root)
|
|
1421
|
+
except Exception: # noqa: BLE001
|
|
1422
|
+
pass
|
|
1423
|
+
try:
|
|
1424
|
+
summary_resolve = resolver.resolve(AUDIT_LOG_DEFAULT)
|
|
1425
|
+
resolver.emit_resolutions(summary_resolve)
|
|
1426
|
+
except Exception: # noqa: BLE001
|
|
1427
|
+
pass
|
|
1428
|
+
|
|
1429
|
+
try:
|
|
1430
|
+
status, summary, demand_metrics = resolver.atrophy_7d_status(AUDIT_LOG_DEFAULT)
|
|
1431
|
+
except Exception as exc: # noqa: BLE001
|
|
1432
|
+
return "yellow", f"persona_atrophy_7d resolver error: {exc}", None
|
|
1433
|
+
|
|
1434
|
+
try:
|
|
1435
|
+
score_metrics = _score_persona_coverage(hours=168.0)
|
|
1436
|
+
_emit_persona_coverage(score_metrics, window_hours=168)
|
|
1437
|
+
except Exception: # noqa: BLE001
|
|
1438
|
+
score_metrics = {"score_x100": 0, "cells_covered": 0, "total_cells": 16}
|
|
1439
|
+
|
|
1440
|
+
return status, summary, {
|
|
1441
|
+
**score_metrics,
|
|
1442
|
+
**demand_metrics,
|
|
1443
|
+
"window_hours": 168,
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
def check_confidence_gate_drift_7d() -> Tuple[str, str, Any]:
|
|
1448
|
+
"""PLAN-106 Wave F.2 — 20th Tier-S check.
|
|
1449
|
+
|
|
1450
|
+
Detects HIGH_CONFIDENCE_BLOCK classes whose 7d FPR > 2% per
|
|
1451
|
+
ADR-019-AMEND-1 §6. Wraps the side-effect-free `detect_drift_7d`
|
|
1452
|
+
function from `.claude/scripts/check-confidence-gate-drift.py`
|
|
1453
|
+
(refactored per Wave F.1).
|
|
1454
|
+
|
|
1455
|
+
Status mapping:
|
|
1456
|
+
- drift NOT detected, valid config + log → green
|
|
1457
|
+
- drift NOT detected, missing config/log → green (fail-OPEN
|
|
1458
|
+
per ADR-095 doctrine — no calendar gates, but also no
|
|
1459
|
+
spurious RED on fresh installs)
|
|
1460
|
+
- drift detected → yellow (advisory; auto-demote is the
|
|
1461
|
+
underlying script's responsibility, not the ceo-boot check)
|
|
1462
|
+
- exception → yellow with error message
|
|
1463
|
+
"""
|
|
1464
|
+
try:
|
|
1465
|
+
import importlib.util
|
|
1466
|
+
scripts_dir = Path(__file__).resolve().parent
|
|
1467
|
+
spec = importlib.util.spec_from_file_location(
|
|
1468
|
+
"check_confidence_gate_drift",
|
|
1469
|
+
scripts_dir / "check-confidence-gate-drift.py",
|
|
1470
|
+
)
|
|
1471
|
+
if spec is None or spec.loader is None:
|
|
1472
|
+
return "yellow", "drift detector module spec load failed", None
|
|
1473
|
+
mod = importlib.util.module_from_spec(spec)
|
|
1474
|
+
spec.loader.exec_module(mod)
|
|
1475
|
+
except Exception as exc: # noqa: BLE001 (Tier-S fail-soft)
|
|
1476
|
+
return "yellow", f"drift detector import error: {exc}", None
|
|
1477
|
+
|
|
1478
|
+
try:
|
|
1479
|
+
drift, summary, detail = mod.detect_drift_7d()
|
|
1480
|
+
except Exception as exc: # noqa: BLE001
|
|
1481
|
+
return "yellow", f"detect_drift_7d error: {exc}", None
|
|
1482
|
+
|
|
1483
|
+
if not drift:
|
|
1484
|
+
return "green", summary, detail
|
|
1485
|
+
return "yellow", summary, detail
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
def _emit_settings_tamper_detected_safe(findings: List[Dict[str, str]]) -> None:
|
|
1489
|
+
"""Emit ONE closed-enum ``settings_tamper_detected`` event per class.
|
|
1490
|
+
|
|
1491
|
+
PLAN-135 W1 S3. Field contract (Sec MF-3, enforced emit-side by
|
|
1492
|
+
``_SETTINGS_TAMPER_DETECTED_ALLOWLIST`` in ``_lib/audit_emit.py``):
|
|
1493
|
+
|
|
1494
|
+
tamper_class — closed enum (the 5 ``effective_config.TAMPER_*``
|
|
1495
|
+
members; off-enum values are COERCED emit-side)
|
|
1496
|
+
layer — closed enum (user/project/local/managed/env/disk;
|
|
1497
|
+
first layer seen for the class)
|
|
1498
|
+
finding_count — int, clamped 0..99
|
|
1499
|
+
|
|
1500
|
+
The finding DETAIL string is NEVER emitted — it can carry endpoint
|
|
1501
|
+
URLs, model ids, helper paths or flag values (mcp_routing.py
|
|
1502
|
+
breadcrumb precedent +
|
|
1503
|
+
[[feedback-closed-enum-breadcrumb-must-not-echo-rejected-value]]).
|
|
1504
|
+
|
|
1505
|
+
Fail-open contract: pre-ceremony (action not yet in ``_KNOWN_ACTIONS``)
|
|
1506
|
+
writes a stderr breadcrumb instead of emitting; any emit failure is
|
|
1507
|
+
swallowed. NEVER raises, NEVER blocks boot.
|
|
1508
|
+
"""
|
|
1509
|
+
if _audit_emit is None or not findings:
|
|
1510
|
+
return
|
|
1511
|
+
try:
|
|
1512
|
+
emit_fn = getattr(_audit_emit, "emit_generic", None)
|
|
1513
|
+
if not callable(emit_fn):
|
|
1514
|
+
return
|
|
1515
|
+
known = getattr(_audit_emit, "_KNOWN_ACTIONS", None)
|
|
1516
|
+
if known is not None and "settings_tamper_detected" not in known:
|
|
1517
|
+
sys.stderr.write(
|
|
1518
|
+
"[ceo-boot] 'settings_tamper_detected' not in _KNOWN_ACTIONS; "
|
|
1519
|
+
"emit dropped until the PLAN-135 W1 kernel ceremony lands.\n"
|
|
1520
|
+
)
|
|
1521
|
+
return
|
|
1522
|
+
by_class: Dict[str, Dict[str, Any]] = {}
|
|
1523
|
+
for f in findings:
|
|
1524
|
+
if not isinstance(f, dict):
|
|
1525
|
+
continue
|
|
1526
|
+
klass = str(f.get("class", ""))[:64]
|
|
1527
|
+
if not klass:
|
|
1528
|
+
continue
|
|
1529
|
+
slot = by_class.setdefault(
|
|
1530
|
+
klass, {"layer": str(f.get("layer", ""))[:16], "count": 0}
|
|
1531
|
+
)
|
|
1532
|
+
slot["count"] += 1
|
|
1533
|
+
for klass in sorted(by_class): # deterministic order (CR-N7)
|
|
1534
|
+
slot = by_class[klass]
|
|
1535
|
+
emit_fn(
|
|
1536
|
+
"settings_tamper_detected",
|
|
1537
|
+
session_id=_ceo_boot_session_id(),
|
|
1538
|
+
tamper_class=klass,
|
|
1539
|
+
layer=slot["layer"],
|
|
1540
|
+
finding_count=max(0, min(99, int(slot["count"]))),
|
|
1541
|
+
)
|
|
1542
|
+
except Exception: # noqa: BLE001 — advisory fail-open
|
|
1543
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
1544
|
+
import traceback
|
|
1545
|
+
traceback.print_exc(file=sys.stderr)
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
def check_settings_tamper_tripwires() -> Tuple[str, str, Any]:
|
|
1549
|
+
"""PLAN-135 W1 S3 — 21st Tier-S check: settings/env tamper tripwires.
|
|
1550
|
+
|
|
1551
|
+
Scans the RESOLVED multi-layer settings (user / project / local /
|
|
1552
|
+
managed — including the gitignored, sentinel-blind
|
|
1553
|
+
``settings.local.json``) plus the import-time env snapshot for the
|
|
1554
|
+
five tamper classes of THREAT-MODEL-WORKSHEET.md §2, via the shared
|
|
1555
|
+
``_lib/effective_config`` module:
|
|
1556
|
+
|
|
1557
|
+
(a) ``disableAllHooks`` truthy in ANY settings layer
|
|
1558
|
+
(b) ``ANTHROPIC_MODEL`` / ``ANTHROPIC_DEFAULT_*`` /
|
|
1559
|
+
``ANTHROPIC_SMALL_FAST_MODEL`` remap outside the ADR-149
|
|
1560
|
+
allowlist (skipped fail-open when the allowlist is unreadable)
|
|
1561
|
+
(c) ``ANTHROPIC_BASE_URL`` / ``ANTHROPIC_AUTH_TOKEN`` /
|
|
1562
|
+
``apiKeyHelper`` endpoint remap (model substitution AND
|
|
1563
|
+
transcript egress outside check (b) entirely)
|
|
1564
|
+
(d) ``permissions.defaultMode: bypassPermissions`` or
|
|
1565
|
+
dangerously-skip flags in any layer (nullifies the S2 floor)
|
|
1566
|
+
(e) effective hook count == registered count (registered-but-
|
|
1567
|
+
missing-on-disk census; a missing hook fails open = allow)
|
|
1568
|
+
|
|
1569
|
+
Env reads come EXCLUSIVELY from the module-import-time snapshot
|
|
1570
|
+
(``_TAMPER_ENV_SNAPSHOT``, trusted_env pattern) — never live
|
|
1571
|
+
``os.environ``. Settings reads come from the RESOLVED multi-layer
|
|
1572
|
+
config (``effective_config.resolve_settings``).
|
|
1573
|
+
|
|
1574
|
+
Status mapping (ADVISORY — /ceo-boot never blocks the session):
|
|
1575
|
+
|
|
1576
|
+
findings present → red (rail integrity is suspect)
|
|
1577
|
+
no findings, layer errors → yellow (a corrupt PRESENT layer is
|
|
1578
|
+
itself an anomaly worth eyes)
|
|
1579
|
+
no findings, clean → green
|
|
1580
|
+
module missing / internal → yellow (advisory fail-open +
|
|
1581
|
+
stderr breadcrumb, never crash)
|
|
1582
|
+
|
|
1583
|
+
Side-effect: one closed-enum ``settings_tamper_detected`` audit emit
|
|
1584
|
+
per detected class via ``_emit_settings_tamper_detected_safe``
|
|
1585
|
+
(``_KNOWN_ACTIONS``-guarded pre-ceremony).
|
|
1586
|
+
"""
|
|
1587
|
+
if _effective_config is None:
|
|
1588
|
+
sys.stderr.write(
|
|
1589
|
+
"[ceo-boot] effective_config unavailable — settings tamper "
|
|
1590
|
+
"tripwires inactive (fail-open).\n"
|
|
1591
|
+
)
|
|
1592
|
+
return (
|
|
1593
|
+
"yellow",
|
|
1594
|
+
"effective_config unavailable — tamper tripwires inactive",
|
|
1595
|
+
None,
|
|
1596
|
+
)
|
|
1597
|
+
try:
|
|
1598
|
+
resolved = _effective_config.resolve_settings(REPO_ROOT)
|
|
1599
|
+
findings = _effective_config.classify_tampering(
|
|
1600
|
+
resolved, _TAMPER_ENV_SNAPSHOT
|
|
1601
|
+
)
|
|
1602
|
+
_emit_settings_tamper_detected_safe(findings)
|
|
1603
|
+
if findings:
|
|
1604
|
+
classes = sorted({
|
|
1605
|
+
str(f.get("class", ""))
|
|
1606
|
+
for f in findings
|
|
1607
|
+
if isinstance(f, dict) and f.get("class")
|
|
1608
|
+
})
|
|
1609
|
+
# Summary carries ONLY closed-enum class names (never the
|
|
1610
|
+
# finding detail — it can embed env values / endpoints).
|
|
1611
|
+
return (
|
|
1612
|
+
"red",
|
|
1613
|
+
f"{len(findings)} tamper finding(s): "
|
|
1614
|
+
f"{','.join(classes)[:160]}",
|
|
1615
|
+
findings,
|
|
1616
|
+
)
|
|
1617
|
+
errors = resolved.get("errors") or []
|
|
1618
|
+
if errors:
|
|
1619
|
+
return (
|
|
1620
|
+
"yellow",
|
|
1621
|
+
f"no tamper indicators; {len(errors)} unparseable "
|
|
1622
|
+
f"settings layer(s)",
|
|
1623
|
+
{"errors": [str(e)[:160] for e in errors[:4]]},
|
|
1624
|
+
)
|
|
1625
|
+
registered: set = set()
|
|
1626
|
+
for layer in resolved.get("layers", []):
|
|
1627
|
+
if isinstance(layer, dict) and layer.get("name") in (
|
|
1628
|
+
"project", "local",
|
|
1629
|
+
):
|
|
1630
|
+
registered.update(
|
|
1631
|
+
_effective_config.registered_hook_basenames(
|
|
1632
|
+
layer.get("data") or {}
|
|
1633
|
+
)
|
|
1634
|
+
)
|
|
1635
|
+
effective = _effective_config.count_effective_hooks(REPO_ROOT)
|
|
1636
|
+
return (
|
|
1637
|
+
"green",
|
|
1638
|
+
f"no tamper indicators ({effective}/{len(registered)} "
|
|
1639
|
+
f"registered hooks effective)",
|
|
1640
|
+
{"registered": len(registered), "effective_on_disk": effective},
|
|
1641
|
+
)
|
|
1642
|
+
except Exception as exc: # noqa: BLE001 (Tier-S fail-soft floor)
|
|
1643
|
+
return "yellow", f"tamper tripwires error: {type(exc).__name__}", None
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
TIER_S_CHECKS: List[Tuple[str, Callable[[], Tuple[str, str, Any]]]] = [
|
|
1647
|
+
("plans_executing", check_plans_executing),
|
|
1648
|
+
("plans_reviewed_pending", check_plans_reviewed_pending),
|
|
1649
|
+
("plans_stranded_executing", check_plans_stranded_executing),
|
|
1650
|
+
("plans_draft", check_plans_draft),
|
|
1651
|
+
("audit_log_freshness", check_audit_log_freshness),
|
|
1652
|
+
("dispatch_count_24h", check_dispatch_count_24h),
|
|
1653
|
+
("skill_unknown_ratio", check_skill_unknown_ratio),
|
|
1654
|
+
("governance_validate", check_governance_validate),
|
|
1655
|
+
# PLAN-082 Codex Item D: `hook_test_baseline` renamed to `hook_live_smoke`
|
|
1656
|
+
# — the check now performs a live hook smoke (settings.json parse + file
|
|
1657
|
+
# existence + py_compile) rather than reading a pytest-baseline cache that
|
|
1658
|
+
# was never populated. Old function symbol preserved as alias for tests.
|
|
1659
|
+
("hook_live_smoke", check_hook_live_smoke),
|
|
1660
|
+
("audit_v3_backlog", check_audit_v3_backlog),
|
|
1661
|
+
("sentinels_pending_gpg", check_sentinels_pending_gpg),
|
|
1662
|
+
("rc_hold_aged", check_rc_hold_aged),
|
|
1663
|
+
("cost_24h_usd", check_cost_24h_usd),
|
|
1664
|
+
("active_plan_burn_ratio", check_active_plan_burn_ratio),
|
|
1665
|
+
("adrs_stale_proposed", check_adrs_stale_proposed),
|
|
1666
|
+
# PLAN-091 Wave A.1 — 16th Tier-S check. Delegates to standalone hook
|
|
1667
|
+
# module `.claude/hooks/check_tier_policy_misrouting_24h.py` per the
|
|
1668
|
+
# PLAN-088 §AC11 18-check target.
|
|
1669
|
+
("tier_policy_misrouting_24h", check_tier_policy_misrouting_24h),
|
|
1670
|
+
# PLAN-093 Wave C.2 — 17th Tier-S check: prompt-cache hit-rate
|
|
1671
|
+
# detection emitting `cache_discipline_alerted` on threshold breach.
|
|
1672
|
+
("cache_discipline_alerted", check_cache_discipline_alerted),
|
|
1673
|
+
# PLAN-093 Wave C.5/C.6 — 18th Tier-S check: 4-persona × 4-task coverage
|
|
1674
|
+
# matrix at 24h cadence (session-smoke). S127 cadence-amendment (Codex R2
|
|
1675
|
+
# `019e33a3` AMEND): demoted to permanent observability, never red.
|
|
1676
|
+
("ceo_boot_persona_coverage_score", check_ceo_boot_persona_coverage_score),
|
|
1677
|
+
# S127 cadence-amendment — 19th Tier-S check: same matrix at 168h cadence
|
|
1678
|
+
# (trend / chronic-atrophy signal). Phase 1: observability-only, never red.
|
|
1679
|
+
# Phase 2 (PLAN-104-persona-demand-ledger): RED authority activated once
|
|
1680
|
+
# `eligible_demand_events` is populated from the demand ledger.
|
|
1681
|
+
("persona_atrophy_7d", check_persona_atrophy_7d),
|
|
1682
|
+
# PLAN-106 Wave F.2 — 20th Tier-S check. Wires the standalone
|
|
1683
|
+
# `.claude/scripts/check-confidence-gate-drift.py` module's
|
|
1684
|
+
# `detect_drift_7d()` importable into the parallel registry per
|
|
1685
|
+
# ADR-019-AMEND-1 §6 (7d rolling FPR > 2% advisory). Read-only;
|
|
1686
|
+
# the underlying script's `--emit` flag remains the canonical
|
|
1687
|
+
# emission surface for `confidence_gate_fp_drift_detected`.
|
|
1688
|
+
("confidence_gate_drift_7d", check_confidence_gate_drift_7d),
|
|
1689
|
+
# PLAN-135 W1 S3 — 21st Tier-S check: settings/env tamper tripwires
|
|
1690
|
+
# over the RESOLVED multi-layer settings (shared _lib/effective_config;
|
|
1691
|
+
# user/project/local/managed incl. the sentinel-blind
|
|
1692
|
+
# settings.local.json) + the import-time env snapshot (trusted_env
|
|
1693
|
+
# pattern). Classes (a)-(e) per THREAT-MODEL-WORKSHEET.md §2; closed-
|
|
1694
|
+
# enum `settings_tamper_detected` emit per class. ADVISORY fail-open:
|
|
1695
|
+
# infra error → yellow + stderr breadcrumb, never crashes, never blocks.
|
|
1696
|
+
("settings_tamper_tripwires", check_settings_tamper_tripwires),
|
|
1697
|
+
]
|
|
1698
|
+
|
|
1699
|
+
assert len(TIER_S_CHECKS) == 21, f"Expected 21 Tier-S checks, got {len(TIER_S_CHECKS)}"
|
|
1700
|
+
|
|
1701
|
+
|
|
1702
|
+
TIER_A_CHECKS: List[Tuple[str, Callable[[], Tuple[str, str, Any]]]] = [
|
|
1703
|
+
("tier_a_debate_transcripts", check_tier_a_debate_transcripts),
|
|
1704
|
+
("tier_a_lessons_30d", check_tier_a_lessons_30d),
|
|
1705
|
+
("tier_a_spec_version_drift", check_tier_a_spec_version_drift),
|
|
1706
|
+
("tier_a_npm_version_match", check_tier_a_npm_version_match),
|
|
1707
|
+
("tier_a_waivers_count", check_tier_a_waivers_count),
|
|
1708
|
+
("tier_a_adrs_recent_status", check_tier_a_adrs_recent_status),
|
|
1709
|
+
("tier_a_cache_hit_rate_24h", check_tier_a_cache_hit_rate_24h),
|
|
1710
|
+
("tier_a_hook_test_baseline_age", check_tier_a_hook_test_baseline_age),
|
|
1711
|
+
("tier_a_sentinel_signers_tracked", check_tier_a_sentinel_signers_tracked),
|
|
1712
|
+
("tier_a_gitignore_state_excluded", check_tier_a_gitignore_state_excluded),
|
|
1713
|
+
]
|
|
1714
|
+
|
|
1715
|
+
assert len(TIER_A_CHECKS) == 10, f"Expected 10 Tier-A checks, got {len(TIER_A_CHECKS)}"
|
|
1716
|
+
|
|
1717
|
+
|
|
1718
|
+
# Verbose-mode aggregate budget: extends Tier-S 5s window to 10s when
|
|
1719
|
+
# Tier-A is dispatched alongside (PLAN-065 §4.3.3).
|
|
1720
|
+
AGGREGATE_TIMEOUT_VERBOSE_S = 10.0
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
# ---- Dispatcher ------------------------------------------------------------
|
|
1724
|
+
|
|
1725
|
+
def _wrap_check(name: str, fn: Callable[[], Tuple[str, str, Any]]) -> CheckResult:
|
|
1726
|
+
t0 = time.perf_counter()
|
|
1727
|
+
try:
|
|
1728
|
+
status, summary, detail = fn()
|
|
1729
|
+
dur = (time.perf_counter() - t0) * 1000
|
|
1730
|
+
return CheckResult(name, status, summary, dur, detail)
|
|
1731
|
+
except Exception as e: # noqa: BLE001 (PoC fail-soft)
|
|
1732
|
+
dur = (time.perf_counter() - t0) * 1000
|
|
1733
|
+
return CheckResult(name, "error", f"{type(e).__name__}: {e}", dur, None)
|
|
1734
|
+
|
|
1735
|
+
|
|
1736
|
+
def dispatch_parallel(
|
|
1737
|
+
*,
|
|
1738
|
+
include_tier_a: bool = False,
|
|
1739
|
+
aggregate_timeout_s: Optional[float] = None,
|
|
1740
|
+
) -> List[CheckResult]:
|
|
1741
|
+
"""Dispatch Tier-S (and optionally Tier-A) checks in parallel via as_completed.
|
|
1742
|
+
|
|
1743
|
+
Codex S82 P0 #2 fix: previous impl iterated future_to_name.items() and
|
|
1744
|
+
called fut.result(timeout=PER_CHECK_TIMEOUT_S) sequentially — so the
|
|
1745
|
+
500ms started counting when each future was *observed*, not when it
|
|
1746
|
+
started running. Timeouts cascaded and the per-check budget was
|
|
1747
|
+
fictional under load. New impl uses as_completed() with the AGGREGATE
|
|
1748
|
+
budget; per-check budget becomes a soft annotation (subprocess timeouts
|
|
1749
|
+
inside each check enforce real CPU/IO ceilings, e.g. governance_validate
|
|
1750
|
+
has subprocess timeout=4.0).
|
|
1751
|
+
|
|
1752
|
+
Tier-A extension (PLAN-065 §4.3.3): when ``include_tier_a=True``,
|
|
1753
|
+
dispatcher also enqueues TIER_A_CHECKS and the aggregate budget
|
|
1754
|
+
defaults to AGGREGATE_TIMEOUT_VERBOSE_S (10s).
|
|
1755
|
+
|
|
1756
|
+
Pool lifecycle (per PLAN-087 A.6 / `F-A-CR-D0012` P2): the
|
|
1757
|
+
``ThreadPoolExecutor`` is NOT used as a context manager because
|
|
1758
|
+
``with`` exit calls ``shutdown(wait=True)`` which blocks on
|
|
1759
|
+
long-running futures past the aggregate timeout. The explicit
|
|
1760
|
+
``shutdown(wait=False, cancel_futures=True)`` in the ``finally``
|
|
1761
|
+
block releases the pool immediately and cancels any futures that
|
|
1762
|
+
have not yet started; in-flight futures continue to run on their
|
|
1763
|
+
daemon threads but their results are dropped (the aggregate
|
|
1764
|
+
timeout has already produced their `AGG_TIMEOUT` rows). Python
|
|
1765
|
+
3.9+ ``cancel_futures`` parameter required; the project min
|
|
1766
|
+
Python is 3.9 per ADR-002.
|
|
1767
|
+
"""
|
|
1768
|
+
registry: List[Tuple[str, Callable[[], Tuple[str, str, Any]]]] = list(TIER_S_CHECKS)
|
|
1769
|
+
if include_tier_a:
|
|
1770
|
+
registry = registry + list(TIER_A_CHECKS)
|
|
1771
|
+
if aggregate_timeout_s is None:
|
|
1772
|
+
aggregate_timeout_s = (
|
|
1773
|
+
AGGREGATE_TIMEOUT_VERBOSE_S if include_tier_a else AGGREGATE_TIMEOUT_S
|
|
1774
|
+
)
|
|
1775
|
+
|
|
1776
|
+
results_by_name: Dict[str, CheckResult] = {}
|
|
1777
|
+
pool = ThreadPoolExecutor(max_workers=MAX_WORKERS)
|
|
1778
|
+
try:
|
|
1779
|
+
future_to_name = {
|
|
1780
|
+
pool.submit(_wrap_check, name, fn): name for name, fn in registry
|
|
1781
|
+
}
|
|
1782
|
+
try:
|
|
1783
|
+
for fut in as_completed(future_to_name, timeout=aggregate_timeout_s):
|
|
1784
|
+
name = future_to_name[fut]
|
|
1785
|
+
try:
|
|
1786
|
+
res = fut.result() # already done, instant
|
|
1787
|
+
except Exception as e: # noqa: BLE001
|
|
1788
|
+
res = CheckResult(name, "error", f"{type(e).__name__}: {e}", 0.0, None)
|
|
1789
|
+
# Soft per-check ceiling: annotate slow but green checks.
|
|
1790
|
+
budget_s = PER_CHECK_TIMEOUT_OVERRIDES_S.get(name, PER_CHECK_TIMEOUT_S)
|
|
1791
|
+
if res.duration_ms > budget_s * 1000 and res.status == "green":
|
|
1792
|
+
res.summary = (
|
|
1793
|
+
f"{res.summary} (slow {res.duration_ms:.0f}ms > "
|
|
1794
|
+
f"budget {int(budget_s * 1000)}ms)"
|
|
1795
|
+
)
|
|
1796
|
+
results_by_name[name] = res
|
|
1797
|
+
except FuturesTimeout:
|
|
1798
|
+
pass # aggregate exceeded — handled below
|
|
1799
|
+
|
|
1800
|
+
# Mark non-completed as aggregate-timeout (Codex P0 #2: explicit, not silent)
|
|
1801
|
+
for fut, name in future_to_name.items():
|
|
1802
|
+
if name not in results_by_name:
|
|
1803
|
+
ms = int(aggregate_timeout_s * 1000)
|
|
1804
|
+
results_by_name[name] = CheckResult(
|
|
1805
|
+
name, "timeout",
|
|
1806
|
+
f"AGG_TIMEOUT (>{ms}ms aggregate)",
|
|
1807
|
+
aggregate_timeout_s * 1000, None,
|
|
1808
|
+
)
|
|
1809
|
+
_emit_ceo_boot_check_skipped_safe(
|
|
1810
|
+
check_name=name,
|
|
1811
|
+
timeout_ms=ms,
|
|
1812
|
+
)
|
|
1813
|
+
finally:
|
|
1814
|
+
# See docstring "Pool lifecycle" — non-blocking shutdown is
|
|
1815
|
+
# required to honor the aggregate timeout.
|
|
1816
|
+
pool.shutdown(wait=False, cancel_futures=True)
|
|
1817
|
+
|
|
1818
|
+
# Codex S82 post-patch fix: emit results in registry order for CR-N7
|
|
1819
|
+
# stability across runs (was completion-order, non-deterministic).
|
|
1820
|
+
return [results_by_name[name] for name, _ in registry if name in results_by_name]
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
# ---- Cached path (PLAN-065 §4.3.2 real per-key cache) -------------------
|
|
1824
|
+
|
|
1825
|
+
def _cache_key_raw() -> str:
|
|
1826
|
+
"""Compose raw cache key string from (HEAD + audit-log mtime + size).
|
|
1827
|
+
|
|
1828
|
+
Per Codex S82 P1 #5 the sub-second precision is NOT required; we use
|
|
1829
|
+
integer seconds + size-in-bytes which together provide collision-safe
|
|
1830
|
+
invalidation when the audit-log is appended.
|
|
1831
|
+
"""
|
|
1832
|
+
try:
|
|
1833
|
+
proc = subprocess.run(
|
|
1834
|
+
["git", "-C", str(REPO_ROOT), "rev-parse", "HEAD"],
|
|
1835
|
+
capture_output=True, text=True, timeout=1.0,
|
|
1836
|
+
)
|
|
1837
|
+
head = proc.stdout.strip() or "nogit"
|
|
1838
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
1839
|
+
head = "nogit"
|
|
1840
|
+
try:
|
|
1841
|
+
st = AUDIT_LOG_DEFAULT.stat()
|
|
1842
|
+
mtime = int(st.st_mtime)
|
|
1843
|
+
size = int(st.st_size)
|
|
1844
|
+
except OSError:
|
|
1845
|
+
mtime, size = 0, 0
|
|
1846
|
+
return f"{head}:{mtime}:{size}"
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
def _cache_key() -> str:
|
|
1850
|
+
"""SHA-256 short-hash of raw cache key (filename-safe + bounded length)."""
|
|
1851
|
+
raw = _cache_key_raw()
|
|
1852
|
+
return hashlib.sha256(raw.encode("utf-8", errors="replace")).hexdigest()[:32]
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
def _cache_path_for_key(key: str) -> Path:
|
|
1856
|
+
"""Resolve cache file path for a given key under the active cache dir."""
|
|
1857
|
+
return _cache_dir() / f"{key}.json"
|
|
1858
|
+
|
|
1859
|
+
|
|
1860
|
+
def cache_lru_evict() -> None:
|
|
1861
|
+
"""LRU-evict oldest cache files when dir size exceeds CACHE_DIR_SIZE_CAP_BYTES.
|
|
1862
|
+
|
|
1863
|
+
Fail-open: any OSError silently breadcrumbs to stderr and returns.
|
|
1864
|
+
Atime-aware where supported; mtime fallback (atime is updated by reads
|
|
1865
|
+
on most filesystems but POSIX `relatime` may suppress it).
|
|
1866
|
+
"""
|
|
1867
|
+
cdir = _cache_dir()
|
|
1868
|
+
if not cdir.exists():
|
|
1869
|
+
return
|
|
1870
|
+
try:
|
|
1871
|
+
entries: List[Tuple[float, int, Path]] = []
|
|
1872
|
+
total = 0
|
|
1873
|
+
for f in cdir.glob("*.json"):
|
|
1874
|
+
try:
|
|
1875
|
+
st = f.stat()
|
|
1876
|
+
except OSError:
|
|
1877
|
+
continue
|
|
1878
|
+
entries.append((st.st_atime, st.st_size, f))
|
|
1879
|
+
total += st.st_size
|
|
1880
|
+
if total <= CACHE_DIR_SIZE_CAP_BYTES:
|
|
1881
|
+
return
|
|
1882
|
+
# Evict oldest-first until under cap.
|
|
1883
|
+
entries.sort(key=lambda e: e[0])
|
|
1884
|
+
for atime, size, path in entries:
|
|
1885
|
+
if total <= CACHE_DIR_SIZE_CAP_BYTES:
|
|
1886
|
+
break
|
|
1887
|
+
try:
|
|
1888
|
+
path.unlink()
|
|
1889
|
+
total -= size
|
|
1890
|
+
except OSError:
|
|
1891
|
+
continue
|
|
1892
|
+
except OSError as e:
|
|
1893
|
+
sys.stderr.write(f"# ceo-boot cache LRU evict failed: {type(e).__name__}\n")
|
|
1894
|
+
|
|
1895
|
+
|
|
1896
|
+
def cached_load() -> Tuple[bool, Any]:
|
|
1897
|
+
"""Per-key cache load. Returns (hit, payload).
|
|
1898
|
+
|
|
1899
|
+
Hit semantics: cache file exists for current key, mtime within TTL,
|
|
1900
|
+
file size within cap, JSON parses cleanly. Otherwise miss (fail-open).
|
|
1901
|
+
Atime is touched on hit (LRU signal).
|
|
1902
|
+
"""
|
|
1903
|
+
key = _cache_key()
|
|
1904
|
+
path = _cache_path_for_key(key)
|
|
1905
|
+
if not path.exists():
|
|
1906
|
+
return False, None
|
|
1907
|
+
try:
|
|
1908
|
+
st = path.stat()
|
|
1909
|
+
if st.st_size > CACHE_FILE_SIZE_CAP_BYTES:
|
|
1910
|
+
return False, None # corrupt / oversized — treat as miss
|
|
1911
|
+
if (time.time() - st.st_mtime) > CACHE_TTL_S:
|
|
1912
|
+
return False, None
|
|
1913
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
1914
|
+
except (OSError, json.JSONDecodeError):
|
|
1915
|
+
return False, None
|
|
1916
|
+
# Defense-in-depth: validate cache_key matches (mtime alone could
|
|
1917
|
+
# collide if filesystem is restored from backup).
|
|
1918
|
+
if data.get("cache_key") != key:
|
|
1919
|
+
return False, None
|
|
1920
|
+
# Touch atime for LRU signal (best-effort; ignore filesystem refusal).
|
|
1921
|
+
try:
|
|
1922
|
+
os.utime(path, None)
|
|
1923
|
+
except OSError:
|
|
1924
|
+
pass
|
|
1925
|
+
return True, data
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
def cached_store(results: List[CheckResult]) -> None:
|
|
1929
|
+
"""Write digest to per-key cache. Atomic (temp + rename); fail-open.
|
|
1930
|
+
|
|
1931
|
+
Codex S82 P1 fix: previous impl had unguarded mkdir + write_text;
|
|
1932
|
+
permission/lock/filesystem errors aborted boot post-checks pre-output.
|
|
1933
|
+
Now wraps all I/O in try/except; on failure emits stderr breadcrumb
|
|
1934
|
+
and returns silently (cache miss next boot, main path unaffected).
|
|
1935
|
+
|
|
1936
|
+
Schema parity: payload includes gate_pass / checks_total / checks_failed
|
|
1937
|
+
/ recommendations / results — identical shape to the live --json output
|
|
1938
|
+
so adopters get the same payload from cache-hit and fresh dispatch.
|
|
1939
|
+
"""
|
|
1940
|
+
cdir = _cache_dir()
|
|
1941
|
+
key = _cache_key()
|
|
1942
|
+
target = _cache_path_for_key(key)
|
|
1943
|
+
try:
|
|
1944
|
+
cdir.mkdir(parents=True, exist_ok=True)
|
|
1945
|
+
failed = sum(1 for r in results if r.status in ("red", "error", "timeout"))
|
|
1946
|
+
gate_pass = (failed == 0)
|
|
1947
|
+
payload = {
|
|
1948
|
+
"cache_key": key,
|
|
1949
|
+
"ts": time.time(),
|
|
1950
|
+
"gate_pass": gate_pass,
|
|
1951
|
+
"checks_total": len(results),
|
|
1952
|
+
"checks_failed": failed,
|
|
1953
|
+
"recommendations": _make_recommendations(results),
|
|
1954
|
+
"results": [
|
|
1955
|
+
{"name": r.name, "status": r.status, "summary": r.summary, "duration_ms": r.duration_ms}
|
|
1956
|
+
for r in results
|
|
1957
|
+
],
|
|
1958
|
+
}
|
|
1959
|
+
body = json.dumps(payload)
|
|
1960
|
+
if len(body.encode("utf-8")) > CACHE_FILE_SIZE_CAP_BYTES:
|
|
1961
|
+
# Drop the recommendations + heavy detail to fit the cap.
|
|
1962
|
+
payload["recommendations"] = []
|
|
1963
|
+
body = json.dumps(payload)
|
|
1964
|
+
# Atomic write: temp file + rename.
|
|
1965
|
+
tmp = target.with_suffix(target.suffix + ".tmp")
|
|
1966
|
+
tmp.write_text(body, encoding="utf-8")
|
|
1967
|
+
os.replace(tmp, target)
|
|
1968
|
+
# Best-effort LRU eviction (post-write, never blocks).
|
|
1969
|
+
cache_lru_evict()
|
|
1970
|
+
except (OSError, PermissionError, json.JSONDecodeError) as e:
|
|
1971
|
+
sys.stderr.write(f"# ceo-boot cache-store failed (fail-open): {type(e).__name__}\n")
|
|
1972
|
+
|
|
1973
|
+
|
|
1974
|
+
# ---- Recommendations engine (PLAN-065 §4.3 Phase 3-D) ---------------------
|
|
1975
|
+
|
|
1976
|
+
def _make_recommendations(results: List[CheckResult]) -> List[str]:
|
|
1977
|
+
"""Rule-based prioritizer ≤5 actionable items (Sec MF-4 sanitized).
|
|
1978
|
+
|
|
1979
|
+
Deterministic ordering (CR-N7): lex-sort by category prefix so ``--json``
|
|
1980
|
+
is stable across runs.
|
|
1981
|
+
"""
|
|
1982
|
+
recs: List[Tuple[str, str]] = [] # (sort_key, formatted)
|
|
1983
|
+
by_name: Dict[str, CheckResult] = {r.name: r for r in results}
|
|
1984
|
+
|
|
1985
|
+
# Codex S82 P1 fix: recs engine ignored timeout/error checks. Since those
|
|
1986
|
+
# flip gate_pass=False, they MUST surface as top-priority recommendations.
|
|
1987
|
+
#
|
|
1988
|
+
# Codex CDX-W5-iter3-P1 closure: the original `_NAMED_RULES` skip
|
|
1989
|
+
# was over-engineered defense against duplicate emit — but the named
|
|
1990
|
+
# rule branches below only fire on `status in {yellow, red}`. A named
|
|
1991
|
+
# check that times out (status "timeout"/"error") therefore matched
|
|
1992
|
+
# NEITHER branch, producing a silent gap where gate_pass=False but
|
|
1993
|
+
# zero recommendation surfaced. We now emit the 00-* row for every
|
|
1994
|
+
# failing check; the named branches can never co-fire (their `status`
|
|
1995
|
+
# gate is incompatible with timeout/error), so dedup is moot.
|
|
1996
|
+
failing = sorted(
|
|
1997
|
+
(r for r in results if r.status in ("timeout", "error")),
|
|
1998
|
+
key=lambda r: r.name,
|
|
1999
|
+
)
|
|
2000
|
+
for r in failing[:3]: # cap at 3 to leave room for named rules
|
|
2001
|
+
recs.append((
|
|
2002
|
+
f"00-{r.name}-{r.status}", # sort BEFORE 01-owner-sentinels
|
|
2003
|
+
f"Check '{r.name}' {r.status}: {_sanitize_for_recs(r.summary)} "
|
|
2004
|
+
f"(blocks gate_pass)",
|
|
2005
|
+
))
|
|
2006
|
+
|
|
2007
|
+
# PLAN-135 W1 S3 — settings/env tamper tripwires (rail integrity).
|
|
2008
|
+
# Sort key "005-*" lands AFTER the 00-* gate-blockers and BEFORE
|
|
2009
|
+
# 01-owner-sentinels (lexicographic: "00-" < "005" < "01-"): a fired
|
|
2010
|
+
# tripwire means every other signal on this digest may already be
|
|
2011
|
+
# produced by a disarmed/redirected rail. Only closed-enum class
|
|
2012
|
+
# names reach the rendered text (finding detail can embed env values).
|
|
2013
|
+
tamper = by_name.get("settings_tamper_tripwires")
|
|
2014
|
+
if tamper and tamper.status == "red" and tamper.detail:
|
|
2015
|
+
items = tamper.detail if isinstance(tamper.detail, list) else []
|
|
2016
|
+
classes = sorted({
|
|
2017
|
+
str(f.get("class", ""))
|
|
2018
|
+
for f in items
|
|
2019
|
+
if isinstance(f, dict) and f.get("class")
|
|
2020
|
+
})
|
|
2021
|
+
if classes:
|
|
2022
|
+
preview = _sanitize_for_recs(", ".join(classes[:3]))
|
|
2023
|
+
recs.append((
|
|
2024
|
+
"005-settings-tamper",
|
|
2025
|
+
f"Settings/env tamper tripwire(s) fired ({len(items)}): "
|
|
2026
|
+
f"{preview}{'...' if len(classes) > 3 else ''} — inspect "
|
|
2027
|
+
f"settings layers + env before trusting this session",
|
|
2028
|
+
))
|
|
2029
|
+
|
|
2030
|
+
# Owner-pending GPG sentinels — highest priority (HARD blocker for ceremony)
|
|
2031
|
+
sent = by_name.get("sentinels_pending_gpg")
|
|
2032
|
+
if sent and sent.status == "yellow" and sent.detail:
|
|
2033
|
+
items = sent.detail if isinstance(sent.detail, list) else []
|
|
2034
|
+
if items:
|
|
2035
|
+
preview = _sanitize_for_recs(", ".join(items[:3]))
|
|
2036
|
+
recs.append((
|
|
2037
|
+
"01-owner-sentinels",
|
|
2038
|
+
f"Owner GPG sign pending: {len(items)} sentinels ({preview}{'...' if len(items) > 3 else ''})",
|
|
2039
|
+
))
|
|
2040
|
+
|
|
2041
|
+
# Stranded executing plans (no commits in 24h)
|
|
2042
|
+
stranded = by_name.get("plans_stranded_executing")
|
|
2043
|
+
if stranded and stranded.status == "red" and stranded.detail:
|
|
2044
|
+
items = stranded.detail if isinstance(stranded.detail, list) else []
|
|
2045
|
+
if items:
|
|
2046
|
+
preview = _sanitize_for_recs(", ".join(items[:3]))
|
|
2047
|
+
recs.append((
|
|
2048
|
+
"02-stranded-plans",
|
|
2049
|
+
f"Stranded executing plans (>24h no commits): {preview}",
|
|
2050
|
+
))
|
|
2051
|
+
|
|
2052
|
+
# Skill-unknown ratio > threshold
|
|
2053
|
+
skill = by_name.get("skill_unknown_ratio")
|
|
2054
|
+
if skill and skill.status == "red":
|
|
2055
|
+
recs.append((
|
|
2056
|
+
"03-skill-unknown",
|
|
2057
|
+
f"Spawn dispatch skill=unknown ratio elevated: {_sanitize_for_recs(skill.summary)}",
|
|
2058
|
+
))
|
|
2059
|
+
|
|
2060
|
+
# Audit-v3 backlog open
|
|
2061
|
+
av3 = by_name.get("audit_v3_backlog")
|
|
2062
|
+
if av3 and av3.status == "yellow" and av3.detail:
|
|
2063
|
+
items = av3.detail if isinstance(av3.detail, list) else []
|
|
2064
|
+
if items:
|
|
2065
|
+
preview = _sanitize_for_recs(", ".join(items[:3]))
|
|
2066
|
+
recs.append((
|
|
2067
|
+
"04-audit-v3-backlog",
|
|
2068
|
+
f"Audit-v3 backlog open ({len(items)}): {preview}",
|
|
2069
|
+
))
|
|
2070
|
+
|
|
2071
|
+
# ADRs stale-proposed >30d
|
|
2072
|
+
adrs = by_name.get("adrs_stale_proposed")
|
|
2073
|
+
if adrs and adrs.status == "yellow" and adrs.detail:
|
|
2074
|
+
items = adrs.detail if isinstance(adrs.detail, list) else []
|
|
2075
|
+
if items:
|
|
2076
|
+
preview = _sanitize_for_recs(", ".join(items[:3]))
|
|
2077
|
+
recs.append((
|
|
2078
|
+
"05-adrs-stale",
|
|
2079
|
+
f"ADRs PROPOSED >30d ({len(items)}): {preview} — promote or retract",
|
|
2080
|
+
))
|
|
2081
|
+
|
|
2082
|
+
# Sort by deterministic key (CR-N7) and cap at 5
|
|
2083
|
+
recs.sort(key=lambda x: x[0])
|
|
2084
|
+
return [text for _, text in recs[:5]]
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
# PLAN-078 Wave 5 — severity-aware view of the recommendations engine.
|
|
2088
|
+
# Mirrors `_make_recommendations` ordering exactly (same sort key + ≤5 cap)
|
|
2089
|
+
# but exposes the (sort_key, text, severity) triple so the marker emitter
|
|
2090
|
+
# can filter by severity≥medium without re-classifying. Severity buckets
|
|
2091
|
+
# track the rule rank assigned in `_make_recommendations`:
|
|
2092
|
+
#
|
|
2093
|
+
# 00-* (timeout/error gate-blockers) → high
|
|
2094
|
+
# 005-settings-tamper → high (PLAN-135 W1 S3 rail integrity)
|
|
2095
|
+
# 01-owner-sentinels → high
|
|
2096
|
+
# 02-stranded-plans → high
|
|
2097
|
+
# 03-skill-unknown → medium
|
|
2098
|
+
# 04-audit-v3-backlog → medium
|
|
2099
|
+
# 05-adrs-stale → low
|
|
2100
|
+
#
|
|
2101
|
+
# Anything else (future rules) defaults to "low" — caller policy is to
|
|
2102
|
+
# only emit markers for medium/high, so unknown future rules are silent
|
|
2103
|
+
# until the mapping is updated. Codex CDX-P1-04 closure: this helper is
|
|
2104
|
+
# deterministic + side-effect-free; the marker emitter consumes the
|
|
2105
|
+
# triple and never mutates `_make_recommendations` output.
|
|
2106
|
+
def _recommendations_with_severity(
|
|
2107
|
+
results: List[CheckResult],
|
|
2108
|
+
) -> List[Tuple[str, str, str]]:
|
|
2109
|
+
"""Return (sort_key, text, severity) triples mirroring _make_recommendations.
|
|
2110
|
+
|
|
2111
|
+
Re-runs the rule pipeline (cheap — already O(N) over results) so this
|
|
2112
|
+
helper is safe to call after `_make_recommendations` without ordering
|
|
2113
|
+
drift. Severity is derived from the sort_key prefix (deterministic).
|
|
2114
|
+
"""
|
|
2115
|
+
recs: List[Tuple[str, str]] = []
|
|
2116
|
+
by_name: Dict[str, CheckResult] = {r.name: r for r in results}
|
|
2117
|
+
|
|
2118
|
+
# Mirror `_make_recommendations` exactly (Codex CDX-W5-iter3-P1):
|
|
2119
|
+
# named-rule skip removed because timeout/error never overlaps with
|
|
2120
|
+
# the yellow/red gates of the named branches.
|
|
2121
|
+
failing = sorted(
|
|
2122
|
+
(r for r in results if r.status in ("timeout", "error")),
|
|
2123
|
+
key=lambda r: r.name,
|
|
2124
|
+
)
|
|
2125
|
+
for r in failing[:3]:
|
|
2126
|
+
recs.append((
|
|
2127
|
+
f"00-{r.name}-{r.status}",
|
|
2128
|
+
f"Check '{r.name}' {r.status}: {_sanitize_for_recs(r.summary)} "
|
|
2129
|
+
f"(blocks gate_pass)",
|
|
2130
|
+
))
|
|
2131
|
+
|
|
2132
|
+
# PLAN-135 W1 S3 — mirror of the _make_recommendations tamper rule
|
|
2133
|
+
# (same sort key + same text so the two pipelines never drift).
|
|
2134
|
+
tamper = by_name.get("settings_tamper_tripwires")
|
|
2135
|
+
if tamper and tamper.status == "red" and tamper.detail:
|
|
2136
|
+
items = tamper.detail if isinstance(tamper.detail, list) else []
|
|
2137
|
+
classes = sorted({
|
|
2138
|
+
str(f.get("class", ""))
|
|
2139
|
+
for f in items
|
|
2140
|
+
if isinstance(f, dict) and f.get("class")
|
|
2141
|
+
})
|
|
2142
|
+
if classes:
|
|
2143
|
+
preview = _sanitize_for_recs(", ".join(classes[:3]))
|
|
2144
|
+
recs.append((
|
|
2145
|
+
"005-settings-tamper",
|
|
2146
|
+
f"Settings/env tamper tripwire(s) fired ({len(items)}): "
|
|
2147
|
+
f"{preview}{'...' if len(classes) > 3 else ''} — inspect "
|
|
2148
|
+
f"settings layers + env before trusting this session",
|
|
2149
|
+
))
|
|
2150
|
+
|
|
2151
|
+
sent = by_name.get("sentinels_pending_gpg")
|
|
2152
|
+
if sent and sent.status == "yellow" and sent.detail:
|
|
2153
|
+
items = sent.detail if isinstance(sent.detail, list) else []
|
|
2154
|
+
if items:
|
|
2155
|
+
preview = _sanitize_for_recs(", ".join(items[:3]))
|
|
2156
|
+
recs.append((
|
|
2157
|
+
"01-owner-sentinels",
|
|
2158
|
+
f"Owner GPG sign pending: {len(items)} sentinels ({preview}{'...' if len(items) > 3 else ''})",
|
|
2159
|
+
))
|
|
2160
|
+
|
|
2161
|
+
stranded = by_name.get("plans_stranded_executing")
|
|
2162
|
+
if stranded and stranded.status == "red" and stranded.detail:
|
|
2163
|
+
items = stranded.detail if isinstance(stranded.detail, list) else []
|
|
2164
|
+
if items:
|
|
2165
|
+
preview = _sanitize_for_recs(", ".join(items[:3]))
|
|
2166
|
+
recs.append((
|
|
2167
|
+
"02-stranded-plans",
|
|
2168
|
+
f"Stranded executing plans (>24h no commits): {preview}",
|
|
2169
|
+
))
|
|
2170
|
+
|
|
2171
|
+
skill = by_name.get("skill_unknown_ratio")
|
|
2172
|
+
if skill and skill.status == "red":
|
|
2173
|
+
recs.append((
|
|
2174
|
+
"03-skill-unknown",
|
|
2175
|
+
f"Spawn dispatch skill=unknown ratio elevated: {_sanitize_for_recs(skill.summary)}",
|
|
2176
|
+
))
|
|
2177
|
+
|
|
2178
|
+
av3 = by_name.get("audit_v3_backlog")
|
|
2179
|
+
if av3 and av3.status == "yellow" and av3.detail:
|
|
2180
|
+
items = av3.detail if isinstance(av3.detail, list) else []
|
|
2181
|
+
if items:
|
|
2182
|
+
preview = _sanitize_for_recs(", ".join(items[:3]))
|
|
2183
|
+
recs.append((
|
|
2184
|
+
"04-audit-v3-backlog",
|
|
2185
|
+
f"Audit-v3 backlog open ({len(items)}): {preview}",
|
|
2186
|
+
))
|
|
2187
|
+
|
|
2188
|
+
adrs = by_name.get("adrs_stale_proposed")
|
|
2189
|
+
if adrs and adrs.status == "yellow" and adrs.detail:
|
|
2190
|
+
items = adrs.detail if isinstance(adrs.detail, list) else []
|
|
2191
|
+
if items:
|
|
2192
|
+
preview = _sanitize_for_recs(", ".join(items[:3]))
|
|
2193
|
+
recs.append((
|
|
2194
|
+
"05-adrs-stale",
|
|
2195
|
+
f"ADRs PROPOSED >30d ({len(items)}): {preview} — promote or retract",
|
|
2196
|
+
))
|
|
2197
|
+
|
|
2198
|
+
recs.sort(key=lambda x: x[0])
|
|
2199
|
+
triples: List[Tuple[str, str, str]] = []
|
|
2200
|
+
for sort_key, text in recs[:5]:
|
|
2201
|
+
if sort_key.startswith("00-") or sort_key in (
|
|
2202
|
+
"005-settings-tamper", # PLAN-135 W1 S3 — rail-integrity = high
|
|
2203
|
+
"01-owner-sentinels", "02-stranded-plans"
|
|
2204
|
+
):
|
|
2205
|
+
severity = "high"
|
|
2206
|
+
elif sort_key in ("03-skill-unknown", "04-audit-v3-backlog"):
|
|
2207
|
+
severity = "medium"
|
|
2208
|
+
elif sort_key == "05-adrs-stale":
|
|
2209
|
+
severity = "low"
|
|
2210
|
+
else: # pragma: no cover — defensive default for future rules
|
|
2211
|
+
severity = "low"
|
|
2212
|
+
triples.append((sort_key, text, severity))
|
|
2213
|
+
return triples
|
|
2214
|
+
|
|
2215
|
+
|
|
2216
|
+
# ---- Renderer ---------------------------------------------------------------
|
|
2217
|
+
|
|
2218
|
+
def render_digest(results: List[CheckResult], short: bool = False) -> str:
|
|
2219
|
+
lines = ["", "## /ceo-boot digest", ""]
|
|
2220
|
+
if short:
|
|
2221
|
+
red = sum(1 for r in results if r.status == "red")
|
|
2222
|
+
yellow = sum(1 for r in results if r.status == "yellow")
|
|
2223
|
+
timeout = sum(1 for r in results if r.status == "timeout")
|
|
2224
|
+
error = sum(1 for r in results if r.status == "error")
|
|
2225
|
+
green = sum(1 for r in results if r.status == "green")
|
|
2226
|
+
lines.append(
|
|
2227
|
+
f"- {green} green / {yellow} yellow / {red} red / "
|
|
2228
|
+
f"{timeout} timeout / {error} error"
|
|
2229
|
+
)
|
|
2230
|
+
# Surface non-green checks one-line for situational awareness
|
|
2231
|
+
for r in results:
|
|
2232
|
+
if r.status != "green":
|
|
2233
|
+
lines.append(f" - {r.name}: {r.status} — {r.summary}")
|
|
2234
|
+
else:
|
|
2235
|
+
lines.append("| Check | Status | Summary | Duration ms |")
|
|
2236
|
+
lines.append("|---|---|---|---|")
|
|
2237
|
+
for r in results:
|
|
2238
|
+
lines.append(f"| {r.name} | {r.status} | {r.summary} | {r.duration_ms:.0f} |")
|
|
2239
|
+
|
|
2240
|
+
# Recommendations engine output
|
|
2241
|
+
recs = _make_recommendations(results)
|
|
2242
|
+
if recs:
|
|
2243
|
+
lines.append("")
|
|
2244
|
+
lines.append("### Recommendations")
|
|
2245
|
+
for i, rec in enumerate(recs, 1):
|
|
2246
|
+
lines.append(f"{i}. {rec}")
|
|
2247
|
+
|
|
2248
|
+
lines.append("")
|
|
2249
|
+
return "\n".join(lines)
|
|
2250
|
+
|
|
2251
|
+
|
|
2252
|
+
# ---- Bench harness ---------------------------------------------------------
|
|
2253
|
+
|
|
2254
|
+
def _percentile(xs: List[float], p: float) -> float:
|
|
2255
|
+
"""Stdlib percentile via sorted index. p in [0,100]. Empty → 0.0.
|
|
2256
|
+
|
|
2257
|
+
Spec (PLAN-065 §4.3 + S82 brief): use ``sorted(arr)[int(0.95 * len(arr))]``
|
|
2258
|
+
style indexing — NOT numpy. With small N the index can hit an off-by-one
|
|
2259
|
+
near the upper bound; we use ``int(round((len(s)-1) * p/100))`` which is
|
|
2260
|
+
monotonic-correct for both N=5 and N=10.
|
|
2261
|
+
"""
|
|
2262
|
+
if not xs:
|
|
2263
|
+
return 0.0
|
|
2264
|
+
s = sorted(xs)
|
|
2265
|
+
k = int(round((len(s) - 1) * p / 100.0))
|
|
2266
|
+
return s[k]
|
|
2267
|
+
|
|
2268
|
+
|
|
2269
|
+
def _rss_kb_current() -> float:
|
|
2270
|
+
"""Return current process RSS in KiB.
|
|
2271
|
+
|
|
2272
|
+
Codex S82 P1 fix: ``resource.getrusage(RUSAGE_SELF).ru_maxrss`` returns
|
|
2273
|
+
BYTES on macOS but KiB on Linux. We normalize to KiB by detecting the
|
|
2274
|
+
platform. This is high-water mark for the process; deltas across runs
|
|
2275
|
+
are still meaningful as long as the platform's unit is consistent.
|
|
2276
|
+
"""
|
|
2277
|
+
rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
2278
|
+
if sys.platform == "darwin":
|
|
2279
|
+
# macOS: bytes → KiB
|
|
2280
|
+
return rss / 1024.0
|
|
2281
|
+
# Linux + most BSDs: already KiB
|
|
2282
|
+
return float(rss)
|
|
2283
|
+
|
|
2284
|
+
|
|
2285
|
+
def bench(n_runs: int = 5, *, include_tier_a: bool = False) -> Dict[str, Any]:
|
|
2286
|
+
"""Run the dispatcher N times. Report p50/p95 wall-clock, per-iter RSS, deltas.
|
|
2287
|
+
|
|
2288
|
+
Output schema includes the legacy fields (``wall_clock_ms`` map, per-check
|
|
2289
|
+
p50/p95, tracemalloc current/peak) PLUS the PLAN-065 §4.3 spec fields
|
|
2290
|
+
(per-iter ``iterations`` list with ``iter``, ``duration_ms``, ``rss_kb``
|
|
2291
|
+
+ summary dict with ``p50_ms``, ``p95_ms``, ``min_ms``, ``max_ms``,
|
|
2292
|
+
``rss_delta_kb``).
|
|
2293
|
+
"""
|
|
2294
|
+
wall_clocks: List[float] = []
|
|
2295
|
+
rss_per_iter: List[float] = []
|
|
2296
|
+
iterations: List[Dict[str, Any]] = []
|
|
2297
|
+
registry = list(TIER_S_CHECKS) + (list(TIER_A_CHECKS) if include_tier_a else [])
|
|
2298
|
+
per_check_durations: Dict[str, List[float]] = {name: [] for name, _ in registry}
|
|
2299
|
+
|
|
2300
|
+
rss_before = _rss_kb_current()
|
|
2301
|
+
tracemalloc.start()
|
|
2302
|
+
snap_before = tracemalloc.take_snapshot()
|
|
2303
|
+
for i in range(n_runs):
|
|
2304
|
+
t0 = time.perf_counter()
|
|
2305
|
+
results = dispatch_parallel(include_tier_a=include_tier_a)
|
|
2306
|
+
wc = (time.perf_counter() - t0) * 1000
|
|
2307
|
+
rss_now = _rss_kb_current()
|
|
2308
|
+
wall_clocks.append(wc)
|
|
2309
|
+
rss_per_iter.append(rss_now)
|
|
2310
|
+
iterations.append({
|
|
2311
|
+
"iter": i + 1,
|
|
2312
|
+
"duration_ms": round(wc, 2),
|
|
2313
|
+
"rss_kb": round(rss_now, 2),
|
|
2314
|
+
})
|
|
2315
|
+
for r in results:
|
|
2316
|
+
per_check_durations.setdefault(r.name, []).append(r.duration_ms)
|
|
2317
|
+
snap_after = tracemalloc.take_snapshot()
|
|
2318
|
+
current, peak = tracemalloc.get_traced_memory()
|
|
2319
|
+
tracemalloc.stop()
|
|
2320
|
+
rss_after = _rss_kb_current()
|
|
2321
|
+
|
|
2322
|
+
diff_stats = snap_after.compare_to(snap_before, "filename")
|
|
2323
|
+
py_delta_kb = sum(stat.size_diff for stat in diff_stats) / 1024.0
|
|
2324
|
+
|
|
2325
|
+
return {
|
|
2326
|
+
"n_runs": n_runs,
|
|
2327
|
+
"include_tier_a": include_tier_a,
|
|
2328
|
+
"iterations": iterations,
|
|
2329
|
+
"wall_clock_ms": {
|
|
2330
|
+
"p50": _percentile(wall_clocks, 50),
|
|
2331
|
+
"p95": _percentile(wall_clocks, 95),
|
|
2332
|
+
"min": min(wall_clocks) if wall_clocks else 0.0,
|
|
2333
|
+
"max": max(wall_clocks) if wall_clocks else 0.0,
|
|
2334
|
+
},
|
|
2335
|
+
"summary": {
|
|
2336
|
+
"p50_ms": round(_percentile(wall_clocks, 50), 2),
|
|
2337
|
+
"p95_ms": round(_percentile(wall_clocks, 95), 2),
|
|
2338
|
+
"min_ms": round(min(wall_clocks), 2) if wall_clocks else 0.0,
|
|
2339
|
+
"max_ms": round(max(wall_clocks), 2) if wall_clocks else 0.0,
|
|
2340
|
+
"rss_delta_kb": round(rss_after - rss_before, 2),
|
|
2341
|
+
},
|
|
2342
|
+
"per_check_p95_ms": {name: _percentile(durs, 95) for name, durs in per_check_durations.items()},
|
|
2343
|
+
"per_check_p50_ms": {name: _percentile(durs, 50) for name, durs in per_check_durations.items()},
|
|
2344
|
+
"memory_python_delta_kb": round(py_delta_kb, 2),
|
|
2345
|
+
"tracemalloc_peak_kb": round(peak / 1024.0, 2),
|
|
2346
|
+
"tracemalloc_current_kb": round(current / 1024.0, 2),
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
|
|
2350
|
+
def render_bench_markdown(report: Dict[str, Any]) -> str:
|
|
2351
|
+
"""Render bench report as a markdown table (PLAN-065 §4.3 spec).
|
|
2352
|
+
|
|
2353
|
+
Header columns: iter # | duration_ms | RSS_kb. Summary row appended
|
|
2354
|
+
with p50/p95/min/max/RSS_delta. Returns the rendered string (caller
|
|
2355
|
+
writes to stdout).
|
|
2356
|
+
"""
|
|
2357
|
+
lines = ["", "## /ceo-boot --bench", ""]
|
|
2358
|
+
lines.append(f"N={report['n_runs']} include_tier_a={report.get('include_tier_a', False)}")
|
|
2359
|
+
lines.append("")
|
|
2360
|
+
lines.append("| iter | duration_ms | RSS_kb |")
|
|
2361
|
+
lines.append("|---|---|---|")
|
|
2362
|
+
for it in report.get("iterations", []):
|
|
2363
|
+
lines.append(f"| {it['iter']} | {it['duration_ms']:.1f} | {it['rss_kb']:.1f} |")
|
|
2364
|
+
s = report.get("summary", {})
|
|
2365
|
+
lines.append(
|
|
2366
|
+
f"| **summary** | p50={s.get('p50_ms', 0):.1f} / p95={s.get('p95_ms', 0):.1f}"
|
|
2367
|
+
f" / min={s.get('min_ms', 0):.1f} / max={s.get('max_ms', 0):.1f}"
|
|
2368
|
+
f" | rss_delta={s.get('rss_delta_kb', 0):.1f} |"
|
|
2369
|
+
)
|
|
2370
|
+
lines.append("")
|
|
2371
|
+
return "\n".join(lines)
|
|
2372
|
+
|
|
2373
|
+
|
|
2374
|
+
# === PLAN-065 Phase 2 audit_emit wire =====================================
|
|
2375
|
+
# Reality-Ledger fixture #4 closure (declared-but-not-wired). Pre-S82,
|
|
2376
|
+
# ceo-boot.py shipped emit comments only. Phase 2 wires the actual call.
|
|
2377
|
+
# Sec MF-3 field allowlist enforced ON THE EMIT SIDE (_lib/audit_emit.py).
|
|
2378
|
+
# Caller passes only allowlisted fields; never raises on emit failure.
|
|
2379
|
+
# Pre-canonical-ceremony the symbol is missing → hasattr() guard short-
|
|
2380
|
+
# circuits silently (advisory log to stderr only when CEO_BOOT_DEBUG=1).
|
|
2381
|
+
|
|
2382
|
+
|
|
2383
|
+
def _ceo_boot_session_id() -> str:
|
|
2384
|
+
"""Derive session id from harness env or a stable fallback.
|
|
2385
|
+
|
|
2386
|
+
Defense-in-depth: never raises. The session_id is used as a forensic
|
|
2387
|
+
correlator across the 15 Tier-S checks; it does NOT need to be
|
|
2388
|
+
cryptographically unique.
|
|
2389
|
+
"""
|
|
2390
|
+
sid = os.environ.get("CLAUDE_SESSION_ID") or os.environ.get("CEO_SESSION_ID")
|
|
2391
|
+
if sid:
|
|
2392
|
+
return sid[:64] # bound length defense-in-depth
|
|
2393
|
+
# Fallback: parent shell PID + start of audit-log mtime. Stable
|
|
2394
|
+
# within a session, advisory across sessions.
|
|
2395
|
+
try:
|
|
2396
|
+
return f"pid-{os.getppid()}-{int(AUDIT_LOG_DEFAULT.stat().st_mtime)}"
|
|
2397
|
+
except OSError:
|
|
2398
|
+
return f"pid-{os.getppid()}"
|
|
2399
|
+
|
|
2400
|
+
|
|
2401
|
+
def _emit_ceo_boot_emitted_safe(
|
|
2402
|
+
*,
|
|
2403
|
+
gate_pass: bool,
|
|
2404
|
+
duration_ms: int,
|
|
2405
|
+
checks_total: int,
|
|
2406
|
+
checks_failed: int,
|
|
2407
|
+
cache_hit: bool = False,
|
|
2408
|
+
) -> None:
|
|
2409
|
+
"""Wire-up to audit_emit.emit_ceo_boot_emitted. Fail-open contract.
|
|
2410
|
+
|
|
2411
|
+
Pre-canonical-ceremony: hasattr() returns False, function is a no-op.
|
|
2412
|
+
Post-ceremony: emits the telemetry event with Sec MF-3 field allowlist
|
|
2413
|
+
enforced on the emit side.
|
|
2414
|
+
"""
|
|
2415
|
+
if _audit_emit is None:
|
|
2416
|
+
return
|
|
2417
|
+
fn = getattr(_audit_emit, "emit_ceo_boot_emitted", None)
|
|
2418
|
+
if not callable(fn):
|
|
2419
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
2420
|
+
sys.stderr.write(
|
|
2421
|
+
"# ceo-boot: audit_emit.emit_ceo_boot_emitted not registered "
|
|
2422
|
+
"(canonical ceremony pending v1.12.0)\n"
|
|
2423
|
+
)
|
|
2424
|
+
return
|
|
2425
|
+
try:
|
|
2426
|
+
fn(
|
|
2427
|
+
session_id=_ceo_boot_session_id(),
|
|
2428
|
+
gate_pass=bool(gate_pass),
|
|
2429
|
+
duration_ms=int(duration_ms),
|
|
2430
|
+
checks_total=int(checks_total),
|
|
2431
|
+
checks_failed=int(checks_failed),
|
|
2432
|
+
cache_hit=bool(cache_hit),
|
|
2433
|
+
)
|
|
2434
|
+
except Exception: # noqa: BLE001 — fail-open per audit_emit contract
|
|
2435
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
2436
|
+
import traceback
|
|
2437
|
+
traceback.print_exc(file=sys.stderr)
|
|
2438
|
+
|
|
2439
|
+
|
|
2440
|
+
def _emit_ceo_boot_check_skipped_safe(
|
|
2441
|
+
*,
|
|
2442
|
+
check_name: str,
|
|
2443
|
+
timeout_ms: int,
|
|
2444
|
+
) -> None:
|
|
2445
|
+
"""Wire-up to audit_emit.emit_ceo_boot_check_skipped. Fail-open contract."""
|
|
2446
|
+
if _audit_emit is None:
|
|
2447
|
+
return
|
|
2448
|
+
fn = getattr(_audit_emit, "emit_ceo_boot_check_skipped", None)
|
|
2449
|
+
if not callable(fn):
|
|
2450
|
+
return
|
|
2451
|
+
try:
|
|
2452
|
+
fn(
|
|
2453
|
+
session_id=_ceo_boot_session_id(),
|
|
2454
|
+
check_name=check_name,
|
|
2455
|
+
timeout_ms=int(timeout_ms),
|
|
2456
|
+
)
|
|
2457
|
+
except Exception: # noqa: BLE001 — fail-open
|
|
2458
|
+
pass
|
|
2459
|
+
|
|
2460
|
+
|
|
2461
|
+
# === END PLAN-065 Phase 2 audit_emit wire =================================
|
|
2462
|
+
|
|
2463
|
+
|
|
2464
|
+
# === PLAN-078 Wave 5 — TaskCreate-candidate marker emit + dedup ============
|
|
2465
|
+
# Layer A of the Wave 5 closure (per PLAN-078 §4 + Codex CDX-UNIQUE-02 +
|
|
2466
|
+
# CDX-P0-03 + CDX-P1-04 + Perf PERF-P1-03). Writes a structured stdout
|
|
2467
|
+
# marker block per top-3 high/medium recommendation when gate_pass=False,
|
|
2468
|
+
# dedup'd by 12-hex subject_hash via a 24h TTL state file under
|
|
2469
|
+
# `_lib/filelock`. The Claude orchestrator running /ceo-boot reads the
|
|
2470
|
+
# marker blocks and invokes TaskCreate; this script never touches the
|
|
2471
|
+
# TaskCreate harness primitive directly. Audit emit goes through
|
|
2472
|
+
# `audit_emit.emit_ceo_boot_task_candidate_emitted` (hasattr-guarded
|
|
2473
|
+
# pre-canonical-ceremony per the W5 staging→canonical model).
|
|
2474
|
+
|
|
2475
|
+
# Default state path lives under the same project state dir as the cache
|
|
2476
|
+
# (parity with audit-log.jsonl). Override `CEO_BOOT_TASK_STATE_PATH` for
|
|
2477
|
+
# tests. Format: {"entries": [{"subject_hash": "...", "ts": <epoch>}, ...]}
|
|
2478
|
+
# bounded to 256 entries (LRU evict on overflow).
|
|
2479
|
+
TASK_EMIT_STATE_PATH_DEFAULT = (
|
|
2480
|
+
Path.home()
|
|
2481
|
+
/ ".claude" / "projects" / "ceo-orchestration"
|
|
2482
|
+
/ "state" / "ceo-boot-tasks-emitted.json"
|
|
2483
|
+
)
|
|
2484
|
+
TASK_EMIT_TTL_S = 24 * 60 * 60 # 24h dedup window
|
|
2485
|
+
TASK_EMIT_TOP_N = 3 # emit at most 3 markers per boot
|
|
2486
|
+
TASK_EMIT_STATE_MAX_ENTRIES = 256 # bounded state size
|
|
2487
|
+
|
|
2488
|
+
|
|
2489
|
+
def _task_emit_state_path() -> Path:
|
|
2490
|
+
"""Resolve dedup state-file path at call time (env override-aware)."""
|
|
2491
|
+
override = os.environ.get("CEO_BOOT_TASK_STATE_PATH")
|
|
2492
|
+
if override:
|
|
2493
|
+
return Path(override)
|
|
2494
|
+
return TASK_EMIT_STATE_PATH_DEFAULT
|
|
2495
|
+
|
|
2496
|
+
|
|
2497
|
+
def _subject_hash(subject: str) -> str:
|
|
2498
|
+
"""Return a 12-hex-char prefix of sha256(subject) for dedup bookkeeping.
|
|
2499
|
+
|
|
2500
|
+
The full subject text is NEVER persisted (Sec MF-3); the hash is the
|
|
2501
|
+
only stable identifier shared between the audit event and the state
|
|
2502
|
+
file. NFKC-normalize first so homoglyph variants collapse to the
|
|
2503
|
+
same dedup key (parity with `_sanitize_for_recs`).
|
|
2504
|
+
"""
|
|
2505
|
+
safe = subject if isinstance(subject, str) else str(subject)
|
|
2506
|
+
try:
|
|
2507
|
+
safe = unicodedata.normalize("NFKC", safe)
|
|
2508
|
+
except (TypeError, ValueError): # pragma: no cover — defensive
|
|
2509
|
+
pass
|
|
2510
|
+
digest = hashlib.sha256(safe.encode("utf-8", errors="replace")).hexdigest()
|
|
2511
|
+
return digest[:12]
|
|
2512
|
+
|
|
2513
|
+
|
|
2514
|
+
def _load_task_emit_state(path: Path) -> Dict[str, Any]:
|
|
2515
|
+
"""Load dedup state, prune entries older than TASK_EMIT_TTL_S.
|
|
2516
|
+
|
|
2517
|
+
Fail-open: corrupt JSON / unreadable file → returns empty state. The
|
|
2518
|
+
caller persists the pruned state on next write so corruption is
|
|
2519
|
+
self-healing across boots.
|
|
2520
|
+
|
|
2521
|
+
Codex CDX-W5-P1-04 closure: drop entries with non-finite timestamps
|
|
2522
|
+
(NaN / inf) and entries with timestamps in the future (NTP jump
|
|
2523
|
+
backward, deliberate clock skew). The TTL window is `[0, TTL)` —
|
|
2524
|
+
age must be a finite non-negative number strictly less than the
|
|
2525
|
+
TTL bound.
|
|
2526
|
+
"""
|
|
2527
|
+
if not path.exists():
|
|
2528
|
+
return {"entries": []}
|
|
2529
|
+
try:
|
|
2530
|
+
raw = path.read_text(encoding="utf-8")
|
|
2531
|
+
data = json.loads(raw)
|
|
2532
|
+
except (OSError, json.JSONDecodeError):
|
|
2533
|
+
return {"entries": []}
|
|
2534
|
+
entries = data.get("entries") if isinstance(data, dict) else None
|
|
2535
|
+
if not isinstance(entries, list):
|
|
2536
|
+
return {"entries": []}
|
|
2537
|
+
now = time.time()
|
|
2538
|
+
pruned: List[Dict[str, Any]] = []
|
|
2539
|
+
for entry in entries:
|
|
2540
|
+
if not isinstance(entry, dict):
|
|
2541
|
+
continue
|
|
2542
|
+
ts = entry.get("ts")
|
|
2543
|
+
sh = entry.get("subject_hash")
|
|
2544
|
+
if not isinstance(sh, str) or not isinstance(ts, (int, float)):
|
|
2545
|
+
continue
|
|
2546
|
+
# Reject NaN / inf — float comparison NaN!=NaN always; inf age
|
|
2547
|
+
# would otherwise be retained as TTL-current.
|
|
2548
|
+
ts_f = float(ts)
|
|
2549
|
+
if ts_f != ts_f or ts_f in (float("inf"), float("-inf")):
|
|
2550
|
+
continue
|
|
2551
|
+
age = now - ts_f
|
|
2552
|
+
if 0 <= age < TASK_EMIT_TTL_S:
|
|
2553
|
+
pruned.append({"subject_hash": sh[:12], "ts": ts_f})
|
|
2554
|
+
# Bound state size — LRU keep most-recent.
|
|
2555
|
+
if len(pruned) > TASK_EMIT_STATE_MAX_ENTRIES:
|
|
2556
|
+
pruned.sort(key=lambda e: e["ts"], reverse=True)
|
|
2557
|
+
pruned = pruned[:TASK_EMIT_STATE_MAX_ENTRIES]
|
|
2558
|
+
return {"entries": pruned}
|
|
2559
|
+
|
|
2560
|
+
|
|
2561
|
+
def _save_task_emit_state(path: Path, state: Dict[str, Any]) -> None:
|
|
2562
|
+
"""Persist state atomically via temp-file + rename. Fail-open.
|
|
2563
|
+
|
|
2564
|
+
Codex CDX-W5-P1-03 closure: `os.replace` is atomic but not
|
|
2565
|
+
crash-durable on macOS — if the box loses power between the rename
|
|
2566
|
+
and the buffer flush, the dedup record is lost. We `fsync(tmp_fd)`
|
|
2567
|
+
before the rename and best-effort `fsync` the parent directory after.
|
|
2568
|
+
Both fsyncs are wrapped — fsync failure must NOT block the user
|
|
2569
|
+
session (the dedup is advisory; over-emitting once on crash is
|
|
2570
|
+
acceptable, lost-update is not).
|
|
2571
|
+
"""
|
|
2572
|
+
try:
|
|
2573
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
2574
|
+
except OSError:
|
|
2575
|
+
return
|
|
2576
|
+
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
2577
|
+
payload = json.dumps(state, separators=(",", ":")).encode("utf-8")
|
|
2578
|
+
try:
|
|
2579
|
+
# Write + fsync the data file before atomic rename.
|
|
2580
|
+
fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
|
|
2581
|
+
try:
|
|
2582
|
+
os.write(fd, payload)
|
|
2583
|
+
try:
|
|
2584
|
+
os.fsync(fd)
|
|
2585
|
+
except OSError: # pragma: no cover — fsync best-effort
|
|
2586
|
+
pass
|
|
2587
|
+
finally:
|
|
2588
|
+
os.close(fd)
|
|
2589
|
+
os.replace(str(tmp), str(path))
|
|
2590
|
+
# Best-effort fsync of the parent directory so the rename
|
|
2591
|
+
# itself is durable. POSIX-only; NotImplementedError on win.
|
|
2592
|
+
try:
|
|
2593
|
+
dir_fd = os.open(str(path.parent), os.O_RDONLY)
|
|
2594
|
+
try:
|
|
2595
|
+
os.fsync(dir_fd)
|
|
2596
|
+
except OSError: # pragma: no cover
|
|
2597
|
+
pass
|
|
2598
|
+
finally:
|
|
2599
|
+
os.close(dir_fd)
|
|
2600
|
+
except OSError: # pragma: no cover — directory fsync optional
|
|
2601
|
+
pass
|
|
2602
|
+
except OSError:
|
|
2603
|
+
# Best-effort cleanup of the tmp file
|
|
2604
|
+
try:
|
|
2605
|
+
if tmp.exists():
|
|
2606
|
+
tmp.unlink()
|
|
2607
|
+
except OSError: # pragma: no cover
|
|
2608
|
+
pass
|
|
2609
|
+
|
|
2610
|
+
|
|
2611
|
+
def _is_subject_recent(state: Dict[str, Any], subject_hash: str) -> bool:
|
|
2612
|
+
"""Return True if `subject_hash` is in state within TTL (already pruned by load)."""
|
|
2613
|
+
for entry in state.get("entries", []):
|
|
2614
|
+
if isinstance(entry, dict) and entry.get("subject_hash") == subject_hash:
|
|
2615
|
+
return True
|
|
2616
|
+
return False
|
|
2617
|
+
|
|
2618
|
+
|
|
2619
|
+
# Codex CDX-W5-P1-05 closure: collapse interior whitespace in a marker
|
|
2620
|
+
# subject so the `Subject:` line stays single-line. `_sanitize_for_recs`
|
|
2621
|
+
# strips angle brackets + backticks but preserves `\n` / `\t` / multi-
|
|
2622
|
+
# space, which can ambiguate the `<!-- /TASKCREATE-CANDIDATE -->`
|
|
2623
|
+
# closing marker if a recommendation summary contains a literal newline.
|
|
2624
|
+
# Bound to 200 chars (parity with `_sanitize_for_recs` length cap).
|
|
2625
|
+
def _collapse_marker_subject(text: str) -> str:
|
|
2626
|
+
"""Single-line, length-bounded subject for `Subject:` marker line.
|
|
2627
|
+
|
|
2628
|
+
Python `re.sub(r"\\s+", " ", ...)` on a `str` matches Unicode
|
|
2629
|
+
whitespace (NBSP / em-space / narrow NBSP / line-tab / vertical-tab /
|
|
2630
|
+
form-feed in addition to ASCII), so the collapse is locale-safe.
|
|
2631
|
+
"""
|
|
2632
|
+
if not isinstance(text, str):
|
|
2633
|
+
text = str(text)
|
|
2634
|
+
# Replace ALL whitespace runs (Unicode-aware) with a single space.
|
|
2635
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
2636
|
+
return text[:200]
|
|
2637
|
+
|
|
2638
|
+
|
|
2639
|
+
def _emit_task_candidate_safe(
|
|
2640
|
+
*,
|
|
2641
|
+
rank: int,
|
|
2642
|
+
severity: str,
|
|
2643
|
+
subject_hash: str,
|
|
2644
|
+
awaiting_confirm: bool = False,
|
|
2645
|
+
) -> None:
|
|
2646
|
+
"""Wire-up to audit_emit.emit_ceo_boot_task_candidate_emitted. Fail-open.
|
|
2647
|
+
|
|
2648
|
+
Pre-canonical-ceremony: hasattr() returns False, function is a no-op.
|
|
2649
|
+
Post-ceremony: emits the telemetry event with Sec MF-3 field allowlist
|
|
2650
|
+
enforced on the emit side (subject text NEVER leaves this script).
|
|
2651
|
+
"""
|
|
2652
|
+
if _audit_emit is None:
|
|
2653
|
+
return
|
|
2654
|
+
fn = getattr(_audit_emit, "emit_ceo_boot_task_candidate_emitted", None)
|
|
2655
|
+
if not callable(fn):
|
|
2656
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
2657
|
+
sys.stderr.write(
|
|
2658
|
+
"# ceo-boot: audit_emit.emit_ceo_boot_task_candidate_emitted "
|
|
2659
|
+
"not registered (canonical ceremony pending)\n"
|
|
2660
|
+
)
|
|
2661
|
+
return
|
|
2662
|
+
try:
|
|
2663
|
+
fn(
|
|
2664
|
+
session_id=_ceo_boot_session_id(),
|
|
2665
|
+
rank=int(rank),
|
|
2666
|
+
severity=str(severity),
|
|
2667
|
+
subject_hash=str(subject_hash),
|
|
2668
|
+
awaiting_confirm=bool(awaiting_confirm),
|
|
2669
|
+
)
|
|
2670
|
+
except Exception: # noqa: BLE001 — fail-open per audit_emit contract
|
|
2671
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
2672
|
+
import traceback
|
|
2673
|
+
traceback.print_exc(file=sys.stderr)
|
|
2674
|
+
|
|
2675
|
+
|
|
2676
|
+
def _emit_task_candidate_markers(
|
|
2677
|
+
results: List[CheckResult],
|
|
2678
|
+
*,
|
|
2679
|
+
gate_pass: bool,
|
|
2680
|
+
short: bool,
|
|
2681
|
+
cached: bool,
|
|
2682
|
+
) -> List[Dict[str, Any]]:
|
|
2683
|
+
"""Write `<!-- TASKCREATE-CANDIDATE -->` blocks to stdout for top-3 recs.
|
|
2684
|
+
|
|
2685
|
+
Layer A of PLAN-078 Wave 5. Bypass paths (return [] without emit):
|
|
2686
|
+
* `gate_pass` is True (no actionable failure)
|
|
2687
|
+
* `short` mode (≤2s budget — skip per Perf table)
|
|
2688
|
+
* `cached` mode (handled by uncached path on next non-cached boot)
|
|
2689
|
+
* Env `CEO_BOOT_AUTO_TASK=0` (operator opt-out)
|
|
2690
|
+
* No medium/high recommendations after dedup
|
|
2691
|
+
|
|
2692
|
+
Returns the list of marker payloads emitted (used by tests + future
|
|
2693
|
+
JSON renderer). Each payload carries `rank`, `severity`,
|
|
2694
|
+
`subject_hash`, `subject` (not persisted — only stdout), and
|
|
2695
|
+
`awaiting_confirm`.
|
|
2696
|
+
|
|
2697
|
+
Sec MF-3 closure: `subject` text passes through `_sanitize_for_recs`
|
|
2698
|
+
(already applied by `_recommendations_with_severity` callee) before
|
|
2699
|
+
rendering; only the 12-hex `subject_hash` is persisted to the
|
|
2700
|
+
audit-log + dedup state. Raw stderr / check detail NEVER appears in
|
|
2701
|
+
the marker block.
|
|
2702
|
+
"""
|
|
2703
|
+
if gate_pass:
|
|
2704
|
+
return []
|
|
2705
|
+
if short or cached:
|
|
2706
|
+
return []
|
|
2707
|
+
if os.environ.get("CEO_BOOT_AUTO_TASK") == "0":
|
|
2708
|
+
return []
|
|
2709
|
+
|
|
2710
|
+
triples = _recommendations_with_severity(results)
|
|
2711
|
+
# Codex CDX-W5-P1-01 closure: do NOT pre-slice to TASK_EMIT_TOP_N
|
|
2712
|
+
# before dedup. Iterate the full medium+/high actionable list and
|
|
2713
|
+
# break only after we've emitted TOP_N markers — otherwise three
|
|
2714
|
+
# already-deduped subjects at the head of the list would silently
|
|
2715
|
+
# block any 4th candidate from ever surfacing.
|
|
2716
|
+
actionable = [(t, s) for (_, t, s) in triples if s in ("medium", "high")]
|
|
2717
|
+
if not actionable:
|
|
2718
|
+
return []
|
|
2719
|
+
|
|
2720
|
+
state_path = _task_emit_state_path()
|
|
2721
|
+
lock_path = state_path.with_suffix(state_path.suffix + ".lock")
|
|
2722
|
+
|
|
2723
|
+
# Acquire filelock for read-modify-write of dedup state. Codex
|
|
2724
|
+
# CDX-W5-P1-02 closure: on FileLockTimeout we still emit markers
|
|
2725
|
+
# (fail-open — better to over-task once than silently drop) but we
|
|
2726
|
+
# do NOT persist the new state. Persisting unlocked state can
|
|
2727
|
+
# clobber a sibling process that just acquired the lock and wrote
|
|
2728
|
+
# different entries (lost-update). Operator pays the price of one
|
|
2729
|
+
# duplicate marker on the next boot in exchange for not corrupting
|
|
2730
|
+
# the audit-bookkeeping channel.
|
|
2731
|
+
#
|
|
2732
|
+
# Codex CDX-W5-iter3-P1 closure: any exception during lock acquisition
|
|
2733
|
+
# (OSError on bad path, PermissionError, NotImplementedError on
|
|
2734
|
+
# non-POSIX, etc.) used to fall through the OUTER except and silently
|
|
2735
|
+
# suppress every marker. We now narrow the lock-acquire try/except to
|
|
2736
|
+
# just lock setup; the marker-emit loop runs unconditionally with
|
|
2737
|
+
# `lock_acquired = False` if anything went wrong.
|
|
2738
|
+
emitted: List[Dict[str, Any]] = []
|
|
2739
|
+
rank = 0
|
|
2740
|
+
lock_acquired = False
|
|
2741
|
+
state: Dict[str, Any] = {"entries": []}
|
|
2742
|
+
lock_ctx = None
|
|
2743
|
+
|
|
2744
|
+
# --- Phase 1: try to acquire the lock + load state ---
|
|
2745
|
+
try:
|
|
2746
|
+
try:
|
|
2747
|
+
from _lib.filelock import FileLock, FileLockTimeout
|
|
2748
|
+
except Exception: # noqa: BLE001 — pre-canonical or import-broken
|
|
2749
|
+
FileLock = None # type: ignore[assignment]
|
|
2750
|
+
FileLockTimeout = Exception # type: ignore[assignment]
|
|
2751
|
+
|
|
2752
|
+
if FileLock is None:
|
|
2753
|
+
# No filelock available (pre-canonical / non-POSIX). Read
|
|
2754
|
+
# state opportunistically; allow persistence (best-effort).
|
|
2755
|
+
state = _load_task_emit_state(state_path)
|
|
2756
|
+
lock_acquired = True # treat as "owned" for save semantics
|
|
2757
|
+
else:
|
|
2758
|
+
try:
|
|
2759
|
+
lock_ctx = FileLock(str(lock_path), timeout=2.5)
|
|
2760
|
+
lock_ctx.__enter__()
|
|
2761
|
+
state = _load_task_emit_state(state_path)
|
|
2762
|
+
lock_acquired = True
|
|
2763
|
+
except FileLockTimeout:
|
|
2764
|
+
# Lock contended — emit unlocked, skip persist.
|
|
2765
|
+
state = _load_task_emit_state(state_path)
|
|
2766
|
+
lock_ctx = None
|
|
2767
|
+
lock_acquired = False
|
|
2768
|
+
except Exception: # noqa: BLE001 — invalid path, perm err, etc.
|
|
2769
|
+
# Any other error during lock acquisition — emit
|
|
2770
|
+
# unlocked, skip persist. Empty state means we may
|
|
2771
|
+
# over-emit (no dedup), but that's better than silent
|
|
2772
|
+
# suppression.
|
|
2773
|
+
state = {"entries": []}
|
|
2774
|
+
lock_ctx = None
|
|
2775
|
+
lock_acquired = False
|
|
2776
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
2777
|
+
import traceback
|
|
2778
|
+
traceback.print_exc(file=sys.stderr)
|
|
2779
|
+
except Exception: # noqa: BLE001 — never let phase-1 abort markers
|
|
2780
|
+
state = {"entries": []}
|
|
2781
|
+
lock_ctx = None
|
|
2782
|
+
lock_acquired = False
|
|
2783
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
2784
|
+
import traceback
|
|
2785
|
+
traceback.print_exc(file=sys.stderr)
|
|
2786
|
+
|
|
2787
|
+
# --- Phase 2: emit markers + persist (always runs, even after
|
|
2788
|
+
# phase-1 failure). Wrapped in its own try/except so any state-file
|
|
2789
|
+
# bug NEVER blocks the user session. ---
|
|
2790
|
+
try:
|
|
2791
|
+
now = time.time()
|
|
2792
|
+
for text, severity in actionable:
|
|
2793
|
+
# Codex CDX-W5-P1-05 closure: collapse interior whitespace
|
|
2794
|
+
# so the `Subject:` line stays single-line — newlines in a
|
|
2795
|
+
# recommendation summary would otherwise ambiguate the
|
|
2796
|
+
# closing marker for the orchestrator parser.
|
|
2797
|
+
safe_subject = _collapse_marker_subject(text)
|
|
2798
|
+
# Codex CDX-W5-iter2-P1 closure: hash the COLLAPSED subject
|
|
2799
|
+
# (the bytes the orchestrator actually parses + re-hashes
|
|
2800
|
+
# for dedup against the live task list). Hashing the raw
|
|
2801
|
+
# pre-collapse text would break the contract documented in
|
|
2802
|
+
# `commands/ceo-boot.md:Step 4.5` where the orchestrator
|
|
2803
|
+
# computes `sha256(NFKC(visible Subject))[:12]`.
|
|
2804
|
+
sh = _subject_hash(safe_subject)
|
|
2805
|
+
if _is_subject_recent(state, sh):
|
|
2806
|
+
continue
|
|
2807
|
+
rank += 1
|
|
2808
|
+
payload = {
|
|
2809
|
+
"rank": rank,
|
|
2810
|
+
"severity": severity,
|
|
2811
|
+
"subject_hash": sh,
|
|
2812
|
+
"subject": safe_subject,
|
|
2813
|
+
"awaiting_confirm": False,
|
|
2814
|
+
}
|
|
2815
|
+
sys.stdout.write(
|
|
2816
|
+
f"\n<!-- TASKCREATE-CANDIDATE rank={rank} "
|
|
2817
|
+
f"severity={severity} awaiting_confirm=false -->\n"
|
|
2818
|
+
)
|
|
2819
|
+
sys.stdout.write(f"Subject: {safe_subject}\n")
|
|
2820
|
+
sys.stdout.write("<!-- /TASKCREATE-CANDIDATE -->\n")
|
|
2821
|
+
state["entries"].append({"subject_hash": sh, "ts": now})
|
|
2822
|
+
emitted.append(payload)
|
|
2823
|
+
_emit_task_candidate_safe(
|
|
2824
|
+
rank=rank,
|
|
2825
|
+
severity=severity,
|
|
2826
|
+
subject_hash=sh,
|
|
2827
|
+
awaiting_confirm=False,
|
|
2828
|
+
)
|
|
2829
|
+
if rank >= TASK_EMIT_TOP_N:
|
|
2830
|
+
break
|
|
2831
|
+
# Codex CDX-W5-iter3 P2: keep state size bound after the post-load
|
|
2832
|
+
# append (load trims to MAX_ENTRIES, but we just added up to TOP_N
|
|
2833
|
+
# entries on top — re-cap before save so persisted state never
|
|
2834
|
+
# exceeds the documented MAX). LRU keep most-recent.
|
|
2835
|
+
entries = state.get("entries", [])
|
|
2836
|
+
if isinstance(entries, list) and len(entries) > TASK_EMIT_STATE_MAX_ENTRIES:
|
|
2837
|
+
entries.sort(key=lambda e: e.get("ts", 0), reverse=True)
|
|
2838
|
+
state["entries"] = entries[:TASK_EMIT_STATE_MAX_ENTRIES]
|
|
2839
|
+
if emitted and lock_acquired:
|
|
2840
|
+
_save_task_emit_state(state_path, state)
|
|
2841
|
+
except Exception: # noqa: BLE001 — fail-open: a state-file bug must
|
|
2842
|
+
# NEVER block the user session
|
|
2843
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
2844
|
+
import traceback
|
|
2845
|
+
traceback.print_exc(file=sys.stderr)
|
|
2846
|
+
finally:
|
|
2847
|
+
if lock_ctx is not None:
|
|
2848
|
+
try:
|
|
2849
|
+
lock_ctx.__exit__(None, None, None)
|
|
2850
|
+
except Exception: # pragma: no cover — fail-open
|
|
2851
|
+
pass
|
|
2852
|
+
return emitted
|
|
2853
|
+
|
|
2854
|
+
|
|
2855
|
+
# === END PLAN-078 Wave 5 marker emit + dedup ===============================
|
|
2856
|
+
|
|
2857
|
+
|
|
2858
|
+
# ---- Main ------------------------------------------------------------------
|
|
2859
|
+
|
|
2860
|
+
# === PLAN-134 W4 — Morning Ledger renderer ================================
|
|
2861
|
+
# Renders the proposal-queue ledger (sign / don't sign / why, founder
|
|
2862
|
+
# language) as an extra default-mode section — same pattern as the Wave 5
|
|
2863
|
+
# TASKCREATE markers: NOT a Tier-S check (the registry is pinned at 20),
|
|
2864
|
+
# never affects gate_pass, fail-open on any error. Fast mode only
|
|
2865
|
+
# (manifest-level Merkle re-derivation); byte-level verification belongs to
|
|
2866
|
+
# morning-ceremony.py. Kill switch: CEO_BOOT_LEDGER=0.
|
|
2867
|
+
def _render_morning_ledger_safe() -> str:
|
|
2868
|
+
if os.environ.get("CEO_BOOT_LEDGER", "1") == "0":
|
|
2869
|
+
return ""
|
|
2870
|
+
try:
|
|
2871
|
+
import importlib.util as _ilu
|
|
2872
|
+
_ml_path = Path(__file__).resolve().parent / "morning_ledger.py"
|
|
2873
|
+
if not _ml_path.is_file():
|
|
2874
|
+
return ""
|
|
2875
|
+
_ml = sys.modules.get("morning_ledger")
|
|
2876
|
+
if _ml is None:
|
|
2877
|
+
_spec = _ilu.spec_from_file_location("morning_ledger", _ml_path)
|
|
2878
|
+
_ml = _ilu.module_from_spec(_spec)
|
|
2879
|
+
# py3.9 dataclasses + `from __future__ import annotations`
|
|
2880
|
+
# resolve field types via sys.modules[cls.__module__] — the
|
|
2881
|
+
# module MUST be registered before exec_module.
|
|
2882
|
+
sys.modules["morning_ledger"] = _ml
|
|
2883
|
+
_spec.loader.exec_module(_ml) # type: ignore[union-attr]
|
|
2884
|
+
if not _ml.pending_bundles():
|
|
2885
|
+
return ""
|
|
2886
|
+
rendered = _ml.render_ledger(deep=False)
|
|
2887
|
+
# Defense-in-depth: ledger text is disk-sourced — pass each line
|
|
2888
|
+
# through the same sanitizer the recommendations use (Sec MF-4).
|
|
2889
|
+
safe_lines = [_sanitize_for_recs(ln) if ln.strip() else ln for ln in rendered.splitlines()]
|
|
2890
|
+
return "\n" + "\n".join(safe_lines) + "\n"
|
|
2891
|
+
except Exception: # noqa: BLE001 — advisory section, never block boot
|
|
2892
|
+
if os.environ.get("CEO_BOOT_DEBUG") == "1":
|
|
2893
|
+
import traceback
|
|
2894
|
+
traceback.print_exc(file=sys.stderr)
|
|
2895
|
+
return ""
|
|
2896
|
+
|
|
2897
|
+
|
|
2898
|
+
def main(argv: List[str]) -> int:
|
|
2899
|
+
parser = argparse.ArgumentParser(description="ceo-boot session-boot autopilot")
|
|
2900
|
+
parser.add_argument("--short", action="store_true", help="terse output (≤15 lines target)")
|
|
2901
|
+
parser.add_argument("--cached", action="store_true", help="prefer cache-hit (≤200ms budget)")
|
|
2902
|
+
parser.add_argument("--bench", action="store_true", help="run N=5 bench harness (markdown table)")
|
|
2903
|
+
parser.add_argument("--bench-n", type=int, default=5, help="bench N runs (default 5)")
|
|
2904
|
+
parser.add_argument("--bench-json", action="store_true", help="emit bench report as JSON instead of markdown")
|
|
2905
|
+
parser.add_argument("--json", action="store_true", help="emit machine-readable JSON digest")
|
|
2906
|
+
parser.add_argument("--verbose", action="store_true", help="include 10 Tier-A checks (~10s budget)")
|
|
2907
|
+
args = parser.parse_args(argv)
|
|
2908
|
+
|
|
2909
|
+
# Codex S82 P1 fix: --short defaults to cached path per spec
|
|
2910
|
+
# (.claude/commands/ceo-boot.md:12 "--short defaults cached mode").
|
|
2911
|
+
# Was running full dispatch ignoring cache.
|
|
2912
|
+
if args.short and not args.cached:
|
|
2913
|
+
args.cached = True
|
|
2914
|
+
|
|
2915
|
+
if args.bench:
|
|
2916
|
+
report = bench(args.bench_n, include_tier_a=args.verbose)
|
|
2917
|
+
if args.bench_json or args.json:
|
|
2918
|
+
sys.stdout.write(json.dumps(report, indent=2))
|
|
2919
|
+
sys.stdout.write("\n")
|
|
2920
|
+
else:
|
|
2921
|
+
sys.stdout.write(render_bench_markdown(report))
|
|
2922
|
+
return 0
|
|
2923
|
+
|
|
2924
|
+
t0 = time.perf_counter()
|
|
2925
|
+
|
|
2926
|
+
if args.cached:
|
|
2927
|
+
hit, payload = cached_load()
|
|
2928
|
+
elapsed = (time.perf_counter() - t0) * 1000
|
|
2929
|
+
if hit:
|
|
2930
|
+
if args.json:
|
|
2931
|
+
sys.stdout.write(json.dumps(payload, indent=2))
|
|
2932
|
+
else:
|
|
2933
|
+
sys.stdout.write(f"\n## /ceo-boot --cached HIT ({elapsed:.0f} ms)\n")
|
|
2934
|
+
for r in payload["results"]:
|
|
2935
|
+
sys.stdout.write(f"- {r['name']}: {r['status']} — {r['summary']}\n")
|
|
2936
|
+
# PLAN-065 Phase 2 wire — cache-hit path. Replay the cached
|
|
2937
|
+
# gate_pass/checks_total summary so adopter telemetry counts
|
|
2938
|
+
# cached invocations (Reality-Ledger fixture #4 closure).
|
|
2939
|
+
cached_failed = sum(
|
|
2940
|
+
1 for r in payload.get("results", [])
|
|
2941
|
+
if r.get("status") in ("red", "error", "timeout")
|
|
2942
|
+
)
|
|
2943
|
+
cached_total = len(payload.get("results", []))
|
|
2944
|
+
_emit_ceo_boot_emitted_safe(
|
|
2945
|
+
gate_pass=(cached_failed == 0),
|
|
2946
|
+
duration_ms=int(elapsed),
|
|
2947
|
+
checks_total=cached_total,
|
|
2948
|
+
checks_failed=cached_failed,
|
|
2949
|
+
cache_hit=True,
|
|
2950
|
+
)
|
|
2951
|
+
return 0
|
|
2952
|
+
else:
|
|
2953
|
+
sys.stderr.write(f"# cache-miss ({elapsed:.0f} ms) — falling back to full digest\n")
|
|
2954
|
+
|
|
2955
|
+
results = dispatch_parallel(include_tier_a=args.verbose)
|
|
2956
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
2957
|
+
|
|
2958
|
+
cached_store(results)
|
|
2959
|
+
|
|
2960
|
+
# Aggregate gate semantics: gate_pass = no red/error/timeout
|
|
2961
|
+
failed = sum(1 for r in results if r.status in ("red", "error", "timeout"))
|
|
2962
|
+
gate_pass = (failed == 0)
|
|
2963
|
+
|
|
2964
|
+
if args.json:
|
|
2965
|
+
out = {
|
|
2966
|
+
"elapsed_ms": elapsed_ms,
|
|
2967
|
+
"gate_pass": gate_pass,
|
|
2968
|
+
"checks_total": len(results),
|
|
2969
|
+
"checks_failed": failed,
|
|
2970
|
+
"recommendations": _make_recommendations(results),
|
|
2971
|
+
"results": [
|
|
2972
|
+
{"name": r.name, "status": r.status, "summary": r.summary, "duration_ms": r.duration_ms}
|
|
2973
|
+
for r in results
|
|
2974
|
+
],
|
|
2975
|
+
}
|
|
2976
|
+
sys.stdout.write(json.dumps(out, indent=2))
|
|
2977
|
+
sys.stdout.write("\n")
|
|
2978
|
+
else:
|
|
2979
|
+
sys.stdout.write(render_digest(results, short=args.short))
|
|
2980
|
+
sys.stdout.write(f"\nWall-clock: {elapsed_ms:.0f} ms (gate_pass={gate_pass}, failed={failed}/{len(results)})\n")
|
|
2981
|
+
# PLAN-134 W4 — Morning Ledger section (default full mode only;
|
|
2982
|
+
# --short keeps its 5-line budget). Empty string when queue is
|
|
2983
|
+
# empty, module missing, or CEO_BOOT_LEDGER=0.
|
|
2984
|
+
if not args.short:
|
|
2985
|
+
sys.stdout.write(_render_morning_ledger_safe())
|
|
2986
|
+
|
|
2987
|
+
# PLAN-078 Wave 5 — TaskCreate-candidate markers. Bypass paths handled
|
|
2988
|
+
# inside `_emit_task_candidate_markers`: gate_pass=True, --short,
|
|
2989
|
+
# --cached, env CEO_BOOT_AUTO_TASK=0, no medium+/high recs after
|
|
2990
|
+
# 24h-TTL dedup. Markers go to stdout (parsed by Claude orchestrator
|
|
2991
|
+
# running /ceo-boot per `commands/ceo-boot.md` workflow). Audit emit
|
|
2992
|
+
# of `ceo_boot_task_candidate_emitted` is invoked per-marker via
|
|
2993
|
+
# `_emit_task_candidate_safe` (hasattr-guarded pre-canonical-ceremony).
|
|
2994
|
+
# JSON mode skips marker emit so machine consumers see only the JSON
|
|
2995
|
+
# payload; switch to default markdown mode to surface markers.
|
|
2996
|
+
if not args.json:
|
|
2997
|
+
_emit_task_candidate_markers(
|
|
2998
|
+
results,
|
|
2999
|
+
gate_pass=gate_pass,
|
|
3000
|
+
short=args.short,
|
|
3001
|
+
cached=args.cached,
|
|
3002
|
+
)
|
|
3003
|
+
|
|
3004
|
+
# PLAN-065 Phase 2 wire — uncached path. Emits gate_pass + counts
|
|
3005
|
+
# only (Sec MF-3 field allowlist denies tokens/cost/paths/prompt/SKILL/env).
|
|
3006
|
+
_emit_ceo_boot_emitted_safe(
|
|
3007
|
+
gate_pass=gate_pass,
|
|
3008
|
+
duration_ms=int(elapsed_ms),
|
|
3009
|
+
checks_total=len(results),
|
|
3010
|
+
checks_failed=failed,
|
|
3011
|
+
cache_hit=False,
|
|
3012
|
+
)
|
|
3013
|
+
return 0
|
|
3014
|
+
|
|
3015
|
+
|
|
3016
|
+
if __name__ == "__main__":
|
|
3017
|
+
sys.exit(main(sys.argv[1:]))
|