@zaip-ai/cli 1.0.7 → 1.3.0

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 (444) 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 +39 -4
  10. package/dist/services/agent-adapters.js.map +1 -1
  11. package/dist/services/agent-adapters.test.js +13 -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 +7 -0
  45. package/dist/services/template-manifest.js.map +1 -1
  46. package/package.json +1 -1
  47. package/templates/.agents/architecture/software-architecture.md +131 -0
  48. package/templates/.agents/branding/brandbook.docx +0 -0
  49. package/templates/.agents/branding/brandbook.md +60 -0
  50. package/templates/.agents/config/agents/catalog.yaml +310 -10
  51. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +30 -0
  52. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +5 -5
  53. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +2 -2
  54. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +30 -0
  55. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +30 -0
  56. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +30 -0
  57. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +8 -8
  58. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +5 -3
  59. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +30 -0
  60. package/templates/.agents/config/project.yaml +1 -1
  61. package/templates/.agents/dashboard/.tmp-run-list.json +254 -0
  62. package/templates/.agents/dashboard/.zaip-managed.json +775 -163
  63. package/templates/.agents/dashboard/DATA-DICTIONARY.md +2227 -0
  64. package/templates/.agents/dashboard/README.md +48 -97
  65. package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-DxR_pgSV.js +1 -0
  66. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-4jZLTWE_.js +2156 -0
  67. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CKcsqb-r.js +1 -0
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BomxqCBU.css +1 -0
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-DmmJD2Th.js +3 -0
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.js +1 -0
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-CaKwQP43.css +1 -0
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +1 -0
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-N5Csr0RP.js +68 -0
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-C98LGC-Q.js → dashboard-localization-CLJ0lT9v.js} +1 -1
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/icons-vendor-C0fj2uU9.js +1 -0
  76. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.js +2 -0
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +1 -0
  78. package/templates/.agents/dashboard/apps/frontend/dist/assets/react-vendor-BEsJ-FYH.js +9 -0
  79. package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-D9-fqq9M.js +1 -0
  80. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-B6y6Gw12.js +1 -0
  81. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DFY7ZDlu.css +1 -0
  82. package/templates/.agents/dashboard/apps/frontend/dist/index.html +9 -2
  83. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  84. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +1129 -600
  85. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +48 -0
  86. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingPanels.tsx +2 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +247 -0
  88. package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +95 -0
  89. package/templates/.agents/dashboard/apps/frontend/src/AppSetupRoutes.tsx +87 -0
  90. package/templates/.agents/dashboard/apps/frontend/src/CostCalculationGuidePage.tsx +641 -0
  91. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +159 -0
  92. package/templates/.agents/dashboard/apps/frontend/src/DashboardCapabilityNav.tsx +13 -13
  93. package/templates/.agents/dashboard/apps/frontend/src/DashboardGlobalSettings.tsx +56 -0
  94. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +266 -315
  95. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +151 -0
  96. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +37 -0
  97. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +114 -12
  98. package/templates/.agents/dashboard/apps/frontend/src/app-preferences.ts +15 -0
  99. package/templates/.agents/dashboard/apps/frontend/src/app-session-token.ts +20 -0
  100. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.css +267 -0
  101. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.test.ts +69 -0
  102. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +85 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +133 -0
  104. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +453 -13
  105. package/templates/.agents/dashboard/apps/frontend/src/dashboard-localization.ts +1 -1
  106. package/templates/.agents/dashboard/apps/frontend/src/main.tsx +1 -0
  107. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.test.ts +55 -0
  108. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.ts +36 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.test.ts +126 -0
  110. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.ts +181 -0
  111. package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +138 -0
  112. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +105 -0
  113. package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +6 -3
  114. package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +9 -5
  115. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +26 -0
  116. package/templates/.agents/dashboard/apps/frontend/src/period-transition-overlay.css +44 -0
  117. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +121 -62
  118. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +482 -3
  119. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +84 -17
  120. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +78 -0
  121. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +234 -39
  122. package/templates/.agents/dashboard/apps/frontend/src/snapshot-notice.ts +15 -0
  123. package/templates/.agents/dashboard/apps/frontend/src/sqlite-cost-adapters.ts +26 -0
  124. package/templates/.agents/dashboard/apps/frontend/src/styles.css +14 -2
  125. package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +38 -2
  126. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +253 -0
  127. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +380 -0
  128. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +175 -0
  129. package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +395 -0
  130. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +141 -0
  131. package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +45 -0
  132. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +46 -0
  133. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +36 -0
  134. package/templates/.agents/dashboard/apps/frontend/vite.config.ts +58 -5
  135. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  136. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +47 -6
  137. package/templates/.agents/dashboard/modules/cost-monitor/src/duration-estimate.ts +13 -0
  138. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +134 -0
  139. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +290 -0
  140. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +133 -19
  141. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +134 -43
  142. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +137 -19
  143. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.test.ts +2 -0
  144. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +13 -10
  145. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +328 -0
  146. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +302 -6
  147. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +627 -400
  148. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostSummary.tsx +5 -13
  149. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +370 -72
  150. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +467 -326
  151. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.test.ts +17 -0
  152. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.tsx +25 -6
  153. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +0 -2
  154. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-helpers.ts +17 -0
  155. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-responsive.css +6 -3
  156. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-row-filters.ts +123 -0
  157. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-summary.ts +8 -3
  158. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +32 -0
  159. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +55 -14
  160. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-presentation.ts +121 -0
  161. package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +3 -0
  162. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.test.ts +68 -0
  163. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +33 -5
  164. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +119 -0
  165. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +12 -0
  166. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +146 -0
  167. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +118 -0
  168. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.test.ts +2 -2
  169. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +5 -3
  170. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-pricing-fallback.test.ts +53 -0
  171. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.basics.test.ts +34 -0
  172. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +168 -161
  173. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +188 -29
  174. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +757 -0
  175. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +784 -0
  176. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +100 -0
  177. package/templates/.agents/dashboard/modules/cost-monitor/src/server/index.ts +1 -0
  178. package/templates/.agents/dashboard/modules/cost-monitor/src/server/refactor-map-xr9l.json +61 -0
  179. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-monitor-projection-agents.test.ts +31 -0
  180. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-summary-projection-duration.test.ts +414 -0
  181. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.test.ts +41 -0
  182. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.ts +55 -11
  183. package/templates/.agents/dashboard/modules/operations/package.json +2 -1
  184. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +185 -0
  185. package/templates/.agents/dashboard/modules/operations/src/duration-population.test.ts +63 -1
  186. package/templates/.agents/dashboard/modules/operations/src/duration-population.ts +60 -5
  187. package/templates/.agents/dashboard/modules/operations/src/react/EntityDetailsDialog.tsx +14 -3
  188. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.test.ts +44 -1
  189. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +345 -18
  190. package/templates/.agents/dashboard/modules/operations/src/react/MacroPlanningDialog.tsx +1 -0
  191. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.test.ts +9 -5
  192. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.tsx +2 -2
  193. package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +168 -0
  194. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +95 -6
  195. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +367 -14
  196. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +562 -131
  197. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +353 -355
  198. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +65 -0
  199. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +169 -0
  200. package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +73 -8
  201. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.test.tsx +32 -0
  202. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.tsx +62 -6
  203. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.test.ts +55 -0
  204. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.tsx +27 -19
  205. package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +105 -42
  206. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.priority.test.ts +19 -0
  207. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +265 -36
  208. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +171 -8
  209. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +98 -68
  210. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.test.ts +72 -1
  211. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +78 -17
  212. package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +6 -4
  213. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +431 -1
  214. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +574 -20
  215. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +42 -4
  216. package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +248 -0
  217. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog-content.css +49 -0
  218. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog.css +93 -50
  219. package/templates/.agents/dashboard/modules/operations/src/react/manual-work-priority.test.ts +82 -0
  220. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +29 -2
  221. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +22 -1
  222. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +77 -4
  223. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +67 -5
  224. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +54 -0
  225. package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +22 -0
  226. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +149 -12
  227. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +27 -0
  228. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +176 -0
  229. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.test.ts +160 -0
  230. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.ts +47 -0
  231. package/templates/.agents/dashboard/modules/operations/src/react/operations-query.ts +10 -0
  232. package/templates/.agents/dashboard/modules/operations/src/react/operations-responsive.css +3 -0
  233. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-labels.ts +28 -0
  234. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +21 -7
  235. package/templates/.agents/dashboard/modules/operations/src/react/operations.css +42 -0
  236. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.test.ts +54 -0
  237. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.ts +32 -0
  238. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +241 -6
  239. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +148 -6
  240. package/templates/.agents/dashboard/modules/operations/src/react/popup-localization.test.ts +1 -1
  241. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css +132 -0
  242. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css.test.ts +13 -0
  243. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.header.test.ts +16 -0
  244. package/templates/.agents/dashboard/modules/operations/src/react/use-infinite-column.ts +11 -5
  245. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +7 -4
  246. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +5 -84
  247. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.test.ts +291 -15
  248. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.ts +182 -472
  249. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.test.ts +40 -0
  250. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.ts +19 -6
  251. package/templates/.agents/dashboard/modules/operations/src/server/canonical-run-projection.ts +31 -6
  252. package/templates/.agents/dashboard/modules/operations/src/server/daily-duration-projection.ts +220 -0
  253. package/templates/.agents/dashboard/modules/operations/src/server/execution-duration-projection.ts +28 -0
  254. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +39 -2
  255. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +25 -13
  256. package/templates/.agents/dashboard/modules/operations/src/server/filter.test.ts +3 -3
  257. package/templates/.agents/dashboard/modules/operations/src/server/filter.ts +7 -2
  258. package/templates/.agents/dashboard/modules/operations/src/server/index.ts +3 -0
  259. package/templates/.agents/dashboard/modules/operations/src/server/macro.ts +3 -12
  260. package/templates/.agents/dashboard/modules/operations/src/server/manifest-usage.ts +65 -0
  261. package/templates/.agents/dashboard/modules/operations/src/server/operations-projection-aggregates.ts +297 -0
  262. package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +330 -0
  263. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.test.ts +505 -0
  264. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.ts +609 -0
  265. package/templates/.agents/dashboard/modules/operations/src/server/runs.ts +18 -19
  266. package/templates/.agents/dashboard/modules/operations/src/work-board-model.ts +76 -0
  267. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  268. package/templates/.agents/dashboard/modules/traceability/src/server/build-task-trace.ts +3 -1
  269. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  270. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  271. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  272. package/templates/.agents/dashboard/packages/contracts/src/index.ts +2 -0
  273. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  274. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +1759 -0
  275. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +7333 -0
  276. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1611 -0
  277. package/templates/.agents/dashboard/packages/local-runtime/package.json +8 -3
  278. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +20 -0
  279. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +114 -0
  280. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +31 -0
  281. package/templates/.agents/dashboard/packages/local-runtime/scripts/source-size-runtime-scripts/sqlite-read-model-fixture.mjs +397 -0
  282. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +589 -0
  283. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +53 -0
  284. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +203 -0
  285. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1646 -107
  286. package/templates/.agents/dashboard/packages/local-runtime/src/agent-adapter-sync.ts +30 -0
  287. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.test.ts +3 -2
  288. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.ts +5 -0
  289. package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +72 -33
  290. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +339 -4
  291. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +392 -1
  292. package/templates/.agents/dashboard/packages/local-runtime/src/runtime.mjs +5 -5
  293. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +110 -0
  294. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +189 -0
  295. package/templates/.agents/dashboard/packages/local-runtime/src/server-cross-layer-parity.test.ts +245 -0
  296. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models-route.ts +64 -0
  297. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.test.ts +204 -0
  298. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.ts +194 -0
  299. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.test.ts +163 -0
  300. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.ts +354 -0
  301. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.test.ts +74 -0
  302. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.ts +113 -0
  303. package/templates/.agents/dashboard/packages/local-runtime/src/server-overview.test.ts +68 -0
  304. package/templates/.agents/dashboard/packages/local-runtime/src/server-period.ts +47 -0
  305. package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +483 -0
  306. package/templates/.agents/dashboard/packages/local-runtime/src/server-settings.test.ts +5 -2
  307. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +178 -84
  308. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +149 -0
  309. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +675 -267
  310. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +1212 -309
  311. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.test.ts +53 -0
  312. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +722 -0
  313. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +3743 -0
  314. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +7017 -0
  315. package/templates/.agents/dashboard/packages/local-runtime/src/work-priority-contract.test.ts +33 -0
  316. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-history.mjs +41 -0
  317. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-processes.mjs +210 -0
  318. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +514 -190
  319. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +324 -24
  320. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  321. package/templates/.agents/dashboard/packages/windows-launcher/LauncherVerificationBehaviorTests.cs +206 -0
  322. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +95 -8
  323. package/templates/.agents/dashboard/packages/windows-launcher/build-dashboard-launcher.ps1 +2 -2
  324. package/templates/.agents/dashboard/packages/windows-launcher/run-launcher-verification-tests.ps1 +22 -0
  325. package/templates/.agents/dashboard/read-model.dbml +2201 -0
  326. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  327. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +1 -1
  328. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +216 -41
  329. package/templates/.agents/dashboard/work-priority.json +34 -0
  330. package/templates/.agents/dashboard/zaip-dashboard-linux.sh +45 -0
  331. package/templates/.agents/dashboard/zaip-dashboard-mac.command +3 -0
  332. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  333. package/templates/.agents/design/.zaip-managed.json +3 -3
  334. package/templates/.agents/design/apps/catalog/package.json +1 -1
  335. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  336. package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +169 -0
  337. package/templates/.agents/growth/strategy.md +27 -0
  338. package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +197 -0
  339. package/templates/.agents/marketing/strategy.md +29 -0
  340. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  341. package/templates/.agents/requirements/comparative-audit.md +1 -1
  342. package/templates/.agents/requirements/context-engine.md +1 -1
  343. package/templates/.agents/requirements/cost-observability.md +1 -1
  344. package/templates/.agents/requirements/dashboard-launch.md +24 -0
  345. package/templates/.agents/requirements/data-export.md +1 -1
  346. package/templates/.agents/requirements/design-system.md +1 -1
  347. package/templates/.agents/requirements/execution-engine.md +1 -1
  348. package/templates/.agents/requirements/kanban-dashboard.md +4 -3
  349. package/templates/.agents/requirements/model-orchestration.md +1 -1
  350. package/templates/.agents/requirements/public-distribution.md +9 -6
  351. package/templates/.agents/requirements/software-architecture.md +1 -1
  352. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  353. package/templates/.agents/requirements/work-lifecycle.md +9 -3
  354. package/templates/.agents/requirements/zaip-strategic-95.md +4 -4
  355. package/templates/.agents/rules/autonomy-decision.md +15 -0
  356. package/templates/.agents/rules/core.md +1 -1
  357. package/templates/.agents/rules/run-lifecycle-closure.md +1 -1
  358. package/templates/.agents/rules/specialist-routing.md +19 -0
  359. package/templates/.agents/rules/workflow.md +1 -1
  360. package/templates/.agents/runtime/VERSION +1 -1
  361. package/templates/.agents/runtime/bin/zaip.mjs +118 -67
  362. package/templates/.agents/runtime/checksums.sha256 +6 -4
  363. package/templates/.agents/runtime/manifest.json +3 -1
  364. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -0
  365. package/templates/.agents/runtime/provenance.json +11 -0
  366. package/templates/.agents/runtime/sbom.cdx.json +1929 -39
  367. package/templates/.agents/sales/playbook.md +32 -0
  368. package/templates/.agents/sales/roteiro-comercial-zaip.md +252 -0
  369. package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
  370. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +22 -0
  371. package/templates/.agents/skills/zaip-skill-brand-governance/agents/openai.yaml +4 -0
  372. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +39 -0
  373. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +30 -0
  374. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +25 -0
  375. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/agents/openai.yaml +4 -0
  376. package/templates/.agents/skills/zaip-skill-enforce-architecture/SKILL.md +2 -0
  377. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/SKILL.md +18 -0
  378. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/agents/openai.yaml +6 -0
  379. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.mjs +71 -0
  380. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.test.mjs +60 -0
  381. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/scheduled-ready-queue-contract.test.mjs +111 -0
  382. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +2 -2
  383. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +3 -1
  384. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +12 -0
  385. package/templates/.agents/skills/zaip-skill-general-operations/agents/openai.yaml +4 -0
  386. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +9 -7
  387. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  388. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +11 -38
  389. package/templates/.agents/skills/zaip-skill-governance-compliance/SKILL.md +12 -0
  390. package/templates/.agents/skills/zaip-skill-governance-compliance/agents/openai.yaml +4 -0
  391. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +21 -0
  392. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/agents/openai.yaml +4 -0
  393. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/references/growth-revops-framework.md +25 -0
  394. package/templates/.agents/skills/zaip-skill-instalar-agendamento/SKILL.md +17 -0
  395. package/templates/.agents/skills/zaip-skill-instalar-agendamento/agents/openai.yaml +6 -0
  396. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +9 -3
  397. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +99 -93
  398. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +132 -23
  399. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +149 -1
  400. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +76 -19
  401. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +217 -7
  402. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +77 -0
  403. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +20 -0
  404. package/templates/.agents/skills/zaip-skill-sales-methodology/agents/openai.yaml +4 -0
  405. package/templates/.agents/skills/zaip-skill-sales-methodology/references/sales-process-framework.md +77 -0
  406. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -0
  407. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +96 -0
  408. package/templates/.agents/skills/zaip-skill-sqlite-read-model/agents/openai.yaml +4 -0
  409. package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +41 -0
  410. package/templates/.agents/zaip-aios/INDEX.md +11 -0
  411. package/templates/.agents/zaip-aios/README.md +38 -8
  412. package/templates/.agents/zaip-aios/VERSION +1 -1
  413. package/templates/.agents/zaip-aios/guides/09-agentes-e-squads.md +43 -6
  414. package/templates/.agents/zaip-aios/guides/10-seguranca.md +21 -7
  415. package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +32 -0
  416. package/templates/.agents/zaip-aios/guides/18-catalogo-de-agentes.md +76 -0
  417. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +72 -0
  418. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +330 -0
  419. package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +52 -0
  420. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +69 -0
  421. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +483 -0
  422. package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +85 -0
  423. package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +18 -0
  424. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +44 -0
  425. package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +167 -0
  426. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +144 -0
  427. package/templates/.agents/zaip-aios/guides/assets/diagrams/lifecycles-task-spec-prd.png +0 -0
  428. package/templates/.agents/zaip-aios/guides/assets/diagrams/prd-lifecycle.png +0 -0
  429. package/templates/.agents/zaip-aios/guides/assets/diagrams/spec-lifecycle.png +0 -0
  430. package/templates/.agents/zaip-aios/guides/assets/diagrams/task-lifecycle.png +0 -0
  431. package/templates/template-manifest.json +1082 -215
  432. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-CLYIi7Vo.js +0 -1
  433. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-DTDNVQGX.css +0 -1
  434. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-Cv-JcoxP.js +0 -1320
  435. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CzEggcRb.js +0 -1
  436. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-05YKn6Z4.js +0 -3
  437. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-YO3yqtgV.css +0 -1
  438. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8AzCnl3a.js +0 -77
  439. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DdBYQ_8D.css +0 -1
  440. package/templates/.agents/dashboard/apps/frontend/src/AgentCatalogSettingsPage.tsx +0 -617
  441. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings-page.css +0 -660
  442. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings.test.ts +0 -249
  443. package/templates/.agents/dashboard/dashboard.config.json +0 -4
  444. package/templates/.agents/dashboard/zaip-dashboard.exe +0 -0
