@zaip-ai/cli 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +3 -2
  2. package/dist/services/agent-adapters.js +1 -1
  3. package/dist/services/agent-adapters.js.map +1 -1
  4. package/dist/services/agent-adapters.test.js +2 -1
  5. package/dist/services/agent-adapters.test.js.map +1 -1
  6. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  7. package/dist/services/codex-pre-tool-hook-template.js +89 -7
  8. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  9. package/dist/services/codex-rules.d.ts +1 -1
  10. package/dist/services/codex-rules.d.ts.map +1 -1
  11. package/dist/services/codex-rules.js +7 -0
  12. package/dist/services/codex-rules.js.map +1 -1
  13. package/dist/services/install-agents-template.d.ts.map +1 -1
  14. package/dist/services/install-agents-template.js +1 -0
  15. package/dist/services/install-agents-template.js.map +1 -1
  16. package/dist/services/runtime-template.test.js +31 -7
  17. package/dist/services/runtime-template.test.js.map +1 -1
  18. package/dist/services/template-manifest.d.ts.map +1 -1
  19. package/dist/services/template-manifest.js +7 -3
  20. package/dist/services/template-manifest.js.map +1 -1
  21. package/package.json +1 -1
  22. package/templates/.agents/config/agents/catalog.yaml +7 -7
  23. package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +2 -2
  24. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +2 -2
  25. package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +8 -8
  26. package/templates/.agents/dashboard/.zaip-managed.json +373 -205
  27. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-DxR_pgSV.js → CostCalculationGuidePage-ovxurkIk.js} +1 -1
  28. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-4jZLTWE_.js → DashboardHelpDialog-BXy6nti1.js} +268 -86
  29. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-CKcsqb-r.js → PricingSettingsDialog-vSvvOHQp.js} +1 -1
  30. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-DmmJD2Th.js → ProjectSettingsPage-zH758vfs.js} +1 -1
  31. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-CTH2wDs5.js +1 -0
  32. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CaKwQP43.css → TraceabilityWorkspace-DvvavDLY.css} +1 -1
  33. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DKwP1stV.js +68 -0
  34. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-PoJA2PI1.css +1 -0
  35. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-C0fj2uU9.js → icons-vendor-B_lDDHn1.js} +1 -1
  36. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-B31GMG5O.css +1 -0
  37. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CuMcoUni.js +2 -0
  38. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-BEsJ-FYH.js → react-vendor-B3TJrD4O.js} +1 -1
  39. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B6y6Gw12.js → traceability-7f1j5wx3.js} +1 -1
  40. package/templates/.agents/dashboard/apps/frontend/dist/index.html +7 -7
  41. package/templates/.agents/dashboard/apps/frontend/playwright.dom.config.ts +32 -0
  42. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +132 -245
  43. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +0 -21
  44. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +9 -0
  45. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.dom.e2e.ts +56 -0
  46. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +18 -1
  47. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +75 -8
  48. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +25 -1
  49. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +7 -9
  50. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +4 -4
  51. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +1 -1
  52. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +7 -0
  53. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +20 -8
  54. package/templates/.agents/dashboard/apps/frontend/src/sqlite-page-export.test.ts +32 -0
  55. package/templates/.agents/dashboard/apps/frontend/src/sqlite-page-export.ts +21 -0
  56. package/templates/.agents/dashboard/apps/frontend/src/styles.css +19 -0
  57. package/templates/.agents/dashboard/apps/frontend/src/traceability-task-pagination.test.ts +32 -0
  58. package/templates/.agents/dashboard/apps/frontend/src/traceability-task-pagination.ts +36 -0
  59. package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +6 -2
  60. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +48 -0
  61. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +31 -27
  62. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +7 -1
  63. package/templates/.agents/dashboard/apps/frontend/src/use-app-work-list-page.test.ts +163 -0
  64. package/templates/.agents/dashboard/apps/frontend/src/use-app-work-list-page.ts +136 -0
  65. package/templates/.agents/dashboard/apps/frontend/src/use-operations-detail-export.ts +76 -0
  66. package/templates/.agents/dashboard/apps/frontend/src/work-list-load-dedup.test.ts +23 -0
  67. package/templates/.agents/dashboard/apps/frontend/src/work-list-load-dedup.ts +27 -0
  68. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +38 -14
  69. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.tsx +60 -0
  70. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionTable.tsx +191 -0
  71. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.loading.test.ts +14 -0
  72. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.run-page.test.ts +18 -0
  73. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +18 -3
  74. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +8 -2
  75. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorLoadErrors.tsx +35 -0
  76. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +47 -1
  77. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +50 -58
  78. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableLoading.test.ts +132 -0
  79. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableLoading.tsx +70 -0
  80. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +128 -138
  81. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +17 -67
  82. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +2 -0
  83. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +3 -1
  84. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-page-range.ts +35 -0
  85. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +16 -4
  86. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +37 -2
  87. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +44 -8
  88. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +6 -1
  89. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +20 -3
  90. package/templates/.agents/dashboard/modules/operations/src/react/DeveloperAttributionPanel.test.ts +54 -0
  91. package/templates/.agents/dashboard/modules/operations/src/react/DeveloperAttributionPanel.tsx +247 -0
  92. package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +39 -13
  93. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +107 -2
  94. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.macro-metrics.test.ts +55 -0
  95. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +20 -28
  96. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +162 -165
  97. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.tsx +127 -0
  98. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +13 -46
  99. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +3 -0
  100. package/templates/.agents/dashboard/modules/operations/src/react/PagedWorkList.test.tsx +43 -0
  101. package/templates/.agents/dashboard/modules/operations/src/react/PagedWorkList.tsx +181 -0
  102. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +4 -2
  103. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +13 -0
  104. package/templates/.agents/dashboard/modules/operations/src/react/developer-identities.test.ts +18 -0
  105. package/templates/.agents/dashboard/modules/operations/src/react/developer-identities.ts +30 -0
  106. package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +1 -0
  107. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +8 -0
  108. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +3 -0
  109. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +14 -5
  110. package/templates/.agents/dashboard/modules/operations/src/react/index.ts +1 -0
  111. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +4 -3
  112. package/templates/.agents/dashboard/modules/operations/src/react/operations-date-filters.css +1 -1
  113. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +21 -11
  114. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.css +4 -0
  115. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +8 -1
  116. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +1 -0
  117. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +1 -0
  118. package/templates/.agents/dashboard/modules/operations/src/server/metric-coverage.ts +1 -0
  119. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +527 -335
  120. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +501 -309
  121. package/templates/.agents/dashboard/packages/local-runtime/gitignore.template +2 -0
  122. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +3 -1
  123. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +3 -3
  124. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +25 -15
  125. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +527 -335
  126. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +34 -0
  127. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +15 -8
  128. package/templates/.agents/dashboard/packages/local-runtime/src/server-classification-page.test.ts +87 -0
  129. package/templates/.agents/dashboard/packages/local-runtime/src/server-period-macro-metric.test.ts +44 -0
  130. package/templates/.agents/dashboard/packages/local-runtime/src/server-period-macro-metric.ts +125 -0
  131. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +66 -0
  132. package/templates/.agents/dashboard/packages/local-runtime/src/server-request-authority.test.ts +61 -0
  133. package/templates/.agents/dashboard/packages/local-runtime/src/server-request-authority.ts +18 -0
  134. package/templates/.agents/dashboard/packages/local-runtime/src/server-run-projections.test.ts +42 -0
  135. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +97 -0
  136. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +3 -1
  137. package/templates/.agents/dashboard/packages/local-runtime/src/server-sqlite-task-trace.ts +91 -0
  138. package/templates/.agents/dashboard/packages/local-runtime/src/server.test-fixtures.ts +36 -0
  139. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +98 -123
  140. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +111 -135
  141. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-artifact-counts.ts +28 -0
  142. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +63 -0
  143. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +109 -0
  144. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +497 -0
  145. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-fast-page-parity.test-support.ts +65 -0
  146. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-page.test.ts +176 -0
  147. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-page.ts +119 -0
  148. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +41 -0
  149. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.test.ts +16 -0
  150. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.ts +29 -0
  151. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-cache.ts +355 -0
  152. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +121 -0
  153. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-timing.ts +27 -0
  154. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +15 -0
  155. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.test.ts +293 -0
  156. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.ts +371 -0
  157. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +23 -0
  158. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-first-fold-types.ts +89 -0
  159. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +227 -0
  160. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +691 -0
  161. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +81 -0
  162. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +440 -863
  163. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1120 -1086
  164. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +129 -0
  165. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-install.test.ts +26 -0
  166. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-install.ts +28 -0
  167. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +116 -0
  168. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +146 -0
  169. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.test.mjs +165 -0
  170. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +140 -0
  171. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +188 -299
  172. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +51 -70
  173. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +5 -2
  174. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +4 -1
  175. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-loading.spec.ts +142 -0
  176. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +232 -0
  177. package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +743 -0
  178. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +183 -210
  179. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  180. package/templates/.agents/requirements/autonomous-assurance.md +6 -6
  181. package/templates/.agents/requirements/comparative-audit.md +1 -1
  182. package/templates/.agents/requirements/context-engine.md +1 -1
  183. package/templates/.agents/requirements/cost-observability.md +1 -1
  184. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  185. package/templates/.agents/requirements/data-export.md +1 -1
  186. package/templates/.agents/requirements/design-system.md +1 -1
  187. package/templates/.agents/requirements/execution-engine.md +3 -3
  188. package/templates/.agents/requirements/kanban-dashboard.md +10 -2
  189. package/templates/.agents/requirements/model-orchestration.md +1 -1
  190. package/templates/.agents/requirements/public-distribution.md +1 -1
  191. package/templates/.agents/requirements/software-architecture.md +1 -1
  192. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  193. package/templates/.agents/requirements/work-lifecycle.md +4 -4
  194. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  195. package/templates/.agents/rules/dashboard-ui-consistency.md +14 -0
  196. package/templates/.agents/rules/frontend-lite.md +4 -4
  197. package/templates/.agents/rules/run-lifecycle-closure.md +4 -4
  198. package/templates/.agents/runtime/bin/zaip.mjs +1137 -1037
  199. package/templates/.agents/runtime/checksums.sha256 +2 -2
  200. package/templates/.agents/runtime/provenance.json +2 -2
  201. package/templates/.agents/skills/criar-pasta-obsidian/scripts/generate-obsidian-vault.test.mjs +20 -16
  202. package/templates/.agents/skills/zaip-skill-agent-catalog/SKILL.md +22 -0
  203. package/templates/.agents/skills/zaip-skill-agent-catalog/agents/openai.yaml +4 -0
  204. package/templates/.agents/skills/zaip-skill-author-skills/SKILL.md +3 -2
  205. package/templates/.agents/skills/zaip-skill-author-skills/scripts/audit-skill-catalog.mjs +49 -0
  206. package/templates/.agents/skills/zaip-skill-author-skills/scripts/author-skill-scripts.test.mjs +27 -0
  207. package/templates/.agents/skills/zaip-skill-author-skills/scripts/run-skill-tests.mjs +37 -0
  208. package/templates/.agents/skills/zaip-skill-backend/SKILL.md +1 -0
  209. package/templates/.agents/skills/zaip-skill-change/SKILL.md +1 -1
  210. package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +83 -0
  211. package/templates/.agents/skills/zaip-skill-dashboard-diagnostics/SKILL.md +22 -0
  212. package/templates/.agents/skills/zaip-skill-dashboard-diagnostics/agents/openai.yaml +4 -0
  213. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +1 -1
  214. package/templates/.agents/skills/zaip-skill-frontend/SKILL.md +4 -3
  215. package/templates/.agents/skills/zaip-skill-frontend/agents/openai.yaml +2 -2
  216. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +10 -10
  217. package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +27 -0
  218. package/templates/.agents/skills/zaip-skill-lifecycle-closure/agents/openai.yaml +4 -0
  219. package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
  220. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +72 -22
  221. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-explicit-recovery.mjs +223 -0
  222. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +42 -1
  223. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +101 -0
  224. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +53 -0
  225. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +20 -0
  226. package/templates/.agents/skills/zaip-skill-release-qualification/agents/openai.yaml +4 -0
  227. package/templates/.agents/skills/zaip-skill-review/SKILL.md +5 -0
  228. package/templates/.agents/skills/zaip-skill-rules/SKILL.md +1 -1
  229. package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
  230. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +22 -90
  231. package/templates/.agents/zaip-aios/INDEX.md +57 -32
  232. package/templates/.agents/zaip-aios/README.md +22 -12
  233. package/templates/.agents/zaip-aios/guides/02-requirements.md +5 -0
  234. package/templates/.agents/zaip-aios/guides/06-execucao.md +13 -4
  235. package/templates/.agents/zaip-aios/guides/07-revisao-e-validacao.md +7 -0
  236. package/templates/.agents/zaip-aios/guides/10-seguranca.md +23 -0
  237. package/templates/.agents/zaip-aios/guides/11-observabilidade-e-custos.md +6 -6
  238. package/templates/.agents/zaip-aios/guides/13-boas-praticas.md +3 -0
  239. package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +12 -3
  240. package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +5 -5
  241. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +23 -0
  242. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +44 -8
  243. package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +8 -14
  244. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +5 -0
  245. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +6 -6
  246. package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +8 -8
  247. package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +3 -2
  248. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +92 -10
  249. package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +8 -3
  250. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +5 -0
  251. package/templates/template-manifest.json +555 -170
  252. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.js +0 -1
  253. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +0 -1
  254. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-N5Csr0RP.js +0 -68
  255. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.js +0 -2
  256. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +0 -1
  257. package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +0 -45
  258. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +0 -7333
