@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,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Scanner Protocol
|
|
3
|
+
|
|
4
|
+
Defines the abstract base class that all scanner modules must implement.
|
|
5
|
+
Each scanner is a pure function: it reads filesystem state and returns
|
|
6
|
+
structured section data without side effects.
|
|
7
|
+
|
|
8
|
+
Contract: contracts/scanner-interface.md
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class ScanResult:
|
|
19
|
+
"""Immutable result from a single scanner execution.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
scanner: Scanner name that produced this result.
|
|
23
|
+
sections: Mapping of section names to section data.
|
|
24
|
+
warnings: Non-fatal warnings encountered during scanning.
|
|
25
|
+
duration_ms: Execution time in milliseconds.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
scanner: str = ""
|
|
29
|
+
sections: Dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
warnings: List[str] = field(default_factory=list)
|
|
31
|
+
duration_ms: float = 0.0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BaseScanner(ABC):
|
|
35
|
+
"""Abstract base class for all scanner modules.
|
|
36
|
+
|
|
37
|
+
Every scanner MUST implement:
|
|
38
|
+
- SCANNER_NAME: Unique scanner identifier (e.g., "stack", "git")
|
|
39
|
+
- SCANNER_VERSION: Semver string for schema tracking (e.g., "1.0.0")
|
|
40
|
+
- OWNED_SECTIONS: List of section names this scanner owns in project-context.json
|
|
41
|
+
- scan(root): Pure function that scans the project and returns a dict of sections
|
|
42
|
+
|
|
43
|
+
Pure Function Contract:
|
|
44
|
+
- No file writes
|
|
45
|
+
- No state modification
|
|
46
|
+
- No network calls
|
|
47
|
+
- No command execution that modifies state
|
|
48
|
+
- Only reads: filesystem reads, command -v, <tool> --version
|
|
49
|
+
- MUST NOT raise exceptions to caller
|
|
50
|
+
- MUST catch all internal errors and return {} or partial results
|
|
51
|
+
- Individual file read failures MUST NOT abort the scanner
|
|
52
|
+
|
|
53
|
+
Performance:
|
|
54
|
+
- SHOULD complete in under 3 seconds for typical projects
|
|
55
|
+
- MUST respect 2-second timeout for --version calls
|
|
56
|
+
|
|
57
|
+
Optional workspace_info attribute:
|
|
58
|
+
- Set by the orchestrator before scan() when workspace type has been
|
|
59
|
+
pre-detected. Scanners can check self.workspace_info for multi-repo
|
|
60
|
+
awareness. Defaults to None (single-repo assumed).
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
self.workspace_info = None # Set by orchestrator if available
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def SCANNER_NAME(self) -> str:
|
|
69
|
+
"""Unique scanner identifier (e.g., 'stack', 'git')."""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def SCANNER_VERSION(self) -> str:
|
|
75
|
+
"""Scanner version for schema tracking (semver)."""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def OWNED_SECTIONS(self) -> List[str]:
|
|
81
|
+
"""Section names this scanner owns in project-context.json."""
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def scan(self, root: Path) -> Dict[str, Any]:
|
|
86
|
+
"""Scan the project at root and return detected sections.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
root: Absolute path to the project root directory.
|
|
90
|
+
MUST be validated by caller (exists, is directory).
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dict mapping section names to section data.
|
|
94
|
+
Empty dict on complete failure.
|
|
95
|
+
Partial dict when some detection succeeds and some fails.
|
|
96
|
+
|
|
97
|
+
Side Effects:
|
|
98
|
+
NONE. This function is pure.
|
|
99
|
+
"""
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def source_tag(self) -> str:
|
|
104
|
+
"""Return the _source metadata tag for sections produced by this scanner."""
|
|
105
|
+
return f"scanner:{self.SCANNER_NAME}"
|
|
106
|
+
|
|
107
|
+
def make_result(
|
|
108
|
+
self,
|
|
109
|
+
sections: Dict[str, Any],
|
|
110
|
+
warnings: Optional[List[str]] = None,
|
|
111
|
+
duration_ms: float = 0.0,
|
|
112
|
+
) -> ScanResult:
|
|
113
|
+
"""Create a ScanResult with this scanner's metadata.
|
|
114
|
+
|
|
115
|
+
Automatically injects _source tag into each section.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
sections: Section name to section data mapping.
|
|
119
|
+
warnings: Optional list of non-fatal warnings.
|
|
120
|
+
duration_ms: Execution time in milliseconds.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Frozen ScanResult instance.
|
|
124
|
+
"""
|
|
125
|
+
tagged_sections = {}
|
|
126
|
+
for name, data in sections.items():
|
|
127
|
+
if isinstance(data, dict):
|
|
128
|
+
tagged_sections[name] = {"_source": self.source_tag, **data}
|
|
129
|
+
else:
|
|
130
|
+
tagged_sections[name] = data
|
|
131
|
+
|
|
132
|
+
return ScanResult(
|
|
133
|
+
scanner=self.SCANNER_NAME,
|
|
134
|
+
sections=tagged_sections,
|
|
135
|
+
warnings=warnings or [],
|
|
136
|
+
duration_ms=duration_ms,
|
|
137
|
+
)
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Environment Scanner
|
|
3
|
+
|
|
4
|
+
Detects OS information (platform, architecture, WSL), installed language
|
|
5
|
+
runtimes, and .env file patterns. Outputs environment.os, environment.runtimes,
|
|
6
|
+
and environment.env_files subsections.
|
|
7
|
+
|
|
8
|
+
Pure Function Contract:
|
|
9
|
+
- No file writes
|
|
10
|
+
- No state modification
|
|
11
|
+
- No network calls
|
|
12
|
+
- NEVER reads .env file contents (FR-043) -- only Path.exists() and Path.name
|
|
13
|
+
- Only reads: /proc/version (for WSL detection), runtime --version output
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import platform
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
24
|
+
|
|
25
|
+
from tools.scan.scanners.base import BaseScanner, ScanResult
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# Runtimes to detect: (binary_name, version_flag)
|
|
30
|
+
_RUNTIME_DEFINITIONS: List[Tuple[str, str]] = [
|
|
31
|
+
("python3", "--version"),
|
|
32
|
+
("node", "--version"),
|
|
33
|
+
("go", "version"),
|
|
34
|
+
("cargo", "--version"),
|
|
35
|
+
("java", "--version"),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# Env file names to check for (presence ONLY -- never read contents)
|
|
39
|
+
_ENV_FILE_NAMES: List[str] = [
|
|
40
|
+
".env",
|
|
41
|
+
".env.local",
|
|
42
|
+
".env.example",
|
|
43
|
+
".env.development",
|
|
44
|
+
".env.production",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Architecture mapping from platform.machine() to canonical names
|
|
48
|
+
_ARCH_MAP: Dict[str, str] = {
|
|
49
|
+
"x86_64": "x64",
|
|
50
|
+
"AMD64": "x64",
|
|
51
|
+
"aarch64": "arm64",
|
|
52
|
+
"arm64": "arm64",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Platform mapping from sys.platform to canonical names
|
|
56
|
+
_PLATFORM_MAP: Dict[str, str] = {
|
|
57
|
+
"linux": "linux",
|
|
58
|
+
"darwin": "darwin",
|
|
59
|
+
"win32": "win32",
|
|
60
|
+
"cygwin": "win32",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class EnvironmentScanner(BaseScanner):
|
|
65
|
+
"""Scanner for OS, runtime, and env file detection.
|
|
66
|
+
|
|
67
|
+
Detects:
|
|
68
|
+
- OS: platform (linux/darwin/win32), architecture (x64/arm64), WSL
|
|
69
|
+
- Runtimes: python3, node, go, cargo, java versions via --version
|
|
70
|
+
- Env files: .env, .env.local, .env.example, etc. by Path.exists() ONLY
|
|
71
|
+
|
|
72
|
+
CRITICAL: NEVER calls open() or read() on any .env file.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def SCANNER_NAME(self) -> str:
|
|
77
|
+
return "environment"
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def SCANNER_VERSION(self) -> str:
|
|
81
|
+
return "1.0.0"
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def OWNED_SECTIONS(self) -> List[str]:
|
|
85
|
+
return ["environment.runtimes", "environment.os", "environment.env_files"]
|
|
86
|
+
|
|
87
|
+
def scan(self, root: Path) -> ScanResult:
|
|
88
|
+
"""Scan for OS info, runtimes, and env files.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
root: Absolute path to the project root directory.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
ScanResult with 'environment' section containing os, runtimes,
|
|
95
|
+
and env_files subsections.
|
|
96
|
+
"""
|
|
97
|
+
start = time.monotonic()
|
|
98
|
+
warnings: List[str] = []
|
|
99
|
+
|
|
100
|
+
os_info = self._detect_os(warnings)
|
|
101
|
+
runtimes = self._detect_runtimes(warnings)
|
|
102
|
+
env_files = self._detect_env_files(root, warnings)
|
|
103
|
+
|
|
104
|
+
elapsed_ms = (time.monotonic() - start) * 1000
|
|
105
|
+
|
|
106
|
+
sections: Dict[str, Any] = {
|
|
107
|
+
"environment": {
|
|
108
|
+
"os": os_info,
|
|
109
|
+
"runtimes": runtimes,
|
|
110
|
+
"env_files": env_files,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return self.make_result(
|
|
115
|
+
sections=sections,
|
|
116
|
+
warnings=warnings,
|
|
117
|
+
duration_ms=elapsed_ms,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _detect_os(self, warnings: List[str]) -> Dict[str, Any]:
|
|
121
|
+
"""Detect OS platform, architecture, and WSL status.
|
|
122
|
+
|
|
123
|
+
Platform and architecture are always available via stdlib.
|
|
124
|
+
WSL detection reads /proc/version if on Linux.
|
|
125
|
+
"""
|
|
126
|
+
raw_platform = sys.platform
|
|
127
|
+
canonical_platform = _PLATFORM_MAP.get(raw_platform, raw_platform)
|
|
128
|
+
|
|
129
|
+
raw_arch = platform.machine()
|
|
130
|
+
canonical_arch = _ARCH_MAP.get(raw_arch, raw_arch)
|
|
131
|
+
|
|
132
|
+
wsl = False
|
|
133
|
+
wsl_version: Optional[str] = None
|
|
134
|
+
|
|
135
|
+
if canonical_platform == "linux":
|
|
136
|
+
wsl, wsl_version = self._detect_wsl(warnings)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
"platform": canonical_platform,
|
|
140
|
+
"architecture": canonical_arch,
|
|
141
|
+
"wsl": wsl,
|
|
142
|
+
"wsl_version": wsl_version,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
def _detect_wsl(self, warnings: List[str]) -> Tuple[bool, Optional[str]]:
|
|
146
|
+
"""Detect WSL by reading /proc/version for 'microsoft' or 'WSL'.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Tuple of (is_wsl, wsl_version). wsl_version is '1' or '2' when
|
|
150
|
+
detectable, None otherwise.
|
|
151
|
+
"""
|
|
152
|
+
proc_version_path = Path("/proc/version")
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
if not proc_version_path.exists():
|
|
156
|
+
return False, None
|
|
157
|
+
|
|
158
|
+
content = proc_version_path.read_text()
|
|
159
|
+
content_lower = content.lower()
|
|
160
|
+
|
|
161
|
+
if "microsoft" not in content_lower and "wsl" not in content_lower:
|
|
162
|
+
return False, None
|
|
163
|
+
|
|
164
|
+
# WSL2 uses a real Linux kernel and identifies as "microsoft-standard-WSL2"
|
|
165
|
+
# WSL1 uses "Microsoft" in the version string but not "WSL2"
|
|
166
|
+
wsl_version: Optional[str] = None
|
|
167
|
+
if "wsl2" in content_lower:
|
|
168
|
+
wsl_version = "2"
|
|
169
|
+
elif "microsoft" in content_lower:
|
|
170
|
+
# Could be WSL1 or WSL2 without explicit marker
|
|
171
|
+
# WSL2 kernels typically contain "microsoft-standard-WSL2"
|
|
172
|
+
# WSL1 kernels contain "Microsoft" but not "WSL2"
|
|
173
|
+
wsl_version = "1"
|
|
174
|
+
|
|
175
|
+
return True, wsl_version
|
|
176
|
+
|
|
177
|
+
except OSError as exc:
|
|
178
|
+
warnings.append(f"WSL detection failed: {exc}")
|
|
179
|
+
return False, None
|
|
180
|
+
|
|
181
|
+
def _detect_runtimes(self, warnings: List[str]) -> List[Dict[str, str]]:
|
|
182
|
+
"""Detect installed language runtimes via --version commands.
|
|
183
|
+
|
|
184
|
+
Uses shutil.which() to find binaries, then subprocess with 2s timeout
|
|
185
|
+
to get version strings.
|
|
186
|
+
"""
|
|
187
|
+
runtimes: List[Dict[str, str]] = []
|
|
188
|
+
|
|
189
|
+
for binary_name, version_flag in _RUNTIME_DEFINITIONS:
|
|
190
|
+
try:
|
|
191
|
+
binary_path = shutil.which(binary_name)
|
|
192
|
+
if binary_path is None:
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
version = self._get_version(binary_name, version_flag, warnings)
|
|
196
|
+
if version is not None:
|
|
197
|
+
runtimes.append({
|
|
198
|
+
"name": binary_name,
|
|
199
|
+
"version": version,
|
|
200
|
+
"path": binary_path,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
except Exception as exc:
|
|
204
|
+
warnings.append(f"Runtime detection failed for {binary_name}: {exc}")
|
|
205
|
+
|
|
206
|
+
return runtimes
|
|
207
|
+
|
|
208
|
+
def _get_version(
|
|
209
|
+
self, binary: str, flag: str, warnings: List[str]
|
|
210
|
+
) -> Optional[str]:
|
|
211
|
+
"""Run '<binary> <flag>' and extract version string.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
binary: Binary name to execute.
|
|
215
|
+
flag: Version flag (e.g., '--version', 'version').
|
|
216
|
+
warnings: List to append non-fatal warnings to.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Version string or None on failure.
|
|
220
|
+
"""
|
|
221
|
+
try:
|
|
222
|
+
result = subprocess.run(
|
|
223
|
+
[binary, flag],
|
|
224
|
+
capture_output=True,
|
|
225
|
+
text=True,
|
|
226
|
+
timeout=2,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Some tools output version to stderr (e.g., java --version)
|
|
230
|
+
output = result.stdout.strip() or result.stderr.strip()
|
|
231
|
+
|
|
232
|
+
if not output:
|
|
233
|
+
return "unknown"
|
|
234
|
+
|
|
235
|
+
# Extract the first line and clean it
|
|
236
|
+
first_line = output.splitlines()[0].strip()
|
|
237
|
+
|
|
238
|
+
# Try to extract version number from common patterns
|
|
239
|
+
version = self._parse_version(first_line)
|
|
240
|
+
return version if version else first_line
|
|
241
|
+
|
|
242
|
+
except subprocess.TimeoutExpired:
|
|
243
|
+
warnings.append(f"{binary} {flag} timed out after 2s")
|
|
244
|
+
return "unknown"
|
|
245
|
+
except FileNotFoundError:
|
|
246
|
+
return None
|
|
247
|
+
except OSError as exc:
|
|
248
|
+
warnings.append(f"{binary} {flag} failed: {exc}")
|
|
249
|
+
return "unknown"
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _parse_version(line: str) -> Optional[str]:
|
|
253
|
+
"""Extract a version number from a version string line.
|
|
254
|
+
|
|
255
|
+
Handles common formats:
|
|
256
|
+
- 'Python 3.11.5' -> '3.11.5'
|
|
257
|
+
- 'v20.10.0' -> '20.10.0'
|
|
258
|
+
- 'go version go1.21.0 linux/amd64' -> '1.21.0'
|
|
259
|
+
- 'cargo 1.72.0 (103a7ff2e 2023-08-15)' -> '1.72.0'
|
|
260
|
+
- 'openjdk 21.0.1 2023-10-17' -> '21.0.1'
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Cleaned version string or None if no version pattern found.
|
|
264
|
+
"""
|
|
265
|
+
import re
|
|
266
|
+
|
|
267
|
+
# Match 'go1.21.0' pattern (go-specific)
|
|
268
|
+
go_match = re.search(r"go(\d+\.\d+(?:\.\d+)?)", line)
|
|
269
|
+
if go_match:
|
|
270
|
+
return go_match.group(1)
|
|
271
|
+
|
|
272
|
+
# Match standard version patterns: v1.2.3, 1.2.3, 1.2
|
|
273
|
+
version_match = re.search(r"v?(\d+\.\d+(?:\.\d+)?(?:[._-]\w+)*)", line)
|
|
274
|
+
if version_match:
|
|
275
|
+
return version_match.group(1)
|
|
276
|
+
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
def _detect_env_files(
|
|
280
|
+
self, root: Path, warnings: List[str]
|
|
281
|
+
) -> List[Dict[str, str]]:
|
|
282
|
+
"""Detect .env file patterns by existence check ONLY.
|
|
283
|
+
|
|
284
|
+
CRITICAL: NEVER calls open() or read() on any .env file.
|
|
285
|
+
Only uses Path.exists() and Path.name.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
root: Project root directory.
|
|
289
|
+
warnings: List to append non-fatal warnings to.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of dicts with 'name' and 'path' for each found env file.
|
|
293
|
+
"""
|
|
294
|
+
env_files: List[Dict[str, str]] = []
|
|
295
|
+
|
|
296
|
+
for env_name in _ENV_FILE_NAMES:
|
|
297
|
+
try:
|
|
298
|
+
env_path = root / env_name
|
|
299
|
+
if env_path.exists():
|
|
300
|
+
env_files.append({
|
|
301
|
+
"name": env_path.name,
|
|
302
|
+
"path": str(env_path.relative_to(root)),
|
|
303
|
+
})
|
|
304
|
+
except (OSError, ValueError) as exc:
|
|
305
|
+
warnings.append(f"Env file check failed for {env_name}: {exc}")
|
|
306
|
+
|
|
307
|
+
return env_files
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def scan(root: Path) -> Dict[str, Any]:
|
|
311
|
+
"""Module-level convenience function for environment scanning.
|
|
312
|
+
|
|
313
|
+
This function provides backward compatibility with the module-level scan()
|
|
314
|
+
pattern used by the scanner registry auto-discovery.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
root: Absolute path to the project root directory.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Dict with 'environment' section containing os, runtimes, and env_files.
|
|
321
|
+
"""
|
|
322
|
+
scanner = EnvironmentScanner()
|
|
323
|
+
result = scanner.scan(root)
|
|
324
|
+
return result.sections
|