@zaip-ai/cli 1.0.7 → 1.0.8

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 (410) hide show
  1. package/README.md +6 -4
  2. package/dist/services/adapters.d.ts +1 -1
  3. package/dist/services/adapters.d.ts.map +1 -1
  4. package/dist/services/adapters.js +15 -5
  5. package/dist/services/adapters.js.map +1 -1
  6. package/dist/services/adapters.test.js +1 -1
  7. package/dist/services/agent-adapters.d.ts +9 -0
  8. package/dist/services/agent-adapters.d.ts.map +1 -1
  9. package/dist/services/agent-adapters.js +38 -3
  10. package/dist/services/agent-adapters.js.map +1 -1
  11. package/dist/services/agent-adapters.test.js +8 -1
  12. package/dist/services/agent-adapters.test.js.map +1 -1
  13. package/dist/services/codex-config-template.d.ts +2 -1
  14. package/dist/services/codex-config-template.d.ts.map +1 -1
  15. package/dist/services/codex-config-template.js +13 -2
  16. package/dist/services/codex-config-template.js.map +1 -1
  17. package/dist/services/codex-rules.d.ts +1 -1
  18. package/dist/services/codex-rules.d.ts.map +1 -1
  19. package/dist/services/codex-rules.js +31 -7
  20. package/dist/services/codex-rules.js.map +1 -1
  21. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  22. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  23. package/dist/services/codex-subagent-hook-template.js +4 -1
  24. package/dist/services/codex-subagent-hook-template.js.map +1 -1
  25. package/dist/services/doctor.d.ts.map +1 -1
  26. package/dist/services/doctor.js +3 -1
  27. package/dist/services/doctor.js.map +1 -1
  28. package/dist/services/global-codex-config.d.ts +6 -3
  29. package/dist/services/global-codex-config.d.ts.map +1 -1
  30. package/dist/services/global-codex-config.js +82 -13
  31. package/dist/services/global-codex-config.js.map +1 -1
  32. package/dist/services/global-codex-config.test.js +57 -10
  33. package/dist/services/global-codex-config.test.js.map +1 -1
  34. package/dist/services/install-agents-template.d.ts.map +1 -1
  35. package/dist/services/install-agents-template.js +15 -2
  36. package/dist/services/install-agents-template.js.map +1 -1
  37. package/dist/services/logger.js +1 -1
  38. package/dist/services/logger.js.map +1 -1
  39. package/dist/services/logger.test.js +3 -3
  40. package/dist/services/logger.test.js.map +1 -1
  41. package/dist/services/runtime-template.test.js +21 -5
  42. package/dist/services/runtime-template.test.js.map +1 -1
  43. package/dist/services/template-manifest.d.ts.map +1 -1
  44. package/dist/services/template-manifest.js +2 -0
  45. package/dist/services/template-manifest.js.map +1 -1
  46. package/package.json +1 -1
  47. package/templates/.agents/config/agents/catalog.yaml +310 -10
  48. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +30 -0
  49. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +5 -5
  50. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -1
  51. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +30 -0
  52. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +30 -0
  53. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +30 -0
  54. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +8 -8
  55. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +5 -3
  56. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +30 -0
  57. package/templates/.agents/config/project.yaml +1 -1
  58. package/templates/.agents/dashboard/.zaip-managed.json +659 -163
  59. package/templates/.agents/dashboard/DATA-DICTIONARY.md +2227 -0
  60. package/templates/.agents/dashboard/README.md +48 -97
  61. package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-Z60df2r-.js +1 -0
  62. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-Cv-JcoxP.js → DashboardHelpDialog-Cs6dzDmW.js} +294 -109
  63. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-Coc6pA-1.js +1 -0
  64. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-QE1k9yZB.css +1 -0
  65. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-_Pf5DA_A.js +3 -0
  66. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BVlz3DLZ.js +1 -0
  67. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-CaKwQP43.css +1 -0
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +1 -0
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cf9ctPVt.js +68 -0
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-C98LGC-Q.js → dashboard-localization-CLJ0lT9v.js} +1 -1
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/icons-vendor-DFBuc_qp.js +1 -0
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8A4DcNXM.js +2 -0
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +1 -0
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/react-vendor-CCQOHdVI.js +9 -0
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-D9-fqq9M.js +1 -0
  76. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DFY7ZDlu.css +1 -0
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-pC2_0J8x.js +1 -0
  78. package/templates/.agents/dashboard/apps/frontend/dist/index.html +9 -2
  79. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  80. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +952 -595
  81. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +48 -0
  82. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingPanels.tsx +2 -0
  83. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +247 -0
  84. package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +95 -0
  85. package/templates/.agents/dashboard/apps/frontend/src/AppSetupRoutes.tsx +87 -0
  86. package/templates/.agents/dashboard/apps/frontend/src/CostCalculationGuidePage.tsx +641 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +159 -0
  88. package/templates/.agents/dashboard/apps/frontend/src/DashboardCapabilityNav.tsx +13 -13
  89. package/templates/.agents/dashboard/apps/frontend/src/DashboardGlobalSettings.tsx +56 -0
  90. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +257 -313
  91. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +149 -0
  92. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +35 -0
  93. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +111 -12
  94. package/templates/.agents/dashboard/apps/frontend/src/app-preferences.ts +15 -0
  95. package/templates/.agents/dashboard/apps/frontend/src/app-session-token.ts +20 -0
  96. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.css +267 -0
  97. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.test.ts +69 -0
  98. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +70 -0
  99. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +111 -0
  100. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +432 -13
  101. package/templates/.agents/dashboard/apps/frontend/src/dashboard-localization.ts +1 -1
  102. package/templates/.agents/dashboard/apps/frontend/src/main.tsx +1 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.test.ts +55 -0
  104. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.ts +36 -0
  105. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.test.ts +126 -0
  106. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.ts +181 -0
  107. package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +138 -0
  108. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +105 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +6 -3
  110. package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +9 -5
  111. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +24 -0
  112. package/templates/.agents/dashboard/apps/frontend/src/period-transition-overlay.css +44 -0
  113. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +121 -62
  114. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +471 -2
  115. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +84 -17
  116. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +44 -0
  117. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +196 -39
  118. package/templates/.agents/dashboard/apps/frontend/src/snapshot-notice.ts +15 -0
  119. package/templates/.agents/dashboard/apps/frontend/src/sqlite-cost-adapters.ts +26 -0
  120. package/templates/.agents/dashboard/apps/frontend/src/styles.css +14 -2
  121. package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +38 -2
  122. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +187 -0
  123. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +190 -0
  124. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +121 -0
  125. package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +398 -0
  126. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +141 -0
  127. package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +45 -0
  128. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +46 -0
  129. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +37 -0
  130. package/templates/.agents/dashboard/apps/frontend/vite.config.ts +55 -5
  131. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  132. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +47 -6
  133. package/templates/.agents/dashboard/modules/cost-monitor/src/duration-estimate.ts +13 -0
  134. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +64 -0
  135. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +275 -0
  136. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +100 -15
  137. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +131 -40
  138. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +137 -19
  139. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.test.ts +2 -0
  140. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +13 -10
  141. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +303 -0
  142. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +226 -6
  143. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +479 -402
  144. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostSummary.tsx +5 -13
  145. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +312 -72
  146. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +373 -321
  147. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.test.ts +17 -0
  148. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.tsx +25 -6
  149. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +0 -2
  150. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-helpers.ts +17 -0
  151. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-responsive.css +6 -3
  152. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-row-filters.ts +123 -0
  153. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-summary.ts +8 -3
  154. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +32 -0
  155. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +52 -14
  156. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-presentation.ts +121 -0
  157. package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +3 -0
  158. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.test.ts +68 -0
  159. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +32 -5
  160. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +108 -0
  161. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +12 -0
  162. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +60 -0
  163. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +106 -0
  164. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.test.ts +2 -2
  165. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +5 -3
  166. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-pricing-fallback.test.ts +53 -0
  167. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.basics.test.ts +34 -0
  168. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +168 -137
  169. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +188 -29
  170. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +956 -0
  171. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +775 -0
  172. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +95 -0
  173. package/templates/.agents/dashboard/modules/cost-monitor/src/server/index.ts +1 -0
  174. package/templates/.agents/dashboard/modules/cost-monitor/src/server/refactor-map-xr9l.json +61 -0
  175. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.test.ts +41 -0
  176. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.ts +55 -11
  177. package/templates/.agents/dashboard/modules/operations/package.json +2 -1
  178. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +182 -0
  179. package/templates/.agents/dashboard/modules/operations/src/duration-population.test.ts +63 -1
  180. package/templates/.agents/dashboard/modules/operations/src/duration-population.ts +60 -5
  181. package/templates/.agents/dashboard/modules/operations/src/react/EntityDetailsDialog.tsx +14 -3
  182. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.test.ts +44 -1
  183. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +325 -18
  184. package/templates/.agents/dashboard/modules/operations/src/react/MacroPlanningDialog.tsx +1 -0
  185. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.test.ts +9 -5
  186. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.tsx +2 -2
  187. package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +168 -0
  188. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +95 -6
  189. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +365 -14
  190. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +532 -131
  191. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +314 -358
  192. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +68 -0
  193. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +169 -0
  194. package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +51 -8
  195. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.test.tsx +32 -0
  196. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.tsx +62 -6
  197. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.test.ts +55 -0
  198. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.tsx +27 -19
  199. package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +99 -42
  200. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.priority.test.ts +19 -0
  201. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +255 -34
  202. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +163 -8
  203. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +101 -68
  204. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.test.ts +72 -1
  205. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +78 -16
  206. package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +6 -4
  207. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +50 -1
  208. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +143 -9
  209. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +29 -4
  210. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog-content.css +49 -0
  211. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog.css +93 -50
  212. package/templates/.agents/dashboard/modules/operations/src/react/manual-work-priority.test.ts +82 -0
  213. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +29 -2
  214. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +22 -1
  215. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +77 -4
  216. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +65 -5
  217. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +54 -0
  218. package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +19 -0
  219. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +149 -12
  220. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +27 -0
  221. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +136 -0
  222. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.test.ts +160 -0
  223. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.ts +47 -0
  224. package/templates/.agents/dashboard/modules/operations/src/react/operations-query.ts +10 -0
  225. package/templates/.agents/dashboard/modules/operations/src/react/operations-responsive.css +3 -0
  226. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-labels.ts +28 -0
  227. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +24 -7
  228. package/templates/.agents/dashboard/modules/operations/src/react/operations.css +42 -0
  229. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.test.ts +54 -0
  230. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.ts +32 -0
  231. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +165 -6
  232. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +148 -6
  233. package/templates/.agents/dashboard/modules/operations/src/react/popup-localization.test.ts +1 -1
  234. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css +132 -0
  235. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css.test.ts +13 -0
  236. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.header.test.ts +16 -0
  237. package/templates/.agents/dashboard/modules/operations/src/react/use-infinite-column.ts +11 -5
  238. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +5 -3
  239. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +4 -83
  240. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.test.ts +291 -15
  241. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.ts +182 -472
  242. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.test.ts +40 -0
  243. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.ts +19 -6
  244. package/templates/.agents/dashboard/modules/operations/src/server/canonical-run-projection.ts +31 -6
  245. package/templates/.agents/dashboard/modules/operations/src/server/daily-duration-projection.ts +220 -0
  246. package/templates/.agents/dashboard/modules/operations/src/server/execution-duration-projection.ts +28 -0
  247. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +27 -2
  248. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +22 -13
  249. package/templates/.agents/dashboard/modules/operations/src/server/filter.test.ts +3 -3
  250. package/templates/.agents/dashboard/modules/operations/src/server/filter.ts +7 -2
  251. package/templates/.agents/dashboard/modules/operations/src/server/index.ts +3 -0
  252. package/templates/.agents/dashboard/modules/operations/src/server/macro.ts +3 -12
  253. package/templates/.agents/dashboard/modules/operations/src/server/manifest-usage.ts +65 -0
  254. package/templates/.agents/dashboard/modules/operations/src/server/operations-projection-aggregates.ts +297 -0
  255. package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +324 -0
  256. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.test.ts +505 -0
  257. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.ts +609 -0
  258. package/templates/.agents/dashboard/modules/operations/src/server/runs.ts +18 -19
  259. package/templates/.agents/dashboard/modules/operations/src/work-board-model.ts +76 -0
  260. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  261. package/templates/.agents/dashboard/modules/traceability/src/server/build-task-trace.ts +3 -1
  262. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  263. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  264. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  265. package/templates/.agents/dashboard/packages/contracts/src/index.ts +1 -0
  266. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  267. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +1756 -0
  268. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1608 -0
  269. package/templates/.agents/dashboard/packages/local-runtime/package.json +8 -3
  270. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +12 -0
  271. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +83 -0
  272. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +28 -0
  273. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +262 -0
  274. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +41 -0
  275. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +105 -0
  276. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1643 -107
  277. package/templates/.agents/dashboard/packages/local-runtime/src/agent-adapter-sync.ts +30 -0
  278. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.test.ts +3 -2
  279. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.ts +5 -0
  280. package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +72 -33
  281. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +335 -4
  282. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +392 -1
  283. package/templates/.agents/dashboard/packages/local-runtime/src/runtime.mjs +5 -5
  284. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +71 -0
  285. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +179 -0
  286. package/templates/.agents/dashboard/packages/local-runtime/src/server-cross-layer-parity.test.ts +245 -0
  287. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models-route.ts +64 -0
  288. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.test.ts +204 -0
  289. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.ts +194 -0
  290. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.test.ts +163 -0
  291. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.ts +354 -0
  292. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.test.ts +74 -0
  293. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.ts +113 -0
  294. package/templates/.agents/dashboard/packages/local-runtime/src/server-overview.test.ts +68 -0
  295. package/templates/.agents/dashboard/packages/local-runtime/src/server-period.ts +47 -0
  296. package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +483 -0
  297. package/templates/.agents/dashboard/packages/local-runtime/src/server-settings.test.ts +5 -2
  298. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +163 -84
  299. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +145 -0
  300. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +551 -266
  301. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +1145 -308
  302. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.test.ts +53 -0
  303. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +708 -0
  304. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +2232 -0
  305. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +6151 -0
  306. package/templates/.agents/dashboard/packages/local-runtime/src/work-priority-contract.test.ts +33 -0
  307. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-history.mjs +41 -0
  308. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-processes.mjs +210 -0
  309. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +437 -190
  310. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +299 -24
  311. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  312. package/templates/.agents/dashboard/packages/windows-launcher/LauncherVerificationBehaviorTests.cs +206 -0
  313. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +95 -8
  314. package/templates/.agents/dashboard/packages/windows-launcher/build-dashboard-launcher.ps1 +2 -2
  315. package/templates/.agents/dashboard/packages/windows-launcher/run-launcher-verification-tests.ps1 +22 -0
  316. package/templates/.agents/dashboard/read-model.dbml +2201 -0
  317. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  318. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +1 -1
  319. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +214 -41
  320. package/templates/.agents/dashboard/work-priority.json +34 -0
  321. package/templates/.agents/dashboard/zaip-dashboard-linux.sh +45 -0
  322. package/templates/.agents/dashboard/zaip-dashboard-mac.command +3 -0
  323. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  324. package/templates/.agents/design/.zaip-managed.json +3 -3
  325. package/templates/.agents/design/apps/catalog/package.json +1 -1
  326. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  327. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  328. package/templates/.agents/requirements/comparative-audit.md +1 -1
  329. package/templates/.agents/requirements/context-engine.md +1 -1
  330. package/templates/.agents/requirements/cost-observability.md +1 -1
  331. package/templates/.agents/requirements/dashboard-launch.md +24 -0
  332. package/templates/.agents/requirements/data-export.md +1 -1
  333. package/templates/.agents/requirements/design-system.md +1 -1
  334. package/templates/.agents/requirements/execution-engine.md +1 -1
  335. package/templates/.agents/requirements/kanban-dashboard.md +4 -3
  336. package/templates/.agents/requirements/model-orchestration.md +1 -1
  337. package/templates/.agents/requirements/public-distribution.md +9 -6
  338. package/templates/.agents/requirements/software-architecture.md +1 -1
  339. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  340. package/templates/.agents/requirements/work-lifecycle.md +9 -3
  341. package/templates/.agents/requirements/zaip-strategic-95.md +4 -4
  342. package/templates/.agents/rules/autonomy-decision.md +15 -0
  343. package/templates/.agents/rules/core.md +1 -1
  344. package/templates/.agents/rules/run-lifecycle-closure.md +1 -1
  345. package/templates/.agents/rules/specialist-routing.md +19 -0
  346. package/templates/.agents/rules/workflow.md +1 -1
  347. package/templates/.agents/runtime/VERSION +1 -1
  348. package/templates/.agents/runtime/bin/zaip.mjs +114 -64
  349. package/templates/.agents/runtime/checksums.sha256 +6 -4
  350. package/templates/.agents/runtime/manifest.json +3 -1
  351. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -0
  352. package/templates/.agents/runtime/provenance.json +11 -0
  353. package/templates/.agents/runtime/sbom.cdx.json +1929 -39
  354. package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
  355. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +15 -0
  356. package/templates/.agents/skills/zaip-skill-brand-governance/agents/openai.yaml +4 -0
  357. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +30 -0
  358. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +25 -0
  359. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/agents/openai.yaml +4 -0
  360. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/SKILL.md +18 -0
  361. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/agents/openai.yaml +6 -0
  362. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.mjs +71 -0
  363. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.test.mjs +60 -0
  364. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/scheduled-ready-queue-contract.test.mjs +111 -0
  365. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +2 -2
  366. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +3 -1
  367. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +12 -0
  368. package/templates/.agents/skills/zaip-skill-general-operations/agents/openai.yaml +4 -0
  369. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +9 -7
  370. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  371. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +11 -38
  372. package/templates/.agents/skills/zaip-skill-governance-compliance/SKILL.md +12 -0
  373. package/templates/.agents/skills/zaip-skill-governance-compliance/agents/openai.yaml +4 -0
  374. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +15 -0
  375. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/agents/openai.yaml +4 -0
  376. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/references/growth-revops-framework.md +25 -0
  377. package/templates/.agents/skills/zaip-skill-instalar-agendamento/SKILL.md +17 -0
  378. package/templates/.agents/skills/zaip-skill-instalar-agendamento/agents/openai.yaml +6 -0
  379. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +3 -3
  380. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +76 -27
  381. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +128 -24
  382. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +122 -4
  383. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +66 -19
  384. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +193 -7
  385. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +14 -0
  386. package/templates/.agents/skills/zaip-skill-sales-methodology/agents/openai.yaml +4 -0
  387. package/templates/.agents/skills/zaip-skill-sales-methodology/references/sales-process-framework.md +77 -0
  388. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -0
  389. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +96 -0
  390. package/templates/.agents/skills/zaip-skill-sqlite-read-model/agents/openai.yaml +4 -0
  391. package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +18 -0
  392. package/templates/.agents/zaip-aios/INDEX.md +1 -0
  393. package/templates/.agents/zaip-aios/README.md +37 -8
  394. package/templates/.agents/zaip-aios/VERSION +1 -1
  395. package/templates/.agents/zaip-aios/guides/09-agentes-e-squads.md +43 -6
  396. package/templates/.agents/zaip-aios/guides/10-seguranca.md +21 -7
  397. package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +32 -0
  398. package/templates/template-manifest.json +945 -214
  399. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-CLYIi7Vo.js +0 -1
  400. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-DTDNVQGX.css +0 -1
  401. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CzEggcRb.js +0 -1
  402. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-05YKn6Z4.js +0 -3
  403. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-YO3yqtgV.css +0 -1
  404. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8AzCnl3a.js +0 -77
  405. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DdBYQ_8D.css +0 -1
  406. package/templates/.agents/dashboard/apps/frontend/src/AgentCatalogSettingsPage.tsx +0 -617
  407. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings-page.css +0 -660
  408. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings.test.ts +0 -249
  409. package/templates/.agents/dashboard/dashboard.config.json +0 -4
  410. package/templates/.agents/dashboard/zaip-dashboard.exe +0 -0
