@jaguilar87/gaia-ops 4.4.0 → 4.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +12 -3
  3. package/ARCHITECTURE.md +9 -8
  4. package/CHANGELOG.md +34 -0
  5. package/README.md +43 -11
  6. package/agents/terraform-architect.md +1 -1
  7. package/bin/README.md +2 -2
  8. package/bin/gaia-doctor.js +18 -5
  9. package/bin/gaia-history.js +0 -1
  10. package/bin/gaia-metrics.js +2 -2
  11. package/bin/gaia-scan.py +23 -1
  12. package/bin/gaia-update.js +346 -54
  13. package/bin/pre-publish-validate.js +33 -10
  14. package/commands/gaia.md +37 -0
  15. package/config/README.md +3 -9
  16. package/config/context-contracts.json +47 -15
  17. package/config/surface-routing.json +9 -1
  18. package/dist/gaia-ops/.claude-plugin/plugin.json +22 -0
  19. package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
  20. package/dist/gaia-ops/agents/devops-developer.md +57 -0
  21. package/dist/gaia-ops/agents/gaia-system.md +58 -0
  22. package/dist/gaia-ops/agents/gitops-operator.md +60 -0
  23. package/dist/gaia-ops/agents/speckit-planner.md +71 -0
  24. package/dist/gaia-ops/agents/terraform-architect.md +60 -0
  25. package/dist/gaia-ops/commands/gaia.md +37 -0
  26. package/dist/gaia-ops/config/README.md +58 -0
  27. package/dist/gaia-ops/config/cloud/aws.json +140 -0
  28. package/dist/gaia-ops/config/cloud/gcp.json +145 -0
  29. package/dist/gaia-ops/config/context-contracts.json +131 -0
  30. package/dist/gaia-ops/config/git_standards.json +72 -0
  31. package/dist/gaia-ops/config/surface-routing.json +197 -0
  32. package/dist/gaia-ops/config/universal-rules.json +10 -0
  33. package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
  34. package/dist/gaia-ops/hooks/adapters/base.py +219 -0
  35. package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
  36. package/dist/gaia-ops/hooks/adapters/claude_code.py +1477 -0
  37. package/dist/gaia-ops/hooks/adapters/types.py +194 -0
  38. package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
  39. package/dist/gaia-ops/hooks/hooks.json +126 -0
  40. package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
  41. package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
  42. package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
  43. package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
  44. package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +124 -0
  45. package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
  46. package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
  47. package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
  48. package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
  49. package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
  50. package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
  51. package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
  52. package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +576 -0
  53. package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
  54. package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
  55. package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
  56. package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +215 -0
  57. package/dist/gaia-ops/hooks/modules/context/context_cache.py +129 -0
  58. package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
  59. package/dist/gaia-ops/hooks/modules/context/context_injector.py +427 -0
  60. package/dist/gaia-ops/hooks/modules/context/context_writer.py +518 -0
  61. package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
  62. package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
  63. package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
  64. package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
  65. package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
  66. package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +558 -0
  67. package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
  68. package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
  69. package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
  70. package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
  71. package/dist/gaia-ops/hooks/modules/identity/__init__.py +0 -0
  72. package/dist/gaia-ops/hooks/modules/identity/identity_provider.py +21 -0
  73. package/dist/gaia-ops/hooks/modules/identity/ops_identity.py +34 -0
  74. package/dist/gaia-ops/hooks/modules/identity/security_identity.py +10 -0
  75. package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
  76. package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +227 -0
  77. package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
  78. package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +128 -0
  79. package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
  80. package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
  81. package/dist/gaia-ops/hooks/modules/security/__init__.py +89 -0
  82. package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
  83. package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
  84. package/dist/gaia-ops/hooks/modules/security/approval_grants.py +912 -0
  85. package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
  86. package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +153 -0
  87. package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +584 -0
  88. package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +86 -0
  89. package/dist/gaia-ops/hooks/modules/security/command_semantics.py +130 -0
  90. package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
  91. package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +850 -0
  92. package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
  93. package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
  94. package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
  95. package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
  96. package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +158 -0
  97. package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
  98. package/dist/gaia-ops/hooks/modules/tools/__init__.py +25 -0
  99. package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +708 -0
  100. package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +181 -0
  101. package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
  102. package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
  103. package/dist/gaia-ops/hooks/modules/tools/task_validator.py +283 -0
  104. package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
  105. package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
  106. package/dist/gaia-ops/hooks/post_compact.py +43 -0
  107. package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
  108. package/dist/gaia-ops/hooks/pre_tool_use.py +383 -0
  109. package/dist/gaia-ops/hooks/session_start.py +69 -0
  110. package/dist/gaia-ops/hooks/stop_hook.py +69 -0
  111. package/dist/gaia-ops/hooks/subagent_start.py +71 -0
  112. package/dist/gaia-ops/hooks/subagent_stop.py +288 -0
  113. package/dist/gaia-ops/hooks/task_completed.py +70 -0
  114. package/dist/gaia-ops/hooks/user_prompt_submit.py +177 -0
  115. package/dist/gaia-ops/settings.json +72 -0
  116. package/dist/gaia-ops/skills/README.md +109 -0
  117. package/dist/gaia-ops/skills/agent-protocol/SKILL.md +105 -0
  118. package/dist/gaia-ops/skills/agent-protocol/examples.md +170 -0
  119. package/dist/gaia-ops/skills/agent-response/SKILL.md +53 -0
  120. package/dist/gaia-ops/skills/approval/SKILL.md +85 -0
  121. package/dist/gaia-ops/skills/approval/examples.md +140 -0
  122. package/dist/gaia-ops/skills/approval/reference.md +57 -0
  123. package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
  124. package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
  125. package/dist/gaia-ops/skills/context-updater/SKILL.md +76 -0
  126. package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
  127. package/dist/gaia-ops/skills/developer-patterns/SKILL.md +93 -0
  128. package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
  129. package/dist/gaia-ops/skills/execution/SKILL.md +66 -0
  130. package/dist/gaia-ops/skills/fast-queries/SKILL.md +47 -0
  131. package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +92 -0
  132. package/dist/gaia-ops/skills/gaia-patterns/reference.md +22 -0
  133. package/dist/gaia-ops/skills/git-conventions/SKILL.md +48 -0
  134. package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +73 -0
  135. package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
  136. package/dist/gaia-ops/skills/investigation/SKILL.md +77 -0
  137. package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +64 -0
  138. package/dist/gaia-ops/skills/reference.md +134 -0
  139. package/dist/gaia-ops/skills/security-tiers/SKILL.md +61 -0
  140. package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
  141. package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
  142. package/dist/gaia-ops/skills/skill-creation/SKILL.md +119 -0
  143. package/dist/gaia-ops/skills/specification/SKILL.md +186 -0
  144. package/dist/gaia-ops/skills/speckit-workflow/SKILL.md +165 -0
  145. package/dist/gaia-ops/skills/speckit-workflow/reference.md +117 -0
  146. package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +63 -0
  147. package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
  148. package/dist/gaia-ops/speckit/README.md +516 -0
  149. package/dist/gaia-ops/speckit/scripts/.gitkeep +0 -0
  150. package/dist/gaia-ops/speckit/templates/adr-template.md +118 -0
  151. package/dist/gaia-ops/speckit/templates/agent-file-template.md +23 -0
  152. package/dist/gaia-ops/speckit/templates/plan-template.md +227 -0
  153. package/dist/gaia-ops/speckit/templates/spec-template.md +140 -0
  154. package/dist/gaia-ops/speckit/templates/tasks-template.md +257 -0
  155. package/dist/gaia-ops/tools/context/README.md +132 -0
  156. package/dist/gaia-ops/tools/context/__init__.py +42 -0
  157. package/dist/gaia-ops/tools/context/_paths.py +20 -0
  158. package/dist/gaia-ops/tools/context/context_provider.py +476 -0
  159. package/dist/gaia-ops/tools/context/context_section_reader.py +330 -0
  160. package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
  161. package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
  162. package/dist/gaia-ops/tools/context/surface_router.py +278 -0
  163. package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
  164. package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
  165. package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  166. package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  167. package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  168. package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  169. package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
  170. package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  171. package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
  172. package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
  173. package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
  174. package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
  175. package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
  176. package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
  177. package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +262 -0
  178. package/dist/gaia-ops/tools/memory/README.md +0 -0
  179. package/dist/gaia-ops/tools/memory/__init__.py +20 -0
  180. package/dist/gaia-ops/tools/memory/episodic.py +1196 -0
  181. package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
  182. package/dist/gaia-ops/tools/review/__init__.py +1 -0
  183. package/dist/gaia-ops/tools/review/review_engine.py +157 -0
  184. package/dist/gaia-ops/tools/scan/__init__.py +35 -0
  185. package/dist/gaia-ops/tools/scan/config.py +247 -0
  186. package/dist/gaia-ops/tools/scan/merge.py +212 -0
  187. package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
  188. package/dist/gaia-ops/tools/scan/registry.py +127 -0
  189. package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
  190. package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
  191. package/dist/gaia-ops/tools/scan/scanners/environment.py +324 -0
  192. package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
  193. package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
  194. package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
  195. package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
  196. package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
  197. package/dist/gaia-ops/tools/scan/setup.py +753 -0
  198. package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
  199. package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
  200. package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
  201. package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
  202. package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
  203. package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
  204. package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
  205. package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
  206. package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
  207. package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
  208. package/dist/gaia-ops/tools/scan/ui.py +624 -0
  209. package/dist/gaia-ops/tools/scan/verify.py +266 -0
  210. package/dist/gaia-ops/tools/scan/walk.py +118 -0
  211. package/dist/gaia-ops/tools/scan/workspace.py +85 -0
  212. package/dist/gaia-ops/tools/validation/README.md +244 -0
  213. package/dist/gaia-ops/tools/validation/__init__.py +17 -0
  214. package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
  215. package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
  216. package/dist/gaia-security/.claude-plugin/plugin.json +22 -0
  217. package/dist/gaia-security/config/universal-rules.json +10 -0
  218. package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
  219. package/dist/gaia-security/hooks/adapters/base.py +219 -0
  220. package/dist/gaia-security/hooks/adapters/channel.py +17 -0
  221. package/dist/gaia-security/hooks/adapters/claude_code.py +1477 -0
  222. package/dist/gaia-security/hooks/adapters/types.py +194 -0
  223. package/dist/gaia-security/hooks/adapters/utils.py +25 -0
  224. package/dist/gaia-security/hooks/hooks.json +57 -0
  225. package/dist/gaia-security/hooks/modules/__init__.py +15 -0
  226. package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
  227. package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
  228. package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
  229. package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +124 -0
  230. package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
  231. package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
  232. package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
  233. package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
  234. package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
  235. package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
  236. package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
  237. package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +576 -0
  238. package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
  239. package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
  240. package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
  241. package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +215 -0
  242. package/dist/gaia-security/hooks/modules/context/context_cache.py +129 -0
  243. package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
  244. package/dist/gaia-security/hooks/modules/context/context_injector.py +427 -0
  245. package/dist/gaia-security/hooks/modules/context/context_writer.py +518 -0
  246. package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
  247. package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
  248. package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
  249. package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
  250. package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
  251. package/dist/gaia-security/hooks/modules/core/plugin_setup.py +558 -0
  252. package/dist/gaia-security/hooks/modules/core/state.py +179 -0
  253. package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
  254. package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
  255. package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
  256. package/dist/gaia-security/hooks/modules/identity/__init__.py +0 -0
  257. package/dist/gaia-security/hooks/modules/identity/identity_provider.py +21 -0
  258. package/dist/gaia-security/hooks/modules/identity/ops_identity.py +34 -0
  259. package/dist/gaia-security/hooks/modules/identity/security_identity.py +10 -0
  260. package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
  261. package/dist/gaia-security/hooks/modules/memory/episode_writer.py +227 -0
  262. package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
  263. package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +128 -0
  264. package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
  265. package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
  266. package/dist/gaia-security/hooks/modules/security/__init__.py +89 -0
  267. package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
  268. package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
  269. package/dist/gaia-security/hooks/modules/security/approval_grants.py +912 -0
  270. package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
  271. package/dist/gaia-security/hooks/modules/security/approval_scopes.py +153 -0
  272. package/dist/gaia-security/hooks/modules/security/blocked_commands.py +584 -0
  273. package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +86 -0
  274. package/dist/gaia-security/hooks/modules/security/command_semantics.py +130 -0
  275. package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
  276. package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +850 -0
  277. package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
  278. package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
  279. package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
  280. package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
  281. package/dist/gaia-security/hooks/modules/session/session_event_injector.py +158 -0
  282. package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
  283. package/dist/gaia-security/hooks/modules/tools/__init__.py +25 -0
  284. package/dist/gaia-security/hooks/modules/tools/bash_validator.py +708 -0
  285. package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +181 -0
  286. package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
  287. package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
  288. package/dist/gaia-security/hooks/modules/tools/task_validator.py +283 -0
  289. package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
  290. package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
  291. package/dist/gaia-security/hooks/post_tool_use.py +54 -0
  292. package/dist/gaia-security/hooks/pre_tool_use.py +383 -0
  293. package/dist/gaia-security/hooks/session_start.py +69 -0
  294. package/dist/gaia-security/hooks/stop_hook.py +69 -0
  295. package/dist/gaia-security/hooks/user_prompt_submit.py +177 -0
  296. package/dist/gaia-security/settings.json +58 -0
  297. package/git-hooks/commit-msg +41 -0
  298. package/hooks/README.md +8 -6
  299. package/hooks/adapters/channel.py +0 -25
  300. package/hooks/adapters/claude_code.py +364 -125
  301. package/hooks/elicitation_result.py +132 -0
  302. package/hooks/hooks.json +10 -1
  303. package/hooks/modules/README.md +3 -2
  304. package/hooks/modules/agents/contract_validator.py +3 -51
  305. package/hooks/modules/agents/response_contract.py +4 -8
  306. package/hooks/modules/agents/transcript_reader.py +4 -5
  307. package/hooks/modules/audit/__init__.py +4 -6
  308. package/hooks/modules/audit/event_detector.py +0 -2
  309. package/hooks/modules/audit/metrics.py +108 -187
  310. package/hooks/modules/audit/workflow_auditor.py +0 -4
  311. package/hooks/modules/audit/workflow_recorder.py +0 -5
  312. package/hooks/modules/context/compact_context_builder.py +1 -0
  313. package/hooks/modules/context/context_cache.py +129 -0
  314. package/hooks/modules/context/context_injector.py +18 -40
  315. package/hooks/modules/context/context_writer.py +1 -25
  316. package/hooks/modules/context/contracts_loader.py +7 -10
  317. package/hooks/modules/core/hook_entry.py +1 -0
  318. package/hooks/modules/core/paths.py +12 -13
  319. package/hooks/modules/core/plugin_mode.py +74 -4
  320. package/hooks/modules/core/plugin_setup.py +395 -23
  321. package/hooks/modules/events/__init__.py +1 -0
  322. package/hooks/modules/events/event_writer.py +210 -0
  323. package/hooks/modules/identity/ops_identity.py +18 -27
  324. package/hooks/modules/memory/episode_writer.py +1 -6
  325. package/hooks/modules/orchestrator/__init__.py +1 -0
  326. package/hooks/modules/orchestrator/delegate_mode.py +128 -0
  327. package/hooks/modules/security/__init__.py +2 -4
  328. package/hooks/modules/security/approval_constants.py +5 -1
  329. package/hooks/modules/security/approval_grants.py +189 -6
  330. package/hooks/modules/security/approval_messages.py +9 -21
  331. package/hooks/modules/security/blocked_commands.py +98 -34
  332. package/hooks/modules/security/command_semantics.py +0 -4
  333. package/hooks/modules/security/gitops_validator.py +1 -11
  334. package/hooks/modules/security/mutative_verbs.py +179 -38
  335. package/hooks/modules/security/tiers.py +1 -19
  336. package/hooks/modules/session/session_event_injector.py +1 -25
  337. package/hooks/modules/tools/bash_validator.py +310 -94
  338. package/hooks/modules/tools/shell_parser.py +0 -1
  339. package/hooks/modules/tools/task_validator.py +9 -29
  340. package/hooks/post_tool_use.py +0 -72
  341. package/hooks/pre_tool_use.py +42 -102
  342. package/hooks/session_start.py +4 -2
  343. package/hooks/subagent_start.py +6 -2
  344. package/hooks/subagent_stop.py +1 -13
  345. package/hooks/user_prompt_submit.py +119 -37
  346. package/index.js +1 -1
  347. package/package.json +5 -3
  348. package/skills/README.md +3 -5
  349. package/skills/agent-protocol/SKILL.md +17 -16
  350. package/skills/agent-protocol/examples.md +6 -6
  351. package/skills/agent-response/SKILL.md +11 -14
  352. package/skills/approval/SKILL.md +28 -13
  353. package/skills/approval/reference.md +2 -2
  354. package/skills/execution/SKILL.md +1 -1
  355. package/skills/gaia-patterns/SKILL.md +2 -3
  356. package/skills/orchestrator-approval/SKILL.md +22 -50
  357. package/skills/security-tiers/SKILL.md +1 -1
  358. package/templates/README.md +9 -9
  359. package/templates/managed-settings.template.json +43 -0
  360. package/tools/gaia_simulator/runner.py +34 -1
  361. package/tools/scan/orchestrator.py +13 -0
  362. package/tools/scan/scanners/base.py +8 -0
  363. package/tools/scan/scanners/git.py +78 -0
  364. package/tools/scan/scanners/infrastructure.py +65 -0
  365. package/tools/scan/scanners/stack.py +110 -0
  366. package/tools/scan/setup.py +120 -13
  367. package/tools/scan/workspace.py +85 -0
  368. package/config/context-contracts.aws.json +0 -42
  369. package/config/context-contracts.gcp.json +0 -39
  370. package/skills/project-dispatch/SKILL.md +0 -34
  371. package/templates/settings.template.json +0 -226
