@jaguilar87/gaia-ops 4.4.0 → 4.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +12 -3
  3. package/ARCHITECTURE.md +9 -8
  4. package/CHANGELOG.md +34 -0
  5. package/README.md +43 -11
  6. package/agents/terraform-architect.md +1 -1
  7. package/bin/README.md +2 -2
  8. package/bin/gaia-doctor.js +18 -5
  9. package/bin/gaia-history.js +0 -1
  10. package/bin/gaia-metrics.js +2 -2
  11. package/bin/gaia-scan.py +23 -1
  12. package/bin/gaia-update.js +346 -54
  13. package/bin/pre-publish-validate.js +33 -10
  14. package/commands/gaia.md +37 -0
  15. package/config/README.md +3 -9
  16. package/config/context-contracts.json +47 -15
  17. package/config/surface-routing.json +9 -1
  18. package/dist/gaia-ops/.claude-plugin/plugin.json +22 -0
  19. package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
  20. package/dist/gaia-ops/agents/devops-developer.md +57 -0
  21. package/dist/gaia-ops/agents/gaia-system.md +58 -0
  22. package/dist/gaia-ops/agents/gitops-operator.md +60 -0
  23. package/dist/gaia-ops/agents/speckit-planner.md +71 -0
  24. package/dist/gaia-ops/agents/terraform-architect.md +60 -0
  25. package/dist/gaia-ops/commands/gaia.md +37 -0
  26. package/dist/gaia-ops/config/README.md +58 -0
  27. package/dist/gaia-ops/config/cloud/aws.json +140 -0
  28. package/dist/gaia-ops/config/cloud/gcp.json +145 -0
  29. package/dist/gaia-ops/config/context-contracts.json +131 -0
  30. package/dist/gaia-ops/config/git_standards.json +72 -0
  31. package/dist/gaia-ops/config/surface-routing.json +197 -0
  32. package/dist/gaia-ops/config/universal-rules.json +10 -0
  33. package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
  34. package/dist/gaia-ops/hooks/adapters/base.py +219 -0
  35. package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
  36. package/dist/gaia-ops/hooks/adapters/claude_code.py +1477 -0
  37. package/dist/gaia-ops/hooks/adapters/types.py +194 -0
  38. package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
  39. package/dist/gaia-ops/hooks/hooks.json +126 -0
  40. package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
  41. package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
  42. package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
  43. package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
  44. package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +124 -0
  45. package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
  46. package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
  47. package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
  48. package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
  49. package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
  50. package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
  51. package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
  52. package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +576 -0
  53. package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
  54. package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
  55. package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
  56. package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +215 -0
  57. package/dist/gaia-ops/hooks/modules/context/context_cache.py +129 -0
  58. package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
  59. package/dist/gaia-ops/hooks/modules/context/context_injector.py +427 -0
  60. package/dist/gaia-ops/hooks/modules/context/context_writer.py +518 -0
  61. package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
  62. package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
  63. package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
  64. package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
  65. package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
  66. package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +558 -0
  67. package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
  68. package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
  69. package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
  70. package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
  71. package/dist/gaia-ops/hooks/modules/identity/__init__.py +0 -0
  72. package/dist/gaia-ops/hooks/modules/identity/identity_provider.py +21 -0
  73. package/dist/gaia-ops/hooks/modules/identity/ops_identity.py +34 -0
  74. package/dist/gaia-ops/hooks/modules/identity/security_identity.py +10 -0
  75. package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
  76. package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +227 -0
  77. package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
  78. package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +128 -0
  79. package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
  80. package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
  81. package/dist/gaia-ops/hooks/modules/security/__init__.py +89 -0
  82. package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
  83. package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
  84. package/dist/gaia-ops/hooks/modules/security/approval_grants.py +912 -0
  85. package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
  86. package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +153 -0
  87. package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +584 -0
  88. package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +86 -0
  89. package/dist/gaia-ops/hooks/modules/security/command_semantics.py +130 -0
  90. package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
  91. package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +850 -0
  92. package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
  93. package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
  94. package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
  95. package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
  96. package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +158 -0
  97. package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
  98. package/dist/gaia-ops/hooks/modules/tools/__init__.py +25 -0
  99. package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +708 -0
  100. package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +181 -0
  101. package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
  102. package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
  103. package/dist/gaia-ops/hooks/modules/tools/task_validator.py +283 -0
  104. package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
  105. package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
  106. package/dist/gaia-ops/hooks/post_compact.py +43 -0
  107. package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
  108. package/dist/gaia-ops/hooks/pre_tool_use.py +383 -0
  109. package/dist/gaia-ops/hooks/session_start.py +69 -0
  110. package/dist/gaia-ops/hooks/stop_hook.py +69 -0
  111. package/dist/gaia-ops/hooks/subagent_start.py +71 -0
  112. package/dist/gaia-ops/hooks/subagent_stop.py +288 -0
  113. package/dist/gaia-ops/hooks/task_completed.py +70 -0
  114. package/dist/gaia-ops/hooks/user_prompt_submit.py +177 -0
  115. package/dist/gaia-ops/settings.json +72 -0
  116. package/dist/gaia-ops/skills/README.md +109 -0
  117. package/dist/gaia-ops/skills/agent-protocol/SKILL.md +105 -0
  118. package/dist/gaia-ops/skills/agent-protocol/examples.md +170 -0
  119. package/dist/gaia-ops/skills/agent-response/SKILL.md +53 -0
  120. package/dist/gaia-ops/skills/approval/SKILL.md +85 -0
  121. package/dist/gaia-ops/skills/approval/examples.md +140 -0
  122. package/dist/gaia-ops/skills/approval/reference.md +57 -0
  123. package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
  124. package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
  125. package/dist/gaia-ops/skills/context-updater/SKILL.md +76 -0
  126. package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
  127. package/dist/gaia-ops/skills/developer-patterns/SKILL.md +93 -0
  128. package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
  129. package/dist/gaia-ops/skills/execution/SKILL.md +66 -0
  130. package/dist/gaia-ops/skills/fast-queries/SKILL.md +47 -0
  131. package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +92 -0
  132. package/dist/gaia-ops/skills/gaia-patterns/reference.md +22 -0
  133. package/dist/gaia-ops/skills/git-conventions/SKILL.md +48 -0
  134. package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +73 -0
  135. package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
  136. package/dist/gaia-ops/skills/investigation/SKILL.md +77 -0
  137. package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +64 -0
  138. package/dist/gaia-ops/skills/reference.md +134 -0
  139. package/dist/gaia-ops/skills/security-tiers/SKILL.md +61 -0
  140. package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
  141. package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
  142. package/dist/gaia-ops/skills/skill-creation/SKILL.md +119 -0
  143. package/dist/gaia-ops/skills/specification/SKILL.md +186 -0
  144. package/dist/gaia-ops/skills/speckit-workflow/SKILL.md +165 -0
  145. package/dist/gaia-ops/skills/speckit-workflow/reference.md +117 -0
  146. package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +63 -0
  147. package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
  148. package/dist/gaia-ops/speckit/README.md +516 -0
  149. package/dist/gaia-ops/speckit/scripts/.gitkeep +0 -0
  150. package/dist/gaia-ops/speckit/templates/adr-template.md +118 -0
  151. package/dist/gaia-ops/speckit/templates/agent-file-template.md +23 -0
  152. package/dist/gaia-ops/speckit/templates/plan-template.md +227 -0
  153. package/dist/gaia-ops/speckit/templates/spec-template.md +140 -0
  154. package/dist/gaia-ops/speckit/templates/tasks-template.md +257 -0
  155. package/dist/gaia-ops/tools/context/README.md +132 -0
  156. package/dist/gaia-ops/tools/context/__init__.py +42 -0
  157. package/dist/gaia-ops/tools/context/_paths.py +20 -0
  158. package/dist/gaia-ops/tools/context/context_provider.py +476 -0
  159. package/dist/gaia-ops/tools/context/context_section_reader.py +330 -0
  160. package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
  161. package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
  162. package/dist/gaia-ops/tools/context/surface_router.py +278 -0
  163. package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
  164. package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
  165. package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  166. package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  167. package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  168. package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  169. package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
  170. package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  171. package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
  172. package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
  173. package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
  174. package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
  175. package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
  176. package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
  177. package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +262 -0
  178. package/dist/gaia-ops/tools/memory/README.md +0 -0
  179. package/dist/gaia-ops/tools/memory/__init__.py +20 -0
  180. package/dist/gaia-ops/tools/memory/episodic.py +1196 -0
  181. package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
  182. package/dist/gaia-ops/tools/review/__init__.py +1 -0
  183. package/dist/gaia-ops/tools/review/review_engine.py +157 -0
  184. package/dist/gaia-ops/tools/scan/__init__.py +35 -0
  185. package/dist/gaia-ops/tools/scan/config.py +247 -0
  186. package/dist/gaia-ops/tools/scan/merge.py +212 -0
  187. package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
  188. package/dist/gaia-ops/tools/scan/registry.py +127 -0
  189. package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
  190. package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
  191. package/dist/gaia-ops/tools/scan/scanners/environment.py +324 -0
  192. package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
  193. package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
  194. package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
  195. package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
  196. package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
  197. package/dist/gaia-ops/tools/scan/setup.py +753 -0
  198. package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
  199. package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
  200. package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
  201. package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
  202. package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
  203. package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
  204. package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
  205. package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
  206. package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
  207. package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
  208. package/dist/gaia-ops/tools/scan/ui.py +624 -0
  209. package/dist/gaia-ops/tools/scan/verify.py +266 -0
  210. package/dist/gaia-ops/tools/scan/walk.py +118 -0
  211. package/dist/gaia-ops/tools/scan/workspace.py +85 -0
  212. package/dist/gaia-ops/tools/validation/README.md +244 -0
  213. package/dist/gaia-ops/tools/validation/__init__.py +17 -0
  214. package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
  215. package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
  216. package/dist/gaia-security/.claude-plugin/plugin.json +22 -0
  217. package/dist/gaia-security/config/universal-rules.json +10 -0
  218. package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
  219. package/dist/gaia-security/hooks/adapters/base.py +219 -0
  220. package/dist/gaia-security/hooks/adapters/channel.py +17 -0
  221. package/dist/gaia-security/hooks/adapters/claude_code.py +1477 -0
  222. package/dist/gaia-security/hooks/adapters/types.py +194 -0
  223. package/dist/gaia-security/hooks/adapters/utils.py +25 -0
  224. package/dist/gaia-security/hooks/hooks.json +57 -0
  225. package/dist/gaia-security/hooks/modules/__init__.py +15 -0
  226. package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
  227. package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
  228. package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
  229. package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +124 -0
  230. package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
  231. package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
  232. package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
  233. package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
  234. package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
  235. package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
  236. package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
  237. package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +576 -0
  238. package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
  239. package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
  240. package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
  241. package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +215 -0
  242. package/dist/gaia-security/hooks/modules/context/context_cache.py +129 -0
  243. package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
  244. package/dist/gaia-security/hooks/modules/context/context_injector.py +427 -0
  245. package/dist/gaia-security/hooks/modules/context/context_writer.py +518 -0
  246. package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
  247. package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
  248. package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
  249. package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
  250. package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
  251. package/dist/gaia-security/hooks/modules/core/plugin_setup.py +558 -0
  252. package/dist/gaia-security/hooks/modules/core/state.py +179 -0
  253. package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
  254. package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
  255. package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
  256. package/dist/gaia-security/hooks/modules/identity/__init__.py +0 -0
  257. package/dist/gaia-security/hooks/modules/identity/identity_provider.py +21 -0
  258. package/dist/gaia-security/hooks/modules/identity/ops_identity.py +34 -0
  259. package/dist/gaia-security/hooks/modules/identity/security_identity.py +10 -0
  260. package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
  261. package/dist/gaia-security/hooks/modules/memory/episode_writer.py +227 -0
  262. package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
  263. package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +128 -0
  264. package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
  265. package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
  266. package/dist/gaia-security/hooks/modules/security/__init__.py +89 -0
  267. package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
  268. package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
  269. package/dist/gaia-security/hooks/modules/security/approval_grants.py +912 -0
  270. package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
  271. package/dist/gaia-security/hooks/modules/security/approval_scopes.py +153 -0
  272. package/dist/gaia-security/hooks/modules/security/blocked_commands.py +584 -0
  273. package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +86 -0
  274. package/dist/gaia-security/hooks/modules/security/command_semantics.py +130 -0
  275. package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
  276. package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +850 -0
  277. package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
  278. package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
  279. package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
  280. package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
  281. package/dist/gaia-security/hooks/modules/session/session_event_injector.py +158 -0
  282. package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
  283. package/dist/gaia-security/hooks/modules/tools/__init__.py +25 -0
  284. package/dist/gaia-security/hooks/modules/tools/bash_validator.py +708 -0
  285. package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +181 -0
  286. package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
  287. package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
  288. package/dist/gaia-security/hooks/modules/tools/task_validator.py +283 -0
  289. package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
  290. package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
  291. package/dist/gaia-security/hooks/post_tool_use.py +54 -0
  292. package/dist/gaia-security/hooks/pre_tool_use.py +383 -0
  293. package/dist/gaia-security/hooks/session_start.py +69 -0
  294. package/dist/gaia-security/hooks/stop_hook.py +69 -0
  295. package/dist/gaia-security/hooks/user_prompt_submit.py +177 -0
  296. package/dist/gaia-security/settings.json +58 -0
  297. package/git-hooks/commit-msg +41 -0
  298. package/hooks/README.md +8 -6
  299. package/hooks/adapters/channel.py +0 -25
  300. package/hooks/adapters/claude_code.py +364 -125
  301. package/hooks/elicitation_result.py +132 -0
  302. package/hooks/hooks.json +10 -1
  303. package/hooks/modules/README.md +3 -2
  304. package/hooks/modules/agents/contract_validator.py +3 -51
  305. package/hooks/modules/agents/response_contract.py +4 -8
  306. package/hooks/modules/agents/transcript_reader.py +4 -5
  307. package/hooks/modules/audit/__init__.py +4 -6
  308. package/hooks/modules/audit/event_detector.py +0 -2
  309. package/hooks/modules/audit/metrics.py +108 -187
  310. package/hooks/modules/audit/workflow_auditor.py +0 -4
  311. package/hooks/modules/audit/workflow_recorder.py +0 -5
  312. package/hooks/modules/context/compact_context_builder.py +1 -0
  313. package/hooks/modules/context/context_cache.py +129 -0
  314. package/hooks/modules/context/context_injector.py +18 -40
  315. package/hooks/modules/context/context_writer.py +1 -25
  316. package/hooks/modules/context/contracts_loader.py +7 -10
  317. package/hooks/modules/core/hook_entry.py +1 -0
  318. package/hooks/modules/core/paths.py +12 -13
  319. package/hooks/modules/core/plugin_mode.py +74 -4
  320. package/hooks/modules/core/plugin_setup.py +395 -23
  321. package/hooks/modules/events/__init__.py +1 -0
  322. package/hooks/modules/events/event_writer.py +210 -0
  323. package/hooks/modules/identity/ops_identity.py +18 -27
  324. package/hooks/modules/memory/episode_writer.py +1 -6
  325. package/hooks/modules/orchestrator/__init__.py +1 -0
  326. package/hooks/modules/orchestrator/delegate_mode.py +128 -0
  327. package/hooks/modules/security/__init__.py +2 -4
  328. package/hooks/modules/security/approval_constants.py +5 -1
  329. package/hooks/modules/security/approval_grants.py +189 -6
  330. package/hooks/modules/security/approval_messages.py +9 -21
  331. package/hooks/modules/security/blocked_commands.py +98 -34
  332. package/hooks/modules/security/command_semantics.py +0 -4
  333. package/hooks/modules/security/gitops_validator.py +1 -11
  334. package/hooks/modules/security/mutative_verbs.py +179 -38
  335. package/hooks/modules/security/tiers.py +1 -19
  336. package/hooks/modules/session/session_event_injector.py +1 -25
  337. package/hooks/modules/tools/bash_validator.py +310 -94
  338. package/hooks/modules/tools/shell_parser.py +0 -1
  339. package/hooks/modules/tools/task_validator.py +9 -29
  340. package/hooks/post_tool_use.py +0 -72
  341. package/hooks/pre_tool_use.py +42 -102
  342. package/hooks/session_start.py +4 -2
  343. package/hooks/subagent_start.py +6 -2
  344. package/hooks/subagent_stop.py +1 -13
  345. package/hooks/user_prompt_submit.py +119 -37
  346. package/index.js +1 -1
  347. package/package.json +5 -3
  348. package/skills/README.md +3 -5
  349. package/skills/agent-protocol/SKILL.md +17 -16
  350. package/skills/agent-protocol/examples.md +6 -6
  351. package/skills/agent-response/SKILL.md +11 -14
  352. package/skills/approval/SKILL.md +28 -13
  353. package/skills/approval/reference.md +2 -2
  354. package/skills/execution/SKILL.md +1 -1
  355. package/skills/gaia-patterns/SKILL.md +2 -3
  356. package/skills/orchestrator-approval/SKILL.md +22 -50
  357. package/skills/security-tiers/SKILL.md +1 -1
  358. package/templates/README.md +9 -9
  359. package/templates/managed-settings.template.json +43 -0
  360. package/tools/gaia_simulator/runner.py +34 -1
  361. package/tools/scan/orchestrator.py +13 -0
  362. package/tools/scan/scanners/base.py +8 -0
  363. package/tools/scan/scanners/git.py +78 -0
  364. package/tools/scan/scanners/infrastructure.py +65 -0
  365. package/tools/scan/scanners/stack.py +110 -0
  366. package/tools/scan/setup.py +120 -13
  367. package/tools/scan/workspace.py +85 -0
  368. package/config/context-contracts.aws.json +0 -42
  369. package/config/context-contracts.gcp.json +0 -39
  370. package/skills/project-dispatch/SKILL.md +0 -34
  371. package/templates/settings.template.json +0 -226
