@jaguilar87/gaia 5.0.0-rc.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 +33 -0
- package/.claude-plugin/plugin.json +26 -0
- package/ARCHITECTURE.md +335 -0
- package/CHANGELOG.md +1298 -0
- package/CODE_OF_CONDUCT.md +11 -0
- package/CONTRIBUTING.md +146 -0
- package/INSTALL.md +436 -0
- package/LICENSE +21 -0
- package/README.md +222 -0
- package/SECURITY.md +47 -0
- package/agents/README.md +78 -0
- package/agents/cloud-troubleshooter.md +73 -0
- package/agents/developer.md +65 -0
- package/agents/gaia-operator.md +64 -0
- package/agents/gaia-orchestrator.md +111 -0
- package/agents/gaia-planner.md +53 -0
- package/agents/gaia-system.md +71 -0
- package/agents/gitops-operator.md +61 -0
- package/agents/terraform-architect.md +63 -0
- package/bin/README.md +106 -0
- package/bin/cli/__init__.py +1 -0
- package/bin/cli/approvals.py +740 -0
- package/bin/cli/cleanup.py +562 -0
- package/bin/cli/context.py +283 -0
- package/bin/cli/doctor.py +651 -0
- package/bin/cli/history.py +305 -0
- package/bin/cli/memory.py +483 -0
- package/bin/cli/metrics.py +1068 -0
- package/bin/cli/plans.py +515 -0
- package/bin/cli/status.py +302 -0
- package/bin/cli/update.py +382 -0
- package/bin/gaia +112 -0
- package/bin/gaia-cleanup.js +531 -0
- package/bin/gaia-doctor.js +635 -0
- package/bin/gaia-evidence +126 -0
- package/bin/gaia-history.js +251 -0
- package/bin/gaia-metrics.js +1278 -0
- package/bin/gaia-review.js +269 -0
- package/bin/gaia-scan +44 -0
- package/bin/gaia-scan.py +589 -0
- package/bin/gaia-skills-diagnose.js +929 -0
- package/bin/gaia-status.js +278 -0
- package/bin/gaia-uninstall.js +111 -0
- package/bin/gaia-update.js +919 -0
- package/bin/pre-publish-validate.js +610 -0
- package/bin/python-detect.js +60 -0
- package/bin/validate-sandbox.sh +601 -0
- package/commands/README.md +64 -0
- package/commands/gaia.md +37 -0
- package/commands/scan-project.md +67 -0
- package/config/README.md +71 -0
- package/config/cloud/aws.json +134 -0
- package/config/cloud/gcp.json +139 -0
- package/config/context-contracts.json +158 -0
- package/config/crons-schema.md +81 -0
- package/config/git_standards.json +72 -0
- package/config/surface-routing.json +417 -0
- package/config/universal-rules.json +102 -0
- package/dist/gaia-ops/.claude-plugin/plugin.json +24 -0
- package/dist/gaia-ops/README.md +80 -0
- package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
- package/dist/gaia-ops/agents/developer.md +65 -0
- package/dist/gaia-ops/agents/gaia-operator.md +64 -0
- package/dist/gaia-ops/agents/gaia-orchestrator.md +111 -0
- package/dist/gaia-ops/agents/gaia-planner.md +53 -0
- package/dist/gaia-ops/agents/gaia-system.md +71 -0
- package/dist/gaia-ops/agents/gitops-operator.md +61 -0
- package/dist/gaia-ops/agents/terraform-architect.md +63 -0
- package/dist/gaia-ops/commands/gaia.md +37 -0
- package/dist/gaia-ops/config/README.md +71 -0
- package/dist/gaia-ops/config/cloud/aws.json +134 -0
- package/dist/gaia-ops/config/cloud/gcp.json +139 -0
- package/dist/gaia-ops/config/context-contracts.json +158 -0
- package/dist/gaia-ops/config/crons-schema.md +81 -0
- package/dist/gaia-ops/config/git_standards.json +72 -0
- package/dist/gaia-ops/config/surface-routing.json +417 -0
- package/dist/gaia-ops/config/universal-rules.json +102 -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 +1890 -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 +192 -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 +120 -0
- package/dist/gaia-ops/hooks/modules/agents/state_tracker.py +267 -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 +611 -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/agentic_loop_detector.py +165 -0
- package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +218 -0
- package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-ops/hooks/modules/context/context_injector.py +558 -0
- package/dist/gaia-ops/hooks/modules/context/context_writer.py +530 -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 +577 -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/memory/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +216 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +122 -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 +120 -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 +1638 -0
- package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +222 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +595 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +87 -0
- package/dist/gaia-ops/hooks/modules/security/command_semantics.py +181 -0
- package/dist/gaia-ops/hooks/modules/security/composition_rules.py +547 -0
- package/dist/gaia-ops/hooks/modules/security/flag_classifiers.py +873 -0
- package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +1131 -0
- package/dist/gaia-ops/hooks/modules/security/network_hosts.py +481 -0
- package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-ops/hooks/modules/security/shell_unwrapper.py +165 -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/pending_scanner.py +174 -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 +160 -0
- package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-ops/hooks/modules/session/session_registry.py +333 -0
- package/dist/gaia-ops/hooks/modules/tools/__init__.py +29 -0
- package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +1008 -0
- package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +231 -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/stage_decomposer.py +315 -0
- package/dist/gaia-ops/hooks/modules/tools/task_validator.py +294 -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_compact.py +60 -0
- package/dist/gaia-ops/hooks/pre_tool_use.py +413 -0
- package/dist/gaia-ops/hooks/session_end_hook.py +77 -0
- package/dist/gaia-ops/hooks/session_start.py +81 -0
- package/dist/gaia-ops/hooks/stop_hook.py +70 -0
- package/dist/gaia-ops/hooks/subagent_start.py +71 -0
- package/dist/gaia-ops/hooks/subagent_stop.py +295 -0
- package/dist/gaia-ops/hooks/task_completed.py +70 -0
- package/dist/gaia-ops/hooks/user_prompt_submit.py +246 -0
- package/dist/gaia-ops/settings.json +72 -0
- package/dist/gaia-ops/skills/README.md +158 -0
- package/dist/gaia-ops/skills/agent-creation/SKILL.md +87 -0
- package/dist/gaia-ops/skills/agent-creation/examples.md +170 -0
- package/dist/gaia-ops/skills/agent-creation/reference.md +191 -0
- package/dist/gaia-ops/skills/agent-protocol/SKILL.md +93 -0
- package/dist/gaia-ops/skills/agent-protocol/examples.md +223 -0
- package/dist/gaia-ops/skills/agent-response/SKILL.md +69 -0
- package/dist/gaia-ops/skills/agentic-loop/SKILL.md +80 -0
- package/dist/gaia-ops/skills/agentic-loop/reference.md +378 -0
- package/dist/gaia-ops/skills/blog-writing/SKILL.md +98 -0
- package/dist/gaia-ops/skills/blog-writing/reference.md +130 -0
- package/dist/gaia-ops/skills/brief-spec/SKILL.md +185 -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 +87 -0
- package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
- package/dist/gaia-ops/skills/developer-patterns/SKILL.md +50 -0
- package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
- package/dist/gaia-ops/skills/execution/SKILL.md +99 -0
- package/dist/gaia-ops/skills/fast-queries/SKILL.md +43 -0
- package/dist/gaia-ops/skills/gaia-compact/SKILL.md +74 -0
- package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +108 -0
- package/dist/gaia-ops/skills/gaia-patterns/reference.md +395 -0
- package/dist/gaia-ops/skills/gaia-planner/SKILL.md +37 -0
- package/dist/gaia-ops/skills/gaia-planner/reference.md +107 -0
- package/dist/gaia-ops/skills/gaia-release/SKILL.md +85 -0
- package/dist/gaia-ops/skills/gaia-release/reference.md +92 -0
- package/dist/gaia-ops/skills/gaia-self-check/SKILL.md +114 -0
- package/dist/gaia-ops/skills/gaia-self-check/reference.md +453 -0
- package/dist/gaia-ops/skills/gaia-verify/SKILL.md +77 -0
- package/dist/gaia-ops/skills/gaia-verify/reference.md +80 -0
- package/dist/gaia-ops/skills/git-conventions/SKILL.md +47 -0
- package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +60 -0
- package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
- package/dist/gaia-ops/skills/gmail-policy/SKILL.md +200 -0
- package/dist/gaia-ops/skills/gmail-policy/reference.md +150 -0
- package/dist/gaia-ops/skills/gmail-triage/SKILL.md +100 -0
- package/dist/gaia-ops/skills/gws-setup/SKILL.md +99 -0
- package/dist/gaia-ops/skills/gws-setup/reference.md +73 -0
- package/dist/gaia-ops/skills/investigation/SKILL.md +100 -0
- package/dist/gaia-ops/skills/memory-curation/SKILL.md +83 -0
- package/dist/gaia-ops/skills/memory-search/SKILL.md +88 -0
- package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +160 -0
- package/dist/gaia-ops/skills/orchestrator-approval/reference.md +174 -0
- package/dist/gaia-ops/skills/pending-approvals/SKILL.md +72 -0
- package/dist/gaia-ops/skills/pending-approvals/reference.md +214 -0
- package/dist/gaia-ops/skills/readme-writing/SKILL.md +71 -0
- package/dist/gaia-ops/skills/readme-writing/reference.md +188 -0
- package/dist/gaia-ops/skills/reference.md +135 -0
- package/dist/gaia-ops/skills/request-approval/SKILL.md +140 -0
- package/dist/gaia-ops/skills/request-approval/examples.md +140 -0
- package/dist/gaia-ops/skills/request-approval/reference.md +57 -0
- package/dist/gaia-ops/skills/schedule-task/SKILL.md +64 -0
- package/dist/gaia-ops/skills/schedule-task/reference.md +233 -0
- package/dist/gaia-ops/skills/security-tiers/SKILL.md +141 -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/session-reflection/SKILL.md +69 -0
- package/dist/gaia-ops/skills/skill-creation/SKILL.md +92 -0
- package/dist/gaia-ops/skills/skill-creation/reference.md +29 -0
- package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +89 -0
- package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
- package/dist/gaia-ops/tools/__init__.py +9 -0
- package/dist/gaia-ops/tools/agentic-loop/decide-status.py +210 -0
- package/dist/gaia-ops/tools/agentic-loop/parse-metric.py +106 -0
- package/dist/gaia-ops/tools/agentic-loop/record-iteration.py +221 -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 +721 -0
- package/dist/gaia-ops/tools/context/context_section_reader.py +342 -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 +264 -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/backfill_fts5.py +107 -0
- package/dist/gaia-ops/tools/memory/conflict_detector.py +295 -0
- package/dist/gaia-ops/tools/memory/episodic.py +1210 -0
- package/dist/gaia-ops/tools/memory/git_invalidator.py +262 -0
- package/dist/gaia-ops/tools/memory/paths.py +102 -0
- package/dist/gaia-ops/tools/memory/scoring.py +193 -0
- package/dist/gaia-ops/tools/memory/search_store.py +375 -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 +349 -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 +686 -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 +270 -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 +24 -0
- package/dist/gaia-security/README.md +90 -0
- package/dist/gaia-security/config/universal-rules.json +102 -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 +1890 -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 +113 -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 +120 -0
- package/dist/gaia-security/hooks/modules/agents/state_tracker.py +267 -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 +611 -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/agentic_loop_detector.py +165 -0
- package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +218 -0
- package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-security/hooks/modules/context/context_injector.py +558 -0
- package/dist/gaia-security/hooks/modules/context/context_writer.py +530 -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 +577 -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/memory/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/memory/episode_writer.py +216 -0
- package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +122 -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 +120 -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 +1638 -0
- package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-security/hooks/modules/security/approval_scopes.py +222 -0
- package/dist/gaia-security/hooks/modules/security/blocked_commands.py +595 -0
- package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +87 -0
- package/dist/gaia-security/hooks/modules/security/command_semantics.py +181 -0
- package/dist/gaia-security/hooks/modules/security/composition_rules.py +547 -0
- package/dist/gaia-security/hooks/modules/security/flag_classifiers.py +873 -0
- package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +1131 -0
- package/dist/gaia-security/hooks/modules/security/network_hosts.py +481 -0
- package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-security/hooks/modules/security/shell_unwrapper.py +165 -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/pending_scanner.py +174 -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 +160 -0
- package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-security/hooks/modules/session/session_registry.py +333 -0
- package/dist/gaia-security/hooks/modules/tools/__init__.py +29 -0
- package/dist/gaia-security/hooks/modules/tools/bash_validator.py +1008 -0
- package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +231 -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/stage_decomposer.py +315 -0
- package/dist/gaia-security/hooks/modules/tools/task_validator.py +294 -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 +413 -0
- package/dist/gaia-security/hooks/session_end_hook.py +77 -0
- package/dist/gaia-security/hooks/session_start.py +81 -0
- package/dist/gaia-security/hooks/stop_hook.py +70 -0
- package/dist/gaia-security/hooks/user_prompt_submit.py +246 -0
- package/dist/gaia-security/settings.json +58 -0
- package/git-hooks/commit-msg +41 -0
- package/hooks/README.md +100 -0
- package/hooks/adapters/__init__.py +52 -0
- package/hooks/adapters/base.py +219 -0
- package/hooks/adapters/channel.py +17 -0
- package/hooks/adapters/claude_code.py +1890 -0
- package/hooks/adapters/types.py +194 -0
- package/hooks/adapters/utils.py +25 -0
- package/hooks/elicitation_result.py +179 -0
- package/hooks/hooks.json +84 -0
- package/hooks/modules/README.md +189 -0
- package/hooks/modules/__init__.py +15 -0
- package/hooks/modules/agents/__init__.py +29 -0
- package/hooks/modules/agents/contract_validator.py +647 -0
- package/hooks/modules/agents/response_contract.py +496 -0
- package/hooks/modules/agents/skill_injection_verifier.py +120 -0
- package/hooks/modules/agents/state_tracker.py +267 -0
- package/hooks/modules/agents/task_info_builder.py +74 -0
- package/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/hooks/modules/agents/transcript_reader.py +152 -0
- package/hooks/modules/audit/__init__.py +28 -0
- package/hooks/modules/audit/event_detector.py +168 -0
- package/hooks/modules/audit/logger.py +131 -0
- package/hooks/modules/audit/metrics.py +134 -0
- package/hooks/modules/audit/workflow_auditor.py +611 -0
- package/hooks/modules/audit/workflow_recorder.py +296 -0
- package/hooks/modules/context/__init__.py +11 -0
- package/hooks/modules/context/agentic_loop_detector.py +165 -0
- package/hooks/modules/context/anchor_tracker.py +317 -0
- package/hooks/modules/context/compact_context_builder.py +218 -0
- package/hooks/modules/context/context_freshness.py +145 -0
- package/hooks/modules/context/context_injector.py +558 -0
- package/hooks/modules/context/context_writer.py +530 -0
- package/hooks/modules/context/contracts_loader.py +161 -0
- package/hooks/modules/core/__init__.py +40 -0
- package/hooks/modules/core/hook_entry.py +78 -0
- package/hooks/modules/core/paths.py +160 -0
- package/hooks/modules/core/plugin_mode.py +149 -0
- package/hooks/modules/core/plugin_setup.py +577 -0
- package/hooks/modules/core/state.py +179 -0
- package/hooks/modules/core/stdin.py +24 -0
- package/hooks/modules/events/__init__.py +1 -0
- package/hooks/modules/events/event_writer.py +210 -0
- package/hooks/modules/evidence/__init__.py +34 -0
- package/hooks/modules/evidence/assertions.py +137 -0
- package/hooks/modules/evidence/index_writer.py +57 -0
- package/hooks/modules/evidence/loader.py +126 -0
- package/hooks/modules/evidence/runner.py +241 -0
- package/hooks/modules/memory/__init__.py +8 -0
- package/hooks/modules/memory/episode_writer.py +216 -0
- package/hooks/modules/orchestrator/__init__.py +1 -0
- package/hooks/modules/orchestrator/delegate_mode.py +122 -0
- package/hooks/modules/scanning/__init__.py +8 -0
- package/hooks/modules/scanning/scan_trigger.py +84 -0
- package/hooks/modules/security/__init__.py +120 -0
- package/hooks/modules/security/approval_cleanup.py +87 -0
- package/hooks/modules/security/approval_constants.py +23 -0
- package/hooks/modules/security/approval_grants.py +1638 -0
- package/hooks/modules/security/approval_messages.py +71 -0
- package/hooks/modules/security/approval_scopes.py +222 -0
- package/hooks/modules/security/blocked_commands.py +595 -0
- package/hooks/modules/security/blocked_message_formatter.py +87 -0
- package/hooks/modules/security/command_semantics.py +181 -0
- package/hooks/modules/security/composition_rules.py +547 -0
- package/hooks/modules/security/flag_classifiers.py +873 -0
- package/hooks/modules/security/gitops_validator.py +179 -0
- package/hooks/modules/security/mutative_verbs.py +1131 -0
- package/hooks/modules/security/network_hosts.py +481 -0
- package/hooks/modules/security/prompt_validator.py +40 -0
- package/hooks/modules/security/shell_unwrapper.py +165 -0
- package/hooks/modules/security/tiers.py +196 -0
- package/hooks/modules/session/__init__.py +10 -0
- package/hooks/modules/session/pending_scanner.py +174 -0
- package/hooks/modules/session/session_context_writer.py +100 -0
- package/hooks/modules/session/session_event_injector.py +160 -0
- package/hooks/modules/session/session_manager.py +31 -0
- package/hooks/modules/session/session_registry.py +333 -0
- package/hooks/modules/tools/__init__.py +29 -0
- package/hooks/modules/tools/bash_validator.py +1008 -0
- package/hooks/modules/tools/cloud_pipe_validator.py +231 -0
- package/hooks/modules/tools/hook_response.py +55 -0
- package/hooks/modules/tools/shell_parser.py +227 -0
- package/hooks/modules/tools/stage_decomposer.py +315 -0
- package/hooks/modules/tools/task_validator.py +294 -0
- package/hooks/modules/validation/__init__.py +23 -0
- package/hooks/modules/validation/commit_validator.py +380 -0
- package/hooks/post_compact.py +43 -0
- package/hooks/post_tool_use.py +54 -0
- package/hooks/pre_compact.py +60 -0
- package/hooks/pre_tool_use.py +413 -0
- package/hooks/session_end_hook.py +77 -0
- package/hooks/session_start.py +81 -0
- package/hooks/stop_hook.py +70 -0
- package/hooks/subagent_start.py +71 -0
- package/hooks/subagent_stop.py +295 -0
- package/hooks/task_completed.py +70 -0
- package/hooks/user_prompt_submit.py +246 -0
- package/index.js +83 -0
- package/package.json +103 -0
- package/pyproject.toml +32 -0
- package/skills/README.md +158 -0
- package/skills/agent-creation/SKILL.md +87 -0
- package/skills/agent-creation/examples.md +170 -0
- package/skills/agent-creation/reference.md +191 -0
- package/skills/agent-protocol/SKILL.md +93 -0
- package/skills/agent-protocol/examples.md +223 -0
- package/skills/agent-response/SKILL.md +69 -0
- package/skills/agentic-loop/SKILL.md +80 -0
- package/skills/agentic-loop/reference.md +378 -0
- package/skills/blog-writing/SKILL.md +98 -0
- package/skills/blog-writing/reference.md +130 -0
- package/skills/brief-spec/SKILL.md +185 -0
- package/skills/command-execution/SKILL.md +64 -0
- package/skills/command-execution/reference.md +83 -0
- package/skills/context-updater/SKILL.md +87 -0
- package/skills/context-updater/examples.md +71 -0
- package/skills/developer-patterns/SKILL.md +50 -0
- package/skills/developer-patterns/reference.md +112 -0
- package/skills/execution/SKILL.md +99 -0
- package/skills/fast-queries/SKILL.md +43 -0
- package/skills/gaia-compact/SKILL.md +74 -0
- package/skills/gaia-patterns/SKILL.md +108 -0
- package/skills/gaia-patterns/reference.md +395 -0
- package/skills/gaia-planner/SKILL.md +37 -0
- package/skills/gaia-planner/reference.md +107 -0
- package/skills/gaia-release/SKILL.md +85 -0
- package/skills/gaia-release/reference.md +92 -0
- package/skills/gaia-self-check/SKILL.md +114 -0
- package/skills/gaia-self-check/reference.md +453 -0
- package/skills/gaia-verify/SKILL.md +77 -0
- package/skills/gaia-verify/reference.md +80 -0
- package/skills/git-conventions/SKILL.md +47 -0
- package/skills/gitops-patterns/SKILL.md +60 -0
- package/skills/gitops-patterns/reference.md +183 -0
- package/skills/gmail-policy/SKILL.md +200 -0
- package/skills/gmail-policy/reference.md +150 -0
- package/skills/gmail-triage/SKILL.md +100 -0
- package/skills/gws-setup/SKILL.md +99 -0
- package/skills/gws-setup/reference.md +73 -0
- package/skills/investigation/SKILL.md +100 -0
- package/skills/memory-curation/SKILL.md +83 -0
- package/skills/memory-search/SKILL.md +88 -0
- package/skills/orchestrator-approval/SKILL.md +160 -0
- package/skills/orchestrator-approval/reference.md +174 -0
- package/skills/pending-approvals/SKILL.md +72 -0
- package/skills/pending-approvals/reference.md +214 -0
- package/skills/readme-writing/SKILL.md +71 -0
- package/skills/readme-writing/reference.md +188 -0
- package/skills/reference.md +135 -0
- package/skills/request-approval/SKILL.md +140 -0
- package/skills/request-approval/examples.md +140 -0
- package/skills/request-approval/reference.md +57 -0
- package/skills/schedule-task/SKILL.md +64 -0
- package/skills/schedule-task/reference.md +233 -0
- package/skills/security-tiers/SKILL.md +141 -0
- package/skills/security-tiers/destructive-commands-reference.md +623 -0
- package/skills/security-tiers/reference.md +39 -0
- package/skills/session-reflection/SKILL.md +69 -0
- package/skills/skill-creation/SKILL.md +92 -0
- package/skills/skill-creation/reference.md +29 -0
- package/skills/terraform-patterns/SKILL.md +89 -0
- package/skills/terraform-patterns/reference.md +93 -0
- package/templates/README.md +69 -0
- package/templates/managed-settings.template.json +43 -0
- package/tools/__init__.py +9 -0
- package/tools/agentic-loop/decide-status.py +210 -0
- package/tools/agentic-loop/parse-metric.py +106 -0
- package/tools/agentic-loop/record-iteration.py +221 -0
- package/tools/context/README.md +132 -0
- package/tools/context/__init__.py +42 -0
- package/tools/context/_paths.py +20 -0
- package/tools/context/context_provider.py +721 -0
- package/tools/context/context_section_reader.py +342 -0
- package/tools/context/deep_merge.py +159 -0
- package/tools/context/pending_updates.py +760 -0
- package/tools/context/surface_router.py +278 -0
- package/tools/fast-queries/README.md +65 -0
- package/tools/fast-queries/__init__.py +30 -0
- package/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
- package/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
- package/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
- package/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
- package/tools/fast-queries/run_triage.sh +59 -0
- package/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
- package/tools/gaia_simulator/__init__.py +33 -0
- package/tools/gaia_simulator/cli.py +354 -0
- package/tools/gaia_simulator/extractor.py +457 -0
- package/tools/gaia_simulator/reporter.py +258 -0
- package/tools/gaia_simulator/routing_simulator.py +334 -0
- package/tools/gaia_simulator/runner.py +539 -0
- package/tools/gaia_simulator/skills_mapper.py +264 -0
- package/tools/memory/README.md +0 -0
- package/tools/memory/__init__.py +20 -0
- package/tools/memory/backfill_fts5.py +107 -0
- package/tools/memory/conflict_detector.py +295 -0
- package/tools/memory/episodic.py +1210 -0
- package/tools/memory/git_invalidator.py +262 -0
- package/tools/memory/paths.py +102 -0
- package/tools/memory/scoring.py +193 -0
- package/tools/memory/search_store.py +375 -0
- package/tools/persist_transcript_analysis.py +85 -0
- package/tools/review/__init__.py +1 -0
- package/tools/review/review_engine.py +157 -0
- package/tools/scan/__init__.py +35 -0
- package/tools/scan/config.py +247 -0
- package/tools/scan/merge.py +212 -0
- package/tools/scan/orchestrator.py +549 -0
- package/tools/scan/registry.py +127 -0
- package/tools/scan/scanners/__init__.py +18 -0
- package/tools/scan/scanners/base.py +137 -0
- package/tools/scan/scanners/environment.py +349 -0
- package/tools/scan/scanners/git.py +570 -0
- package/tools/scan/scanners/infrastructure.py +875 -0
- package/tools/scan/scanners/orchestration.py +600 -0
- package/tools/scan/scanners/stack.py +1085 -0
- package/tools/scan/scanners/tools.py +260 -0
- package/tools/scan/setup.py +686 -0
- package/tools/scan/tests/__init__.py +1 -0
- package/tools/scan/tests/conftest.py +796 -0
- package/tools/scan/tests/test_environment.py +323 -0
- package/tools/scan/tests/test_git.py +419 -0
- package/tools/scan/tests/test_infrastructure.py +382 -0
- package/tools/scan/tests/test_integration.py +920 -0
- package/tools/scan/tests/test_merge.py +269 -0
- package/tools/scan/tests/test_orchestration.py +304 -0
- package/tools/scan/tests/test_stack.py +604 -0
- package/tools/scan/tests/test_tools.py +349 -0
- package/tools/scan/ui.py +624 -0
- package/tools/scan/verify.py +270 -0
- package/tools/scan/walk.py +118 -0
- package/tools/scan/workspace.py +85 -0
- package/tools/validation/README.md +244 -0
- package/tools/validation/__init__.py +17 -0
- package/tools/validation/approval_gate.py +321 -0
- package/tools/validation/validate_skills.py +189 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stage Decomposer - Shell command stage tracking with operator preservation.
|
|
3
|
+
|
|
4
|
+
Wraps ShellCommandParser to add operator tracking between stages.
|
|
5
|
+
ShellCommandParser.parse() discards operators; StageDecomposer preserves them
|
|
6
|
+
so downstream classifiers know how stages are connected (pipe vs AND vs OR).
|
|
7
|
+
|
|
8
|
+
A "stage" is one command with its arguments plus the operator that links it
|
|
9
|
+
to the next stage. The last stage has operator=None.
|
|
10
|
+
|
|
11
|
+
Handles:
|
|
12
|
+
- Pipes (|), semicolons (;), AND (&&), OR (||)
|
|
13
|
+
- Command substitution $(...)
|
|
14
|
+
- Backtick substitution `...`
|
|
15
|
+
- Nested command substitution
|
|
16
|
+
|
|
17
|
+
Dependencies: Python stdlib only.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import re
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from typing import List, Optional
|
|
25
|
+
|
|
26
|
+
from .shell_parser import ShellCommandParser
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Stage:
|
|
31
|
+
"""A single command stage in a pipeline or chain."""
|
|
32
|
+
|
|
33
|
+
# The raw command token (e.g., "grep -r foo")
|
|
34
|
+
command: str
|
|
35
|
+
# Arguments as a list parsed from the command token
|
|
36
|
+
args: List[str] = field(default_factory=list)
|
|
37
|
+
# Operator connecting THIS stage to the NEXT stage (None for the last stage)
|
|
38
|
+
operator: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
return self.command
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def executable(self) -> str:
|
|
45
|
+
"""Return the executable name (first token of the command)."""
|
|
46
|
+
tokens = self.command.strip().split()
|
|
47
|
+
return tokens[0] if tokens else ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class DecomposedCommand:
|
|
52
|
+
"""Result of decomposing a raw command string into stages."""
|
|
53
|
+
|
|
54
|
+
# Original command string (before decomposition)
|
|
55
|
+
raw: str
|
|
56
|
+
# Ordered list of stages
|
|
57
|
+
stages: List[Stage] = field(default_factory=list)
|
|
58
|
+
# Command substitutions extracted from the command ($(...) or `...`)
|
|
59
|
+
substitutions: List[str] = field(default_factory=list)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_compound(self) -> bool:
|
|
63
|
+
"""Return True if the command has more than one stage."""
|
|
64
|
+
return len(self.stages) > 1
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def executables(self) -> List[str]:
|
|
68
|
+
"""Return the list of executables across all stages."""
|
|
69
|
+
return [s.executable for s in self.stages if s.executable]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class StageDecomposer:
|
|
73
|
+
"""
|
|
74
|
+
Decomposes a raw shell command string into ordered Stage objects.
|
|
75
|
+
|
|
76
|
+
Wraps ShellCommandParser for quote-aware splitting, then re-walks the
|
|
77
|
+
original string to recover the operators that ShellCommandParser.parse()
|
|
78
|
+
discards.
|
|
79
|
+
|
|
80
|
+
Zero external dependencies -- Python stdlib only.
|
|
81
|
+
|
|
82
|
+
Usage::
|
|
83
|
+
|
|
84
|
+
decomposer = StageDecomposer()
|
|
85
|
+
result = decomposer.decompose("ls | grep foo && wc -l")
|
|
86
|
+
# result.stages[0].command == "ls"
|
|
87
|
+
# result.stages[0].operator == "|"
|
|
88
|
+
# result.stages[1].command == "grep foo"
|
|
89
|
+
# result.stages[1].operator == "&&"
|
|
90
|
+
# result.stages[2].command == "wc -l"
|
|
91
|
+
# result.stages[2].operator is None
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self) -> None:
|
|
95
|
+
self._parser = ShellCommandParser()
|
|
96
|
+
|
|
97
|
+
# ------------------------------------------------------------------
|
|
98
|
+
# Public API
|
|
99
|
+
# ------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def decompose(self, command: str) -> DecomposedCommand:
|
|
102
|
+
"""
|
|
103
|
+
Decompose a raw command string into Stage objects.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
command: Raw shell command string.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
DecomposedCommand with stages and any extracted substitutions.
|
|
110
|
+
"""
|
|
111
|
+
if not command or not command.strip():
|
|
112
|
+
return DecomposedCommand(raw=command or "", stages=[], substitutions=[])
|
|
113
|
+
|
|
114
|
+
command = command.strip()
|
|
115
|
+
|
|
116
|
+
# Extract command substitutions before splitting so they don't
|
|
117
|
+
# interfere with operator detection.
|
|
118
|
+
substitutions = self._extract_substitutions(command)
|
|
119
|
+
|
|
120
|
+
# Walk the command string once to collect (command_text, operator) pairs.
|
|
121
|
+
pairs = self._split_with_operators(command)
|
|
122
|
+
|
|
123
|
+
stages: List[Stage] = []
|
|
124
|
+
for cmd_text, op in pairs:
|
|
125
|
+
cmd_text = cmd_text.strip()
|
|
126
|
+
if not cmd_text:
|
|
127
|
+
continue
|
|
128
|
+
args = self._tokenize_args(cmd_text)
|
|
129
|
+
stages.append(Stage(command=cmd_text, args=args, operator=op))
|
|
130
|
+
|
|
131
|
+
return DecomposedCommand(raw=command, stages=stages, substitutions=substitutions)
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
# Internal: operator-aware splitting
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
def _split_with_operators(self, command: str) -> List[tuple]:
|
|
138
|
+
"""
|
|
139
|
+
Walk *command* and return a list of (segment, operator_or_None) tuples.
|
|
140
|
+
|
|
141
|
+
The final segment always has operator=None.
|
|
142
|
+
Quotes and escape sequences are respected so operators inside quoted
|
|
143
|
+
strings or $(...) subshells are not treated as segment boundaries.
|
|
144
|
+
"""
|
|
145
|
+
segments: List[tuple] = []
|
|
146
|
+
current: List[str] = []
|
|
147
|
+
i = 0
|
|
148
|
+
n = len(command)
|
|
149
|
+
|
|
150
|
+
in_single_quote = False
|
|
151
|
+
in_double_quote = False
|
|
152
|
+
paren_depth = 0 # tracks $( ... ) nesting
|
|
153
|
+
backtick_depth = 0 # tracks ` ... ` nesting
|
|
154
|
+
|
|
155
|
+
while i < n:
|
|
156
|
+
ch = command[i]
|
|
157
|
+
|
|
158
|
+
# ---- escape sequence (outside single-quotes) ----
|
|
159
|
+
if ch == "\\" and not in_single_quote and i + 1 < n:
|
|
160
|
+
current.append(ch)
|
|
161
|
+
current.append(command[i + 1])
|
|
162
|
+
i += 2
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
# ---- single quote toggle ----
|
|
166
|
+
if ch == "'" and not in_double_quote:
|
|
167
|
+
in_single_quote = not in_single_quote
|
|
168
|
+
current.append(ch)
|
|
169
|
+
i += 1
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# ---- double quote toggle ----
|
|
173
|
+
if ch == '"' and not in_single_quote:
|
|
174
|
+
in_double_quote = not in_double_quote
|
|
175
|
+
current.append(ch)
|
|
176
|
+
i += 1
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# ---- inside quotes: pass through ----
|
|
180
|
+
if in_single_quote or in_double_quote:
|
|
181
|
+
current.append(ch)
|
|
182
|
+
i += 1
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
# ---- $( ... ) command substitution ----
|
|
186
|
+
if ch == "$" and i + 1 < n and command[i + 1] == "(":
|
|
187
|
+
paren_depth += 1
|
|
188
|
+
current.append(ch)
|
|
189
|
+
current.append("(")
|
|
190
|
+
i += 2
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
if ch == "(" and paren_depth > 0:
|
|
194
|
+
paren_depth += 1
|
|
195
|
+
current.append(ch)
|
|
196
|
+
i += 1
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
if ch == ")" and paren_depth > 0:
|
|
200
|
+
paren_depth -= 1
|
|
201
|
+
current.append(ch)
|
|
202
|
+
i += 1
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
# ---- backtick command substitution ----
|
|
206
|
+
if ch == "`":
|
|
207
|
+
if backtick_depth > 0:
|
|
208
|
+
backtick_depth -= 1
|
|
209
|
+
else:
|
|
210
|
+
backtick_depth += 1
|
|
211
|
+
current.append(ch)
|
|
212
|
+
i += 1
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
# ---- if inside a substitution, pass through ----
|
|
216
|
+
if paren_depth > 0 or backtick_depth > 0:
|
|
217
|
+
current.append(ch)
|
|
218
|
+
i += 1
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
# ---- two-character operators: &&, || ----
|
|
222
|
+
if i + 1 < n:
|
|
223
|
+
two = command[i : i + 2]
|
|
224
|
+
if two in ("&&", "||"):
|
|
225
|
+
segments.append(("".join(current), two))
|
|
226
|
+
current = []
|
|
227
|
+
i += 2
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
# ---- single-character operators: |, ;, \n ----
|
|
231
|
+
if ch in ("|", ";", "\n"):
|
|
232
|
+
segments.append(("".join(current), ch))
|
|
233
|
+
current = []
|
|
234
|
+
i += 1
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
current.append(ch)
|
|
238
|
+
i += 1
|
|
239
|
+
|
|
240
|
+
# Final segment has no following operator.
|
|
241
|
+
if current:
|
|
242
|
+
segments.append(("".join(current), None))
|
|
243
|
+
|
|
244
|
+
return segments
|
|
245
|
+
|
|
246
|
+
# ------------------------------------------------------------------
|
|
247
|
+
# Internal: argument tokenisation
|
|
248
|
+
# ------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
def _tokenize_args(self, command_text: str) -> List[str]:
|
|
251
|
+
"""
|
|
252
|
+
Split *command_text* into tokens (command + args).
|
|
253
|
+
|
|
254
|
+
Uses a simple quote-aware tokeniser -- does NOT invoke shlex so we
|
|
255
|
+
stay dependency-free and avoid locale issues.
|
|
256
|
+
|
|
257
|
+
Returns a list where element 0 is the executable and the remainder
|
|
258
|
+
are arguments.
|
|
259
|
+
"""
|
|
260
|
+
tokens: List[str] = []
|
|
261
|
+
current: List[str] = []
|
|
262
|
+
i = 0
|
|
263
|
+
n = len(command_text)
|
|
264
|
+
in_single = False
|
|
265
|
+
in_double = False
|
|
266
|
+
|
|
267
|
+
while i < n:
|
|
268
|
+
ch = command_text[i]
|
|
269
|
+
|
|
270
|
+
if ch == "\\" and not in_single and i + 1 < n:
|
|
271
|
+
current.append(command_text[i + 1])
|
|
272
|
+
i += 2
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
if ch == "'" and not in_double:
|
|
276
|
+
in_single = not in_single
|
|
277
|
+
i += 1
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
if ch == '"' and not in_single:
|
|
281
|
+
in_double = not in_double
|
|
282
|
+
i += 1
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
if not in_single and not in_double and ch in (" ", "\t"):
|
|
286
|
+
if current:
|
|
287
|
+
tokens.append("".join(current))
|
|
288
|
+
current = []
|
|
289
|
+
i += 1
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
current.append(ch)
|
|
293
|
+
i += 1
|
|
294
|
+
|
|
295
|
+
if current:
|
|
296
|
+
tokens.append("".join(current))
|
|
297
|
+
|
|
298
|
+
return tokens
|
|
299
|
+
|
|
300
|
+
# ------------------------------------------------------------------
|
|
301
|
+
# Internal: command substitution extraction
|
|
302
|
+
# ------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
# Match $(...) -- does not handle arbitrarily deep nesting but covers
|
|
305
|
+
# the common single-level case.
|
|
306
|
+
_SUBST_PAREN_RE = re.compile(r"\$\(([^()]*(?:\([^()]*\)[^()]*)*)\)")
|
|
307
|
+
# Match `...` (non-greedy, no nesting)
|
|
308
|
+
_SUBST_BACKTICK_RE = re.compile(r"`([^`]*)`")
|
|
309
|
+
|
|
310
|
+
def _extract_substitutions(self, command: str) -> List[str]:
|
|
311
|
+
"""Return a list of inner strings from $(...) and `...` substitutions."""
|
|
312
|
+
results: List[str] = []
|
|
313
|
+
results.extend(m.group(1).strip() for m in self._SUBST_PAREN_RE.finditer(command))
|
|
314
|
+
results.extend(m.group(1).strip() for m in self._SUBST_BACKTICK_RE.finditer(command))
|
|
315
|
+
return results
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task tool validator.
|
|
3
|
+
|
|
4
|
+
Validates Task tool invocations:
|
|
5
|
+
- Agent existence verification
|
|
6
|
+
- Context provisioning enforcement
|
|
7
|
+
- T3 operation detection for user approval workflow
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import re
|
|
12
|
+
from typing import Dict, Any, List, Optional, Tuple
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from ..security.tiers import SecurityTier
|
|
16
|
+
from ..security.mutative_verbs import (
|
|
17
|
+
detect_mutative_command,
|
|
18
|
+
MutativeResult,
|
|
19
|
+
CLI_FAMILY_LOOKUP,
|
|
20
|
+
COMMAND_ALIASES,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Available agents for Task invocation — both bare and plugin-namespaced forms
|
|
26
|
+
_BASE_AGENTS = [
|
|
27
|
+
"terraform-architect",
|
|
28
|
+
"gitops-operator",
|
|
29
|
+
"cloud-troubleshooter",
|
|
30
|
+
"developer",
|
|
31
|
+
"gaia-operator",
|
|
32
|
+
"gaia-system",
|
|
33
|
+
"gaia-planner",
|
|
34
|
+
"Explore",
|
|
35
|
+
"Plan",
|
|
36
|
+
"claude-code-guide",
|
|
37
|
+
"general-purpose",
|
|
38
|
+
]
|
|
39
|
+
# Support both "cloud-troubleshooter" and "gaia-ops:cloud-troubleshooter"
|
|
40
|
+
AVAILABLE_AGENTS = _BASE_AGENTS + [f"gaia-ops:{a}" for a in _BASE_AGENTS]
|
|
41
|
+
|
|
42
|
+
# Native Claude Code agent types — utility subagents built into the harness,
|
|
43
|
+
# not gaia domain specialists. They don't require context_provider and don't
|
|
44
|
+
# appear in surface routing. They are valid dispatch targets that the
|
|
45
|
+
# orchestrator can legitimately use.
|
|
46
|
+
NATIVE_AGENTS = ["Explore", "Plan", "general-purpose", "claude-code-guide"]
|
|
47
|
+
|
|
48
|
+
# Meta-agents that don't require context_provider.
|
|
49
|
+
# gaia-system was removed: it now receives context injection (has a contract
|
|
50
|
+
# in context-contracts.json) so it can see project state when working on
|
|
51
|
+
# Gaia internals.
|
|
52
|
+
META_AGENTS = list(NATIVE_AGENTS)
|
|
53
|
+
|
|
54
|
+
# T3_KEYWORDS is test-only: used by tests and cross-layer consistency checks
|
|
55
|
+
# to verify that these commands are classified as T3 by the verb detector.
|
|
56
|
+
# NOT used at runtime -- detection is handled entirely by detect_mutative_command().
|
|
57
|
+
T3_KEYWORDS = [
|
|
58
|
+
"git commit",
|
|
59
|
+
"git push",
|
|
60
|
+
"terraform apply",
|
|
61
|
+
"terragrunt apply",
|
|
62
|
+
"terragrunt run-all apply",
|
|
63
|
+
"kubectl apply",
|
|
64
|
+
"kubectl delete",
|
|
65
|
+
"kubectl create",
|
|
66
|
+
"kubectl rollout restart",
|
|
67
|
+
"kubectl scale",
|
|
68
|
+
"kubectl set image",
|
|
69
|
+
"git push origin main",
|
|
70
|
+
"git push origin master",
|
|
71
|
+
"helm install",
|
|
72
|
+
"helm upgrade",
|
|
73
|
+
"flux reconcile",
|
|
74
|
+
"npm publish",
|
|
75
|
+
"docker push",
|
|
76
|
+
"gcloud sql import",
|
|
77
|
+
"gcloud storage cp",
|
|
78
|
+
"gcloud storage rsync",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
_EMBEDDED_COMMAND_QUOTE_CHARS = "\"'`"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _sanitize_candidate_fragment(fragment: str) -> str:
|
|
86
|
+
"""Normalize a prose-embedded command fragment for verb detection.
|
|
87
|
+
|
|
88
|
+
Task prompts often mention commands inside backticks or quotes:
|
|
89
|
+
- Please run `terraform apply` in prod
|
|
90
|
+
- Need to execute "terraform apply" in prod
|
|
91
|
+
|
|
92
|
+
The detector only needs the command skeleton, so strip quote delimiters and
|
|
93
|
+
collapse whitespace before handing the fragment to the dangerous verb
|
|
94
|
+
classifier.
|
|
95
|
+
"""
|
|
96
|
+
if not fragment:
|
|
97
|
+
return ""
|
|
98
|
+
cleaned = fragment.translate(str.maketrans({char: " " for char in _EMBEDDED_COMMAND_QUOTE_CHARS}))
|
|
99
|
+
cleaned = re.sub(r"\s+", " ", cleaned).strip()
|
|
100
|
+
return cleaned.rstrip(".,;:!?")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _extract_command_candidates(text: str) -> List[str]:
|
|
104
|
+
"""Extract command-like lines from free-form text for verb detection.
|
|
105
|
+
|
|
106
|
+
Looks for lines that start with known CLI prefixes or contain command-like
|
|
107
|
+
patterns (e.g., "git push", "terraform apply").
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
text: Free-form text (prompt or description).
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of candidate command strings to scan.
|
|
114
|
+
"""
|
|
115
|
+
if not text:
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
candidates: List[str] = []
|
|
119
|
+
# Derive CLI prefixes from the canonical CLI_FAMILY_LOOKUP and COMMAND_ALIASES
|
|
120
|
+
cli_prefixes = tuple(
|
|
121
|
+
f"{cli} " for cli in sorted(
|
|
122
|
+
set(CLI_FAMILY_LOOKUP.keys()) | set(COMMAND_ALIASES.keys()),
|
|
123
|
+
key=len,
|
|
124
|
+
reverse=True,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
text_lower = text.lower()
|
|
129
|
+
|
|
130
|
+
# Strategy 1: Scan the full text for known CLI command patterns
|
|
131
|
+
for prefix in cli_prefixes:
|
|
132
|
+
idx = 0
|
|
133
|
+
while True:
|
|
134
|
+
pos = text_lower.find(prefix, idx)
|
|
135
|
+
if pos == -1:
|
|
136
|
+
break
|
|
137
|
+
# Only match at word boundaries (start of string or preceded by whitespace/punctuation)
|
|
138
|
+
if pos > 0 and text_lower[pos - 1].isalnum():
|
|
139
|
+
idx = pos + len(prefix)
|
|
140
|
+
continue
|
|
141
|
+
# Extract from the prefix to end of line (or next sentence boundary)
|
|
142
|
+
end = text.find("\n", pos)
|
|
143
|
+
if end == -1:
|
|
144
|
+
end = len(text)
|
|
145
|
+
fragment = text[pos:end].strip()
|
|
146
|
+
# Trim trailing punctuation/quotes that are part of prose
|
|
147
|
+
fragment = fragment.rstrip(".,;:!?\"')")
|
|
148
|
+
fragment = _sanitize_candidate_fragment(fragment)
|
|
149
|
+
if fragment:
|
|
150
|
+
candidates.append(fragment)
|
|
151
|
+
idx = pos + len(prefix)
|
|
152
|
+
|
|
153
|
+
return candidates
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _scan_text_for_t3(text: str) -> Tuple[bool, str, Optional[MutativeResult]]:
|
|
157
|
+
"""Scan free-form text for T3 (dangerous) command intent using the verb detector.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
text: Combined prompt/description text.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
(is_t3, matched_command, danger_result) tuple.
|
|
164
|
+
"""
|
|
165
|
+
candidates = _extract_command_candidates(text)
|
|
166
|
+
|
|
167
|
+
for candidate in candidates:
|
|
168
|
+
result = detect_mutative_command(candidate)
|
|
169
|
+
if result.is_mutative:
|
|
170
|
+
return True, candidate, result
|
|
171
|
+
|
|
172
|
+
return False, "", None
|
|
173
|
+
|
|
174
|
+
__all__ = [
|
|
175
|
+
"TaskValidator",
|
|
176
|
+
"TaskValidationResult",
|
|
177
|
+
"validate_task_invocation",
|
|
178
|
+
"AVAILABLE_AGENTS",
|
|
179
|
+
"META_AGENTS",
|
|
180
|
+
"NATIVE_AGENTS",
|
|
181
|
+
"T3_KEYWORDS",
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class TaskValidationResult:
|
|
187
|
+
"""Result of Task tool validation."""
|
|
188
|
+
allowed: bool
|
|
189
|
+
tier: SecurityTier
|
|
190
|
+
reason: str
|
|
191
|
+
agent_name: str = ""
|
|
192
|
+
has_context: bool = False
|
|
193
|
+
is_t3_operation: bool = False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class TaskValidator:
|
|
197
|
+
"""Validator for Task tool invocations."""
|
|
198
|
+
|
|
199
|
+
def __init__(self, available_agents: Optional[List[str]] = None):
|
|
200
|
+
"""
|
|
201
|
+
Initialize validator.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
available_agents: Override available agents list
|
|
205
|
+
"""
|
|
206
|
+
self.available_agents = available_agents or AVAILABLE_AGENTS
|
|
207
|
+
|
|
208
|
+
def validate(self, parameters: Dict[str, Any]) -> TaskValidationResult:
|
|
209
|
+
"""
|
|
210
|
+
Validate Task tool invocation.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
parameters: Task tool parameters
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
TaskValidationResult with validation details
|
|
217
|
+
"""
|
|
218
|
+
agent_name = parameters.get("subagent_type", "unknown")
|
|
219
|
+
prompt = parameters.get("prompt", "")
|
|
220
|
+
description = parameters.get("description", "")
|
|
221
|
+
|
|
222
|
+
# additionalContext means prompt is never mutated, so T3 detection
|
|
223
|
+
# runs directly against the original user prompt.
|
|
224
|
+
user_task_for_t3_check = prompt
|
|
225
|
+
|
|
226
|
+
logger.info(f"Task tool validation for agent: {agent_name}")
|
|
227
|
+
|
|
228
|
+
# Check agent exists
|
|
229
|
+
if agent_name not in self.available_agents:
|
|
230
|
+
error_msg = f"Unknown agent: '{agent_name}'\n\n"
|
|
231
|
+
error_msg += f"Available agents:\n"
|
|
232
|
+
for agent in sorted(self.available_agents):
|
|
233
|
+
error_msg += f" - {agent}\n"
|
|
234
|
+
error_msg += "\nRefer to the Surface Routing Recommendation for agent selection.\n"
|
|
235
|
+
error_msg += f"\nCorrect usage: Task(subagent_type=\"<agent-name>\", ...)"
|
|
236
|
+
|
|
237
|
+
return TaskValidationResult(
|
|
238
|
+
allowed=False,
|
|
239
|
+
tier=SecurityTier.T3_BLOCKED,
|
|
240
|
+
reason=error_msg,
|
|
241
|
+
agent_name=agent_name,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Context is injected via additionalContext by the adapter, not by
|
|
245
|
+
# mutating the prompt. The validator cannot check additionalContext
|
|
246
|
+
# (it only sees parameters), so we determine context status by agent type.
|
|
247
|
+
# Meta-agents never receive context by design.
|
|
248
|
+
has_context = agent_name not in META_AGENTS
|
|
249
|
+
|
|
250
|
+
# Check for T3 operations (use original user task to avoid false positives from context)
|
|
251
|
+
is_t3 = self._is_t3_operation(user_task_for_t3_check, description)
|
|
252
|
+
|
|
253
|
+
logger.info(
|
|
254
|
+
f"Task invocation validated: {agent_name} "
|
|
255
|
+
f"(T3={is_t3}, context={has_context})"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
tier = SecurityTier.T3_BLOCKED if is_t3 else SecurityTier.T0_READ_ONLY
|
|
259
|
+
reason = (
|
|
260
|
+
f"Task invocation allowed for {agent_name}; T3 execution still requires "
|
|
261
|
+
f"nonce-based approval at Bash time"
|
|
262
|
+
if is_t3
|
|
263
|
+
else f"Task invocation allowed for {agent_name}"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return TaskValidationResult(
|
|
267
|
+
allowed=True,
|
|
268
|
+
tier=tier,
|
|
269
|
+
reason=reason,
|
|
270
|
+
agent_name=agent_name,
|
|
271
|
+
has_context=has_context,
|
|
272
|
+
is_t3_operation=is_t3,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def _is_t3_operation(self, prompt: str, description: str) -> bool:
|
|
276
|
+
"""Check if this is a T3 (destructive) operation using the verb detector."""
|
|
277
|
+
combined = f"{description} {prompt}"
|
|
278
|
+
is_t3, _, _ = _scan_text_for_t3(combined)
|
|
279
|
+
return is_t3
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def validate_task_invocation(parameters: Dict[str, Any]) -> TaskValidationResult:
|
|
284
|
+
"""
|
|
285
|
+
Validate Task tool invocation (convenience function).
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
parameters: Task tool parameters
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
TaskValidationResult
|
|
292
|
+
"""
|
|
293
|
+
validator = TaskValidator()
|
|
294
|
+
return validator.validate(parameters)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation Module: Commit message validation for bash_validator
|
|
3
|
+
|
|
4
|
+
This module provides commit message validation that is exclusively used
|
|
5
|
+
by hooks/modules/tools/bash_validator.py to enforce git commit standards.
|
|
6
|
+
|
|
7
|
+
Note: This is an internal module. Do not import directly in agent code.
|
|
8
|
+
Commit validation is automatically enforced via bash_validator.py.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .commit_validator import (
|
|
12
|
+
CommitMessageValidator,
|
|
13
|
+
ValidationResult,
|
|
14
|
+
validate_commit_message,
|
|
15
|
+
safe_validate_before_commit,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"CommitMessageValidator",
|
|
20
|
+
"ValidationResult",
|
|
21
|
+
"validate_commit_message",
|
|
22
|
+
"safe_validate_before_commit",
|
|
23
|
+
]
|