@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,458 @@
1
+ """
2
+ Transcript analysis and compliance scoring for Claude Code agent transcripts.
3
+
4
+ Provides:
5
+ - ToolCall, DuplicateCall, TranscriptAnalysis: Data structures for analysis results
6
+ - analyze(): Single-pass JSONL transcript parser
7
+ - ComplianceScore: Compliance scoring data structure
8
+ - compute_compliance_score(): Score agent behavior against compliance factors
9
+ """
10
+
11
+ import hashlib
12
+ import json
13
+ import logging
14
+ import re
15
+ from dataclasses import dataclass, field
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ # ============================================================================
24
+ # T004 — Dataclasses
25
+ # ============================================================================
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class ToolCall:
30
+ """A single tool invocation extracted from the transcript."""
31
+
32
+ index: int # 1-based position in the tool sequence
33
+ tool_name: str
34
+ arguments: Dict[str, Any]
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class DuplicateCall:
39
+ """A group of identical tool calls detected via argument hashing."""
40
+
41
+ tool_name: str
42
+ arguments_hash: str
43
+ indices: List[int]
44
+
45
+
46
+ @dataclass
47
+ class TranscriptAnalysis:
48
+ """Aggregated metrics from a single-pass JSONL transcript parse."""
49
+
50
+ input_tokens: int = 0
51
+ cache_creation_tokens: int = 0
52
+ cache_read_tokens: int = 0
53
+ output_tokens: int = 0
54
+ duration_ms: Optional[int] = None
55
+ first_timestamp: Optional[str] = None
56
+ last_timestamp: Optional[str] = None
57
+ model: str = ""
58
+ stop_reasons: List[str] = field(default_factory=list)
59
+ api_call_count: int = 0
60
+ tool_sequence: List[ToolCall] = field(default_factory=list)
61
+ tool_call_count: int = 0
62
+ bash_commands: List[str] = field(default_factory=list)
63
+ skills_injected: List[str] = field(default_factory=list)
64
+ duplicate_tool_calls: List[DuplicateCall] = field(default_factory=list)
65
+ pipe_commands: List[str] = field(default_factory=list)
66
+ first_tool_name: Optional[str] = None
67
+
68
+
69
+ # ============================================================================
70
+ # T005 — analyze() function
71
+ # ============================================================================
72
+
73
+ # Matches <command-name>...</command-name> tags in user messages
74
+ _COMMAND_NAME_RE = re.compile(r"<command-name>\s*(.+?)\s*</command-name>")
75
+
76
+
77
+ def _has_unquoted_pipe(command: str) -> bool:
78
+ """Detect unquoted pipe characters in a bash command string.
79
+
80
+ Uses a character-walk approach to track quote state, which is more
81
+ reliable than regex for nested quotes.
82
+ """
83
+ in_single = False
84
+ in_double = False
85
+ escaped = False
86
+
87
+ for ch in command:
88
+ if escaped:
89
+ escaped = False
90
+ continue
91
+ if ch == "\\":
92
+ escaped = True
93
+ continue
94
+ if ch == "'" and not in_double:
95
+ in_single = not in_single
96
+ continue
97
+ if ch == '"' and not in_single:
98
+ in_double = not in_double
99
+ continue
100
+ if ch == "|" and not in_single and not in_double:
101
+ return True
102
+
103
+ return False
104
+
105
+
106
+ def _hash_tool_call(tool_name: str, arguments: Dict[str, Any]) -> str:
107
+ """Produce a stable hash for a (tool_name, arguments) pair."""
108
+ canonical = json.dumps(
109
+ {"tool_name": tool_name, "arguments": arguments},
110
+ sort_keys=True,
111
+ separators=(",", ":"),
112
+ )
113
+ return hashlib.sha256(canonical.encode()).hexdigest()[:16]
114
+
115
+
116
+ def _parse_timestamp(ts_str: str) -> Optional[datetime]:
117
+ """Best-effort ISO timestamp parse."""
118
+ for fmt in (
119
+ "%Y-%m-%dT%H:%M:%S.%fZ",
120
+ "%Y-%m-%dT%H:%M:%SZ",
121
+ "%Y-%m-%dT%H:%M:%S.%f%z",
122
+ "%Y-%m-%dT%H:%M:%S%z",
123
+ "%Y-%m-%dT%H:%M:%S.%f",
124
+ "%Y-%m-%dT%H:%M:%S",
125
+ ):
126
+ try:
127
+ return datetime.strptime(ts_str, fmt)
128
+ except ValueError:
129
+ continue
130
+ return None
131
+
132
+
133
+ def _extract_tool_calls_from_content(
134
+ content: Any,
135
+ result: TranscriptAnalysis,
136
+ tool_index_counter: List[int],
137
+ hash_map: Dict[str, Dict[str, Any]],
138
+ ) -> None:
139
+ """Extract tool_use blocks from a content list and update result in place."""
140
+ if not isinstance(content, list):
141
+ return
142
+
143
+ for block in content:
144
+ if not isinstance(block, dict):
145
+ continue
146
+ if block.get("type") != "tool_use":
147
+ continue
148
+
149
+ tool_name = block.get("name", "")
150
+ arguments = block.get("input", {})
151
+ if not isinstance(arguments, dict):
152
+ arguments = {}
153
+
154
+ idx = tool_index_counter[0]
155
+ tool_index_counter[0] += 1
156
+
157
+ tc = ToolCall(index=idx, tool_name=tool_name, arguments=arguments)
158
+ result.tool_sequence.append(tc)
159
+ result.tool_call_count += 1
160
+
161
+ if result.first_tool_name is None:
162
+ result.first_tool_name = tool_name
163
+
164
+ # Bash command extraction
165
+ if tool_name in ("Bash", "bash"):
166
+ cmd = arguments.get("command", "")
167
+ if isinstance(cmd, str) and cmd:
168
+ result.bash_commands.append(cmd)
169
+ if _has_unquoted_pipe(cmd):
170
+ result.pipe_commands.append(cmd)
171
+
172
+ # Duplicate detection
173
+ h = _hash_tool_call(tool_name, arguments)
174
+ hash_map.setdefault(h, {"tool_name": tool_name, "indices": []})
175
+ hash_map[h]["indices"].append(idx)
176
+
177
+
178
+ def _extract_skills_from_content(content: Any, result: TranscriptAnalysis) -> None:
179
+ """Search content for <command-name> tags and populate skills_injected."""
180
+ if isinstance(content, str):
181
+ for m in _COMMAND_NAME_RE.finditer(content):
182
+ skill = m.group(1).strip()
183
+ if skill and skill not in result.skills_injected:
184
+ result.skills_injected.append(skill)
185
+ elif isinstance(content, list):
186
+ for block in content:
187
+ if isinstance(block, str):
188
+ for m in _COMMAND_NAME_RE.finditer(block):
189
+ skill = m.group(1).strip()
190
+ if skill and skill not in result.skills_injected:
191
+ result.skills_injected.append(skill)
192
+ elif isinstance(block, dict) and block.get("type") == "text":
193
+ text = block.get("text", "")
194
+ for m in _COMMAND_NAME_RE.finditer(text):
195
+ skill = m.group(1).strip()
196
+ if skill and skill not in result.skills_injected:
197
+ result.skills_injected.append(skill)
198
+
199
+
200
+ def analyze(transcript_path: str) -> TranscriptAnalysis:
201
+ """Single-pass JSONL parser for Claude Code agent transcripts.
202
+
203
+ Reads the transcript file line by line and accumulates:
204
+ - Token usage (input, cache_creation, cache_read, output)
205
+ - Model name (from first assistant turn)
206
+ - Stop reasons
207
+ - API call count (assistant turns)
208
+ - Tool sequence with ToolCall entries
209
+ - Bash commands and pipe violations
210
+ - Skills injected (from <command-name> tags in user messages)
211
+ - Timestamps and duration
212
+ - Duplicate tool call detection
213
+
214
+ Args:
215
+ transcript_path: Path to the JSONL transcript file.
216
+
217
+ Returns:
218
+ TranscriptAnalysis with all accumulated metrics.
219
+ Returns default TranscriptAnalysis() for empty or missing files.
220
+ """
221
+ result = TranscriptAnalysis()
222
+
223
+ if not transcript_path:
224
+ return result
225
+
226
+ path = Path(transcript_path).expanduser()
227
+ if not path.exists():
228
+ logger.debug("Transcript file not found: %s", path)
229
+ return result
230
+
231
+ try:
232
+ text = path.read_text()
233
+ except Exception as e:
234
+ logger.debug("Failed to read transcript: %s", e)
235
+ return result
236
+
237
+ lines = text.strip().splitlines()
238
+ if not lines:
239
+ return result
240
+
241
+ # Mutable counter for tool indexing (1-based)
242
+ tool_index_counter = [1]
243
+ # Hash map for duplicate detection: hash -> {tool_name, indices}
244
+ hash_map: Dict[str, Dict[str, Any]] = {}
245
+
246
+ first_ts_dt: Optional[datetime] = None
247
+ last_ts_dt: Optional[datetime] = None
248
+
249
+ for line in lines:
250
+ line = line.strip()
251
+ if not line:
252
+ continue
253
+
254
+ try:
255
+ entry = json.loads(line)
256
+ except (json.JSONDecodeError, TypeError):
257
+ logger.debug("Skipping malformed JSON line: %.80s", line)
258
+ continue
259
+
260
+ if not isinstance(entry, dict):
261
+ continue
262
+
263
+ # --- Timestamp tracking ---
264
+ timestamp = entry.get("timestamp", "")
265
+ if isinstance(timestamp, str) and timestamp:
266
+ parsed_ts = _parse_timestamp(timestamp)
267
+ if parsed_ts is not None:
268
+ if result.first_timestamp is None:
269
+ result.first_timestamp = timestamp
270
+ first_ts_dt = parsed_ts
271
+ result.last_timestamp = timestamp
272
+ last_ts_dt = parsed_ts
273
+
274
+ msg = entry.get("message", entry)
275
+ if not isinstance(msg, dict):
276
+ continue
277
+
278
+ role = msg.get("role", "")
279
+ content = msg.get("content", "")
280
+
281
+ # --- Assistant turns ---
282
+ if role == "assistant":
283
+ # Usage accumulation — check both top-level and nested in message
284
+ # Claude Code transcripts store usage/model/stop_reason inside
285
+ # message object, but some formats keep them at entry level.
286
+ usage = entry.get("usage") or msg.get("usage") or {}
287
+ if isinstance(usage, dict):
288
+ result.input_tokens += int(usage.get("input_tokens", 0))
289
+ result.cache_creation_tokens += int(
290
+ usage.get("cache_creation_input_tokens", 0)
291
+ )
292
+ result.cache_read_tokens += int(
293
+ usage.get("cache_read_input_tokens", 0)
294
+ )
295
+ result.output_tokens += int(usage.get("output_tokens", 0))
296
+
297
+ # Model from first assistant turn — check both locations
298
+ model = entry.get("model") or msg.get("model") or ""
299
+ if isinstance(model, str) and model and not result.model:
300
+ result.model = model
301
+
302
+ # Stop reason — check both locations
303
+ stop_reason = entry.get("stop_reason") or msg.get("stop_reason") or ""
304
+ if isinstance(stop_reason, str) and stop_reason:
305
+ result.stop_reasons.append(stop_reason)
306
+
307
+ # API call count
308
+ result.api_call_count += 1
309
+
310
+ # --- Tool calls from all content lists ---
311
+ _extract_tool_calls_from_content(
312
+ content, result, tool_index_counter, hash_map
313
+ )
314
+
315
+ # --- User messages: skill injection detection ---
316
+ if role == "user":
317
+ _extract_skills_from_content(content, result)
318
+
319
+ # --- Duration computation ---
320
+ if first_ts_dt is not None and last_ts_dt is not None:
321
+ delta = last_ts_dt - first_ts_dt
322
+ result.duration_ms = int(delta.total_seconds() * 1000)
323
+
324
+ # --- Duplicate detection finalization ---
325
+ for h, info in hash_map.items():
326
+ if len(info["indices"]) > 1:
327
+ result.duplicate_tool_calls.append(
328
+ DuplicateCall(
329
+ tool_name=info["tool_name"],
330
+ arguments_hash=h,
331
+ indices=info["indices"],
332
+ )
333
+ )
334
+
335
+ return result
336
+
337
+
338
+ # ============================================================================
339
+ # T006 — ComplianceScore
340
+ # ============================================================================
341
+
342
+
343
+ @dataclass(frozen=True)
344
+ class ComplianceScore:
345
+ """Compliance score computed from transcript analysis and external signals."""
346
+
347
+ total: int
348
+ grade: str
349
+ factors: Dict[str, int]
350
+ deductions: List[str]
351
+
352
+
353
+ # Tools considered disciplined as first-tool choices
354
+ _DISCIPLINED_FIRST_TOOLS = {"Read", "Glob", "Grep"}
355
+
356
+
357
+ def _grade_from_total(total: int) -> str:
358
+ """Map a numeric score to a letter grade."""
359
+ if total >= 90:
360
+ return "A"
361
+ if total >= 75:
362
+ return "B"
363
+ if total >= 50:
364
+ return "C"
365
+ return "F"
366
+
367
+
368
+ def compute_compliance_score(
369
+ analysis: TranscriptAnalysis,
370
+ contract_valid: bool,
371
+ has_scope_escalation: bool,
372
+ anchor_hit_rate: float,
373
+ ) -> ComplianceScore:
374
+ """Compute a compliance score from transcript analysis and external signals.
375
+
376
+ Factors (100 points total):
377
+ - contract_valid: 25 pts (binary)
378
+ - investigation_discipline: 20 pts (first tool is Read/Glob/Grep/None)
379
+ - context_utilization: 15 pts (proportional to anchor_hit_rate)
380
+ - no_pipe_violations: 15 pts (minus 3 per pipe command, floor 0)
381
+ - no_duplicate_calls: 10 pts (minus 2 per duplicate group, floor 0)
382
+ - no_scope_escalation: 15 pts (binary)
383
+
384
+ Args:
385
+ analysis: TranscriptAnalysis from analyze().
386
+ contract_valid: Whether the agent's response contract passed validation.
387
+ has_scope_escalation: Whether the agent escalated beyond its scope.
388
+ anchor_hit_rate: Float 0.0-1.0 representing how many context anchors
389
+ the agent referenced in its evidence.
390
+
391
+ Returns:
392
+ ComplianceScore with total, grade, factors breakdown, and deductions.
393
+ """
394
+ factors: Dict[str, int] = {}
395
+ deductions: List[str] = []
396
+
397
+ # 1. contract_valid (25 pts, binary)
398
+ if contract_valid:
399
+ factors["contract_valid"] = 25
400
+ else:
401
+ factors["contract_valid"] = 0
402
+ deductions.append("contract_valid: invalid contract (-25)")
403
+
404
+ # 2. investigation_discipline (20 pts)
405
+ first_tool = analysis.first_tool_name
406
+ if first_tool is None or first_tool in _DISCIPLINED_FIRST_TOOLS:
407
+ factors["investigation_discipline"] = 20
408
+ else:
409
+ factors["investigation_discipline"] = 0
410
+ deductions.append(
411
+ f"investigation_discipline: first tool was {first_tool}, "
412
+ f"expected Read/Glob/Grep/None (-20)"
413
+ )
414
+
415
+ # 3. context_utilization (15 pts, proportional)
416
+ clamped_rate = max(0.0, min(1.0, anchor_hit_rate))
417
+ ctx_points = round(15 * clamped_rate)
418
+ factors["context_utilization"] = ctx_points
419
+ if ctx_points < 15:
420
+ deductions.append(
421
+ f"context_utilization: anchor_hit_rate={clamped_rate:.2f} "
422
+ f"(-{15 - ctx_points})"
423
+ )
424
+
425
+ # 4. no_pipe_violations (15 pts, -3 per pipe, floor 0)
426
+ pipe_count = len(analysis.pipe_commands)
427
+ pipe_points = max(0, 15 - 3 * pipe_count)
428
+ factors["no_pipe_violations"] = pipe_points
429
+ if pipe_count > 0:
430
+ deductions.append(
431
+ f"no_pipe_violations: {pipe_count} pipe command(s) (-{15 - pipe_points})"
432
+ )
433
+
434
+ # 5. no_duplicate_calls (10 pts, -2 per duplicate group, floor 0)
435
+ dup_count = len(analysis.duplicate_tool_calls)
436
+ dup_points = max(0, 10 - 2 * dup_count)
437
+ factors["no_duplicate_calls"] = dup_points
438
+ if dup_count > 0:
439
+ deductions.append(
440
+ f"no_duplicate_calls: {dup_count} duplicate group(s) (-{10 - dup_points})"
441
+ )
442
+
443
+ # 6. no_scope_escalation (15 pts, binary)
444
+ if not has_scope_escalation:
445
+ factors["no_scope_escalation"] = 15
446
+ else:
447
+ factors["no_scope_escalation"] = 0
448
+ deductions.append("no_scope_escalation: scope escalation detected (-15)")
449
+
450
+ total = sum(factors.values())
451
+ grade = _grade_from_total(total)
452
+
453
+ return ComplianceScore(
454
+ total=total,
455
+ grade=grade,
456
+ factors=factors,
457
+ deductions=deductions,
458
+ )
@@ -0,0 +1,152 @@
1
+ """
2
+ Transcript reading and parsing for Claude Code agent transcripts.
3
+
4
+ Provides:
5
+ - read_transcript(): Read assistant messages from transcript JSONL
6
+ - read_first_user_content_from_transcript(): Read first user message content
7
+ - extract_task_description_from_transcript(): Extract task description
8
+ - extract_injected_context_payload_from_transcript(): Extract auto-injected JSON
9
+ """
10
+
11
+ import json
12
+ import logging
13
+ from pathlib import Path
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def read_transcript(transcript_path: str) -> str:
20
+ """Read agent transcript from file path provided by Claude Code.
21
+
22
+ Claude Code provides ``agent_transcript_path`` pointing to a JSONL file.
23
+ Each line has the structure:
24
+ {"type": "assistant", "message": {"role": "assistant", "content": [...]}, ...}
25
+ The role/content are nested inside the ``message`` field.
26
+
27
+ Falls back to empty string on any error so the hook never crashes.
28
+ """
29
+ try:
30
+ # Expand ~ to home directory (Claude Code may use ~ in paths)
31
+ path = Path(transcript_path).expanduser()
32
+ logger.debug("Reading transcript from: %s", path)
33
+
34
+ if not path.exists():
35
+ logger.warning("Transcript file not found: %s", path)
36
+ return ""
37
+
38
+ lines = path.read_text().strip().splitlines()
39
+
40
+ text_parts: List[str] = []
41
+ for line in lines:
42
+ if not line.strip():
43
+ continue
44
+ try:
45
+ entry = json.loads(line)
46
+
47
+ # Claude Code transcript format: content is inside entry["message"]
48
+ msg = entry.get("message", entry) # fallback to entry itself for simple format
49
+ role = msg.get("role", "")
50
+ if role != "assistant":
51
+ continue
52
+
53
+ content = msg.get("content", "")
54
+ if isinstance(content, str):
55
+ text_parts.append(content)
56
+ elif isinstance(content, list):
57
+ for block in content:
58
+ if isinstance(block, dict) and block.get("type") == "text":
59
+ text_parts.append(block.get("text", ""))
60
+ elif isinstance(block, str):
61
+ text_parts.append(block)
62
+ except (json.JSONDecodeError, TypeError):
63
+ continue
64
+
65
+ result = "\n".join(text_parts)
66
+ logger.debug("Extracted %d text parts, total length: %d chars", len(text_parts), len(result))
67
+ return result
68
+
69
+ except Exception as e:
70
+ logger.debug("Failed to read transcript from %s: %s", transcript_path, e)
71
+ return ""
72
+
73
+
74
+ def read_first_user_content_from_transcript(transcript_path: str) -> Optional[str]:
75
+ """Read the raw content string of the first user message from a transcript JSONL.
76
+
77
+ Handles: empty path guard, path expansion, existence check, JSONL iteration,
78
+ JSON parse, role=="user" check, content normalization (str vs list).
79
+ Returns the raw content string or None.
80
+ """
81
+ if not transcript_path:
82
+ return None
83
+ try:
84
+ path = Path(transcript_path).expanduser()
85
+ if not path.exists():
86
+ return None
87
+ with open(path, "r") as f:
88
+ for line in f:
89
+ line = line.strip()
90
+ if not line:
91
+ continue
92
+ try:
93
+ entry = json.loads(line)
94
+ msg = entry.get("message", entry)
95
+ if msg.get("role") != "user":
96
+ continue
97
+ content = msg.get("content", "")
98
+ if isinstance(content, str):
99
+ return content
100
+ elif isinstance(content, list):
101
+ return " ".join(
102
+ b.get("text", "") for b in content
103
+ if isinstance(b, dict) and b.get("type") == "text"
104
+ )
105
+ return None
106
+ except (json.JSONDecodeError, TypeError):
107
+ continue
108
+ except Exception as e:
109
+ logger.debug("Failed to read first user content from transcript: %s", e)
110
+ return None
111
+
112
+
113
+ def extract_task_description_from_transcript(transcript_path: str) -> str:
114
+ """Read the first user message from the subagent transcript JSONL.
115
+
116
+ Claude Code's agent_transcript_path contains the full subagent conversation.
117
+ The first ``role: "user"`` entry is the task prompt sent by the orchestrator --
118
+ which is the most meaningful description of what the agent was asked to do.
119
+
120
+ Context is delivered via additionalContext (not prompt mutation), so the
121
+ first user message IS the original prompt without any wrapping.
122
+
123
+ Returns empty string on any error so the hook never crashes.
124
+ """
125
+ content = read_first_user_content_from_transcript(transcript_path)
126
+ if not content:
127
+ return ""
128
+
129
+ return content.strip()[:500]
130
+
131
+
132
+ def extract_injected_context_payload_from_transcript(
133
+ transcript_path: str,
134
+ ) -> Dict[str, Any]:
135
+ """Extract the auto-injected context payload from disk cache.
136
+
137
+ Context is delivered via additionalContext and the payload is persisted to
138
+ disk by context_injector. Prompts do not contain embedded payloads.
139
+ """
140
+ import os
141
+
142
+ try:
143
+ payload_dir = Path(os.environ.get("TMPDIR", "/tmp")) / "gaia-context-payloads"
144
+ if payload_dir.exists():
145
+ agent_file = Path(transcript_path).stem # e.g. "agent-ae190a4da68d626d4"
146
+ # Match by agent ID substring
147
+ for candidate in payload_dir.glob("*.json"):
148
+ if candidate.stem in agent_file or agent_file in candidate.stem:
149
+ return json.loads(candidate.read_text())
150
+ except Exception:
151
+ pass
152
+ return {}
@@ -0,0 +1,28 @@
1
+ """
2
+ Audit module - Logging, metrics aggregation, and event detection.
3
+
4
+ Provides:
5
+ - logger: AuditLogger for tool executions (write path)
6
+ - metrics: generate_summary reads audit logs and aggregates (read path)
7
+ - event_detector: CriticalEventDetector
8
+ """
9
+
10
+ from .logger import AuditLogger, log_execution
11
+ from .metrics import generate_summary
12
+ from .event_detector import (
13
+ CriticalEventDetector,
14
+ detect_critical_event,
15
+ EventType,
16
+ )
17
+
18
+ __all__ = [
19
+ # Logger
20
+ "AuditLogger",
21
+ "log_execution",
22
+ # Metrics
23
+ "generate_summary",
24
+ # Event detector
25
+ "CriticalEventDetector",
26
+ "detect_critical_event",
27
+ "EventType",
28
+ ]