@@ -1,49 +1,126 @@
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 CompactRunMetric, LifecycleManifestStore } from "@zaip-ai/lifecycle-manifests";
21
28
  import { type Context, Hono } from "hono";
22
29
  import { getCookie, setCookie } from "hono/cookie";
23
30
  import type { ContentfulStatusCode } from "hono/utils/http-status";
31
+ import type { TaskTrace, TraceDocument } from "../../../modules/traceability/src/contracts.js";
32
+ import { runAgentAdapterSync } from "./agent-adapter-sync.js";
24
33
  import {
25
34
  type AgentAdapterSyncResult,
26
35
  AgentCatalogUpdateError,
27
36
  persistAgentCatalogUpdate,
28
37
  readAgentCatalogSettings,
29
38
  } from "./agent-catalog-settings.js";
30
- import { mergeManifestRuns } from "./manifest-runs.js";
31
- import { persistProjectSettings, readProjectSettings } from "./project-settings.js";
32
39
  import {
33
- configuredAgentNames,
34
- invocationSummariesForPeriod,
35
- queryValues,
36
- selectedCurrency,
37
- } from "./server-summary-helpers.js";
40
+ persistProjectSettings,
41
+ readProjectOverview,
42
+ readProjectSettings,
43
+ } from "./project-settings.js";
44
+ import {
45
+ type BoardClassification,
46
+ boardFilters,
47
+ boardPage,
48
+ boardStage,
49
+ classificationPage,
50
+ } from "./server-board.js";
51
+ import { registerDailyReadModelRoute } from "./server-daily-read-models-route.js";
52
+ import {
53
+ changeEntityPageRows,
54
+ decisionEntityPageRows,
55
+ filterLifecyclePageRows,
56
+ operationsPageFilters,
57
+ } from "./server-operations-pages.js";
58
+ import { registerOperationsRunsRoute } from "./server-operations-runs-route.js";
59
+ import { projectionPeriodFilter } from "./server-period.js";
60
+ import {
61
+ buildCanonicalOperationsProjection,
62
+ buildCurrentCostProjection,
63
+ buildManifestCostRunPage,
64
+ buildManifestFirstCostProjection,
65
+ filterCostRunRows,
66
+ isUnfilteredCostRequest,
67
+ knownWorkIds,
68
+ normalizeWorkPriority,
69
+ pageCostRunRows,
70
+ readCurrentFxRate,
71
+ readWorkPriority,
72
+ } from "./server-projections.js";
73
+ import { queryValues, selectedCurrency } from "./server-summary-helpers.js";
74
+ import { dashboardIdentity, dashboardWorkspaceKey, reconcileOpenWork } from "./server-workspace.js";
75
+ import {
76
+ rebuildSqliteReadModel,
77
+ SqliteReadModelFileSystem,
78
+ sqliteViewMetadata,
79
+ } from "./sqlite-read-model.js";
38
80
 
