@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,929 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @jaguilar87/gaia - Skills and Injection Diagnostic CLI
|
|
5
|
+
*
|
|
6
|
+
* Validates:
|
|
7
|
+
* 1. Skills catalog integrity (SKILL.md + frontmatter + content quality)
|
|
8
|
+
* 2. Agent -> skill wiring (frontmatter skills references)
|
|
9
|
+
* 3. Injection wiring (hooks + settings + runtime paths)
|
|
10
|
+
* 4. Known gap patterns (legacy tests vs current implementation)
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* npx gaia-skills-diagnose
|
|
14
|
+
* npx gaia-skills-diagnose --run-tests
|
|
15
|
+
* npx gaia-skills-diagnose --json
|
|
16
|
+
* npx gaia-skills-diagnose --strict
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import { findPython } from "./python-detect.js";
|
|
22
|
+
import { spawnSync } from "child_process";
|
|
23
|
+
import { fileURLToPath } from "url";
|
|
24
|
+
import chalk from "chalk";
|
|
25
|
+
import yargs from "yargs";
|
|
26
|
+
import { hideBin } from "yargs/helpers";
|
|
27
|
+
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = path.dirname(__filename);
|
|
30
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
31
|
+
|
|
32
|
+
const META_AGENTS = new Set(["gaia-orchestrator", "Explore", "Plan", "general-purpose", "claude-code-guide"]);
|
|
33
|
+
const CONTEXT_INJECTED_AGENTS = new Set([
|
|
34
|
+
"gaia-operator",
|
|
35
|
+
"gaia-system",
|
|
36
|
+
"terraform-architect",
|
|
37
|
+
"gitops-operator",
|
|
38
|
+
"cloud-troubleshooter",
|
|
39
|
+
"developer",
|
|
40
|
+
]);
|
|
41
|
+
const REQUIRED_PROJECT_SKILLS = ["agent-protocol", "context-updater"];
|
|
42
|
+
|
|
43
|
+
const SEVERITY_WEIGHT = {
|
|
44
|
+
critical: 25,
|
|
45
|
+
high: 12,
|
|
46
|
+
medium: 5,
|
|
47
|
+
info: 1,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function parseFrontmatter(markdown) {
|
|
51
|
+
if (!markdown.startsWith("---")) return {};
|
|
52
|
+
const endIdx = markdown.indexOf("\n---", 3);
|
|
53
|
+
if (endIdx === -1) return {};
|
|
54
|
+
|
|
55
|
+
const block = markdown.slice(3, endIdx).trim();
|
|
56
|
+
const result = {};
|
|
57
|
+
let currentKey = null;
|
|
58
|
+
let currentList = null;
|
|
59
|
+
|
|
60
|
+
for (const rawLine of block.split("\n")) {
|
|
61
|
+
const line = rawLine.trim();
|
|
62
|
+
if (!line || line.startsWith("#")) continue;
|
|
63
|
+
|
|
64
|
+
if (line.startsWith("- ") && currentKey && Array.isArray(currentList)) {
|
|
65
|
+
currentList.push(line.slice(2).trim());
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (line.includes(":")) {
|
|
70
|
+
if (currentKey && Array.isArray(currentList)) {
|
|
71
|
+
result[currentKey] = currentList;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const [keyPart, ...rest] = line.split(":");
|
|
75
|
+
const key = keyPart.trim();
|
|
76
|
+
const value = rest.join(":").trim();
|
|
77
|
+
|
|
78
|
+
if (value) {
|
|
79
|
+
result[key] = value;
|
|
80
|
+
currentKey = key;
|
|
81
|
+
currentList = null;
|
|
82
|
+
} else {
|
|
83
|
+
currentKey = key;
|
|
84
|
+
currentList = [];
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (currentKey && Array.isArray(currentList)) {
|
|
90
|
+
result[currentKey] = currentList;
|
|
91
|
+
currentKey = null;
|
|
92
|
+
currentList = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (currentKey && Array.isArray(currentList)) {
|
|
97
|
+
result[currentKey] = currentList;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function stripFrontmatter(markdown) {
|
|
104
|
+
if (!markdown.startsWith("---")) return markdown.trim();
|
|
105
|
+
const endIdx = markdown.indexOf("\n---", 3);
|
|
106
|
+
if (endIdx === -1) return markdown.trim();
|
|
107
|
+
return markdown.slice(endIdx + 4).trim();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function readText(filePath) {
|
|
111
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function exists(filePath) {
|
|
115
|
+
return fs.existsSync(filePath);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function listDirectories(dir) {
|
|
119
|
+
if (!exists(dir)) return [];
|
|
120
|
+
return fs
|
|
121
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
122
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith(".") && d.name !== "__pycache__")
|
|
123
|
+
.map((d) => d.name);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function listMarkdownFiles(dir) {
|
|
127
|
+
if (!exists(dir)) return [];
|
|
128
|
+
return fs
|
|
129
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
130
|
+
.filter((d) => d.isFile() && d.name.endsWith(".md") && !d.name.toUpperCase().includes("README"))
|
|
131
|
+
.map((d) => path.join(dir, d.name));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function collectFilesRecursively(root, matcher) {
|
|
135
|
+
const files = [];
|
|
136
|
+
if (!exists(root)) return files;
|
|
137
|
+
|
|
138
|
+
const stack = [root];
|
|
139
|
+
while (stack.length > 0) {
|
|
140
|
+
const current = stack.pop();
|
|
141
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
142
|
+
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
if (entry.name.startsWith(".") || entry.name === "__pycache__") continue;
|
|
145
|
+
const absolute = path.join(current, entry.name);
|
|
146
|
+
if (entry.isDirectory()) {
|
|
147
|
+
stack.push(absolute);
|
|
148
|
+
} else if (matcher(absolute)) {
|
|
149
|
+
files.push(absolute);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return files;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function addFinding(findings, finding) {
|
|
158
|
+
findings.push({
|
|
159
|
+
severity: finding.severity,
|
|
160
|
+
code: finding.code,
|
|
161
|
+
title: finding.title,
|
|
162
|
+
detail: finding.detail || "",
|
|
163
|
+
evidence: finding.evidence || "",
|
|
164
|
+
remediation: finding.remediation || "",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function resolveRuntimePaths(projectRoot) {
|
|
169
|
+
const claudeDir = path.join(projectRoot, ".claude");
|
|
170
|
+
const inProject = exists(claudeDir);
|
|
171
|
+
|
|
172
|
+
const runtime = {
|
|
173
|
+
projectRoot,
|
|
174
|
+
packageRoot: PACKAGE_ROOT,
|
|
175
|
+
claudeDir,
|
|
176
|
+
inProject,
|
|
177
|
+
agentsDir: inProject && exists(path.join(claudeDir, "agents"))
|
|
178
|
+
? path.join(claudeDir, "agents")
|
|
179
|
+
: path.join(PACKAGE_ROOT, "agents"),
|
|
180
|
+
skillsDir: inProject && exists(path.join(claudeDir, "skills"))
|
|
181
|
+
? path.join(claudeDir, "skills")
|
|
182
|
+
: path.join(PACKAGE_ROOT, "skills"),
|
|
183
|
+
hooksDir: inProject && exists(path.join(claudeDir, "hooks"))
|
|
184
|
+
? path.join(claudeDir, "hooks")
|
|
185
|
+
: path.join(PACKAGE_ROOT, "hooks"),
|
|
186
|
+
settingsPath: exists(path.join(claudeDir, "settings.json"))
|
|
187
|
+
? path.join(claudeDir, "settings.json")
|
|
188
|
+
: path.join(claudeDir, "settings.local.json"),
|
|
189
|
+
testsDir: path.join(PACKAGE_ROOT, "tests"),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
runtime.preToolUsePath = exists(path.join(runtime.hooksDir, "pre_tool_use.py"))
|
|
193
|
+
? path.join(runtime.hooksDir, "pre_tool_use.py")
|
|
194
|
+
: null;
|
|
195
|
+
|
|
196
|
+
runtime.taskValidatorPath = path.join(PACKAGE_ROOT, "hooks", "modules", "tools", "task_validator.py");
|
|
197
|
+
runtime.rootClaudeMd = path.join(PACKAGE_ROOT, "CLAUDE.md");
|
|
198
|
+
|
|
199
|
+
return runtime;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function validateSkillsCatalog(ctx, findings, checks) {
|
|
203
|
+
const skillsDir = ctx.skillsDir;
|
|
204
|
+
if (!exists(skillsDir)) {
|
|
205
|
+
addFinding(findings, {
|
|
206
|
+
severity: "critical",
|
|
207
|
+
code: "SKILLS_DIR_MISSING",
|
|
208
|
+
title: "Skills directory not found",
|
|
209
|
+
detail: "No runtime skills directory was found.",
|
|
210
|
+
evidence: skillsDir,
|
|
211
|
+
remediation: "Ensure .claude/skills is linked or package skills/ exists.",
|
|
212
|
+
});
|
|
213
|
+
checks.push({ name: "skills-catalog", ok: false, detail: "skills directory missing" });
|
|
214
|
+
return {
|
|
215
|
+
skillNames: new Set(),
|
|
216
|
+
skillBodies: new Map(),
|
|
217
|
+
readmeContent: "",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const skillDirs = listDirectories(skillsDir);
|
|
222
|
+
const skillNames = new Set();
|
|
223
|
+
const skillBodies = new Map();
|
|
224
|
+
let validSkills = 0;
|
|
225
|
+
|
|
226
|
+
for (const skillName of skillDirs) {
|
|
227
|
+
skillNames.add(skillName);
|
|
228
|
+
const skillMd = path.join(skillsDir, skillName, "SKILL.md");
|
|
229
|
+
|
|
230
|
+
if (!exists(skillMd)) {
|
|
231
|
+
addFinding(findings, {
|
|
232
|
+
severity: "critical",
|
|
233
|
+
code: "SKILL_MD_MISSING",
|
|
234
|
+
title: "Skill directory missing SKILL.md",
|
|
235
|
+
detail: `Skill '${skillName}' does not contain SKILL.md.`,
|
|
236
|
+
evidence: skillMd,
|
|
237
|
+
remediation: "Create SKILL.md in each skill directory.",
|
|
238
|
+
});
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const content = readText(skillMd);
|
|
243
|
+
const fm = parseFrontmatter(content);
|
|
244
|
+
const stripped = stripFrontmatter(content);
|
|
245
|
+
skillBodies.set(skillName, content);
|
|
246
|
+
|
|
247
|
+
if (Object.keys(fm).length === 0) {
|
|
248
|
+
addFinding(findings, {
|
|
249
|
+
severity: "high",
|
|
250
|
+
code: "SKILL_FRONTMATTER_MISSING",
|
|
251
|
+
title: "Skill without frontmatter",
|
|
252
|
+
detail: `Skill '${skillName}' has no parseable frontmatter.`,
|
|
253
|
+
evidence: skillMd,
|
|
254
|
+
remediation: "Add YAML frontmatter with at least name and description.",
|
|
255
|
+
});
|
|
256
|
+
} else {
|
|
257
|
+
if (fm.name !== skillName) {
|
|
258
|
+
addFinding(findings, {
|
|
259
|
+
severity: "high",
|
|
260
|
+
code: "SKILL_NAME_MISMATCH",
|
|
261
|
+
title: "Skill frontmatter name mismatch",
|
|
262
|
+
detail: `Frontmatter name '${fm.name || "undefined"}' differs from directory '${skillName}'.`,
|
|
263
|
+
evidence: skillMd,
|
|
264
|
+
remediation: "Align frontmatter name with the directory name.",
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!fm.description || String(fm.description).trim().length < 10) {
|
|
269
|
+
addFinding(findings, {
|
|
270
|
+
severity: "medium",
|
|
271
|
+
code: "SKILL_DESCRIPTION_WEAK",
|
|
272
|
+
title: "Skill description missing or too short",
|
|
273
|
+
detail: `Skill '${skillName}' should have a meaningful description.`,
|
|
274
|
+
evidence: skillMd,
|
|
275
|
+
remediation: "Set description in frontmatter with >10 characters.",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (stripped.length < 100) {
|
|
281
|
+
addFinding(findings, {
|
|
282
|
+
severity: "medium",
|
|
283
|
+
code: "SKILL_CONTENT_TOO_SHORT",
|
|
284
|
+
title: "Skill content too short",
|
|
285
|
+
detail: `Skill '${skillName}' content is only ${stripped.length} characters after frontmatter.`,
|
|
286
|
+
evidence: skillMd,
|
|
287
|
+
remediation: "Expand operational instructions in SKILL.md.",
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
validSkills += 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const readmePath = path.join(skillsDir, "README.md");
|
|
295
|
+
const readmeContent = exists(readmePath) ? readText(readmePath) : "";
|
|
296
|
+
|
|
297
|
+
checks.push({
|
|
298
|
+
name: "skills-catalog",
|
|
299
|
+
ok: skillDirs.length > 0,
|
|
300
|
+
detail: `${validSkills}/${skillDirs.length} skill directories inspected`,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
return { skillNames, skillBodies, readmeContent };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function validateAgentSkillWiring(ctx, data, findings, checks) {
|
|
307
|
+
const agentsDir = ctx.agentsDir;
|
|
308
|
+
if (!exists(agentsDir)) {
|
|
309
|
+
addFinding(findings, {
|
|
310
|
+
severity: "critical",
|
|
311
|
+
code: "AGENTS_DIR_MISSING",
|
|
312
|
+
title: "Agents directory not found",
|
|
313
|
+
detail: "No runtime agents directory was found.",
|
|
314
|
+
evidence: agentsDir,
|
|
315
|
+
remediation: "Ensure .claude/agents is linked or package agents/ exists.",
|
|
316
|
+
});
|
|
317
|
+
checks.push({ name: "agent-skill-wiring", ok: false, detail: "agents directory missing" });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const agentFiles = listMarkdownFiles(agentsDir);
|
|
322
|
+
if (agentFiles.length === 0) {
|
|
323
|
+
addFinding(findings, {
|
|
324
|
+
severity: "critical",
|
|
325
|
+
code: "AGENT_FILES_MISSING",
|
|
326
|
+
title: "No agent definition files found",
|
|
327
|
+
detail: "Expected at least one agent markdown file.",
|
|
328
|
+
evidence: agentsDir,
|
|
329
|
+
remediation: "Restore agent definition files (*.md).",
|
|
330
|
+
});
|
|
331
|
+
checks.push({ name: "agent-skill-wiring", ok: false, detail: "no agent files found" });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const referenced = new Set();
|
|
336
|
+
let checkedAgents = 0;
|
|
337
|
+
|
|
338
|
+
for (const agentPath of agentFiles) {
|
|
339
|
+
const content = readText(agentPath);
|
|
340
|
+
const fm = parseFrontmatter(content);
|
|
341
|
+
const agentName = path.basename(agentPath, ".md");
|
|
342
|
+
const isMeta = META_AGENTS.has(agentName);
|
|
343
|
+
const rawSkills = fm.skills;
|
|
344
|
+
const skills = Array.isArray(rawSkills)
|
|
345
|
+
? rawSkills
|
|
346
|
+
: typeof rawSkills === "string" && rawSkills.trim()
|
|
347
|
+
? rawSkills.split(",").map((v) => v.trim()).filter(Boolean)
|
|
348
|
+
: [];
|
|
349
|
+
|
|
350
|
+
if (Object.keys(fm).length === 0) {
|
|
351
|
+
addFinding(findings, {
|
|
352
|
+
severity: "high",
|
|
353
|
+
code: "AGENT_FRONTMATTER_MISSING",
|
|
354
|
+
title: "Agent missing parseable frontmatter",
|
|
355
|
+
detail: `Agent '${agentName}' has no parseable frontmatter.`,
|
|
356
|
+
evidence: agentPath,
|
|
357
|
+
remediation: "Add YAML frontmatter with name, description, tools, and skills.",
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (!isMeta && skills.length === 0) {
|
|
362
|
+
addFinding(findings, {
|
|
363
|
+
severity: "high",
|
|
364
|
+
code: "AGENT_SKILLS_EMPTY",
|
|
365
|
+
title: "Project agent missing skills",
|
|
366
|
+
detail: `Agent '${agentName}' has an empty or missing skills list.`,
|
|
367
|
+
evidence: agentPath,
|
|
368
|
+
remediation: "Add required skills in frontmatter to enable runtime injection.",
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
for (const skill of skills) {
|
|
373
|
+
referenced.add(skill);
|
|
374
|
+
if (!data.skillNames.has(skill)) {
|
|
375
|
+
addFinding(findings, {
|
|
376
|
+
severity: "critical",
|
|
377
|
+
code: "AGENT_SKILL_NOT_FOUND",
|
|
378
|
+
title: "Agent references non-existent skill",
|
|
379
|
+
detail: `Agent '${agentName}' references missing skill '${skill}'.`,
|
|
380
|
+
evidence: agentPath,
|
|
381
|
+
remediation: "Create the skill directory or fix the frontmatter reference.",
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!isMeta && CONTEXT_INJECTED_AGENTS.has(agentName)) {
|
|
387
|
+
for (const required of REQUIRED_PROJECT_SKILLS) {
|
|
388
|
+
if (!skills.includes(required)) {
|
|
389
|
+
addFinding(findings, {
|
|
390
|
+
severity: "high",
|
|
391
|
+
code: "AGENT_REQUIRED_SKILL_MISSING",
|
|
392
|
+
title: "Project agent missing required baseline skill",
|
|
393
|
+
detail: `Agent '${agentName}' is missing '${required}'.`,
|
|
394
|
+
evidence: agentPath,
|
|
395
|
+
remediation: `Add '${required}' to the skills list.`,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const skillName of data.skillNames) {
|
|
402
|
+
if (content.includes(`skills/${skillName}`)) {
|
|
403
|
+
referenced.add(skillName);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
checkedAgents += 1;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
for (const [skillName, skillBody] of data.skillBodies.entries()) {
|
|
411
|
+
for (const otherSkill of data.skillNames) {
|
|
412
|
+
if (skillName === otherSkill) continue;
|
|
413
|
+
if (skillBody.includes(`skills/${otherSkill}`) || skillBody.includes(`/${otherSkill}/`)) {
|
|
414
|
+
referenced.add(otherSkill);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
for (const skillName of data.skillNames) {
|
|
420
|
+
if (data.readmeContent.includes(skillName)) {
|
|
421
|
+
referenced.add(skillName);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Scan root CLAUDE.md for skills/ references (orchestrator-consumed skills)
|
|
426
|
+
const projectClaudeMd = path.join(ctx.projectRoot, "CLAUDE.md");
|
|
427
|
+
if (exists(projectClaudeMd)) {
|
|
428
|
+
const claudeMdContent = readText(projectClaudeMd);
|
|
429
|
+
for (const skillName of data.skillNames) {
|
|
430
|
+
if (claudeMdContent.includes(`skills/${skillName}`)) {
|
|
431
|
+
referenced.add(skillName);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const orphanSkills = [...data.skillNames].filter((s) => !referenced.has(s));
|
|
437
|
+
if (orphanSkills.length > 0) {
|
|
438
|
+
addFinding(findings, {
|
|
439
|
+
severity: "medium",
|
|
440
|
+
code: "ORPHAN_SKILLS",
|
|
441
|
+
title: "Skills not referenced by any agent or skill",
|
|
442
|
+
detail: `Found ${orphanSkills.length} orphan skills.`,
|
|
443
|
+
evidence: orphanSkills.join(", "),
|
|
444
|
+
remediation: "Reference these skills from agents/skills README or remove deprecated skills.",
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
checks.push({
|
|
449
|
+
name: "agent-skill-wiring",
|
|
450
|
+
ok: checkedAgents > 0,
|
|
451
|
+
detail: `${checkedAgents} agents scanned, ${referenced.size} referenced skills`,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function hookMatcherContains(hooksArray, matcher, fragment) {
|
|
456
|
+
if (!Array.isArray(hooksArray)) return false;
|
|
457
|
+
for (const entry of hooksArray) {
|
|
458
|
+
if (!entry || entry.matcher !== matcher || !Array.isArray(entry.hooks)) continue;
|
|
459
|
+
for (const hook of entry.hooks) {
|
|
460
|
+
if (hook?.type === "command" && String(hook.command || "").includes(fragment)) {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function validateInjectionWiring(ctx, findings, checks) {
|
|
469
|
+
let ok = true;
|
|
470
|
+
const preToolUsePath = ctx.preToolUsePath;
|
|
471
|
+
|
|
472
|
+
if (!preToolUsePath) {
|
|
473
|
+
addFinding(findings, {
|
|
474
|
+
severity: "critical",
|
|
475
|
+
code: "PRE_TOOL_USE_MISSING",
|
|
476
|
+
title: "pre_tool_use hook not found",
|
|
477
|
+
detail: "Task context injection hook is missing.",
|
|
478
|
+
evidence: path.join(ctx.hooksDir, "pre_tool_use.py"),
|
|
479
|
+
remediation: "Restore pre_tool_use.py in hooks directory.",
|
|
480
|
+
});
|
|
481
|
+
ok = false;
|
|
482
|
+
} else {
|
|
483
|
+
const content = readText(preToolUsePath);
|
|
484
|
+
|
|
485
|
+
if (!content.includes("build_project_context")) {
|
|
486
|
+
addFinding(findings, {
|
|
487
|
+
severity: "high",
|
|
488
|
+
code: "CONTEXT_INJECTION_FUNCTION_MISSING",
|
|
489
|
+
title: "Context injection function missing",
|
|
490
|
+
detail: "build_project_context() not found in pre_tool_use.py.",
|
|
491
|
+
evidence: preToolUsePath,
|
|
492
|
+
remediation: "Reintroduce or repair context injection handler.",
|
|
493
|
+
});
|
|
494
|
+
ok = false;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (!content.includes("updatedInput")) {
|
|
498
|
+
addFinding(findings, {
|
|
499
|
+
severity: "high",
|
|
500
|
+
code: "UPDATED_INPUT_NOT_EMITTED",
|
|
501
|
+
title: "Hook may not return updated input",
|
|
502
|
+
detail: "updatedInput marker not found in pre_tool_use.py.",
|
|
503
|
+
evidence: preToolUsePath,
|
|
504
|
+
remediation: "Ensure modified Task prompts are returned with updatedInput.",
|
|
505
|
+
});
|
|
506
|
+
ok = false;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (ctx.inProject) {
|
|
512
|
+
for (const name of ["agents", "skills", "hooks"]) {
|
|
513
|
+
const target = path.join(ctx.claudeDir, name);
|
|
514
|
+
if (!exists(target)) {
|
|
515
|
+
addFinding(findings, {
|
|
516
|
+
severity: "high",
|
|
517
|
+
code: "RUNTIME_DIR_MISSING",
|
|
518
|
+
title: "Missing runtime .claude directory",
|
|
519
|
+
detail: `Missing .claude/${name}, runtime injection may fail.`,
|
|
520
|
+
evidence: target,
|
|
521
|
+
remediation: "Run gaia-scan to recreate runtime links/directories.",
|
|
522
|
+
});
|
|
523
|
+
ok = false;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (exists(ctx.settingsPath)) {
|
|
529
|
+
try {
|
|
530
|
+
const settings = JSON.parse(readText(ctx.settingsPath));
|
|
531
|
+
|
|
532
|
+
if (!hookMatcherContains(settings?.hooks?.PreToolUse, "Task", "pre_tool_use.py")) {
|
|
533
|
+
addFinding(findings, {
|
|
534
|
+
severity: "high",
|
|
535
|
+
code: "SETTINGS_PRE_TOOL_TASK_HOOK_MISSING",
|
|
536
|
+
title: "Task PreToolUse hook not configured",
|
|
537
|
+
detail: "settings.json lacks Task -> pre_tool_use.py mapping.",
|
|
538
|
+
evidence: ctx.settingsPath,
|
|
539
|
+
remediation: "Add PreToolUse matcher for Task using .claude/hooks/pre_tool_use.py.",
|
|
540
|
+
});
|
|
541
|
+
ok = false;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (!hookMatcherContains(settings?.hooks?.PostToolUse, "Bash", "post_tool_use.py")) {
|
|
545
|
+
addFinding(findings, {
|
|
546
|
+
severity: "medium",
|
|
547
|
+
code: "SETTINGS_POST_TOOL_BASH_HOOK_MISSING",
|
|
548
|
+
title: "Bash PostToolUse hook not configured",
|
|
549
|
+
detail: "settings.json lacks Bash -> post_tool_use.py mapping.",
|
|
550
|
+
evidence: ctx.settingsPath,
|
|
551
|
+
remediation: "Add PostToolUse matcher for Bash using .claude/hooks/post_tool_use.py.",
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!hookMatcherContains(settings?.hooks?.SubagentStop, "*", "subagent_stop.py")) {
|
|
556
|
+
addFinding(findings, {
|
|
557
|
+
severity: "medium",
|
|
558
|
+
code: "SETTINGS_SUBAGENT_STOP_HOOK_MISSING",
|
|
559
|
+
title: "SubagentStop hook not configured",
|
|
560
|
+
detail: "settings.json lacks SubagentStop -> subagent_stop.py mapping.",
|
|
561
|
+
evidence: ctx.settingsPath,
|
|
562
|
+
remediation: "Add SubagentStop hook in settings.json.",
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
} catch (error) {
|
|
566
|
+
addFinding(findings, {
|
|
567
|
+
severity: "high",
|
|
568
|
+
code: "SETTINGS_JSON_INVALID",
|
|
569
|
+
title: "Invalid settings.json",
|
|
570
|
+
detail: `Failed to parse settings.json: ${error.message}`,
|
|
571
|
+
evidence: ctx.settingsPath,
|
|
572
|
+
remediation: "Fix JSON syntax or regenerate settings via gaia-scan.",
|
|
573
|
+
});
|
|
574
|
+
ok = false;
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
addFinding(findings, {
|
|
578
|
+
severity: ctx.inProject ? "high" : "info",
|
|
579
|
+
code: "SETTINGS_JSON_NOT_FOUND",
|
|
580
|
+
title: "settings.json not found",
|
|
581
|
+
detail: ctx.inProject
|
|
582
|
+
? "Project appears installed but .claude/settings.json is missing."
|
|
583
|
+
: "Running in package mode; runtime settings validation skipped.",
|
|
584
|
+
evidence: ctx.settingsPath,
|
|
585
|
+
remediation: "Generate settings with gaia-scan in project context.",
|
|
586
|
+
});
|
|
587
|
+
if (ctx.inProject) ok = false;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
checks.push({
|
|
591
|
+
name: "injection-wiring",
|
|
592
|
+
ok,
|
|
593
|
+
detail: preToolUsePath
|
|
594
|
+
? `using ${path.relative(ctx.projectRoot, preToolUsePath)}`
|
|
595
|
+
: "missing pre_tool_use.py",
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function validateRoutingContract(ctx, findings, checks) {
|
|
600
|
+
const agentsOnDisk = new Set(
|
|
601
|
+
listMarkdownFiles(ctx.agentsDir).map((file) => path.basename(file, ".md"))
|
|
602
|
+
);
|
|
603
|
+
const validatorPath = ctx.taskValidatorPath;
|
|
604
|
+
|
|
605
|
+
if (!exists(validatorPath)) {
|
|
606
|
+
checks.push({
|
|
607
|
+
name: "routing-contract",
|
|
608
|
+
ok: true,
|
|
609
|
+
detail: "task_validator.py not found; skipped",
|
|
610
|
+
});
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const content = readText(validatorPath);
|
|
615
|
+
// Try AVAILABLE_AGENTS first, then fall back to _BASE_AGENTS (used when
|
|
616
|
+
// AVAILABLE_AGENTS is derived via list comprehension)
|
|
617
|
+
let match = content.match(/AVAILABLE_AGENTS\s*=\s*\[(.*?)\]/s);
|
|
618
|
+
if (!match) {
|
|
619
|
+
match = content.match(/_BASE_AGENTS\s*=\s*\[(.*?)\]/s);
|
|
620
|
+
}
|
|
621
|
+
if (!match) {
|
|
622
|
+
addFinding(findings, {
|
|
623
|
+
severity: "medium",
|
|
624
|
+
code: "AVAILABLE_AGENTS_PARSE_FAILED",
|
|
625
|
+
title: "Could not parse AVAILABLE_AGENTS",
|
|
626
|
+
detail: "Routing contract check skipped.",
|
|
627
|
+
evidence: validatorPath,
|
|
628
|
+
remediation: "Keep AVAILABLE_AGENTS or _BASE_AGENTS as a simple static list for validation tooling.",
|
|
629
|
+
});
|
|
630
|
+
checks.push({ name: "routing-contract", ok: false, detail: "could not parse AVAILABLE_AGENTS" });
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const availableAgents = new Set(
|
|
635
|
+
[...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1])
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
const missingOnDisk = [...availableAgents].filter((agent) => !META_AGENTS.has(agent) && !agentsOnDisk.has(agent));
|
|
639
|
+
const missingInValidator = [...agentsOnDisk].filter((agent) => !META_AGENTS.has(agent) && !availableAgents.has(agent));
|
|
640
|
+
|
|
641
|
+
if (missingOnDisk.length > 0) {
|
|
642
|
+
addFinding(findings, {
|
|
643
|
+
severity: "high",
|
|
644
|
+
code: "AVAILABLE_AGENT_MISSING_FILE",
|
|
645
|
+
title: "AVAILABLE_AGENTS entry has no agent file",
|
|
646
|
+
detail: `${missingOnDisk.length} agent names are not present on disk.`,
|
|
647
|
+
evidence: missingOnDisk.join(", "),
|
|
648
|
+
remediation: "Create missing agent files or remove stale names from AVAILABLE_AGENTS.",
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (missingInValidator.length > 0) {
|
|
653
|
+
addFinding(findings, {
|
|
654
|
+
severity: "high",
|
|
655
|
+
code: "AGENT_FILE_NOT_IN_AVAILABLE_AGENTS",
|
|
656
|
+
title: "Agent file not listed in AVAILABLE_AGENTS",
|
|
657
|
+
detail: `${missingInValidator.length} agent files are not routable by validator.`,
|
|
658
|
+
evidence: missingInValidator.join(", "),
|
|
659
|
+
remediation: "Add these agents to AVAILABLE_AGENTS or remove deprecated files.",
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
checks.push({
|
|
664
|
+
name: "routing-contract",
|
|
665
|
+
ok: missingOnDisk.length === 0 && missingInValidator.length === 0,
|
|
666
|
+
detail: `${availableAgents.size} available agents vs ${agentsOnDisk.size} agent files`,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function detectLegacyGapPatterns(ctx, findings, checks) {
|
|
671
|
+
const testsDir = ctx.testsDir;
|
|
672
|
+
if (!exists(testsDir)) {
|
|
673
|
+
checks.push({ name: "gap-patterns", ok: true, detail: "tests directory not found; skipped" });
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const preToolUse = ctx.preToolUsePath && exists(ctx.preToolUsePath) ? readText(ctx.preToolUsePath) : "";
|
|
678
|
+
const hasRuntimeLoadFn = preToolUse.includes("def _load_agent_skills");
|
|
679
|
+
const testFiles = collectFilesRecursively(testsDir, (f) => f.endsWith(".py"));
|
|
680
|
+
|
|
681
|
+
const legacyRefs = [];
|
|
682
|
+
for (const file of testFiles) {
|
|
683
|
+
const content = readText(file);
|
|
684
|
+
if (content.includes("_load_agent_skills(")) {
|
|
685
|
+
legacyRefs.push(path.relative(PACKAGE_ROOT, file));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (legacyRefs.length > 0 && !hasRuntimeLoadFn) {
|
|
690
|
+
addFinding(findings, {
|
|
691
|
+
severity: "critical",
|
|
692
|
+
code: "LEGACY_TEST_EXPECTS_LOAD_AGENT_SKILLS",
|
|
693
|
+
title: "Tests still expect deprecated _load_agent_skills API",
|
|
694
|
+
detail: `${legacyRefs.length} test file(s) reference _load_agent_skills(), but runtime hook does not expose it.`,
|
|
695
|
+
evidence: legacyRefs.join(", "),
|
|
696
|
+
remediation: "Update tests to validate frontmatter-based native skills injection instead of hook-based loading.",
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const conftestPath = path.join(testsDir, "conftest.py");
|
|
701
|
+
if (exists(conftestPath)) {
|
|
702
|
+
const content = readText(conftestPath);
|
|
703
|
+
// CLAUDE.md is no longer generated -- identity lives in agents/gaia-orchestrator.md.
|
|
704
|
+
// conftest.py fixture reads agents/gaia-orchestrator.md directly.
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
checks.push({
|
|
708
|
+
name: "gap-patterns",
|
|
709
|
+
ok: true,
|
|
710
|
+
detail: `${legacyRefs.length} legacy pattern reference(s) scanned`,
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function runTestProbe(ctx, findings, checks) {
|
|
715
|
+
if (!exists(ctx.testsDir)) {
|
|
716
|
+
checks.push({ name: "test-probe", ok: true, detail: "tests directory missing; skipped" });
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const args = [
|
|
721
|
+
"-m",
|
|
722
|
+
"pytest",
|
|
723
|
+
"-q",
|
|
724
|
+
"tests/layer1_prompt_regression/test_skills_cross_reference.py",
|
|
725
|
+
"tests/layer1_prompt_regression/test_agent_frontmatter.py",
|
|
726
|
+
"tests/layer1_prompt_regression/test_agent_prompt_content.py",
|
|
727
|
+
"tests/layer1_prompt_regression/test_routing_table.py",
|
|
728
|
+
"tests/integration/test_subagent_lifecycle.py",
|
|
729
|
+
"-k",
|
|
730
|
+
"Phase1SkillsInjection or load_skills",
|
|
731
|
+
];
|
|
732
|
+
|
|
733
|
+
const pyCmd = findPython() || "python3";
|
|
734
|
+
const res = spawnSync(pyCmd, args, {
|
|
735
|
+
cwd: ctx.packageRoot,
|
|
736
|
+
encoding: "utf-8",
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// In some sandboxed environments spawnSync can return a non-fatal EPERM
|
|
740
|
+
// while still providing a valid exit status and output.
|
|
741
|
+
if (res.error && typeof res.status !== "number") {
|
|
742
|
+
addFinding(findings, {
|
|
743
|
+
severity: "high",
|
|
744
|
+
code: "PYTEST_PROBE_EXEC_FAILED",
|
|
745
|
+
title: "Unable to execute pytest probe",
|
|
746
|
+
detail: res.error.message,
|
|
747
|
+
evidence: `${pyCmd} ${args.join(" ")}`,
|
|
748
|
+
remediation: "Install pytest and Python dependencies to run the probe.",
|
|
749
|
+
});
|
|
750
|
+
checks.push({ name: "test-probe", ok: false, detail: "execution failed" });
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const combined = `${res.stdout || ""}\n${res.stderr || ""}`;
|
|
755
|
+
const status = typeof res.status === "number" ? res.status : 1;
|
|
756
|
+
const failed = status !== 0;
|
|
757
|
+
|
|
758
|
+
if (failed) {
|
|
759
|
+
if (combined.includes("has no attribute '_load_agent_skills'")) {
|
|
760
|
+
addFinding(findings, {
|
|
761
|
+
severity: "critical",
|
|
762
|
+
code: "PYTEST_FAILS_ON_LOAD_AGENT_SKILLS",
|
|
763
|
+
title: "Integration tests fail due to removed _load_agent_skills API",
|
|
764
|
+
detail: "test_subagent_lifecycle.py still validates legacy hook behavior.",
|
|
765
|
+
evidence: "AttributeError: module 'pre_tool_use_*' has no attribute '_load_agent_skills'",
|
|
766
|
+
remediation: "Rewrite Phase 1 tests to validate frontmatter-based injection contract.",
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// CLAUDE.md is no longer required -- conftest.py reads agents/gaia-orchestrator.md
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
checks.push({
|
|
774
|
+
name: "test-probe",
|
|
775
|
+
ok: !failed,
|
|
776
|
+
detail: failed ? `pytest exited with status ${status}` : "pytest probe passed",
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function summarizeFindings(findings) {
|
|
781
|
+
const counts = { critical: 0, high: 0, medium: 0, info: 0 };
|
|
782
|
+
let score = 100;
|
|
783
|
+
|
|
784
|
+
for (const finding of findings) {
|
|
785
|
+
counts[finding.severity] = (counts[finding.severity] || 0) + 1;
|
|
786
|
+
score -= SEVERITY_WEIGHT[finding.severity] || 0;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (score < 0) score = 0;
|
|
790
|
+
|
|
791
|
+
let status = "healthy";
|
|
792
|
+
if (counts.critical > 0 || score < 70) {
|
|
793
|
+
status = "at-risk";
|
|
794
|
+
} else if (counts.high > 0 || score < 90) {
|
|
795
|
+
status = "degraded";
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return { counts, score, status };
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function printHumanReport(report) {
|
|
802
|
+
console.log(chalk.cyan("\n Gaia-Ops Skills Injection Diagnostic\n"));
|
|
803
|
+
console.log(chalk.gray(` Project root: ${report.roots.projectRoot}`));
|
|
804
|
+
console.log(chalk.gray(` Runtime skills: ${report.roots.skillsDir}`));
|
|
805
|
+
console.log(chalk.gray(` Runtime agents: ${report.roots.agentsDir}\n`));
|
|
806
|
+
|
|
807
|
+
for (const check of report.checks) {
|
|
808
|
+
const icon = check.ok ? chalk.green("✓") : chalk.yellow("⚠");
|
|
809
|
+
const detail = check.ok ? chalk.gray(check.detail) : chalk.yellow(check.detail);
|
|
810
|
+
console.log(` ${icon} ${check.name.padEnd(20)} ${detail}`);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const { counts, score, status } = report.summary;
|
|
814
|
+
console.log("");
|
|
815
|
+
console.log(` Score: ${score}/100`);
|
|
816
|
+
console.log(` Status: ${status.toUpperCase()}`);
|
|
817
|
+
console.log(
|
|
818
|
+
` Findings: critical=${counts.critical}, high=${counts.high}, medium=${counts.medium}, info=${counts.info}\n`
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
if (report.findings.length === 0) {
|
|
822
|
+
console.log(chalk.green(" No gaps detected.\n"));
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, info: 3 };
|
|
827
|
+
const sorted = [...report.findings].sort(
|
|
828
|
+
(a, b) => severityOrder[a.severity] - severityOrder[b.severity]
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
console.log(chalk.cyan(" Gap diagnosis:\n"));
|
|
832
|
+
for (const finding of sorted) {
|
|
833
|
+
const sevColor =
|
|
834
|
+
finding.severity === "critical"
|
|
835
|
+
? chalk.red.bold
|
|
836
|
+
: finding.severity === "high"
|
|
837
|
+
? chalk.yellow.bold
|
|
838
|
+
: finding.severity === "medium"
|
|
839
|
+
? chalk.blue.bold
|
|
840
|
+
: chalk.gray;
|
|
841
|
+
console.log(` [${sevColor(finding.severity.toUpperCase())}] ${finding.code} - ${finding.title}`);
|
|
842
|
+
if (finding.detail) console.log(` ${finding.detail}`);
|
|
843
|
+
if (finding.evidence) console.log(chalk.gray(` Evidence: ${finding.evidence}`));
|
|
844
|
+
if (finding.remediation) console.log(chalk.gray(` Remediation: ${finding.remediation}`));
|
|
845
|
+
}
|
|
846
|
+
console.log("");
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function buildReport(args) {
|
|
850
|
+
const ctx = resolveRuntimePaths(path.resolve(args.projectRoot));
|
|
851
|
+
const findings = [];
|
|
852
|
+
const checks = [];
|
|
853
|
+
|
|
854
|
+
const skillData = validateSkillsCatalog(ctx, findings, checks);
|
|
855
|
+
validateAgentSkillWiring(ctx, skillData, findings, checks);
|
|
856
|
+
validateInjectionWiring(ctx, findings, checks);
|
|
857
|
+
validateRoutingContract(ctx, findings, checks);
|
|
858
|
+
detectLegacyGapPatterns(ctx, findings, checks);
|
|
859
|
+
|
|
860
|
+
if (args.runTests) {
|
|
861
|
+
runTestProbe(ctx, findings, checks);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const summary = summarizeFindings(findings);
|
|
865
|
+
return {
|
|
866
|
+
generatedAt: new Date().toISOString(),
|
|
867
|
+
roots: {
|
|
868
|
+
projectRoot: ctx.projectRoot,
|
|
869
|
+
packageRoot: ctx.packageRoot,
|
|
870
|
+
skillsDir: ctx.skillsDir,
|
|
871
|
+
agentsDir: ctx.agentsDir,
|
|
872
|
+
hooksDir: ctx.hooksDir,
|
|
873
|
+
settingsPath: ctx.settingsPath,
|
|
874
|
+
preToolUsePath: ctx.preToolUsePath || "",
|
|
875
|
+
},
|
|
876
|
+
checks,
|
|
877
|
+
findings,
|
|
878
|
+
summary,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function main() {
|
|
883
|
+
const args = yargs(hideBin(process.argv))
|
|
884
|
+
.usage("Usage: $0 [options]")
|
|
885
|
+
.option("project-root", {
|
|
886
|
+
type: "string",
|
|
887
|
+
default: process.cwd(),
|
|
888
|
+
description: "Project root to inspect (.claude/ expected here in project mode)",
|
|
889
|
+
})
|
|
890
|
+
.option("run-tests", {
|
|
891
|
+
type: "boolean",
|
|
892
|
+
default: false,
|
|
893
|
+
description: "Run focused pytest probe for skills/injection regressions",
|
|
894
|
+
})
|
|
895
|
+
.option("json", {
|
|
896
|
+
type: "boolean",
|
|
897
|
+
default: false,
|
|
898
|
+
description: "Output JSON report",
|
|
899
|
+
})
|
|
900
|
+
.option("strict", {
|
|
901
|
+
type: "boolean",
|
|
902
|
+
default: false,
|
|
903
|
+
description: "Exit non-zero for HIGH findings too (CI mode)",
|
|
904
|
+
})
|
|
905
|
+
.help("h")
|
|
906
|
+
.alias("h", "help")
|
|
907
|
+
.version(false)
|
|
908
|
+
.parse();
|
|
909
|
+
|
|
910
|
+
const report = buildReport(args);
|
|
911
|
+
|
|
912
|
+
if (args.json) {
|
|
913
|
+
console.log(JSON.stringify(report, null, 2));
|
|
914
|
+
} else {
|
|
915
|
+
printHumanReport(report);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const { critical, high } = report.summary.counts;
|
|
919
|
+
let exitCode = 0;
|
|
920
|
+
if (critical > 0) {
|
|
921
|
+
exitCode = 2;
|
|
922
|
+
} else if (args.strict && high > 0) {
|
|
923
|
+
exitCode = 1;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
process.exit(exitCode);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
main();
|