@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
package/hooks/post_tool_use.py
CHANGED
|
@@ -18,8 +18,6 @@ sys.path.insert(0, str(Path(__file__).parent))
|
|
|
18
18
|
|
|
19
19
|
from modules.core.paths import get_logs_dir
|
|
20
20
|
from adapters.claude_code import ClaudeCodeAdapter
|
|
21
|
-
from modules.core.stdin import has_stdin_data
|
|
22
|
-
from adapters.utils import warn_if_dual_channel
|
|
23
21
|
from modules.core.hook_entry import run_hook
|
|
24
22
|
|
|
25
23
|
# Configure logging
|
|
@@ -48,79 +46,9 @@ def _handle_post_tool_use(event) -> None:
|
|
|
48
46
|
sys.exit(response.exit_code)
|
|
49
47
|
|
|
50
48
|
|
|
51
|
-
# ============================================================================
|
|
52
|
-
# Backward-compatible API for direct callers (e.g. --test mode)
|
|
53
|
-
# ============================================================================
|
|
54
|
-
|
|
55
|
-
def post_tool_use_hook(
|
|
56
|
-
tool_name: str,
|
|
57
|
-
parameters: dict,
|
|
58
|
-
result: object,
|
|
59
|
-
duration: float,
|
|
60
|
-
success: bool = True,
|
|
61
|
-
) -> None:
|
|
62
|
-
"""Post-tool use hook: audit + event detection + context update.
|
|
63
|
-
|
|
64
|
-
Backward-compatible entry point that calls the same modules as the adapter.
|
|
65
|
-
"""
|
|
66
|
-
import time
|
|
67
|
-
from modules.core.state import get_hook_state, clear_hook_state
|
|
68
|
-
from modules.audit.logger import log_execution
|
|
69
|
-
from modules.audit.event_detector import detect_critical_event
|
|
70
|
-
from modules.session.session_context_writer import SessionContextWriter
|
|
71
|
-
from modules.security.approval_grants import check_approval_grant, confirm_grant
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
pre_state = get_hook_state()
|
|
75
|
-
tier = pre_state.tier if pre_state else "unknown"
|
|
76
|
-
|
|
77
|
-
computed_duration = duration
|
|
78
|
-
if pre_state and pre_state.start_time_epoch > 0:
|
|
79
|
-
computed_duration = time.time() - pre_state.start_time_epoch
|
|
80
|
-
|
|
81
|
-
log_execution(
|
|
82
|
-
tool_name=tool_name,
|
|
83
|
-
parameters=parameters,
|
|
84
|
-
result=result,
|
|
85
|
-
duration=computed_duration,
|
|
86
|
-
exit_code=0 if success else 1,
|
|
87
|
-
tier=tier,
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
if tool_name == "Bash" and success:
|
|
91
|
-
command = parameters.get("command", "")
|
|
92
|
-
if command:
|
|
93
|
-
grant = check_approval_grant(command)
|
|
94
|
-
if grant is not None and not grant.confirmed:
|
|
95
|
-
confirm_grant(command)
|
|
96
|
-
logger.info(
|
|
97
|
-
"T3 grant confirmed post-execution: %s", command[:80],
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
events = detect_critical_event(tool_name, parameters, result, success)
|
|
101
|
-
if events:
|
|
102
|
-
writer = SessionContextWriter()
|
|
103
|
-
for event in events:
|
|
104
|
-
writer.update_context(event.to_dict())
|
|
105
|
-
|
|
106
|
-
clear_hook_state()
|
|
107
|
-
logger.debug("Post-hook completed for %s", tool_name)
|
|
108
|
-
|
|
109
|
-
except Exception as e:
|
|
110
|
-
logger.error("Error in post_tool_use_hook: %s", e, exc_info=True)
|
|
111
|
-
|
|
112
|
-
|
|
113
49
|
# ============================================================================
|
|
114
50
|
# STDIN HANDLER (Claude Code integration)
|
|
115
51
|
# ============================================================================
|
|
116
52
|
|
|
117
53
|
if __name__ == "__main__":
|
|
118
|
-
if len(sys.argv) > 1 and sys.argv[1] == "--test":
|
|
119
|
-
post_tool_use_hook(
|
|
120
|
-
"bash", {"command": "kubectl get pods"},
|
|
121
|
-
"pod/test-pod 1/1 Running 0 1m", 0.5, True,
|
|
122
|
-
)
|
|
123
|
-
print(f"Test completed. Check {get_logs_dir()} for audit logs")
|
|
124
|
-
sys.exit(0)
|
|
125
|
-
|
|
126
54
|
run_hook(_handle_post_tool_use, hook_name="post_tool_use")
|
package/hooks/pre_tool_use.py
CHANGED
|
@@ -11,6 +11,7 @@ Architecture:
|
|
|
11
11
|
- All business logic lives in ClaudeCodeAdapter.adapt_pre_tool_use()
|
|
12
12
|
- This file is stdin/stdout glue only
|
|
13
13
|
"""
|
|
14
|
+
from __future__ import annotations
|
|
14
15
|
|
|
15
16
|
import sys
|
|
16
17
|
import json
|
|
@@ -47,20 +48,10 @@ logger = logging.getLogger(__name__)
|
|
|
47
48
|
from modules.tools.bash_validator import BashValidator
|
|
48
49
|
from modules.tools.task_validator import TaskValidator, AVAILABLE_AGENTS, META_AGENTS
|
|
49
50
|
from modules.security.prompt_validator import classify_resume_prompt
|
|
50
|
-
from modules.context.context_injector import
|
|
51
|
-
from modules.session.session_event_injector import
|
|
51
|
+
from modules.context.context_injector import build_project_context
|
|
52
|
+
from modules.session.session_event_injector import build_session_events
|
|
52
53
|
from modules.core.state import create_pre_hook_state, save_hook_state
|
|
53
|
-
from modules.security.approval_constants import (
|
|
54
|
-
NONCE_APPROVAL_PREFIX,
|
|
55
|
-
NONCE_APPROVAL_PATTERN,
|
|
56
|
-
)
|
|
57
|
-
from modules.security.approval_messages import (
|
|
58
|
-
build_activation_failed_message,
|
|
59
|
-
build_deprecated_approval_message,
|
|
60
|
-
build_invalid_nonce_message,
|
|
61
|
-
)
|
|
62
54
|
from modules.security.approval_grants import (
|
|
63
|
-
activate_pending_approval,
|
|
64
55
|
cleanup_expired_grants,
|
|
65
56
|
)
|
|
66
57
|
|
|
@@ -74,16 +65,6 @@ def _classify_resume_prompt(prompt: str) -> str:
|
|
|
74
65
|
return classify_resume_prompt(prompt)
|
|
75
66
|
|
|
76
67
|
|
|
77
|
-
def _inject_project_context(parameters: dict) -> dict:
|
|
78
|
-
"""Inject project context. Delegates to modules.context.context_injector."""
|
|
79
|
-
return inject_project_context(parameters, PROJECT_AGENTS, _HOOKS_DIR)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _inject_session_events(parameters: dict) -> dict:
|
|
83
|
-
"""Inject session events. Delegates to modules.session.session_event_injector."""
|
|
84
|
-
return inject_session_events(parameters, PROJECT_AGENTS)
|
|
85
|
-
|
|
86
|
-
|
|
87
68
|
def pre_tool_use_hook(tool_name: str, parameters: dict) -> str | dict | None:
|
|
88
69
|
"""
|
|
89
70
|
Pre-tool use hook implementation (backward-compatible API).
|
|
@@ -137,24 +118,13 @@ def _handle_bash(tool_name: str, parameters: dict) -> str | dict | None:
|
|
|
137
118
|
result = validator.validate(command)
|
|
138
119
|
|
|
139
120
|
if not result.allowed:
|
|
140
|
-
from modules.core.plugin_mode import is_ops_mode
|
|
141
121
|
logger.warning(f"BLOCKED: {command[:100]} - {result.reason}")
|
|
142
122
|
|
|
143
|
-
#
|
|
144
|
-
if not is_ops_mode():
|
|
145
|
-
reason_line = result.reason.split('\n')[0] if result.reason else f"T3 operation: {command[:80]}"
|
|
146
|
-
logger.info(f"SECURITY MODE: returning 'ask' for T3: {command[:80]}")
|
|
147
|
-
return {
|
|
148
|
-
"hookSpecificOutput": {
|
|
149
|
-
"hookEventName": "PreToolUse",
|
|
150
|
-
"permissionDecision": "ask",
|
|
151
|
-
"permissionDecisionReason": f"[{result.tier}] {reason_line}",
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
# Ops mode: block with nonce
|
|
123
|
+
# Structured response from bash_validator (ask or deny)
|
|
156
124
|
if result.block_response is not None:
|
|
157
125
|
return result.block_response
|
|
126
|
+
|
|
127
|
+
# Permanently blocked (no structured response) — hard block (exit 2)
|
|
158
128
|
return _format_blocked_message(result)
|
|
159
129
|
|
|
160
130
|
effective_command = result.modified_input.get("command", command) if result.modified_input else command
|
|
@@ -185,8 +155,9 @@ def _handle_task(tool_name: str, parameters: dict) -> str | dict | None:
|
|
|
185
155
|
"""
|
|
186
156
|
Handle Task/Agent tool validation for new task dispatches.
|
|
187
157
|
|
|
188
|
-
|
|
189
|
-
|
|
158
|
+
Context is built here and cached for SubagentStart to forward to the
|
|
159
|
+
subagent. PreToolUse no longer returns additionalContext (that would
|
|
160
|
+
inject it into the orchestrator, not the subagent).
|
|
190
161
|
"""
|
|
191
162
|
context_text, _telemetry = build_project_context(parameters, PROJECT_AGENTS, _HOOKS_DIR)
|
|
192
163
|
events_text = build_session_events(parameters, PROJECT_AGENTS)
|
|
@@ -210,17 +181,31 @@ def _handle_task(tool_name: str, parameters: dict) -> str | dict | None:
|
|
|
210
181
|
|
|
211
182
|
logger.info(f"ALLOWED Task: {result.agent_name}")
|
|
212
183
|
|
|
184
|
+
# Cache context for SubagentStart to pick up and forward to the subagent.
|
|
213
185
|
additional = "\n".join(filter(None, [context_text, events_text]))
|
|
186
|
+
|
|
187
|
+
# Fallback: if build_project_context returned None because the
|
|
188
|
+
# orchestrator already embedded context in the prompt (dedup guard),
|
|
189
|
+
# extract the embedded context so SubagentStart can still inject it.
|
|
190
|
+
if not additional:
|
|
191
|
+
prompt = parameters.get("prompt", "")
|
|
192
|
+
marker = "# Project Context"
|
|
193
|
+
if marker in prompt:
|
|
194
|
+
idx = prompt.index(marker)
|
|
195
|
+
additional = prompt[idx:]
|
|
196
|
+
logger.info(
|
|
197
|
+
"Extracted embedded context from prompt for caching "
|
|
198
|
+
"(len=%d, agent=%s)",
|
|
199
|
+
len(additional), result.agent_name,
|
|
200
|
+
)
|
|
201
|
+
|
|
214
202
|
if additional:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"additionalContext": additional,
|
|
222
|
-
}
|
|
223
|
-
}
|
|
203
|
+
from adapters.claude_code import ClaudeCodeAdapter
|
|
204
|
+
adapter = ClaudeCodeAdapter()
|
|
205
|
+
session_id = parameters.get("session_id", "") or "unknown"
|
|
206
|
+
agent_type = result.agent_name or "unknown"
|
|
207
|
+
adapter._cache_context_for_subagent(session_id, agent_type, additional)
|
|
208
|
+
logger.info(f"Cached context for SubagentStart: agent={agent_type}")
|
|
224
209
|
|
|
225
210
|
return None
|
|
226
211
|
|
|
@@ -229,8 +214,9 @@ def _handle_send_message(tool_name: str, parameters: dict) -> str | None:
|
|
|
229
214
|
"""
|
|
230
215
|
Handle SendMessage tool validation for agent resumption.
|
|
231
216
|
|
|
232
|
-
Validates agent ID format and message content
|
|
233
|
-
|
|
217
|
+
Validates agent ID format and message content. Does NOT inject
|
|
218
|
+
project context (it's a resume). Nonce relay is no longer processed
|
|
219
|
+
here -- approval grants are activated by the UserPromptSubmit hook.
|
|
234
220
|
|
|
235
221
|
Returns:
|
|
236
222
|
None: allowed (no modification)
|
|
@@ -265,17 +251,13 @@ def _handle_send_message(tool_name: str, parameters: dict) -> str | None:
|
|
|
265
251
|
|
|
266
252
|
logger.info(f"SENDMESSAGE: Resuming agent {agent_id}")
|
|
267
253
|
|
|
268
|
-
approval_error, has_approval = _handle_resume_approval(agent_id, message)
|
|
269
|
-
if approval_error:
|
|
270
|
-
return approval_error
|
|
271
|
-
|
|
272
254
|
state = create_pre_hook_state(
|
|
273
255
|
tool_name=tool_name,
|
|
274
256
|
command=f"SendMessage:{agent_id}",
|
|
275
257
|
tier="T0",
|
|
276
258
|
allowed=True,
|
|
277
259
|
is_t3=False,
|
|
278
|
-
has_approval=
|
|
260
|
+
has_approval=False,
|
|
279
261
|
)
|
|
280
262
|
save_hook_state(state)
|
|
281
263
|
|
|
@@ -283,53 +265,6 @@ def _handle_send_message(tool_name: str, parameters: dict) -> str | None:
|
|
|
283
265
|
return None
|
|
284
266
|
|
|
285
267
|
|
|
286
|
-
def _handle_resume_approval(resume_id: str, prompt: str) -> tuple[str | None, bool]:
|
|
287
|
-
"""Process nonce approval indicators for Task resume."""
|
|
288
|
-
classification = _classify_resume_prompt(prompt)
|
|
289
|
-
|
|
290
|
-
if classification == "nonce":
|
|
291
|
-
nonce = NONCE_APPROVAL_PATTERN.search(prompt).group(1)
|
|
292
|
-
activation = activate_pending_approval(nonce)
|
|
293
|
-
status_text = getattr(activation.status, "value", str(activation.status))
|
|
294
|
-
if activation.success:
|
|
295
|
-
grant_path = activation.grant_path
|
|
296
|
-
grant_name = grant_path.name if grant_path else "<unknown>"
|
|
297
|
-
logger.info(
|
|
298
|
-
"Nonce approval activated for resume %s: nonce=%s, file=%s",
|
|
299
|
-
resume_id,
|
|
300
|
-
nonce,
|
|
301
|
-
grant_name,
|
|
302
|
-
)
|
|
303
|
-
return None, True
|
|
304
|
-
|
|
305
|
-
logger.warning(
|
|
306
|
-
"Denied resume %s: nonce approval activation failed for nonce=%s "
|
|
307
|
-
"(status=%s, reason=%s)",
|
|
308
|
-
resume_id,
|
|
309
|
-
nonce,
|
|
310
|
-
status_text,
|
|
311
|
-
activation.reason,
|
|
312
|
-
)
|
|
313
|
-
return build_activation_failed_message(nonce, status_text, activation.reason), False
|
|
314
|
-
|
|
315
|
-
if classification == "malformed_nonce":
|
|
316
|
-
logger.warning(
|
|
317
|
-
"Denied resume %s: malformed nonce approval token in prompt='%s'",
|
|
318
|
-
resume_id,
|
|
319
|
-
prompt[:120],
|
|
320
|
-
)
|
|
321
|
-
return build_invalid_nonce_message(), False
|
|
322
|
-
|
|
323
|
-
if classification == "deprecated":
|
|
324
|
-
logger.warning(
|
|
325
|
-
"Denied resume %s: deprecated legacy approval phrase detected",
|
|
326
|
-
resume_id,
|
|
327
|
-
)
|
|
328
|
-
return build_deprecated_approval_message(), False
|
|
329
|
-
|
|
330
|
-
return None, False
|
|
331
|
-
|
|
332
|
-
|
|
333
268
|
def _format_blocked_message(result) -> str:
|
|
334
269
|
"""Format blocked command message. Delegates to blocked_message_formatter."""
|
|
335
270
|
from modules.security.blocked_message_formatter import format_blocked_message
|
|
@@ -413,10 +348,15 @@ if __name__ == "__main__":
|
|
|
413
348
|
|
|
414
349
|
if isinstance(response.output, dict) and response.output:
|
|
415
350
|
hook_output = response.output.get("hookSpecificOutput", {})
|
|
416
|
-
|
|
351
|
+
decision = hook_output.get("permissionDecision")
|
|
352
|
+
if decision in ("block", "deny"):
|
|
417
353
|
reason = hook_output.get("permissionDecisionReason", "Command blocked by hook policy")
|
|
418
354
|
summary = reason.split('\n')[0]
|
|
419
355
|
print(f"BLOCKED: {summary}", file=sys.stderr)
|
|
356
|
+
elif decision == "ask":
|
|
357
|
+
reason = hook_output.get("permissionDecisionReason", "")
|
|
358
|
+
summary = reason.split('\n')[0]
|
|
359
|
+
print(f"T3: {summary}", file=sys.stderr)
|
|
420
360
|
print(json.dumps(response.output))
|
|
421
361
|
sys.exit(response.exit_code)
|
|
422
362
|
elif isinstance(response.output, str) and response.output:
|
package/hooks/session_start.py
CHANGED
|
@@ -31,8 +31,10 @@ if __name__ == "__main__":
|
|
|
31
31
|
try:
|
|
32
32
|
sys.stdin.read()
|
|
33
33
|
|
|
34
|
-
# First-time setup: create project permissions if needed
|
|
35
|
-
|
|
34
|
+
# First-time setup: create project permissions if needed.
|
|
35
|
+
# mark_done=False so UserPromptSubmit can detect first-run
|
|
36
|
+
# and show the welcome message before marking initialized.
|
|
37
|
+
setup_message = run_first_time_setup(mark_done=False)
|
|
36
38
|
if setup_message:
|
|
37
39
|
logger.info("First-time setup: %s", setup_message)
|
|
38
40
|
|
package/hooks/subagent_start.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""SubagentStart hook — logs agent dispatch
|
|
3
|
-
|
|
2
|
+
"""SubagentStart hook — logs agent dispatch, records skill snapshots,
|
|
3
|
+
and forwards cached project context into the subagent.
|
|
4
|
+
|
|
5
|
+
PreToolUse:Agent builds and caches the context; this hook reads the
|
|
6
|
+
cache and returns it as additionalContext so it reaches the subagent
|
|
7
|
+
(not the orchestrator)."""
|
|
4
8
|
|
|
5
9
|
import sys
|
|
6
10
|
import json
|
package/hooks/subagent_stop.py
CHANGED
|
@@ -32,8 +32,6 @@ sys.path.insert(0, str(Path(__file__).parent))
|
|
|
32
32
|
|
|
33
33
|
# Adapter layer
|
|
34
34
|
from adapters.claude_code import ClaudeCodeAdapter
|
|
35
|
-
from modules.core.stdin import has_stdin_data
|
|
36
|
-
from adapters.utils import warn_if_dual_channel
|
|
37
35
|
from modules.core.hook_entry import run_hook
|
|
38
36
|
|
|
39
37
|
# Configure structured logging with file handler
|
|
@@ -62,7 +60,6 @@ logger = logging.getLogger(__name__)
|
|
|
62
60
|
from modules.agents.contract_validator import (
|
|
63
61
|
extract_commands_from_evidence,
|
|
64
62
|
extract_exit_code_from_output,
|
|
65
|
-
extract_plan_status_from_output,
|
|
66
63
|
parse_contract,
|
|
67
64
|
requires_consolidation_report,
|
|
68
65
|
validate as validate_contract,
|
|
@@ -75,7 +72,7 @@ from modules.agents.response_contract import (
|
|
|
75
72
|
from modules.agents.task_info_builder import build_task_info_from_hook_data
|
|
76
73
|
from modules.agents.transcript_reader import read_transcript
|
|
77
74
|
from modules.audit.workflow_auditor import audit as audit_workflow, signal_gaia_analysis
|
|
78
|
-
from modules.audit.workflow_recorder import record as record_workflow
|
|
75
|
+
from modules.audit.workflow_recorder import record as record_workflow
|
|
79
76
|
from modules.context.context_writer import process_context_updates
|
|
80
77
|
from modules.memory.episode_writer import write as write_episode
|
|
81
78
|
from modules.security.approval_cleanup import cleanup as cleanup_approval
|
|
@@ -83,18 +80,9 @@ from modules.session.session_manager import get_or_create_session_id
|
|
|
83
80
|
|
|
84
81
|
_extract_commands_from_evidence = extract_commands_from_evidence
|
|
85
82
|
_extract_exit_code_from_output = extract_exit_code_from_output
|
|
86
|
-
_extract_plan_status_from_output = extract_plan_status_from_output
|
|
87
|
-
_requires_consolidation_report = requires_consolidation_report
|
|
88
83
|
_read_transcript = read_transcript
|
|
89
|
-
_consume_approval_file = cleanup_approval
|
|
90
84
|
_process_context_updates = process_context_updates
|
|
91
85
|
|
|
92
|
-
# Backward-compatible aliases for old function names used in tests
|
|
93
|
-
capture_episodic_memory = write_episode
|
|
94
|
-
detect_anomalies = audit_workflow
|
|
95
|
-
capture_workflow_metrics = record_workflow
|
|
96
|
-
consume_approval_file = cleanup_approval
|
|
97
|
-
|
|
98
86
|
|
|
99
87
|
def _build_task_info_from_hook_data(
|
|
100
88
|
hook_data: Dict[str, Any],
|
|
@@ -24,39 +24,101 @@ logging.basicConfig(
|
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def _extract_user_prompt(raw_input: str) -> str:
|
|
28
|
+
"""Extract user prompt text from stdin event.
|
|
29
|
+
|
|
30
|
+
The UserPromptSubmit event is JSON with the user's message.
|
|
31
|
+
Try known field names; return empty string if extraction fails.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
event = json.loads(raw_input)
|
|
35
|
+
# Try known field names from Claude Code hook events
|
|
36
|
+
for field in ("user_message", "prompt", "message", "content"):
|
|
37
|
+
if field in event and isinstance(event[field], str):
|
|
38
|
+
return event[field]
|
|
39
|
+
# Check nested hookEventInput
|
|
40
|
+
hook_input = event.get("hookEventInput", {})
|
|
41
|
+
if isinstance(hook_input, dict):
|
|
42
|
+
for field in ("user_message", "prompt", "message", "content"):
|
|
43
|
+
if field in hook_input and isinstance(hook_input[field], str):
|
|
44
|
+
return hook_input[field]
|
|
45
|
+
except (json.JSONDecodeError, TypeError, AttributeError):
|
|
46
|
+
pass
|
|
47
|
+
return ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_routing_recommendation(prompt_text: str) -> str:
|
|
51
|
+
"""Run surface classification and format as a routing recommendation block.
|
|
52
|
+
|
|
53
|
+
Returns empty string if classification fails or produces no active surfaces.
|
|
54
|
+
This is advisory — never raises exceptions.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
# Import surface_router from tools/context
|
|
58
|
+
tools_dir = Path(__file__).resolve().parent.parent / "tools" / "context"
|
|
59
|
+
if str(tools_dir) not in sys.path:
|
|
60
|
+
sys.path.insert(0, str(tools_dir))
|
|
61
|
+
|
|
62
|
+
from surface_router import classify_surfaces
|
|
63
|
+
|
|
64
|
+
routing = classify_surfaces(prompt_text)
|
|
65
|
+
|
|
66
|
+
active_surfaces = routing.get("active_surfaces", [])
|
|
67
|
+
if not active_surfaces:
|
|
68
|
+
logger.info("Surface routing: no active surfaces for prompt")
|
|
69
|
+
return ""
|
|
70
|
+
|
|
71
|
+
agents = routing.get("recommended_agents", [])
|
|
72
|
+
dispatch_mode = routing.get("dispatch_mode", "single_surface")
|
|
73
|
+
confidence = routing.get("confidence", 0.0)
|
|
74
|
+
matched_signals = routing.get("matched_signals", {})
|
|
75
|
+
|
|
76
|
+
# Flatten matched signals into a single list for display
|
|
77
|
+
all_signals = []
|
|
78
|
+
for surface_signals in matched_signals.values():
|
|
79
|
+
all_signals.extend(surface_signals)
|
|
80
|
+
|
|
81
|
+
lines = [
|
|
82
|
+
"\n\n## Surface Routing Recommendation",
|
|
83
|
+
f"- Recommended agents: {agents}",
|
|
84
|
+
f"- Dispatch mode: {dispatch_mode}",
|
|
85
|
+
f"- Confidence: {confidence}",
|
|
86
|
+
f"- Matched signals: {json.dumps(all_signals)}",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
logger.info(
|
|
90
|
+
"Surface routing: agents=%s mode=%s confidence=%.2f signals=%s",
|
|
91
|
+
agents, dispatch_mode, confidence, all_signals,
|
|
92
|
+
)
|
|
93
|
+
return "\n".join(lines)
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.warning("Surface routing failed (advisory, skipping): %s", e)
|
|
97
|
+
return ""
|
|
98
|
+
|
|
99
|
+
|
|
27
100
|
def _build_welcome(mode: str) -> str:
|
|
28
|
-
"""Build first-run welcome message for the user.
|
|
101
|
+
"""Build first-run welcome message for the user.
|
|
102
|
+
|
|
103
|
+
This message is injected as additionalContext so Claude receives it,
|
|
104
|
+
not the user directly. The framing explains WHY the user needs to
|
|
105
|
+
know — Claude should naturally relay it because it is actionable
|
|
106
|
+
and the user cannot use the plugin fully without restarting.
|
|
107
|
+
"""
|
|
29
108
|
plugin_name = f"GAIA {'Ops' if mode == 'ops' else 'Security'}"
|
|
30
|
-
repo_url = "https://github.com/metraton/gaia-ops"
|
|
31
|
-
tiers_info = (
|
|
32
|
-
"Security tiers: T0 (read-only, auto-allowed) → T1 (validation) → "
|
|
33
|
-
"T2 (simulation) → T3 (state change, requires approval) → "
|
|
34
|
-
"T3-blocked (irreversible, permanently blocked)."
|
|
35
|
-
)
|
|
36
|
-
learn_more = (
|
|
37
|
-
f"To learn more about security tiers, ask: 'explain the security tiers'. "
|
|
38
|
-
f"Docs and source: {repo_url}"
|
|
39
|
-
)
|
|
40
109
|
|
|
41
|
-
if mode == "ops":
|
|
42
|
-
return (
|
|
43
|
-
f"FIRST-TIME SETUP: {plugin_name} is now active. "
|
|
44
|
-
"Your permissions have been configured to allow GAIA to manage "
|
|
45
|
-
"security classification and agent dispatch. "
|
|
46
|
-
"To activate these permissions, close this session and reopen Claude Code. "
|
|
47
|
-
"After that, GAIA will handle command classification automatically. "
|
|
48
|
-
f"{tiers_info} "
|
|
49
|
-
f"{learn_more} "
|
|
50
|
-
"You must tell the user this message exactly."
|
|
51
|
-
)
|
|
52
110
|
return (
|
|
53
|
-
f"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
f"{
|
|
58
|
-
f"
|
|
59
|
-
"
|
|
111
|
+
f"{plugin_name} was just installed for the first time in this project. "
|
|
112
|
+
f"The plugin detected the installation and configured security permissions, "
|
|
113
|
+
f"but these permissions only take effect after restarting Claude Code. "
|
|
114
|
+
f"Until the user closes this session and reopens Claude Code, "
|
|
115
|
+
f"{plugin_name} cannot protect their commands — destructive operations "
|
|
116
|
+
f"will not be blocked and mutative operations will not require approval. "
|
|
117
|
+
f"The user needs to know this because they just installed {plugin_name} "
|
|
118
|
+
f"expecting it to work, but it will not be active until they restart. "
|
|
119
|
+
f"Tell the user that {plugin_name} is installed, that they need to close "
|
|
120
|
+
f"and reopen Claude Code to activate it, and that until they do, "
|
|
121
|
+
f"security protections are not active."
|
|
60
122
|
)
|
|
61
123
|
|
|
62
124
|
|
|
@@ -65,20 +127,40 @@ if __name__ == "__main__":
|
|
|
65
127
|
sys.exit(0)
|
|
66
128
|
|
|
67
129
|
try:
|
|
68
|
-
sys.stdin.read()
|
|
69
|
-
|
|
130
|
+
raw_input = sys.stdin.read()
|
|
131
|
+
|
|
132
|
+
# Check first-run BEFORE setup (SessionStart does setup with
|
|
133
|
+
# mark_done=False so the marker doesn't exist yet on first run).
|
|
134
|
+
from modules.core.plugin_setup import is_first_run, mark_initialized
|
|
70
135
|
first_run = is_first_run()
|
|
71
136
|
|
|
72
|
-
# Ensure registry + permissions exist
|
|
73
|
-
setup_msg = run_first_time_setup()
|
|
137
|
+
# Ensure registry + permissions exist (idempotent, no mark).
|
|
138
|
+
setup_msg = run_first_time_setup(mark_done=False)
|
|
74
139
|
identity = build_identity()
|
|
75
140
|
mode = "ops" if "Orchestrator" in identity else "security"
|
|
76
141
|
|
|
77
|
-
# First-time welcome:
|
|
78
|
-
|
|
142
|
+
# First-time welcome: the marker does not exist yet because
|
|
143
|
+
# neither SessionStart nor this call marked it.
|
|
144
|
+
if first_run:
|
|
79
145
|
welcome = _build_welcome(mode)
|
|
80
|
-
identity
|
|
81
|
-
|
|
146
|
+
identity = f"{welcome}\n\n{identity}"
|
|
147
|
+
mark_initialized() # Mark AFTER building the welcome
|
|
148
|
+
logger.info("First-run welcome prepended for %s mode", mode)
|
|
149
|
+
|
|
150
|
+
# Append deterministic surface routing recommendation (ops mode only)
|
|
151
|
+
if mode == "ops":
|
|
152
|
+
prompt_text = _extract_user_prompt(raw_input)
|
|
153
|
+
|
|
154
|
+
# NOTE: Approval activation moved to ElicitationResult hook.
|
|
155
|
+
# AskUserQuestion responses trigger ElicitationResult, not
|
|
156
|
+
# UserPromptSubmit, so approval detection lives there now.
|
|
157
|
+
|
|
158
|
+
if prompt_text:
|
|
159
|
+
routing_block = _build_routing_recommendation(prompt_text)
|
|
160
|
+
if routing_block:
|
|
161
|
+
identity = identity + routing_block
|
|
162
|
+
else:
|
|
163
|
+
logger.info("Could not extract user prompt from stdin, skipping routing")
|
|
82
164
|
|
|
83
165
|
logger.info("Identity injected: %s mode (%d chars)", mode, len(identity))
|
|
84
166
|
|
package/index.js
CHANGED
|
@@ -56,7 +56,7 @@ export function getCommandPath(commandName) {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Get absolute path to a template
|
|
59
|
-
* @param {string} templateName - Name of the template (e.g., '
|
|
59
|
+
* @param {string} templateName - Name of the template (e.g., 'governance.template.md')
|
|
60
60
|
* @returns {string} Absolute path to template file
|
|
61
61
|
*/
|
|
62
62
|
export function getTemplatePath(templateName) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaguilar87/gaia-ops",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.2",
|
|
4
4
|
"description": "Multi-agent orchestration system for Claude Code - DevOps automation toolkit",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
"config/",
|
|
54
54
|
"speckit/",
|
|
55
55
|
"skills/",
|
|
56
|
+
"dist/",
|
|
57
|
+
"git-hooks/",
|
|
56
58
|
"README.md",
|
|
57
59
|
"INSTALL.md",
|
|
58
60
|
"CHANGELOG.md",
|
|
@@ -77,11 +79,11 @@
|
|
|
77
79
|
"pre-publish:dry": "node bin/pre-publish-validate.js --dry-run",
|
|
78
80
|
"pre-publish:validate": "node bin/pre-publish-validate.js --validate-only",
|
|
79
81
|
"prepack": "npm run clean",
|
|
80
|
-
"prepublishOnly": "npm run
|
|
82
|
+
"prepublishOnly": "npm run build:plugins && node bin/pre-publish-validate.js",
|
|
81
83
|
"postinstall": "node bin/gaia-update.js",
|
|
82
84
|
"preuninstall": "node bin/gaia-cleanup.js"
|
|
83
85
|
},
|
|
84
|
-
"_postinstall_note": "postinstall:
|
|
86
|
+
"_postinstall_note": "postinstall: settings.json replaced (hooks only), permissions merged into settings.local.json (union, user config preserved)",
|
|
85
87
|
"dependencies": {
|
|
86
88
|
"chalk": "^5.3.0",
|
|
87
89
|
"ora": "^7.0.1",
|
package/skills/README.md
CHANGED
|
@@ -8,7 +8,6 @@ Skills are knowledge modules that extend agent capabilities. They use Claude Cod
|
|
|
8
8
|
.claude/skills/
|
|
9
9
|
├── agent-protocol/ # json:contract format, state machine, repair flow
|
|
10
10
|
├── agent-response/ # Orchestrator: interpret agent json:contract responses
|
|
11
|
-
├── project-dispatch/ # Orchestrator: agent table and dispatch rules
|
|
12
11
|
├── security-tiers/ # T0-T3 classification
|
|
13
12
|
│ └── reference.md
|
|
14
13
|
├── investigation/ # Diagnosis methodology and pattern analysis
|
|
@@ -28,7 +27,7 @@ Skills are knowledge modules that extend agent capabilities. They use Claude Cod
|
|
|
28
27
|
├── fast-queries/ # Quick diagnostic scripts
|
|
29
28
|
├── speckit-workflow/ # Speckit phase management
|
|
30
29
|
├── specification/ # Feature specification workflow
|
|
31
|
-
├── orchestrator-approval/ # T3
|
|
30
|
+
├── orchestrator-approval/ # T3 approval presentation for orchestrator
|
|
32
31
|
├── approval/ # T3 plan presentation and approval workflow
|
|
33
32
|
│ └── examples.md
|
|
34
33
|
├── execution/ # Post-approval execution protocol
|
|
@@ -65,9 +64,8 @@ skills:
|
|
|
65
64
|
| speckit-planner | agent-protocol, security-tiers, speckit-workflow | - |
|
|
66
65
|
|
|
67
66
|
Orchestrator skills (loaded on-demand via Skill tool, not assigned to agents):
|
|
68
|
-
- **project-dispatch** -- agent table and dispatch rules
|
|
69
67
|
- **agent-response** -- contract status interpretation and presentation
|
|
70
|
-
- **orchestrator-approval** -- T3
|
|
68
|
+
- **orchestrator-approval** -- T3 approval presentation and grant activation
|
|
71
69
|
|
|
72
70
|
## Skill Types
|
|
73
71
|
|
|
@@ -77,7 +75,7 @@ Orchestrator skills (loaded on-demand via Skill tool, not assigned to agents):
|
|
|
77
75
|
| **Common** | Most agents via `skills:` | command-execution, context-updater |
|
|
78
76
|
| **Domain** | Per-agent via `skills:` | terraform-patterns, gitops-patterns, developer-patterns, gaia-patterns |
|
|
79
77
|
| **Workflow** | On-demand (agent reads file) | approval, execution, git-conventions |
|
|
80
|
-
| **Orchestrator** | On-demand via Skill tool |
|
|
78
|
+
| **Orchestrator** | On-demand via Skill tool | agent-response, orchestrator-approval |
|
|
81
79
|
|
|
82
80
|
Workflow skills are loaded on-demand -- agents read them from disk when needed rather than receiving them at startup. Supporting files (`examples.md`, `reference.md`) are also read on-demand.
|
|
83
81
|
|