@@ -1,49 +1,125 @@
1
- import { execFile } from "node:child_process";
2
1
  import { createHash, randomBytes } from "node:crypto";
3
2
  import { readFile } from "node:fs/promises";
4
3
  import { resolve } from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
- import { promisify } from "node:util";
7
5
  import { serve } from "@hono/node-server";
8
6
  import { serveStatic } from "@hono/node-server/serve-static";
9
7
  import { loadWorkspaceLayout, WorkspaceFileSystem, type WorkspaceLayout } from "@zaip-ai/core";
10
8
  import { fxRateSchema, PricingRepository, pricingCatalogSchema } from "@zaip-ai/costs";
11
- import { buildCostMonitorProjection } from "@zaip-ai/dashboard-cost-monitor/server";
9
+ import {
10
+ buildCostSummaryProjection,
11
+ type CostSummarySource,
12
+ } from "@zaip-ai/dashboard-cost-monitor/server";
13
+ import type { OperationsProjection } from "@zaip-ai/dashboard-operations/contracts";
12
14
  import {
13
15
  buildOperationsProjection,
14
- projectExecutionDurations,
16
+ buildQuickOperationsProjection,
15
17
  readPrdDetail,
16
18
  readSpecDetail,
17
19
  readTaskDetail,
18
- recalculateOperationsRunSummary,
19
20
  } from "@zaip-ai/dashboard-operations/server";
20
- import { buildTaskTrace } from "@zaip-ai/dashboard-traceability/server";
21
+ import {
22
+ buildTaskTrace,
23
+ buildTraceStageRecords,
24
+ classifyTraceDocument,
25
+ timestampsFromContent,
26
+ } from "@zaip-ai/dashboard-traceability/server";
27
+ import type {
28
+ TaskTrace,
29
+ TraceDocument,
30
+ } from "../../../modules/traceability/src/contracts.js";
31
+ import { type CompactRunMetric, LifecycleManifestStore } from "@zaip-ai/lifecycle-manifests";
21
32
  import { type Context, Hono } from "hono";
22
33
  import { getCookie, setCookie } from "hono/cookie";
23
34
  import type { ContentfulStatusCode } from "hono/utils/http-status";
35
+ import { runAgentAdapterSync } from "./agent-adapter-sync.js";
24
36
  import {
25
37
  type AgentAdapterSyncResult,
26
38
  AgentCatalogUpdateError,
27
39
  persistAgentCatalogUpdate,
28
40
  readAgentCatalogSettings,
29
41
  } from "./agent-catalog-settings.js";
30
- import { mergeManifestRuns } from "./manifest-runs.js";
31
- import { persistProjectSettings, readProjectSettings } from "./project-settings.js";
42
+ import { persistProjectSettings, readProjectOverview, readProjectSettings } from "./project-settings.js";
43
+ import {
44
+ type BoardClassification,
45
+ boardFilters,
46
+ boardPage,
47
+ boardStage,
48
+ classificationPage,
49
+ } from "./server-board.js";
50
+ import { registerDailyReadModelRoute } from "./server-daily-read-models-route.js";
51
+ import {
52
+ changeEntityPageRows,
53
+ decisionEntityPageRows,
54
+ filterLifecyclePageRows,
55
+ operationsPageFilters,
56
+ } from "./server-operations-pages.js";
57
+ import { registerOperationsRunsRoute } from "./server-operations-runs-route.js";
58
+ import { projectionPeriodFilter } from "./server-period.js";
32
59
  import {
33
- configuredAgentNames,
34
- invocationSummariesForPeriod,
35
- queryValues,
36
- selectedCurrency,
37
- } from "./server-summary-helpers.js";
60
+ buildCanonicalOperationsProjection,
61
+ buildCurrentCostProjection,
62
+ buildManifestCostRunPage,
63
+ buildManifestFirstCostProjection,
64
+ filterCostRunRows,
65
+ isUnfilteredCostRequest,
66
+ knownWorkIds,
67
+ normalizeWorkPriority,
68
+ pageCostRunRows,
69
+ readCurrentFxRate,
70
+ readWorkPriority,
71
+ } from "./server-projections.js";
72
+ import { queryValues, selectedCurrency } from "./server-summary-helpers.js";
73
+ import { dashboardIdentity, dashboardWorkspaceKey, reconcileOpenWork } from "./server-workspace.js";
74
+ import {
75
+ rebuildSqliteReadModel,
76
+ SqliteReadModelFileSystem,
77
+ sqliteViewMetadata,
78
+ } from "./sqlite-read-model.js";
38
79
 
39
80
  export { manifestRun, mergeManifestRuns } from "./manifest-runs.js";
