@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.
Files changed (609) hide show
  1. package/.claude-plugin/marketplace.json +33 -0
  2. package/.claude-plugin/plugin.json +26 -0
  3. package/ARCHITECTURE.md +335 -0
  4. package/CHANGELOG.md +1212 -0
  5. package/CODE_OF_CONDUCT.md +11 -0
  6. package/CONTRIBUTING.md +146 -0
  7. package/INSTALL.md +436 -0
  8. package/LICENSE +21 -0
  9. package/README.md +222 -0
  10. package/SECURITY.md +47 -0
  11. package/agents/README.md +78 -0
  12. package/agents/cloud-troubleshooter.md +73 -0
  13. package/agents/developer.md +65 -0
  14. package/agents/gaia-operator.md +64 -0
  15. package/agents/gaia-orchestrator.md +237 -0
  16. package/agents/gaia-planner.md +53 -0
  17. package/agents/gaia-system.md +70 -0
  18. package/agents/gitops-operator.md +61 -0
  19. package/agents/terraform-architect.md +63 -0
  20. package/bin/README.md +106 -0
  21. package/bin/cli/__init__.py +1 -0
  22. package/bin/cli/approvals.py +740 -0
  23. package/bin/cli/cleanup.py +562 -0
  24. package/bin/cli/context.py +283 -0
  25. package/bin/cli/doctor.py +628 -0
  26. package/bin/cli/history.py +305 -0
  27. package/bin/cli/memory.py +464 -0
  28. package/bin/cli/metrics.py +1068 -0
  29. package/bin/cli/plans.py +515 -0
  30. package/bin/cli/status.py +302 -0
  31. package/bin/cli/update.py +382 -0
  32. package/bin/gaia +112 -0
  33. package/bin/gaia-cleanup.js +531 -0
  34. package/bin/gaia-doctor.js +635 -0
  35. package/bin/gaia-evidence +126 -0
  36. package/bin/gaia-history.js +251 -0
  37. package/bin/gaia-metrics.js +1278 -0
  38. package/bin/gaia-review.js +269 -0
  39. package/bin/gaia-scan +44 -0
  40. package/bin/gaia-scan.py +589 -0
  41. package/bin/gaia-skills-diagnose.js +929 -0
  42. package/bin/gaia-status.js +278 -0
  43. package/bin/gaia-uninstall.js +111 -0
  44. package/bin/gaia-update.js +816 -0
  45. package/bin/pre-publish-validate.js +610 -0
  46. package/bin/python-detect.js +60 -0
  47. package/commands/README.md +64 -0
  48. package/commands/gaia.md +37 -0
  49. package/commands/scan-project.md +67 -0
  50. package/config/README.md +71 -0
  51. package/config/cloud/aws.json +134 -0
  52. package/config/cloud/gcp.json +139 -0
  53. package/config/context-contracts.json +158 -0
  54. package/config/crons-schema.md +81 -0
  55. package/config/git_standards.json +72 -0
  56. package/config/surface-routing.json +421 -0
  57. package/config/universal-rules.json +102 -0
  58. package/dist/gaia-ops/.claude-plugin/plugin.json +24 -0
  59. package/dist/gaia-ops/README.md +80 -0
  60. package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
  61. package/dist/gaia-ops/agents/developer.md +65 -0
  62. package/dist/gaia-ops/agents/gaia-operator.md +64 -0
  63. package/dist/gaia-ops/agents/gaia-orchestrator.md +237 -0
  64. package/dist/gaia-ops/agents/gaia-planner.md +53 -0
  65. package/dist/gaia-ops/agents/gaia-system.md +70 -0
  66. package/dist/gaia-ops/agents/gitops-operator.md +61 -0
  67. package/dist/gaia-ops/agents/terraform-architect.md +63 -0
  68. package/dist/gaia-ops/commands/gaia.md +37 -0
  69. package/dist/gaia-ops/config/README.md +71 -0
  70. package/dist/gaia-ops/config/cloud/aws.json +134 -0
  71. package/dist/gaia-ops/config/cloud/gcp.json +139 -0
  72. package/dist/gaia-ops/config/context-contracts.json +158 -0
  73. package/dist/gaia-ops/config/crons-schema.md +81 -0
  74. package/dist/gaia-ops/config/git_standards.json +72 -0
  75. package/dist/gaia-ops/config/surface-routing.json +421 -0
  76. package/dist/gaia-ops/config/universal-rules.json +102 -0
  77. package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
  78. package/dist/gaia-ops/hooks/adapters/base.py +219 -0
  79. package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
  80. package/dist/gaia-ops/hooks/adapters/claude_code.py +1890 -0
  81. package/dist/gaia-ops/hooks/adapters/types.py +194 -0
  82. package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
  83. package/dist/gaia-ops/hooks/hooks.json +163 -0
  84. package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
  85. package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
  86. package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
  87. package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
  88. package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +120 -0
  89. package/dist/gaia-ops/hooks/modules/agents/state_tracker.py +267 -0
  90. package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
  91. package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
  92. package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
  93. package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
  94. package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
  95. package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
  96. package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
  97. package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +611 -0
  98. package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
  99. package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
  100. package/dist/gaia-ops/hooks/modules/context/agentic_loop_detector.py +165 -0
  101. package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
  102. package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +218 -0
  103. package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
  104. package/dist/gaia-ops/hooks/modules/context/context_injector.py +558 -0
  105. package/dist/gaia-ops/hooks/modules/context/context_writer.py +530 -0
  106. package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
  107. package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
  108. package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
  109. package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
  110. package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
  111. package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +577 -0
  112. package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
  113. package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
  114. package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
  115. package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
  116. package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
  117. package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +216 -0
  118. package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
  119. package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +122 -0
  120. package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
  121. package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
  122. package/dist/gaia-ops/hooks/modules/security/__init__.py +120 -0
  123. package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
  124. package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
  125. package/dist/gaia-ops/hooks/modules/security/approval_grants.py +1638 -0
  126. package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
  127. package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +222 -0
  128. package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +595 -0
  129. package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +87 -0
  130. package/dist/gaia-ops/hooks/modules/security/command_semantics.py +181 -0
  131. package/dist/gaia-ops/hooks/modules/security/composition_rules.py +547 -0
  132. package/dist/gaia-ops/hooks/modules/security/flag_classifiers.py +873 -0
  133. package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
  134. package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +1131 -0
  135. package/dist/gaia-ops/hooks/modules/security/network_hosts.py +481 -0
  136. package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
  137. package/dist/gaia-ops/hooks/modules/security/shell_unwrapper.py +165 -0
  138. package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
  139. package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
  140. package/dist/gaia-ops/hooks/modules/session/pending_scanner.py +174 -0
  141. package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
  142. package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +160 -0
  143. package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
  144. package/dist/gaia-ops/hooks/modules/session/session_registry.py +232 -0
  145. package/dist/gaia-ops/hooks/modules/tools/__init__.py +29 -0
  146. package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +1008 -0
  147. package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  148. package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
  149. package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
  150. package/dist/gaia-ops/hooks/modules/tools/stage_decomposer.py +315 -0
  151. package/dist/gaia-ops/hooks/modules/tools/task_validator.py +294 -0
  152. package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
  153. package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
  154. package/dist/gaia-ops/hooks/post_compact.py +43 -0
  155. package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
  156. package/dist/gaia-ops/hooks/pre_compact.py +60 -0
  157. package/dist/gaia-ops/hooks/pre_tool_use.py +413 -0
  158. package/dist/gaia-ops/hooks/session_start.py +81 -0
  159. package/dist/gaia-ops/hooks/stop_hook.py +82 -0
  160. package/dist/gaia-ops/hooks/subagent_start.py +71 -0
  161. package/dist/gaia-ops/hooks/subagent_stop.py +295 -0
  162. package/dist/gaia-ops/hooks/task_completed.py +70 -0
  163. package/dist/gaia-ops/hooks/user_prompt_submit.py +246 -0
  164. package/dist/gaia-ops/settings.json +72 -0
  165. package/dist/gaia-ops/skills/README.md +154 -0
  166. package/dist/gaia-ops/skills/agent-protocol/SKILL.md +93 -0
  167. package/dist/gaia-ops/skills/agent-protocol/examples.md +223 -0
  168. package/dist/gaia-ops/skills/agent-response/SKILL.md +69 -0
  169. package/dist/gaia-ops/skills/agentic-loop/SKILL.md +80 -0
  170. package/dist/gaia-ops/skills/agentic-loop/reference.md +378 -0
  171. package/dist/gaia-ops/skills/blog-writing/SKILL.md +98 -0
  172. package/dist/gaia-ops/skills/blog-writing/reference.md +130 -0
  173. package/dist/gaia-ops/skills/brief-spec/SKILL.md +182 -0
  174. package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
  175. package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
  176. package/dist/gaia-ops/skills/context-updater/SKILL.md +87 -0
  177. package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
  178. package/dist/gaia-ops/skills/developer-patterns/SKILL.md +50 -0
  179. package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
  180. package/dist/gaia-ops/skills/execution/SKILL.md +99 -0
  181. package/dist/gaia-ops/skills/fast-queries/SKILL.md +43 -0
  182. package/dist/gaia-ops/skills/gaia-compact/SKILL.md +74 -0
  183. package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +108 -0
  184. package/dist/gaia-ops/skills/gaia-patterns/reference.md +395 -0
  185. package/dist/gaia-ops/skills/gaia-planner/SKILL.md +37 -0
  186. package/dist/gaia-ops/skills/gaia-planner/reference.md +107 -0
  187. package/dist/gaia-ops/skills/gaia-release/SKILL.md +82 -0
  188. package/dist/gaia-ops/skills/gaia-release/reference.md +102 -0
  189. package/dist/gaia-ops/skills/gaia-self-check/SKILL.md +114 -0
  190. package/dist/gaia-ops/skills/gaia-self-check/reference.md +453 -0
  191. package/dist/gaia-ops/skills/gaia-verify/SKILL.md +77 -0
  192. package/dist/gaia-ops/skills/gaia-verify/reference.md +80 -0
  193. package/dist/gaia-ops/skills/git-conventions/SKILL.md +47 -0
  194. package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +60 -0
  195. package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
  196. package/dist/gaia-ops/skills/gmail-policy/SKILL.md +200 -0
  197. package/dist/gaia-ops/skills/gmail-policy/reference.md +150 -0
  198. package/dist/gaia-ops/skills/gmail-triage/SKILL.md +100 -0
  199. package/dist/gaia-ops/skills/gws-setup/SKILL.md +99 -0
  200. package/dist/gaia-ops/skills/gws-setup/reference.md +73 -0
  201. package/dist/gaia-ops/skills/investigation/SKILL.md +100 -0
  202. package/dist/gaia-ops/skills/memory-curation/SKILL.md +83 -0
  203. package/dist/gaia-ops/skills/memory-search/SKILL.md +88 -0
  204. package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +160 -0
  205. package/dist/gaia-ops/skills/orchestrator-approval/reference.md +174 -0
  206. package/dist/gaia-ops/skills/pending-approvals/SKILL.md +72 -0
  207. package/dist/gaia-ops/skills/pending-approvals/reference.md +214 -0
  208. package/dist/gaia-ops/skills/readme-writing/SKILL.md +71 -0
  209. package/dist/gaia-ops/skills/readme-writing/reference.md +188 -0
  210. package/dist/gaia-ops/skills/reference.md +135 -0
  211. package/dist/gaia-ops/skills/request-approval/SKILL.md +140 -0
  212. package/dist/gaia-ops/skills/request-approval/examples.md +140 -0
  213. package/dist/gaia-ops/skills/request-approval/reference.md +57 -0
  214. package/dist/gaia-ops/skills/schedule-task/SKILL.md +64 -0
  215. package/dist/gaia-ops/skills/schedule-task/reference.md +233 -0
  216. package/dist/gaia-ops/skills/security-tiers/SKILL.md +141 -0
  217. package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
  218. package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
  219. package/dist/gaia-ops/skills/skill-creation/SKILL.md +92 -0
  220. package/dist/gaia-ops/skills/skill-creation/reference.md +29 -0
  221. package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +89 -0
  222. package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
  223. package/dist/gaia-ops/tools/__init__.py +9 -0
  224. package/dist/gaia-ops/tools/agentic-loop/decide-status.py +210 -0
  225. package/dist/gaia-ops/tools/agentic-loop/parse-metric.py +106 -0
  226. package/dist/gaia-ops/tools/agentic-loop/record-iteration.py +221 -0
  227. package/dist/gaia-ops/tools/context/README.md +132 -0
  228. package/dist/gaia-ops/tools/context/__init__.py +42 -0
  229. package/dist/gaia-ops/tools/context/_paths.py +20 -0
  230. package/dist/gaia-ops/tools/context/context_provider.py +721 -0
  231. package/dist/gaia-ops/tools/context/context_section_reader.py +342 -0
  232. package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
  233. package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
  234. package/dist/gaia-ops/tools/context/surface_router.py +278 -0
  235. package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
  236. package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
  237. package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  238. package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  239. package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  240. package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  241. package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
  242. package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  243. package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
  244. package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
  245. package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
  246. package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
  247. package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
  248. package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
  249. package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +264 -0
  250. package/dist/gaia-ops/tools/memory/README.md +0 -0
  251. package/dist/gaia-ops/tools/memory/__init__.py +20 -0
  252. package/dist/gaia-ops/tools/memory/backfill_fts5.py +107 -0
  253. package/dist/gaia-ops/tools/memory/conflict_detector.py +295 -0
  254. package/dist/gaia-ops/tools/memory/episodic.py +1210 -0
  255. package/dist/gaia-ops/tools/memory/git_invalidator.py +262 -0
  256. package/dist/gaia-ops/tools/memory/paths.py +102 -0
  257. package/dist/gaia-ops/tools/memory/scoring.py +193 -0
  258. package/dist/gaia-ops/tools/memory/search_store.py +360 -0
  259. package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
  260. package/dist/gaia-ops/tools/review/__init__.py +1 -0
  261. package/dist/gaia-ops/tools/review/review_engine.py +157 -0
  262. package/dist/gaia-ops/tools/scan/__init__.py +35 -0
  263. package/dist/gaia-ops/tools/scan/config.py +247 -0
  264. package/dist/gaia-ops/tools/scan/merge.py +212 -0
  265. package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
  266. package/dist/gaia-ops/tools/scan/registry.py +127 -0
  267. package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
  268. package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
  269. package/dist/gaia-ops/tools/scan/scanners/environment.py +349 -0
  270. package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
  271. package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
  272. package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
  273. package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
  274. package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
  275. package/dist/gaia-ops/tools/scan/setup.py +686 -0
  276. package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
  277. package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
  278. package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
  279. package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
  280. package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
  281. package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
  282. package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
  283. package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
  284. package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
  285. package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
  286. package/dist/gaia-ops/tools/scan/ui.py +624 -0
  287. package/dist/gaia-ops/tools/scan/verify.py +270 -0
  288. package/dist/gaia-ops/tools/scan/walk.py +118 -0
  289. package/dist/gaia-ops/tools/scan/workspace.py +85 -0
  290. package/dist/gaia-ops/tools/validation/README.md +244 -0
  291. package/dist/gaia-ops/tools/validation/__init__.py +17 -0
  292. package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
  293. package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
  294. package/dist/gaia-security/.claude-plugin/plugin.json +24 -0
  295. package/dist/gaia-security/README.md +90 -0
  296. package/dist/gaia-security/config/universal-rules.json +102 -0
  297. package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
  298. package/dist/gaia-security/hooks/adapters/base.py +219 -0
  299. package/dist/gaia-security/hooks/adapters/channel.py +17 -0
  300. package/dist/gaia-security/hooks/adapters/claude_code.py +1890 -0
  301. package/dist/gaia-security/hooks/adapters/types.py +194 -0
  302. package/dist/gaia-security/hooks/adapters/utils.py +25 -0
  303. package/dist/gaia-security/hooks/hooks.json +84 -0
  304. package/dist/gaia-security/hooks/modules/__init__.py +15 -0
  305. package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
  306. package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
  307. package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
  308. package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +120 -0
  309. package/dist/gaia-security/hooks/modules/agents/state_tracker.py +267 -0
  310. package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
  311. package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
  312. package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
  313. package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
  314. package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
  315. package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
  316. package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
  317. package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +611 -0
  318. package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
  319. package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
  320. package/dist/gaia-security/hooks/modules/context/agentic_loop_detector.py +165 -0
  321. package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
  322. package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +218 -0
  323. package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
  324. package/dist/gaia-security/hooks/modules/context/context_injector.py +558 -0
  325. package/dist/gaia-security/hooks/modules/context/context_writer.py +530 -0
  326. package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
  327. package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
  328. package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
  329. package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
  330. package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
  331. package/dist/gaia-security/hooks/modules/core/plugin_setup.py +577 -0
  332. package/dist/gaia-security/hooks/modules/core/state.py +179 -0
  333. package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
  334. package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
  335. package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
  336. package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
  337. package/dist/gaia-security/hooks/modules/memory/episode_writer.py +216 -0
  338. package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
  339. package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +122 -0
  340. package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
  341. package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
  342. package/dist/gaia-security/hooks/modules/security/__init__.py +120 -0
  343. package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
  344. package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
  345. package/dist/gaia-security/hooks/modules/security/approval_grants.py +1638 -0
  346. package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
  347. package/dist/gaia-security/hooks/modules/security/approval_scopes.py +222 -0
  348. package/dist/gaia-security/hooks/modules/security/blocked_commands.py +595 -0
  349. package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +87 -0
  350. package/dist/gaia-security/hooks/modules/security/command_semantics.py +181 -0
  351. package/dist/gaia-security/hooks/modules/security/composition_rules.py +547 -0
  352. package/dist/gaia-security/hooks/modules/security/flag_classifiers.py +873 -0
  353. package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
  354. package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +1131 -0
  355. package/dist/gaia-security/hooks/modules/security/network_hosts.py +481 -0
  356. package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
  357. package/dist/gaia-security/hooks/modules/security/shell_unwrapper.py +165 -0
  358. package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
  359. package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
  360. package/dist/gaia-security/hooks/modules/session/pending_scanner.py +174 -0
  361. package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
  362. package/dist/gaia-security/hooks/modules/session/session_event_injector.py +160 -0
  363. package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
  364. package/dist/gaia-security/hooks/modules/session/session_registry.py +232 -0
  365. package/dist/gaia-security/hooks/modules/tools/__init__.py +29 -0
  366. package/dist/gaia-security/hooks/modules/tools/bash_validator.py +1008 -0
  367. package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  368. package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
  369. package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
  370. package/dist/gaia-security/hooks/modules/tools/stage_decomposer.py +315 -0
  371. package/dist/gaia-security/hooks/modules/tools/task_validator.py +294 -0
  372. package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
  373. package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
  374. package/dist/gaia-security/hooks/post_tool_use.py +54 -0
  375. package/dist/gaia-security/hooks/pre_tool_use.py +413 -0
  376. package/dist/gaia-security/hooks/session_start.py +81 -0
  377. package/dist/gaia-security/hooks/stop_hook.py +82 -0
  378. package/dist/gaia-security/hooks/user_prompt_submit.py +246 -0
  379. package/dist/gaia-security/settings.json +58 -0
  380. package/git-hooks/commit-msg +41 -0
  381. package/hooks/README.md +100 -0
  382. package/hooks/adapters/__init__.py +52 -0
  383. package/hooks/adapters/base.py +219 -0
  384. package/hooks/adapters/channel.py +17 -0
  385. package/hooks/adapters/claude_code.py +1890 -0
  386. package/hooks/adapters/types.py +194 -0
  387. package/hooks/adapters/utils.py +25 -0
  388. package/hooks/elicitation_result.py +179 -0
  389. package/hooks/hooks.json +84 -0
  390. package/hooks/modules/README.md +189 -0
  391. package/hooks/modules/__init__.py +15 -0
  392. package/hooks/modules/agents/__init__.py +29 -0
  393. package/hooks/modules/agents/contract_validator.py +647 -0
  394. package/hooks/modules/agents/response_contract.py +496 -0
  395. package/hooks/modules/agents/skill_injection_verifier.py +120 -0
  396. package/hooks/modules/agents/state_tracker.py +267 -0
  397. package/hooks/modules/agents/task_info_builder.py +74 -0
  398. package/hooks/modules/agents/transcript_analyzer.py +458 -0
  399. package/hooks/modules/agents/transcript_reader.py +152 -0
  400. package/hooks/modules/audit/__init__.py +28 -0
  401. package/hooks/modules/audit/event_detector.py +168 -0
  402. package/hooks/modules/audit/logger.py +131 -0
  403. package/hooks/modules/audit/metrics.py +134 -0
  404. package/hooks/modules/audit/workflow_auditor.py +611 -0
  405. package/hooks/modules/audit/workflow_recorder.py +296 -0
  406. package/hooks/modules/context/__init__.py +11 -0
  407. package/hooks/modules/context/agentic_loop_detector.py +165 -0
  408. package/hooks/modules/context/anchor_tracker.py +317 -0
  409. package/hooks/modules/context/compact_context_builder.py +218 -0
  410. package/hooks/modules/context/context_freshness.py +145 -0
  411. package/hooks/modules/context/context_injector.py +558 -0
  412. package/hooks/modules/context/context_writer.py +530 -0
  413. package/hooks/modules/context/contracts_loader.py +161 -0
  414. package/hooks/modules/core/__init__.py +40 -0
  415. package/hooks/modules/core/hook_entry.py +78 -0
  416. package/hooks/modules/core/paths.py +160 -0
  417. package/hooks/modules/core/plugin_mode.py +149 -0
  418. package/hooks/modules/core/plugin_setup.py +577 -0
  419. package/hooks/modules/core/state.py +179 -0
  420. package/hooks/modules/core/stdin.py +24 -0
  421. package/hooks/modules/events/__init__.py +1 -0
  422. package/hooks/modules/events/event_writer.py +210 -0
  423. package/hooks/modules/evidence/__init__.py +34 -0
  424. package/hooks/modules/evidence/assertions.py +137 -0
  425. package/hooks/modules/evidence/index_writer.py +57 -0
  426. package/hooks/modules/evidence/loader.py +126 -0
  427. package/hooks/modules/evidence/runner.py +241 -0
  428. package/hooks/modules/memory/__init__.py +8 -0
  429. package/hooks/modules/memory/episode_writer.py +216 -0
  430. package/hooks/modules/orchestrator/__init__.py +1 -0
  431. package/hooks/modules/orchestrator/delegate_mode.py +122 -0
  432. package/hooks/modules/scanning/__init__.py +8 -0
  433. package/hooks/modules/scanning/scan_trigger.py +84 -0
  434. package/hooks/modules/security/__init__.py +120 -0
  435. package/hooks/modules/security/approval_cleanup.py +87 -0
  436. package/hooks/modules/security/approval_constants.py +23 -0
  437. package/hooks/modules/security/approval_grants.py +1638 -0
  438. package/hooks/modules/security/approval_messages.py +71 -0
  439. package/hooks/modules/security/approval_scopes.py +222 -0
  440. package/hooks/modules/security/blocked_commands.py +595 -0
  441. package/hooks/modules/security/blocked_message_formatter.py +87 -0
  442. package/hooks/modules/security/command_semantics.py +181 -0
  443. package/hooks/modules/security/composition_rules.py +547 -0
  444. package/hooks/modules/security/flag_classifiers.py +873 -0
  445. package/hooks/modules/security/gitops_validator.py +179 -0
  446. package/hooks/modules/security/mutative_verbs.py +1131 -0
  447. package/hooks/modules/security/network_hosts.py +481 -0
  448. package/hooks/modules/security/prompt_validator.py +40 -0
  449. package/hooks/modules/security/shell_unwrapper.py +165 -0
  450. package/hooks/modules/security/tiers.py +196 -0
  451. package/hooks/modules/session/__init__.py +10 -0
  452. package/hooks/modules/session/pending_scanner.py +174 -0
  453. package/hooks/modules/session/session_context_writer.py +100 -0
  454. package/hooks/modules/session/session_event_injector.py +160 -0
  455. package/hooks/modules/session/session_manager.py +31 -0
  456. package/hooks/modules/session/session_registry.py +232 -0
  457. package/hooks/modules/tools/__init__.py +29 -0
  458. package/hooks/modules/tools/bash_validator.py +1008 -0
  459. package/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  460. package/hooks/modules/tools/hook_response.py +55 -0
  461. package/hooks/modules/tools/shell_parser.py +227 -0
  462. package/hooks/modules/tools/stage_decomposer.py +315 -0
  463. package/hooks/modules/tools/task_validator.py +294 -0
  464. package/hooks/modules/validation/__init__.py +23 -0
  465. package/hooks/modules/validation/commit_validator.py +380 -0
  466. package/hooks/post_compact.py +43 -0
  467. package/hooks/post_tool_use.py +54 -0
  468. package/hooks/pre_compact.py +60 -0
  469. package/hooks/pre_tool_use.py +413 -0
  470. package/hooks/session_start.py +81 -0
  471. package/hooks/stop_hook.py +82 -0
  472. package/hooks/subagent_start.py +71 -0
  473. package/hooks/subagent_stop.py +295 -0
  474. package/hooks/task_completed.py +70 -0
  475. package/hooks/user_prompt_submit.py +246 -0
  476. package/index.js +83 -0
  477. package/package.json +99 -0
  478. package/pyproject.toml +32 -0
  479. package/skills/README.md +154 -0
  480. package/skills/agent-protocol/SKILL.md +93 -0
  481. package/skills/agent-protocol/examples.md +223 -0
  482. package/skills/agent-response/SKILL.md +69 -0
  483. package/skills/agentic-loop/SKILL.md +80 -0
  484. package/skills/agentic-loop/reference.md +378 -0
  485. package/skills/blog-writing/SKILL.md +98 -0
  486. package/skills/blog-writing/reference.md +130 -0
  487. package/skills/brief-spec/SKILL.md +182 -0
  488. package/skills/command-execution/SKILL.md +64 -0
  489. package/skills/command-execution/reference.md +83 -0
  490. package/skills/context-updater/SKILL.md +87 -0
  491. package/skills/context-updater/examples.md +71 -0
  492. package/skills/developer-patterns/SKILL.md +50 -0
  493. package/skills/developer-patterns/reference.md +112 -0
  494. package/skills/execution/SKILL.md +99 -0
  495. package/skills/fast-queries/SKILL.md +43 -0
  496. package/skills/gaia-compact/SKILL.md +74 -0
  497. package/skills/gaia-patterns/SKILL.md +108 -0
  498. package/skills/gaia-patterns/reference.md +395 -0
  499. package/skills/gaia-planner/SKILL.md +37 -0
  500. package/skills/gaia-planner/reference.md +107 -0
  501. package/skills/gaia-release/SKILL.md +82 -0
  502. package/skills/gaia-release/reference.md +102 -0
  503. package/skills/gaia-self-check/SKILL.md +114 -0
  504. package/skills/gaia-self-check/reference.md +453 -0
  505. package/skills/gaia-verify/SKILL.md +77 -0
  506. package/skills/gaia-verify/reference.md +80 -0
  507. package/skills/git-conventions/SKILL.md +47 -0
  508. package/skills/gitops-patterns/SKILL.md +60 -0
  509. package/skills/gitops-patterns/reference.md +183 -0
  510. package/skills/gmail-policy/SKILL.md +200 -0
  511. package/skills/gmail-policy/reference.md +150 -0
  512. package/skills/gmail-triage/SKILL.md +100 -0
  513. package/skills/gws-setup/SKILL.md +99 -0
  514. package/skills/gws-setup/reference.md +73 -0
  515. package/skills/investigation/SKILL.md +100 -0
  516. package/skills/memory-curation/SKILL.md +83 -0
  517. package/skills/memory-search/SKILL.md +88 -0
  518. package/skills/orchestrator-approval/SKILL.md +160 -0
  519. package/skills/orchestrator-approval/reference.md +174 -0
  520. package/skills/pending-approvals/SKILL.md +72 -0
  521. package/skills/pending-approvals/reference.md +214 -0
  522. package/skills/readme-writing/SKILL.md +71 -0
  523. package/skills/readme-writing/reference.md +188 -0
  524. package/skills/reference.md +135 -0
  525. package/skills/request-approval/SKILL.md +140 -0
  526. package/skills/request-approval/examples.md +140 -0
  527. package/skills/request-approval/reference.md +57 -0
  528. package/skills/schedule-task/SKILL.md +64 -0
  529. package/skills/schedule-task/reference.md +233 -0
  530. package/skills/security-tiers/SKILL.md +141 -0
  531. package/skills/security-tiers/destructive-commands-reference.md +623 -0
  532. package/skills/security-tiers/reference.md +39 -0
  533. package/skills/skill-creation/SKILL.md +92 -0
  534. package/skills/skill-creation/reference.md +29 -0
  535. package/skills/terraform-patterns/SKILL.md +89 -0
  536. package/skills/terraform-patterns/reference.md +93 -0
  537. package/templates/README.md +69 -0
  538. package/templates/managed-settings.template.json +43 -0
  539. package/tools/__init__.py +9 -0
  540. package/tools/agentic-loop/decide-status.py +210 -0
  541. package/tools/agentic-loop/parse-metric.py +106 -0
  542. package/tools/agentic-loop/record-iteration.py +221 -0
  543. package/tools/context/README.md +132 -0
  544. package/tools/context/__init__.py +42 -0
  545. package/tools/context/_paths.py +20 -0
  546. package/tools/context/context_provider.py +721 -0
  547. package/tools/context/context_section_reader.py +342 -0
  548. package/tools/context/deep_merge.py +159 -0
  549. package/tools/context/pending_updates.py +760 -0
  550. package/tools/context/surface_router.py +278 -0
  551. package/tools/fast-queries/README.md +65 -0
  552. package/tools/fast-queries/__init__.py +30 -0
  553. package/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  554. package/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  555. package/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  556. package/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  557. package/tools/fast-queries/run_triage.sh +59 -0
  558. package/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  559. package/tools/gaia_simulator/__init__.py +33 -0
  560. package/tools/gaia_simulator/cli.py +354 -0
  561. package/tools/gaia_simulator/extractor.py +457 -0
  562. package/tools/gaia_simulator/reporter.py +258 -0
  563. package/tools/gaia_simulator/routing_simulator.py +334 -0
  564. package/tools/gaia_simulator/runner.py +539 -0
  565. package/tools/gaia_simulator/skills_mapper.py +264 -0
  566. package/tools/memory/README.md +0 -0
  567. package/tools/memory/__init__.py +20 -0
  568. package/tools/memory/backfill_fts5.py +107 -0
  569. package/tools/memory/conflict_detector.py +295 -0
  570. package/tools/memory/episodic.py +1210 -0
  571. package/tools/memory/git_invalidator.py +262 -0
  572. package/tools/memory/paths.py +102 -0
  573. package/tools/memory/scoring.py +193 -0
  574. package/tools/memory/search_store.py +360 -0
  575. package/tools/persist_transcript_analysis.py +85 -0
  576. package/tools/review/__init__.py +1 -0
  577. package/tools/review/review_engine.py +157 -0
  578. package/tools/scan/__init__.py +35 -0
  579. package/tools/scan/config.py +247 -0
  580. package/tools/scan/merge.py +212 -0
  581. package/tools/scan/orchestrator.py +549 -0
  582. package/tools/scan/registry.py +127 -0
  583. package/tools/scan/scanners/__init__.py +18 -0
  584. package/tools/scan/scanners/base.py +137 -0
  585. package/tools/scan/scanners/environment.py +349 -0
  586. package/tools/scan/scanners/git.py +570 -0
  587. package/tools/scan/scanners/infrastructure.py +875 -0
  588. package/tools/scan/scanners/orchestration.py +600 -0
  589. package/tools/scan/scanners/stack.py +1085 -0
  590. package/tools/scan/scanners/tools.py +260 -0
  591. package/tools/scan/setup.py +686 -0
  592. package/tools/scan/tests/__init__.py +1 -0
  593. package/tools/scan/tests/conftest.py +796 -0
  594. package/tools/scan/tests/test_environment.py +323 -0
  595. package/tools/scan/tests/test_git.py +419 -0
  596. package/tools/scan/tests/test_infrastructure.py +382 -0
  597. package/tools/scan/tests/test_integration.py +920 -0
  598. package/tools/scan/tests/test_merge.py +269 -0
  599. package/tools/scan/tests/test_orchestration.py +304 -0
  600. package/tools/scan/tests/test_stack.py +604 -0
  601. package/tools/scan/tests/test_tools.py +349 -0
  602. package/tools/scan/ui.py +624 -0
  603. package/tools/scan/verify.py +270 -0
  604. package/tools/scan/walk.py +118 -0
  605. package/tools/scan/workspace.py +85 -0
  606. package/tools/validation/README.md +244 -0
  607. package/tools/validation/__init__.py +17 -0
  608. package/tools/validation/approval_gate.py +321 -0
  609. package/tools/validation/validate_skills.py +189 -0