39
81
  export { manifestRun, mergeManifestRuns } from "./manifest-runs.js";
82
+ export { projectionPeriodFilter } from "./server-period.js";
83
+ export {
84
+ dashboardWorkspaceKey,
85
+ githubRepositoryFromRemote,
86
+ type ReconciliationItem,
87
+ type ReconciliationResult,
88
+ reconciliationResult,
89
+ } from "./server-workspace.js";
90
+ export {
91
+ rebuildSqliteReadModel,
92
+ SqliteReadModelFileSystem,
93
+ validateSqliteReadModelSchema,
94
+ } from "./sqlite-read-model.js";
95
+
96
+ type AllTimeTopTaskFact = {
97
+ task_id: string;
98
+ total_duration_ms: number | null;
99
+ total_tokens: number | null;
100
+ total_cost_usd: number | null;
101
+ total_cost_brl: number | null;
102
+ total_run_count: number;
103
+ };
104
+
105
+ export function projectAllTimeTopTasks(
106
+ tasks: readonly AllTimeTopTaskFact[],
107
+ ): NonNullable<NonNullable<OperationsProjection["summary"]["overview_metrics"]>["top_tasks"]> {
108
+ return tasks.map((task) => ({
109
+ task_id: task.task_id,
110
+ duration_ms: task.total_duration_ms,
111
+ total_tokens: task.total_tokens,
112
+ total_cost_usd: task.total_cost_usd,
113
+ cost_brl: task.total_cost_brl,
114
+ run_count: task.total_run_count,
115
+ }));
116
+ }
40
117
 
41
118
  const API_PREFIX = "/api/v2";
42
119
  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);