@@ -0,0 +1,796 @@
1
+ """
2
+ Shared test fixtures for scan module tests.
3
+
4
+ Provides mock project trees, temporary directories, and common
5
+ test data for scanner unit tests.
6
+ """
7
+
8
+ import json
9
+ import textwrap
10
+ from pathlib import Path
11
+ from typing import Any, Dict
12
+
13
+ import pytest
14
+
15
+
16
+ # ---------------------------------------------------------------------------
17
+ # Basic project fixtures
18
+ # ---------------------------------------------------------------------------
19
+
20
+
21
+ @pytest.fixture
22
+ def tmp_project(tmp_path: Path) -> Path:
23
+ """Create a minimal temporary project directory."""
24
+ return tmp_path
25
+
26
+
27
+ @pytest.fixture
28
+ def empty_project(tmp_path: Path) -> Path:
29
+ """Create an empty temporary project directory."""
30
+ return tmp_path
31
+
32
+
33
+ @pytest.fixture
34
+ def minimal_project(tmp_path: Path) -> Path:
35
+ """Create a minimal project with just a README.md."""
36
+ (tmp_path / "README.md").write_text("# Test Project\n")
37
+ return tmp_path
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Language-specific project fixtures
42
+ # ---------------------------------------------------------------------------
43
+
44
+
45
+ @pytest.fixture
46
+ def node_project(tmp_path: Path) -> Path:
47
+ """Create a Node.js project with express + react deps and CI.
48
+
49
+ Includes: package.json (express, react), package-lock.json,
50
+ .github/workflows/ci.yml
51
+ """
52
+ package_json = {
53
+ "name": "test-node-project",
54
+ "version": "1.0.0",
55
+ "description": "A test Node.js project",
56
+ "dependencies": {
57
+ "express": "^4.18.0",
58
+ "react": "^18.2.0",
59
+ },
60
+ "devDependencies": {
61
+ "@nestjs/core": "^10.0.0",
62
+ },
63
+ }
64
+ (tmp_path / "package.json").write_text(json.dumps(package_json, indent=2))
65
+ (tmp_path / "package-lock.json").write_text("{}")
66
+
67
+ # GitHub Actions CI
68
+ workflows_dir = tmp_path / ".github" / "workflows"
69
+ workflows_dir.mkdir(parents=True)
70
+ (workflows_dir / "ci.yml").write_text(
71
+ "name: CI\non: [push]\njobs:\n test:\n runs-on: ubuntu-latest\n"
72
+ )
73
+ return tmp_path
74
+
75
+
76
+ @pytest.fixture
77
+ def python_project(tmp_path: Path) -> Path:
78
+ """Create a Python project with fastapi dep and requirements.txt.
79
+
80
+ Includes: pyproject.toml (fastapi), requirements.txt
81
+ """
82
+ pyproject = textwrap.dedent("""\
83
+ [project]
84
+ name = "test-python-project"
85
+ version = "1.0.0"
86
+ description = "A test Python project"
87
+ dependencies = [
88
+ "fastapi>=0.100.0",
89
+ ]
90
+ """)
91
+ (tmp_path / "pyproject.toml").write_text(pyproject)
92
+ (tmp_path / "requirements.txt").write_text("fastapi>=0.100.0\nuvicorn>=0.23.0\n")
93
+ return tmp_path
94
+
95
+
96
+ @pytest.fixture
97
+ def go_project(tmp_path: Path) -> Path:
98
+ """Create a Go project with go.mod and go.sum.
99
+
100
+ Includes: go.mod with module path, go.sum
101
+ """
102
+ go_mod = textwrap.dedent("""\
103
+ module github.com/example/test-go-project
104
+
105
+ go 1.21
106
+
107
+ require github.com/gin-gonic/gin v1.9.1
108
+ """)
109
+ (tmp_path / "go.mod").write_text(go_mod)
110
+ (tmp_path / "go.sum").write_text("github.com/gin-gonic/gin v1.9.1 h1:abc=\n")
111
+ return tmp_path
112
+
113
+
114
+ @pytest.fixture
115
+ def rust_project(tmp_path: Path) -> Path:
116
+ """Create a Rust project with Cargo.toml and Cargo.lock.
117
+
118
+ Includes: Cargo.toml, Cargo.lock
119
+ """
120
+ cargo_toml = textwrap.dedent("""\
121
+ [package]
122
+ name = "test-rust-project"
123
+ version = "0.1.0"
124
+ description = "A test Rust project"
125
+ edition = "2021"
126
+
127
+ [dependencies]
128
+ serde = "1.0"
129
+ """)
130
+ (tmp_path / "Cargo.toml").write_text(cargo_toml)
131
+ (tmp_path / "Cargo.lock").write_text("# This file is automatically generated\n")
132
+ return tmp_path
133
+
134
+
135
+ @pytest.fixture
136
+ def java_maven_project(tmp_path: Path) -> Path:
137
+ """Create a Java project with pom.xml."""
138
+ pom_xml = textwrap.dedent("""\
139
+ <?xml version="1.0" encoding="UTF-8"?>
140
+ <project>
141
+ <modelVersion>4.0.0</modelVersion>
142
+ <groupId>com.example</groupId>
143
+ <artifactId>test-java-project</artifactId>
144
+ <version>1.0.0</version>
145
+ </project>
146
+ """)
147
+ (tmp_path / "pom.xml").write_text(pom_xml)
148
+ return tmp_path
149
+
150
+
151
+ @pytest.fixture
152
+ def java_gradle_project(tmp_path: Path) -> Path:
153
+ """Create a Java project with build.gradle."""
154
+ (tmp_path / "build.gradle").write_text(
155
+ "plugins { id 'java' }\nversion = '1.0.0'\n"
156
+ )
157
+ return tmp_path
158
+
159
+
160
+ @pytest.fixture
161
+ def php_project(tmp_path: Path) -> Path:
162
+ """Create a PHP project with composer.json."""
163
+ composer_json = {
164
+ "name": "example/test-php-project",
165
+ "description": "A test PHP project",
166
+ "require": {"php": "^8.1"},
167
+ }
168
+ (tmp_path / "composer.json").write_text(json.dumps(composer_json, indent=2))
169
+ return tmp_path
170
+
171
+
172
+ @pytest.fixture
173
+ def ruby_project(tmp_path: Path) -> Path:
174
+ """Create a Ruby project with Gemfile."""
175
+ (tmp_path / "Gemfile").write_text(
176
+ 'source "https://rubygems.org"\ngem "rails", "~> 7.0"\n'
177
+ )
178
+ return tmp_path
179
+
180
+
181
+ @pytest.fixture
182
+ def csharp_project(tmp_path: Path) -> Path:
183
+ """Create a C#/.NET project with .csproj."""
184
+ (tmp_path / "TestProject.csproj").write_text(
185
+ '<Project Sdk="Microsoft.NET.Sdk">\n'
186
+ " <PropertyGroup>\n"
187
+ " <TargetFramework>net8.0</TargetFramework>\n"
188
+ " </PropertyGroup>\n"
189
+ "</Project>\n"
190
+ )
191
+ return tmp_path
192
+
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # Monorepo fixture
196
+ # ---------------------------------------------------------------------------
197
+
198
+
199
+ @pytest.fixture
200
+ def monorepo_project(tmp_path: Path) -> Path:
201
+ """Create a monorepo with React frontend and FastAPI backend.
202
+
203
+ Includes: turbo.json, apps/frontend/package.json (React),
204
+ apps/backend/pyproject.toml (FastAPI)
205
+ """
206
+ # Turbo config
207
+ (tmp_path / "turbo.json").write_text(json.dumps({"pipeline": {}}, indent=2))
208
+
209
+ # Root package.json with workspaces
210
+ root_pkg = {
211
+ "name": "test-monorepo",
212
+ "private": True,
213
+ "workspaces": ["apps/*"],
214
+ }
215
+ (tmp_path / "package.json").write_text(json.dumps(root_pkg, indent=2))
216
+
217
+ # Frontend (React)
218
+ frontend_dir = tmp_path / "apps" / "frontend"
219
+ frontend_dir.mkdir(parents=True)
220
+ frontend_pkg = {
221
+ "name": "@monorepo/frontend",
222
+ "version": "1.0.0",
223
+ "dependencies": {"react": "^18.2.0", "next": "^14.0.0"},
224
+ }
225
+ (frontend_dir / "package.json").write_text(json.dumps(frontend_pkg, indent=2))
226
+
227
+ # Backend (FastAPI)
228
+ backend_dir = tmp_path / "apps" / "backend"
229
+ backend_dir.mkdir(parents=True)
230
+ pyproject = textwrap.dedent("""\
231
+ [project]
232
+ name = "monorepo-backend"
233
+ version = "1.0.0"
234
+ dependencies = [
235
+ "fastapi>=0.100.0",
236
+ ]
237
+ """)
238
+ (backend_dir / "pyproject.toml").write_text(pyproject)
239
+
240
+ return tmp_path
241
+
242
+
243
+ # ---------------------------------------------------------------------------
244
+ # Full DevOps project fixture
245
+ # ---------------------------------------------------------------------------
246
+
247
+
248
+ @pytest.fixture
249
+ def devops_project(tmp_path: Path) -> Path:
250
+ """Create a full DevOps project with infrastructure, GitOps, and CI.
251
+
252
+ Includes: package.json, Dockerfile, terraform/main.tf (GCP provider),
253
+ gitops/clusters/dev/kustomization.yaml (Flux), .github/workflows/ci.yml
254
+ """
255
+ # Application
256
+ pkg = {
257
+ "name": "devops-app",
258
+ "version": "1.0.0",
259
+ "dependencies": {"express": "^4.18.0"},
260
+ }
261
+ (tmp_path / "package.json").write_text(json.dumps(pkg, indent=2))
262
+ (tmp_path / "package-lock.json").write_text("{}")
263
+
264
+ # Dockerfile
265
+ (tmp_path / "Dockerfile").write_text("FROM node:20-alpine\nWORKDIR /app\n")
266
+
267
+ # Terraform with GCP provider
268
+ tf_dir = tmp_path / "terraform"
269
+ tf_dir.mkdir()
270
+ (tf_dir / "main.tf").write_text(
271
+ textwrap.dedent("""\
272
+ provider "google" {
273
+ project = "my-gcp-project"
274
+ region = "us-central1"
275
+ }
276
+
277
+ resource "google_compute_instance" "default" {
278
+ name = "test"
279
+ machine_type = "e2-medium"
280
+ }
281
+ """)
282
+ )
283
+
284
+ # GitOps with Flux (kustomization.yaml with toolkit.fluxcd.io)
285
+ gitops_dir = tmp_path / "gitops" / "clusters" / "dev"
286
+ gitops_dir.mkdir(parents=True)
287
+ (gitops_dir / "kustomization.yaml").write_text(
288
+ textwrap.dedent("""\
289
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
290
+ kind: Kustomization
291
+ metadata:
292
+ name: dev-cluster
293
+ namespace: flux-system
294
+ spec:
295
+ interval: 5m
296
+ path: ./gitops/clusters/dev
297
+ prune: true
298
+ """)
299
+ )
300
+
301
+ # GitHub Actions CI
302
+ workflows_dir = tmp_path / ".github" / "workflows"
303
+ workflows_dir.mkdir(parents=True)
304
+ (workflows_dir / "ci.yml").write_text(
305
+ "name: CI\non: [push]\njobs:\n test:\n runs-on: ubuntu-latest\n"
306
+ )
307
+
308
+ return tmp_path
309
+
310
+
311
+ # ---------------------------------------------------------------------------
312
+ # Git directory fixtures
313
+ # ---------------------------------------------------------------------------
314
+
315
+
316
+ @pytest.fixture
317
+ def git_project_github(tmp_path: Path) -> Path:
318
+ """Create a project with .git directory configured for GitHub.
319
+
320
+ Sets up .git/config with origin remote pointing to github.com,
321
+ .git/HEAD pointing to main, and branch refs.
322
+ """
323
+ git_dir = tmp_path / ".git"
324
+ git_dir.mkdir()
325
+
326
+ # Git config with origin remote
327
+ config_content = textwrap.dedent("""\
328
+ [core]
329
+ repositoryformatversion = 0
330
+ filemode = true
331
+ [remote "origin"]
332
+ url = git@github.com:example/test-project.git
333
+ fetch = +refs/heads/*:refs/remotes/origin/*
334
+ [remote "upstream"]
335
+ url = https://github.com/upstream/test-project.git
336
+ fetch = +refs/heads/*:refs/remotes/upstream/*
337
+ [branch "main"]
338
+ remote = origin
339
+ merge = refs/heads/main
340
+ """)
341
+ (git_dir / "config").write_text(config_content)
342
+
343
+ # HEAD points to main
344
+ (git_dir / "HEAD").write_text("ref: refs/heads/main\n")
345
+
346
+ # Branch refs
347
+ refs_heads = git_dir / "refs" / "heads"
348
+ refs_heads.mkdir(parents=True)
349
+ (refs_heads / "main").write_text("abc123\n")
350
+
351
+ refs_remotes_origin = git_dir / "refs" / "remotes" / "origin"
352
+ refs_remotes_origin.mkdir(parents=True)
353
+ (refs_remotes_origin / "main").write_text("abc123\n")
354
+
355
+ return tmp_path
356
+
357
+
358
+ @pytest.fixture
359
+ def git_project_gitlab(tmp_path: Path) -> Path:
360
+ """Create a project with .git directory configured for GitLab."""
361
+ git_dir = tmp_path / ".git"
362
+ git_dir.mkdir()
363
+
364
+ config_content = textwrap.dedent("""\
365
+ [remote "origin"]
366
+ url = git@gitlab.com:group/project.git
367
+ fetch = +refs/heads/*:refs/remotes/origin/*
368
+ """)
369
+ (git_dir / "config").write_text(config_content)
370
+ (git_dir / "HEAD").write_text("ref: refs/heads/main\n")
371
+
372
+ refs_heads = git_dir / "refs" / "heads"
373
+ refs_heads.mkdir(parents=True)
374
+ (refs_heads / "main").write_text("abc123\n")
375
+
376
+ return tmp_path
377
+
378
+
379
+ @pytest.fixture
380
+ def git_project_bitbucket(tmp_path: Path) -> Path:
381
+ """Create a project with .git directory configured for Bitbucket."""
382
+ git_dir = tmp_path / ".git"
383
+ git_dir.mkdir()
384
+
385
+ config_content = textwrap.dedent("""\
386
+ [remote "origin"]
387
+ url = git@bitbucket.org:team/project.git
388
+ fetch = +refs/heads/*:refs/remotes/origin/*
389
+ """)
390
+ (git_dir / "config").write_text(config_content)
391
+ (git_dir / "HEAD").write_text("ref: refs/heads/master\n")
392
+
393
+ refs_heads = git_dir / "refs" / "heads"
394
+ refs_heads.mkdir(parents=True)
395
+ (refs_heads / "master").write_text("abc123\n")
396
+
397
+ return tmp_path
398
+
399
+
400
+ @pytest.fixture
401
+ def git_project_selfhosted(tmp_path: Path) -> Path:
402
+ """Create a project with .git directory configured for self-hosted Git."""
403
+ git_dir = tmp_path / ".git"
404
+ git_dir.mkdir()
405
+
406
+ config_content = textwrap.dedent("""\
407
+ [remote "origin"]
408
+ url = git@git.internal.company.com:team/project.git
409
+ fetch = +refs/heads/*:refs/remotes/origin/*
410
+ """)
411
+ (git_dir / "config").write_text(config_content)
412
+ (git_dir / "HEAD").write_text("ref: refs/heads/main\n")
413
+
414
+ refs_heads = git_dir / "refs" / "heads"
415
+ refs_heads.mkdir(parents=True)
416
+ (refs_heads / "main").write_text("abc123\n")
417
+
418
+ return tmp_path
419
+
420
+
421
+ @pytest.fixture
422
+ def git_project_gitflow(tmp_path: Path) -> Path:
423
+ """Create a project with gitflow branch strategy.
424
+
425
+ Has: main, develop, feature/xyz, release/1.0
426
+ """
427
+ git_dir = tmp_path / ".git"
428
+ git_dir.mkdir()
429
+
430
+ config_content = textwrap.dedent("""\
431
+ [remote "origin"]
432
+ url = git@github.com:example/gitflow-project.git
433
+ fetch = +refs/heads/*:refs/remotes/origin/*
434
+ """)
435
+ (git_dir / "config").write_text(config_content)
436
+ (git_dir / "HEAD").write_text("ref: refs/heads/develop\n")
437
+
438
+ refs_heads = git_dir / "refs" / "heads"
439
+ refs_heads.mkdir(parents=True)
440
+ (refs_heads / "main").write_text("abc123\n")
441
+ (refs_heads / "develop").write_text("def456\n")
442
+
443
+ feature_dir = refs_heads / "feature"
444
+ feature_dir.mkdir()
445
+ (feature_dir / "xyz").write_text("ghi789\n")
446
+
447
+ release_dir = refs_heads / "release"
448
+ release_dir.mkdir()
449
+ (release_dir / "1.0").write_text("jkl012\n")
450
+
451
+ return tmp_path
452
+
453
+
454
+ # ---------------------------------------------------------------------------
455
+ # Infrastructure-specific fixtures
456
+ # ---------------------------------------------------------------------------
457
+
458
+
459
+ @pytest.fixture
460
+ def terraform_gcp_project(tmp_path: Path) -> Path:
461
+ """Create a project with Terraform GCP provider."""
462
+ (tmp_path / "main.tf").write_text(
463
+ 'provider "google" {\n project = "my-project"\n region = "us-central1"\n}\n'
464
+ )
465
+ return tmp_path
466
+
467
+
468
+ @pytest.fixture
469
+ def terraform_aws_project(tmp_path: Path) -> Path:
470
+ """Create a project with Terraform AWS provider."""
471
+ (tmp_path / "main.tf").write_text(
472
+ 'provider "aws" {\n region = "us-east-1"\n}\n'
473
+ )
474
+ return tmp_path
475
+
476
+
477
+ @pytest.fixture
478
+ def terraform_azure_project(tmp_path: Path) -> Path:
479
+ """Create a project with Terraform Azure provider."""
480
+ (tmp_path / "main.tf").write_text(
481
+ 'provider "azurerm" {\n features {}\n}\n'
482
+ )
483
+ return tmp_path
484
+
485
+
486
+ @pytest.fixture
487
+ def terraform_multicloud_project(tmp_path: Path) -> Path:
488
+ """Create a project with multiple cloud providers."""
489
+ (tmp_path / "gcp.tf").write_text('provider "google" {\n project = "my-project"\n}\n')
490
+ (tmp_path / "aws.tf").write_text('provider "aws" {\n region = "us-east-1"\n}\n')
491
+ return tmp_path
492
+
493
+
494
+ # ---------------------------------------------------------------------------
495
+ # Orchestration-specific fixtures
496
+ # ---------------------------------------------------------------------------
497
+
498
+
499
+ @pytest.fixture
500
+ def k8s_project(tmp_path: Path) -> Path:
501
+ """Create a project with Kubernetes manifests."""
502
+ manifests_dir = tmp_path / "k8s"
503
+ manifests_dir.mkdir()
504
+ (manifests_dir / "deployment.yaml").write_text(
505
+ textwrap.dedent("""\
506
+ apiVersion: apps/v1
507
+ kind: Deployment
508
+ metadata:
509
+ name: test-app
510
+ spec:
511
+ replicas: 2
512
+ """)
513
+ )
514
+ (manifests_dir / "service.yaml").write_text(
515
+ textwrap.dedent("""\
516
+ apiVersion: v1
517
+ kind: Service
518
+ metadata:
519
+ name: test-app
520
+ spec:
521
+ type: ClusterIP
522
+ """)
523
+ )
524
+ return tmp_path
525
+
526
+
527
+ @pytest.fixture
528
+ def helm_project(tmp_path: Path) -> Path:
529
+ """Create a project with Helm chart."""
530
+ chart_dir = tmp_path / "charts" / "myapp"
531
+ chart_dir.mkdir(parents=True)
532
+ (chart_dir / "Chart.yaml").write_text(
533
+ "apiVersion: v2\nname: myapp\nversion: 1.0.0\n"
534
+ )
535
+ return tmp_path
536
+
537
+
538
+ @pytest.fixture
539
+ def flux_project(tmp_path: Path) -> Path:
540
+ """Create a project with Flux GitOps configuration."""
541
+ clusters_dir = tmp_path / "clusters" / "production"
542
+ clusters_dir.mkdir(parents=True)
543
+ (clusters_dir / "kustomization.yaml").write_text(
544
+ textwrap.dedent("""\
545
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
546
+ kind: Kustomization
547
+ metadata:
548
+ name: production
549
+ namespace: flux-system
550
+ spec:
551
+ interval: 5m
552
+ path: ./clusters/production
553
+ """)
554
+ )
555
+ infra_dir = tmp_path / "infrastructure"
556
+ infra_dir.mkdir()
557
+ (infra_dir / "sources.yaml").write_text(
558
+ textwrap.dedent("""\
559
+ apiVersion: source.toolkit.fluxcd.io/v1
560
+ kind: GitRepository
561
+ metadata:
562
+ name: flux-system
563
+ """)
564
+ )
565
+ apps_dir = tmp_path / "apps"
566
+ apps_dir.mkdir()
567
+ return tmp_path
568
+
569
+
570
+ @pytest.fixture
571
+ def argocd_project(tmp_path: Path) -> Path:
572
+ """Create a project with ArgoCD configuration."""
573
+ argo_dir = tmp_path / "argocd"
574
+ argo_dir.mkdir()
575
+ (argo_dir / "application.yaml").write_text(
576
+ textwrap.dedent("""\
577
+ apiVersion: argoproj.io/v1alpha1
578
+ kind: Application
579
+ metadata:
580
+ name: my-app
581
+ spec:
582
+ destination:
583
+ server: https://kubernetes.default.svc
584
+ """)
585
+ )
586
+ return tmp_path
587
+
588
+
589
+ @pytest.fixture
590
+ def istio_project(tmp_path: Path) -> Path:
591
+ """Create a project with Istio service mesh annotations."""
592
+ manifests_dir = tmp_path / "k8s"
593
+ manifests_dir.mkdir()
594
+ (manifests_dir / "deployment.yaml").write_text(
595
+ textwrap.dedent("""\
596
+ apiVersion: apps/v1
597
+ kind: Deployment
598
+ metadata:
599
+ name: test-app
600
+ annotations:
601
+ sidecar.istio.io/inject: "true"
602
+ spec:
603
+ replicas: 1
604
+ """)
605
+ )
606
+ return tmp_path
607
+
608
+
609
+ @pytest.fixture
610
+ def linkerd_project(tmp_path: Path) -> Path:
611
+ """Create a project with Linkerd service mesh annotations."""
612
+ manifests_dir = tmp_path / "k8s"
613
+ manifests_dir.mkdir()
614
+ (manifests_dir / "deployment.yaml").write_text(
615
+ textwrap.dedent("""\
616
+ apiVersion: apps/v1
617
+ kind: Deployment
618
+ metadata:
619
+ name: test-app
620
+ annotations:
621
+ linkerd.io/inject: enabled
622
+ spec:
623
+ replicas: 1
624
+ """)
625
+ )
626
+ return tmp_path
627
+
628
+
629
+ # ---------------------------------------------------------------------------
630
+ # Sample project-context.json fixtures
631
+ # ---------------------------------------------------------------------------
632
+
633
+
634
+ @pytest.fixture
635
+ def sample_project_context() -> Dict[str, Any]:
636
+ """Return a minimal project-context.json structure (v2 format)."""
637
+ return {
638
+ "metadata": {
639
+ "version": "2.0",
640
+ "last_updated": "2026-01-01T00:00:00Z",
641
+ "project_name": "test-project",
642
+ "scan_config": {
643
+ "staleness_hours": 24,
644
+ "last_scan": "2026-01-01T00:00:00Z",
645
+ "scanner_version": "0.1.0",
646
+ },
647
+ },
648
+ "sections": {},
649
+ }
650
+
651
+
652
+ @pytest.fixture
653
+ def sample_project_context_v1() -> Dict[str, Any]:
654
+ """Return a v1-style project-context.json (no scan_config, old sections).
655
+
656
+ This represents existing project-context.json files that were created
657
+ before the gaia-scan system, with agent-enriched data.
658
+ """
659
+ return {
660
+ "metadata": {
661
+ "version": "1.0",
662
+ "last_updated": "2025-12-01T00:00:00Z",
663
+ "project_name": "legacy-project",
664
+ },
665
+ "project_details": {
666
+ "cloud_provider": "gcp",
667
+ "project_id": "my-gcp-project",
668
+ "region": "us-central1",
669
+ "speckit_root": "specs/",
670
+ },
671
+ "application_architecture": {
672
+ "package_name": "legacy-app",
673
+ "runtime_language": "typescript",
674
+ },
675
+ "development_standards": {
676
+ "python_version": "3.11",
677
+ "node_version": "20",
678
+ },
679
+ "operational_guidelines": {
680
+ "deployment_strategy": "blue-green",
681
+ "rollback_procedure": "manual",
682
+ },
683
+ }
684
+
685
+
686
+ @pytest.fixture
687
+ def sample_project_context_with_sections() -> Dict[str, Any]:
688
+ """Return a project-context.json with scanner + agent-enriched sections."""
689
+ return {
690
+ "metadata": {
691
+ "version": "2.0",
692
+ "last_updated": "2026-01-01T00:00:00Z",
693
+ "project_name": "test-project",
694
+ "scan_config": {
695
+ "staleness_hours": 24,
696
+ "last_scan": "2026-01-01T00:00:00Z",
697
+ "scanner_version": "0.1.0",
698
+ },
699
+ },
700
+ "project_identity": {
701
+ "_source": "scanner:stack",
702
+ "name": "test-project",
703
+ "type": "application",
704
+ },
705
+ "stack": {
706
+ "_source": "scanner:stack",
707
+ "languages": [{"name": "typescript", "manifest": "package.json", "primary": True}],
708
+ "frameworks": [{"name": "express", "language": "typescript", "version": "4.18.0"}],
709
+ "build_tools": [{"name": "npm", "detected_by": "lock_file", "lock_file": "package-lock.json"}],
710
+ },
711
+ "git": {
712
+ "_source": "scanner:git",
713
+ "platform": "github",
714
+ "remotes": [{"name": "origin", "url": "git@github.com:example/test.git", "platform": "github"}],
715
+ "default_branch": "main",
716
+ },
717
+ "environment": {
718
+ "_source": "scanner:environment",
719
+ "os": {"platform": "linux", "architecture": "x64", "wsl": True, "wsl_version": "2"},
720
+ "runtimes": [{"name": "python3", "version": "3.11.5", "path": "/usr/bin/python3"}],
721
+ "env_files": [{"name": ".env", "path": ".env"}],
722
+ "tools": [{"name": "git", "path": "/usr/bin/git", "version": "2.40.0", "category": "git"}],
723
+ "tool_preferences": {"file_viewer": "bat"},
724
+ },
725
+ "infrastructure": {
726
+ "_source": "scanner:infrastructure",
727
+ "cloud_providers": [{"name": "gcp", "detected_by": "terraform_provider"}],
728
+ "iac": [{"tool": "terraform", "base_path": "terraform"}],
729
+ },
730
+ "operational_guidelines": {
731
+ "deployment_strategy": "blue-green",
732
+ "rollback_procedure": "manual",
733
+ },
734
+ "my_custom_notes": {
735
+ "author": "user",
736
+ "notes": "This is a user-custom section",
737
+ },
738
+ }
739
+
740
+
741
+ # ---------------------------------------------------------------------------
742
+ # Helpers
743
+ # ---------------------------------------------------------------------------
744
+
745
+
746
+ def create_git_dir(
747
+ root: Path,
748
+ remote_url: str = "git@github.com:example/test.git",
749
+ default_branch: str = "main",
750
+ extra_remotes: Dict[str, str] | None = None,
751
+ branches: list[str] | None = None,
752
+ ) -> Path:
753
+ """Helper to create a realistic .git directory structure.
754
+
755
+ Args:
756
+ root: Project root path.
757
+ remote_url: URL for the origin remote.
758
+ default_branch: Branch name for HEAD.
759
+ extra_remotes: Additional remotes as {name: url} dict.
760
+ branches: List of branch names to create refs for.
761
+
762
+ Returns:
763
+ Path to the .git directory.
764
+ """
765
+ git_dir = root / ".git"
766
+ git_dir.mkdir(exist_ok=True)
767
+
768
+ # Build config
769
+ config_lines = ['[core]\n repositoryformatversion = 0\n filemode = true\n']
770
+ config_lines.append(
771
+ f'[remote "origin"]\n'
772
+ f" url = {remote_url}\n"
773
+ f" fetch = +refs/heads/*:refs/remotes/origin/*\n"
774
+ )
775
+ if extra_remotes:
776
+ for name, url in extra_remotes.items():
777
+ config_lines.append(
778
+ f'[remote "{name}"]\n'
779
+ f" url = {url}\n"
780
+ f" fetch = +refs/heads/*:refs/remotes/{name}/*\n"
781
+ )
782
+
783
+ (git_dir / "config").write_text("".join(config_lines))
784
+ (git_dir / "HEAD").write_text(f"ref: refs/heads/{default_branch}\n")
785
+
786
+ # Create branch refs
787
+ refs_heads = git_dir / "refs" / "heads"
788
+ refs_heads.mkdir(parents=True, exist_ok=True)
789
+
790
+ all_branches = [default_branch] + (branches or [])
791
+ for branch in all_branches:
792
+ branch_path = refs_heads / branch.replace("/", "/")
793
+ branch_path.parent.mkdir(parents=True, exist_ok=True)
794
+ branch_path.write_text("0" * 40 + "\n")
795
+
796
+ return git_dir