81
+ export { projectionPeriodFilter } from "./server-period.js";
82
+ export {
83
+ dashboardWorkspaceKey,
84
+ githubRepositoryFromRemote,
85
+ type ReconciliationItem,
86
+ type ReconciliationResult,
87
+ reconciliationResult,
88
+ } from "./server-workspace.js";
89
+ export {
90
+ rebuildSqliteReadModel,
91
+ SqliteReadModelFileSystem,
92
+ validateSqliteReadModelSchema,
93
+ } from "./sqlite-read-model.js";
94
+
95
+ type AllTimeTopTaskFact = {
96
+ task_id: string;
97
+ total_duration_ms: number | null;
98
+ total_tokens: number | null;
99
+ total_cost_usd: number | null;
100
+ total_cost_brl: number | null;
101
+ total_run_count: number;
102
+ };
103
+
104
+ export function projectAllTimeTopTasks(
105
+ tasks: readonly AllTimeTopTaskFact[],
106
+ ): NonNullable<NonNullable<OperationsProjection["summary"]["overview_metrics"]>["top_tasks"]> {
107
+ return tasks.map((task) => ({
108
+ task_id: task.task_id,
109
+ duration_ms: task.total_duration_ms,
110
+ total_tokens: task.total_tokens,
111
+ total_cost_usd: task.total_cost_usd,
112
+ cost_brl: task.total_cost_brl,
113
+ run_count: task.total_run_count,
114
+ }));
115
+ }
40
116
 
41
117
  const API_PREFIX = "/api/v2";
42
118
  const HOST = "127.0.0.1";
