@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,686 @@
1
+ """
2
+ Setup / Installation Functions for gaia-scan
3
+
4
+ Ported from the original gaia-init — provides all the installation and setup
5
+ functionality that gaia-scan needs when operating on a fresh project
6
+ (Mode 1) or refreshing an existing project (Mode 2).
7
+
8
+ Functions:
9
+ - create_claude_directory: mkdir .claude/ with symlinks and subdirs
10
+ - copy_claude_md: deprecated no-op (identity now via submit hook)
11
+ - copy_settings_json: create minimal settings.json only if missing (non-invasive)
12
+ - install_git_hooks: copy commit-msg hook to all git repos
13
+ - ensure_gaia_ops_package: npm install @jaguilar87/gaia
14
+ - ensure_claude_code: check/install claude CLI
15
+ - generate_project_context: create/merge project-context.json
16
+ """
17
+
18
+ import json
19
+ import logging
20
+ import os
21
+ import platform
22
+ import shutil
23
+ import subprocess
24
+ from datetime import datetime, timezone
25
+ from pathlib import Path
26
+ from typing import Any, Dict, List, Optional
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Windows detection: junctions don't require admin privileges
31
+ _IS_WINDOWS = platform.system() == "Windows"
32
+
33
+
34
+ def _create_dir_link(target: str, link: str) -> None:
35
+ """Create a directory link: junction on Windows, symlink on Unix.
36
+
37
+ Windows junctions don't require admin/developer-mode privileges,
38
+ unlike directory symlinks. The target must be an absolute path
39
+ on Windows (junctions don't support relative targets).
40
+ """
41
+ if _IS_WINDOWS:
42
+ import _winapi # stdlib on Windows, unavailable elsewhere
43
+ _winapi.CreateJunction(target, link)
44
+ else:
45
+ os.symlink(target, link)
46
+
47
+
48
+ def _find_package_root() -> Path:
49
+ """Find the gaia-ops plugin root directory.
50
+
51
+ Returns the directory containing this file's grandparent (tools/scan/setup.py
52
+ -> tools/ -> plugin root). This works both when running from the plugin
53
+ directory directly and when installed as a package.
54
+ """
55
+ return Path(__file__).resolve().parent.parent.parent
56
+
57
+
58
+ def _find_installed_package_root(project_root: Path) -> Optional[Path]:
59
+ """Find the installed @jaguilar87/gaia package in node_modules.
60
+
61
+ Args:
62
+ project_root: Project root directory.
63
+
64
+ Returns:
65
+ Path to the package root, or None if not found.
66
+ """
67
+ pkg_path = project_root / "node_modules" / "@jaguilar87" / "gaia"
68
+ if pkg_path.is_dir():
69
+ return pkg_path
70
+ return None
71
+
72
+
73
+ def ensure_gaia_ops_package(project_root: Path) -> bool:
74
+ """Ensure @jaguilar87/gaia is installed as npm dependency.
75
+
76
+ Checks node_modules for the package. If not found, creates package.json
77
+ if needed and runs npm install.
78
+
79
+ Args:
80
+ project_root: Project root directory.
81
+
82
+ Returns:
83
+ True if package is available (already installed or newly installed).
84
+ """
85
+ pkg_path = project_root / "node_modules" / "@jaguilar87" / "gaia" / "package.json"
86
+ if pkg_path.is_file():
87
+ logger.info("@jaguilar87/gaia already installed")
88
+ return True
89
+
90
+ # Create package.json if missing
91
+ package_json_path = project_root / "package.json"
92
+ if not package_json_path.is_file():
93
+ initial_pkg = {
94
+ "name": "my-project",
95
+ "version": "1.0.0",
96
+ "private": True,
97
+ "dependencies": {},
98
+ }
99
+ package_json_path.write_text(json.dumps(initial_pkg, indent=2))
100
+
101
+ try:
102
+ subprocess.run(
103
+ ["npm", "install", "@jaguilar87/gaia"],
104
+ cwd=str(project_root),
105
+ capture_output=True,
106
+ text=True,
107
+ timeout=120,
108
+ check=True,
109
+ )
110
+ logger.info("@jaguilar87/gaia installed")
111
+ return True
112
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError) as exc:
113
+ logger.error("Failed to install @jaguilar87/gaia: %s", exc)
114
+ return False
115
+
116
+
117
+ def ensure_claude_code(skip_install: bool = False) -> Dict[str, Any]:
118
+ """Check if Claude Code CLI is installed, optionally install it.
119
+
120
+ Args:
121
+ skip_install: If True, skip installation attempt.
122
+
123
+ Returns:
124
+ Dict with 'installed' (bool) and 'version' (str or None).
125
+ """
126
+ # Try to get version
127
+ for cmd in ["claude --version", "claude-code --version"]:
128
+ try:
129
+ result = subprocess.run(
130
+ cmd.split(),
131
+ capture_output=True,
132
+ text=True,
133
+ timeout=10,
134
+ )
135
+ if result.returncode == 0:
136
+ version = result.stdout.strip().split("\n")[0]
137
+ return {"installed": True, "version": version}
138
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
139
+ continue
140
+
141
+ if skip_install:
142
+ logger.warning("Claude Code not installed (--skip-claude-install used)")
143
+ return {"installed": False, "version": None}
144
+
145
+ # Attempt installation
146
+ try:
147
+ subprocess.run(
148
+ ["npm", "install", "-g", "@anthropic-ai/claude-code"],
149
+ capture_output=True,
150
+ text=True,
151
+ timeout=120,
152
+ check=True,
153
+ )
154
+ logger.info("Claude Code installed")
155
+ return {"installed": True, "version": "newly installed"}
156
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError) as exc:
157
+ logger.warning("Failed to install Claude Code: %s", exc)
158
+ return {"installed": False, "version": None}
159
+
160
+
161
+ def create_claude_directory(project_root: Path) -> List[str]:
162
+ """Create .claude/ directory with symlinks to the gaia-ops package.
163
+
164
+ Creates:
165
+ - Symlinks: agents, tools, hooks, commands, templates, config, skills, CHANGELOG.md
166
+ - Directories: logs, tests, project-context, project-context/workflow-episodic-memory, approvals
167
+
168
+ Args:
169
+ project_root: Project root directory.
170
+
171
+ Returns:
172
+ List of created symlink names (for reporting).
173
+ """
174
+ claude_dir = project_root / ".claude"
175
+ claude_dir.mkdir(exist_ok=True)
176
+
177
+ # Find the installed package for symlinks
178
+ package_path = _find_installed_package_root(project_root)
179
+ if package_path is None:
180
+ # Fallback: use the plugin root directly (running from source)
181
+ package_path = _find_package_root()
182
+
183
+ # Compute relative path from .claude/ to the package
184
+ try:
185
+ rel_path = os.path.relpath(str(package_path), str(claude_dir))
186
+ except ValueError:
187
+ # On Windows, relpath can fail across drives
188
+ rel_path = str(package_path)
189
+
190
+ # Create symlinks
191
+ symlink_names = [
192
+ "agents", "tools", "hooks", "commands",
193
+ "templates", "config", "skills",
194
+ ]
195
+ created = []
196
+
197
+ for name in symlink_names:
198
+ link_path = claude_dir / name
199
+ # Junctions on Windows require absolute targets; symlinks on Unix use relative
200
+ if _IS_WINDOWS:
201
+ target = str(package_path / name)
202
+ else:
203
+ target = os.path.join(rel_path, name)
204
+
205
+ if link_path.exists() or link_path.is_symlink():
206
+ link_path.unlink()
207
+
208
+ try:
209
+ _create_dir_link(target, str(link_path))
210
+ created.append(name)
211
+ except OSError as exc:
212
+ logger.warning("Failed to create symlink %s: %s", name, exc)
213
+
214
+ # CHANGELOG.md symlink (file, not directory — junctions only work for dirs)
215
+ changelog_link = claude_dir / "CHANGELOG.md"
216
+ if changelog_link.exists() or changelog_link.is_symlink():
217
+ changelog_link.unlink()
218
+ try:
219
+ if _IS_WINDOWS:
220
+ # File symlinks need admin on Windows; copy instead
221
+ shutil.copy2(str(package_path / "CHANGELOG.md"), str(changelog_link))
222
+ else:
223
+ os.symlink(os.path.join(rel_path, "CHANGELOG.md"), str(changelog_link))
224
+ created.append("CHANGELOG.md")
225
+ except OSError as exc:
226
+ logger.warning("Failed to create CHANGELOG.md symlink: %s", exc)
227
+
228
+ # Create project-specific directories (NOT symlinked)
229
+ for subdir in [
230
+ "logs",
231
+ "tests",
232
+ "project-context",
233
+ os.path.join("project-context", "workflow-episodic-memory"),
234
+ "approvals",
235
+ ]:
236
+ (claude_dir / subdir).mkdir(parents=True, exist_ok=True)
237
+
238
+ return created
239
+
240
+
241
+ def copy_claude_md(project_root: Path) -> bool:
242
+ """Deprecated — CLAUDE.md is no longer generated from template.
243
+
244
+ Orchestrator identity lives in agents/gaia-orchestrator.md, activated via
245
+ settings.local.json agent field.
246
+
247
+ Kept as no-op for backward compatibility with callers.
248
+ """
249
+ logger.info("copy_claude_md skipped — identity now injected via submit hook")
250
+ return True
251
+
252
+
253
+ def copy_settings_json(project_root: Path) -> bool:
254
+ """Create a minimal .claude/settings.json only if it does not exist.
255
+
256
+ Non-invasive: never overwrites an existing settings.json. Hooks are
257
+ provided by hooks.json (auto-discovered via the .claude/hooks symlink).
258
+ Env vars and permissions live in settings.local.json.
259
+
260
+ Args:
261
+ project_root: Project root directory.
262
+
263
+ Returns:
264
+ True if file exists (created or already present).
265
+ """
266
+ dest_path = project_root / ".claude" / "settings.json"
267
+
268
+ if dest_path.is_file():
269
+ logger.info("settings.json already exists — not overwriting")
270
+ return True
271
+
272
+ try:
273
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
274
+ dest_path.write_text("{}\n")
275
+ logger.info("settings.json created (minimal — hooks from hooks.json, env from settings.local.json)")
276
+ return True
277
+ except OSError as exc:
278
+ logger.error("Failed to write settings.json: %s", exc)
279
+ return False
280
+
281
+
282
+ def merge_hooks_to_settings_local(project_root: Path) -> bool:
283
+ """Merge hooks from hooks.json into .claude/settings.local.json.
284
+
285
+ In npm mode, Claude Code reads hooks from settings files, not hooks.json
286
+ directly. This reads hooks.json from the installed package, converts
287
+ ${CLAUDE_PLUGIN_ROOT}/hooks/<script> paths to .claude/hooks/<script>,
288
+ and merges into settings.local.json with deduplication by command string.
289
+
290
+ Args:
291
+ project_root: Project root directory.
292
+
293
+ Returns:
294
+ True if settings.local.json was modified.
295
+ """
296
+ import re
297
+
298
+ claude_dir = project_root / ".claude"
299
+ settings_path = claude_dir / "settings.local.json"
300
+
301
+ # Find hooks.json from the package
302
+ hooks_json_path = None
303
+ # Strategy 1: installed npm package
304
+ pkg_root = _find_installed_package_root(project_root)
305
+ if pkg_root:
306
+ candidate = pkg_root / "hooks" / "hooks.json"
307
+ if candidate.is_file():
308
+ hooks_json_path = candidate
309
+ # Strategy 2: running from source (gaia-scan direct)
310
+ if not hooks_json_path:
311
+ candidate = _find_package_root() / "hooks" / "hooks.json"
312
+ if candidate.is_file():
313
+ hooks_json_path = candidate
314
+
315
+ if not hooks_json_path:
316
+ logger.info("hooks.json not found, skipping hooks merge")
317
+ return False
318
+
319
+ try:
320
+ hooks_data = json.loads(hooks_json_path.read_text())
321
+ except (json.JSONDecodeError, OSError):
322
+ logger.warning("hooks.json is invalid, skipping hooks merge")
323
+ return False
324
+
325
+ # Unwrap outer "hooks" key if present
326
+ source_hooks = hooks_data.get("hooks", hooks_data)
327
+
328
+ # Convert ${CLAUDE_PLUGIN_ROOT}/hooks/<script> to .claude/hooks/<script>
329
+ def convert_command(cmd: str) -> str:
330
+ return re.sub(r'\$\{CLAUDE_PLUGIN_ROOT\}/hooks/', '.claude/hooks/', cmd)
331
+
332
+ converted_hooks: Dict[str, list] = {}
333
+ for event, entries in source_hooks.items():
334
+ converted_hooks[event] = []
335
+ for entry in entries:
336
+ new_entry = dict(entry)
337
+ if "hooks" in new_entry:
338
+ new_entry["hooks"] = [
339
+ {**h, "command": convert_command(h["command"])} if "command" in h else h
340
+ for h in new_entry["hooks"]
341
+ ]
342
+ converted_hooks[event].append(new_entry)
343
+
344
+ # Load existing settings.local.json
345
+ existing: Dict[str, Any] = {}
346
+ if settings_path.exists():
347
+ try:
348
+ existing = json.loads(settings_path.read_text())
349
+ except (json.JSONDecodeError, OSError):
350
+ existing = {}
351
+
352
+ # Smart merge: deduplicate by command string
353
+ existing_hooks = existing.get("hooks", {})
354
+ changed = False
355
+
356
+ for event, new_entries in converted_hooks.items():
357
+ if event not in existing_hooks:
358
+ existing_hooks[event] = new_entries
359
+ changed = True
360
+ continue
361
+
362
+ # Collect existing command strings
363
+ existing_commands: set = set()
364
+ for entry in existing_hooks[event]:
365
+ for h in entry.get("hooks", []):
366
+ if "command" in h:
367
+ existing_commands.add(h["command"])
368
+
369
+ # Add entries whose commands are not already present
370
+ for new_entry in new_entries:
371
+ new_commands = [h["command"] for h in new_entry.get("hooks", []) if "command" in h]
372
+ all_present = len(new_commands) > 0 and all(c in existing_commands for c in new_commands)
373
+ if not all_present:
374
+ existing_hooks[event].append(new_entry)
375
+ changed = True
376
+
377
+ if not changed:
378
+ logger.info("settings.local.json hooks already up to date")
379
+ return False
380
+
381
+ existing["hooks"] = existing_hooks
382
+ claude_dir.mkdir(parents=True, exist_ok=True)
383
+ settings_path.write_text(json.dumps(existing, indent=2) + "\n")
384
+ logger.info("Merged hooks into %s", settings_path)
385
+ return True
386
+
387
+
388
+ def install_git_hooks(project_root: Path) -> int:
389
+ """Install commit-msg git hook to all detected git repositories.
390
+
391
+ Copies git-hooks/commit-msg from the package to .git/hooks/ in all
392
+ repos found in the project root and its immediate subdirectories.
393
+
394
+ Args:
395
+ project_root: Project root directory.
396
+
397
+ Returns:
398
+ Number of repos where hooks were installed.
399
+ """
400
+ hook_source = _find_package_root() / "git-hooks" / "commit-msg"
401
+ if not hook_source.is_file():
402
+ logger.warning("git-hooks/commit-msg not found in package, skipping")
403
+ return 0
404
+
405
+ # Find git repos: project root and immediate subdirectories
406
+ candidates = [project_root]
407
+ try:
408
+ for entry in project_root.iterdir():
409
+ if entry.is_dir() and not entry.name.startswith(".") and entry.name != "node_modules":
410
+ candidates.append(entry)
411
+ except OSError:
412
+ pass
413
+
414
+ installed = 0
415
+ for dir_path in candidates:
416
+ git_hooks_dir = dir_path / ".git" / "hooks"
417
+ if not git_hooks_dir.is_dir():
418
+ continue
419
+
420
+ dest = git_hooks_dir / "commit-msg"
421
+ try:
422
+ shutil.copy2(str(hook_source), str(dest))
423
+ os.chmod(str(dest), 0o755)
424
+ installed += 1
425
+ except OSError as exc:
426
+ logger.warning("Failed to install hook in %s: %s", dir_path, exc)
427
+
428
+ return installed
429
+
430
+
431
+ def generate_project_context(
432
+ project_root: Path,
433
+ config: Dict[str, Any],
434
+ scan_context: Optional[Dict[str, Any]] = None,
435
+ ) -> bool:
436
+ """Generate or merge project-context.json from config and scan results.
437
+
438
+ For fresh projects (no existing file): writes a full generated context
439
+ that includes scan results if available.
440
+
441
+ For existing projects: merges metadata and paths from scan, preserves
442
+ agent-enriched sections.
443
+
444
+ Args:
445
+ project_root: Project root directory.
446
+ config: Configuration dict with detected/user-provided values.
447
+ scan_context: Full context from scan orchestrator (if available).
448
+
449
+ Returns:
450
+ True if file was written successfully.
451
+ """
452
+ dest_path = (
453
+ project_root / ".claude" / "project-context" / "project-context.json"
454
+ )
455
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
456
+
457
+ now_iso = datetime.now(timezone.utc).isoformat()
458
+
459
+ # If we have scan_context from the orchestrator, it already has the
460
+ # correct v2 schema structure. Use it as the base.
461
+ if scan_context:
462
+ # Enrich scan context with user-provided config values
463
+ context = _enrich_scan_context(scan_context, config, now_iso, project_root)
464
+ else:
465
+ # Build a minimal context from config alone
466
+ context = _build_minimal_context(config, now_iso, project_root)
467
+
468
+ try:
469
+ if not dest_path.is_file():
470
+ # First-time install: write the full context
471
+ dest_path.write_text(json.dumps(context, indent=2) + "\n")
472
+ logger.info("project-context.json generated")
473
+ return True
474
+
475
+ # File exists -- merge
476
+ try:
477
+ existing = json.loads(dest_path.read_text())
478
+ except (json.JSONDecodeError, OSError):
479
+ dest_path.write_text(json.dumps(context, indent=2) + "\n")
480
+ logger.info("project-context.json regenerated (previous was invalid)")
481
+ return True
482
+
483
+ merged = _merge_project_context(existing, context)
484
+ dest_path.write_text(json.dumps(merged, indent=2) + "\n")
485
+ logger.info("project-context.json updated (metadata+paths synced, sections preserved)")
486
+ return True
487
+
488
+ except OSError as exc:
489
+ logger.error("Failed to write project-context.json: %s", exc)
490
+ return False
491
+
492
+
493
+ def _enrich_scan_context(
494
+ scan_context: Dict[str, Any],
495
+ config: Dict[str, Any],
496
+ now_iso: str,
497
+ project_root: Path,
498
+ ) -> Dict[str, Any]:
499
+ """Enrich scan context with user-provided config values."""
500
+ import copy
501
+ context = copy.deepcopy(scan_context)
502
+
503
+ # Ensure metadata exists
504
+ meta = context.setdefault("metadata", {})
505
+ meta["version"] = meta.get("version", "2.0")
506
+ meta["last_updated"] = now_iso
507
+ meta["created_by"] = "gaia-scan"
508
+
509
+ # Update infrastructure.paths from config (user overrides trump scan)
510
+ sections = context.setdefault("sections", {})
511
+ infra = sections.setdefault("infrastructure", {})
512
+ infra_paths = infra.setdefault("paths", {})
513
+ if config.get("gitops"):
514
+ infra_paths["gitops"] = config["gitops"]
515
+ if config.get("terraform"):
516
+ infra_paths["terraform"] = config["terraform"]
517
+ if config.get("app_services"):
518
+ infra_paths["app_services"] = config["app_services"]
519
+ # Remove top-level paths if present (single source: infrastructure.paths)
520
+ context.pop("paths", None)
521
+
522
+ # Enrich sections from contract file
523
+ _enrich_from_contracts(context, config, project_root)
524
+
525
+ return context
526
+
527
+
528
+ def _build_minimal_context(
529
+ config: Dict[str, Any],
530
+ now_iso: str,
531
+ project_root: Path,
532
+ ) -> Dict[str, Any]:
533
+ """Build a minimal project-context.json from config when no scan data available."""
534
+ cloud_provider = config.get("cloud_provider", "gcp")
535
+ project_name = config.get("project_name", project_root.name)
536
+
537
+ metadata = {
538
+ "version": "2.0",
539
+ "last_updated": now_iso,
540
+ "project_name": project_name,
541
+ "project_root": ".",
542
+ "created_by": "gaia-scan",
543
+ "cloud_provider": cloud_provider,
544
+ "environment": "non-prod",
545
+ "primary_region": config.get("region", ""),
546
+ }
547
+
548
+ if cloud_provider in ("gcp", "multi-cloud") and config.get("project_id"):
549
+ metadata["project_id"] = config["project_id"]
550
+ if cloud_provider in ("aws", "multi-cloud") and config.get("project_id"):
551
+ metadata["aws_account"] = config["project_id"]
552
+
553
+ cloud_entry: Dict[str, Any] = {
554
+ "name": cloud_provider,
555
+ "region": config.get("region", ""),
556
+ }
557
+ if cloud_provider in ("gcp", "multi-cloud") and config.get("project_id"):
558
+ cloud_entry["project_id"] = config["project_id"]
559
+ if cloud_provider in ("aws", "multi-cloud") and config.get("project_id"):
560
+ cloud_entry["account_id"] = config["project_id"]
561
+
562
+ # Build paths dict, filtering out empty strings
563
+ infra_paths: Dict[str, str] = {}
564
+ for key in ("gitops", "terraform", "app_services"):
565
+ val = config.get(key, "")
566
+ if val:
567
+ infra_paths[key] = val
568
+
569
+ context = {
570
+ "metadata": metadata,
571
+ "sections": {
572
+ "project_identity": {
573
+ "name": project_name,
574
+ "type": "application",
575
+ },
576
+ "stack": {"languages": [], "frameworks": [], "build_tools": []},
577
+ "git": {
578
+ "platform": config.get("git_platform"),
579
+ "remotes": [],
580
+ "default_branch": "main",
581
+ },
582
+ "environment": {"runtimes": [], "os": {}},
583
+ "infrastructure": {
584
+ "cloud_providers": [cloud_entry],
585
+ "ci_cd": (
586
+ [{"platform": config["ci_platform"]}]
587
+ if config.get("ci_platform")
588
+ else []
589
+ ),
590
+ "paths": infra_paths,
591
+ },
592
+ "operational_guidelines": {
593
+ "commit_standards": {
594
+ "format": "conventional_commits",
595
+ "validation_required": True,
596
+ "config_path": ".claude/config/git_standards.json",
597
+ },
598
+ },
599
+ },
600
+ }
601
+
602
+ _enrich_from_contracts(context, config, project_root)
603
+ return context
604
+
605
+
606
+ def _enrich_from_contracts(
607
+ context: Dict[str, Any],
608
+ config: Dict[str, Any],
609
+ project_root: Path,
610
+ ) -> None:
611
+ """Enrich context sections from contract file (progressive context enrichment).
612
+
613
+ Only creates empty {} placeholders for scanner-owned sections that agents
614
+ need to read. Agent-enriched and mixed sections are NOT pre-created --
615
+ they should only exist when populated with actual data. The exception is
616
+ architecture_overview, which always exists (even empty) because all agent
617
+ contracts reference it.
618
+ """
619
+ try:
620
+ cloud_provider = config.get("cloud_provider", "gcp")
621
+ provider = "gcp" if cloud_provider == "multi-cloud" else cloud_provider
622
+ contract_path = _find_package_root() / "config" / f"context-contracts.{provider}.json"
623
+
624
+ if not contract_path.is_file():
625
+ return
626
+
627
+ contracts = json.loads(contract_path.read_text())
628
+ contract_sections: set = set()
629
+ for agent in (contracts.get("agents") or {}).values():
630
+ for s in agent.get("read", []):
631
+ contract_sections.add(s)
632
+ for s in agent.get("write", []):
633
+ contract_sections.add(s)
634
+
635
+ # Sections that should NOT be pre-created as empty {}.
636
+ # They only exist when an agent or scanner populates them with data.
637
+ # architecture_overview is the exception -- always present.
638
+ from tools.scan.merge import AGENT_ENRICHED_SECTIONS, MIXED_SECTION_SCANNER_FIELDS
639
+ skip_empty = (
640
+ AGENT_ENRICHED_SECTIONS
641
+ | frozenset(MIXED_SECTION_SCANNER_FIELDS.keys())
642
+ ) - {"architecture_overview"}
643
+
644
+ sections = context.setdefault("sections", {})
645
+ for section in contract_sections:
646
+ if section not in sections:
647
+ if section in skip_empty:
648
+ continue
649
+ sections[section] = {}
650
+
651
+ except (json.JSONDecodeError, OSError):
652
+ pass
653
+
654
+
655
+ def _merge_project_context(
656
+ existing: Dict[str, Any],
657
+ new_context: Dict[str, Any],
658
+ ) -> Dict[str, Any]:
659
+ """Merge new context into existing, preserving agent-enriched sections.
660
+
661
+ Strategy:
662
+ - metadata: field-by-field replace from new
663
+ - paths: field-by-field replace from new
664
+ - sections: preserve existing content; add new sections if absent
665
+ """
666
+ import copy
667
+
668
+ merged = {
669
+ "metadata": {
670
+ **(existing.get("metadata") or {}),
671
+ **(new_context.get("metadata") or {}),
672
+ "last_updated": datetime.now(timezone.utc).isoformat(),
673
+ },
674
+ "sections": {
675
+ # Start from new context sections as schema base,
676
+ # then override with existing sections that have content
677
+ **(new_context.get("sections") or {}),
678
+ **{
679
+ k: v
680
+ for k, v in (existing.get("sections") or {}).items()
681
+ if v is not None and isinstance(v, dict) and len(v) > 0
682
+ },
683
+ },
684
+ }
685
+
686
+ return merged
@@ -0,0 +1 @@
1
+ """Scan module tests."""