@jaguilar87/gaia 5.0.0-rc1

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 (609) 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 +1212 -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 +237 -0
  16. package/agents/gaia-planner.md +53 -0
  17. package/agents/gaia-system.md +70 -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 +628 -0
  26. package/bin/cli/history.py +305 -0
  27. package/bin/cli/memory.py +464 -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 +816 -0
  45. package/bin/pre-publish-validate.js +610 -0
  46. package/bin/python-detect.js +60 -0
  47. package/commands/README.md +64 -0
  48. package/commands/gaia.md +37 -0
  49. package/commands/scan-project.md +67 -0
  50. package/config/README.md +71 -0
  51. package/config/cloud/aws.json +134 -0
  52. package/config/cloud/gcp.json +139 -0
  53. package/config/context-contracts.json +158 -0
  54. package/config/crons-schema.md +81 -0
  55. package/config/git_standards.json +72 -0
  56. package/config/surface-routing.json +421 -0
  57. package/config/universal-rules.json +102 -0
  58. package/dist/gaia-ops/.claude-plugin/plugin.json +24 -0
  59. package/dist/gaia-ops/README.md +80 -0
  60. package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
  61. package/dist/gaia-ops/agents/developer.md +65 -0
  62. package/dist/gaia-ops/agents/gaia-operator.md +64 -0
  63. package/dist/gaia-ops/agents/gaia-orchestrator.md +237 -0
  64. package/dist/gaia-ops/agents/gaia-planner.md +53 -0
  65. package/dist/gaia-ops/agents/gaia-system.md +70 -0
  66. package/dist/gaia-ops/agents/gitops-operator.md +61 -0
  67. package/dist/gaia-ops/agents/terraform-architect.md +63 -0
  68. package/dist/gaia-ops/commands/gaia.md +37 -0
  69. package/dist/gaia-ops/config/README.md +71 -0
  70. package/dist/gaia-ops/config/cloud/aws.json +134 -0
  71. package/dist/gaia-ops/config/cloud/gcp.json +139 -0
  72. package/dist/gaia-ops/config/context-contracts.json +158 -0
  73. package/dist/gaia-ops/config/crons-schema.md +81 -0
  74. package/dist/gaia-ops/config/git_standards.json +72 -0
  75. package/dist/gaia-ops/config/surface-routing.json +421 -0
  76. package/dist/gaia-ops/config/universal-rules.json +102 -0
  77. package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
  78. package/dist/gaia-ops/hooks/adapters/base.py +219 -0
  79. package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
  80. package/dist/gaia-ops/hooks/adapters/claude_code.py +1890 -0
  81. package/dist/gaia-ops/hooks/adapters/types.py +194 -0
  82. package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
  83. package/dist/gaia-ops/hooks/hooks.json +163 -0
  84. package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
  85. package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
  86. package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
  87. package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
  88. package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +120 -0
  89. package/dist/gaia-ops/hooks/modules/agents/state_tracker.py +267 -0
  90. package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
  91. package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
  92. package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
  93. package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
  94. package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
  95. package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
  96. package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
  97. package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +611 -0
  98. package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
  99. package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
  100. package/dist/gaia-ops/hooks/modules/context/agentic_loop_detector.py +165 -0
  101. package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
  102. package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +218 -0
  103. package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
  104. package/dist/gaia-ops/hooks/modules/context/context_injector.py +558 -0
  105. package/dist/gaia-ops/hooks/modules/context/context_writer.py +530 -0
  106. package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
  107. package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
  108. package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
  109. package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
  110. package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
  111. package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +577 -0
  112. package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
  113. package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
  114. package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
  115. package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
  116. package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
  117. package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +216 -0
  118. package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
  119. package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +122 -0
  120. package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
  121. package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
  122. package/dist/gaia-ops/hooks/modules/security/__init__.py +120 -0
  123. package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
  124. package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
  125. package/dist/gaia-ops/hooks/modules/security/approval_grants.py +1638 -0
  126. package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
  127. package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +222 -0
  128. package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +595 -0
  129. package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +87 -0
  130. package/dist/gaia-ops/hooks/modules/security/command_semantics.py +181 -0
  131. package/dist/gaia-ops/hooks/modules/security/composition_rules.py +547 -0
  132. package/dist/gaia-ops/hooks/modules/security/flag_classifiers.py +873 -0
  133. package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
  134. package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +1131 -0
  135. package/dist/gaia-ops/hooks/modules/security/network_hosts.py +481 -0
  136. package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
  137. package/dist/gaia-ops/hooks/modules/security/shell_unwrapper.py +165 -0
  138. package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
  139. package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
  140. package/dist/gaia-ops/hooks/modules/session/pending_scanner.py +174 -0
  141. package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
  142. package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +160 -0
  143. package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
  144. package/dist/gaia-ops/hooks/modules/session/session_registry.py +232 -0
  145. package/dist/gaia-ops/hooks/modules/tools/__init__.py +29 -0
  146. package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +1008 -0
  147. package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  148. package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
  149. package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
  150. package/dist/gaia-ops/hooks/modules/tools/stage_decomposer.py +315 -0
  151. package/dist/gaia-ops/hooks/modules/tools/task_validator.py +294 -0
  152. package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
  153. package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
  154. package/dist/gaia-ops/hooks/post_compact.py +43 -0
  155. package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
  156. package/dist/gaia-ops/hooks/pre_compact.py +60 -0
  157. package/dist/gaia-ops/hooks/pre_tool_use.py +413 -0
  158. package/dist/gaia-ops/hooks/session_start.py +81 -0
  159. package/dist/gaia-ops/hooks/stop_hook.py +82 -0
  160. package/dist/gaia-ops/hooks/subagent_start.py +71 -0
  161. package/dist/gaia-ops/hooks/subagent_stop.py +295 -0
  162. package/dist/gaia-ops/hooks/task_completed.py +70 -0
  163. package/dist/gaia-ops/hooks/user_prompt_submit.py +246 -0
  164. package/dist/gaia-ops/settings.json +72 -0
  165. package/dist/gaia-ops/skills/README.md +154 -0
  166. package/dist/gaia-ops/skills/agent-protocol/SKILL.md +93 -0
  167. package/dist/gaia-ops/skills/agent-protocol/examples.md +223 -0
  168. package/dist/gaia-ops/skills/agent-response/SKILL.md +69 -0
  169. package/dist/gaia-ops/skills/agentic-loop/SKILL.md +80 -0
  170. package/dist/gaia-ops/skills/agentic-loop/reference.md +378 -0
  171. package/dist/gaia-ops/skills/blog-writing/SKILL.md +98 -0
  172. package/dist/gaia-ops/skills/blog-writing/reference.md +130 -0
  173. package/dist/gaia-ops/skills/brief-spec/SKILL.md +182 -0
  174. package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
  175. package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
  176. package/dist/gaia-ops/skills/context-updater/SKILL.md +87 -0
  177. package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
  178. package/dist/gaia-ops/skills/developer-patterns/SKILL.md +50 -0
  179. package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
  180. package/dist/gaia-ops/skills/execution/SKILL.md +99 -0
  181. package/dist/gaia-ops/skills/fast-queries/SKILL.md +43 -0
  182. package/dist/gaia-ops/skills/gaia-compact/SKILL.md +74 -0
  183. package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +108 -0
  184. package/dist/gaia-ops/skills/gaia-patterns/reference.md +395 -0
  185. package/dist/gaia-ops/skills/gaia-planner/SKILL.md +37 -0
  186. package/dist/gaia-ops/skills/gaia-planner/reference.md +107 -0
  187. package/dist/gaia-ops/skills/gaia-release/SKILL.md +82 -0
  188. package/dist/gaia-ops/skills/gaia-release/reference.md +102 -0
  189. package/dist/gaia-ops/skills/gaia-self-check/SKILL.md +114 -0
  190. package/dist/gaia-ops/skills/gaia-self-check/reference.md +453 -0
  191. package/dist/gaia-ops/skills/gaia-verify/SKILL.md +77 -0
  192. package/dist/gaia-ops/skills/gaia-verify/reference.md +80 -0
  193. package/dist/gaia-ops/skills/git-conventions/SKILL.md +47 -0
  194. package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +60 -0
  195. package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
  196. package/dist/gaia-ops/skills/gmail-policy/SKILL.md +200 -0
  197. package/dist/gaia-ops/skills/gmail-policy/reference.md +150 -0
  198. package/dist/gaia-ops/skills/gmail-triage/SKILL.md +100 -0
  199. package/dist/gaia-ops/skills/gws-setup/SKILL.md +99 -0
  200. package/dist/gaia-ops/skills/gws-setup/reference.md +73 -0
  201. package/dist/gaia-ops/skills/investigation/SKILL.md +100 -0
  202. package/dist/gaia-ops/skills/memory-curation/SKILL.md +83 -0
  203. package/dist/gaia-ops/skills/memory-search/SKILL.md +88 -0
  204. package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +160 -0
  205. package/dist/gaia-ops/skills/orchestrator-approval/reference.md +174 -0
  206. package/dist/gaia-ops/skills/pending-approvals/SKILL.md +72 -0
  207. package/dist/gaia-ops/skills/pending-approvals/reference.md +214 -0
  208. package/dist/gaia-ops/skills/readme-writing/SKILL.md +71 -0
  209. package/dist/gaia-ops/skills/readme-writing/reference.md +188 -0
  210. package/dist/gaia-ops/skills/reference.md +135 -0
  211. package/dist/gaia-ops/skills/request-approval/SKILL.md +140 -0
  212. package/dist/gaia-ops/skills/request-approval/examples.md +140 -0
  213. package/dist/gaia-ops/skills/request-approval/reference.md +57 -0
  214. package/dist/gaia-ops/skills/schedule-task/SKILL.md +64 -0
  215. package/dist/gaia-ops/skills/schedule-task/reference.md +233 -0
  216. package/dist/gaia-ops/skills/security-tiers/SKILL.md +141 -0
  217. package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
  218. package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
  219. package/dist/gaia-ops/skills/skill-creation/SKILL.md +92 -0
  220. package/dist/gaia-ops/skills/skill-creation/reference.md +29 -0
  221. package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +89 -0
  222. package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
  223. package/dist/gaia-ops/tools/__init__.py +9 -0
  224. package/dist/gaia-ops/tools/agentic-loop/decide-status.py +210 -0
  225. package/dist/gaia-ops/tools/agentic-loop/parse-metric.py +106 -0
  226. package/dist/gaia-ops/tools/agentic-loop/record-iteration.py +221 -0
  227. package/dist/gaia-ops/tools/context/README.md +132 -0
  228. package/dist/gaia-ops/tools/context/__init__.py +42 -0
  229. package/dist/gaia-ops/tools/context/_paths.py +20 -0
  230. package/dist/gaia-ops/tools/context/context_provider.py +721 -0
  231. package/dist/gaia-ops/tools/context/context_section_reader.py +342 -0
  232. package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
  233. package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
  234. package/dist/gaia-ops/tools/context/surface_router.py +278 -0
  235. package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
  236. package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
  237. package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  238. package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  239. package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  240. package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  241. package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
  242. package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  243. package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
  244. package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
  245. package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
  246. package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
  247. package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
  248. package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
  249. package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +264 -0
  250. package/dist/gaia-ops/tools/memory/README.md +0 -0
  251. package/dist/gaia-ops/tools/memory/__init__.py +20 -0
  252. package/dist/gaia-ops/tools/memory/backfill_fts5.py +107 -0
  253. package/dist/gaia-ops/tools/memory/conflict_detector.py +295 -0
  254. package/dist/gaia-ops/tools/memory/episodic.py +1210 -0
  255. package/dist/gaia-ops/tools/memory/git_invalidator.py +262 -0
  256. package/dist/gaia-ops/tools/memory/paths.py +102 -0
  257. package/dist/gaia-ops/tools/memory/scoring.py +193 -0
  258. package/dist/gaia-ops/tools/memory/search_store.py +360 -0
  259. package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
  260. package/dist/gaia-ops/tools/review/__init__.py +1 -0
  261. package/dist/gaia-ops/tools/review/review_engine.py +157 -0
  262. package/dist/gaia-ops/tools/scan/__init__.py +35 -0
  263. package/dist/gaia-ops/tools/scan/config.py +247 -0
  264. package/dist/gaia-ops/tools/scan/merge.py +212 -0
  265. package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
  266. package/dist/gaia-ops/tools/scan/registry.py +127 -0
  267. package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
  268. package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
  269. package/dist/gaia-ops/tools/scan/scanners/environment.py +349 -0
  270. package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
  271. package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
  272. package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
  273. package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
  274. package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
  275. package/dist/gaia-ops/tools/scan/setup.py +686 -0
  276. package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
  277. package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
  278. package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
  279. package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
  280. package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
  281. package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
  282. package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
  283. package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
  284. package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
  285. package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
  286. package/dist/gaia-ops/tools/scan/ui.py +624 -0
  287. package/dist/gaia-ops/tools/scan/verify.py +270 -0
  288. package/dist/gaia-ops/tools/scan/walk.py +118 -0
  289. package/dist/gaia-ops/tools/scan/workspace.py +85 -0
  290. package/dist/gaia-ops/tools/validation/README.md +244 -0
  291. package/dist/gaia-ops/tools/validation/__init__.py +17 -0
  292. package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
  293. package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
  294. package/dist/gaia-security/.claude-plugin/plugin.json +24 -0
  295. package/dist/gaia-security/README.md +90 -0
  296. package/dist/gaia-security/config/universal-rules.json +102 -0
  297. package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
  298. package/dist/gaia-security/hooks/adapters/base.py +219 -0
  299. package/dist/gaia-security/hooks/adapters/channel.py +17 -0
  300. package/dist/gaia-security/hooks/adapters/claude_code.py +1890 -0
  301. package/dist/gaia-security/hooks/adapters/types.py +194 -0
  302. package/dist/gaia-security/hooks/adapters/utils.py +25 -0
  303. package/dist/gaia-security/hooks/hooks.json +84 -0
  304. package/dist/gaia-security/hooks/modules/__init__.py +15 -0
  305. package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
  306. package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
  307. package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
  308. package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +120 -0
  309. package/dist/gaia-security/hooks/modules/agents/state_tracker.py +267 -0
  310. package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
  311. package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
  312. package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
  313. package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
  314. package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
  315. package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
  316. package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
  317. package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +611 -0
  318. package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
  319. package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
  320. package/dist/gaia-security/hooks/modules/context/agentic_loop_detector.py +165 -0
  321. package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
  322. package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +218 -0
  323. package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
  324. package/dist/gaia-security/hooks/modules/context/context_injector.py +558 -0
  325. package/dist/gaia-security/hooks/modules/context/context_writer.py +530 -0
  326. package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
  327. package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
  328. package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
  329. package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
  330. package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
  331. package/dist/gaia-security/hooks/modules/core/plugin_setup.py +577 -0
  332. package/dist/gaia-security/hooks/modules/core/state.py +179 -0
  333. package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
  334. package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
  335. package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
  336. package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
  337. package/dist/gaia-security/hooks/modules/memory/episode_writer.py +216 -0
  338. package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
  339. package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +122 -0
  340. package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
  341. package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
  342. package/dist/gaia-security/hooks/modules/security/__init__.py +120 -0
  343. package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
  344. package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
  345. package/dist/gaia-security/hooks/modules/security/approval_grants.py +1638 -0
  346. package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
  347. package/dist/gaia-security/hooks/modules/security/approval_scopes.py +222 -0
  348. package/dist/gaia-security/hooks/modules/security/blocked_commands.py +595 -0
  349. package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +87 -0
  350. package/dist/gaia-security/hooks/modules/security/command_semantics.py +181 -0
  351. package/dist/gaia-security/hooks/modules/security/composition_rules.py +547 -0
  352. package/dist/gaia-security/hooks/modules/security/flag_classifiers.py +873 -0
  353. package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
  354. package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +1131 -0
  355. package/dist/gaia-security/hooks/modules/security/network_hosts.py +481 -0
  356. package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
  357. package/dist/gaia-security/hooks/modules/security/shell_unwrapper.py +165 -0
  358. package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
  359. package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
  360. package/dist/gaia-security/hooks/modules/session/pending_scanner.py +174 -0
  361. package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
  362. package/dist/gaia-security/hooks/modules/session/session_event_injector.py +160 -0
  363. package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
  364. package/dist/gaia-security/hooks/modules/session/session_registry.py +232 -0
  365. package/dist/gaia-security/hooks/modules/tools/__init__.py +29 -0
  366. package/dist/gaia-security/hooks/modules/tools/bash_validator.py +1008 -0
  367. package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  368. package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
  369. package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
  370. package/dist/gaia-security/hooks/modules/tools/stage_decomposer.py +315 -0
  371. package/dist/gaia-security/hooks/modules/tools/task_validator.py +294 -0
  372. package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
  373. package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
  374. package/dist/gaia-security/hooks/post_tool_use.py +54 -0
  375. package/dist/gaia-security/hooks/pre_tool_use.py +413 -0
  376. package/dist/gaia-security/hooks/session_start.py +81 -0
  377. package/dist/gaia-security/hooks/stop_hook.py +82 -0
  378. package/dist/gaia-security/hooks/user_prompt_submit.py +246 -0
  379. package/dist/gaia-security/settings.json +58 -0
  380. package/git-hooks/commit-msg +41 -0
  381. package/hooks/README.md +100 -0
  382. package/hooks/adapters/__init__.py +52 -0
  383. package/hooks/adapters/base.py +219 -0
  384. package/hooks/adapters/channel.py +17 -0
  385. package/hooks/adapters/claude_code.py +1890 -0
  386. package/hooks/adapters/types.py +194 -0
  387. package/hooks/adapters/utils.py +25 -0
  388. package/hooks/elicitation_result.py +179 -0
  389. package/hooks/hooks.json +84 -0
  390. package/hooks/modules/README.md +189 -0
  391. package/hooks/modules/__init__.py +15 -0
  392. package/hooks/modules/agents/__init__.py +29 -0
  393. package/hooks/modules/agents/contract_validator.py +647 -0
  394. package/hooks/modules/agents/response_contract.py +496 -0
  395. package/hooks/modules/agents/skill_injection_verifier.py +120 -0
  396. package/hooks/modules/agents/state_tracker.py +267 -0
  397. package/hooks/modules/agents/task_info_builder.py +74 -0
  398. package/hooks/modules/agents/transcript_analyzer.py +458 -0
  399. package/hooks/modules/agents/transcript_reader.py +152 -0
  400. package/hooks/modules/audit/__init__.py +28 -0
  401. package/hooks/modules/audit/event_detector.py +168 -0
  402. package/hooks/modules/audit/logger.py +131 -0
  403. package/hooks/modules/audit/metrics.py +134 -0
  404. package/hooks/modules/audit/workflow_auditor.py +611 -0
  405. package/hooks/modules/audit/workflow_recorder.py +296 -0
  406. package/hooks/modules/context/__init__.py +11 -0
  407. package/hooks/modules/context/agentic_loop_detector.py +165 -0
  408. package/hooks/modules/context/anchor_tracker.py +317 -0
  409. package/hooks/modules/context/compact_context_builder.py +218 -0
  410. package/hooks/modules/context/context_freshness.py +145 -0
  411. package/hooks/modules/context/context_injector.py +558 -0
  412. package/hooks/modules/context/context_writer.py +530 -0
  413. package/hooks/modules/context/contracts_loader.py +161 -0
  414. package/hooks/modules/core/__init__.py +40 -0
  415. package/hooks/modules/core/hook_entry.py +78 -0
  416. package/hooks/modules/core/paths.py +160 -0
  417. package/hooks/modules/core/plugin_mode.py +149 -0
  418. package/hooks/modules/core/plugin_setup.py +577 -0
  419. package/hooks/modules/core/state.py +179 -0
  420. package/hooks/modules/core/stdin.py +24 -0
  421. package/hooks/modules/events/__init__.py +1 -0
  422. package/hooks/modules/events/event_writer.py +210 -0
  423. package/hooks/modules/evidence/__init__.py +34 -0
  424. package/hooks/modules/evidence/assertions.py +137 -0
  425. package/hooks/modules/evidence/index_writer.py +57 -0
  426. package/hooks/modules/evidence/loader.py +126 -0
  427. package/hooks/modules/evidence/runner.py +241 -0
  428. package/hooks/modules/memory/__init__.py +8 -0
  429. package/hooks/modules/memory/episode_writer.py +216 -0
  430. package/hooks/modules/orchestrator/__init__.py +1 -0
  431. package/hooks/modules/orchestrator/delegate_mode.py +122 -0
  432. package/hooks/modules/scanning/__init__.py +8 -0
  433. package/hooks/modules/scanning/scan_trigger.py +84 -0
  434. package/hooks/modules/security/__init__.py +120 -0
  435. package/hooks/modules/security/approval_cleanup.py +87 -0
  436. package/hooks/modules/security/approval_constants.py +23 -0
  437. package/hooks/modules/security/approval_grants.py +1638 -0
  438. package/hooks/modules/security/approval_messages.py +71 -0
  439. package/hooks/modules/security/approval_scopes.py +222 -0
  440. package/hooks/modules/security/blocked_commands.py +595 -0
  441. package/hooks/modules/security/blocked_message_formatter.py +87 -0
  442. package/hooks/modules/security/command_semantics.py +181 -0
  443. package/hooks/modules/security/composition_rules.py +547 -0
  444. package/hooks/modules/security/flag_classifiers.py +873 -0
  445. package/hooks/modules/security/gitops_validator.py +179 -0
  446. package/hooks/modules/security/mutative_verbs.py +1131 -0
  447. package/hooks/modules/security/network_hosts.py +481 -0
  448. package/hooks/modules/security/prompt_validator.py +40 -0
  449. package/hooks/modules/security/shell_unwrapper.py +165 -0
  450. package/hooks/modules/security/tiers.py +196 -0
  451. package/hooks/modules/session/__init__.py +10 -0
  452. package/hooks/modules/session/pending_scanner.py +174 -0
  453. package/hooks/modules/session/session_context_writer.py +100 -0
  454. package/hooks/modules/session/session_event_injector.py +160 -0
  455. package/hooks/modules/session/session_manager.py +31 -0
  456. package/hooks/modules/session/session_registry.py +232 -0
  457. package/hooks/modules/tools/__init__.py +29 -0
  458. package/hooks/modules/tools/bash_validator.py +1008 -0
  459. package/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  460. package/hooks/modules/tools/hook_response.py +55 -0
  461. package/hooks/modules/tools/shell_parser.py +227 -0
  462. package/hooks/modules/tools/stage_decomposer.py +315 -0
  463. package/hooks/modules/tools/task_validator.py +294 -0
  464. package/hooks/modules/validation/__init__.py +23 -0
  465. package/hooks/modules/validation/commit_validator.py +380 -0
  466. package/hooks/post_compact.py +43 -0
  467. package/hooks/post_tool_use.py +54 -0
  468. package/hooks/pre_compact.py +60 -0
  469. package/hooks/pre_tool_use.py +413 -0
  470. package/hooks/session_start.py +81 -0
  471. package/hooks/stop_hook.py +82 -0
  472. package/hooks/subagent_start.py +71 -0
  473. package/hooks/subagent_stop.py +295 -0
  474. package/hooks/task_completed.py +70 -0
  475. package/hooks/user_prompt_submit.py +246 -0
  476. package/index.js +83 -0
  477. package/package.json +99 -0
  478. package/pyproject.toml +32 -0
  479. package/skills/README.md +154 -0
  480. package/skills/agent-protocol/SKILL.md +93 -0
  481. package/skills/agent-protocol/examples.md +223 -0
  482. package/skills/agent-response/SKILL.md +69 -0
  483. package/skills/agentic-loop/SKILL.md +80 -0
  484. package/skills/agentic-loop/reference.md +378 -0
  485. package/skills/blog-writing/SKILL.md +98 -0
  486. package/skills/blog-writing/reference.md +130 -0
  487. package/skills/brief-spec/SKILL.md +182 -0
  488. package/skills/command-execution/SKILL.md +64 -0
  489. package/skills/command-execution/reference.md +83 -0
  490. package/skills/context-updater/SKILL.md +87 -0
  491. package/skills/context-updater/examples.md +71 -0
  492. package/skills/developer-patterns/SKILL.md +50 -0
  493. package/skills/developer-patterns/reference.md +112 -0
  494. package/skills/execution/SKILL.md +99 -0
  495. package/skills/fast-queries/SKILL.md +43 -0
  496. package/skills/gaia-compact/SKILL.md +74 -0
  497. package/skills/gaia-patterns/SKILL.md +108 -0
  498. package/skills/gaia-patterns/reference.md +395 -0
  499. package/skills/gaia-planner/SKILL.md +37 -0
  500. package/skills/gaia-planner/reference.md +107 -0
  501. package/skills/gaia-release/SKILL.md +82 -0
  502. package/skills/gaia-release/reference.md +102 -0
  503. package/skills/gaia-self-check/SKILL.md +114 -0
  504. package/skills/gaia-self-check/reference.md +453 -0
  505. package/skills/gaia-verify/SKILL.md +77 -0
  506. package/skills/gaia-verify/reference.md +80 -0
  507. package/skills/git-conventions/SKILL.md +47 -0
  508. package/skills/gitops-patterns/SKILL.md +60 -0
  509. package/skills/gitops-patterns/reference.md +183 -0
  510. package/skills/gmail-policy/SKILL.md +200 -0
  511. package/skills/gmail-policy/reference.md +150 -0
  512. package/skills/gmail-triage/SKILL.md +100 -0
  513. package/skills/gws-setup/SKILL.md +99 -0
  514. package/skills/gws-setup/reference.md +73 -0
  515. package/skills/investigation/SKILL.md +100 -0
  516. package/skills/memory-curation/SKILL.md +83 -0
  517. package/skills/memory-search/SKILL.md +88 -0
  518. package/skills/orchestrator-approval/SKILL.md +160 -0
  519. package/skills/orchestrator-approval/reference.md +174 -0
  520. package/skills/pending-approvals/SKILL.md +72 -0
  521. package/skills/pending-approvals/reference.md +214 -0
  522. package/skills/readme-writing/SKILL.md +71 -0
  523. package/skills/readme-writing/reference.md +188 -0
  524. package/skills/reference.md +135 -0
  525. package/skills/request-approval/SKILL.md +140 -0
  526. package/skills/request-approval/examples.md +140 -0
  527. package/skills/request-approval/reference.md +57 -0
  528. package/skills/schedule-task/SKILL.md +64 -0
  529. package/skills/schedule-task/reference.md +233 -0
  530. package/skills/security-tiers/SKILL.md +141 -0
  531. package/skills/security-tiers/destructive-commands-reference.md +623 -0
  532. package/skills/security-tiers/reference.md +39 -0
  533. package/skills/skill-creation/SKILL.md +92 -0
  534. package/skills/skill-creation/reference.md +29 -0
  535. package/skills/terraform-patterns/SKILL.md +89 -0
  536. package/skills/terraform-patterns/reference.md +93 -0
  537. package/templates/README.md +69 -0
  538. package/templates/managed-settings.template.json +43 -0
  539. package/tools/__init__.py +9 -0
  540. package/tools/agentic-loop/decide-status.py +210 -0
  541. package/tools/agentic-loop/parse-metric.py +106 -0
  542. package/tools/agentic-loop/record-iteration.py +221 -0
  543. package/tools/context/README.md +132 -0
  544. package/tools/context/__init__.py +42 -0
  545. package/tools/context/_paths.py +20 -0
  546. package/tools/context/context_provider.py +721 -0
  547. package/tools/context/context_section_reader.py +342 -0
  548. package/tools/context/deep_merge.py +159 -0
  549. package/tools/context/pending_updates.py +760 -0
  550. package/tools/context/surface_router.py +278 -0
  551. package/tools/fast-queries/README.md +65 -0
  552. package/tools/fast-queries/__init__.py +30 -0
  553. package/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  554. package/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  555. package/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  556. package/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  557. package/tools/fast-queries/run_triage.sh +59 -0
  558. package/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  559. package/tools/gaia_simulator/__init__.py +33 -0
  560. package/tools/gaia_simulator/cli.py +354 -0
  561. package/tools/gaia_simulator/extractor.py +457 -0
  562. package/tools/gaia_simulator/reporter.py +258 -0
  563. package/tools/gaia_simulator/routing_simulator.py +334 -0
  564. package/tools/gaia_simulator/runner.py +539 -0
  565. package/tools/gaia_simulator/skills_mapper.py +264 -0
  566. package/tools/memory/README.md +0 -0
  567. package/tools/memory/__init__.py +20 -0
  568. package/tools/memory/backfill_fts5.py +107 -0
  569. package/tools/memory/conflict_detector.py +295 -0
  570. package/tools/memory/episodic.py +1210 -0
  571. package/tools/memory/git_invalidator.py +262 -0
  572. package/tools/memory/paths.py +102 -0
  573. package/tools/memory/scoring.py +193 -0
  574. package/tools/memory/search_store.py +360 -0
  575. package/tools/persist_transcript_analysis.py +85 -0
  576. package/tools/review/__init__.py +1 -0
  577. package/tools/review/review_engine.py +157 -0
  578. package/tools/scan/__init__.py +35 -0
  579. package/tools/scan/config.py +247 -0
  580. package/tools/scan/merge.py +212 -0
  581. package/tools/scan/orchestrator.py +549 -0
  582. package/tools/scan/registry.py +127 -0
  583. package/tools/scan/scanners/__init__.py +18 -0
  584. package/tools/scan/scanners/base.py +137 -0
  585. package/tools/scan/scanners/environment.py +349 -0
  586. package/tools/scan/scanners/git.py +570 -0
  587. package/tools/scan/scanners/infrastructure.py +875 -0
  588. package/tools/scan/scanners/orchestration.py +600 -0
  589. package/tools/scan/scanners/stack.py +1085 -0
  590. package/tools/scan/scanners/tools.py +260 -0
  591. package/tools/scan/setup.py +686 -0
  592. package/tools/scan/tests/__init__.py +1 -0
  593. package/tools/scan/tests/conftest.py +796 -0
  594. package/tools/scan/tests/test_environment.py +323 -0
  595. package/tools/scan/tests/test_git.py +419 -0
  596. package/tools/scan/tests/test_infrastructure.py +382 -0
  597. package/tools/scan/tests/test_integration.py +920 -0
  598. package/tools/scan/tests/test_merge.py +269 -0
  599. package/tools/scan/tests/test_orchestration.py +304 -0
  600. package/tools/scan/tests/test_stack.py +604 -0
  601. package/tools/scan/tests/test_tools.py +349 -0
  602. package/tools/scan/ui.py +624 -0
  603. package/tools/scan/verify.py +270 -0
  604. package/tools/scan/walk.py +118 -0
  605. package/tools/scan/workspace.py +85 -0
  606. package/tools/validation/README.md +244 -0
  607. package/tools/validation/__init__.py +17 -0
  608. package/tools/validation/approval_gate.py +321 -0
  609. package/tools/validation/validate_skills.py +189 -0
