@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,54 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Post-tool use hook - Thin gate.
4
+
5
+ Architecture:
6
+ - Uses adapter layer to parse and process the full PostToolUse lifecycle
7
+ - All business logic lives in ClaudeCodeAdapter.adapt_post_tool_use()
8
+ - This file is stdin/stdout glue only
9
+ """
10
+
11
+ import sys
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+ from datetime import datetime
16
+
17
+ sys.path.insert(0, str(Path(__file__).parent))
18
+
19
+ from modules.core.paths import get_logs_dir
20
+ from adapters.claude_code import ClaudeCodeAdapter
21
+ from modules.core.hook_entry import run_hook
22
+
23
+ # Configure logging
24
+ log_file = get_logs_dir() / f"hooks-{datetime.now().strftime('%Y-%m-%d')}.log"
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format='%(asctime)s [post_tool_use] %(name)s - %(levelname)s - %(message)s',
28
+ handlers=[logging.FileHandler(log_file)],
29
+ )
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ def _handle_post_tool_use(event) -> None:
34
+ """Process a PostToolUse event.
35
+
36
+ Delegates all business logic to the adapter.
37
+
38
+ Args:
39
+ event: Parsed HookEvent from the adapter layer.
40
+ """
41
+ adapter = ClaudeCodeAdapter()
42
+ response = adapter.adapt_post_tool_use(event)
43
+
44
+ if response.output:
45
+ print(json.dumps(response.output))
46
+ sys.exit(response.exit_code)
47
+
48
+
49
+ # ============================================================================
50
+ # STDIN HANDLER (Claude Code integration)
51
+ # ============================================================================
52
+
53
+ if __name__ == "__main__":
54
+ run_hook(_handle_post_tool_use, hook_name="post_tool_use")
@@ -0,0 +1,383 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Pre-tool use hook - Thin Gate Architecture.
4
+
5
+ Entry point for Bash and Task/Agent tool validation. The hook is the primary
6
+ security gate: with Bash(*) in the settings.json allow list, all commands
7
+ reach this hook regardless of settings.json permissions.
8
+
9
+ Architecture:
10
+ - Uses adapter layer to parse and process the full PreToolUse lifecycle
11
+ - All business logic lives in ClaudeCodeAdapter.adapt_pre_tool_use()
12
+ - This file is stdin/stdout glue only
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+ import json
18
+ import logging
19
+ from pathlib import Path
20
+ from datetime import datetime
21
+
22
+ sys.path.insert(0, str(Path(__file__).parent))
23
+ from modules.core.paths import get_logs_dir
24
+
25
+ # Adapter layer
26
+ from adapters.claude_code import ClaudeCodeAdapter
27
+ from modules.core.stdin import has_stdin_data
28
+ from adapters.utils import warn_if_dual_channel
29
+
30
+ # Configure logging -- all hooks share hooks-YYYY-MM-DD.log for easy tailing
31
+ log_file = get_logs_dir() / f"hooks-{datetime.now().strftime('%Y-%m-%d')}.log"
32
+ logging.basicConfig(
33
+ level=logging.INFO,
34
+ format='%(asctime)s [pre_tool_use] %(name)s - %(levelname)s - %(message)s',
35
+ handlers=[
36
+ logging.FileHandler(log_file),
37
+ ]
38
+ )
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ # ============================================================================
43
+ # BACKWARD-COMPATIBLE API
44
+ # ============================================================================
45
+ # Tests and e2e scripts import these names directly. They delegate to the
46
+ # adapter internally but preserve the original call signatures.
47
+
48
+ from modules.tools.bash_validator import BashValidator
49
+ from modules.tools.task_validator import TaskValidator, AVAILABLE_AGENTS, META_AGENTS
50
+ from modules.security.prompt_validator import classify_resume_prompt
51
+ from modules.context.context_injector import build_project_context
52
+ from modules.session.session_event_injector import build_session_events
53
+ from modules.core.state import create_pre_hook_state, save_hook_state
54
+ from modules.security.approval_grants import (
55
+ cleanup_expired_grants,
56
+ )
57
+
58
+ # Derived constants used by backward-compat wrappers
59
+ PROJECT_AGENTS = [a for a in AVAILABLE_AGENTS if a not in META_AGENTS]
60
+ _HOOKS_DIR = Path(__file__).parent
61
+
62
+
63
+ def _classify_resume_prompt(prompt: str) -> str:
64
+ """Classify a resume prompt. Delegates to modules.security.prompt_validator."""
65
+ return classify_resume_prompt(prompt)
66
+
67
+
68
+ def pre_tool_use_hook(tool_name: str, parameters: dict) -> str | dict | None:
69
+ """
70
+ Pre-tool use hook implementation (backward-compatible API).
71
+
72
+ Delegates to adapter but preserves the original return signature:
73
+ None: allowed (no modification)
74
+ str: blocked (error message)
75
+ dict: allowed with modification (JSON with updatedInput)
76
+ """
77
+ adapter = ClaudeCodeAdapter()
78
+
79
+ # Build a minimal HookEvent-like payload for the adapter's internal methods
80
+ logger.info(f"Hook invoked: tool={tool_name}, params={json.dumps(parameters)[:200]}")
81
+
82
+ try:
83
+ cleanup_expired_grants()
84
+
85
+ if not isinstance(tool_name, str):
86
+ return "Error: Invalid tool name"
87
+ if not isinstance(parameters, dict):
88
+ return "Error: Invalid parameters"
89
+
90
+ if tool_name.lower() == "bash":
91
+ return _handle_bash(tool_name, parameters)
92
+ elif tool_name.lower() in ("task", "agent"):
93
+ return _handle_task(tool_name, parameters)
94
+ elif tool_name.lower() == "sendmessage":
95
+ return _handle_send_message(tool_name, parameters)
96
+ else:
97
+ return None
98
+
99
+ except Exception as e:
100
+ logger.error(f"Unexpected error in pre_tool_use_hook: {e}", exc_info=True)
101
+ return f"Error during security validation: {str(e)}"
102
+
103
+
104
+ def _handle_bash(tool_name: str, parameters: dict) -> str | dict | None:
105
+ """
106
+ Handle Bash tool validation.
107
+
108
+ Returns:
109
+ None: allowed (no modification)
110
+ str: blocked (error message)
111
+ dict: allowed with modification (hookSpecificOutput JSON)
112
+ """
113
+ command = parameters.get("command", "")
114
+ if not command:
115
+ return "Error: Bash tool requires a command"
116
+
117
+ validator = BashValidator()
118
+ result = validator.validate(command)
119
+
120
+ if not result.allowed:
121
+ logger.warning(f"BLOCKED: {command[:100]} - {result.reason}")
122
+
123
+ # Structured response from bash_validator (ask or deny)
124
+ if result.block_response is not None:
125
+ return result.block_response
126
+
127
+ # Permanently blocked (no structured response) — hard block (exit 2)
128
+ return _format_blocked_message(result)
129
+
130
+ effective_command = result.modified_input.get("command", command) if result.modified_input else command
131
+ state = create_pre_hook_state(
132
+ tool_name=tool_name,
133
+ command=effective_command,
134
+ tier=str(result.tier),
135
+ allowed=True,
136
+ )
137
+ save_hook_state(state)
138
+
139
+ if result.modified_input:
140
+ logger.info(f"MODIFIED: {command[:80]} -> stripped footer - tier={result.tier}")
141
+ return {
142
+ "hookSpecificOutput": {
143
+ "hookEventName": "PreToolUse",
144
+ "permissionDecision": "allow",
145
+ "permissionDecisionReason": result.reason,
146
+ "updatedInput": result.modified_input
147
+ }
148
+ }
149
+
150
+ logger.info(f"ALLOWED: {command[:100]} - tier={result.tier}")
151
+ return None
152
+
153
+
154
+ def _handle_task(tool_name: str, parameters: dict) -> str | dict | None:
155
+ """
156
+ Handle Task/Agent tool validation for new task dispatches.
157
+
158
+ Context is built here and cached for SubagentStart to forward to the
159
+ subagent. PreToolUse no longer returns additionalContext (that would
160
+ inject it into the orchestrator, not the subagent).
161
+ """
162
+ context_text, _telemetry = build_project_context(parameters, PROJECT_AGENTS, _HOOKS_DIR)
163
+ events_text = build_session_events(parameters, PROJECT_AGENTS)
164
+
165
+ # Standard task validation (runs against ORIGINAL prompt -- no workaround needed)
166
+ validator = TaskValidator()
167
+ result = validator.validate(parameters)
168
+
169
+ if not result.allowed:
170
+ logger.warning(f"BLOCKED Task: {result.agent_name} - {result.reason}")
171
+ return result.reason
172
+
173
+ state = create_pre_hook_state(
174
+ tool_name=tool_name,
175
+ command=f"Task:{result.agent_name}",
176
+ tier=str(result.tier),
177
+ allowed=True,
178
+ is_t3=result.is_t3_operation,
179
+ )
180
+ save_hook_state(state)
181
+
182
+ logger.info(f"ALLOWED Task: {result.agent_name}")
183
+
184
+ # Cache context for SubagentStart to pick up and forward to the subagent.
185
+ additional = "\n".join(filter(None, [context_text, events_text]))
186
+
187
+ # Fallback: if build_project_context returned None because the
188
+ # orchestrator already embedded context in the prompt (dedup guard),
189
+ # extract the embedded context so SubagentStart can still inject it.
190
+ if not additional:
191
+ prompt = parameters.get("prompt", "")
192
+ marker = "# Project Context"
193
+ if marker in prompt:
194
+ idx = prompt.index(marker)
195
+ additional = prompt[idx:]
196
+ logger.info(
197
+ "Extracted embedded context from prompt for caching "
198
+ "(len=%d, agent=%s)",
199
+ len(additional), result.agent_name,
200
+ )
201
+
202
+ if additional:
203
+ from adapters.claude_code import ClaudeCodeAdapter
204
+ adapter = ClaudeCodeAdapter()
205
+ session_id = parameters.get("session_id", "") or "unknown"
206
+ agent_type = result.agent_name or "unknown"
207
+ adapter._cache_context_for_subagent(session_id, agent_type, additional)
208
+ logger.info(f"Cached context for SubagentStart: agent={agent_type}")
209
+
210
+ return None
211
+
212
+
213
+ def _handle_send_message(tool_name: str, parameters: dict) -> str | None:
214
+ """
215
+ Handle SendMessage tool validation for agent resumption.
216
+
217
+ Validates agent ID format and message content. Does NOT inject
218
+ project context (it's a resume). Nonce relay is no longer processed
219
+ here -- approval grants are activated by the UserPromptSubmit hook.
220
+
221
+ Returns:
222
+ None: allowed (no modification)
223
+ str: blocked (error message)
224
+ """
225
+ import re
226
+
227
+ agent_id = parameters.get("to", "")
228
+ message = parameters.get("message", "")
229
+
230
+ if not agent_id or not re.match(r'^a[0-9a-f]{5,}$', agent_id):
231
+ logger.warning(f"BLOCKED SendMessage: Invalid agentId format '{agent_id}'")
232
+ return (
233
+ f"[ERROR] Invalid agent ID format: '{agent_id}'\n\n"
234
+ "Agent ID should be 'a' followed by hex characters.\n"
235
+ "Example: a12345f or a51a0cbbf6afb831d\n\n"
236
+ "The agent ID is returned at the end of agent responses.\n"
237
+ "Look for: 'agentId: a...' in the previous agent output."
238
+ )
239
+
240
+ if not message or not message.strip():
241
+ logger.warning(f"BLOCKED SendMessage: Missing message for agent {agent_id}")
242
+ return (
243
+ "[ERROR] SendMessage requires a message\n\n"
244
+ "When resuming an agent, you must provide a message:\n\n"
245
+ "SendMessage(\n"
246
+ " to=\"a12345\",\n"
247
+ " message=\"Continue with the latest user instruction.\"\n"
248
+ ")\n\n"
249
+ "The message tells the agent what to do next."
250
+ )
251
+
252
+ logger.info(f"SENDMESSAGE: Resuming agent {agent_id}")
253
+
254
+ state = create_pre_hook_state(
255
+ tool_name=tool_name,
256
+ command=f"SendMessage:{agent_id}",
257
+ tier="T0",
258
+ allowed=True,
259
+ is_t3=False,
260
+ has_approval=False,
261
+ )
262
+ save_hook_state(state)
263
+
264
+ logger.info(f"ALLOWED SendMessage: agent {agent_id} - message length: {len(message)}")
265
+ return None
266
+
267
+
268
+ def _format_blocked_message(result) -> str:
269
+ """Format blocked command message. Delegates to blocked_message_formatter."""
270
+ from modules.security.blocked_message_formatter import format_blocked_message
271
+ return format_blocked_message(result)
272
+
273
+
274
+ # ============================================================================
275
+ # CLI INTERFACE
276
+ # ============================================================================
277
+
278
+ def main():
279
+ """CLI interface for testing."""
280
+ if len(sys.argv) < 2:
281
+ print("Usage: python pre_tool_use.py <command>")
282
+ print(" python pre_tool_use.py --test")
283
+ sys.exit(1)
284
+
285
+ if sys.argv[1] == "--test":
286
+ _run_tests()
287
+ else:
288
+ command = " ".join(sys.argv[1:])
289
+ result = pre_tool_use_hook("bash", {"command": command})
290
+ if result:
291
+ print(f"BLOCKED: {result}")
292
+ sys.exit(1)
293
+ else:
294
+ print(f"ALLOWED: {command}")
295
+
296
+
297
+ def _run_tests():
298
+ """Run validation tests."""
299
+ print("Testing Pre-Tool Use Hook...\n")
300
+
301
+ test_cases = [
302
+ ("terraform validate", True, "T1"),
303
+ ("terraform apply", False, "T3"),
304
+ ("kubectl get pods", True, "T0"),
305
+ ("kubectl apply -f manifest.yaml", False, "T3"),
306
+ ("kubectl apply -f manifest.yaml --dry-run=client", True, "T2"),
307
+ ("ls -la", True, "T0"),
308
+ ("rm -rf /", False, "T3"),
309
+ ]
310
+
311
+ for command, expected_allowed, expected_tier in test_cases:
312
+ result = pre_tool_use_hook("bash", {"command": command})
313
+ actual_allowed = result is None
314
+ status = "PASS" if actual_allowed == expected_allowed else "FAIL"
315
+ print(f"{status}: {command}")
316
+ if status == "FAIL":
317
+ print(f" Expected: allowed={expected_allowed}, Got: allowed={actual_allowed}")
318
+
319
+ print("\nTest completed")
320
+
321
+
322
+ # ============================================================================
323
+ # STDIN HANDLER (Claude Code integration)
324
+ # ============================================================================
325
+
326
+ if __name__ == "__main__":
327
+ # Check if running from CLI with arguments
328
+ if len(sys.argv) > 1:
329
+ main()
330
+ elif has_stdin_data():
331
+ try:
332
+ adapter = ClaudeCodeAdapter()
333
+ warn_if_dual_channel()
334
+
335
+ stdin_data = sys.stdin.read()
336
+
337
+ try:
338
+ event = adapter.parse_event(stdin_data)
339
+ except ValueError as e:
340
+ error_msg = str(e)
341
+ logger.error(f"Adapter parse failed: {error_msg}")
342
+ print(f"HOOK ERROR: {error_msg}", file=sys.stderr)
343
+ if "Empty stdin" in error_msg:
344
+ print(f"Error: {error_msg}")
345
+ sys.exit(1)
346
+
347
+ response = adapter.adapt_pre_tool_use(event)
348
+
349
+ if isinstance(response.output, dict) and response.output:
350
+ hook_output = response.output.get("hookSpecificOutput", {})
351
+ decision = hook_output.get("permissionDecision")
352
+ if decision in ("block", "deny"):
353
+ reason = hook_output.get("permissionDecisionReason", "Command blocked by hook policy")
354
+ summary = reason.split('\n')[0]
355
+ print(f"BLOCKED: {summary}", file=sys.stderr)
356
+ elif decision == "ask":
357
+ reason = hook_output.get("permissionDecisionReason", "")
358
+ summary = reason.split('\n')[0]
359
+ print(f"T3: {summary}", file=sys.stderr)
360
+ print(json.dumps(response.output))
361
+ sys.exit(response.exit_code)
362
+ elif isinstance(response.output, str) and response.output:
363
+ summary = response.output.split('\n')[0]
364
+ print(f"BLOCKED: {summary}", file=sys.stderr)
365
+ print(response.output)
366
+ sys.exit(response.exit_code)
367
+ else:
368
+ sys.exit(0)
369
+
370
+ except json.JSONDecodeError as e:
371
+ logger.error(f"Invalid JSON from stdin: {e}")
372
+ print(f"HOOK ERROR: Invalid JSON from stdin: {e}", file=sys.stderr)
373
+ sys.exit(1)
374
+ except Exception as e:
375
+ logger.error(f"Error processing hook: {e}", exc_info=True)
376
+ print(f"HOOK ERROR: {str(e)}", file=sys.stderr)
377
+ print(f"Hook error: {str(e)}")
378
+ sys.exit(1)
379
+ else:
380
+ print("Usage: python pre_tool_use.py <command>")
381
+ print(" python pre_tool_use.py --test")
382
+ print(" echo '{\"tool_name\":\"bash\",\"tool_input\":{\"command\":\"ls\"}}' | python pre_tool_use.py")
383
+ sys.exit(1)
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """SessionStart hook — first-time setup + project scan (ops only)."""
3
+
4
+ import sys
5
+ import json
6
+ import logging
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+
10
+ sys.path.insert(0, str(Path(__file__).parent))
11
+
12
+ from modules.core.stdin import has_stdin_data
13
+ from modules.core.paths import get_logs_dir
14
+ from modules.core.plugin_mode import is_ops_mode
15
+ from modules.core.plugin_setup import run_first_time_setup
16
+
17
+ # Configure logging — file only
18
+ _log_file = get_logs_dir() / f"hooks-{datetime.now().strftime('%Y-%m-%d')}.log"
19
+ logging.basicConfig(
20
+ level=logging.INFO,
21
+ format='%(asctime)s [session_start] %(name)s - %(levelname)s - %(message)s',
22
+ handlers=[logging.FileHandler(_log_file)],
23
+ )
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ if __name__ == "__main__":
28
+ if not has_stdin_data():
29
+ sys.exit(0)
30
+
31
+ try:
32
+ sys.stdin.read()
33
+
34
+ # First-time setup: create project permissions if needed.
35
+ # mark_done=False so UserPromptSubmit can detect first-run
36
+ # and show the welcome message before marking initialized.
37
+ setup_message = run_first_time_setup(mark_done=False)
38
+ if setup_message:
39
+ logger.info("First-time setup: %s", setup_message)
40
+
41
+ # Project scan: only in ops mode
42
+ project_scanned = False
43
+ if is_ops_mode():
44
+ from modules.context.context_freshness import check_freshness
45
+ from modules.scanning.scan_trigger import trigger_lightweight_scan
46
+
47
+ freshness = check_freshness()
48
+ if freshness.is_fresh:
49
+ logger.info("SessionStart: skipped scan (fresh)")
50
+ else:
51
+ logger.info("SessionStart: %s — running lightweight scan", freshness.reason)
52
+ scan_ok = trigger_lightweight_scan(Path.cwd())
53
+ if scan_ok:
54
+ project_scanned = True
55
+ logger.info("Auto-refresh completed successfully")
56
+ else:
57
+ logger.warning("Auto-refresh failed")
58
+
59
+ response = {"session_type": "startup", "project_scanned": project_scanned}
60
+ if setup_message:
61
+ response["setup_message"] = setup_message
62
+
63
+ print(json.dumps(response))
64
+ sys.exit(0)
65
+
66
+ except Exception as e:
67
+ logger.error("SessionStart error (non-fatal): %s", e)
68
+ print(json.dumps({}))
69
+ sys.exit(0)
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Stop hook for Claude Code Agent System.
4
+
5
+ Fires when Claude finishes responding. Evaluates whether the response has
6
+ adequate evidence quality. For MVP: logs the event and allows stop (exit 0).
7
+ Quality check logic will be wired in a future iteration.
8
+
9
+ Architecture:
10
+ - Uses adapter layer to parse Stop event
11
+ - Calls adapter.adapt_stop() for quality assessment
12
+ - Returns quality result via adapter format_quality_response()
13
+ - Exit code 0 = allow stop, exit code 2 = continue instead of stop
14
+ """
15
+
16
+ import sys
17
+ import json
18
+ import logging
19
+ from datetime import datetime
20
+ from pathlib import Path
21
+
22
+ sys.path.insert(0, str(Path(__file__).parent))
23
+
24
+ from adapters.claude_code import ClaudeCodeAdapter
25
+ from modules.core.hook_entry import run_hook
26
+ from modules.core.paths import get_logs_dir
27
+
28
+ # Configure logging
29
+ _log_file = get_logs_dir() / f"hooks-{datetime.now().strftime('%Y-%m-%d')}.log"
30
+ logging.basicConfig(
31
+ level=logging.INFO,
32
+ format='%(asctime)s [stop_hook] %(name)s - %(levelname)s - %(message)s',
33
+ handlers=[logging.FileHandler(_log_file)],
34
+ )
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ def _handle_stop(event) -> None:
39
+ """Process a Stop event.
40
+
41
+ Evaluates response quality and decides whether to allow the stop.
42
+ For MVP, always allows stop (exit 0).
43
+
44
+ Args:
45
+ event: Parsed HookEvent from the adapter layer.
46
+ """
47
+ adapter = ClaudeCodeAdapter()
48
+
49
+ quality_result = adapter.adapt_stop(event.payload)
50
+ stop_reason = event.payload.get("stop_reason", "unknown")
51
+
52
+ logger.info(
53
+ "Stop: reason=%s, quality_sufficient=%s, score=%.2f",
54
+ stop_reason,
55
+ quality_result.quality_sufficient,
56
+ quality_result.score,
57
+ )
58
+
59
+ response = adapter.format_quality_response(quality_result)
60
+ print(json.dumps(response.output))
61
+ sys.exit(0)
62
+
63
+
64
+ # ============================================================================
65
+ # STDIN HANDLER (Claude Code integration)
66
+ # ============================================================================
67
+
68
+ if __name__ == "__main__":
69
+ run_hook(_handle_stop, hook_name="stop_hook")