120
+ const RUNTIME_KIND = "zaip-dashboard-sqlite-runtime";
121
+ const SESSION_COOKIE = "zaip-dashboard-sqlite-session";
122
+ const WORK_PRIORITY_PATH = ".agents/dashboard/work-priority.json";
123
+ const SNAPSHOT_CACHE_TTL_MS = 5_000;
47
124
  const agentPortraitFiles: Record<string, string> = {
48
125
  "agent-orchestrator": "1 - Dave - Product Owner.png",
49
126
  "agent-builder": "2 - Rafa - Tech Lead.png",
@@ -87,6 +164,13 @@ export function openWorkIds(items: readonly ActiveWorkItem[]): string[] {
87
164
 
88
165
  interface DashboardAppOptions {
89
166
  syncAgentAdapters?: () => Promise<AgentAdapterSyncResult>;
167
+ filesystem?: WorkspaceFileSystem;
168
+ canonicalFilesystem?: WorkspaceFileSystem;
169
+ /** Test seam for the disposable SQLite serving mode. */
170
+ snapshotReadOnly?: boolean;
171
+ reconcileOpenWork?: typeof reconcileOpenWork;
172
+ refreshReadModel?: () => Promise<void>;
173
+ snapshotState?: "current" | "updating";
90
174
  }
91
175
 
92
176
  class V2WorkspaceRequiredError extends Error {
@@ -126,164 +210,520 @@ function shouldBootstrapBrowserSession(context: Context): boolean {
126
210
  return context.req.header("sec-fetch-site") !== "cross-site";
127
211
  }
128
212
 
129
- async function dashboardIdentity(
213
+ async function requireV2Layout(filesystem: WorkspaceFileSystem): Promise<WorkspaceLayout> {
214
+ const layout = await loadWorkspaceLayout(filesystem);
215
+ if (layout.version === 1) {
216
+ throw new V2WorkspaceRequiredError("Dashboard API requires a modern ZAIP-AIOS workspace");
217
+ }
218
+ return layout;
219
+ }
220
+
221
+ function headOrJson(
222
+ context: Context,
223
+ payload: unknown,
224
+ status: ContentfulStatusCode = 200,
225
+ cacheable = false,
226
+ ): Response {
227
+ if (cacheable) {
228
+ const etag = `"${createHash("sha256").update(JSON.stringify(payload)).digest("base64url")}"`;
229
+ context.header("ETag", etag);
230
+ context.header("Cache-Control", "private, no-cache");
231
+ if (context.req.header("if-none-match") === etag) return context.body(null, 304);
232
+ }
233
+ if (context.req.method === "HEAD") return context.body(null, status);
234
+ return context.json(payload, status);
235
+ }
236
+
237
+ /**
238
+ * Keep the entity/macro details on the snapshot filesystem, while making the
239
+ * first-fold counters and token/cost facts come from SQL.
240
+ * Duration intentionally retains the shared hybrid legacy policy in the
241
+ * Operations module; its underlying daily rows are still SQLite-backed.
242
+ */
243
+ async function buildFirstFoldQuickProjection(
130
244
  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
- }
245
+ layout: WorkspaceLayout,
246
+ filter: { from: string | null; to: string | null },
247
+ options: {
248
+ includeBoardTasks?: boolean;
249
+ includeExtended?: boolean;
250
+ includePeriods?: boolean;
251
+ includeTopTasks?: boolean;
252
+ } = {},
253
+ ): Promise<OperationsProjection> {
254
+ // The comparison runtime deliberately has no lifecycle daily aggregates in
255
+ // its snapshot. Its immutable filesystem contains only canonical records,
256
+ // while the SQL views supply the first-fold facts. Never enter the legacy
257
+ // quick projection here: it requires lifecycle daily read models and would
258
+ // make an otherwise valid SQLite dashboard fail on first paint.
259
+ const projection =
260
+ filesystem instanceof SqliteReadModelFileSystem
261
+ ? buildSqliteOperationsProjection(filesystem, layout, filter, options)
262
+ : await buildQuickOperationsProjection(filesystem, layout, filter, options);
263
+ if (!(filesystem instanceof SqliteReadModelFileSystem)) return projection;
264
+ const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
265
+ const aggregate =
266
+ filter.from === null && filter.to === null
267
+ ? filesystem.firstFoldViewportAllTime().aggregate
268
+ : (cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter));
269
+ return {
270
+ ...projection,
271
+ summary: {
272
+ ...projection.summary,
273
+ run_count: aggregate.total_run_count,
274
+ overview_metrics: {
275
+ ...projection.summary.overview_metrics,
276
+ duration_ms: aggregate.duration_ms,
277
+ tokens: aggregate.total_tokens,
278
+ cost_brl: aggregate.cost_brl,
279
+ execution_day_count: aggregate.execution_day_count,
280
+ data_source: "run_records",
281
+ },
282
+ },
283
+ };
284
+ }
285
+
286
+ /**
287
+ * SQLite's files snapshot intentionally excludes derived lifecycle manifests.
288
+ * Build the Operations first fold from its canonical SQL views instead of
289
+ * treating a missing manifest as a broken workspace.
290
+ */
291
+ function buildSqliteOperationsProjection(
292
+ filesystem: SqliteReadModelFileSystem,
293
+ layout: WorkspaceLayout,
294
+ filter: { from: string | null; to: string | null },
295
+ options: {
296
+ includeBoardTasks?: boolean;
297
+ includeExtended?: boolean;
298
+ includePeriods?: boolean;
299
+ includeTopTasks?: boolean;
300
+ },
301
+ ): OperationsProjection {
302
+ const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
303
+ const fullAllTime =
304
+ filter.from === null && filter.to === null
305
+ ? options.includeExtended === true
306
+ ? filesystem.firstFoldAllTime()
307
+ : null
308
+ : null;
309
+ const allTime =
310
+ fullAllTime ??
311
+ (filter.from === null && filter.to === null ? filesystem.firstFoldViewportAllTime() : null);
312
+ const page = <T extends "task" | "spec" | "prd">(kind: T) =>
313
+ cachedMonthlyQuick?.pages[kind] ?? filesystem.summaryPage(kind, filter, 0, 10_000).rows;
314
+ const includeEntities = allTime === null || options.includeBoardTasks === true;
315
+ const tasks = includeEntities ? page("task") : [];
316
+ const specs = includeEntities ? page("spec") : [];
317
+ const prds = includeEntities ? page("prd") : [];
318
+ const aggregate =
319
+ allTime?.aggregate ?? cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter);
320
+ const taskCount = allTime?.aggregate.task_count ?? tasks.length;
321
+ const allTimeTopTasks =
322
+ filter.from === null &&
323
+ filter.to === null &&
324
+ (fullAllTime !== null || options.includeTopTasks === true)
325
+ ? (fullAllTime?.top_tasks ?? filesystem.firstFoldTopTasksAllTime())
326
+ : null;
327
+ const topTasks =
328
+ allTimeTopTasks === null
329
+ ? (cachedMonthlyQuick?.top_tasks ?? filesystem.topTasks(filter)).map((task) => ({
330
+ task_id: task.task_id,
331
+ duration_ms: task.duration_ms,
332
+ total_tokens: task.total_tokens,
333
+ cost_brl: task.cost_brl,
334
+ run_count: task.run_count,
335
+ }))
336
+ : projectAllTimeTopTasks(allTimeTopTasks);
337
+ const macroMetrics =
338
+ fullAllTime === null
339
+ ? undefined
340
+ : (Object.fromEntries(
341
+ (["specs", "prds"] as const).map((kind) => {
342
+ const metric = fullAllTime.macro_metrics[kind];
343
+ return [
344
+ kind,
345
+ {
346
+ status_counts: metric.status_counts,
347
+ classification_counts: metric.classification_counts,
348
+ entity_count: metric.entity_count,
349
+ total_run_count: metric.total_run_count,
350
+ total_duration_ms: metric.total_duration_ms,
351
+ total_tokens: metric.total_tokens,
352
+ total_cost_usd: metric.total_cost_usd,
353
+ total_cost_brl: metric.total_cost_brl,
354
+ average_duration_ms: metric.average_duration_ms,
355
+ average_tokens: metric.average_tokens,
356
+ average_cost_usd: metric.average_cost_usd,
357
+ average_cost_brl: metric.average_cost_brl,
358
+ latest_created_at: metric.latest_created_at,
359
+ top_entities: metric.top_entities.map((entity) => ({
360
+ entity_id: entity.entity_id,
361
+ title: entity.title,
362
+ duration_ms: entity.total_duration_ms,
363
+ total_tokens: entity.total_tokens,
364
+ total_cost_usd: entity.total_cost_usd,
365
+ cost_brl: entity.total_cost_brl,
366
+ run_count: entity.total_run_count,
367
+ duration_share: entity.duration_share,
368
+ })),
369
+ },
370
+ ];
371
+ }),
372
+ ) as NonNullable<
373
+ NonNullable<OperationsProjection["summary"]["overview_metrics"]>["macro_metrics"]
374
+ >);
375
+ const statuses: Record<string, number> = allTime?.task_status_counts ?? {};
376
+ const risks: Record<string, number> = allTime?.task_risk_counts ?? {};
377
+ if (allTime === null) {
378
+ for (const task of tasks) {
379
+ const status = typeof task.status === "string" ? task.status : "draft";
380
+ const risk = typeof task.risk === "string" ? task.risk : "standard";
381
+ statuses[status] = (statuses[status] ?? 0) + 1;
382
+ risks[risk] = (risks[risk] ?? 0) + 1;
145
383
  }
146
- } catch {
147
- // A configuração visual nunca deve impedir a inicialização do dashboard.
148
384
  }
149
- if (repositorySetting.toLowerCase() !== "auto") {
385
+ const latest =
386
+ allTime?.aggregate.latest_task_created_at ??
387
+ [...tasks, ...specs, ...prds]
388
+ .map((row) => (typeof row.occurred_at === "string" ? row.occurred_at : null))
389
+ .filter((value): value is string => value !== null)
390
+ .sort()
391
+ .at(-1) ??
392
+ null;
393
+ const unavailableUsage = {
394
+ events: 0,
395
+ tokens: { input: null, cached_input: null, output: null, reasoning: null, total: null },
396
+ cost: { usd: null, brl: null },
397
+ coverage: { events: 0, tokens: 0, usd: 0, brl: 0 },
398
+ };
399
+ const totalUsage = {
400
+ ...unavailableUsage,
401
+ tokens: { ...unavailableUsage.tokens, total: aggregate.total_tokens },
402
+ cost: { ...unavailableUsage.cost, brl: aggregate.cost_brl },
403
+ };
404
+ const usage = {
405
+ conversation: unavailableUsage,
406
+ planning: unavailableUsage,
407
+ execution: unavailableUsage,
408
+ total: totalUsage,
409
+ new_work: unavailableUsage,
410
+ rework: unavailableUsage,
411
+ };
412
+ const dailyPeriods = options.includePeriods === true ? filesystem.dailyPeriods(filter) : {};
413
+ const hasUnknownUsage = Object.values(dailyPeriods).some(
414
+ (period) => period.usage.total_tokens.unknown > 0 || period.usage.cost.unknown > 0,
415
+ );
416
+ const taskProjection = (row: Record<string, unknown>) => ({
417
+ schema_version: 2 as const,
418
+ id: String(row.id),
419
+ revision: 1,
420
+ change_id: "",
421
+ title: String(row.title ?? row.id),
422
+ short_name: String(row.title ?? row.id),
423
+ objective: "",
424
+ status: normalizeTaskStatus(row.status),
425
+ attempt_count: null,
426
+ risk: normalizeRisk(row.risk),
427
+ priority: "medium" as const,
428
+ owner: null,
429
+ requirement_ids: [],
430
+ acceptance_ids: [],
431
+ depends_on: [],
432
+ write_scopes: [],
433
+ created_at: String(row.occurred_at),
434
+ updated_at: String(row.occurred_at),
435
+ started_at: null,
436
+ finished_at: null,
437
+ result: null,
438
+ artifact_path: ".agents/records",
439
+ progress: 0,
440
+ spec_id: null,
441
+ execution_started: false,
442
+ telemetry: {
443
+ duration_ms: nullableMetric(row.total_duration),
444
+ tokens: nullableMetric(row.total_tokens),
445
+ cost_brl: nullableMetric(row.total_cost_brl),
446
+ },
447
+ });
448
+ const macroProjection = (row: Record<string, unknown>, kind: "prd" | "spec") => {
449
+ const entityUsage = {
450
+ ...unavailableUsage,
451
+ tokens: { ...unavailableUsage.tokens, total: nullableMetric(row.total_tokens) },
452
+ cost: { ...unavailableUsage.cost, brl: nullableMetric(row.total_cost_brl) },
453
+ };
150
454
  return {
151
- title,
152
- repository_name: githubRepositoryFromRemote(repositorySetting) ?? "",
455
+ id: String(row.id),
456
+ title: String(row.title ?? row.id),
457
+ created_at: String(row.occurred_at),
458
+ updated_at: String(row.occurred_at),
459
+ kind,
460
+ parent_id: null,
461
+ artifact_path: ".agents/records",
462
+ lifecycle_status: String(row.status ?? "draft"),
463
+ work_status: normalizeMacroStatus(row.status),
464
+ execution_ids: [],
465
+ task_ids: [],
466
+ planned_tasks: [],
467
+ canonical_run_ids: [],
468
+ run_ids: [],
469
+ root_execution_run_ids: [],
470
+ execution_count: 0,
471
+ task_count: 0,
472
+ run_count: nullableMetric(row.invocation_count) ?? 0,
473
+ duration_ms: nullableMetric(row.total_duration),
474
+ decisions: { human: 0, agent: 0 },
475
+ rework_run_count: 0,
476
+ quality: {
477
+ completed_tasks: 0,
478
+ task_completion_rate: 0,
479
+ terminal_runs: 0,
480
+ successful_runs: 0,
481
+ terminal_success_rate: 0,
482
+ rework_rate: 0,
483
+ },
484
+ usage: {
485
+ conversation: unavailableUsage,
486
+ planning: unavailableUsage,
487
+ execution: unavailableUsage,
488
+ total: entityUsage,
489
+ new_work: unavailableUsage,
490
+ rework: unavailableUsage,
491
+ },
153
492
  };
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: "" };
493
+ };
494
+ return {
495
+ schema_version: 2,
496
+ layout_version: layout.version as 2 | 3,
497
+ readonly: true,
498
+ source: ".agents/records",
499
+ filters: filter,
500
+ summary: {
501
+ prd_count: allTime?.aggregate.prd_count ?? prds.length,
502
+ spec_count: allTime?.aggregate.spec_count ?? specs.length,
503
+ execution_count: 0,
504
+ task_count: taskCount,
505
+ run_count: aggregate.total_run_count,
506
+ overview_metrics: {
507
+ duration_ms: aggregate.duration_ms,
508
+ execution_day_count: aggregate.execution_day_count,
509
+ tokens: aggregate.total_tokens,
510
+ cost_brl: aggregate.cost_brl,
511
+ prd_count: allTime?.aggregate.prd_count ?? prds.length,
512
+ spec_count: allTime?.aggregate.spec_count ?? specs.length,
513
+ selected_counts: {
514
+ prds: allTime?.aggregate.prd_count ?? prds.length,
515
+ specs: allTime?.aggregate.spec_count ?? specs.length,
516
+ },
517
+ task_count: taskCount,
518
+ completed_prd_count:
519
+ allTime?.aggregate.completed_prd_count ??
520
+ prds.filter((row) => row.status === "completed").length,
521
+ completed_spec_count:
522
+ allTime?.aggregate.completed_spec_count ??
523
+ specs.filter((row) => row.status === "completed").length,
524
+ completed_task_count: allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0,
525
+ task_status_counts: statuses,
526
+ task_risk_counts: risks,
527
+ top_tasks: topTasks,
528
+ ...(macroMetrics === undefined ? {} : { macro_metrics: macroMetrics }),
529
+ latest_task_created_at: latest,
530
+ coverage: { duration: 1, tokens: hasUnknownUsage ? 0 : 1, cost: hasUnknownUsage ? 0 : 1 },
531
+ data_source: "run_records",
532
+ partial: true,
533
+ },
534
+ decisions: { human: 0, agent: 0 },
535
+ source_latest_at: latest,
536
+ quality: {
537
+ completed_tasks: statuses.completed ?? 0,
538
+ task_completion_rate:
539
+ taskCount === 0
540
+ ? 0
541
+ : (allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0) / taskCount,
542
+ terminal_runs: 0,
543
+ successful_runs: 0,
544
+ terminal_success_rate: 0,
545
+ rework_rate: 0,
546
+ },
547
+ reconciliation: {
548
+ phase_tokens_match_total: true,
549
+ phase_usd_match_total: true,
550
+ phase_brl_match_total: true,
551
+ origin_tokens_match_total: true,
552
+ },
553
+ usage,
554
+ // The period tabs are part of the first-fold contract too. Supplying an
555
+ // empty object for the all-time fast path made Daily, Monthly and Annual
556
+ // look empty even though the SQLite daily aggregate already existed.
557
+ periods: { daily: options.includePeriods === true ? dailyPeriods : {} },
558
+ },
559
+ origins: { conversation: usage, task: usage, spec: usage, prd: usage },
560
+ prds: prds.map((row) => macroProjection(row, "prd")),
561
+ specs: specs.map((row) => macroProjection(row, "spec")),
562
+ tasks: options.includeBoardTasks ? tasks.map(taskProjection) : [],
563
+ changes: [],
564
+ executions: [],
565
+ runs: [],
566
+ decisions: [],
567
+ evidences: [],
568
+ validations: [],
569
+ };
166
570
  }
167
571
 
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;
572
+ function nullableMetric(value: unknown): number | null {
573
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
174
574
  }
175
575
 
176
- export function dashboardWorkspaceKey(workspaceRoot: string): string {
177
- return createHash("sha256").update(resolve(workspaceRoot)).digest("hex").slice(0, 16);
576
+ function normalizeTaskStatus(
577
+ value: unknown,
578
+ ):
579
+ | "draft"
580
+ | "planned"
581
+ | "executing"
582
+ | "verifying"
583
+ | "completed"
584
+ | "failed"
585
+ | "blocked"
586
+ | "cancelled" {
587
+ return [
588
+ "draft",
589
+ "planned",
590
+ "executing",
591
+ "verifying",
592
+ "completed",
593
+ "failed",
594
+ "blocked",
595
+ "cancelled",
596
+ ].includes(String(value))
597
+ ? (String(value) as
598
+ | "draft"
599
+ | "planned"
600
+ | "executing"
601
+ | "verifying"
602
+ | "completed"
603
+ | "failed"
604
+ | "blocked"
605
+ | "cancelled")
606
+ : "draft";
178
607
  }
179
608
 
180
- export interface ReconciliationItem {
181
- id: string;
182
- reason: string | null;
609
+ function normalizeRisk(value: unknown): "lite" | "standard" | "critical" {
610
+ return ["lite", "standard", "critical"].includes(String(value))
611
+ ? (String(value) as "lite" | "standard" | "critical")
612
+ : "standard";
183
613
  }
184
614
 
185
- export interface ReconciliationResult {
186
- reconciled_ids: string[];
187
- blocked: ReconciliationItem[];
188
- not_reconciled: ReconciliationItem[];
615
+ function normalizeMacroStatus(
616
+ value: unknown,
617
+ ): "planning" | "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled" {
618
+ const status = String(value);
619
+ if (["executing", "verifying", "completed", "blocked", "failed", "cancelled"].includes(status))
620
+ return status as "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled";
621
+ return "planning";
189
622
  }
190
623
 
191
- async function reconcileOpenWork(
624
+ async function firstFoldCostSource(
192
625
  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: [] };
626
+ filters: Parameters<SqliteReadModelFileSystem["invocationCostRows"]>[0] = {
627
+ from: null,
628
+ to: null,
629
+ },
630
+ ): Promise<CostSummarySource> {
631
+ if (filesystem instanceof SqliteReadModelFileSystem) {
632
+ return filesystem.costMonitorSummaryAggregate(filters) as CostSummarySource;
197
633
  }
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);
634
+ const daily = await new LifecycleManifestStore(filesystem).readDailyReadModels("overview");
635
+ if (daily === null) throw new Error("Lifecycle daily overview is not initialized");
636
+ return {
637
+ periods: {
638
+ daily: Object.fromEntries(
639
+ Object.entries(daily.days).map(([day, model]) => [day, model.overview]),
640
+ ),
641
+ },
642
+ } satisfies CostSummarySource;
205
643
  }
206
644
 
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);
645
+ function buildSqliteTaskTrace(
646
+ filesystem: SqliteReadModelFileSystem,
647
+ layout: WorkspaceLayout,
648
+ taskId: string,
649
+ ): TaskTrace {
650
+ const initial = filesystem.canonicalTraceFiles([taskId]);
651
+ const taskFile = initial.find(
652
+ (row) =>
653
+ row.path.includes("/.agents/records/tasks/") || row.path.includes(".agents/records/tasks/"),
654
+ );
655
+ if (taskFile === undefined) throw new Error(`Canonical Task ${taskId} was not found`);
656
+ const scalar = (key: string): string | null => {
657
+ const frontMatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(taskFile.content)?.[1] ?? "";
658
+ const value = new RegExp(`^${key}:\\s*(.+?)\\s*$`, "m").exec(frontMatter)?.[1]?.trim();
659
+ if (value === undefined || value === "null") return null;
660
+ return value.replace(/^['"]|['"]$/g, "");
214
661
  };
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));
662
+ const relatedId = (key: string, prefix: string): string | null =>
663
+ scalar(key) ?? new RegExp(`\\b${prefix}[A-Z0-9-]+`).exec(taskFile.content)?.[0] ?? null;
664
+ const changeId = relatedId("change_id", "CHG-") ?? taskId;
665
+ const specId = relatedId("spec_id", "SPEC-");
666
+ const prdId = relatedId("prd_id", "PRD-");
667
+ const markers = [taskId, changeId, specId, prdId].filter(
668
+ (value): value is string => value !== null,
669
+ );
670
+ const documents = filesystem
671
+ .canonicalTraceFiles(markers)
672
+ .filter(
673
+ (row) => /\.(?:md|json|jsonl|txt|log)$/i.test(row.path) && row.content.length <= 250_000,
674
+ )
675
+ .map((row) => {
676
+ const classification = classifyTraceDocument(row.path);
677
+ const timestamps = timestampsFromContent(row.content);
678
+ return {
679
+ id: createHash("sha256").update(row.path).digest("hex").slice(0, 16),
680
+ path: row.path,
681
+ title:
682
+ row.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? row.path.split("/").at(-1) ?? 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,111 @@ 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 =
746
+ options.refreshReadModel ??
747
+ (async () => {
748
+ if (!isSqliteSnapshot) return;
749
+ // Explicit user refreshes must replace the immutable generation with the
750
+ // current canonical records. Keep stale-seed fallback exclusively for
751
+ // startup warmth, never for this visible convergence request.
752
+ await filesystem.rebuildAndRefresh(() =>
753
+ rebuildSqliteReadModel(workspaceRoot, { forceCanonical: true }),
754
+ );
755
+ });
295
756
  const workspaceKey = dashboardWorkspaceKey(workspaceRoot);
296
757
  const syncAgentAdapters = options.syncAgentAdapters ?? (() => runAgentAdapterSync(workspaceRoot));
297
758
  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") ?? ""}`;
759
+ type DashboardSnapshot = Awaited<ReturnType<typeof dashboardSnapshot>>;
760
+ const snapshotInFlight = new Map<string, Promise<DashboardSnapshot>>();
761
+ const cachedSnapshots = new Map<string, { snapshot: DashboardSnapshot; cachedAt: number }>();
762
+ const aggregatePayloads = new Map<string, { payload: unknown; cachedAt: number }>();
763
+ const aggregateInFlight = new Map<string, Promise<unknown>>();
764
+ const readAggregate = async <T>(
765
+ key: string,
766
+ build: () => Promise<T>,
767
+ fresh = false,
768
+ ): Promise<T> => {
769
+ const cached = aggregatePayloads.get(key);
770
+ // Kanban cards are canonical projections. SQLite generations are immutable,
771
+ // so retain their expensive board projection until an explicit refresh
772
+ // clears this map rather than expiring it on the short snapshot TTL.
304
773
  if (
305
- cachedSnapshot !== null &&
306
- cachedSnapshotKey === key &&
307
- Date.now() - cachedSnapshotAt < 2_000
774
+ !fresh &&
775
+ cached !== undefined &&
776
+ (key.startsWith("operations:board:") || Date.now() - cached.cachedAt < SNAPSHOT_CACHE_TTL_MS)
308
777
  )
309
- return Promise.resolve(cachedSnapshot);
310
- if (snapshotInFlight !== null) return snapshotInFlight;
311
- snapshotInFlight = dashboardSnapshot(filesystem, workspaceRoot, context)
778
+ return cached.payload as T;
779
+ const pending = aggregateInFlight.get(key);
780
+ // A bootstrap fresh read must not observe a normal request that began
781
+ // before reload. Wait for it to settle, then rebuild and replace cache.
782
+ if (pending !== undefined) {
783
+ if (!fresh) return pending as Promise<T>;
784
+ await pending;
785
+ return readAggregate(key, build, true);
786
+ }
787
+ const pendingBuild = build()
788
+ .then((payload) => {
789
+ aggregatePayloads.set(key, { payload, cachedAt: Date.now() });
790
+ return payload;
791
+ })
792
+ .finally(() => aggregateInFlight.delete(key));
793
+ aggregateInFlight.set(key, pendingBuild);
794
+ return pendingBuild;
795
+ };
796
+ const snapshotKey = (context: Context) => {
797
+ const { from, to } = projectionPeriodFilter(context);
798
+ return `${selectedCurrency(context)}:${from ?? ""}:${to ?? ""}`;
799
+ };
800
+ const clearSnapshotCache = () => {
801
+ cachedSnapshots.clear();
802
+ aggregatePayloads.clear();
803
+ aggregateInFlight.clear();
804
+ };
805
+ let projectionRefreshInFlight: Promise<void> | null = null;
806
+ const rebuildProjection = async () => {
807
+ if (projectionRefreshInFlight !== null) return projectionRefreshInFlight;
808
+ projectionRefreshInFlight = refreshReadModel()
809
+ .then(() => clearSnapshotCache())
810
+ .finally(() => {
811
+ projectionRefreshInFlight = null;
812
+ });
813
+ return projectionRefreshInFlight;
814
+ };
815
+ const readSnapshot = (context: Context) => {
816
+ const key = snapshotKey(context);
817
+ const cached = cachedSnapshots.get(key);
818
+ if (cached !== undefined && Date.now() - cached.cachedAt < SNAPSHOT_CACHE_TTL_MS) {
819
+ context.header("X-ZAIP-Snapshot-Cache", "hit");
820
+ return Promise.resolve(cached.snapshot);
821
+ }
822
+ const pending = snapshotInFlight.get(key);
823
+ if (pending !== undefined) {
824
+ context.header("X-ZAIP-Snapshot-Cache", "inflight");
825
+ return pending;
826
+ }
827
+ context.header("X-ZAIP-Snapshot-Cache", "miss");
828
+ const inFlight = readLayout()
829
+ .then((layout) => dashboardSnapshot(filesystem, workspaceRoot, context, layout))
312
830
  .then((snapshot) => {
313
- cachedSnapshot = snapshot;
314
- cachedSnapshotKey = key;
315
- cachedSnapshotAt = Date.now();
831
+ cachedSnapshots.set(key, { snapshot, cachedAt: Date.now() });
316
832
  return snapshot;
317
833
  })
318
834
  .finally(() => {
319
- snapshotInFlight = null;
835
+ snapshotInFlight.delete(key);
320
836
  });
321
- return snapshotInFlight;
837
+ snapshotInFlight.set(key, inFlight);
838
+ return inFlight;
322
839
  };
323
840
 
324
841
  app.use("*", async (context, next) => {
@@ -342,32 +859,44 @@ export function createDashboardApp(
342
859
  app.use(`${API_PREFIX}/*`, async (context, next) => {
343
860
  const settingsWrite =
344
861
  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`);
862
+ (context.req.path === `${API_PREFIX}/work-priority` ||
863
+ context.req.path === `${API_PREFIX}/project-settings` ||
864
+ (!snapshotReadOnly &&
865
+ (context.req.path === `${API_PREFIX}/pricing` ||
866
+ context.req.path === `${API_PREFIX}/fx-rate` ||
867
+ context.req.path === `${API_PREFIX}/agent-catalog`)));
868
+ // Reconciliation updates canonical lifecycle records through the supported
869
+ // CLI. It does not mutate the disposable SQLite read model, so it remains
870
+ // available while the dashboard serves a read-only snapshot.
349
871
  const reconcileWrite =
350
872
  context.req.method === "POST" && context.req.path === `${API_PREFIX}/reconcile-open-work`;
873
+ const projectionRefreshWrite =
874
+ context.req.method === "POST" && context.req.path === `${API_PREFIX}/refresh-projection`;
351
875
  if (
352
876
  context.req.method !== "GET" &&
353
877
  context.req.method !== "HEAD" &&
354
878
  !settingsWrite &&
355
- !reconcileWrite
879
+ !reconcileWrite &&
880
+ !projectionRefreshWrite
356
881
  ) {
357
882
  context.header(
358
883
  "Allow",
359
884
  context.req.path === `${API_PREFIX}/pricing` ||
360
885
  context.req.path === `${API_PREFIX}/fx-rate` ||
361
886
  context.req.path === `${API_PREFIX}/agent-catalog` ||
362
- context.req.path === `${API_PREFIX}/project-settings`
887
+ context.req.path === `${API_PREFIX}/project-settings` ||
888
+ context.req.path === `${API_PREFIX}/work-priority`
363
889
  ? "GET, HEAD, PUT"
364
890
  : context.req.path === `${API_PREFIX}/reconcile-open-work`
365
891
  ? "POST"
366
- : "GET, HEAD",
892
+ : context.req.path === `${API_PREFIX}/refresh-projection`
893
+ ? "POST"
894
+ : "GET, HEAD",
367
895
  );
368
896
  return context.json({ error: "Dashboard API is read-only" }, 405);
369
897
  }
370
- if (context.req.path === `${API_PREFIX}/health`) return next();
898
+ if (context.req.path === `${API_PREFIX}/health` || context.req.path === `${API_PREFIX}/ready`)
899
+ return next();
371
900
 
372
901
  const origin = context.req.header("origin");
373
902
  if (origin !== undefined && !isAllowedOrigin(origin)) {
@@ -392,7 +921,7 @@ export function createDashboardApp(
392
921
 
393
922
  app.on(["GET", "HEAD"], `${API_PREFIX}/health`, async (context) => {
394
923
  try {
395
- const layout = await requireV2Layout(filesystem);
924
+ const layout = await readLayout();
396
925
  return headOrJson(context, {
397
926
  kind: RUNTIME_KIND,
398
927
  workspaceKey,
@@ -401,6 +930,8 @@ export function createDashboardApp(
401
930
  api_version: 2,
402
931
  workspace_layout: layout.version,
403
932
  read_only: true,
933
+ storage: isSqliteSnapshot ? filesystem.metadata() : { storage_mode: "filesystem" },
934
+ snapshot_state: snapshotState,
404
935
  });
405
936
  } catch (error) {
406
937
  return headOrJson(
@@ -419,17 +950,184 @@ export function createDashboardApp(
419
950
  }
420
951
  });
421
952
 
953
+ // Launcher readiness proves a canonical projection can be read; health alone
954
+ // intentionally remains a lightweight process/layout probe.
955
+ app.on(["GET", "HEAD"], `${API_PREFIX}/ready`, async (context) => {
956
+ try {
957
+ const layout = await readLayout();
958
+ if (isSqliteSnapshot) {
959
+ const metadata = filesystem.metadata();
960
+ return headOrJson(context, {
961
+ kind: RUNTIME_KIND,
962
+ workspaceKey,
963
+ status: "ready",
964
+ workspace_layout: layout.version,
965
+ storage_mode: metadata.storage_mode,
966
+ activity_count: metadata.activity_count,
967
+ // Readiness is only an immutable-generation probe. The default
968
+ // monthly Operations projection was materialized during rebuild;
969
+ // /ready must never start a competing projection in the background.
970
+ day_count: metadata.daily_aggregate_count,
971
+ snapshot_state: snapshotState,
972
+ });
973
+ }
974
+ const projection = await buildFirstFoldQuickProjection(filesystem, layout, {
975
+ from: null,
976
+ to: null,
977
+ });
978
+ return headOrJson(context, {
979
+ kind: RUNTIME_KIND,
980
+ workspaceKey,
981
+ status: "ready",
982
+ source_latest_at: projection.summary.source_latest_at,
983
+ });
984
+ } catch (error) {
985
+ return headOrJson(
986
+ context,
987
+ {
988
+ kind: RUNTIME_KIND,
989
+ workspaceKey,
990
+ status: "unavailable",
991
+ error: error instanceof Error ? error.message : String(error),
992
+ },
993
+ 503,
994
+ );
995
+ }
996
+ });
997
+
422
998
  app.on(["GET", "HEAD"], `${API_PREFIX}/operations`, async (context) => {
423
999
  try {
424
- const layout = await requireV2Layout(filesystem);
1000
+ const requestStartedAt = performance.now();
1001
+ const scope = context.req.query("scope");
1002
+ if (
1003
+ scope !== "overview" &&
1004
+ scope !== "quick" &&
1005
+ scope !== "detail" &&
1006
+ scope !== "board" &&
1007
+ scope !== "classification"
1008
+ )
1009
+ return headOrJson(context, (await readSnapshot(context)).operations);
1010
+ const layout = await readLayout();
1011
+ const layoutMs = performance.now() - requestStartedAt;
425
1012
  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);
1013
+ if (scope === "board") {
1014
+ const stage = boardStage(context.req.query("stage"));
1015
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1016
+ const limit = Math.min(
1017
+ 50,
1018
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "10", 10) || 10),
1019
+ );
1020
+ const board = await readAggregate(
1021
+ `operations:board:${from ?? ""}:${to ?? ""}`,
1022
+ async () => {
1023
+ const projection = await buildFirstFoldQuickProjection(
1024
+ filesystem,
1025
+ layout,
1026
+ { from, to },
1027
+ { includeBoardTasks: true },
1028
+ );
1029
+ return {
1030
+ projection,
1031
+ priority: await readWorkPriority(canonicalFilesystem, knownWorkIds(projection)),
1032
+ };
1033
+ },
1034
+ );
1035
+ const filters = boardFilters(context);
1036
+ const pageKey = JSON.stringify({ from, to, stage, offset, limit, ...filters });
1037
+ const page = await readAggregate(`operations:board-page:${pageKey}`, async () =>
1038
+ boardPage(board.projection, stage, offset, limit, board.priority.item_ids, filters),
1039
+ );
1040
+ return headOrJson(context, page, 200, true);
1041
+ }
1042
+ if (scope === "classification") {
1043
+ const kind = context.req.query("kind");
1044
+ const classification = context.req.query("classification");
1045
+ if (kind !== "task" && kind !== "spec" && kind !== "prd")
1046
+ return headOrJson(context, { error: "A valid work kind is required" }, 400);
1047
+ if (
1048
+ !["lite", "standard", "critical", "empty", "structured", "rework"].includes(
1049
+ classification ?? "",
1050
+ )
1051
+ )
1052
+ return headOrJson(context, { error: "A valid classification is required" }, 400);
1053
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1054
+ const limit = Math.min(
1055
+ 50,
1056
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "50", 10) || 50),
1057
+ );
1058
+ const projection = await readAggregate(
1059
+ `operations:classification:${from ?? ""}:${to ?? ""}`,
1060
+ () =>
1061
+ buildFirstFoldQuickProjection(
1062
+ filesystem,
1063
+ layout,
1064
+ { from, to },
1065
+ { includeBoardTasks: true },
1066
+ ),
1067
+ );
1068
+ return headOrJson(
1069
+ context,
1070
+ classificationPage(
1071
+ projection,
1072
+ kind,
1073
+ classification as BoardClassification,
1074
+ offset,
1075
+ limit,
1076
+ ),
1077
+ 200,
1078
+ true,
1079
+ );
1080
+ }
1081
+ if (scope === "quick") {
1082
+ const projectionStartedAt = performance.now();
1083
+ const quick = await readAggregate(
1084
+ `operations:quick:${from ?? ""}:${to ?? ""}`,
1085
+ () =>
1086
+ buildFirstFoldQuickProjection(
1087
+ filesystem,
1088
+ layout,
1089
+ { from, to },
1090
+ {
1091
+ includeTopTasks: from === null && to === null,
1092
+ },
1093
+ ),
1094
+ context.req.query("fresh") === "1",
1095
+ );
1096
+ if (
1097
+ process.env.ZAIP_DASHBOARD_PROFILE_FIRST_FOLD === "1" &&
1098
+ filesystem instanceof SqliteReadModelFileSystem
1099
+ ) {
1100
+ const timings = filesystem.firstFoldViewportProfile();
1101
+ context.header(
1102
+ "Server-Timing",
1103
+ [
1104
+ `layout;dur=${Math.round(layoutMs * 10) / 10}`,
1105
+ `projection;dur=${Math.round((performance.now() - projectionStartedAt) * 10) / 10}`,
1106
+ ...Object.entries(timings).map(([name, duration]) => `${name};dur=${duration}`),
1107
+ ].join(", "),
1108
+ );
1109
+ }
1110
+ return headOrJson(context, quick, 200, true);
1111
+ }
1112
+ const projection =
1113
+ filesystem instanceof SqliteReadModelFileSystem
1114
+ ? await buildFirstFoldQuickProjection(
1115
+ filesystem,
1116
+ layout,
1117
+ { from, to },
1118
+ {
1119
+ includeBoardTasks: scope === "detail",
1120
+ includeExtended: scope === "detail" || scope === "overview",
1121
+ includePeriods: scope === "detail" || scope === "overview",
1122
+ },
1123
+ )
1124
+ : await buildCanonicalOperationsProjection(
1125
+ filesystem,
1126
+ layout,
1127
+ { from, to },
1128
+ scope === "overview",
1129
+ );
1130
+ return headOrJson(context, projection, 200, scope === "overview");
433
1131
  } catch (error) {
434
1132
  if (error instanceof V2WorkspaceRequiredError) throw error;
435
1133
  return headOrJson(
@@ -440,6 +1138,146 @@ export function createDashboardApp(
440
1138
  }
441
1139
  });
442
1140
 
1141
+ registerOperationsRunsRoute(app, filesystem, readAggregate, headOrJson);
1142
+
1143
+ // Explicit SQL read-model API for the comparison dashboard. It never falls
1144
+ // back to filesystem pagination, making first-fold/entity query plans
1145
+ // inspectable while preserving the established Operations contracts.
1146
+ app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-read-models`, async (context) => {
1147
+ if (!(filesystem instanceof SqliteReadModelFileSystem))
1148
+ return headOrJson(context, { error: "SQLite read model is not active" }, 409);
1149
+ const kind = context.req.query("kind");
1150
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1151
+ const limit = Math.min(
1152
+ 200,
1153
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "20", 10) || 20),
1154
+ );
1155
+ if (kind === "overview") {
1156
+ const period = context.req.query("period");
1157
+ if (period !== "day" && period !== "month" && period !== "year")
1158
+ return headOrJson(context, { error: "Unsupported SQLite overview period" }, 400);
1159
+ return headOrJson(context, filesystem.overviewPage(period, offset, limit), 200, true);
1160
+ }
1161
+ if (kind !== "task" && kind !== "spec" && kind !== "prd")
1162
+ return headOrJson(context, { error: "Unsupported SQLite read model" }, 400);
1163
+ const { from, to } = projectionPeriodFilter(context);
1164
+ const filters = operationsPageFilters(context);
1165
+ return headOrJson(
1166
+ context,
1167
+ filesystem.summaryPage(
1168
+ kind,
1169
+ { from, to, ...filters },
1170
+ offset,
1171
+ limit,
1172
+ context.req.query("cursor") ?? null,
1173
+ ),
1174
+ 200,
1175
+ true,
1176
+ );
1177
+ });
1178
+
1179
+ app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-views`, async (context) => {
1180
+ if (!(filesystem instanceof SqliteReadModelFileSystem))
1181
+ return headOrJson(context, { error: "SQLite read model is not active" }, 409);
1182
+ const view = context.req.query("view");
1183
+ if (view === undefined || !(view in sqliteViewMetadata))
1184
+ return headOrJson(context, { error: "Unsupported SQLite view" }, 400);
1185
+ const parsed = Number.parseInt(context.req.query("limit") ?? "", 10);
1186
+ const defaultLimit = view.includes("agent_invocations")
1187
+ ? 100
1188
+ : view === "first_fold_top_tasks"
1189
+ ? 5
1190
+ : 50;
1191
+ const limit = Math.min(200, Math.max(1, Number.isFinite(parsed) ? parsed : defaultLimit));
1192
+ const { from, to } = projectionPeriodFilter(context);
1193
+ const optional = (name: string) => {
1194
+ const value = context.req.query(name);
1195
+ return value === undefined ? {} : { [name]: value };
1196
+ };
1197
+ return headOrJson(
1198
+ context,
1199
+ filesystem.viewPage(view as keyof typeof sqliteViewMetadata, {
1200
+ from,
1201
+ to,
1202
+ ...optional("query"),
1203
+ ...optional("status"),
1204
+ ...optional("risk"),
1205
+ ...optional("provider"),
1206
+ ...optional("model"),
1207
+ ...optional("agent"),
1208
+ ...optional("phase"),
1209
+ ...optional("origin"),
1210
+ ...optional("workType"),
1211
+ ...optional("priority"),
1212
+ offset: Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0),
1213
+ limit,
1214
+ }),
1215
+ 200,
1216
+ true,
1217
+ );
1218
+ });
1219
+
1220
+ app.on(["GET", "HEAD"], `${API_PREFIX}/work-priority`, async (context) => {
1221
+ const layout = await requireV2Layout(canonicalFilesystem);
1222
+ const operations = await buildOperationsProjection(canonicalFilesystem, layout, {
1223
+ from: null,
1224
+ to: null,
1225
+ });
1226
+ return headOrJson(
1227
+ context,
1228
+ await readWorkPriority(canonicalFilesystem, knownWorkIds(operations)),
1229
+ );
1230
+ });
1231
+
1232
+ app.on(["GET", "HEAD"], `${API_PREFIX}/operations/entities`, async (context) => {
1233
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1234
+ const limit = Math.min(
1235
+ 500,
1236
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
1237
+ );
1238
+ const kind = context.req.query("kind");
1239
+ if (kind !== "changes" && kind !== "decisions")
1240
+ return headOrJson(context, { error: "Unsupported Operations entity page" }, 400);
1241
+ const { from, to } = projectionPeriodFilter(context);
1242
+ const pageFilters = operationsPageFilters(context);
1243
+ const payload = await readAggregate(
1244
+ `operations:entities:${kind}:${from ?? ""}:${to ?? ""}:${offset}:${limit}:${pageFilters.query}:${pageFilters.status}:${pageFilters.risk}`,
1245
+ async () => {
1246
+ const layout = await readLayout();
1247
+ const entities = await new LifecycleManifestStore(filesystem).readEntities(
1248
+ kind === "changes" ? "change" : "decision",
1249
+ );
1250
+ const entityRows =
1251
+ kind === "changes"
1252
+ ? changeEntityPageRows(entities, layout, from, to)
1253
+ : decisionEntityPageRows(entities, layout, from, to);
1254
+ const rows = filterLifecyclePageRows(entityRows, pageFilters);
1255
+ return {
1256
+ data_source: "lifecycle_manifest_shards",
1257
+ kind,
1258
+ offset,
1259
+ limit,
1260
+ total: rows.length,
1261
+ rows: rows.slice(offset, offset + limit),
1262
+ };
1263
+ },
1264
+ );
1265
+ return headOrJson(context, payload, 200, true);
1266
+ });
1267
+
1268
+ registerDailyReadModelRoute(app, filesystem, readAggregate, headOrJson);
1269
+ app.put(`${API_PREFIX}/work-priority`, async (context) => {
1270
+ const candidate = normalizeWorkPriority(await context.req.json());
1271
+ // Priority is canonical queue configuration, never snapshot content.
1272
+ // Keep every other settings mutation blocked in snapshot-serving mode.
1273
+ await canonicalFilesystem.writeTextAtomic(
1274
+ WORK_PRIORITY_PATH,
1275
+ `${JSON.stringify(candidate, null, 2)}\n`,
1276
+ );
1277
+ clearSnapshotCache();
1278
+ return context.json(candidate);
1279
+ });
1280
+
443
1281
  app.on(["GET", "HEAD"], `${API_PREFIX}/snapshot`, async (context) => {
444
1282
  try {
445
1283
  return headOrJson(context, await readSnapshot(context));
@@ -455,10 +1293,14 @@ export function createDashboardApp(
455
1293
 
456
1294
  app.post(`${API_PREFIX}/reconcile-open-work`, async (context) => {
457
1295
  try {
458
- await requireV2Layout(filesystem);
459
- const reconciledIds = await reconcileOpenWork(filesystem, workspaceRoot);
1296
+ await readLayout();
1297
+ // The served snapshot is immutable by design. Reconciliation must use
1298
+ // canonical records, then atomically rebuild and reopen the snapshot
1299
+ // before the response exposes fresh projections.
1300
+ const reconciledIds = await reconcileOpen(canonicalFilesystem, workspaceRoot);
1301
+ await rebuildProjection();
460
1302
  return context.json({
461
- ...(await dashboardSnapshot(filesystem, workspaceRoot, context)),
1303
+ ...(await readSnapshot(context)),
462
1304
  reconciliation: reconciledIds,
463
1305
  });
464
1306
  } catch (error) {
@@ -467,9 +1309,87 @@ export function createDashboardApp(
467
1309
  }
468
1310
  });
469
1311
 
1312
+ app.post(`${API_PREFIX}/refresh-projection`, async (context) => {
1313
+ try {
1314
+ await readLayout();
1315
+ await rebuildProjection();
1316
+ return context.json(await readSnapshot(context));
1317
+ } catch (error) {
1318
+ if (error instanceof V2WorkspaceRequiredError) throw error;
1319
+ return context.json({ error: error instanceof Error ? error.message : String(error) }, 400);
1320
+ }
1321
+ });
1322
+
470
1323
  app.on(["GET", "HEAD"], `${API_PREFIX}/cost-monitor`, async (context) => {
471
1324
  try {
472
- const layout = await requireV2Layout(filesystem);
1325
+ const scope = context.req.query("scope") ?? "summary";
1326
+ if (scope === "summary") {
1327
+ const layout = await readLayout();
1328
+ const { from, to } = projectionPeriodFilter(context);
1329
+ const currency = selectedCurrency(context);
1330
+ const aggregateStartedAt = performance.now();
1331
+ const [source, rate] = await Promise.all([
1332
+ firstFoldCostSource(filesystem, {
1333
+ from,
1334
+ to,
1335
+ providers: queryValues(context, "provider"),
1336
+ models: queryValues(context, "model"),
1337
+ agents: queryValues(context, "agent"),
1338
+ phases: queryValues(context, "phase"),
1339
+ statuses: queryValues(context, "status"),
1340
+ tasks: queryValues(context, "task"),
1341
+ specs: queryValues(context, "spec"),
1342
+ prds: queryValues(context, "prd"),
1343
+ executions: queryValues(context, "execution"),
1344
+ changes: queryValues(context, "change"),
1345
+ }),
1346
+ currency === "BRL" ? readCurrentFxRate(filesystem) : Promise.resolve(null),
1347
+ ]);
1348
+ // First fold receives only aggregate facts. Detailed Runs and entity
1349
+ // arrays remain on their explicit paginated endpoints.
1350
+ const aggregateMs = performance.now() - aggregateStartedAt;
1351
+ const costOperations = {
1352
+ schema_version: 1,
1353
+ layout_version: layout.version,
1354
+ readonly: true,
1355
+ source: ".agents/records",
1356
+ filters: { from, to },
1357
+ summary: { task_count: source.task_count ?? 0 },
1358
+ prds: [],
1359
+ specs: [],
1360
+ runs: [],
1361
+ } as unknown as OperationsProjection;
1362
+ const projectionStartedAt = performance.now();
1363
+ const payload = buildCostSummaryProjection(
1364
+ source,
1365
+ costOperations,
1366
+ currency,
1367
+ rate?.rate ?? null,
1368
+ );
1369
+ context.header(
1370
+ "Server-Timing",
1371
+ `cost_sql;dur=${aggregateMs.toFixed(1)},cost_projection;dur=${(performance.now() - projectionStartedAt).toFixed(1)}`,
1372
+ );
1373
+ return headOrJson(context, payload, 200, true);
1374
+ }
1375
+ if (scope === "runs") {
1376
+ const layout = await readLayout();
1377
+ const { from, to } = projectionPeriodFilter(context);
1378
+ const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1379
+ const limit = Math.min(
1380
+ 500,
1381
+ Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
1382
+ );
1383
+ return headOrJson(
1384
+ context,
1385
+ await buildManifestCostRunPage(filesystem, layout, context, { from, to }, offset, limit),
1386
+ 200,
1387
+ true,
1388
+ );
1389
+ }
1390
+ if (isUnfilteredCostRequest(context))
1391
+ return headOrJson(context, (await readSnapshot(context)).costs);
1392
+ const layout = await readLayout();
473
1393
  const { from, to } = projectionPeriodFilter(context);
474
1394
  // Cost and Operations must start from the same manifest-corrected Run
475
1395
  // population; financial availability is represented on each row, never
@@ -507,6 +1427,7 @@ export function createDashboardApp(
507
1427
  ".agents/config/pricing/models.json",
508
1428
  `${JSON.stringify(catalog, null, 2)}\n`,
509
1429
  );
1430
+ clearSnapshotCache();
510
1431
  return context.json(catalog);
511
1432
  });
512
1433
 
@@ -518,6 +1439,7 @@ export function createDashboardApp(
518
1439
  app.put(`${API_PREFIX}/fx-rate`, async (context) => {
519
1440
  const input = fxRateSchema.pick({ rate: true }).parse(await context.req.json());
520
1441
  const rate = await new PricingRepository(filesystem).writeCurrentFxRate(input.rate);
1442
+ clearSnapshotCache();
521
1443
  return context.json(rate);
522
1444
  });
523
1445
 
@@ -553,9 +1475,13 @@ export function createDashboardApp(
553
1475
  app.put(`${API_PREFIX}/agent-catalog`, async (context) => {
554
1476
  const update = async () => {
555
1477
  try {
556
- return context.json(
557
- await persistAgentCatalogUpdate(filesystem, await context.req.json(), syncAgentAdapters),
1478
+ const persisted = await persistAgentCatalogUpdate(
1479
+ filesystem,
1480
+ await context.req.json(),
1481
+ syncAgentAdapters,
558
1482
  );
1483
+ clearSnapshotCache();
1484
+ return context.json(persisted);
559
1485
  } catch (error) {
560
1486
  if (error instanceof AgentCatalogUpdateError) {
561
1487
  return context.json({ error: error.message }, error.status);
@@ -573,7 +1499,7 @@ export function createDashboardApp(
573
1499
 
574
1500
  app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings`, async (context) => {
575
1501
  try {
576
- return headOrJson(context, await readProjectSettings(filesystem));
1502
+ return headOrJson(context, await readProjectSettings(canonicalFilesystem));
577
1503
  } catch (error) {
578
1504
  return headOrJson(
579
1505
  context,
@@ -583,9 +1509,15 @@ export function createDashboardApp(
583
1509
  }
584
1510
  });
585
1511
 
1512
+ app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings/overview`, async (context) => {
1513
+ return headOrJson(context, await readProjectOverview(canonicalFilesystem));
1514
+ });
1515
+
586
1516
  app.put(`${API_PREFIX}/project-settings`, async (context) => {
587
1517
  try {
588
- return context.json(await persistProjectSettings(filesystem, await context.req.json()));
1518
+ const persisted = await persistProjectSettings(canonicalFilesystem, await context.req.json());
1519
+ clearSnapshotCache();
1520
+ return context.json(persisted);
589
1521
  } catch (error) {
590
1522
  return context.json(
591
1523
  { error: error instanceof Error ? error.message : "Project settings update failed" },
@@ -596,7 +1528,7 @@ export function createDashboardApp(
596
1528
 
597
1529
  app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id`, async (context) => {
598
1530
  try {
599
- const layout = await requireV2Layout(filesystem);
1531
+ const layout = await readLayout();
600
1532
  return headOrJson(context, await readTaskDetail(filesystem, layout, context.req.param("id")));
601
1533
  } catch (error) {
602
1534
  if (error instanceof V2WorkspaceRequiredError) throw error;
@@ -606,7 +1538,7 @@ export function createDashboardApp(
606
1538
 
607
1539
  app.on(["GET", "HEAD"], `${API_PREFIX}/prds/:id`, async (context) => {
608
1540
  try {
609
- const layout = await requireV2Layout(filesystem);
1541
+ const layout = await readLayout();
610
1542
  return headOrJson(context, await readPrdDetail(filesystem, layout, context.req.param("id")));
611
1543
  } catch (error) {
612
1544
  if (error instanceof V2WorkspaceRequiredError) throw error;
@@ -616,7 +1548,7 @@ export function createDashboardApp(
616
1548
 
617
1549
  app.on(["GET", "HEAD"], `${API_PREFIX}/specs/:id`, async (context) => {
618
1550
  try {
619
- const layout = await requireV2Layout(filesystem);
1551
+ const layout = await readLayout();
620
1552
  return headOrJson(context, await readSpecDetail(filesystem, layout, context.req.param("id")));
621
1553
  } catch (error) {
622
1554
  if (error instanceof V2WorkspaceRequiredError) throw error;
@@ -626,8 +1558,14 @@ export function createDashboardApp(
626
1558
 
627
1559
  app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id/trace`, async (context) => {
628
1560
  try {
629
- const layout = await requireV2Layout(filesystem);
630
- return headOrJson(context, await buildTaskTrace(filesystem, layout, context.req.param("id")));
1561
+ const layout = await readLayout();
1562
+ const taskId = context.req.param("id");
1563
+ return headOrJson(
1564
+ context,
1565
+ filesystem instanceof SqliteReadModelFileSystem
1566
+ ? buildSqliteTaskTrace(filesystem, layout, taskId)
1567
+ : await buildTaskTrace(filesystem, layout, taskId),
1568
+ );
631
1569
  } catch (error) {
632
1570
  if (error instanceof V2WorkspaceRequiredError) throw error;
633
1571
  return headOrJson(context, { error: "Task trace not found" }, 404);
@@ -657,57 +1595,28 @@ export function createDashboardApp(
657
1595
  return app;
658
1596
  }
659
1597
 
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
1598
  async function dashboardSnapshot(
703
1599
  filesystem: WorkspaceFileSystem,
704
1600
  workspaceRoot: string,
705
1601
  context: Context,
1602
+ cachedLayout?: WorkspaceLayout,
706
1603
  ) {
707
- const layout = await requireV2Layout(filesystem);
1604
+ const layout = cachedLayout ?? (await requireV2Layout(filesystem));
708
1605
  const { from, to } = projectionPeriodFilter(context);
709
- const operations = await buildCanonicalOperationsProjection(filesystem, layout, { from, to });
710
- const costs = await buildCurrentCostProjection(filesystem, operations, selectedCurrency(context));
1606
+ const currency = selectedCurrency(context);
1607
+ const operations =
1608
+ filesystem instanceof SqliteReadModelFileSystem
1609
+ ? await buildFirstFoldQuickProjection(filesystem, layout, { from, to })
1610
+ : await buildCanonicalOperationsProjection(filesystem, layout, { from, to });
1611
+ const costs =
1612
+ filesystem instanceof SqliteReadModelFileSystem
1613
+ ? buildCostSummaryProjection(
1614
+ await firstFoldCostSource(filesystem, { from, to }),
1615
+ operations,
1616
+ currency,
1617
+ currency === "BRL" ? ((await readCurrentFxRate(filesystem))?.rate ?? null) : null,
1618
+ )
1619
+ : await buildCurrentCostProjection(filesystem, operations, currency);
711
1620
  return {
712
1621
  operations,
713
1622
  costs,
@@ -719,81 +1628,75 @@ async function dashboardSnapshot(
719
1628
  };
720
1629
  }
721
1630
 
1631
+ const moduleStartedAt = performance.now();
1632
+ const sessionToken = defaultSessionToken();
1633
+ const runtimeWorkspaceRoot = process.env.ZAIP_WORKSPACE ?? process.cwd();
1634
+
1635
+ let defaultApp: Hono | undefined;
1636
+
722
1637
  /**
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.
1638
+ * Compatibility export for direct module consumers. The executable must not
1639
+ * construct this filesystem-backed app: `main` creates the SQLite app after
1640
+ * its private read model is ready. Construct on first consumer access instead.
726
1641
  */
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
- });
1642
+ export function defaultDashboardApp(): Hono {
1643
+ defaultApp ??= createDashboardApp(runtimeWorkspaceRoot, sessionToken);
1644
+ return defaultApp;
745
1645
  }
746
1646
 
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
- }
1647
+ export const app = new Proxy({} as Hono, {
1648
+ get(_target, property) {
1649
+ const value = Reflect.get(defaultDashboardApp(), property);
1650
+ return typeof value === "function" ? value.bind(defaultDashboardApp()) : value;
1651
+ },
1652
+ });
760
1653
 
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));
1654
+ function startupPhase(phase: string): void {
1655
+ process.stderr.write(
1656
+ `${JSON.stringify({
1657
+ event: "dashboard_sqlite_startup_phase",
1658
+ phase,
1659
+ elapsed_ms: Math.round(performance.now() - moduleStartedAt),
1660
+ })}\n`,
1661
+ );
783
1662
  }
784
1663
 
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) {
1664
+ async function main(): Promise<void> {
793
1665
  const port = Number.parseInt(process.env.ZAIP_SERVER_PORT ?? "4317", 10);
794
- serve({ fetch: app.fetch, hostname: HOST, port }, ({ port: boundPort }) => {
1666
+ startupPhase("main_entered");
1667
+ const startedAt = performance.now();
1668
+ const readModel = await rebuildSqliteReadModel(runtimeWorkspaceRoot);
1669
+ // The parent launcher captures stderr even when stdout is intentionally
1670
+ // hidden. This makes seed hit/miss and private-copy cost observable without
1671
+ // exposing a shared handle or workspace payload.
1672
+ process.stderr.write(
1673
+ `${JSON.stringify({
1674
+ event: "dashboard_sqlite_read_model",
1675
+ elapsed_ms: Math.round(performance.now() - startedAt),
1676
+ cache: readModel.cache,
1677
+ fingerprint_ms: readModel.fingerprint_ms,
1678
+ materialize_ms: readModel.materialize_ms,
1679
+ source_files: readModel.files,
1680
+ })}\n`,
1681
+ );
1682
+ startupPhase("read_model_ready");
1683
+ const runtimeApp = createDashboardApp(runtimeWorkspaceRoot, sessionToken, undefined, {
1684
+ filesystem: new SqliteReadModelFileSystem(runtimeWorkspaceRoot),
1685
+ snapshotState: readModel.cache === "stale_seed" ? "updating" : "current",
1686
+ });
1687
+ startupPhase("runtime_app_ready");
1688
+ serve({ fetch: runtimeApp.fetch, hostname: HOST, port }, ({ port: boundPort }) => {
1689
+ startupPhase("listening");
795
1690
  process.stdout.write(
796
1691
  `ZAIP-AIOS dashboard API: http://${HOST}:${boundPort}${API_PREFIX}/health\n`,
797
1692
  );
798
1693
  });
799
1694
  }
1695
+
1696
+ if (
1697
+ process.argv[1]?.endsWith("server.cjs") ||
1698
+ process.argv[1]?.endsWith("server.js") ||
1699
+ process.argv[1]?.endsWith("server.mjs")
1700
+ ) {
1701
+ void main();
1702
+ }