@jaguilar87/gaia 5.0.0-rc1
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 +1212 -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 +237 -0
- package/agents/gaia-planner.md +53 -0
- package/agents/gaia-system.md +70 -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 +628 -0
- package/bin/cli/history.py +305 -0
- package/bin/cli/memory.py +464 -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 +816 -0
- package/bin/pre-publish-validate.js +610 -0
- package/bin/python-detect.js +60 -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 +421 -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 +237 -0
- package/dist/gaia-ops/agents/gaia-planner.md +53 -0
- package/dist/gaia-ops/agents/gaia-system.md +70 -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 +421 -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 +163 -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 +232 -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_start.py +81 -0
- package/dist/gaia-ops/hooks/stop_hook.py +82 -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 +154 -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 +182 -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 +82 -0
- package/dist/gaia-ops/skills/gaia-release/reference.md +102 -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/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 +360 -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 +84 -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 +232 -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_start.py +81 -0
- package/dist/gaia-security/hooks/stop_hook.py +82 -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 +232 -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_start.py +81 -0
- package/hooks/stop_hook.py +82 -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 +99 -0
- package/pyproject.toml +32 -0
- package/skills/README.md +154 -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 +182 -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 +82 -0
- package/skills/gaia-release/reference.md +102 -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/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 +360 -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,816 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @jaguilar87/gaia - Update script
|
|
5
|
+
*
|
|
6
|
+
* Runs automatically on npm install/update (postinstall hook).
|
|
7
|
+
* Also available as: npx gaia-update
|
|
8
|
+
*
|
|
9
|
+
* Behavior:
|
|
10
|
+
* - First-time install (.claude/ doesn't exist):
|
|
11
|
+
* 1. Check Python 3 is available
|
|
12
|
+
* 2. Run gaia-scan --npm-postinstall to create .claude/, symlinks, settings, project-context
|
|
13
|
+
* 3. Create plugin-registry.json
|
|
14
|
+
* 4. Merge permissions into settings.local.json
|
|
15
|
+
* 5. Merge hooks into settings.local.json
|
|
16
|
+
* 6. Fall through to verification
|
|
17
|
+
* - Update (.claude/ exists):
|
|
18
|
+
* 1. Show version transition (previous → current)
|
|
19
|
+
* 2. settings.json: create only if missing (non-invasive, never overwrites)
|
|
20
|
+
* 3. Merge permissions, env vars, and agent key into settings.local.json (union, preserves user config)
|
|
21
|
+
* 4. Merge hooks from hooks.json into settings.local.json (npm mode requires this)
|
|
22
|
+
* 5. Symlinks: recreate if missing, fix broken ones
|
|
23
|
+
* 5. Verify: hooks, python, project-context, config files
|
|
24
|
+
* 6. Report: summary with any issues found
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* npm update @jaguilar87/gaia # Automatic via postinstall
|
|
28
|
+
* npx gaia-update # Manual trigger
|
|
29
|
+
* npx gaia-update --verbose # Show all checks
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { fileURLToPath } from 'url';
|
|
33
|
+
import { dirname, join, relative } from 'path';
|
|
34
|
+
import fs from 'fs/promises';
|
|
35
|
+
import { existsSync, realpathSync } from 'fs';
|
|
36
|
+
import { exec } from 'child_process';
|
|
37
|
+
import { promisify } from 'util';
|
|
38
|
+
import chalk from 'chalk';
|
|
39
|
+
import ora from 'ora';
|
|
40
|
+
import { findPython } from './python-detect.js';
|
|
41
|
+
|
|
42
|
+
const execAsync = promisify(exec);
|
|
43
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
44
|
+
const __dirname = dirname(__filename);
|
|
45
|
+
const CWD = process.env.INIT_CWD || process.cwd();
|
|
46
|
+
const VERBOSE = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
47
|
+
|
|
48
|
+
// Use junctions on Windows (no admin required), regular symlinks elsewhere
|
|
49
|
+
const LINK_TYPE = process.platform === 'win32' ? 'junction' : 'dir';
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Version Detection
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
async function detectVersions() {
|
|
56
|
+
const current = await readPackageVersion(join(__dirname, '..', 'package.json'));
|
|
57
|
+
|
|
58
|
+
// Try to find previous version from the installed package.json backup or lock
|
|
59
|
+
let previous = null;
|
|
60
|
+
try {
|
|
61
|
+
const lockPath = join(CWD, 'package-lock.json');
|
|
62
|
+
if (existsSync(lockPath)) {
|
|
63
|
+
const lock = JSON.parse(await fs.readFile(lockPath, 'utf-8'));
|
|
64
|
+
const dep = lock.packages?.['node_modules/@jaguilar87/gaia']
|
|
65
|
+
|| lock.dependencies?.['@jaguilar87/gaia'];
|
|
66
|
+
if (dep) previous = dep.version;
|
|
67
|
+
}
|
|
68
|
+
} catch { /* ignore */ }
|
|
69
|
+
|
|
70
|
+
return { previous, current };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function readPackageVersion(path) {
|
|
74
|
+
try {
|
|
75
|
+
const pkg = JSON.parse(await fs.readFile(path, 'utf-8'));
|
|
76
|
+
return pkg.version;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Update Steps
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
async function updateSettingsJson() {
|
|
87
|
+
const spinner = ora('Checking settings.json...').start();
|
|
88
|
+
try {
|
|
89
|
+
const settingsPath = join(CWD, '.claude', 'settings.json');
|
|
90
|
+
|
|
91
|
+
if (!existsSync(join(CWD, '.claude'))) {
|
|
92
|
+
spinner.info('Skipped (.claude/ not found)');
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Non-invasive: only create if missing. Never overwrite.
|
|
97
|
+
// Hooks come from hooks.json (auto-discovered via symlink).
|
|
98
|
+
// Env vars and permissions live in settings.local.json.
|
|
99
|
+
if (existsSync(settingsPath)) {
|
|
100
|
+
spinner.succeed('settings.json already exists (not overwriting)');
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await fs.writeFile(settingsPath, '{}\n');
|
|
105
|
+
spinner.succeed('settings.json created (minimal — hooks from hooks.json)');
|
|
106
|
+
return true;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
spinner.fail(`settings.json: ${error.message}`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function updateLocalPermissions() {
|
|
114
|
+
const spinner = ora('Merging permissions into settings.local.json...').start();
|
|
115
|
+
try {
|
|
116
|
+
const claudeDir = join(CWD, '.claude');
|
|
117
|
+
const localPath = join(claudeDir, 'settings.local.json');
|
|
118
|
+
|
|
119
|
+
if (!existsSync(claudeDir)) {
|
|
120
|
+
spinner.info('Skipped (.claude/ not found)');
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Load existing settings.local.json — preserve everything (enabledPlugins, MCP servers, etc.)
|
|
125
|
+
let existing = {};
|
|
126
|
+
if (existsSync(localPath)) {
|
|
127
|
+
try {
|
|
128
|
+
existing = JSON.parse(await fs.readFile(localPath, 'utf-8'));
|
|
129
|
+
} catch {
|
|
130
|
+
existing = {};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Track what changed
|
|
135
|
+
let changed = false;
|
|
136
|
+
|
|
137
|
+
// Set the orchestrator agent identity (always, even if Python extraction fails)
|
|
138
|
+
if (existing.agent !== 'gaia-orchestrator') {
|
|
139
|
+
existing.agent = 'gaia-orchestrator';
|
|
140
|
+
changed = true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add env vars (smart merge: add if not present, don't overwrite)
|
|
144
|
+
existing.env = existing.env || {};
|
|
145
|
+
if (!('CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS' in existing.env)) {
|
|
146
|
+
existing.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
|
|
147
|
+
changed = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Load permissions from plugin_setup.py — the single source of truth.
|
|
151
|
+
// We use ast.literal_eval to extract the constants without importing
|
|
152
|
+
// the module (which has relative imports that fail standalone).
|
|
153
|
+
let gaiaPerms;
|
|
154
|
+
try {
|
|
155
|
+
const setupPath = join(__dirname, '..', 'hooks', 'modules', 'core', 'plugin_setup.py');
|
|
156
|
+
const pythonCmd = findPython() || 'python3';
|
|
157
|
+
|
|
158
|
+
// Write the extraction script to a temp file instead of using -c with
|
|
159
|
+
// inline code. This avoids shell quoting issues on Windows where
|
|
160
|
+
// backslash paths and nested quotes break the inline Python string.
|
|
161
|
+
const tempScript = join(claudeDir, '.gaia-extract-perms.py');
|
|
162
|
+
const scriptContent = `
|
|
163
|
+
import ast, json, re, sys
|
|
164
|
+
|
|
165
|
+
setup_path = sys.argv[1]
|
|
166
|
+
source = open(setup_path, encoding="utf-8").read()
|
|
167
|
+
|
|
168
|
+
# Extract _DENY_RULES list
|
|
169
|
+
deny_match = re.search(r'^_DENY_RULES\\s*=\\s*\\[', source, re.MULTILINE)
|
|
170
|
+
if deny_match:
|
|
171
|
+
bracket_start = deny_match.start() + source[deny_match.start():].index('[')
|
|
172
|
+
depth, i = 0, bracket_start
|
|
173
|
+
for i, ch in enumerate(source[bracket_start:], bracket_start):
|
|
174
|
+
if ch == '[': depth += 1
|
|
175
|
+
elif ch == ']': depth -= 1
|
|
176
|
+
if depth == 0: break
|
|
177
|
+
deny_rules = ast.literal_eval(source[bracket_start:i+1])
|
|
178
|
+
else:
|
|
179
|
+
deny_rules = []
|
|
180
|
+
|
|
181
|
+
# Extract OPS_PERMISSIONS allow list
|
|
182
|
+
ops_match = re.search(r'^OPS_PERMISSIONS\\s*=', source, re.MULTILINE)
|
|
183
|
+
if ops_match:
|
|
184
|
+
bracket_start = source.index('{', ops_match.start())
|
|
185
|
+
depth, i = 0, bracket_start
|
|
186
|
+
for i, ch in enumerate(source[bracket_start:], bracket_start):
|
|
187
|
+
if ch == '{': depth += 1
|
|
188
|
+
elif ch == '}': depth -= 1
|
|
189
|
+
if depth == 0: break
|
|
190
|
+
# Replace _DENY_RULES reference with actual list for eval
|
|
191
|
+
ops_str = source[bracket_start:i+1].replace('_DENY_RULES', json.dumps(deny_rules))
|
|
192
|
+
ops_perms = ast.literal_eval(ops_str)
|
|
193
|
+
else:
|
|
194
|
+
ops_perms = {'permissions': {'allow': [], 'deny': deny_rules, 'ask': []}}
|
|
195
|
+
|
|
196
|
+
print(json.dumps(ops_perms))
|
|
197
|
+
`;
|
|
198
|
+
await fs.writeFile(tempScript, scriptContent);
|
|
199
|
+
try {
|
|
200
|
+
const { stdout } = await execAsync(
|
|
201
|
+
`${pythonCmd} "${tempScript}" "${setupPath}"`,
|
|
202
|
+
{ timeout: 10000 }
|
|
203
|
+
);
|
|
204
|
+
gaiaPerms = JSON.parse(stdout.trim());
|
|
205
|
+
} finally {
|
|
206
|
+
// Clean up temp script
|
|
207
|
+
try { await fs.unlink(tempScript); } catch { /* ignore */ }
|
|
208
|
+
}
|
|
209
|
+
} catch (pyError) {
|
|
210
|
+
spinner.warn(`Could not load permissions from Python — ${pyError.message || 'unknown error'}`);
|
|
211
|
+
// Still write agent and env changes even if permissions extraction fails
|
|
212
|
+
if (changed) {
|
|
213
|
+
await fs.writeFile(localPath, JSON.stringify(existing, null, 2) + '\n');
|
|
214
|
+
spinner.succeed('settings.local.json agent and env merged (permissions skipped)');
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const ourAllow = new Set(gaiaPerms.permissions.allow || []);
|
|
221
|
+
const ourDeny = new Set(gaiaPerms.permissions.deny || []);
|
|
222
|
+
|
|
223
|
+
const perms = existing.permissions || {};
|
|
224
|
+
const currentAllow = new Set(perms.allow || []);
|
|
225
|
+
const currentDeny = new Set(perms.deny || []);
|
|
226
|
+
|
|
227
|
+
// Union merge — add ours without removing user's
|
|
228
|
+
const mergedAllow = [...new Set([...currentAllow, ...ourAllow])].sort();
|
|
229
|
+
const mergedDeny = [...new Set([...currentDeny, ...ourDeny])].sort();
|
|
230
|
+
|
|
231
|
+
// Check if permissions changed
|
|
232
|
+
const allowChanged = mergedAllow.length !== currentAllow.size
|
|
233
|
+
|| mergedAllow.some(r => !currentAllow.has(r));
|
|
234
|
+
const denyChanged = mergedDeny.length !== currentDeny.size
|
|
235
|
+
|| mergedDeny.some(r => !currentDeny.has(r));
|
|
236
|
+
|
|
237
|
+
if (allowChanged || denyChanged) {
|
|
238
|
+
existing.permissions = existing.permissions || {};
|
|
239
|
+
existing.permissions.allow = mergedAllow;
|
|
240
|
+
existing.permissions.deny = mergedDeny;
|
|
241
|
+
existing.permissions.ask = existing.permissions.ask || [];
|
|
242
|
+
changed = true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!changed) {
|
|
246
|
+
spinner.succeed('settings.local.json permissions already up to date');
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
await fs.writeFile(localPath, JSON.stringify(existing, null, 2) + '\n');
|
|
251
|
+
spinner.succeed('settings.local.json permissions, env, and agent merged');
|
|
252
|
+
return true;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
spinner.fail(`settings.local.json: ${error.message}`);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function updateLocalHooks() {
|
|
260
|
+
const spinner = ora('Merging hooks into settings.local.json...').start();
|
|
261
|
+
try {
|
|
262
|
+
const claudeDir = join(CWD, '.claude');
|
|
263
|
+
const localPath = join(claudeDir, 'settings.local.json');
|
|
264
|
+
|
|
265
|
+
if (!existsSync(claudeDir)) {
|
|
266
|
+
spinner.info('Skipped (.claude/ not found)');
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Read hooks.json from the installed package
|
|
271
|
+
const hooksJsonPath = join(__dirname, '..', 'hooks', 'hooks.json');
|
|
272
|
+
if (!existsSync(hooksJsonPath)) {
|
|
273
|
+
spinner.warn('hooks.json not found in package');
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let hooksData;
|
|
278
|
+
try {
|
|
279
|
+
hooksData = JSON.parse(await fs.readFile(hooksJsonPath, 'utf-8'));
|
|
280
|
+
} catch {
|
|
281
|
+
spinner.warn('hooks.json is invalid JSON');
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Unwrap outer "hooks" key if present
|
|
286
|
+
const sourceHooks = hooksData.hooks || hooksData;
|
|
287
|
+
|
|
288
|
+
// Resolve absolute path to hooks directory so hooks work regardless of
|
|
289
|
+
// CWD at execution time (Stop/PostCompact hooks may run from unknown CWD)
|
|
290
|
+
const hooksSymlink = join(claudeDir, 'hooks');
|
|
291
|
+
let hooksAbs;
|
|
292
|
+
try {
|
|
293
|
+
hooksAbs = realpathSync(hooksSymlink);
|
|
294
|
+
} catch {
|
|
295
|
+
hooksAbs = hooksSymlink; // Fallback if symlink not yet created
|
|
296
|
+
}
|
|
297
|
+
const convertCommand = (cmd) => {
|
|
298
|
+
return cmd.replace(/\$\{CLAUDE_PLUGIN_ROOT\}\/hooks\//g, `${hooksAbs}/`);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const convertedHooks = {};
|
|
302
|
+
for (const [event, entries] of Object.entries(sourceHooks)) {
|
|
303
|
+
convertedHooks[event] = entries.map(entry => {
|
|
304
|
+
const converted = { ...entry };
|
|
305
|
+
if (converted.hooks) {
|
|
306
|
+
converted.hooks = converted.hooks.map(h => ({
|
|
307
|
+
...h,
|
|
308
|
+
command: h.command ? convertCommand(h.command) : h.command,
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
return converted;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Load existing settings.local.json
|
|
316
|
+
let existing = {};
|
|
317
|
+
if (existsSync(localPath)) {
|
|
318
|
+
try {
|
|
319
|
+
existing = JSON.parse(await fs.readFile(localPath, 'utf-8'));
|
|
320
|
+
} catch {
|
|
321
|
+
existing = {};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Migrate existing relative .claude/hooks/ paths to absolute
|
|
326
|
+
const existingHooks = existing.hooks || {};
|
|
327
|
+
let changed = false;
|
|
328
|
+
|
|
329
|
+
for (const [event, entries] of Object.entries(existingHooks)) {
|
|
330
|
+
for (const entry of entries) {
|
|
331
|
+
for (const h of (entry.hooks || [])) {
|
|
332
|
+
if (h.command && h.command.startsWith('.claude/hooks/')) {
|
|
333
|
+
h.command = h.command.replace('.claude/hooks/', `${hooksAbs}/`);
|
|
334
|
+
changed = true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Smart merge: for each hook event, deduplicate by command string
|
|
341
|
+
for (const [event, newEntries] of Object.entries(convertedHooks)) {
|
|
342
|
+
if (!existingHooks[event]) {
|
|
343
|
+
existingHooks[event] = newEntries;
|
|
344
|
+
changed = true;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Collect existing command strings for deduplication
|
|
349
|
+
const existingCommands = new Set();
|
|
350
|
+
for (const entry of existingHooks[event]) {
|
|
351
|
+
for (const h of (entry.hooks || [])) {
|
|
352
|
+
if (h.command) existingCommands.add(h.command);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Add new entries whose commands are not already present
|
|
357
|
+
for (const newEntry of newEntries) {
|
|
358
|
+
const newCommands = (newEntry.hooks || []).map(h => h.command).filter(Boolean);
|
|
359
|
+
const allPresent = newCommands.length > 0 && newCommands.every(c => existingCommands.has(c));
|
|
360
|
+
if (!allPresent) {
|
|
361
|
+
existingHooks[event].push(newEntry);
|
|
362
|
+
changed = true;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!changed) {
|
|
368
|
+
spinner.succeed('settings.local.json hooks already up to date');
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
existing.hooks = existingHooks;
|
|
373
|
+
await fs.writeFile(localPath, JSON.stringify(existing, null, 2) + '\n');
|
|
374
|
+
spinner.succeed('settings.local.json hooks merged');
|
|
375
|
+
return true;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
spinner.fail(`hooks merge: ${error.message}`);
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function updateSymlinks() {
|
|
383
|
+
const spinner = ora('Checking symlinks...').start();
|
|
384
|
+
try {
|
|
385
|
+
const claudeDir = join(CWD, '.claude');
|
|
386
|
+
if (!existsSync(claudeDir)) {
|
|
387
|
+
spinner.info('Skipped (.claude/ not found)');
|
|
388
|
+
return { updated: false, fixed: 0, total: 0 };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const packagePath = join(CWD, 'node_modules', '@jaguilar87', 'gaia-ops');
|
|
392
|
+
if (!existsSync(packagePath)) {
|
|
393
|
+
spinner.fail('Package not found in node_modules');
|
|
394
|
+
return { updated: false, fixed: 0, total: 0 };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const relativePath = relative(claudeDir, packagePath);
|
|
398
|
+
const symlinks = ['agents', 'tools', 'hooks', 'commands', 'templates', 'config', 'skills'];
|
|
399
|
+
let fixed = 0;
|
|
400
|
+
|
|
401
|
+
for (const name of symlinks) {
|
|
402
|
+
const link = join(claudeDir, name);
|
|
403
|
+
// Junctions on Windows require absolute targets; symlinks on Unix use relative
|
|
404
|
+
const target = process.platform === 'win32'
|
|
405
|
+
? join(packagePath, name)
|
|
406
|
+
: join(relativePath, name);
|
|
407
|
+
|
|
408
|
+
if (!existsSync(link)) {
|
|
409
|
+
try {
|
|
410
|
+
await fs.symlink(target, link, LINK_TYPE);
|
|
411
|
+
fixed++;
|
|
412
|
+
} catch { /* skip */ }
|
|
413
|
+
} else {
|
|
414
|
+
// Check if symlink is broken (target doesn't resolve)
|
|
415
|
+
try {
|
|
416
|
+
await fs.realpath(link);
|
|
417
|
+
} catch {
|
|
418
|
+
// Broken symlink — remove and recreate
|
|
419
|
+
try {
|
|
420
|
+
await fs.unlink(link);
|
|
421
|
+
await fs.symlink(target, link, LINK_TYPE);
|
|
422
|
+
fixed++;
|
|
423
|
+
} catch { /* skip */ }
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// CHANGELOG.md
|
|
429
|
+
const changelogLink = join(claudeDir, 'CHANGELOG.md');
|
|
430
|
+
if (!existsSync(changelogLink)) {
|
|
431
|
+
try {
|
|
432
|
+
if (process.platform === 'win32') {
|
|
433
|
+
// Junctions only work for directories; copy the file on Windows
|
|
434
|
+
await fs.copyFile(join(packagePath, 'CHANGELOG.md'), changelogLink);
|
|
435
|
+
} else {
|
|
436
|
+
await fs.symlink(join(relativePath, 'CHANGELOG.md'), changelogLink);
|
|
437
|
+
}
|
|
438
|
+
fixed++;
|
|
439
|
+
} catch { /* skip */ }
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const total = symlinks.length + 1;
|
|
443
|
+
if (fixed > 0) {
|
|
444
|
+
spinner.succeed(`Symlinks: fixed ${fixed}/${total}`);
|
|
445
|
+
} else {
|
|
446
|
+
spinner.succeed(`Symlinks: ${total}/${total} valid`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return { updated: fixed > 0, fixed, total };
|
|
450
|
+
} catch (error) {
|
|
451
|
+
spinner.fail(`Symlinks: ${error.message}`);
|
|
452
|
+
return { updated: false, fixed: 0, total: 0 };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ============================================================================
|
|
457
|
+
// FTS5 Backfill Safety-Net
|
|
458
|
+
// ============================================================================
|
|
459
|
+
//
|
|
460
|
+
// On upgrade paths, a project may already have episodes in
|
|
461
|
+
// `.claude/project-context/episodic-memory/index.json` but an empty FTS5
|
|
462
|
+
// `search.db`. This happens when episodes were produced before FTS5 was wired
|
|
463
|
+
// in, or when `search.db` was deleted for any reason. Fresh installs have
|
|
464
|
+
// zero episodes and will no-op through this check.
|
|
465
|
+
//
|
|
466
|
+
// Opt-out: pass `--no-fts5-backfill` (or set GAIA_SKIP_FTS5_BACKFILL=1).
|
|
467
|
+
|
|
468
|
+
async function maybeBackfillFts5() {
|
|
469
|
+
if (process.argv.includes('--no-fts5-backfill')
|
|
470
|
+
|| process.env.GAIA_SKIP_FTS5_BACKFILL === '1') {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const claudeDir = join(CWD, '.claude');
|
|
475
|
+
if (!existsSync(claudeDir)) return;
|
|
476
|
+
|
|
477
|
+
const memoryDir = join(claudeDir, 'project-context', 'episodic-memory');
|
|
478
|
+
const indexPath = join(memoryDir, 'index.json');
|
|
479
|
+
const dbPath = join(memoryDir, 'search.db');
|
|
480
|
+
|
|
481
|
+
if (!existsSync(indexPath)) return; // Fresh install — nothing to backfill
|
|
482
|
+
|
|
483
|
+
let total = 0;
|
|
484
|
+
try {
|
|
485
|
+
const idx = JSON.parse(await fs.readFile(indexPath, 'utf-8'));
|
|
486
|
+
total = Array.isArray(idx.episodes) ? idx.episodes.length : 0;
|
|
487
|
+
} catch {
|
|
488
|
+
return; // Unreadable index — let doctor handle it
|
|
489
|
+
}
|
|
490
|
+
if (total === 0) return; // No episodes to index
|
|
491
|
+
|
|
492
|
+
if (!existsSync(dbPath)) return; // doctor --fix will create it on first use
|
|
493
|
+
|
|
494
|
+
// Query FTS5 count via python3 subprocess (sqlite3 binary may not be on PATH
|
|
495
|
+
// on Windows; python3 is already a hard requirement for gaia-ops hooks).
|
|
496
|
+
const pyCmd = findPython();
|
|
497
|
+
if (!pyCmd) return; // Python missing — the health check will report it
|
|
498
|
+
|
|
499
|
+
const spinner = ora('Checking FTS5 backfill status...').start();
|
|
500
|
+
let indexed;
|
|
501
|
+
try {
|
|
502
|
+
const probeScript = join(memoryDir, '.gaia-fts5-probe.py');
|
|
503
|
+
const probeContent = `
|
|
504
|
+
import sqlite3, sys
|
|
505
|
+
try:
|
|
506
|
+
con = sqlite3.connect(sys.argv[1])
|
|
507
|
+
cur = con.execute("SELECT COUNT(*) FROM episodes_fts")
|
|
508
|
+
print(cur.fetchone()[0])
|
|
509
|
+
except Exception:
|
|
510
|
+
print(-1)
|
|
511
|
+
`;
|
|
512
|
+
await fs.writeFile(probeScript, probeContent);
|
|
513
|
+
try {
|
|
514
|
+
const { stdout } = await execAsync(
|
|
515
|
+
`${pyCmd} "${probeScript}" "${dbPath}"`,
|
|
516
|
+
{ timeout: 10000 }
|
|
517
|
+
);
|
|
518
|
+
indexed = parseInt(stdout.trim(), 10);
|
|
519
|
+
} finally {
|
|
520
|
+
try { await fs.unlink(probeScript); } catch { /* ignore */ }
|
|
521
|
+
}
|
|
522
|
+
} catch {
|
|
523
|
+
spinner.info('FTS5 probe skipped (sqlite3/python issue)');
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!Number.isFinite(indexed) || indexed < 0) {
|
|
528
|
+
spinner.info('FTS5 probe inconclusive (table may not exist yet)');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// If indexed is already >=90% of total, no backfill needed — matches doctor
|
|
533
|
+
// threshold exactly so we don't loop users through unnecessary work.
|
|
534
|
+
if (indexed / total >= 0.9) {
|
|
535
|
+
spinner.succeed(`FTS5 backfill: ${indexed}/${total} episodes indexed (ok)`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
spinner.text = `FTS5 backfill: rebuilding ${total} episodes (had ${indexed})...`;
|
|
540
|
+
|
|
541
|
+
// Invoke backfill via the gaia CLI doctor --fix. We call `python3 bin/gaia
|
|
542
|
+
// doctor --fix` from the installed package directory, with CWD set to the
|
|
543
|
+
// consumer project so doctor locates the right .claude/ tree.
|
|
544
|
+
const packageDir = join(__dirname, '..');
|
|
545
|
+
const gaiaEntry = join(packageDir, 'bin', 'gaia');
|
|
546
|
+
|
|
547
|
+
if (!existsSync(gaiaEntry)) {
|
|
548
|
+
spinner.warn('FTS5 backfill skipped (bin/gaia not found)');
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
const { stdout, stderr } = await execAsync(
|
|
554
|
+
`${pyCmd} "${gaiaEntry}" doctor --fix`,
|
|
555
|
+
{ timeout: 120000, cwd: CWD }
|
|
556
|
+
);
|
|
557
|
+
if (VERBOSE) {
|
|
558
|
+
if (stdout) console.log(chalk.gray(stdout));
|
|
559
|
+
if (stderr) console.log(chalk.yellow(stderr));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Re-probe to report outcome.
|
|
563
|
+
const probeScript = join(memoryDir, '.gaia-fts5-probe.py');
|
|
564
|
+
const probeContent = `
|
|
565
|
+
import sqlite3, sys
|
|
566
|
+
try:
|
|
567
|
+
con = sqlite3.connect(sys.argv[1])
|
|
568
|
+
cur = con.execute("SELECT COUNT(*) FROM episodes_fts")
|
|
569
|
+
print(cur.fetchone()[0])
|
|
570
|
+
except Exception:
|
|
571
|
+
print(-1)
|
|
572
|
+
`;
|
|
573
|
+
await fs.writeFile(probeScript, probeContent);
|
|
574
|
+
let newIndexed = -1;
|
|
575
|
+
try {
|
|
576
|
+
const { stdout: newOut } = await execAsync(
|
|
577
|
+
`${pyCmd} "${probeScript}" "${dbPath}"`,
|
|
578
|
+
{ timeout: 10000 }
|
|
579
|
+
);
|
|
580
|
+
newIndexed = parseInt(newOut.trim(), 10);
|
|
581
|
+
} finally {
|
|
582
|
+
try { await fs.unlink(probeScript); } catch { /* ignore */ }
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (Number.isFinite(newIndexed) && newIndexed > indexed) {
|
|
586
|
+
spinner.succeed(`FTS5 backfill: rebuilt ${newIndexed}/${total} episodes`);
|
|
587
|
+
} else {
|
|
588
|
+
spinner.warn(`FTS5 backfill completed but index still at ${newIndexed}/${total}`);
|
|
589
|
+
}
|
|
590
|
+
} catch (err) {
|
|
591
|
+
spinner.warn(`FTS5 backfill skipped: ${err.message || 'unknown error'}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ============================================================================
|
|
596
|
+
// Post-Update Verification
|
|
597
|
+
// ============================================================================
|
|
598
|
+
|
|
599
|
+
async function runVerification() {
|
|
600
|
+
const spinner = ora('Verifying installation health...').start();
|
|
601
|
+
const checks = [];
|
|
602
|
+
const issues = [];
|
|
603
|
+
|
|
604
|
+
// 1. Hooks exist and are reachable
|
|
605
|
+
const hookFiles = ['pre_tool_use.py', 'post_tool_use.py', 'subagent_stop.py'];
|
|
606
|
+
for (const hook of hookFiles) {
|
|
607
|
+
const path = join(CWD, '.claude', 'hooks', hook);
|
|
608
|
+
if (existsSync(path)) {
|
|
609
|
+
checks.push({ name: hook, ok: true });
|
|
610
|
+
} else {
|
|
611
|
+
checks.push({ name: hook, ok: false });
|
|
612
|
+
issues.push(`Hook missing: .claude/hooks/${hook}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// 2. Python available (try python3 first, fall back to python on Windows)
|
|
617
|
+
{
|
|
618
|
+
const pyCmd = findPython();
|
|
619
|
+
if (pyCmd) {
|
|
620
|
+
const { stdout } = await execAsync(`${pyCmd} --version`, { timeout: 5000 });
|
|
621
|
+
checks.push({ name: 'python3', ok: true, detail: stdout.trim() });
|
|
622
|
+
} else {
|
|
623
|
+
checks.push({ name: 'python3', ok: false });
|
|
624
|
+
issues.push('Python 3 not found (required for hooks)');
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// 3. project-context.json exists and is valid
|
|
629
|
+
const ctxPath = join(CWD, '.claude', 'project-context', 'project-context.json');
|
|
630
|
+
if (existsSync(ctxPath)) {
|
|
631
|
+
try {
|
|
632
|
+
const ctx = JSON.parse(await fs.readFile(ctxPath, 'utf-8'));
|
|
633
|
+
const sections = Object.keys(ctx.sections || {}).length;
|
|
634
|
+
checks.push({ name: 'project-context.json', ok: sections >= 3, detail: `${sections} sections` });
|
|
635
|
+
if (sections < 3) issues.push('project-context.json has fewer than 3 sections');
|
|
636
|
+
} catch {
|
|
637
|
+
checks.push({ name: 'project-context.json', ok: false });
|
|
638
|
+
issues.push('project-context.json is invalid JSON');
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
checks.push({ name: 'project-context.json', ok: false });
|
|
642
|
+
issues.push('project-context.json not found (run gaia-scan)');
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// 4. Config files accessible
|
|
646
|
+
const configFiles = ['git_standards.json', 'universal-rules.json', 'surface-routing.json'];
|
|
647
|
+
for (const cfg of configFiles) {
|
|
648
|
+
const path = join(CWD, '.claude', 'config', cfg);
|
|
649
|
+
if (existsSync(path)) {
|
|
650
|
+
checks.push({ name: cfg, ok: true });
|
|
651
|
+
} else {
|
|
652
|
+
checks.push({ name: cfg, ok: false });
|
|
653
|
+
if (VERBOSE) issues.push(`Config missing: .claude/config/${cfg}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// 5. Agent definitions accessible
|
|
658
|
+
const agentFiles = ['gaia-orchestrator.md', 'gaia-operator.md', 'terraform-architect.md', 'gitops-operator.md', 'cloud-troubleshooter.md', 'developer.md', 'gaia-system.md', 'gaia-planner.md'];
|
|
659
|
+
let agentsOk = 0;
|
|
660
|
+
for (const agent of agentFiles) {
|
|
661
|
+
if (existsSync(join(CWD, '.claude', 'agents', agent))) agentsOk++;
|
|
662
|
+
}
|
|
663
|
+
checks.push({ name: 'agent definitions', ok: agentsOk === agentFiles.length, detail: `${agentsOk}/${agentFiles.length}` });
|
|
664
|
+
if (agentsOk < agentFiles.length) issues.push(`${agentFiles.length - agentsOk} agent definition(s) missing`);
|
|
665
|
+
|
|
666
|
+
// 6. hooks.json exists (hooks are auto-discovered from hooks directory)
|
|
667
|
+
const hooksJsonPath = join(CWD, '.claude', 'hooks', 'hooks.json');
|
|
668
|
+
if (existsSync(hooksJsonPath)) {
|
|
669
|
+
try {
|
|
670
|
+
const hooksData = JSON.parse(await fs.readFile(hooksJsonPath, 'utf-8'));
|
|
671
|
+
const hasHooks = hooksData.hooks && Object.keys(hooksData.hooks).length > 0;
|
|
672
|
+
checks.push({ name: 'hooks.json', ok: hasHooks });
|
|
673
|
+
if (!hasHooks) issues.push('hooks.json has no hooks configured');
|
|
674
|
+
} catch {
|
|
675
|
+
checks.push({ name: 'hooks.json', ok: false });
|
|
676
|
+
issues.push('hooks.json is invalid');
|
|
677
|
+
}
|
|
678
|
+
} else {
|
|
679
|
+
checks.push({ name: 'hooks.json', ok: false });
|
|
680
|
+
issues.push('hooks.json not found (hooks symlink may be broken)');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const passed = checks.filter(c => c.ok).length;
|
|
684
|
+
const total = checks.length;
|
|
685
|
+
|
|
686
|
+
if (issues.length === 0) {
|
|
687
|
+
spinner.succeed(`Health check: ${passed}/${total} passed`);
|
|
688
|
+
} else {
|
|
689
|
+
spinner.warn(`Health check: ${passed}/${total} passed, ${issues.length} issue(s)`);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return { checks, issues, passed, total };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ============================================================================
|
|
696
|
+
// Main
|
|
697
|
+
// ============================================================================
|
|
698
|
+
|
|
699
|
+
async function runFreshInstall() {
|
|
700
|
+
const packageDir = join(__dirname, '..');
|
|
701
|
+
const scanScript = join(packageDir, 'bin', 'gaia-scan.py');
|
|
702
|
+
const { current } = await detectVersions();
|
|
703
|
+
|
|
704
|
+
console.log(chalk.cyan(`\n gaia-ops ${chalk.green(current)} — fresh install\n`));
|
|
705
|
+
|
|
706
|
+
// 1. Check Python 3 is available (try python3, then python)
|
|
707
|
+
const spinner = ora('Checking Python 3...').start();
|
|
708
|
+
const pyCmd = findPython();
|
|
709
|
+
if (pyCmd) {
|
|
710
|
+
spinner.succeed(`Python 3 found (${pyCmd})`);
|
|
711
|
+
} else {
|
|
712
|
+
spinner.warn('Python 3 not found — skipping project setup');
|
|
713
|
+
console.log(chalk.gray(' Install Python 3.9+ and run: npx gaia-scan\n'));
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// 2. Run gaia-scan --npm-postinstall
|
|
718
|
+
const scanSpinner = ora('Running gaia-scan...').start();
|
|
719
|
+
try {
|
|
720
|
+
const { stdout, stderr } = await execAsync(
|
|
721
|
+
`${pyCmd} "${scanScript}" --npm-postinstall --root "${CWD}"`,
|
|
722
|
+
{ timeout: 60000 }
|
|
723
|
+
);
|
|
724
|
+
scanSpinner.succeed('Project scanned and configured');
|
|
725
|
+
if (VERBOSE && stdout) console.log(chalk.gray(stdout));
|
|
726
|
+
if (VERBOSE && stderr) console.log(chalk.yellow(stderr));
|
|
727
|
+
} catch (error) {
|
|
728
|
+
scanSpinner.warn('gaia-scan encountered issues (non-fatal)');
|
|
729
|
+
if (VERBOSE && error.stderr) console.log(chalk.gray(error.stderr));
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// 3. Create plugin-registry.json (in .claude/, same path Python hooks expect)
|
|
733
|
+
try {
|
|
734
|
+
const claudeDirPath = join(CWD, '.claude');
|
|
735
|
+
if (!existsSync(claudeDirPath)) {
|
|
736
|
+
await fs.mkdir(claudeDirPath, { recursive: true });
|
|
737
|
+
}
|
|
738
|
+
const registryPath = join(claudeDirPath, 'plugin-registry.json');
|
|
739
|
+
const registry = {
|
|
740
|
+
installed: [{ name: 'gaia-ops', version: current || 'unknown' }],
|
|
741
|
+
source: 'npm-postinstall',
|
|
742
|
+
};
|
|
743
|
+
await fs.writeFile(registryPath, JSON.stringify(registry, null, 2) + '\n');
|
|
744
|
+
} catch {
|
|
745
|
+
// Non-fatal — plugin-registry is a convenience, not critical
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// 4. Merge permissions into settings.local.json (same approach as plugin mode)
|
|
749
|
+
await updateLocalPermissions();
|
|
750
|
+
|
|
751
|
+
// 5. Merge hooks into settings.local.json (npm mode — Claude Code reads hooks from settings, not hooks.json)
|
|
752
|
+
await updateLocalHooks();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
async function main() {
|
|
756
|
+
process.stderr.write('[DEPRECATED] gaia-update.js is deprecated. Use: python3 bin/gaia update\n[DEPRECATED] Migration guide: see CHANGELOG.md\n');
|
|
757
|
+
|
|
758
|
+
const claudeDir = join(CWD, '.claude');
|
|
759
|
+
const isUpdate = existsSync(claudeDir);
|
|
760
|
+
|
|
761
|
+
if (!isUpdate) {
|
|
762
|
+
// First-time install — run gaia-scan to bootstrap everything
|
|
763
|
+
await runFreshInstall();
|
|
764
|
+
} else {
|
|
765
|
+
// Version info
|
|
766
|
+
const { previous, current } = await detectVersions();
|
|
767
|
+
const versionLine = previous && previous !== current
|
|
768
|
+
? `${chalk.gray(previous)} → ${chalk.green(current)}`
|
|
769
|
+
: chalk.green(current);
|
|
770
|
+
|
|
771
|
+
console.log(chalk.cyan(`\n gaia-ops update ${versionLine}\n`));
|
|
772
|
+
|
|
773
|
+
// Step 1-4: Update files
|
|
774
|
+
await updateSettingsJson();
|
|
775
|
+
await updateLocalPermissions();
|
|
776
|
+
await updateLocalHooks();
|
|
777
|
+
await updateSymlinks();
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Ensure plugin-registry.json exists in .claude/ (both fresh and update)
|
|
781
|
+
try {
|
|
782
|
+
const registryPath = join(CWD, '.claude', 'plugin-registry.json');
|
|
783
|
+
if (!existsSync(registryPath)) {
|
|
784
|
+
const { current } = await detectVersions();
|
|
785
|
+
const registry = {
|
|
786
|
+
installed: [{ name: 'gaia-ops', version: current || 'unknown' }],
|
|
787
|
+
source: 'npm-postinstall',
|
|
788
|
+
};
|
|
789
|
+
await fs.writeFile(registryPath, JSON.stringify(registry, null, 2) + '\n');
|
|
790
|
+
}
|
|
791
|
+
} catch { /* non-fatal */ }
|
|
792
|
+
|
|
793
|
+
// FTS5 backfill safety-net (no-op on fresh install; only fires when
|
|
794
|
+
// episodes exist in index.json but search.db is under-indexed)
|
|
795
|
+
await maybeBackfillFts5();
|
|
796
|
+
|
|
797
|
+
// Verify (runs for both fresh install and update)
|
|
798
|
+
const { issues, passed, total } = await runVerification();
|
|
799
|
+
|
|
800
|
+
console.log('');
|
|
801
|
+
if (issues.length > 0) {
|
|
802
|
+
console.log(chalk.yellow(` ${issues.length} issue(s) found:`));
|
|
803
|
+
for (const issue of issues) {
|
|
804
|
+
console.log(chalk.yellow(` - ${issue}`));
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
console.log(chalk.green(' Everything up to date'));
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
console.log(chalk.gray(`\n Health: ${passed}/${total} checks passed\n`));
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
main().catch(error => {
|
|
814
|
+
console.error(chalk.red(`\n Update failed: ${error.message}\n`));
|
|
815
|
+
process.exit(0); // Never fail npm install
|
|
816
|
+
});
|