@@ -0,0 +1,624 @@
1
+ """
2
+ Rail UI for gaia-scan
3
+
4
+ Clack-style rail output for scan results. Zero prompts. Fully automatic.
5
+ All output goes to stderr. stdout is reserved for JSON only.
6
+
7
+ Classes:
8
+ - RailUI: Clack-style rail output renderer
9
+
10
+ Functions:
11
+ - format_scanner_results: Transform raw scan output into display sections
12
+ """
13
+
14
+ import os
15
+ import sys
16
+ from typing import Any, Dict, List, Optional
17
+
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # ANSI color helpers
21
+ # ---------------------------------------------------------------------------
22
+
23
+ def _supports_color() -> bool:
24
+ """Check if the terminal supports ANSI colors."""
25
+ if os.environ.get("NO_COLOR"):
26
+ return False
27
+ if not hasattr(sys.stderr, "isatty"):
28
+ return False
29
+ return sys.stderr.isatty()
30
+
31
+
32
+ _COLOR = _supports_color()
33
+
34
+
35
+ def _c(code: str, text: str) -> str:
36
+ """Apply ANSI color code if color is supported."""
37
+ return f"\033[{code}m{text}\033[0m" if _COLOR else text
38
+
39
+
40
+ def _cyan(text: str) -> str:
41
+ return _c("36", text)
42
+
43
+
44
+ def _green(text: str) -> str:
45
+ return _c("32", text)
46
+
47
+
48
+ def _yellow(text: str) -> str:
49
+ return _c("33", text)
50
+
51
+
52
+ def _dim(text: str) -> str:
53
+ return _c("2", text)
54
+
55
+
56
+ def _bold(text: str) -> str:
57
+ return _c("1", text)
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Rail UI
62
+ # ---------------------------------------------------------------------------
63
+
64
+ class RailUI:
65
+ """Clack-style rail output for scan results.
66
+
67
+ All output goes to stderr. The rail character is dimmed.
68
+
69
+ Args:
70
+ version: Scanner version string for the header.
71
+ color: Whether to use ANSI colors (overrides auto-detect).
72
+ """
73
+
74
+ def __init__(self, version: str, color: Optional[bool] = None):
75
+ self.version = version
76
+ self._color = color if color is not None else _COLOR
77
+
78
+ def _c(self, code: str, text: str) -> str:
79
+ """Apply ANSI color code if color is enabled for this instance."""
80
+ return f"\033[{code}m{text}\033[0m" if self._color else text
81
+
82
+ def _cyan(self, text: str) -> str:
83
+ return self._c("36", text)
84
+
85
+ def _green(self, text: str) -> str:
86
+ return self._c("32", text)
87
+
88
+ def _yellow(self, text: str) -> str:
89
+ return self._c("33", text)
90
+
91
+ def _dim(self, text: str) -> str:
92
+ return self._c("2", text)
93
+
94
+ def _bold(self, text: str) -> str:
95
+ return self._c("1", text)
96
+
97
+ def _rail(self) -> str:
98
+ """Return the dimmed rail character."""
99
+ return self._dim("\u2502")
100
+
101
+ def _write(self, text: str) -> None:
102
+ """Write a line to stderr."""
103
+ print(text, file=sys.stderr)
104
+
105
+ def start(self) -> None:
106
+ """Print the header: top-left corner + version."""
107
+ self._write(self._cyan(f"\u250c gaia-scan v{self.version}"))
108
+ self._write(self._rail())
109
+
110
+ def scanning(self) -> None:
111
+ """Print the scanning indicator."""
112
+ self._write(self._cyan(f"\u25d2 Scanning..."))
113
+ self._write(self._rail())
114
+
115
+ def section(self, name: str, lines: List[str]) -> None:
116
+ """Print a section with its detail lines.
117
+
118
+ Args:
119
+ name: Section title (e.g. "Stack", "Infrastructure").
120
+ lines: Detail lines to display under the section.
121
+ """
122
+ self._write(f"{self._green('\u25c7')} {self._cyan(name)}")
123
+ for line in lines:
124
+ self._write(f"{self._rail()} {line}")
125
+ self._write(self._rail())
126
+
127
+ def section_compact(self, names: List[str]) -> None:
128
+ """Print multiple section names on a single line (scan-only mode).
129
+
130
+ Args:
131
+ names: List of section names to join with middle-dot.
132
+ """
133
+ joined = self._cyan(" \u00b7 ".join(names))
134
+ self._write(f"{self._green('\u25c7')} {joined}")
135
+ self._write(self._rail())
136
+
137
+ def warning(self, count: int, messages: List[str]) -> None:
138
+ """Print warnings section.
139
+
140
+ Args:
141
+ count: Total number of warnings.
142
+ messages: Warning messages to display.
143
+ """
144
+ self._write(f"{self._yellow('\u26a0')} {self._yellow(f'Warnings ({count})')}")
145
+ for msg in messages:
146
+ self._write(f"{self._rail()} {msg}")
147
+ self._write(self._rail())
148
+
149
+ def done(self, duration_s: float, suffix: str = "") -> None:
150
+ """Print the done marker with duration.
151
+
152
+ Args:
153
+ duration_s: Scan duration in seconds.
154
+ suffix: Optional text to append after duration.
155
+ """
156
+ text = f"\u25c6 Done in {duration_s:.1f}s"
157
+ if suffix:
158
+ text += f" \u00b7 {suffix}"
159
+ self._write(self._green(text))
160
+ self._write(self._rail())
161
+
162
+ def created(self, items: Dict[str, str]) -> None:
163
+ """Print the 'Created:' summary for fresh installs.
164
+
165
+ Args:
166
+ items: Dict of {name: description} for created items.
167
+ """
168
+ self._write(f"{self._rail()} Created:")
169
+ for name, desc in items.items():
170
+ self._write(f"{self._rail()} {name:<18s} {self._dim(desc)}")
171
+ self._write(self._rail())
172
+
173
+ def updated(self, sections_updated: int, sections_preserved: int) -> None:
174
+ """Print the 'Updated/Preserved' summary for rescans.
175
+
176
+ Args:
177
+ sections_updated: Number of scanner-updated sections.
178
+ sections_preserved: Number of agent-enriched preserved sections.
179
+ """
180
+ self._write(f"{self._rail()} Updated: {sections_updated} sections")
181
+ self._write(f"{self._rail()} Preserved: {sections_preserved} agent-enriched sections")
182
+ self._write(f"{self._rail()} Synced: CLAUDE.md, settings.json")
183
+ self._write(self._rail())
184
+
185
+ def footer(self, message: str) -> None:
186
+ """Print the footer with closing rail corner.
187
+
188
+ Args:
189
+ message: Footer message text.
190
+ """
191
+ self._write(f"{self._dim('\u2514')} {message}")
192
+
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # Format scanner results for display
196
+ # ---------------------------------------------------------------------------
197
+
198
+ def format_scanner_results(output: Any, project_root: Any = None) -> List[Dict[str, Any]]:
199
+ """Transform raw scan output into display sections for RailUI.
200
+
201
+ Produces a project-aware context summary with:
202
+ - Project section(s): identity line + infrastructure line
203
+ - Tools section: detected CLI tools
204
+ - Runtime section: language runtimes + OS
205
+
206
+ Args:
207
+ output: ScanOutput from the orchestrator (has .context, .scanner_results).
208
+ project_root: Path to the project root (used for fallback project name).
209
+
210
+ Returns:
211
+ List of dicts with 'name' and 'lines' keys.
212
+ """
213
+ from pathlib import Path
214
+
215
+ ctx = output.context
216
+ scan_sections = ctx.get("sections", {})
217
+ root = Path(project_root) if project_root else None
218
+
219
+ sections: List[Dict[str, Any]] = []
220
+
221
+ # --- Project section(s) ---
222
+ project_sections = _build_project_sections(scan_sections, root)
223
+ sections.extend(project_sections)
224
+
225
+ # --- Tools ---
226
+ env = scan_sections.get("environment", {})
227
+ tool_list = env.get("tools", [])
228
+ if tool_list:
229
+ count = len(tool_list)
230
+ names = [t.get("name", "") for t in tool_list if isinstance(t, dict)]
231
+ # Show first 6 tools + ellipsis if more
232
+ if len(names) > 6:
233
+ display = " \u00b7 ".join(names[:6]) + " ..."
234
+ else:
235
+ display = " \u00b7 ".join(names)
236
+ sections.append({"name": f"Tools ({count})", "lines": [display]})
237
+
238
+ # --- Runtime ---
239
+ os_info = env.get("os", {})
240
+ runtimes = env.get("runtimes", [])
241
+
242
+ rt_parts = []
243
+ for rt in runtimes:
244
+ name = _capitalize(rt.get("name", ""))
245
+ ver = rt.get("version", "")
246
+ if name and ver:
247
+ # Use major.minor for cleaner display
248
+ parts = ver.split(".")
249
+ short_ver = ".".join(parts[:2]) if len(parts) >= 2 else parts[0]
250
+ rt_parts.append(f"{name} {short_ver}")
251
+
252
+ wsl = os_info.get("wsl", False)
253
+ if wsl:
254
+ wsl_ver = os_info.get("wsl_version", "")
255
+ rt_parts.append(f"WSL{wsl_ver}")
256
+ else:
257
+ platform = os_info.get("platform", "")
258
+ if platform:
259
+ rt_parts.append(_capitalize(platform))
260
+
261
+ if rt_parts:
262
+ sections.append({"name": "Runtime", "lines": [" \u00b7 ".join(rt_parts)]})
263
+
264
+ return sections
265
+
266
+
267
+ def _build_project_sections(
268
+ scan_sections: Dict[str, Any],
269
+ project_root: Any,
270
+ ) -> List[Dict[str, Any]]:
271
+ """Build project context sections. Returns list for future multi-project support.
272
+
273
+ Args:
274
+ scan_sections: The 'sections' dict from scan output context.
275
+ project_root: Path to the project root (for fallback name).
276
+
277
+ Returns:
278
+ List of section dicts, one per project.
279
+ """
280
+ projects = []
281
+ projects.append(_build_single_project(scan_sections, project_root))
282
+ return projects
283
+
284
+
285
+ def _build_single_project(
286
+ scan_sections: Dict[str, Any],
287
+ project_root: Any,
288
+ ) -> Dict[str, Any]:
289
+ """Build a single project summary section.
290
+
291
+ Line 1: Project type + service count + git platform
292
+ Line 2: Cloud providers + orchestration + IaC
293
+
294
+ Args:
295
+ scan_sections: The 'sections' dict from scan output context.
296
+ project_root: Path to the project root (for fallback name).
297
+
298
+ Returns:
299
+ Section dict with 'name' and 'lines'.
300
+ """
301
+ from pathlib import Path
302
+
303
+ # --- Project name ---
304
+ project_identity = scan_sections.get("project_identity", {})
305
+ name = project_identity.get("name", "")
306
+ # Fallback: if name is npm-init default or empty, use directory name
307
+ if not name or name == "my-project":
308
+ if project_root:
309
+ name = Path(project_root).name
310
+ else:
311
+ name = "project"
312
+
313
+ # --- Line 1: Identity ---
314
+ line1_parts = []
315
+
316
+ # Project type
317
+ monorepo = project_identity.get("monorepo", {})
318
+ proj_type = project_identity.get("type", "")
319
+ if monorepo.get("detected") or proj_type == "monorepo":
320
+ line1_parts.append("Monorepo")
321
+ elif proj_type == "library":
322
+ line1_parts.append("Library")
323
+ else:
324
+ line1_parts.append("Single app")
325
+
326
+ # Service count from Dockerfiles (non-worktree)
327
+ infra = scan_sections.get("infrastructure", {})
328
+ service_count = _count_services(infra)
329
+ if service_count > 0:
330
+ if service_count >= 15:
331
+ line1_parts.append(f"{service_count}+ services")
332
+ else:
333
+ line1_parts.append(f"{service_count} services")
334
+
335
+ # Git platform
336
+ git_platform = _detect_git_platform(scan_sections)
337
+ if git_platform:
338
+ line1_parts.append(git_platform)
339
+
340
+ # --- Line 2: Infrastructure ---
341
+ line2_parts = []
342
+
343
+ # Cloud providers
344
+ cloud_providers = infra.get("cloud_providers", [])
345
+ if cloud_providers:
346
+ cloud_names = []
347
+ for cp in cloud_providers:
348
+ cp_name = cp.get("name", "").upper()
349
+ if cp_name:
350
+ cloud_names.append(cp_name)
351
+ if cloud_names:
352
+ line2_parts.append(" + ".join(cloud_names))
353
+
354
+ # Orchestration: Kubernetes + GitOps tool
355
+ orch_summary = _build_orchestration_summary(scan_sections)
356
+ if orch_summary:
357
+ line2_parts.append(orch_summary)
358
+
359
+ # IaC tools
360
+ iac = infra.get("iac", [])
361
+ if iac:
362
+ iac_names = []
363
+ for tool_entry in iac:
364
+ tool_name = _capitalize(tool_entry.get("tool", ""))
365
+ if tool_name and tool_name not in iac_names:
366
+ iac_names.append(tool_name)
367
+ if iac_names:
368
+ line2_parts.append("/".join(iac_names))
369
+
370
+ lines = []
371
+ if line1_parts:
372
+ lines.append(" \u00b7 ".join(line1_parts))
373
+ if line2_parts:
374
+ lines.append(" \u00b7 ".join(line2_parts))
375
+
376
+ return {"name": name, "lines": lines}
377
+
378
+
379
+ def _count_services(infra: Dict[str, Any]) -> int:
380
+ """Count unique services from Docker container files.
381
+
382
+ Counts non-worktree Dockerfiles as a proxy for service count.
383
+
384
+ Args:
385
+ infra: Infrastructure section from scan results.
386
+
387
+ Returns:
388
+ Number of services detected.
389
+ """
390
+ containers = infra.get("containers", [])
391
+ for ct in containers:
392
+ if ct.get("tool") == "docker":
393
+ files = ct.get("files", [])
394
+ # Count non-worktree, non-template, non-example Dockerfiles
395
+ count = sum(
396
+ 1 for f in files
397
+ if not f.startswith("worktrees/")
398
+ and "template" not in f.lower()
399
+ and not f.endswith(".example")
400
+ )
401
+ return count
402
+ return 0
403
+
404
+
405
+ def _detect_git_platform(scan_sections: Dict[str, Any]) -> Optional[str]:
406
+ """Detect git platform from scan results.
407
+
408
+ Checks git.platform first, then parses remotes, then falls back to
409
+ tool presence (glab -> GitLab, gh -> GitHub).
410
+
411
+ Args:
412
+ scan_sections: The 'sections' dict from scan output.
413
+
414
+ Returns:
415
+ Platform name (e.g. "GitLab", "GitHub") or None.
416
+ """
417
+ git = scan_sections.get("git", {})
418
+
419
+ # Direct platform detection
420
+ platform = git.get("platform")
421
+ if platform:
422
+ return _capitalize_platform(platform)
423
+
424
+ # Parse remotes for platform hints
425
+ remotes = git.get("remotes", [])
426
+ for remote in remotes:
427
+ remote_platform = remote.get("platform")
428
+ if remote_platform:
429
+ return _capitalize_platform(remote_platform)
430
+ url = remote.get("url", "")
431
+ if "gitlab" in url.lower():
432
+ return "GitLab"
433
+ if "github" in url.lower():
434
+ return "GitHub"
435
+
436
+ # Fallback: check for glab or gh tool presence
437
+ env = scan_sections.get("environment", {})
438
+ tools = env.get("tools", [])
439
+ tool_names = {t.get("name", "") for t in tools if isinstance(t, dict)}
440
+ if "glab" in tool_names:
441
+ return "GitLab"
442
+ if "gh" in tool_names:
443
+ return "GitHub"
444
+
445
+ return None
446
+
447
+
448
+ def _capitalize_platform(platform: str) -> str:
449
+ """Capitalize a git platform name.
450
+
451
+ Args:
452
+ platform: Raw platform string (e.g. "gitlab", "github").
453
+
454
+ Returns:
455
+ Human-friendly name (e.g. "GitLab", "GitHub").
456
+ """
457
+ platform_map = {
458
+ "gitlab": "GitLab",
459
+ "gitlab-ci": "GitLab",
460
+ "github": "GitHub",
461
+ "github-actions": "GitHub",
462
+ "bitbucket": "Bitbucket",
463
+ }
464
+ return platform_map.get(platform.lower(), platform.capitalize())
465
+
466
+
467
+ def _build_orchestration_summary(scan_sections: Dict[str, Any]) -> Optional[str]:
468
+ """Build orchestration summary string.
469
+
470
+ Produces strings like "Kubernetes (Flux)" or "Kubernetes".
471
+
472
+ Args:
473
+ scan_sections: The 'sections' dict from scan output.
474
+
475
+ Returns:
476
+ Orchestration summary string, or None.
477
+ """
478
+ env = scan_sections.get("environment", {})
479
+ tools = env.get("tools", [])
480
+ tool_names = {t.get("name", "") for t in tools if isinstance(t, dict)}
481
+
482
+ has_k8s = "kubectl" in tool_names
483
+ if not has_k8s:
484
+ return None
485
+
486
+ # Detect GitOps tool
487
+ gitops_tool = None
488
+ if "flux" in tool_names or "fluxctl" in tool_names:
489
+ gitops_tool = "Flux"
490
+
491
+ # Check infrastructure for gitops hints (flux files, argocd, etc.)
492
+ infra = scan_sections.get("infrastructure", {})
493
+ ci_cd = infra.get("ci_cd", [])
494
+ for ci in ci_cd:
495
+ if isinstance(ci, dict):
496
+ ci_platform = ci.get("platform", "").lower()
497
+ if "flux" in ci_platform:
498
+ gitops_tool = "Flux"
499
+ elif "argo" in ci_platform or "argocd" in ci_platform:
500
+ gitops_tool = "ArgoCD"
501
+
502
+ # Check orchestration section if present
503
+ orch = scan_sections.get("orchestration", {})
504
+ if isinstance(orch, dict):
505
+ k8s = orch.get("kubernetes", {})
506
+ if isinstance(k8s, dict) and k8s.get("detected"):
507
+ has_k8s = True
508
+ gitops = orch.get("gitops", {})
509
+ if isinstance(gitops, dict) and gitops.get("tool"):
510
+ gitops_tool = gitops["tool"].capitalize()
511
+
512
+ if has_k8s:
513
+ if gitops_tool:
514
+ return f"Kubernetes ({gitops_tool})"
515
+ return "Kubernetes"
516
+
517
+ return None
518
+
519
+
520
+ def collect_warnings(output: Any) -> List[str]:
521
+ """Collect user-facing warnings from scan output.
522
+
523
+ Args:
524
+ output: ScanOutput from the orchestrator.
525
+
526
+ Returns:
527
+ List of warning message strings.
528
+ """
529
+ warnings = []
530
+
531
+ # Check for no git directory
532
+ git_section = output.context.get("sections", {}).get("git", {})
533
+ remotes = git_section.get("remotes", [])
534
+ if not remotes and not git_section.get("platform"):
535
+ warnings.append("No .git directory found at project root")
536
+
537
+ # Include scanner-level warnings (deduplicated, user-facing only)
538
+ for w in output.warnings:
539
+ # Skip if already covered by a more descriptive version
540
+ if w not in warnings and not any(w in existing for existing in warnings):
541
+ warnings.append(w)
542
+
543
+ return warnings
544
+
545
+
546
+ def collect_created_summary(project_root: "Path", output: Any) -> Dict[str, str]:
547
+ """Collect summary of created artifacts for fresh install display.
548
+
549
+ Args:
550
+ project_root: Project root directory.
551
+ output: ScanOutput from the orchestrator.
552
+
553
+ Returns:
554
+ Dict of {artifact_name: description}.
555
+ """
556
+ from pathlib import Path
557
+
558
+ items = {}
559
+ claude_dir = Path(project_root) / ".claude"
560
+
561
+ # Count symlinks
562
+ symlink_count = 0
563
+ if claude_dir.is_dir():
564
+ for entry in claude_dir.iterdir():
565
+ if entry.is_symlink():
566
+ symlink_count += 1
567
+ if symlink_count:
568
+ items[".claude/"] = f"{symlink_count} symlinks"
569
+
570
+ # CLAUDE.md
571
+ claude_md = Path(project_root) / "CLAUDE.md"
572
+ if claude_md.is_file():
573
+ items["CLAUDE.md"] = "orchestrator identity"
574
+
575
+ # settings.json
576
+ settings = claude_dir / "settings.json"
577
+ if settings.is_file():
578
+ items["settings.json"] = "hooks + permissions"
579
+
580
+ # project-context sections
581
+ ctx_path = claude_dir / "project-context" / "project-context.json"
582
+ if ctx_path.is_file():
583
+ try:
584
+ import json
585
+ data = json.loads(ctx_path.read_text())
586
+ section_count = len(data.get("sections", {}))
587
+ items["project-context"] = f"{section_count} sections detected"
588
+ except Exception:
589
+ items["project-context"] = "generated"
590
+
591
+ return items
592
+
593
+
594
+ # ---------------------------------------------------------------------------
595
+ # Helpers
596
+ # ---------------------------------------------------------------------------
597
+
598
+ def _capitalize(s: str) -> str:
599
+ """Capitalize first letter, keep rest. Handle known names."""
600
+ name_map = {
601
+ "javascript": "JavaScript",
602
+ "typescript": "TypeScript",
603
+ "python": "Python",
604
+ "go": "Go",
605
+ "java": "Java",
606
+ "rust": "Rust",
607
+ "express": "Express",
608
+ "terraform": "Terraform",
609
+ "terragrunt": "Terragrunt",
610
+ "docker": "Docker",
611
+ "node": "Node",
612
+ "python3": "Python",
613
+ "npm": "npm",
614
+ "pnpm": "pnpm",
615
+ "yarn": "yarn",
616
+ "linux": "Linux",
617
+ "darwin": "macOS",
618
+ "win32": "Windows",
619
+ "terraform_provider": "Terraform",
620
+ "cli_config": "CLI",
621
+ }
622
+ return name_map.get(s, s.capitalize() if s else s)
623
+
624
+