@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,427 @@
|
|
|
1
|
+
"""Core context injection subsystem for project agents.
|
|
2
|
+
|
|
3
|
+
Handles:
|
|
4
|
+
- build_project_context: builds context string for additionalContext injection
|
|
5
|
+
- check_pending_updates_threshold: warns when pending updates accumulate
|
|
6
|
+
- check_recent_critical_anomalies: surfaces critical anomalies from JSONL log
|
|
7
|
+
- consume_anomaly_flag: reads and deletes anomaly signal flags
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import subprocess
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from ..core.paths import get_plugin_data_dir
|
|
18
|
+
from ..session.session_manager import get_or_create_session_id
|
|
19
|
+
from .anchor_tracker import extract_anchors, save_anchors
|
|
20
|
+
from .contracts_loader import build_context_update_reminder
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _prune_empty_values(data: dict) -> dict:
|
|
26
|
+
"""Drop keys with empty telemetry values while preserving False/0."""
|
|
27
|
+
pruned = {}
|
|
28
|
+
for key, value in data.items():
|
|
29
|
+
if value in (None, "", [], {}):
|
|
30
|
+
continue
|
|
31
|
+
pruned[key] = value
|
|
32
|
+
return pruned
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_context_telemetry_snapshot(context_payload: dict) -> dict:
|
|
36
|
+
"""Build a compact telemetry snapshot from injected context payload data."""
|
|
37
|
+
if not isinstance(context_payload, dict) or not context_payload:
|
|
38
|
+
return {}
|
|
39
|
+
|
|
40
|
+
project_knowledge = context_payload.get("project_knowledge") or {}
|
|
41
|
+
metadata = context_payload.get("metadata") or {}
|
|
42
|
+
surface_routing = context_payload.get("surface_routing") or {}
|
|
43
|
+
investigation_brief = context_payload.get("investigation_brief") or {}
|
|
44
|
+
write_permissions = context_payload.get("write_permissions") or {}
|
|
45
|
+
|
|
46
|
+
contract_sections = sorted(project_knowledge.keys())
|
|
47
|
+
readable_sections = sorted(write_permissions.get("readable_sections") or [])
|
|
48
|
+
writable_sections = sorted(write_permissions.get("writable_sections") or [])
|
|
49
|
+
|
|
50
|
+
return _prune_empty_values({
|
|
51
|
+
"contract_sections": contract_sections,
|
|
52
|
+
"contract_sections_count": len(contract_sections),
|
|
53
|
+
"metadata": _prune_empty_values({
|
|
54
|
+
"cloud_provider": metadata.get("cloud_provider"),
|
|
55
|
+
"contract_version": metadata.get("contract_version"),
|
|
56
|
+
"rules_count": metadata.get("rules_count"),
|
|
57
|
+
"historical_episodes_count": metadata.get("historical_episodes_count"),
|
|
58
|
+
"surface_routing_version": metadata.get("surface_routing_version"),
|
|
59
|
+
"active_surfaces_count": metadata.get("active_surfaces_count"),
|
|
60
|
+
"surface_routing_confidence": metadata.get("surface_routing_confidence"),
|
|
61
|
+
}),
|
|
62
|
+
"surface_routing": _prune_empty_values({
|
|
63
|
+
"primary_surface": surface_routing.get("primary_surface"),
|
|
64
|
+
"active_surfaces": sorted(surface_routing.get("active_surfaces") or []),
|
|
65
|
+
"dispatch_mode": surface_routing.get("dispatch_mode"),
|
|
66
|
+
"multi_surface": surface_routing.get("multi_surface"),
|
|
67
|
+
"recommended_agents": sorted(surface_routing.get("recommended_agents") or []),
|
|
68
|
+
}),
|
|
69
|
+
"investigation_brief": _prune_empty_values({
|
|
70
|
+
"agent_role": investigation_brief.get("agent_role"),
|
|
71
|
+
"primary_surface": investigation_brief.get("primary_surface"),
|
|
72
|
+
"adjacent_surfaces": sorted(investigation_brief.get("adjacent_surfaces") or []),
|
|
73
|
+
"cross_check_required": investigation_brief.get("cross_check_required"),
|
|
74
|
+
"consolidation_required": investigation_brief.get("consolidation_required"),
|
|
75
|
+
"required_checks_count": len(investigation_brief.get("required_checks") or []),
|
|
76
|
+
"evidence_required": sorted(investigation_brief.get("evidence_required") or []),
|
|
77
|
+
}),
|
|
78
|
+
"context_update_scope": _prune_empty_values({
|
|
79
|
+
"readable_sections": readable_sections,
|
|
80
|
+
"readable_sections_count": len(readable_sections),
|
|
81
|
+
"writable_sections": writable_sections,
|
|
82
|
+
"writable_sections_count": len(writable_sections),
|
|
83
|
+
}),
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def check_pending_updates_threshold() -> str:
|
|
88
|
+
"""
|
|
89
|
+
Check if pending updates count exceeds threshold and return warning text.
|
|
90
|
+
|
|
91
|
+
Returns warning string to inject into prompt, or empty string if below threshold.
|
|
92
|
+
Must NEVER block or slow down context injection (target: <50ms).
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
threshold = int(os.environ.get("PENDING_UPDATE_THRESHOLD", "10"))
|
|
96
|
+
|
|
97
|
+
# Fast path: try to read index directly (no module import)
|
|
98
|
+
index_path = Path(".claude/project-context/pending-updates/pending-index.json")
|
|
99
|
+
if not index_path.exists():
|
|
100
|
+
return ""
|
|
101
|
+
|
|
102
|
+
with open(index_path, 'r') as f:
|
|
103
|
+
index_data = json.load(f)
|
|
104
|
+
|
|
105
|
+
pending_count = index_data.get("pending_count", 0)
|
|
106
|
+
if pending_count < threshold:
|
|
107
|
+
return ""
|
|
108
|
+
|
|
109
|
+
logger.info(f"Pending updates threshold reached: {pending_count} >= {threshold}")
|
|
110
|
+
return (
|
|
111
|
+
f"\n# Pending Context Updates Warning\n"
|
|
112
|
+
f"There are {pending_count} pending context update suggestions awaiting review. "
|
|
113
|
+
f"Run `gaia-review` or `python3 tools/review/review_engine.py list` to review them.\n\n"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.debug(f"Pending updates check failed (non-fatal): {e}")
|
|
118
|
+
return ""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def check_recent_critical_anomalies() -> str:
|
|
122
|
+
"""Check anomalies.jsonl for recent critical anomalies and return a summary.
|
|
123
|
+
|
|
124
|
+
Scans the last 20 lines of the anomaly log for critical-severity entries
|
|
125
|
+
from the past hour. Returns a short warning string suitable for context
|
|
126
|
+
injection, or empty string if nothing noteworthy is found.
|
|
127
|
+
|
|
128
|
+
This is intentionally lightweight: reads only the tail of the file and
|
|
129
|
+
returns at most a one-line count + type summary.
|
|
130
|
+
"""
|
|
131
|
+
anomaly_log = (
|
|
132
|
+
get_plugin_data_dir() / "project-context" / "workflow-episodic-memory" / "anomalies.jsonl"
|
|
133
|
+
)
|
|
134
|
+
if not anomaly_log.exists():
|
|
135
|
+
return ""
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
# Read only the tail (last 20 lines) for speed
|
|
139
|
+
lines = anomaly_log.read_text().splitlines()[-20:]
|
|
140
|
+
one_hour_ago = datetime.now().timestamp() - 3600
|
|
141
|
+
critical_types: list[str] = []
|
|
142
|
+
|
|
143
|
+
for line in lines:
|
|
144
|
+
if not line.strip():
|
|
145
|
+
continue
|
|
146
|
+
try:
|
|
147
|
+
entry = json.loads(line)
|
|
148
|
+
except json.JSONDecodeError:
|
|
149
|
+
continue
|
|
150
|
+
ts = entry.get("timestamp", "")
|
|
151
|
+
if ts:
|
|
152
|
+
try:
|
|
153
|
+
entry_time = datetime.fromisoformat(ts).timestamp()
|
|
154
|
+
except (ValueError, TypeError):
|
|
155
|
+
continue
|
|
156
|
+
if entry_time < one_hour_ago:
|
|
157
|
+
continue
|
|
158
|
+
for anomaly in entry.get("anomalies", []):
|
|
159
|
+
if anomaly.get("severity") == "critical":
|
|
160
|
+
critical_types.append(anomaly.get("type", "unknown"))
|
|
161
|
+
|
|
162
|
+
if not critical_types:
|
|
163
|
+
return ""
|
|
164
|
+
|
|
165
|
+
# Deduplicate and summarize
|
|
166
|
+
unique_types = sorted(set(critical_types))
|
|
167
|
+
return (
|
|
168
|
+
f"\n# Recent Critical Anomalies\n"
|
|
169
|
+
f"{len(critical_types)} critical anomaly(ies) in the last hour "
|
|
170
|
+
f"(types: {', '.join(unique_types)}). "
|
|
171
|
+
f"Consider investigating with /gaia.\n"
|
|
172
|
+
)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.debug(f"Critical anomaly check failed (non-fatal): {e}")
|
|
175
|
+
return ""
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def consume_anomaly_flag(enriched_prompt: str) -> str:
|
|
179
|
+
"""Read and delete the needs_analysis.flag if it exists, appending a warning.
|
|
180
|
+
|
|
181
|
+
The flag is created by subagent_stop.py when workflow anomalies are
|
|
182
|
+
detected. Reading it once and deleting ensures the warning is shown
|
|
183
|
+
exactly once. Must not slow down context injection -- returns
|
|
184
|
+
immediately if the file does not exist.
|
|
185
|
+
|
|
186
|
+
TTL enforcement: flags older than 1 hour (by created_at or file mtime)
|
|
187
|
+
are auto-expired and deleted without injecting a warning.
|
|
188
|
+
"""
|
|
189
|
+
flag_path = get_plugin_data_dir() / "project-context" / "workflow-episodic-memory" / "signals" / "needs_analysis.flag"
|
|
190
|
+
if not flag_path.exists():
|
|
191
|
+
return enriched_prompt
|
|
192
|
+
try:
|
|
193
|
+
signal_data = json.loads(flag_path.read_text())
|
|
194
|
+
|
|
195
|
+
# TTL check: auto-expire flags older than ttl_hours (default 1 hour)
|
|
196
|
+
ttl_hours = signal_data.get("ttl_hours", 1)
|
|
197
|
+
ttl_seconds = ttl_hours * 3600
|
|
198
|
+
created_at = signal_data.get("created_at") or signal_data.get("timestamp")
|
|
199
|
+
if created_at:
|
|
200
|
+
created_dt = datetime.fromisoformat(created_at)
|
|
201
|
+
age_seconds = (datetime.now() - created_dt).total_seconds()
|
|
202
|
+
if age_seconds > ttl_seconds:
|
|
203
|
+
flag_path.unlink()
|
|
204
|
+
logger.info(
|
|
205
|
+
"Auto-expired anomaly flag (age: %.0fs, ttl: %ds)",
|
|
206
|
+
age_seconds, ttl_seconds,
|
|
207
|
+
)
|
|
208
|
+
return enriched_prompt
|
|
209
|
+
else:
|
|
210
|
+
# Fallback: check file modification time
|
|
211
|
+
mtime = flag_path.stat().st_mtime
|
|
212
|
+
age_seconds = datetime.now().timestamp() - mtime
|
|
213
|
+
if age_seconds > ttl_seconds:
|
|
214
|
+
flag_path.unlink()
|
|
215
|
+
logger.info(
|
|
216
|
+
"Auto-expired anomaly flag by mtime (age: %.0fs, ttl: %ds)",
|
|
217
|
+
age_seconds, ttl_seconds,
|
|
218
|
+
)
|
|
219
|
+
return enriched_prompt
|
|
220
|
+
|
|
221
|
+
anomalies = signal_data.get("anomalies", [])
|
|
222
|
+
summary = "; ".join(a.get("message", "") for a in anomalies if a.get("message"))
|
|
223
|
+
if summary:
|
|
224
|
+
enriched_prompt += (
|
|
225
|
+
f"\n# Anomaly Alert\n"
|
|
226
|
+
f"Recent anomalies detected: {summary}. "
|
|
227
|
+
f"Consider investigating with /gaia.\n"
|
|
228
|
+
)
|
|
229
|
+
flag_path.unlink()
|
|
230
|
+
logger.info("Consumed anomaly flag and injected warning")
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.debug(f"Failed to consume anomaly flag (non-fatal): {e}")
|
|
233
|
+
return enriched_prompt
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def build_project_context(
|
|
237
|
+
parameters: dict,
|
|
238
|
+
project_agents: list,
|
|
239
|
+
hooks_dir: Path = None,
|
|
240
|
+
) -> tuple:
|
|
241
|
+
"""
|
|
242
|
+
Build project context string for a project agent without mutating parameters.
|
|
243
|
+
|
|
244
|
+
Returns the context string suitable for additionalContext injection, plus a
|
|
245
|
+
telemetry snapshot. Does NOT modify parameters in any way.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
parameters: Task tool parameters (read-only).
|
|
249
|
+
project_agents: List of valid project agent names.
|
|
250
|
+
hooks_dir: Path to the hooks directory (for fallback paths).
|
|
251
|
+
Defaults to Path(__file__).parent.parent.parent if None.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
(context_string, telemetry_snapshot) or (None, {}) if no context to inject.
|
|
255
|
+
"""
|
|
256
|
+
if hooks_dir is None:
|
|
257
|
+
hooks_dir = Path(__file__).parent.parent.parent
|
|
258
|
+
|
|
259
|
+
subagent_type = parameters.get("subagent_type", "")
|
|
260
|
+
|
|
261
|
+
# Only inject for project agents (not for generic agents like Explore, general-purpose, etc.)
|
|
262
|
+
if subagent_type not in project_agents:
|
|
263
|
+
logger.debug(f"Skipping context injection for non-project agent: {subagent_type}")
|
|
264
|
+
return None, {}
|
|
265
|
+
|
|
266
|
+
prompt = parameters.get("prompt", "")
|
|
267
|
+
if not prompt:
|
|
268
|
+
logger.warning(f"No prompt provided for {subagent_type}, skipping context injection")
|
|
269
|
+
return None, {}
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
# Find context_provider.py
|
|
273
|
+
context_provider_paths = [
|
|
274
|
+
hooks_dir.parent / "tools" / "context" / "context_provider.py", # plugin root (works in both modes)
|
|
275
|
+
Path(".claude/tools/context/context_provider.py"), # npm symlink fallback
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
context_provider = None
|
|
279
|
+
for path in context_provider_paths:
|
|
280
|
+
if path.exists():
|
|
281
|
+
context_provider = path
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
if not context_provider:
|
|
285
|
+
logger.warning("context_provider.py not found, skipping context injection")
|
|
286
|
+
return None, {}
|
|
287
|
+
|
|
288
|
+
# Execute context_provider.py to get filtered context
|
|
289
|
+
logger.info(f"Building context for {subagent_type}...")
|
|
290
|
+
result = subprocess.run(
|
|
291
|
+
["python3", str(context_provider), subagent_type, prompt],
|
|
292
|
+
capture_output=True,
|
|
293
|
+
text=True,
|
|
294
|
+
timeout=15,
|
|
295
|
+
cwd=os.getcwd()
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if result.returncode != 0:
|
|
299
|
+
logger.error(f"context_provider.py failed: {result.stderr}")
|
|
300
|
+
return None, {}
|
|
301
|
+
|
|
302
|
+
# Parse context JSON
|
|
303
|
+
try:
|
|
304
|
+
context_payload = json.loads(result.stdout)
|
|
305
|
+
except json.JSONDecodeError as e:
|
|
306
|
+
logger.error(f"Failed to parse context JSON: {e}")
|
|
307
|
+
return None, {}
|
|
308
|
+
|
|
309
|
+
# Extract and save context anchors for hit tracking
|
|
310
|
+
try:
|
|
311
|
+
anchors = extract_anchors(context_payload)
|
|
312
|
+
if anchors:
|
|
313
|
+
session_id = get_or_create_session_id()
|
|
314
|
+
save_anchors(session_id, subagent_type, anchors)
|
|
315
|
+
logger.debug(
|
|
316
|
+
"Saved %d context anchors for %s", len(anchors), subagent_type,
|
|
317
|
+
)
|
|
318
|
+
except Exception as exc:
|
|
319
|
+
logger.debug("Anchor extraction failed (non-fatal): %s", exc)
|
|
320
|
+
|
|
321
|
+
# Check pending update count (non-blocking, fast path)
|
|
322
|
+
pending_warning = check_pending_updates_threshold()
|
|
323
|
+
|
|
324
|
+
# Build context update reminder for empty writable sections
|
|
325
|
+
update_reminder = build_context_update_reminder(
|
|
326
|
+
subagent_type, project_agents, hooks_dir
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Build context sections from payload
|
|
330
|
+
project_knowledge = context_payload.get("project_knowledge", {})
|
|
331
|
+
write_perms = context_payload.get("write_permissions", {})
|
|
332
|
+
investigation_brief = context_payload.get("investigation_brief", {})
|
|
333
|
+
rules = context_payload.get("rules", {})
|
|
334
|
+
surface_routing_data = context_payload.get("surface_routing", {})
|
|
335
|
+
metadata = context_payload.get("metadata", {})
|
|
336
|
+
historical = context_payload.get("historical_context", {})
|
|
337
|
+
|
|
338
|
+
# Optional sections
|
|
339
|
+
rules_section = f"\n## Rules\n\n{json.dumps(rules, indent=2)}\n" if rules.get("universal") or rules.get("agent_specific") else ""
|
|
340
|
+
routing_section = f"\n## Surface Routing\n\n{json.dumps(surface_routing_data, indent=2)}\n" if surface_routing_data else ""
|
|
341
|
+
metadata_section = f"\n## Metadata\n\n{json.dumps(metadata, indent=2)}\n" if metadata else ""
|
|
342
|
+
historical_section = f"\n## Historical Context\n\n{json.dumps(historical, indent=2)}\n" if historical else ""
|
|
343
|
+
|
|
344
|
+
# Save context_payload to disk for downstream hooks (SubagentStop)
|
|
345
|
+
try:
|
|
346
|
+
payload_dir = Path(os.environ.get("TMPDIR", "/tmp")) / "gaia-context-payloads"
|
|
347
|
+
payload_dir.mkdir(parents=True, exist_ok=True)
|
|
348
|
+
agent_id = parameters.get("_agent_id", "") or subagent_type
|
|
349
|
+
payload_path = payload_dir / f"{agent_id}.json"
|
|
350
|
+
payload_path.write_text(json.dumps(context_payload, separators=(',', ':')))
|
|
351
|
+
logger.debug(f"Context payload saved to {payload_path}")
|
|
352
|
+
except Exception as exc:
|
|
353
|
+
logger.debug(f"Failed to save context payload to disk (non-fatal): {exc}")
|
|
354
|
+
|
|
355
|
+
# Build brief as full JSON (all fields, not just 3)
|
|
356
|
+
brief_json = json.dumps(investigation_brief, indent=2) if investigation_brief else "{}"
|
|
357
|
+
|
|
358
|
+
# Build write permissions as JSON
|
|
359
|
+
write_perms_json = json.dumps({
|
|
360
|
+
"writable": write_perms.get("writable_sections", []),
|
|
361
|
+
"readable": write_perms.get("readable_sections", []),
|
|
362
|
+
"context_update_required": [s for s in write_perms.get("writable_sections", [])
|
|
363
|
+
if not project_knowledge.get(s)]
|
|
364
|
+
}, indent=2)
|
|
365
|
+
|
|
366
|
+
context_string = f"""{rules_section}
|
|
367
|
+
# Project Context
|
|
368
|
+
|
|
369
|
+
{json.dumps(project_knowledge, indent=2)}
|
|
370
|
+
|
|
371
|
+
# Routing
|
|
372
|
+
{routing_section}
|
|
373
|
+
# Brief
|
|
374
|
+
|
|
375
|
+
{brief_json}
|
|
376
|
+
|
|
377
|
+
# Permissions
|
|
378
|
+
|
|
379
|
+
{write_perms_json}
|
|
380
|
+
{pending_warning}{update_reminder}{metadata_section}{historical_section}"""
|
|
381
|
+
|
|
382
|
+
# Append anomaly signal flag (consume once)
|
|
383
|
+
context_string = consume_anomaly_flag(context_string)
|
|
384
|
+
|
|
385
|
+
# Surface recent critical anomalies from the JSONL log
|
|
386
|
+
critical_summary = check_recent_critical_anomalies()
|
|
387
|
+
if critical_summary:
|
|
388
|
+
context_string += critical_summary
|
|
389
|
+
|
|
390
|
+
# Inject recent operational events (non-blocking)
|
|
391
|
+
try:
|
|
392
|
+
from ..events.event_writer import read_events
|
|
393
|
+
recent = read_events(hours=24, limit=20)
|
|
394
|
+
if recent:
|
|
395
|
+
lines = ["\n# Recent Events (last 24h)"]
|
|
396
|
+
for evt in recent:
|
|
397
|
+
ts_short = evt.get("ts", "")[:16]
|
|
398
|
+
etype = evt.get("type", "")
|
|
399
|
+
agent_name = evt.get("agent", "")
|
|
400
|
+
result_str = evt.get("result", "")
|
|
401
|
+
label = f"{agent_name}: " if agent_name else ""
|
|
402
|
+
lines.append(f"- [{ts_short}] {etype}: {label}{result_str}")
|
|
403
|
+
context_string += "\n".join(lines) + "\n"
|
|
404
|
+
except Exception as exc:
|
|
405
|
+
logger.debug("Event context injection failed (non-fatal): %s", exc)
|
|
406
|
+
|
|
407
|
+
# Build telemetry snapshot
|
|
408
|
+
telemetry = build_context_telemetry_snapshot(context_payload)
|
|
409
|
+
|
|
410
|
+
sections_count = len(context_payload.get("project_knowledge", {}))
|
|
411
|
+
rules_count = context_payload.get("metadata", {}).get("rules_count", 0)
|
|
412
|
+
|
|
413
|
+
logger.info(
|
|
414
|
+
f"Context built for {subagent_type} "
|
|
415
|
+
f"(sections={sections_count}, rules={rules_count})"
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
return context_string, telemetry
|
|
419
|
+
|
|
420
|
+
except subprocess.TimeoutExpired:
|
|
421
|
+
logger.error("context_provider.py timed out (15s)")
|
|
422
|
+
return None, {}
|
|
423
|
+
except Exception as e:
|
|
424
|
+
logger.error(f"Error building context: {e}", exc_info=True)
|
|
425
|
+
return None, {}
|
|
426
|
+
|
|
427
|
+
|