43
- const RUNTIME_KIND = "zaip-dashboard-local-runtime";
44
- const SESSION_COOKIE = "zaip-dashboard-session";
45
- const DASHBOARD_CONFIG_PATH = ".agents/dashboard/dashboard.config.json";
46
- const execFileAsync = promisify(execFile);
119
+ const RUNTIME_KIND = "zaip-dashboard-sqlite-runtime";
120
+ const SESSION_COOKIE = "zaip-dashboard-sqlite-session";
121
+ const WORK_PRIORITY_PATH = ".agents/dashboard/work-priority.json";
122
+ const SNAPSHOT_CACHE_TTL_MS = 5_000;
47
123
  const agentPortraitFiles: Record<string, string> = {
48
124
  "agent-orchestrator": "1 - Dave - Product Owner.png",
49
125
  "agent-builder": "2 - Rafa - Tech Lead.png",
@@ -87,6 +163,13 @@ export function openWorkIds(items: readonly ActiveWorkItem[]): string[] {
87
163
 
88
164
  interface DashboardAppOptions {
89
165
  syncAgentAdapters?: () => Promise<AgentAdapterSyncResult>;
166
+ filesystem?: WorkspaceFileSystem;
167
+ canonicalFilesystem?: WorkspaceFileSystem;
168
+ /** Test seam for the disposable SQLite serving mode. */
169
+ snapshotReadOnly?: boolean;
170
+ reconcileOpenWork?: typeof reconcileOpenWork;
171
+ refreshReadModel?: () => Promise<void>;
172
+ snapshotState?: "current" | "updating";
90
173
  }
91
174
 
92
175
  class V2WorkspaceRequiredError extends Error {
@@ -126,164 +209,521 @@ function shouldBootstrapBrowserSession(context: Context): boolean {
126
209
  return context.req.header("sec-fetch-site") !== "cross-site";
127
210
  }
128
211
 
129
- async function dashboardIdentity(
212
+ async function requireV2Layout(filesystem: WorkspaceFileSystem): Promise<WorkspaceLayout> {
213
+ const layout = await loadWorkspaceLayout(filesystem);
214
+ if (layout.version === 1) {
215
+ throw new V2WorkspaceRequiredError("Dashboard API requires a modern ZAIP-AIOS workspace");
216
+ }
217
+ return layout;
218
+ }
219
+
220
+ function headOrJson(
221
+ context: Context,
222
+ payload: unknown,
223
+ status: ContentfulStatusCode = 200,
224
+ cacheable = false,
225
+ ): Response {
226
+ if (cacheable) {
227
+ const etag = `"${createHash("sha256").update(JSON.stringify(payload)).digest("base64url")}"`;
228
+ context.header("ETag", etag);
229
+ context.header("Cache-Control", "private, no-cache");
230
+ if (context.req.header("if-none-match") === etag) return context.body(null, 304);
231
+ }
232
+ if (context.req.method === "HEAD") return context.body(null, status);
233
+ return context.json(payload, status);
234
+ }
235
+
236
+ /**
237
+ * Keep the entity/macro details on the snapshot filesystem, while making the
238
+ * first-fold counters and token/cost facts come from SQL.
239
+ * Duration intentionally retains the shared hybrid legacy policy in the
240
+ * Operations module; its underlying daily rows are still SQLite-backed.
241
+ */
242
+ async function buildFirstFoldQuickProjection(
130
243
  filesystem: WorkspaceFileSystem,
131
- workspaceRoot: string,
132
- ): Promise<{ title: string; repository_name: string }> {
133
- let title = process.env.ZAIP_DASHBOARD_TITLE?.trim() || "Dashboard Zaip AIOS";
134
- let repositorySetting = "auto";
135
- try {
136
- if (await filesystem.exists(DASHBOARD_CONFIG_PATH)) {
137
- const config = JSON.parse(await filesystem.readText(DASHBOARD_CONFIG_PATH)) as {
138
- github_repository?: unknown;
139
- title?: unknown;
140
- };
141
- if (typeof config.title === "string" && config.title.trim()) title = config.title.trim();
142
- if (typeof config.github_repository === "string") {
143
- repositorySetting = config.github_repository.trim();
144
- }
244
+ layout: WorkspaceLayout,
245
+ filter: { from: string | null; to: string | null },
246
+ options: {
247
+ includeBoardTasks?: boolean;
248
+ includeExtended?: boolean;
249
+ includePeriods?: boolean;
250
+ includeTopTasks?: boolean;
251
+ } = {},
252
+ ): Promise<OperationsProjection> {
253
+ // The comparison runtime deliberately has no lifecycle daily aggregates in
254
+ // its snapshot. Its immutable filesystem contains only canonical records,
255
+ // while the SQL views supply the first-fold facts. Never enter the legacy
256
+ // quick projection here: it requires lifecycle daily read models and would
257
+ // make an otherwise valid SQLite dashboard fail on first paint.
258
+ const projection =
259
+ filesystem instanceof SqliteReadModelFileSystem
260
+ ? buildSqliteOperationsProjection(filesystem, layout, filter, options)
261
+ : await buildQuickOperationsProjection(filesystem, layout, filter, options);
262
+ if (!(filesystem instanceof SqliteReadModelFileSystem)) return projection;
263
+ const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
264
+ const aggregate =
265
+ filter.from === null && filter.to === null
266
+ ? filesystem.firstFoldViewportAllTime().aggregate
267
+ : cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter);
268
+ return {
269
+ ...projection,
270
+ summary: {
271
+ ...projection.summary,
272
+ run_count: aggregate.total_run_count,
273
+ overview_metrics: {
274
+ ...projection.summary.overview_metrics,
275
+ duration_ms: aggregate.duration_ms,
276
+ tokens: aggregate.total_tokens,
277
+ cost_brl: aggregate.cost_brl,
278
+ execution_day_count: aggregate.execution_day_count,
279
+ data_source: "run_records",
280
+ },
281
+ },
282
+ };
283
+ }
284
+
285
+ /**
286
+ * SQLite's files snapshot intentionally excludes derived lifecycle manifests.
287
+ * Build the Operations first fold from its canonical SQL views instead of
288
+ * treating a missing manifest as a broken workspace.
289
+ */
290
+ function buildSqliteOperationsProjection(
291
+ filesystem: SqliteReadModelFileSystem,
292
+ layout: WorkspaceLayout,
293
+ filter: { from: string | null; to: string | null },
294
+ options: {
295
+ includeBoardTasks?: boolean;
296
+ includeExtended?: boolean;
297
+ includePeriods?: boolean;
298
+ includeTopTasks?: boolean;
299
+ },
300
+ ): OperationsProjection {
301
+ const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
302
+ const fullAllTime =
303
+ filter.from === null && filter.to === null
304
+ ? options.includeExtended === true
305
+ ? filesystem.firstFoldAllTime()
306
+ : null
307
+ : null;
308
+ const allTime =
309
+ fullAllTime ??
310
+ (filter.from === null && filter.to === null ? filesystem.firstFoldViewportAllTime() : null);
311
+ const page = <T extends "task" | "spec" | "prd">(kind: T) =>
312
+ cachedMonthlyQuick?.pages[kind] ?? filesystem.summaryPage(kind, filter, 0, 10_000).rows;
313
+ const includeEntities = allTime === null || options.includeBoardTasks === true;
314
+ const tasks = includeEntities ? page("task") : [];
315
+ const specs = includeEntities ? page("spec") : [];
316
+ const prds = includeEntities ? page("prd") : [];
317
+ const aggregate = allTime?.aggregate ?? cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter);
318
+ const taskCount = allTime?.aggregate.task_count ?? tasks.length;
319
+ const allTimeTopTasks =
320
+ filter.from === null && filter.to === null &&
321
+ (fullAllTime !== null || options.includeTopTasks === true)
322
+ ? (fullAllTime?.top_tasks ?? filesystem.firstFoldTopTasksAllTime())
323
+ : null;
324
+ const topTasks =
325
+ allTimeTopTasks === null
326
+ ? (cachedMonthlyQuick?.top_tasks ?? filesystem.topTasks(filter)).map((task) => ({
327
+ task_id: task.task_id,
328
+ duration_ms: task.duration_ms,
329
+ total_tokens: task.total_tokens,
330
+ cost_brl: task.cost_brl,
331
+ run_count: task.run_count,
332
+ }))
333
+ : projectAllTimeTopTasks(allTimeTopTasks);
334
+ const macroMetrics =
335
+ fullAllTime === null
336
+ ? undefined
337
+ : Object.fromEntries(
338
+ (["specs", "prds"] as const).map((kind) => {
339
+ const metric = fullAllTime.macro_metrics[kind];
340
+ return [
341
+ kind,
342
+ {
343
+ status_counts: metric.status_counts,
344
+ classification_counts: metric.classification_counts,
345
+ entity_count: metric.entity_count,
346
+ total_run_count: metric.total_run_count,
347
+ total_duration_ms: metric.total_duration_ms,
348
+ total_tokens: metric.total_tokens,
349
+ total_cost_usd: metric.total_cost_usd,
350
+ total_cost_brl: metric.total_cost_brl,
351
+ average_duration_ms: metric.average_duration_ms,
352
+ average_tokens: metric.average_tokens,
353
+ average_cost_usd: metric.average_cost_usd,
354
+ average_cost_brl: metric.average_cost_brl,
355
+ latest_created_at: metric.latest_created_at,
356
+ top_entities: metric.top_entities.map((entity) => ({
357
+ entity_id: entity.entity_id,
358
+ title: entity.title,
359
+ duration_ms: entity.total_duration_ms,
360
+ total_tokens: entity.total_tokens,
361
+ total_cost_usd: entity.total_cost_usd,
362
+ cost_brl: entity.total_cost_brl,
363
+ run_count: entity.total_run_count,
364
+ duration_share: entity.duration_share,
365
+ })),
366
+ },
367
+ ];
368
+ }),
369
+ ) as NonNullable<
370
+ NonNullable<OperationsProjection["summary"]["overview_metrics"]>["macro_metrics"]
371
+ >;
372
+ const statuses: Record<string, number> = allTime?.task_status_counts ?? {};
373
+ const risks: Record<string, number> = allTime?.task_risk_counts ?? {};
374
+ if (allTime === null) {
375
+ for (const task of tasks) {
376
+ const status = typeof task.status === "string" ? task.status : "draft";
377
+ const risk = typeof task.risk === "string" ? task.risk : "standard";
378
+ statuses[status] = (statuses[status] ?? 0) + 1;
379
+ risks[risk] = (risks[risk] ?? 0) + 1;
145
380
  }
146
- } catch {
147
- // A configuração visual nunca deve impedir a inicialização do dashboard.
148
381
  }
149
- if (repositorySetting.toLowerCase() !== "auto") {
382
+ const latest =
383
+ allTime?.aggregate.latest_task_created_at ??
384
+ [...tasks, ...specs, ...prds]
385
+ .map((row) => (typeof row.occurred_at === "string" ? row.occurred_at : null))
386
+ .filter((value): value is string => value !== null)
387
+ .sort()
388
+ .at(-1) ??
389
+ null;
390
+ const unavailableUsage = {
391
+ events: 0,
392
+ tokens: { input: null, cached_input: null, output: null, reasoning: null, total: null },
393
+ cost: { usd: null, brl: null },
394
+ coverage: { events: 0, tokens: 0, usd: 0, brl: 0 },
395
+ };
396
+ const totalUsage = {
397
+ ...unavailableUsage,
398
+ tokens: { ...unavailableUsage.tokens, total: aggregate.total_tokens },
399
+ cost: { ...unavailableUsage.cost, brl: aggregate.cost_brl },
400
+ };
401
+ const usage = {
402
+ conversation: unavailableUsage,
403
+ planning: unavailableUsage,
404
+ execution: unavailableUsage,
405
+ total: totalUsage,
406
+ new_work: unavailableUsage,
407
+ rework: unavailableUsage,
408
+ };
409
+ const dailyPeriods = options.includePeriods === true ? filesystem.dailyPeriods(filter) : {};
410
+ const hasUnknownUsage = Object.values(dailyPeriods).some(
411
+ (period) => period.usage.total_tokens.unknown > 0 || period.usage.cost.unknown > 0,
412
+ );
413
+ const taskProjection = (row: Record<string, unknown>) => ({
414
+ schema_version: 2 as const,
415
+ id: String(row.id),
416
+ revision: 1,
417
+ change_id: "",
418
+ title: String(row.title ?? row.id),
419
+ short_name: String(row.title ?? row.id),
420
+ objective: "",
421
+ status: normalizeTaskStatus(row.status),
422
+ attempt_count: null,
423
+ risk: normalizeRisk(row.risk),
424
+ priority: "medium" as const,
425
+ owner: null,
426
+ requirement_ids: [],
427
+ acceptance_ids: [],
428
+ depends_on: [],
429
+ write_scopes: [],
430
+ created_at: String(row.occurred_at),
431
+ updated_at: String(row.occurred_at),
432
+ started_at: null,
433
+ finished_at: null,
434
+ result: null,
435
+ artifact_path: ".agents/records",
436
+ progress: 0,
437
+ spec_id: null,
438
+ execution_started: false,
439
+ telemetry: {
440
+ duration_ms: nullableMetric(row.total_duration),
441
+ tokens: nullableMetric(row.total_tokens),
442
+ cost_brl: nullableMetric(row.total_cost_brl),
443
+ },
444
+ });
445
+ const macroProjection = (row: Record<string, unknown>, kind: "prd" | "spec") => {
446
+ const entityUsage = {
447
+ ...unavailableUsage,
448
+ tokens: { ...unavailableUsage.tokens, total: nullableMetric(row.total_tokens) },
449
+ cost: { ...unavailableUsage.cost, brl: nullableMetric(row.total_cost_brl) },
450
+ };
150
451
  return {
151
- title,
152
- repository_name: githubRepositoryFromRemote(repositorySetting) ?? "",
452
+ id: String(row.id),
453
+ title: String(row.title ?? row.id),
454
+ created_at: String(row.occurred_at),
455
+ updated_at: String(row.occurred_at),
456
+ kind,
457
+ parent_id: null,
458
+ artifact_path: ".agents/records",
459
+ lifecycle_status: String(row.status ?? "draft"),
460
+ work_status: normalizeMacroStatus(row.status),
461
+ execution_ids: [],
462
+ task_ids: [],
463
+ planned_tasks: [],
464
+ canonical_run_ids: [],
465
+ run_ids: [],
466
+ root_execution_run_ids: [],
467
+ execution_count: 0,
468
+ task_count: 0,
469
+ run_count: nullableMetric(row.invocation_count) ?? 0,
470
+ duration_ms: nullableMetric(row.total_duration),
471
+ decisions: { human: 0, agent: 0 },
472
+ rework_run_count: 0,
473
+ quality: {
474
+ completed_tasks: 0,
475
+ task_completion_rate: 0,
476
+ terminal_runs: 0,
477
+ successful_runs: 0,
478
+ terminal_success_rate: 0,
479
+ rework_rate: 0,
480
+ },
481
+ usage: {
482
+ conversation: unavailableUsage,
483
+ planning: unavailableUsage,
484
+ execution: unavailableUsage,
485
+ total: entityUsage,
486
+ new_work: unavailableUsage,
487
+ rework: unavailableUsage,
488
+ },
153
489
  };
154
- }
155
- try {
156
- const { stdout } = await execFileAsync(
157
- "git",
158
- ["-C", workspaceRoot, "remote", "get-url", "origin"],
159
- { encoding: "utf8", windowsHide: true },
160
- );
161
- return { title, repository_name: githubRepositoryFromRemote(stdout) ?? "" };
162
- } catch {
163
- // Falha de detecção mantém o badge ausente sem impedir o dashboard.
164
- }
165
- return { title, repository_name: "" };
490
+ };
491
+ return {
492
+ schema_version: 2,
493
+ layout_version: layout.version as 2 | 3,
494
+ readonly: true,
495
+ source: ".agents/records",
496
+ filters: filter,
497
+ summary: {
498
+ prd_count: allTime?.aggregate.prd_count ?? prds.length,
499
+ spec_count: allTime?.aggregate.spec_count ?? specs.length,
500
+ execution_count: 0,
501
+ task_count: taskCount,
502
+ run_count: aggregate.total_run_count,
503
+ overview_metrics: {
504
+ duration_ms: aggregate.duration_ms,
505
+ execution_day_count: aggregate.execution_day_count,
506
+ tokens: aggregate.total_tokens,
507
+ cost_brl: aggregate.cost_brl,
508
+ prd_count: allTime?.aggregate.prd_count ?? prds.length,
509
+ spec_count: allTime?.aggregate.spec_count ?? specs.length,
510
+ selected_counts: {
511
+ prds: allTime?.aggregate.prd_count ?? prds.length,
512
+ specs: allTime?.aggregate.spec_count ?? specs.length,
513
+ },
514
+ task_count: taskCount,
515
+ completed_prd_count:
516
+ allTime?.aggregate.completed_prd_count ??
517
+ prds.filter((row) => row.status === "completed").length,
518
+ completed_spec_count:
519
+ allTime?.aggregate.completed_spec_count ??
520
+ specs.filter((row) => row.status === "completed").length,
521
+ completed_task_count: allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0,
522
+ task_status_counts: statuses,
523
+ task_risk_counts: risks,
524
+ top_tasks: topTasks,
525
+ ...(macroMetrics === undefined ? {} : { macro_metrics: macroMetrics }),
526
+ latest_task_created_at: latest,
527
+ coverage: { duration: 1, tokens: hasUnknownUsage ? 0 : 1, cost: hasUnknownUsage ? 0 : 1 },
528
+ data_source: "run_records",
529
+ partial: true,
530
+ },
531
+ decisions: { human: 0, agent: 0 },
532
+ source_latest_at: latest,
533
+ quality: {
534
+ completed_tasks: statuses.completed ?? 0,
535
+ task_completion_rate:
536
+ taskCount === 0
537
+ ? 0
538
+ : (allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0) /
539
+ taskCount,
540
+ terminal_runs: 0,
541
+ successful_runs: 0,
542
+ terminal_success_rate: 0,
543
+ rework_rate: 0,
544
+ },
545
+ reconciliation: {
546
+ phase_tokens_match_total: true,
547
+ phase_usd_match_total: true,
548
+ phase_brl_match_total: true,
549
+ origin_tokens_match_total: true,
550
+ },
551
+ usage,
552
+ // The period tabs are part of the first-fold contract too. Supplying an
553
+ // empty object for the all-time fast path made Daily, Monthly and Annual
554
+ // look empty even though the SQLite daily aggregate already existed.
555
+ periods: { daily: options.includePeriods === true ? dailyPeriods : {} },
556
+ },
557
+ origins: { conversation: usage, task: usage, spec: usage, prd: usage },
558
+ prds: prds.map((row) => macroProjection(row, "prd")),
559
+ specs: specs.map((row) => macroProjection(row, "spec")),
560
+ tasks: options.includeBoardTasks ? tasks.map(taskProjection) : [],
561
+ changes: [],
562
+ executions: [],
563
+ runs: [],
564
+ decisions: [],
565
+ evidences: [],
566
+ validations: [],
567
+ };
166
568
  }
167
569
 
168
- export function githubRepositoryFromRemote(remote: string): string | null {
169
- const trimmed = remote.trim();
170
- const match =
171
- trimmed.match(/github\.com(?::|\/)([^/\s]+)\/([^/\s]+?)(?:\.git)?$/i) ??
172
- trimmed.match(/^([^/:\s]+)\/([^/\s]+?)(?:\.git)?$/);
173
- return match ? `${match[1]}/${match[2]}` : null;
570
+ function nullableMetric(value: unknown): number | null {
571
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
174
572
  }
175
573
 
176
- export function dashboardWorkspaceKey(workspaceRoot: string): string {
177
- return createHash("sha256").update(resolve(workspaceRoot)).digest("hex").slice(0, 16);
574
+ function normalizeTaskStatus(
575
+ value: unknown,
576
+ ):
577
+ | "draft"
578
+ | "planned"
579
+ | "executing"
580
+ | "verifying"
581
+ | "completed"
582
+ | "failed"
583
+ | "blocked"
584
+ | "cancelled" {
585
+ return [
586
+ "draft",
587
+ "planned",
588
+ "executing",
589
+ "verifying",
590
+ "completed",
591
+ "failed",
592
+ "blocked",
593
+ "cancelled",
594
+ ].includes(String(value))
595
+ ? (String(value) as
596
+ | "draft"
597
+ | "planned"
598
+ | "executing"
599
+ | "verifying"
600
+ | "completed"
601
+ | "failed"
602
+ | "blocked"
603
+ | "cancelled")
604
+ : "draft";
178
605
  }
179
606
 
180
- export interface ReconciliationItem {
181
- id: string;
182
- reason: string | null;
607
+ function normalizeRisk(value: unknown): "lite" | "standard" | "critical" {
608
+ return ["lite", "standard", "critical"].includes(String(value))
609
+ ? (String(value) as "lite" | "standard" | "critical")
610
+ : "standard";
183
611
  }
184
612
 
185
- export interface ReconciliationResult {
186
- reconciled_ids: string[];
187
- blocked: ReconciliationItem[];
188
- not_reconciled: ReconciliationItem[];
613
+ function normalizeMacroStatus(
614
+ value: unknown,
615
+ ): "planning" | "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled" {
616
+ const status = String(value);
617
+ if (["executing", "verifying", "completed", "blocked", "failed", "cancelled"].includes(status))
618
+ return status as "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled";
619
+ return "planning";
189
620
  }
190
621
 
191
- async function reconcileOpenWork(
622
+ async function firstFoldCostSource(
192
623
  filesystem: WorkspaceFileSystem,
193
- workspaceRoot: string,
194
- ): Promise<ReconciliationResult> {
195
- if (!(await filesystem.exists(".agents/runtime/bin/zaip.mjs"))) {
196
- return { reconciled_ids: [], blocked: [], not_reconciled: [] };
624
+ filters: Parameters<SqliteReadModelFileSystem["invocationCostRows"]>[0] = {
625
+ from: null,
626
+ to: null,
627
+ },
628
+ ): Promise<CostSummarySource> {
629
+ if (filesystem instanceof SqliteReadModelFileSystem) {
630
+ return filesystem.costMonitorSummaryAggregate(filters) as CostSummarySource;
197
631
  }
198
- const cliPath = resolve(workspaceRoot, ".agents", "runtime", "bin", "zaip.mjs");
199
- const { stdout } = await execFileAsync(
200
- process.execPath,
201
- [cliPath, "run", "reconcile", "--open", "--json"],
202
- { cwd: workspaceRoot, windowsHide: true, timeout: 120_000, maxBuffer: 10 * 1_024 * 1_024 },
203
- );
204
- return reconciliationResult(JSON.parse(stdout) as unknown);
632
+ const daily = await new LifecycleManifestStore(filesystem).readDailyReadModels("overview");
633
+ if (daily === null) throw new Error("Lifecycle daily overview is not initialized");
634
+ return {
635
+ periods: {
636
+ daily: Object.fromEntries(
637
+ Object.entries(daily.days).map(([day, model]) => [day, model.overview]),
638
+ ),
639
+ },
640
+ } satisfies CostSummarySource;
205
641
  }
206
642
 
207
- export function reconciliationResult(value: unknown): ReconciliationResult {
208
- const reconciled = new Set<string>();
209
- const blocked = new Map<string, string | null>();
210
- const notReconciled = new Map<string, string | null>();
211
- const remember = (target: Map<string, string | null>, id: unknown, reason: unknown): void => {
212
- if (typeof id !== "string") return;
213
- target.set(id, typeof reason === "string" && reason.trim() ? reason : null);
643
+ function buildSqliteTaskTrace(
644
+ filesystem: SqliteReadModelFileSystem,
645
+ layout: WorkspaceLayout,
646
+ taskId: string,
647
+ ): TaskTrace {
648
+ const initial = filesystem.canonicalTraceFiles([taskId]);
649
+ const taskFile = initial.find(
650
+ (row) => row.path.includes("/.agents/records/tasks/") || row.path.includes(".agents/records/tasks/"),
651
+ );
652
+ if (taskFile === undefined) throw new Error(`Canonical Task ${taskId} was not found`);
653
+ const scalar = (key: string): string | null => {
654
+ const frontMatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(taskFile.content)?.[1] ?? "";
655
+ const value = new RegExp(`^${key}:\\s*(.+?)\\s*$`, "m").exec(frontMatter)?.[1]?.trim();
656
+ if (value === undefined || value === "null") return null;
657
+ return value.replace(/^['"]|['"]$/g, "");
214
658
  };
215
- const records =
216
- typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {};
217
- const reconciliations = Array.isArray(records.reconciliations) ? records.reconciliations : [];
218
- for (const candidate of reconciliations) {
219
- if (typeof candidate !== "object" || candidate === null) continue;
220
- const item = candidate as Record<string, unknown>;
221
- const reason = item.error;
222
- const entities = ["tasks", "changes", "executions", "specs", "prds"].flatMap((key) =>
223
- Array.isArray(item[key]) ? item[key] : [],
224
- );
225
- for (const entity of entities) {
226
- if (typeof entity !== "object" || entity === null) continue;
227
- const record = entity as Record<string, unknown>;
228
- if (
229
- item.status === "closed" &&
230
- record.status === "completed" &&
231
- typeof record.id === "string"
232
- ) {
233
- reconciled.add(record.id);
234
- } else if (record.status === "blocked") {
235
- remember(blocked, record.id, record.status_reason ?? reason);
236
- } else if (item.status === "deferred") {
237
- remember(notReconciled, record.id, reason);
238
- }
239
- }
240
- }
241
- const orphanedExecutions = Array.isArray(records.orphaned_executions)
242
- ? records.orphaned_executions
243
- : [];
244
- orphanedExecutions.forEach((id) => {
245
- remember(blocked, id, "Execution orphaned after the reconciliation grace period.");
246
- });
247
- const errors = Array.isArray(records.reconciliation_errors) ? records.reconciliation_errors : [];
248
- errors.forEach((candidate) => {
249
- if (typeof candidate !== "object" || candidate === null) return;
250
- const item = candidate as Record<string, unknown>;
251
- remember(notReconciled, item.run_id, item.error);
252
- });
253
- const unresolved = Array.isArray(records.unresolved_running_run_ids)
254
- ? records.unresolved_running_run_ids
255
- : [];
256
- unresolved.forEach((id) => {
257
- remember(notReconciled, id, "Invocation is still running.");
258
- });
259
- const entries = (items: Map<string, string | null>): ReconciliationItem[] =>
260
- [...items.entries()]
261
- .map(([id, reason]) => ({ id, reason }))
262
- .sort((left, right) => left.id.localeCompare(right.id));
659
+ const relatedId = (key: string, prefix: string): string | null =>
660
+ scalar(key) ?? new RegExp(`\\b${prefix}[A-Z0-9-]+`).exec(taskFile.content)?.[0] ?? null;
661
+ const changeId = relatedId("change_id", "CHG-") ?? taskId;
662
+ const specId = relatedId("spec_id", "SPEC-");
663
+ const prdId = relatedId("prd_id", "PRD-");
664
+ const markers = [taskId, changeId, specId, prdId].filter(
665
+ (value): value is string => value !== null,
666
+ );
667
+ const documents = filesystem
668
+ .canonicalTraceFiles(markers)
669
+ .filter(
670
+ (row) =>
671
+ /\.(?:md|json|jsonl|txt|log)$/i.test(row.path) && row.content.length <= 250_000,
672
+ )
673
+ .map((row) => {
674
+ const classification = classifyTraceDocument(row.path);
675
+ const timestamps = timestampsFromContent(row.content);
676
+ return {
677
+ id: createHash("sha256").update(row.path).digest("hex").slice(0, 16),
678
+ path: row.path,
679
+ title:
680
+ row.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ??
681
+ row.path.split("/").at(-1) ??
682
+ row.path,
683
+ ...classification,
684
+ content: row.content,
685
+ ...timestamps,
686
+ } satisfies TraceDocument;
687
+ });
688
+ const stages = buildTraceStageRecords(documents);
689
+ const coveredStages = new Set(documents.map((document) => document.stage));
690
+ const risk = scalar("risk");
691
+ const status = scalar("status");
263
692
  return {
264
- reconciled_ids: [...reconciled].sort(),
265
- blocked: entries(blocked),
266
- not_reconciled: entries(notReconciled),
693
+ schema_version: 2,
694
+ layout_version: layout.version as 2 | 3,
695
+ readonly: true,
696
+ source: ".agents/records",
697
+ task_id: taskId,
698
+ task_title: scalar("title") ?? taskFile.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? taskId,
699
+ task_risk: risk === "lite" || risk === "standard" || risk === "critical" ? risk : null,
700
+ task_created_at: scalar("created_at"),
701
+ task_progress:
702
+ status === "completed"
703
+ ? 100
704
+ : status === "verifying"
705
+ ? 80
706
+ : status === "executing"
707
+ ? 50
708
+ : status === "blocked" || status === "failed" || status === "cancelled"
709
+ ? 0
710
+ : 10,
711
+ spec_id: specId,
712
+ prd_id: prdId,
713
+ change_id: changeId,
714
+ usage: filesystem.taskTraceUsage(taskId),
715
+ documents,
716
+ stages,
717
+ coverage: {
718
+ stages_with_documents: coveredStages.size,
719
+ stages_with_timestamps: stages.filter((stage) => stage.status === "recorded").length,
720
+ total_stages: 6,
721
+ evidence_count: documents.filter((document) => document.kind === "evidence").length,
722
+ test_count: documents.filter((document) => document.kind === "test").length,
723
+ },
267
724
  };
268
725
  }
269
726
 
270
- async function requireV2Layout(filesystem: WorkspaceFileSystem): Promise<WorkspaceLayout> {
271
- const layout = await loadWorkspaceLayout(filesystem);
272
- if (layout.version === 1) {
273
- throw new V2WorkspaceRequiredError("Dashboard API requires a modern ZAIP-AIOS workspace");
274
- }
275
- return layout;
276
- }
277
-
278
- function headOrJson(
279
- context: Context,
280
- payload: unknown,
281
- status: ContentfulStatusCode = 200,
282
- ): Response {
283
- if (context.req.method === "HEAD") return context.body(null, status);
284
- return context.json(payload, status);
285
- }
286
-
287
727
  export function createDashboardApp(
288
728
  workspaceRoot = process.env.ZAIP_WORKSPACE ?? process.cwd(),
289
729
  sessionToken = defaultSessionToken(),
@@ -291,34 +731,92 @@ export function createDashboardApp(
291
731
  options: DashboardAppOptions = {},
292
732
  ): Hono {
293
733
  const app = new Hono();
294
- const filesystem = new WorkspaceFileSystem(workspaceRoot);
734
+ const filesystem = options.filesystem ?? new WorkspaceFileSystem(workspaceRoot);
735
+ const isSqliteSnapshot = filesystem instanceof SqliteReadModelFileSystem;
736
+ // A private SQLite generation is immutable for this app instance. Loading
737
+ // its V2 layout repeatedly costs hundreds of milliseconds per navigation;
738
+ // canonical filesystems remain deliberately uncached.
739
+ const sqliteLayout = isSqliteSnapshot ? requireV2Layout(filesystem) : null;
740
+ const readLayout = () => sqliteLayout ?? requireV2Layout(filesystem);
741
+ const snapshotReadOnly = options.snapshotReadOnly ?? isSqliteSnapshot;
742
+ const snapshotState = options.snapshotState ?? "current";
743
+ const canonicalFilesystem = options.canonicalFilesystem ?? new WorkspaceFileSystem(workspaceRoot);
744
+ const reconcileOpen = options.reconcileOpenWork ?? reconcileOpenWork;
745
+ const refreshReadModel = options.refreshReadModel ?? (async () => {
746
+ if (!isSqliteSnapshot) return;
747
+ await filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(workspaceRoot));
748
+ });
295
749
  const workspaceKey = dashboardWorkspaceKey(workspaceRoot);
296
750
  const syncAgentAdapters = options.syncAgentAdapters ?? (() => runAgentAdapterSync(workspaceRoot));
297
751
  let catalogWriteTail = Promise.resolve();
298
- let snapshotInFlight: Promise<Awaited<ReturnType<typeof dashboardSnapshot>>> | null = null;
299
- let cachedSnapshot: Awaited<ReturnType<typeof dashboardSnapshot>> | null = null;
300
- let cachedSnapshotAt = 0;
301
- let cachedSnapshotKey: string | null = null;
302
- const readSnapshot = (context: Context) => {
303
- const key = `${selectedCurrency(context)}:${context.req.query("from") ?? ""}:${context.req.query("to") ?? ""}`;
752
+ type DashboardSnapshot = Awaited<ReturnType<typeof dashboardSnapshot>>;
753
+ const snapshotInFlight = new Map<string, Promise<DashboardSnapshot>>();
754
+ const cachedSnapshots = new Map<string, { snapshot: DashboardSnapshot; cachedAt: number }>();
755
+ const aggregatePayloads = new Map<string, { payload: unknown; cachedAt: number }>();
756
+ const aggregateInFlight = new Map<string, Promise<unknown>>();
757
+ const readAggregate = async <T>(
758
+ key: string,
759
+ build: () => Promise<T>,
760
+ fresh = false,
761
+ ): Promise<T> => {
762
+ const cached = aggregatePayloads.get(key);
763
+ // Kanban cards are canonical projections. SQLite generations are immutable,
764
+ // so retain their expensive board projection until an explicit refresh
765
+ // clears this map rather than expiring it on the short snapshot TTL.
304
766
  if (
305
- cachedSnapshot !== null &&
306
- cachedSnapshotKey === key &&
307
- Date.now() - cachedSnapshotAt < 2_000
767
+ !fresh && cached !== undefined &&
768
+ (key.startsWith("operations:board:") || Date.now() - cached.cachedAt < SNAPSHOT_CACHE_TTL_MS)
308
769
  )
309
- return Promise.resolve(cachedSnapshot);
310
- if (snapshotInFlight !== null) return snapshotInFlight;
311
- snapshotInFlight = dashboardSnapshot(filesystem, workspaceRoot, context)
770
+ return cached.payload as T;
771
+ const pending = aggregateInFlight.get(key);
772
+ // A bootstrap fresh read must not observe a normal request that began
773
+ // before reload. Wait for it to settle, then rebuild and replace cache.
774
+ if (pending !== undefined) {
775
+ if (!fresh) return pending as Promise<T>;
776
+ await pending;
777
+ return readAggregate(key, build, true);
778
+ }
779
+ const pendingBuild = build()
780
+ .then((payload) => {
781
+ aggregatePayloads.set(key, { payload, cachedAt: Date.now() });
782
+ return payload;
783
+ })
784
+ .finally(() => aggregateInFlight.delete(key));
785
+ aggregateInFlight.set(key, pendingBuild);
786
+ return pendingBuild;
787
+ };
788
+ const snapshotKey = (context: Context) => {
789
+ const { from, to } = projectionPeriodFilter(context);
790
+ return `${selectedCurrency(context)}:${from ?? ""}:${to ?? ""}`;
791
+ };
792
+ const clearSnapshotCache = () => {
793
+ cachedSnapshots.clear();
794
+ aggregatePayloads.clear();
795
+ aggregateInFlight.clear();
796
+ };
797
+ const readSnapshot = (context: Context) => {
798
+ const key = snapshotKey(context);
799
+ const cached = cachedSnapshots.get(key);
800
+ if (cached !== undefined && Date.now() - cached.cachedAt < SNAPSHOT_CACHE_TTL_MS) {
801
+ context.header("X-ZAIP-Snapshot-Cache", "hit");
802
+ return Promise.resolve(cached.snapshot);
803
+ }
804
+ const pending = snapshotInFlight.get(key);
805
+ if (pending !== undefined) {
806
+ context.header("X-ZAIP-Snapshot-Cache", "inflight");
807
+ return pending;
808
+ }
809
+ context.header("X-ZAIP-Snapshot-Cache", "miss");
810
+ const inFlight = readLayout().then((layout) => dashboardSnapshot(filesystem, workspaceRoot, context, layout))
312
811
  .then((snapshot) => {
313
- cachedSnapshot = snapshot;
314
- cachedSnapshotKey = key;
315
- cachedSnapshotAt = Date.now();
812
+ cachedSnapshots.set(key, { snapshot, cachedAt: Date.now() });
316
813
  return snapshot;
317
814
  })
318
815
  .finally(() => {
319
- snapshotInFlight = null;
816
+ snapshotInFlight.delete(key);
320
817
  });
321
- return snapshotInFlight;
818
+ snapshotInFlight.set(key, inFlight);
819
+ return inFlight;
322
820
  };
323
821
 
324
822
  app.use("*", async (context, next) => {
@@ -342,12 +840,18 @@ export function createDashboardApp(
342
840
  app.use(`${API_PREFIX}/*`, async (context, next) => {
343
841
  const settingsWrite =
344
842
  context.req.method === "PUT" &&
345
- (context.req.path === `${API_PREFIX}/pricing` ||
346
- context.req.path === `${API_PREFIX}/fx-rate` ||
347
- context.req.path === `${API_PREFIX}/agent-catalog` ||
348
- context.req.path === `${API_PREFIX}/project-settings`);
843
+ (context.req.path === `${API_PREFIX}/work-priority` ||
844
+ context.req.path === `${API_PREFIX}/project-settings` ||
845
+ (!snapshotReadOnly &&
846
+ (context.req.path === `${API_PREFIX}/pricing` ||
847
+ context.req.path === `${API_PREFIX}/fx-rate` ||
848
+ context.req.path === `${API_PREFIX}/agent-catalog`)));
849
+ // Reconciliation updates canonical lifecycle records through the supported
850
+ // CLI. It does not mutate the disposable SQLite read model, so it remains
851
+ // available while the dashboard serves a read-only snapshot.
349
852
  const reconcileWrite =
350
- context.req.method === "POST" && context.req.path === `${API_PREFIX}/reconcile-open-work`;
853
+ context.req.method === "POST" &&
854
+ context.req.path === `${API_PREFIX}/reconcile-open-work`;
351
855
  if (
352
856
  context.req.method !== "GET" &&
353
857
  context.req.method !== "HEAD" &&
@@ -359,7 +863,8 @@ export function createDashboardApp(
359
863
  context.req.path === `${API_PREFIX}/pricing` ||
360
864
  context.req.path === `${API_PREFIX}/fx-rate` ||
361
865
  context.req.path === `${API_PREFIX}/agent-catalog` ||
362
- context.req.path === `${API_PREFIX}/project-settings`
866
+ context.req.path === `${API_PREFIX}/project-settings` ||
867
+ context.req.path === `${API_PREFIX}/work-priority`
363
868
  ? "GET, HEAD, PUT"
364
869
  : context.req.path === `${API_PREFIX}/reconcile-open-work`
365
870
  ? "POST"
@@ -367,7 +872,8 @@ export function createDashboardApp(
367
872
  );
368
873
  return context.json({ error: "Dashboard API is read-only" }, 405);
369
874
  }
370
- if (context.req.path === `${API_PREFIX}/health`) return next();
875
+ if (context.req.path === `${API_PREFIX}/health` || context.req.path === `${API_PREFIX}/ready`)
876
+ return next();
371
877
 
372
878
  const origin = context.req.header("origin");
373
879
  if (origin !== undefined && !isAllowedOrigin(origin)) {
@@ -392,7 +898,7 @@ export function createDashboardApp(
392
898
 
393
899
  app.on(["GET", "HEAD"], `${API_PREFIX}/health`, async (context) => {
394
900
  try {
395
- const layout = await requireV2Layout(filesystem);
901
+ const layout = await readLayout();
396
902
  return headOrJson(context, {
397
903
  kind: RUNTIME_KIND,
398
904
  workspaceKey,
@@ -401,6 +907,8 @@ export function createDashboardApp(
401
907
  api_version: 2,
402
908
  workspace_layout: layout.version,
403
909
  read_only: true,
910
+ storage: isSqliteSnapshot ? filesystem.metadata() : { storage_mode: "filesystem" },
911
+ snapshot_state: snapshotState,
404
912
  });
405
913
  } catch (error) {
406
914
  return headOrJson(
@@ -419,17 +927,151 @@ export function createDashboardApp(
419
927
  }
420
928
  });
421
929
 
930
+ // Launcher readiness proves a canonical projection can be read; health alone
931
+ // intentionally remains a lightweight process/layout probe.
932
+ app.on(["GET", "HEAD"], `${API_PREFIX}/ready`, async (context) => {
933
+ try {
934
+ const layout = await readLayout();
935
+ if (isSqliteSnapshot) {
936
+ const metadata = filesystem.metadata();
937
+ return headOrJson(context, {
938
+ kind: RUNTIME_KIND,
939
+ workspaceKey,
940
+ status: "ready",
941
+ workspace_layout: layout.version,
942
+ storage_mode: metadata.storage_mode,
943
+ activity_count: metadata.activity_count,
944
+ // Readiness is only an immutable-generation probe. The default
945
+ // monthly Operations projection was materialized during rebuild;
946
+ // /ready must never start a competing projection in the background.
947
+ day_count: metadata.daily_aggregate_count,
948
+ snapshot_state: snapshotState,
949
+ });
950
+ }
951
+ const projection = await buildFirstFoldQuickProjection(filesystem, layout, {
952
+ from: null,
953
+ to: null,
954
+ });
955
+ return headOrJson(context, {
956
+ kind: RUNTIME_KIND,
957
+ workspaceKey,
958
+ status: "ready",
959
+ source_latest_at: projection.summary.source_latest_at,
960
+ });
961
+ } catch (error) {
962
+ return headOrJson(
963
+ context,
964
+ {
965
+ kind: RUNTIME_KIND,
966
+ workspaceKey,
967
+ status: "unavailable",
968
+ error: error instanceof Error ? error.message : String(error),
969
+ },
970
+ 503,
971
+ );
972
+ }
973
+ });
974
+
422
975
  app.on(["GET", "HEAD"], `${API_PREFIX}/operations`, async (context) => {
423
976
  try {
424
- const layout = await requireV2Layout(filesystem);
977
+ const requestStartedAt = performance.now();
978
+ const scope = context.req.query("scope");
979
+ if (scope !== "overview" && scope !== "quick" && scope !== "detail" && scope !== "board" && scope !== "classification")
980
+ return headOrJson(context, (await readSnapshot(context)).operations);
981
+ const layout = await readLayout();
982
+ const layoutMs = performance.now() - requestStartedAt;
425
983
  const { from, to } = projectionPeriodFilter(context);
426
- const projection = await buildCanonicalOperationsProjection(
427
- filesystem,
428
- layout,
429
- { from, to },
430
- context.req.query("scope") === "overview",
431
- );
432
- return headOrJson(context, projection);
984
+ if (scope === "board") {
985
+ const stage = boardStage(context.req.query("stage"));
986
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
987
+ const limit = Math.min(
988
+ 50,
989
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "10", 10) || 10),
990
+ );
991
+ const board = await readAggregate(`operations:board:${from ?? ""}:${to ?? ""}`, async () => {
992
+ const projection = await buildFirstFoldQuickProjection(
993
+ filesystem,
994
+ layout,
995
+ { from, to },
996
+ { includeBoardTasks: true },
997
+ );
998
+ return {
999
+ projection,
1000
+ priority: await readWorkPriority(canonicalFilesystem, knownWorkIds(projection)),
1001
+ };
1002
+ });
1003
+ const filters = boardFilters(context);
1004
+ const pageKey = JSON.stringify({ from, to, stage, offset, limit, ...filters });
1005
+ const page = await readAggregate(`operations:board-page:${pageKey}`, async () =>
1006
+ boardPage(board.projection, stage, offset, limit, board.priority.item_ids, filters),
1007
+ );
1008
+ return headOrJson(
1009
+ context,
1010
+ page,
1011
+ 200,
1012
+ true,
1013
+ );
1014
+ }
1015
+ if (scope === "classification") {
1016
+ const kind = context.req.query("kind");
1017
+ const classification = context.req.query("classification");
1018
+ if (kind !== "task" && kind !== "spec" && kind !== "prd")
1019
+ return headOrJson(context, { error: "A valid work kind is required" }, 400);
1020
+ if (!['lite', 'standard', 'critical', 'empty', 'structured', 'rework'].includes(classification ?? ''))
1021
+ return headOrJson(context, { error: "A valid classification is required" }, 400);
1022
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1023
+ const limit = Math.min(50, Math.max(1, Number.parseInt(context.req.query("limit") ?? "50", 10) || 50));
1024
+ const projection = await readAggregate(`operations:classification:${from ?? ""}:${to ?? ""}`, () =>
1025
+ buildFirstFoldQuickProjection(filesystem, layout, { from, to }, { includeBoardTasks: true }),
1026
+ );
1027
+ return headOrJson(context, classificationPage(projection, kind, classification as BoardClassification, offset, limit), 200, true);
1028
+ }
1029
+ if (scope === "quick") {
1030
+ const projectionStartedAt = performance.now();
1031
+ const quick = await readAggregate(
1032
+ `operations:quick:${from ?? ""}:${to ?? ""}`,
1033
+ () =>
1034
+ buildFirstFoldQuickProjection(filesystem, layout, { from, to }, {
1035
+ includeTopTasks: from === null && to === null,
1036
+ }),
1037
+ context.req.query("fresh") === "1",
1038
+ );
1039
+ if (
1040
+ process.env.ZAIP_DASHBOARD_PROFILE_FIRST_FOLD === "1" &&
1041
+ filesystem instanceof SqliteReadModelFileSystem
1042
+ ) {
1043
+ const timings = filesystem.firstFoldViewportProfile();
1044
+ context.header(
1045
+ "Server-Timing",
1046
+ [
1047
+ `layout;dur=${Math.round(layoutMs * 10) / 10}`,
1048
+ `projection;dur=${Math.round((performance.now() - projectionStartedAt) * 10) / 10}`,
1049
+ ...Object.entries(timings)
1050
+ .map(([name, duration]) => `${name};dur=${duration}`)
1051
+ ].join(", "),
1052
+ );
1053
+ }
1054
+ return headOrJson(context, quick, 200, true);
1055
+ }
1056
+ const projection =
1057
+ filesystem instanceof SqliteReadModelFileSystem
1058
+ ? await buildFirstFoldQuickProjection(
1059
+ filesystem,
1060
+ layout,
1061
+ { from, to },
1062
+ {
1063
+ includeBoardTasks: scope === "detail",
1064
+ includeExtended: scope === "detail" || scope === "overview",
1065
+ includePeriods: scope === "detail" || scope === "overview",
1066
+ },
1067
+ )
1068
+ : await buildCanonicalOperationsProjection(
1069
+ filesystem,
1070
+ layout,
1071
+ { from, to },
1072
+ scope === "overview",
1073
+ );
1074
+ return headOrJson(context, projection, 200, scope === "overview");
433
1075
  } catch (error) {
434
1076
  if (error instanceof V2WorkspaceRequiredError) throw error;
435
1077
  return headOrJson(
@@ -440,6 +1082,146 @@ export function createDashboardApp(
440
1082
  }
441
1083
  });
442
1084
 
1085
+ registerOperationsRunsRoute(app, filesystem, readAggregate, headOrJson);
1086
+
1087
+ // Explicit SQL read-model API for the comparison dashboard. It never falls
1088
+ // back to filesystem pagination, making first-fold/entity query plans
1089
+ // inspectable while preserving the established Operations contracts.
1090
+ app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-read-models`, async (context) => {
1091
+ if (!(filesystem instanceof SqliteReadModelFileSystem))
1092
+ return headOrJson(context, { error: "SQLite read model is not active" }, 409);
1093
+ const kind = context.req.query("kind");
1094
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1095
+ const limit = Math.min(
1096
+ 200,
1097
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "20", 10) || 20),
1098
+ );
1099
+ if (kind === "overview") {
1100
+ const period = context.req.query("period");
1101
+ if (period !== "day" && period !== "month" && period !== "year")
1102
+ return headOrJson(context, { error: "Unsupported SQLite overview period" }, 400);
1103
+ return headOrJson(context, filesystem.overviewPage(period, offset, limit), 200, true);
1104
+ }
1105
+ if (kind !== "task" && kind !== "spec" && kind !== "prd")
1106
+ return headOrJson(context, { error: "Unsupported SQLite read model" }, 400);
1107
+ const { from, to } = projectionPeriodFilter(context);
1108
+ const filters = operationsPageFilters(context);
1109
+ return headOrJson(
1110
+ context,
1111
+ filesystem.summaryPage(
1112
+ kind,
1113
+ { from, to, ...filters },
1114
+ offset,
1115
+ limit,
1116
+ context.req.query("cursor") ?? null,
1117
+ ),
1118
+ 200,
1119
+ true,
1120
+ );
1121
+ });
1122
+
1123
+ app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-views`, async (context) => {
1124
+ if (!(filesystem instanceof SqliteReadModelFileSystem))
1125
+ return headOrJson(context, { error: "SQLite read model is not active" }, 409);
1126
+ const view = context.req.query("view");
1127
+ if (view === undefined || !(view in sqliteViewMetadata))
1128
+ return headOrJson(context, { error: "Unsupported SQLite view" }, 400);
1129
+ const parsed = Number.parseInt(context.req.query("limit") ?? "", 10);
1130
+ const defaultLimit = view.includes("agent_invocations")
1131
+ ? 100
1132
+ : view === "first_fold_top_tasks"
1133
+ ? 5
1134
+ : 50;
1135
+ const limit = Math.min(200, Math.max(1, Number.isFinite(parsed) ? parsed : defaultLimit));
1136
+ const { from, to } = projectionPeriodFilter(context);
1137
+ const optional = (name: string) => {
1138
+ const value = context.req.query(name);
1139
+ return value === undefined ? {} : { [name]: value };
1140
+ };
1141
+ return headOrJson(
1142
+ context,
1143
+ filesystem.viewPage(view as keyof typeof sqliteViewMetadata, {
1144
+ from,
1145
+ to,
1146
+ ...optional("query"),
1147
+ ...optional("status"),
1148
+ ...optional("risk"),
1149
+ ...optional("provider"),
1150
+ ...optional("model"),
1151
+ ...optional("agent"),
1152
+ ...optional("phase"),
1153
+ ...optional("origin"),
1154
+ ...optional("workType"),
1155
+ ...optional("priority"),
1156
+ offset: Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0),
1157
+ limit,
1158
+ }),
1159
+ 200,
1160
+ true,
1161
+ );
1162
+ });
1163
+
1164
+ app.on(["GET", "HEAD"], `${API_PREFIX}/work-priority`, async (context) => {
1165
+ const layout = await requireV2Layout(canonicalFilesystem);
1166
+ const operations = await buildOperationsProjection(canonicalFilesystem, layout, {
1167
+ from: null,
1168
+ to: null,
1169
+ });
1170
+ return headOrJson(
1171
+ context,
1172
+ await readWorkPriority(canonicalFilesystem, knownWorkIds(operations)),
1173
+ );
1174
+ });
1175
+
1176
+ app.on(["GET", "HEAD"], `${API_PREFIX}/operations/entities`, async (context) => {
1177
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1178
+ const limit = Math.min(
1179
+ 500,
1180
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
1181
+ );
1182
+ const kind = context.req.query("kind");
1183
+ if (kind !== "changes" && kind !== "decisions")
1184
+ return headOrJson(context, { error: "Unsupported Operations entity page" }, 400);
1185
+ const { from, to } = projectionPeriodFilter(context);
1186
+ const pageFilters = operationsPageFilters(context);
1187
+ const payload = await readAggregate(
1188
+ `operations:entities:${kind}:${from ?? ""}:${to ?? ""}:${offset}:${limit}:${pageFilters.query}:${pageFilters.status}:${pageFilters.risk}`,
1189
+ async () => {
1190
+ const layout = await readLayout();
1191
+ const entities = await new LifecycleManifestStore(filesystem).readEntities(
1192
+ kind === "changes" ? "change" : "decision",
1193
+ );
1194
+ const entityRows =
1195
+ kind === "changes"
1196
+ ? changeEntityPageRows(entities, layout, from, to)
1197
+ : decisionEntityPageRows(entities, layout, from, to);
1198
+ const rows = filterLifecyclePageRows(entityRows, pageFilters);
1199
+ return {
1200
+ data_source: "lifecycle_manifest_shards",
1201
+ kind,
1202
+ offset,
1203
+ limit,
1204
+ total: rows.length,
1205
+ rows: rows.slice(offset, offset + limit),
1206
+ };
1207
+ },
1208
+ );
1209
+ return headOrJson(context, payload, 200, true);
1210
+ });
1211
+
1212
+ registerDailyReadModelRoute(app, filesystem, readAggregate, headOrJson);
1213
+ app.put(`${API_PREFIX}/work-priority`, async (context) => {
1214
+ const candidate = normalizeWorkPriority(await context.req.json());
1215
+ // Priority is canonical queue configuration, never snapshot content.
1216
+ // Keep every other settings mutation blocked in snapshot-serving mode.
1217
+ await canonicalFilesystem.writeTextAtomic(
1218
+ WORK_PRIORITY_PATH,
1219
+ `${JSON.stringify(candidate, null, 2)}\n`,
1220
+ );
1221
+ clearSnapshotCache();
1222
+ return context.json(candidate);
1223
+ });
1224
+
443
1225
  app.on(["GET", "HEAD"], `${API_PREFIX}/snapshot`, async (context) => {
444
1226
  try {
445
1227
  return headOrJson(context, await readSnapshot(context));
@@ -455,10 +1237,15 @@ export function createDashboardApp(
455
1237
 
456
1238
  app.post(`${API_PREFIX}/reconcile-open-work`, async (context) => {
457
1239
  try {
458
- await requireV2Layout(filesystem);
459
- const reconciledIds = await reconcileOpenWork(filesystem, workspaceRoot);
1240
+ await readLayout();
1241
+ // The served snapshot is immutable by design. Reconciliation must use
1242
+ // canonical records, then atomically rebuild and reopen the snapshot
1243
+ // before the response exposes fresh projections.
1244
+ const reconciledIds = await reconcileOpen(canonicalFilesystem, workspaceRoot);
1245
+ await refreshReadModel();
1246
+ clearSnapshotCache();
460
1247
  return context.json({
461
- ...(await dashboardSnapshot(filesystem, workspaceRoot, context)),
1248
+ ...(await readSnapshot(context)),
462
1249
  reconciliation: reconciledIds,
463
1250
  });
464
1251
  } catch (error) {
@@ -469,7 +1256,74 @@ export function createDashboardApp(
469
1256
 
470
1257
  app.on(["GET", "HEAD"], `${API_PREFIX}/cost-monitor`, async (context) => {
471
1258
  try {
472
- const layout = await requireV2Layout(filesystem);
1259
+ const scope = context.req.query("scope") ?? "summary";
1260
+ if (scope === "summary") {
1261
+ const layout = await readLayout();
1262
+ const { from, to } = projectionPeriodFilter(context);
1263
+ const currency = selectedCurrency(context);
1264
+ const aggregateStartedAt = performance.now();
1265
+ const [source, rate] = await Promise.all([
1266
+ firstFoldCostSource(filesystem, {
1267
+ from,
1268
+ to,
1269
+ providers: queryValues(context, "provider"),
1270
+ models: queryValues(context, "model"),
1271
+ agents: queryValues(context, "agent"),
1272
+ phases: queryValues(context, "phase"),
1273
+ statuses: queryValues(context, "status"),
1274
+ tasks: queryValues(context, "task"),
1275
+ specs: queryValues(context, "spec"),
1276
+ prds: queryValues(context, "prd"),
1277
+ executions: queryValues(context, "execution"),
1278
+ changes: queryValues(context, "change"),
1279
+ }),
1280
+ currency === "BRL" ? readCurrentFxRate(filesystem) : Promise.resolve(null),
1281
+ ]);
1282
+ // First fold receives only aggregate facts. Detailed Runs and entity
1283
+ // arrays remain on their explicit paginated endpoints.
1284
+ const aggregateMs = performance.now() - aggregateStartedAt;
1285
+ const costOperations = {
1286
+ schema_version: 1,
1287
+ layout_version: layout.version,
1288
+ readonly: true,
1289
+ source: ".agents/records",
1290
+ filters: { from, to },
1291
+ summary: { task_count: source.task_count ?? 0 },
1292
+ prds: [],
1293
+ specs: [],
1294
+ runs: [],
1295
+ } as unknown as OperationsProjection;
1296
+ const projectionStartedAt = performance.now();
1297
+ const payload = buildCostSummaryProjection(
1298
+ source,
1299
+ costOperations,
1300
+ currency,
1301
+ rate?.rate ?? null,
1302
+ );
1303
+ context.header(
1304
+ "Server-Timing",
1305
+ `cost_sql;dur=${aggregateMs.toFixed(1)},cost_projection;dur=${(performance.now() - projectionStartedAt).toFixed(1)}`,
1306
+ );
1307
+ return headOrJson(context, payload, 200, true);
1308
+ }
1309
+ if (scope === "runs") {
1310
+ const layout = await readLayout();
1311
+ const { from, to } = projectionPeriodFilter(context);
1312
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1313
+ const limit = Math.min(
1314
+ 500,
1315
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
1316
+ );
1317
+ return headOrJson(
1318
+ context,
1319
+ await buildManifestCostRunPage(filesystem, layout, context, { from, to }, offset, limit),
1320
+ 200,
1321
+ true,
1322
+ );
1323
+ }
1324
+ if (isUnfilteredCostRequest(context))
1325
+ return headOrJson(context, (await readSnapshot(context)).costs);
1326
+ const layout = await readLayout();
473
1327
  const { from, to } = projectionPeriodFilter(context);
474
1328
  // Cost and Operations must start from the same manifest-corrected Run
475
1329
  // population; financial availability is represented on each row, never
@@ -507,6 +1361,7 @@ export function createDashboardApp(
507
1361
  ".agents/config/pricing/models.json",
508
1362
  `${JSON.stringify(catalog, null, 2)}\n`,
509
1363
  );
1364
+ clearSnapshotCache();
510
1365
  return context.json(catalog);
511
1366
  });
512
1367
 
@@ -518,6 +1373,7 @@ export function createDashboardApp(
518
1373
  app.put(`${API_PREFIX}/fx-rate`, async (context) => {
519
1374
  const input = fxRateSchema.pick({ rate: true }).parse(await context.req.json());
520
1375
  const rate = await new PricingRepository(filesystem).writeCurrentFxRate(input.rate);
1376
+ clearSnapshotCache();
521
1377
  return context.json(rate);
522
1378
  });
523
1379
 
@@ -553,9 +1409,13 @@ export function createDashboardApp(
553
1409
  app.put(`${API_PREFIX}/agent-catalog`, async (context) => {
554
1410
  const update = async () => {
555
1411
  try {
556
- return context.json(
557
- await persistAgentCatalogUpdate(filesystem, await context.req.json(), syncAgentAdapters),
1412
+ const persisted = await persistAgentCatalogUpdate(
1413
+ filesystem,
1414
+ await context.req.json(),
1415
+ syncAgentAdapters,
558
1416
  );
1417
+ clearSnapshotCache();
1418
+ return context.json(persisted);
559
1419
  } catch (error) {
560
1420
  if (error instanceof AgentCatalogUpdateError) {
561
1421
  return context.json({ error: error.message }, error.status);
@@ -573,7 +1433,7 @@ export function createDashboardApp(
573
1433
 
574
1434
  app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings`, async (context) => {
575
1435
  try {
576
- return headOrJson(context, await readProjectSettings(filesystem));
1436
+ return headOrJson(context, await readProjectSettings(canonicalFilesystem));
577
1437
  } catch (error) {
578
1438
  return headOrJson(
579
1439
  context,
@@ -583,9 +1443,15 @@ export function createDashboardApp(
583
1443
  }
584
1444
  });
585
1445
 
1446
+ app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings/overview`, async (context) => {
1447
+ return headOrJson(context, await readProjectOverview(canonicalFilesystem));
1448
+ });
1449
+
586
1450
  app.put(`${API_PREFIX}/project-settings`, async (context) => {
587
1451
  try {
588
- return context.json(await persistProjectSettings(filesystem, await context.req.json()));
1452
+ const persisted = await persistProjectSettings(canonicalFilesystem, await context.req.json());
1453
+ clearSnapshotCache();
1454
+ return context.json(persisted);
589
1455
  } catch (error) {
590
1456
  return context.json(
591
1457
  { error: error instanceof Error ? error.message : "Project settings update failed" },
@@ -596,7 +1462,7 @@ export function createDashboardApp(
596
1462
 
597
1463
  app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id`, async (context) => {
598
1464
  try {
599
- const layout = await requireV2Layout(filesystem);
1465
+ const layout = await readLayout();
600
1466
  return headOrJson(context, await readTaskDetail(filesystem, layout, context.req.param("id")));
601
1467
  } catch (error) {
602
1468
  if (error instanceof V2WorkspaceRequiredError) throw error;
@@ -606,7 +1472,7 @@ export function createDashboardApp(
606
1472
 
607
1473
  app.on(["GET", "HEAD"], `${API_PREFIX}/prds/:id`, async (context) => {
608
1474
  try {
609
- const layout = await requireV2Layout(filesystem);
1475
+ const layout = await readLayout();
610
1476
  return headOrJson(context, await readPrdDetail(filesystem, layout, context.req.param("id")));
611
1477
  } catch (error) {
612
1478
  if (error instanceof V2WorkspaceRequiredError) throw error;
@@ -616,7 +1482,7 @@ export function createDashboardApp(
616
1482
 
617
1483
  app.on(["GET", "HEAD"], `${API_PREFIX}/specs/:id`, async (context) => {
618
1484
  try {
619
- const layout = await requireV2Layout(filesystem);
1485
+ const layout = await readLayout();
620
1486
  return headOrJson(context, await readSpecDetail(filesystem, layout, context.req.param("id")));
621
1487
  } catch (error) {
622
1488
  if (error instanceof V2WorkspaceRequiredError) throw error;
@@ -626,8 +1492,14 @@ export function createDashboardApp(
626
1492
 
627
1493
  app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id/trace`, async (context) => {
628
1494
  try {
629
- const layout = await requireV2Layout(filesystem);
630
- return headOrJson(context, await buildTaskTrace(filesystem, layout, context.req.param("id")));
1495
+ const layout = await readLayout();
1496
+ const taskId = context.req.param("id");
1497
+ return headOrJson(
1498
+ context,
1499
+ filesystem instanceof SqliteReadModelFileSystem
1500
+ ? buildSqliteTaskTrace(filesystem, layout, taskId)
1501
+ : await buildTaskTrace(filesystem, layout, taskId),
1502
+ );
631
1503
  } catch (error) {
632
1504
  if (error instanceof V2WorkspaceRequiredError) throw error;
633
1505
  return headOrJson(context, { error: "Task trace not found" }, 404);
@@ -657,57 +1529,28 @@ export function createDashboardApp(
657
1529
  return app;
658
1530
  }
659
1531
 
660
- async function runAgentAdapterSync(workspaceRoot: string): Promise<AgentAdapterSyncResult> {
661
- const cliPath = resolve(workspaceRoot, ".agents", "runtime", "bin", "zaip.mjs");
662
- const commandOptions = {
663
- cwd: workspaceRoot,
664
- encoding: "utf8" as const,
665
- windowsHide: true,
666
- timeout: 120_000,
667
- maxBuffer: 10 * 1_024 * 1_024,
668
- };
669
- await execFileAsync(process.execPath, [cliPath, "adapters", "build", "--json"], commandOptions);
670
- const { stdout } = await execFileAsync(
671
- process.execPath,
672
- [cliPath, "adapters", "check", "--json"],
673
- commandOptions,
674
- );
675
- const result = JSON.parse(stdout) as { valid?: unknown; drifted?: unknown };
676
- return {
677
- valid: result.valid === true,
678
- drifted: Array.isArray(result.drifted)
679
- ? result.drifted.filter((value): value is string => typeof value === "string")
680
- : [],
681
- };
682
- }
683
-
684
- /** Date-only filters in the dashboard represent complete calendar days. */
685
- export function projectionPeriodFilter(context: Pick<Context, "req">): {
686
- from: string | null;
687
- to: string | null;
688
- } {
689
- const from = context.req.query("from") ?? null;
690
- const to = context.req.query("to") ?? null;
691
- return {
692
- from: dateOnlyBoundary(from, "start"),
693
- to: dateOnlyBoundary(to, "end"),
694
- };
695
- }
696
-
697
- function dateOnlyBoundary(value: string | null, boundary: "start" | "end"): string | null {
698
- if (value === null || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
699
- return boundary === "start" ? `${value}T00:00:00.000Z` : `${value}T23:59:59.999Z`;
700
- }
701
-
702
1532
  async function dashboardSnapshot(
703
1533
  filesystem: WorkspaceFileSystem,
704
1534
  workspaceRoot: string,
705
1535
  context: Context,
1536
+ cachedLayout?: WorkspaceLayout,
706
1537
  ) {
707
- const layout = await requireV2Layout(filesystem);
1538
+ const layout = cachedLayout ?? await requireV2Layout(filesystem);
708
1539
  const { from, to } = projectionPeriodFilter(context);
709
- const operations = await buildCanonicalOperationsProjection(filesystem, layout, { from, to });
710
- const costs = await buildCurrentCostProjection(filesystem, operations, selectedCurrency(context));
1540
+ const currency = selectedCurrency(context);
1541
+ const operations =
1542
+ filesystem instanceof SqliteReadModelFileSystem
1543
+ ? await buildFirstFoldQuickProjection(filesystem, layout, { from, to })
1544
+ : await buildCanonicalOperationsProjection(filesystem, layout, { from, to });
1545
+ const costs =
1546
+ filesystem instanceof SqliteReadModelFileSystem
1547
+ ? buildCostSummaryProjection(
1548
+ await firstFoldCostSource(filesystem, { from, to }),
1549
+ operations,
1550
+ currency,
1551
+ currency === "BRL" ? ((await readCurrentFxRate(filesystem))?.rate ?? null) : null,
1552
+ )
1553
+ : await buildCurrentCostProjection(filesystem, operations, currency);
711
1554
  return {
712
1555
  operations,
713
1556
  costs,
@@ -719,81 +1562,75 @@ async function dashboardSnapshot(
719
1562
  };
720
1563
  }
721
1564
 
1565
+ const moduleStartedAt = performance.now();
1566
+ const sessionToken = defaultSessionToken();
1567
+ const runtimeWorkspaceRoot = process.env.ZAIP_WORKSPACE ?? process.cwd();
1568
+
1569
+ let defaultApp: Hono | undefined;
1570
+
722
1571
  /**
723
- * Assemble Operations from the tracked lifecycle records and, when a bounded
724
- * period was requested, the signed InvocationSummary manifests for that same
725
- * period. Runtime projections are deliberately not read or cached here.
1572
+ * Compatibility export for direct module consumers. The executable must not
1573
+ * construct this filesystem-backed app: `main` creates the SQLite app after
1574
+ * its private read model is ready. Construct on first consumer access instead.
726
1575
  */
727
- async function buildCanonicalOperationsProjection(
728
- filesystem: WorkspaceFileSystem,
729
- layout: WorkspaceLayout,
730
- filter: { from: string | null; to: string | null },
731
- overviewOnly = false,
732
- ) {
733
- const operations = await buildOperationsProjection(filesystem, layout, filter, { overviewOnly });
734
- if (overviewOnly || filter.from === null || filter.to === null) return operations;
735
- const summaries = await invocationSummariesForPeriod(filesystem, {
736
- from: filter.from,
737
- to: filter.to,
738
- });
739
- return summaries.length === 0
740
- ? operations
741
- : recalculateOperationsRunSummary({
742
- ...operations,
743
- runs: projectExecutionDurations(mergeManifestRuns(operations.runs, summaries)),
744
- });
1576
+ export function defaultDashboardApp(): Hono {
1577
+ defaultApp ??= createDashboardApp(runtimeWorkspaceRoot, sessionToken);
1578
+ return defaultApp;
745
1579
  }
746
1580
 
747
- async function buildCurrentCostProjection(
748
- filesystem: WorkspaceFileSystem,
749
- operations: Awaited<ReturnType<typeof buildCanonicalOperationsProjection>>,
750
- currency: "BRL" | "USD",
751
- ) {
752
- const pricing = new PricingRepository(filesystem);
753
- const [catalog, fxRate] = await Promise.all([
754
- pricing.readCatalog(),
755
- currency === "BRL" ? pricing.readLatestFxRate() : Promise.resolve(null),
756
- ]);
757
- const agentNames = await configuredAgentNames(filesystem);
758
- return buildCostMonitorProjection(operations, { catalog, fxRate, currency }, agentNames);
759
- }
1581
+ export const app = new Proxy({} as Hono, {
1582
+ get(_target, property) {
1583
+ const value = Reflect.get(defaultDashboardApp(), property);
1584
+ return typeof value === "function" ? value.bind(defaultDashboardApp()) : value;
1585
+ },
1586
+ });
760
1587
 
761
- async function buildManifestFirstCostProjection(
762
- filesystem: WorkspaceFileSystem,
763
- operations: Awaited<ReturnType<typeof buildCanonicalOperationsProjection>>,
764
- context: Context,
765
- ) {
766
- const from = context.req.query("from") ?? null;
767
- const to = context.req.query("to") ?? null;
768
- if (from === null || to === null) {
769
- return buildCurrentCostProjection(filesystem, operations, selectedCurrency(context));
770
- }
771
- const filters = {
772
- providers: queryValues(context, "provider"),
773
- models: queryValues(context, "model"),
774
- zaip_agents: queryValues(context, "agent"),
775
- phases: queryValues(context, "phase"),
776
- statuses: queryValues(context, "status"),
777
- };
778
- const summaries = await invocationSummariesForPeriod(filesystem, { from, to, filters });
779
- if (summaries.length === 0)
780
- return buildCurrentCostProjection(filesystem, operations, selectedCurrency(context));
781
- const runs = projectExecutionDurations(mergeManifestRuns(operations.runs, summaries));
782
- return buildCurrentCostProjection(filesystem, { ...operations, runs }, selectedCurrency(context));
1588
+ function startupPhase(phase: string): void {
1589
+ process.stderr.write(
1590
+ `${JSON.stringify({
1591
+ event: "dashboard_sqlite_startup_phase",
1592
+ phase,
1593
+ elapsed_ms: Math.round(performance.now() - moduleStartedAt),
1594
+ })}\n`,
1595
+ );
783
1596
  }
784
1597
 
785
- const sessionToken = defaultSessionToken();
786
- export const app = createDashboardApp(process.env.ZAIP_WORKSPACE ?? process.cwd(), sessionToken);
787
-
788
- const isMain = process.argv[1]
789
- ? resolve(process.argv[1]) === fileURLToPath(import.meta.url)
790
- : false;
791
-
792
- if (isMain) {
1598
+ async function main(): Promise<void> {
793
1599
  const port = Number.parseInt(process.env.ZAIP_SERVER_PORT ?? "4317", 10);
794
- serve({ fetch: app.fetch, hostname: HOST, port }, ({ port: boundPort }) => {
1600
+ startupPhase("main_entered");
1601
+ const startedAt = performance.now();
1602
+ const readModel = await rebuildSqliteReadModel(runtimeWorkspaceRoot);
1603
+ // The parent launcher captures stderr even when stdout is intentionally
1604
+ // hidden. This makes seed hit/miss and private-copy cost observable without
1605
+ // exposing a shared handle or workspace payload.
1606
+ process.stderr.write(
1607
+ `${JSON.stringify({
1608
+ event: "dashboard_sqlite_read_model",
1609
+ elapsed_ms: Math.round(performance.now() - startedAt),
1610
+ cache: readModel.cache,
1611
+ fingerprint_ms: readModel.fingerprint_ms,
1612
+ materialize_ms: readModel.materialize_ms,
1613
+ source_files: readModel.files,
1614
+ })}\n`,
1615
+ );
1616
+ startupPhase("read_model_ready");
1617
+ const runtimeApp = createDashboardApp(runtimeWorkspaceRoot, sessionToken, undefined, {
1618
+ filesystem: new SqliteReadModelFileSystem(runtimeWorkspaceRoot),
1619
+ snapshotState: readModel.cache === "stale_seed" ? "updating" : "current",
1620
+ });
1621
+ startupPhase("runtime_app_ready");
1622
+ serve({ fetch: runtimeApp.fetch, hostname: HOST, port }, ({ port: boundPort }) => {
1623
+ startupPhase("listening");
795
1624
  process.stdout.write(
796
1625
  `ZAIP-AIOS dashboard API: http://${HOST}:${boundPort}${API_PREFIX}/health\n`,
797
1626
  );
798
1627
  });
799
1628
  }
1629
+
1630
+ if (
1631
+ process.argv[1]?.endsWith("server.cjs") ||
1632
+ process.argv[1]?.endsWith("server.js") ||
1633
+ process.argv[1]?.endsWith("server.mjs")
1634
+ ) {
1635
+ void main();
1636
+ }