@jaguilar87/gaia-ops 4.4.0 → 4.7.2
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-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +12 -3
- package/ARCHITECTURE.md +9 -8
- package/CHANGELOG.md +34 -0
- package/README.md +43 -11
- package/agents/terraform-architect.md +1 -1
- package/bin/README.md +2 -2
- package/bin/gaia-doctor.js +18 -5
- package/bin/gaia-history.js +0 -1
- package/bin/gaia-metrics.js +2 -2
- package/bin/gaia-scan.py +23 -1
- package/bin/gaia-update.js +346 -54
- package/bin/pre-publish-validate.js +33 -10
- package/commands/gaia.md +37 -0
- package/config/README.md +3 -9
- package/config/context-contracts.json +47 -15
- package/config/surface-routing.json +9 -1
- package/dist/gaia-ops/.claude-plugin/plugin.json +22 -0
- package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
- package/dist/gaia-ops/agents/devops-developer.md +57 -0
- package/dist/gaia-ops/agents/gaia-system.md +58 -0
- package/dist/gaia-ops/agents/gitops-operator.md +60 -0
- package/dist/gaia-ops/agents/speckit-planner.md +71 -0
- package/dist/gaia-ops/agents/terraform-architect.md +60 -0
- package/dist/gaia-ops/commands/gaia.md +37 -0
- package/dist/gaia-ops/config/README.md +58 -0
- package/dist/gaia-ops/config/cloud/aws.json +140 -0
- package/dist/gaia-ops/config/cloud/gcp.json +145 -0
- package/dist/gaia-ops/config/context-contracts.json +131 -0
- package/dist/gaia-ops/config/git_standards.json +72 -0
- package/dist/gaia-ops/config/surface-routing.json +197 -0
- package/dist/gaia-ops/config/universal-rules.json +10 -0
- package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
- package/dist/gaia-ops/hooks/adapters/base.py +219 -0
- package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
- package/dist/gaia-ops/hooks/adapters/claude_code.py +1477 -0
- package/dist/gaia-ops/hooks/adapters/types.py +194 -0
- package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
- package/dist/gaia-ops/hooks/hooks.json +126 -0
- package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
- package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
- package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
- package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
- package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +124 -0
- package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
- package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
- package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
- package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
- package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
- package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
- package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +576 -0
- package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
- package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
- package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +215 -0
- package/dist/gaia-ops/hooks/modules/context/context_cache.py +129 -0
- package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-ops/hooks/modules/context/context_injector.py +427 -0
- package/dist/gaia-ops/hooks/modules/context/context_writer.py +518 -0
- package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
- package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
- package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
- package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
- package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
- package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +558 -0
- package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
- package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
- package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
- package/dist/gaia-ops/hooks/modules/identity/__init__.py +0 -0
- package/dist/gaia-ops/hooks/modules/identity/identity_provider.py +21 -0
- package/dist/gaia-ops/hooks/modules/identity/ops_identity.py +34 -0
- package/dist/gaia-ops/hooks/modules/identity/security_identity.py +10 -0
- package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +227 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
- package/dist/gaia-ops/hooks/modules/security/__init__.py +89 -0
- package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
- package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
- package/dist/gaia-ops/hooks/modules/security/approval_grants.py +912 -0
- package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +153 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +584 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +86 -0
- package/dist/gaia-ops/hooks/modules/security/command_semantics.py +130 -0
- package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +850 -0
- package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
- package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
- package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
- package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +158 -0
- package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-ops/hooks/modules/tools/__init__.py +25 -0
- package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +708 -0
- package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +181 -0
- package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
- package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
- package/dist/gaia-ops/hooks/modules/tools/task_validator.py +283 -0
- package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
- package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
- package/dist/gaia-ops/hooks/post_compact.py +43 -0
- package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
- package/dist/gaia-ops/hooks/pre_tool_use.py +383 -0
- package/dist/gaia-ops/hooks/session_start.py +69 -0
- package/dist/gaia-ops/hooks/stop_hook.py +69 -0
- package/dist/gaia-ops/hooks/subagent_start.py +71 -0
- package/dist/gaia-ops/hooks/subagent_stop.py +288 -0
- package/dist/gaia-ops/hooks/task_completed.py +70 -0
- package/dist/gaia-ops/hooks/user_prompt_submit.py +177 -0
- package/dist/gaia-ops/settings.json +72 -0
- package/dist/gaia-ops/skills/README.md +109 -0
- package/dist/gaia-ops/skills/agent-protocol/SKILL.md +105 -0
- package/dist/gaia-ops/skills/agent-protocol/examples.md +170 -0
- package/dist/gaia-ops/skills/agent-response/SKILL.md +53 -0
- package/dist/gaia-ops/skills/approval/SKILL.md +85 -0
- package/dist/gaia-ops/skills/approval/examples.md +140 -0
- package/dist/gaia-ops/skills/approval/reference.md +57 -0
- package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
- package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
- package/dist/gaia-ops/skills/context-updater/SKILL.md +76 -0
- package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
- package/dist/gaia-ops/skills/developer-patterns/SKILL.md +93 -0
- package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
- package/dist/gaia-ops/skills/execution/SKILL.md +66 -0
- package/dist/gaia-ops/skills/fast-queries/SKILL.md +47 -0
- package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +92 -0
- package/dist/gaia-ops/skills/gaia-patterns/reference.md +22 -0
- package/dist/gaia-ops/skills/git-conventions/SKILL.md +48 -0
- package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +73 -0
- package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
- package/dist/gaia-ops/skills/investigation/SKILL.md +77 -0
- package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +64 -0
- package/dist/gaia-ops/skills/reference.md +134 -0
- package/dist/gaia-ops/skills/security-tiers/SKILL.md +61 -0
- package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
- package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
- package/dist/gaia-ops/skills/skill-creation/SKILL.md +119 -0
- package/dist/gaia-ops/skills/specification/SKILL.md +186 -0
- package/dist/gaia-ops/skills/speckit-workflow/SKILL.md +165 -0
- package/dist/gaia-ops/skills/speckit-workflow/reference.md +117 -0
- package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +63 -0
- package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
- package/dist/gaia-ops/speckit/README.md +516 -0
- package/dist/gaia-ops/speckit/scripts/.gitkeep +0 -0
- package/dist/gaia-ops/speckit/templates/adr-template.md +118 -0
- package/dist/gaia-ops/speckit/templates/agent-file-template.md +23 -0
- package/dist/gaia-ops/speckit/templates/plan-template.md +227 -0
- package/dist/gaia-ops/speckit/templates/spec-template.md +140 -0
- package/dist/gaia-ops/speckit/templates/tasks-template.md +257 -0
- package/dist/gaia-ops/tools/context/README.md +132 -0
- package/dist/gaia-ops/tools/context/__init__.py +42 -0
- package/dist/gaia-ops/tools/context/_paths.py +20 -0
- package/dist/gaia-ops/tools/context/context_provider.py +476 -0
- package/dist/gaia-ops/tools/context/context_section_reader.py +330 -0
- package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
- package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
- package/dist/gaia-ops/tools/context/surface_router.py +278 -0
- package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
- package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
- package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
- package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
- package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
- package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
- package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
- package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
- package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
- package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
- package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
- package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
- package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
- package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
- package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +262 -0
- package/dist/gaia-ops/tools/memory/README.md +0 -0
- package/dist/gaia-ops/tools/memory/__init__.py +20 -0
- package/dist/gaia-ops/tools/memory/episodic.py +1196 -0
- package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
- package/dist/gaia-ops/tools/review/__init__.py +1 -0
- package/dist/gaia-ops/tools/review/review_engine.py +157 -0
- package/dist/gaia-ops/tools/scan/__init__.py +35 -0
- package/dist/gaia-ops/tools/scan/config.py +247 -0
- package/dist/gaia-ops/tools/scan/merge.py +212 -0
- package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
- package/dist/gaia-ops/tools/scan/registry.py +127 -0
- package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
- package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
- package/dist/gaia-ops/tools/scan/scanners/environment.py +324 -0
- package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
- package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
- package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
- package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
- package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
- package/dist/gaia-ops/tools/scan/setup.py +753 -0
- package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
- package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
- package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
- package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
- package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
- package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
- package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
- package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
- package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
- package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
- package/dist/gaia-ops/tools/scan/ui.py +624 -0
- package/dist/gaia-ops/tools/scan/verify.py +266 -0
- package/dist/gaia-ops/tools/scan/walk.py +118 -0
- package/dist/gaia-ops/tools/scan/workspace.py +85 -0
- package/dist/gaia-ops/tools/validation/README.md +244 -0
- package/dist/gaia-ops/tools/validation/__init__.py +17 -0
- package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
- package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
- package/dist/gaia-security/.claude-plugin/plugin.json +22 -0
- package/dist/gaia-security/config/universal-rules.json +10 -0
- package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
- package/dist/gaia-security/hooks/adapters/base.py +219 -0
- package/dist/gaia-security/hooks/adapters/channel.py +17 -0
- package/dist/gaia-security/hooks/adapters/claude_code.py +1477 -0
- package/dist/gaia-security/hooks/adapters/types.py +194 -0
- package/dist/gaia-security/hooks/adapters/utils.py +25 -0
- package/dist/gaia-security/hooks/hooks.json +57 -0
- package/dist/gaia-security/hooks/modules/__init__.py +15 -0
- package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
- package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
- package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
- package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +124 -0
- package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
- package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
- package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
- package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
- package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
- package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
- package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +576 -0
- package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
- package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
- package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +215 -0
- package/dist/gaia-security/hooks/modules/context/context_cache.py +129 -0
- package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-security/hooks/modules/context/context_injector.py +427 -0
- package/dist/gaia-security/hooks/modules/context/context_writer.py +518 -0
- package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
- package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
- package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
- package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
- package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
- package/dist/gaia-security/hooks/modules/core/plugin_setup.py +558 -0
- package/dist/gaia-security/hooks/modules/core/state.py +179 -0
- package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
- package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
- package/dist/gaia-security/hooks/modules/identity/__init__.py +0 -0
- package/dist/gaia-security/hooks/modules/identity/identity_provider.py +21 -0
- package/dist/gaia-security/hooks/modules/identity/ops_identity.py +34 -0
- package/dist/gaia-security/hooks/modules/identity/security_identity.py +10 -0
- package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/memory/episode_writer.py +227 -0
- package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
- package/dist/gaia-security/hooks/modules/security/__init__.py +89 -0
- package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
- package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
- package/dist/gaia-security/hooks/modules/security/approval_grants.py +912 -0
- package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-security/hooks/modules/security/approval_scopes.py +153 -0
- package/dist/gaia-security/hooks/modules/security/blocked_commands.py +584 -0
- package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +86 -0
- package/dist/gaia-security/hooks/modules/security/command_semantics.py +130 -0
- package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +850 -0
- package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
- package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
- package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
- package/dist/gaia-security/hooks/modules/session/session_event_injector.py +158 -0
- package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-security/hooks/modules/tools/__init__.py +25 -0
- package/dist/gaia-security/hooks/modules/tools/bash_validator.py +708 -0
- package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +181 -0
- package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
- package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
- package/dist/gaia-security/hooks/modules/tools/task_validator.py +283 -0
- package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
- package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
- package/dist/gaia-security/hooks/post_tool_use.py +54 -0
- package/dist/gaia-security/hooks/pre_tool_use.py +383 -0
- package/dist/gaia-security/hooks/session_start.py +69 -0
- package/dist/gaia-security/hooks/stop_hook.py +69 -0
- package/dist/gaia-security/hooks/user_prompt_submit.py +177 -0
- package/dist/gaia-security/settings.json +58 -0
- package/git-hooks/commit-msg +41 -0
- package/hooks/README.md +8 -6
- package/hooks/adapters/channel.py +0 -25
- package/hooks/adapters/claude_code.py +364 -125
- package/hooks/elicitation_result.py +132 -0
- package/hooks/hooks.json +10 -1
- package/hooks/modules/README.md +3 -2
- package/hooks/modules/agents/contract_validator.py +3 -51
- package/hooks/modules/agents/response_contract.py +4 -8
- package/hooks/modules/agents/transcript_reader.py +4 -5
- package/hooks/modules/audit/__init__.py +4 -6
- package/hooks/modules/audit/event_detector.py +0 -2
- package/hooks/modules/audit/metrics.py +108 -187
- package/hooks/modules/audit/workflow_auditor.py +0 -4
- package/hooks/modules/audit/workflow_recorder.py +0 -5
- package/hooks/modules/context/compact_context_builder.py +1 -0
- package/hooks/modules/context/context_cache.py +129 -0
- package/hooks/modules/context/context_injector.py +18 -40
- package/hooks/modules/context/context_writer.py +1 -25
- package/hooks/modules/context/contracts_loader.py +7 -10
- package/hooks/modules/core/hook_entry.py +1 -0
- package/hooks/modules/core/paths.py +12 -13
- package/hooks/modules/core/plugin_mode.py +74 -4
- package/hooks/modules/core/plugin_setup.py +395 -23
- package/hooks/modules/events/__init__.py +1 -0
- package/hooks/modules/events/event_writer.py +210 -0
- package/hooks/modules/identity/ops_identity.py +18 -27
- package/hooks/modules/memory/episode_writer.py +1 -6
- package/hooks/modules/orchestrator/__init__.py +1 -0
- package/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/hooks/modules/security/__init__.py +2 -4
- package/hooks/modules/security/approval_constants.py +5 -1
- package/hooks/modules/security/approval_grants.py +189 -6
- package/hooks/modules/security/approval_messages.py +9 -21
- package/hooks/modules/security/blocked_commands.py +98 -34
- package/hooks/modules/security/command_semantics.py +0 -4
- package/hooks/modules/security/gitops_validator.py +1 -11
- package/hooks/modules/security/mutative_verbs.py +179 -38
- package/hooks/modules/security/tiers.py +1 -19
- package/hooks/modules/session/session_event_injector.py +1 -25
- package/hooks/modules/tools/bash_validator.py +310 -94
- package/hooks/modules/tools/shell_parser.py +0 -1
- package/hooks/modules/tools/task_validator.py +9 -29
- package/hooks/post_tool_use.py +0 -72
- package/hooks/pre_tool_use.py +42 -102
- package/hooks/session_start.py +4 -2
- package/hooks/subagent_start.py +6 -2
- package/hooks/subagent_stop.py +1 -13
- package/hooks/user_prompt_submit.py +119 -37
- package/index.js +1 -1
- package/package.json +5 -3
- package/skills/README.md +3 -5
- package/skills/agent-protocol/SKILL.md +17 -16
- package/skills/agent-protocol/examples.md +6 -6
- package/skills/agent-response/SKILL.md +11 -14
- package/skills/approval/SKILL.md +28 -13
- package/skills/approval/reference.md +2 -2
- package/skills/execution/SKILL.md +1 -1
- package/skills/gaia-patterns/SKILL.md +2 -3
- package/skills/orchestrator-approval/SKILL.md +22 -50
- package/skills/security-tiers/SKILL.md +1 -1
- package/templates/README.md +9 -9
- package/templates/managed-settings.template.json +43 -0
- package/tools/gaia_simulator/runner.py +34 -1
- package/tools/scan/orchestrator.py +13 -0
- package/tools/scan/scanners/base.py +8 -0
- package/tools/scan/scanners/git.py +78 -0
- package/tools/scan/scanners/infrastructure.py +65 -0
- package/tools/scan/scanners/stack.py +110 -0
- package/tools/scan/setup.py +120 -13
- package/tools/scan/workspace.py +85 -0
- package/config/context-contracts.aws.json +0 -42
- package/config/context-contracts.gcp.json +0 -39
- package/skills/project-dispatch/SKILL.md +0 -34
- package/templates/settings.template.json +0 -226
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workflow auditing: anomaly detection and Gaia analysis signaling.
|
|
3
|
+
|
|
4
|
+
Renamed from anomaly_detector.py and expanded with additional anomaly types.
|
|
5
|
+
|
|
6
|
+
Provides:
|
|
7
|
+
- audit(): Full anomaly detection suite -> list of anomaly dicts
|
|
8
|
+
- signal_gaia_analysis(): Create flag file for Gaia analysis
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
from collections import deque
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from ..agents.transcript_analyzer import TranscriptAnalysis
|
|
20
|
+
from .workflow_recorder import get_workflow_memory_dir
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Transcript-analysis check helpers (T009)
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _check_investigation_skip(
|
|
31
|
+
analysis: TranscriptAnalysis,
|
|
32
|
+
) -> Optional[Dict[str, str]]:
|
|
33
|
+
"""Warning if the agent's first tool call was Bash (skipped investigation)."""
|
|
34
|
+
if analysis.first_tool_name == "Bash":
|
|
35
|
+
return {
|
|
36
|
+
"type": "investigation_skip",
|
|
37
|
+
"severity": "warning",
|
|
38
|
+
"message": (
|
|
39
|
+
"Agent's first tool call was Bash instead of a "
|
|
40
|
+
"read-only investigation tool (Read/Glob/Grep)"
|
|
41
|
+
),
|
|
42
|
+
}
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _check_context_ignored(
|
|
47
|
+
analysis: TranscriptAnalysis,
|
|
48
|
+
) -> Optional[Dict[str, str]]:
|
|
49
|
+
"""Warning if the first tool call does not reference project-context paths."""
|
|
50
|
+
if not analysis.tool_sequence:
|
|
51
|
+
return None
|
|
52
|
+
first_call = analysis.tool_sequence[0]
|
|
53
|
+
args_str = json.dumps(first_call.arguments)
|
|
54
|
+
# Look for any project-context path references
|
|
55
|
+
context_indicators = [
|
|
56
|
+
"project-context",
|
|
57
|
+
".claude/",
|
|
58
|
+
"CLAUDE.md",
|
|
59
|
+
"context-contracts",
|
|
60
|
+
]
|
|
61
|
+
if not any(indicator in args_str for indicator in context_indicators):
|
|
62
|
+
return {
|
|
63
|
+
"type": "context_ignored",
|
|
64
|
+
"severity": "warning",
|
|
65
|
+
"message": (
|
|
66
|
+
"First tool call does not reference any project-context "
|
|
67
|
+
"paths — agent may have ignored injected context"
|
|
68
|
+
),
|
|
69
|
+
}
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _check_context_update_missing(
|
|
74
|
+
analysis: TranscriptAnalysis,
|
|
75
|
+
agent_output: str,
|
|
76
|
+
) -> Optional[Dict[str, str]]:
|
|
77
|
+
"""Info if context-updater skill was injected but no CONTEXT_UPDATE emitted."""
|
|
78
|
+
if "context-updater" in analysis.skills_injected:
|
|
79
|
+
if "CONTEXT_UPDATE" not in agent_output:
|
|
80
|
+
return {
|
|
81
|
+
"type": "context_update_missing",
|
|
82
|
+
"severity": "info",
|
|
83
|
+
"message": (
|
|
84
|
+
"context-updater skill was injected but agent did not "
|
|
85
|
+
"emit a CONTEXT_UPDATE block"
|
|
86
|
+
),
|
|
87
|
+
}
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _check_excessive_tool_calls(
|
|
92
|
+
analysis: TranscriptAnalysis,
|
|
93
|
+
) -> Optional[Dict[str, str]]:
|
|
94
|
+
"""Warning if tool_call_count exceeds 75."""
|
|
95
|
+
if analysis.tool_call_count > 75:
|
|
96
|
+
return {
|
|
97
|
+
"type": "excessive_tool_calls",
|
|
98
|
+
"severity": "warning",
|
|
99
|
+
"message": (
|
|
100
|
+
f"Agent made {analysis.tool_call_count} tool calls "
|
|
101
|
+
f"(threshold: 75) — may indicate inefficient exploration"
|
|
102
|
+
),
|
|
103
|
+
}
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _check_token_budget(
|
|
108
|
+
analysis: TranscriptAnalysis,
|
|
109
|
+
) -> Optional[Dict[str, str]]:
|
|
110
|
+
"""Info if cache_creation_tokens exceeds 200000."""
|
|
111
|
+
if analysis.cache_creation_tokens > 200000:
|
|
112
|
+
return {
|
|
113
|
+
"type": "token_budget",
|
|
114
|
+
"severity": "info",
|
|
115
|
+
"message": (
|
|
116
|
+
f"Cache creation tokens ({analysis.cache_creation_tokens}) "
|
|
117
|
+
f"exceeded 200,000 — large context was created"
|
|
118
|
+
),
|
|
119
|
+
}
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _check_pipe_retroactive(
|
|
124
|
+
analysis: TranscriptAnalysis,
|
|
125
|
+
) -> List[Dict[str, str]]:
|
|
126
|
+
"""Warning per pipe command detected in transcript."""
|
|
127
|
+
results: List[Dict[str, str]] = []
|
|
128
|
+
for cmd in analysis.pipe_commands:
|
|
129
|
+
# Truncate long commands for readability
|
|
130
|
+
display_cmd = cmd[:120] + "..." if len(cmd) > 120 else cmd
|
|
131
|
+
results.append({
|
|
132
|
+
"type": "pipe_retroactive",
|
|
133
|
+
"severity": "warning",
|
|
134
|
+
"message": f"Pipe command detected in transcript: {display_cmd}",
|
|
135
|
+
})
|
|
136
|
+
return results
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _check_model_mismatch(
|
|
140
|
+
analysis: TranscriptAnalysis,
|
|
141
|
+
metrics: Dict[str, Any],
|
|
142
|
+
) -> Optional[Dict[str, str]]:
|
|
143
|
+
"""Info if transcript model differs from agent definition model."""
|
|
144
|
+
definition_model = ""
|
|
145
|
+
snapshot = metrics.get("default_skills_snapshot")
|
|
146
|
+
if isinstance(snapshot, dict):
|
|
147
|
+
definition_model = snapshot.get("model", "")
|
|
148
|
+
if (
|
|
149
|
+
analysis.model
|
|
150
|
+
and definition_model
|
|
151
|
+
and analysis.model != definition_model
|
|
152
|
+
):
|
|
153
|
+
return {
|
|
154
|
+
"type": "model_mismatch",
|
|
155
|
+
"severity": "info",
|
|
156
|
+
"message": (
|
|
157
|
+
f"Transcript model ({analysis.model}) differs from "
|
|
158
|
+
f"agent definition model ({definition_model})"
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _check_skill_order(
|
|
165
|
+
analysis: TranscriptAnalysis,
|
|
166
|
+
metrics: Dict[str, Any],
|
|
167
|
+
) -> Optional[Dict[str, str]]:
|
|
168
|
+
"""Info if skills were injected in an unexpected order."""
|
|
169
|
+
snapshot = metrics.get("default_skills_snapshot")
|
|
170
|
+
if not isinstance(snapshot, dict):
|
|
171
|
+
return None
|
|
172
|
+
expected_skills = snapshot.get("skills", [])
|
|
173
|
+
if not expected_skills or not analysis.skills_injected:
|
|
174
|
+
return None
|
|
175
|
+
# Check if the injected skills appear in the expected order
|
|
176
|
+
# (only consider skills that are in the expected list)
|
|
177
|
+
expected_set = set(expected_skills)
|
|
178
|
+
actual_ordered = [s for s in analysis.skills_injected if s in expected_set]
|
|
179
|
+
expected_ordered = [s for s in expected_skills if s in set(actual_ordered)]
|
|
180
|
+
if actual_ordered and expected_ordered and actual_ordered != expected_ordered:
|
|
181
|
+
return {
|
|
182
|
+
"type": "skill_order",
|
|
183
|
+
"severity": "info",
|
|
184
|
+
"message": (
|
|
185
|
+
f"Skills injected in unexpected order: "
|
|
186
|
+
f"{actual_ordered} (expected: {expected_ordered})"
|
|
187
|
+
),
|
|
188
|
+
}
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _check_duplicate_tools(
|
|
193
|
+
analysis: TranscriptAnalysis,
|
|
194
|
+
) -> Optional[Dict[str, str]]:
|
|
195
|
+
"""Info if duplicate tool calls were detected."""
|
|
196
|
+
if analysis.duplicate_tool_calls:
|
|
197
|
+
dup_summary = ", ".join(
|
|
198
|
+
f"{d.tool_name}(x{len(d.indices)})"
|
|
199
|
+
for d in analysis.duplicate_tool_calls
|
|
200
|
+
)
|
|
201
|
+
return {
|
|
202
|
+
"type": "duplicate_tools",
|
|
203
|
+
"severity": "info",
|
|
204
|
+
"message": (
|
|
205
|
+
f"Duplicate tool calls detected: {dup_summary}"
|
|
206
|
+
),
|
|
207
|
+
}
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _check_token_explosion(
|
|
212
|
+
analysis: TranscriptAnalysis,
|
|
213
|
+
) -> Optional[Dict[str, str]]:
|
|
214
|
+
"""Warning if total token consumption exceeds 10 million."""
|
|
215
|
+
total = (
|
|
216
|
+
analysis.input_tokens
|
|
217
|
+
+ analysis.cache_creation_tokens
|
|
218
|
+
+ analysis.output_tokens
|
|
219
|
+
)
|
|
220
|
+
if total > 10_000_000:
|
|
221
|
+
return {
|
|
222
|
+
"type": "token_explosion",
|
|
223
|
+
"severity": "warning",
|
|
224
|
+
"message": (
|
|
225
|
+
f"Total token consumption ({total:,}) exceeded 10,000,000 "
|
|
226
|
+
f"— possible runaway generation"
|
|
227
|
+
),
|
|
228
|
+
}
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _check_cache_efficiency(
|
|
233
|
+
analysis: TranscriptAnalysis,
|
|
234
|
+
) -> Optional[Dict[str, str]]:
|
|
235
|
+
"""Info if cache read ratio is below 60% with significant token volume."""
|
|
236
|
+
total = (
|
|
237
|
+
analysis.cache_read_tokens
|
|
238
|
+
+ analysis.cache_creation_tokens
|
|
239
|
+
+ analysis.input_tokens
|
|
240
|
+
)
|
|
241
|
+
if total > 1000:
|
|
242
|
+
ratio = analysis.cache_read_tokens / total
|
|
243
|
+
if ratio < 0.60:
|
|
244
|
+
return {
|
|
245
|
+
"type": "cache_efficiency",
|
|
246
|
+
"severity": "info",
|
|
247
|
+
"message": (
|
|
248
|
+
f"Cache read ratio is {ratio:.1%} (below 60%) "
|
|
249
|
+
f"with {total:,} total input tokens — cache may be underutilized"
|
|
250
|
+
),
|
|
251
|
+
}
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _check_duplicate_write_storm(
|
|
256
|
+
analysis: TranscriptAnalysis,
|
|
257
|
+
) -> Optional[Dict[str, str]]:
|
|
258
|
+
"""Warning if Write or Edit tool calls appear 3+ times with identical args."""
|
|
259
|
+
for dup in analysis.duplicate_tool_calls:
|
|
260
|
+
if dup.tool_name in ("Write", "Edit") and len(dup.indices) >= 3:
|
|
261
|
+
return {
|
|
262
|
+
"type": "duplicate_write_storm",
|
|
263
|
+
"severity": "warning",
|
|
264
|
+
"message": (
|
|
265
|
+
f"Duplicate {dup.tool_name} storm detected: "
|
|
266
|
+
f"{len(dup.indices)} identical calls at indices "
|
|
267
|
+
f"{dup.indices}"
|
|
268
|
+
),
|
|
269
|
+
}
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _check_duration_outlier(
|
|
274
|
+
analysis: TranscriptAnalysis,
|
|
275
|
+
) -> Optional[Dict[str, str]]:
|
|
276
|
+
"""Warning if agent execution exceeded 10 minutes."""
|
|
277
|
+
if analysis.duration_ms is not None and analysis.duration_ms > 600_000:
|
|
278
|
+
minutes = analysis.duration_ms / 60_000
|
|
279
|
+
return {
|
|
280
|
+
"type": "duration_outlier",
|
|
281
|
+
"severity": "warning",
|
|
282
|
+
"message": (
|
|
283
|
+
f"Agent execution took {minutes:.1f} minutes "
|
|
284
|
+
f"(threshold: 10 min) — may indicate stalled or inefficient work"
|
|
285
|
+
),
|
|
286
|
+
}
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _check_tool_call_velocity(
|
|
291
|
+
analysis: TranscriptAnalysis,
|
|
292
|
+
) -> Optional[Dict[str, str]]:
|
|
293
|
+
"""Warning if tool call rate exceeds 20 calls per minute."""
|
|
294
|
+
if (
|
|
295
|
+
analysis.duration_ms is not None
|
|
296
|
+
and analysis.duration_ms > 0
|
|
297
|
+
and (analysis.tool_call_count / (analysis.duration_ms / 60_000)) > 20
|
|
298
|
+
):
|
|
299
|
+
velocity = analysis.tool_call_count / (analysis.duration_ms / 60_000)
|
|
300
|
+
return {
|
|
301
|
+
"type": "tool_call_velocity",
|
|
302
|
+
"severity": "warning",
|
|
303
|
+
"message": (
|
|
304
|
+
f"Tool call velocity is {velocity:.1f} calls/min "
|
|
305
|
+
f"(threshold: 20) — agent may be thrashing"
|
|
306
|
+
),
|
|
307
|
+
}
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def audit(
|
|
312
|
+
metrics: Dict[str, Any],
|
|
313
|
+
agent_output: str = "",
|
|
314
|
+
task_info: Optional[Dict[str, Any]] = None,
|
|
315
|
+
rejected_sections: Optional[List[str]] = None,
|
|
316
|
+
transcript_analysis: Optional[TranscriptAnalysis] = None,
|
|
317
|
+
) -> List[Dict[str, str]]:
|
|
318
|
+
"""
|
|
319
|
+
Detect anomalies in workflow execution.
|
|
320
|
+
|
|
321
|
+
Checks:
|
|
322
|
+
- execution_failure: exit_code != 0
|
|
323
|
+
- consecutive_failures: 3+ failures in a row for same agent
|
|
324
|
+
- missing_evidence: COMPLETE but no evidence in json:contract block
|
|
325
|
+
- empty_evidence: json:contract evidence exists but commands_run empty or all "not run"
|
|
326
|
+
- skipped_verification: task has verify command in injected_context but not in commands_run
|
|
327
|
+
- scope_escalation: rejected_sections exist (agent tried to write outside its scope)
|
|
328
|
+
|
|
329
|
+
Transcript-analysis checks (only when transcript_analysis is provided):
|
|
330
|
+
- investigation_skip: first tool was Bash
|
|
331
|
+
- context_ignored: first tool call has no project-context paths
|
|
332
|
+
- context_update_missing: context-updater injected but no CONTEXT_UPDATE emitted
|
|
333
|
+
- excessive_tool_calls: tool_call_count > 75
|
|
334
|
+
- token_budget: cache_creation_tokens > 200000
|
|
335
|
+
- token_explosion: total tokens (input+cache_creation+output) > 10M
|
|
336
|
+
- cache_efficiency: cache read ratio < 60% with significant volume
|
|
337
|
+
- duplicate_write_storm: Write/Edit tool with 3+ identical calls
|
|
338
|
+
- duration_outlier: duration_ms > 600,000 (10 min)
|
|
339
|
+
- tool_call_velocity: > 20 tool calls per minute
|
|
340
|
+
- pipe_retroactive: pipe commands found in transcript
|
|
341
|
+
- model_mismatch: transcript model != agent definition model
|
|
342
|
+
- skill_order: skills injected in unexpected order
|
|
343
|
+
- duplicate_tools: duplicate tool calls detected
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
metrics: Workflow metrics dict (from workflow_recorder.record()).
|
|
347
|
+
agent_output: Complete agent output string (for evidence checks).
|
|
348
|
+
task_info: Task metadata including injected_context (for verification checks).
|
|
349
|
+
rejected_sections: List of context sections rejected by permission validation.
|
|
350
|
+
transcript_analysis: Optional TranscriptAnalysis from transcript_analyzer.
|
|
351
|
+
When None (default), transcript-based checks are skipped for backward
|
|
352
|
+
compatibility.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
List of anomaly descriptions
|
|
356
|
+
"""
|
|
357
|
+
anomalies: List[Dict[str, str]] = []
|
|
358
|
+
task_info = task_info or {}
|
|
359
|
+
|
|
360
|
+
# --- existing: execution_failure ---
|
|
361
|
+
if metrics.get("exit_code", 0) != 0:
|
|
362
|
+
anomalies.append({
|
|
363
|
+
"type": "execution_failure",
|
|
364
|
+
"severity": "error",
|
|
365
|
+
"message": f"Agent {metrics['agent']} failed with exit code {metrics['exit_code']}"
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
# --- existing: consecutive_failures ---
|
|
369
|
+
try:
|
|
370
|
+
workflow_memory_dir = get_workflow_memory_dir()
|
|
371
|
+
metrics_file = workflow_memory_dir / "metrics.jsonl"
|
|
372
|
+
|
|
373
|
+
if metrics_file.exists():
|
|
374
|
+
with open(metrics_file) as f:
|
|
375
|
+
recent = list(deque(f, maxlen=7))
|
|
376
|
+
# Get last 5 metrics (excluding current which is the last line)
|
|
377
|
+
last_5 = (
|
|
378
|
+
[json.loads(line) for line in recent[:-1]][-5:]
|
|
379
|
+
if len(recent) > 1
|
|
380
|
+
else []
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Count recent failures for same agent
|
|
384
|
+
agent = metrics["agent"]
|
|
385
|
+
recent_failures = [
|
|
386
|
+
m for m in last_5
|
|
387
|
+
if m.get("agent") == agent and m.get("exit_code", 0) != 0
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
# If current also failed and we have 2+ previous failures
|
|
391
|
+
if metrics.get("exit_code", 0) != 0 and len(recent_failures) >= 2:
|
|
392
|
+
anomalies.append({
|
|
393
|
+
"type": "consecutive_failures",
|
|
394
|
+
"severity": "critical",
|
|
395
|
+
"message": (
|
|
396
|
+
f"Agent {agent} has failed "
|
|
397
|
+
f"{len(recent_failures) + 1} times consecutively"
|
|
398
|
+
),
|
|
399
|
+
})
|
|
400
|
+
except Exception as e:
|
|
401
|
+
logger.debug(f"Could not check consecutive failures: {e}")
|
|
402
|
+
|
|
403
|
+
# --- NEW: missing_evidence ---
|
|
404
|
+
if agent_output:
|
|
405
|
+
plan_status = metrics.get("plan_status", "")
|
|
406
|
+
if "COMPLETE" in plan_status:
|
|
407
|
+
from ..agents.contract_validator import parse_contract
|
|
408
|
+
contract = parse_contract(agent_output)
|
|
409
|
+
has_evidence = (
|
|
410
|
+
contract is not None
|
|
411
|
+
and isinstance(contract.get("evidence_report"), dict)
|
|
412
|
+
and bool(contract["evidence_report"])
|
|
413
|
+
)
|
|
414
|
+
if not has_evidence:
|
|
415
|
+
anomalies.append({
|
|
416
|
+
"type": "missing_evidence",
|
|
417
|
+
"severity": "warning",
|
|
418
|
+
"message": (
|
|
419
|
+
f"Agent {metrics['agent']} completed but "
|
|
420
|
+
f"did not include evidence in json:contract block"
|
|
421
|
+
),
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
# --- NEW: empty_evidence ---
|
|
425
|
+
if agent_output:
|
|
426
|
+
from ..agents.contract_validator import parse_contract
|
|
427
|
+
contract = parse_contract(agent_output)
|
|
428
|
+
if contract is not None:
|
|
429
|
+
evidence = contract.get("evidence_report")
|
|
430
|
+
if isinstance(evidence, dict):
|
|
431
|
+
commands_run = evidence.get("commands_run", [])
|
|
432
|
+
if isinstance(commands_run, list):
|
|
433
|
+
not_run_pattern = re.compile(
|
|
434
|
+
r"\b(not\s+run|not\s+executed|skipped|n/a|none)\b",
|
|
435
|
+
re.IGNORECASE,
|
|
436
|
+
)
|
|
437
|
+
if not commands_run:
|
|
438
|
+
# commands_run key exists but is empty list
|
|
439
|
+
anomalies.append({
|
|
440
|
+
"type": "empty_evidence",
|
|
441
|
+
"severity": "warning",
|
|
442
|
+
"message": (
|
|
443
|
+
f"Agent {metrics['agent']} has evidence in "
|
|
444
|
+
f"json:contract but commands_run is empty"
|
|
445
|
+
),
|
|
446
|
+
})
|
|
447
|
+
elif all(
|
|
448
|
+
isinstance(c, str) and not_run_pattern.search(c)
|
|
449
|
+
for c in commands_run
|
|
450
|
+
):
|
|
451
|
+
anomalies.append({
|
|
452
|
+
"type": "empty_evidence",
|
|
453
|
+
"severity": "warning",
|
|
454
|
+
"message": (
|
|
455
|
+
f"Agent {metrics['agent']} has evidence in "
|
|
456
|
+
f"json:contract but all commands_run entries "
|
|
457
|
+
f"indicate 'not run'"
|
|
458
|
+
),
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
# --- NEW: skipped_verification ---
|
|
462
|
+
injected = task_info.get("injected_context") or {}
|
|
463
|
+
investigation_brief = injected.get("investigation_brief", {}) or {}
|
|
464
|
+
required_checks = investigation_brief.get("required_checks", [])
|
|
465
|
+
if required_checks and agent_output:
|
|
466
|
+
# Extract commands that were actually run from evidence
|
|
467
|
+
from ..agents.contract_validator import extract_commands_from_evidence
|
|
468
|
+
commands_run = extract_commands_from_evidence(agent_output)
|
|
469
|
+
|
|
470
|
+
# Check if any required check mentions a verify command
|
|
471
|
+
for check in required_checks:
|
|
472
|
+
if isinstance(check, str) and "verify" in check.lower():
|
|
473
|
+
# If there are required verification checks but no commands were run at all
|
|
474
|
+
if not commands_run:
|
|
475
|
+
anomalies.append({
|
|
476
|
+
"type": "skipped_verification",
|
|
477
|
+
"severity": "warning",
|
|
478
|
+
"message": (
|
|
479
|
+
f"Agent {metrics['agent']} has verification requirements "
|
|
480
|
+
f"in injected_context but ran no commands"
|
|
481
|
+
),
|
|
482
|
+
})
|
|
483
|
+
break
|
|
484
|
+
|
|
485
|
+
# --- NEW: scope_escalation ---
|
|
486
|
+
if rejected_sections:
|
|
487
|
+
anomalies.append({
|
|
488
|
+
"type": "scope_escalation",
|
|
489
|
+
"severity": "warning",
|
|
490
|
+
"message": (
|
|
491
|
+
f"Agent {metrics['agent']} attempted to write to "
|
|
492
|
+
f"unauthorized sections: {', '.join(rejected_sections)}"
|
|
493
|
+
),
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
# --- Transcript-analysis checks (T009) ---
|
|
497
|
+
if transcript_analysis is not None:
|
|
498
|
+
for check_fn in (
|
|
499
|
+
_check_investigation_skip,
|
|
500
|
+
_check_context_ignored,
|
|
501
|
+
_check_excessive_tool_calls,
|
|
502
|
+
_check_token_budget,
|
|
503
|
+
_check_duplicate_tools,
|
|
504
|
+
_check_token_explosion,
|
|
505
|
+
_check_cache_efficiency,
|
|
506
|
+
_check_duplicate_write_storm,
|
|
507
|
+
_check_duration_outlier,
|
|
508
|
+
_check_tool_call_velocity,
|
|
509
|
+
):
|
|
510
|
+
result = check_fn(transcript_analysis)
|
|
511
|
+
if result is not None:
|
|
512
|
+
anomalies.append(result)
|
|
513
|
+
|
|
514
|
+
# Checks that need agent_output
|
|
515
|
+
result = _check_context_update_missing(transcript_analysis, agent_output)
|
|
516
|
+
if result is not None:
|
|
517
|
+
anomalies.append(result)
|
|
518
|
+
|
|
519
|
+
# Checks that need metrics
|
|
520
|
+
for check_fn_m in (_check_model_mismatch, _check_skill_order):
|
|
521
|
+
result = check_fn_m(transcript_analysis, metrics)
|
|
522
|
+
if result is not None:
|
|
523
|
+
anomalies.append(result)
|
|
524
|
+
|
|
525
|
+
# Pipe check returns a list (one per pipe command)
|
|
526
|
+
anomalies.extend(_check_pipe_retroactive(transcript_analysis))
|
|
527
|
+
|
|
528
|
+
return anomalies
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def signal_gaia_analysis(
|
|
533
|
+
anomalies: List[Dict],
|
|
534
|
+
metrics: Dict[str, Any],
|
|
535
|
+
) -> None:
|
|
536
|
+
"""
|
|
537
|
+
Signal that Gaia analysis is needed.
|
|
538
|
+
|
|
539
|
+
Creates a flag file that orchestrator can detect.
|
|
540
|
+
"""
|
|
541
|
+
try:
|
|
542
|
+
signals_dir = get_workflow_memory_dir() / "signals"
|
|
543
|
+
signals_dir.mkdir(parents=True, exist_ok=True)
|
|
544
|
+
|
|
545
|
+
signal_file = signals_dir / "needs_analysis.flag"
|
|
546
|
+
|
|
547
|
+
signal_data = {
|
|
548
|
+
"timestamp": datetime.now().isoformat(),
|
|
549
|
+
"created_at": datetime.now().isoformat(),
|
|
550
|
+
"ttl_hours": 1,
|
|
551
|
+
"anomalies": anomalies,
|
|
552
|
+
"metrics_summary": {
|
|
553
|
+
"agent": metrics["agent"],
|
|
554
|
+
"task_id": metrics["task_id"],
|
|
555
|
+
"duration_ms": metrics.get("duration_ms"),
|
|
556
|
+
"exit_code": metrics.get("exit_code"),
|
|
557
|
+
},
|
|
558
|
+
"suggested_action": "Invoke /gaia for system analysis",
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
with open(signal_file, "w") as f:
|
|
562
|
+
json.dump(signal_data, f, indent=2)
|
|
563
|
+
|
|
564
|
+
logger.info(f"Gaia analysis signal created: {signal_file}")
|
|
565
|
+
|
|
566
|
+
# Also log to a permanent anomaly log
|
|
567
|
+
anomaly_log = signals_dir.parent / "anomalies.jsonl"
|
|
568
|
+
with open(anomaly_log, "a") as f:
|
|
569
|
+
f.write(json.dumps({
|
|
570
|
+
"timestamp": datetime.now().isoformat(),
|
|
571
|
+
"anomalies": anomalies,
|
|
572
|
+
"metrics": metrics,
|
|
573
|
+
}) + "\n")
|
|
574
|
+
|
|
575
|
+
except Exception as e:
|
|
576
|
+
logger.warning(f"Could not create analysis signal: {e}")
|