@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,349 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the Tool Scanner (T024).
|
|
3
|
+
|
|
4
|
+
Tests tool detection via command -v, version extraction with timeout,
|
|
5
|
+
tool_preferences resolution, and handling of tools that hang.
|
|
6
|
+
All subprocess calls are mocked for reproducibility.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List
|
|
12
|
+
from unittest.mock import MagicMock, call, patch
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from tools.scan.config import TOOL_DEFINITIONS, ToolCategory, ToolDefinition
|
|
17
|
+
from tools.scan.scanners.tools import ToolScanner, _VERSION_TIMEOUT
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def scanner() -> ToolScanner:
|
|
22
|
+
"""Create a ToolScanner instance."""
|
|
23
|
+
return ToolScanner()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Scanner basics
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestToolScannerBasics:
|
|
32
|
+
"""Test scanner metadata and basic contract."""
|
|
33
|
+
|
|
34
|
+
def test_scanner_name(self, scanner: ToolScanner) -> None:
|
|
35
|
+
assert scanner.SCANNER_NAME == "tools"
|
|
36
|
+
|
|
37
|
+
def test_scanner_version(self, scanner: ToolScanner) -> None:
|
|
38
|
+
assert scanner.SCANNER_VERSION == "1.1.0"
|
|
39
|
+
|
|
40
|
+
def test_owned_sections(self, scanner: ToolScanner) -> None:
|
|
41
|
+
assert "environment.tools" in scanner.OWNED_SECTIONS
|
|
42
|
+
assert "environment.tool_preferences" in scanner.OWNED_SECTIONS
|
|
43
|
+
|
|
44
|
+
def test_source_tag(self, scanner: ToolScanner) -> None:
|
|
45
|
+
assert scanner.source_tag == "scanner:tools"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Tool detection
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestToolDetection:
|
|
54
|
+
"""Test tool detection via shutil.which."""
|
|
55
|
+
|
|
56
|
+
def test_detect_tool_via_shutil_which(self, scanner: ToolScanner) -> None:
|
|
57
|
+
"""Verify shutil.which is used for path detection."""
|
|
58
|
+
with patch("tools.scan.scanners.tools.shutil.which", return_value="/usr/bin/python3") as mock_which:
|
|
59
|
+
path = scanner._detect_path("python3")
|
|
60
|
+
mock_which.assert_called_once_with("python3")
|
|
61
|
+
assert path == "/usr/bin/python3"
|
|
62
|
+
|
|
63
|
+
def test_no_subprocess_for_path_detection(self, scanner: ToolScanner) -> None:
|
|
64
|
+
"""Ensure subprocess is NOT used for path detection."""
|
|
65
|
+
with patch("tools.scan.scanners.tools.shutil.which", return_value="/usr/bin/git"):
|
|
66
|
+
with patch("tools.scan.scanners.tools.subprocess.run") as mock_run:
|
|
67
|
+
scanner._detect_path("git")
|
|
68
|
+
mock_run.assert_not_called()
|
|
69
|
+
|
|
70
|
+
def test_missing_tool_returns_none(self, scanner: ToolScanner) -> None:
|
|
71
|
+
"""Tool not found by shutil.which returns None."""
|
|
72
|
+
with patch("tools.scan.scanners.tools.shutil.which", return_value=None):
|
|
73
|
+
path = scanner._detect_path("nonexistent_tool_xyz")
|
|
74
|
+
assert path is None
|
|
75
|
+
|
|
76
|
+
def test_detected_tool_has_required_fields(
|
|
77
|
+
self, scanner: ToolScanner, tmp_path: Path
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Test that detected tools have name, path, version, category."""
|
|
80
|
+
tool_def = ToolDefinition(
|
|
81
|
+
name="test-tool",
|
|
82
|
+
category=ToolCategory.UTILITY,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Mock version extraction (subprocess is only used for --version now)
|
|
86
|
+
version_result = MagicMock()
|
|
87
|
+
version_result.returncode = 0
|
|
88
|
+
version_result.stdout = "test-tool 1.2.3\n"
|
|
89
|
+
version_result.stderr = ""
|
|
90
|
+
|
|
91
|
+
with patch("tools.scan.scanners.tools.shutil.which", return_value="/usr/local/bin/test-tool"):
|
|
92
|
+
with patch(
|
|
93
|
+
"tools.scan.scanners.tools.subprocess.run",
|
|
94
|
+
return_value=version_result,
|
|
95
|
+
):
|
|
96
|
+
tool_info = scanner._probe_tool(tool_def)
|
|
97
|
+
|
|
98
|
+
assert tool_info is not None
|
|
99
|
+
assert tool_info["name"] == "test-tool"
|
|
100
|
+
assert tool_info["path"] == "/usr/local/bin/test-tool"
|
|
101
|
+
assert tool_info["version"] is not None
|
|
102
|
+
assert tool_info["category"] == "utility"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Version extraction
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TestVersionExtraction:
|
|
111
|
+
"""Test version extraction with timeout handling."""
|
|
112
|
+
|
|
113
|
+
def test_version_extracted_from_stdout(self, scanner: ToolScanner) -> None:
|
|
114
|
+
result = scanner._extract_version(
|
|
115
|
+
"/usr/bin/python3", "--version", None
|
|
116
|
+
)
|
|
117
|
+
# On real system this would return a version string;
|
|
118
|
+
# if python3 is installed it should not be "unknown"
|
|
119
|
+
# We just verify it returns a string
|
|
120
|
+
assert isinstance(result, str)
|
|
121
|
+
|
|
122
|
+
def test_timeout_returns_unknown(self, scanner: ToolScanner) -> None:
|
|
123
|
+
"""Tool that hangs during --version gets version 'unknown'."""
|
|
124
|
+
with patch(
|
|
125
|
+
"tools.scan.scanners.tools.subprocess.run",
|
|
126
|
+
side_effect=subprocess.TimeoutExpired(cmd="tool", timeout=2),
|
|
127
|
+
):
|
|
128
|
+
version = scanner._extract_version("/usr/bin/slow-tool", "--version", None)
|
|
129
|
+
assert version == "unknown"
|
|
130
|
+
|
|
131
|
+
def test_nonzero_exit_returns_unknown(self, scanner: ToolScanner) -> None:
|
|
132
|
+
"""Tool with non-zero exit for --version gets version 'unknown'."""
|
|
133
|
+
mock_result = MagicMock()
|
|
134
|
+
mock_result.returncode = 1
|
|
135
|
+
mock_result.stdout = ""
|
|
136
|
+
mock_result.stderr = ""
|
|
137
|
+
|
|
138
|
+
with patch("tools.scan.scanners.tools.subprocess.run", return_value=mock_result):
|
|
139
|
+
version = scanner._extract_version("/usr/bin/bad-tool", "--version", None)
|
|
140
|
+
assert version == "unknown"
|
|
141
|
+
|
|
142
|
+
def test_version_regex_extraction(self, scanner: ToolScanner) -> None:
|
|
143
|
+
"""Test version regex extracts from complex output."""
|
|
144
|
+
mock_result = MagicMock()
|
|
145
|
+
mock_result.returncode = 0
|
|
146
|
+
mock_result.stdout = "Super Tool version 3.14.159 (build 12345)\n"
|
|
147
|
+
mock_result.stderr = ""
|
|
148
|
+
|
|
149
|
+
with patch("tools.scan.scanners.tools.subprocess.run", return_value=mock_result):
|
|
150
|
+
version = scanner._extract_version(
|
|
151
|
+
"/usr/bin/super-tool", "--version", r"version (\d+\.\d+\.\d+)"
|
|
152
|
+
)
|
|
153
|
+
assert version == "3.14.159"
|
|
154
|
+
|
|
155
|
+
def test_oserror_returns_unknown(self, scanner: ToolScanner) -> None:
|
|
156
|
+
"""OSError during version extraction returns 'unknown'."""
|
|
157
|
+
with patch(
|
|
158
|
+
"tools.scan.scanners.tools.subprocess.run",
|
|
159
|
+
side_effect=OSError("Permission denied"),
|
|
160
|
+
):
|
|
161
|
+
version = scanner._extract_version("/usr/bin/noperm", "--version", None)
|
|
162
|
+
assert version == "unknown"
|
|
163
|
+
|
|
164
|
+
def test_version_timeout_value(self) -> None:
|
|
165
|
+
"""Verify timeout constant is 2 seconds."""
|
|
166
|
+
assert _VERSION_TIMEOUT == 2
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# Tool preferences
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestToolPreferences:
|
|
175
|
+
"""Test tool_preferences resolution."""
|
|
176
|
+
|
|
177
|
+
def test_preference_map_built(self, scanner: ToolScanner, tmp_path: Path) -> None:
|
|
178
|
+
"""Test that preference map is populated with all known keys."""
|
|
179
|
+
# Create minimal tool definitions for testing
|
|
180
|
+
mock_defs = [
|
|
181
|
+
ToolDefinition(
|
|
182
|
+
name="bat",
|
|
183
|
+
category=ToolCategory.FILE_VIEWER,
|
|
184
|
+
preference_key="file_viewer",
|
|
185
|
+
preference_priority=10,
|
|
186
|
+
),
|
|
187
|
+
ToolDefinition(
|
|
188
|
+
name="stern",
|
|
189
|
+
category=ToolCategory.KUBERNETES,
|
|
190
|
+
preference_key="log_viewer",
|
|
191
|
+
preference_priority=10,
|
|
192
|
+
),
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
def mock_which(name):
|
|
196
|
+
return f"/usr/bin/{name}" if name in ("bat", "stern") else None
|
|
197
|
+
|
|
198
|
+
version_result = MagicMock()
|
|
199
|
+
version_result.returncode = 0
|
|
200
|
+
version_result.stdout = "1.0.0\n"
|
|
201
|
+
version_result.stderr = ""
|
|
202
|
+
|
|
203
|
+
with patch("tools.scan.config.TOOL_DEFINITIONS", mock_defs):
|
|
204
|
+
with patch("tools.scan.scanners.tools.TOOL_DEFINITIONS", mock_defs):
|
|
205
|
+
with patch("tools.scan.scanners.tools.shutil.which", side_effect=mock_which):
|
|
206
|
+
with patch("tools.scan.scanners.tools.subprocess.run", return_value=version_result):
|
|
207
|
+
result = scanner.scan(tmp_path)
|
|
208
|
+
|
|
209
|
+
env = result.sections["environment"]
|
|
210
|
+
prefs = env["tool_preferences"]
|
|
211
|
+
assert prefs["file_viewer"] == "bat"
|
|
212
|
+
assert prefs["log_viewer"] == "stern"
|
|
213
|
+
|
|
214
|
+
def test_highest_priority_wins(self, scanner: ToolScanner, tmp_path: Path) -> None:
|
|
215
|
+
"""When two tools compete for same key, highest priority wins."""
|
|
216
|
+
mock_defs = [
|
|
217
|
+
ToolDefinition(
|
|
218
|
+
name="docker",
|
|
219
|
+
category=ToolCategory.CONTAINER,
|
|
220
|
+
preference_key="container_runtime",
|
|
221
|
+
preference_priority=10,
|
|
222
|
+
),
|
|
223
|
+
ToolDefinition(
|
|
224
|
+
name="podman",
|
|
225
|
+
category=ToolCategory.CONTAINER,
|
|
226
|
+
preference_key="container_runtime",
|
|
227
|
+
preference_priority=5,
|
|
228
|
+
),
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
def mock_which(name):
|
|
232
|
+
return f"/usr/bin/{name}" if name in ("docker", "podman") else None
|
|
233
|
+
|
|
234
|
+
version_result = MagicMock()
|
|
235
|
+
version_result.returncode = 0
|
|
236
|
+
version_result.stdout = "1.0.0\n"
|
|
237
|
+
version_result.stderr = ""
|
|
238
|
+
|
|
239
|
+
with patch("tools.scan.config.TOOL_DEFINITIONS", mock_defs):
|
|
240
|
+
with patch("tools.scan.scanners.tools.TOOL_DEFINITIONS", mock_defs):
|
|
241
|
+
with patch("tools.scan.scanners.tools.shutil.which", side_effect=mock_which):
|
|
242
|
+
with patch("tools.scan.scanners.tools.subprocess.run", return_value=version_result):
|
|
243
|
+
result = scanner.scan(tmp_path)
|
|
244
|
+
|
|
245
|
+
env = result.sections["environment"]
|
|
246
|
+
assert env["tool_preferences"]["container_runtime"] == "docker"
|
|
247
|
+
|
|
248
|
+
def test_undetected_preference_is_none(
|
|
249
|
+
self, scanner: ToolScanner, tmp_path: Path
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Preference key with no detected tools gets None."""
|
|
252
|
+
mock_defs = [
|
|
253
|
+
ToolDefinition(
|
|
254
|
+
name="nonexistent_special_tool",
|
|
255
|
+
category=ToolCategory.UTILITY,
|
|
256
|
+
preference_key="special_viewer",
|
|
257
|
+
preference_priority=10,
|
|
258
|
+
),
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
with patch("tools.scan.config.TOOL_DEFINITIONS", mock_defs):
|
|
262
|
+
with patch("tools.scan.scanners.tools.TOOL_DEFINITIONS", mock_defs):
|
|
263
|
+
with patch("tools.scan.scanners.tools.shutil.which", return_value=None):
|
|
264
|
+
result = scanner.scan(tmp_path)
|
|
265
|
+
|
|
266
|
+
env = result.sections["environment"]
|
|
267
|
+
assert env["tool_preferences"]["special_viewer"] is None
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
# All ToolCategory values
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class TestToolCategories:
|
|
276
|
+
"""Test that all 11 ToolCategory values exist."""
|
|
277
|
+
|
|
278
|
+
def test_all_11_categories(self) -> None:
|
|
279
|
+
assert len(ToolCategory) == 11
|
|
280
|
+
|
|
281
|
+
def test_category_values(self) -> None:
|
|
282
|
+
expected = {
|
|
283
|
+
"kubernetes", "cloud", "iac", "container", "file_viewer",
|
|
284
|
+
"file_search", "git", "language_runtime", "build", "utility",
|
|
285
|
+
"ai_assistant",
|
|
286
|
+
}
|
|
287
|
+
actual = {cat.value for cat in ToolCategory}
|
|
288
|
+
assert actual == expected
|
|
289
|
+
|
|
290
|
+
def test_all_tool_definitions_have_valid_category(self) -> None:
|
|
291
|
+
for td in TOOL_DEFINITIONS:
|
|
292
|
+
assert isinstance(td.category, ToolCategory)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# ---------------------------------------------------------------------------
|
|
296
|
+
# Full scan with mocked subprocess
|
|
297
|
+
# ---------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class TestFullScanMocked:
|
|
301
|
+
"""Test full scan with all subprocess calls mocked."""
|
|
302
|
+
|
|
303
|
+
def test_scan_returns_environment_section(
|
|
304
|
+
self, scanner: ToolScanner, tmp_path: Path
|
|
305
|
+
) -> None:
|
|
306
|
+
"""Full scan produces environment section with tools and preferences."""
|
|
307
|
+
def mock_which(name):
|
|
308
|
+
return "/usr/bin/python3" if name == "python3" else None
|
|
309
|
+
|
|
310
|
+
version_result = MagicMock()
|
|
311
|
+
version_result.returncode = 0
|
|
312
|
+
version_result.stdout = "Python 3.11.5\n"
|
|
313
|
+
version_result.stderr = ""
|
|
314
|
+
|
|
315
|
+
with patch("tools.scan.scanners.tools.shutil.which", side_effect=mock_which):
|
|
316
|
+
with patch("tools.scan.scanners.tools.subprocess.run", return_value=version_result):
|
|
317
|
+
result = scanner.scan(tmp_path)
|
|
318
|
+
|
|
319
|
+
assert "environment" in result.sections
|
|
320
|
+
env = result.sections["environment"]
|
|
321
|
+
assert "tools" in env
|
|
322
|
+
assert "tool_preferences" in env
|
|
323
|
+
|
|
324
|
+
def test_scan_individual_failure_does_not_abort(
|
|
325
|
+
self, scanner: ToolScanner, tmp_path: Path
|
|
326
|
+
) -> None:
|
|
327
|
+
"""A tool that throws an exception during probe doesn't abort scan."""
|
|
328
|
+
call_count = 0
|
|
329
|
+
|
|
330
|
+
def mock_which(name):
|
|
331
|
+
nonlocal call_count
|
|
332
|
+
call_count += 1
|
|
333
|
+
if call_count == 1:
|
|
334
|
+
raise OSError("Simulated failure")
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
with patch("tools.scan.scanners.tools.shutil.which", side_effect=mock_which):
|
|
338
|
+
result = scanner.scan(tmp_path)
|
|
339
|
+
|
|
340
|
+
# Scanner should still return a valid result
|
|
341
|
+
assert "environment" in result.sections
|
|
342
|
+
|
|
343
|
+
def test_scan_result_has_source_tag(
|
|
344
|
+
self, scanner: ToolScanner, tmp_path: Path
|
|
345
|
+
) -> None:
|
|
346
|
+
with patch("tools.scan.scanners.tools.shutil.which", return_value=None):
|
|
347
|
+
result = scanner.scan(tmp_path)
|
|
348
|
+
|
|
349
|
+
assert result.sections["environment"]["_source"] == "scanner:tools"
|