@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
@@ -5,7 +5,8 @@ Primary security gate for all Bash tool invocations. With Bash(*) in the
5
5
  settings.json allow list, ALL commands reach this hook -- it is the sole
6
6
  enforcement layer for dangerous command detection.
7
7
 
8
- Simplified three-category pipeline:
8
+ Pipeline (ordered by priority):
9
+ 0. Indirect execution detection -- bash -c, eval, python -c etc. (T2 approval)
9
10
  1. blocked_commands FIRST -- permanently denied patterns (exit 2)
10
11
  2. Claude footer stripping -- transparent cleanup via updatedInput
11
12
  3. Commit message validation -- conventional commits format
@@ -30,6 +31,7 @@ from ..security.mutative_verbs import (
30
31
  from ..security.approval_grants import (
31
32
  check_approval_grant,
32
33
  confirm_grant,
34
+ find_pending_for_command,
33
35
  generate_nonce,
34
36
  last_check_found_expired,
35
37
  write_pending_approval,
@@ -63,10 +65,40 @@ class BashValidationResult:
63
65
  self.suggestions = []
64
66
 
65
67
 
66
- # Patterns for Claude Code attribution footers (forbidden)
68
+ # Patterns for AI tool attribution footers (auto-stripped from commits).
69
+ # Covers Claude Code, GitHub Copilot, Aider, Windsurf, and any future
70
+ # tool using the Co-authored-by git trailer convention.
67
71
  FORBIDDEN_FOOTER_PATTERNS = [
68
72
  r"Generated with\s+Claude Code",
69
- r"Co-Authored-By:\s+Claude",
73
+ r"Generated with\s+\[?Claude Code\]?",
74
+ r"Co-Authored-By:\s+Claude\b",
75
+ r"Co-authored-by:\s+GitHub Copilot\b",
76
+ r"Co-authored-by:\s+aider\b",
77
+ r"Co-authored-by:\s+Windsurf\b",
78
+ r"Co-authored-by:\s+Cursor\b",
79
+ r"Co-authored-by:\s+Codex\b",
80
+ r"Co-authored-by:\s+Gemini\b",
81
+ ]
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Indirect execution wrappers — commands that execute arbitrary strings.
85
+ # These bypass regex-based command blocking because the real command is
86
+ # hidden inside a string argument. Classified as T2 (requires approval)
87
+ # so the user sees what will actually run.
88
+ # ---------------------------------------------------------------------------
89
+ INDIRECT_EXEC_PATTERNS = [
90
+ re.compile(r"^bash\s+-c\s+", re.IGNORECASE),
91
+ re.compile(r"^sh\s+-c\s+", re.IGNORECASE),
92
+ re.compile(r"^zsh\s+-c\s+", re.IGNORECASE),
93
+ re.compile(r"^dash\s+-c\s+", re.IGNORECASE),
94
+ re.compile(r"^\s*eval\s+", re.IGNORECASE),
95
+ re.compile(r"^python3?\s+-c\s+", re.IGNORECASE),
96
+ re.compile(r"^node\s+-e\s+", re.IGNORECASE),
97
+ re.compile(r"^perl\s+-e\s+", re.IGNORECASE),
98
+ re.compile(r"^ruby\s+-e\s+", re.IGNORECASE),
99
+ # Process substitution and heredoc piped to shell
100
+ re.compile(r"^bash\s+<\(", re.IGNORECASE),
101
+ re.compile(r"^sh\s+<\(", re.IGNORECASE),
70
102
  ]
71
103
 
72
104
  class BashValidator:
@@ -76,6 +108,91 @@ class BashValidator:
76
108
  """Initialize validator."""
77
109
  self.shell_parser = get_shell_parser()
78
110
 
111
+ def _detect_indirect_execution(self, command: str) -> Optional[BashValidationResult]:
112
+ """Detect indirect execution wrappers that can bypass regex blocking.
113
+
114
+ Commands like 'bash -c "az group delete"' hide the real command inside
115
+ a string. We classify these as T2 (mutative) so they require user
116
+ approval via the nonce workflow, giving the human a chance to inspect
117
+ what will actually run.
118
+
119
+ Returns BashValidationResult if indirect execution detected, else None.
120
+ """
121
+ for pattern in INDIRECT_EXEC_PATTERNS:
122
+ if pattern.search(command):
123
+ # Also check if the inner payload contains a blocked command.
124
+ # Extract the string argument after the wrapper.
125
+ inner = self._extract_inner_command(command)
126
+ if inner:
127
+ blocked = is_blocked_command(inner)
128
+ if blocked.is_blocked:
129
+ return BashValidationResult(
130
+ allowed=False,
131
+ tier=SecurityTier.T3_BLOCKED,
132
+ reason=(
133
+ f"Indirect execution of blocked command detected: "
134
+ f"{blocked.category} (via wrapper)"
135
+ ),
136
+ suggestions=[
137
+ blocked.suggestion or "Run the command directly instead of via a shell wrapper.",
138
+ ],
139
+ )
140
+
141
+ # Not blocked but still indirect — route through approval
142
+ logger.info("Indirect execution detected: %s", command[:80])
143
+ result = detect_mutative_command(command)
144
+ if result.is_mutative:
145
+ return None # Already mutative, will be caught by mutative_verbs
146
+
147
+ # For interpreters with inline code analysis (python3 -c),
148
+ # mutative_verbs.py has dedicated pattern scanning that
149
+ # distinguishes safe code (json.dumps, sys.version) from
150
+ # dangerous code (os.system, subprocess.run). If it classified
151
+ # the inline code as safe, trust that analysis and allow it
152
+ # through without forcing an "ask" dialog.
153
+ from ..security.mutative_verbs import _INLINE_CODE_CLIS
154
+ base_cmd = command.strip().split()[0].rsplit("/", 1)[-1].lower()
155
+ if base_cmd in _INLINE_CODE_CLIS:
156
+ logger.info(
157
+ "Inline code classified as safe by pattern scanner: %s",
158
+ command[:80],
159
+ )
160
+ return None # Safe inline code, proceed to normal validation
161
+
162
+ # Shell wrappers (bash -c, eval, etc.) hide the real command
163
+ # in a string — no dedicated scanner exists. Force "ask" so
164
+ # the user can inspect what will actually run.
165
+ hook_block = build_hook_permission_response(
166
+ "ask",
167
+ (
168
+ "Indirect execution detected. The command uses a shell "
169
+ "wrapper (bash -c, eval, etc.) that can bypass "
170
+ "security checks. Please confirm you want to run this."
171
+ ),
172
+ )
173
+ return BashValidationResult(
174
+ allowed=False,
175
+ tier=SecurityTier.T2_DRY_RUN,
176
+ reason="Indirect execution wrapper detected — requires confirmation",
177
+ block_response=hook_block,
178
+ )
179
+ return None
180
+
181
+ def _extract_inner_command(self, command: str) -> Optional[str]:
182
+ """Extract the inner command from an indirect execution wrapper.
183
+
184
+ E.g., 'bash -c "az group delete --name foo"' → 'az group delete --name foo'
185
+ """
186
+ # Match: shell -c "..." or shell -c '...'
187
+ match = re.search(r"""-[ce]\s+(['"])(.*?)\1""", command, re.DOTALL)
188
+ if match:
189
+ return match.group(2).strip()
190
+ # Match: shell -c ... (unquoted, take rest of line)
191
+ match = re.search(r"-[ce]\s+(\S+.*)", command)
192
+ if match:
193
+ return match.group(1).strip()
194
+ return None
195
+
79
196
  def _has_operators(self, command: str) -> bool:
80
197
  """Quick check if command has operators (before parsing)."""
81
198
  # Fast check for common operators outside quotes
@@ -84,12 +201,19 @@ class BashValidator:
84
201
  return False
85
202
  return True
86
203
 
87
- def validate(self, command: str) -> BashValidationResult:
204
+ def validate(
205
+ self,
206
+ command: str,
207
+ is_subagent: bool = False,
208
+ session_id: str = "",
209
+ ) -> BashValidationResult:
88
210
  """
89
211
  Validate a Bash command.
90
212
 
91
213
  Args:
92
214
  command: Command string to validate
215
+ is_subagent: True when running in subagent context
216
+ session_id: Session ID for approval scoping
93
217
 
94
218
  Returns:
95
219
  BashValidationResult with validation details
@@ -103,6 +227,32 @@ class BashValidator:
103
227
 
104
228
  command = command.strip()
105
229
 
230
+ # ================================================================
231
+ # EARLY NORMALIZATION: Strip AI attribution footers before any
232
+ # other processing. This ensures the same normalized command
233
+ # string is used for blocked-command checks, compound parsing,
234
+ # mutative verb detection, pending approval writes, AND pending
235
+ # approval lookups. Without this, write_pending_approval() and
236
+ # find_pending_for_command() could see different strings on the
237
+ # first attempt vs. retry, causing nonce mismatch loops.
238
+ # ================================================================
239
+ command_was_modified = False
240
+ if self._detect_claude_footers(command):
241
+ command = self._strip_claude_footers(command)
242
+ command_was_modified = True
243
+ logger.info("Auto-stripped Claude Code footer from commit command")
244
+
245
+ # ================================================================
246
+ # PRIORITY 0: Indirect execution detection.
247
+ # Commands like "bash -c '...'" or "eval '...'" can hide blocked
248
+ # commands inside string arguments, bypassing regex patterns.
249
+ # Detected wrappers are routed to approval or blocked if the inner
250
+ # payload matches a blocked command.
251
+ # ================================================================
252
+ indirect_result = self._detect_indirect_execution(command)
253
+ if indirect_result is not None:
254
+ return indirect_result
255
+
106
256
  # ================================================================
107
257
  # PRIORITY 1: Blocked commands check on FULL command (exit 2).
108
258
  # This MUST run before any other validator to ensure permanently
@@ -120,6 +270,7 @@ class BashValidator:
120
270
  )
121
271
 
122
272
  # Parse compound commands once (reused for blocked-command check and validation dispatch).
273
+ # Runs AFTER footer stripping so components also use the normalized command.
123
274
  has_operators = self._has_operators(command)
124
275
  parsed_components = None
125
276
  if has_operators:
@@ -136,14 +287,6 @@ class BashValidator:
136
287
  suggestions=[comp_blocked.suggestion] if comp_blocked.suggestion else [],
137
288
  )
138
289
 
139
- # Auto-strip forbidden footers from git commits (instead of blocking).
140
- # Uses updatedInput to transparently clean the command before execution.
141
- command_was_modified = False
142
- if self._detect_claude_footers(command):
143
- command = self._strip_claude_footers(command)
144
- command_was_modified = True
145
- logger.info("Auto-stripped Claude Code footer from commit command")
146
-
147
290
  # Validate git commit messages (on the potentially cleaned command)
148
291
  if "git commit" in command and "-m" in command:
149
292
  commit_validation = self._validate_commit_message(command)
@@ -167,11 +310,17 @@ class BashValidator:
167
310
 
168
311
  # Dispatch to single or compound validation using already-parsed components
169
312
  if not has_operators:
170
- result = self._validate_single_command(command)
313
+ result = self._validate_single_command(
314
+ command, is_subagent=is_subagent, session_id=session_id,
315
+ )
171
316
  elif parsed_components is not None and len(parsed_components) > 1:
172
- result = self._validate_compound_command(parsed_components)
317
+ result = self._validate_compound_command(
318
+ parsed_components, is_subagent=is_subagent, session_id=session_id,
319
+ )
173
320
  else:
174
- result = self._validate_single_command(command)
321
+ result = self._validate_single_command(
322
+ command, is_subagent=is_subagent, session_id=session_id,
323
+ )
175
324
 
176
325
  # Attach cleaned command for hook to emit via updatedInput.
177
326
  # Set regardless of result.allowed so the ask path can include it too.
@@ -191,99 +340,156 @@ class BashValidator:
191
340
 
192
341
  return result
193
342
 
194
- def _validate_single_command(self, command: str) -> BashValidationResult:
343
+ def _validate_single_command(
344
+ self,
345
+ command: str,
346
+ is_subagent: bool = False,
347
+ session_id: str = "",
348
+ ) -> BashValidationResult:
195
349
  """Validate a single command (no operators).
196
350
 
197
351
  Simplified pipeline:
352
+ 0. Indirect execution detection (for compound command components)
198
353
  1. Mutative verb detection -> block with nonce or allow with grant
199
354
  2. GitOps policy validation (for kubectl/helm/flux)
200
355
  3. Everything else -> SAFE by elimination
201
356
 
357
+ Args:
358
+ command: The command to validate.
359
+ is_subagent: True when running in subagent context (generates
360
+ approval_id + deny). False for orchestrator (returns ask).
361
+ session_id: Session ID for pending approval scoping.
362
+
202
363
  Note: is_blocked_command() is NOT called here because validate()
203
364
  already checks the full command AND each compound component against
204
365
  the deny list before dispatching to this method.
205
366
  """
206
367
 
368
+ # Indirect execution check for compound command components.
369
+ # When validate() splits "cd /tmp && python3 -c '...'" into parts,
370
+ # the python3 -c component needs the same indirect execution gate
371
+ # that the full command gets in validate().
372
+ indirect_result = self._detect_indirect_execution(command)
373
+ if indirect_result is not None:
374
+ return indirect_result
375
+
207
376
  # Mutative verb detection
208
377
  result = detect_mutative_command(command)
209
378
  if result.is_mutative:
210
379
  # Check for an active approval grant before blocking.
211
- grant = check_approval_grant(command)
380
+ grant = check_approval_grant(command, session_id=session_id)
212
381
  if grant is not None:
213
- if not grant.confirmed:
214
- # First execution after nonce activation: return "ask"
215
- # to trigger Claude Code's native permission dialog
216
- # (double-barrier security).
217
- #
218
- # DO NOT call confirm_grant() here. The native dialog
219
- # has not been answered yet. If the user declines, the
220
- # grant must stay unconfirmed. Confirmation happens on
221
- # the NEXT hook invocation: if we see the same command
222
- # with an unconfirmed grant again, the native dialog
223
- # must have succeeded (the command executed and the
224
- # agent is retrying or a new matching command arrived).
382
+ if grant.confirmed:
383
+ # Already confirmed and consumed -- should not reach
384
+ # here (single-use). But if it does, allow through.
225
385
  logger.info(
226
- "T3 command requires native confirmation: %s (scope='%s')",
386
+ "T3 command allowed via confirmed grant: %s (scope='%s')",
227
387
  command[:80], grant.approved_scope,
228
388
  )
229
- hook_ask = build_hook_permission_response(
230
- "ask",
231
- "T3 operation approved. Confirm execution.",
389
+ return BashValidationResult(
390
+ allowed=True,
391
+ tier=SecurityTier.T3_BLOCKED,
392
+ reason="Grant confirmed",
393
+ )
394
+ else:
395
+ # Grant exists, not yet confirmed -- GAIA approved,
396
+ # let it through. PostToolUse will confirm and consume
397
+ # the grant after successful execution.
398
+ logger.info(
399
+ "T3 command passthrough via active grant: %s (scope='%s')",
400
+ command[:80], grant.approved_scope,
232
401
  )
233
402
  return BashValidationResult(
234
- allowed=False,
403
+ allowed=True,
235
404
  tier=SecurityTier.T3_BLOCKED,
236
- reason="T3 approved by user, confirming execution via native dialog",
237
- block_response=hook_ask,
405
+ reason="Grant active, pending confirmation",
238
406
  )
239
- logger.info(
240
- "T3 command allowed via approval grant: %s (scope='%s')",
241
- command[:80], grant.approved_scope,
242
- )
243
- return BashValidationResult(
244
- allowed=True,
245
- tier=SecurityTier.T3_BLOCKED,
246
- reason=f"Command allowed via approval grant (tier T3)",
247
- )
248
407
  else:
249
- # If a matching grant was found but expired, prepend a clear
250
- # expiry message so the agent knows the previous approval lapsed.
251
- expired_prefix = ""
252
- if last_check_found_expired():
253
- expired_prefix = (
254
- "[EXPIRED] Approval grant has expired (TTL exceeded). "
255
- "Retry the command to generate a fresh nonce and "
256
- "restart the approval workflow.\n\n"
408
+ if is_subagent:
409
+ # Subagent context: check for an existing pending
410
+ # approval first (retry scenario). If found, reuse
411
+ # the same nonce to prevent infinite approval_id
412
+ # generation loops while the user reviews.
413
+ existing_nonce = find_pending_for_command(
414
+ session_id or "", command,
257
415
  )
258
-
259
- # Generate a cryptographic nonce and write a pending approval.
260
- nonce = generate_nonce()
261
- pending_file = write_pending_approval(
262
- nonce=nonce,
263
- command=command,
264
- danger_verb=result.verb,
265
- danger_category=result.category,
266
- )
267
- if pending_file is None:
268
- hook_block = build_hook_permission_response(
269
- "deny",
270
- build_pending_approval_unavailable_message(),
416
+ if existing_nonce:
417
+ approval_id = existing_nonce
418
+ logger.info(
419
+ "Reusing pending approval_id=%s for retry: %s",
420
+ approval_id, command[:80],
421
+ )
422
+ reason = (
423
+ f"[T3_BLOCKED] This command requires user approval.\n"
424
+ f"Do NOT retry this command. Report REVIEW with this approval_id in your json:contract.\n"
425
+ f"Command: {command}\n"
426
+ f"Verb: '{result.verb}' ({result.category})\n"
427
+ f"approval_id: {approval_id}"
428
+ )
429
+ hook_deny = build_hook_permission_response("deny", reason)
430
+ return BashValidationResult(
431
+ allowed=False,
432
+ tier=SecurityTier.T3_BLOCKED,
433
+ reason=f"T3 {result.category.lower()} command: {result.reason}",
434
+ block_response=hook_deny,
435
+ )
436
+ # No existing pending -- generate a new nonce.
437
+ # The ElicitationResult hook will activate the
438
+ # grant when the user approves via AskUserQuestion.
439
+ approval_id = generate_nonce()
440
+ pending_path = write_pending_approval(
441
+ nonce=approval_id,
442
+ command=command,
443
+ danger_verb=result.verb,
444
+ danger_category=result.category,
445
+ session_id=session_id or None,
446
+ )
447
+ if pending_path is None:
448
+ # Persistence failure — fall back to ask
449
+ logger.warning(
450
+ "Failed to persist pending approval for subagent; "
451
+ "falling back to ask: %s",
452
+ command[:80],
453
+ )
454
+ reason = build_pending_approval_unavailable_message()
455
+ hook_ask = build_hook_permission_response("ask", reason)
456
+ return BashValidationResult(
457
+ allowed=False,
458
+ tier=SecurityTier.T3_BLOCKED,
459
+ reason="Pending approval persistence failed",
460
+ block_response=hook_ask,
461
+ )
462
+ reason = (
463
+ f"[T3_BLOCKED] This command requires user approval.\n"
464
+ f"Do NOT retry this command. Report REVIEW with this approval_id in your json:contract.\n"
465
+ f"Command: {command}\n"
466
+ f"Verb: '{result.verb}' ({result.category})\n"
467
+ f"approval_id: {approval_id}"
271
468
  )
469
+ hook_deny = build_hook_permission_response("deny", reason)
272
470
  return BashValidationResult(
273
471
  allowed=False,
274
472
  tier=SecurityTier.T3_BLOCKED,
275
- reason="Failed to persist pending approval for T3 command",
276
- block_response=hook_block,
473
+ reason=f"T3 {result.category.lower()} command: {result.reason}",
474
+ block_response=hook_deny,
475
+ )
476
+ else:
477
+ # Orchestrator context: route through native 'ask' dialog.
478
+ # The user sees the native permission prompt and approves
479
+ # directly. No approval_id is generated.
480
+ reason = (
481
+ f"[T3_APPROVAL_REQUIRED] {result.category} operation detected.\n"
482
+ f"Command: {command}\n"
483
+ f"Verb: '{result.verb}' ({result.category})\n"
484
+ f"Reason: {result.reason}"
485
+ )
486
+ hook_ask = build_hook_permission_response("ask", reason)
487
+ return BashValidationResult(
488
+ allowed=False,
489
+ tier=SecurityTier.T3_BLOCKED,
490
+ reason=f"Dangerous {result.category.lower()} command: {result.reason}",
491
+ block_response=hook_ask,
277
492
  )
278
-
279
- t3_block = build_t3_block_response(command, result, nonce=nonce)
280
- hook_block = build_hook_permission_response("deny", expired_prefix + t3_block["message"])
281
- return BashValidationResult(
282
- allowed=False,
283
- tier=SecurityTier.T3_BLOCKED,
284
- reason=f"Dangerous {result.category.lower()} command: {result.reason}",
285
- block_response=hook_block,
286
- )
287
493
 
288
494
  # Check GitOps policy for kubectl/helm/flux commands
289
495
  if any(keyword in command for keyword in ("kubectl", "helm", "flux")):
@@ -303,13 +509,20 @@ class BashValidator:
303
509
  reason="Safe by elimination (not blocked, not mutative)",
304
510
  )
305
511
 
306
- def _validate_compound_command(self, components: List[str]) -> BashValidationResult:
512
+ def _validate_compound_command(
513
+ self,
514
+ components: List[str],
515
+ is_subagent: bool = False,
516
+ session_id: str = "",
517
+ ) -> BashValidationResult:
307
518
  """Validate a compound command (multiple components)."""
308
519
  logger.info(f"Compound command detected with {len(components)} components")
309
520
 
310
521
  component_results: List[BashValidationResult] = []
311
522
  for i, component in enumerate(components, 1):
312
- result = self._validate_single_command(component)
523
+ result = self._validate_single_command(
524
+ component, is_subagent=is_subagent, session_id=session_id,
525
+ )
313
526
 
314
527
  if not result.allowed:
315
528
  return BashValidationResult(
@@ -352,6 +565,8 @@ class BashValidator:
352
565
 
353
566
  Removes full lines matching forbidden footer patterns.
354
567
  Works on raw command string regardless of quoting/HEREDOC format.
568
+ Preserves trailing quote/paren characters that close the commit
569
+ message (e.g., the closing " in -m "...footer").
355
570
 
356
571
  Args:
357
572
  command: Raw command string
@@ -359,11 +574,15 @@ class BashValidator:
359
574
  Returns:
360
575
  Command with footer lines removed
361
576
  """
362
- # Remove full lines that contain forbidden patterns
577
+ # Remove full lines that contain AI attribution patterns.
578
+ # Each pattern matches the newline + footer content, then uses a
579
+ # lookahead to stop before any trailing quote/paren/bracket
580
+ # sequence that closes the command structure. The captured group
581
+ # is replaced with empty string, leaving the closing chars intact.
363
582
  footer_line_patterns = [
364
- r'\n\s*Co-Authored-By:\s+Claude[^\n]*',
365
- r'\n\s*Generated with\s+\[?Claude Code\]?[^\n]*',
366
- r'\n\s*🤖\s*Generated with[^\n]*',
583
+ r'\n\s*Co-[Aa]uthored-[Bb]y:\s+(?:Claude|GitHub Copilot|aider|Windsurf|Cursor|Codex|Gemini)[^\n]*?(?=["\')\]]*(?:\n|$))',
584
+ r'\n\s*Generated with\s+\[?Claude Code\]?[^\n]*?(?=["\')\]]*(?:\n|$))',
585
+ r'\n\s*🤖\s*Generated with[^\n]*?(?=["\')\]]*(?:\n|$))',
367
586
  ]
368
587
  for pattern in footer_line_patterns:
369
588
  command = re.sub(pattern, '', command, flags=re.IGNORECASE)
@@ -469,24 +688,21 @@ class BashValidator:
469
688
 
470
689
  return None
471
690
 
472
- def validate_bash_command(command: str) -> BashValidationResult:
691
+ def validate_bash_command(
692
+ command: str,
693
+ is_subagent: bool = False,
694
+ session_id: str = "",
695
+ ) -> BashValidationResult:
473
696
  """
474
697
  Validate a Bash command (convenience function).
475
698
 
476
699
  Args:
477
700
  command: Command to validate
701
+ is_subagent: True when running in subagent context
702
+ session_id: Session ID for approval scoping
478
703
 
479
704
  Returns:
480
705
  BashValidationResult
481
706
  """
482
707
  validator = BashValidator()
483
- return validator.validate(command)
484
-
485
-
486
- def create_permission_allow_response(reason: str) -> str:
487
- """
488
- Create JSON response to auto-approve a command.
489
-
490
- This response tells Claude Code to skip the permission check.
491
- """
492
- return json.dumps(build_hook_permission_response("allow", reason))
708
+ return validator.validate(command, is_subagent=is_subagent, session_id=session_id)
@@ -15,7 +15,6 @@ Features:
15
15
  Dependencies: Python stdlib only (shlex, re)
16
16
  """
17
17
 
18
- import re
19
18
  from typing import List, Optional
20
19
  from dataclasses import dataclass
21
20
 
@@ -4,7 +4,7 @@ Task tool validator.
4
4
  Validates Task tool invocations:
5
5
  - Agent existence verification
6
6
  - Context provisioning enforcement
7
- - T3 operation detection for Bash-time nonce approval
7
+ - T3 operation detection for user approval workflow
8
8
  """
9
9
 
10
10
  import logging
@@ -18,19 +18,10 @@ from ..security.mutative_verbs import (
18
18
  MutativeResult,
19
19
  CLI_FAMILY_LOOKUP,
20
20
  COMMAND_ALIASES,
21
- CATEGORY_MUTATIVE,
22
21
  )
23
22
 
24
23
  logger = logging.getLogger(__name__)
25
24
 
26
- # ============================================================================
27
- # ROUTER INTEGRATION - REMOVED
28
- # ============================================================================
29
- # Router suggestion removed for simplicity. Orchestrator should choose correct
30
- # agent based on improved descriptions in CLAUDE.md. If wrong agent selected,
31
- # error message shows available agents and orchestrator self-corrects.
32
-
33
-
34
25
  # Available agents for Task invocation — both bare and plugin-namespaced forms
35
26
  _BASE_AGENTS = [
36
27
  "terraform-architect",
@@ -217,8 +208,8 @@ class TaskValidator:
217
208
  prompt = parameters.get("prompt", "")
218
209
  description = parameters.get("description", "")
219
210
 
220
- # Phase 2: additionalContext means prompt is never mutated, so T3 detection
221
- # runs directly against the original user prompt -- no workaround needed.
211
+ # additionalContext means prompt is never mutated, so T3 detection
212
+ # runs directly against the original user prompt.
222
213
  user_task_for_t3_check = prompt
223
214
 
224
215
  logger.info(f"Task tool validation for agent: {agent_name}")
@@ -229,7 +220,7 @@ class TaskValidator:
229
220
  error_msg += f"Available agents:\n"
230
221
  for agent in sorted(self.available_agents):
231
222
  error_msg += f" - {agent}\n"
232
- error_msg += "\nRefer to agent descriptions in the project-dispatch skill.\n"
223
+ error_msg += "\nRefer to the Surface Routing Recommendation for agent selection.\n"
233
224
  error_msg += f"\nCorrect usage: Task(subagent_type=\"<agent-name>\", ...)"
234
225
 
235
226
  return TaskValidationResult(
@@ -239,14 +230,11 @@ class TaskValidator:
239
230
  agent_name=agent_name,
240
231
  )
241
232
 
242
- # Check context provisioning (for project agents)
243
- has_context = self._check_context_provisioning(prompt, agent_name)
244
-
245
- if not has_context and agent_name not in META_AGENTS:
246
- logger.warning(
247
- f"Task invocation for {agent_name} without apparent context provisioning. "
248
- f"Orchestrator should call context_provider.py first (Phase 2)."
249
- )
233
+ # Context is injected via additionalContext by the adapter, not by
234
+ # mutating the prompt. The validator cannot check additionalContext
235
+ # (it only sees parameters), so we determine context status by agent type.
236
+ # Meta-agents never receive context by design.
237
+ has_context = agent_name not in META_AGENTS
250
238
 
251
239
  # Check for T3 operations (use original user task to avoid false positives from context)
252
240
  is_t3 = self._is_t3_operation(user_task_for_t3_check, description)
@@ -273,14 +261,6 @@ class TaskValidator:
273
261
  is_t3_operation=is_t3,
274
262
  )
275
263
 
276
- def _check_context_provisioning(self, prompt: str, agent_name: str) -> bool:
277
- """Check if context was properly provisioned."""
278
- return (
279
- "# Project Context" in prompt or
280
- "contract" in prompt.lower() or
281
- "context_provider.py" in prompt.lower()
282
- )
283
-
284
264
  def _is_t3_operation(self, prompt: str, description: str) -> bool:
285
265
  """Check if this is a T3 (destructive) operation using the verb detector."""
286
266
  combined = f"{description} {prompt}"