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,3333 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""audit-query — query the ceo-orchestration agent spawn audit log.
|
|
3
|
+
|
|
4
|
+
Stdlib-only CLI that reads `audit-log.jsonl` (+ rotated siblings) and
|
|
5
|
+
answers common operator questions:
|
|
6
|
+
|
|
7
|
+
audit-query.py summary
|
|
8
|
+
audit-query.py by-skill [--top N]
|
|
9
|
+
audit-query.py compliance
|
|
10
|
+
audit-query.py by-day [--days N]
|
|
11
|
+
audit-query.py search <regex>
|
|
12
|
+
audit-query.py since <ISO-date>
|
|
13
|
+
audit-query.py errors
|
|
14
|
+
audit-query.py stats
|
|
15
|
+
audit-query.py export [--format csv|json|tsv]
|
|
16
|
+
audit-query.py by-domain [--window=30d|--start=YYYY-MM-DD --end=YYYY-MM-DD]
|
|
17
|
+
[--check-reopen]
|
|
18
|
+
|
|
19
|
+
Default input path:
|
|
20
|
+
${CEO_AUDIT_LOG_PATH:-$HOME/.claude/projects/ceo-orchestration/audit-log.jsonl}
|
|
21
|
+
|
|
22
|
+
Pass `--log <path>` to override or `--include-rotated` to aggregate
|
|
23
|
+
across all `audit-log*.jsonl` in the audit directory.
|
|
24
|
+
|
|
25
|
+
Output formats:
|
|
26
|
+
- Default: human-readable tab-separated table
|
|
27
|
+
- `--json` flag: machine-readable JSON
|
|
28
|
+
- `--csv` flag: CSV
|
|
29
|
+
|
|
30
|
+
Exit codes:
|
|
31
|
+
0 — success (including empty result set)
|
|
32
|
+
1 — log file missing OR bad query
|
|
33
|
+
2 — unreadable log file (permissions, etc.)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import argparse
|
|
39
|
+
import csv
|
|
40
|
+
import io
|
|
41
|
+
import json
|
|
42
|
+
import os
|
|
43
|
+
import re
|
|
44
|
+
import sys
|
|
45
|
+
from collections import Counter, defaultdict
|
|
46
|
+
from datetime import datetime, timedelta, timezone
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Path resolution (mirrors audit_log.py conventions)
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def default_log_path() -> Path:
|
|
57
|
+
"""Return the conventional audit log path from env vars / defaults."""
|
|
58
|
+
home = Path(os.environ.get("HOME") or str(Path.home()))
|
|
59
|
+
default_dir = home / ".claude" / "projects" / "ceo-orchestration"
|
|
60
|
+
return Path(
|
|
61
|
+
os.environ.get("CEO_AUDIT_LOG_PATH") or str(default_dir / "audit-log.jsonl")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def default_errors_path() -> Path:
|
|
66
|
+
home = Path(os.environ.get("HOME") or str(Path.home()))
|
|
67
|
+
default_dir = home / ".claude" / "projects" / "ceo-orchestration"
|
|
68
|
+
return Path(
|
|
69
|
+
os.environ.get("CEO_AUDIT_LOG_ERR") or str(default_dir / "audit-log.errors")
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def discover_logs(primary: Path, include_rotated: bool) -> List[Path]:
|
|
74
|
+
"""Return the list of log files to read, sorted by modification time."""
|
|
75
|
+
if not include_rotated:
|
|
76
|
+
return [primary] if primary.is_file() else []
|
|
77
|
+
if not primary.parent.is_dir():
|
|
78
|
+
return []
|
|
79
|
+
stem = primary.stem # "audit-log"
|
|
80
|
+
siblings = []
|
|
81
|
+
for candidate in primary.parent.glob(f"{stem}*.jsonl"):
|
|
82
|
+
if candidate.is_file():
|
|
83
|
+
siblings.append(candidate)
|
|
84
|
+
# Sort: primary file last so its entries are newest in the stream
|
|
85
|
+
siblings.sort(key=lambda p: p.stat().st_mtime)
|
|
86
|
+
return siblings
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Streaming reader — tolerates malformed lines with a stderr breadcrumb
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Perf-P1-002 — large-log materialization warning threshold.
|
|
96
|
+
#
|
|
97
|
+
# Subcommands that need FULL context (median, percentile, debate grouping,
|
|
98
|
+
# weekly-summary) still materialize the list. At this size threshold we
|
|
99
|
+
# emit a stderr hint so operators learn the log grew past the streaming
|
|
100
|
+
# regime and consider `--include-rotated=false` or log rotation.
|
|
101
|
+
# Value = 100_000 entries ≈ ~40-80 MB typical ≈ ~500-900ms parse on a
|
|
102
|
+
# modern laptop. Under this bar the materialization is unnoticeable.
|
|
103
|
+
_MATERIALIZATION_WARN_THRESHOLD = 100_000
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def read_entries(
|
|
107
|
+
paths: Iterable[Path],
|
|
108
|
+
*,
|
|
109
|
+
warn_stream=None,
|
|
110
|
+
) -> Iterator[Dict[str, Any]]:
|
|
111
|
+
"""Yield parsed JSON entries from the given log paths, streaming.
|
|
112
|
+
|
|
113
|
+
Malformed lines are skipped with a breadcrumb to warn_stream.
|
|
114
|
+
Large logs are read line-by-line so memory stays constant.
|
|
115
|
+
|
|
116
|
+
Note: warn_stream defaults to None → resolved to sys.stderr at call
|
|
117
|
+
time, not definition time, so test harness stderr redirection works.
|
|
118
|
+
"""
|
|
119
|
+
if warn_stream is None:
|
|
120
|
+
warn_stream = sys.stderr
|
|
121
|
+
for path in paths:
|
|
122
|
+
try:
|
|
123
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
124
|
+
for lineno, line in enumerate(f, start=1):
|
|
125
|
+
line = line.strip()
|
|
126
|
+
if not line:
|
|
127
|
+
continue
|
|
128
|
+
try:
|
|
129
|
+
entry = json.loads(line)
|
|
130
|
+
except json.JSONDecodeError as e:
|
|
131
|
+
print(
|
|
132
|
+
f"[audit-query] WARN: {path}:{lineno}: "
|
|
133
|
+
f"skipping malformed JSONL ({e.msg})",
|
|
134
|
+
file=warn_stream,
|
|
135
|
+
)
|
|
136
|
+
continue
|
|
137
|
+
if not isinstance(entry, dict):
|
|
138
|
+
continue
|
|
139
|
+
yield entry
|
|
140
|
+
except OSError as e:
|
|
141
|
+
print(
|
|
142
|
+
f"[audit-query] WARN: cannot read {path}: {e}",
|
|
143
|
+
file=warn_stream,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# Sub-command implementations
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def cmd_summary(entries, args) -> Dict[str, Any]:
|
|
153
|
+
"""Summary aggregates — single-pass streaming (Perf-P1-002).
|
|
154
|
+
|
|
155
|
+
Accepts any iterable of entries. Computes total / first_ts / last_ts /
|
|
156
|
+
top 5 skills / compliant count in one pass so a 100k-row log does not
|
|
157
|
+
need to be fully materialized.
|
|
158
|
+
"""
|
|
159
|
+
total = 0
|
|
160
|
+
first_ts = ""
|
|
161
|
+
last_ts = ""
|
|
162
|
+
skill_counts: Counter = Counter()
|
|
163
|
+
compliant = 0
|
|
164
|
+
for e in entries:
|
|
165
|
+
total += 1
|
|
166
|
+
ts = e.get("ts", "")
|
|
167
|
+
if ts:
|
|
168
|
+
if not first_ts or ts < first_ts:
|
|
169
|
+
first_ts = ts
|
|
170
|
+
if ts > last_ts:
|
|
171
|
+
last_ts = ts
|
|
172
|
+
skill_counts[e.get("skill", "unknown")] += 1
|
|
173
|
+
if (
|
|
174
|
+
e.get("has_profile")
|
|
175
|
+
and e.get("has_file_assignment")
|
|
176
|
+
and e.get("skill") != "unknown"
|
|
177
|
+
):
|
|
178
|
+
compliant += 1
|
|
179
|
+
|
|
180
|
+
if total == 0:
|
|
181
|
+
return {
|
|
182
|
+
"total_spawns": 0,
|
|
183
|
+
"date_range": None,
|
|
184
|
+
"top_skills": [],
|
|
185
|
+
"compliance_rate": None,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
top_skills = skill_counts.most_common(5)
|
|
189
|
+
compliance_rate = compliant / total
|
|
190
|
+
return {
|
|
191
|
+
"total_spawns": total,
|
|
192
|
+
"date_range": {"from": first_ts, "to": last_ts},
|
|
193
|
+
"top_skills": [{"skill": s, "count": c} for s, c in top_skills],
|
|
194
|
+
"compliance_rate": round(compliance_rate, 3),
|
|
195
|
+
"compliant_spawns": compliant,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def cmd_by_skill(entries, args) -> List[Dict[str, Any]]:
|
|
200
|
+
"""Streamable — single-pass Counter aggregation (Perf-P1-002)."""
|
|
201
|
+
counts: Counter = Counter()
|
|
202
|
+
for e in entries:
|
|
203
|
+
counts[e.get("skill", "unknown")] += 1
|
|
204
|
+
top = counts.most_common(args.top)
|
|
205
|
+
return [{"skill": s, "count": c} for s, c in top]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def cmd_compliance(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
209
|
+
"""Handle the `audit-query compliance` sub-command — SOC2/LGPD evidence pack."""
|
|
210
|
+
total = len(entries)
|
|
211
|
+
if total == 0:
|
|
212
|
+
return {
|
|
213
|
+
"total": 0,
|
|
214
|
+
"has_profile_rate": None,
|
|
215
|
+
"has_file_assignment_rate": None,
|
|
216
|
+
"known_skill_rate": None,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
with_profile = sum(1 for e in entries if e.get("has_profile"))
|
|
220
|
+
with_fa = sum(1 for e in entries if e.get("has_file_assignment"))
|
|
221
|
+
with_skill = sum(1 for e in entries if e.get("skill") != "unknown")
|
|
222
|
+
|
|
223
|
+
non_compliant = [
|
|
224
|
+
{
|
|
225
|
+
"ts": e.get("ts"),
|
|
226
|
+
"skill": e.get("skill", "unknown"),
|
|
227
|
+
"has_profile": e.get("has_profile", False),
|
|
228
|
+
"has_file_assignment": e.get("has_file_assignment", False),
|
|
229
|
+
"desc_preview": e.get("desc_preview", ""),
|
|
230
|
+
}
|
|
231
|
+
for e in entries
|
|
232
|
+
if not (
|
|
233
|
+
e.get("has_profile")
|
|
234
|
+
and e.get("has_file_assignment")
|
|
235
|
+
and e.get("skill") != "unknown"
|
|
236
|
+
)
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
"total": total,
|
|
241
|
+
"has_profile": with_profile,
|
|
242
|
+
"has_profile_rate": round(with_profile / total, 3),
|
|
243
|
+
"has_file_assignment": with_fa,
|
|
244
|
+
"has_file_assignment_rate": round(with_fa / total, 3),
|
|
245
|
+
"known_skill": with_skill,
|
|
246
|
+
"known_skill_rate": round(with_skill / total, 3),
|
|
247
|
+
"non_compliant_count": len(non_compliant),
|
|
248
|
+
"non_compliant": non_compliant[:20], # cap preview
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def cmd_by_day(entries, args) -> List[Dict[str, Any]]:
|
|
253
|
+
"""Streamable — single-pass histogram (Perf-P1-002).
|
|
254
|
+
|
|
255
|
+
Fast-path: cutoff is compared as a fixed YYYY-MM-DD string (since our
|
|
256
|
+
`ts` format is lexicographically sortable). We only fall back to
|
|
257
|
+
``datetime.strptime`` when the raw timestamp doesn't match the
|
|
258
|
+
expected 20-char ISO-Z shape — a rare case (parse errors / rotated
|
|
259
|
+
schemas) that does not dominate 100k-entry runs.
|
|
260
|
+
"""
|
|
261
|
+
days = args.days
|
|
262
|
+
now = datetime.now(timezone.utc)
|
|
263
|
+
cutoff = now - timedelta(days=days)
|
|
264
|
+
cutoff_day = cutoff.strftime("%Y-%m-%d")
|
|
265
|
+
histogram: Dict[str, int] = defaultdict(int)
|
|
266
|
+
for e in entries:
|
|
267
|
+
ts = e.get("ts", "")
|
|
268
|
+
if not ts:
|
|
269
|
+
continue
|
|
270
|
+
# Fast path: YYYY-MM-DDTHH:MM:SSZ is 20 chars with day at 0..10
|
|
271
|
+
if len(ts) >= 20 and ts.endswith("Z") and ts[10:11] == "T":
|
|
272
|
+
day_key = ts[:10]
|
|
273
|
+
# Lexicographic compare works because YYYY-MM-DD is fixed width.
|
|
274
|
+
if day_key < cutoff_day:
|
|
275
|
+
continue
|
|
276
|
+
else:
|
|
277
|
+
try:
|
|
278
|
+
when = datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ").replace(
|
|
279
|
+
tzinfo=timezone.utc
|
|
280
|
+
)
|
|
281
|
+
except ValueError:
|
|
282
|
+
continue
|
|
283
|
+
if when < cutoff:
|
|
284
|
+
continue
|
|
285
|
+
day_key = when.strftime("%Y-%m-%d")
|
|
286
|
+
histogram[day_key] += 1
|
|
287
|
+
|
|
288
|
+
result = [
|
|
289
|
+
{"date": day, "count": count}
|
|
290
|
+
for day, count in sorted(histogram.items())
|
|
291
|
+
]
|
|
292
|
+
return result
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def cmd_search(entries, args) -> List[Dict[str, Any]]:
|
|
296
|
+
"""Streamable — filter via regex (Perf-P1-002)."""
|
|
297
|
+
try:
|
|
298
|
+
pattern = re.compile(args.regex, flags=re.IGNORECASE)
|
|
299
|
+
except re.error as e:
|
|
300
|
+
print(f"[audit-query] ERROR: bad regex: {e}", file=sys.stderr)
|
|
301
|
+
sys.exit(1)
|
|
302
|
+
matches = []
|
|
303
|
+
for e in entries:
|
|
304
|
+
preview = e.get("desc_preview", "")
|
|
305
|
+
if pattern.search(preview):
|
|
306
|
+
matches.append(
|
|
307
|
+
{
|
|
308
|
+
"ts": e.get("ts"),
|
|
309
|
+
"skill": e.get("skill"),
|
|
310
|
+
"desc_preview": preview,
|
|
311
|
+
"desc_hash": e.get("desc_hash"),
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
return matches
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def cmd_since(entries, args) -> List[Dict[str, Any]]:
|
|
318
|
+
"""Streamable — filter by date cutoff (Perf-P1-002)."""
|
|
319
|
+
# Parse the input date (supports YYYY-MM-DD, YYYY-MM-DDTHH:MM:SSZ)
|
|
320
|
+
raw = args.iso_date
|
|
321
|
+
parsed = None
|
|
322
|
+
for fmt in ("%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%d"):
|
|
323
|
+
try:
|
|
324
|
+
parsed = datetime.strptime(raw, fmt).replace(tzinfo=timezone.utc)
|
|
325
|
+
break
|
|
326
|
+
except ValueError:
|
|
327
|
+
continue
|
|
328
|
+
if parsed is None:
|
|
329
|
+
print(
|
|
330
|
+
f"[audit-query] ERROR: cannot parse date {raw!r} "
|
|
331
|
+
"(use YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ)",
|
|
332
|
+
file=sys.stderr,
|
|
333
|
+
)
|
|
334
|
+
sys.exit(1)
|
|
335
|
+
|
|
336
|
+
# Fast path: both parsed cutoff and entry ts are ISO-Z → string compare
|
|
337
|
+
# is equivalent to datetime compare (fixed-width YYYY-MM-DDTHH:MM:SSZ).
|
|
338
|
+
# Falls back to strptime for non-canonical ts shapes.
|
|
339
|
+
cutoff_str = parsed.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
340
|
+
out = []
|
|
341
|
+
for e in entries:
|
|
342
|
+
ts = e.get("ts", "")
|
|
343
|
+
if len(ts) >= 20 and ts.endswith("Z") and ts[10:11] == "T":
|
|
344
|
+
if ts >= cutoff_str:
|
|
345
|
+
out.append(e)
|
|
346
|
+
continue
|
|
347
|
+
try:
|
|
348
|
+
when = datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ").replace(
|
|
349
|
+
tzinfo=timezone.utc
|
|
350
|
+
)
|
|
351
|
+
except ValueError:
|
|
352
|
+
continue
|
|
353
|
+
if when >= parsed:
|
|
354
|
+
out.append(e)
|
|
355
|
+
return out
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def cmd_stats(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
359
|
+
"""Handle the `audit-query stats` sub-command — aggregate counts by action."""
|
|
360
|
+
# PLAN-125 WS-1 — per-tool-call lifecycle latency view (--tool-latency).
|
|
361
|
+
if getattr(args, "tool_latency", False):
|
|
362
|
+
return _tool_latency_stats(entries)
|
|
363
|
+
|
|
364
|
+
total = len(entries)
|
|
365
|
+
if total == 0:
|
|
366
|
+
return {"total": 0}
|
|
367
|
+
|
|
368
|
+
bucket_counts = Counter(
|
|
369
|
+
e.get("prompt_len_bucket", "unknown") for e in entries
|
|
370
|
+
)
|
|
371
|
+
response_kinds = Counter(e.get("response_kind", "absent") for e in entries)
|
|
372
|
+
|
|
373
|
+
# hook_duration_ms stats (tolerates missing — older entries don't have it)
|
|
374
|
+
durations = [
|
|
375
|
+
e.get("hook_duration_ms")
|
|
376
|
+
for e in entries
|
|
377
|
+
if isinstance(e.get("hook_duration_ms"), (int, float))
|
|
378
|
+
]
|
|
379
|
+
duration_summary: Dict[str, Any]
|
|
380
|
+
if durations:
|
|
381
|
+
durations_sorted = sorted(durations)
|
|
382
|
+
duration_summary = {
|
|
383
|
+
"count": len(durations_sorted),
|
|
384
|
+
"min_ms": durations_sorted[0],
|
|
385
|
+
"max_ms": durations_sorted[-1],
|
|
386
|
+
"mean_ms": round(sum(durations_sorted) / len(durations_sorted), 1),
|
|
387
|
+
"p50_ms": _percentile(durations_sorted, 50),
|
|
388
|
+
"p95_ms": _percentile(durations_sorted, 95),
|
|
389
|
+
"p99_ms": _percentile(durations_sorted, 99),
|
|
390
|
+
}
|
|
391
|
+
else:
|
|
392
|
+
duration_summary = {"count": 0}
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
"total": total,
|
|
396
|
+
"prompt_len_buckets": dict(bucket_counts),
|
|
397
|
+
"response_kinds": dict(response_kinds),
|
|
398
|
+
"hook_duration_ms": duration_summary,
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _tool_latency_stats(entries: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
403
|
+
"""PLAN-125 WS-1 — per-tool lifecycle bucket histogram.
|
|
404
|
+
|
|
405
|
+
Reads ``tool_call_lifecycle_recorded`` rows and rolls up, per
|
|
406
|
+
``tool_name_enum``, a Counter of ``duration_bucket`` plus success / orphan
|
|
407
|
+
tallies. Bucket-counts ONLY — the action never records a raw
|
|
408
|
+
``duration_ms`` (MF-SEC-3), so there are no percentiles to compute.
|
|
409
|
+
"""
|
|
410
|
+
rows = [
|
|
411
|
+
e for e in entries
|
|
412
|
+
if e.get("action") == "tool_call_lifecycle_recorded"
|
|
413
|
+
]
|
|
414
|
+
per_tool: Dict[str, Dict[str, Any]] = {}
|
|
415
|
+
for e in rows:
|
|
416
|
+
tool = e.get("tool_name_enum", "other")
|
|
417
|
+
if not isinstance(tool, str):
|
|
418
|
+
tool = "other"
|
|
419
|
+
slot = per_tool.setdefault(
|
|
420
|
+
tool,
|
|
421
|
+
{"count": 0, "duration_buckets": Counter(),
|
|
422
|
+
"success": 0, "failure": 0, "orphan": 0},
|
|
423
|
+
)
|
|
424
|
+
slot["count"] += 1
|
|
425
|
+
bucket = e.get("duration_bucket", "unknown")
|
|
426
|
+
if not isinstance(bucket, str):
|
|
427
|
+
bucket = "unknown"
|
|
428
|
+
slot["duration_buckets"][bucket] += 1
|
|
429
|
+
if e.get("orphan") is True:
|
|
430
|
+
slot["orphan"] += 1
|
|
431
|
+
if e.get("success") is True:
|
|
432
|
+
slot["success"] += 1
|
|
433
|
+
elif e.get("success") is False:
|
|
434
|
+
slot["failure"] += 1
|
|
435
|
+
|
|
436
|
+
# Materialize Counters to plain dicts for JSON / display.
|
|
437
|
+
tool_latency = {
|
|
438
|
+
tool: {
|
|
439
|
+
"count": slot["count"],
|
|
440
|
+
"duration_buckets": dict(slot["duration_buckets"]),
|
|
441
|
+
"success": slot["success"],
|
|
442
|
+
"failure": slot["failure"],
|
|
443
|
+
"orphan": slot["orphan"],
|
|
444
|
+
}
|
|
445
|
+
for tool, slot in sorted(per_tool.items())
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
"total_lifecycle_rows": len(rows),
|
|
449
|
+
"tool_latency": tool_latency,
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _percentile(sorted_values: List[float], p: float) -> float:
|
|
454
|
+
if not sorted_values:
|
|
455
|
+
return 0.0
|
|
456
|
+
k = (len(sorted_values) - 1) * (p / 100.0)
|
|
457
|
+
f = int(k)
|
|
458
|
+
c = min(f + 1, len(sorted_values) - 1)
|
|
459
|
+
if f == c:
|
|
460
|
+
return round(float(sorted_values[f]), 1)
|
|
461
|
+
return round(
|
|
462
|
+
sorted_values[f] + (sorted_values[c] - sorted_values[f]) * (k - f),
|
|
463
|
+
1,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def cmd_errors(args) -> Dict[str, Any]:
|
|
468
|
+
err_path = Path(args.errors_path) if args.errors_path else default_errors_path()
|
|
469
|
+
if not err_path.is_file():
|
|
470
|
+
return {"errors_path": str(err_path), "count": 0, "lines": []}
|
|
471
|
+
try:
|
|
472
|
+
text = err_path.read_text(encoding="utf-8", errors="replace")
|
|
473
|
+
except OSError as e:
|
|
474
|
+
print(f"[audit-query] ERROR: cannot read {err_path}: {e}", file=sys.stderr)
|
|
475
|
+
sys.exit(2)
|
|
476
|
+
lines = [ln for ln in text.split("\n") if ln.strip()]
|
|
477
|
+
return {
|
|
478
|
+
"errors_path": str(err_path),
|
|
479
|
+
"count": len(lines),
|
|
480
|
+
"lines": lines[-50:], # tail
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def cmd_debate(entries: List[Dict[str, Any]], args) -> List[Dict[str, Any]]:
|
|
485
|
+
"""Group debate_event rows by (plan_id, round). Sprint 5 A.2."""
|
|
486
|
+
groups: Dict[Tuple[str, int], Dict[str, Any]] = {}
|
|
487
|
+
for e in entries:
|
|
488
|
+
if e.get("action") != "debate_event":
|
|
489
|
+
continue
|
|
490
|
+
key = (e.get("plan_id", "?"), int(e.get("round") or 0))
|
|
491
|
+
g = groups.setdefault(
|
|
492
|
+
key,
|
|
493
|
+
{
|
|
494
|
+
"plan_id": key[0],
|
|
495
|
+
"round": key[1],
|
|
496
|
+
"start_ts": None,
|
|
497
|
+
"consensus_ts": None,
|
|
498
|
+
"agents": [],
|
|
499
|
+
"consensus_adjustments": None,
|
|
500
|
+
},
|
|
501
|
+
)
|
|
502
|
+
phase = e.get("phase")
|
|
503
|
+
ts = e.get("ts")
|
|
504
|
+
if phase == "start":
|
|
505
|
+
g["start_ts"] = ts
|
|
506
|
+
elif phase == "agent-done":
|
|
507
|
+
agent = e.get("agent")
|
|
508
|
+
if agent and agent not in g["agents"]:
|
|
509
|
+
g["agents"].append(agent)
|
|
510
|
+
elif phase == "consensus":
|
|
511
|
+
g["consensus_ts"] = ts
|
|
512
|
+
if e.get("consensus_adjustments_count") is not None:
|
|
513
|
+
g["consensus_adjustments"] = e["consensus_adjustments_count"]
|
|
514
|
+
# Sort by (plan_id, round)
|
|
515
|
+
return [groups[k] for k in sorted(groups.keys())]
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def cmd_plans(entries: List[Dict[str, Any]], args) -> List[Dict[str, Any]]:
|
|
519
|
+
"""Show plan status transitions over time. Sprint 5 A.2."""
|
|
520
|
+
by_plan: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
521
|
+
for e in entries:
|
|
522
|
+
if e.get("action") != "plan_transition":
|
|
523
|
+
continue
|
|
524
|
+
by_plan[e.get("plan_id", "?")].append(
|
|
525
|
+
{
|
|
526
|
+
"ts": e.get("ts"),
|
|
527
|
+
"from": e.get("from_status"),
|
|
528
|
+
"to": e.get("to_status"),
|
|
529
|
+
"editor": e.get("editor_tool"),
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
# Sort transitions by ts, then emit one row per plan with a chain summary
|
|
533
|
+
out = []
|
|
534
|
+
for plan_id in sorted(by_plan.keys()):
|
|
535
|
+
trans = sorted(by_plan[plan_id], key=lambda t: t.get("ts") or "")
|
|
536
|
+
chain = "→".join(
|
|
537
|
+
[t["from"] for t in trans] + [trans[-1]["to"]] if trans else []
|
|
538
|
+
)
|
|
539
|
+
out.append(
|
|
540
|
+
{
|
|
541
|
+
"plan_id": plan_id,
|
|
542
|
+
"transitions": len(trans),
|
|
543
|
+
"chain": chain,
|
|
544
|
+
"first_ts": trans[0]["ts"] if trans else None,
|
|
545
|
+
"last_ts": trans[-1]["ts"] if trans else None,
|
|
546
|
+
"current_status": trans[-1]["to"] if trans else None,
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
return out
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def cmd_vetoes(entries: List[Dict[str, Any]], args) -> List[Dict[str, Any]]:
|
|
553
|
+
"""Aggregate veto_triggered rows by (hook, reason_code). Sprint 5 A.2."""
|
|
554
|
+
counts: Dict[Tuple[str, str], Dict[str, Any]] = {}
|
|
555
|
+
for e in entries:
|
|
556
|
+
if e.get("action") != "veto_triggered":
|
|
557
|
+
continue
|
|
558
|
+
key = (e.get("hook", "?"), e.get("reason_code", "?"))
|
|
559
|
+
g = counts.setdefault(
|
|
560
|
+
key,
|
|
561
|
+
{
|
|
562
|
+
"hook": key[0],
|
|
563
|
+
"reason_code": key[1],
|
|
564
|
+
"count": 0,
|
|
565
|
+
"first_ts": None,
|
|
566
|
+
"last_ts": None,
|
|
567
|
+
"sample_preview": "",
|
|
568
|
+
},
|
|
569
|
+
)
|
|
570
|
+
g["count"] += 1
|
|
571
|
+
ts = e.get("ts") or ""
|
|
572
|
+
if g["first_ts"] is None or ts < g["first_ts"]:
|
|
573
|
+
g["first_ts"] = ts
|
|
574
|
+
if g["last_ts"] is None or ts > g["last_ts"]:
|
|
575
|
+
g["last_ts"] = ts
|
|
576
|
+
if not g["sample_preview"] and e.get("reason_preview"):
|
|
577
|
+
g["sample_preview"] = e["reason_preview"]
|
|
578
|
+
return sorted(
|
|
579
|
+
counts.values(),
|
|
580
|
+
key=lambda g: (-g["count"], g["hook"], g["reason_code"]),
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _bench_cost_usd(r: Dict[str, Any]) -> float:
|
|
585
|
+
"""Per-run benchmark cost in USD. Prefers the int-encoded
|
|
586
|
+
``cost_usd_cents`` (÷100); falls back to a legacy float ``cost_usd``.
|
|
587
|
+
Returns 0.0 when neither is present (pre-cost-instrumented runs)."""
|
|
588
|
+
cents = r.get("cost_usd_cents")
|
|
589
|
+
if cents is not None:
|
|
590
|
+
try:
|
|
591
|
+
return int(cents) / 100.0
|
|
592
|
+
except (TypeError, ValueError):
|
|
593
|
+
return 0.0
|
|
594
|
+
try:
|
|
595
|
+
return float(r.get("cost_usd") or 0.0)
|
|
596
|
+
except (TypeError, ValueError):
|
|
597
|
+
return 0.0
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def _bench_duration_s(r: Dict[str, Any]) -> float:
|
|
601
|
+
"""Per-run wall-clock (the harbor 'compute' column) in seconds.
|
|
602
|
+
Prefers int-encoded ``duration_ms`` (÷1000); falls back to legacy
|
|
603
|
+
float ``duration_s``."""
|
|
604
|
+
ms = r.get("duration_ms")
|
|
605
|
+
if ms is not None:
|
|
606
|
+
try:
|
|
607
|
+
return int(ms) / 1000.0
|
|
608
|
+
except (TypeError, ValueError):
|
|
609
|
+
return 0.0
|
|
610
|
+
try:
|
|
611
|
+
return float(r.get("duration_s") or 0.0)
|
|
612
|
+
except (TypeError, ValueError):
|
|
613
|
+
return 0.0
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _bench_turns(r: Dict[str, Any]) -> int:
|
|
617
|
+
"""Per-run scenario count (the harbor 'turns' column). Each scenario
|
|
618
|
+
is one model interaction unit; pass+fail covers every scored
|
|
619
|
+
scenario. Tolerates missing counts."""
|
|
620
|
+
try:
|
|
621
|
+
return int(r.get("pass_count") or 0) + int(r.get("fail_count") or 0)
|
|
622
|
+
except (TypeError, ValueError):
|
|
623
|
+
return 0
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def cmd_benchmarks(
|
|
627
|
+
entries: List[Dict[str, Any]], args
|
|
628
|
+
) -> List[Dict[str, Any]]:
|
|
629
|
+
"""Aggregate benchmark_run rows by skill. Sprint 5 A.2; PLAN-133 C4.
|
|
630
|
+
|
|
631
|
+
Reports run count, latest pass_rate, median across runs, cumulative
|
|
632
|
+
lessons_written per skill.
|
|
633
|
+
|
|
634
|
+
PLAN-133 C4 (harbor-style row): co-reports **cost + compute + turns
|
|
635
|
+
alongside pass-rate** so a benchmark is never read as a bare scalar.
|
|
636
|
+
The added columns are strictly additive (existing keys unchanged) and
|
|
637
|
+
derive only from fields already on the ``benchmark_run`` event
|
|
638
|
+
(``cost_usd_cents``, ``duration_ms``, ``pass_count``/``fail_count``) —
|
|
639
|
+
no SPEC change is required, so this reader stays $0 and canonical-free.
|
|
640
|
+
"""
|
|
641
|
+
by_skill: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
642
|
+
for e in entries:
|
|
643
|
+
if e.get("action") != "benchmark_run":
|
|
644
|
+
continue
|
|
645
|
+
by_skill[e.get("skill", "?")].append(e)
|
|
646
|
+
out = []
|
|
647
|
+
for skill in sorted(by_skill.keys()):
|
|
648
|
+
runs = sorted(by_skill[skill], key=lambda r: r.get("ts") or "")
|
|
649
|
+
# Prefer new int-encoded fields (bps ÷ 1000 → float); fall back to
|
|
650
|
+
# the legacy float fields for logs written before the migration.
|
|
651
|
+
def _pass_rate(r: Dict[str, Any]) -> float:
|
|
652
|
+
bps = r.get("pass_rate_bps")
|
|
653
|
+
if bps is not None:
|
|
654
|
+
return int(bps) / 1000.0
|
|
655
|
+
return float(r.get("pass_rate") or 0.0)
|
|
656
|
+
|
|
657
|
+
def _floor_rate(r: Dict[str, Any]) -> float:
|
|
658
|
+
bps = r.get("floor_bps")
|
|
659
|
+
if bps is not None:
|
|
660
|
+
return int(bps) / 1000.0
|
|
661
|
+
return float(r.get("floor") or 0.0)
|
|
662
|
+
|
|
663
|
+
rates = [_pass_rate(r) for r in runs]
|
|
664
|
+
if rates:
|
|
665
|
+
med = _percentile(sorted(rates), 50)
|
|
666
|
+
else:
|
|
667
|
+
med = 0.0
|
|
668
|
+
latest = runs[-1]
|
|
669
|
+
# PLAN-133 C4 — harbor-style compute/cost/turns co-report.
|
|
670
|
+
# Cumulative across all runs for the skill + the latest single run,
|
|
671
|
+
# so an operator sees both the trend cost and the marginal cost.
|
|
672
|
+
total_cost = sum(_bench_cost_usd(r) for r in runs)
|
|
673
|
+
total_compute_s = sum(_bench_duration_s(r) for r in runs)
|
|
674
|
+
total_turns = sum(_bench_turns(r) for r in runs)
|
|
675
|
+
out.append(
|
|
676
|
+
{
|
|
677
|
+
"skill": skill,
|
|
678
|
+
"runs": len(runs),
|
|
679
|
+
"latest_pass_rate": round(_pass_rate(latest), 3),
|
|
680
|
+
"median_pass_rate": med,
|
|
681
|
+
"last_floor": _floor_rate(latest),
|
|
682
|
+
"latest_ts": latest.get("ts"),
|
|
683
|
+
"total_lessons_written": sum(
|
|
684
|
+
int(r.get("lessons_written") or 0) for r in runs
|
|
685
|
+
),
|
|
686
|
+
# --- PLAN-133 C4 harbor-style row (additive) ---
|
|
687
|
+
"latest_cost_usd": round(_bench_cost_usd(latest), 6),
|
|
688
|
+
"total_cost_usd": round(total_cost, 6),
|
|
689
|
+
"latest_compute_s": round(_bench_duration_s(latest), 3),
|
|
690
|
+
"total_compute_s": round(total_compute_s, 3),
|
|
691
|
+
"latest_turns": _bench_turns(latest),
|
|
692
|
+
"total_turns": total_turns,
|
|
693
|
+
}
|
|
694
|
+
)
|
|
695
|
+
return out
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def cmd_lessons(entries: List[Dict[str, Any]], args) -> List[Dict[str, Any]]:
|
|
699
|
+
"""Aggregate lesson_write rows by archetype. Sprint 5 A.2."""
|
|
700
|
+
by_arch: Dict[str, Dict[str, Any]] = {}
|
|
701
|
+
triggers = Counter()
|
|
702
|
+
for e in entries:
|
|
703
|
+
if e.get("action") != "lesson_write":
|
|
704
|
+
continue
|
|
705
|
+
arch = e.get("archetype", "?")
|
|
706
|
+
g = by_arch.setdefault(
|
|
707
|
+
arch,
|
|
708
|
+
{
|
|
709
|
+
"archetype": arch,
|
|
710
|
+
"count": 0,
|
|
711
|
+
"triggers": Counter(),
|
|
712
|
+
"last_ts": None,
|
|
713
|
+
},
|
|
714
|
+
)
|
|
715
|
+
g["count"] += 1
|
|
716
|
+
g["triggers"][e.get("trigger", "unknown")] += 1
|
|
717
|
+
ts = e.get("ts") or ""
|
|
718
|
+
if g["last_ts"] is None or ts > g["last_ts"]:
|
|
719
|
+
g["last_ts"] = ts
|
|
720
|
+
triggers[e.get("trigger", "unknown")] += 1
|
|
721
|
+
|
|
722
|
+
out = []
|
|
723
|
+
for arch in sorted(by_arch.keys(), key=lambda a: (-by_arch[a]["count"], a)):
|
|
724
|
+
g = by_arch[arch]
|
|
725
|
+
# Collapse the Counter to a dict for JSON-friendly rendering
|
|
726
|
+
g["triggers"] = dict(g["triggers"])
|
|
727
|
+
out.append(g)
|
|
728
|
+
return out
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def _parse_since_arg(raw: str) -> Optional[datetime]:
|
|
732
|
+
"""Parse `--since` value: ``24h`` / ``7d`` / ``30m`` / ISO 8601 / ``all``.
|
|
733
|
+
|
|
734
|
+
Returns a timezone-aware UTC datetime cutoff, or None for "all time".
|
|
735
|
+
PLAN-009 A18 (shared across new sub-commands).
|
|
736
|
+
"""
|
|
737
|
+
if not raw or raw == "all":
|
|
738
|
+
return None
|
|
739
|
+
raw = raw.strip().lower()
|
|
740
|
+
# Shorthand: <int><unit> (m=minutes, h=hours, d=days)
|
|
741
|
+
if re.fullmatch(r"\d+[mhd]", raw):
|
|
742
|
+
n = int(raw[:-1])
|
|
743
|
+
unit = raw[-1]
|
|
744
|
+
delta = {"m": timedelta(minutes=n), "h": timedelta(hours=n), "d": timedelta(days=n)}[unit]
|
|
745
|
+
return datetime.now(timezone.utc) - delta
|
|
746
|
+
# Try ISO 8601 parse
|
|
747
|
+
try:
|
|
748
|
+
if raw.endswith("z"):
|
|
749
|
+
raw = raw[:-1] + "+00:00"
|
|
750
|
+
dt = datetime.fromisoformat(raw)
|
|
751
|
+
if dt.tzinfo is None:
|
|
752
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
753
|
+
return dt
|
|
754
|
+
except ValueError:
|
|
755
|
+
return None
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def cmd_lessons_effectiveness(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
759
|
+
"""`audit-query lessons-effectiveness` — per-lesson effectiveness ranking.
|
|
760
|
+
|
|
761
|
+
PLAN-009 Phase 5 P5.1. Aggregates `lesson_outcome` + `lesson_read`
|
|
762
|
+
events per `lesson_id`; computes effectiveness + injection count +
|
|
763
|
+
recency; sorts by operator-chosen axis.
|
|
764
|
+
|
|
765
|
+
Output envelope (SPEC/v1/audit-query.schema.md):
|
|
766
|
+
{"query": "lessons-effectiveness", "version": "1",
|
|
767
|
+
"data": {"lessons": [...]}}
|
|
768
|
+
"""
|
|
769
|
+
since = _parse_since_arg(getattr(args, "since", "24h"))
|
|
770
|
+
include_window_only = getattr(args, "include_window_only", False)
|
|
771
|
+
sort_by = getattr(args, "by", "effectiveness")
|
|
772
|
+
top_n = int(getattr(args, "top", 0) or 0)
|
|
773
|
+
bottom_n = int(getattr(args, "bottom", 0) or 0)
|
|
774
|
+
|
|
775
|
+
def _in_window(e: Dict[str, Any]) -> bool:
|
|
776
|
+
if since is None:
|
|
777
|
+
return True
|
|
778
|
+
ts = _parse_since_arg(e.get("ts", ""))
|
|
779
|
+
return ts is None or ts >= since
|
|
780
|
+
|
|
781
|
+
from collections import defaultdict
|
|
782
|
+
agg: Dict[str, Dict[str, Any]] = defaultdict(
|
|
783
|
+
lambda: {"hit": 0, "miss": 0, "injections": 0,
|
|
784
|
+
"last": "", "modes": {}}
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
for e in entries:
|
|
788
|
+
action = e.get("action")
|
|
789
|
+
if action == "lesson_outcome":
|
|
790
|
+
mode = e.get("inference_mode", "") or "unspecified"
|
|
791
|
+
if not include_window_only and mode == "window-only":
|
|
792
|
+
continue
|
|
793
|
+
if not _in_window(e):
|
|
794
|
+
continue
|
|
795
|
+
lid = e.get("lesson_id", "")
|
|
796
|
+
if not lid:
|
|
797
|
+
continue
|
|
798
|
+
# When lesson_id is comma-separated (emit_architect_outcome
|
|
799
|
+
# aggregate emit), split into component ids
|
|
800
|
+
for lid_part in lid.split(","):
|
|
801
|
+
lid_part = lid_part.strip()
|
|
802
|
+
if not lid_part:
|
|
803
|
+
continue
|
|
804
|
+
a = agg[lid_part]
|
|
805
|
+
if e.get("hit"):
|
|
806
|
+
a["hit"] += 1
|
|
807
|
+
else:
|
|
808
|
+
a["miss"] += 1
|
|
809
|
+
a["modes"][mode] = a["modes"].get(mode, 0) + 1
|
|
810
|
+
ts = e.get("ts", "")
|
|
811
|
+
if ts > a["last"]:
|
|
812
|
+
a["last"] = ts
|
|
813
|
+
elif action == "lesson_read":
|
|
814
|
+
if not _in_window(e):
|
|
815
|
+
continue
|
|
816
|
+
for lid in e.get("lesson_ids", []) or []:
|
|
817
|
+
agg[lid]["injections"] += 1
|
|
818
|
+
|
|
819
|
+
# Compute effectiveness + days-since-last
|
|
820
|
+
from datetime import datetime, timezone
|
|
821
|
+
now = datetime.now(timezone.utc)
|
|
822
|
+
lessons = []
|
|
823
|
+
for lid, a in agg.items():
|
|
824
|
+
total = a["hit"] + a["miss"]
|
|
825
|
+
eff = (a["hit"] / total) if total > 0 else None
|
|
826
|
+
days_since = None
|
|
827
|
+
last_dt = _parse_since_arg(a["last"]) if a["last"] else None
|
|
828
|
+
if last_dt is not None:
|
|
829
|
+
days_since = (now - last_dt).total_seconds() / 86400.0
|
|
830
|
+
lessons.append({
|
|
831
|
+
"lesson_id": lid,
|
|
832
|
+
"hit_count": a["hit"],
|
|
833
|
+
"miss_count": a["miss"],
|
|
834
|
+
"effectiveness": eff,
|
|
835
|
+
"days_since_last_outcome": days_since,
|
|
836
|
+
"injection_count": a["injections"],
|
|
837
|
+
"inference_mode_breakdown": a["modes"],
|
|
838
|
+
})
|
|
839
|
+
|
|
840
|
+
# Sorting — null effectiveness sorts AFTER non-null (A5/A9)
|
|
841
|
+
warning = None
|
|
842
|
+
if sort_by == "effectiveness":
|
|
843
|
+
lessons.sort(key=lambda x: (
|
|
844
|
+
x["effectiveness"] is None,
|
|
845
|
+
-(x["effectiveness"] or 0.0),
|
|
846
|
+
))
|
|
847
|
+
elif sort_by == "recency":
|
|
848
|
+
lessons.sort(key=lambda x: (
|
|
849
|
+
x["days_since_last_outcome"] is None,
|
|
850
|
+
x["days_since_last_outcome"] or float("inf"),
|
|
851
|
+
))
|
|
852
|
+
elif sort_by == "injections":
|
|
853
|
+
# VP unseen #4 — warn about gameable axis
|
|
854
|
+
warning = "sorting by 'injections' is a gameable axis; use with caution"
|
|
855
|
+
lessons.sort(key=lambda x: -x["injection_count"])
|
|
856
|
+
|
|
857
|
+
if top_n > 0:
|
|
858
|
+
lessons = lessons[:top_n]
|
|
859
|
+
elif bottom_n > 0:
|
|
860
|
+
lessons = lessons[-bottom_n:][::-1]
|
|
861
|
+
|
|
862
|
+
data = {
|
|
863
|
+
"sort_by": sort_by,
|
|
864
|
+
"include_window_only": include_window_only,
|
|
865
|
+
"since": since.isoformat() if since else "all",
|
|
866
|
+
"lesson_count": len(lessons),
|
|
867
|
+
"lessons": lessons,
|
|
868
|
+
}
|
|
869
|
+
if warning:
|
|
870
|
+
data["warning"] = warning
|
|
871
|
+
return {
|
|
872
|
+
"query": "lessons-effectiveness",
|
|
873
|
+
"version": "1",
|
|
874
|
+
"data": data,
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def cmd_spawn_stats(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
879
|
+
"""PLAN-025 Batch D F-obs-001 — spawn distribution by model/skill.
|
|
880
|
+
|
|
881
|
+
Reads audit-log entries, filters to `action == "agent_spawn"`, and
|
|
882
|
+
aggregates counts by `model` (ADR-052 v2.8 field), `skill`, or both.
|
|
883
|
+
|
|
884
|
+
Respects `--since` with either ISO-8601 timestamps or relative
|
|
885
|
+
offsets ("7d", "30d", "1h"). Renders a human-friendly table (or JSON
|
|
886
|
+
via `--json`).
|
|
887
|
+
"""
|
|
888
|
+
import re as _re
|
|
889
|
+
from datetime import datetime as _dt, timedelta as _td, timezone as _tz
|
|
890
|
+
|
|
891
|
+
# Resolve --since
|
|
892
|
+
since_dt: Optional[_dt] = None
|
|
893
|
+
if getattr(args, "since", None):
|
|
894
|
+
raw = str(args.since).strip()
|
|
895
|
+
m = _re.match(r"^(\d+)([hdwm])$", raw)
|
|
896
|
+
if m:
|
|
897
|
+
n, unit = int(m.group(1)), m.group(2)
|
|
898
|
+
delta = {"h": _td(hours=n), "d": _td(days=n),
|
|
899
|
+
"w": _td(weeks=n), "m": _td(days=30 * n)}[unit]
|
|
900
|
+
since_dt = _dt.now(tz=_tz.utc) - delta
|
|
901
|
+
else:
|
|
902
|
+
try:
|
|
903
|
+
since_dt = _dt.fromisoformat(raw.replace("Z", "+00:00"))
|
|
904
|
+
if since_dt.tzinfo is None:
|
|
905
|
+
since_dt = since_dt.replace(tzinfo=_tz.utc)
|
|
906
|
+
except ValueError:
|
|
907
|
+
return {"error": f"unparseable --since: {raw!r}"}
|
|
908
|
+
|
|
909
|
+
by_model: Dict[str, int] = {}
|
|
910
|
+
by_skill: Dict[str, int] = {}
|
|
911
|
+
by_pair: Dict[str, int] = {} # "model|skill"
|
|
912
|
+
total = 0
|
|
913
|
+
|
|
914
|
+
for ev in entries:
|
|
915
|
+
if ev.get("action") != "agent_spawn":
|
|
916
|
+
continue
|
|
917
|
+
if since_dt is not None:
|
|
918
|
+
ts_s = ev.get("ts") or ev.get("timestamp") or ""
|
|
919
|
+
try:
|
|
920
|
+
ev_ts = _dt.fromisoformat(str(ts_s).replace("Z", "+00:00"))
|
|
921
|
+
if ev_ts.tzinfo is None:
|
|
922
|
+
ev_ts = ev_ts.replace(tzinfo=_tz.utc)
|
|
923
|
+
except (ValueError, TypeError):
|
|
924
|
+
continue
|
|
925
|
+
if ev_ts < since_dt:
|
|
926
|
+
continue
|
|
927
|
+
|
|
928
|
+
total += 1
|
|
929
|
+
model = ev.get("model") or "unknown_model"
|
|
930
|
+
skill = ev.get("skill") or "unknown_skill"
|
|
931
|
+
by_model[model] = by_model.get(model, 0) + 1
|
|
932
|
+
by_skill[skill] = by_skill.get(skill, 0) + 1
|
|
933
|
+
by_pair[f"{model}|{skill}"] = by_pair.get(f"{model}|{skill}", 0) + 1
|
|
934
|
+
|
|
935
|
+
out: Dict[str, Any] = {
|
|
936
|
+
"since": str(since_dt) if since_dt else "all-time",
|
|
937
|
+
"total_spawns": total,
|
|
938
|
+
}
|
|
939
|
+
by = getattr(args, "by", "model")
|
|
940
|
+
if by in ("model", "both"):
|
|
941
|
+
out["by_model"] = dict(sorted(by_model.items(), key=lambda kv: -kv[1]))
|
|
942
|
+
if by in ("skill", "both"):
|
|
943
|
+
out["by_skill"] = dict(sorted(by_skill.items(), key=lambda kv: -kv[1]))
|
|
944
|
+
if by == "both":
|
|
945
|
+
out["by_model_and_skill"] = dict(sorted(by_pair.items(), key=lambda kv: -kv[1]))
|
|
946
|
+
return out
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
# ---------------------------------------------------------------------------
|
|
950
|
+
# weekly-summary helpers (PLAN-023 Phase E decomposition)
|
|
951
|
+
# ---------------------------------------------------------------------------
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def _weekly_parse_ts(e: Dict[str, Any]) -> Optional[datetime]:
|
|
955
|
+
"""Parse ISO-8601 ``ts`` from an audit entry (None on missing/malformed)."""
|
|
956
|
+
raw = e.get("ts")
|
|
957
|
+
if not isinstance(raw, str):
|
|
958
|
+
return None
|
|
959
|
+
try:
|
|
960
|
+
s = raw[:-1] + "+00:00" if raw.endswith("Z") else raw
|
|
961
|
+
dt = datetime.fromisoformat(s)
|
|
962
|
+
return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)
|
|
963
|
+
except ValueError:
|
|
964
|
+
return None
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def _weekly_parse_window(window_raw: str) -> "Optional[timedelta]":
|
|
968
|
+
"""Translate ``7d``/``14d``/``30m``/``all`` into a timedelta (or None)."""
|
|
969
|
+
if window_raw == "all":
|
|
970
|
+
return None
|
|
971
|
+
m = re.fullmatch(r"(\d+)([mhd])", window_raw.strip().lower())
|
|
972
|
+
if not m:
|
|
973
|
+
print(
|
|
974
|
+
"[audit-query] ERROR: weekly-summary: bad --window {0!r}; "
|
|
975
|
+
"expected 7d/14d/30m/all".format(window_raw),
|
|
976
|
+
file=sys.stderr,
|
|
977
|
+
)
|
|
978
|
+
sys.exit(1)
|
|
979
|
+
n, unit = int(m.group(1)), m.group(2)
|
|
980
|
+
return {"m": timedelta(minutes=n), "h": timedelta(hours=n),
|
|
981
|
+
"d": timedelta(days=n)}[unit]
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def _weekly_empty_bucket() -> Dict[str, Any]:
|
|
985
|
+
return {
|
|
986
|
+
"spawns": 0,
|
|
987
|
+
"vetoes": 0,
|
|
988
|
+
"plan_transitions": 0,
|
|
989
|
+
"confidence_total": 0,
|
|
990
|
+
"confidence_failed": 0,
|
|
991
|
+
"veto_reasons": Counter(),
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
def _weekly_collect_buckets(
|
|
996
|
+
entries: List[Dict[str, Any]],
|
|
997
|
+
current_start: Optional[datetime],
|
|
998
|
+
prior_start: Optional[datetime],
|
|
999
|
+
prior_end: Optional[datetime],
|
|
1000
|
+
now: datetime,
|
|
1001
|
+
) -> "Tuple[Dict[str, Any], Dict[str, Any]]":
|
|
1002
|
+
"""Single-pass accumulation of audit events into current/prior buckets."""
|
|
1003
|
+
current = _weekly_empty_bucket()
|
|
1004
|
+
prior = _weekly_empty_bucket()
|
|
1005
|
+
for e in entries:
|
|
1006
|
+
dt = _weekly_parse_ts(e)
|
|
1007
|
+
if dt is None:
|
|
1008
|
+
continue
|
|
1009
|
+
if current_start is None:
|
|
1010
|
+
bucket_name = "current" # --window all
|
|
1011
|
+
elif current_start <= dt <= now:
|
|
1012
|
+
bucket_name = "current"
|
|
1013
|
+
elif (
|
|
1014
|
+
prior_start is not None
|
|
1015
|
+
and prior_end is not None
|
|
1016
|
+
and prior_start <= dt < prior_end
|
|
1017
|
+
):
|
|
1018
|
+
bucket_name = "prior"
|
|
1019
|
+
else:
|
|
1020
|
+
continue
|
|
1021
|
+
bucket = current if bucket_name == "current" else prior
|
|
1022
|
+
action = e.get("action")
|
|
1023
|
+
if action == "agent_spawn":
|
|
1024
|
+
bucket["spawns"] += 1
|
|
1025
|
+
elif action == "veto_triggered":
|
|
1026
|
+
bucket["vetoes"] += 1
|
|
1027
|
+
reason = e.get("reason_code") or "unknown"
|
|
1028
|
+
bucket["veto_reasons"][reason] += 1
|
|
1029
|
+
elif action == "plan_transition":
|
|
1030
|
+
bucket["plan_transitions"] += 1
|
|
1031
|
+
elif action == "confidence_gate":
|
|
1032
|
+
bucket["confidence_total"] += 1
|
|
1033
|
+
if e.get("outcome") == "fail" or e.get("failed_claim_count"):
|
|
1034
|
+
bucket["confidence_failed"] += 1
|
|
1035
|
+
return current, prior
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
def _weekly_render_bucket(bucket: Dict[str, Any]) -> Dict[str, Any]:
|
|
1039
|
+
"""Compute derived rates for one accumulator bucket."""
|
|
1040
|
+
denom = bucket["spawns"] + bucket["vetoes"]
|
|
1041
|
+
veto_rate = bucket["vetoes"] / denom if denom else None
|
|
1042
|
+
conf_total = bucket["confidence_total"]
|
|
1043
|
+
conf_fail_rate = bucket["confidence_failed"] / conf_total if conf_total else None
|
|
1044
|
+
return {
|
|
1045
|
+
"spawns": bucket["spawns"],
|
|
1046
|
+
"vetoes": bucket["vetoes"],
|
|
1047
|
+
"veto_rate": round(veto_rate, 3) if veto_rate is not None else None,
|
|
1048
|
+
"plan_transitions": bucket["plan_transitions"],
|
|
1049
|
+
"confidence_total": bucket["confidence_total"],
|
|
1050
|
+
"confidence_failed": bucket["confidence_failed"],
|
|
1051
|
+
"confidence_fail_rate": round(conf_fail_rate, 3) if conf_fail_rate is not None else None,
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def _weekly_delta(
|
|
1056
|
+
a: Optional[float], b: Optional[float], *, pp: bool = False,
|
|
1057
|
+
) -> "Optional[float]":
|
|
1058
|
+
"""Signed delta between two optional floats (pp=True → ×100 rounded 1dp)."""
|
|
1059
|
+
if a is None or b is None:
|
|
1060
|
+
return None
|
|
1061
|
+
d = a - b
|
|
1062
|
+
if pp:
|
|
1063
|
+
return round(d * 100.0, 1)
|
|
1064
|
+
return round(d, 3) if isinstance(d, float) else int(d)
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def cmd_weekly_summary(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
1068
|
+
"""`audit-query weekly-summary` — adopter-side weekly triage metrics.
|
|
1069
|
+
|
|
1070
|
+
PLAN-015 Phase 0.5. Compares a current window against the prior window
|
|
1071
|
+
of the same length and reports signed deltas so the Owner can see
|
|
1072
|
+
week-over-week trends (spawn growth, veto-rate shifts, plan velocity,
|
|
1073
|
+
confidence-gate health, top vetoed reasons).
|
|
1074
|
+
|
|
1075
|
+
Window sizing: ``--window`` accepts the shared `_parse_since_arg`
|
|
1076
|
+
shorthand (``7d`` default, ``14d``, ``30d``) or ``all`` (disables
|
|
1077
|
+
the prior-window comparison — current-window only).
|
|
1078
|
+
``--now ISO`` overrides "now" for deterministic tests.
|
|
1079
|
+
|
|
1080
|
+
PLAN-023 Phase E decomposition: delegates window parsing, bucket
|
|
1081
|
+
accumulation, rendering, and delta computation to module-level
|
|
1082
|
+
helpers (``_weekly_parse_window``, ``_weekly_collect_buckets``,
|
|
1083
|
+
``_weekly_render_bucket``, ``_weekly_delta``). Behavior byte-
|
|
1084
|
+
identical to the pre-decomposition 151-LoC monolith.
|
|
1085
|
+
"""
|
|
1086
|
+
window_raw = str(getattr(args, "window", "7d"))
|
|
1087
|
+
now_raw = getattr(args, "now", None)
|
|
1088
|
+
now = _parse_since_arg(now_raw) if now_raw else datetime.now(timezone.utc)
|
|
1089
|
+
if now is None:
|
|
1090
|
+
now = datetime.now(timezone.utc)
|
|
1091
|
+
|
|
1092
|
+
window_delta = _weekly_parse_window(window_raw)
|
|
1093
|
+
|
|
1094
|
+
if window_delta is None:
|
|
1095
|
+
current_start: Optional[datetime] = None
|
|
1096
|
+
prior_start: Optional[datetime] = None
|
|
1097
|
+
prior_end: Optional[datetime] = None
|
|
1098
|
+
else:
|
|
1099
|
+
current_start = now - window_delta
|
|
1100
|
+
prior_start = now - (2 * window_delta)
|
|
1101
|
+
prior_end = current_start
|
|
1102
|
+
|
|
1103
|
+
current, prior = _weekly_collect_buckets(
|
|
1104
|
+
entries, current_start, prior_start, prior_end, now,
|
|
1105
|
+
)
|
|
1106
|
+
cur = _weekly_render_bucket(current)
|
|
1107
|
+
pri = _weekly_render_bucket(prior)
|
|
1108
|
+
|
|
1109
|
+
trend = {
|
|
1110
|
+
"spawn_delta": cur["spawns"] - pri["spawns"],
|
|
1111
|
+
"veto_rate_delta_pp": _weekly_delta(cur["veto_rate"], pri["veto_rate"], pp=True),
|
|
1112
|
+
"plan_transition_delta": cur["plan_transitions"] - pri["plan_transitions"],
|
|
1113
|
+
"confidence_fail_rate_delta_pp": _weekly_delta(
|
|
1114
|
+
cur["confidence_fail_rate"], pri["confidence_fail_rate"], pp=True
|
|
1115
|
+
),
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
top_vetoed = [
|
|
1119
|
+
{"reason_code": code, "count": count}
|
|
1120
|
+
for code, count in current["veto_reasons"].most_common(3)
|
|
1121
|
+
]
|
|
1122
|
+
|
|
1123
|
+
return {
|
|
1124
|
+
"query": "weekly-summary",
|
|
1125
|
+
"version": "1",
|
|
1126
|
+
"window": window_raw,
|
|
1127
|
+
"now": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
1128
|
+
"current_window": cur,
|
|
1129
|
+
"previous_window": pri,
|
|
1130
|
+
"trend": trend,
|
|
1131
|
+
"top_vetoed_reasons_current": top_vetoed,
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
def cmd_architect_outcomes(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
1136
|
+
"""`audit-query architect-outcomes` — per-lesson hit/miss from Architect spawns.
|
|
1137
|
+
|
|
1138
|
+
PLAN-009 P3.4. Aggregates `lesson_outcome` events filtered by
|
|
1139
|
+
`consumer="architect"` (default) and `inference_mode` (default
|
|
1140
|
+
"session-correlated" — dirty window-only data excluded unless
|
|
1141
|
+
`--include-window-only` is passed).
|
|
1142
|
+
|
|
1143
|
+
Output envelope (SPEC/v1/audit-query.schema.md):
|
|
1144
|
+
{"query": "architect-outcomes", "version": "1",
|
|
1145
|
+
"data": {"lessons": [{lesson_id, hit_count, miss_count,
|
|
1146
|
+
effectiveness, last_outcome_at,
|
|
1147
|
+
inference_modes: {...}}, ...]}}
|
|
1148
|
+
"""
|
|
1149
|
+
since = _parse_since_arg(getattr(args, "since", "24h"))
|
|
1150
|
+
include_window_only = getattr(args, "include_window_only", False)
|
|
1151
|
+
consumer_filter = getattr(args, "consumer", "architect")
|
|
1152
|
+
|
|
1153
|
+
def _in_window(e: Dict[str, Any]) -> bool:
|
|
1154
|
+
if since is None:
|
|
1155
|
+
return True
|
|
1156
|
+
ts = _parse_since_arg(e.get("ts", ""))
|
|
1157
|
+
return ts is None or ts >= since
|
|
1158
|
+
|
|
1159
|
+
# Aggregate per lesson_id
|
|
1160
|
+
from collections import defaultdict
|
|
1161
|
+
per_lesson: Dict[str, Dict[str, Any]] = defaultdict(
|
|
1162
|
+
lambda: {"hit_count": 0, "miss_count": 0,
|
|
1163
|
+
"last_outcome_at": "",
|
|
1164
|
+
"inference_modes": {}, "archetype": ""}
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
for e in entries:
|
|
1168
|
+
if e.get("action") != "lesson_outcome":
|
|
1169
|
+
continue
|
|
1170
|
+
if e.get("consumer", "benchmark") != consumer_filter:
|
|
1171
|
+
continue
|
|
1172
|
+
mode = e.get("inference_mode", "")
|
|
1173
|
+
if not include_window_only and mode == "window-only":
|
|
1174
|
+
continue
|
|
1175
|
+
if not _in_window(e):
|
|
1176
|
+
continue
|
|
1177
|
+
lid = e.get("lesson_id", "")
|
|
1178
|
+
if not lid:
|
|
1179
|
+
continue
|
|
1180
|
+
agg = per_lesson[lid]
|
|
1181
|
+
if e.get("hit"):
|
|
1182
|
+
agg["hit_count"] += 1
|
|
1183
|
+
else:
|
|
1184
|
+
agg["miss_count"] += 1
|
|
1185
|
+
agg["inference_modes"][mode or "unspecified"] = (
|
|
1186
|
+
agg["inference_modes"].get(mode or "unspecified", 0) + 1
|
|
1187
|
+
)
|
|
1188
|
+
ts = e.get("ts", "")
|
|
1189
|
+
if ts > agg["last_outcome_at"]:
|
|
1190
|
+
agg["last_outcome_at"] = ts
|
|
1191
|
+
if not agg["archetype"]:
|
|
1192
|
+
agg["archetype"] = e.get("archetype", "")
|
|
1193
|
+
|
|
1194
|
+
lessons = []
|
|
1195
|
+
for lid, agg in per_lesson.items():
|
|
1196
|
+
total = agg["hit_count"] + agg["miss_count"]
|
|
1197
|
+
effectiveness = (agg["hit_count"] / total) if total > 0 else None
|
|
1198
|
+
lessons.append({
|
|
1199
|
+
"lesson_id": lid,
|
|
1200
|
+
"archetype": agg["archetype"],
|
|
1201
|
+
"hit_count": agg["hit_count"],
|
|
1202
|
+
"miss_count": agg["miss_count"],
|
|
1203
|
+
"effectiveness": effectiveness,
|
|
1204
|
+
"last_outcome_at": agg["last_outcome_at"],
|
|
1205
|
+
"inference_modes": agg["inference_modes"],
|
|
1206
|
+
})
|
|
1207
|
+
lessons.sort(key=lambda x: (x["effectiveness"] is None, -(x["effectiveness"] or 0)))
|
|
1208
|
+
|
|
1209
|
+
data = {
|
|
1210
|
+
"consumer": consumer_filter,
|
|
1211
|
+
"include_window_only": include_window_only,
|
|
1212
|
+
"since": since.isoformat() if since else "all",
|
|
1213
|
+
"lesson_count": len(lessons),
|
|
1214
|
+
"lessons": lessons,
|
|
1215
|
+
}
|
|
1216
|
+
return {
|
|
1217
|
+
"query": "architect-outcomes",
|
|
1218
|
+
"version": "1",
|
|
1219
|
+
"data": data,
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
def cmd_prune_restore_ratio(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
1224
|
+
"""`audit-query prune-restore-ratio` — ADR-020 measurement hook.
|
|
1225
|
+
|
|
1226
|
+
Reads `lesson_archived` + `lesson_restored` events; computes
|
|
1227
|
+
restored/archived. Default window 24h (PLAN-009 A18). Dedupes
|
|
1228
|
+
by lesson_id; warns on >1 restore event per lesson (C12/A13).
|
|
1229
|
+
|
|
1230
|
+
Output shape (SPEC/v1/audit-query.schema.md envelope):
|
|
1231
|
+
{"query": "prune-restore-ratio", "version": "1",
|
|
1232
|
+
"data": { ... }}
|
|
1233
|
+
"""
|
|
1234
|
+
since = _parse_since_arg(getattr(args, "since", "24h"))
|
|
1235
|
+
until = _parse_since_arg(getattr(args, "until", None)) if getattr(args, "until", None) else None
|
|
1236
|
+
|
|
1237
|
+
def _in_window(e: Dict[str, Any]) -> bool:
|
|
1238
|
+
ts = e.get("ts", "")
|
|
1239
|
+
if not ts:
|
|
1240
|
+
return True
|
|
1241
|
+
dt = _parse_since_arg(ts)
|
|
1242
|
+
if dt is None:
|
|
1243
|
+
return True
|
|
1244
|
+
if since and dt < since:
|
|
1245
|
+
return False
|
|
1246
|
+
if until and dt > until:
|
|
1247
|
+
return False
|
|
1248
|
+
return True
|
|
1249
|
+
|
|
1250
|
+
archived = [e for e in entries if e.get("action") == "lesson_archived" and _in_window(e)]
|
|
1251
|
+
restored = [e for e in entries if e.get("action") == "lesson_restored" and _in_window(e)]
|
|
1252
|
+
|
|
1253
|
+
# Dedupe restored by lesson_id + count multi-restore warnings
|
|
1254
|
+
restored_counts: Dict[str, int] = {}
|
|
1255
|
+
for e in restored:
|
|
1256
|
+
lid = e.get("lesson_id", "")
|
|
1257
|
+
if lid:
|
|
1258
|
+
restored_counts[lid] = restored_counts.get(lid, 0) + 1
|
|
1259
|
+
unique_restored = len(restored_counts)
|
|
1260
|
+
multi_restored = {lid: n for lid, n in restored_counts.items() if n > 1}
|
|
1261
|
+
|
|
1262
|
+
archived_count = len(archived)
|
|
1263
|
+
if archived_count == 0:
|
|
1264
|
+
ratio: Optional[float] = None
|
|
1265
|
+
else:
|
|
1266
|
+
ratio = unique_restored / archived_count
|
|
1267
|
+
|
|
1268
|
+
data = {
|
|
1269
|
+
"archived_count": archived_count,
|
|
1270
|
+
"restored_count": len(restored),
|
|
1271
|
+
"unique_restored_lesson_ids": unique_restored,
|
|
1272
|
+
"restore_ratio": ratio,
|
|
1273
|
+
"since": since.isoformat() if since else "all",
|
|
1274
|
+
"until": until.isoformat() if until else "now",
|
|
1275
|
+
"multi_restore_warnings": multi_restored,
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
"query": "prune-restore-ratio",
|
|
1279
|
+
"version": "1",
|
|
1280
|
+
"data": data,
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
def cmd_claims(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
1285
|
+
"""Confidence-gate events aggregated (Sprint 8 Phase 2, ADR-018).
|
|
1286
|
+
|
|
1287
|
+
Returns totals + per-kind counts + pass/fail breakdown + per-agent
|
|
1288
|
+
aggregates. Filters via --kind / --agent / --failed-only.
|
|
1289
|
+
"""
|
|
1290
|
+
kind_filter = getattr(args, "kind", None)
|
|
1291
|
+
agent_filter = getattr(args, "agent", None)
|
|
1292
|
+
failed_only = getattr(args, "failed_only", False)
|
|
1293
|
+
|
|
1294
|
+
total = 0
|
|
1295
|
+
claim_count = 0
|
|
1296
|
+
pass_count = 0
|
|
1297
|
+
fail_count = 0
|
|
1298
|
+
per_kind: Dict[str, Dict[str, int]] = {}
|
|
1299
|
+
per_agent: Dict[str, Dict[str, int]] = {}
|
|
1300
|
+
|
|
1301
|
+
for e in entries:
|
|
1302
|
+
if e.get("action") != "confidence_gate":
|
|
1303
|
+
continue
|
|
1304
|
+
if failed_only and int(e.get("fail_count", 0)) == 0:
|
|
1305
|
+
continue
|
|
1306
|
+
if agent_filter and e.get("agent_name", "") != agent_filter:
|
|
1307
|
+
continue
|
|
1308
|
+
|
|
1309
|
+
kind_counts = e.get("verifier_kind_counts", {}) or {}
|
|
1310
|
+
if kind_filter:
|
|
1311
|
+
if kind_filter not in kind_counts:
|
|
1312
|
+
continue
|
|
1313
|
+
|
|
1314
|
+
total += 1
|
|
1315
|
+
claim_count += int(e.get("claim_count", 0))
|
|
1316
|
+
pass_count += int(e.get("pass_count", 0))
|
|
1317
|
+
fail_count += int(e.get("fail_count", 0))
|
|
1318
|
+
|
|
1319
|
+
for kind, cnt in kind_counts.items():
|
|
1320
|
+
g = per_kind.setdefault(kind, {"total": 0, "events": 0})
|
|
1321
|
+
g["total"] += int(cnt)
|
|
1322
|
+
g["events"] += 1
|
|
1323
|
+
|
|
1324
|
+
agent = e.get("agent_name", "") or "(unnamed)"
|
|
1325
|
+
a = per_agent.setdefault(agent, {"events": 0, "pass": 0, "fail": 0, "claims": 0})
|
|
1326
|
+
a["events"] += 1
|
|
1327
|
+
a["pass"] += int(e.get("pass_count", 0))
|
|
1328
|
+
a["fail"] += int(e.get("fail_count", 0))
|
|
1329
|
+
a["claims"] += int(e.get("claim_count", 0))
|
|
1330
|
+
|
|
1331
|
+
fpr = (fail_count / claim_count) if claim_count else None
|
|
1332
|
+
|
|
1333
|
+
return {
|
|
1334
|
+
"event_count": total,
|
|
1335
|
+
"claim_count": claim_count,
|
|
1336
|
+
"pass_count": pass_count,
|
|
1337
|
+
"fail_count": fail_count,
|
|
1338
|
+
"failure_rate": fpr,
|
|
1339
|
+
"per_kind": per_kind,
|
|
1340
|
+
"per_agent": per_agent,
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
def cmd_metrics(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
1345
|
+
"""Cross-cutting derived metrics. Sprint 5 A.2.
|
|
1346
|
+
|
|
1347
|
+
Action-type distribution, total veto rate, average duration per
|
|
1348
|
+
benchmark_run, debate completion rate (rounds that reached consensus).
|
|
1349
|
+
"""
|
|
1350
|
+
action_counts = Counter(e.get("action", "unknown") for e in entries)
|
|
1351
|
+
|
|
1352
|
+
# Veto rate: vetoes / (spawns + vetoes)
|
|
1353
|
+
spawns = action_counts.get("agent_spawn", 0)
|
|
1354
|
+
vetoes = action_counts.get("veto_triggered", 0)
|
|
1355
|
+
denom = spawns + vetoes
|
|
1356
|
+
veto_rate = (vetoes / denom) if denom else None
|
|
1357
|
+
|
|
1358
|
+
# Debate completion: rounds that reached consensus / rounds started
|
|
1359
|
+
rounds_started: set = set()
|
|
1360
|
+
rounds_concluded: set = set()
|
|
1361
|
+
for e in entries:
|
|
1362
|
+
if e.get("action") != "debate_event":
|
|
1363
|
+
continue
|
|
1364
|
+
key = (e.get("plan_id"), e.get("round"))
|
|
1365
|
+
if e.get("phase") == "start":
|
|
1366
|
+
rounds_started.add(key)
|
|
1367
|
+
elif e.get("phase") == "consensus":
|
|
1368
|
+
rounds_concluded.add(key)
|
|
1369
|
+
debate_completion = (
|
|
1370
|
+
len(rounds_concluded) / len(rounds_started) if rounds_started else None
|
|
1371
|
+
)
|
|
1372
|
+
|
|
1373
|
+
# Benchmark duration avg — prefer new duration_ms (÷1000 → s), fall back
|
|
1374
|
+
# to legacy duration_s float for logs written before the migration.
|
|
1375
|
+
durations = []
|
|
1376
|
+
for e in entries:
|
|
1377
|
+
if e.get("action") != "benchmark_run":
|
|
1378
|
+
continue
|
|
1379
|
+
dur_ms = e.get("duration_ms")
|
|
1380
|
+
if dur_ms is not None:
|
|
1381
|
+
durations.append(int(dur_ms) / 1000.0)
|
|
1382
|
+
elif e.get("duration_s") is not None:
|
|
1383
|
+
durations.append(float(e.get("duration_s")))
|
|
1384
|
+
avg_bench_duration = (
|
|
1385
|
+
round(sum(durations) / len(durations), 3) if durations else None
|
|
1386
|
+
)
|
|
1387
|
+
|
|
1388
|
+
return {
|
|
1389
|
+
"action_counts": dict(action_counts),
|
|
1390
|
+
"veto_rate": round(veto_rate, 3) if veto_rate is not None else None,
|
|
1391
|
+
"debate_rounds_started": len(rounds_started),
|
|
1392
|
+
"debate_rounds_concluded": len(rounds_concluded),
|
|
1393
|
+
"debate_completion_rate": (
|
|
1394
|
+
round(debate_completion, 3) if debate_completion is not None else None
|
|
1395
|
+
),
|
|
1396
|
+
"benchmark_run_count": action_counts.get("benchmark_run", 0),
|
|
1397
|
+
"benchmark_avg_duration_s": avg_bench_duration,
|
|
1398
|
+
"lesson_write_count": action_counts.get("lesson_write", 0),
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
|
|
1402
|
+
def cmd_tokens(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
1403
|
+
"""Spawn token aggregates — PLAN-006 Phase 5a (ADR-016).
|
|
1404
|
+
|
|
1405
|
+
Groups `agent_spawn` events by archetype / skill / day and sums
|
|
1406
|
+
`tokens_in` / `tokens_out`. Null fields are counted under
|
|
1407
|
+
`records_without_tokens` so the operator knows adapter coverage.
|
|
1408
|
+
"""
|
|
1409
|
+
per_skill: Dict[str, Dict[str, int]] = {}
|
|
1410
|
+
per_subagent: Dict[str, Dict[str, int]] = {}
|
|
1411
|
+
per_day: Dict[str, Dict[str, int]] = {}
|
|
1412
|
+
total_in = 0
|
|
1413
|
+
total_out = 0
|
|
1414
|
+
with_tokens = 0
|
|
1415
|
+
without_tokens = 0
|
|
1416
|
+
|
|
1417
|
+
for e in entries:
|
|
1418
|
+
if e.get("action") != "agent_spawn":
|
|
1419
|
+
continue
|
|
1420
|
+
tin = e.get("tokens_in")
|
|
1421
|
+
tout = e.get("tokens_out")
|
|
1422
|
+
tin_n = tin if isinstance(tin, int) else 0
|
|
1423
|
+
tout_n = tout if isinstance(tout, int) else 0
|
|
1424
|
+
has_any = isinstance(tin, int) or isinstance(tout, int)
|
|
1425
|
+
if has_any:
|
|
1426
|
+
with_tokens += 1
|
|
1427
|
+
total_in += tin_n
|
|
1428
|
+
total_out += tout_n
|
|
1429
|
+
else:
|
|
1430
|
+
without_tokens += 1
|
|
1431
|
+
|
|
1432
|
+
skill = str(e.get("skill") or "unknown")
|
|
1433
|
+
sk = per_skill.setdefault(skill, {"tokens_in": 0, "tokens_out": 0, "spawns": 0})
|
|
1434
|
+
sk["tokens_in"] += tin_n
|
|
1435
|
+
sk["tokens_out"] += tout_n
|
|
1436
|
+
sk["spawns"] += 1
|
|
1437
|
+
|
|
1438
|
+
sub = str(e.get("subagent_type") or "unknown")
|
|
1439
|
+
su = per_subagent.setdefault(sub, {"tokens_in": 0, "tokens_out": 0, "spawns": 0})
|
|
1440
|
+
su["tokens_in"] += tin_n
|
|
1441
|
+
su["tokens_out"] += tout_n
|
|
1442
|
+
su["spawns"] += 1
|
|
1443
|
+
|
|
1444
|
+
ts = str(e.get("ts") or "")
|
|
1445
|
+
day = ts[:10] if len(ts) >= 10 else "unknown"
|
|
1446
|
+
dy = per_day.setdefault(day, {"tokens_in": 0, "tokens_out": 0, "spawns": 0})
|
|
1447
|
+
dy["tokens_in"] += tin_n
|
|
1448
|
+
dy["tokens_out"] += tout_n
|
|
1449
|
+
dy["spawns"] += 1
|
|
1450
|
+
|
|
1451
|
+
return {
|
|
1452
|
+
"totals": {
|
|
1453
|
+
"tokens_in": total_in,
|
|
1454
|
+
"tokens_out": total_out,
|
|
1455
|
+
"tokens_total": total_in + total_out,
|
|
1456
|
+
"spawns_with_tokens": with_tokens,
|
|
1457
|
+
"spawns_without_tokens": without_tokens,
|
|
1458
|
+
},
|
|
1459
|
+
"per_skill": dict(sorted(per_skill.items())),
|
|
1460
|
+
"per_subagent_type": dict(sorted(per_subagent.items())),
|
|
1461
|
+
"per_day": dict(sorted(per_day.items())),
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
def cmd_health(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
1466
|
+
"""High-level framework health. Sprint 5 A.2.
|
|
1467
|
+
|
|
1468
|
+
Rolls up several gates into an overall PASS/WARN/FAIL verdict so
|
|
1469
|
+
`audit-dashboard.py` and ops tooling have one call to make.
|
|
1470
|
+
"""
|
|
1471
|
+
total = len(entries)
|
|
1472
|
+
if total == 0:
|
|
1473
|
+
return {"verdict": "NO_DATA", "total_events": 0, "gates": {}}
|
|
1474
|
+
|
|
1475
|
+
compliance = cmd_compliance(entries, args)
|
|
1476
|
+
metrics = cmd_metrics(entries, args)
|
|
1477
|
+
|
|
1478
|
+
gates: Dict[str, str] = {}
|
|
1479
|
+
|
|
1480
|
+
# Gate 1: compliance rate >= 0.95
|
|
1481
|
+
known_rate = compliance.get("known_skill_rate")
|
|
1482
|
+
if known_rate is None:
|
|
1483
|
+
gates["compliance"] = "NO_DATA"
|
|
1484
|
+
elif known_rate >= 0.95:
|
|
1485
|
+
gates["compliance"] = "PASS"
|
|
1486
|
+
elif known_rate >= 0.8:
|
|
1487
|
+
gates["compliance"] = "WARN"
|
|
1488
|
+
else:
|
|
1489
|
+
gates["compliance"] = "FAIL"
|
|
1490
|
+
|
|
1491
|
+
# Gate 2: veto rate < 0.15
|
|
1492
|
+
veto_rate = metrics.get("veto_rate")
|
|
1493
|
+
if veto_rate is None:
|
|
1494
|
+
gates["vetoes"] = "NO_DATA"
|
|
1495
|
+
elif veto_rate < 0.05:
|
|
1496
|
+
gates["vetoes"] = "PASS"
|
|
1497
|
+
elif veto_rate < 0.15:
|
|
1498
|
+
gates["vetoes"] = "WARN"
|
|
1499
|
+
else:
|
|
1500
|
+
gates["vetoes"] = "FAIL"
|
|
1501
|
+
|
|
1502
|
+
# Gate 3: debate completion rate >= 0.8 (if any rounds started)
|
|
1503
|
+
dc = metrics.get("debate_completion_rate")
|
|
1504
|
+
if dc is None:
|
|
1505
|
+
gates["debate_completion"] = "NO_DATA"
|
|
1506
|
+
elif dc >= 0.8:
|
|
1507
|
+
gates["debate_completion"] = "PASS"
|
|
1508
|
+
elif dc >= 0.5:
|
|
1509
|
+
gates["debate_completion"] = "WARN"
|
|
1510
|
+
else:
|
|
1511
|
+
gates["debate_completion"] = "FAIL"
|
|
1512
|
+
|
|
1513
|
+
# Verdict reduction
|
|
1514
|
+
if any(v == "FAIL" for v in gates.values()):
|
|
1515
|
+
verdict = "FAIL"
|
|
1516
|
+
elif any(v == "WARN" for v in gates.values()):
|
|
1517
|
+
verdict = "WARN"
|
|
1518
|
+
elif all(v in ("PASS", "NO_DATA") for v in gates.values()):
|
|
1519
|
+
verdict = "PASS"
|
|
1520
|
+
else:
|
|
1521
|
+
verdict = "WARN"
|
|
1522
|
+
|
|
1523
|
+
return {
|
|
1524
|
+
"verdict": verdict,
|
|
1525
|
+
"total_events": total,
|
|
1526
|
+
"gates": gates,
|
|
1527
|
+
"known_skill_rate": known_rate,
|
|
1528
|
+
"veto_rate": veto_rate,
|
|
1529
|
+
"debate_completion_rate": dc,
|
|
1530
|
+
"action_counts": metrics.get("action_counts", {}),
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
def cmd_export(entries: List[Dict[str, Any]], args) -> Any:
|
|
1535
|
+
"""Handle the `audit-query export` sub-command — emit filtered audit-log slices."""
|
|
1536
|
+
fmt = args.export_format
|
|
1537
|
+
if fmt == "json":
|
|
1538
|
+
return entries
|
|
1539
|
+
if fmt == "csv":
|
|
1540
|
+
buf = io.StringIO()
|
|
1541
|
+
if not entries:
|
|
1542
|
+
return ""
|
|
1543
|
+
fieldnames = sorted({k for e in entries for k in e.keys()})
|
|
1544
|
+
writer = csv.DictWriter(buf, fieldnames=fieldnames, extrasaction="ignore")
|
|
1545
|
+
writer.writeheader()
|
|
1546
|
+
for e in entries:
|
|
1547
|
+
writer.writerow(e)
|
|
1548
|
+
return buf.getvalue()
|
|
1549
|
+
if fmt == "tsv":
|
|
1550
|
+
buf = io.StringIO()
|
|
1551
|
+
if not entries:
|
|
1552
|
+
return ""
|
|
1553
|
+
fieldnames = sorted({k for e in entries for k in e.keys()})
|
|
1554
|
+
writer = csv.DictWriter(
|
|
1555
|
+
buf, fieldnames=fieldnames, extrasaction="ignore", delimiter="\t"
|
|
1556
|
+
)
|
|
1557
|
+
writer.writeheader()
|
|
1558
|
+
for e in entries:
|
|
1559
|
+
writer.writerow(e)
|
|
1560
|
+
return buf.getvalue()
|
|
1561
|
+
raise ValueError(f"unsupported export format: {fmt}")
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
# ---------------------------------------------------------------------------
|
|
1565
|
+
# PLAN-080 Phase 1 — by-domain sub-command
|
|
1566
|
+
# ---------------------------------------------------------------------------
|
|
1567
|
+
|
|
1568
|
+
# Default window for by-domain (calendar days, trailing from UTC midnight)
|
|
1569
|
+
_BY_DOMAIN_DEFAULT_WINDOW_DAYS = 30
|
|
1570
|
+
|
|
1571
|
+
# Sentinel used when no dispatch_archetype_hint is available
|
|
1572
|
+
_UNKNOWN_BUCKET = "UNKNOWN"
|
|
1573
|
+
|
|
1574
|
+
# Default policy file location (relative to repo root, resolved at runtime)
|
|
1575
|
+
_GRANDFATHER_POLICY_RELPATH = ".claude/policies/grandfather-cap.policy.yaml"
|
|
1576
|
+
|
|
1577
|
+
|
|
1578
|
+
def _parse_domain_date(raw: str) -> Optional[datetime]:
|
|
1579
|
+
"""Parse YYYY-MM-DD into a UTC-aware datetime (midnight UTC).
|
|
1580
|
+
|
|
1581
|
+
Returns None on failure (not sys.exit — caller handles the error).
|
|
1582
|
+
"""
|
|
1583
|
+
try:
|
|
1584
|
+
dt = datetime.strptime(raw.strip(), "%Y-%m-%d")
|
|
1585
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
1586
|
+
except ValueError:
|
|
1587
|
+
return None
|
|
1588
|
+
|
|
1589
|
+
|
|
1590
|
+
def _resolve_window_bounds(
|
|
1591
|
+
args,
|
|
1592
|
+
) -> Tuple[Optional[datetime], Optional[datetime], str]:
|
|
1593
|
+
"""Resolve --window / --start / --end into (start_dt, end_dt, window_label).
|
|
1594
|
+
|
|
1595
|
+
Returns (start_dt, end_dt, label) where:
|
|
1596
|
+
- start_dt: inclusive lower bound (UTC midnight); None means no lower bound
|
|
1597
|
+
- end_dt: inclusive upper bound (UTC now for default windows)
|
|
1598
|
+
- label: human-readable description of the window
|
|
1599
|
+
|
|
1600
|
+
Validates start <= end; prints error + sys.exit(1) on invalid input.
|
|
1601
|
+
"""
|
|
1602
|
+
now = datetime.now(timezone.utc)
|
|
1603
|
+
# --start / --end take precedence over --window
|
|
1604
|
+
start_raw = getattr(args, "start", None)
|
|
1605
|
+
end_raw = getattr(args, "end", None)
|
|
1606
|
+
window_raw = getattr(args, "window", None) or f"{_BY_DOMAIN_DEFAULT_WINDOW_DAYS}d"
|
|
1607
|
+
|
|
1608
|
+
if start_raw or end_raw:
|
|
1609
|
+
if not start_raw or not end_raw:
|
|
1610
|
+
print(
|
|
1611
|
+
"[audit-query] ERROR: by-domain: --start and --end must both be provided",
|
|
1612
|
+
file=sys.stderr,
|
|
1613
|
+
)
|
|
1614
|
+
sys.exit(1)
|
|
1615
|
+
start_dt = _parse_domain_date(start_raw)
|
|
1616
|
+
end_dt = _parse_domain_date(end_raw)
|
|
1617
|
+
if start_dt is None:
|
|
1618
|
+
print(
|
|
1619
|
+
f"[audit-query] ERROR: by-domain: invalid --start date {start_raw!r} "
|
|
1620
|
+
"(use YYYY-MM-DD)",
|
|
1621
|
+
file=sys.stderr,
|
|
1622
|
+
)
|
|
1623
|
+
sys.exit(1)
|
|
1624
|
+
if end_dt is None:
|
|
1625
|
+
print(
|
|
1626
|
+
f"[audit-query] ERROR: by-domain: invalid --end date {end_raw!r} "
|
|
1627
|
+
"(use YYYY-MM-DD)",
|
|
1628
|
+
file=sys.stderr,
|
|
1629
|
+
)
|
|
1630
|
+
sys.exit(1)
|
|
1631
|
+
# Make end_dt the end of that calendar day (23:59:59 UTC)
|
|
1632
|
+
end_dt = end_dt.replace(hour=23, minute=59, second=59)
|
|
1633
|
+
if start_dt > end_dt:
|
|
1634
|
+
print(
|
|
1635
|
+
f"[audit-query] ERROR: by-domain: --start ({start_raw}) is after "
|
|
1636
|
+
f"--end ({end_raw})",
|
|
1637
|
+
file=sys.stderr,
|
|
1638
|
+
)
|
|
1639
|
+
sys.exit(1)
|
|
1640
|
+
label = f"{start_raw} to {end_raw}"
|
|
1641
|
+
return start_dt, end_dt, label
|
|
1642
|
+
|
|
1643
|
+
# Parse --window (e.g. "30d", "7d")
|
|
1644
|
+
m = re.fullmatch(r"(\d+)([dhm])", window_raw.strip().lower())
|
|
1645
|
+
if not m:
|
|
1646
|
+
print(
|
|
1647
|
+
f"[audit-query] ERROR: by-domain: invalid --window {window_raw!r}; "
|
|
1648
|
+
"expected e.g. 30d, 7d, 14d",
|
|
1649
|
+
file=sys.stderr,
|
|
1650
|
+
)
|
|
1651
|
+
sys.exit(1)
|
|
1652
|
+
n, unit = int(m.group(1)), m.group(2)
|
|
1653
|
+
delta = {"d": timedelta(days=n), "h": timedelta(hours=n), "m": timedelta(minutes=n)}[unit]
|
|
1654
|
+
start_dt = now - delta
|
|
1655
|
+
label = f"trailing {window_raw}"
|
|
1656
|
+
return start_dt, now, label
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
def _load_sunset_domains(args) -> Optional[List[str]]:
|
|
1660
|
+
"""Load sunset domain list for --check-reopen.
|
|
1661
|
+
|
|
1662
|
+
Sources (in priority order):
|
|
1663
|
+
1. CEO_GRANDFATHER_POLICY_PATH env var
|
|
1664
|
+
2. <repo_root>/.claude/policies/grandfather-cap.policy.yaml (if exists)
|
|
1665
|
+
3. stdin JSON list (if CEO_BY_DOMAIN_SUNSET_STDIN=1)
|
|
1666
|
+
|
|
1667
|
+
Returns a list of domain slugs, or None if the policy file is not found
|
|
1668
|
+
and stdin mode is not active. An empty list [] means "policy found but
|
|
1669
|
+
no sunset members".
|
|
1670
|
+
"""
|
|
1671
|
+
# Check env override
|
|
1672
|
+
policy_path_env = os.environ.get("CEO_GRANDFATHER_POLICY_PATH")
|
|
1673
|
+
if policy_path_env:
|
|
1674
|
+
p = Path(policy_path_env)
|
|
1675
|
+
if p.is_file():
|
|
1676
|
+
return _parse_sunset_from_policy(p)
|
|
1677
|
+
print(
|
|
1678
|
+
f"[audit-query] WARN: CEO_GRANDFATHER_POLICY_PATH={policy_path_env!r} "
|
|
1679
|
+
"not found; no sunset list loaded",
|
|
1680
|
+
file=sys.stderr,
|
|
1681
|
+
)
|
|
1682
|
+
return []
|
|
1683
|
+
|
|
1684
|
+
# Try conventional project-relative path via CLAUDE_PROJECT_DIR or cwd
|
|
1685
|
+
for base_env in ("CLAUDE_PROJECT_DIR",):
|
|
1686
|
+
base = os.environ.get(base_env)
|
|
1687
|
+
if base:
|
|
1688
|
+
p = Path(base) / _GRANDFATHER_POLICY_RELPATH
|
|
1689
|
+
if p.is_file():
|
|
1690
|
+
return _parse_sunset_from_policy(p)
|
|
1691
|
+
|
|
1692
|
+
# Try cwd-relative (developer convenience)
|
|
1693
|
+
p = Path(_GRANDFATHER_POLICY_RELPATH)
|
|
1694
|
+
if p.is_file():
|
|
1695
|
+
return _parse_sunset_from_policy(p)
|
|
1696
|
+
|
|
1697
|
+
# Stdin JSON fallback (CEO_BY_DOMAIN_SUNSET_STDIN=1 for testing)
|
|
1698
|
+
if os.environ.get("CEO_BY_DOMAIN_SUNSET_STDIN") == "1":
|
|
1699
|
+
try:
|
|
1700
|
+
raw = sys.stdin.read()
|
|
1701
|
+
data = json.loads(raw)
|
|
1702
|
+
if isinstance(data, list):
|
|
1703
|
+
return [str(x) for x in data if x]
|
|
1704
|
+
except (json.JSONDecodeError, OSError):
|
|
1705
|
+
pass
|
|
1706
|
+
return []
|
|
1707
|
+
|
|
1708
|
+
return None # policy not found — caller will skip reopen check
|
|
1709
|
+
|
|
1710
|
+
|
|
1711
|
+
def _parse_sunset_from_policy(path: Path) -> List[str]:
|
|
1712
|
+
"""Extract sunset domain members from grandfather-cap.policy.yaml.
|
|
1713
|
+
|
|
1714
|
+
Parses the YAML manually using stdlib (no PyYAML dependency).
|
|
1715
|
+
Looks for `domain_bundles.members:` list block.
|
|
1716
|
+
|
|
1717
|
+
Returns list of domain slug strings (may be empty).
|
|
1718
|
+
"""
|
|
1719
|
+
try:
|
|
1720
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
1721
|
+
except OSError:
|
|
1722
|
+
return []
|
|
1723
|
+
|
|
1724
|
+
# Simple line-based YAML parser for the members list.
|
|
1725
|
+
# We scan for "members:" under "domain_bundles:" section.
|
|
1726
|
+
in_domain_bundles = False
|
|
1727
|
+
in_members = False
|
|
1728
|
+
members: List[str] = []
|
|
1729
|
+
|
|
1730
|
+
for line in text.splitlines():
|
|
1731
|
+
stripped = line.strip()
|
|
1732
|
+
if stripped.startswith("#"):
|
|
1733
|
+
continue
|
|
1734
|
+
if stripped == "domain_bundles:":
|
|
1735
|
+
in_domain_bundles = True
|
|
1736
|
+
in_members = False
|
|
1737
|
+
continue
|
|
1738
|
+
if in_domain_bundles:
|
|
1739
|
+
# Another top-level key → leave domain_bundles section
|
|
1740
|
+
if stripped and not line.startswith(" ") and not line.startswith("\t"):
|
|
1741
|
+
in_domain_bundles = False
|
|
1742
|
+
in_members = False
|
|
1743
|
+
continue
|
|
1744
|
+
if stripped == "members:":
|
|
1745
|
+
in_members = True
|
|
1746
|
+
continue
|
|
1747
|
+
if in_members:
|
|
1748
|
+
if stripped.startswith("- "):
|
|
1749
|
+
member = stripped[2:].strip()
|
|
1750
|
+
if member:
|
|
1751
|
+
members.append(member)
|
|
1752
|
+
elif stripped and not stripped.startswith("-"):
|
|
1753
|
+
# End of list
|
|
1754
|
+
in_members = False
|
|
1755
|
+
|
|
1756
|
+
return members
|
|
1757
|
+
|
|
1758
|
+
|
|
1759
|
+
def _load_sunset_reopen_options(args) -> Optional[Dict[str, bool]]:
|
|
1760
|
+
"""M2-CDX-4 + M2-CDX-7 (Codex Phase 1 iter 1) — load reopen filter flags.
|
|
1761
|
+
|
|
1762
|
+
Returns dict with `requires_hint_match` and `unknown_excluded` boolean
|
|
1763
|
+
flags read from the same grandfather-cap.policy.yaml as
|
|
1764
|
+
`_load_sunset_domains`. Defaults to {True, True} if either flag is
|
|
1765
|
+
missing/unparseable. Returns None if the policy file is not found.
|
|
1766
|
+
"""
|
|
1767
|
+
# Reuse the same path resolution as _load_sunset_domains for consistency
|
|
1768
|
+
policy_path_env = os.environ.get("CEO_GRANDFATHER_POLICY_PATH")
|
|
1769
|
+
candidates: List[Path] = []
|
|
1770
|
+
if policy_path_env:
|
|
1771
|
+
candidates.append(Path(policy_path_env))
|
|
1772
|
+
base = os.environ.get("CLAUDE_PROJECT_DIR")
|
|
1773
|
+
if base:
|
|
1774
|
+
candidates.append(Path(base) / _GRANDFATHER_POLICY_RELPATH)
|
|
1775
|
+
candidates.append(Path(_GRANDFATHER_POLICY_RELPATH))
|
|
1776
|
+
|
|
1777
|
+
for p in candidates:
|
|
1778
|
+
if not p.is_file():
|
|
1779
|
+
continue
|
|
1780
|
+
try:
|
|
1781
|
+
text = p.read_text(encoding="utf-8", errors="replace")
|
|
1782
|
+
except OSError:
|
|
1783
|
+
continue
|
|
1784
|
+
return _parse_sunset_reopen_flags(text)
|
|
1785
|
+
return None
|
|
1786
|
+
|
|
1787
|
+
|
|
1788
|
+
def _parse_sunset_reopen_flags(text: str) -> Dict[str, bool]:
|
|
1789
|
+
"""Parse `sunset_reopen_requires_hint_match` and `sunset_reopen_unknown_excluded`.
|
|
1790
|
+
|
|
1791
|
+
Both flags default to True if missing (defensive — secure-by-default).
|
|
1792
|
+
"""
|
|
1793
|
+
requires_hint_match = True
|
|
1794
|
+
unknown_excluded = True
|
|
1795
|
+
for line in text.splitlines():
|
|
1796
|
+
s = line.strip()
|
|
1797
|
+
if s.startswith("#"):
|
|
1798
|
+
continue
|
|
1799
|
+
if s.startswith("sunset_reopen_requires_hint_match:"):
|
|
1800
|
+
v = s.split(":", 1)[1].strip().lower()
|
|
1801
|
+
requires_hint_match = (v == "true" or v.startswith("true"))
|
|
1802
|
+
elif s.startswith("sunset_reopen_unknown_excluded:"):
|
|
1803
|
+
v = s.split(":", 1)[1].strip().lower()
|
|
1804
|
+
unknown_excluded = (v == "true" or v.startswith("true"))
|
|
1805
|
+
return {
|
|
1806
|
+
"requires_hint_match": requires_hint_match,
|
|
1807
|
+
"unknown_excluded": unknown_excluded,
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
|
|
1811
|
+
def _entry_ts_to_dt(ts: str) -> Optional[datetime]:
|
|
1812
|
+
"""Parse a log entry `ts` field into a UTC datetime."""
|
|
1813
|
+
if not ts:
|
|
1814
|
+
return None
|
|
1815
|
+
try:
|
|
1816
|
+
s = ts[:-1] + "+00:00" if ts.endswith("Z") else ts
|
|
1817
|
+
dt = datetime.fromisoformat(s)
|
|
1818
|
+
return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)
|
|
1819
|
+
except ValueError:
|
|
1820
|
+
return None
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
def cmd_by_domain(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
1824
|
+
"""`audit-query by-domain` — spawn activity grouped by dispatch_archetype_hint.
|
|
1825
|
+
|
|
1826
|
+
PLAN-080 Phase 1 — domain-level observability for squad-bundle governance.
|
|
1827
|
+
|
|
1828
|
+
Groups `agent_spawn` events by `dispatch_archetype_hint` field if present;
|
|
1829
|
+
falls back to `archetype` field, then to the "UNKNOWN" bucket. Entries
|
|
1830
|
+
outside the requested time window are excluded.
|
|
1831
|
+
|
|
1832
|
+
Output columns:
|
|
1833
|
+
Domain | Spawns | First seen | Last seen | hint_coverage_pct
|
|
1834
|
+
|
|
1835
|
+
Sorted deterministically by domain name (alphabetic, UNKNOWN last).
|
|
1836
|
+
|
|
1837
|
+
With --check-reopen: additionally filters to domains present in the sunset
|
|
1838
|
+
list from grandfather-cap.policy.yaml. Reports domains with >= 1 spawn
|
|
1839
|
+
where hint matches a sunset domain (UNKNOWN excluded per M2-CDX-7).
|
|
1840
|
+
"""
|
|
1841
|
+
start_dt, end_dt, window_label = _resolve_window_bounds(args)
|
|
1842
|
+
|
|
1843
|
+
# Load sunset list + reopen options if --check-reopen requested
|
|
1844
|
+
check_reopen = getattr(args, "check_reopen", False)
|
|
1845
|
+
sunset_domains: Optional[List[str]] = None
|
|
1846
|
+
# M2-CDX-4 + M2-CDX-7 (Codex Phase 1 iter 1): honor policy flags.
|
|
1847
|
+
# Defaults match policy semantics when flags missing/unparseable.
|
|
1848
|
+
requires_hint_match = True # sunset_reopen_requires_hint_match (default true per §8)
|
|
1849
|
+
unknown_excluded = True # sunset_reopen_unknown_excluded (M2-CDX-7 default true)
|
|
1850
|
+
if check_reopen:
|
|
1851
|
+
sunset_domains = _load_sunset_domains(args)
|
|
1852
|
+
sunset_options = _load_sunset_reopen_options(args)
|
|
1853
|
+
if sunset_options is not None:
|
|
1854
|
+
requires_hint_match = sunset_options.get("requires_hint_match", True)
|
|
1855
|
+
unknown_excluded = sunset_options.get("unknown_excluded", True)
|
|
1856
|
+
if sunset_domains is None:
|
|
1857
|
+
print(
|
|
1858
|
+
"[audit-query] WARN: by-domain --check-reopen: no sunset policy file "
|
|
1859
|
+
"found; skipping reopen filter. Set CEO_GRANDFATHER_POLICY_PATH or "
|
|
1860
|
+
"place grandfather-cap.policy.yaml at "
|
|
1861
|
+
f"{_GRANDFATHER_POLICY_RELPATH}",
|
|
1862
|
+
file=sys.stderr,
|
|
1863
|
+
)
|
|
1864
|
+
|
|
1865
|
+
# Aggregation state per domain bucket
|
|
1866
|
+
# domain → {spawns, first_ts, last_ts, with_hint, total}
|
|
1867
|
+
domain_agg: Dict[str, Dict[str, Any]] = {}
|
|
1868
|
+
|
|
1869
|
+
total_in_window = 0
|
|
1870
|
+
total_with_hint = 0
|
|
1871
|
+
|
|
1872
|
+
for e in entries:
|
|
1873
|
+
if e.get("action") != "agent_spawn":
|
|
1874
|
+
continue
|
|
1875
|
+
|
|
1876
|
+
ts_raw = e.get("ts", "")
|
|
1877
|
+
entry_dt = _entry_ts_to_dt(ts_raw)
|
|
1878
|
+
|
|
1879
|
+
# Apply window filter
|
|
1880
|
+
if start_dt is not None and entry_dt is not None:
|
|
1881
|
+
if entry_dt < start_dt:
|
|
1882
|
+
continue
|
|
1883
|
+
if end_dt is not None and entry_dt is not None:
|
|
1884
|
+
if entry_dt > end_dt:
|
|
1885
|
+
continue
|
|
1886
|
+
|
|
1887
|
+
total_in_window += 1
|
|
1888
|
+
|
|
1889
|
+
# Determine domain bucket
|
|
1890
|
+
hint = e.get("dispatch_archetype_hint")
|
|
1891
|
+
if hint and isinstance(hint, str) and hint.strip():
|
|
1892
|
+
domain = hint.strip()
|
|
1893
|
+
has_hint = True
|
|
1894
|
+
total_with_hint += 1
|
|
1895
|
+
else:
|
|
1896
|
+
# Fallback: archetype field
|
|
1897
|
+
arch = e.get("archetype")
|
|
1898
|
+
if arch and isinstance(arch, str) and arch.strip():
|
|
1899
|
+
domain = arch.strip()
|
|
1900
|
+
else:
|
|
1901
|
+
domain = _UNKNOWN_BUCKET
|
|
1902
|
+
has_hint = False
|
|
1903
|
+
|
|
1904
|
+
bucket = domain_agg.setdefault(
|
|
1905
|
+
domain,
|
|
1906
|
+
{
|
|
1907
|
+
"spawns": 0,
|
|
1908
|
+
"first_ts": ts_raw or "",
|
|
1909
|
+
"last_ts": ts_raw or "",
|
|
1910
|
+
"with_hint": 0,
|
|
1911
|
+
"total": 0,
|
|
1912
|
+
},
|
|
1913
|
+
)
|
|
1914
|
+
bucket["spawns"] += 1
|
|
1915
|
+
bucket["total"] += 1
|
|
1916
|
+
if has_hint:
|
|
1917
|
+
bucket["with_hint"] += 1
|
|
1918
|
+
if ts_raw:
|
|
1919
|
+
if not bucket["first_ts"] or ts_raw < bucket["first_ts"]:
|
|
1920
|
+
bucket["first_ts"] = ts_raw
|
|
1921
|
+
if ts_raw > bucket["last_ts"]:
|
|
1922
|
+
bucket["last_ts"] = ts_raw
|
|
1923
|
+
|
|
1924
|
+
# Build output rows
|
|
1925
|
+
rows: List[Dict[str, Any]] = []
|
|
1926
|
+
for domain in sorted(domain_agg.keys(), key=lambda d: ("" if d != _UNKNOWN_BUCKET else "\xff") + d):
|
|
1927
|
+
b = domain_agg[domain]
|
|
1928
|
+
hint_pct = round(b["with_hint"] / b["total"] * 100, 1) if b["total"] > 0 else 0.0
|
|
1929
|
+
row: Dict[str, Any] = {
|
|
1930
|
+
"domain": domain,
|
|
1931
|
+
"spawns": b["spawns"],
|
|
1932
|
+
"first_seen": b["first_ts"][:10] if b["first_ts"] else "",
|
|
1933
|
+
"last_seen": b["last_ts"][:10] if b["last_ts"] else "",
|
|
1934
|
+
"hint_coverage_pct": hint_pct,
|
|
1935
|
+
}
|
|
1936
|
+
rows.append(row)
|
|
1937
|
+
|
|
1938
|
+
# Apply --check-reopen filter
|
|
1939
|
+
# M2-CDX-7: UNKNOWN excluded when sunset_reopen_unknown_excluded=true (default)
|
|
1940
|
+
# M2-CDX-4: When sunset_reopen_requires_hint_match=true (default), only
|
|
1941
|
+
# spawns whose ORIGINAL audit row carried `dispatch_archetype_hint`
|
|
1942
|
+
# (has_hint=True) qualify for reopen. archetype-fallback rows do not
|
|
1943
|
+
# trigger reopen (per PLAN-080 §8: "spawn carries dispatch_archetype_hint
|
|
1944
|
+
# matching the sunset domain's archetype set").
|
|
1945
|
+
if check_reopen and sunset_domains is not None:
|
|
1946
|
+
sunset_set = set(sunset_domains)
|
|
1947
|
+
reopen_rows = []
|
|
1948
|
+
for r in rows:
|
|
1949
|
+
if unknown_excluded and r["domain"] == _UNKNOWN_BUCKET:
|
|
1950
|
+
continue
|
|
1951
|
+
if r["domain"] not in sunset_set:
|
|
1952
|
+
continue
|
|
1953
|
+
if r["spawns"] < 1:
|
|
1954
|
+
continue
|
|
1955
|
+
if requires_hint_match:
|
|
1956
|
+
# Reopen requires at least one hint-source spawn matching this
|
|
1957
|
+
# sunset domain. Use the per-bucket `with_hint` count from the
|
|
1958
|
+
# aggregation — bucket["with_hint"] tracks how many spawns in
|
|
1959
|
+
# this domain bucket arrived with dispatch_archetype_hint set.
|
|
1960
|
+
bucket = domain_agg.get(r["domain"], {})
|
|
1961
|
+
if int(bucket.get("with_hint", 0)) < 1:
|
|
1962
|
+
continue
|
|
1963
|
+
reopen_rows.append(r)
|
|
1964
|
+
else:
|
|
1965
|
+
reopen_rows = []
|
|
1966
|
+
|
|
1967
|
+
# Build markdown table
|
|
1968
|
+
def _md_table(table_rows: List[Dict[str, Any]]) -> str:
|
|
1969
|
+
if not table_rows:
|
|
1970
|
+
return "| Domain | Spawns | First seen | Last seen | hint_coverage_pct |\n" \
|
|
1971
|
+
"| ------ | ------ | ---------- | --------- | ----------------- |\n" \
|
|
1972
|
+
"| (no results) | - | - | - | - |\n"
|
|
1973
|
+
lines = [
|
|
1974
|
+
"| Domain | Spawns | First seen | Last seen | hint_coverage_pct |",
|
|
1975
|
+
"| ------ | ------ | ---------- | --------- | ----------------- |",
|
|
1976
|
+
]
|
|
1977
|
+
for r in table_rows:
|
|
1978
|
+
lines.append(
|
|
1979
|
+
f"| {r['domain']} | {r['spawns']} | {r['first_seen']} "
|
|
1980
|
+
f"| {r['last_seen']} | {r['hint_coverage_pct']}% |"
|
|
1981
|
+
)
|
|
1982
|
+
return "\n".join(lines) + "\n"
|
|
1983
|
+
|
|
1984
|
+
output: Dict[str, Any] = {
|
|
1985
|
+
"query": "by-domain",
|
|
1986
|
+
"version": "1",
|
|
1987
|
+
"window": window_label,
|
|
1988
|
+
"total_spawns_in_window": total_in_window,
|
|
1989
|
+
"overall_hint_coverage_pct": (
|
|
1990
|
+
round(total_with_hint / total_in_window * 100, 1)
|
|
1991
|
+
if total_in_window > 0
|
|
1992
|
+
else 0.0
|
|
1993
|
+
),
|
|
1994
|
+
"domain_count": len(rows),
|
|
1995
|
+
"domains": rows,
|
|
1996
|
+
"markdown_table": _md_table(rows),
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
if check_reopen:
|
|
2000
|
+
output["check_reopen"] = {
|
|
2001
|
+
"sunset_domains_loaded": len(sunset_domains) if sunset_domains is not None else 0,
|
|
2002
|
+
"reopen_candidates": reopen_rows,
|
|
2003
|
+
"reopen_count": len(reopen_rows),
|
|
2004
|
+
"note": (
|
|
2005
|
+
"Domains from sunset list with >= 1 spawn in window. "
|
|
2006
|
+
"UNKNOWN bucket excluded per M2-CDX-7."
|
|
2007
|
+
),
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
return output
|
|
2011
|
+
|
|
2012
|
+
|
|
2013
|
+
# ---------------------------------------------------------------------------
|
|
2014
|
+
# Output formatting
|
|
2015
|
+
# ---------------------------------------------------------------------------
|
|
2016
|
+
|
|
2017
|
+
|
|
2018
|
+
def render(result: Any, *, as_json: bool, as_csv: bool) -> str:
|
|
2019
|
+
"""Render query results into the requested output format (text / json / markdown)."""
|
|
2020
|
+
if as_json:
|
|
2021
|
+
return json.dumps(result, indent=2, ensure_ascii=True)
|
|
2022
|
+
if as_csv and isinstance(result, list) and result and isinstance(result[0], dict):
|
|
2023
|
+
buf = io.StringIO()
|
|
2024
|
+
fieldnames = sorted({k for e in result for k in e.keys()})
|
|
2025
|
+
writer = csv.DictWriter(buf, fieldnames=fieldnames, extrasaction="ignore")
|
|
2026
|
+
writer.writeheader()
|
|
2027
|
+
for e in result:
|
|
2028
|
+
writer.writerow(e)
|
|
2029
|
+
return buf.getvalue()
|
|
2030
|
+
|
|
2031
|
+
# Default: pretty-print
|
|
2032
|
+
if isinstance(result, dict):
|
|
2033
|
+
# by-domain: prefer the embedded markdown table for human output
|
|
2034
|
+
if result.get("query") == "by-domain" and "markdown_table" in result:
|
|
2035
|
+
lines = [
|
|
2036
|
+
f"window: {result.get('window', '')}",
|
|
2037
|
+
f"total_spawns: {result.get('total_spawns_in_window', 0)}",
|
|
2038
|
+
f"overall_hint_coverage: {result.get('overall_hint_coverage_pct', 0)}%",
|
|
2039
|
+
f"domain_count: {result.get('domain_count', 0)}",
|
|
2040
|
+
"",
|
|
2041
|
+
result["markdown_table"],
|
|
2042
|
+
]
|
|
2043
|
+
if "check_reopen" in result:
|
|
2044
|
+
cr = result["check_reopen"]
|
|
2045
|
+
lines += [
|
|
2046
|
+
"--- Reopen check ---",
|
|
2047
|
+
f"sunset_domains_loaded: {cr.get('sunset_domains_loaded', 0)}",
|
|
2048
|
+
f"reopen_candidates: {cr.get('reopen_count', 0)}",
|
|
2049
|
+
]
|
|
2050
|
+
for row in cr.get("reopen_candidates", []):
|
|
2051
|
+
lines.append(f" * {row['domain']} ({row['spawns']} spawns)")
|
|
2052
|
+
return "\n".join(lines)
|
|
2053
|
+
return _format_dict(result)
|
|
2054
|
+
if isinstance(result, list):
|
|
2055
|
+
if not result:
|
|
2056
|
+
return "(no results)"
|
|
2057
|
+
if isinstance(result[0], dict):
|
|
2058
|
+
return _format_table(result)
|
|
2059
|
+
return "\n".join(str(x) for x in result)
|
|
2060
|
+
return str(result)
|
|
2061
|
+
|
|
2062
|
+
|
|
2063
|
+
def _format_dict(d: Dict[str, Any]) -> str:
|
|
2064
|
+
lines = []
|
|
2065
|
+
for key, value in d.items():
|
|
2066
|
+
if isinstance(value, (list, dict)):
|
|
2067
|
+
lines.append(f"{key}:")
|
|
2068
|
+
sub = _format_dict(value) if isinstance(value, dict) else _format_list_under(value)
|
|
2069
|
+
for ln in sub.split("\n"):
|
|
2070
|
+
lines.append(f" {ln}")
|
|
2071
|
+
else:
|
|
2072
|
+
lines.append(f"{key}: {value}")
|
|
2073
|
+
return "\n".join(lines)
|
|
2074
|
+
|
|
2075
|
+
|
|
2076
|
+
def _format_list_under(items: List[Any]) -> str:
|
|
2077
|
+
if not items:
|
|
2078
|
+
return "(empty)"
|
|
2079
|
+
if isinstance(items[0], dict):
|
|
2080
|
+
return _format_table(items)
|
|
2081
|
+
return "\n".join(f"- {x}" for x in items)
|
|
2082
|
+
|
|
2083
|
+
|
|
2084
|
+
def _format_table(rows: List[Dict[str, Any]]) -> str:
|
|
2085
|
+
if not rows:
|
|
2086
|
+
return "(no rows)"
|
|
2087
|
+
fieldnames = list(rows[0].keys())
|
|
2088
|
+
# Compute column widths
|
|
2089
|
+
widths = {f: len(f) for f in fieldnames}
|
|
2090
|
+
str_rows = []
|
|
2091
|
+
for r in rows:
|
|
2092
|
+
str_row = {}
|
|
2093
|
+
for f in fieldnames:
|
|
2094
|
+
s = str(r.get(f, ""))
|
|
2095
|
+
str_row[f] = s
|
|
2096
|
+
if len(s) > widths[f]:
|
|
2097
|
+
widths[f] = min(len(s), 80)
|
|
2098
|
+
str_rows.append(str_row)
|
|
2099
|
+
header = " | ".join(f.ljust(widths[f]) for f in fieldnames)
|
|
2100
|
+
sep = "-+-".join("-" * widths[f] for f in fieldnames)
|
|
2101
|
+
body = "\n".join(
|
|
2102
|
+
" | ".join(str(r[f])[: widths[f]].ljust(widths[f]) for f in fieldnames)
|
|
2103
|
+
for r in str_rows
|
|
2104
|
+
)
|
|
2105
|
+
return f"{header}\n{sep}\n{body}"
|
|
2106
|
+
|
|
2107
|
+
|
|
2108
|
+
# ---------------------------------------------------------------------------
|
|
2109
|
+
# Argument parsing
|
|
2110
|
+
# ---------------------------------------------------------------------------
|
|
2111
|
+
|
|
2112
|
+
|
|
2113
|
+
# ---------------------------------------------------------------------------
|
|
2114
|
+
# PLAN-081 Phase 6-bis — Pair-Rail label store + fp-rate aggregator + Codex
|
|
2115
|
+
# writeguard summary (R1 S-TDE-3 + S-TDE Q2). Owner labels Case B verdicts
|
|
2116
|
+
# post-hoc; fp-rate computes lower/upper bounds; codex-writeguard-summary
|
|
2117
|
+
# aggregates deny-list hits.
|
|
2118
|
+
# ---------------------------------------------------------------------------
|
|
2119
|
+
|
|
2120
|
+
_LABEL_STORE_PATH = (
|
|
2121
|
+
Path(__file__).resolve().parent / "audit-log-labels.jsonl"
|
|
2122
|
+
)
|
|
2123
|
+
# ADR-108 §Operational labeling protocol: Owner labels Case-B verdicts with
|
|
2124
|
+
# fp (false-positive — Codex was wrong; close as advisory), tp (true-positive
|
|
2125
|
+
# — block stands), or triage_pending (extends grace by 24h; max 1 extension
|
|
2126
|
+
# before mechanical close-as-advisory). retracted is added for explicit Owner
|
|
2127
|
+
# revocation of a prior label (creates a new chain entry that supersedes).
|
|
2128
|
+
_LABEL_VALID_CASES = frozenset(["A", "B", "C", "D", "E", "F"])
|
|
2129
|
+
_LABEL_VALID_LABELS = frozenset(["fp", "tp", "triage_pending", "retracted"])
|
|
2130
|
+
|
|
2131
|
+
|
|
2132
|
+
def _label_store_path() -> Path:
|
|
2133
|
+
"""Return the labels jsonl path; env override for tests."""
|
|
2134
|
+
override = os.environ.get("CEO_PAIR_RAIL_LABEL_STORE_PATH")
|
|
2135
|
+
return Path(override) if override else _LABEL_STORE_PATH
|
|
2136
|
+
|
|
2137
|
+
|
|
2138
|
+
def _canonical_json_for_chain(record: Dict[str, Any]) -> str:
|
|
2139
|
+
"""Canonical JSON for HMAC chain — sort keys + no whitespace + UTF-8."""
|
|
2140
|
+
return json.dumps(record, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
|
2141
|
+
|
|
2142
|
+
|
|
2143
|
+
def _compute_label_hmac(prev_hmac_hex: str, record: Dict[str, Any]) -> str:
|
|
2144
|
+
"""Compute HMAC-SHA256 over (prev_hmac || canonical_record) using
|
|
2145
|
+
the same key as `audit_hmac.get_or_create_key()`. Each record's
|
|
2146
|
+
`hmac` field links to the previous record's hmac forming a chain.
|
|
2147
|
+
|
|
2148
|
+
Hexlified output for jsonl-friendly storage.
|
|
2149
|
+
"""
|
|
2150
|
+
import hmac as _hmac
|
|
2151
|
+
import hashlib as _hashlib
|
|
2152
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "hooks" / "_lib"))
|
|
2153
|
+
try:
|
|
2154
|
+
import audit_hmac # type: ignore
|
|
2155
|
+
key = audit_hmac.get_or_create_key()
|
|
2156
|
+
except Exception:
|
|
2157
|
+
# Fallback: deterministic per-project SHA-256 chain when key infra
|
|
2158
|
+
# unavailable (e.g. fresh adopter clone). This still provides
|
|
2159
|
+
# tamper-evidence (mutating an old record breaks subsequent chain
|
|
2160
|
+
# verification) but is not authenticated against an external key.
|
|
2161
|
+
key = b"ceo-pair-rail-label-chain-fallback-key-v1"
|
|
2162
|
+
msg = (prev_hmac_hex + _canonical_json_for_chain(record)).encode("utf-8")
|
|
2163
|
+
return _hmac.new(key, msg, _hashlib.sha256).hexdigest()
|
|
2164
|
+
|
|
2165
|
+
|
|
2166
|
+
def _load_label_records() -> List[Dict[str, Any]]:
|
|
2167
|
+
"""Read label jsonl + verify HMAC chain. Returns list of records or [].
|
|
2168
|
+
|
|
2169
|
+
On HMAC mismatch, raises ValueError with the offending record index.
|
|
2170
|
+
"""
|
|
2171
|
+
p = _label_store_path()
|
|
2172
|
+
if not p.exists():
|
|
2173
|
+
return []
|
|
2174
|
+
records: List[Dict[str, Any]] = []
|
|
2175
|
+
prev_hmac = "" # genesis
|
|
2176
|
+
with p.open("r", encoding="utf-8") as fh:
|
|
2177
|
+
for idx, line in enumerate(fh):
|
|
2178
|
+
line = line.strip()
|
|
2179
|
+
if not line:
|
|
2180
|
+
continue
|
|
2181
|
+
try:
|
|
2182
|
+
rec = json.loads(line)
|
|
2183
|
+
except json.JSONDecodeError as e:
|
|
2184
|
+
raise ValueError(
|
|
2185
|
+
f"audit-log-labels.jsonl record {idx}: invalid JSON: {e}"
|
|
2186
|
+
) from e
|
|
2187
|
+
stored_hmac = rec.pop("hmac", "")
|
|
2188
|
+
record_for_compute = dict(rec)
|
|
2189
|
+
expected_hmac = _compute_label_hmac(prev_hmac, record_for_compute)
|
|
2190
|
+
if stored_hmac != expected_hmac:
|
|
2191
|
+
raise ValueError(
|
|
2192
|
+
f"audit-log-labels.jsonl record {idx}: HMAC chain broken "
|
|
2193
|
+
f"(stored={stored_hmac[:16]}, computed={expected_hmac[:16]})"
|
|
2194
|
+
)
|
|
2195
|
+
rec["hmac"] = stored_hmac
|
|
2196
|
+
records.append(rec)
|
|
2197
|
+
prev_hmac = stored_hmac
|
|
2198
|
+
return records
|
|
2199
|
+
|
|
2200
|
+
|
|
2201
|
+
def _append_label_record(record: Dict[str, Any]) -> str:
|
|
2202
|
+
"""Append a record to label store with HMAC chain link. Returns the new HMAC."""
|
|
2203
|
+
p = _label_store_path()
|
|
2204
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
2205
|
+
records = _load_label_records()
|
|
2206
|
+
prev_hmac = records[-1]["hmac"] if records else ""
|
|
2207
|
+
new_hmac = _compute_label_hmac(prev_hmac, record)
|
|
2208
|
+
full_record = dict(record)
|
|
2209
|
+
full_record["hmac"] = new_hmac
|
|
2210
|
+
with p.open("a", encoding="utf-8") as fh:
|
|
2211
|
+
fh.write(_canonical_json_for_chain(full_record) + "\n")
|
|
2212
|
+
return new_hmac
|
|
2213
|
+
|
|
2214
|
+
|
|
2215
|
+
def cmd_label(entries, args) -> Dict[str, Any]:
|
|
2216
|
+
"""Owner labels a pair_rail_case event post-hoc. PLAN-081 Phase 6-bis.
|
|
2217
|
+
|
|
2218
|
+
Append-only via HMAC chain to `.claude/scripts/audit-log-labels.jsonl`.
|
|
2219
|
+
The Owner labels Case B (Claude PASS + Codex BLOCK) verdicts per
|
|
2220
|
+
ADR-108 §Owner labeling protocol with one of:
|
|
2221
|
+
|
|
2222
|
+
- fp (false-positive — Codex was wrong; close as advisory)
|
|
2223
|
+
- tp (true-positive — block stands)
|
|
2224
|
+
- triage_pending (extends grace by 24h; max 1 extension before
|
|
2225
|
+
mechanical close-as-advisory)
|
|
2226
|
+
- retracted (creates new chain entry that supersedes the latest
|
|
2227
|
+
prior label for the same run_id; if retracted is the most-recent
|
|
2228
|
+
entry, the event reverts to unlabeled status in fp-rate computations)
|
|
2229
|
+
|
|
2230
|
+
fp-rate aggregator (cmd_fp_rate) denominator behavior: case_b_total
|
|
2231
|
+
ALWAYS includes every Case-B event in the window. The numerator
|
|
2232
|
+
counts only `fp` labels for the lower bound; unlabeled +
|
|
2233
|
+
triage_pending count as worst-case FP for the upper bound. See
|
|
2234
|
+
cmd_fp_rate docstring for the Wilson 95% bounds details.
|
|
2235
|
+
"""
|
|
2236
|
+
run_id = getattr(args, "run_id", None)
|
|
2237
|
+
case = getattr(args, "case", None)
|
|
2238
|
+
label = getattr(args, "label", None)
|
|
2239
|
+
note = getattr(args, "note", "") or ""
|
|
2240
|
+
|
|
2241
|
+
if not run_id:
|
|
2242
|
+
return {"verdict": "ERROR", "reason": "--run-id required"}
|
|
2243
|
+
if case not in _LABEL_VALID_CASES:
|
|
2244
|
+
return {
|
|
2245
|
+
"verdict": "ERROR",
|
|
2246
|
+
"reason": f"--case must be one of {sorted(_LABEL_VALID_CASES)}, got {case!r}",
|
|
2247
|
+
}
|
|
2248
|
+
if label not in _LABEL_VALID_LABELS:
|
|
2249
|
+
return {
|
|
2250
|
+
"verdict": "ERROR",
|
|
2251
|
+
"reason": f"--label must be one of {sorted(_LABEL_VALID_LABELS)}, got {label!r}",
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
2255
|
+
record = {
|
|
2256
|
+
"ts": ts,
|
|
2257
|
+
"run_id": run_id,
|
|
2258
|
+
"case": case,
|
|
2259
|
+
"label": label,
|
|
2260
|
+
"note_bucket": "empty" if not note else (
|
|
2261
|
+
"short" if len(note) <= 50 else "medium" if len(note) <= 200 else "long"
|
|
2262
|
+
),
|
|
2263
|
+
}
|
|
2264
|
+
new_hmac = _append_label_record(record)
|
|
2265
|
+
return {
|
|
2266
|
+
"verdict": "OK",
|
|
2267
|
+
"run_id": run_id,
|
|
2268
|
+
"case": case,
|
|
2269
|
+
"label": label,
|
|
2270
|
+
"ts": ts,
|
|
2271
|
+
"hmac_prefix": new_hmac[:16],
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
|
|
2275
|
+
def _wilson_bounds(successes: int, n: int, z: float = 1.96) -> Tuple[float, float]:
|
|
2276
|
+
"""95% Wilson score interval for binomial proportion (ADR-108 §FP-rate).
|
|
2277
|
+
|
|
2278
|
+
More accurate than naive (p ± z·sqrt(p(1-p)/n)) at small n or
|
|
2279
|
+
extreme p. Returns (lower, upper) ∈ [0, 1].
|
|
2280
|
+
"""
|
|
2281
|
+
if n <= 0:
|
|
2282
|
+
return 0.0, 0.0
|
|
2283
|
+
p = successes / n
|
|
2284
|
+
z2 = z * z
|
|
2285
|
+
denom = 1.0 + z2 / n
|
|
2286
|
+
center = (p + z2 / (2.0 * n)) / denom
|
|
2287
|
+
half_width = (z * ((p * (1.0 - p) + z2 / (4.0 * n)) / n) ** 0.5) / denom
|
|
2288
|
+
lo = max(0.0, center - half_width)
|
|
2289
|
+
hi = min(1.0, center + half_width)
|
|
2290
|
+
return lo, hi
|
|
2291
|
+
|
|
2292
|
+
|
|
2293
|
+
def cmd_fp_rate(entries, args) -> Dict[str, Any]:
|
|
2294
|
+
"""PLAN-081 Phase 6-bis (R1 S-TDE-3 + ADR-108 §FP-rate): Case-B
|
|
2295
|
+
false-positive rate aggregator with 95% Wilson score interval.
|
|
2296
|
+
|
|
2297
|
+
The Wilson interval provides robust lower/upper bounds at small n or
|
|
2298
|
+
extreme proportions, which a simple Laplace bound underestimates. Per
|
|
2299
|
+
ADR-108 §FP-rate, the reopen criterion fires when fp_rate_30d > 30%
|
|
2300
|
+
via `disable_predicate_eval.py` `fp_rate_30d_above_30pct` predicate.
|
|
2301
|
+
|
|
2302
|
+
Labels per ADR-108 §Owner labeling protocol:
|
|
2303
|
+
- `fp` = labeled false-positive — counted in numerator + denominator
|
|
2304
|
+
- `tp` = labeled true-positive — counted in denominator (not in fp num)
|
|
2305
|
+
- `triage_pending` = grace extended — counted in denominator (still
|
|
2306
|
+
provisional; treated as labeled-pending for Wilson computation)
|
|
2307
|
+
- `retracted` = supersedes prior label — most-recent non-retracted
|
|
2308
|
+
label wins; if retracted is the latest entry for a run_id, the
|
|
2309
|
+
event reverts to unlabeled
|
|
2310
|
+
The denominator (case_b_total) ALWAYS includes every Case-B event in
|
|
2311
|
+
the window — labeled or not. Wilson upper bound treats unlabeled +
|
|
2312
|
+
triage_pending as worst-case FP.
|
|
2313
|
+
|
|
2314
|
+
Window selection via --window-days (default 30).
|
|
2315
|
+
"""
|
|
2316
|
+
window_days = getattr(args, "window_days", None)
|
|
2317
|
+
if window_days is None:
|
|
2318
|
+
window_days = 30 # default 30-day window per ADR-108 §FP-rate
|
|
2319
|
+
|
|
2320
|
+
now = datetime.now(timezone.utc)
|
|
2321
|
+
cutoff = now - timedelta(days=window_days)
|
|
2322
|
+
cutoff_iso = cutoff.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
2323
|
+
|
|
2324
|
+
# Filter pair_rail_case events in window — only Case B (asymmetric case)
|
|
2325
|
+
case_b_total = 0
|
|
2326
|
+
case_b_run_ids: List[str] = []
|
|
2327
|
+
for e in entries:
|
|
2328
|
+
if e.get("action") != "pair_rail_case":
|
|
2329
|
+
continue
|
|
2330
|
+
if str(e.get("ts", "")) < cutoff_iso:
|
|
2331
|
+
continue
|
|
2332
|
+
if e.get("case") != "B":
|
|
2333
|
+
continue
|
|
2334
|
+
case_b_total += 1
|
|
2335
|
+
rid = str(e.get("run_id") or e.get("pair_rail_run_id") or "")
|
|
2336
|
+
if rid:
|
|
2337
|
+
case_b_run_ids.append(rid)
|
|
2338
|
+
|
|
2339
|
+
# Load labels + match by run_id (latest label wins per run_id; retracted
|
|
2340
|
+
# entries supersede prior labels)
|
|
2341
|
+
try:
|
|
2342
|
+
labels = _load_label_records()
|
|
2343
|
+
except ValueError as exc:
|
|
2344
|
+
return {"verdict": "ERROR", "reason": str(exc)}
|
|
2345
|
+
|
|
2346
|
+
label_index: Dict[str, str] = {} # run_id → most-recent non-retracted label
|
|
2347
|
+
for r in labels:
|
|
2348
|
+
if r.get("case") != "B":
|
|
2349
|
+
continue
|
|
2350
|
+
rid = str(r.get("run_id", ""))
|
|
2351
|
+
lab = str(r.get("label", ""))
|
|
2352
|
+
if lab == "retracted":
|
|
2353
|
+
label_index.pop(rid, None)
|
|
2354
|
+
else:
|
|
2355
|
+
label_index[rid] = lab
|
|
2356
|
+
|
|
2357
|
+
labeled_fp = 0
|
|
2358
|
+
labeled_tp = 0
|
|
2359
|
+
labeled_triage = 0
|
|
2360
|
+
unlabeled = 0
|
|
2361
|
+
for rid in case_b_run_ids:
|
|
2362
|
+
lab = label_index.get(rid)
|
|
2363
|
+
if lab == "fp":
|
|
2364
|
+
labeled_fp += 1
|
|
2365
|
+
elif lab == "tp":
|
|
2366
|
+
labeled_tp += 1
|
|
2367
|
+
elif lab == "triage_pending":
|
|
2368
|
+
labeled_triage += 1
|
|
2369
|
+
else:
|
|
2370
|
+
unlabeled += 1
|
|
2371
|
+
|
|
2372
|
+
# Wilson 95% bounds: numerator = labeled_fp, denominator = case_b_total
|
|
2373
|
+
# Upper bound assumes both unlabeled AND triage_pending are worst-case
|
|
2374
|
+
# FP per ADR-108 §FP-rate + cmd_label/cmd_fp_rate docstring contract
|
|
2375
|
+
# (Codex iter-6 P2 fix — implementation now matches docstring).
|
|
2376
|
+
fp_lo, _ = _wilson_bounds(labeled_fp, case_b_total)
|
|
2377
|
+
worst_case_fp = labeled_fp + unlabeled + labeled_triage
|
|
2378
|
+
_, fp_hi_via_worst = _wilson_bounds(worst_case_fp, case_b_total)
|
|
2379
|
+
fp_hi = fp_hi_via_worst
|
|
2380
|
+
|
|
2381
|
+
# Reopen threshold per ADR-108 §FP-rate (default 0.30 — the 30% bar that
|
|
2382
|
+
# triggers `fp_rate_30d_above_30pct` predicate in disable_predicate_eval.py)
|
|
2383
|
+
threshold = float(getattr(args, "reopen_threshold", None) or 0.30)
|
|
2384
|
+
trigger_reopen = fp_lo > threshold
|
|
2385
|
+
|
|
2386
|
+
return {
|
|
2387
|
+
"window_days": window_days,
|
|
2388
|
+
"window_start": cutoff_iso,
|
|
2389
|
+
"window_end": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
2390
|
+
"case_b_total": case_b_total,
|
|
2391
|
+
"case_b_labeled_fp": labeled_fp,
|
|
2392
|
+
"case_b_labeled_tp": labeled_tp,
|
|
2393
|
+
"case_b_labeled_triage": labeled_triage,
|
|
2394
|
+
"case_b_unlabeled": unlabeled,
|
|
2395
|
+
"fp_rate_lower_bound": round(fp_lo, 4),
|
|
2396
|
+
"fp_rate_upper_bound": round(fp_hi, 4),
|
|
2397
|
+
"wilson_z": 1.96,
|
|
2398
|
+
"reopen_threshold": threshold,
|
|
2399
|
+
"trigger_reopen": trigger_reopen,
|
|
2400
|
+
"predicate_ref": "fp_rate_30d_above_30pct",
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
|
|
2404
|
+
def cmd_case_summary(entries, args) -> Dict[str, Any]:
|
|
2405
|
+
"""PLAN-081 Phase 6-bis (ADR-108 §Operational): Cases A-F distribution.
|
|
2406
|
+
|
|
2407
|
+
Aggregates `pair_rail_case` audit events over the window and returns
|
|
2408
|
+
counts + percentages for each Case (A=both PASS, B=Claude PASS+Codex
|
|
2409
|
+
BLOCK, C=Claude BLOCK+Codex PASS, D=both BLOCK, E=Jaccard divergence,
|
|
2410
|
+
F=Codex outage). Operators use this to monitor healthy distribution
|
|
2411
|
+
per ADR-108 §Operational (Case A typically 70-85%; Case F < 2%).
|
|
2412
|
+
"""
|
|
2413
|
+
window_days = getattr(args, "window_days", None)
|
|
2414
|
+
if window_days is None:
|
|
2415
|
+
window_days = 7 # default 7-day window (faster operational signal)
|
|
2416
|
+
|
|
2417
|
+
now = datetime.now(timezone.utc)
|
|
2418
|
+
cutoff = now - timedelta(days=window_days)
|
|
2419
|
+
cutoff_iso = cutoff.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
2420
|
+
|
|
2421
|
+
per_case: Dict[str, int] = {"A": 0, "B": 0, "C": 0, "D": 0, "E": 0, "F": 0}
|
|
2422
|
+
unknown_case = 0
|
|
2423
|
+
precondition_met_count = 0
|
|
2424
|
+
precondition_not_met_count = 0 # Case B'
|
|
2425
|
+
for e in entries:
|
|
2426
|
+
if e.get("action") != "pair_rail_case":
|
|
2427
|
+
continue
|
|
2428
|
+
if str(e.get("ts", "")) < cutoff_iso:
|
|
2429
|
+
continue
|
|
2430
|
+
case = str(e.get("case") or "")
|
|
2431
|
+
if case in per_case:
|
|
2432
|
+
per_case[case] += 1
|
|
2433
|
+
if case == "B":
|
|
2434
|
+
pm = e.get("precondition_met")
|
|
2435
|
+
if pm is True or pm == "true":
|
|
2436
|
+
precondition_met_count += 1
|
|
2437
|
+
elif pm is False or pm == "false":
|
|
2438
|
+
precondition_not_met_count += 1
|
|
2439
|
+
else:
|
|
2440
|
+
unknown_case += 1
|
|
2441
|
+
|
|
2442
|
+
total = sum(per_case.values()) + unknown_case
|
|
2443
|
+
per_case_pct = {
|
|
2444
|
+
k: round(v / total * 100.0, 2) if total else 0.0
|
|
2445
|
+
for k, v in per_case.items()
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
# Healthy distribution sentinels per ADR-108 §Operational
|
|
2449
|
+
healthy_ranges = {
|
|
2450
|
+
"A": (70.0, 85.0),
|
|
2451
|
+
"B": (1.0, 8.0),
|
|
2452
|
+
"C": (2.0, 10.0),
|
|
2453
|
+
"D": (1.0, 5.0),
|
|
2454
|
+
"E": (1.0, 5.0),
|
|
2455
|
+
"F": (0.0, 2.0),
|
|
2456
|
+
}
|
|
2457
|
+
health = {}
|
|
2458
|
+
for k, pct in per_case_pct.items():
|
|
2459
|
+
lo, hi = healthy_ranges[k]
|
|
2460
|
+
if total == 0:
|
|
2461
|
+
health[k] = "NO_DATA"
|
|
2462
|
+
elif lo <= pct <= hi:
|
|
2463
|
+
health[k] = "OK"
|
|
2464
|
+
elif pct < lo:
|
|
2465
|
+
health[k] = "LOW"
|
|
2466
|
+
else:
|
|
2467
|
+
health[k] = "HIGH"
|
|
2468
|
+
|
|
2469
|
+
return {
|
|
2470
|
+
"window_days": window_days,
|
|
2471
|
+
"window_start": cutoff_iso,
|
|
2472
|
+
"window_end": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
2473
|
+
"total_pair_rail_case_events": total,
|
|
2474
|
+
"case_counts": per_case,
|
|
2475
|
+
"case_percentages": per_case_pct,
|
|
2476
|
+
"unknown_case_count": unknown_case,
|
|
2477
|
+
"case_b_precondition_met": precondition_met_count,
|
|
2478
|
+
"case_b_precondition_not_met": precondition_not_met_count,
|
|
2479
|
+
"health_per_case": health,
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
|
|
2483
|
+
def cmd_codex_writeguard_summary(entries, args) -> Dict[str, Any]:
|
|
2484
|
+
"""PLAN-081 Phase 6-bis (R1 S-TDE Q2): Codex codando deny-list hit summary.
|
|
2485
|
+
|
|
2486
|
+
Aggregates `pair_rail_codex_denylist_hit` audit events by target_path
|
|
2487
|
+
bucket. Surfaces top-attempted forbidden paths so operators can detect
|
|
2488
|
+
deny-list coverage gaps.
|
|
2489
|
+
"""
|
|
2490
|
+
window_days = getattr(args, "window_days", None)
|
|
2491
|
+
if window_days is None:
|
|
2492
|
+
window_days = 30
|
|
2493
|
+
|
|
2494
|
+
now = datetime.now(timezone.utc)
|
|
2495
|
+
cutoff = now - timedelta(days=window_days)
|
|
2496
|
+
cutoff_iso = cutoff.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
2497
|
+
|
|
2498
|
+
per_path: Dict[str, int] = {}
|
|
2499
|
+
total_hits = 0
|
|
2500
|
+
for e in entries:
|
|
2501
|
+
if e.get("action") != "pair_rail_codex_denylist_hit":
|
|
2502
|
+
continue
|
|
2503
|
+
if str(e.get("ts", "")) < cutoff_iso:
|
|
2504
|
+
continue
|
|
2505
|
+
path = str(e.get("target_path_bucket") or e.get("target_path") or "unknown")
|
|
2506
|
+
per_path[path] = per_path.get(path, 0) + 1
|
|
2507
|
+
total_hits += 1
|
|
2508
|
+
|
|
2509
|
+
top_n = int(getattr(args, "top", None) or 10)
|
|
2510
|
+
top_paths = sorted(per_path.items(), key=lambda kv: (-kv[1], kv[0]))[:top_n]
|
|
2511
|
+
|
|
2512
|
+
return {
|
|
2513
|
+
"window_days": window_days,
|
|
2514
|
+
"window_start": cutoff_iso,
|
|
2515
|
+
"window_end": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
2516
|
+
"total_hits": total_hits,
|
|
2517
|
+
"unique_paths": len(per_path),
|
|
2518
|
+
"top_paths": [{"path_bucket": p, "hits": c} for p, c in top_paths],
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
|
|
2522
|
+
# ---------------------------------------------------------------------------
|
|
2523
|
+
# PLAN-113 Phase B, Wave W1 — critical-security-action reader (audit-reader
|
|
2524
|
+
# coverage). These 38 actions are emitted by hooks but had NO reader handler
|
|
2525
|
+
# in this CLI, so an operator could not surface them (PLAN-112 finding class
|
|
2526
|
+
# "Critical action has no reader handler"). cmd_critical reads the registry
|
|
2527
|
+
# below and surfaces, per action, count + first/last timestamp + a compact
|
|
2528
|
+
# set of SAFE summary fields ALREADY PRESENT on the event (never invented).
|
|
2529
|
+
# Actions with ZERO occurrences are still listed (count=0) so a
|
|
2530
|
+
# missing-but-expected critical event is visible — surfacing ABSENCE is the
|
|
2531
|
+
# whole point.
|
|
2532
|
+
# ---------------------------------------------------------------------------
|
|
2533
|
+
|
|
2534
|
+
_CRITICAL_SECURITY_ACTIONS: Tuple[str, ...] = (
|
|
2535
|
+
# --- federation (cross-machine peer trust / write-mode) ---
|
|
2536
|
+
"federation_autonomous_call_blocked",
|
|
2537
|
+
"federation_cert_revoked",
|
|
2538
|
+
"federation_event_action_blocked",
|
|
2539
|
+
"federation_hmac_secret_rotated",
|
|
2540
|
+
"federation_key_floor_rejected",
|
|
2541
|
+
"federation_lan_bind_denied",
|
|
2542
|
+
"federation_peer_revoked_remote",
|
|
2543
|
+
"federation_scope_denied",
|
|
2544
|
+
"federation_spki_fingerprint_mismatch",
|
|
2545
|
+
"federation_tamper_detected",
|
|
2546
|
+
"federation_write_attempt_blocked",
|
|
2547
|
+
"federation_write_endpoint_denied",
|
|
2548
|
+
# --- mcp (bearer-token / tenant isolation) ---
|
|
2549
|
+
"mcp_bearer_replay_rejected",
|
|
2550
|
+
"mcp_cross_tenant_denied",
|
|
2551
|
+
"mcp_non_loopback_rejected",
|
|
2552
|
+
# --- sentinel-signer (GPG quorum / rotation / revocation) ---
|
|
2553
|
+
"gpg_signed",
|
|
2554
|
+
"gpg_verified",
|
|
2555
|
+
"sentinel_signer_expiry_warned",
|
|
2556
|
+
"sentinel_signer_quorum_attempted",
|
|
2557
|
+
"sentinel_signer_quorum_failed",
|
|
2558
|
+
"sentinel_signer_revoked",
|
|
2559
|
+
"sentinel_signer_rotated",
|
|
2560
|
+
# --- trading (kill-switch / write-override) ---
|
|
2561
|
+
"trading_kill_switch_disabled",
|
|
2562
|
+
"trading_kill_switch_invoked",
|
|
2563
|
+
"trading_write_override_used",
|
|
2564
|
+
# --- credential (age / emergency override) ---
|
|
2565
|
+
"credential_blocked_due_to_age",
|
|
2566
|
+
"credential_emergency_override_used",
|
|
2567
|
+
# --- audit-spool (tamper detection) ---
|
|
2568
|
+
"audit_spool_tamper_detected",
|
|
2569
|
+
# --- governance (kernel / kill-switch / overrides / pair-rail / swarm) ---
|
|
2570
|
+
"anti_ceo_overhead_override_used",
|
|
2571
|
+
"bash_canonical_bypass_invoked",
|
|
2572
|
+
"confidence_gate_blocked",
|
|
2573
|
+
"kernel_extension_landed",
|
|
2574
|
+
"kill_switch_invoked",
|
|
2575
|
+
"live_adapter_blocked",
|
|
2576
|
+
"pair_rail_codex_injection_detected",
|
|
2577
|
+
"pair_rail_outgoing_redaction_applied",
|
|
2578
|
+
"phase_c_enforcing_flipped",
|
|
2579
|
+
"swarm_layer_3_4_blocked",
|
|
2580
|
+
)
|
|
2581
|
+
|
|
2582
|
+
# Map each critical action to its domain group (for the table/json output).
|
|
2583
|
+
# Domains mirror the comment blocks in _CRITICAL_SECURITY_ACTIONS above.
|
|
2584
|
+
_CRITICAL_ACTION_DOMAIN: Dict[str, str] = {
|
|
2585
|
+
# federation
|
|
2586
|
+
"federation_autonomous_call_blocked": "federation",
|
|
2587
|
+
"federation_cert_revoked": "federation",
|
|
2588
|
+
"federation_event_action_blocked": "federation",
|
|
2589
|
+
"federation_hmac_secret_rotated": "federation",
|
|
2590
|
+
"federation_key_floor_rejected": "federation",
|
|
2591
|
+
"federation_lan_bind_denied": "federation",
|
|
2592
|
+
"federation_peer_revoked_remote": "federation",
|
|
2593
|
+
"federation_scope_denied": "federation",
|
|
2594
|
+
"federation_spki_fingerprint_mismatch": "federation",
|
|
2595
|
+
"federation_tamper_detected": "federation",
|
|
2596
|
+
"federation_write_attempt_blocked": "federation",
|
|
2597
|
+
"federation_write_endpoint_denied": "federation",
|
|
2598
|
+
# mcp
|
|
2599
|
+
"mcp_bearer_replay_rejected": "mcp",
|
|
2600
|
+
"mcp_cross_tenant_denied": "mcp",
|
|
2601
|
+
"mcp_non_loopback_rejected": "mcp",
|
|
2602
|
+
# sentinel-signer
|
|
2603
|
+
"gpg_signed": "sentinel-signer",
|
|
2604
|
+
"gpg_verified": "sentinel-signer",
|
|
2605
|
+
"sentinel_signer_expiry_warned": "sentinel-signer",
|
|
2606
|
+
"sentinel_signer_quorum_attempted": "sentinel-signer",
|
|
2607
|
+
"sentinel_signer_quorum_failed": "sentinel-signer",
|
|
2608
|
+
"sentinel_signer_revoked": "sentinel-signer",
|
|
2609
|
+
"sentinel_signer_rotated": "sentinel-signer",
|
|
2610
|
+
# trading
|
|
2611
|
+
"trading_kill_switch_disabled": "trading",
|
|
2612
|
+
"trading_kill_switch_invoked": "trading",
|
|
2613
|
+
"trading_write_override_used": "trading",
|
|
2614
|
+
# credential
|
|
2615
|
+
"credential_blocked_due_to_age": "credential",
|
|
2616
|
+
"credential_emergency_override_used": "credential",
|
|
2617
|
+
# audit-spool
|
|
2618
|
+
"audit_spool_tamper_detected": "audit-spool",
|
|
2619
|
+
# governance
|
|
2620
|
+
"anti_ceo_overhead_override_used": "governance",
|
|
2621
|
+
"bash_canonical_bypass_invoked": "governance",
|
|
2622
|
+
"confidence_gate_blocked": "governance",
|
|
2623
|
+
"kernel_extension_landed": "governance",
|
|
2624
|
+
"kill_switch_invoked": "governance",
|
|
2625
|
+
"live_adapter_blocked": "governance",
|
|
2626
|
+
"pair_rail_codex_injection_detected": "governance",
|
|
2627
|
+
"pair_rail_outgoing_redaction_applied": "governance",
|
|
2628
|
+
"phase_c_enforcing_flipped": "governance",
|
|
2629
|
+
"swarm_layer_3_4_blocked": "governance",
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
# Allowlist of SAFE, schema-stable scalar summary keys to echo when present
|
|
2633
|
+
# on a critical event. We never echo arbitrary fields (avoids surfacing
|
|
2634
|
+
# large/unexpected payloads) and never INVENT a field — only keys in this set
|
|
2635
|
+
# that actually exist on the event dict are surfaced, and only scalar values
|
|
2636
|
+
# (str/int/float/bool). These keys are all part of audit_emit's per-action
|
|
2637
|
+
# allowlists (already scrubbed at emit time) so echoing them is safe.
|
|
2638
|
+
_CRITICAL_SAFE_SUMMARY_KEYS: Tuple[str, ...] = (
|
|
2639
|
+
"session_id",
|
|
2640
|
+
"project",
|
|
2641
|
+
"reason",
|
|
2642
|
+
"reason_code",
|
|
2643
|
+
"scope",
|
|
2644
|
+
"phase",
|
|
2645
|
+
"migration_phase",
|
|
2646
|
+
"env_value",
|
|
2647
|
+
"mode",
|
|
2648
|
+
"outcome",
|
|
2649
|
+
"decision",
|
|
2650
|
+
"peer_id",
|
|
2651
|
+
"endpoint",
|
|
2652
|
+
"tenant",
|
|
2653
|
+
"signer",
|
|
2654
|
+
"key_id",
|
|
2655
|
+
"fingerprint",
|
|
2656
|
+
"loop_id",
|
|
2657
|
+
"redaction_count",
|
|
2658
|
+
"field_count",
|
|
2659
|
+
)
|
|
2660
|
+
|
|
2661
|
+
|
|
2662
|
+
def cmd_critical(entries: List[Dict[str, Any]], args) -> Dict[str, Any]:
|
|
2663
|
+
"""`audit-query critical` — surface the 38 critical-security actions.
|
|
2664
|
+
|
|
2665
|
+
PLAN-113 Phase B, Wave W1 (audit-reader-coverage). Each action in
|
|
2666
|
+
``_CRITICAL_SECURITY_ACTIONS`` is emitted by a hook but had no reader
|
|
2667
|
+
handler, so an operator could not query it. This command aggregates,
|
|
2668
|
+
per critical action that appears in the log: ``count``, first/last ISO
|
|
2669
|
+
timestamp, and a compact set of SAFE summary fields drawn from the
|
|
2670
|
+
LATEST matching event (only keys in ``_CRITICAL_SAFE_SUMMARY_KEYS`` that
|
|
2671
|
+
are actually present + scalar — no invented fields).
|
|
2672
|
+
|
|
2673
|
+
Actions with ZERO occurrences are STILL emitted with ``count=0`` so a
|
|
2674
|
+
missing-but-expected critical event is visible (surfacing absence is the
|
|
2675
|
+
whole point of the reader-coverage gap PLAN-112 found).
|
|
2676
|
+
|
|
2677
|
+
``--action <name>`` drills into one action; the name must be in the
|
|
2678
|
+
registry (else error). Output is a table (default) or JSON (``--json``).
|
|
2679
|
+
|
|
2680
|
+
Output envelope (mirrors the other v-era commands):
|
|
2681
|
+
{"query": "critical", "version": "1",
|
|
2682
|
+
"data": {"action_filter": ..., "total_critical_events": N,
|
|
2683
|
+
"present_action_count": M, "registry_size": 38,
|
|
2684
|
+
"actions": [{action, domain, count, first_ts, last_ts,
|
|
2685
|
+
last_summary: {...}}, ...]}}
|
|
2686
|
+
"""
|
|
2687
|
+
action_filter = getattr(args, "action", None)
|
|
2688
|
+
if action_filter is not None:
|
|
2689
|
+
if action_filter not in _CRITICAL_SECURITY_ACTION_SET:
|
|
2690
|
+
print(
|
|
2691
|
+
f"[audit-query] ERROR: critical: unknown --action "
|
|
2692
|
+
f"{action_filter!r}; must be one of the "
|
|
2693
|
+
f"{len(_CRITICAL_SECURITY_ACTIONS)} registry actions",
|
|
2694
|
+
file=sys.stderr,
|
|
2695
|
+
)
|
|
2696
|
+
sys.exit(1)
|
|
2697
|
+
registry: Tuple[str, ...] = (action_filter,)
|
|
2698
|
+
else:
|
|
2699
|
+
registry = _CRITICAL_SECURITY_ACTIONS
|
|
2700
|
+
|
|
2701
|
+
# Single pass: accumulate count / first_ts / last_ts / latest-event
|
|
2702
|
+
# safe-summary per critical action. We only track actions in `registry`.
|
|
2703
|
+
registry_set = set(registry)
|
|
2704
|
+
agg: Dict[str, Dict[str, Any]] = {
|
|
2705
|
+
a: {"count": 0, "first_ts": "", "last_ts": "", "last_summary": {}}
|
|
2706
|
+
for a in registry
|
|
2707
|
+
}
|
|
2708
|
+
total_critical_events = 0
|
|
2709
|
+
|
|
2710
|
+
for e in entries:
|
|
2711
|
+
action = e.get("action")
|
|
2712
|
+
if action not in registry_set:
|
|
2713
|
+
continue
|
|
2714
|
+
a = agg[action]
|
|
2715
|
+
a["count"] += 1
|
|
2716
|
+
total_critical_events += 1
|
|
2717
|
+
ts = str(e.get("ts") or "")
|
|
2718
|
+
if ts:
|
|
2719
|
+
if not a["first_ts"] or ts < a["first_ts"]:
|
|
2720
|
+
a["first_ts"] = ts
|
|
2721
|
+
if ts >= a["last_ts"]:
|
|
2722
|
+
a["last_ts"] = ts
|
|
2723
|
+
a["last_summary"] = _critical_safe_summary(e)
|
|
2724
|
+
|
|
2725
|
+
actions: List[Dict[str, Any]] = []
|
|
2726
|
+
present_action_count = 0
|
|
2727
|
+
# Stable order: by domain, then action name (registry is the universe).
|
|
2728
|
+
for action in sorted(
|
|
2729
|
+
registry,
|
|
2730
|
+
key=lambda x: (_CRITICAL_ACTION_DOMAIN.get(x, "zzz"), x),
|
|
2731
|
+
):
|
|
2732
|
+
a = agg[action]
|
|
2733
|
+
if a["count"] > 0:
|
|
2734
|
+
present_action_count += 1
|
|
2735
|
+
actions.append(
|
|
2736
|
+
{
|
|
2737
|
+
"action": action,
|
|
2738
|
+
"domain": _CRITICAL_ACTION_DOMAIN.get(action, "unknown"),
|
|
2739
|
+
"count": a["count"],
|
|
2740
|
+
"first_ts": a["first_ts"],
|
|
2741
|
+
"last_ts": a["last_ts"],
|
|
2742
|
+
"last_summary": a["last_summary"],
|
|
2743
|
+
}
|
|
2744
|
+
)
|
|
2745
|
+
|
|
2746
|
+
data = {
|
|
2747
|
+
"action_filter": action_filter,
|
|
2748
|
+
"registry_size": len(_CRITICAL_SECURITY_ACTIONS),
|
|
2749
|
+
"total_critical_events": total_critical_events,
|
|
2750
|
+
"present_action_count": present_action_count,
|
|
2751
|
+
"absent_action_count": len(registry) - present_action_count,
|
|
2752
|
+
"actions": actions,
|
|
2753
|
+
}
|
|
2754
|
+
return {
|
|
2755
|
+
"query": "critical",
|
|
2756
|
+
"version": "1",
|
|
2757
|
+
"data": data,
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
|
|
2761
|
+
# Set form for O(1) membership tests (validation + per-entry filter).
|
|
2762
|
+
_CRITICAL_SECURITY_ACTION_SET = frozenset(_CRITICAL_SECURITY_ACTIONS)
|
|
2763
|
+
|
|
2764
|
+
|
|
2765
|
+
def _critical_safe_summary(event: Dict[str, Any]) -> Dict[str, Any]:
|
|
2766
|
+
"""Echo only the SAFE, scalar summary keys present on a critical event.
|
|
2767
|
+
|
|
2768
|
+
Never invents a field: iterates ``_CRITICAL_SAFE_SUMMARY_KEYS`` and copies
|
|
2769
|
+
a key only when it exists on the event AND its value is a scalar
|
|
2770
|
+
(str/int/float/bool). This keeps the per-action summary compact and avoids
|
|
2771
|
+
surfacing large/unexpected payloads.
|
|
2772
|
+
"""
|
|
2773
|
+
out: Dict[str, Any] = {}
|
|
2774
|
+
for key in _CRITICAL_SAFE_SUMMARY_KEYS:
|
|
2775
|
+
if key not in event:
|
|
2776
|
+
continue
|
|
2777
|
+
val = event[key]
|
|
2778
|
+
if isinstance(val, bool) or isinstance(val, (str, int, float)):
|
|
2779
|
+
out[key] = val
|
|
2780
|
+
return out
|
|
2781
|
+
|
|
2782
|
+
|
|
2783
|
+
def _build_shared_parser() -> argparse.ArgumentParser:
|
|
2784
|
+
"""Return the parent parser with the shared flags.
|
|
2785
|
+
|
|
2786
|
+
Parent parser carrying the shared flags. These are added to each
|
|
2787
|
+
sub-command via ``parents=[]``, so users can write either::
|
|
2788
|
+
|
|
2789
|
+
audit-query.py summary --json
|
|
2790
|
+
audit-query.py --json summary # also works
|
|
2791
|
+
"""
|
|
2792
|
+
shared = argparse.ArgumentParser(add_help=False)
|
|
2793
|
+
shared.add_argument(
|
|
2794
|
+
"--log",
|
|
2795
|
+
default=None,
|
|
2796
|
+
help="Path to audit-log.jsonl (default: CEO_AUDIT_LOG_PATH or ~)",
|
|
2797
|
+
)
|
|
2798
|
+
shared.add_argument(
|
|
2799
|
+
"--include-rotated",
|
|
2800
|
+
action="store_true",
|
|
2801
|
+
help="Also read audit-log-YYYY-MM*.jsonl siblings",
|
|
2802
|
+
)
|
|
2803
|
+
shared.add_argument("--json", dest="as_json", action="store_true")
|
|
2804
|
+
shared.add_argument("--csv", dest="as_csv", action="store_true")
|
|
2805
|
+
shared.add_argument(
|
|
2806
|
+
"--errors-path",
|
|
2807
|
+
default=None,
|
|
2808
|
+
help="Override path for the `errors` sub-command",
|
|
2809
|
+
)
|
|
2810
|
+
return shared
|
|
2811
|
+
|
|
2812
|
+
|
|
2813
|
+
def _add_v1_subparsers(sub: "argparse._SubParsersAction",
|
|
2814
|
+
shared: argparse.ArgumentParser) -> None:
|
|
2815
|
+
"""Register the original v1 sub-commands (summary/by-skill/... export)."""
|
|
2816
|
+
sub.add_parser(
|
|
2817
|
+
"summary",
|
|
2818
|
+
parents=[shared],
|
|
2819
|
+
help="Overview: count, range, top skills, compliance",
|
|
2820
|
+
)
|
|
2821
|
+
|
|
2822
|
+
byskill = sub.add_parser("by-skill", parents=[shared], help="Rank skills by usage count")
|
|
2823
|
+
byskill.add_argument("--top", type=int, default=10)
|
|
2824
|
+
|
|
2825
|
+
sub.add_parser("compliance", parents=[shared], help="Governance compliance breakdown")
|
|
2826
|
+
|
|
2827
|
+
byday = sub.add_parser("by-day", parents=[shared], help="Spawns-per-day histogram")
|
|
2828
|
+
byday.add_argument("--days", type=int, default=14)
|
|
2829
|
+
|
|
2830
|
+
search = sub.add_parser("search", parents=[shared], help="Regex-match against desc_preview")
|
|
2831
|
+
search.add_argument("regex")
|
|
2832
|
+
|
|
2833
|
+
since = sub.add_parser("since", parents=[shared], help="Entries on or after a date")
|
|
2834
|
+
since.add_argument("iso_date", help="YYYY-MM-DD or full ISO 8601")
|
|
2835
|
+
|
|
2836
|
+
sub.add_parser("errors", parents=[shared], help="Tail the audit-log.errors breadcrumb file")
|
|
2837
|
+
|
|
2838
|
+
stats_p = sub.add_parser(
|
|
2839
|
+
"stats",
|
|
2840
|
+
parents=[shared],
|
|
2841
|
+
help="Prompt-length + response_kind distributions + latency; "
|
|
2842
|
+
"--tool-latency for per-tool lifecycle buckets",
|
|
2843
|
+
)
|
|
2844
|
+
# PLAN-125 WS-1 — per-tool-call lifecycle latency view. Bucket-counts ONLY
|
|
2845
|
+
# (the tool_call_lifecycle_recorded action carries NO raw duration_ms per
|
|
2846
|
+
# MF-SEC-3), so this is a histogram, NOT percentiles.
|
|
2847
|
+
stats_p.add_argument(
|
|
2848
|
+
"--tool-latency",
|
|
2849
|
+
dest="tool_latency",
|
|
2850
|
+
action="store_true",
|
|
2851
|
+
help="Per-tool_name_enum duration_bucket histogram (+ success / orphan "
|
|
2852
|
+
"rollups) from tool_call_lifecycle_recorded rows. Bucket-counts "
|
|
2853
|
+
"only — no percentiles (raw duration_ms is never recorded).",
|
|
2854
|
+
)
|
|
2855
|
+
|
|
2856
|
+
export = sub.add_parser("export", parents=[shared], help="Dump all entries in csv/json/tsv")
|
|
2857
|
+
export.add_argument(
|
|
2858
|
+
"--format", dest="export_format", choices=["csv", "json", "tsv"], default="json"
|
|
2859
|
+
)
|
|
2860
|
+
|
|
2861
|
+
|
|
2862
|
+
def _add_v2_subparsers(sub: "argparse._SubParsersAction",
|
|
2863
|
+
shared: argparse.ArgumentParser) -> None:
|
|
2864
|
+
"""Register Sprint 5 A.2 v2 event-stream sub-commands."""
|
|
2865
|
+
sub.add_parser(
|
|
2866
|
+
"debate",
|
|
2867
|
+
parents=[shared],
|
|
2868
|
+
help="Debate rounds: grouped by (plan_id, round) with agents + consensus",
|
|
2869
|
+
)
|
|
2870
|
+
sub.add_parser(
|
|
2871
|
+
"plans",
|
|
2872
|
+
parents=[shared],
|
|
2873
|
+
help="Plan status transitions per plan_id",
|
|
2874
|
+
)
|
|
2875
|
+
sub.add_parser(
|
|
2876
|
+
"vetoes",
|
|
2877
|
+
parents=[shared],
|
|
2878
|
+
help="Veto events aggregated by (hook, reason_code)",
|
|
2879
|
+
)
|
|
2880
|
+
sub.add_parser(
|
|
2881
|
+
"benchmarks",
|
|
2882
|
+
parents=[shared],
|
|
2883
|
+
help=(
|
|
2884
|
+
"Benchmark runs aggregated by skill — harbor-style row: "
|
|
2885
|
+
"pass_rate + cost + compute + turns (PLAN-133 C4)"
|
|
2886
|
+
),
|
|
2887
|
+
)
|
|
2888
|
+
sub.add_parser(
|
|
2889
|
+
"lessons",
|
|
2890
|
+
parents=[shared],
|
|
2891
|
+
help="Lesson-write events grouped by archetype + trigger",
|
|
2892
|
+
)
|
|
2893
|
+
sub.add_parser(
|
|
2894
|
+
"metrics",
|
|
2895
|
+
parents=[shared],
|
|
2896
|
+
help="Cross-cutting derived metrics (veto rate, debate completion)",
|
|
2897
|
+
)
|
|
2898
|
+
sub.add_parser(
|
|
2899
|
+
"health",
|
|
2900
|
+
parents=[shared],
|
|
2901
|
+
help="Framework health verdict (PASS / WARN / FAIL / NO_DATA)",
|
|
2902
|
+
)
|
|
2903
|
+
sub.add_parser(
|
|
2904
|
+
"tokens",
|
|
2905
|
+
parents=[shared],
|
|
2906
|
+
help="Spawn token aggregates (PLAN-006 Phase 5a / ADR-016)",
|
|
2907
|
+
)
|
|
2908
|
+
|
|
2909
|
+
|
|
2910
|
+
def _add_sprint8_9_subparsers(sub: "argparse._SubParsersAction",
|
|
2911
|
+
shared: argparse.ArgumentParser) -> None:
|
|
2912
|
+
"""Register confidence-gate + ADR-020 + lesson-effectiveness commands."""
|
|
2913
|
+
# Sprint 8 Phase 2 — confidence_gate aggregates (ADR-018)
|
|
2914
|
+
claims = sub.add_parser(
|
|
2915
|
+
"claims",
|
|
2916
|
+
parents=[shared],
|
|
2917
|
+
help="Confidence-gate verification aggregates (pass/fail by kind + agent)",
|
|
2918
|
+
)
|
|
2919
|
+
claims.add_argument("--kind", help="Filter by claim kind (e.g. path_exists)")
|
|
2920
|
+
claims.add_argument("--agent", help="Filter by agent name")
|
|
2921
|
+
claims.add_argument(
|
|
2922
|
+
"--failed-only",
|
|
2923
|
+
action="store_true",
|
|
2924
|
+
help="Only include events with ≥1 failed claim",
|
|
2925
|
+
)
|
|
2926
|
+
|
|
2927
|
+
# Sprint 9 Phase 2 (ADR-020) — prune-restore-ratio measurement
|
|
2928
|
+
prr = sub.add_parser(
|
|
2929
|
+
"prune-restore-ratio",
|
|
2930
|
+
parents=[shared],
|
|
2931
|
+
help="Ratio of restored/archived lessons over a time window (ADR-020)",
|
|
2932
|
+
)
|
|
2933
|
+
prr.add_argument(
|
|
2934
|
+
"--since", default="24h",
|
|
2935
|
+
help="Window start: 24h / 7d / 30m / ISO 8601 / 'all' (default 24h)",
|
|
2936
|
+
)
|
|
2937
|
+
prr.add_argument(
|
|
2938
|
+
"--until", default=None,
|
|
2939
|
+
help="Window end: ISO 8601 (default: now)",
|
|
2940
|
+
)
|
|
2941
|
+
|
|
2942
|
+
# Sprint 9 Phase 3 (PLAN-009 P3.4) — Architect outcome tracking
|
|
2943
|
+
aout = sub.add_parser(
|
|
2944
|
+
"architect-outcomes",
|
|
2945
|
+
parents=[shared],
|
|
2946
|
+
help="Per-lesson hit/miss from Architect spawns (ADR-015 amended)",
|
|
2947
|
+
)
|
|
2948
|
+
aout.add_argument(
|
|
2949
|
+
"--since", default="24h",
|
|
2950
|
+
help="Window start: 24h / 7d / 30m / ISO 8601 / 'all' (default 24h)",
|
|
2951
|
+
)
|
|
2952
|
+
aout.add_argument(
|
|
2953
|
+
"--consumer", default="architect",
|
|
2954
|
+
choices=["architect", "benchmark"],
|
|
2955
|
+
help="Filter by consumer (default: architect)",
|
|
2956
|
+
)
|
|
2957
|
+
aout.add_argument(
|
|
2958
|
+
"--include-window-only", action="store_true",
|
|
2959
|
+
help="Include pre-Sprint-9 window-only events (dirty signal)",
|
|
2960
|
+
)
|
|
2961
|
+
|
|
2962
|
+
# Sprint 9 Phase 5 (PLAN-009 P5.1) — lessons effectiveness ranking
|
|
2963
|
+
leff = sub.add_parser(
|
|
2964
|
+
"lessons-effectiveness",
|
|
2965
|
+
parents=[shared],
|
|
2966
|
+
help="Per-lesson effectiveness ranking (PLAN-009 Phase 5)",
|
|
2967
|
+
)
|
|
2968
|
+
leff.add_argument("--since", default="24h",
|
|
2969
|
+
help="Window start (default 24h; 'all' for no filter)")
|
|
2970
|
+
leff.add_argument("--include-window-only", action="store_true")
|
|
2971
|
+
leff.add_argument("--by", default="effectiveness",
|
|
2972
|
+
choices=["effectiveness", "recency", "injections"],
|
|
2973
|
+
help="Sort axis (default: effectiveness)")
|
|
2974
|
+
leff.add_argument("--top", type=int, default=0, help="Return top N")
|
|
2975
|
+
leff.add_argument("--bottom", type=int, default=0, help="Return bottom N")
|
|
2976
|
+
|
|
2977
|
+
|
|
2978
|
+
def _add_plan015_subparsers(sub: "argparse._SubParsersAction",
|
|
2979
|
+
shared: argparse.ArgumentParser) -> None:
|
|
2980
|
+
"""Register PLAN-015 adopter-triage sub-commands."""
|
|
2981
|
+
# PLAN-015 Phase 0.5 — adopter-side weekly triage
|
|
2982
|
+
wks = sub.add_parser(
|
|
2983
|
+
"weekly-summary",
|
|
2984
|
+
parents=[shared],
|
|
2985
|
+
help="Week-over-week spawn/veto/plan trend for adopter triage (PLAN-015)",
|
|
2986
|
+
)
|
|
2987
|
+
wks.add_argument("--window", default="7d",
|
|
2988
|
+
help="Window length: 7d (default), 14d, 30d, or 'all'")
|
|
2989
|
+
wks.add_argument("--now", default=None,
|
|
2990
|
+
help="Override 'now' with an ISO-8601 timestamp (testing)")
|
|
2991
|
+
|
|
2992
|
+
# PLAN-025 Batch D F-obs-001 — ADR-052 multi-model spawn distribution.
|
|
2993
|
+
# Referenced from SLO-SLA.md + DAY-1-CHECKLIST; was missing pre-Batch-D.
|
|
2994
|
+
spawn_stats = sub.add_parser(
|
|
2995
|
+
"spawn-stats",
|
|
2996
|
+
parents=[shared],
|
|
2997
|
+
help="Spawn distribution by skill + model (PLAN-025 F-obs-001; ADR-052)",
|
|
2998
|
+
)
|
|
2999
|
+
spawn_stats.add_argument(
|
|
3000
|
+
"--since",
|
|
3001
|
+
default=None,
|
|
3002
|
+
help="Filter to entries at or after this ISO-8601 timestamp or relative (e.g. '7d')",
|
|
3003
|
+
)
|
|
3004
|
+
spawn_stats.add_argument(
|
|
3005
|
+
"--by",
|
|
3006
|
+
default="model",
|
|
3007
|
+
choices=("model", "skill", "both"),
|
|
3008
|
+
help="Group-by dimension (default: model)",
|
|
3009
|
+
)
|
|
3010
|
+
|
|
3011
|
+
|
|
3012
|
+
def _add_plan080_subparsers(sub: "argparse._SubParsersAction",
|
|
3013
|
+
shared: argparse.ArgumentParser) -> None:
|
|
3014
|
+
"""Register PLAN-080 Phase 1 sub-commands.
|
|
3015
|
+
|
|
3016
|
+
by-domain: group spawn events by dispatch_archetype_hint for squad-bundle
|
|
3017
|
+
governance observability (PLAN-080 Phase 1 / ADR-112).
|
|
3018
|
+
"""
|
|
3019
|
+
by_domain = sub.add_parser(
|
|
3020
|
+
"by-domain",
|
|
3021
|
+
parents=[shared],
|
|
3022
|
+
help=(
|
|
3023
|
+
"Group agent_spawn events by dispatch_archetype_hint domain "
|
|
3024
|
+
"(PLAN-080 Phase 1 / ADR-112)"
|
|
3025
|
+
),
|
|
3026
|
+
)
|
|
3027
|
+
# Window options — mutually exclusive groups handled in cmd_by_domain
|
|
3028
|
+
by_domain.add_argument(
|
|
3029
|
+
"--window",
|
|
3030
|
+
default=f"{_BY_DOMAIN_DEFAULT_WINDOW_DAYS}d",
|
|
3031
|
+
metavar="WINDOW",
|
|
3032
|
+
help=(
|
|
3033
|
+
"Trailing window (e.g. 30d, 7d, 14d). "
|
|
3034
|
+
f"Default: {_BY_DOMAIN_DEFAULT_WINDOW_DAYS}d. "
|
|
3035
|
+
"Ignored when --start/--end are provided."
|
|
3036
|
+
),
|
|
3037
|
+
)
|
|
3038
|
+
by_domain.add_argument(
|
|
3039
|
+
"--start",
|
|
3040
|
+
default=None,
|
|
3041
|
+
metavar="YYYY-MM-DD",
|
|
3042
|
+
help="Start date (inclusive). Must be paired with --end.",
|
|
3043
|
+
)
|
|
3044
|
+
by_domain.add_argument(
|
|
3045
|
+
"--end",
|
|
3046
|
+
default=None,
|
|
3047
|
+
metavar="YYYY-MM-DD",
|
|
3048
|
+
help="End date (inclusive). Must be paired with --start.",
|
|
3049
|
+
)
|
|
3050
|
+
by_domain.add_argument(
|
|
3051
|
+
"--check-reopen",
|
|
3052
|
+
action="store_true",
|
|
3053
|
+
dest="check_reopen",
|
|
3054
|
+
help=(
|
|
3055
|
+
"Filter output to sunset domains (from grandfather-cap.policy.yaml) "
|
|
3056
|
+
"with >= 1 spawn. UNKNOWN bucket excluded per M2-CDX-7."
|
|
3057
|
+
),
|
|
3058
|
+
)
|
|
3059
|
+
|
|
3060
|
+
|
|
3061
|
+
def _add_plan081_subparsers(sub: "argparse._SubParsersAction",
|
|
3062
|
+
shared: argparse.ArgumentParser) -> None:
|
|
3063
|
+
"""Register PLAN-081 Phase 6-bis sub-commands.
|
|
3064
|
+
|
|
3065
|
+
label: Owner labels a pair_rail_case event post-hoc (R1 S-TDE-3).
|
|
3066
|
+
fp-rate: false-positive rate aggregator with reopen-trigger threshold.
|
|
3067
|
+
codex-writeguard-summary: aggregates pair_rail_codex_denylist_hit events
|
|
3068
|
+
by target_path bucket (R1 S-TDE Q2).
|
|
3069
|
+
"""
|
|
3070
|
+
# --- label ---
|
|
3071
|
+
label_p = sub.add_parser(
|
|
3072
|
+
"label",
|
|
3073
|
+
parents=[shared],
|
|
3074
|
+
help="Append a Case-B verdict label to audit-log-labels.jsonl (PLAN-081 Phase 6-bis)",
|
|
3075
|
+
)
|
|
3076
|
+
label_p.add_argument(
|
|
3077
|
+
"--run-id", required=True, dest="run_id",
|
|
3078
|
+
help="pair_rail_promotion_run_id (UUID hex) of the event being labeled",
|
|
3079
|
+
)
|
|
3080
|
+
label_p.add_argument(
|
|
3081
|
+
"--case", required=True, choices=sorted(_LABEL_VALID_CASES),
|
|
3082
|
+
help="Case letter A-F to label",
|
|
3083
|
+
)
|
|
3084
|
+
label_p.add_argument(
|
|
3085
|
+
"--label", required=True, choices=sorted(_LABEL_VALID_LABELS),
|
|
3086
|
+
help="Verdict label: fp | tp | triage_pending | retracted (per ADR-108 §Owner labeling protocol)",
|
|
3087
|
+
)
|
|
3088
|
+
label_p.add_argument(
|
|
3089
|
+
"--note", default="",
|
|
3090
|
+
help="Optional free-form note (length is bucketed in audit emit; content NOT stored)",
|
|
3091
|
+
)
|
|
3092
|
+
|
|
3093
|
+
# --- fp-rate ---
|
|
3094
|
+
fp_p = sub.add_parser(
|
|
3095
|
+
"fp-rate",
|
|
3096
|
+
parents=[shared],
|
|
3097
|
+
help="Case-B false-positive rate aggregator (PLAN-081 Phase 6-bis R1 S-TDE-3)",
|
|
3098
|
+
)
|
|
3099
|
+
fp_p.add_argument(
|
|
3100
|
+
"--window-days", type=int, default=30, dest="window_days",
|
|
3101
|
+
help="Trailing window in days (default 30)",
|
|
3102
|
+
)
|
|
3103
|
+
fp_p.add_argument(
|
|
3104
|
+
"--reopen-threshold", type=float, default=0.30, dest="reopen_threshold",
|
|
3105
|
+
help="trigger_reopen=true when fp_rate_lower_bound > threshold (default 0.30 per ADR-108 §FP-rate)",
|
|
3106
|
+
)
|
|
3107
|
+
|
|
3108
|
+
# --- case-summary ---
|
|
3109
|
+
cs_p = sub.add_parser(
|
|
3110
|
+
"case-summary",
|
|
3111
|
+
parents=[shared],
|
|
3112
|
+
help="Cases A-F distribution rollup over window (PLAN-081 Phase 6-bis ADR-108 §Operational)",
|
|
3113
|
+
)
|
|
3114
|
+
cs_p.add_argument(
|
|
3115
|
+
"--window-days", type=int, default=7, dest="window_days",
|
|
3116
|
+
help="Trailing window in days (default 7 — faster operational signal than 30d)",
|
|
3117
|
+
)
|
|
3118
|
+
|
|
3119
|
+
# --- codex-writeguard-summary ---
|
|
3120
|
+
cw_p = sub.add_parser(
|
|
3121
|
+
"codex-writeguard-summary",
|
|
3122
|
+
parents=[shared],
|
|
3123
|
+
help="Codex codando deny-list hit summary by path bucket (PLAN-081 Phase 6-bis R1 S-TDE Q2)",
|
|
3124
|
+
)
|
|
3125
|
+
cw_p.add_argument(
|
|
3126
|
+
"--window-days", type=int, default=30, dest="window_days",
|
|
3127
|
+
help="Trailing window in days (default 30)",
|
|
3128
|
+
)
|
|
3129
|
+
cw_p.add_argument(
|
|
3130
|
+
"--top", type=int, default=10,
|
|
3131
|
+
help="Top N most-attempted forbidden paths (default 10)",
|
|
3132
|
+
)
|
|
3133
|
+
|
|
3134
|
+
|
|
3135
|
+
def _add_plan113_subparsers(sub: "argparse._SubParsersAction",
|
|
3136
|
+
shared: argparse.ArgumentParser) -> None:
|
|
3137
|
+
"""Register PLAN-113 Phase B Wave W1 sub-command.
|
|
3138
|
+
|
|
3139
|
+
critical: surface the 38 critical-security actions (count + first/last
|
|
3140
|
+
ts + safe summary), listing zero-occurrence actions so missing-but-
|
|
3141
|
+
expected critical events are visible (audit-reader-coverage gap).
|
|
3142
|
+
"""
|
|
3143
|
+
crit = sub.add_parser(
|
|
3144
|
+
"critical",
|
|
3145
|
+
parents=[shared],
|
|
3146
|
+
help=(
|
|
3147
|
+
"Surface critical-security actions (count + last-seen + safe "
|
|
3148
|
+
"summary); absent actions listed with count=0 (PLAN-113 W1)"
|
|
3149
|
+
),
|
|
3150
|
+
)
|
|
3151
|
+
crit.add_argument(
|
|
3152
|
+
"--action",
|
|
3153
|
+
default=None,
|
|
3154
|
+
metavar="ACTION",
|
|
3155
|
+
help=(
|
|
3156
|
+
"Drill into a single critical action (must be one of the "
|
|
3157
|
+
"registry actions; errors otherwise)"
|
|
3158
|
+
),
|
|
3159
|
+
)
|
|
3160
|
+
|
|
3161
|
+
|
|
3162
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
3163
|
+
"""Construct the full audit-query argparse tree.
|
|
3164
|
+
|
|
3165
|
+
Delegates each era of sub-commands to a private helper
|
|
3166
|
+
(``_add_v1_subparsers`` / ``_add_v2_subparsers`` /
|
|
3167
|
+
``_add_sprint8_9_subparsers`` / ``_add_plan015_subparsers`` /
|
|
3168
|
+
``_add_plan080_subparsers`` / ``_add_plan081_subparsers``). The
|
|
3169
|
+
public contract of this function is unchanged — callers get back
|
|
3170
|
+
an ``argparse.ArgumentParser`` with ``cmd`` as the required
|
|
3171
|
+
subcommand.
|
|
3172
|
+
"""
|
|
3173
|
+
shared = _build_shared_parser()
|
|
3174
|
+
|
|
3175
|
+
parser = argparse.ArgumentParser(
|
|
3176
|
+
prog="audit-query.py",
|
|
3177
|
+
parents=[shared],
|
|
3178
|
+
description="Query the ceo-orchestration agent spawn audit log",
|
|
3179
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
3180
|
+
epilog=(
|
|
3181
|
+
"Examples:\n"
|
|
3182
|
+
" audit-query.py summary\n"
|
|
3183
|
+
" audit-query.py by-skill --top 10\n"
|
|
3184
|
+
" audit-query.py compliance --json\n"
|
|
3185
|
+
" audit-query.py search 'security' --include-rotated\n"
|
|
3186
|
+
" audit-query.py by-day --days 7\n"
|
|
3187
|
+
" audit-query.py since 2026-04-01\n"
|
|
3188
|
+
" audit-query.py export --format csv > spawns.csv\n"
|
|
3189
|
+
" audit-query.py stats --json\n"
|
|
3190
|
+
" audit-query.py errors\n"
|
|
3191
|
+
" audit-query.py by-domain --window 30d\n"
|
|
3192
|
+
" audit-query.py by-domain --start 2026-04-01 --end 2026-05-01\n"
|
|
3193
|
+
" audit-query.py by-domain --window 30d --check-reopen\n"
|
|
3194
|
+
" audit-query.py critical\n"
|
|
3195
|
+
" audit-query.py critical --action kill_switch_invoked --json\n"
|
|
3196
|
+
),
|
|
3197
|
+
)
|
|
3198
|
+
|
|
3199
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
3200
|
+
_add_v1_subparsers(sub, shared)
|
|
3201
|
+
_add_v2_subparsers(sub, shared)
|
|
3202
|
+
_add_sprint8_9_subparsers(sub, shared)
|
|
3203
|
+
_add_plan015_subparsers(sub, shared)
|
|
3204
|
+
_add_plan080_subparsers(sub, shared)
|
|
3205
|
+
_add_plan081_subparsers(sub, shared)
|
|
3206
|
+
_add_plan113_subparsers(sub, shared)
|
|
3207
|
+
|
|
3208
|
+
return parser
|
|
3209
|
+
|
|
3210
|
+
|
|
3211
|
+
# ---------------------------------------------------------------------------
|
|
3212
|
+
# main
|
|
3213
|
+
# ---------------------------------------------------------------------------
|
|
3214
|
+
|
|
3215
|
+
|
|
3216
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
3217
|
+
"""CLI entrypoint — dispatch audit-query sub-commands."""
|
|
3218
|
+
parser = build_parser()
|
|
3219
|
+
args = parser.parse_args(argv)
|
|
3220
|
+
|
|
3221
|
+
# `errors` subcommand reads a different file, doesn't need the jsonl
|
|
3222
|
+
if args.cmd == "errors":
|
|
3223
|
+
result = cmd_errors(args)
|
|
3224
|
+
print(render(result, as_json=args.as_json, as_csv=args.as_csv))
|
|
3225
|
+
return 0
|
|
3226
|
+
|
|
3227
|
+
log_path = Path(args.log) if args.log else default_log_path()
|
|
3228
|
+
log_files = discover_logs(log_path, args.include_rotated)
|
|
3229
|
+
|
|
3230
|
+
# Perf-P1-002 — streamable subcommands consume the iterator directly so
|
|
3231
|
+
# a 100k-row log does not sit in RAM as a Python list. Non-streamable
|
|
3232
|
+
# subcommands (median / percentile / cross-reference) still materialize
|
|
3233
|
+
# but emit a stderr hint when the log crosses the warn threshold.
|
|
3234
|
+
_STREAMABLE_CMDS = frozenset({
|
|
3235
|
+
"summary", "by-skill", "by-day", "search", "since",
|
|
3236
|
+
})
|
|
3237
|
+
|
|
3238
|
+
if not log_files:
|
|
3239
|
+
entries_iter: Iterable[Dict[str, Any]] = iter(())
|
|
3240
|
+
entries: List[Dict[str, Any]] = []
|
|
3241
|
+
elif args.cmd in _STREAMABLE_CMDS:
|
|
3242
|
+
entries_iter = read_entries(log_files)
|
|
3243
|
+
entries = [] # sentinel — not used on streaming path
|
|
3244
|
+
else:
|
|
3245
|
+
entries = list(read_entries(log_files))
|
|
3246
|
+
entries_iter = entries
|
|
3247
|
+
if len(entries) >= _MATERIALIZATION_WARN_THRESHOLD:
|
|
3248
|
+
print(
|
|
3249
|
+
"[audit-query] NOTE: loaded {n} entries into RAM for "
|
|
3250
|
+
"subcommand {cmd!r} (threshold {thr}). Consider rotating "
|
|
3251
|
+
"older logs or running a streamable subcommand.".format(
|
|
3252
|
+
n=len(entries),
|
|
3253
|
+
cmd=args.cmd,
|
|
3254
|
+
thr=_MATERIALIZATION_WARN_THRESHOLD,
|
|
3255
|
+
),
|
|
3256
|
+
file=sys.stderr,
|
|
3257
|
+
)
|
|
3258
|
+
|
|
3259
|
+
if args.cmd == "summary":
|
|
3260
|
+
result = cmd_summary(entries_iter, args)
|
|
3261
|
+
elif args.cmd == "by-skill":
|
|
3262
|
+
result = cmd_by_skill(entries_iter, args)
|
|
3263
|
+
elif args.cmd == "compliance":
|
|
3264
|
+
result = cmd_compliance(entries, args)
|
|
3265
|
+
elif args.cmd == "by-day":
|
|
3266
|
+
result = cmd_by_day(entries_iter, args)
|
|
3267
|
+
elif args.cmd == "search":
|
|
3268
|
+
result = cmd_search(entries_iter, args)
|
|
3269
|
+
elif args.cmd == "since":
|
|
3270
|
+
result = cmd_since(entries_iter, args)
|
|
3271
|
+
elif args.cmd == "stats":
|
|
3272
|
+
result = cmd_stats(entries, args)
|
|
3273
|
+
elif args.cmd == "export":
|
|
3274
|
+
result = cmd_export(entries, args)
|
|
3275
|
+
# Export handles its own formatting
|
|
3276
|
+
if args.export_format in ("csv", "tsv"):
|
|
3277
|
+
sys.stdout.write(result)
|
|
3278
|
+
return 0
|
|
3279
|
+
print(json.dumps(result, indent=2, ensure_ascii=True))
|
|
3280
|
+
return 0
|
|
3281
|
+
# Sprint 5 A.2 — 7 new sub-commands
|
|
3282
|
+
elif args.cmd == "debate":
|
|
3283
|
+
result = cmd_debate(entries, args)
|
|
3284
|
+
elif args.cmd == "plans":
|
|
3285
|
+
result = cmd_plans(entries, args)
|
|
3286
|
+
elif args.cmd == "vetoes":
|
|
3287
|
+
result = cmd_vetoes(entries, args)
|
|
3288
|
+
elif args.cmd == "benchmarks":
|
|
3289
|
+
result = cmd_benchmarks(entries, args)
|
|
3290
|
+
elif args.cmd == "lessons":
|
|
3291
|
+
result = cmd_lessons(entries, args)
|
|
3292
|
+
elif args.cmd == "metrics":
|
|
3293
|
+
result = cmd_metrics(entries, args)
|
|
3294
|
+
elif args.cmd == "health":
|
|
3295
|
+
result = cmd_health(entries, args)
|
|
3296
|
+
elif args.cmd == "tokens":
|
|
3297
|
+
result = cmd_tokens(entries, args)
|
|
3298
|
+
elif args.cmd == "claims":
|
|
3299
|
+
result = cmd_claims(entries, args)
|
|
3300
|
+
elif args.cmd == "prune-restore-ratio":
|
|
3301
|
+
result = cmd_prune_restore_ratio(entries, args)
|
|
3302
|
+
elif args.cmd == "architect-outcomes":
|
|
3303
|
+
result = cmd_architect_outcomes(entries, args)
|
|
3304
|
+
elif args.cmd == "lessons-effectiveness":
|
|
3305
|
+
result = cmd_lessons_effectiveness(entries, args)
|
|
3306
|
+
elif args.cmd == "weekly-summary":
|
|
3307
|
+
result = cmd_weekly_summary(entries, args)
|
|
3308
|
+
elif args.cmd == "spawn-stats":
|
|
3309
|
+
result = cmd_spawn_stats(entries, args)
|
|
3310
|
+
# PLAN-080 Phase 1
|
|
3311
|
+
elif args.cmd == "by-domain":
|
|
3312
|
+
result = cmd_by_domain(entries, args)
|
|
3313
|
+
# PLAN-081 Phase 6-bis
|
|
3314
|
+
elif args.cmd == "label":
|
|
3315
|
+
result = cmd_label(entries, args)
|
|
3316
|
+
elif args.cmd == "fp-rate":
|
|
3317
|
+
result = cmd_fp_rate(entries, args)
|
|
3318
|
+
elif args.cmd == "case-summary":
|
|
3319
|
+
result = cmd_case_summary(entries, args)
|
|
3320
|
+
elif args.cmd == "codex-writeguard-summary":
|
|
3321
|
+
result = cmd_codex_writeguard_summary(entries, args)
|
|
3322
|
+
# PLAN-113 Phase B Wave W1 — critical-security-action reader
|
|
3323
|
+
elif args.cmd == "critical":
|
|
3324
|
+
result = cmd_critical(entries, args)
|
|
3325
|
+
else: # pragma: no cover — argparse prevents this
|
|
3326
|
+
parser.error(f"unknown cmd: {args.cmd}")
|
|
3327
|
+
|
|
3328
|
+
print(render(result, as_json=args.as_json, as_csv=args.as_csv))
|
|
3329
|
+
return 0
|
|
3330
|
+
|
|
3331
|
+
|
|
3332
|
+
if __name__ == "__main__":
|
|
3333
|
+
sys.exit(main())
|