@@ -0,0 +1,628 @@
1
+ """
2
+ gaia doctor -- Health check for Gaia-Ops installation.
3
+
4
+ Mirrors the checks in gaia-doctor.js:
5
+ 1. gaia-version - package.json readable
6
+ 2. claude-code - CLI installed
7
+ 3. python - Python 3.9+ available
8
+ 4. plugin-mode - ops vs security, registry valid
9
+ 5. symlinks - .claude/ symlinks resolve
10
+ 6. identity - orchestrator agent configured
11
+ 7. settings - hooks registered, permissions, deny rules
12
+ 8. hook-files - all hook scripts present
13
+ 9. project-context - project-context.json valid
14
+ 10. project-dirs - paths declared in context exist
15
+ 11. memory-dirs - episodic memory dirs present
16
+
17
+ Severity: pass / info / warning / error
18
+ Exit codes: 0=healthy, 1=warnings, 2=errors
19
+ """
20
+
21
+ import json
22
+ import os
23
+ import shutil
24
+ import subprocess
25
+ import sys
26
+ from pathlib import Path
27
+
28
+
29
+ # ============================================================================
30
+ # Helpers
31
+ # ============================================================================
32
+
33
+ def _result(name: str, severity: str, detail: str, fix: str = None) -> dict:
34
+ """Create a check result dict."""
35
+ ok = severity in ("pass", "info")
36
+ r = {"name": name, "severity": severity, "ok": ok, "detail": detail}
37
+ if fix:
38
+ r["fix"] = fix
39
+ return r
40
+
41
+
42
+ def _find_project_root() -> Path:
43
+ """Walk up from cwd until .claude/ is found."""
44
+ init_cwd = os.environ.get("INIT_CWD")
45
+ if init_cwd and (Path(init_cwd) / ".claude").is_dir():
46
+ return Path(init_cwd)
47
+
48
+ current = Path.cwd()
49
+ root = Path(current.anchor)
50
+ while current != root:
51
+ if (current / ".claude").is_dir():
52
+ return current
53
+ current = current.parent
54
+
55
+ return Path(init_cwd) if init_cwd else Path.cwd()
56
+
57
+
58
+ def _read_json(path: Path):
59
+ """Read and parse a JSON file, returning None on any error."""
60
+ try:
61
+ return json.loads(path.read_text())
62
+ except Exception:
63
+ return None
64
+
65
+
66
+ def _package_root() -> Path:
67
+ """Return the gaia-ops package root (parent of bin/)."""
68
+ return Path(__file__).resolve().parent.parent.parent
69
+
70
+
71
+ # ============================================================================
72
+ # Health Checks
73
+ # ============================================================================
74
+
75
+ def check_gaia_version() -> dict:
76
+ """Check that package.json is readable and has a version."""
77
+ pkg_path = _package_root() / "package.json"
78
+ data = _read_json(pkg_path)
79
+ if data and "version" in data:
80
+ return _result("Gaia-Ops", "pass", f"v{data['version']}")
81
+ return _result("Gaia-Ops", "error", "Version unknown", "Reinstall @jaguilar87/gaia")
82
+
83
+
84
+ def check_claude_code() -> dict:
85
+ """Check if Claude Code CLI is installed."""
86
+ for cmd in ("claude", "claude-code"):
87
+ if shutil.which(cmd):
88
+ try:
89
+ proc = subprocess.run(
90
+ [cmd, "--version"],
91
+ capture_output=True,
92
+ text=True,
93
+ timeout=10,
94
+ )
95
+ version_line = proc.stdout.strip().split("\n")[0] if proc.stdout else cmd
96
+ return _result("Claude Code", "pass", version_line)
97
+ except Exception:
98
+ return _result("Claude Code", "pass", cmd)
99
+
100
+ return _result("Claude Code", "info", "Not installed", "npm install -g @anthropic-ai/claude-code")
101
+
102
+
103
+ def check_python() -> dict:
104
+ """Check Python version >= 3.9."""
105
+ version = sys.version.split()[0]
106
+ parts = version.split(".")
107
+ try:
108
+ major, minor = int(parts[0]), int(parts[1])
109
+ except (IndexError, ValueError):
110
+ return _result("Python", "error", f"Could not parse: {version}", "Install Python 3.9+")
111
+
112
+ if major < 3 or (major == 3 and minor < 9):
113
+ return _result("Python", "error", f"Python {version} (need >=3.9)", "Upgrade Python to 3.9+")
114
+
115
+ return _result("Python", "pass", f"Python {version}")
116
+
117
+
118
+ def check_plugin_mode(project_root: Path) -> dict:
119
+ """Check plugin mode from plugin-registry.json."""
120
+ registry_path = project_root / ".claude" / "plugin-registry.json"
121
+ if not registry_path.is_file():
122
+ return _result("Plugin mode", "warning", "No plugin-registry.json", "Run gaia-scan or restart Claude Code")
123
+
124
+ data = _read_json(registry_path)
125
+ if not data:
126
+ return _result("Plugin mode", "warning", "Invalid plugin-registry.json", "Delete and restart Claude Code")
127
+
128
+ installed = [p.get("name", "") for p in (data.get("installed") or [])]
129
+ source = data.get("source", "unknown")
130
+
131
+ if "gaia-ops" in installed:
132
+ return _result("Plugin mode", "pass", f"ops (source: {source})")
133
+ if "gaia-security" in installed:
134
+ return _result("Plugin mode", "pass", f"security (source: {source})")
135
+
136
+ return _result("Plugin mode", "warning", f"Unknown plugin: {', '.join(installed)}", "Verify installation")
137
+
138
+
139
+ def check_symlinks(project_root: Path) -> dict:
140
+ """Check .claude/ symlinks resolve to package content."""
141
+ names = ["agents", "tools", "hooks", "commands", "templates", "config", "skills", "CHANGELOG.md"]
142
+ critical = {"agents", "hooks", "skills"}
143
+ valid = 0
144
+ has_critical_missing = False
145
+
146
+ for name in names:
147
+ link_path = project_root / ".claude" / name
148
+ if link_path.exists():
149
+ try:
150
+ link_path.resolve(strict=True)
151
+ valid += 1
152
+ except OSError:
153
+ if name in critical:
154
+ has_critical_missing = True
155
+ else:
156
+ if name in critical:
157
+ has_critical_missing = True
158
+
159
+ total = len(names)
160
+ if valid == total:
161
+ return _result("Symlinks", "pass", f"{valid}/{total} valid")
162
+
163
+ severity = "error" if has_critical_missing else "warning"
164
+ return _result("Symlinks", severity, f"{valid}/{total} valid", "Run gaia-scan to recreate symlinks")
165
+
166
+
167
+ def check_identity(project_root: Path) -> dict:
168
+ """Check orchestrator agent is configured."""
169
+ issues = []
170
+ infos = []
171
+
172
+ agent_path = project_root / ".claude" / "agents" / "gaia-orchestrator.md"
173
+ if not agent_path.is_file():
174
+ issues.append("gaia-orchestrator.md not found")
175
+
176
+ local_settings = project_root / ".claude" / "settings.local.json"
177
+ if local_settings.is_file():
178
+ data = _read_json(local_settings)
179
+ if data:
180
+ agent = data.get("agent")
181
+ if agent == "gaia-orchestrator":
182
+ pass # correct
183
+ elif agent:
184
+ issues.append(f'Agent set to "{agent}" (expected "gaia-orchestrator")')
185
+ else:
186
+ issues.append("No agent field in settings.local.json")
187
+ else:
188
+ issues.append("settings.local.json missing")
189
+
190
+ claude_md = project_root / "CLAUDE.md"
191
+ if claude_md.is_file():
192
+ infos.append("Legacy CLAUDE.md present (no longer used)")
193
+
194
+ if issues:
195
+ return _result("Identity", "error", "; ".join(issues), "Run gaia-scan or gaia update")
196
+ if infos:
197
+ return _result("Identity", "info", f"Orchestrator configured -- {'; '.join(infos)}")
198
+ return _result("Identity", "pass", "Orchestrator agent configured")
199
+
200
+
201
+ def check_settings(project_root: Path) -> dict:
202
+ """Check settings.local.json for hooks, permissions, deny rules."""
203
+ local_path = project_root / ".claude" / "settings.local.json"
204
+ if not local_path.is_file():
205
+ return _result("Settings", "error", "settings.local.json missing", "Run gaia-scan or gaia update")
206
+
207
+ data = _read_json(local_path)
208
+ if not data:
209
+ return _result("Settings", "error", "Invalid JSON in settings.local.json", "Delete and run gaia-scan")
210
+
211
+ issues = []
212
+ infos = []
213
+
214
+ hooks_config = data.get("hooks")
215
+ if not hooks_config:
216
+ issues.append("No hooks configured")
217
+ else:
218
+ required = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SessionStart"]
219
+ missing = [h for h in required if h not in hooks_config]
220
+ if missing:
221
+ issues.append(f"Missing hooks: {', '.join(missing)}")
222
+
223
+ perms = data.get("permissions", {})
224
+ allow_count = len(perms.get("allow", []))
225
+ deny_count = len(perms.get("deny", []))
226
+ if allow_count == 0:
227
+ infos.append("No allow rules (tools will prompt for approval)")
228
+ if deny_count == 0:
229
+ issues.append("No deny rules (destructive commands not blocked)")
230
+
231
+ env = data.get("env", {})
232
+ if not env.get("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"):
233
+ infos.append("AGENT_TEAMS env not set")
234
+
235
+ if issues:
236
+ return _result("Settings", "error", "; ".join(issues), "Run gaia-scan or gaia update")
237
+
238
+ hook_count = len(hooks_config) if hooks_config else 0
239
+ perm_count = allow_count + deny_count
240
+
241
+ if infos:
242
+ return _result("Settings", "info", f"{hook_count} hook types, {perm_count} rules -- {'; '.join(infos)}")
243
+ return _result("Settings", "pass", f"{hook_count} hook types, {perm_count} rules")
244
+
245
+
246
+ def check_hook_files(project_root: Path) -> dict:
247
+ """Check all expected hook scripts exist."""
248
+ hooks = [
249
+ ("pre_tool_use.py", True),
250
+ ("post_tool_use.py", True),
251
+ ("user_prompt_submit.py", True),
252
+ ("session_start.py", True),
253
+ ("subagent_stop.py", False),
254
+ ("subagent_start.py", False),
255
+ ("stop_hook.py", False),
256
+ ("task_completed.py", False),
257
+ ("post_compact.py", False),
258
+ ("elicitation_result.py", False),
259
+ ]
260
+
261
+ errors = []
262
+ warnings = []
263
+ valid = 0
264
+ total = len(hooks)
265
+
266
+ for filename, required in hooks:
267
+ hook_path = project_root / ".claude" / "hooks" / filename
268
+ if hook_path.is_file():
269
+ valid += 1
270
+ elif required:
271
+ errors.append(f"{filename} missing")
272
+ else:
273
+ warnings.append(filename)
274
+
275
+ if errors:
276
+ return _result("Hook files", "error", "; ".join(errors), "Recreate symlinks: gaia-scan")
277
+ if warnings:
278
+ return _result(
279
+ "Hook files",
280
+ "warning",
281
+ f"{valid}/{total} found (missing: {', '.join(warnings)})",
282
+ "Run gaia-scan to recreate symlinks",
283
+ )
284
+ return _result("Hook files", "pass", f"{valid}/{total} found")
285
+
286
+
287
+ def check_project_context(project_root: Path) -> dict:
288
+ """Check project-context.json is valid and enriched."""
289
+ path = project_root / ".claude" / "project-context" / "project-context.json"
290
+ if not path.is_file():
291
+ return _result("project-context", "warning", "Missing", "Run gaia-scan")
292
+
293
+ data = _read_json(path)
294
+ if not data:
295
+ return _result("project-context", "warning", "Invalid JSON", "Regenerate with gaia-scan")
296
+
297
+ warnings = []
298
+ infos = []
299
+
300
+ if not data.get("metadata"):
301
+ warnings.append("Missing metadata section")
302
+ if not data.get("sections"):
303
+ warnings.append("Missing sections")
304
+
305
+ is_v2 = (data.get("metadata") or {}).get("version") == "2.0"
306
+
307
+ has_paths = bool((data.get("sections") or {}).get("infrastructure", {}).get("paths")) if is_v2 else bool(data.get("paths"))
308
+ if not has_paths:
309
+ infos.append("No paths section")
310
+
311
+ sections = data.get("sections")
312
+ if sections:
313
+ section_count = len(sections)
314
+ if section_count < 3:
315
+ infos.append(f"Only {section_count} sections (expected >=3)")
316
+ else:
317
+ section_count = 0
318
+
319
+ if warnings:
320
+ detail = "; ".join(warnings + infos)
321
+ return _result("project-context", "warning", detail, "Run gaia-scan to enrich")
322
+
323
+ if infos:
324
+ return _result("project-context", "info", f"{section_count} sections -- {'; '.join(infos)}")
325
+
326
+ return _result("project-context", "pass", f"{section_count} sections")
327
+
328
+
329
+ def check_project_dirs(project_root: Path) -> dict:
330
+ """Check paths declared in project-context exist on disk."""
331
+ context_path = project_root / ".claude" / "project-context" / "project-context.json"
332
+ if not context_path.is_file():
333
+ return _result("Project dirs", "pass", "Skipped (no context)")
334
+
335
+ data = _read_json(context_path)
336
+ if not data:
337
+ return _result("Project dirs", "pass", "Skipped (parse error)")
338
+
339
+ sections = data.get("sections") or {}
340
+ paths = sections.get("infrastructure", {}).get("paths") or data.get("paths") or {}
341
+ issues = []
342
+
343
+ for key, dir_path in paths.items():
344
+ if dir_path and not (project_root / dir_path).exists():
345
+ issues.append(f"{key}: {dir_path} not found")
346
+
347
+ if issues:
348
+ return _result("Project dirs", "warning", "; ".join(issues), "Create missing directories or update paths")
349
+
350
+ return _result("Project dirs", "pass", f"{len(paths)} paths verified")
351
+
352
+
353
+ def check_memory_fts5_db(project_root: Path) -> dict:
354
+ """Check if the FTS5 search.db exists for episodic memory."""
355
+ db_path = project_root / ".claude" / "project-context" / "episodic-memory" / "search.db"
356
+ if db_path.is_file():
357
+ return _result("memory_fts5_db", "pass", f"search.db present ({db_path.stat().st_size} bytes)")
358
+ return _result(
359
+ "memory_fts5_db",
360
+ "info",
361
+ "search.db not found (created on first use)",
362
+ "Run: gaia doctor --fix",
363
+ )
364
+
365
+
366
+ def check_memory_fts5_count(project_root: Path) -> dict:
367
+ """Check FTS5 indexed count against total episode count in index.json."""
368
+ index_path = project_root / ".claude" / "project-context" / "episodic-memory" / "index.json"
369
+
370
+ if not index_path.is_file():
371
+ return _result("memory_fts5_count", "info", "index.json not found — no episodes yet")
372
+
373
+ index_data = _read_json(index_path)
374
+ if not index_data:
375
+ return _result("memory_fts5_count", "info", "index.json unreadable")
376
+
377
+ total = len(index_data.get("episodes") or [])
378
+
379
+ try:
380
+ import sys as _sys
381
+ # Ensure package root is on path for lazy import
382
+ pkg_root = str(_package_root())
383
+ if pkg_root not in _sys.path:
384
+ _sys.path.insert(0, pkg_root)
385
+ from tools.memory import search_store # noqa: PLC0415
386
+ indexed = search_store.count()
387
+ except ImportError:
388
+ return _result(
389
+ "memory_fts5_count",
390
+ "info",
391
+ "tools.memory.search_store not importable — FTS5 count skipped",
392
+ )
393
+ except Exception as exc:
394
+ return _result("memory_fts5_count", "info", f"Could not query FTS5 count: {exc}")
395
+
396
+ if total == 0:
397
+ return _result("memory_fts5_count", "pass", "No episodes to index")
398
+
399
+ pct = indexed / total
400
+ if pct < 0.90:
401
+ return _result(
402
+ "memory_fts5_count",
403
+ "warning",
404
+ f"FTS5 index incomplete: {indexed}/{total} episodes indexed ({pct:.0%})",
405
+ "Run: gaia doctor --fix",
406
+ )
407
+ return _result("memory_fts5_count", "pass", f"{indexed}/{total} episodes indexed ({pct:.0%})")
408
+
409
+
410
+ def check_memory_scoring(project_root: Path) -> dict:
411
+ """Check that tools.memory.scoring is importable (scoring module available)."""
412
+ try:
413
+ import sys as _sys
414
+ pkg_root = str(_package_root())
415
+ if pkg_root not in _sys.path:
416
+ _sys.path.insert(0, pkg_root)
417
+ import tools.memory.scoring # noqa: F401, PLC0415
418
+ return _result("memory_scoring", "pass", "Scoring module importable")
419
+ except ImportError as exc:
420
+ return _result(
421
+ "memory_scoring",
422
+ "warning",
423
+ f"Scoring module unavailable: {exc} (scoring disabled)",
424
+ )
425
+ except Exception as exc:
426
+ return _result("memory_scoring", "warning", f"Scoring module error: {exc}")
427
+
428
+
429
+ def _apply_fts5_backfill(project_root: Path) -> dict:
430
+ """Run FTS5 backfill and return a fix-result dict."""
431
+ try:
432
+ import sys as _sys
433
+ pkg_root = str(_package_root())
434
+ if pkg_root not in _sys.path:
435
+ _sys.path.insert(0, pkg_root)
436
+
437
+ # Ensure backfill_fts5 finds the correct project root by setting cwd context
438
+ # via the module's own _find_project_root (walks up from cwd).
439
+ # We temporarily add project_root to env if needed, but the module uses cwd.
440
+ import os as _os
441
+ orig_cwd = _os.getcwd()
442
+ try:
443
+ _os.chdir(project_root)
444
+ from tools.memory import backfill_fts5 # noqa: PLC0415
445
+ rc = backfill_fts5.main()
446
+ finally:
447
+ _os.chdir(orig_cwd)
448
+
449
+ if rc == 0:
450
+ return {"name": "fts5_backfill", "status": "applied", "detail": "FTS5 index rebuilt successfully"}
451
+ return {"name": "fts5_backfill", "status": "failed", "detail": f"backfill_fts5.main() returned {rc}"}
452
+ except ImportError as exc:
453
+ return {"name": "fts5_backfill", "status": "failed", "detail": f"Cannot import backfill_fts5: {exc}"}
454
+ except Exception as exc:
455
+ return {"name": "fts5_backfill", "status": "failed", "detail": f"Backfill error: {exc}"}
456
+
457
+
458
+ def check_memory_dirs(project_root: Path) -> dict:
459
+ """Check episodic memory directories are present."""
460
+ checks = [
461
+ (
462
+ project_root / ".claude" / "project-context" / "workflow-episodic-memory",
463
+ "workflow-episodic-memory",
464
+ "warning",
465
+ "Run gaia-scan to create workflow memory directory",
466
+ ),
467
+ (
468
+ project_root / ".claude" / "project-context" / "episodic-memory",
469
+ "episodic-memory",
470
+ "info",
471
+ "Created automatically on first agent run",
472
+ ),
473
+ ]
474
+
475
+ warnings = []
476
+ infos = []
477
+ found = 0
478
+
479
+ for path, label, severity, fix in checks:
480
+ if path.is_dir():
481
+ found += 1
482
+ elif severity == "info":
483
+ infos.append({"label": label, "fix": fix})
484
+ else:
485
+ warnings.append({"label": label, "fix": fix})
486
+
487
+ total = len(checks)
488
+
489
+ if warnings:
490
+ detail = "; ".join(f"{w['label']} missing" for w in warnings)
491
+ return _result("Memory dirs", "warning", detail, warnings[0]["fix"])
492
+
493
+ if infos:
494
+ info_parts = ["{}: {}".format(i["label"], i["fix"]) for i in infos]
495
+ detail = "{}/{} present ({})".format(found, total, "; ".join(info_parts))
496
+ return _result("Memory dirs", "info", detail)
497
+
498
+ return _result("Memory dirs", "pass", f"{found}/{total} present")
499
+
500
+
501
+ # ============================================================================
502
+ # Severity display
503
+ # ============================================================================
504
+
505
+ _SEVERITY_ICONS = {
506
+ "pass": "PASS",
507
+ "info": "INFO",
508
+ "warning": "WARN",
509
+ "error": "FAIL",
510
+ }
511
+
512
+
513
+ def _print_human(results: list, version_detail: str = "") -> None:
514
+ """Print human-readable doctor output."""
515
+ version_tag = f" ({version_detail})" if version_detail else ""
516
+ print(f"\n Gaia-Ops Health Check{version_tag}\n")
517
+
518
+ for r in results:
519
+ icon = _SEVERITY_ICONS.get(r["severity"], "????")
520
+ print(f" [{icon}] {r['name']:<18} {r['detail']}")
521
+ if r["severity"] in ("warning", "error") and r.get("fix"):
522
+ print(f" Fix: {r['fix']}")
523
+
524
+ print()
525
+
526
+ has_errors = any(r["severity"] == "error" for r in results)
527
+ has_warnings = any(r["severity"] == "warning" for r in results)
528
+
529
+ if has_errors:
530
+ print(" Status: CRITICAL\n")
531
+ elif has_warnings:
532
+ print(" Status: ISSUES FOUND\n")
533
+ else:
534
+ print(" Status: HEALTHY\n")
535
+
536
+
537
+ # ============================================================================
538
+ # Command interface
539
+ # ============================================================================
540
+
541
+ def register(subparsers):
542
+ """Register the doctor subcommand."""
543
+ sub = subparsers.add_parser("doctor", help="Run Gaia-Ops health checks")
544
+ sub.add_argument("--json", action="store_true", default=False, help="Output as JSON")
545
+ sub.add_argument("--fix", action="store_true", default=False, help="Attempt auto-fix for common issues")
546
+
547
+
548
+ def cmd_doctor(args) -> int:
549
+ """Handler for `gaia doctor`."""
550
+ project_root = _find_project_root()
551
+
552
+ check_fns = [
553
+ lambda: check_gaia_version(),
554
+ lambda: check_claude_code(),
555
+ lambda: check_python(),
556
+ lambda: check_plugin_mode(project_root),
557
+ lambda: check_symlinks(project_root),
558
+ lambda: check_identity(project_root),
559
+ lambda: check_settings(project_root),
560
+ lambda: check_hook_files(project_root),
561
+ lambda: check_project_context(project_root),
562
+ lambda: check_project_dirs(project_root),
563
+ lambda: check_memory_dirs(project_root),
564
+ lambda: check_memory_fts5_db(project_root),
565
+ lambda: check_memory_fts5_count(project_root),
566
+ lambda: check_memory_scoring(project_root),
567
+ ]
568
+
569
+ results = []
570
+ for fn in check_fns:
571
+ try:
572
+ results.append(fn())
573
+ except Exception as exc:
574
+ results.append(_result(fn.__name__, "error", f"Error: {exc}"))
575
+
576
+ has_errors = any(r["severity"] == "error" for r in results)
577
+ has_warnings = any(r["severity"] == "warning" for r in results)
578
+
579
+ # --fix: run auto-fixers for triggered checks
580
+ fixes = []
581
+ if getattr(args, "fix", False):
582
+ fts5_db_check = next((r for r in results if r["name"] == "memory_fts5_db"), None)
583
+ fts5_count_check = next((r for r in results if r["name"] == "memory_fts5_count"), None)
584
+
585
+ db_needs_fix = fts5_db_check and fts5_db_check["severity"] == "info"
586
+ count_needs_fix = fts5_count_check and fts5_count_check["severity"] == "warning"
587
+
588
+ if db_needs_fix or count_needs_fix:
589
+ fix_result = _apply_fts5_backfill(project_root)
590
+ fixes.append(fix_result)
591
+
592
+ if fix_result["status"] == "applied":
593
+ # Re-run the affected checks to reflect post-fix state
594
+ if fts5_db_check:
595
+ idx = results.index(fts5_db_check)
596
+ results[idx] = check_memory_fts5_db(project_root)
597
+ if fts5_count_check:
598
+ idx = results.index(fts5_count_check)
599
+ results[idx] = check_memory_fts5_count(project_root)
600
+
601
+ # Recompute summary flags after re-checks
602
+ has_errors = any(r["severity"] == "error" for r in results)
603
+ has_warnings = any(r["severity"] == "warning" for r in results)
604
+
605
+ if getattr(args, "json", False):
606
+ status = "critical" if has_errors else "degraded" if has_warnings else "healthy"
607
+ output = {
608
+ "healthy": not has_errors and not has_warnings,
609
+ "status": status,
610
+ "checks": results,
611
+ "fixes": fixes,
612
+ }
613
+ print(json.dumps(output, indent=2))
614
+ else:
615
+ gaia_check = next((r for r in results if r["name"] == "Gaia-Ops"), None)
616
+ version_detail = gaia_check["detail"] if gaia_check and gaia_check["severity"] == "pass" else ""
617
+ _print_human(results, version_detail)
618
+ if fixes:
619
+ print(" Fixes applied:")
620
+ for fix in fixes:
621
+ print(f" [{fix['status'].upper()}] {fix['name']}: {fix['detail']}")
622
+ print()
623
+
624
+ if has_errors:
625
+ return 2
626
+ if has_warnings:
627
+ return 1
628
+ return 0