@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,227 @@
1
+ """
2
+ Episodic memory capture for workflow episodes.
3
+
4
+ Renamed from episode_capture.py. Absorbs get_session_events() from
5
+ session_state.py directly into this module.
6
+
7
+ Provides:
8
+ - write(): Store workflow as episodic memory
9
+ - get_session_events(): Read context.json, categorize events
10
+ """
11
+
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ # ============================================================================
21
+ # Session events (absorbed from session_state.py)
22
+ # ============================================================================
23
+
24
+ def get_session_events() -> Dict[str, Any]:
25
+ """
26
+ Get critical events from active session context.
27
+
28
+ Returns:
29
+ Dict with categorized session events (commits, pushes, file_mods, speckit)
30
+ """
31
+ context_path = Path(".claude/session/active/context.json")
32
+
33
+ if not context_path.exists():
34
+ logger.debug("No session context found")
35
+ return {}
36
+
37
+ try:
38
+ with open(context_path, "r") as f:
39
+ context = json.load(f)
40
+
41
+ critical_events = context.get("critical_events", [])
42
+
43
+ if not critical_events:
44
+ return {}
45
+
46
+ commits = [
47
+ {
48
+ "hash": e.get("commit_hash", ""),
49
+ "message": e.get("commit_message", ""),
50
+ "timestamp": e.get("timestamp", "")
51
+ }
52
+ for e in critical_events
53
+ if e.get("event_type") == "git_commit" and e.get("commit_hash")
54
+ ]
55
+
56
+ pushes = [
57
+ {
58
+ "branch": e.get("branch", ""),
59
+ "timestamp": e.get("timestamp", "")
60
+ }
61
+ for e in critical_events
62
+ if e.get("event_type") == "git_push" and e.get("branch")
63
+ ]
64
+
65
+ file_mods = [
66
+ {
67
+ "count": e.get("modification_count", 0),
68
+ "timestamp": e.get("timestamp", "")
69
+ }
70
+ for e in critical_events
71
+ if e.get("event_type") == "file_modifications"
72
+ ]
73
+
74
+ speckit = [
75
+ {
76
+ "command": e.get("command", ""),
77
+ "timestamp": e.get("timestamp", "")
78
+ }
79
+ for e in critical_events
80
+ if e.get("event_type") == "speckit_milestone"
81
+ ]
82
+
83
+ result = {}
84
+ if commits:
85
+ result["git_commits"] = commits
86
+ if pushes:
87
+ result["git_pushes"] = pushes
88
+ if file_mods:
89
+ result["file_modifications"] = file_mods
90
+ if speckit:
91
+ result["speckit_milestones"] = speckit
92
+
93
+ if result:
94
+ logger.info(f"Found {len(critical_events)} session events")
95
+
96
+ return result
97
+
98
+ except Exception as e:
99
+ logger.warning(f"Failed to read session events: {e}")
100
+ return {}
101
+
102
+
103
+ # ============================================================================
104
+ # Episodic memory capture
105
+ # ============================================================================
106
+
107
+ def write(
108
+ metrics: Dict[str, Any],
109
+ anomalies: Optional[List[Dict[str, str]]] = None,
110
+ commands_executed: Optional[List[str]] = None,
111
+ ) -> Optional[str]:
112
+ """
113
+ Capture workflow as episodic memory.
114
+
115
+ Args:
116
+ metrics: Subagent metrics from workflow (includes plan_status, tier, task description)
117
+ anomalies: Detected anomalies from audit(), stored in episode context
118
+ commands_executed: List of commands extracted from EVIDENCE_REPORT
119
+
120
+ Returns:
121
+ Episode ID if stored, None otherwise
122
+ """
123
+ try:
124
+ import importlib.util
125
+
126
+ candidates = [
127
+ Path(__file__).parent.parent.parent.parent / "tools" / "memory" / "episodic.py",
128
+ Path(".claude/tools/memory/episodic.py"),
129
+ ]
130
+
131
+ episodic_module = None
132
+ for path in candidates:
133
+ if path.exists():
134
+ try:
135
+ spec = importlib.util.spec_from_file_location("episodic", path)
136
+ if spec and spec.loader:
137
+ episodic_module = importlib.util.module_from_spec(spec)
138
+ spec.loader.exec_module(episodic_module)
139
+ logger.debug(f"Loaded episodic module from {path}")
140
+ break
141
+ except Exception as e:
142
+ logger.debug(f"Could not load episodic from {path}: {e}")
143
+ continue
144
+
145
+ if not episodic_module:
146
+ logger.debug("Episodic memory module not found - skipping episode capture")
147
+ return None
148
+
149
+ memory = episodic_module.EpisodicMemory()
150
+
151
+ # Use the real task description captured from the transcript.
152
+ # metrics["prompt"] now holds the first user message (task description)
153
+ # rather than the generic "SubagentStop for <agent>".
154
+ prompt = metrics.get("prompt", "")
155
+ if not prompt:
156
+ prompt = f"Task for {metrics.get('agent', 'unknown')}"
157
+
158
+ subagent_type = metrics.get("agent", "unknown")
159
+ duration_seconds = metrics.get("duration_ms", 0) / 1000.0 if metrics.get("duration_ms") else None
160
+
161
+ # Determine outcome: prefer plan_status string, fall back to exit_code
162
+ plan_status = metrics.get("plan_status", "")
163
+ exit_code = metrics.get("exit_code", 0)
164
+ if plan_status:
165
+ if "COMPLETE" in plan_status:
166
+ outcome = "success"
167
+ success = True
168
+ elif "BLOCKED" in plan_status or "ERROR" in plan_status:
169
+ outcome = "failed"
170
+ success = False
171
+ else:
172
+ # IN_PROGRESS, REVIEW, NEEDS_INPUT -> partial
173
+ outcome = "partial"
174
+ success = None
175
+ elif exit_code == 0:
176
+ outcome = "success"
177
+ success = True
178
+ else:
179
+ outcome = "failed"
180
+ success = False
181
+
182
+ # Tags from metrics -- filter empty strings defensively
183
+ tags = [t for t in metrics.get("tags", []) if t]
184
+ if not tags and subagent_type and subagent_type != "unknown":
185
+ tags = [subagent_type]
186
+
187
+ # Enrich with session events and anomalies
188
+ session_events = get_session_events()
189
+ context = {"metrics": metrics}
190
+ if session_events:
191
+ context["session_events"] = session_events
192
+ logger.info(f"Enriched episode with session events: {list(session_events.keys())}")
193
+ if anomalies:
194
+ context["anomalies"] = anomalies
195
+ logger.info(f"Episode has {len(anomalies)} anomaly/anomalies")
196
+
197
+ # Include context anchor hit tracking if available
198
+ anchor_hits = metrics.get("context_anchor_hits")
199
+ if anchor_hits:
200
+ context["context_anchor_hits"] = anchor_hits
201
+ logger.info(
202
+ "Episode anchor hits: %d/%d (%.0f%%)",
203
+ anchor_hits.get("hits", 0),
204
+ anchor_hits.get("total_checked", 0),
205
+ anchor_hits.get("hit_rate", 0) * 100,
206
+ )
207
+
208
+ # P3 CLI compatibility fields
209
+ episode_id = memory.store_episode(
210
+ prompt=prompt,
211
+ clarifications={},
212
+ enriched_prompt=prompt,
213
+ context=context,
214
+ tags=tags,
215
+ outcome=outcome,
216
+ success=success,
217
+ duration_seconds=duration_seconds,
218
+ commands_executed=commands_executed or [],
219
+ workflow_metrics=metrics,
220
+ )
221
+
222
+ logger.info(f"Captured episode: {episode_id} (outcome: {outcome}, plan_status: {plan_status})")
223
+ return episode_id
224
+
225
+ except Exception as e:
226
+ logger.debug(f"Failed to capture episodic memory: {e}")
227
+ return None
@@ -0,0 +1 @@
1
+ # Orchestrator enforcement modules.
@@ -0,0 +1,128 @@
1
+ """Orchestrator delegate mode enforcement.
2
+
3
+ When GAIA is installed, delegate mode is always active. The orchestrator
4
+ (main session) is restricted to dispatch-only tools. Direct investigation
5
+ tools (Bash, Read, Edit, etc.) are blocked so the orchestrator must
6
+ delegate to specialist agents.
7
+
8
+ Detection: Claude Code includes ``agent_id`` and ``agent_type`` in the
9
+ PreToolUse payload ONLY when the hook fires inside a subagent. Their absence
10
+ means the call originates from the main session (orchestrator).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ from dataclasses import dataclass
17
+ from typing import Any, Dict, Optional
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Tools the orchestrator is allowed to use in delegate mode.
22
+ # Everything NOT in this set is blocked for the main session.
23
+ ORCHESTRATOR_ALLOWED_TOOLS = frozenset({
24
+ # Dispatch and communication
25
+ "agent",
26
+ "task",
27
+ "sendmessage",
28
+
29
+ # On-demand skills / procedures
30
+ "skill",
31
+
32
+ # Agent teams task management
33
+ "taskcreate",
34
+ "taskupdate",
35
+ "tasklist",
36
+ "taskget",
37
+
38
+ # Tool discovery
39
+ "toolsearch",
40
+
41
+ # Web research (read-only, T0)
42
+ "websearch",
43
+ "webfetch",
44
+
45
+ # User interaction (built-in, may not always trigger hooks)
46
+ "askuserquestion",
47
+ })
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class DelegateModeResult:
52
+ """Result of delegate mode check."""
53
+
54
+ blocked: bool
55
+ reason: Optional[str] = None
56
+
57
+
58
+ def is_orchestrator_context(hook_payload: Dict[str, Any]) -> bool:
59
+ """Determine if the hook is firing in the main session (orchestrator).
60
+
61
+ Claude Code includes ``agent_id`` in the PreToolUse payload only when
62
+ the tool call originates from a subagent. Its absence means the call
63
+ is from the main session.
64
+
65
+ Args:
66
+ hook_payload: The full stdin JSON dict from Claude Code.
67
+
68
+ Returns:
69
+ True if this is the orchestrator (main session), False if subagent.
70
+ """
71
+ agent_id = hook_payload.get("agent_id")
72
+ # agent_id is absent or empty string for the main session
73
+ return not agent_id
74
+
75
+
76
+ def check_delegate_mode(
77
+ tool_name: str, hook_payload: Dict[str, Any]
78
+ ) -> DelegateModeResult:
79
+ """Check whether a tool call should be blocked by delegate mode.
80
+
81
+ This is the single entry point. Call it early in the PreToolUse flow.
82
+
83
+ Args:
84
+ tool_name: The tool being invoked (e.g., "Bash", "Read", "Edit").
85
+ hook_payload: The full stdin JSON dict from Claude Code.
86
+
87
+ Returns:
88
+ DelegateModeResult with blocked=True and a reason if the call
89
+ should be denied, or blocked=False if it should proceed.
90
+ """
91
+ is_orchestrator = is_orchestrator_context(hook_payload)
92
+ if not is_orchestrator:
93
+ # Subagents have full tool access -- delegate mode does not apply
94
+ agent_id = hook_payload.get("agent_id", "<none>")
95
+ logger.debug(
96
+ "delegate_mode check: SKIP (subagent %s) tool=%s",
97
+ agent_id,
98
+ tool_name,
99
+ )
100
+ return DelegateModeResult(blocked=False)
101
+
102
+ normalized = tool_name.lower().strip()
103
+ if normalized in ORCHESTRATOR_ALLOWED_TOOLS:
104
+ logger.debug(
105
+ "delegate_mode check: ALLOW (orchestrator allowed tool) tool=%s",
106
+ tool_name,
107
+ )
108
+ return DelegateModeResult(blocked=False)
109
+
110
+ logger.warning(
111
+ "DELEGATE_MODE blocked tool '%s' for orchestrator (main session)",
112
+ tool_name,
113
+ )
114
+
115
+ return DelegateModeResult(
116
+ blocked=True,
117
+ reason=(
118
+ f"[DELEGATE MODE] Tool '{tool_name}' is not available to the orchestrator.\n\n"
119
+ f"As the orchestrator, you must delegate work to specialist agents.\n"
120
+ f"Use the Agent tool to dispatch to the appropriate agent, or use\n"
121
+ f"SendMessage to resume an existing agent.\n\n"
122
+ f"Allowed orchestrator tools: Agent, SendMessage, Skill, TaskCreate, "
123
+ f"TaskUpdate, TaskList, TaskGet, ToolSearch, WebSearch, WebFetch, "
124
+ f"AskUserQuestion.\n\n"
125
+ f"Do NOT attempt to use {tool_name} directly. Identify the right\n"
126
+ f"specialist agent and delegate the work."
127
+ ),
128
+ )
@@ -0,0 +1,8 @@
1
+ """
2
+ Scanning Module
3
+
4
+ Provides tools for triggering project scans:
5
+ - scan_trigger: Lightweight scan invocation for session-start auto-refresh
6
+ """
7
+
8
+ __all__ = []
@@ -0,0 +1,84 @@
1
+ """
2
+ Lightweight scan trigger for SessionStart hook.
3
+
4
+ Runs a subset of project scanners (e.g., tools + environment) to refresh
5
+ project-context.json without significant startup delay (<3s target).
6
+
7
+ Uses the scan engine directly (in-process) — no dependency on bin/gaia-scan.py.
8
+ Works in both npm and plugin mode since tools/scan/ is always available.
9
+
10
+ Public API:
11
+ - trigger_lightweight_scan(project_root: Path, scanners: list) -> bool
12
+ """
13
+
14
+ import logging
15
+ import sys
16
+ import time
17
+ from pathlib import Path
18
+ from typing import List
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def trigger_lightweight_scan(
24
+ project_root: Path,
25
+ scanners: List[str] = None,
26
+ ) -> bool:
27
+ """Run a lightweight scan using the scan engine directly.
28
+
29
+ Args:
30
+ project_root: Working directory for the scan.
31
+ scanners: List of scanner names to run. Defaults to
32
+ ["tools", "environment"].
33
+
34
+ Returns:
35
+ True on success, False on failure. Designed to complete in <3s.
36
+ """
37
+ if scanners is None:
38
+ scanners = ["tools", "environment"]
39
+
40
+ # Ensure tools.scan is importable by adding plugin root to sys.path
41
+ hooks_dir = Path(__file__).resolve().parents[2] # hooks/
42
+ plugin_root = hooks_dir.parent
43
+ if str(plugin_root) not in sys.path:
44
+ sys.path.insert(0, str(plugin_root))
45
+
46
+ try:
47
+ from tools.scan.config import ScanConfig
48
+ from tools.scan.orchestrator import ScanOrchestrator
49
+ from tools.scan.registry import ScannerRegistry
50
+ except ImportError as e:
51
+ logger.warning("Cannot import scan engine: %s", e)
52
+ return False
53
+
54
+ try:
55
+ start = time.monotonic()
56
+
57
+ config = ScanConfig(
58
+ scanners=scanners,
59
+ project_root=project_root,
60
+ )
61
+ registry = ScannerRegistry()
62
+ orchestrator = ScanOrchestrator(registry=registry, config=config)
63
+ output = orchestrator.run(project_root=project_root)
64
+ elapsed = time.monotonic() - start
65
+
66
+ if output.errors:
67
+ logger.warning(
68
+ "Lightweight scan completed with errors in %.1fs: %s",
69
+ elapsed,
70
+ output.errors[:3],
71
+ )
72
+ return False
73
+
74
+ logger.info(
75
+ "Lightweight scan completed in %.1fs (scanners: %s, sections: %d)",
76
+ elapsed,
77
+ ", ".join(scanners),
78
+ output.sections_updated,
79
+ )
80
+ return True
81
+
82
+ except Exception as e:
83
+ logger.warning("Failed to run lightweight scan: %s", e)
84
+ return False
@@ -0,0 +1,89 @@
1
+ """
2
+ Security module - Security tiers, blocked patterns, mutative verb detection.
3
+
4
+ Provides:
5
+ - tiers: SecurityTier enum and classification
6
+ - blocked_commands: Permanently blocked pattern matching
7
+ - mutative_verbs: Mutative verb detection (user approval workflow)
8
+ - gitops_validator: kubectl/helm/flux validation
9
+ - approval_constants: Approval token patterns (legacy APPROVE: and ElicitationResult)
10
+ - approval_grants: Time-limited T3 command passthrough after user approval
11
+ """
12
+
13
+ from .tiers import SecurityTier, classify_command_tier
14
+ from .command_semantics import analyze_command, CommandSemantics
15
+ from .blocked_commands import (
16
+ is_blocked_command,
17
+ get_blocked_patterns,
18
+ BlockedCommandResult,
19
+ )
20
+ from .gitops_validator import validate_gitops_workflow, GitOpsValidationResult
21
+ from .mutative_verbs import (
22
+ CLI_FAMILY_LOOKUP,
23
+ CATEGORY_MUTATIVE,
24
+ CATEGORY_SIMULATION,
25
+ CATEGORY_READ_ONLY,
26
+ CATEGORY_UNKNOWN,
27
+ )
28
+ from .approval_constants import NONCE_APPROVAL_PATTERN, NONCE_APPROVAL_PREFIX
29
+ from .approval_messages import (
30
+ CANONICAL_APPROVAL_TOKEN,
31
+ CANONICAL_APPROVAL_TOKEN_FORMAT,
32
+ CANONICAL_APPROVAL_TOKEN_GUIDANCE,
33
+ CANONICAL_APPROVAL_FORMAT_GUIDANCE,
34
+ LATEST_BLOCKED_COMMAND_PHRASE,
35
+ )
36
+ from .approval_scopes import (
37
+ ApprovalSignature,
38
+ SCOPE_EXACT_COMMAND,
39
+ SCOPE_SEMANTIC_SIGNATURE,
40
+ build_approval_signature,
41
+ matches_approval_signature,
42
+ )
43
+ from .approval_grants import (
44
+ check_approval_grant,
45
+ cleanup_expired_grants,
46
+ get_latest_pending_approval,
47
+ last_check_found_expired,
48
+ ApprovalGrant,
49
+ )
50
+
51
+ __all__ = [
52
+ # Tiers
53
+ "SecurityTier",
54
+ "classify_command_tier",
55
+ "analyze_command",
56
+ "CommandSemantics",
57
+ # Blocked commands
58
+ "is_blocked_command",
59
+ "get_blocked_patterns",
60
+ "BlockedCommandResult",
61
+ # GitOps
62
+ "validate_gitops_workflow",
63
+ "GitOpsValidationResult",
64
+ # Mutative verbs
65
+ "CLI_FAMILY_LOOKUP",
66
+ "CATEGORY_MUTATIVE",
67
+ "CATEGORY_SIMULATION",
68
+ "CATEGORY_READ_ONLY",
69
+ "CATEGORY_UNKNOWN",
70
+ # Approval
71
+ "NONCE_APPROVAL_PREFIX",
72
+ "NONCE_APPROVAL_PATTERN",
73
+ "CANONICAL_APPROVAL_TOKEN",
74
+ "CANONICAL_APPROVAL_TOKEN_FORMAT",
75
+ "CANONICAL_APPROVAL_TOKEN_GUIDANCE",
76
+ "CANONICAL_APPROVAL_FORMAT_GUIDANCE",
77
+ "LATEST_BLOCKED_COMMAND_PHRASE",
78
+ "ApprovalSignature",
79
+ "SCOPE_EXACT_COMMAND",
80
+ "SCOPE_SEMANTIC_SIGNATURE",
81
+ "build_approval_signature",
82
+ "matches_approval_signature",
83
+ # Approval Grants
84
+ "check_approval_grant",
85
+ "cleanup_expired_grants",
86
+ "get_latest_pending_approval",
87
+ "last_check_found_expired",
88
+ "ApprovalGrant",
89
+ ]
@@ -0,0 +1,87 @@
1
+ """
2
+ Approval file cleanup for the subagent stop hook.
3
+
4
+ Cleans up pending approval files after an agent completes, using the current
5
+ per-nonce file layout under .claude/cache/approvals/pending-{nonce}.json.
6
+
7
+ Provides:
8
+ - cleanup(): Delete pending approval files that match agent session
9
+ - consume_approval_file(): Backward-compatible alias for cleanup()
10
+ """
11
+
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import Optional
16
+
17
+ from ..core.paths import find_claude_dir
18
+ from ..core.state import get_session_id
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def _get_approvals_dir() -> Path:
24
+ """Return the approvals cache directory."""
25
+ return find_claude_dir() / "cache" / "approvals"
26
+
27
+
28
+ def cleanup(agent_type: str, session_id: Optional[str] = None) -> bool:
29
+ """
30
+ Delete pending-{nonce}.json files for the current session after agent completion.
31
+
32
+ Scans .claude/cache/approvals/ for pending files scoped to the current
33
+ session and removes them, preventing stale pending approvals from
34
+ accumulating after the agent run finishes.
35
+
36
+ Args:
37
+ agent_type: The agent type that just completed (for logging).
38
+ session_id: Session ID to scope cleanup (defaults to CLAUDE_SESSION_ID).
39
+
40
+ Returns:
41
+ True if any pending approval files were consumed, False otherwise.
42
+ """
43
+ if session_id is None:
44
+ session_id = get_session_id()
45
+
46
+ approvals_dir = _get_approvals_dir()
47
+ if not approvals_dir.exists():
48
+ return False
49
+
50
+ consumed = False
51
+ try:
52
+ for pending_file in approvals_dir.glob("pending-*.json"):
53
+ # Skip the per-session index files
54
+ if pending_file.name.startswith("pending-index-"):
55
+ continue
56
+ try:
57
+ data = json.loads(pending_file.read_text())
58
+ if data.get("session_id") != session_id:
59
+ continue
60
+
61
+ pending_file.unlink(missing_ok=True)
62
+ logger.info(
63
+ "Consumed pending approval for agent '%s' "
64
+ "(nonce: %s, command: %s)",
65
+ agent_type,
66
+ data.get("nonce", "unknown"),
67
+ data.get("command", "unknown"),
68
+ )
69
+ consumed = True
70
+
71
+ except (json.JSONDecodeError, TypeError):
72
+ # Corrupt file -- remove it
73
+ pending_file.unlink(missing_ok=True)
74
+ consumed = True
75
+ except Exception as e:
76
+ logger.debug(
77
+ "Failed to process pending file %s (non-fatal): %s",
78
+ pending_file.name, e,
79
+ )
80
+ except Exception as e:
81
+ logger.debug("Failed to scan approvals dir (non-fatal): %s", e)
82
+
83
+ return consumed
84
+
85
+
86
+ # Backward-compatible alias
87
+ consume_approval_file = cleanup
@@ -0,0 +1,23 @@
1
+ """Approval token patterns and deprecated approval phrases for T3 operation resumes.
2
+
3
+ The APPROVE: prefix is a legacy path (SendMessage-based nonce relay). The primary
4
+ approval flow now uses ElicitationResult (AskUserQuestion -> user clicks Approve).
5
+ """
6
+
7
+ import re
8
+
9
+ NONCE_APPROVAL_PREFIX = "APPROVE:"
10
+ NONCE_APPROVAL_PATTERN = re.compile(r"\bAPPROVE:([a-f0-9]{32})\b")
11
+
12
+ # Deprecated approval phrases that agents should not use.
13
+ # Moved here from pre_tool_use.py so all approval-related constants live together.
14
+ DEPRECATED_APPROVAL_PHRASES = (
15
+ "user approved:",
16
+ "user approval received",
17
+ "approved by user",
18
+ "approval confirmed",
19
+ "approved. execute",
20
+ "approved, execute",
21
+ "proceed with execution",
22
+ "confirmed. proceed",
23
+ )