@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,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core module - Shared utilities for all hook modules.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- paths: Unified path resolution (find_claude_dir)
|
|
6
|
+
- plugin_mode: Plugin mode detection (security vs ops)
|
|
7
|
+
- state: Pre/post hook state sharing
|
|
8
|
+
- stdin: Stdin availability check (has_stdin_data)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .paths import find_claude_dir, get_plugin_data_dir, get_logs_dir, get_metrics_dir, get_memory_dir
|
|
12
|
+
from .plugin_mode import get_plugin_mode, is_ops_mode, is_security_mode, has_plugin, clear_mode_cache
|
|
13
|
+
from .state import HookState, get_hook_state, save_hook_state, clear_hook_state, get_session_id
|
|
14
|
+
from .stdin import has_stdin_data
|
|
15
|
+
from .hook_entry import run_hook
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Paths
|
|
19
|
+
"find_claude_dir",
|
|
20
|
+
"get_plugin_data_dir",
|
|
21
|
+
"get_logs_dir",
|
|
22
|
+
"get_metrics_dir",
|
|
23
|
+
"get_memory_dir",
|
|
24
|
+
# Plugin mode
|
|
25
|
+
"get_plugin_mode",
|
|
26
|
+
"is_ops_mode",
|
|
27
|
+
"is_security_mode",
|
|
28
|
+
"has_plugin",
|
|
29
|
+
"clear_mode_cache",
|
|
30
|
+
# State
|
|
31
|
+
"HookState",
|
|
32
|
+
"get_hook_state",
|
|
33
|
+
"save_hook_state",
|
|
34
|
+
"clear_hook_state",
|
|
35
|
+
"get_session_id",
|
|
36
|
+
# Stdin
|
|
37
|
+
"has_stdin_data",
|
|
38
|
+
# Hook entry
|
|
39
|
+
"run_hook",
|
|
40
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common hook entrypoint boilerplate.
|
|
3
|
+
|
|
4
|
+
Provides ``run_hook()`` which encapsulates the repeated pattern found in
|
|
5
|
+
every hook entrypoint:
|
|
6
|
+
|
|
7
|
+
1. Check ``has_stdin_data()``
|
|
8
|
+
2. Read stdin
|
|
9
|
+
3. Parse via the adapter
|
|
10
|
+
4. Call a caller-provided handler with the parsed ``HookEvent``
|
|
11
|
+
5. Catch and log exceptions, exiting appropriately
|
|
12
|
+
|
|
13
|
+
Usage in a hook entrypoint::
|
|
14
|
+
|
|
15
|
+
from modules.core.hook_entry import run_hook
|
|
16
|
+
|
|
17
|
+
def _handle(event: HookEvent) -> None:
|
|
18
|
+
... # business logic, call sys.exit() / print as needed
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
run_hook(_handle, hook_name="stop_hook")
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import logging
|
|
27
|
+
import sys
|
|
28
|
+
from typing import Callable
|
|
29
|
+
|
|
30
|
+
from .stdin import has_stdin_data
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_hook(
|
|
36
|
+
handler: Callable,
|
|
37
|
+
*,
|
|
38
|
+
hook_name: str = "hook",
|
|
39
|
+
usage_message: str | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Read stdin, parse via ClaudeCodeAdapter, and delegate to *handler*.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
handler: Callable that receives an ``adapters.types.HookEvent``.
|
|
45
|
+
The handler is responsible for printing output and calling
|
|
46
|
+
``sys.exit()`` with the appropriate code.
|
|
47
|
+
hook_name: Human-readable name used in log messages and the default
|
|
48
|
+
usage string.
|
|
49
|
+
usage_message: Custom usage text shown when stdin is absent. If
|
|
50
|
+
*None*, a sensible default is generated from *hook_name*.
|
|
51
|
+
"""
|
|
52
|
+
if not has_stdin_data():
|
|
53
|
+
msg = usage_message or (
|
|
54
|
+
f"Usage: echo '{{...}}' | python {hook_name}.py (stdin mode)"
|
|
55
|
+
)
|
|
56
|
+
print(msg)
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
# Deferred adapter import avoids circular dependencies at module level;
|
|
61
|
+
# the adapter package is a sibling of modules/.
|
|
62
|
+
from adapters.claude_code import ClaudeCodeAdapter
|
|
63
|
+
|
|
64
|
+
adapter = ClaudeCodeAdapter()
|
|
65
|
+
stdin_data = sys.stdin.read()
|
|
66
|
+
event = adapter.parse_event(stdin_data)
|
|
67
|
+
handler(event)
|
|
68
|
+
except ValueError as e:
|
|
69
|
+
logger.error("Adapter parse failed in %s: %s", hook_name, e)
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
except json.JSONDecodeError as e:
|
|
72
|
+
logger.error("Invalid JSON from stdin in %s: %s", hook_name, e)
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
except SystemExit:
|
|
75
|
+
raise # Let explicit sys.exit() calls propagate
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error("Error processing %s hook: %s", hook_name, e)
|
|
78
|
+
sys.exit(1)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified path resolution for gaia-ops hooks.
|
|
3
|
+
|
|
4
|
+
Single source of truth for finding .claude directory and its subdirectories.
|
|
5
|
+
|
|
6
|
+
Path resolution follows two base directories:
|
|
7
|
+
- PLUGIN_ROOT (.claude/): Code, config, agents, skills, context providers
|
|
8
|
+
- PLUGIN_DATA (CLAUDE_PLUGIN_DATA or .claude/ fallback): Logs, sessions,
|
|
9
|
+
approval grants, workflow episodic memory — data that survives plugin updates
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@lru_cache(maxsize=1)
|
|
22
|
+
def find_claude_dir() -> Path:
|
|
23
|
+
"""
|
|
24
|
+
Find the .claude directory by searching upward from current location.
|
|
25
|
+
|
|
26
|
+
Search order:
|
|
27
|
+
1. If currently in .claude, return it
|
|
28
|
+
2. Check current directory for .claude/
|
|
29
|
+
3. Search parent directories upward
|
|
30
|
+
4. Fall back to current/.claude (without creating it)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Path to the .claude directory
|
|
34
|
+
|
|
35
|
+
Note:
|
|
36
|
+
Result is cached for performance. Clear with find_claude_dir.cache_clear()
|
|
37
|
+
"""
|
|
38
|
+
current = Path.cwd()
|
|
39
|
+
|
|
40
|
+
# If we're already in a .claude directory, return it
|
|
41
|
+
if current.name == ".claude":
|
|
42
|
+
return current
|
|
43
|
+
|
|
44
|
+
# Look for .claude in current directory
|
|
45
|
+
claude_dir = current / ".claude"
|
|
46
|
+
if claude_dir.exists():
|
|
47
|
+
return claude_dir
|
|
48
|
+
|
|
49
|
+
# Search upward through parent directories
|
|
50
|
+
for parent in current.parents:
|
|
51
|
+
claude_dir = parent / ".claude"
|
|
52
|
+
if claude_dir.exists():
|
|
53
|
+
return claude_dir
|
|
54
|
+
|
|
55
|
+
# Fallback - use current directory's .claude (but don't create it yet)
|
|
56
|
+
logger.warning(f"No .claude directory found, using {current}/.claude")
|
|
57
|
+
return current / ".claude"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@lru_cache(maxsize=1)
|
|
61
|
+
def get_plugin_data_dir() -> Path:
|
|
62
|
+
"""
|
|
63
|
+
Get the base directory for persistent plugin data.
|
|
64
|
+
|
|
65
|
+
Resolution order:
|
|
66
|
+
1. CLAUDE_PLUGIN_DATA env var (set by Claude Code >= 2.1.78)
|
|
67
|
+
2. Fallback to find_claude_dir() for backward compatibility
|
|
68
|
+
|
|
69
|
+
Data stored here survives plugin updates. Code and config remain
|
|
70
|
+
under PLUGIN_ROOT (find_claude_dir()).
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Path to the plugin data directory (created if needed)
|
|
74
|
+
|
|
75
|
+
Note:
|
|
76
|
+
Result is cached for performance. Clear with clear_path_cache()
|
|
77
|
+
"""
|
|
78
|
+
plugin_data = os.environ.get("CLAUDE_PLUGIN_DATA")
|
|
79
|
+
if plugin_data:
|
|
80
|
+
data_dir = Path(plugin_data)
|
|
81
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
return data_dir
|
|
83
|
+
# Fallback: data co-located with code in .claude/
|
|
84
|
+
return find_claude_dir()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_logs_dir() -> Path:
|
|
88
|
+
"""
|
|
89
|
+
Get the logs directory, creating it if necessary.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Path to .claude/logs/
|
|
93
|
+
"""
|
|
94
|
+
logs_dir = get_plugin_data_dir() / "logs"
|
|
95
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
return logs_dir
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_metrics_dir() -> Path:
|
|
100
|
+
"""
|
|
101
|
+
Get the metrics directory, creating it if necessary.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Path to .claude/metrics/
|
|
105
|
+
"""
|
|
106
|
+
metrics_dir = get_plugin_data_dir() / "metrics"
|
|
107
|
+
metrics_dir.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
return metrics_dir
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_memory_dir(subdir: Optional[str] = None) -> Path:
|
|
112
|
+
"""
|
|
113
|
+
Get the memory directory, creating it if necessary.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
subdir: Optional subdirectory (e.g., "workflow-episodic")
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Path to .claude/memory/ or .claude/memory/{subdir}/
|
|
120
|
+
"""
|
|
121
|
+
memory_dir = get_plugin_data_dir() / "memory"
|
|
122
|
+
if subdir:
|
|
123
|
+
memory_dir = memory_dir / subdir
|
|
124
|
+
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
return memory_dir
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_events_dir() -> Path:
|
|
129
|
+
"""
|
|
130
|
+
Get the events directory, creating it if necessary.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Path to .claude/events/
|
|
134
|
+
"""
|
|
135
|
+
events_dir = get_plugin_data_dir() / "events"
|
|
136
|
+
events_dir.mkdir(parents=True, exist_ok=True)
|
|
137
|
+
return events_dir
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_session_dir() -> Path:
|
|
141
|
+
"""
|
|
142
|
+
Get the active session directory, creating it if necessary.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Path to .claude/session/active/
|
|
146
|
+
"""
|
|
147
|
+
session_dir = get_plugin_data_dir() / "session" / "active"
|
|
148
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
return session_dir
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def clear_path_cache():
|
|
153
|
+
"""Clear all cached path results (useful for testing)."""
|
|
154
|
+
find_claude_dir.cache_clear()
|
|
155
|
+
get_plugin_data_dir.cache_clear()
|
|
156
|
+
try:
|
|
157
|
+
from .plugin_mode import clear_mode_cache
|
|
158
|
+
clear_mode_cache()
|
|
159
|
+
except ImportError:
|
|
160
|
+
pass
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Plugin mode detection for gaia hooks.
|
|
2
|
+
|
|
3
|
+
Determines whether the installation is security-only or ops (full orchestrator).
|
|
4
|
+
Security mode: T3 operations use native Claude Code approval dialog (permissionDecision: ask).
|
|
5
|
+
Ops mode: T3 operations block with nonce for orchestrator agent approval flow.
|
|
6
|
+
|
|
7
|
+
Detection order:
|
|
8
|
+
1. plugin-registry.json in plugin data directory
|
|
9
|
+
2. NPM package name detection (gaia-ops vs gaia-security)
|
|
10
|
+
3. GAIA_PLUGIN_MODE env var fallback
|
|
11
|
+
4. Default: "security" (most restrictive)
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
from functools import lru_cache
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
VALID_MODES = ("security", "ops")
|
|
24
|
+
DEFAULT_MODE = "security"
|
|
25
|
+
|
|
26
|
+
# Map NPM package names to plugin modes
|
|
27
|
+
_NPM_PACKAGE_MODE = {
|
|
28
|
+
"gaia-ops": "ops",
|
|
29
|
+
"gaia-security": "security",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _detect_mode_from_npm_package() -> str | None:
|
|
34
|
+
"""Detect plugin mode from the NPM package name.
|
|
35
|
+
|
|
36
|
+
When installed via npm, this module lives at a path like:
|
|
37
|
+
.../node_modules/@jaguilar87/gaia-ops/hooks/modules/core/plugin_mode.py
|
|
38
|
+
|
|
39
|
+
The package directory name (gaia-ops or gaia-security) determines the mode.
|
|
40
|
+
Also checks .claude/ symlinks as a secondary signal for npm installs.
|
|
41
|
+
|
|
42
|
+
Returns the mode string or None if not detectable.
|
|
43
|
+
"""
|
|
44
|
+
# Primary: check our own file path for node_modules package name
|
|
45
|
+
module_path = Path(__file__).resolve()
|
|
46
|
+
parts = module_path.parts
|
|
47
|
+
for i, part in enumerate(parts):
|
|
48
|
+
if part == "node_modules" and i + 2 < len(parts):
|
|
49
|
+
# Could be @scope/package-name or just package-name
|
|
50
|
+
pkg_name = parts[i + 1]
|
|
51
|
+
if pkg_name.startswith("@") and i + 2 < len(parts):
|
|
52
|
+
pkg_name = parts[i + 2]
|
|
53
|
+
mode = _NPM_PACKAGE_MODE.get(pkg_name)
|
|
54
|
+
if mode:
|
|
55
|
+
logger.debug("Detected mode '%s' from npm package path: %s", mode, pkg_name)
|
|
56
|
+
return mode
|
|
57
|
+
|
|
58
|
+
# Secondary: check if .claude/agents symlink points to a gaia package
|
|
59
|
+
try:
|
|
60
|
+
from .paths import find_claude_dir
|
|
61
|
+
claude_dir = find_claude_dir()
|
|
62
|
+
agents_link = claude_dir / "agents"
|
|
63
|
+
if agents_link.is_symlink():
|
|
64
|
+
target = str(agents_link.resolve())
|
|
65
|
+
for pkg_name, mode in _NPM_PACKAGE_MODE.items():
|
|
66
|
+
if pkg_name in target:
|
|
67
|
+
logger.debug("Detected mode '%s' from .claude/agents symlink target", mode)
|
|
68
|
+
return mode
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@lru_cache(maxsize=1)
|
|
76
|
+
def get_plugin_mode() -> str:
|
|
77
|
+
"""Get the current plugin mode.
|
|
78
|
+
|
|
79
|
+
Returns "security" or "ops".
|
|
80
|
+
"""
|
|
81
|
+
# 1. Check plugin registry
|
|
82
|
+
try:
|
|
83
|
+
from .paths import get_plugin_data_dir
|
|
84
|
+
registry_path = get_plugin_data_dir() / "plugin-registry.json"
|
|
85
|
+
if registry_path.exists():
|
|
86
|
+
registry = json.loads(registry_path.read_text())
|
|
87
|
+
installed = [p.get("name", "") for p in registry.get("installed", [])]
|
|
88
|
+
if "gaia-ops" in installed:
|
|
89
|
+
return "ops"
|
|
90
|
+
if "gaia-security" in installed:
|
|
91
|
+
return "security"
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.debug("Registry check failed (non-fatal): %s", e)
|
|
94
|
+
|
|
95
|
+
# 2. CLAUDE_PLUGIN_ROOT + plugin.json (--plugin-dir mode)
|
|
96
|
+
plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", "")
|
|
97
|
+
if plugin_root:
|
|
98
|
+
try:
|
|
99
|
+
pjson = Path(plugin_root) / ".claude-plugin" / "plugin.json"
|
|
100
|
+
if pjson.exists():
|
|
101
|
+
pdata = json.loads(pjson.read_text())
|
|
102
|
+
pname = pdata.get("name", "")
|
|
103
|
+
mode = _NPM_PACKAGE_MODE.get(pname)
|
|
104
|
+
if mode:
|
|
105
|
+
logger.debug("Detected mode '%s' from plugin.json name: %s", mode, pname)
|
|
106
|
+
return mode
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.debug("Plugin.json check failed (non-fatal): %s", e)
|
|
109
|
+
|
|
110
|
+
# 3. NPM package name detection
|
|
111
|
+
npm_mode = _detect_mode_from_npm_package()
|
|
112
|
+
if npm_mode:
|
|
113
|
+
return npm_mode
|
|
114
|
+
|
|
115
|
+
# 4. Env var fallback
|
|
116
|
+
mode = os.environ.get("GAIA_PLUGIN_MODE", "").lower()
|
|
117
|
+
if mode in VALID_MODES:
|
|
118
|
+
return mode
|
|
119
|
+
|
|
120
|
+
# 5. Default: security (most restrictive)
|
|
121
|
+
return DEFAULT_MODE
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def has_plugin(name: str) -> bool:
|
|
125
|
+
"""Check if a specific plugin is installed."""
|
|
126
|
+
try:
|
|
127
|
+
from .paths import get_plugin_data_dir
|
|
128
|
+
registry_path = get_plugin_data_dir() / "plugin-registry.json"
|
|
129
|
+
if registry_path.exists():
|
|
130
|
+
registry = json.loads(registry_path.read_text())
|
|
131
|
+
return any(p.get("name") == name for p in registry.get("installed", []))
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def is_ops_mode() -> bool:
|
|
138
|
+
"""Convenience: check if running in ops mode."""
|
|
139
|
+
return get_plugin_mode() == "ops"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def is_security_mode() -> bool:
|
|
143
|
+
"""Convenience: check if running in security-only mode."""
|
|
144
|
+
return get_plugin_mode() == "security"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def clear_mode_cache():
|
|
148
|
+
"""Clear cached mode (for testing)."""
|
|
149
|
+
get_plugin_mode.cache_clear()
|