@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,651 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gaia doctor -- Health check for Gaia-Ops installation.
|
|
3
|
+
|
|
4
|
+
Mirrors the checks in gaia-doctor.js:
|
|
5
|
+
1. gaia-version - package.json readable
|
|
6
|
+
2. claude-code - CLI installed
|
|
7
|
+
3. python - Python 3.9+ available
|
|
8
|
+
4. plugin-mode - ops vs security, registry valid
|
|
9
|
+
5. symlinks - .claude/ symlinks resolve
|
|
10
|
+
6. identity - orchestrator agent configured
|
|
11
|
+
7. settings - hooks registered, permissions, deny rules
|
|
12
|
+
8. hook-files - all hook scripts present
|
|
13
|
+
9. project-context - project-context.json valid
|
|
14
|
+
10. project-dirs - paths declared in context exist
|
|
15
|
+
11. memory-dirs - episodic memory dirs present
|
|
16
|
+
|
|
17
|
+
Severity: pass / info / warning / error
|
|
18
|
+
Exit codes: 0=healthy, 1=warnings, 2=errors
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from functools import partial
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# Helpers
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
def _result(name: str, severity: str, detail: str, fix: str = None) -> dict:
|
|
35
|
+
"""Create a check result dict."""
|
|
36
|
+
ok = severity in ("pass", "info")
|
|
37
|
+
r = {"name": name, "severity": severity, "ok": ok, "detail": detail}
|
|
38
|
+
if fix:
|
|
39
|
+
r["fix"] = fix
|
|
40
|
+
return r
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _find_project_root() -> Path:
|
|
44
|
+
"""Walk up from cwd until .claude/ is found."""
|
|
45
|
+
init_cwd = os.environ.get("INIT_CWD")
|
|
46
|
+
if init_cwd and (Path(init_cwd) / ".claude").is_dir():
|
|
47
|
+
return Path(init_cwd)
|
|
48
|
+
|
|
49
|
+
current = Path.cwd()
|
|
50
|
+
root = Path(current.anchor)
|
|
51
|
+
while current != root:
|
|
52
|
+
if (current / ".claude").is_dir():
|
|
53
|
+
return current
|
|
54
|
+
current = current.parent
|
|
55
|
+
|
|
56
|
+
return Path(init_cwd) if init_cwd else Path.cwd()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _read_json(path: Path):
|
|
60
|
+
"""Read and parse a JSON file, returning None on any error."""
|
|
61
|
+
try:
|
|
62
|
+
return json.loads(path.read_text())
|
|
63
|
+
except Exception:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _package_root() -> Path:
|
|
68
|
+
"""Return the gaia-ops package root (parent of bin/)."""
|
|
69
|
+
return Path(__file__).resolve().parent.parent.parent
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ============================================================================
|
|
73
|
+
# Health Checks
|
|
74
|
+
# ============================================================================
|
|
75
|
+
|
|
76
|
+
def check_gaia_version() -> dict:
|
|
77
|
+
"""Check that package.json is readable and has a version."""
|
|
78
|
+
pkg_path = _package_root() / "package.json"
|
|
79
|
+
data = _read_json(pkg_path)
|
|
80
|
+
if data and "version" in data:
|
|
81
|
+
return _result("Gaia-Ops", "pass", f"v{data['version']}")
|
|
82
|
+
return _result("Gaia-Ops", "error", "Version unknown", "Reinstall @jaguilar87/gaia")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def check_claude_code() -> dict:
|
|
86
|
+
"""Check if Claude Code CLI is installed."""
|
|
87
|
+
for cmd in ("claude", "claude-code"):
|
|
88
|
+
if shutil.which(cmd):
|
|
89
|
+
try:
|
|
90
|
+
proc = subprocess.run(
|
|
91
|
+
[cmd, "--version"],
|
|
92
|
+
capture_output=True,
|
|
93
|
+
text=True,
|
|
94
|
+
timeout=10,
|
|
95
|
+
)
|
|
96
|
+
version_line = proc.stdout.strip().split("\n")[0] if proc.stdout else cmd
|
|
97
|
+
return _result("Claude Code", "pass", version_line)
|
|
98
|
+
except Exception:
|
|
99
|
+
return _result("Claude Code", "pass", cmd)
|
|
100
|
+
|
|
101
|
+
return _result("Claude Code", "info", "Not installed", "npm install -g @anthropic-ai/claude-code")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def check_python() -> dict:
|
|
105
|
+
"""Check Python version >= 3.9."""
|
|
106
|
+
version = sys.version.split()[0]
|
|
107
|
+
parts = version.split(".")
|
|
108
|
+
try:
|
|
109
|
+
major, minor = int(parts[0]), int(parts[1])
|
|
110
|
+
except (IndexError, ValueError):
|
|
111
|
+
return _result("Python", "error", f"Could not parse: {version}", "Install Python 3.9+")
|
|
112
|
+
|
|
113
|
+
if major < 3 or (major == 3 and minor < 9):
|
|
114
|
+
return _result("Python", "error", f"Python {version} (need >=3.9)", "Upgrade Python to 3.9+")
|
|
115
|
+
|
|
116
|
+
return _result("Python", "pass", f"Python {version}")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def check_plugin_mode(project_root: Path) -> dict:
|
|
120
|
+
"""Check plugin mode from plugin-registry.json."""
|
|
121
|
+
registry_path = project_root / ".claude" / "plugin-registry.json"
|
|
122
|
+
if not registry_path.is_file():
|
|
123
|
+
return _result("Plugin mode", "warning", "No plugin-registry.json", "Run gaia-scan or restart Claude Code")
|
|
124
|
+
|
|
125
|
+
data = _read_json(registry_path)
|
|
126
|
+
if not data:
|
|
127
|
+
return _result("Plugin mode", "warning", "Invalid plugin-registry.json", "Delete and restart Claude Code")
|
|
128
|
+
|
|
129
|
+
installed = [p.get("name", "") for p in (data.get("installed") or [])]
|
|
130
|
+
source = data.get("source", "unknown")
|
|
131
|
+
|
|
132
|
+
if "gaia-ops" in installed:
|
|
133
|
+
return _result("Plugin mode", "pass", f"ops (source: {source})")
|
|
134
|
+
if "gaia-security" in installed:
|
|
135
|
+
return _result("Plugin mode", "pass", f"security (source: {source})")
|
|
136
|
+
|
|
137
|
+
return _result("Plugin mode", "warning", f"Unknown plugin: {', '.join(installed)}", "Verify installation")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def check_symlinks(project_root: Path) -> dict:
|
|
141
|
+
"""Check .claude/ symlinks resolve to package content."""
|
|
142
|
+
names = ["agents", "tools", "hooks", "commands", "templates", "config", "skills", "CHANGELOG.md"]
|
|
143
|
+
critical = {"agents", "hooks", "skills"}
|
|
144
|
+
valid = 0
|
|
145
|
+
has_critical_missing = False
|
|
146
|
+
|
|
147
|
+
for name in names:
|
|
148
|
+
link_path = project_root / ".claude" / name
|
|
149
|
+
if link_path.exists():
|
|
150
|
+
try:
|
|
151
|
+
link_path.resolve(strict=True)
|
|
152
|
+
valid += 1
|
|
153
|
+
except OSError:
|
|
154
|
+
if name in critical:
|
|
155
|
+
has_critical_missing = True
|
|
156
|
+
else:
|
|
157
|
+
if name in critical:
|
|
158
|
+
has_critical_missing = True
|
|
159
|
+
|
|
160
|
+
total = len(names)
|
|
161
|
+
if valid == total:
|
|
162
|
+
return _result("Symlinks", "pass", f"{valid}/{total} valid")
|
|
163
|
+
|
|
164
|
+
severity = "error" if has_critical_missing else "warning"
|
|
165
|
+
return _result("Symlinks", severity, f"{valid}/{total} valid", "Run gaia-scan to recreate symlinks")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def check_identity(project_root: Path) -> dict:
|
|
169
|
+
"""Check orchestrator agent is configured."""
|
|
170
|
+
issues = []
|
|
171
|
+
infos = []
|
|
172
|
+
|
|
173
|
+
agent_path = project_root / ".claude" / "agents" / "gaia-orchestrator.md"
|
|
174
|
+
if not agent_path.is_file():
|
|
175
|
+
issues.append("gaia-orchestrator.md not found")
|
|
176
|
+
|
|
177
|
+
local_settings = project_root / ".claude" / "settings.local.json"
|
|
178
|
+
if local_settings.is_file():
|
|
179
|
+
data = _read_json(local_settings)
|
|
180
|
+
if data:
|
|
181
|
+
agent = data.get("agent")
|
|
182
|
+
if agent == "gaia-orchestrator":
|
|
183
|
+
pass # correct
|
|
184
|
+
elif agent:
|
|
185
|
+
issues.append(f'Agent set to "{agent}" (expected "gaia-orchestrator")')
|
|
186
|
+
else:
|
|
187
|
+
issues.append("No agent field in settings.local.json")
|
|
188
|
+
else:
|
|
189
|
+
issues.append("settings.local.json missing")
|
|
190
|
+
|
|
191
|
+
claude_md = project_root / "CLAUDE.md"
|
|
192
|
+
if claude_md.is_file():
|
|
193
|
+
infos.append("Legacy CLAUDE.md present (no longer used)")
|
|
194
|
+
|
|
195
|
+
if issues:
|
|
196
|
+
return _result("Identity", "error", "; ".join(issues), "Run gaia-scan or gaia update")
|
|
197
|
+
if infos:
|
|
198
|
+
return _result("Identity", "info", f"Orchestrator configured -- {'; '.join(infos)}")
|
|
199
|
+
return _result("Identity", "pass", "Orchestrator agent configured")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def check_settings(project_root: Path) -> dict:
|
|
203
|
+
"""Check settings.local.json for hooks, permissions, deny rules."""
|
|
204
|
+
local_path = project_root / ".claude" / "settings.local.json"
|
|
205
|
+
if not local_path.is_file():
|
|
206
|
+
return _result("Settings", "error", "settings.local.json missing", "Run gaia-scan or gaia update")
|
|
207
|
+
|
|
208
|
+
data = _read_json(local_path)
|
|
209
|
+
if not data:
|
|
210
|
+
return _result("Settings", "error", "Invalid JSON in settings.local.json", "Delete and run gaia-scan")
|
|
211
|
+
|
|
212
|
+
issues = []
|
|
213
|
+
infos = []
|
|
214
|
+
|
|
215
|
+
hooks_config = data.get("hooks")
|
|
216
|
+
if not hooks_config:
|
|
217
|
+
issues.append("No hooks configured")
|
|
218
|
+
else:
|
|
219
|
+
required = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SessionStart"]
|
|
220
|
+
missing = [h for h in required if h not in hooks_config]
|
|
221
|
+
if missing:
|
|
222
|
+
issues.append(f"Missing hooks: {', '.join(missing)}")
|
|
223
|
+
|
|
224
|
+
perms = data.get("permissions", {})
|
|
225
|
+
allow_count = len(perms.get("allow", []))
|
|
226
|
+
deny_count = len(perms.get("deny", []))
|
|
227
|
+
if allow_count == 0:
|
|
228
|
+
infos.append("No allow rules (tools will prompt for approval)")
|
|
229
|
+
if deny_count == 0:
|
|
230
|
+
issues.append("No deny rules (destructive commands not blocked)")
|
|
231
|
+
|
|
232
|
+
env = data.get("env", {})
|
|
233
|
+
if not env.get("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"):
|
|
234
|
+
infos.append("AGENT_TEAMS env not set")
|
|
235
|
+
|
|
236
|
+
if issues:
|
|
237
|
+
return _result("Settings", "error", "; ".join(issues), "Run gaia-scan or gaia update")
|
|
238
|
+
|
|
239
|
+
hook_count = len(hooks_config) if hooks_config else 0
|
|
240
|
+
perm_count = allow_count + deny_count
|
|
241
|
+
|
|
242
|
+
if infos:
|
|
243
|
+
return _result("Settings", "info", f"{hook_count} hook types, {perm_count} rules -- {'; '.join(infos)}")
|
|
244
|
+
return _result("Settings", "pass", f"{hook_count} hook types, {perm_count} rules")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def check_hook_files(project_root: Path) -> dict:
|
|
248
|
+
"""Check all expected hook scripts exist."""
|
|
249
|
+
hooks = [
|
|
250
|
+
("pre_tool_use.py", True),
|
|
251
|
+
("post_tool_use.py", True),
|
|
252
|
+
("user_prompt_submit.py", True),
|
|
253
|
+
("session_start.py", True),
|
|
254
|
+
("subagent_stop.py", False),
|
|
255
|
+
("subagent_start.py", False),
|
|
256
|
+
("stop_hook.py", False),
|
|
257
|
+
("task_completed.py", False),
|
|
258
|
+
("post_compact.py", False),
|
|
259
|
+
("elicitation_result.py", False),
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
errors = []
|
|
263
|
+
warnings = []
|
|
264
|
+
valid = 0
|
|
265
|
+
total = len(hooks)
|
|
266
|
+
|
|
267
|
+
for filename, required in hooks:
|
|
268
|
+
hook_path = project_root / ".claude" / "hooks" / filename
|
|
269
|
+
if hook_path.is_file():
|
|
270
|
+
valid += 1
|
|
271
|
+
elif required:
|
|
272
|
+
errors.append(f"{filename} missing")
|
|
273
|
+
else:
|
|
274
|
+
warnings.append(filename)
|
|
275
|
+
|
|
276
|
+
if errors:
|
|
277
|
+
return _result("Hook files", "error", "; ".join(errors), "Recreate symlinks: gaia-scan")
|
|
278
|
+
if warnings:
|
|
279
|
+
return _result(
|
|
280
|
+
"Hook files",
|
|
281
|
+
"warning",
|
|
282
|
+
f"{valid}/{total} found (missing: {', '.join(warnings)})",
|
|
283
|
+
"Run gaia-scan to recreate symlinks",
|
|
284
|
+
)
|
|
285
|
+
return _result("Hook files", "pass", f"{valid}/{total} found")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def check_project_context(project_root: Path) -> dict:
|
|
289
|
+
"""Check project-context.json is valid and enriched."""
|
|
290
|
+
path = project_root / ".claude" / "project-context" / "project-context.json"
|
|
291
|
+
if not path.is_file():
|
|
292
|
+
return _result("project-context", "warning", "Missing", "Run gaia-scan")
|
|
293
|
+
|
|
294
|
+
data = _read_json(path)
|
|
295
|
+
if not data:
|
|
296
|
+
return _result("project-context", "warning", "Invalid JSON", "Regenerate with gaia-scan")
|
|
297
|
+
|
|
298
|
+
warnings = []
|
|
299
|
+
infos = []
|
|
300
|
+
|
|
301
|
+
if not data.get("metadata"):
|
|
302
|
+
warnings.append("Missing metadata section")
|
|
303
|
+
if not data.get("sections"):
|
|
304
|
+
warnings.append("Missing sections")
|
|
305
|
+
|
|
306
|
+
is_v2 = (data.get("metadata") or {}).get("version") == "2.0"
|
|
307
|
+
|
|
308
|
+
has_paths = bool((data.get("sections") or {}).get("infrastructure", {}).get("paths")) if is_v2 else bool(data.get("paths"))
|
|
309
|
+
if not has_paths:
|
|
310
|
+
infos.append("No paths section")
|
|
311
|
+
|
|
312
|
+
sections = data.get("sections")
|
|
313
|
+
if sections:
|
|
314
|
+
section_count = len(sections)
|
|
315
|
+
if section_count < 3:
|
|
316
|
+
infos.append(f"Only {section_count} sections (expected >=3)")
|
|
317
|
+
else:
|
|
318
|
+
section_count = 0
|
|
319
|
+
|
|
320
|
+
if warnings:
|
|
321
|
+
detail = "; ".join(warnings + infos)
|
|
322
|
+
return _result("project-context", "warning", detail, "Run gaia-scan to enrich")
|
|
323
|
+
|
|
324
|
+
if infos:
|
|
325
|
+
return _result("project-context", "info", f"{section_count} sections -- {'; '.join(infos)}")
|
|
326
|
+
|
|
327
|
+
return _result("project-context", "pass", f"{section_count} sections")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def check_project_dirs(project_root: Path) -> dict:
|
|
331
|
+
"""Check paths declared in project-context exist on disk."""
|
|
332
|
+
context_path = project_root / ".claude" / "project-context" / "project-context.json"
|
|
333
|
+
if not context_path.is_file():
|
|
334
|
+
return _result("Project dirs", "pass", "Skipped (no context)")
|
|
335
|
+
|
|
336
|
+
data = _read_json(context_path)
|
|
337
|
+
if not data:
|
|
338
|
+
return _result("Project dirs", "pass", "Skipped (parse error)")
|
|
339
|
+
|
|
340
|
+
sections = data.get("sections") or {}
|
|
341
|
+
paths = sections.get("infrastructure", {}).get("paths") or data.get("paths") or {}
|
|
342
|
+
issues = []
|
|
343
|
+
verified = 0
|
|
344
|
+
|
|
345
|
+
# Path values may be a single string (e.g. "project_root": ".") or a list
|
|
346
|
+
# of strings (e.g. "scan_targets": [".", "src"]). Normalize to a flat list
|
|
347
|
+
# of (label, str) pairs so `project_root / value` is always Path / str.
|
|
348
|
+
for key, value in paths.items():
|
|
349
|
+
if value is None:
|
|
350
|
+
continue
|
|
351
|
+
if isinstance(value, (list, tuple)):
|
|
352
|
+
entries = [(f"{key}[{i}]", str(v)) for i, v in enumerate(value) if v]
|
|
353
|
+
else:
|
|
354
|
+
entries = [(key, str(value))]
|
|
355
|
+
|
|
356
|
+
for label, dir_path in entries:
|
|
357
|
+
verified += 1
|
|
358
|
+
if not (project_root / dir_path).exists():
|
|
359
|
+
issues.append(f"{label}: {dir_path} not found")
|
|
360
|
+
|
|
361
|
+
if issues:
|
|
362
|
+
return _result("Project dirs", "warning", "; ".join(issues), "Create missing directories or update paths")
|
|
363
|
+
|
|
364
|
+
return _result("Project dirs", "pass", f"{verified} paths verified")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def check_memory_fts5_db(project_root: Path) -> dict:
|
|
368
|
+
"""Check if the FTS5 search.db exists for episodic memory."""
|
|
369
|
+
db_path = project_root / ".claude" / "project-context" / "episodic-memory" / "search.db"
|
|
370
|
+
if db_path.is_file():
|
|
371
|
+
return _result("memory_fts5_db", "pass", f"search.db present ({db_path.stat().st_size} bytes)")
|
|
372
|
+
return _result(
|
|
373
|
+
"memory_fts5_db",
|
|
374
|
+
"info",
|
|
375
|
+
"search.db not found (created on first use)",
|
|
376
|
+
"Run: gaia doctor --fix",
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def check_memory_fts5_count(project_root: Path) -> dict:
|
|
381
|
+
"""Check FTS5 indexed count against total episode count in index.json."""
|
|
382
|
+
index_path = project_root / ".claude" / "project-context" / "episodic-memory" / "index.json"
|
|
383
|
+
|
|
384
|
+
if not index_path.is_file():
|
|
385
|
+
return _result("memory_fts5_count", "info", "index.json not found — no episodes yet")
|
|
386
|
+
|
|
387
|
+
index_data = _read_json(index_path)
|
|
388
|
+
if not index_data:
|
|
389
|
+
return _result("memory_fts5_count", "info", "index.json unreadable")
|
|
390
|
+
|
|
391
|
+
total = len(index_data.get("episodes") or [])
|
|
392
|
+
|
|
393
|
+
try:
|
|
394
|
+
import sys as _sys
|
|
395
|
+
# Ensure package root is on path for lazy import
|
|
396
|
+
pkg_root = str(_package_root())
|
|
397
|
+
if pkg_root not in _sys.path:
|
|
398
|
+
_sys.path.insert(0, pkg_root)
|
|
399
|
+
from tools.memory import search_store # noqa: PLC0415
|
|
400
|
+
indexed = search_store.count()
|
|
401
|
+
except ImportError:
|
|
402
|
+
return _result(
|
|
403
|
+
"memory_fts5_count",
|
|
404
|
+
"info",
|
|
405
|
+
"tools.memory.search_store not importable — FTS5 count skipped",
|
|
406
|
+
)
|
|
407
|
+
except Exception as exc:
|
|
408
|
+
return _result("memory_fts5_count", "info", f"Could not query FTS5 count: {exc}")
|
|
409
|
+
|
|
410
|
+
if total == 0:
|
|
411
|
+
return _result("memory_fts5_count", "pass", "No episodes to index")
|
|
412
|
+
|
|
413
|
+
pct = indexed / total
|
|
414
|
+
if pct < 0.90:
|
|
415
|
+
return _result(
|
|
416
|
+
"memory_fts5_count",
|
|
417
|
+
"warning",
|
|
418
|
+
f"FTS5 index incomplete: {indexed}/{total} episodes indexed ({pct:.0%})",
|
|
419
|
+
"Run: gaia doctor --fix",
|
|
420
|
+
)
|
|
421
|
+
return _result("memory_fts5_count", "pass", f"{indexed}/{total} episodes indexed ({pct:.0%})")
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def check_memory_scoring(project_root: Path) -> dict:
|
|
425
|
+
"""Check that tools.memory.scoring is importable (scoring module available)."""
|
|
426
|
+
try:
|
|
427
|
+
import sys as _sys
|
|
428
|
+
pkg_root = str(_package_root())
|
|
429
|
+
if pkg_root not in _sys.path:
|
|
430
|
+
_sys.path.insert(0, pkg_root)
|
|
431
|
+
import tools.memory.scoring # noqa: F401, PLC0415
|
|
432
|
+
return _result("memory_scoring", "pass", "Scoring module importable")
|
|
433
|
+
except ImportError as exc:
|
|
434
|
+
return _result(
|
|
435
|
+
"memory_scoring",
|
|
436
|
+
"warning",
|
|
437
|
+
f"Scoring module unavailable: {exc} (scoring disabled)",
|
|
438
|
+
)
|
|
439
|
+
except Exception as exc:
|
|
440
|
+
return _result("memory_scoring", "warning", f"Scoring module error: {exc}")
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _apply_fts5_backfill(project_root: Path) -> dict:
|
|
444
|
+
"""Run FTS5 backfill and return a fix-result dict."""
|
|
445
|
+
try:
|
|
446
|
+
import sys as _sys
|
|
447
|
+
pkg_root = str(_package_root())
|
|
448
|
+
if pkg_root not in _sys.path:
|
|
449
|
+
_sys.path.insert(0, pkg_root)
|
|
450
|
+
|
|
451
|
+
# Ensure backfill_fts5 finds the correct project root by setting cwd context
|
|
452
|
+
# via the module's own _find_project_root (walks up from cwd).
|
|
453
|
+
# We temporarily add project_root to env if needed, but the module uses cwd.
|
|
454
|
+
import os as _os
|
|
455
|
+
orig_cwd = _os.getcwd()
|
|
456
|
+
try:
|
|
457
|
+
_os.chdir(project_root)
|
|
458
|
+
from tools.memory import backfill_fts5 # noqa: PLC0415
|
|
459
|
+
rc = backfill_fts5.main()
|
|
460
|
+
finally:
|
|
461
|
+
_os.chdir(orig_cwd)
|
|
462
|
+
|
|
463
|
+
if rc == 0:
|
|
464
|
+
return {"name": "fts5_backfill", "status": "applied", "detail": "FTS5 index rebuilt successfully"}
|
|
465
|
+
return {"name": "fts5_backfill", "status": "failed", "detail": f"backfill_fts5.main() returned {rc}"}
|
|
466
|
+
except ImportError as exc:
|
|
467
|
+
return {"name": "fts5_backfill", "status": "failed", "detail": f"Cannot import backfill_fts5: {exc}"}
|
|
468
|
+
except Exception as exc:
|
|
469
|
+
return {"name": "fts5_backfill", "status": "failed", "detail": f"Backfill error: {exc}"}
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def check_memory_dirs(project_root: Path) -> dict:
|
|
473
|
+
"""Check episodic memory directories are present."""
|
|
474
|
+
checks = [
|
|
475
|
+
(
|
|
476
|
+
project_root / ".claude" / "project-context" / "workflow-episodic-memory",
|
|
477
|
+
"workflow-episodic-memory",
|
|
478
|
+
"warning",
|
|
479
|
+
"Run gaia-scan to create workflow memory directory",
|
|
480
|
+
),
|
|
481
|
+
(
|
|
482
|
+
project_root / ".claude" / "project-context" / "episodic-memory",
|
|
483
|
+
"episodic-memory",
|
|
484
|
+
"info",
|
|
485
|
+
"Created automatically on first agent run",
|
|
486
|
+
),
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
warnings = []
|
|
490
|
+
infos = []
|
|
491
|
+
found = 0
|
|
492
|
+
|
|
493
|
+
for path, label, severity, fix in checks:
|
|
494
|
+
if path.is_dir():
|
|
495
|
+
found += 1
|
|
496
|
+
elif severity == "info":
|
|
497
|
+
infos.append({"label": label, "fix": fix})
|
|
498
|
+
else:
|
|
499
|
+
warnings.append({"label": label, "fix": fix})
|
|
500
|
+
|
|
501
|
+
total = len(checks)
|
|
502
|
+
|
|
503
|
+
if warnings:
|
|
504
|
+
detail = "; ".join(f"{w['label']} missing" for w in warnings)
|
|
505
|
+
return _result("Memory dirs", "warning", detail, warnings[0]["fix"])
|
|
506
|
+
|
|
507
|
+
if infos:
|
|
508
|
+
info_parts = ["{}: {}".format(i["label"], i["fix"]) for i in infos]
|
|
509
|
+
detail = "{}/{} present ({})".format(found, total, "; ".join(info_parts))
|
|
510
|
+
return _result("Memory dirs", "info", detail)
|
|
511
|
+
|
|
512
|
+
return _result("Memory dirs", "pass", f"{found}/{total} present")
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
# ============================================================================
|
|
516
|
+
# Severity display
|
|
517
|
+
# ============================================================================
|
|
518
|
+
|
|
519
|
+
_SEVERITY_ICONS = {
|
|
520
|
+
"pass": "PASS",
|
|
521
|
+
"info": "INFO",
|
|
522
|
+
"warning": "WARN",
|
|
523
|
+
"error": "FAIL",
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _print_human(results: list, version_detail: str = "") -> None:
|
|
528
|
+
"""Print human-readable doctor output."""
|
|
529
|
+
version_tag = f" ({version_detail})" if version_detail else ""
|
|
530
|
+
print(f"\n Gaia-Ops Health Check{version_tag}\n")
|
|
531
|
+
|
|
532
|
+
for r in results:
|
|
533
|
+
icon = _SEVERITY_ICONS.get(r["severity"], "????")
|
|
534
|
+
print(f" [{icon}] {r['name']:<18} {r['detail']}")
|
|
535
|
+
if r["severity"] in ("warning", "error") and r.get("fix"):
|
|
536
|
+
print(f" Fix: {r['fix']}")
|
|
537
|
+
|
|
538
|
+
print()
|
|
539
|
+
|
|
540
|
+
has_errors = any(r["severity"] == "error" for r in results)
|
|
541
|
+
has_warnings = any(r["severity"] == "warning" for r in results)
|
|
542
|
+
|
|
543
|
+
if has_errors:
|
|
544
|
+
print(" Status: CRITICAL\n")
|
|
545
|
+
elif has_warnings:
|
|
546
|
+
print(" Status: ISSUES FOUND\n")
|
|
547
|
+
else:
|
|
548
|
+
print(" Status: HEALTHY\n")
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
# ============================================================================
|
|
552
|
+
# Command interface
|
|
553
|
+
# ============================================================================
|
|
554
|
+
|
|
555
|
+
def register(subparsers):
|
|
556
|
+
"""Register the doctor subcommand."""
|
|
557
|
+
sub = subparsers.add_parser("doctor", help="Run Gaia-Ops health checks")
|
|
558
|
+
sub.add_argument("--json", action="store_true", default=False, help="Output as JSON")
|
|
559
|
+
sub.add_argument("--fix", action="store_true", default=False, help="Attempt auto-fix for common issues")
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def cmd_doctor(args) -> int:
|
|
563
|
+
"""Handler for `gaia doctor`."""
|
|
564
|
+
project_root = _find_project_root()
|
|
565
|
+
|
|
566
|
+
# Use functools.partial so each entry's __name__ resolves to the wrapped
|
|
567
|
+
# check function (e.g. "check_project_dirs"). Bare lambdas would surface
|
|
568
|
+
# as "<lambda>" in the JSON output, hiding which check actually failed.
|
|
569
|
+
check_fns = [
|
|
570
|
+
check_gaia_version,
|
|
571
|
+
check_claude_code,
|
|
572
|
+
check_python,
|
|
573
|
+
partial(check_plugin_mode, project_root),
|
|
574
|
+
partial(check_symlinks, project_root),
|
|
575
|
+
partial(check_identity, project_root),
|
|
576
|
+
partial(check_settings, project_root),
|
|
577
|
+
partial(check_hook_files, project_root),
|
|
578
|
+
partial(check_project_context, project_root),
|
|
579
|
+
partial(check_project_dirs, project_root),
|
|
580
|
+
partial(check_memory_dirs, project_root),
|
|
581
|
+
partial(check_memory_fts5_db, project_root),
|
|
582
|
+
partial(check_memory_fts5_count, project_root),
|
|
583
|
+
partial(check_memory_scoring, project_root),
|
|
584
|
+
]
|
|
585
|
+
|
|
586
|
+
def _fn_name(fn):
|
|
587
|
+
# functools.partial wraps a function in .func; bare functions expose
|
|
588
|
+
# __name__ directly. Either way, surface a human-readable identifier.
|
|
589
|
+
target = getattr(fn, "func", fn)
|
|
590
|
+
return getattr(target, "__name__", repr(fn))
|
|
591
|
+
|
|
592
|
+
results = []
|
|
593
|
+
for fn in check_fns:
|
|
594
|
+
try:
|
|
595
|
+
results.append(fn())
|
|
596
|
+
except Exception as exc:
|
|
597
|
+
results.append(_result(_fn_name(fn), "error", f"Error: {exc}"))
|
|
598
|
+
|
|
599
|
+
has_errors = any(r["severity"] == "error" for r in results)
|
|
600
|
+
has_warnings = any(r["severity"] == "warning" for r in results)
|
|
601
|
+
|
|
602
|
+
# --fix: run auto-fixers for triggered checks
|
|
603
|
+
fixes = []
|
|
604
|
+
if getattr(args, "fix", False):
|
|
605
|
+
fts5_db_check = next((r for r in results if r["name"] == "memory_fts5_db"), None)
|
|
606
|
+
fts5_count_check = next((r for r in results if r["name"] == "memory_fts5_count"), None)
|
|
607
|
+
|
|
608
|
+
db_needs_fix = fts5_db_check and fts5_db_check["severity"] == "info"
|
|
609
|
+
count_needs_fix = fts5_count_check and fts5_count_check["severity"] == "warning"
|
|
610
|
+
|
|
611
|
+
if db_needs_fix or count_needs_fix:
|
|
612
|
+
fix_result = _apply_fts5_backfill(project_root)
|
|
613
|
+
fixes.append(fix_result)
|
|
614
|
+
|
|
615
|
+
if fix_result["status"] == "applied":
|
|
616
|
+
# Re-run the affected checks to reflect post-fix state
|
|
617
|
+
if fts5_db_check:
|
|
618
|
+
idx = results.index(fts5_db_check)
|
|
619
|
+
results[idx] = check_memory_fts5_db(project_root)
|
|
620
|
+
if fts5_count_check:
|
|
621
|
+
idx = results.index(fts5_count_check)
|
|
622
|
+
results[idx] = check_memory_fts5_count(project_root)
|
|
623
|
+
|
|
624
|
+
# Recompute summary flags after re-checks
|
|
625
|
+
has_errors = any(r["severity"] == "error" for r in results)
|
|
626
|
+
has_warnings = any(r["severity"] == "warning" for r in results)
|
|
627
|
+
|
|
628
|
+
if getattr(args, "json", False):
|
|
629
|
+
status = "critical" if has_errors else "degraded" if has_warnings else "healthy"
|
|
630
|
+
output = {
|
|
631
|
+
"healthy": not has_errors and not has_warnings,
|
|
632
|
+
"status": status,
|
|
633
|
+
"checks": results,
|
|
634
|
+
"fixes": fixes,
|
|
635
|
+
}
|
|
636
|
+
print(json.dumps(output, indent=2))
|
|
637
|
+
else:
|
|
638
|
+
gaia_check = next((r for r in results if r["name"] == "Gaia-Ops"), None)
|
|
639
|
+
version_detail = gaia_check["detail"] if gaia_check and gaia_check["severity"] == "pass" else ""
|
|
640
|
+
_print_human(results, version_detail)
|
|
641
|
+
if fixes:
|
|
642
|
+
print(" Fixes applied:")
|
|
643
|
+
for fix in fixes:
|
|
644
|
+
print(f" [{fix['status'].upper()}] {fix['name']}: {fix['detail']}")
|
|
645
|
+
print()
|
|
646
|
+
|
|
647
|
+
if has_errors:
|
|
648
|
+
return 2
|
|
649
|
+
if has_warnings:
|
|
650
|
+
return 1
|
|
651
|
+
return 0
|