@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,576 @@
1
+ """
2
+ Workflow auditing: anomaly detection and Gaia analysis signaling.
3
+
4
+ Renamed from anomaly_detector.py and expanded with additional anomaly types.
5
+
6
+ Provides:
7
+ - audit(): Full anomaly detection suite -> list of anomaly dicts
8
+ - signal_gaia_analysis(): Create flag file for Gaia analysis
9
+ """
10
+
11
+ import json
12
+ import logging
13
+ import re
14
+ from collections import deque
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ from ..agents.transcript_analyzer import TranscriptAnalysis
20
+ from .workflow_recorder import get_workflow_memory_dir
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Transcript-analysis check helpers (T009)
27
+ # ---------------------------------------------------------------------------
28
+
29
+
30
+ def _check_investigation_skip(
31
+ analysis: TranscriptAnalysis,
32
+ ) -> Optional[Dict[str, str]]:
33
+ """Warning if the agent's first tool call was Bash (skipped investigation)."""
34
+ if analysis.first_tool_name == "Bash":
35
+ return {
36
+ "type": "investigation_skip",
37
+ "severity": "warning",
38
+ "message": (
39
+ "Agent's first tool call was Bash instead of a "
40
+ "read-only investigation tool (Read/Glob/Grep)"
41
+ ),
42
+ }
43
+ return None
44
+
45
+
46
+ def _check_context_ignored(
47
+ analysis: TranscriptAnalysis,
48
+ ) -> Optional[Dict[str, str]]:
49
+ """Warning if the first tool call does not reference project-context paths."""
50
+ if not analysis.tool_sequence:
51
+ return None
52
+ first_call = analysis.tool_sequence[0]
53
+ args_str = json.dumps(first_call.arguments)
54
+ # Look for any project-context path references
55
+ context_indicators = [
56
+ "project-context",
57
+ ".claude/",
58
+ "CLAUDE.md",
59
+ "context-contracts",
60
+ ]
61
+ if not any(indicator in args_str for indicator in context_indicators):
62
+ return {
63
+ "type": "context_ignored",
64
+ "severity": "warning",
65
+ "message": (
66
+ "First tool call does not reference any project-context "
67
+ "paths — agent may have ignored injected context"
68
+ ),
69
+ }
70
+ return None
71
+
72
+
73
+ def _check_context_update_missing(
74
+ analysis: TranscriptAnalysis,
75
+ agent_output: str,
76
+ ) -> Optional[Dict[str, str]]:
77
+ """Info if context-updater skill was injected but no CONTEXT_UPDATE emitted."""
78
+ if "context-updater" in analysis.skills_injected:
79
+ if "CONTEXT_UPDATE" not in agent_output:
80
+ return {
81
+ "type": "context_update_missing",
82
+ "severity": "info",
83
+ "message": (
84
+ "context-updater skill was injected but agent did not "
85
+ "emit a CONTEXT_UPDATE block"
86
+ ),
87
+ }
88
+ return None
89
+
90
+
91
+ def _check_excessive_tool_calls(
92
+ analysis: TranscriptAnalysis,
93
+ ) -> Optional[Dict[str, str]]:
94
+ """Warning if tool_call_count exceeds 75."""
95
+ if analysis.tool_call_count > 75:
96
+ return {
97
+ "type": "excessive_tool_calls",
98
+ "severity": "warning",
99
+ "message": (
100
+ f"Agent made {analysis.tool_call_count} tool calls "
101
+ f"(threshold: 75) — may indicate inefficient exploration"
102
+ ),
103
+ }
104
+ return None
105
+
106
+
107
+ def _check_token_budget(
108
+ analysis: TranscriptAnalysis,
109
+ ) -> Optional[Dict[str, str]]:
110
+ """Info if cache_creation_tokens exceeds 200000."""
111
+ if analysis.cache_creation_tokens > 200000:
112
+ return {
113
+ "type": "token_budget",
114
+ "severity": "info",
115
+ "message": (
116
+ f"Cache creation tokens ({analysis.cache_creation_tokens}) "
117
+ f"exceeded 200,000 — large context was created"
118
+ ),
119
+ }
120
+ return None
121
+
122
+
123
+ def _check_pipe_retroactive(
124
+ analysis: TranscriptAnalysis,
125
+ ) -> List[Dict[str, str]]:
126
+ """Warning per pipe command detected in transcript."""
127
+ results: List[Dict[str, str]] = []
128
+ for cmd in analysis.pipe_commands:
129
+ # Truncate long commands for readability
130
+ display_cmd = cmd[:120] + "..." if len(cmd) > 120 else cmd
131
+ results.append({
132
+ "type": "pipe_retroactive",
133
+ "severity": "warning",
134
+ "message": f"Pipe command detected in transcript: {display_cmd}",
135
+ })
136
+ return results
137
+
138
+
139
+ def _check_model_mismatch(
140
+ analysis: TranscriptAnalysis,
141
+ metrics: Dict[str, Any],
142
+ ) -> Optional[Dict[str, str]]:
143
+ """Info if transcript model differs from agent definition model."""
144
+ definition_model = ""
145
+ snapshot = metrics.get("default_skills_snapshot")
146
+ if isinstance(snapshot, dict):
147
+ definition_model = snapshot.get("model", "")
148
+ if (
149
+ analysis.model
150
+ and definition_model
151
+ and analysis.model != definition_model
152
+ ):
153
+ return {
154
+ "type": "model_mismatch",
155
+ "severity": "info",
156
+ "message": (
157
+ f"Transcript model ({analysis.model}) differs from "
158
+ f"agent definition model ({definition_model})"
159
+ ),
160
+ }
161
+ return None
162
+
163
+
164
+ def _check_skill_order(
165
+ analysis: TranscriptAnalysis,
166
+ metrics: Dict[str, Any],
167
+ ) -> Optional[Dict[str, str]]:
168
+ """Info if skills were injected in an unexpected order."""
169
+ snapshot = metrics.get("default_skills_snapshot")
170
+ if not isinstance(snapshot, dict):
171
+ return None
172
+ expected_skills = snapshot.get("skills", [])
173
+ if not expected_skills or not analysis.skills_injected:
174
+ return None
175
+ # Check if the injected skills appear in the expected order
176
+ # (only consider skills that are in the expected list)
177
+ expected_set = set(expected_skills)
178
+ actual_ordered = [s for s in analysis.skills_injected if s in expected_set]
179
+ expected_ordered = [s for s in expected_skills if s in set(actual_ordered)]
180
+ if actual_ordered and expected_ordered and actual_ordered != expected_ordered:
181
+ return {
182
+ "type": "skill_order",
183
+ "severity": "info",
184
+ "message": (
185
+ f"Skills injected in unexpected order: "
186
+ f"{actual_ordered} (expected: {expected_ordered})"
187
+ ),
188
+ }
189
+ return None
190
+
191
+
192
+ def _check_duplicate_tools(
193
+ analysis: TranscriptAnalysis,
194
+ ) -> Optional[Dict[str, str]]:
195
+ """Info if duplicate tool calls were detected."""
196
+ if analysis.duplicate_tool_calls:
197
+ dup_summary = ", ".join(
198
+ f"{d.tool_name}(x{len(d.indices)})"
199
+ for d in analysis.duplicate_tool_calls
200
+ )
201
+ return {
202
+ "type": "duplicate_tools",
203
+ "severity": "info",
204
+ "message": (
205
+ f"Duplicate tool calls detected: {dup_summary}"
206
+ ),
207
+ }
208
+ return None
209
+
210
+
211
+ def _check_token_explosion(
212
+ analysis: TranscriptAnalysis,
213
+ ) -> Optional[Dict[str, str]]:
214
+ """Warning if total token consumption exceeds 10 million."""
215
+ total = (
216
+ analysis.input_tokens
217
+ + analysis.cache_creation_tokens
218
+ + analysis.output_tokens
219
+ )
220
+ if total > 10_000_000:
221
+ return {
222
+ "type": "token_explosion",
223
+ "severity": "warning",
224
+ "message": (
225
+ f"Total token consumption ({total:,}) exceeded 10,000,000 "
226
+ f"— possible runaway generation"
227
+ ),
228
+ }
229
+ return None
230
+
231
+
232
+ def _check_cache_efficiency(
233
+ analysis: TranscriptAnalysis,
234
+ ) -> Optional[Dict[str, str]]:
235
+ """Info if cache read ratio is below 60% with significant token volume."""
236
+ total = (
237
+ analysis.cache_read_tokens
238
+ + analysis.cache_creation_tokens
239
+ + analysis.input_tokens
240
+ )
241
+ if total > 1000:
242
+ ratio = analysis.cache_read_tokens / total
243
+ if ratio < 0.60:
244
+ return {
245
+ "type": "cache_efficiency",
246
+ "severity": "info",
247
+ "message": (
248
+ f"Cache read ratio is {ratio:.1%} (below 60%) "
249
+ f"with {total:,} total input tokens — cache may be underutilized"
250
+ ),
251
+ }
252
+ return None
253
+
254
+
255
+ def _check_duplicate_write_storm(
256
+ analysis: TranscriptAnalysis,
257
+ ) -> Optional[Dict[str, str]]:
258
+ """Warning if Write or Edit tool calls appear 3+ times with identical args."""
259
+ for dup in analysis.duplicate_tool_calls:
260
+ if dup.tool_name in ("Write", "Edit") and len(dup.indices) >= 3:
261
+ return {
262
+ "type": "duplicate_write_storm",
263
+ "severity": "warning",
264
+ "message": (
265
+ f"Duplicate {dup.tool_name} storm detected: "
266
+ f"{len(dup.indices)} identical calls at indices "
267
+ f"{dup.indices}"
268
+ ),
269
+ }
270
+ return None
271
+
272
+
273
+ def _check_duration_outlier(
274
+ analysis: TranscriptAnalysis,
275
+ ) -> Optional[Dict[str, str]]:
276
+ """Warning if agent execution exceeded 10 minutes."""
277
+ if analysis.duration_ms is not None and analysis.duration_ms > 600_000:
278
+ minutes = analysis.duration_ms / 60_000
279
+ return {
280
+ "type": "duration_outlier",
281
+ "severity": "warning",
282
+ "message": (
283
+ f"Agent execution took {minutes:.1f} minutes "
284
+ f"(threshold: 10 min) — may indicate stalled or inefficient work"
285
+ ),
286
+ }
287
+ return None
288
+
289
+
290
+ def _check_tool_call_velocity(
291
+ analysis: TranscriptAnalysis,
292
+ ) -> Optional[Dict[str, str]]:
293
+ """Warning if tool call rate exceeds 20 calls per minute."""
294
+ if (
295
+ analysis.duration_ms is not None
296
+ and analysis.duration_ms > 0
297
+ and (analysis.tool_call_count / (analysis.duration_ms / 60_000)) > 20
298
+ ):
299
+ velocity = analysis.tool_call_count / (analysis.duration_ms / 60_000)
300
+ return {
301
+ "type": "tool_call_velocity",
302
+ "severity": "warning",
303
+ "message": (
304
+ f"Tool call velocity is {velocity:.1f} calls/min "
305
+ f"(threshold: 20) — agent may be thrashing"
306
+ ),
307
+ }
308
+ return None
309
+
310
+
311
+ def audit(
312
+ metrics: Dict[str, Any],
313
+ agent_output: str = "",
314
+ task_info: Optional[Dict[str, Any]] = None,
315
+ rejected_sections: Optional[List[str]] = None,
316
+ transcript_analysis: Optional[TranscriptAnalysis] = None,
317
+ ) -> List[Dict[str, str]]:
318
+ """
319
+ Detect anomalies in workflow execution.
320
+
321
+ Checks:
322
+ - execution_failure: exit_code != 0
323
+ - consecutive_failures: 3+ failures in a row for same agent
324
+ - missing_evidence: COMPLETE but no evidence in json:contract block
325
+ - empty_evidence: json:contract evidence exists but commands_run empty or all "not run"
326
+ - skipped_verification: task has verify command in injected_context but not in commands_run
327
+ - scope_escalation: rejected_sections exist (agent tried to write outside its scope)
328
+
329
+ Transcript-analysis checks (only when transcript_analysis is provided):
330
+ - investigation_skip: first tool was Bash
331
+ - context_ignored: first tool call has no project-context paths
332
+ - context_update_missing: context-updater injected but no CONTEXT_UPDATE emitted
333
+ - excessive_tool_calls: tool_call_count > 75
334
+ - token_budget: cache_creation_tokens > 200000
335
+ - token_explosion: total tokens (input+cache_creation+output) > 10M
336
+ - cache_efficiency: cache read ratio < 60% with significant volume
337
+ - duplicate_write_storm: Write/Edit tool with 3+ identical calls
338
+ - duration_outlier: duration_ms > 600,000 (10 min)
339
+ - tool_call_velocity: > 20 tool calls per minute
340
+ - pipe_retroactive: pipe commands found in transcript
341
+ - model_mismatch: transcript model != agent definition model
342
+ - skill_order: skills injected in unexpected order
343
+ - duplicate_tools: duplicate tool calls detected
344
+
345
+ Args:
346
+ metrics: Workflow metrics dict (from workflow_recorder.record()).
347
+ agent_output: Complete agent output string (for evidence checks).
348
+ task_info: Task metadata including injected_context (for verification checks).
349
+ rejected_sections: List of context sections rejected by permission validation.
350
+ transcript_analysis: Optional TranscriptAnalysis from transcript_analyzer.
351
+ When None (default), transcript-based checks are skipped for backward
352
+ compatibility.
353
+
354
+ Returns:
355
+ List of anomaly descriptions
356
+ """
357
+ anomalies: List[Dict[str, str]] = []
358
+ task_info = task_info or {}
359
+
360
+ # --- existing: execution_failure ---
361
+ if metrics.get("exit_code", 0) != 0:
362
+ anomalies.append({
363
+ "type": "execution_failure",
364
+ "severity": "error",
365
+ "message": f"Agent {metrics['agent']} failed with exit code {metrics['exit_code']}"
366
+ })
367
+
368
+ # --- existing: consecutive_failures ---
369
+ try:
370
+ workflow_memory_dir = get_workflow_memory_dir()
371
+ metrics_file = workflow_memory_dir / "metrics.jsonl"
372
+
373
+ if metrics_file.exists():
374
+ with open(metrics_file) as f:
375
+ recent = list(deque(f, maxlen=7))
376
+ # Get last 5 metrics (excluding current which is the last line)
377
+ last_5 = (
378
+ [json.loads(line) for line in recent[:-1]][-5:]
379
+ if len(recent) > 1
380
+ else []
381
+ )
382
+
383
+ # Count recent failures for same agent
384
+ agent = metrics["agent"]
385
+ recent_failures = [
386
+ m for m in last_5
387
+ if m.get("agent") == agent and m.get("exit_code", 0) != 0
388
+ ]
389
+
390
+ # If current also failed and we have 2+ previous failures
391
+ if metrics.get("exit_code", 0) != 0 and len(recent_failures) >= 2:
392
+ anomalies.append({
393
+ "type": "consecutive_failures",
394
+ "severity": "critical",
395
+ "message": (
396
+ f"Agent {agent} has failed "
397
+ f"{len(recent_failures) + 1} times consecutively"
398
+ ),
399
+ })
400
+ except Exception as e:
401
+ logger.debug(f"Could not check consecutive failures: {e}")
402
+
403
+ # --- NEW: missing_evidence ---
404
+ if agent_output:
405
+ plan_status = metrics.get("plan_status", "")
406
+ if "COMPLETE" in plan_status:
407
+ from ..agents.contract_validator import parse_contract
408
+ contract = parse_contract(agent_output)
409
+ has_evidence = (
410
+ contract is not None
411
+ and isinstance(contract.get("evidence_report"), dict)
412
+ and bool(contract["evidence_report"])
413
+ )
414
+ if not has_evidence:
415
+ anomalies.append({
416
+ "type": "missing_evidence",
417
+ "severity": "warning",
418
+ "message": (
419
+ f"Agent {metrics['agent']} completed but "
420
+ f"did not include evidence in json:contract block"
421
+ ),
422
+ })
423
+
424
+ # --- NEW: empty_evidence ---
425
+ if agent_output:
426
+ from ..agents.contract_validator import parse_contract
427
+ contract = parse_contract(agent_output)
428
+ if contract is not None:
429
+ evidence = contract.get("evidence_report")
430
+ if isinstance(evidence, dict):
431
+ commands_run = evidence.get("commands_run", [])
432
+ if isinstance(commands_run, list):
433
+ not_run_pattern = re.compile(
434
+ r"\b(not\s+run|not\s+executed|skipped|n/a|none)\b",
435
+ re.IGNORECASE,
436
+ )
437
+ if not commands_run:
438
+ # commands_run key exists but is empty list
439
+ anomalies.append({
440
+ "type": "empty_evidence",
441
+ "severity": "warning",
442
+ "message": (
443
+ f"Agent {metrics['agent']} has evidence in "
444
+ f"json:contract but commands_run is empty"
445
+ ),
446
+ })
447
+ elif all(
448
+ isinstance(c, str) and not_run_pattern.search(c)
449
+ for c in commands_run
450
+ ):
451
+ anomalies.append({
452
+ "type": "empty_evidence",
453
+ "severity": "warning",
454
+ "message": (
455
+ f"Agent {metrics['agent']} has evidence in "
456
+ f"json:contract but all commands_run entries "
457
+ f"indicate 'not run'"
458
+ ),
459
+ })
460
+
461
+ # --- NEW: skipped_verification ---
462
+ injected = task_info.get("injected_context") or {}
463
+ investigation_brief = injected.get("investigation_brief", {}) or {}
464
+ required_checks = investigation_brief.get("required_checks", [])
465
+ if required_checks and agent_output:
466
+ # Extract commands that were actually run from evidence
467
+ from ..agents.contract_validator import extract_commands_from_evidence
468
+ commands_run = extract_commands_from_evidence(agent_output)
469
+
470
+ # Check if any required check mentions a verify command
471
+ for check in required_checks:
472
+ if isinstance(check, str) and "verify" in check.lower():
473
+ # If there are required verification checks but no commands were run at all
474
+ if not commands_run:
475
+ anomalies.append({
476
+ "type": "skipped_verification",
477
+ "severity": "warning",
478
+ "message": (
479
+ f"Agent {metrics['agent']} has verification requirements "
480
+ f"in injected_context but ran no commands"
481
+ ),
482
+ })
483
+ break
484
+
485
+ # --- NEW: scope_escalation ---
486
+ if rejected_sections:
487
+ anomalies.append({
488
+ "type": "scope_escalation",
489
+ "severity": "warning",
490
+ "message": (
491
+ f"Agent {metrics['agent']} attempted to write to "
492
+ f"unauthorized sections: {', '.join(rejected_sections)}"
493
+ ),
494
+ })
495
+
496
+ # --- Transcript-analysis checks (T009) ---
497
+ if transcript_analysis is not None:
498
+ for check_fn in (
499
+ _check_investigation_skip,
500
+ _check_context_ignored,
501
+ _check_excessive_tool_calls,
502
+ _check_token_budget,
503
+ _check_duplicate_tools,
504
+ _check_token_explosion,
505
+ _check_cache_efficiency,
506
+ _check_duplicate_write_storm,
507
+ _check_duration_outlier,
508
+ _check_tool_call_velocity,
509
+ ):
510
+ result = check_fn(transcript_analysis)
511
+ if result is not None:
512
+ anomalies.append(result)
513
+
514
+ # Checks that need agent_output
515
+ result = _check_context_update_missing(transcript_analysis, agent_output)
516
+ if result is not None:
517
+ anomalies.append(result)
518
+
519
+ # Checks that need metrics
520
+ for check_fn_m in (_check_model_mismatch, _check_skill_order):
521
+ result = check_fn_m(transcript_analysis, metrics)
522
+ if result is not None:
523
+ anomalies.append(result)
524
+
525
+ # Pipe check returns a list (one per pipe command)
526
+ anomalies.extend(_check_pipe_retroactive(transcript_analysis))
527
+
528
+ return anomalies
529
+
530
+
531
+
532
+ def signal_gaia_analysis(
533
+ anomalies: List[Dict],
534
+ metrics: Dict[str, Any],
535
+ ) -> None:
536
+ """
537
+ Signal that Gaia analysis is needed.
538
+
539
+ Creates a flag file that orchestrator can detect.
540
+ """
541
+ try:
542
+ signals_dir = get_workflow_memory_dir() / "signals"
543
+ signals_dir.mkdir(parents=True, exist_ok=True)
544
+
545
+ signal_file = signals_dir / "needs_analysis.flag"
546
+
547
+ signal_data = {
548
+ "timestamp": datetime.now().isoformat(),
549
+ "created_at": datetime.now().isoformat(),
550
+ "ttl_hours": 1,
551
+ "anomalies": anomalies,
552
+ "metrics_summary": {
553
+ "agent": metrics["agent"],
554
+ "task_id": metrics["task_id"],
555
+ "duration_ms": metrics.get("duration_ms"),
556
+ "exit_code": metrics.get("exit_code"),
557
+ },
558
+ "suggested_action": "Invoke /gaia for system analysis",
559
+ }
560
+
561
+ with open(signal_file, "w") as f:
562
+ json.dump(signal_data, f, indent=2)
563
+
564
+ logger.info(f"Gaia analysis signal created: {signal_file}")
565
+
566
+ # Also log to a permanent anomaly log
567
+ anomaly_log = signals_dir.parent / "anomalies.jsonl"
568
+ with open(anomaly_log, "a") as f:
569
+ f.write(json.dumps({
570
+ "timestamp": datetime.now().isoformat(),
571
+ "anomalies": anomalies,
572
+ "metrics": metrics,
573
+ }) + "\n")
574
+
575
+ except Exception as e:
576
+ logger.warning(f"Could not create analysis signal: {e}")