@jaimevalasek/aioson 1.3.0 → 1.5.1

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 (330) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/LICENSE +661 -21
  3. package/README.md +22 -3
  4. package/docs/en/squad-dashboard.md +372 -0
  5. package/docs/openclaw-bridge.md +308 -0
  6. package/docs/pt/README.md +62 -2
  7. package/docs/pt/advisor-spec.md +5 -5
  8. package/docs/pt/agentes-customizados.md +670 -0
  9. package/docs/pt/agentes.md +235 -23
  10. package/docs/pt/automacao-squads.md +407 -0
  11. package/docs/pt/cenarios.md +49 -5
  12. package/docs/pt/clientes-ai.md +62 -0
  13. package/docs/pt/comandos-cli.md +226 -17
  14. package/docs/pt/deyvin.md +115 -0
  15. package/docs/pt/genome-3.0-spec.md +11 -11
  16. package/docs/pt/inicio-rapido.md +63 -2
  17. package/docs/pt/memoria-contexto.md +255 -0
  18. package/docs/pt/output-strategy-delivery.md +655 -0
  19. package/docs/pt/profiler-system.md +17 -17
  20. package/docs/pt/runtime-observability.md +5 -1
  21. package/docs/pt/skills.md +175 -0
  22. package/docs/pt/squad-dashboard.md +373 -0
  23. package/docs/pt/{squad-genoma.md → squad-genome.md} +81 -75
  24. package/docs/testing/genome-2.0-matrix.md +5 -5
  25. package/docs/testing/genome-2.0-rollout.md +10 -10
  26. package/package.json +4 -4
  27. package/src/agents.js +21 -5
  28. package/src/backup-local.js +74 -0
  29. package/src/backup-provider.js +303 -0
  30. package/src/cli.js +276 -2
  31. package/src/commands/agents.js +22 -4
  32. package/src/commands/backup-local-cmd.js +25 -0
  33. package/src/commands/backup.js +533 -0
  34. package/src/commands/cloud.js +17 -17
  35. package/src/commands/context-pack.js +45 -0
  36. package/src/commands/implementation-plan.js +340 -0
  37. package/src/commands/learning.js +134 -0
  38. package/src/commands/live.js +1583 -0
  39. package/src/commands/runtime.js +1075 -2
  40. package/src/commands/scan-project.js +288 -24
  41. package/src/commands/setup-context.js +30 -2
  42. package/src/commands/skill.js +558 -0
  43. package/src/commands/squad-agent-create.js +788 -0
  44. package/src/commands/squad-daemon.js +209 -0
  45. package/src/commands/squad-dashboard.js +39 -0
  46. package/src/commands/squad-deploy.js +64 -0
  47. package/src/commands/squad-doctor.js +103 -1
  48. package/src/commands/squad-investigate.js +261 -0
  49. package/src/commands/squad-learning.js +209 -0
  50. package/src/commands/squad-mcp.js +270 -0
  51. package/src/commands/squad-pipeline.js +247 -1
  52. package/src/commands/squad-plan.js +329 -0
  53. package/src/commands/squad-processes.js +56 -0
  54. package/src/commands/squad-recovery.js +42 -0
  55. package/src/commands/squad-roi.js +291 -0
  56. package/src/commands/squad-score.js +250 -0
  57. package/src/commands/squad-status.js +38 -2
  58. package/src/commands/squad-validate.js +118 -1
  59. package/src/commands/squad-webhook.js +160 -0
  60. package/src/commands/squad-worker.js +191 -0
  61. package/src/commands/squad-worktrees.js +75 -0
  62. package/src/commands/test-agents.js +6 -1
  63. package/src/commands/web-map.js +70 -0
  64. package/src/commands/web-scrape.js +71 -0
  65. package/src/commands/workflow-next.js +8 -1
  66. package/src/commands/workflow-status.js +250 -0
  67. package/src/constants.js +88 -16
  68. package/src/context-memory.js +837 -0
  69. package/src/context-writer.js +47 -1
  70. package/src/delivery-runner.js +319 -0
  71. package/src/genome-files.js +1 -1
  72. package/src/genome-format.js +1 -1
  73. package/src/i18n/messages/en.js +333 -8
  74. package/src/i18n/messages/es.js +240 -6
  75. package/src/i18n/messages/fr.js +239 -5
  76. package/src/i18n/messages/pt-BR.js +330 -12
  77. package/src/installer.js +30 -2
  78. package/src/lib/genomes/compat.js +1 -1
  79. package/src/lib/webhook-server.js +328 -0
  80. package/src/mcp-connectors/registry.js +602 -0
  81. package/src/runtime-store.js +1037 -42
  82. package/src/session-handoff.js +77 -0
  83. package/src/squad/external-session.js +180 -0
  84. package/src/squad/inter-squad.js +74 -0
  85. package/src/squad/recovery-context.js +201 -0
  86. package/src/squad/worktree-manager.js +114 -0
  87. package/src/squad-daemon.js +490 -0
  88. package/src/squad-dashboard/api.js +223 -0
  89. package/src/squad-dashboard/attachment-handler.js +93 -0
  90. package/src/squad-dashboard/context-monitor.js +157 -0
  91. package/src/squad-dashboard/execution-logs.js +115 -0
  92. package/src/squad-dashboard/hunk-review.js +209 -0
  93. package/src/squad-dashboard/metrics.js +133 -0
  94. package/src/squad-dashboard/process-monitor.js +125 -0
  95. package/src/squad-dashboard/renderer.js +858 -0
  96. package/src/squad-dashboard/server.js +232 -0
  97. package/src/squad-dashboard/styles.js +525 -0
  98. package/src/squad-dashboard/token-tracker.js +99 -0
  99. package/src/web.js +284 -0
  100. package/src/worker-runner.js +339 -0
  101. package/template/.aioson/agents/analyst.md +40 -9
  102. package/template/.aioson/agents/architect.md +24 -5
  103. package/template/.aioson/agents/dev.md +254 -25
  104. package/template/.aioson/agents/deyvin.md +174 -0
  105. package/template/.aioson/agents/discovery-design-doc.md +25 -1
  106. package/template/.aioson/agents/{genoma.md → genome.md} +20 -20
  107. package/template/.aioson/agents/neo.md +152 -0
  108. package/template/.aioson/agents/orache.md +388 -0
  109. package/template/.aioson/agents/orchestrator.md +63 -2
  110. package/template/.aioson/agents/pair.md +5 -0
  111. package/template/.aioson/agents/pm.md +17 -5
  112. package/template/.aioson/agents/product.md +113 -29
  113. package/template/.aioson/agents/profiler-enricher.md +1 -1
  114. package/template/.aioson/agents/profiler-forge.md +9 -9
  115. package/template/.aioson/agents/profiler-researcher.md +1 -1
  116. package/template/.aioson/agents/qa.md +18 -5
  117. package/template/.aioson/agents/setup.md +138 -18
  118. package/template/.aioson/agents/sheldon.md +603 -0
  119. package/template/.aioson/agents/squad.md +866 -28
  120. package/template/.aioson/agents/tester.md +254 -0
  121. package/template/.aioson/agents/ux-ui.md +289 -34
  122. package/template/.aioson/config.md +181 -0
  123. package/template/.aioson/context/spec.md.template +17 -0
  124. package/template/.aioson/genomes/.gitkeep +0 -0
  125. package/template/.aioson/installed-skills/.gitkeep +0 -0
  126. package/template/.aioson/locales/en/agents/analyst.md +34 -4
  127. package/template/.aioson/locales/en/agents/architect.md +18 -0
  128. package/template/.aioson/locales/en/agents/dev.md +155 -11
  129. package/template/.aioson/locales/en/agents/deyvin.md +137 -0
  130. package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
  131. package/template/.aioson/locales/en/agents/neo.md +8 -0
  132. package/template/.aioson/locales/en/agents/orchestrator.md +62 -2
  133. package/template/.aioson/locales/en/agents/pair.md +5 -0
  134. package/template/.aioson/locales/en/agents/pm.md +7 -0
  135. package/template/.aioson/locales/en/agents/product.md +35 -17
  136. package/template/.aioson/locales/en/agents/qa.md +56 -0
  137. package/template/.aioson/locales/en/agents/setup.md +53 -6
  138. package/template/.aioson/locales/en/agents/sheldon.md +340 -0
  139. package/template/.aioson/locales/en/agents/squad.md +203 -15
  140. package/template/.aioson/locales/en/agents/ux-ui.md +383 -35
  141. package/template/.aioson/locales/es/agents/analyst.md +24 -4
  142. package/template/.aioson/locales/es/agents/architect.md +18 -0
  143. package/template/.aioson/locales/es/agents/dev.md +136 -9
  144. package/template/.aioson/locales/es/agents/deyvin.md +97 -0
  145. package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
  146. package/template/.aioson/locales/es/agents/neo.md +48 -0
  147. package/template/.aioson/locales/es/agents/orache.md +103 -0
  148. package/template/.aioson/locales/es/agents/orchestrator.md +62 -2
  149. package/template/.aioson/locales/es/agents/pair.md +5 -0
  150. package/template/.aioson/locales/es/agents/pm.md +7 -0
  151. package/template/.aioson/locales/es/agents/product.md +13 -3
  152. package/template/.aioson/locales/es/agents/qa.md +33 -0
  153. package/template/.aioson/locales/es/agents/setup.md +30 -6
  154. package/template/.aioson/locales/es/agents/sheldon.md +192 -0
  155. package/template/.aioson/locales/es/agents/squad.md +284 -15
  156. package/template/.aioson/locales/es/agents/ux-ui.md +34 -25
  157. package/template/.aioson/locales/fr/agents/analyst.md +24 -4
  158. package/template/.aioson/locales/fr/agents/architect.md +18 -0
  159. package/template/.aioson/locales/fr/agents/dev.md +136 -9
  160. package/template/.aioson/locales/fr/agents/deyvin.md +97 -0
  161. package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
  162. package/template/.aioson/locales/fr/agents/neo.md +48 -0
  163. package/template/.aioson/locales/fr/agents/orache.md +104 -0
  164. package/template/.aioson/locales/fr/agents/orchestrator.md +62 -2
  165. package/template/.aioson/locales/fr/agents/pair.md +5 -0
  166. package/template/.aioson/locales/fr/agents/pm.md +7 -0
  167. package/template/.aioson/locales/fr/agents/product.md +13 -3
  168. package/template/.aioson/locales/fr/agents/qa.md +33 -0
  169. package/template/.aioson/locales/fr/agents/setup.md +30 -6
  170. package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
  171. package/template/.aioson/locales/fr/agents/squad.md +279 -10
  172. package/template/.aioson/locales/fr/agents/ux-ui.md +34 -25
  173. package/template/.aioson/locales/pt-BR/agents/analyst.md +45 -4
  174. package/template/.aioson/locales/pt-BR/agents/architect.md +29 -0
  175. package/template/.aioson/locales/pt-BR/agents/dev.md +167 -15
  176. package/template/.aioson/locales/pt-BR/agents/deyvin.md +137 -0
  177. package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
  178. package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
  179. package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
  180. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +62 -2
  181. package/template/.aioson/locales/pt-BR/agents/pair.md +5 -0
  182. package/template/.aioson/locales/pt-BR/agents/pm.md +7 -0
  183. package/template/.aioson/locales/pt-BR/agents/product.md +43 -20
  184. package/template/.aioson/locales/pt-BR/agents/qa.md +67 -0
  185. package/template/.aioson/locales/pt-BR/agents/setup.md +53 -6
  186. package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
  187. package/template/.aioson/locales/pt-BR/agents/squad.md +591 -47
  188. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +369 -22
  189. package/template/.aioson/my-agents/.gitkeep +0 -0
  190. package/template/.aioson/rules/.gitkeep +0 -0
  191. package/template/.aioson/rules/squad/.gitkeep +0 -0
  192. package/template/.aioson/rules/squad/README.md +50 -0
  193. package/template/.aioson/schemas/genome-meta.schema.json +1 -1
  194. package/template/.aioson/schemas/genome.schema.json +1 -1
  195. package/template/.aioson/schemas/squad-blueprint.schema.json +32 -0
  196. package/template/.aioson/schemas/squad-manifest.schema.json +434 -1
  197. package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
  198. package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
  199. package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
  200. package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
  201. package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
  202. package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
  203. package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
  204. package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
  205. package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
  206. package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
  207. package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
  208. package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
  209. package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
  210. package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
  211. package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
  212. package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
  213. package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +203 -0
  214. package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
  215. package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -0
  216. package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +272 -0
  217. package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +524 -0
  218. package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +277 -0
  219. package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +289 -0
  220. package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +437 -0
  221. package/template/.aioson/skills/design/interface-design/SKILL.md +47 -0
  222. package/template/.aioson/skills/design/interface-design/references/components-and-states.md +105 -0
  223. package/template/.aioson/skills/design/interface-design/references/design-directions.md +101 -0
  224. package/template/.aioson/skills/design/interface-design/references/handoff-and-quality.md +71 -0
  225. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +74 -0
  226. package/template/.aioson/skills/design/interface-design/references/tokens-and-depth.md +173 -0
  227. package/template/.aioson/skills/design/premium-command-center-ui/SKILL.md +62 -0
  228. package/template/.aioson/skills/design/premium-command-center-ui/references/operations.md +74 -0
  229. package/template/.aioson/skills/design/premium-command-center-ui/references/patterns.md +116 -0
  230. package/template/.aioson/skills/design/premium-command-center-ui/references/validation.md +47 -0
  231. package/template/.aioson/skills/design/premium-command-center-ui/references/visual-system.md +215 -0
  232. package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
  233. package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
  234. package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
  235. package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
  236. package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
  237. package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
  238. package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
  239. package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
  240. package/template/.aioson/skills/design-system/SKILL.md +92 -0
  241. package/template/.aioson/skills/design-system/cognitive-core-ui.skill +0 -0
  242. package/template/.aioson/skills/design-system/components/SKILL.md +274 -0
  243. package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
  244. package/template/.aioson/skills/design-system/dashboards/SKILL.md +184 -0
  245. package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
  246. package/template/.aioson/skills/design-system/foundations/SKILL.md +250 -0
  247. package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
  248. package/template/.aioson/skills/design-system/motion/SKILL.md +197 -0
  249. package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
  250. package/template/.aioson/skills/design-system/patterns/SKILL.md +231 -0
  251. package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
  252. package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
  253. package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
  254. package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
  255. package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
  256. package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
  257. package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
  258. package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
  259. package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
  260. package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
  261. package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
  262. package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
  263. package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
  264. package/template/.aioson/skills/squad/SKILL.md +58 -0
  265. package/template/.aioson/skills/squad/domains/.gitkeep +0 -0
  266. package/template/.aioson/skills/squad/formats/.gitkeep +0 -0
  267. package/template/.aioson/skills/squad/formats/catalog.json +15 -0
  268. package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
  269. package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
  270. package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
  271. package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
  272. package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
  273. package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
  274. package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
  275. package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
  276. package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
  277. package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
  278. package/template/.aioson/skills/squad/patterns/.gitkeep +0 -0
  279. package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
  280. package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
  281. package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
  282. package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
  283. package/template/.aioson/skills/squad/references/.gitkeep +0 -0
  284. package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
  285. package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
  286. package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
  287. package/template/.aioson/skills/static/debugging-protocol.md +42 -0
  288. package/template/.aioson/skills/static/git-worktrees.md +36 -0
  289. package/template/.aioson/tasks/implementation-plan.md +307 -0
  290. package/template/.aioson/tasks/squad-create.md +1 -1
  291. package/template/.aioson/tasks/squad-design.md +28 -0
  292. package/template/.aioson/tasks/squad-execution-plan.md +279 -0
  293. package/template/.aioson/tasks/squad-export.md +1 -1
  294. package/template/.aioson/tasks/squad-investigate.md +44 -0
  295. package/template/.aioson/tasks/squad-learning-review.md +44 -0
  296. package/template/.aioson/tasks/squad-output-config.md +177 -0
  297. package/template/.aioson/tasks/squad-profile.md +48 -0
  298. package/template/.aioson/tasks/squad-review.md +61 -0
  299. package/template/.aioson/tasks/squad-task-decompose.md +66 -0
  300. package/template/.aioson/tasks/squad-validate.md +1 -1
  301. package/template/.claude/commands/aioson/agent/deyvin.md +5 -0
  302. package/template/.claude/commands/aioson/agent/discovery-design-doc.md +5 -0
  303. package/template/.claude/commands/aioson/agent/genome.md +5 -0
  304. package/template/.claude/commands/aioson/agent/neo.md +5 -0
  305. package/template/.claude/commands/aioson/agent/product.md +5 -0
  306. package/template/.claude/commands/aioson/agent/profiler-enricher.md +5 -0
  307. package/template/.claude/commands/aioson/agent/profiler-forge.md +5 -0
  308. package/template/.claude/commands/aioson/agent/profiler-researcher.md +5 -0
  309. package/template/.claude/commands/aioson/agent/squad.md +5 -0
  310. package/template/.claude/commands/aioson/agent/tester.md +5 -0
  311. package/template/.gemini/GEMINI.md +3 -0
  312. package/template/.gemini/commands/aios-deyvin.toml +6 -0
  313. package/template/.gemini/commands/aios-neo.toml +4 -0
  314. package/template/.gemini/commands/aios-pair.toml +6 -0
  315. package/template/.gemini/commands/aios-tester.toml +6 -0
  316. package/template/AGENTS.md +37 -6
  317. package/template/CLAUDE.md +34 -4
  318. package/template/OPENCODE.md +8 -2
  319. package/template/squad-searches/.gitkeep +0 -0
  320. package/template/.aioson/skills/static/interface-design.md +0 -372
  321. package/template/.aioson/skills/static/premium-command-center-ui.md +0 -190
  322. /package/template/.aioson/{genomas → docs}/.gitkeep +0 -0
  323. /package/template/.claude/commands/aioson/{analyst.md → agent/analyst.md} +0 -0
  324. /package/template/.claude/commands/aioson/{architect.md → agent/architect.md} +0 -0
  325. /package/template/.claude/commands/aioson/{dev.md → agent/dev.md} +0 -0
  326. /package/template/.claude/commands/aioson/{orchestrator.md → agent/orchestrator.md} +0 -0
  327. /package/template/.claude/commands/aioson/{pm.md → agent/pm.md} +0 -0
  328. /package/template/.claude/commands/aioson/{qa.md → agent/qa.md} +0 -0
  329. /package/template/.claude/commands/aioson/{setup.md → agent/setup.md} +0 -0
  330. /package/template/.claude/commands/aioson/{ux-ui.md → agent/ux-ui.md} +0 -0
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ const SQUADS_DIR = path.join('.aioson', 'squads');
7
+
8
+ const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']);
9
+
10
+ const MIME_MAP = {
11
+ '.png': 'image/png',
12
+ '.jpg': 'image/jpeg',
13
+ '.jpeg': 'image/jpeg',
14
+ '.gif': 'image/gif',
15
+ '.webp': 'image/webp',
16
+ '.svg': 'image/svg+xml',
17
+ '.pdf': 'application/pdf',
18
+ '.txt': 'text/plain',
19
+ '.md': 'text/markdown',
20
+ '.json': 'application/json'
21
+ };
22
+
23
+ function attachmentsDir(projectDir, squadSlug) {
24
+ return path.join(projectDir, SQUADS_DIR, squadSlug, 'attachments');
25
+ }
26
+
27
+ function safeName(filename) {
28
+ return path.basename(filename).replace(/[^a-zA-Z0-9._-]/g, '_');
29
+ }
30
+
31
+ /**
32
+ * Save a Buffer or string as an attachment.
33
+ * Returns { ok, filename, filePath, isImage }.
34
+ */
35
+ async function saveAttachment(projectDir, squadSlug, filename, buffer) {
36
+ const dir = attachmentsDir(projectDir, squadSlug);
37
+ await fs.mkdir(dir, { recursive: true });
38
+ const safe = safeName(filename);
39
+ const dest = path.join(dir, safe);
40
+ await fs.writeFile(dest, buffer);
41
+ const ext = path.extname(safe).toLowerCase();
42
+ return { ok: true, filePath: dest, filename: safe, isImage: IMAGE_EXTS.has(ext) };
43
+ }
44
+
45
+ /**
46
+ * List all attachments for a squad.
47
+ * Returns array of { filename, filePath, isImage, mime, size }.
48
+ */
49
+ async function listAttachments(projectDir, squadSlug) {
50
+ const dir = attachmentsDir(projectDir, squadSlug);
51
+ let entries;
52
+ try {
53
+ entries = await fs.readdir(dir, { withFileTypes: true });
54
+ } catch {
55
+ return [];
56
+ }
57
+ const result = [];
58
+ for (const entry of entries) {
59
+ if (!entry.isFile()) continue;
60
+ const ext = path.extname(entry.name).toLowerCase();
61
+ let size = 0;
62
+ try {
63
+ const stat = await fs.stat(path.join(dir, entry.name));
64
+ size = stat.size;
65
+ } catch { /* ignore */ }
66
+ result.push({
67
+ filename: entry.name,
68
+ filePath: path.join(dir, entry.name),
69
+ isImage: IMAGE_EXTS.has(ext),
70
+ mime: MIME_MAP[ext] || 'application/octet-stream',
71
+ size
72
+ });
73
+ }
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * Read an attachment file for serving (e.g. inline image preview).
79
+ * Returns { ok, buffer, mime, filename } or { ok: false }.
80
+ */
81
+ async function readAttachment(projectDir, squadSlug, filename) {
82
+ const safe = safeName(filename);
83
+ const filePath = path.join(attachmentsDir(projectDir, squadSlug), safe);
84
+ try {
85
+ const buffer = await fs.readFile(filePath);
86
+ const ext = path.extname(safe).toLowerCase();
87
+ return { ok: true, buffer, mime: MIME_MAP[ext] || 'application/octet-stream', filename: safe };
88
+ } catch {
89
+ return { ok: false };
90
+ }
91
+ }
92
+
93
+ module.exports = { saveAttachment, listAttachments, readAttachment, IMAGE_EXTS, MIME_MAP };
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { generateRecovery, shouldRefreshOnEvent } = require('../squad/recovery-context');
6
+
7
+ const SQUADS_DIR = path.join('.aioson', 'squads');
8
+
9
+ // Minimum ratio drop between consecutive measurements to be considered a compact
10
+ const COMPACT_DROP_THRESHOLD = 0.30;
11
+
12
+ // Warning level thresholds (ratio of used/windowSize)
13
+ const THRESHOLDS = { warning: 0.85, critical: 0.95 };
14
+
15
+ // Notification event types (dispatched by caller when notification system is available)
16
+ const EVENTS = {
17
+ CONTEXT_WARNING: 'context_warning',
18
+ CONTEXT_CRITICAL: 'context_critical'
19
+ };
20
+
21
+ // Context categories (6) — order determines donut segment order
22
+ const CATEGORIES = [
23
+ 'system_prompt',
24
+ 'conversation_history',
25
+ 'tool_outputs',
26
+ 'files_loaded',
27
+ 'inline_data',
28
+ 'other'
29
+ ];
30
+
31
+ function computeWarningLevel(used, windowSize) {
32
+ if (!windowSize || windowSize <= 0) return 'unknown';
33
+ const ratio = used / windowSize;
34
+ if (ratio >= 1.0) return 'overflow';
35
+ if (ratio >= THRESHOLDS.critical) return 'critical';
36
+ if (ratio >= THRESHOLDS.warning) return 'warning';
37
+ return 'normal';
38
+ }
39
+
40
+ /**
41
+ * Read context-monitor.json for a squad and compute warning levels.
42
+ * @param {string} projectDir
43
+ * @param {string} squadSlug
44
+ * @param {string|null} agentSlug — if set, return only that agent
45
+ * @returns {object|null}
46
+ */
47
+ async function getContextUsage(projectDir, squadSlug, agentSlug) {
48
+ const filePath = path.join(projectDir, SQUADS_DIR, squadSlug, 'context-monitor.json');
49
+ let data;
50
+ try {
51
+ const raw = await fs.readFile(filePath, 'utf8');
52
+ data = JSON.parse(raw);
53
+ } catch {
54
+ return null;
55
+ }
56
+
57
+ const agents = data.agents || {};
58
+
59
+ if (agentSlug) {
60
+ const agent = agents[agentSlug];
61
+ if (!agent) return null;
62
+ const warningLevel = computeWarningLevel(agent.totalUsed || 0, agent.windowSize || 0);
63
+ return { squadSlug, agentSlug, ...agent, warningLevel };
64
+ }
65
+
66
+ const enrichedAgents = {};
67
+ for (const [slug, agent] of Object.entries(agents)) {
68
+ enrichedAgents[slug] = {
69
+ ...agent,
70
+ warningLevel: computeWarningLevel(agent.totalUsed || 0, agent.windowSize || 0)
71
+ };
72
+ }
73
+ return { squadSlug, agents: enrichedAgents, updatedAt: data.updatedAt };
74
+ }
75
+
76
+ /**
77
+ * Return pending notification events for a context snapshot.
78
+ * Caller dispatches these once the notification system exists.
79
+ */
80
+ function checkNotificationEvents(squadSlug, contextData) {
81
+ if (!contextData || !contextData.agents) return [];
82
+ const events = [];
83
+ for (const [agentSlug, agent] of Object.entries(contextData.agents)) {
84
+ if (agent.warningLevel === 'critical' || agent.warningLevel === 'overflow') {
85
+ events.push({ type: EVENTS.CONTEXT_CRITICAL, squadSlug, agentSlug, warningLevel: agent.warningLevel });
86
+ } else if (agent.warningLevel === 'warning') {
87
+ events.push({ type: EVENTS.CONTEXT_WARNING, squadSlug, agentSlug, warningLevel: agent.warningLevel });
88
+ }
89
+ }
90
+ return events;
91
+ }
92
+
93
+ /**
94
+ * Detect if a context compact occurred between two consecutive measurements.
95
+ * A compact is inferred when totalUsed drops by > 30% from the previous snapshot.
96
+ * @param {number} prevUsed — previous totalUsed value
97
+ * @param {number} currUsed — current totalUsed value
98
+ * @returns {boolean}
99
+ */
100
+ function isCompactDetected(prevUsed, currUsed) {
101
+ if (!prevUsed || prevUsed <= 0) return false;
102
+ const drop = (prevUsed - currUsed) / prevUsed;
103
+ return drop > COMPACT_DROP_THRESHOLD;
104
+ }
105
+
106
+ /**
107
+ * Compare two context-monitor snapshots and trigger recovery injection if a
108
+ * compact is detected for any agent.
109
+ *
110
+ * @param {string} projectDir
111
+ * @param {string} squadSlug
112
+ * @param {object} prevData — previous result from getContextUsage (or null)
113
+ * @param {object} currData — current result from getContextUsage
114
+ * @returns {Array<{agentSlug, recovery}>} list of recovery results triggered
115
+ */
116
+ async function checkAndInjectRecovery(projectDir, squadSlug, prevData, currData) {
117
+ if (!prevData || !currData) return [];
118
+ const prevAgents = prevData.agents || {};
119
+ const currAgents = currData.agents || {};
120
+ const triggered = [];
121
+
122
+ for (const [agentSlug, curr] of Object.entries(currAgents)) {
123
+ const prev = prevAgents[agentSlug];
124
+ if (!prev) continue;
125
+ if (isCompactDetected(prev.totalUsed || 0, curr.totalUsed || 0)) {
126
+ const recovery = await generateRecovery(projectDir, squadSlug, agentSlug);
127
+ triggered.push({ agentSlug, recovery });
128
+ }
129
+ }
130
+
131
+ return triggered;
132
+ }
133
+
134
+ /**
135
+ * Optionally trigger a recovery refresh when a specific runtime event fires.
136
+ * @param {string} projectDir
137
+ * @param {string} squadSlug
138
+ * @param {string} agentSlug
139
+ * @param {string} eventType
140
+ */
141
+ async function onRuntimeEvent(projectDir, squadSlug, agentSlug, eventType) {
142
+ if (!shouldRefreshOnEvent(eventType)) return null;
143
+ return generateRecovery(projectDir, squadSlug, agentSlug);
144
+ }
145
+
146
+ module.exports = {
147
+ getContextUsage,
148
+ computeWarningLevel,
149
+ checkNotificationEvents,
150
+ isCompactDetected,
151
+ checkAndInjectRecovery,
152
+ onRuntimeEvent,
153
+ CATEGORIES,
154
+ EVENTS,
155
+ THRESHOLDS,
156
+ COMPACT_DROP_THRESHOLD
157
+ };
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ const SQUADS_DIR = path.join('.aioson', 'squads');
7
+
8
+ const ENTRY_TYPES = {
9
+ TOOL_CALL: 'tool_call',
10
+ REASONING: 'reasoning',
11
+ MILESTONE: 'milestone',
12
+ ERROR: 'error'
13
+ };
14
+
15
+ /**
16
+ * Session log file schema:
17
+ * {
18
+ * agentSlug: string,
19
+ * taskId: string,
20
+ * startedAt: ISO string,
21
+ * summary: string | null,
22
+ * entries: Array<{
23
+ * type: 'tool_call' | 'reasoning' | 'milestone' | 'error',
24
+ * timestamp: ISO string,
25
+ * // tool_call
26
+ * toolName?: string,
27
+ * input?: any,
28
+ * output?: any,
29
+ * durationMs?: number,
30
+ * // reasoning
31
+ * text?: string,
32
+ * // milestone
33
+ * label?: string,
34
+ * // error
35
+ * message?: string,
36
+ * stack?: string
37
+ * }>
38
+ * }
39
+ *
40
+ * File path: .aioson/squads/{slug}/logs/{task-id}/session-{timestamp}.json
41
+ */
42
+
43
+ function logsDir(projectDir, squadSlug, taskId) {
44
+ return path.join(projectDir, SQUADS_DIR, squadSlug, 'logs', taskId);
45
+ }
46
+
47
+ async function listSessionFiles(projectDir, squadSlug, taskId) {
48
+ const dir = logsDir(projectDir, squadSlug, taskId);
49
+ let entries;
50
+ try {
51
+ entries = await fs.readdir(dir);
52
+ } catch {
53
+ return [];
54
+ }
55
+ return entries
56
+ .filter(f => f.startsWith('session-') && f.endsWith('.json'))
57
+ .map(f => {
58
+ const ts = f.replace(/^session-/, '').replace(/\.json$/, '');
59
+ return { sessionId: f.replace('.json', ''), filename: f, timestamp: ts, filePath: path.join(dir, f) };
60
+ })
61
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
62
+ }
63
+
64
+ /**
65
+ * Returns all sessions (with entries) for a given task, sorted oldest-first.
66
+ */
67
+ async function getLogsForTask(projectDir, squadSlug, taskId) {
68
+ const files = await listSessionFiles(projectDir, squadSlug, taskId);
69
+ const sessions = [];
70
+ for (const file of files) {
71
+ try {
72
+ const raw = await fs.readFile(file.filePath, 'utf8');
73
+ const session = JSON.parse(raw);
74
+ sessions.push({
75
+ sessionId: file.sessionId,
76
+ timestamp: file.timestamp,
77
+ taskId,
78
+ squadSlug,
79
+ agentSlug: session.agentSlug || null,
80
+ startedAt: session.startedAt || file.timestamp,
81
+ summary: session.summary || null,
82
+ entries: session.entries || []
83
+ });
84
+ } catch {
85
+ sessions.push({
86
+ sessionId: file.sessionId,
87
+ timestamp: file.timestamp,
88
+ taskId,
89
+ squadSlug,
90
+ agentSlug: null,
91
+ startedAt: file.timestamp,
92
+ summary: null,
93
+ entries: [],
94
+ parseError: true
95
+ });
96
+ }
97
+ }
98
+ return sessions;
99
+ }
100
+
101
+ /**
102
+ * Returns a single session log by sessionId (filename without .json).
103
+ */
104
+ async function getSessionLog(projectDir, squadSlug, taskId, sessionId) {
105
+ const dir = logsDir(projectDir, squadSlug, taskId);
106
+ const filePath = path.join(dir, `${sessionId}.json`);
107
+ try {
108
+ const raw = await fs.readFile(filePath, 'utf8');
109
+ return JSON.parse(raw);
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ module.exports = { ENTRY_TYPES, getLogsForTask, getSessionLog };
@@ -0,0 +1,209 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ const SQUADS_DIR = path.join('.aioson', 'squads');
7
+
8
+ // Valid hunk states
9
+ const HUNK_STATES = { PENDING: 'pending', APPROVED: 'approved', REJECTED: 'rejected', REVISED: 'revised' };
10
+
11
+ /**
12
+ * Parse a unified diff string into individual hunks.
13
+ * Each hunk includes its header, lines, and a stable id.
14
+ *
15
+ * @param {string} diff — full unified diff text
16
+ * @returns {Array<{id, fileHeader, header, lines, additions, deletions}>}
17
+ */
18
+ function parseDiffHunks(diff) {
19
+ if (!diff || typeof diff !== 'string') return [];
20
+
21
+ const hunks = [];
22
+ let currentFile = '';
23
+ let currentHunk = null;
24
+ let hunkIndex = 0;
25
+
26
+ for (const rawLine of diff.split('\n')) {
27
+ // File header lines
28
+ if (rawLine.startsWith('--- ') || rawLine.startsWith('+++ ')) {
29
+ if (rawLine.startsWith('+++ ')) {
30
+ // Strip b/ prefix from git diffs
31
+ currentFile = rawLine.slice(4).replace(/^b\//, '').trim();
32
+ }
33
+ if (currentHunk) {
34
+ hunks.push(finalizeHunk(currentHunk));
35
+ currentHunk = null;
36
+ }
37
+ continue;
38
+ }
39
+
40
+ // Hunk header: @@ -a,b +c,d @@
41
+ if (rawLine.startsWith('@@ ')) {
42
+ if (currentHunk) {
43
+ hunks.push(finalizeHunk(currentHunk));
44
+ }
45
+ currentHunk = {
46
+ id: `hunk-${hunkIndex++}`,
47
+ fileHeader: currentFile,
48
+ header: rawLine,
49
+ lines: [],
50
+ additions: 0,
51
+ deletions: 0
52
+ };
53
+ continue;
54
+ }
55
+
56
+ if (currentHunk) {
57
+ currentHunk.lines.push(rawLine);
58
+ if (rawLine.startsWith('+') && !rawLine.startsWith('+++')) currentHunk.additions++;
59
+ if (rawLine.startsWith('-') && !rawLine.startsWith('---')) currentHunk.deletions++;
60
+ }
61
+ }
62
+
63
+ if (currentHunk) hunks.push(finalizeHunk(currentHunk));
64
+ return hunks;
65
+ }
66
+
67
+ function finalizeHunk(hunk) {
68
+ return {
69
+ id: hunk.id,
70
+ fileHeader: hunk.fileHeader,
71
+ header: hunk.header,
72
+ lines: hunk.lines,
73
+ additions: hunk.additions,
74
+ deletions: hunk.deletions
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Resolve the hunk-review state file path for a task.
80
+ */
81
+ function hunkStatePath(projectDir, squadSlug, taskId) {
82
+ return path.join(projectDir, SQUADS_DIR, squadSlug, 'tasks', taskId, 'hunk-review.json');
83
+ }
84
+
85
+ /**
86
+ * Load hunk review state for a task. Returns null if not found.
87
+ */
88
+ async function loadHunkState(projectDir, squadSlug, taskId) {
89
+ try {
90
+ const raw = await fs.readFile(hunkStatePath(projectDir, squadSlug, taskId), 'utf8');
91
+ return JSON.parse(raw);
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Save hunk review state for a task.
99
+ */
100
+ async function saveHunkState(projectDir, squadSlug, taskId, state) {
101
+ const p = hunkStatePath(projectDir, squadSlug, taskId);
102
+ await fs.mkdir(path.dirname(p), { recursive: true });
103
+ await fs.writeFile(p, JSON.stringify(state, null, 2), 'utf8');
104
+ }
105
+
106
+ /**
107
+ * Initialize hunk review state from a diff string.
108
+ * Returns the initial state object (persisted to disk).
109
+ */
110
+ async function initHunkReview(projectDir, squadSlug, taskId, diff) {
111
+ const hunks = parseDiffHunks(diff);
112
+ const state = {
113
+ taskId,
114
+ squadSlug,
115
+ diff,
116
+ hunks: hunks.map(h => ({
117
+ id: h.id,
118
+ fileHeader: h.fileHeader,
119
+ header: h.header,
120
+ lines: h.lines,
121
+ additions: h.additions,
122
+ deletions: h.deletions,
123
+ status: HUNK_STATES.PENDING,
124
+ comment: null,
125
+ reviewedAt: null
126
+ })),
127
+ createdAt: new Date().toISOString(),
128
+ updatedAt: new Date().toISOString()
129
+ };
130
+ await saveHunkState(projectDir, squadSlug, taskId, state);
131
+ return state;
132
+ }
133
+
134
+ /**
135
+ * Get current hunk review state (or init from diff if missing).
136
+ */
137
+ async function getHunks(projectDir, squadSlug, taskId, diff) {
138
+ let state = await loadHunkState(projectDir, squadSlug, taskId);
139
+ if (!state && diff) {
140
+ state = await initHunkReview(projectDir, squadSlug, taskId, diff);
141
+ }
142
+ return state;
143
+ }
144
+
145
+ /**
146
+ * Update the status of a single hunk.
147
+ * @param {string} newStatus — one of HUNK_STATES values
148
+ * @param {string|null} comment
149
+ * @returns {{ state, dispatch: string|null }}
150
+ * dispatch is 'task_done' | 'task_needs_revision' | null based on overall state after update
151
+ */
152
+ async function updateHunk(projectDir, squadSlug, taskId, hunkId, newStatus, comment) {
153
+ const state = await loadHunkState(projectDir, squadSlug, taskId);
154
+ if (!state) return { ok: false, error: 'No hunk review state found' };
155
+
156
+ const hunk = state.hunks.find(h => h.id === hunkId);
157
+ if (!hunk) return { ok: false, error: `Hunk "${hunkId}" not found` };
158
+
159
+ hunk.status = newStatus;
160
+ if (comment !== undefined && comment !== null) hunk.comment = comment;
161
+ hunk.reviewedAt = new Date().toISOString();
162
+ state.updatedAt = new Date().toISOString();
163
+
164
+ await saveHunkState(projectDir, squadSlug, taskId, state);
165
+
166
+ const dispatch = computeDispatch(state.hunks);
167
+ return { ok: true, hunk, dispatch, state };
168
+ }
169
+
170
+ /**
171
+ * Compute the dispatch event based on all hunk statuses.
172
+ * Returns: 'task_done' if all approved, 'task_needs_revision' if any rejected,
173
+ * null if still pending.
174
+ */
175
+ function computeDispatch(hunks) {
176
+ const allReviewed = hunks.every(h => h.status !== HUNK_STATES.PENDING);
177
+ if (!allReviewed) return null;
178
+
179
+ const rejectedHunks = hunks.filter(h => h.status === HUNK_STATES.REJECTED);
180
+ if (rejectedHunks.length > 0) {
181
+ return { event: 'task_needs_revision', rejectedHunks: rejectedHunks.map(h => h.id) };
182
+ }
183
+
184
+ return { event: 'task_done' };
185
+ }
186
+
187
+ /**
188
+ * Get a summary of review progress: total, approved, rejected, pending.
189
+ */
190
+ function getReviewProgress(hunks) {
191
+ return {
192
+ total: hunks.length,
193
+ approved: hunks.filter(h => h.status === HUNK_STATES.APPROVED).length,
194
+ rejected: hunks.filter(h => h.status === HUNK_STATES.REJECTED).length,
195
+ revised: hunks.filter(h => h.status === HUNK_STATES.REVISED).length,
196
+ pending: hunks.filter(h => h.status === HUNK_STATES.PENDING).length
197
+ };
198
+ }
199
+
200
+ module.exports = {
201
+ parseDiffHunks,
202
+ initHunkReview,
203
+ getHunks,
204
+ updateHunk,
205
+ loadHunkState,
206
+ computeDispatch,
207
+ getReviewProgress,
208
+ HUNK_STATES
209
+ };
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ function countContentItems(db, squadSlug) {
4
+ const row = db.prepare('SELECT COUNT(*) AS cnt FROM content_items WHERE squad_slug = ?').get(squadSlug);
5
+ return row ? row.cnt : 0;
6
+ }
7
+
8
+ function countSessions(db, squadSlug) {
9
+ const row = db.prepare(
10
+ "SELECT COUNT(*) AS cnt FROM tasks WHERE meta_json LIKE ? AND task_kind = 'live_session'"
11
+ ).get(`%${squadSlug}%`);
12
+ return row ? row.cnt : 0;
13
+ }
14
+
15
+ function countLearnings(db, squadSlug) {
16
+ const row = db.prepare('SELECT COUNT(*) AS cnt FROM squad_learnings WHERE squad_slug = ?').get(squadSlug);
17
+ return row ? row.cnt : 0;
18
+ }
19
+
20
+ function calcDeliveryRate(db, squadSlug) {
21
+ const total = db.prepare('SELECT COUNT(*) AS cnt FROM delivery_log WHERE squad_slug = ?').get(squadSlug);
22
+ if (!total || total.cnt === 0) return null;
23
+ const success = db.prepare(
24
+ 'SELECT COUNT(*) AS cnt FROM delivery_log WHERE squad_slug = ? AND status_code >= 200 AND status_code < 300'
25
+ ).get(squadSlug);
26
+ return Math.round(((success ? success.cnt : 0) / total.cnt) * 100);
27
+ }
28
+
29
+ function getRecentDeliveries(db, squadSlug, limit = 20) {
30
+ return db.prepare(
31
+ 'SELECT * FROM delivery_log WHERE squad_slug = ? ORDER BY created_at DESC LIMIT ?'
32
+ ).all(squadSlug, limit);
33
+ }
34
+
35
+ function getRecentContent(db, squadSlug, limit = 20) {
36
+ return db.prepare(
37
+ 'SELECT content_key, title, content_type, layout_type, status, created_at, updated_at FROM content_items WHERE squad_slug = ? ORDER BY updated_at DESC LIMIT ?'
38
+ ).all(squadSlug, limit);
39
+ }
40
+
41
+ function getLearnings(db, squadSlug, statusFilter = null) {
42
+ if (statusFilter) {
43
+ return db.prepare(
44
+ 'SELECT * FROM squad_learnings WHERE squad_slug = ? AND status = ? ORDER BY updated_at DESC'
45
+ ).all(squadSlug, statusFilter);
46
+ }
47
+ return db.prepare(
48
+ 'SELECT * FROM squad_learnings WHERE squad_slug = ? ORDER BY updated_at DESC'
49
+ ).all(squadSlug);
50
+ }
51
+
52
+ function getLearningStats(db, squadSlug) {
53
+ const rows = db.prepare(
54
+ 'SELECT status, COUNT(*) AS cnt FROM squad_learnings WHERE squad_slug = ? GROUP BY status'
55
+ ).all(squadSlug);
56
+ const stats = { active: 0, stale: 0, archived: 0, promoted: 0 };
57
+ for (const row of rows) {
58
+ if (Object.prototype.hasOwnProperty.call(stats, row.status)) {
59
+ stats[row.status] = row.cnt;
60
+ }
61
+ }
62
+ return stats;
63
+ }
64
+
65
+ function getExecutionPlan(db, squadSlug) {
66
+ const plan = db.prepare(
67
+ 'SELECT * FROM squad_execution_plans WHERE squad_slug = ? ORDER BY updated_at DESC LIMIT 1'
68
+ ).get(squadSlug);
69
+ if (!plan) return null;
70
+ const rounds = db.prepare(
71
+ 'SELECT * FROM squad_plan_rounds WHERE plan_slug = ? ORDER BY round_number ASC'
72
+ ).all(plan.plan_slug);
73
+ return { ...plan, rounds };
74
+ }
75
+
76
+ function getPipelineInfo(db, squadSlug) {
77
+ const node = db.prepare(
78
+ 'SELECT * FROM pipeline_nodes WHERE squad_slug = ?'
79
+ ).get(squadSlug);
80
+ if (!node) return null;
81
+ const pipeline = db.prepare(
82
+ 'SELECT * FROM squad_pipelines WHERE pipeline_slug = ?'
83
+ ).get(node.pipeline_slug);
84
+ const handoffs = db.prepare(
85
+ 'SELECT * FROM squad_handoffs WHERE (from_squad = ? OR to_squad = ?) ORDER BY created_at DESC LIMIT 20'
86
+ ).all(squadSlug, squadSlug);
87
+ return { pipeline, node, handoffs };
88
+ }
89
+
90
+ function getSquadMetrics(db, squadSlug) {
91
+ try {
92
+ return db.prepare(
93
+ 'SELECT * FROM squad_metrics WHERE squad_slug = ? ORDER BY period DESC, metric_key ASC'
94
+ ).all(squadSlug);
95
+ } catch {
96
+ return [];
97
+ }
98
+ }
99
+
100
+ function getRecentEvents(db, squadSlug, limit = 30) {
101
+ return db.prepare(
102
+ "SELECT * FROM execution_events WHERE run_key LIKE ? ORDER BY created_at DESC LIMIT ?"
103
+ ).all(`%${squadSlug}%`, limit);
104
+ }
105
+
106
+ function getSquadOverview(db, squadSlug) {
107
+ return {
108
+ contentItems: countContentItems(db, squadSlug),
109
+ sessions: countSessions(db, squadSlug),
110
+ learnings: countLearnings(db, squadSlug),
111
+ deliveryRate: calcDeliveryRate(db, squadSlug),
112
+ learningStats: getLearningStats(db, squadSlug),
113
+ executionPlan: getExecutionPlan(db, squadSlug),
114
+ pipelineInfo: getPipelineInfo(db, squadSlug),
115
+ customMetrics: getSquadMetrics(db, squadSlug)
116
+ };
117
+ }
118
+
119
+ module.exports = {
120
+ countContentItems,
121
+ countSessions,
122
+ countLearnings,
123
+ calcDeliveryRate,
124
+ getRecentDeliveries,
125
+ getRecentContent,
126
+ getLearnings,
127
+ getLearningStats,
128
+ getExecutionPlan,
129
+ getPipelineInfo,
130
+ getSquadMetrics,
131
+ getRecentEvents,
132
+ getSquadOverview
133
+ };