@@ -0,0 +1,293 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ attachOperationsWorkListDevelopers,
5
+ operationsWorkListPayloadPage,
6
+ operationsWorkListSql,
7
+ operationsWorkListSqlPage,
8
+ } from "./sqlite-operations-work-list.js";
9
+
10
+ function page(input: Parameters<typeof operationsWorkListSql>[0]) {
11
+ const database = new DatabaseSync(":memory:");
12
+ database.exec(`
13
+ CREATE TABLE canonical_work_items (entity_kind TEXT, id TEXT, stage TEXT, risk TEXT, spec_id TEXT, change_id TEXT, occurred_day_sp TEXT);
14
+ CREATE TABLE canonical_changes (id TEXT, source_type TEXT, task_ids_json TEXT, created_at TEXT);
15
+ CREATE TABLE operations_work_kanban (entity_kind TEXT, id TEXT, title TEXT, status TEXT, risk TEXT, occurred_at TEXT, occurred_day_sp TEXT);
16
+ INSERT INTO canonical_work_items VALUES
17
+ ('task','TASK-PRD','executing','standard','SPEC-1','CHG-PRD','2026-08-01'),
18
+ ('task','TASK-SPEC','ready','standard','SPEC-1',NULL,'2026-08-02'),
19
+ ('task','TASK-STANDALONE','executing','standard',NULL,NULL,'2026-08-03'),
20
+ ('task','TASK-ORIGIN-SPEC','ready','standard',NULL,'CHG-SPEC','2026-08-03'),
21
+ ('prd','PRD-1','definition','standard',NULL,NULL,'2026-08-04'),
22
+ ('spec','SPEC-1','planning','standard',NULL,NULL,'2026-08-05');
23
+ INSERT INTO operations_work_kanban SELECT entity_kind,id,id,'active',risk,occurred_day_sp || 'T00:00:00Z',occurred_day_sp FROM canonical_work_items;
24
+ INSERT INTO canonical_changes VALUES
25
+ ('CHG-PRD','prd','["TASK-PRD"]','2026-08-04T00:00:00Z'),
26
+ ('CHG-SPEC','spec','["TASK-ORIGIN-SPEC"]','2026-08-04T00:00:00Z'),
27
+ ('CHG-NEWER','spec','["TASK-PRD"]','2026-08-05T00:00:00Z');
28
+ `);
29
+ const sql = operationsWorkListSql(input);
30
+ const rows = database
31
+ .prepare(`${sql.select} FROM ${sql.source}${sql.where} ORDER BY work.id`)
32
+ .all(...sql.values) as Array<Record<string, unknown>>;
33
+ database.close();
34
+ return rows;
35
+ }
36
+
37
+ describe("operations work list SQL", () => {
38
+ it("matches Kanban task kind and Change-origin precedence", () => {
39
+ expect(page({ kind: "all-tasks" }).map((row) => row.id)).toEqual([
40
+ "TASK-ORIGIN-SPEC",
41
+ "TASK-PRD",
42
+ "TASK-SPEC",
43
+ "TASK-STANDALONE",
44
+ ]);
45
+ expect(page({ kind: "task" }).map((row) => row.id)).toEqual([
46
+ "TASK-ORIGIN-SPEC",
47
+ "TASK-PRD",
48
+ "TASK-SPEC",
49
+ ]);
50
+ expect(page({ kind: "standalone-task" }).map((row) => row.id)).toEqual(["TASK-STANDALONE"]);
51
+ expect(page({ origin: "prd" }).map((row) => row.id)).toEqual(["TASK-PRD"]);
52
+ expect(page({ origin: "spec" }).map((row) => row.id)).toEqual([
53
+ "TASK-ORIGIN-SPEC",
54
+ "TASK-SPEC",
55
+ ]);
56
+ expect(page({ origin: "task" }).map((row) => row.id)).toEqual(["TASK-STANDALONE"]);
57
+ });
58
+
59
+ it("filters the public WorkStage, independent of lifecycle status", () => {
60
+ expect(page({ status: "executing", kind: "all" }).map((row) => row.id)).toEqual([
61
+ "TASK-PRD",
62
+ "TASK-STANDALONE",
63
+ ]);
64
+ });
65
+
66
+ it("paginates complete all-time payloads with public kinds and filters", () => {
67
+ const payload = {
68
+ pages: {
69
+ task: [
70
+ {
71
+ id: "TASK-LINKED",
72
+ title: "Linked task",
73
+ stage: "executing",
74
+ risk: "standard",
75
+ origin: "spec",
76
+ spec_id: "SPEC-1",
77
+ occurred_at: "2026-08-03T00:00:00Z",
78
+ },
79
+ {
80
+ id: "TASK-STANDALONE",
81
+ title: "Standalone task",
82
+ stage: "ready",
83
+ risk: "lite",
84
+ origin: "task",
85
+ spec_id: null,
86
+ occurred_at: "2026-08-02T00:00:00Z",
87
+ },
88
+ ],
89
+ spec: [
90
+ {
91
+ id: "SPEC-1",
92
+ title: "Specification",
93
+ stage: "planning",
94
+ risk: "standard",
95
+ occurred_at: "2026-08-04T00:00:00Z",
96
+ },
97
+ ],
98
+ prd: [
99
+ {
100
+ id: "PRD-1",
101
+ title: "Product requirement",
102
+ stage: "definition",
103
+ risk: "critical",
104
+ occurred_at: "2026-08-05T00:00:00Z",
105
+ },
106
+ ],
107
+ },
108
+ };
109
+ expect(operationsWorkListPayloadPage({ kind: "all", limit: 2 }, payload)).toMatchObject({
110
+ total_count: 4,
111
+ has_more: true,
112
+ rows: [
113
+ { id: "PRD-1", kind: "prd" },
114
+ { id: "SPEC-1", kind: "spec" },
115
+ ],
116
+ });
117
+ expect(
118
+ operationsWorkListPayloadPage({ kind: "all", offset: 2, limit: 2 }, payload)?.rows.map(
119
+ (row) => row.id,
120
+ ),
121
+ ).toEqual(["TASK-LINKED", "TASK-STANDALONE"]);
122
+ expect(
123
+ operationsWorkListPayloadPage({ kind: "task", status: "executing", origin: "spec" }, payload)
124
+ ?.rows,
125
+ ).toMatchObject([{ id: "TASK-LINKED", kind: "task" }]);
126
+ expect(
127
+ operationsWorkListPayloadPage({ kind: "standalone-task", risk: "lite" }, payload)?.rows,
128
+ ).toMatchObject([{ id: "TASK-STANDALONE", kind: "standalone-task" }]);
129
+ expect(
130
+ operationsWorkListPayloadPage({ kind: "all-tasks" }, payload)?.rows.map((row) => row.id),
131
+ ).toEqual(["TASK-LINKED", "TASK-STANDALONE"]);
132
+ expect(
133
+ operationsWorkListPayloadPage({ kind: "prd", query: "product" }, payload)?.rows,
134
+ ).toMatchObject([{ id: "PRD-1", kind: "prd" }]);
135
+ expect(operationsWorkListPayloadPage({ kind: "all" }, {})).toBeNull();
136
+ });
137
+
138
+ it("accepts the validated default-month payload but never applies all-time data to a range", () => {
139
+ const month = {
140
+ from_day: "2026-08-01",
141
+ to_day: "2026-08-31",
142
+ pages: { task: [], spec: [], prd: [] },
143
+ };
144
+ expect(
145
+ operationsWorkListPayloadPage({ from: "2026-08-01", to: "2026-08-31" }, month),
146
+ ).toMatchObject({
147
+ total_count: 0,
148
+ });
149
+ expect(
150
+ operationsWorkListPayloadPage(
151
+ { from: "2026-08-01", to: "2026-08-31" },
152
+ { pages: month.pages },
153
+ ),
154
+ ).toBeNull();
155
+ });
156
+
157
+ it("retains more than 200 rows for exports and later pages", () => {
158
+ const task = Array.from({ length: 205 }, (_, index) => ({
159
+ id: `TASK-${String(index).padStart(3, "0")}`,
160
+ title: "Complete population",
161
+ stage: "ready",
162
+ risk: "standard",
163
+ origin: "spec",
164
+ occurred_at: `2026-08-${String((index % 28) + 1).padStart(2, "0")}T00:00:00Z`,
165
+ }));
166
+ const payload = { pages: { task, spec: [], prd: [] } };
167
+ const firstPage = operationsWorkListPayloadPage({ kind: "task", limit: 100 }, payload);
168
+ expect(firstPage).toMatchObject({
169
+ total_count: 205,
170
+ has_more: true,
171
+ });
172
+ expect(firstPage?.rows).toHaveLength(100);
173
+ expect(firstPage?.rows.map((row) => row.id)).toContain("TASK-195");
174
+ expect(
175
+ operationsWorkListPayloadPage({ kind: "task", offset: 200, limit: 100 }, payload),
176
+ ).toMatchObject({ total_count: 205, has_more: false, rows: expect.any(Array) });
177
+ expect(
178
+ operationsWorkListPayloadPage({ kind: "task", offset: 200, limit: 100 }, payload)?.rows,
179
+ ).toHaveLength(5);
180
+ });
181
+
182
+ it("uses the physical projection for export offsets when no views are available", () => {
183
+ const database = new DatabaseSync(":memory:");
184
+ database.exec(`
185
+ CREATE TABLE operations_work_projection (
186
+ entity_kind TEXT, id TEXT, title TEXT, risk TEXT, occurred_at TEXT, occurred_day_sp TEXT,
187
+ stage TEXT, spec_id TEXT, origin TEXT, kind TEXT
188
+ );
189
+ `);
190
+ const insert = database.prepare(`
191
+ INSERT INTO operations_work_projection VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
192
+ `);
193
+ for (let index = 0; index < 205; index += 1) {
194
+ insert.run(
195
+ "task",
196
+ `TASK-${String(index).padStart(3, "0")}`,
197
+ index === 4 ? "Needle task" : "Projected task",
198
+ index === 3 ? "lite" : "standard",
199
+ `2026-08-31T00:00:${String(index).padStart(3, "0")}Z`,
200
+ "2026-08-31",
201
+ index === 2 ? "executing" : "ready",
202
+ index % 2 === 0 ? "SPEC-1" : null,
203
+ index === 0 ? "prd" : null,
204
+ index % 2 === 0 ? "task" : "standalone-task",
205
+ );
206
+ }
207
+ const first = operationsWorkListSqlPage(database, { limit: 100, offset: 0 });
208
+ const second = operationsWorkListSqlPage(database, { limit: 100, offset: 100 });
209
+ const final = operationsWorkListSqlPage(database, { limit: 100, offset: 200 });
210
+ expect(first).toMatchObject({ total_count: 205, has_more: true, rows: expect.any(Array) });
211
+ expect(first.rows).toHaveLength(100);
212
+ expect(first.rows[0]).toMatchObject({ id: "TASK-204" });
213
+ expect(second).toMatchObject({ total_count: 205, has_more: true });
214
+ expect(second.rows).toHaveLength(100);
215
+ expect(final).toMatchObject({ total_count: 205, has_more: false });
216
+ expect(final.rows).toHaveLength(5);
217
+ expect(
218
+ operationsWorkListSqlPage(database, {
219
+ kind: "task",
220
+ origin: "spec",
221
+ status: "ready",
222
+ query: "TASK-004",
223
+ }).rows.map((row) => row.id),
224
+ ).toContain("TASK-004");
225
+ expect(
226
+ operationsWorkListSqlPage(database, { kind: "standalone-task", risk: "lite" }).rows,
227
+ ).toMatchObject([{ id: "TASK-003" }]);
228
+ expect(operationsWorkListSqlPage(database, { query: "needle" }).rows).toMatchObject([
229
+ { id: "TASK-004" },
230
+ ]);
231
+ database.close();
232
+ });
233
+
234
+ it("keeps the legacy view fallback when no physical projection exists", () => {
235
+ const database = new DatabaseSync(":memory:");
236
+ database.exec(`
237
+ CREATE TABLE canonical_work_items (entity_kind TEXT, id TEXT, stage TEXT, risk TEXT, spec_id TEXT, change_id TEXT, occurred_day_sp TEXT);
238
+ CREATE TABLE canonical_changes (id TEXT, source_type TEXT, task_ids_json TEXT, created_at TEXT);
239
+ CREATE TABLE operations_work_kanban (entity_kind TEXT, id TEXT, title TEXT, status TEXT, risk TEXT, occurred_at TEXT, occurred_day_sp TEXT);
240
+ INSERT INTO canonical_work_items VALUES ('task', 'TASK-LEGACY', 'ready', 'standard', NULL, NULL, '2026-08-01');
241
+ INSERT INTO operations_work_kanban VALUES ('task', 'TASK-LEGACY', 'Legacy task', 'active', 'standard', '2026-08-01T00:00:00Z', '2026-08-01');
242
+ `);
243
+ expect(operationsWorkListSqlPage(database, { limit: 10 })).toMatchObject({
244
+ total_count: 1,
245
+ has_more: false,
246
+ rows: [{ id: "TASK-LEGACY", kind: "standalone-task" }],
247
+ });
248
+ database.close();
249
+ });
250
+
251
+ it("projects each page's factual developer identities in one period-bounded set", () => {
252
+ const database = new DatabaseSync(":memory:");
253
+ database.exec(`
254
+ CREATE TABLE zaip_aios_activities (
255
+ payload_type TEXT, task_id TEXT, spec_id TEXT, prd_id TEXT,
256
+ developer_account_id TEXT, developer_login TEXT, occurred_day_sp TEXT
257
+ );
258
+ INSERT INTO zaip_aios_activities VALUES
259
+ (NULL, 'TASK-1', NULL, NULL, '2', 'alpha', '2026-08-10'),
260
+ (NULL, 'TASK-1', NULL, NULL, '2', 'beta', '2026-08-10'),
261
+ (NULL, 'TASK-1', NULL, NULL, '10', NULL, '2026-08-10'),
262
+ (NULL, 'TASK-1', NULL, NULL, 'outside-period', 'ignored', '2026-08-09'),
263
+ (NULL, NULL, 'SPEC-1', NULL, '7', 'spec-dev', '2026-08-10'),
264
+ (NULL, NULL, NULL, 'PRD-NOT-ON-PAGE', '99', 'not-selected', '2026-08-10');
265
+ `);
266
+ const rows = attachOperationsWorkListDevelopers(
267
+ database,
268
+ [
269
+ { id: "TASK-1", kind: "task" },
270
+ { id: "SPEC-1", entity_kind: "spec" },
271
+ { id: "TASK-WITHOUT-RUN", kind: "standalone-task" },
272
+ ],
273
+ { from: "2026-08-10", to: "2026-08-10" },
274
+ );
275
+ expect(rows).toEqual([
276
+ {
277
+ id: "TASK-1",
278
+ kind: "task",
279
+ developers: [
280
+ { account_id: "10", login_snapshot: null },
281
+ { account_id: "2", login_snapshot: "beta" },
282
+ ],
283
+ },
284
+ {
285
+ id: "SPEC-1",
286
+ entity_kind: "spec",
287
+ developers: [{ account_id: "7", login_snapshot: "spec-dev" }],
288
+ },
289
+ { id: "TASK-WITHOUT-RUN", kind: "standalone-task", developers: [] },
290
+ ]);
291
+ database.close();
292
+ });
293
+ });
@@ -0,0 +1,371 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+
3
+ export type OperationsWorkListQuery = {
4
+ from?: string | null;
5
+ to?: string | null;
6
+ query?: string;
7
+ status?: string;
8
+ risk?: string;
9
+ origin?: string;
10
+ kind?: "all" | "all-tasks" | "task" | "standalone-task" | "prd" | "spec";
11
+ };
12
+
13
+ type WorkRow = Record<string, unknown>;
14
+ type WorkPagesPayload = { pages: { task: WorkRow[]; spec: WorkRow[]; prd: WorkRow[] } };
15
+
16
+ export type OperationsWorkListDeveloper = {
17
+ account_id: string;
18
+ login_snapshot: string | null;
19
+ };
20
+
21
+ export type OperationsWorkListPayloadPage = {
22
+ offset: number;
23
+ limit: number;
24
+ total_count: number;
25
+ has_more: boolean;
26
+ rows: WorkRow[];
27
+ };
28
+
29
+ function isWorkPagesPayload(value: unknown): value is WorkPagesPayload {
30
+ if (value === null || typeof value !== "object") return false;
31
+ const pages = (value as { pages?: unknown }).pages;
32
+ if (pages === null || typeof pages !== "object") return false;
33
+ return ["task", "spec", "prd"].every((kind) =>
34
+ Array.isArray((pages as Record<string, unknown>)[kind]),
35
+ );
36
+ }
37
+
38
+ function publicKind(row: WorkRow, sourceKind: "task" | "spec" | "prd") {
39
+ if (sourceKind !== "task") return sourceKind;
40
+ if (row.kind === "task" || row.kind === "standalone-task") return row.kind;
41
+ return (typeof row.spec_id === "string" && row.spec_id.length > 0) ||
42
+ row.origin === "spec" ||
43
+ row.origin === "prd"
44
+ ? "task"
45
+ : "standalone-task";
46
+ }
47
+
48
+ function matchesPayloadFilter(row: WorkRow, kind: string, input: OperationsWorkListQuery): boolean {
49
+ if (input.kind && input.kind !== "all") {
50
+ const matchesKind =
51
+ input.kind === "all-tasks"
52
+ ? kind === "task" || kind === "standalone-task"
53
+ : kind === input.kind;
54
+ if (!matchesKind) return false;
55
+ }
56
+ if (input.query) {
57
+ const query = input.query.toLowerCase();
58
+ if (
59
+ !String(row.id ?? "")
60
+ .toLowerCase()
61
+ .includes(query) &&
62
+ !String(row.title ?? "")
63
+ .toLowerCase()
64
+ .includes(query)
65
+ )
66
+ return false;
67
+ }
68
+ if (input.status && row.stage !== input.status) return false;
69
+ if (input.risk && row.risk !== input.risk) return false;
70
+ // SQL only permits origin filters for Tasks. If an older payload lacks the
71
+ // persisted origin, preserve factual parity by falling back to SQL instead.
72
+ if (input.origin && (kind === "prd" || kind === "spec" || typeof row.origin !== "string"))
73
+ return false;
74
+ if (input.origin && row.origin !== input.origin) return false;
75
+ return true;
76
+ }
77
+
78
+ /**
79
+ * Paginates already-materialized Board pages. Returning null deliberately
80
+ * delegates to SQL when a payload is absent or not rich enough for a filter.
81
+ */
82
+ export function operationsWorkListPayloadPage(
83
+ input: OperationsWorkListQuery & { offset?: number; limit?: number },
84
+ payload: unknown,
85
+ ): OperationsWorkListPayloadPage | null {
86
+ if (!isWorkPagesPayload(payload)) return null;
87
+ if (input.from || input.to) {
88
+ // Monthly payload eligibility is established by readFirstFoldMonthlyQuick;
89
+ // all-time payload callers do not provide a temporal filter.
90
+ const hasRange = typeof (payload as { from_day?: unknown }).from_day === "string";
91
+ if (!hasRange) return null;
92
+ }
93
+ const rows = (["task", "spec", "prd"] as const)
94
+ .flatMap((sourceKind) =>
95
+ payload.pages[sourceKind].map(
96
+ (row): WorkRow => ({ ...row, kind: publicKind(row, sourceKind) }),
97
+ ),
98
+ )
99
+ .filter((row) => matchesPayloadFilter(row, String(row.kind), input))
100
+ .sort(
101
+ (left, right) =>
102
+ String(right.occurred_at ?? "").localeCompare(String(left.occurred_at ?? "")) ||
103
+ String(right.id ?? "").localeCompare(String(left.id ?? "")),
104
+ );
105
+ const limit = Math.min(200, Math.max(1, input.limit ?? 50));
106
+ const offset = Math.max(0, input.offset ?? 0);
107
+ const page = rows.slice(offset, offset + limit);
108
+ return {
109
+ offset,
110
+ limit,
111
+ total_count: rows.length,
112
+ has_more: offset + page.length < rows.length,
113
+ rows: page,
114
+ };
115
+ }
116
+
117
+ const linkedToSpec = "canonical.spec_id IS NOT NULL";
118
+ const changeId = `(COALESCE(canonical.change_id, (SELECT change.id FROM canonical_changes change, json_each(change.task_ids_json) task WHERE task.value = canonical.id ORDER BY change.created_at DESC, change.id DESC LIMIT 1)))`;
119
+ const changeOrigin = `(SELECT change.source_type FROM canonical_changes change WHERE change.id=${changeId})`;
120
+ const linkedTask = `(${linkedToSpec} OR COALESCE(${changeOrigin} IN ('spec', 'prd'), 0))`;
121
+
122
+ /** Builds count/page SQL for the canonical, server-paginated Operations List. */
123
+ export function operationsWorkListSql(input: OperationsWorkListQuery) {
124
+ const clauses: string[] = [];
125
+ const values: Array<string | number> = [];
126
+ if (input.from) {
127
+ clauses.push("canonical.occurred_day_sp >= ?");
128
+ values.push(input.from.slice(0, 10));
129
+ }
130
+ if (input.to) {
131
+ clauses.push("canonical.occurred_day_sp <= ?");
132
+ values.push(input.to.slice(0, 10));
133
+ }
134
+ if (input.query) {
135
+ clauses.push("(work.id LIKE ? OR work.title LIKE ?)");
136
+ values.push(`%${input.query}%`, `%${input.query}%`);
137
+ }
138
+ if (input.status) {
139
+ clauses.push("canonical.stage = ?");
140
+ values.push(input.status);
141
+ }
142
+ if (input.risk) {
143
+ clauses.push("canonical.risk = ?");
144
+ values.push(input.risk);
145
+ }
146
+ if (input.origin) {
147
+ clauses.push("canonical.entity_kind = 'task'");
148
+ const fallback =
149
+ input.origin === "spec"
150
+ ? linkedToSpec
151
+ : input.origin === "task"
152
+ ? `NOT ${linkedToSpec}`
153
+ : "0";
154
+ clauses.push(`(${changeOrigin} = ? OR (${changeOrigin} IS NULL AND ${fallback}))`);
155
+ values.push(input.origin);
156
+ }
157
+ if (input.kind === "prd" || input.kind === "spec") {
158
+ clauses.push("canonical.entity_kind = ?");
159
+ values.push(input.kind);
160
+ } else if (
161
+ input.kind === "all-tasks" ||
162
+ input.kind === "task" ||
163
+ input.kind === "standalone-task"
164
+ ) {
165
+ clauses.push("canonical.entity_kind = 'task'");
166
+ if (input.kind !== "all-tasks")
167
+ clauses.push(input.kind === "task" ? linkedTask : `NOT ${linkedTask}`);
168
+ }
169
+ const source =
170
+ "operations_work_kanban work JOIN canonical_work_items canonical ON canonical.entity_kind=work.entity_kind AND canonical.id=work.id";
171
+ return {
172
+ source,
173
+ where: clauses.length === 0 ? "" : ` WHERE ${clauses.join(" AND ")}`,
174
+ values,
175
+ select: `SELECT work.*, canonical.stage, canonical.spec_id,
176
+ ${changeId} AS change_id, ${changeOrigin} AS origin,
177
+ CASE WHEN canonical.entity_kind='task' THEN CASE WHEN ${linkedTask} THEN 'task' ELSE 'standalone-task' END ELSE canonical.entity_kind END AS kind`,
178
+ };
179
+ }
180
+
181
+ function hasOperationsWorkProjection(database: DatabaseSync): boolean {
182
+ return (
183
+ database
184
+ .prepare(
185
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='operations_work_projection' LIMIT 1",
186
+ )
187
+ .get() !== undefined
188
+ );
189
+ }
190
+
191
+ function operationsWorkListProjectionSql(input: OperationsWorkListQuery) {
192
+ const clauses: string[] = [];
193
+ const values: Array<string | number> = [];
194
+ if (input.from) {
195
+ clauses.push("occurred_day_sp >= ?");
196
+ values.push(input.from.slice(0, 10));
197
+ }
198
+ if (input.to) {
199
+ clauses.push("occurred_day_sp <= ?");
200
+ values.push(input.to.slice(0, 10));
201
+ }
202
+ if (input.query) {
203
+ clauses.push("(id LIKE ? OR title LIKE ?)");
204
+ values.push(`%${input.query}%`, `%${input.query}%`);
205
+ }
206
+ if (input.status) {
207
+ clauses.push("stage = ?");
208
+ values.push(input.status);
209
+ }
210
+ if (input.risk) {
211
+ clauses.push("risk = ?");
212
+ values.push(input.risk);
213
+ }
214
+ if (input.origin) {
215
+ clauses.push("entity_kind = 'task'");
216
+ const fallback =
217
+ input.origin === "spec"
218
+ ? "spec_id IS NOT NULL"
219
+ : input.origin === "task"
220
+ ? "spec_id IS NULL"
221
+ : "0";
222
+ clauses.push(`(origin = ? OR (origin IS NULL AND ${fallback}))`);
223
+ values.push(input.origin);
224
+ }
225
+ if (input.kind === "prd" || input.kind === "spec") {
226
+ clauses.push("entity_kind = ?");
227
+ values.push(input.kind);
228
+ } else if (input.kind === "all-tasks") {
229
+ clauses.push("entity_kind = 'task'");
230
+ } else if (input.kind === "task" || input.kind === "standalone-task") {
231
+ clauses.push("kind = ?");
232
+ values.push(input.kind);
233
+ }
234
+ return { where: clauses.length === 0 ? "" : ` WHERE ${clauses.join(" AND ")}`, values };
235
+ }
236
+
237
+ export function operationsWorkListSqlPage(
238
+ database: DatabaseSync,
239
+ input: OperationsWorkListQuery & { offset?: number; limit?: number },
240
+ ): OperationsWorkListPayloadPage {
241
+ const limit = Math.min(200, Math.max(1, input.limit ?? 50));
242
+ const offset = Math.max(0, input.offset ?? 0);
243
+ if (hasOperationsWorkProjection(database)) {
244
+ const page = operationsWorkListProjectionSql(input);
245
+ const total_count = Number(
246
+ (
247
+ database
248
+ .prepare(`SELECT COUNT(*) AS count FROM operations_work_projection${page.where}`)
249
+ .get(...page.values) as { count: number }
250
+ ).count,
251
+ );
252
+ const rows = database
253
+ .prepare(
254
+ `SELECT * FROM operations_work_projection${page.where} ORDER BY occurred_at DESC, id DESC LIMIT ? OFFSET ?`,
255
+ )
256
+ .all(...page.values, limit, offset) as WorkRow[];
257
+ return { offset, limit, total_count, has_more: offset + rows.length < total_count, rows };
258
+ }
259
+ const page = operationsWorkListSql(input);
260
+ const total_count = Number(
261
+ (
262
+ database
263
+ .prepare(`SELECT COUNT(*) AS count FROM ${page.source}${page.where}`)
264
+ .get(...page.values) as {
265
+ count: number;
266
+ }
267
+ ).count,
268
+ );
269
+ const rows = database
270
+ .prepare(
271
+ `${page.select} FROM ${page.source}${page.where} ORDER BY work.occurred_at DESC, work.id DESC LIMIT ? OFFSET ?`,
272
+ )
273
+ .all(...page.values, limit, offset) as WorkRow[];
274
+ return { offset, limit, total_count, has_more: offset + rows.length < total_count, rows };
275
+ }
276
+
277
+ function workEntityKind(row: WorkRow): "task" | "spec" | "prd" | null {
278
+ const kind = row.entity_kind ?? row.kind;
279
+ if (kind === "task" || kind === "standalone-task") return "task";
280
+ if (kind === "spec" || kind === "prd") return kind;
281
+ return null;
282
+ }
283
+
284
+ /** Adds factual developer identities for only the Work List page IDs. */
285
+ export function attachOperationsWorkListDevelopers(
286
+ database: DatabaseSync,
287
+ rows: readonly WorkRow[],
288
+ input: Pick<OperationsWorkListQuery, "from" | "to">,
289
+ ): WorkRow[] {
290
+ const pageItems = rows.flatMap((row) => {
291
+ const entityKind = workEntityKind(row);
292
+ const id = typeof row.id === "string" && row.id.length > 0 ? row.id : null;
293
+ return entityKind && id ? [{ entityKind, id }] : [];
294
+ });
295
+ if (pageItems.length === 0) return rows.map((row) => ({ ...row, developers: [] }));
296
+
297
+ const values = pageItems.flatMap(({ entityKind, id }) => [entityKind, id]);
298
+ const from = input.from?.slice(0, 10) ?? null;
299
+ const to = input.to?.slice(0, 10) ?? null;
300
+ const identities = database
301
+ .prepare(
302
+ `WITH page_items(entity_kind, id) AS (VALUES ${pageItems.map(() => "(?, ?)").join(", ")}),
303
+ identified_developers AS (
304
+ SELECT page_items.entity_kind, page_items.id,
305
+ CAST(activity.developer_account_id AS TEXT) AS account_id,
306
+ MAX(NULLIF(TRIM(activity.developer_login), '')) AS login_snapshot
307
+ FROM page_items
308
+ JOIN zaip_aios_activities activity
309
+ ON activity.payload_type IS NULL
310
+ AND activity.developer_account_id IS NOT NULL
311
+ AND TRIM(activity.developer_account_id) <> ''
312
+ AND (
313
+ (page_items.entity_kind='task' AND activity.task_id=page_items.id)
314
+ OR (page_items.entity_kind='spec' AND activity.spec_id=page_items.id)
315
+ OR (page_items.entity_kind='prd' AND activity.prd_id=page_items.id)
316
+ )
317
+ WHERE (? IS NULL OR activity.occurred_day_sp >= ?)
318
+ AND (? IS NULL OR activity.occurred_day_sp <= ?)
319
+ GROUP BY page_items.entity_kind, page_items.id, CAST(activity.developer_account_id AS TEXT)
320
+ )
321
+ SELECT entity_kind, id, account_id, login_snapshot
322
+ FROM identified_developers
323
+ ORDER BY entity_kind, id, account_id COLLATE BINARY`,
324
+ )
325
+ .all(...values, from, from, to, to) as Array<{
326
+ entity_kind: "task" | "spec" | "prd";
327
+ id: string;
328
+ account_id: string;
329
+ login_snapshot: string | null;
330
+ }>;
331
+ const developersByItem = new Map<string, OperationsWorkListDeveloper[]>();
332
+ for (const identity of identities) {
333
+ const key = `${identity.entity_kind}:${identity.id}`;
334
+ const developers = developersByItem.get(key) ?? [];
335
+ developers.push({ account_id: identity.account_id, login_snapshot: identity.login_snapshot });
336
+ developersByItem.set(key, developers);
337
+ }
338
+ return rows.map((row) => {
339
+ const entityKind = workEntityKind(row);
340
+ const id = typeof row.id === "string" ? row.id : "";
341
+ return {
342
+ ...row,
343
+ developers: entityKind ? (developersByItem.get(`${entityKind}:${id}`) ?? []) : [],
344
+ };
345
+ });
346
+ }
347
+
348
+ /** One physical expansion used only while materializing first-fold payloads. */
349
+ export function operationsWorkListSqlRows(database: DatabaseSync): WorkRow[] {
350
+ if (hasOperationsWorkProjection(database))
351
+ return database
352
+ .prepare("SELECT * FROM operations_work_projection ORDER BY occurred_at DESC, id DESC")
353
+ .all() as WorkRow[];
354
+ const page = operationsWorkListSql({ kind: "all" });
355
+ return database
356
+ .prepare(
357
+ `${page.select} FROM ${page.source}${page.where} ORDER BY work.occurred_at DESC, work.id DESC`,
358
+ )
359
+ .all(...page.values) as WorkRow[];
360
+ }
361
+
362
+ export function operationsWorkRowsInRange(
363
+ rows: readonly WorkRow[],
364
+ from: string,
365
+ to: string,
366
+ ): WorkRow[] {
367
+ return rows.filter((row) => {
368
+ const day = typeof row.occurred_day_sp === "string" ? row.occurred_day_sp : "";
369
+ return day >= from && day <= to;
370
+ });
371
+ }
@@ -0,0 +1,23 @@
1
+ import { resolve } from "node:path";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ import { expect } from "vitest";
4
+
5
+ export function expectPersistedFirstFoldCaches(root: string, allTime: unknown, board: unknown) {
6
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
7
+ readOnly: true,
8
+ });
9
+ try {
10
+ const read = (payloadType: string) =>
11
+ database
12
+ .prepare("SELECT payload_json FROM zaip_aios_activities WHERE payload_type=?")
13
+ .get(payloadType) as { payload_json: string } | undefined;
14
+ const persistedAllTime = read("first_fold_all_time_cache");
15
+ const persistedBoard = read("first_fold_all_time_board_cache");
16
+ expect(persistedAllTime).toBeDefined();
17
+ expect(persistedBoard).toBeDefined();
18
+ expect(JSON.parse(persistedAllTime!.payload_json)).toEqual(allTime);
19
+ expect(JSON.parse(persistedBoard!.payload_json)).toEqual(board);
20
+ } finally {
21
+ database.close();
22
+ }
23
+ }