@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,349 @@
1
+ """
2
+ Unit tests for the Tool Scanner (T024).
3
+
4
+ Tests tool detection via command -v, version extraction with timeout,
5
+ tool_preferences resolution, and handling of tools that hang.
6
+ All subprocess calls are mocked for reproducibility.
7
+ """
8
+
9
+ import subprocess
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List
12
+ from unittest.mock import MagicMock, call, patch
13
+
14
+ import pytest
15
+
16
+ from tools.scan.config import TOOL_DEFINITIONS, ToolCategory, ToolDefinition
17
+ from tools.scan.scanners.tools import ToolScanner, _VERSION_TIMEOUT
18
+
19
+
20
+ @pytest.fixture
21
+ def scanner() -> ToolScanner:
22
+ """Create a ToolScanner instance."""
23
+ return ToolScanner()
24
+
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Scanner basics
28
+ # ---------------------------------------------------------------------------
29
+
30
+
31
+ class TestToolScannerBasics:
32
+ """Test scanner metadata and basic contract."""
33
+
34
+ def test_scanner_name(self, scanner: ToolScanner) -> None:
35
+ assert scanner.SCANNER_NAME == "tools"
36
+
37
+ def test_scanner_version(self, scanner: ToolScanner) -> None:
38
+ assert scanner.SCANNER_VERSION == "1.1.0"
39
+
40
+ def test_owned_sections(self, scanner: ToolScanner) -> None:
41
+ assert "environment.tools" in scanner.OWNED_SECTIONS
42
+ assert "environment.tool_preferences" in scanner.OWNED_SECTIONS
43
+
44
+ def test_source_tag(self, scanner: ToolScanner) -> None:
45
+ assert scanner.source_tag == "scanner:tools"
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Tool detection
50
+ # ---------------------------------------------------------------------------
51
+
52
+
53
+ class TestToolDetection:
54
+ """Test tool detection via shutil.which."""
55
+
56
+ def test_detect_tool_via_shutil_which(self, scanner: ToolScanner) -> None:
57
+ """Verify shutil.which is used for path detection."""
58
+ with patch("tools.scan.scanners.tools.shutil.which", return_value="/usr/bin/python3") as mock_which:
59
+ path = scanner._detect_path("python3")
60
+ mock_which.assert_called_once_with("python3")
61
+ assert path == "/usr/bin/python3"
62
+
63
+ def test_no_subprocess_for_path_detection(self, scanner: ToolScanner) -> None:
64
+ """Ensure subprocess is NOT used for path detection."""
65
+ with patch("tools.scan.scanners.tools.shutil.which", return_value="/usr/bin/git"):
66
+ with patch("tools.scan.scanners.tools.subprocess.run") as mock_run:
67
+ scanner._detect_path("git")
68
+ mock_run.assert_not_called()
69
+
70
+ def test_missing_tool_returns_none(self, scanner: ToolScanner) -> None:
71
+ """Tool not found by shutil.which returns None."""
72
+ with patch("tools.scan.scanners.tools.shutil.which", return_value=None):
73
+ path = scanner._detect_path("nonexistent_tool_xyz")
74
+ assert path is None
75
+
76
+ def test_detected_tool_has_required_fields(
77
+ self, scanner: ToolScanner, tmp_path: Path
78
+ ) -> None:
79
+ """Test that detected tools have name, path, version, category."""
80
+ tool_def = ToolDefinition(
81
+ name="test-tool",
82
+ category=ToolCategory.UTILITY,
83
+ )
84
+
85
+ # Mock version extraction (subprocess is only used for --version now)
86
+ version_result = MagicMock()
87
+ version_result.returncode = 0
88
+ version_result.stdout = "test-tool 1.2.3\n"
89
+ version_result.stderr = ""
90
+
91
+ with patch("tools.scan.scanners.tools.shutil.which", return_value="/usr/local/bin/test-tool"):
92
+ with patch(
93
+ "tools.scan.scanners.tools.subprocess.run",
94
+ return_value=version_result,
95
+ ):
96
+ tool_info = scanner._probe_tool(tool_def)
97
+
98
+ assert tool_info is not None
99
+ assert tool_info["name"] == "test-tool"
100
+ assert tool_info["path"] == "/usr/local/bin/test-tool"
101
+ assert tool_info["version"] is not None
102
+ assert tool_info["category"] == "utility"
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Version extraction
107
+ # ---------------------------------------------------------------------------
108
+
109
+
110
+ class TestVersionExtraction:
111
+ """Test version extraction with timeout handling."""
112
+
113
+ def test_version_extracted_from_stdout(self, scanner: ToolScanner) -> None:
114
+ result = scanner._extract_version(
115
+ "/usr/bin/python3", "--version", None
116
+ )
117
+ # On real system this would return a version string;
118
+ # if python3 is installed it should not be "unknown"
119
+ # We just verify it returns a string
120
+ assert isinstance(result, str)
121
+
122
+ def test_timeout_returns_unknown(self, scanner: ToolScanner) -> None:
123
+ """Tool that hangs during --version gets version 'unknown'."""
124
+ with patch(
125
+ "tools.scan.scanners.tools.subprocess.run",
126
+ side_effect=subprocess.TimeoutExpired(cmd="tool", timeout=2),
127
+ ):
128
+ version = scanner._extract_version("/usr/bin/slow-tool", "--version", None)
129
+ assert version == "unknown"
130
+
131
+ def test_nonzero_exit_returns_unknown(self, scanner: ToolScanner) -> None:
132
+ """Tool with non-zero exit for --version gets version 'unknown'."""
133
+ mock_result = MagicMock()
134
+ mock_result.returncode = 1
135
+ mock_result.stdout = ""
136
+ mock_result.stderr = ""
137
+
138
+ with patch("tools.scan.scanners.tools.subprocess.run", return_value=mock_result):
139
+ version = scanner._extract_version("/usr/bin/bad-tool", "--version", None)
140
+ assert version == "unknown"
141
+
142
+ def test_version_regex_extraction(self, scanner: ToolScanner) -> None:
143
+ """Test version regex extracts from complex output."""
144
+ mock_result = MagicMock()
145
+ mock_result.returncode = 0
146
+ mock_result.stdout = "Super Tool version 3.14.159 (build 12345)\n"
147
+ mock_result.stderr = ""
148
+
149
+ with patch("tools.scan.scanners.tools.subprocess.run", return_value=mock_result):
150
+ version = scanner._extract_version(
151
+ "/usr/bin/super-tool", "--version", r"version (\d+\.\d+\.\d+)"
152
+ )
153
+ assert version == "3.14.159"
154
+
155
+ def test_oserror_returns_unknown(self, scanner: ToolScanner) -> None:
156
+ """OSError during version extraction returns 'unknown'."""
157
+ with patch(
158
+ "tools.scan.scanners.tools.subprocess.run",
159
+ side_effect=OSError("Permission denied"),
160
+ ):
161
+ version = scanner._extract_version("/usr/bin/noperm", "--version", None)
162
+ assert version == "unknown"
163
+
164
+ def test_version_timeout_value(self) -> None:
165
+ """Verify timeout constant is 2 seconds."""
166
+ assert _VERSION_TIMEOUT == 2
167
+
168
+
169
+ # ---------------------------------------------------------------------------
170
+ # Tool preferences
171
+ # ---------------------------------------------------------------------------
172
+
173
+
174
+ class TestToolPreferences:
175
+ """Test tool_preferences resolution."""
176
+
177
+ def test_preference_map_built(self, scanner: ToolScanner, tmp_path: Path) -> None:
178
+ """Test that preference map is populated with all known keys."""
179
+ # Create minimal tool definitions for testing
180
+ mock_defs = [
181
+ ToolDefinition(
182
+ name="bat",
183
+ category=ToolCategory.FILE_VIEWER,
184
+ preference_key="file_viewer",
185
+ preference_priority=10,
186
+ ),
187
+ ToolDefinition(
188
+ name="stern",
189
+ category=ToolCategory.KUBERNETES,
190
+ preference_key="log_viewer",
191
+ preference_priority=10,
192
+ ),
193
+ ]
194
+
195
+ def mock_which(name):
196
+ return f"/usr/bin/{name}" if name in ("bat", "stern") else None
197
+
198
+ version_result = MagicMock()
199
+ version_result.returncode = 0
200
+ version_result.stdout = "1.0.0\n"
201
+ version_result.stderr = ""
202
+
203
+ with patch("tools.scan.config.TOOL_DEFINITIONS", mock_defs):
204
+ with patch("tools.scan.scanners.tools.TOOL_DEFINITIONS", mock_defs):
205
+ with patch("tools.scan.scanners.tools.shutil.which", side_effect=mock_which):
206
+ with patch("tools.scan.scanners.tools.subprocess.run", return_value=version_result):
207
+ result = scanner.scan(tmp_path)
208
+
209
+ env = result.sections["environment"]
210
+ prefs = env["tool_preferences"]
211
+ assert prefs["file_viewer"] == "bat"
212
+ assert prefs["log_viewer"] == "stern"
213
+
214
+ def test_highest_priority_wins(self, scanner: ToolScanner, tmp_path: Path) -> None:
215
+ """When two tools compete for same key, highest priority wins."""
216
+ mock_defs = [
217
+ ToolDefinition(
218
+ name="docker",
219
+ category=ToolCategory.CONTAINER,
220
+ preference_key="container_runtime",
221
+ preference_priority=10,
222
+ ),
223
+ ToolDefinition(
224
+ name="podman",
225
+ category=ToolCategory.CONTAINER,
226
+ preference_key="container_runtime",
227
+ preference_priority=5,
228
+ ),
229
+ ]
230
+
231
+ def mock_which(name):
232
+ return f"/usr/bin/{name}" if name in ("docker", "podman") else None
233
+
234
+ version_result = MagicMock()
235
+ version_result.returncode = 0
236
+ version_result.stdout = "1.0.0\n"
237
+ version_result.stderr = ""
238
+
239
+ with patch("tools.scan.config.TOOL_DEFINITIONS", mock_defs):
240
+ with patch("tools.scan.scanners.tools.TOOL_DEFINITIONS", mock_defs):
241
+ with patch("tools.scan.scanners.tools.shutil.which", side_effect=mock_which):
242
+ with patch("tools.scan.scanners.tools.subprocess.run", return_value=version_result):
243
+ result = scanner.scan(tmp_path)
244
+
245
+ env = result.sections["environment"]
246
+ assert env["tool_preferences"]["container_runtime"] == "docker"
247
+
248
+ def test_undetected_preference_is_none(
249
+ self, scanner: ToolScanner, tmp_path: Path
250
+ ) -> None:
251
+ """Preference key with no detected tools gets None."""
252
+ mock_defs = [
253
+ ToolDefinition(
254
+ name="nonexistent_special_tool",
255
+ category=ToolCategory.UTILITY,
256
+ preference_key="special_viewer",
257
+ preference_priority=10,
258
+ ),
259
+ ]
260
+
261
+ with patch("tools.scan.config.TOOL_DEFINITIONS", mock_defs):
262
+ with patch("tools.scan.scanners.tools.TOOL_DEFINITIONS", mock_defs):
263
+ with patch("tools.scan.scanners.tools.shutil.which", return_value=None):
264
+ result = scanner.scan(tmp_path)
265
+
266
+ env = result.sections["environment"]
267
+ assert env["tool_preferences"]["special_viewer"] is None
268
+
269
+
270
+ # ---------------------------------------------------------------------------
271
+ # All ToolCategory values
272
+ # ---------------------------------------------------------------------------
273
+
274
+
275
+ class TestToolCategories:
276
+ """Test that all 11 ToolCategory values exist."""
277
+
278
+ def test_all_11_categories(self) -> None:
279
+ assert len(ToolCategory) == 11
280
+
281
+ def test_category_values(self) -> None:
282
+ expected = {
283
+ "kubernetes", "cloud", "iac", "container", "file_viewer",
284
+ "file_search", "git", "language_runtime", "build", "utility",
285
+ "ai_assistant",
286
+ }
287
+ actual = {cat.value for cat in ToolCategory}
288
+ assert actual == expected
289
+
290
+ def test_all_tool_definitions_have_valid_category(self) -> None:
291
+ for td in TOOL_DEFINITIONS:
292
+ assert isinstance(td.category, ToolCategory)
293
+
294
+
295
+ # ---------------------------------------------------------------------------
296
+ # Full scan with mocked subprocess
297
+ # ---------------------------------------------------------------------------
298
+
299
+
300
+ class TestFullScanMocked:
301
+ """Test full scan with all subprocess calls mocked."""
302
+
303
+ def test_scan_returns_environment_section(
304
+ self, scanner: ToolScanner, tmp_path: Path
305
+ ) -> None:
306
+ """Full scan produces environment section with tools and preferences."""
307
+ def mock_which(name):
308
+ return "/usr/bin/python3" if name == "python3" else None
309
+
310
+ version_result = MagicMock()
311
+ version_result.returncode = 0
312
+ version_result.stdout = "Python 3.11.5\n"
313
+ version_result.stderr = ""
314
+
315
+ with patch("tools.scan.scanners.tools.shutil.which", side_effect=mock_which):
316
+ with patch("tools.scan.scanners.tools.subprocess.run", return_value=version_result):
317
+ result = scanner.scan(tmp_path)
318
+
319
+ assert "environment" in result.sections
320
+ env = result.sections["environment"]
321
+ assert "tools" in env
322
+ assert "tool_preferences" in env
323
+
324
+ def test_scan_individual_failure_does_not_abort(
325
+ self, scanner: ToolScanner, tmp_path: Path
326
+ ) -> None:
327
+ """A tool that throws an exception during probe doesn't abort scan."""
328
+ call_count = 0
329
+
330
+ def mock_which(name):
331
+ nonlocal call_count
332
+ call_count += 1
333
+ if call_count == 1:
334
+ raise OSError("Simulated failure")
335
+ return None
336
+
337
+ with patch("tools.scan.scanners.tools.shutil.which", side_effect=mock_which):
338
+ result = scanner.scan(tmp_path)
339
+
340
+ # Scanner should still return a valid result
341
+ assert "environment" in result.sections
342
+
343
+ def test_scan_result_has_source_tag(
344
+ self, scanner: ToolScanner, tmp_path: Path
345
+ ) -> None:
346
+ with patch("tools.scan.scanners.tools.shutil.which", return_value=None):
347
+ result = scanner.scan(tmp_path)
348
+
349
+ assert result.sections["environment"]["_source"] == "scanner:tools"