@jaguilar87/gaia 5.0.0-rc.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 (621) hide show
  1. package/.claude-plugin/marketplace.json +33 -0
  2. package/.claude-plugin/plugin.json +26 -0
  3. package/ARCHITECTURE.md +335 -0
  4. package/CHANGELOG.md +1298 -0
  5. package/CODE_OF_CONDUCT.md +11 -0
  6. package/CONTRIBUTING.md +146 -0
  7. package/INSTALL.md +436 -0
  8. package/LICENSE +21 -0
  9. package/README.md +222 -0
  10. package/SECURITY.md +47 -0
  11. package/agents/README.md +78 -0
  12. package/agents/cloud-troubleshooter.md +73 -0
  13. package/agents/developer.md +65 -0
  14. package/agents/gaia-operator.md +64 -0
  15. package/agents/gaia-orchestrator.md +111 -0
  16. package/agents/gaia-planner.md +53 -0
  17. package/agents/gaia-system.md +71 -0
  18. package/agents/gitops-operator.md +61 -0
  19. package/agents/terraform-architect.md +63 -0
  20. package/bin/README.md +106 -0
  21. package/bin/cli/__init__.py +1 -0
  22. package/bin/cli/approvals.py +740 -0
  23. package/bin/cli/cleanup.py +562 -0
  24. package/bin/cli/context.py +283 -0
  25. package/bin/cli/doctor.py +651 -0
  26. package/bin/cli/history.py +305 -0
  27. package/bin/cli/memory.py +483 -0
  28. package/bin/cli/metrics.py +1068 -0
  29. package/bin/cli/plans.py +515 -0
  30. package/bin/cli/status.py +302 -0
  31. package/bin/cli/update.py +382 -0
  32. package/bin/gaia +112 -0
  33. package/bin/gaia-cleanup.js +531 -0
  34. package/bin/gaia-doctor.js +635 -0
  35. package/bin/gaia-evidence +126 -0
  36. package/bin/gaia-history.js +251 -0
  37. package/bin/gaia-metrics.js +1278 -0
  38. package/bin/gaia-review.js +269 -0
  39. package/bin/gaia-scan +44 -0
  40. package/bin/gaia-scan.py +589 -0
  41. package/bin/gaia-skills-diagnose.js +929 -0
  42. package/bin/gaia-status.js +278 -0
  43. package/bin/gaia-uninstall.js +111 -0
  44. package/bin/gaia-update.js +919 -0
  45. package/bin/pre-publish-validate.js +610 -0
  46. package/bin/python-detect.js +60 -0
  47. package/bin/validate-sandbox.sh +601 -0
  48. package/commands/README.md +64 -0
  49. package/commands/gaia.md +37 -0
  50. package/commands/scan-project.md +67 -0
  51. package/config/README.md +71 -0
  52. package/config/cloud/aws.json +134 -0
  53. package/config/cloud/gcp.json +139 -0
  54. package/config/context-contracts.json +158 -0
  55. package/config/crons-schema.md +81 -0
  56. package/config/git_standards.json +72 -0
  57. package/config/surface-routing.json +417 -0
  58. package/config/universal-rules.json +102 -0
  59. package/dist/gaia-ops/.claude-plugin/plugin.json +24 -0
  60. package/dist/gaia-ops/README.md +80 -0
  61. package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
  62. package/dist/gaia-ops/agents/developer.md +65 -0
  63. package/dist/gaia-ops/agents/gaia-operator.md +64 -0
  64. package/dist/gaia-ops/agents/gaia-orchestrator.md +111 -0
  65. package/dist/gaia-ops/agents/gaia-planner.md +53 -0
  66. package/dist/gaia-ops/agents/gaia-system.md +71 -0
  67. package/dist/gaia-ops/agents/gitops-operator.md +61 -0
  68. package/dist/gaia-ops/agents/terraform-architect.md +63 -0
  69. package/dist/gaia-ops/commands/gaia.md +37 -0
  70. package/dist/gaia-ops/config/README.md +71 -0
  71. package/dist/gaia-ops/config/cloud/aws.json +134 -0
  72. package/dist/gaia-ops/config/cloud/gcp.json +139 -0
  73. package/dist/gaia-ops/config/context-contracts.json +158 -0
  74. package/dist/gaia-ops/config/crons-schema.md +81 -0
  75. package/dist/gaia-ops/config/git_standards.json +72 -0
  76. package/dist/gaia-ops/config/surface-routing.json +417 -0
  77. package/dist/gaia-ops/config/universal-rules.json +102 -0
  78. package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
  79. package/dist/gaia-ops/hooks/adapters/base.py +219 -0
  80. package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
  81. package/dist/gaia-ops/hooks/adapters/claude_code.py +1890 -0
  82. package/dist/gaia-ops/hooks/adapters/types.py +194 -0
  83. package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
  84. package/dist/gaia-ops/hooks/hooks.json +192 -0
  85. package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
  86. package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
  87. package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
  88. package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
  89. package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +120 -0
  90. package/dist/gaia-ops/hooks/modules/agents/state_tracker.py +267 -0
  91. package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
  92. package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
  93. package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
  94. package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
  95. package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
  96. package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
  97. package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
  98. package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +611 -0
  99. package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
  100. package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
  101. package/dist/gaia-ops/hooks/modules/context/agentic_loop_detector.py +165 -0
  102. package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
  103. package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +218 -0
  104. package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
  105. package/dist/gaia-ops/hooks/modules/context/context_injector.py +558 -0
  106. package/dist/gaia-ops/hooks/modules/context/context_writer.py +530 -0
  107. package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
  108. package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
  109. package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
  110. package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
  111. package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
  112. package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +577 -0
  113. package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
  114. package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
  115. package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
  116. package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
  117. package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
  118. package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +216 -0
  119. package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
  120. package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +122 -0
  121. package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
  122. package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
  123. package/dist/gaia-ops/hooks/modules/security/__init__.py +120 -0
  124. package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
  125. package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
  126. package/dist/gaia-ops/hooks/modules/security/approval_grants.py +1638 -0
  127. package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
  128. package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +222 -0
  129. package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +595 -0
  130. package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +87 -0
  131. package/dist/gaia-ops/hooks/modules/security/command_semantics.py +181 -0
  132. package/dist/gaia-ops/hooks/modules/security/composition_rules.py +547 -0
  133. package/dist/gaia-ops/hooks/modules/security/flag_classifiers.py +873 -0
  134. package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
  135. package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +1131 -0
  136. package/dist/gaia-ops/hooks/modules/security/network_hosts.py +481 -0
  137. package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
  138. package/dist/gaia-ops/hooks/modules/security/shell_unwrapper.py +165 -0
  139. package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
  140. package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
  141. package/dist/gaia-ops/hooks/modules/session/pending_scanner.py +174 -0
  142. package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
  143. package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +160 -0
  144. package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
  145. package/dist/gaia-ops/hooks/modules/session/session_registry.py +333 -0
  146. package/dist/gaia-ops/hooks/modules/tools/__init__.py +29 -0
  147. package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +1008 -0
  148. package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  149. package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
  150. package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
  151. package/dist/gaia-ops/hooks/modules/tools/stage_decomposer.py +315 -0
  152. package/dist/gaia-ops/hooks/modules/tools/task_validator.py +294 -0
  153. package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
  154. package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
  155. package/dist/gaia-ops/hooks/post_compact.py +43 -0
  156. package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
  157. package/dist/gaia-ops/hooks/pre_compact.py +60 -0
  158. package/dist/gaia-ops/hooks/pre_tool_use.py +413 -0
  159. package/dist/gaia-ops/hooks/session_end_hook.py +77 -0
  160. package/dist/gaia-ops/hooks/session_start.py +81 -0
  161. package/dist/gaia-ops/hooks/stop_hook.py +70 -0
  162. package/dist/gaia-ops/hooks/subagent_start.py +71 -0
  163. package/dist/gaia-ops/hooks/subagent_stop.py +295 -0
  164. package/dist/gaia-ops/hooks/task_completed.py +70 -0
  165. package/dist/gaia-ops/hooks/user_prompt_submit.py +246 -0
  166. package/dist/gaia-ops/settings.json +72 -0
  167. package/dist/gaia-ops/skills/README.md +158 -0
  168. package/dist/gaia-ops/skills/agent-creation/SKILL.md +87 -0
  169. package/dist/gaia-ops/skills/agent-creation/examples.md +170 -0
  170. package/dist/gaia-ops/skills/agent-creation/reference.md +191 -0
  171. package/dist/gaia-ops/skills/agent-protocol/SKILL.md +93 -0
  172. package/dist/gaia-ops/skills/agent-protocol/examples.md +223 -0
  173. package/dist/gaia-ops/skills/agent-response/SKILL.md +69 -0
  174. package/dist/gaia-ops/skills/agentic-loop/SKILL.md +80 -0
  175. package/dist/gaia-ops/skills/agentic-loop/reference.md +378 -0
  176. package/dist/gaia-ops/skills/blog-writing/SKILL.md +98 -0
  177. package/dist/gaia-ops/skills/blog-writing/reference.md +130 -0
  178. package/dist/gaia-ops/skills/brief-spec/SKILL.md +185 -0
  179. package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
  180. package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
  181. package/dist/gaia-ops/skills/context-updater/SKILL.md +87 -0
  182. package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
  183. package/dist/gaia-ops/skills/developer-patterns/SKILL.md +50 -0
  184. package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
  185. package/dist/gaia-ops/skills/execution/SKILL.md +99 -0
  186. package/dist/gaia-ops/skills/fast-queries/SKILL.md +43 -0
  187. package/dist/gaia-ops/skills/gaia-compact/SKILL.md +74 -0
  188. package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +108 -0
  189. package/dist/gaia-ops/skills/gaia-patterns/reference.md +395 -0
  190. package/dist/gaia-ops/skills/gaia-planner/SKILL.md +37 -0
  191. package/dist/gaia-ops/skills/gaia-planner/reference.md +107 -0
  192. package/dist/gaia-ops/skills/gaia-release/SKILL.md +85 -0
  193. package/dist/gaia-ops/skills/gaia-release/reference.md +92 -0
  194. package/dist/gaia-ops/skills/gaia-self-check/SKILL.md +114 -0
  195. package/dist/gaia-ops/skills/gaia-self-check/reference.md +453 -0
  196. package/dist/gaia-ops/skills/gaia-verify/SKILL.md +77 -0
  197. package/dist/gaia-ops/skills/gaia-verify/reference.md +80 -0
  198. package/dist/gaia-ops/skills/git-conventions/SKILL.md +47 -0
  199. package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +60 -0
  200. package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
  201. package/dist/gaia-ops/skills/gmail-policy/SKILL.md +200 -0
  202. package/dist/gaia-ops/skills/gmail-policy/reference.md +150 -0
  203. package/dist/gaia-ops/skills/gmail-triage/SKILL.md +100 -0
  204. package/dist/gaia-ops/skills/gws-setup/SKILL.md +99 -0
  205. package/dist/gaia-ops/skills/gws-setup/reference.md +73 -0
  206. package/dist/gaia-ops/skills/investigation/SKILL.md +100 -0
  207. package/dist/gaia-ops/skills/memory-curation/SKILL.md +83 -0
  208. package/dist/gaia-ops/skills/memory-search/SKILL.md +88 -0
  209. package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +160 -0
  210. package/dist/gaia-ops/skills/orchestrator-approval/reference.md +174 -0
  211. package/dist/gaia-ops/skills/pending-approvals/SKILL.md +72 -0
  212. package/dist/gaia-ops/skills/pending-approvals/reference.md +214 -0
  213. package/dist/gaia-ops/skills/readme-writing/SKILL.md +71 -0
  214. package/dist/gaia-ops/skills/readme-writing/reference.md +188 -0
  215. package/dist/gaia-ops/skills/reference.md +135 -0
  216. package/dist/gaia-ops/skills/request-approval/SKILL.md +140 -0
  217. package/dist/gaia-ops/skills/request-approval/examples.md +140 -0
  218. package/dist/gaia-ops/skills/request-approval/reference.md +57 -0
  219. package/dist/gaia-ops/skills/schedule-task/SKILL.md +64 -0
  220. package/dist/gaia-ops/skills/schedule-task/reference.md +233 -0
  221. package/dist/gaia-ops/skills/security-tiers/SKILL.md +141 -0
  222. package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
  223. package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
  224. package/dist/gaia-ops/skills/session-reflection/SKILL.md +69 -0
  225. package/dist/gaia-ops/skills/skill-creation/SKILL.md +92 -0
  226. package/dist/gaia-ops/skills/skill-creation/reference.md +29 -0
  227. package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +89 -0
  228. package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
  229. package/dist/gaia-ops/tools/__init__.py +9 -0
  230. package/dist/gaia-ops/tools/agentic-loop/decide-status.py +210 -0
  231. package/dist/gaia-ops/tools/agentic-loop/parse-metric.py +106 -0
  232. package/dist/gaia-ops/tools/agentic-loop/record-iteration.py +221 -0
  233. package/dist/gaia-ops/tools/context/README.md +132 -0
  234. package/dist/gaia-ops/tools/context/__init__.py +42 -0
  235. package/dist/gaia-ops/tools/context/_paths.py +20 -0
  236. package/dist/gaia-ops/tools/context/context_provider.py +721 -0
  237. package/dist/gaia-ops/tools/context/context_section_reader.py +342 -0
  238. package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
  239. package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
  240. package/dist/gaia-ops/tools/context/surface_router.py +278 -0
  241. package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
  242. package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
  243. package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  244. package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  245. package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  246. package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  247. package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
  248. package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  249. package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
  250. package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
  251. package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
  252. package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
  253. package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
  254. package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
  255. package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +264 -0
  256. package/dist/gaia-ops/tools/memory/README.md +0 -0
  257. package/dist/gaia-ops/tools/memory/__init__.py +20 -0
  258. package/dist/gaia-ops/tools/memory/backfill_fts5.py +107 -0
  259. package/dist/gaia-ops/tools/memory/conflict_detector.py +295 -0
  260. package/dist/gaia-ops/tools/memory/episodic.py +1210 -0
  261. package/dist/gaia-ops/tools/memory/git_invalidator.py +262 -0
  262. package/dist/gaia-ops/tools/memory/paths.py +102 -0
  263. package/dist/gaia-ops/tools/memory/scoring.py +193 -0
  264. package/dist/gaia-ops/tools/memory/search_store.py +375 -0
  265. package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
  266. package/dist/gaia-ops/tools/review/__init__.py +1 -0
  267. package/dist/gaia-ops/tools/review/review_engine.py +157 -0
  268. package/dist/gaia-ops/tools/scan/__init__.py +35 -0
  269. package/dist/gaia-ops/tools/scan/config.py +247 -0
  270. package/dist/gaia-ops/tools/scan/merge.py +212 -0
  271. package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
  272. package/dist/gaia-ops/tools/scan/registry.py +127 -0
  273. package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
  274. package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
  275. package/dist/gaia-ops/tools/scan/scanners/environment.py +349 -0
  276. package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
  277. package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
  278. package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
  279. package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
  280. package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
  281. package/dist/gaia-ops/tools/scan/setup.py +686 -0
  282. package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
  283. package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
  284. package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
  285. package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
  286. package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
  287. package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
  288. package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
  289. package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
  290. package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
  291. package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
  292. package/dist/gaia-ops/tools/scan/ui.py +624 -0
  293. package/dist/gaia-ops/tools/scan/verify.py +270 -0
  294. package/dist/gaia-ops/tools/scan/walk.py +118 -0
  295. package/dist/gaia-ops/tools/scan/workspace.py +85 -0
  296. package/dist/gaia-ops/tools/validation/README.md +244 -0
  297. package/dist/gaia-ops/tools/validation/__init__.py +17 -0
  298. package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
  299. package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
  300. package/dist/gaia-security/.claude-plugin/plugin.json +24 -0
  301. package/dist/gaia-security/README.md +90 -0
  302. package/dist/gaia-security/config/universal-rules.json +102 -0
  303. package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
  304. package/dist/gaia-security/hooks/adapters/base.py +219 -0
  305. package/dist/gaia-security/hooks/adapters/channel.py +17 -0
  306. package/dist/gaia-security/hooks/adapters/claude_code.py +1890 -0
  307. package/dist/gaia-security/hooks/adapters/types.py +194 -0
  308. package/dist/gaia-security/hooks/adapters/utils.py +25 -0
  309. package/dist/gaia-security/hooks/hooks.json +113 -0
  310. package/dist/gaia-security/hooks/modules/__init__.py +15 -0
  311. package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
  312. package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
  313. package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
  314. package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +120 -0
  315. package/dist/gaia-security/hooks/modules/agents/state_tracker.py +267 -0
  316. package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
  317. package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
  318. package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
  319. package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
  320. package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
  321. package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
  322. package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
  323. package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +611 -0
  324. package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
  325. package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
  326. package/dist/gaia-security/hooks/modules/context/agentic_loop_detector.py +165 -0
  327. package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
  328. package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +218 -0
  329. package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
  330. package/dist/gaia-security/hooks/modules/context/context_injector.py +558 -0
  331. package/dist/gaia-security/hooks/modules/context/context_writer.py +530 -0
  332. package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
  333. package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
  334. package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
  335. package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
  336. package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
  337. package/dist/gaia-security/hooks/modules/core/plugin_setup.py +577 -0
  338. package/dist/gaia-security/hooks/modules/core/state.py +179 -0
  339. package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
  340. package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
  341. package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
  342. package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
  343. package/dist/gaia-security/hooks/modules/memory/episode_writer.py +216 -0
  344. package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
  345. package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +122 -0
  346. package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
  347. package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
  348. package/dist/gaia-security/hooks/modules/security/__init__.py +120 -0
  349. package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
  350. package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
  351. package/dist/gaia-security/hooks/modules/security/approval_grants.py +1638 -0
  352. package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
  353. package/dist/gaia-security/hooks/modules/security/approval_scopes.py +222 -0
  354. package/dist/gaia-security/hooks/modules/security/blocked_commands.py +595 -0
  355. package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +87 -0
  356. package/dist/gaia-security/hooks/modules/security/command_semantics.py +181 -0
  357. package/dist/gaia-security/hooks/modules/security/composition_rules.py +547 -0
  358. package/dist/gaia-security/hooks/modules/security/flag_classifiers.py +873 -0
  359. package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
  360. package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +1131 -0
  361. package/dist/gaia-security/hooks/modules/security/network_hosts.py +481 -0
  362. package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
  363. package/dist/gaia-security/hooks/modules/security/shell_unwrapper.py +165 -0
  364. package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
  365. package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
  366. package/dist/gaia-security/hooks/modules/session/pending_scanner.py +174 -0
  367. package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
  368. package/dist/gaia-security/hooks/modules/session/session_event_injector.py +160 -0
  369. package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
  370. package/dist/gaia-security/hooks/modules/session/session_registry.py +333 -0
  371. package/dist/gaia-security/hooks/modules/tools/__init__.py +29 -0
  372. package/dist/gaia-security/hooks/modules/tools/bash_validator.py +1008 -0
  373. package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  374. package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
  375. package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
  376. package/dist/gaia-security/hooks/modules/tools/stage_decomposer.py +315 -0
  377. package/dist/gaia-security/hooks/modules/tools/task_validator.py +294 -0
  378. package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
  379. package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
  380. package/dist/gaia-security/hooks/post_tool_use.py +54 -0
  381. package/dist/gaia-security/hooks/pre_tool_use.py +413 -0
  382. package/dist/gaia-security/hooks/session_end_hook.py +77 -0
  383. package/dist/gaia-security/hooks/session_start.py +81 -0
  384. package/dist/gaia-security/hooks/stop_hook.py +70 -0
  385. package/dist/gaia-security/hooks/user_prompt_submit.py +246 -0
  386. package/dist/gaia-security/settings.json +58 -0
  387. package/git-hooks/commit-msg +41 -0
  388. package/hooks/README.md +100 -0
  389. package/hooks/adapters/__init__.py +52 -0
  390. package/hooks/adapters/base.py +219 -0
  391. package/hooks/adapters/channel.py +17 -0
  392. package/hooks/adapters/claude_code.py +1890 -0
  393. package/hooks/adapters/types.py +194 -0
  394. package/hooks/adapters/utils.py +25 -0
  395. package/hooks/elicitation_result.py +179 -0
  396. package/hooks/hooks.json +84 -0
  397. package/hooks/modules/README.md +189 -0
  398. package/hooks/modules/__init__.py +15 -0
  399. package/hooks/modules/agents/__init__.py +29 -0
  400. package/hooks/modules/agents/contract_validator.py +647 -0
  401. package/hooks/modules/agents/response_contract.py +496 -0
  402. package/hooks/modules/agents/skill_injection_verifier.py +120 -0
  403. package/hooks/modules/agents/state_tracker.py +267 -0
  404. package/hooks/modules/agents/task_info_builder.py +74 -0
  405. package/hooks/modules/agents/transcript_analyzer.py +458 -0
  406. package/hooks/modules/agents/transcript_reader.py +152 -0
  407. package/hooks/modules/audit/__init__.py +28 -0
  408. package/hooks/modules/audit/event_detector.py +168 -0
  409. package/hooks/modules/audit/logger.py +131 -0
  410. package/hooks/modules/audit/metrics.py +134 -0
  411. package/hooks/modules/audit/workflow_auditor.py +611 -0
  412. package/hooks/modules/audit/workflow_recorder.py +296 -0
  413. package/hooks/modules/context/__init__.py +11 -0
  414. package/hooks/modules/context/agentic_loop_detector.py +165 -0
  415. package/hooks/modules/context/anchor_tracker.py +317 -0
  416. package/hooks/modules/context/compact_context_builder.py +218 -0
  417. package/hooks/modules/context/context_freshness.py +145 -0
  418. package/hooks/modules/context/context_injector.py +558 -0
  419. package/hooks/modules/context/context_writer.py +530 -0
  420. package/hooks/modules/context/contracts_loader.py +161 -0
  421. package/hooks/modules/core/__init__.py +40 -0
  422. package/hooks/modules/core/hook_entry.py +78 -0
  423. package/hooks/modules/core/paths.py +160 -0
  424. package/hooks/modules/core/plugin_mode.py +149 -0
  425. package/hooks/modules/core/plugin_setup.py +577 -0
  426. package/hooks/modules/core/state.py +179 -0
  427. package/hooks/modules/core/stdin.py +24 -0
  428. package/hooks/modules/events/__init__.py +1 -0
  429. package/hooks/modules/events/event_writer.py +210 -0
  430. package/hooks/modules/evidence/__init__.py +34 -0
  431. package/hooks/modules/evidence/assertions.py +137 -0
  432. package/hooks/modules/evidence/index_writer.py +57 -0
  433. package/hooks/modules/evidence/loader.py +126 -0
  434. package/hooks/modules/evidence/runner.py +241 -0
  435. package/hooks/modules/memory/__init__.py +8 -0
  436. package/hooks/modules/memory/episode_writer.py +216 -0
  437. package/hooks/modules/orchestrator/__init__.py +1 -0
  438. package/hooks/modules/orchestrator/delegate_mode.py +122 -0
  439. package/hooks/modules/scanning/__init__.py +8 -0
  440. package/hooks/modules/scanning/scan_trigger.py +84 -0
  441. package/hooks/modules/security/__init__.py +120 -0
  442. package/hooks/modules/security/approval_cleanup.py +87 -0
  443. package/hooks/modules/security/approval_constants.py +23 -0
  444. package/hooks/modules/security/approval_grants.py +1638 -0
  445. package/hooks/modules/security/approval_messages.py +71 -0
  446. package/hooks/modules/security/approval_scopes.py +222 -0
  447. package/hooks/modules/security/blocked_commands.py +595 -0
  448. package/hooks/modules/security/blocked_message_formatter.py +87 -0
  449. package/hooks/modules/security/command_semantics.py +181 -0
  450. package/hooks/modules/security/composition_rules.py +547 -0
  451. package/hooks/modules/security/flag_classifiers.py +873 -0
  452. package/hooks/modules/security/gitops_validator.py +179 -0
  453. package/hooks/modules/security/mutative_verbs.py +1131 -0
  454. package/hooks/modules/security/network_hosts.py +481 -0
  455. package/hooks/modules/security/prompt_validator.py +40 -0
  456. package/hooks/modules/security/shell_unwrapper.py +165 -0
  457. package/hooks/modules/security/tiers.py +196 -0
  458. package/hooks/modules/session/__init__.py +10 -0
  459. package/hooks/modules/session/pending_scanner.py +174 -0
  460. package/hooks/modules/session/session_context_writer.py +100 -0
  461. package/hooks/modules/session/session_event_injector.py +160 -0
  462. package/hooks/modules/session/session_manager.py +31 -0
  463. package/hooks/modules/session/session_registry.py +333 -0
  464. package/hooks/modules/tools/__init__.py +29 -0
  465. package/hooks/modules/tools/bash_validator.py +1008 -0
  466. package/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  467. package/hooks/modules/tools/hook_response.py +55 -0
  468. package/hooks/modules/tools/shell_parser.py +227 -0
  469. package/hooks/modules/tools/stage_decomposer.py +315 -0
  470. package/hooks/modules/tools/task_validator.py +294 -0
  471. package/hooks/modules/validation/__init__.py +23 -0
  472. package/hooks/modules/validation/commit_validator.py +380 -0
  473. package/hooks/post_compact.py +43 -0
  474. package/hooks/post_tool_use.py +54 -0
  475. package/hooks/pre_compact.py +60 -0
  476. package/hooks/pre_tool_use.py +413 -0
  477. package/hooks/session_end_hook.py +77 -0
  478. package/hooks/session_start.py +81 -0
  479. package/hooks/stop_hook.py +70 -0
  480. package/hooks/subagent_start.py +71 -0
  481. package/hooks/subagent_stop.py +295 -0
  482. package/hooks/task_completed.py +70 -0
  483. package/hooks/user_prompt_submit.py +246 -0
  484. package/index.js +83 -0
  485. package/package.json +103 -0
  486. package/pyproject.toml +32 -0
  487. package/skills/README.md +158 -0
  488. package/skills/agent-creation/SKILL.md +87 -0
  489. package/skills/agent-creation/examples.md +170 -0
  490. package/skills/agent-creation/reference.md +191 -0
  491. package/skills/agent-protocol/SKILL.md +93 -0
  492. package/skills/agent-protocol/examples.md +223 -0
  493. package/skills/agent-response/SKILL.md +69 -0
  494. package/skills/agentic-loop/SKILL.md +80 -0
  495. package/skills/agentic-loop/reference.md +378 -0
  496. package/skills/blog-writing/SKILL.md +98 -0
  497. package/skills/blog-writing/reference.md +130 -0
  498. package/skills/brief-spec/SKILL.md +185 -0
  499. package/skills/command-execution/SKILL.md +64 -0
  500. package/skills/command-execution/reference.md +83 -0
  501. package/skills/context-updater/SKILL.md +87 -0
  502. package/skills/context-updater/examples.md +71 -0
  503. package/skills/developer-patterns/SKILL.md +50 -0
  504. package/skills/developer-patterns/reference.md +112 -0
  505. package/skills/execution/SKILL.md +99 -0
  506. package/skills/fast-queries/SKILL.md +43 -0
  507. package/skills/gaia-compact/SKILL.md +74 -0
  508. package/skills/gaia-patterns/SKILL.md +108 -0
  509. package/skills/gaia-patterns/reference.md +395 -0
  510. package/skills/gaia-planner/SKILL.md +37 -0
  511. package/skills/gaia-planner/reference.md +107 -0
  512. package/skills/gaia-release/SKILL.md +85 -0
  513. package/skills/gaia-release/reference.md +92 -0
  514. package/skills/gaia-self-check/SKILL.md +114 -0
  515. package/skills/gaia-self-check/reference.md +453 -0
  516. package/skills/gaia-verify/SKILL.md +77 -0
  517. package/skills/gaia-verify/reference.md +80 -0
  518. package/skills/git-conventions/SKILL.md +47 -0
  519. package/skills/gitops-patterns/SKILL.md +60 -0
  520. package/skills/gitops-patterns/reference.md +183 -0
  521. package/skills/gmail-policy/SKILL.md +200 -0
  522. package/skills/gmail-policy/reference.md +150 -0
  523. package/skills/gmail-triage/SKILL.md +100 -0
  524. package/skills/gws-setup/SKILL.md +99 -0
  525. package/skills/gws-setup/reference.md +73 -0
  526. package/skills/investigation/SKILL.md +100 -0
  527. package/skills/memory-curation/SKILL.md +83 -0
  528. package/skills/memory-search/SKILL.md +88 -0
  529. package/skills/orchestrator-approval/SKILL.md +160 -0
  530. package/skills/orchestrator-approval/reference.md +174 -0
  531. package/skills/pending-approvals/SKILL.md +72 -0
  532. package/skills/pending-approvals/reference.md +214 -0
  533. package/skills/readme-writing/SKILL.md +71 -0
  534. package/skills/readme-writing/reference.md +188 -0
  535. package/skills/reference.md +135 -0
  536. package/skills/request-approval/SKILL.md +140 -0
  537. package/skills/request-approval/examples.md +140 -0
  538. package/skills/request-approval/reference.md +57 -0
  539. package/skills/schedule-task/SKILL.md +64 -0
  540. package/skills/schedule-task/reference.md +233 -0
  541. package/skills/security-tiers/SKILL.md +141 -0
  542. package/skills/security-tiers/destructive-commands-reference.md +623 -0
  543. package/skills/security-tiers/reference.md +39 -0
  544. package/skills/session-reflection/SKILL.md +69 -0
  545. package/skills/skill-creation/SKILL.md +92 -0
  546. package/skills/skill-creation/reference.md +29 -0
  547. package/skills/terraform-patterns/SKILL.md +89 -0
  548. package/skills/terraform-patterns/reference.md +93 -0
  549. package/templates/README.md +69 -0
  550. package/templates/managed-settings.template.json +43 -0
  551. package/tools/__init__.py +9 -0
  552. package/tools/agentic-loop/decide-status.py +210 -0
  553. package/tools/agentic-loop/parse-metric.py +106 -0
  554. package/tools/agentic-loop/record-iteration.py +221 -0
  555. package/tools/context/README.md +132 -0
  556. package/tools/context/__init__.py +42 -0
  557. package/tools/context/_paths.py +20 -0
  558. package/tools/context/context_provider.py +721 -0
  559. package/tools/context/context_section_reader.py +342 -0
  560. package/tools/context/deep_merge.py +159 -0
  561. package/tools/context/pending_updates.py +760 -0
  562. package/tools/context/surface_router.py +278 -0
  563. package/tools/fast-queries/README.md +65 -0
  564. package/tools/fast-queries/__init__.py +30 -0
  565. package/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  566. package/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  567. package/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  568. package/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  569. package/tools/fast-queries/run_triage.sh +59 -0
  570. package/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  571. package/tools/gaia_simulator/__init__.py +33 -0
  572. package/tools/gaia_simulator/cli.py +354 -0
  573. package/tools/gaia_simulator/extractor.py +457 -0
  574. package/tools/gaia_simulator/reporter.py +258 -0
  575. package/tools/gaia_simulator/routing_simulator.py +334 -0
  576. package/tools/gaia_simulator/runner.py +539 -0
  577. package/tools/gaia_simulator/skills_mapper.py +264 -0
  578. package/tools/memory/README.md +0 -0
  579. package/tools/memory/__init__.py +20 -0
  580. package/tools/memory/backfill_fts5.py +107 -0
  581. package/tools/memory/conflict_detector.py +295 -0
  582. package/tools/memory/episodic.py +1210 -0
  583. package/tools/memory/git_invalidator.py +262 -0
  584. package/tools/memory/paths.py +102 -0
  585. package/tools/memory/scoring.py +193 -0
  586. package/tools/memory/search_store.py +375 -0
  587. package/tools/persist_transcript_analysis.py +85 -0
  588. package/tools/review/__init__.py +1 -0
  589. package/tools/review/review_engine.py +157 -0
  590. package/tools/scan/__init__.py +35 -0
  591. package/tools/scan/config.py +247 -0
  592. package/tools/scan/merge.py +212 -0
  593. package/tools/scan/orchestrator.py +549 -0
  594. package/tools/scan/registry.py +127 -0
  595. package/tools/scan/scanners/__init__.py +18 -0
  596. package/tools/scan/scanners/base.py +137 -0
  597. package/tools/scan/scanners/environment.py +349 -0
  598. package/tools/scan/scanners/git.py +570 -0
  599. package/tools/scan/scanners/infrastructure.py +875 -0
  600. package/tools/scan/scanners/orchestration.py +600 -0
  601. package/tools/scan/scanners/stack.py +1085 -0
  602. package/tools/scan/scanners/tools.py +260 -0
  603. package/tools/scan/setup.py +686 -0
  604. package/tools/scan/tests/__init__.py +1 -0
  605. package/tools/scan/tests/conftest.py +796 -0
  606. package/tools/scan/tests/test_environment.py +323 -0
  607. package/tools/scan/tests/test_git.py +419 -0
  608. package/tools/scan/tests/test_infrastructure.py +382 -0
  609. package/tools/scan/tests/test_integration.py +920 -0
  610. package/tools/scan/tests/test_merge.py +269 -0
  611. package/tools/scan/tests/test_orchestration.py +304 -0
  612. package/tools/scan/tests/test_stack.py +604 -0
  613. package/tools/scan/tests/test_tools.py +349 -0
  614. package/tools/scan/ui.py +624 -0
  615. package/tools/scan/verify.py +270 -0
  616. package/tools/scan/walk.py +118 -0
  617. package/tools/scan/workspace.py +85 -0
  618. package/tools/validation/README.md +244 -0
  619. package/tools/validation/__init__.py +17 -0
  620. package/tools/validation/approval_gate.py +321 -0
  621. package/tools/validation/validate_skills.py +189 -0