@@ -0,0 +1,1278 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @jaguilar87/gaia - Metrics viewer
5
+ *
6
+ * Displays system metrics using real fields from audit/metrics logs.
7
+ *
8
+ * Usage:
9
+ * npx gaia-metrics # full dashboard
10
+ * npx gaia-metrics --agent <name> # agent detail view
11
+ *
12
+ * Metrics shown:
13
+ * - Security tier usage distribution
14
+ * - Command type breakdown (terraform, git, kubernetes, etc.)
15
+ * - Top commands by frequency
16
+ * - Agent invocations (from episodic-memory/index.json)
17
+ * - Anomaly summary (last 30 days, from anomalies.jsonl)
18
+ * - Activity summary for today
19
+ *
20
+ * Data sources:
21
+ * - .claude/logs/audit-*.jsonl (SSOT for command metrics)
22
+ * - .claude/project-context/episodic-memory/index.json (primary, agent metrics)
23
+ * - .claude/project-context/workflow-episodic-memory/metrics.jsonl (fallback)
24
+ * - .claude/project-context/workflow-episodic-memory/anomalies.jsonl
25
+ */
26
+
27
+ import { join, dirname, resolve } from 'path';
28
+ import fs from 'fs/promises';
29
+ import { existsSync } from 'fs';
30
+ import chalk from 'chalk';
31
+ import ora from 'ora';
32
+
33
+ /**
34
+ * Find the project root directory by looking for .claude/ directory
35
+ */
36
+ function findProjectRoot() {
37
+ if (process.env.INIT_CWD) {
38
+ const claudeDir = join(process.env.INIT_CWD, '.claude');
39
+ if (existsSync(claudeDir)) {
40
+ return process.env.INIT_CWD;
41
+ }
42
+ }
43
+
44
+ let currentDir = process.cwd();
45
+ const root = resolve('/');
46
+
47
+ while (currentDir !== root) {
48
+ const claudeDir = join(currentDir, '.claude');
49
+ if (existsSync(claudeDir)) {
50
+ return currentDir;
51
+ }
52
+ currentDir = dirname(currentDir);
53
+ }
54
+
55
+ return process.env.INIT_CWD || process.cwd();
56
+ }
57
+
58
+ const CWD = findProjectRoot();
59
+
60
+ async function readJsonLines(path) {
61
+ if (!existsSync(path)) return [];
62
+
63
+ try {
64
+ const content = await fs.readFile(path, 'utf-8');
65
+ return content.split('\n')
66
+ .filter(line => line.trim())
67
+ .map(line => {
68
+ try {
69
+ return JSON.parse(line);
70
+ } catch {
71
+ return null;
72
+ }
73
+ })
74
+ .filter(Boolean);
75
+ } catch {
76
+ return [];
77
+ }
78
+ }
79
+
80
+ // ─────────────────────────────────────────────────────────────
81
+ // DATA READERS
82
+ // ─────────────────────────────────────────────────────────────
83
+
84
+ /**
85
+ * Read audit logs from .claude/logs/audit-*.jsonl
86
+ * Fields: timestamp, tool_name, command, tier, exit_code, session_id
87
+ */
88
+ async function readAuditLogs() {
89
+ try {
90
+ const logsDir = join(CWD, '.claude', 'logs');
91
+ if (!existsSync(logsDir)) return [];
92
+
93
+ const files = await fs.readdir(logsDir);
94
+ const auditFiles = files.filter(f => f.startsWith('audit-') && f.endsWith('.jsonl'));
95
+
96
+ let all = [];
97
+ for (const file of auditFiles) {
98
+ try {
99
+ const content = await fs.readFile(join(logsDir, file), 'utf-8');
100
+ const parsed = content.split('\n')
101
+ .filter(l => l.trim())
102
+ .map(l => { try { return JSON.parse(l); } catch { return null; } })
103
+ .filter(Boolean);
104
+ all = all.concat(parsed);
105
+ } catch { /* skip */ }
106
+ }
107
+ return all;
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+
113
+ // metrics-*.jsonl removed: audit logs are the SSOT for command metrics.
114
+ // The command_type field is now derived via classifyCommand() from the
115
+ // audit log's command field. Set GAIA_WRITE_METRICS=1 to re-enable writing.
116
+
117
+ /**
118
+ * Read workflow metrics from the episodic memory index (primary source).
119
+ * Falls back to workflow-episodic-memory/metrics.jsonl for backward compatibility.
120
+ * Fields: timestamp, agent, exit_code, output_length, task_id, session_id,
121
+ * plan_status, output_tokens_approx, prompt
122
+ */
123
+ async function readWorkflowMetrics() {
124
+ // Primary: episodic-memory/index.json
125
+ try {
126
+ const indexPath = join(CWD, '.claude', 'project-context', 'episodic-memory', 'index.json');
127
+ if (existsSync(indexPath)) {
128
+ const data = JSON.parse(await fs.readFile(indexPath, 'utf-8'));
129
+ const episodes = (data.episodes || []).filter(e => e.agent);
130
+ if (episodes.length > 0) return episodes;
131
+ }
132
+ } catch { /* fall through to legacy */ }
133
+
134
+ // Fallback: workflow-episodic-memory/metrics.jsonl
135
+ try {
136
+ const metricsPath = join(CWD, '.claude', 'project-context', 'workflow-episodic-memory', 'metrics.jsonl');
137
+ if (!existsSync(metricsPath)) return [];
138
+
139
+ const content = await fs.readFile(metricsPath, 'utf-8');
140
+ return content.split('\n')
141
+ .filter(l => l.trim())
142
+ .map(l => { try { return JSON.parse(l); } catch { return null; } })
143
+ .filter(r => r !== null && r.agent); // exclude empty-agent entries
144
+ } catch {
145
+ return [];
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Read structured run telemetry snapshots from workflow memory.
151
+ * Fields: timestamp, session_id, task_id, agent, tier, plan_status,
152
+ * context_snapshot, context_updated, context_sections_updated,
153
+ * context_rejected_sections, default_skills_snapshot
154
+ */
155
+ async function readRunSnapshots() {
156
+ const path = join(CWD, '.claude', 'project-context', 'workflow-episodic-memory', 'run-snapshots.jsonl');
157
+ return readJsonLines(path);
158
+ }
159
+
160
+ /**
161
+ * Read persisted runtime skill snapshots from workflow memory.
162
+ * Fields: timestamp, session_id, agent, task_description, model, tools,
163
+ * skills, skills_count
164
+ */
165
+ async function readAgentSkillSnapshots() {
166
+ const path = join(CWD, '.claude', 'project-context', 'workflow-episodic-memory', 'agent-skills.jsonl');
167
+ return readJsonLines(path);
168
+ }
169
+
170
+ /**
171
+ * Read anomaly records from workflow memory.
172
+ * Fields: timestamp, anomalies, metrics
173
+ */
174
+ async function readAnomalyEntries() {
175
+ const path = join(CWD, '.claude', 'project-context', 'workflow-episodic-memory', 'anomalies.jsonl');
176
+ return readJsonLines(path);
177
+ }
178
+
179
+ /**
180
+ * Read and parse agent definition from .claude/agents/<name>.md
181
+ * Extracts description and skills list from YAML frontmatter.
182
+ * Returns { description, skills } or null if not found.
183
+ */
184
+ async function readAgentDefinition(agentName) {
185
+ const agentPath = join(CWD, '.claude', 'agents', `${agentName}.md`);
186
+ if (!existsSync(agentPath)) return null;
187
+
188
+ try {
189
+ const content = await fs.readFile(agentPath, 'utf-8');
190
+ if (!content.startsWith('---')) return null;
191
+
192
+ const endIdx = content.indexOf('---', 3);
193
+ if (endIdx === -1) return null;
194
+
195
+ const frontmatter = content.slice(3, endIdx);
196
+ let description = '';
197
+ const skills = [];
198
+ let inSkills = false;
199
+
200
+ for (const line of frontmatter.split('\n')) {
201
+ const trimmed = line.trim();
202
+ if (trimmed.startsWith('description:')) {
203
+ description = trimmed.replace(/^description:\s*/, '').replace(/^['"]|['"]$/g, '');
204
+ inSkills = false;
205
+ } else if (trimmed === 'skills:') {
206
+ inSkills = true;
207
+ } else if (inSkills && trimmed.startsWith('- ')) {
208
+ skills.push(trimmed.slice(2).trim());
209
+ } else if (inSkills && trimmed && !trimmed.startsWith('-')) {
210
+ inSkills = false;
211
+ }
212
+ }
213
+
214
+ return { description, skills };
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+
220
+ // ─────────────────────────────────────────────────────────────
221
+ // UTILITY FUNCTIONS
222
+ // ─────────────────────────────────────────────────────────────
223
+
224
+ /**
225
+ * Classify a raw command string into a technology category.
226
+ * Used as fallback when metrics log command_type is unavailable.
227
+ */
228
+ function classifyCommand(command) {
229
+ if (!command) return 'general';
230
+ const cmd = command.trim().toLowerCase();
231
+
232
+ if (cmd.startsWith('terragrunt') || cmd.startsWith('terraform')) return 'terraform';
233
+ if (cmd.startsWith('kubectl')) return 'kubernetes';
234
+ if (cmd.startsWith('helm') || cmd.startsWith('flux')) return 'gitops';
235
+ if (cmd.startsWith('git') || cmd.startsWith('glab')) return 'git';
236
+ if (cmd.startsWith('gcloud') || cmd.startsWith('gsutil')) return 'gcp';
237
+ if (cmd.startsWith('aws')) return 'aws';
238
+ if (cmd.startsWith('docker')) return 'docker';
239
+ if (cmd.startsWith('npm') || cmd.startsWith('node') || cmd.startsWith('python') || cmd.startsWith('pip')) return 'dev';
240
+ return 'general';
241
+ }
242
+
243
+ /**
244
+ * Extract a short human-readable label from a full command string.
245
+ * Strips wrappers like `timeout 30s`, env vars, and path prefixes.
246
+ * Takes the tool name + first subcommand, truncated to 32 chars.
247
+ */
248
+ function extractCommandLabel(command) {
249
+ if (!command) return '(unknown)';
250
+ let cmd = command.trim();
251
+
252
+ // Strip leading env var assignments (FOO=bar cmd ...)
253
+ cmd = cmd.replace(/^(?:[A-Z_][A-Z0-9_]*=\S+\s+)+/, '');
254
+
255
+ // Strip timeout wrapper: "timeout 30s <real cmd>"
256
+ cmd = cmd.replace(/^timeout\s+\S+\s+/, '');
257
+
258
+ // Strip leading cd/pushd navigation: "cd /some/path && <real cmd>"
259
+ const cdMatch = cmd.match(/^(?:cd|pushd)\s+\S+\s*(?:&&|;)\s*(.*)/);
260
+ if (cdMatch) cmd = cdMatch[1].trim();
261
+
262
+ // Strip shell redirections and anything after pipe/semicolon/&&
263
+ cmd = cmd.split(/\s*(?:[|;&]|&&|\|\|)\s*/)[0].trim();
264
+ // Strip trailing redirections like 2>&1 or > /tmp/file
265
+ cmd = cmd.replace(/\s*\d*>.*$/, '').trim();
266
+
267
+ const tokens = cmd.split(/\s+/);
268
+ // Take tool (token 0) + first non-flag, non-path, non-quote argument
269
+ const parts = [tokens[0]];
270
+ for (let i = 1; i < tokens.length && parts.length < 3; i++) {
271
+ const t = tokens[i];
272
+ if (!t.startsWith('-') && !t.startsWith('/') && !t.startsWith('"') && !t.startsWith("'")) {
273
+ parts.push(t);
274
+ }
275
+ }
276
+ return parts.join(' ').slice(0, 32);
277
+ }
278
+
279
+ /**
280
+ * Format output_length as human-readable string (e.g. "23.3k chars").
281
+ */
282
+ function formatChars(n) {
283
+ if (n === null || n === undefined) return 'n/a';
284
+ if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
285
+ return `${n}`;
286
+ }
287
+
288
+ /**
289
+ * Build a fixed-width bar string using block characters.
290
+ */
291
+ function makeBar(percentage, maxWidth = 14) {
292
+ const filled = Math.max(0, Math.round((percentage / 100) * maxWidth));
293
+ return '█'.repeat(filled);
294
+ }
295
+
296
+ // ─────────────────────────────────────────────────────────────
297
+ // METRIC CALCULATORS
298
+ // ─────────────────────────────────────────────────────────────
299
+
300
+ /**
301
+ * Security tier usage distribution.
302
+ * Also extracts today's activity stats and peak hour.
303
+ */
304
+ function calculateTierUsage(auditLogs) {
305
+ const tierEntries = auditLogs.filter(l => l.tier);
306
+
307
+ const counts = {};
308
+ for (const entry of tierEntries) {
309
+ const t = entry.tier || 'unknown';
310
+ counts[t] = (counts[t] || 0) + 1;
311
+ }
312
+
313
+ const total = tierEntries.length;
314
+ const distribution = Object.entries(counts)
315
+ .map(([tier, count]) => ({
316
+ tier,
317
+ count,
318
+ percentage: total > 0 ? (count / total * 100) : 0,
319
+ }))
320
+ .sort((a, b) => a.tier.localeCompare(b.tier));
321
+
322
+ // Today's stats
323
+ const today = new Date().toISOString().slice(0, 10);
324
+ const todayEntries = auditLogs.filter(l => l.timestamp && l.timestamp.startsWith(today));
325
+ const todayT3 = todayEntries.filter(l => l.tier === 'T3').length;
326
+
327
+ // Peak hour from today's entries
328
+ const hourCounts = {};
329
+ for (const entry of todayEntries) {
330
+ if (entry.timestamp) {
331
+ const hour = entry.timestamp.slice(11, 13);
332
+ hourCounts[hour] = (hourCounts[hour] || 0) + 1;
333
+ }
334
+ }
335
+ let peakHour = null;
336
+ let peakCount = 0;
337
+ for (const [hour, count] of Object.entries(hourCounts)) {
338
+ if (count > peakCount) { peakCount = count; peakHour = hour; }
339
+ }
340
+
341
+ return { total, distribution, todayCount: todayEntries.length, todayT3, peakHour, peakCount };
342
+ }
343
+
344
+ /**
345
+ * Command type breakdown.
346
+ * Derives command_type via classifyCommand() from the audit log command field.
347
+ * Audit logs are the SSOT; metrics-*.jsonl is no longer written.
348
+ */
349
+ function calculateCommandTypeBreakdown(auditLogs) {
350
+ const counts = {};
351
+ for (const entry of auditLogs) {
352
+ const type = classifyCommand(entry.command);
353
+ counts[type] = (counts[type] || 0) + 1;
354
+ }
355
+
356
+ const total = auditLogs.length;
357
+ const breakdown = Object.entries(counts)
358
+ .map(([type, count]) => ({
359
+ type,
360
+ count,
361
+ percentage: total > 0 ? (count / total * 100) : 0,
362
+ }))
363
+ .sort((a, b) => b.count - a.count);
364
+
365
+ return { total, breakdown };
366
+ }
367
+
368
+ /**
369
+ * Top 10 commands by frequency from audit logs.
370
+ * Labels are the first 2-3 non-flag tokens of the command string.
371
+ * Tracks the highest tier seen per label and whether any T3 was used.
372
+ */
373
+ function calculateTopCommands(auditLogs) {
374
+ const tierOrder = { T3: 3, T2: 2, T1: 1, T0: 0, unknown: -1 };
375
+ const labelMap = {};
376
+
377
+ for (const entry of auditLogs) {
378
+ if (!entry.command) continue;
379
+ const label = extractCommandLabel(entry.command);
380
+ const tier = entry.tier || 'unknown';
381
+
382
+ if (!labelMap[label]) {
383
+ labelMap[label] = { count: 0, tier, t3count: 0 };
384
+ }
385
+ labelMap[label].count++;
386
+ if (tier === 'T3') labelMap[label].t3count++;
387
+ if ((tierOrder[tier] ?? -1) > (tierOrder[labelMap[label].tier] ?? -1)) {
388
+ labelMap[label].tier = tier;
389
+ }
390
+ }
391
+
392
+ return Object.entries(labelMap)
393
+ .map(([label, { count, tier, t3count }]) => ({ label, count, tier, t3count }))
394
+ .sort((a, b) => b.count - a.count)
395
+ .slice(0, 10);
396
+ }
397
+
398
+ /**
399
+ * Error rate analysis from audit logs.
400
+ * Detects whether the known hook API bug (exit_code always 0) is present.
401
+ */
402
+ function calculateErrorRate(auditLogs) {
403
+ const withExitCode = auditLogs.filter(l => 'exit_code' in l);
404
+ const errors = withExitCode.filter(l => l.exit_code !== 0);
405
+ const allZero = withExitCode.length > 0 && errors.length === 0;
406
+
407
+ return {
408
+ total: withExitCode.length,
409
+ errors: errors.length,
410
+ errorRate: withExitCode.length > 0 ? (errors.length / withExitCode.length * 100) : 0,
411
+ limitedByApi: allZero,
412
+ };
413
+ }
414
+
415
+ /**
416
+ * Agent invocations summary from workflow episodic metrics.
417
+ * Groups by agent name, computes count, avg output_length, success rate.
418
+ * Also returns todayCount for the header.
419
+ */
420
+ function calculateAgentInvocations(workflowMetrics) {
421
+ const today = new Date().toISOString().slice(0, 10);
422
+ const todayCount = workflowMetrics.filter(
423
+ r => r.timestamp && r.timestamp.startsWith(today)
424
+ ).length;
425
+
426
+ const agentMap = {};
427
+ for (const entry of workflowMetrics) {
428
+ const name = entry.agent;
429
+ if (!agentMap[name]) {
430
+ agentMap[name] = { count: 0, totalOutput: 0, successes: 0 };
431
+ }
432
+ agentMap[name].count++;
433
+ agentMap[name].totalOutput += (entry.output_length || 0);
434
+ if (entry.exit_code === 0) agentMap[name].successes++;
435
+ }
436
+
437
+ const total = workflowMetrics.length;
438
+ const agents = Object.entries(agentMap)
439
+ .map(([name, { count, totalOutput, successes }]) => ({
440
+ name,
441
+ count,
442
+ avgOutput: count > 0 ? Math.round(totalOutput / count) : 0,
443
+ successRate: count > 0 ? (successes / count * 100) : 0,
444
+ percentage: total > 0 ? (count / total * 100) : 0,
445
+ }))
446
+ .sort((a, b) => b.count - a.count);
447
+
448
+ return { agents, total, todayCount };
449
+ }
450
+
451
+ /**
452
+ * Agent outcome distribution from plan_status field.
453
+ * Counts COMPLETE, BLOCKED, NEEDS_INPUT, IN_PROGRESS, REVIEW, and others.
454
+ * Returns null if no entries have the plan_status field (older data).
455
+ */
456
+ function calculateAgentOutcomes(workflowMetrics) {
457
+ const withStatus = workflowMetrics.filter(r => r.plan_status && r.plan_status !== '');
458
+ if (withStatus.length === 0) return null;
459
+
460
+ const counts = {};
461
+ for (const entry of withStatus) {
462
+ const status = entry.plan_status.toUpperCase();
463
+ counts[status] = (counts[status] || 0) + 1;
464
+ }
465
+
466
+ const total = withStatus.length;
467
+ const distribution = Object.entries(counts)
468
+ .map(([status, count]) => ({ status, count, percentage: (count / total) * 100 }))
469
+ .sort((a, b) => b.count - a.count);
470
+
471
+ return { distribution, total };
472
+ }
473
+
474
+ /**
475
+ * Token usage approximation from output_tokens_approx field.
476
+ * Groups by agent, computes total and average.
477
+ * Returns null if no entries have the field (older data).
478
+ */
479
+ function calculateTokenUsage(workflowMetrics) {
480
+ const withTokens = workflowMetrics.filter(r => typeof r.output_tokens_approx === 'number');
481
+ if (withTokens.length === 0) return null;
482
+
483
+ const agentMap = {};
484
+ for (const entry of withTokens) {
485
+ const name = entry.agent || 'unknown';
486
+ if (!agentMap[name]) agentMap[name] = { total: 0, count: 0 };
487
+ agentMap[name].total += entry.output_tokens_approx;
488
+ agentMap[name].count++;
489
+ }
490
+
491
+ const grandTotal = withTokens.reduce((s, r) => s + r.output_tokens_approx, 0);
492
+ const agents = Object.entries(agentMap)
493
+ .map(([name, { total, count }]) => ({
494
+ name,
495
+ total,
496
+ avg: count > 0 ? Math.round(total / count) : 0,
497
+ count,
498
+ }))
499
+ .sort((a, b) => b.total - a.total);
500
+
501
+ return { agents, grandTotal, entryCount: withTokens.length };
502
+ }
503
+
504
+ /**
505
+ * Runtime skill snapshot summary from agent-skills.jsonl and run snapshots.
506
+ */
507
+ function calculateRuntimeSkillSummary(skillSnapshots, runSnapshots) {
508
+ const explicitSnapshots = skillSnapshots.filter(entry => entry && entry.agent);
509
+ const runtimeDefaults = runSnapshots
510
+ .filter(entry => entry && entry.agent && entry.default_skills_snapshot)
511
+ .map(entry => ({
512
+ timestamp: entry.timestamp,
513
+ session_id: entry.session_id,
514
+ agent: entry.agent,
515
+ model: entry.default_skills_snapshot.model || '',
516
+ tools: entry.default_skills_snapshot.tools || [],
517
+ skills: entry.default_skills_snapshot.skills || [],
518
+ skills_count: entry.default_skills_snapshot.skills_count || 0,
519
+ source: 'run-default',
520
+ }));
521
+
522
+ const latestByAgent = new Map();
523
+ for (const snapshot of [...runtimeDefaults, ...explicitSnapshots]) {
524
+ const agent = snapshot.agent || 'unknown';
525
+ const current = latestByAgent.get(agent);
526
+ if (!current || String(snapshot.timestamp || '') >= String(current.timestamp || '')) {
527
+ latestByAgent.set(agent, {
528
+ agent,
529
+ timestamp: snapshot.timestamp || '',
530
+ model: snapshot.model || '',
531
+ tools: Array.isArray(snapshot.tools) ? snapshot.tools : [],
532
+ skills: Array.isArray(snapshot.skills) ? snapshot.skills : [],
533
+ skillsCount: typeof snapshot.skills_count === 'number'
534
+ ? snapshot.skills_count
535
+ : Array.isArray(snapshot.skills) ? snapshot.skills.length : 0,
536
+ source: snapshot.source || 'explicit',
537
+ });
538
+ }
539
+ }
540
+
541
+ const latestProfiles = [...latestByAgent.values()]
542
+ .sort((a, b) => a.agent.localeCompare(b.agent));
543
+ const topSkillsSummary = topCounts(latestProfiles.flatMap(profile => profile.skills), 6);
544
+
545
+ return {
546
+ explicitCount: explicitSnapshots.length,
547
+ runDefaultCount: runtimeDefaults.length,
548
+ agentCount: latestProfiles.length,
549
+ latestProfiles,
550
+ topSkills: topSkillsSummary,
551
+ };
552
+ }
553
+
554
+ /**
555
+ * Context snapshot summary from run-snapshots.jsonl.
556
+ */
557
+ function calculateContextSnapshotSummary(runSnapshots) {
558
+ const withContext = runSnapshots.filter(entry => {
559
+ const snapshot = entry.context_snapshot || {};
560
+ return Object.keys(snapshot).length > 0;
561
+ });
562
+
563
+ if (withContext.length === 0) return null;
564
+
565
+ const primarySurfaces = [];
566
+ const contractSections = [];
567
+ const writableSections = [];
568
+ let multiSurfaceCount = 0;
569
+
570
+ for (const entry of withContext) {
571
+ const snapshot = entry.context_snapshot || {};
572
+ if (snapshot.surface_routing?.primary_surface) {
573
+ primarySurfaces.push(snapshot.surface_routing.primary_surface);
574
+ }
575
+ if (snapshot.surface_routing?.multi_surface) {
576
+ multiSurfaceCount++;
577
+ }
578
+ contractSections.push(...(snapshot.contract_sections || []));
579
+ writableSections.push(...(snapshot.context_update_scope?.writable_sections || []));
580
+ }
581
+
582
+ return {
583
+ total: withContext.length,
584
+ multiSurfaceCount,
585
+ primarySurfaces: topCounts(primarySurfaces, 6),
586
+ contractSections: topCounts(contractSections, 6),
587
+ writableSections: topCounts(writableSections, 6),
588
+ };
589
+ }
590
+
591
+ /**
592
+ * Context update summary from run-snapshots.jsonl.
593
+ */
594
+ function calculateContextUpdateSummary(runSnapshots) {
595
+ if (runSnapshots.length === 0) return null;
596
+
597
+ const updatedRuns = runSnapshots.filter(entry => entry.context_updated);
598
+ const rejectedRuns = runSnapshots.filter(
599
+ entry => Array.isArray(entry.context_rejected_sections) && entry.context_rejected_sections.length > 0
600
+ );
601
+
602
+ return {
603
+ totalRuns: runSnapshots.length,
604
+ updatedRuns: updatedRuns.length,
605
+ rejectedRuns: rejectedRuns.length,
606
+ updatedSections: topCounts(
607
+ updatedRuns.flatMap(entry => entry.context_sections_updated || []),
608
+ 6
609
+ ),
610
+ rejectedSections: topCounts(
611
+ runSnapshots.flatMap(entry => entry.context_rejected_sections || []),
612
+ 6
613
+ ),
614
+ };
615
+ }
616
+
617
+ /**
618
+ * Anomaly summary from workflow-episodic-memory/anomalies.jsonl.
619
+ * Groups anomalies by type for the last 30 days.
620
+ */
621
+ function calculateAnomalySummary(anomalyEntries) {
622
+ const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
623
+ const entries = anomalyEntries.filter(
624
+ entry => entry && entry.timestamp && entry.timestamp >= cutoff
625
+ );
626
+
627
+ if (entries.length === 0) return null;
628
+
629
+ const typeCounts = {};
630
+ const agentCounts = {};
631
+ for (const entry of entries) {
632
+ const anomalies = entry.anomalies || [];
633
+ const agent = entry.metrics?.agent || 'unknown';
634
+ for (const anomaly of anomalies) {
635
+ const type = anomaly.type || 'unknown';
636
+ typeCounts[type] = (typeCounts[type] || 0) + 1;
637
+ agentCounts[agent] = (agentCounts[agent] || 0) + 1;
638
+ }
639
+ }
640
+
641
+ const total = Object.values(typeCounts).reduce((sum, count) => sum + count, 0);
642
+ const byType = Object.entries(typeCounts)
643
+ .map(([type, count]) => ({
644
+ type,
645
+ count,
646
+ percentage: total > 0 ? (count / total * 100) : 0,
647
+ }))
648
+ .sort((a, b) => b.count - a.count);
649
+
650
+ return {
651
+ total,
652
+ sessionCount: entries.length,
653
+ byType,
654
+ byAgent: sortedCounts(agentCounts).slice(0, 5),
655
+ };
656
+ }
657
+
658
+ /**
659
+ * Extract audit log entries that fall within the time window of a given
660
+ * agent session. Uses the session timestamp as the end boundary, and
661
+ * the previous named-agent session as the start boundary (approximation).
662
+ *
663
+ * This is a best-effort time-window correlation, documented as approximate.
664
+ */
665
+ function correlateAuditLogsToSession(auditLogs, sessionEnd, sessionStart) {
666
+ const endTs = new Date(sessionEnd).getTime();
667
+ const startTs = sessionStart ? new Date(sessionStart).getTime() : endTs - 10 * 60 * 1000; // 10 min fallback
668
+
669
+ return auditLogs.filter(entry => {
670
+ if (!entry.timestamp) return false;
671
+ const ts = new Date(entry.timestamp).getTime();
672
+ return ts >= startTs && ts <= endTs;
673
+ });
674
+ }
675
+
676
+ function getLatestRuntimeProfile(agentName, skillSnapshots, runSnapshots) {
677
+ const explicit = skillSnapshots
678
+ .filter(entry => entry.agent === agentName)
679
+ .map(entry => ({
680
+ timestamp: entry.timestamp || '',
681
+ model: entry.model || '',
682
+ tools: Array.isArray(entry.tools) ? entry.tools : [],
683
+ skills: Array.isArray(entry.skills) ? entry.skills : [],
684
+ skillsCount: typeof entry.skills_count === 'number'
685
+ ? entry.skills_count
686
+ : Array.isArray(entry.skills) ? entry.skills.length : 0,
687
+ source: 'explicit',
688
+ }));
689
+
690
+ const defaults = runSnapshots
691
+ .filter(entry => entry.agent === agentName && entry.default_skills_snapshot)
692
+ .map(entry => ({
693
+ timestamp: entry.timestamp || '',
694
+ model: entry.default_skills_snapshot.model || '',
695
+ tools: entry.default_skills_snapshot.tools || [],
696
+ skills: entry.default_skills_snapshot.skills || [],
697
+ skillsCount: typeof entry.default_skills_snapshot.skills_count === 'number'
698
+ ? entry.default_skills_snapshot.skills_count
699
+ : Array.isArray(entry.default_skills_snapshot.skills)
700
+ ? entry.default_skills_snapshot.skills.length
701
+ : 0,
702
+ source: 'run-default',
703
+ }));
704
+
705
+ const snapshots = [...defaults, ...explicit].sort(
706
+ (a, b) => String(b.timestamp).localeCompare(String(a.timestamp))
707
+ );
708
+
709
+ return {
710
+ latest: snapshots[0] || null,
711
+ explicitCount: explicit.length,
712
+ runDefaultCount: defaults.length,
713
+ };
714
+ }
715
+
716
+ function calculateAgentAnomalySummary(agentName, anomalyEntries) {
717
+ const entries = anomalyEntries.filter(entry => entry.metrics?.agent === agentName);
718
+ if (entries.length === 0) return null;
719
+
720
+ const typeCounts = {};
721
+ for (const entry of entries) {
722
+ for (const anomaly of (entry.anomalies || [])) {
723
+ const type = anomaly.type || 'unknown';
724
+ typeCounts[type] = (typeCounts[type] || 0) + 1;
725
+ }
726
+ }
727
+
728
+ return {
729
+ total: Object.values(typeCounts).reduce((sum, count) => sum + count, 0),
730
+ sessionCount: entries.length,
731
+ byType: sortedCounts(typeCounts).slice(0, 6),
732
+ };
733
+ }
734
+
735
+ // ─────────────────────────────────────────────────────────────
736
+ // DISPLAY FUNCTIONS
737
+ // ─────────────────────────────────────────────────────────────
738
+
739
+ /**
740
+ * Format token count as human-readable (e.g. "6.9k", "1.2M").
741
+ */
742
+ function formatTokens(n) {
743
+ if (n === null || n === undefined) return 'n/a';
744
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
745
+ if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
746
+ return `${n}`;
747
+ }
748
+
749
+ function countValues(values) {
750
+ const counts = {};
751
+ for (const value of values) {
752
+ if (!value) continue;
753
+ counts[value] = (counts[value] || 0) + 1;
754
+ }
755
+ return counts;
756
+ }
757
+
758
+ function sortedCounts(counts) {
759
+ return Object.entries(counts)
760
+ .map(([name, count]) => ({ name, count }))
761
+ .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
762
+ }
763
+
764
+ function topCounts(values, limit = 5) {
765
+ return sortedCounts(countValues(values)).slice(0, limit);
766
+ }
767
+
768
+ function formatCountSummary(entries, emptyLabel = 'none') {
769
+ if (!entries || entries.length === 0) return emptyLabel;
770
+ return entries.map(({ name, count }) => `${name}(${count})`).join(', ');
771
+ }
772
+
773
+ function formatSkills(skills, limit = 4) {
774
+ if (!Array.isArray(skills) || skills.length === 0) return 'none';
775
+ if (skills.length <= limit) return skills.join(', ');
776
+ return `${skills.slice(0, limit).join(', ')}, +${skills.length - limit} more`;
777
+ }
778
+
779
+ /**
780
+ * Display the main dashboard metrics.
781
+ */
782
+ function displayMetrics(
783
+ tiers,
784
+ cmdTypes,
785
+ topCmds,
786
+ agentInvocations,
787
+ errorStats,
788
+ auditTotal,
789
+ agentOutcomes,
790
+ tokenUsage,
791
+ anomalySummary,
792
+ runtimeSkills,
793
+ contextSnapshots,
794
+ contextUpdates
795
+ ) {
796
+ const SEP = chalk.gray('═'.repeat(52));
797
+
798
+ console.log(chalk.cyan('\n📊 Gaia-Ops System Metrics'));
799
+ console.log(SEP);
800
+
801
+ // ── Security Tier Usage ──────────────────────────────
802
+ console.log(chalk.bold(`\n🔒 Security Tier Usage (${tiers.total} operations)`));
803
+
804
+ if (tiers.total === 0) {
805
+ console.log(chalk.gray(' no tier data'));
806
+ } else {
807
+ const tierLabel = { T0: 'read-only', T1: 'validation', T2: 'simulation', T3: 'realization' };
808
+ for (const { tier, count, percentage } of tiers.distribution) {
809
+ const color = tier === 'T3' ? chalk.red
810
+ : tier === 'T2' ? chalk.yellow
811
+ : chalk.green;
812
+ const bar = makeBar(percentage, 14);
813
+ const pct = percentage.toFixed(1).padStart(5);
814
+ const label = tierLabel[tier] || tier;
815
+ const suffix = tier === 'T3' ? chalk.red(' ⚠️ realization') : ` ${label}`;
816
+ console.log(color(
817
+ ` ${tier.padEnd(4)} ${count.toString().padStart(4)} ${bar.padEnd(14)} ${pct}%${suffix}`
818
+ ));
819
+ }
820
+ }
821
+
822
+ // ── Command Type Breakdown ───────────────────────────
823
+ console.log(chalk.bold(`\n🛠 Command Type Breakdown (derived from ${auditTotal} audit entries)`));
824
+
825
+ if (cmdTypes.breakdown.length === 0) {
826
+ console.log(chalk.gray(' no command data'));
827
+ } else {
828
+ for (const { type, count, percentage } of cmdTypes.breakdown) {
829
+ const bar = makeBar(percentage, 10);
830
+ const pct = percentage.toFixed(1).padStart(5);
831
+ console.log(` ${type.padEnd(12)} ${count.toString().padStart(4)} ${bar.padEnd(10)} ${pct}%`);
832
+ }
833
+ }
834
+
835
+ // ── Top Commands ─────────────────────────────────────
836
+ console.log(chalk.bold('\n🔝 Top Commands'));
837
+
838
+ if (topCmds.length === 0) {
839
+ console.log(chalk.gray(' no command data'));
840
+ } else {
841
+ for (const { label, count, tier, t3count } of topCmds) {
842
+ const tierColor = tier === 'T3' ? chalk.red
843
+ : tier === 'T2' ? chalk.yellow
844
+ : chalk.gray;
845
+ const warn = t3count > 0 ? chalk.red(' ⚠️') : '';
846
+ console.log(
847
+ ` ${label.padEnd(30)} ${count.toString().padStart(4)} ${tierColor(tier)}${warn}`
848
+ );
849
+ }
850
+ }
851
+
852
+ // ── Agent Invocations ────────────────────────────────
853
+ const agentHeader = agentInvocations.todayCount > 0
854
+ ? `(${agentInvocations.todayCount} sessions today)`
855
+ : `(${agentInvocations.total} total)`;
856
+ console.log(chalk.bold(`\n🤖 Agent Invocations ${agentHeader}`));
857
+
858
+ if (agentInvocations.agents.length === 0) {
859
+ console.log(chalk.gray(' no invocation data'));
860
+ } else {
861
+ for (const { name, count, avgOutput, successRate, percentage } of agentInvocations.agents) {
862
+ const bar = makeBar(percentage, 8);
863
+ const avg = `avg ${formatChars(avgOutput).padStart(6)} chars`;
864
+ const ok = successRate === 100
865
+ ? chalk.green('100% ok')
866
+ : chalk.yellow(`${successRate.toFixed(0)}% ok`);
867
+ console.log(
868
+ ` ${name.padEnd(24)} ${count.toString().padStart(3)} ${bar.padEnd(8)} ${avg} ${ok}`
869
+ );
870
+ }
871
+ console.log(chalk.gray(` 💡 tip: npx gaia-metrics --agent <name> for detail view`));
872
+ }
873
+
874
+ // ── Agent Outcomes ───────────────────────────────────
875
+ if (agentOutcomes) {
876
+ console.log(chalk.bold(`\n📋 Agent Outcomes (${agentOutcomes.total} sessions with status)`));
877
+ const outcomeColor = { COMPLETE: chalk.green, BLOCKED: chalk.red, NEEDS_INPUT: chalk.yellow, IN_PROGRESS: chalk.cyan, REVIEW: chalk.magenta };
878
+ for (const { status, count, percentage } of agentOutcomes.distribution) {
879
+ const bar = makeBar(percentage, 10);
880
+ const pct = percentage.toFixed(1).padStart(5);
881
+ const color = outcomeColor[status] || chalk.gray;
882
+ console.log(color(` ${status.padEnd(16)} ${count.toString().padStart(3)} ${bar.padEnd(10)} ${pct}%`));
883
+ }
884
+ }
885
+
886
+ // ── Token Usage (approx) ─────────────────────────────
887
+ if (tokenUsage) {
888
+ console.log(chalk.bold(`\n🪙 Token Usage (approx) total: ~${formatTokens(tokenUsage.grandTotal)}`));
889
+ for (const { name, total, avg, count } of tokenUsage.agents) {
890
+ const totalFmt = formatTokens(total).padStart(6);
891
+ const avgFmt = formatTokens(avg).padStart(6);
892
+ console.log(` ${name.padEnd(24)} ${count.toString().padStart(3)} sessions total ${totalFmt} avg ${avgFmt}`);
893
+ }
894
+ }
895
+
896
+ // ── Runtime Skill Snapshots ───────────────────────────
897
+ if (runtimeSkills && runtimeSkills.agentCount > 0) {
898
+ console.log(chalk.bold(
899
+ `\n🧠 Runtime Skill Snapshots (${runtimeSkills.agentCount} agents, ${runtimeSkills.explicitCount} explicit, ${runtimeSkills.runDefaultCount} run defaults)`
900
+ ));
901
+ for (const profile of runtimeSkills.latestProfiles.slice(0, 6)) {
902
+ const model = profile.model || 'default';
903
+ console.log(
904
+ ` ${profile.agent.padEnd(24)} model ${model.padEnd(8)} ` +
905
+ `skills ${String(profile.skillsCount).padStart(2)} tools ${String(profile.tools.length).padStart(2)} ` +
906
+ `${formatSkills(profile.skills, 3)}`
907
+ );
908
+ }
909
+ if (runtimeSkills.latestProfiles.length > 6) {
910
+ console.log(chalk.gray(` ... ${runtimeSkills.latestProfiles.length - 6} more agents with captured snapshots`));
911
+ }
912
+ console.log(` Common skills: ${formatCountSummary(runtimeSkills.topSkills)}`);
913
+ }
914
+
915
+ // ── Context Snapshot Summary ──────────────────────────
916
+ if (contextSnapshots) {
917
+ console.log(chalk.bold(`\n🗺 Context Snapshot Summary (${contextSnapshots.total} sessions)`));
918
+ console.log(` Primary surfaces: ${formatCountSummary(contextSnapshots.primarySurfaces)}`);
919
+ console.log(` Multi-surface: ${contextSnapshots.multiSurfaceCount}/${contextSnapshots.total} sessions`);
920
+ console.log(` Contract sections: ${formatCountSummary(contextSnapshots.contractSections)}`);
921
+ if (contextSnapshots.writableSections.length > 0) {
922
+ console.log(` Writable scope: ${formatCountSummary(contextSnapshots.writableSections)}`);
923
+ }
924
+ }
925
+
926
+ // ── Context Updates ───────────────────────────────────
927
+ if (contextUpdates) {
928
+ console.log(chalk.bold(`\n📝 Context Updates (${contextUpdates.updatedRuns}/${contextUpdates.totalRuns} sessions updated)`));
929
+ console.log(` Rejected writes: ${contextUpdates.rejectedRuns} sessions`);
930
+ console.log(` Updated sections: ${formatCountSummary(contextUpdates.updatedSections)}`);
931
+ if (contextUpdates.rejectedSections.length > 0) {
932
+ console.log(chalk.yellow(` Rejected sections: ${formatCountSummary(contextUpdates.rejectedSections)}`));
933
+ }
934
+ }
935
+
936
+ // ── Anomaly Summary (last 30 days) ─────────────────
937
+ if (anomalySummary && anomalySummary.total > 0) {
938
+ console.log(chalk.bold(
939
+ `\n⚠️ Anomaly Summary (last 30 days) ${anomalySummary.total} anomalies across ${anomalySummary.sessionCount} sessions`
940
+ ));
941
+ for (const { type, count, percentage } of anomalySummary.byType) {
942
+ const bar = makeBar(percentage, 10);
943
+ const pct = percentage.toFixed(1).padStart(5);
944
+ const color = type.includes('contract') ? chalk.red : chalk.yellow;
945
+ console.log(color(` ${type.padEnd(28)} ${count.toString().padStart(3)} ${bar.padEnd(10)} ${pct}%`));
946
+ }
947
+ if (anomalySummary.byAgent.length > 0) {
948
+ console.log(` Agents: ${formatCountSummary(anomalySummary.byAgent)}`);
949
+ }
950
+ }
951
+
952
+ // ── Activity Today ───────────────────────────────────
953
+ console.log(chalk.bold('\n⚡ Activity Today'));
954
+ console.log(` Total calls: ${tiers.todayCount}`);
955
+
956
+ if (tiers.todayT3 > 0) {
957
+ console.log(chalk.red(` T3 operations: ${tiers.todayT3} ⚠️`));
958
+ } else {
959
+ console.log(chalk.green(` T3 operations: ${tiers.todayT3}`));
960
+ }
961
+
962
+ if (tiers.peakHour !== null) {
963
+ console.log(` Peak hour: ${tiers.peakHour}:00-${tiers.peakHour}:59 (${tiers.peakCount} calls)`);
964
+ } else {
965
+ console.log(chalk.gray(' Peak hour: no data for today'));
966
+ }
967
+
968
+ if (errorStats.limitedByApi) {
969
+ console.log(chalk.gray(' Error rate: n/a (hook API limitation — exit_code always 0)'));
970
+ } else if (errorStats.total === 0) {
971
+ console.log(chalk.gray(' Error rate: no exit_code data'));
972
+ } else {
973
+ const errColor = errorStats.errors > 0 ? chalk.red : chalk.green;
974
+ console.log(
975
+ ` Error rate: ${errColor(`${errorStats.errors}/${errorStats.total} (${errorStats.errorRate.toFixed(1)}%)`)}`
976
+ );
977
+ }
978
+
979
+ console.log('\n' + SEP);
980
+ console.log(
981
+ chalk.gray(
982
+ '💡 Source: .claude/logs/audit-*.jsonl | episodic-memory/index.json | workflow-episodic-memory/*.jsonl\n'
983
+ )
984
+ );
985
+ }
986
+
987
+ /**
988
+ * Display the agent detail view (--agent <name>).
989
+ */
990
+ async function displayAgentDetail(agentName, workflowMetrics, auditLogs, runSnapshots, skillSnapshots, anomalyEntries) {
991
+ const SEP = chalk.gray('═'.repeat(52));
992
+
993
+ console.log(chalk.cyan(`\n🤖 Agent: ${agentName}`));
994
+ console.log(SEP);
995
+
996
+ // ── Profile ──────────────────────────────────────────
997
+ console.log(chalk.bold('\n📋 Profile'));
998
+ const agentDef = await readAgentDefinition(agentName);
999
+
1000
+ if (!agentDef) {
1001
+ console.log(chalk.yellow(' Agent definition not found in .claude/agents/'));
1002
+ } else {
1003
+ if (agentDef.description) {
1004
+ console.log(` Description: ${agentDef.description}`);
1005
+ }
1006
+ if (agentDef.skills.length > 0) {
1007
+ // Wrap skills at ~60 chars
1008
+ const skillLine = agentDef.skills.join(', ');
1009
+ if (skillLine.length <= 56) {
1010
+ console.log(` Skills: ${skillLine}`);
1011
+ } else {
1012
+ const chunks = [];
1013
+ let current = [];
1014
+ let len = 0;
1015
+ for (const s of agentDef.skills) {
1016
+ if (len + s.length + 2 > 56 && current.length > 0) {
1017
+ chunks.push(current.join(', '));
1018
+ current = [s];
1019
+ len = s.length;
1020
+ } else {
1021
+ current.push(s);
1022
+ len += s.length + 2;
1023
+ }
1024
+ }
1025
+ if (current.length) chunks.push(current.join(', '));
1026
+ console.log(` Skills: ${chunks[0]}`);
1027
+ for (let i = 1; i < chunks.length; i++) {
1028
+ console.log(` ${chunks[i]}`);
1029
+ }
1030
+ }
1031
+ }
1032
+ }
1033
+
1034
+ // ── Runtime Snapshot ─────────────────────────────────
1035
+ console.log(chalk.bold('\n🧠 Runtime Snapshot'));
1036
+ const runtimeProfile = getLatestRuntimeProfile(agentName, skillSnapshots, runSnapshots);
1037
+ if (!runtimeProfile.latest) {
1038
+ console.log(chalk.gray(' no runtime skill snapshot data'));
1039
+ } else {
1040
+ const latest = runtimeProfile.latest;
1041
+ console.log(` Latest model: ${latest.model || 'default'}`);
1042
+ console.log(
1043
+ ` Snapshot source: ${latest.source === 'explicit'
1044
+ ? 'agent-skills.jsonl'
1045
+ : 'run-snapshots default profile'}`
1046
+ );
1047
+ console.log(` Snapshots seen: ${runtimeProfile.explicitCount} explicit, ${runtimeProfile.runDefaultCount} run defaults`);
1048
+ console.log(` Tools: ${latest.tools.length > 0 ? latest.tools.join(', ') : 'none'}`);
1049
+ console.log(` Skills: ${formatSkills(latest.skills, 6)}`);
1050
+ }
1051
+
1052
+ // ── Invocation History ───────────────────────────────
1053
+ const agentSessions = workflowMetrics
1054
+ .filter(r => r.agent === agentName)
1055
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1056
+
1057
+ const successCount = agentSessions.filter(r => r.exit_code === 0).length;
1058
+ const totalOutput = agentSessions.reduce((s, r) => s + (r.output_length || 0), 0);
1059
+ const avgOutput = agentSessions.length > 0 ? Math.round(totalOutput / agentSessions.length) : 0;
1060
+
1061
+ console.log(chalk.bold(`\n📊 Invocation History (last 7 days)`));
1062
+
1063
+ if (agentSessions.length === 0) {
1064
+ console.log(chalk.gray(' no invocations found in episodic-memory/index.json'));
1065
+ } else {
1066
+ console.log(
1067
+ ` Total: ${agentSessions.length} invocations | ` +
1068
+ `Success: ${successCount}/${agentSessions.length} | ` +
1069
+ `Avg output: ${formatChars(avgOutput)} chars`
1070
+ );
1071
+ console.log('');
1072
+
1073
+ for (const session of agentSessions) {
1074
+ const dt = session.timestamp.slice(0, 16).replace('T', ' ');
1075
+ const ok = session.exit_code === 0
1076
+ ? chalk.green('✓')
1077
+ : chalk.red('✗');
1078
+ const chars = (session.output_length || 0).toLocaleString();
1079
+ const taskShort = session.task_id ? session.task_id.slice(0, 8) : 'n/a';
1080
+ console.log(
1081
+ ` ${dt} ${ok} ${chars.padStart(7)} chars task: ${taskShort}`
1082
+ );
1083
+ }
1084
+ }
1085
+
1086
+ // ── Context Snapshot Summary ─────────────────────────
1087
+ const agentRunSnapshots = runSnapshots.filter(entry => entry.agent === agentName);
1088
+ const agentContextSummary = calculateContextSnapshotSummary(agentRunSnapshots);
1089
+ const agentContextUpdates = calculateContextUpdateSummary(agentRunSnapshots);
1090
+ const agentAnomalies = calculateAgentAnomalySummary(agentName, anomalyEntries);
1091
+
1092
+ console.log(chalk.bold('\n🗺 Context Snapshot Summary'));
1093
+ if (!agentContextSummary) {
1094
+ console.log(chalk.gray(' no context snapshot data'));
1095
+ } else {
1096
+ console.log(` Sessions with context: ${agentContextSummary.total}`);
1097
+ console.log(` Primary surfaces: ${formatCountSummary(agentContextSummary.primarySurfaces)}`);
1098
+ console.log(` Multi-surface: ${agentContextSummary.multiSurfaceCount}/${agentContextSummary.total}`);
1099
+ console.log(` Contract sections: ${formatCountSummary(agentContextSummary.contractSections)}`);
1100
+ if (agentContextSummary.writableSections.length > 0) {
1101
+ console.log(` Writable scope: ${formatCountSummary(agentContextSummary.writableSections)}`);
1102
+ }
1103
+ }
1104
+
1105
+ console.log(chalk.bold('\n📝 Context Updates + Anomalies'));
1106
+ if (!agentContextUpdates && !agentAnomalies) {
1107
+ console.log(chalk.gray(' no context update or anomaly data'));
1108
+ } else {
1109
+ if (agentContextUpdates) {
1110
+ console.log(` Context updated: ${agentContextUpdates.updatedRuns}/${agentContextUpdates.totalRuns} sessions`);
1111
+ console.log(` Updated sections: ${formatCountSummary(agentContextUpdates.updatedSections)}`);
1112
+ if (agentContextUpdates.rejectedSections.length > 0) {
1113
+ console.log(chalk.yellow(` Rejected sections: ${formatCountSummary(agentContextUpdates.rejectedSections)}`));
1114
+ }
1115
+ }
1116
+ if (agentAnomalies) {
1117
+ console.log(` Anomalies: ${agentAnomalies.total} across ${agentAnomalies.sessionCount} sessions`);
1118
+ console.log(` Types: ${formatCountSummary(agentAnomalies.byType)}`);
1119
+ }
1120
+ }
1121
+
1122
+ // ── Top Commands (correlated from audit log) ─────────
1123
+ console.log(chalk.bold('\n🔝 Top Commands (sampled from audit log, approximate time windows)'));
1124
+
1125
+ if (agentSessions.length === 0 || auditLogs.length === 0) {
1126
+ console.log(chalk.gray(' no data to correlate'));
1127
+ } else {
1128
+ // Build session windows: for each named-agent stop, find the previous named-agent stop
1129
+ // to define the start of the window. Falls back to 10 minutes before the stop.
1130
+ const namedStops = workflowMetrics
1131
+ .filter(r => r.agent)
1132
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1133
+
1134
+ const agentStopIdxs = agentSessions.map(s =>
1135
+ namedStops.findIndex(r => r.task_id === s.task_id)
1136
+ );
1137
+
1138
+ const tierOrder = { T3: 3, T2: 2, T1: 1, T0: 0, unknown: -1 };
1139
+ const labelMap = {};
1140
+
1141
+ for (let i = 0; i < agentSessions.length; i++) {
1142
+ const session = agentSessions[i];
1143
+ const stopIdx = agentStopIdxs[i];
1144
+ const prevStop = stopIdx > 0 ? namedStops[stopIdx - 1] : null;
1145
+ const windowStart = prevStop ? prevStop.timestamp : null;
1146
+
1147
+ const windowCmds = correlateAuditLogsToSession(
1148
+ auditLogs,
1149
+ session.timestamp,
1150
+ windowStart
1151
+ );
1152
+
1153
+ for (const entry of windowCmds) {
1154
+ if (!entry.command) continue;
1155
+ const label = extractCommandLabel(entry.command);
1156
+ const tier = entry.tier || 'unknown';
1157
+ if (!labelMap[label]) labelMap[label] = { count: 0, tier, t3count: 0 };
1158
+ labelMap[label].count++;
1159
+ if (tier === 'T3') labelMap[label].t3count++;
1160
+ if ((tierOrder[tier] ?? -1) > (tierOrder[labelMap[label].tier] ?? -1)) {
1161
+ labelMap[label].tier = tier;
1162
+ }
1163
+ }
1164
+ }
1165
+
1166
+ const topCmds = Object.entries(labelMap)
1167
+ .map(([label, { count, tier, t3count }]) => ({ label, count, tier, t3count }))
1168
+ .sort((a, b) => b.count - a.count)
1169
+ .slice(0, 10);
1170
+
1171
+ if (topCmds.length === 0) {
1172
+ console.log(chalk.gray(' no overlapping commands found in audit window'));
1173
+ } else {
1174
+ for (const { label, count, tier, t3count } of topCmds) {
1175
+ const tierColor = tier === 'T3' ? chalk.red
1176
+ : tier === 'T2' ? chalk.yellow
1177
+ : chalk.gray;
1178
+ const warn = t3count > 0 ? chalk.red(' ⚠️') : '';
1179
+ console.log(
1180
+ ` ${tierColor(tier.padEnd(3))} ${label.padEnd(28)} ${count.toString().padStart(4)}${warn}`
1181
+ );
1182
+ }
1183
+ }
1184
+
1185
+ console.log(chalk.gray('\n Note: command windows are approximated from SubagentStop timestamps'));
1186
+ }
1187
+
1188
+ console.log('\n' + SEP + '\n');
1189
+ }
1190
+
1191
+ // ─────────────────────────────────────────────────────────────
1192
+ // MAIN
1193
+ // ─────────────────────────────────────────────────────────────
1194
+
1195
+ async function main() {
1196
+ process.stderr.write('[DEPRECATED] gaia-metrics.js is deprecated. Use: python3 bin/gaia metrics\n[DEPRECATED] Migration guide: see CHANGELOG.md\n');
1197
+
1198
+ // Parse --agent flag
1199
+ const agentFlagIdx = process.argv.indexOf('--agent');
1200
+ const agentName = agentFlagIdx !== -1 ? process.argv[agentFlagIdx + 1] : null;
1201
+
1202
+ const spinner = ora('Loading metrics...').start();
1203
+
1204
+ try {
1205
+ const claudeDir = join(CWD, '.claude');
1206
+ if (!existsSync(claudeDir)) {
1207
+ spinner.fail('.claude/ directory not found');
1208
+ console.log(chalk.yellow('\n⚠️ Gaia-ops not installed in this directory'));
1209
+ console.log(chalk.gray(' Run: npx gaia-scan\n'));
1210
+ process.exit(1);
1211
+ }
1212
+
1213
+ const [auditLogs, workflowMetrics, runSnapshots, skillSnapshots, anomalyEntries] = await Promise.all([
1214
+ readAuditLogs(),
1215
+ readWorkflowMetrics(),
1216
+ readRunSnapshots(),
1217
+ readAgentSkillSnapshots(),
1218
+ readAnomalyEntries(),
1219
+ ]);
1220
+
1221
+ if (
1222
+ auditLogs.length === 0 &&
1223
+ workflowMetrics.length === 0 &&
1224
+ runSnapshots.length === 0 &&
1225
+ skillSnapshots.length === 0 &&
1226
+ anomalyEntries.length === 0
1227
+ ) {
1228
+ spinner.info('No log data found');
1229
+ console.log(chalk.yellow('\n⚠️ No metrics data available yet'));
1230
+ console.log(chalk.gray(' Metrics will be generated as you use the system\n'));
1231
+ process.exit(0);
1232
+ }
1233
+
1234
+ spinner.succeed(
1235
+ `Loaded ${auditLogs.length} audit + ${workflowMetrics.length} workflow + ${runSnapshots.length} telemetry entries`
1236
+ );
1237
+
1238
+ if (agentName) {
1239
+ // Agent detail view
1240
+ await displayAgentDetail(agentName, workflowMetrics, auditLogs, runSnapshots, skillSnapshots, anomalyEntries);
1241
+ } else {
1242
+ // Full dashboard
1243
+ const tiers = calculateTierUsage(auditLogs);
1244
+ const cmdTypes = calculateCommandTypeBreakdown(auditLogs);
1245
+ const topCmds = calculateTopCommands(auditLogs);
1246
+ const agentInvocations = calculateAgentInvocations(workflowMetrics);
1247
+ const errorStats = calculateErrorRate(auditLogs);
1248
+ const agentOutcomes = calculateAgentOutcomes(workflowMetrics);
1249
+ const tokenUsage = calculateTokenUsage(workflowMetrics);
1250
+ const anomalySummary = calculateAnomalySummary(anomalyEntries);
1251
+ const runtimeSkills = calculateRuntimeSkillSummary(skillSnapshots, runSnapshots);
1252
+ const contextSnapshots = calculateContextSnapshotSummary(runSnapshots);
1253
+ const contextUpdates = calculateContextUpdateSummary(runSnapshots);
1254
+
1255
+ displayMetrics(
1256
+ tiers,
1257
+ cmdTypes,
1258
+ topCmds,
1259
+ agentInvocations,
1260
+ errorStats,
1261
+ auditLogs.length,
1262
+ agentOutcomes,
1263
+ tokenUsage,
1264
+ anomalySummary,
1265
+ runtimeSkills,
1266
+ contextSnapshots,
1267
+ contextUpdates
1268
+ );
1269
+ }
1270
+
1271
+ } catch (error) {
1272
+ spinner.fail(`Failed to load metrics: ${error.message}`);
1273
+ console.error(chalk.red(`\n❌ Error: ${error.stack}\n`));
1274
+ process.exit(1);
1275
+ }
1276
+ }
1277
+
1278
+ main();