@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,530 @@
1
+ """
2
+ ContextWriter module for gaia-ops progressive context enrichment.
3
+
4
+ Parses CONTEXT_UPDATE blocks from agent output, validates write permissions
5
+ against contracts, and applies deep-merge updates to project-context.json.
6
+
7
+ Public API:
8
+ - parse_context_update(agent_output) -> Optional[dict]
9
+ - validate_permissions(update, agent_type, contracts) -> (dict, list)
10
+ - apply_update(context_path, update, agent_type) -> dict
11
+ - load_contracts(provider, config_dir) -> dict
12
+ - process_agent_output(agent_output, task_info) -> dict
13
+ """
14
+
15
+ import json
16
+ import logging
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Dict, List, Optional
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Dynamic import: deep_merge from tools/context/deep_merge.py
26
+ # ---------------------------------------------------------------------------
27
+ def _import_deep_merge():
28
+ """Import deep_merge function from tools/context/deep_merge.py."""
29
+ import importlib.util
30
+ import sys
31
+
32
+ # Resolve paths relative to this file:
33
+ # this file: hooks/modules/context/context_writer.py
34
+ # deep_merge: tools/context/deep_merge.py
35
+ hooks_dir = Path(__file__).resolve().parents[2] # hooks/
36
+ repo_root = hooks_dir.parent # repo root
37
+ deep_merge_path = repo_root / "tools" / "context" / "deep_merge.py"
38
+
39
+ if not deep_merge_path.exists():
40
+ raise ImportError(f"deep_merge.py not found at {deep_merge_path}")
41
+
42
+ spec = importlib.util.spec_from_file_location("deep_merge", str(deep_merge_path))
43
+ module = importlib.util.module_from_spec(spec)
44
+ sys.modules["deep_merge"] = module
45
+ spec.loader.exec_module(module)
46
+ return module.deep_merge
47
+
48
+
49
+ _deep_merge = _import_deep_merge()
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # LEGACY_AGENT_CONTRACTS (fallback when no contracts file exists)
54
+ # In legacy mode, write permissions = read permissions.
55
+ # ---------------------------------------------------------------------------
56
+ LEGACY_AGENT_CONTRACTS: Dict[str, List[str]] = {
57
+ "terraform-architect": [
58
+ "project_identity",
59
+ "stack",
60
+ "git",
61
+ "environment",
62
+ "infrastructure",
63
+ "terraform_infrastructure",
64
+ "infrastructure_topology",
65
+ "operational_guidelines",
66
+ ],
67
+ "gitops-operator": [
68
+ "project_identity",
69
+ "stack",
70
+ "git",
71
+ "environment",
72
+ "infrastructure",
73
+ "gitops_configuration",
74
+ "infrastructure_topology",
75
+ "cluster_details",
76
+ "operational_guidelines",
77
+ ],
78
+ "cloud-troubleshooter": [
79
+ "project_identity",
80
+ "stack",
81
+ "git",
82
+ "environment",
83
+ "infrastructure",
84
+ "infrastructure_topology",
85
+ "terraform_infrastructure",
86
+ "gitops_configuration",
87
+ "application_services",
88
+ "monitoring_observability",
89
+ "cluster_details",
90
+ ],
91
+ "developer": [
92
+ "project_identity",
93
+ "stack",
94
+ "git",
95
+ "environment",
96
+ "infrastructure",
97
+ "application_services",
98
+ "operational_guidelines",
99
+ ],
100
+ "gaia-operator": [
101
+ "project_identity",
102
+ "stack",
103
+ "git",
104
+ "environment",
105
+ "infrastructure",
106
+ "infrastructure_topology",
107
+ "application_services",
108
+ "architecture_overview",
109
+ "operational_guidelines",
110
+ "workspace_repos",
111
+ ],
112
+ }
113
+
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # Module-level cache for load_contracts
117
+ # ---------------------------------------------------------------------------
118
+ _contracts_cache: Dict[str, dict] = {}
119
+
120
+
121
+ # ============================================================================
122
+ # 1. parse_context_update
123
+ # ============================================================================
124
+
125
+ def parse_context_update(agent_output: str) -> Optional[dict]:
126
+ """Extract and parse a CONTEXT_UPDATE block from agent output.
127
+
128
+ Searches for the ``CONTEXT_UPDATE:`` marker (case-sensitive, on its own
129
+ line), extracts the JSON that follows until end-of-output or the next
130
+ known marker, and returns the parsed dict.
131
+
132
+ Returns None when:
133
+ - No marker is found
134
+ - The JSON is malformed
135
+ - The parsed value is not a dict
136
+ """
137
+ # Find the CONTEXT_UPDATE: marker on its own line
138
+ marker = "CONTEXT_UPDATE:"
139
+ lines = agent_output.split("\n")
140
+
141
+ marker_idx = None
142
+ for i, line in enumerate(lines):
143
+ if line.strip() == marker:
144
+ marker_idx = i
145
+ break
146
+
147
+ if marker_idx is None:
148
+ return None
149
+
150
+ # Collect all text after the marker
151
+ remaining = "\n".join(lines[marker_idx + 1:]).strip()
152
+
153
+ if not remaining:
154
+ return None
155
+
156
+ # Strip markdown code fences — LLMs reading SKILL.md documentation
157
+ # often wrap the JSON in ```json ... ``` or ``` ... ``` blocks.
158
+ if remaining.startswith("```"):
159
+ fence_lines = remaining.split("\n")
160
+ # Remove opening fence (```json, ```JSON, ```, etc.)
161
+ fence_lines.pop(0)
162
+ # Remove closing fence if present
163
+ for i in range(len(fence_lines) - 1, -1, -1):
164
+ if fence_lines[i].strip() == "```":
165
+ fence_lines.pop(i)
166
+ break
167
+ remaining = "\n".join(fence_lines).strip()
168
+
169
+ if not remaining:
170
+ return None
171
+
172
+ # Use raw_decode to extract the first complete JSON value, ignoring
173
+ # any trailing text (summaries, AGENT_STATUS blocks, etc.)
174
+ decoder = json.JSONDecoder()
175
+ try:
176
+ parsed, _ = decoder.raw_decode(remaining)
177
+ except (json.JSONDecodeError, ValueError) as exc:
178
+ logger.warning("Malformed JSON in CONTEXT_UPDATE block: %s", exc)
179
+ return None
180
+
181
+ if not isinstance(parsed, dict):
182
+ return None
183
+
184
+ return parsed
185
+
186
+
187
+ # ============================================================================
188
+ # 2. validate_permissions
189
+ # ============================================================================
190
+
191
+ def validate_permissions(
192
+ update: dict,
193
+ agent_type: str,
194
+ contracts: dict,
195
+ ) -> tuple:
196
+ """Validate which sections the agent is allowed to write.
197
+
198
+ Returns ``(allowed_updates, rejected_sections)`` where:
199
+ - ``allowed_updates``: dict with only permitted sections
200
+ - ``rejected_sections``: list of section names that were rejected
201
+ """
202
+ allowed: dict = {}
203
+ rejected: List[str] = []
204
+
205
+ # Determine writable sections for this agent
206
+ agents_map = contracts.get("agents", {})
207
+
208
+ if agent_type in agents_map:
209
+ # Use explicit write list from contracts file
210
+ writable = set(agents_map[agent_type].get("write", []))
211
+ elif agent_type in LEGACY_AGENT_CONTRACTS:
212
+ # Fallback: in legacy mode, write = read
213
+ writable = set(LEGACY_AGENT_CONTRACTS[agent_type])
214
+ else:
215
+ # Unknown agent: no permissions
216
+ writable = set()
217
+
218
+ for section, data in update.items():
219
+ if section in writable:
220
+ allowed[section] = data
221
+ else:
222
+ rejected.append(section)
223
+
224
+ return allowed, rejected
225
+
226
+
227
+ # ============================================================================
228
+ # 3. apply_update
229
+ # ============================================================================
230
+
231
+ def apply_update(
232
+ context_path: Path,
233
+ update: dict,
234
+ agent_type: str,
235
+ ) -> dict:
236
+ """Apply a validated update to project-context.json using deep merge.
237
+
238
+ Performs an atomic write (write to .tmp, then rename) and appends an
239
+ audit entry to ``context-audit.jsonl`` in the same directory.
240
+
241
+ Returns an audit entry dict. Never raises on I/O errors.
242
+ """
243
+ context_path = Path(context_path)
244
+ timestamp = datetime.now(timezone.utc).isoformat()
245
+ sections_updated = list(update.keys())
246
+
247
+ audit_entry = {
248
+ "timestamp": timestamp,
249
+ "agent": agent_type,
250
+ "sections_updated": sections_updated,
251
+ "changes": {},
252
+ "success": False,
253
+ "error": None,
254
+ }
255
+
256
+ try:
257
+ # Read current context
258
+ current = json.loads(context_path.read_text())
259
+
260
+ # Deep merge each section
261
+ all_changes = {}
262
+ for section, section_data in update.items():
263
+ current_section = current.get("sections", {}).get(section, {})
264
+ merged_section, diff = _deep_merge(current_section, section_data)
265
+
266
+ # Ensure sections dict exists
267
+ if "sections" not in current:
268
+ current["sections"] = {}
269
+ current["sections"][section] = merged_section
270
+
271
+ if diff:
272
+ all_changes[section] = diff
273
+
274
+ # Update metadata timestamp
275
+ if "metadata" not in current:
276
+ current["metadata"] = {}
277
+ current["metadata"]["last_updated"] = timestamp
278
+
279
+ # Atomic write: write to .tmp, then rename
280
+ tmp_path = context_path.with_suffix(".tmp")
281
+ tmp_path.write_text(json.dumps(current, indent=2))
282
+ tmp_path.rename(context_path)
283
+
284
+ audit_entry["changes"] = all_changes
285
+ audit_entry["success"] = True
286
+
287
+ except Exception as exc:
288
+ logger.error("Failed to apply context update: %s", exc)
289
+ audit_entry["error"] = str(exc)
290
+ audit_entry["success"] = False
291
+ return audit_entry
292
+
293
+ # Append audit entry to context-audit.jsonl
294
+ try:
295
+ audit_file = context_path.parent / "context-audit.jsonl"
296
+ with open(audit_file, "a") as f:
297
+ f.write(json.dumps(audit_entry) + "\n")
298
+ except Exception as exc:
299
+ logger.warning("Failed to write audit entry: %s", exc)
300
+ # Audit write failure doesn't affect the main result
301
+
302
+ return audit_entry
303
+
304
+
305
+ # ============================================================================
306
+ # 4. load_contracts
307
+ # ============================================================================
308
+
309
+ def load_contracts(provider: str, config_dir: Path) -> dict:
310
+ """Load agent contracts using the base+cloud merge strategy, with caching.
311
+
312
+ Strategy (in priority order):
313
+ 1. Load base contracts from context-contracts.json (cloud-agnostic)
314
+ 2. Merge cloud overrides from cloud/{provider}.json (extend read/write lists)
315
+ 3. Fallback: try legacy context-contracts.{provider}.json
316
+ 4. Final fallback: LEGACY_AGENT_CONTRACTS hardcoded dict (write = read)
317
+
318
+ Results are cached by provider string.
319
+ """
320
+ config_dir = Path(config_dir)
321
+
322
+ # Check cache first
323
+ if provider in _contracts_cache:
324
+ return _contracts_cache[provider]
325
+
326
+ result = _merge_base_and_cloud(provider, config_dir)
327
+ _contracts_cache[provider] = result
328
+ return result
329
+
330
+
331
+ def _merge_base_and_cloud(provider: str, config_dir: Path) -> dict:
332
+ """Load and merge base + cloud/{provider}.json contracts.
333
+
334
+ Returns a merged contracts dict with 'agents' keyed by agent name,
335
+ each containing 'read' and 'write' lists.
336
+ """
337
+ base_file = config_dir / "context-contracts.json"
338
+ cloud_file = config_dir / "cloud" / f"{provider}.json"
339
+
340
+ # Step 1: Load base contracts
341
+ base_contracts = None
342
+ if base_file.exists():
343
+ try:
344
+ base_contracts = json.loads(base_file.read_text())
345
+ except (json.JSONDecodeError, OSError) as exc:
346
+ logger.warning("Failed to load base contracts from %s: %s", base_file, exc)
347
+
348
+ # Step 2: Final fallback to hardcoded LEGACY_AGENT_CONTRACTS
349
+ if base_contracts is None:
350
+ logger.warning("No contract files found in %s, using hardcoded legacy contracts", config_dir)
351
+ return {
352
+ "version": "legacy",
353
+ "provider": provider,
354
+ "agents": {
355
+ agent: {"read": sections, "write": list(sections)}
356
+ for agent, sections in LEGACY_AGENT_CONTRACTS.items()
357
+ },
358
+ }
359
+
360
+ # Step 4: Merge cloud-specific overrides
361
+ if cloud_file.exists():
362
+ try:
363
+ cloud_overrides = json.loads(cloud_file.read_text())
364
+ for agent_name, agent_overrides in cloud_overrides.get("agents", {}).items():
365
+ if agent_name in base_contracts.get("agents", {}):
366
+ existing_read = base_contracts["agents"][agent_name].get("read", [])
367
+ existing_write = base_contracts["agents"][agent_name].get("write", [])
368
+ extra_read = [s for s in agent_overrides.get("read", []) if s not in existing_read]
369
+ extra_write = [s for s in agent_overrides.get("write", []) if s not in existing_write]
370
+ base_contracts["agents"][agent_name]["read"] = existing_read + extra_read
371
+ base_contracts["agents"][agent_name]["write"] = existing_write + extra_write
372
+ else:
373
+ base_contracts["agents"][agent_name] = agent_overrides
374
+ logger.info("Merged %s cloud overrides from %s", provider.upper(), cloud_file)
375
+ except (json.JSONDecodeError, OSError) as exc:
376
+ logger.warning("Failed to load cloud overrides from %s: %s — skipping", cloud_file, exc)
377
+
378
+ return base_contracts
379
+
380
+
381
+ # ============================================================================
382
+ # 5. process_agent_output
383
+ # ============================================================================
384
+
385
+ def process_agent_output(agent_output: str, task_info: dict) -> dict:
386
+ """Orchestrate the full context-update flow.
387
+
388
+ Steps: parse -> detect provider -> load contracts -> validate -> apply.
389
+
390
+ Parameters
391
+ ----------
392
+ agent_output : str
393
+ Full agent output string.
394
+ task_info : dict
395
+ Must contain: ``agent_type``, ``context_path``, ``config_dir``.
396
+
397
+ Returns
398
+ -------
399
+ dict
400
+ ``{updated, sections/sections_updated, rejected, error}``
401
+ """
402
+ result = {
403
+ "updated": False,
404
+ "sections_updated": [],
405
+ "rejected": [],
406
+ "error": None,
407
+ }
408
+
409
+ # 1. Parse CONTEXT_UPDATE
410
+ update = parse_context_update(agent_output)
411
+ if update is None:
412
+ return result
413
+
414
+ agent_type = task_info.get("agent_type", "unknown")
415
+ context_path = Path(task_info.get("context_path", ""))
416
+ config_dir = Path(task_info.get("config_dir", ""))
417
+
418
+ # 2. Detect cloud provider from existing context metadata
419
+ provider = "gcp" # default
420
+ try:
421
+ if context_path.exists():
422
+ ctx = json.loads(context_path.read_text())
423
+ provider = ctx.get("metadata", {}).get("cloud_provider", "gcp").lower()
424
+ except Exception:
425
+ pass
426
+
427
+ # 3. Load contracts
428
+ # Clear cache to avoid cross-test pollution (keyed by provider+config_dir)
429
+ cache_key = f"{provider}:{config_dir}"
430
+ contracts = _load_contracts_with_dir(provider, config_dir)
431
+
432
+ # 4. Validate permissions
433
+ allowed, rejected = validate_permissions(update, agent_type, contracts)
434
+ result["rejected"] = rejected
435
+
436
+ # 5. If nothing allowed, return early
437
+ if not allowed:
438
+ return result
439
+
440
+ # 6. Apply update
441
+ audit = apply_update(context_path, allowed, agent_type)
442
+
443
+ if audit.get("success"):
444
+ result["updated"] = True
445
+ result["sections_updated"] = list(allowed.keys())
446
+ else:
447
+ result["error"] = audit.get("error")
448
+
449
+ return result
450
+
451
+
452
+ def _load_contracts_with_dir(provider: str, config_dir: Path) -> dict:
453
+ """Load contracts, bypassing the module-level cache for process_agent_output.
454
+
455
+ This ensures each call with a different config_dir gets fresh results
456
+ while still allowing load_contracts to cache for repeated calls with
457
+ the same provider. Uses the same base+cloud merge strategy as load_contracts.
458
+ """
459
+ return _merge_base_and_cloud(provider, Path(config_dir))
460
+
461
+
462
+ # ============================================================================
463
+ # 6. process_context_updates (thin wrapper for subagent_stop integration)
464
+ # ============================================================================
465
+
466
+ def process_context_updates(
467
+ agent_output: str,
468
+ task_info: dict,
469
+ find_claude_dir_fn=None,
470
+ ) -> Optional[dict]:
471
+ """
472
+ Process CONTEXT_UPDATE blocks from agent output via context_writer.
473
+
474
+ Loads project-context.json, resolves config_dir from .claude, and calls
475
+ process_agent_output() to apply progressive enrichment.
476
+
477
+ This function MUST NOT break the existing hook flow -- all errors are caught
478
+ and logged, returning None on failure.
479
+
480
+ Args:
481
+ agent_output: Complete output from agent execution
482
+ task_info: Task metadata (agent, description, task_id)
483
+ find_claude_dir_fn: Callable that returns the .claude Path. Defaults to
484
+ modules.core.paths.find_claude_dir if not provided.
485
+
486
+ Returns:
487
+ Result dict from process_agent_output, or None on error
488
+ """
489
+ try:
490
+ if find_claude_dir_fn is None:
491
+ from ..core.paths import find_claude_dir
492
+ find_claude_dir_fn = find_claude_dir
493
+
494
+ # Find project-context.json via find_claude_dir
495
+ claude_dir = find_claude_dir_fn()
496
+ context_path = claude_dir / "project-context" / "project-context.json"
497
+
498
+ if not context_path.exists():
499
+ logger.debug("project-context.json not found at %s, skipping context updates", context_path)
500
+ return None
501
+
502
+ # Determine config_dir (inside .claude directory)
503
+ config_dir = claude_dir / "config"
504
+
505
+ # Build task_info dict for process_agent_output
506
+ agent_type = task_info.get("agent", "unknown")
507
+ task_info_for_writer = {
508
+ "agent_type": agent_type,
509
+ "context_path": str(context_path),
510
+ "config_dir": str(config_dir),
511
+ }
512
+
513
+ result = process_agent_output(agent_output, task_info_for_writer)
514
+
515
+ if result and result.get("updated"):
516
+ logger.info(
517
+ "Context updated by %s: sections=%s",
518
+ agent_type, result.get("sections_updated", []),
519
+ )
520
+ if result and result.get("rejected"):
521
+ logger.debug(
522
+ "Context sections rejected for %s: %s",
523
+ agent_type, result.get("rejected", []),
524
+ )
525
+
526
+ return result
527
+
528
+ except Exception as e:
529
+ logger.debug("Context update processing failed (non-fatal): %s", e)
530
+ return None
@@ -0,0 +1,161 @@
1
+ """Load context contracts and merge agent permissions.
2
+
3
+ Subsystem 2 of the pre_tool_use Task/Agent path.
4
+
5
+ Loads context-contracts.json + cloud overlays, merges agent permissions,
6
+ finds empty writable sections, and builds a reminder string.
7
+
8
+ Cloud provider detection (formerly infrastructure_reader) is internal
9
+ to the contract loading process.
10
+ """
11
+
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _detect_cloud_from_infrastructure(sections: dict) -> str:
20
+ """Extract cloud provider name from v2 infrastructure.cloud_providers section.
21
+
22
+ Args:
23
+ sections: The sections dict from project-context.json.
24
+
25
+ Returns:
26
+ Cloud provider name string (e.g. aws, gcp) or empty string.
27
+ """
28
+ infra = sections.get("infrastructure", {})
29
+ if isinstance(infra, dict):
30
+ providers = infra.get("cloud_providers", [])
31
+ if isinstance(providers, list) and providers:
32
+ primary = providers[0]
33
+ if isinstance(primary, dict):
34
+ return primary.get("name", "")
35
+ return ""
36
+
37
+
38
+ def build_context_update_reminder(
39
+ subagent_type: str,
40
+ project_agents: list,
41
+ hooks_dir: Path = None,
42
+ ) -> str:
43
+ """Check which writable sections are empty and build a reminder.
44
+
45
+ Reads the context contracts to find writable sections for this agent,
46
+ then checks project-context.json to see which are empty.
47
+
48
+ Args:
49
+ subagent_type: The agent type string (e.g. developer).
50
+ project_agents: List of valid project agent names.
51
+ hooks_dir: Path to the hooks directory (for fallback config lookup).
52
+ Defaults to Path(__file__).parent.parent.parent if None.
53
+
54
+ Returns:
55
+ Reminder string or empty string if no empty sections.
56
+ """
57
+ if subagent_type not in project_agents:
58
+ return ""
59
+
60
+ if hooks_dir is None:
61
+ hooks_dir = Path(__file__).parent.parent.parent
62
+
63
+ # Load contracts to find writable sections.
64
+ # Strategy: load context-contracts.json (base) then merge cloud/{provider}.json.
65
+ # Fallback to legacy per-provider files for backward compatibility.
66
+ # We detect the cloud provider from project-context.json first.
67
+ cloud_provider = "gcp" # default
68
+ pc_paths_for_provider = [
69
+ Path(".claude/project-context/project-context.json"),
70
+ Path("project-context.json"),
71
+ ]
72
+ for pp in pc_paths_for_provider:
73
+ if pp.exists():
74
+ try:
75
+ pc_data = json.loads(pp.read_text())
76
+ detected = (
77
+ pc_data.get("metadata", {}).get("cloud_provider", "")
78
+ or _detect_cloud_from_infrastructure(pc_data.get("sections", {}))
79
+ )
80
+ if detected:
81
+ cloud_provider = detected.lower()
82
+ break
83
+ except Exception:
84
+ continue
85
+
86
+ # Candidate config directories (installed project first, package fallback)
87
+ config_dirs = [
88
+ Path(".claude/config"),
89
+ hooks_dir.parent / "config",
90
+ ]
91
+
92
+ writable = []
93
+ for config_dir in config_dirs:
94
+ if not config_dir.is_dir():
95
+ continue
96
+ # Load base contracts
97
+ base_file = config_dir / "context-contracts.json"
98
+ cloud_file = config_dir / "cloud" / f"{cloud_provider}.json"
99
+
100
+ merged_agents = {}
101
+ if base_file.exists():
102
+ try:
103
+ data = json.loads(base_file.read_text())
104
+ merged_agents = data.get("agents", {})
105
+ except Exception:
106
+ pass
107
+
108
+ # Merge cloud overrides
109
+ if merged_agents and cloud_file.exists():
110
+ try:
111
+ cloud_data = json.loads(cloud_file.read_text())
112
+ agent_cloud = cloud_data.get("agents", {}).get(subagent_type, {})
113
+ base_write = merged_agents.get(subagent_type, {}).get("write", [])
114
+ extra_write = [s for s in agent_cloud.get("write", []) if s not in base_write]
115
+ if subagent_type in merged_agents:
116
+ merged_agents[subagent_type]["write"] = base_write + extra_write
117
+ except Exception:
118
+ pass
119
+
120
+ if merged_agents:
121
+ agent_perms = merged_agents.get(subagent_type, {})
122
+ writable = agent_perms.get("write", [])
123
+ if writable:
124
+ break
125
+
126
+ if not writable:
127
+ return ""
128
+
129
+ # Load project-context.json to find empty sections
130
+ pc_paths = [
131
+ Path(".claude/project-context/project-context.json"),
132
+ Path("project-context.json"),
133
+ ]
134
+
135
+ sections = {}
136
+ for pp in pc_paths:
137
+ if pp.exists():
138
+ try:
139
+ pc = json.loads(pp.read_text())
140
+ sections = pc.get("sections", {})
141
+ break
142
+ except Exception:
143
+ continue
144
+
145
+ # Find empty writable sections
146
+ empty = []
147
+ for section_name in writable:
148
+ section_data = sections.get(section_name, {})
149
+ if not section_data or section_data == {}:
150
+ empty.append(section_name)
151
+
152
+ if not empty:
153
+ return ""
154
+
155
+ empty_list = ", ".join(f"`{s}`" for s in empty)
156
+ return (
157
+ f"\n**CONTEXT_UPDATE REQUIRED:** Your writable sections {empty_list} "
158
+ f"are currently EMPTY. After completing your task, you MUST emit a "
159
+ f"CONTEXT_UPDATE block with any data you discovered. "
160
+ f"See \"Context Updater Protocol\" above for the format.\n\n"
161
+ )