@@ -0,0 +1,317 @@
1
+ """
2
+ Context anchor hit tracking for project context effectiveness measurement.
3
+
4
+ Extracts "anchors" (paths, names, IDs) from injected project context and checks
5
+ whether the agent's early tool calls reference them. This measures whether agents
6
+ use injected context as search anchors versus discovering on their own.
7
+
8
+ Provides:
9
+ - extract_anchors(): Extract searchable anchors from a context payload
10
+ - save_anchors(): Persist anchors to a session-scoped temp file
11
+ - load_anchors(): Load persisted anchors for a session
12
+ - extract_tool_calls_from_transcript(): Parse early tool calls from JSONL transcript
13
+ - compute_anchor_hits(): Compare tool call args against anchors
14
+ """
15
+
16
+ import json
17
+ import logging
18
+ import re
19
+ from pathlib import Path
20
+ from typing import Any, Dict, List, Optional, Set
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # How many early tool calls to check
25
+ MAX_TOOL_CALLS_TO_CHECK = 5
26
+
27
+ # Tool types that have inspectable path/keyword arguments
28
+ TRACKABLE_TOOLS = {"Glob", "Grep", "Read", "Bash"}
29
+
30
+ # Minimum anchor length to avoid false-positive matches on short strings
31
+ MIN_ANCHOR_LENGTH = 4
32
+
33
+
34
+ def _anchors_dir() -> Path:
35
+ """Return the directory for anchor temp files."""
36
+ return Path("/tmp/gaia-context-anchors")
37
+
38
+
39
+ def extract_anchors(context_payload: Dict[str, Any]) -> Set[str]:
40
+ """Extract searchable anchor strings from a context payload.
41
+
42
+ Walks the project knowledge sections and collects values from fields that
43
+ are likely to appear in agent tool calls: paths, names, IDs, clusters,
44
+ regions, namespaces, service accounts.
45
+
46
+ Args:
47
+ context_payload: The full context JSON payload injected into agent prompt.
48
+
49
+ Returns:
50
+ Set of anchor strings (paths, names, identifiers).
51
+ """
52
+ anchors: Set[str] = set()
53
+ contract = context_payload.get("project_knowledge", {})
54
+
55
+ # Anchor-worthy field name patterns
56
+ anchor_fields = re.compile(
57
+ r"(path|name|cluster|project|region|namespace|service|image|"
58
+ r"base_path|config_path|module_path|repository|bucket|sa$|"
59
+ r"service_account|pod_name|terragrunt_path)",
60
+ re.IGNORECASE,
61
+ )
62
+
63
+ def _walk(obj: Any, depth: int = 0) -> None:
64
+ if depth > 10:
65
+ return
66
+ if isinstance(obj, dict):
67
+ for key, value in obj.items():
68
+ if isinstance(value, str) and value and anchor_fields.search(key):
69
+ # Normalize: strip leading ./ for path matching
70
+ clean = value.lstrip("./")
71
+ if len(clean) >= MIN_ANCHOR_LENGTH:
72
+ anchors.add(clean)
73
+ elif isinstance(value, (dict, list)):
74
+ _walk(value, depth + 1)
75
+ elif isinstance(obj, list):
76
+ for item in obj:
77
+ _walk(item, depth + 1)
78
+
79
+ _walk(contract)
80
+
81
+ # Also extract from top-level metadata
82
+ metadata = context_payload.get("metadata", {})
83
+ for key in ("project_id", "cluster_name", "region"):
84
+ val = metadata.get(key)
85
+ if isinstance(val, str) and len(val) >= MIN_ANCHOR_LENGTH:
86
+ anchors.add(val)
87
+
88
+ return anchors
89
+
90
+
91
+ def save_anchors(session_id: str, agent_type: str, anchors: Set[str]) -> Optional[Path]:
92
+ """Persist anchors to a session+agent-scoped temp file.
93
+
94
+ Args:
95
+ session_id: Current session identifier.
96
+ agent_type: Agent name (e.g. "terraform-architect").
97
+ anchors: Set of anchor strings to save.
98
+
99
+ Returns:
100
+ Path to the saved file, or None on failure.
101
+ """
102
+ if not anchors:
103
+ return None
104
+
105
+ try:
106
+ anchor_dir = _anchors_dir()
107
+ anchor_dir.mkdir(parents=True, exist_ok=True)
108
+
109
+ safe_session = re.sub(r"[^a-zA-Z0-9_-]", "_", session_id or "unknown")[:32]
110
+ safe_agent = re.sub(r"[^a-zA-Z0-9_-]", "_", agent_type or "unknown")[:32]
111
+ anchor_file = anchor_dir / f"{safe_session}-{safe_agent}.json"
112
+
113
+ anchor_file.write_text(json.dumps(sorted(anchors)))
114
+ logger.debug(
115
+ "Saved %d anchors for %s/%s -> %s",
116
+ len(anchors), session_id, agent_type, anchor_file,
117
+ )
118
+ return anchor_file
119
+ except Exception as e:
120
+ logger.debug("Failed to save anchors: %s", e)
121
+ return None
122
+
123
+
124
+ def load_anchors(session_id: str, agent_type: str) -> Set[str]:
125
+ """Load persisted anchors for a session+agent.
126
+
127
+ Args:
128
+ session_id: Current session identifier.
129
+ agent_type: Agent name.
130
+
131
+ Returns:
132
+ Set of anchor strings, or empty set if not found.
133
+ """
134
+ try:
135
+ safe_session = re.sub(r"[^a-zA-Z0-9_-]", "_", session_id or "unknown")[:32]
136
+ safe_agent = re.sub(r"[^a-zA-Z0-9_-]", "_", agent_type or "unknown")[:32]
137
+ anchor_file = _anchors_dir() / f"{safe_session}-{safe_agent}.json"
138
+
139
+ if not anchor_file.exists():
140
+ return set()
141
+
142
+ data = json.loads(anchor_file.read_text())
143
+ return set(data) if isinstance(data, list) else set()
144
+ except Exception as e:
145
+ logger.debug("Failed to load anchors: %s", e)
146
+ return set()
147
+
148
+
149
+ def extract_tool_calls_from_transcript(
150
+ transcript_path: str,
151
+ max_calls: int = MAX_TOOL_CALLS_TO_CHECK,
152
+ ) -> List[Dict[str, Any]]:
153
+ """Extract the first N trackable tool calls from a Claude Code transcript JSONL.
154
+
155
+ Claude Code transcripts contain tool_use entries in the assistant messages
156
+ (content blocks with type "tool_use").
157
+
158
+ Args:
159
+ transcript_path: Path to the transcript JSONL file.
160
+ max_calls: Maximum number of tool calls to extract.
161
+
162
+ Returns:
163
+ List of dicts with keys: tool_name, arguments (dict), call_index (1-based).
164
+ """
165
+ if not transcript_path:
166
+ return []
167
+
168
+ try:
169
+ path = Path(transcript_path).expanduser()
170
+ if not path.exists():
171
+ return []
172
+
173
+ tool_calls: List[Dict[str, Any]] = []
174
+ call_index = 0
175
+
176
+ for line in path.read_text().strip().splitlines():
177
+ if not line.strip():
178
+ continue
179
+ if call_index >= max_calls:
180
+ break
181
+
182
+ try:
183
+ entry = json.loads(line)
184
+ msg = entry.get("message", entry)
185
+
186
+ if msg.get("role") != "assistant":
187
+ continue
188
+
189
+ content = msg.get("content", [])
190
+ if not isinstance(content, list):
191
+ continue
192
+
193
+ for block in content:
194
+ if call_index >= max_calls:
195
+ break
196
+ if not isinstance(block, dict):
197
+ continue
198
+ if block.get("type") != "tool_use":
199
+ continue
200
+
201
+ tool_name = block.get("name", "")
202
+ if tool_name not in TRACKABLE_TOOLS:
203
+ continue
204
+
205
+ call_index += 1
206
+ tool_calls.append({
207
+ "tool_name": tool_name,
208
+ "arguments": block.get("input", {}),
209
+ "call_index": call_index,
210
+ })
211
+
212
+ except (json.JSONDecodeError, TypeError):
213
+ continue
214
+
215
+ return tool_calls
216
+
217
+ except Exception as e:
218
+ logger.debug("Failed to extract tool calls from transcript: %s", e)
219
+ return []
220
+
221
+
222
+ def _extract_searchable_text(tool_name: str, arguments: Dict[str, Any]) -> str:
223
+ """Extract the searchable text from a tool call's arguments.
224
+
225
+ Returns a single string containing all path/keyword-relevant arguments
226
+ concatenated for substring matching.
227
+ """
228
+ parts: List[str] = []
229
+
230
+ if tool_name == "Glob":
231
+ parts.append(arguments.get("pattern", ""))
232
+ parts.append(arguments.get("path", ""))
233
+ elif tool_name == "Grep":
234
+ parts.append(arguments.get("pattern", ""))
235
+ parts.append(arguments.get("path", ""))
236
+ parts.append(arguments.get("glob", ""))
237
+ elif tool_name == "Read":
238
+ parts.append(arguments.get("file_path", ""))
239
+ elif tool_name == "Bash":
240
+ parts.append(arguments.get("command", ""))
241
+
242
+ return " ".join(p for p in parts if p)
243
+
244
+
245
+ def compute_anchor_hits(
246
+ tool_calls: List[Dict[str, Any]],
247
+ anchors: Set[str],
248
+ ) -> Dict[str, Any]:
249
+ """Compare tool call arguments against known anchors.
250
+
251
+ For each tool call, checks if any anchor appears as a substring in the
252
+ tool's searchable arguments. This is a lightweight prefix/substring match.
253
+
254
+ Args:
255
+ tool_calls: List from extract_tool_calls_from_transcript().
256
+ anchors: Set of anchor strings from extract_anchors().
257
+
258
+ Returns:
259
+ Dict with hit tracking data.
260
+ """
261
+ if not tool_calls or not anchors:
262
+ return {
263
+ "total_checked": len(tool_calls),
264
+ "hits": 0,
265
+ "hit_rate": 0.0,
266
+ "details": [],
267
+ }
268
+
269
+ details: List[Dict[str, Any]] = []
270
+ hits = 0
271
+
272
+ for call in tool_calls:
273
+ searchable = _extract_searchable_text(call["tool_name"], call["arguments"])
274
+ matched_anchor: Optional[str] = None
275
+
276
+ if searchable:
277
+ for anchor in anchors:
278
+ if anchor in searchable:
279
+ matched_anchor = anchor
280
+ break
281
+
282
+ is_hit = matched_anchor is not None
283
+ if is_hit:
284
+ hits += 1
285
+
286
+ details.append({
287
+ "call_index": call["call_index"],
288
+ "tool": call["tool_name"],
289
+ "anchor": matched_anchor,
290
+ "hit": is_hit,
291
+ })
292
+
293
+ total = len(tool_calls)
294
+ return {
295
+ "total_checked": total,
296
+ "hits": hits,
297
+ "hit_rate": round(hits / total, 2) if total > 0 else 0.0,
298
+ "details": details,
299
+ }
300
+
301
+
302
+ def cleanup_anchors(session_id: str, agent_type: str) -> None:
303
+ """Remove the anchor temp file after use.
304
+
305
+ Args:
306
+ session_id: Current session identifier.
307
+ agent_type: Agent name.
308
+ """
309
+ try:
310
+ safe_session = re.sub(r"[^a-zA-Z0-9_-]", "_", session_id or "unknown")[:32]
311
+ safe_agent = re.sub(r"[^a-zA-Z0-9_-]", "_", agent_type or "unknown")[:32]
312
+ anchor_file = _anchors_dir() / f"{safe_session}-{safe_agent}.json"
313
+ if anchor_file.exists():
314
+ anchor_file.unlink()
315
+ logger.debug("Cleaned up anchor file: %s", anchor_file)
316
+ except Exception as e:
317
+ logger.debug("Failed to cleanup anchors: %s", e)
@@ -0,0 +1,218 @@
1
+ """Compact context builder for post-compaction re-injection.
2
+
3
+ Builds a lightweight context summary from session data sources.
4
+ Each source is independent and fail-safe.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import logging
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+
13
+ from ..core.paths import get_plugin_data_dir
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Defaults
18
+ DEFAULT_MAX_SNAPSHOTS = 5
19
+ DEFAULT_ANOMALY_WINDOW_HOURS = 1
20
+ DEFAULT_MAX_EVENTS = 5
21
+
22
+
23
+ def build_compact_context(
24
+ *,
25
+ max_snapshots: int = DEFAULT_MAX_SNAPSHOTS,
26
+ anomaly_window_hours: int = DEFAULT_ANOMALY_WINDOW_HOURS,
27
+ max_events: int = DEFAULT_MAX_EVENTS,
28
+ ) -> str:
29
+ """Build compact context for post-compaction re-injection.
30
+
31
+ Returns a markdown string with 4 blocks:
32
+ 1. Orchestrator identity reminder
33
+ 2. Session activity summary (from run-snapshots.jsonl)
34
+ 3. Active anomalies (from anomalies.jsonl)
35
+ 4. Recent session events (from context.json)
36
+
37
+ Each block is independent — if a source fails, the others still produce output.
38
+ """
39
+ blocks = []
40
+
41
+ # Block 1: Orchestrator identity (always present, static)
42
+ blocks.append(_build_identity_block())
43
+
44
+ # Block 2: Session activity from run-snapshots.jsonl
45
+ activity = _build_activity_block(max_snapshots)
46
+ if activity:
47
+ blocks.append(activity)
48
+
49
+ # Block 3: Active anomalies from anomalies.jsonl
50
+ anomalies = _build_anomalies_block(anomaly_window_hours)
51
+ if anomalies:
52
+ blocks.append(anomalies)
53
+
54
+ # Block 4: Recent events from context.json
55
+ events = _build_events_block(max_events)
56
+ if events:
57
+ blocks.append(events)
58
+
59
+ return "\n\n".join(blocks)
60
+
61
+
62
+ def _build_identity_block() -> str:
63
+ """Minimal post-compaction identity reminder.
64
+
65
+ Full identity lives in agents/gaia-orchestrator.md and is injected at
66
+ session start. This block only restores the core posture after context
67
+ compaction — it intentionally does NOT list specific agents because
68
+ the agent roster can change and a stale list causes drift.
69
+ """
70
+ return (
71
+ "# Post-Compaction Context Refresh\n\n"
72
+ "You are the orchestrator. Dispatch work via Agent, resume agents via "
73
+ "SendMessage(to: agentId), get user approval via AskUserQuestion."
74
+ )
75
+
76
+
77
+ def _build_activity_block(max_snapshots: int) -> str | None:
78
+ """Build session activity summary from run-snapshots.jsonl."""
79
+ snapshots_path = (
80
+ get_plugin_data_dir() / "project-context" / "workflow-episodic-memory" / "run-snapshots.jsonl"
81
+ )
82
+ if not snapshots_path.exists():
83
+ return None
84
+
85
+ try:
86
+ lines = snapshots_path.read_text().splitlines()
87
+ # Take last N lines
88
+ recent = lines[-max_snapshots:] if len(lines) > max_snapshots else lines
89
+
90
+ entries = []
91
+ for line in recent:
92
+ if not line.strip():
93
+ continue
94
+ try:
95
+ snap = json.loads(line)
96
+ agent = snap.get("agent", "unknown")
97
+ status = snap.get("plan_status", "unknown")
98
+ prompt = snap.get("prompt", "")[:80]
99
+ cmd_count = snap.get("commands_executed_count", 0)
100
+ entries.append(f"- {agent} → {status} ({prompt}, {cmd_count} commands)")
101
+ except json.JSONDecodeError:
102
+ continue
103
+
104
+ if not entries:
105
+ return None
106
+
107
+ return "## Session Activity\n" + "\n".join(entries)
108
+
109
+ except Exception as e:
110
+ logger.debug("Failed to build activity block (non-fatal): %s", e)
111
+ return None
112
+
113
+
114
+ def _build_anomalies_block(window_hours: int) -> str | None:
115
+ """Build active anomalies summary from anomalies.jsonl."""
116
+ anomaly_path = (
117
+ get_plugin_data_dir() / "project-context" / "workflow-episodic-memory" / "anomalies.jsonl"
118
+ )
119
+ if not anomaly_path.exists():
120
+ return None
121
+
122
+ try:
123
+ lines = anomaly_path.read_text().splitlines()[-20:]
124
+ cutoff = datetime.now().timestamp() - (window_hours * 3600)
125
+
126
+ critical_types: list[str] = []
127
+ warning_types: list[str] = []
128
+
129
+ for line in lines:
130
+ if not line.strip():
131
+ continue
132
+ try:
133
+ entry = json.loads(line)
134
+ ts = entry.get("timestamp", "")
135
+ if ts:
136
+ try:
137
+ entry_time = datetime.fromisoformat(ts).timestamp()
138
+ if entry_time < cutoff:
139
+ continue
140
+ except (ValueError, TypeError):
141
+ continue
142
+
143
+ for anomaly in entry.get("anomalies", []):
144
+ severity = anomaly.get("severity", "")
145
+ atype = anomaly.get("type", "unknown")
146
+ if severity == "critical":
147
+ critical_types.append(atype)
148
+ elif severity == "warning":
149
+ warning_types.append(atype)
150
+ except json.JSONDecodeError:
151
+ continue
152
+
153
+ if not critical_types and not warning_types:
154
+ return None
155
+
156
+ parts = []
157
+ if critical_types:
158
+ unique = sorted(set(critical_types))
159
+ parts.append(f"- {len(critical_types)} critical: {', '.join(unique)}")
160
+ if warning_types:
161
+ unique = sorted(set(warning_types))
162
+ parts.append(f"- {len(warning_types)} warning: {', '.join(unique)}")
163
+
164
+ return "## Active Anomalies\n" + "\n".join(parts)
165
+
166
+ except Exception as e:
167
+ logger.debug("Failed to build anomalies block (non-fatal): %s", e)
168
+ return None
169
+
170
+
171
+ def _build_events_block(max_events: int) -> str | None:
172
+ """Build recent events summary from session context.json."""
173
+ context_path = Path(".claude/session/active/context.json")
174
+ if not context_path.exists():
175
+ return None
176
+
177
+ try:
178
+ with open(context_path) as f:
179
+ context = json.load(f)
180
+
181
+ events = context.get("critical_events", [])
182
+ if not events:
183
+ return None
184
+
185
+ # Take last N events
186
+ recent = events[-max_events:]
187
+
188
+ lines = []
189
+ for event in recent:
190
+ etype = event.get("event_type", "")
191
+ ts = event.get("timestamp", "")[:16]
192
+
193
+ if etype == "git_commit":
194
+ msg = event.get("commit_message", "")
195
+ hash_val = event.get("commit_hash", "")[:7]
196
+ if hash_val and msg:
197
+ lines.append(f"- [{ts}] Commit {hash_val}: {msg}")
198
+ elif etype == "git_push":
199
+ branch = event.get("branch", "")
200
+ if branch:
201
+ lines.append(f"- [{ts}] Pushed to {branch}")
202
+ elif etype == "file_modifications":
203
+ count = event.get("modification_count", 0)
204
+ if count:
205
+ lines.append(f"- [{ts}] Modified {count} files")
206
+ elif etype == "infrastructure_change":
207
+ cmd = event.get("command", "")
208
+ if cmd:
209
+ lines.append(f"- [{ts}] Infrastructure: {cmd}")
210
+
211
+ if not lines:
212
+ return None
213
+
214
+ return "## Recent Events\n" + "\n".join(lines)
215
+
216
+ except Exception as e:
217
+ logger.debug("Failed to build events block (non-fatal): %s", e)
218
+ return None
@@ -0,0 +1,145 @@
1
+ """
2
+ Context freshness checker for SessionStart hook.
3
+
4
+ Determines whether project-context.json is fresh enough to skip a rescan.
5
+ Uses metadata.scan_config.last_scan (preferred) or file mtime as fallback.
6
+
7
+ Public API:
8
+ - check_freshness(project_root: Path) -> FreshnessResult
9
+ """
10
+
11
+ import json
12
+ import logging
13
+ import os
14
+ from dataclasses import dataclass
15
+ from datetime import datetime, timedelta, timezone
16
+ from pathlib import Path
17
+ from typing import Optional
18
+
19
+ from ..core.paths import find_claude_dir
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Context freshness threshold (hours) -- env var overrides default
24
+ DEFAULT_FRESHNESS_HOURS = 24
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class FreshnessResult:
29
+ """Result of a context freshness check."""
30
+
31
+ is_fresh: bool
32
+ reason: str
33
+ age_hours: float = 0.0
34
+
35
+
36
+ def _get_context_path() -> Path:
37
+ """Return path to project-context.json."""
38
+ claude_dir = find_claude_dir()
39
+ return claude_dir / "project-context" / "project-context.json"
40
+
41
+
42
+ def _read_staleness_from_context(context_path: Path) -> Optional[int]:
43
+ """Read staleness_hours from metadata.scan_config in the context file.
44
+
45
+ Returns None if the file cannot be read or the field is absent.
46
+ """
47
+ if not context_path.is_file():
48
+ return None
49
+ try:
50
+ with open(context_path, "r") as f:
51
+ data = json.load(f)
52
+ return (
53
+ int(
54
+ data.get("metadata", {})
55
+ .get("scan_config", {})
56
+ .get("staleness_hours", 0)
57
+ )
58
+ or None
59
+ )
60
+ except (json.JSONDecodeError, OSError, ValueError, TypeError):
61
+ return None
62
+
63
+
64
+ def _get_effective_threshold() -> int:
65
+ """Determine the effective freshness threshold in hours."""
66
+ return int(
67
+ os.environ.get(
68
+ "GAIA_SCAN_STALENESS_HOURS",
69
+ os.environ.get("CONTEXT_FRESHNESS_HOURS", str(DEFAULT_FRESHNESS_HOURS)),
70
+ )
71
+ )
72
+
73
+
74
+ def check_freshness(project_root: Path = None) -> FreshnessResult:
75
+ """Check if project-context.json exists and is fresh (< threshold).
76
+
77
+ Args:
78
+ project_root: Unused, kept for API compatibility. Context path
79
+ is resolved via find_claude_dir().
80
+
81
+ Returns:
82
+ FreshnessResult with is_fresh, reason, and age_hours.
83
+ """
84
+ context_path = _get_context_path()
85
+
86
+ if not context_path.exists():
87
+ logger.info("project-context.json not found at %s", context_path)
88
+ return FreshnessResult(is_fresh=False, reason="missing", age_hours=0.0)
89
+
90
+ # Determine effective threshold: env var > context file > default
91
+ effective_hours = _get_effective_threshold()
92
+ ctx_hours = _read_staleness_from_context(context_path)
93
+ if ctx_hours and not os.environ.get("GAIA_SCAN_STALENESS_HOURS"):
94
+ effective_hours = ctx_hours
95
+
96
+ try:
97
+ # Try metadata.scan_config.last_scan first (more accurate)
98
+ with open(context_path, "r") as f:
99
+ data = json.load(f)
100
+ last_scan = data.get("metadata", {}).get("scan_config", {}).get("last_scan")
101
+
102
+ if last_scan:
103
+ scan_dt = datetime.fromisoformat(last_scan)
104
+ now = datetime.now(timezone.utc)
105
+ age = now - scan_dt
106
+ age_hours = age.total_seconds() / 3600.0
107
+ threshold = timedelta(hours=effective_hours)
108
+
109
+ if age > threshold:
110
+ logger.info(
111
+ "project-context.json is stale (last_scan age: %s, threshold: %sh)",
112
+ age,
113
+ effective_hours,
114
+ )
115
+ return FreshnessResult(
116
+ is_fresh=False, reason="stale", age_hours=age_hours
117
+ )
118
+
119
+ logger.debug("project-context.json is fresh (last_scan age: %s)", age)
120
+ return FreshnessResult(
121
+ is_fresh=True, reason="fresh", age_hours=age_hours
122
+ )
123
+
124
+ # Fallback: use file mtime
125
+ mtime = datetime.fromtimestamp(context_path.stat().st_mtime)
126
+ age = datetime.now() - mtime
127
+ age_hours = age.total_seconds() / 3600.0
128
+ threshold = timedelta(hours=effective_hours)
129
+
130
+ if age > threshold:
131
+ logger.info(
132
+ "project-context.json is stale (mtime age: %s, threshold: %sh)",
133
+ age,
134
+ effective_hours,
135
+ )
136
+ return FreshnessResult(
137
+ is_fresh=False, reason="stale", age_hours=age_hours
138
+ )
139
+
140
+ logger.debug("project-context.json is fresh (mtime age: %s)", age)
141
+ return FreshnessResult(is_fresh=True, reason="fresh", age_hours=age_hours)
142
+
143
+ except Exception as e:
144
+ logger.warning("Error checking context freshness: %s", e)
145
+ return FreshnessResult(is_fresh=False, reason="error", age_hours=0.0)