@zaip-ai/cli 2.2.0 → 2.6.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 (420) hide show
  1. package/dist/services/adapters.d.ts +1 -1
  2. package/dist/services/adapters.d.ts.map +1 -1
  3. package/dist/services/adapters.js +23 -9
  4. package/dist/services/adapters.js.map +1 -1
  5. package/dist/services/agent-adapters.js +25 -9
  6. package/dist/services/agent-adapters.js.map +1 -1
  7. package/dist/services/agent-discovery.d.ts +27 -0
  8. package/dist/services/agent-discovery.d.ts.map +1 -0
  9. package/dist/services/agent-discovery.js +73 -0
  10. package/dist/services/agent-discovery.js.map +1 -0
  11. package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -1
  12. package/dist/services/codex-conversation-lifecycle-hook.js +153 -24
  13. package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -1
  14. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  15. package/dist/services/codex-pre-tool-hook-template.js +146 -72
  16. package/dist/services/codex-pre-tool-hook-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 +3 -3
  20. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  21. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  22. package/dist/services/codex-subagent-hook-template.js +117 -66
  23. package/dist/services/codex-subagent-hook-template.js.map +1 -1
  24. package/dist/services/install-agents-template.d.ts.map +1 -1
  25. package/dist/services/install-agents-template.js +56 -6
  26. package/dist/services/install-agents-template.js.map +1 -1
  27. package/dist/services/logger.js +1 -1
  28. package/dist/services/logger.js.map +1 -1
  29. package/dist/services/logger.test.js +22 -1
  30. package/dist/services/logger.test.js.map +1 -1
  31. package/dist/services/runtime-template.test.js +129 -0
  32. package/dist/services/runtime-template.test.js.map +1 -1
  33. package/package.json +1 -1
  34. package/templates/.agents/.env.example +9 -3
  35. package/templates/.agents/README-ENG.md +30 -5
  36. package/templates/.agents/README.md +30 -7
  37. package/templates/.agents/architecture/software-architecture.md +1 -59
  38. package/templates/.agents/branding/brandbook.md +3 -0
  39. package/templates/.agents/config/agents/catalog.yaml +22 -10
  40. package/templates/.agents/config/agents/discovery.json +319 -0
  41. package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +2 -2
  42. package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +2 -2
  43. package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +11 -8
  44. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +2 -2
  45. package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +2 -2
  46. package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +2 -2
  47. package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +2 -2
  48. package/templates/.agents/config/pricing/models.json +9 -5
  49. package/templates/.agents/config/project.yaml +12 -1
  50. package/templates/.agents/dashboard/.zaip-managed.json +375 -95
  51. package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-CGxeQak4.js +1 -0
  52. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-DaSllaJp.js → DashboardHelpDialog-tzS7YxYB.js} +57 -33
  53. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-DEbCi1gP.js +1 -0
  54. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-1L7Qw-Gq.js +3 -0
  55. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-EInWr641.css +1 -0
  56. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-t1Om0Abw.js +1 -0
  57. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-B6aZfo5f.js +68 -0
  58. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-TPUT2IUt.js → icons-vendor-DiGC-dsJ.js} +1 -1
  59. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DgTmNHN5.js +2 -0
  60. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-Ct48XHGL.js → react-vendor-kPO6yNed.js} +1 -1
  61. package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-Bh1tDfsg.js +1 -0
  62. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DQpKX9c1.js +1 -0
  63. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  64. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  65. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +3 -1264
  66. package/templates/.agents/dashboard/apps/frontend/src/AppDashboardContent.tsx +363 -0
  67. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.dom.test.ts +99 -0
  68. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +62 -6
  69. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +3 -1
  70. package/templates/.agents/dashboard/apps/frontend/src/__tests__/app-composition-support.ts +7 -0
  71. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +9 -7
  72. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-settings.ts +151 -0
  73. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +28 -153
  74. package/templates/.agents/dashboard/apps/frontend/src/dashboard-app-shell.tsx +33 -0
  75. package/templates/.agents/dashboard/apps/frontend/src/dashboard-cost-facts.ts +30 -0
  76. package/templates/.agents/dashboard/apps/frontend/src/dashboard-project-overview.ts +7 -1
  77. package/templates/.agents/dashboard/apps/frontend/src/dashboard-retry.ts +17 -0
  78. package/templates/.agents/dashboard/apps/frontend/src/dashboard-setup-route.ts +29 -0
  79. package/templates/.agents/dashboard/apps/frontend/src/dashboard-view-actions.ts +66 -0
  80. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +3 -2
  81. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +2 -2
  82. package/templates/.agents/dashboard/apps/frontend/src/project-installation-status.test.ts +48 -0
  83. package/templates/.agents/dashboard/apps/frontend/src/project-installation-status.ts +29 -0
  84. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +68 -0
  85. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +2 -2
  86. package/templates/.agents/dashboard/apps/frontend/src/sao-paulo-day-rollover.test.ts +36 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/sao-paulo-day-rollover.ts +30 -0
  88. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +2 -5
  89. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +30 -19
  90. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +9 -1
  91. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-task-pagination.ts +77 -0
  92. package/templates/.agents/dashboard/apps/frontend/src/use-app-work-board.ts +397 -0
  93. package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +782 -0
  94. package/templates/.agents/dashboard/apps/frontend/src/use-snapshot-notice.tsx +49 -0
  95. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  96. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperPanelSlot.tsx +16 -0
  97. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostGroupsTable.tsx +459 -0
  98. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +27 -17
  99. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +62 -186
  100. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorRunDialog.tsx +20 -0
  101. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables-hides-calls-that-have-no-collected.test.ts +80 -0
  102. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables-keeps-legacy-codex-identity-fields-blank.test.ts +455 -0
  103. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +18 -525
  104. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +32 -508
  105. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-export-sources.ts +74 -0
  106. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-actions.ts +19 -0
  107. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-selectors.ts +49 -0
  108. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-formatters.ts +115 -0
  109. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test-fixtures.ts +2 -0
  110. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  111. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +4 -0
  112. package/templates/.agents/dashboard/modules/operations/src/react/EntityTableBadges.tsx +34 -0
  113. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +8 -38
  114. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview-keeps-structure-classes-mutually-exclusive.test.ts +243 -0
  115. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +27 -275
  116. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +70 -247
  117. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewCharts.tsx +171 -0
  118. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewPanels.tsx +64 -0
  119. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +4 -3
  120. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.test.tsx +2 -1
  121. package/templates/.agents/dashboard/modules/operations/src/react/manual-work-priority.test.ts +2 -1
  122. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +6 -2
  123. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +6 -14
  124. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +3 -1
  125. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +2 -0
  126. package/templates/.agents/dashboard/modules/operations/src/react/overview-metric-format.ts +32 -0
  127. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  128. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  129. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  130. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  131. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  132. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +1458 -1458
  133. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1425 -1425
  134. package/templates/.agents/dashboard/packages/local-runtime/package.json +2 -1
  135. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +7 -3
  136. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1458 -1459
  137. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +25 -0
  138. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.test.ts +1 -1
  139. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +53 -8
  140. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +79 -24
  141. package/templates/.agents/dashboard/packages/local-runtime/src/projection-refresh-diagnostics.ts +35 -0
  142. package/templates/.agents/dashboard/packages/local-runtime/src/server-costs-routes.ts +168 -0
  143. package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +344 -0
  144. package/templates/.agents/dashboard/packages/local-runtime/src/server-http-contracts.ts +31 -0
  145. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-routes.ts +345 -0
  146. package/templates/.agents/dashboard/packages/local-runtime/src/server-read-projections.ts +461 -0
  147. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +78 -0
  148. package/templates/.agents/dashboard/packages/local-runtime/src/server-route-context.ts +13 -0
  149. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +18 -0
  150. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -3
  151. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +22 -21
  152. package/templates/.agents/dashboard/packages/local-runtime/src/server-sqlite-view-allowlist.test.ts +25 -0
  153. package/templates/.agents/dashboard/packages/local-runtime/src/server-trace-routes.ts +64 -0
  154. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +2 -30
  155. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +6 -23
  156. package/templates/.agents/dashboard/packages/local-runtime/src/server.test-fixtures.ts +1 -1
  157. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -330
  158. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +73 -1060
  159. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-materialize-first-fold.ts +78 -0
  160. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-materialize-projections.ts +561 -0
  161. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-materialize-runs.ts +600 -0
  162. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.test.ts +1 -1
  163. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-agent-dimension.ts +269 -0
  164. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-context.ts +32 -0
  165. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-entity-group.ts +244 -0
  166. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-filtered-dimension.ts +292 -0
  167. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-financial.ts +547 -0
  168. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-model-dimension.ts +360 -0
  169. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +169 -0
  170. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +475 -0
  171. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-temporal-dimension.ts +132 -0
  172. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-timing.ts +299 -0
  173. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +692 -0
  174. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts +739 -0
  175. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-does-not-convert-delayed-historical-finalizer.test.ts +706 -0
  176. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-filesystem.ts +748 -0
  177. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-first-fold-types.ts +1 -1
  178. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-keeps-orphan-telemetry-out-kanban-without.test.ts +710 -0
  179. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-marks-token-cost-telemetry-unavailable-when.test.ts +618 -0
  180. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +755 -0
  181. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuilds-disposable-snapshot-serves-zaip-records.test.ts +276 -0
  182. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +193 -0
  183. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-types.ts +123 -0
  184. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +1 -2805
  185. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +17 -6930
  186. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-base-views.ts +385 -0
  187. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-dashboard-views.ts +412 -0
  188. package/templates/.agents/dashboard/packages/local-runtime/src/work-priority-contract.test.ts +4 -3
  189. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-build.mjs +195 -0
  190. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.test.mjs +2 -7
  191. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +35 -0
  192. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-navigation.mjs +114 -0
  193. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-paths.mjs +90 -0
  194. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +89 -418
  195. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +22 -12
  196. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  197. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  198. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +4 -1
  199. package/templates/.agents/design/.zaip-managed.json +5 -5
  200. package/templates/.agents/design/DESIGN.md +2 -186
  201. package/templates/.agents/design/README.md +12 -2
  202. package/templates/.agents/design/apps/catalog/package.json +1 -1
  203. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  204. package/templates/.agents/growth/strategy.md +3 -0
  205. package/templates/.agents/marketing/strategy.md +3 -0
  206. package/templates/.agents/requirements/autonomous-assurance.md +2 -1
  207. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  208. package/templates/.agents/requirements/comparative-audit.md +1 -1
  209. package/templates/.agents/requirements/context-engine.md +1 -1
  210. package/templates/.agents/requirements/cost-observability.md +1 -1
  211. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  212. package/templates/.agents/requirements/data-export.md +1 -1
  213. package/templates/.agents/requirements/design-system.md +13 -4
  214. package/templates/.agents/requirements/execution-engine.md +1 -1
  215. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  216. package/templates/.agents/requirements/model-orchestration.md +1 -1
  217. package/templates/.agents/requirements/public-distribution.md +4 -2
  218. package/templates/.agents/requirements/software-architecture.md +1 -1
  219. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  220. package/templates/.agents/requirements/work-lifecycle.md +3 -3
  221. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  222. package/templates/.agents/rules/architecture.md +2 -0
  223. package/templates/.agents/rules/astra-diagnostic.md +1 -1
  224. package/templates/.agents/rules/autonomy-decision.md +3 -1
  225. package/templates/.agents/rules/cloud-hostinger-autonomy.md +2 -0
  226. package/templates/.agents/rules/context.md +2 -1
  227. package/templates/.agents/rules/core.md +3 -3
  228. package/templates/.agents/rules/design-system-central-contract.md +17 -0
  229. package/templates/.agents/rules/evolution.md +3 -1
  230. package/templates/.agents/rules/frontend-lite.md +3 -1
  231. package/templates/.agents/rules/local-sqlite-storage.md +2 -2
  232. package/templates/.agents/rules/meta-mcp.md +3 -1
  233. package/templates/.agents/rules/model-orchestration.md +1 -1
  234. package/templates/.agents/rules/run-lifecycle-closure.md +6 -4
  235. package/templates/.agents/rules/security.md +3 -1
  236. package/templates/.agents/rules/specialist-routing.md +3 -1
  237. package/templates/.agents/rules/text-writing-utf8.md +1 -1
  238. package/templates/.agents/rules/workflow.md +7 -5
  239. package/templates/.agents/runtime/VERSION +1 -1
  240. package/templates/.agents/runtime/bin/zaip.mjs +519 -247
  241. package/templates/.agents/runtime/checksums.sha256 +6 -6
  242. package/templates/.agents/runtime/manifest.json +1 -1
  243. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
  244. package/templates/.agents/runtime/provenance.json +5 -5
  245. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  246. package/templates/.agents/sales/playbook.md +3 -0
  247. package/templates/.agents/skills/zaip-aios/SKILL.md +5 -3
  248. package/templates/.agents/skills/zaip-skill-adapt-stack/SKILL.md +1 -0
  249. package/templates/.agents/skills/zaip-skill-api-contracts/SKILL.md +14 -0
  250. package/templates/.agents/skills/zaip-skill-api-contracts/agents/openai.yaml +4 -0
  251. package/templates/.agents/skills/zaip-skill-application-observability/SKILL.md +15 -0
  252. package/templates/.agents/skills/zaip-skill-application-observability/agents/openai.yaml +4 -0
  253. package/templates/.agents/skills/zaip-skill-application-observability/references/signal-design.md +3 -0
  254. package/templates/.agents/skills/zaip-skill-application-readiness/SKILL.md +15 -0
  255. package/templates/.agents/skills/zaip-skill-application-readiness/agents/openai.yaml +4 -0
  256. package/templates/.agents/skills/zaip-skill-application-readiness/references/evidence-matrix.md +3 -0
  257. package/templates/.agents/skills/zaip-skill-architecture-design/SKILL.md +14 -0
  258. package/templates/.agents/skills/zaip-skill-architecture-design/agents/openai.yaml +4 -0
  259. package/templates/.agents/skills/zaip-skill-async-jobs/SKILL.md +14 -0
  260. package/templates/.agents/skills/zaip-skill-async-jobs/agents/openai.yaml +4 -0
  261. package/templates/.agents/skills/zaip-skill-auth-tenancy/SKILL.md +15 -0
  262. package/templates/.agents/skills/zaip-skill-auth-tenancy/agents/openai.yaml +4 -0
  263. package/templates/.agents/skills/zaip-skill-auth-tenancy/references/data-access-paths.md +9 -0
  264. package/templates/.agents/skills/zaip-skill-auth-tenancy/references/worker-scenario.md +5 -0
  265. package/templates/.agents/skills/zaip-skill-auth-tenancy/scripts/worker-contract.test.mjs +27 -0
  266. package/templates/.agents/skills/zaip-skill-author-skills/SKILL.md +2 -0
  267. package/templates/.agents/skills/zaip-skill-author-skills/references/engineering-skill-index.md +9 -0
  268. package/templates/.agents/skills/zaip-skill-author-skills/scripts/audit-skill-catalog.mjs +5 -3
  269. package/templates/.agents/skills/zaip-skill-author-skills/scripts/author-skill-scripts.test.mjs +27 -0
  270. package/templates/.agents/skills/zaip-skill-backend/SKILL.md +4 -0
  271. package/templates/.agents/skills/zaip-skill-backend/references/deployment-environments.md +75 -0
  272. package/templates/.agents/skills/zaip-skill-backend/references/supabase-migration-contract.md +23 -0
  273. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +2 -0
  274. package/templates/.agents/skills/zaip-skill-cache-strategy/SKILL.md +14 -0
  275. package/templates/.agents/skills/zaip-skill-cache-strategy/agents/openai.yaml +4 -0
  276. package/templates/.agents/skills/zaip-skill-change/SKILL.md +1 -1
  277. package/templates/.agents/skills/zaip-skill-cloud-engineering/SKILL.md +7 -0
  278. package/templates/.agents/skills/zaip-skill-cloudflare-github-deploy/SKILL.md +37 -0
  279. package/templates/.agents/skills/zaip-skill-cloudflare-github-deploy/agents/openai.yaml +6 -0
  280. package/templates/.agents/skills/zaip-skill-cloudflare-github-deploy/references/workers-static-assets.md +26 -0
  281. package/templates/.agents/skills/zaip-skill-cloudflare-github-deploy/references/workflow-templates.md +71 -0
  282. package/templates/.agents/skills/zaip-skill-cloudflare-github-deploy/scripts/cloudflare-github-deploy-contract.test.mjs +77 -0
  283. package/templates/.agents/skills/zaip-skill-config/SKILL.md +21 -11
  284. package/templates/.agents/skills/zaip-skill-config/agents/openai.yaml +2 -2
  285. package/templates/.agents/skills/zaip-skill-container-runtime/SKILL.md +14 -0
  286. package/templates/.agents/skills/zaip-skill-container-runtime/agents/openai.yaml +4 -0
  287. package/templates/.agents/skills/zaip-skill-context/SKILL.md +6 -1
  288. package/templates/.agents/skills/zaip-skill-context/references/prd-spec-task-handoff.md +10 -0
  289. package/templates/.agents/skills/zaip-skill-context/references/rule-selection.md +20 -0
  290. package/templates/.agents/skills/zaip-skill-cqrs/SKILL.md +1 -0
  291. package/templates/.agents/skills/zaip-skill-cybersecurity/SKILL.md +18 -0
  292. package/templates/.agents/skills/zaip-skill-cybersecurity/agents/openai.yaml +4 -0
  293. package/templates/.agents/skills/zaip-skill-cybersecurity/references/audit-contract.md +94 -0
  294. package/templates/.agents/skills/zaip-skill-cybersecurity/scripts/score-audit.mjs +185 -0
  295. package/templates/.agents/skills/zaip-skill-cybersecurity/scripts/score-audit.test.mjs +101 -0
  296. package/templates/.agents/skills/zaip-skill-data-modeling/SKILL.md +14 -0
  297. package/templates/.agents/skills/zaip-skill-data-modeling/agents/openai.yaml +4 -0
  298. package/templates/.agents/skills/zaip-skill-database-migrations/SKILL.md +20 -0
  299. package/templates/.agents/skills/zaip-skill-database-migrations/agents/openai.yaml +4 -0
  300. package/templates/.agents/skills/zaip-skill-database-migrations/references/manual-mode.md +15 -0
  301. package/templates/.agents/skills/zaip-skill-database-migrations/references/promotion-example.md +5 -0
  302. package/templates/.agents/skills/zaip-skill-database-migrations/scripts/export-manual-sql.mjs +145 -0
  303. package/templates/.agents/skills/zaip-skill-database-migrations/scripts/export-manual-sql.test.mjs +178 -0
  304. package/templates/.agents/skills/zaip-skill-dependency-upgrade/SKILL.md +14 -0
  305. package/templates/.agents/skills/zaip-skill-dependency-upgrade/agents/openai.yaml +4 -0
  306. package/templates/.agents/skills/zaip-skill-deployment-pipeline/SKILL.md +15 -0
  307. package/templates/.agents/skills/zaip-skill-deployment-pipeline/agents/openai.yaml +4 -0
  308. package/templates/.agents/skills/zaip-skill-deployment-pipeline/references/runtime-shapes.md +3 -0
  309. package/templates/.agents/skills/zaip-skill-design-system/SKILL.md +20 -9
  310. package/templates/.agents/skills/zaip-skill-dev-environment/SKILL.md +14 -0
  311. package/templates/.agents/skills/zaip-skill-dev-environment/agents/openai.yaml +4 -0
  312. package/templates/.agents/skills/zaip-skill-dev-environment/references/inventory-contract.md +7 -0
  313. package/templates/.agents/skills/zaip-skill-dev-environment/scripts/inventory.mjs +77 -0
  314. package/templates/.agents/skills/zaip-skill-disaster-recovery/SKILL.md +15 -0
  315. package/templates/.agents/skills/zaip-skill-disaster-recovery/agents/openai.yaml +4 -0
  316. package/templates/.agents/skills/zaip-skill-disaster-recovery/references/recovery-scope.md +3 -0
  317. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +3 -3
  318. package/templates/.agents/skills/zaip-skill-execution/references/correction-cycle.md +7 -0
  319. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +1 -5
  320. package/templates/.agents/skills/zaip-skill-external-integrations/SKILL.md +14 -0
  321. package/templates/.agents/skills/zaip-skill-external-integrations/agents/openai.yaml +4 -0
  322. package/templates/.agents/skills/zaip-skill-frontend/SKILL.md +4 -0
  323. package/templates/.agents/skills/zaip-skill-frontend-componentization/SKILL.md +44 -0
  324. package/templates/.agents/skills/zaip-skill-frontend-componentization/agents/openai.yaml +4 -0
  325. package/templates/.agents/skills/zaip-skill-frontend-data-flows/SKILL.md +14 -0
  326. package/templates/.agents/skills/zaip-skill-frontend-data-flows/agents/openai.yaml +4 -0
  327. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +2 -0
  328. package/templates/.agents/skills/zaip-skill-github-actions-setup/SKILL.md +36 -0
  329. package/templates/.agents/skills/zaip-skill-github-actions-setup/agents/openai.yaml +4 -0
  330. package/templates/.agents/skills/zaip-skill-github-actions-setup/references/cloudflare-pages.md +33 -0
  331. package/templates/.agents/skills/zaip-skill-github-actions-setup/references/github.md +63 -0
  332. package/templates/.agents/skills/zaip-skill-github-actions-setup/references/hostinger-vps.md +55 -0
  333. package/templates/.agents/skills/zaip-skill-github-actions-setup/references/npm.md +30 -0
  334. package/templates/.agents/skills/zaip-skill-github-actions-setup/scripts/check-skill.mjs +55 -0
  335. package/templates/.agents/skills/zaip-skill-github-actions-setup/scripts/verify-cloudflare.mjs +61 -0
  336. package/templates/.agents/skills/zaip-skill-github-actions-setup/scripts/verify-cloudflare.test.mjs +128 -0
  337. package/templates/.agents/skills/zaip-skill-github-actions-setup/scripts/verify-ssh.integration.test.mjs +106 -0
  338. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +43 -76
  339. package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +131 -0
  340. package/templates/.agents/skills/zaip-skill-github-deploy/references/github-release-workflow-template.md +31 -0
  341. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-gate-matrix.md +24 -0
  342. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +44 -0
  343. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-notes.md +134 -0
  344. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/consumer-release-preparation.mjs +389 -0
  345. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/consumer-release-preparation.test.mjs +134 -0
  346. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +25 -2
  347. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-readiness-api.test.mjs +134 -0
  348. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-readiness.mjs +98 -0
  349. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-readiness.test.mjs +25 -0
  350. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/install-github-readiness-workflow.mjs +159 -0
  351. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/install-github-readiness-workflow.test.mjs +160 -0
  352. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.mjs +149 -12
  353. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.test.mjs +64 -5
  354. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/release-notes-coverage.test.mjs +126 -0
  355. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/resolve-release-identity.mjs +78 -0
  356. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/resolve-release-identity.test.mjs +51 -0
  357. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/validate-release-notes.mjs +184 -0
  358. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/validate-release-notes.test.mjs +109 -0
  359. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +2 -0
  360. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/SKILL.md +18 -10
  361. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/agents/openai.yaml +3 -4
  362. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/aws-cli-terraform.md +62 -0
  363. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/guia-iniciante-hostinger-vps.md +183 -0
  364. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +2 -0
  365. package/templates/.agents/skills/zaip-skill-module-scaffold/SKILL.md +14 -0
  366. package/templates/.agents/skills/zaip-skill-module-scaffold/agents/openai.yaml +4 -0
  367. package/templates/.agents/skills/zaip-skill-module-scaffold/references/scaffold-contract.md +9 -0
  368. package/templates/.agents/skills/zaip-skill-module-scaffold/scripts/helpers.test.mjs +121 -0
  369. package/templates/.agents/skills/zaip-skill-module-scaffold/scripts/scaffold.mjs +169 -0
  370. package/templates/.agents/skills/zaip-skill-move-prd/SKILL.md +1 -1
  371. package/templates/.agents/skills/zaip-skill-move-spec/SKILL.md +1 -1
  372. package/templates/.agents/skills/zaip-skill-performance-engineering/SKILL.md +14 -0
  373. package/templates/.agents/skills/zaip-skill-performance-engineering/agents/openai.yaml +4 -0
  374. package/templates/.agents/skills/zaip-skill-prd/SKILL.md +1 -1
  375. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +1 -0
  376. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +26 -9
  377. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +39 -13
  378. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +1 -0
  379. package/templates/.agents/skills/zaip-skill-refactor-landing-page/SKILL.md +66 -0
  380. package/templates/.agents/skills/zaip-skill-refactor-landing-page/agents/openai.yaml +4 -0
  381. package/templates/.agents/skills/zaip-skill-refactor-landing-page/references/behavioral-dry-run.md +23 -0
  382. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +40 -1
  383. package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +98 -0
  384. package/templates/.agents/skills/zaip-skill-review/SKILL.md +1 -0
  385. package/templates/.agents/skills/zaip-skill-rules/SKILL.md +3 -3
  386. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +2 -0
  387. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +19 -21
  388. package/templates/.agents/skills/zaip-skill-setup/agents/openai.yaml +2 -2
  389. package/templates/.agents/skills/zaip-skill-spec/SKILL.md +3 -2
  390. package/templates/.agents/skills/zaip-skill-storage-uploads/SKILL.md +14 -0
  391. package/templates/.agents/skills/zaip-skill-storage-uploads/agents/openai.yaml +4 -0
  392. package/templates/.agents/skills/zaip-skill-systematic-debugging/SKILL.md +14 -0
  393. package/templates/.agents/skills/zaip-skill-systematic-debugging/agents/openai.yaml +4 -0
  394. package/templates/.agents/skills/zaip-skill-task/SKILL.md +16 -7
  395. package/templates/.agents/skills/zaip-skill-task/references/commit-policy.md +25 -0
  396. package/templates/.agents/skills/zaip-skill-test-data/SKILL.md +14 -0
  397. package/templates/.agents/skills/zaip-skill-test-data/agents/openai.yaml +4 -0
  398. package/templates/.agents/skills/zaip-skill-test-data/scripts/generate.mjs +72 -0
  399. package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +15 -0
  400. package/templates/.agents/skills/zaip-skill-testing-strategy/agents/openai.yaml +4 -0
  401. package/templates/.agents/skills/zaip-skill-testing-strategy/references/example-map.md +3 -0
  402. package/templates/.agents/skills/zaip-skill-website-readiness/SKILL.md +8 -0
  403. package/templates/.agents/zaip-aios/VERSION +1 -1
  404. package/templates/.agents/zaip-aios/guides/10-seguranca.md +2 -1
  405. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +5 -3
  406. package/templates/scripts/validate-cloud-hostinger-contract.mjs +34 -0
  407. package/templates/template-manifest.json +955 -211
  408. package/templates/.agents/branding/.gitkeep +0 -0
  409. package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-snUCQYTC.js +0 -1
  410. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-BPtVUGgs.js +0 -1
  411. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-3Awyaovx.js +0 -3
  412. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-GgzpZ_PY.css +0 -1
  413. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BCvZFMEf.js +0 -1
  414. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DbaLbU2q.js +0 -68
  415. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-_MEktQNM.js +0 -2
  416. package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-D9-fqq9M.js +0 -1
  417. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-BDe4_Ubl.js +0 -1
  418. package/templates/.agents/growth/.gitkeep +0 -0
  419. package/templates/.agents/marketing/.gitkeep +0 -0
  420. package/templates/.agents/sales/.gitkeep +0 -0
@@ -1,27 +1,13 @@
1
- import { createHash, randomBytes } from "node:crypto";
1
+ import { randomBytes } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
4
  import { serve } from "@hono/node-server";
5
5
  import { serveStatic } from "@hono/node-server/serve-static";
6
- import { loadWorkspaceLayout, WorkspaceFileSystem, type WorkspaceLayout } from "@zaip-ai/core";
6
+ import { WorkspaceFileSystem, type WorkspaceLayout } from "@zaip-ai/core";
7
7
  import { fxRateSchema, PricingRepository, pricingCatalogSchema } from "@zaip-ai/costs";
8
- import {
9
- buildCostSummaryProjection,
10
- type CostSummarySource,
11
- } from "@zaip-ai/dashboard-cost-monitor/server";
12
- import type { OperationsProjection } from "@zaip-ai/dashboard-operations/contracts";
13
- import {
14
- buildOperationsProjection,
15
- buildQuickOperationsProjection,
16
- readPrdDetail,
17
- readSpecDetail,
18
- readTaskDetail,
19
- } from "@zaip-ai/dashboard-operations/server";
20
- import { buildTaskTrace } from "@zaip-ai/dashboard-traceability/server";
21
- import { LifecycleManifestStore } from "@zaip-ai/lifecycle-manifests";
22
8
  import { type Context, Hono } from "hono";
23
9
  import { getCookie, setCookie } from "hono/cookie";
24
- import type { ContentfulStatusCode } from "hono/utils/http-status";
10
+ import { captureActiveCodexTelemetry } from "./active-codex-telemetry.js";
25
11
  import { runAgentAdapterSync } from "./agent-adapter-sync.js";
26
12
  import {
27
13
  type AgentAdapterSyncResult,
@@ -34,47 +20,28 @@ import {
34
20
  readProjectOverview,
35
21
  readProjectSettings,
36
22
  } from "./project-settings.js";
37
- import { agentPortraitFiles, openWorkIds } from "./server-agent-assets.js";
38
- import {
39
- type BoardClassification,
40
- boardFilters,
41
- boardPage,
42
- boardStage,
43
- classificationPage,
44
- } from "./server-board.js";
23
+ import { createProjectionRefreshDiagnostics } from "./projection-refresh-diagnostics.js";
24
+ import { agentPortraitFiles } from "./server-agent-assets.js";
45
25
  import { registerBrowserLeaseRoute } from "./server-browser-lease.js";
46
- import { registerDailyReadModelRoute } from "./server-daily-read-models-route.js";
26
+ import { registerCostsRoutes } from "./server-costs-routes.js";
47
27
  import {
48
- changeEntityPageRows,
49
- decisionEntityPageRows,
50
- filterLifecyclePageRows,
51
- operationsPageFilters,
52
- } from "./server-operations-pages.js";
53
- import { registerOperationsRunsRoute } from "./server-operations-runs-route.js";
28
+ API_PREFIX,
29
+ headOrJson,
30
+ requireV2Layout,
31
+ V2WorkspaceRequiredError,
32
+ } from "./server-http-contracts.js";
33
+ import { registerOperationsRoutes } from "./server-operations-routes.js";
54
34
  import { projectionPeriodFilter } from "./server-period.js";
55
- import { periodMacroMetric, projectAllTimeTopTasks } from "./server-period-macro-metric.js";
56
- import {
57
- buildCanonicalOperationsProjection,
58
- buildCurrentCostProjection,
59
- buildManifestCostRunPage,
60
- buildManifestFirstCostProjection,
61
- isUnfilteredCostRequest,
62
- knownWorkIds,
63
- normalizeWorkPriority,
64
- readCurrentFxRate,
65
- readWorkPriority,
66
- } from "./server-projections.js";
35
+ import { buildFirstFoldQuickProjection, dashboardSnapshot } from "./server-read-projections.js";
67
36
  import { isAllowedDashboardAuthority } from "./server-request-authority.js";
68
37
  import { refreshSqliteSnapshotInWorker } from "./server-snapshot-refresh.js";
69
38
  import { readCommittedSnapshotRevision } from "./server-snapshot-revision.js";
70
39
  import { startSnapshotSynchronization } from "./server-snapshot-synchronization.js";
71
- import { buildSqliteTaskTrace } from "./server-sqlite-task-trace.js";
72
40
  import { writeReadModelStartup, writeStartupPhase } from "./server-startup.js";
73
- import { queryValues, selectedCurrency } from "./server-summary-helpers.js";
41
+ import { selectedCurrency } from "./server-summary-helpers.js";
42
+ import { registerTraceRoutes } from "./server-trace-routes.js";
74
43
  import {
75
44
  createWorkReconciliation,
76
- reconciliationNeedsRefresh,
77
- startHostTerminalReconciliation,
78
45
  waitForWorkReconciliation,
79
46
  } from "./server-work-reconciliation.js";
80
47
 
@@ -84,14 +51,9 @@ import {
84
51
  dashboardIdentity,
85
52
  dashboardWorkspaceKey,
86
53
  ReconciliationCommandTimeoutError,
87
- type ReconciliationResult,
88
54
  reconcileOpenWork,
89
55
  } from "./server-workspace.js";
90
- import {
91
- rebuildSqliteReadModel,
92
- SqliteReadModelFileSystem,
93
- sqliteViewMetadata,
94
- } from "./sqlite-read-model.js";
56
+ import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
95
57
 
96
58
  export { manifestRun, mergeManifestRuns } from "./manifest-runs.js";
97
59
  export { openWorkIds } from "./server-agent-assets.js";
@@ -110,15 +72,10 @@ export {
110
72
  validateSqliteReadModelSchema,
111
73
  } from "./sqlite-read-model.js";
112
74
 
113
- type OverviewMacroMetrics = NonNullable<
114
- NonNullable<OperationsProjection["summary"]["overview_metrics"]>["macro_metrics"]
115
- >;
116
-
117
- const API_PREFIX = "/api/v2";
118
75
  const HOST = "127.0.0.1";
119
76
  const RUNTIME_KIND = "zaip-dashboard-sqlite-runtime";
120
77
  const SESSION_COOKIE = "zaip-dashboard-sqlite-session";
121
- const WORK_PRIORITY_PATH = ".agents/dashboard/work-priority.json";
78
+
122
79
  const SNAPSHOT_CACHE_TTL_MS = 5_000;
123
80
  interface DashboardAppOptions {
124
81
  syncAgentAdapters?: () => Promise<AgentAdapterSyncResult>;
@@ -137,10 +94,6 @@ interface DashboardAppOptions {
137
94
  launcherOwned?: boolean;
138
95
  }
139
96
 
140
- class V2WorkspaceRequiredError extends Error {
141
- override readonly name = "V2WorkspaceRequiredError";
142
- }
143
-
144
97
  function isAllowedOrigin(origin: string): boolean {
145
98
  try {
146
99
  const parsed = new URL(origin);
@@ -151,7 +104,6 @@ function isAllowedOrigin(origin: string): boolean {
151
104
  return false;
152
105
  }
153
106
  }
154
-
155
107
  function isSafeUiPath(pathname: string): boolean {
156
108
  try {
157
109
  const decoded = decodeURIComponent(pathname).replaceAll("\\", "/");
@@ -160,462 +112,29 @@ function isSafeUiPath(pathname: string): boolean {
160
112
  return false;
161
113
  }
162
114
  }
163
-
164
115
  function defaultSessionToken(): string {
165
116
  return process.env.ZAIP_UI_TOKEN ?? randomBytes(24).toString("base64url");
166
117
  }
167
-
168
118
  function shouldBootstrapBrowserSession(context: Context): boolean {
169
119
  if (context.req.method !== "GET" && context.req.method !== "HEAD") return false;
170
120
  if (context.req.path !== "/" && context.req.path !== "/index.html") return false;
171
-
172
121
  const origin = context.req.header("origin");
173
122
  if (origin !== undefined && !isAllowedOrigin(origin)) return false;
174
123
  return context.req.header("sec-fetch-site") !== "cross-site";
175
124
  }
176
125
 
177
- async function requireV2Layout(filesystem: WorkspaceFileSystem): Promise<WorkspaceLayout> {
178
- const layout = await loadWorkspaceLayout(filesystem);
179
- if (layout.version === 1) {
180
- throw new V2WorkspaceRequiredError("Dashboard API requires a modern ZAIP-AIOS workspace");
181
- }
182
- return layout;
183
- }
184
-
185
- function headOrJson(
186
- context: Context,
187
- payload: unknown,
188
- status: ContentfulStatusCode = 200,
189
- cacheable = false,
190
- ): Response {
191
- if (cacheable) {
192
- const etag = `"${createHash("sha256").update(JSON.stringify(payload)).digest("base64url")}"`;
193
- context.header("ETag", etag);
194
- context.header("Cache-Control", "private, no-cache");
195
- if (context.req.header("if-none-match") === etag) return context.body(null, 304);
196
- }
197
- if (context.req.method === "HEAD") return context.body(null, status);
198
- return context.json(payload, status);
199
- }
200
126
  /**
201
127
  * Keep the entity/macro details on the snapshot filesystem, while making the
202
128
  * first-fold counters and token/cost facts come from SQL.
203
129
  * Duration intentionally retains the shared hybrid legacy policy in the
204
130
  * Operations module; its underlying daily rows are still SQLite-backed.
205
131
  */
206
- async function buildFirstFoldQuickProjection(
207
- filesystem: WorkspaceFileSystem,
208
- layout: WorkspaceLayout,
209
- filter: { from: string | null; to: string | null },
210
- options: {
211
- includeBoardTasks?: boolean;
212
- includeExtended?: boolean;
213
- includePeriods?: boolean;
214
- includeTopTasks?: boolean;
215
- } = {},
216
- ): Promise<OperationsProjection> {
217
- // The comparison runtime deliberately has no lifecycle daily aggregates in
218
- // its snapshot. Its immutable filesystem contains only canonical records,
219
- // while the SQL views supply the first-fold facts. Never enter the legacy
220
- // quick projection here: it requires lifecycle daily read models and would
221
- // make an otherwise valid SQLite dashboard fail on first paint.
222
- const projection =
223
- filesystem instanceof SqliteReadModelFileSystem
224
- ? buildSqliteOperationsProjection(filesystem, layout, filter, options)
225
- : await buildQuickOperationsProjection(filesystem, layout, filter, options);
226
- if (!(filesystem instanceof SqliteReadModelFileSystem)) return projection;
227
- const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
228
- const aggregate =
229
- filter.from === null && filter.to === null
230
- ? filesystem.firstFoldViewportAllTime().aggregate
231
- : (cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter));
232
- return {
233
- ...projection,
234
- summary: {
235
- ...projection.summary,
236
- run_count: aggregate.total_run_count,
237
- overview_metrics: {
238
- ...projection.summary.overview_metrics,
239
- duration_ms: aggregate.duration_ms,
240
- tokens: aggregate.total_tokens,
241
- cost_brl: aggregate.cost_brl,
242
- execution_day_count: aggregate.execution_day_count,
243
- data_source: "run_records",
244
- },
245
- },
246
- };
247
- }
248
132
 
249
133
  /**
250
134
  * SQLite's files snapshot intentionally excludes derived lifecycle manifests.
251
135
  * Build the Operations first fold from its canonical SQL views instead of
252
136
  * treating a missing manifest as a broken workspace.
253
137
  */
254
- function buildSqliteOperationsProjection(
255
- filesystem: SqliteReadModelFileSystem,
256
- layout: WorkspaceLayout,
257
- filter: { from: string | null; to: string | null },
258
- options: {
259
- includeBoardTasks?: boolean;
260
- includeExtended?: boolean;
261
- includePeriods?: boolean;
262
- includeTopTasks?: boolean;
263
- },
264
- ): OperationsProjection {
265
- const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
266
- const fullAllTime =
267
- filter.from === null && filter.to === null
268
- ? options.includeExtended === true
269
- ? filesystem.firstFoldAllTime()
270
- : null
271
- : null;
272
- const allTime =
273
- fullAllTime ??
274
- (filter.from === null && filter.to === null ? filesystem.firstFoldViewportAllTime() : null);
275
- const allTimeBoard =
276
- filter.from === null && filter.to === null && options.includeBoardTasks === true
277
- ? filesystem.firstFoldBoardAllTime()
278
- : null;
279
- const page = <T extends "task" | "spec" | "prd">(kind: T) =>
280
- cachedMonthlyQuick?.pages[kind] ??
281
- allTimeBoard?.pages[kind] ??
282
- filesystem.canonicalWorkPage(kind, filter, 0, 10_000).rows;
283
- const includeEntities = allTime === null || options.includeBoardTasks === true;
284
- const tasks = includeEntities ? page("task") : [];
285
- const specs = includeEntities ? page("spec") : [];
286
- const prds = includeEntities ? page("prd") : [];
287
- const aggregate =
288
- allTime?.aggregate ?? cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter);
289
- const legacyDraftCount = allTime?.task_status_counts.draft ?? 0;
290
- const taskCount =
291
- allTime === null ? tasks.length : Math.max(0, allTime.aggregate.task_count - legacyDraftCount);
292
- const allTimeTopTasks =
293
- filter.from === null &&
294
- filter.to === null &&
295
- (fullAllTime !== null || options.includeTopTasks === true)
296
- ? (fullAllTime?.top_tasks ?? filesystem.firstFoldTopTasksAllTime())
297
- : null;
298
- const topTasks =
299
- allTimeTopTasks === null
300
- ? (cachedMonthlyQuick?.top_tasks ?? filesystem.topTasks(filter)).map((task) => ({
301
- task_id: task.task_id,
302
- duration_ms: task.duration_ms,
303
- total_tokens: task.total_tokens,
304
- cost_brl: task.cost_brl,
305
- run_count: task.run_count,
306
- }))
307
- : projectAllTimeTopTasks(allTimeTopTasks);
308
- const macroMetrics =
309
- fullAllTime === null
310
- ? ({
311
- specs: periodMacroMetric(specs, "spec"),
312
- prds: periodMacroMetric(prds, "prd"),
313
- } satisfies OverviewMacroMetrics)
314
- : (Object.fromEntries(
315
- (["specs", "prds"] as const).map((kind) => {
316
- const metric = fullAllTime.macro_metrics[kind];
317
- return [
318
- kind,
319
- {
320
- status_counts: metric.status_counts,
321
- classification_counts: metric.classification_counts,
322
- entity_count: metric.entity_count,
323
- total_run_count: metric.total_run_count,
324
- total_duration_ms: metric.total_duration_ms,
325
- total_tokens: metric.total_tokens,
326
- total_cost_usd: metric.total_cost_usd,
327
- total_cost_brl: metric.total_cost_brl,
328
- average_duration_ms: metric.average_duration_ms,
329
- average_tokens: metric.average_tokens,
330
- average_cost_usd: metric.average_cost_usd,
331
- average_cost_brl: metric.average_cost_brl,
332
- latest_created_at: metric.latest_created_at,
333
- top_entities: metric.top_entities.map((entity) => ({
334
- entity_id: entity.entity_id,
335
- title: entity.title,
336
- duration_ms: entity.total_duration_ms,
337
- total_tokens: entity.total_tokens,
338
- total_cost_usd: entity.total_cost_usd,
339
- cost_brl: entity.total_cost_brl,
340
- run_count: entity.total_run_count,
341
- duration_share: entity.duration_share,
342
- })),
343
- },
344
- ];
345
- }),
346
- ) as NonNullable<
347
- NonNullable<OperationsProjection["summary"]["overview_metrics"]>["macro_metrics"]
348
- >);
349
- const statuses: Record<string, number> = Object.fromEntries(
350
- Object.entries(allTime?.task_status_counts ?? {}).filter(([status]) => status !== "draft"),
351
- );
352
- const risks: Record<string, number> = allTime?.task_risk_counts ?? {};
353
- if (allTime === null) {
354
- for (const task of tasks) {
355
- const status = typeof task.status === "string" ? task.status : "draft";
356
- const risk = typeof task.risk === "string" ? task.risk : "standard";
357
- statuses[status] = (statuses[status] ?? 0) + 1;
358
- risks[risk] = (risks[risk] ?? 0) + 1;
359
- }
360
- }
361
- const latest =
362
- allTime?.aggregate.latest_task_created_at ??
363
- [...tasks, ...specs, ...prds]
364
- .map((row) => (typeof row.occurred_at === "string" ? row.occurred_at : null))
365
- .filter((value): value is string => value !== null)
366
- .sort()
367
- .at(-1) ??
368
- null;
369
- const unavailableUsage = {
370
- events: 0,
371
- tokens: { input: null, cached_input: null, output: null, reasoning: null, total: null },
372
- cost: { usd: null, brl: null },
373
- coverage: { events: 0, tokens: 0, usd: 0, brl: 0 },
374
- };
375
- const totalUsage = {
376
- ...unavailableUsage,
377
- tokens: { ...unavailableUsage.tokens, total: aggregate.total_tokens },
378
- cost: { ...unavailableUsage.cost, brl: aggregate.cost_brl },
379
- };
380
- const usage = {
381
- conversation: unavailableUsage,
382
- planning: unavailableUsage,
383
- execution: unavailableUsage,
384
- total: totalUsage,
385
- new_work: unavailableUsage,
386
- rework: unavailableUsage,
387
- };
388
- const dailyPeriods = options.includePeriods === true ? filesystem.dailyPeriods(filter) : {};
389
- const hasUnknownUsage = Object.values(dailyPeriods).some(
390
- (period) => period.usage.total_tokens.unknown > 0 || period.usage.cost.unknown > 0,
391
- );
392
- const canonicalSource = filesystem.canonicalSource();
393
- const taskProjection = (row: Record<string, unknown>) => ({
394
- schema_version: 2 as const,
395
- id: String(row.id),
396
- revision: 1,
397
- change_id: typeof row.change_id === "string" ? row.change_id : "",
398
- title: String(row.title ?? row.id),
399
- short_name: String(row.title ?? row.id),
400
- objective: "",
401
- status: normalizeTaskStatus(row.status),
402
- attempt_count: null,
403
- risk: normalizeRisk(row.risk),
404
- priority: "medium" as const,
405
- owner: null,
406
- requirement_ids: [],
407
- acceptance_ids: [],
408
- depends_on: [],
409
- write_scopes: [],
410
- created_at: String(row.occurred_at),
411
- updated_at: String(row.occurred_at),
412
- started_at: null,
413
- finished_at: null,
414
- result: null,
415
- artifact_path: canonicalSource,
416
- progress: 0,
417
- spec_id: typeof row.spec_id === "string" ? row.spec_id : null,
418
- origin: typeof row.origin === "string" ? row.origin : null,
419
- execution_started: false,
420
- telemetry: {
421
- duration_ms: nullableMetric(row.total_duration),
422
- tokens: nullableMetric(row.total_tokens),
423
- cost_brl: nullableMetric(row.total_cost_brl),
424
- },
425
- });
426
- const macroProjection = (row: Record<string, unknown>, kind: "prd" | "spec") => {
427
- const entityUsage = {
428
- ...unavailableUsage,
429
- tokens: { ...unavailableUsage.tokens, total: nullableMetric(row.total_tokens) },
430
- cost: { ...unavailableUsage.cost, brl: nullableMetric(row.total_cost_brl) },
431
- };
432
- return {
433
- id: String(row.id),
434
- title: String(row.title ?? row.id),
435
- created_at: String(row.occurred_at),
436
- updated_at: String(row.occurred_at),
437
- kind,
438
- parent_id: null,
439
- artifact_path: canonicalSource,
440
- lifecycle_status: String(row.status ?? "draft"),
441
- work_status: normalizeMacroStatus(row.status),
442
- execution_ids: [],
443
- task_ids: [],
444
- planned_tasks: [],
445
- canonical_run_ids: [],
446
- run_ids: [],
447
- root_execution_run_ids: [],
448
- execution_count: 0,
449
- task_count: 0,
450
- run_count: nullableMetric(row.invocation_count) ?? 0,
451
- duration_ms: nullableMetric(row.total_duration),
452
- decisions: { human: 0, agent: 0 },
453
- rework_run_count: 0,
454
- quality: {
455
- completed_tasks: 0,
456
- task_completion_rate: 0,
457
- terminal_runs: 0,
458
- successful_runs: 0,
459
- terminal_success_rate: 0,
460
- rework_rate: 0,
461
- },
462
- usage: {
463
- conversation: unavailableUsage,
464
- planning: unavailableUsage,
465
- execution: unavailableUsage,
466
- total: entityUsage,
467
- new_work: unavailableUsage,
468
- rework: unavailableUsage,
469
- },
470
- };
471
- };
472
- return {
473
- schema_version: 2,
474
- layout_version: layout.version as 2 | 3,
475
- readonly: true,
476
- source: canonicalSource,
477
- filters: filter,
478
- summary: {
479
- prd_count: allTime?.aggregate.prd_count ?? prds.length,
480
- spec_count: allTime?.aggregate.spec_count ?? specs.length,
481
- execution_count: 0,
482
- task_count: taskCount,
483
- run_count: aggregate.total_run_count,
484
- overview_metrics: {
485
- duration_ms: aggregate.duration_ms,
486
- execution_day_count: aggregate.execution_day_count,
487
- tokens: aggregate.total_tokens,
488
- cost_brl: aggregate.cost_brl,
489
- prd_count: allTime?.aggregate.prd_count ?? prds.length,
490
- spec_count: allTime?.aggregate.spec_count ?? specs.length,
491
- selected_counts: {
492
- prds: allTime?.aggregate.prd_count ?? prds.length,
493
- specs: allTime?.aggregate.spec_count ?? specs.length,
494
- },
495
- task_count: taskCount,
496
- completed_prd_count:
497
- allTime?.aggregate.completed_prd_count ??
498
- prds.filter((row) => row.status === "completed").length,
499
- completed_spec_count:
500
- allTime?.aggregate.completed_spec_count ??
501
- specs.filter((row) => row.status === "completed").length,
502
- completed_task_count: allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0,
503
- task_status_counts: statuses,
504
- task_risk_counts: risks,
505
- top_tasks: topTasks,
506
- ...(macroMetrics === undefined ? {} : { macro_metrics: macroMetrics }),
507
- latest_task_created_at: latest,
508
- coverage: { duration: 1, tokens: hasUnknownUsage ? 0 : 1, cost: hasUnknownUsage ? 0 : 1 },
509
- data_source: "run_records",
510
- partial: true,
511
- },
512
- decisions: { human: 0, agent: 0 },
513
- source_latest_at: latest,
514
- quality: {
515
- completed_tasks: statuses.completed ?? 0,
516
- task_completion_rate:
517
- taskCount === 0
518
- ? 0
519
- : (allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0) / taskCount,
520
- terminal_runs: 0,
521
- successful_runs: 0,
522
- terminal_success_rate: 0,
523
- rework_rate: 0,
524
- },
525
- reconciliation: {
526
- phase_tokens_match_total: true,
527
- phase_usd_match_total: true,
528
- phase_brl_match_total: true,
529
- origin_tokens_match_total: true,
530
- },
531
- usage,
532
- periods: { daily: options.includePeriods === true ? dailyPeriods : {} },
533
- },
534
- origins: { conversation: usage, task: usage, spec: usage, prd: usage },
535
- prds: prds.map((row) => macroProjection(row, "prd")),
536
- specs: specs.map((row) => macroProjection(row, "spec")),
537
- tasks: options.includeBoardTasks ? tasks.map(taskProjection) : [],
538
- changes: [],
539
- executions: [],
540
- runs: [],
541
- decisions: [],
542
- evidences: [],
543
- validations: [],
544
- };
545
- }
546
-
547
- function nullableMetric(value: unknown): number | null {
548
- return typeof value === "number" && Number.isFinite(value) ? value : null;
549
- }
550
-
551
- function normalizeTaskStatus(
552
- value: unknown,
553
- ):
554
- | "draft"
555
- | "planned"
556
- | "executing"
557
- | "verifying"
558
- | "completed"
559
- | "failed"
560
- | "blocked"
561
- | "cancelled" {
562
- return [
563
- "draft",
564
- "planned",
565
- "executing",
566
- "verifying",
567
- "completed",
568
- "failed",
569
- "blocked",
570
- "cancelled",
571
- ].includes(String(value))
572
- ? (String(value) as
573
- | "draft"
574
- | "planned"
575
- | "executing"
576
- | "verifying"
577
- | "completed"
578
- | "failed"
579
- | "blocked"
580
- | "cancelled")
581
- : "draft";
582
- }
583
-
584
- function normalizeRisk(value: unknown): "lite" | "standard" | "critical" {
585
- return ["lite", "standard", "critical"].includes(String(value))
586
- ? (String(value) as "lite" | "standard" | "critical")
587
- : "standard";
588
- }
589
-
590
- function normalizeMacroStatus(
591
- value: unknown,
592
- ): "planning" | "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled" {
593
- const status = String(value);
594
- if (["executing", "verifying", "completed", "blocked", "failed", "cancelled"].includes(status))
595
- return status as "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled";
596
- return "planning";
597
- }
598
-
599
- async function firstFoldCostSource(
600
- filesystem: WorkspaceFileSystem,
601
- filters: Parameters<SqliteReadModelFileSystem["invocationCostRows"]>[0] = {
602
- from: null,
603
- to: null,
604
- },
605
- ): Promise<CostSummarySource> {
606
- if (filesystem instanceof SqliteReadModelFileSystem) {
607
- return filesystem.costMonitorSummaryAggregate(filters) as CostSummarySource;
608
- }
609
- const daily = await new LifecycleManifestStore(filesystem).readDailyReadModels("overview");
610
- if (daily === null) throw new Error("Lifecycle daily overview is not initialized");
611
- return {
612
- periods: {
613
- daily: Object.fromEntries(
614
- Object.entries(daily.days).map(([day, model]) => [day, model.overview]),
615
- ),
616
- },
617
- } satisfies CostSummarySource;
618
- }
619
138
 
620
139
  export function createDashboardApp(
621
140
  workspaceRoot = process.env.ZAIP_WORKSPACE ?? process.cwd(),
@@ -692,6 +211,7 @@ export function createDashboardApp(
692
211
  aggregateInFlight.clear();
693
212
  };
694
213
  let observedSnapshotRevision: string | null = null;
214
+ const projectionRefreshDiagnostics = createProjectionRefreshDiagnostics();
695
215
  let projectionRefreshInFlight: Promise<void> | null = null;
696
216
  let projectionRefreshRequested = false;
697
217
  const rebuildProjection = async () => {
@@ -701,15 +221,35 @@ export function createDashboardApp(
701
221
  }
702
222
  snapshotState = "updating";
703
223
  projectionRefreshInFlight = (async () => {
224
+ let passes = 0;
704
225
  do {
705
226
  projectionRefreshRequested = false;
706
227
  await refreshReadModel();
707
228
  if (isSqliteSnapshot) sqliteLayout = requireV2Layout(filesystem);
708
229
  clearSnapshotCache();
709
- } while (projectionRefreshRequested);
230
+ passes += 1;
231
+ // Publish a committed generation after a bounded catch-up pass. A
232
+ // busy watcher must not hold `/ready` in `updating` indefinitely.
233
+ } while (projectionRefreshRequested && passes < 2);
710
234
  snapshotState = "current";
235
+ projectionRefreshDiagnostics.clear();
711
236
  })().finally(() => {
712
237
  projectionRefreshInFlight = null;
238
+ // Preserve a source change that arrived during the bounded catch-up.
239
+ // It starts after the committed generation becomes observable, rather
240
+ // than extending the current `updating` state without bound.
241
+ if (projectionRefreshRequested) {
242
+ projectionRefreshRequested = false;
243
+ // Yield a real event-loop turn with `current` exposed before starting
244
+ // another rebuild. `/ready` can then advertise the committed SQLite
245
+ // generation and open tabs can read it coherently.
246
+ const followUp = setTimeout(() => {
247
+ void rebuildProjection().catch((error: unknown) =>
248
+ projectionRefreshDiagnostics.record({ trigger: "source_change", error }),
249
+ );
250
+ }, 250);
251
+ followUp.unref();
252
+ }
713
253
  });
714
254
  return projectionRefreshInFlight;
715
255
  };
@@ -718,6 +258,10 @@ export function createDashboardApp(
718
258
  staleSeed: isSqliteSnapshot && snapshotState === "updating",
719
259
  watchSources: isSqliteSnapshot && options.watchSources === true,
720
260
  refresh: rebuildProjection,
261
+ ...(isSqliteSnapshot && options.watchSources === true
262
+ ? { captureActiveTelemetry: () => captureActiveCodexTelemetry(workspaceRoot) }
263
+ : {}),
264
+ onRefreshFailure: projectionRefreshDiagnostics.record,
721
265
  });
722
266
  const reconcileWork = createWorkReconciliation({
723
267
  reconcile: (mode) =>
@@ -726,13 +270,9 @@ export function createDashboardApp(
726
270
  }),
727
271
  refresh: rebuildProjection,
728
272
  });
729
- if (
730
- isSqliteSnapshot &&
731
- options.watchSources === true &&
732
- process.env.ZAIP_DASHBOARD_WATCH_SOURCES !== "0"
733
- ) {
734
- startHostTerminalReconciliation(() => reconcileWork("host-terminal"));
735
- }
273
+ // Terminal usage is persisted by the host/CLI lifecycle boundary. The
274
+ // source synchronizer above projects those records, but the Dashboard must
275
+ // never run lifecycle reconciliation automatically on startup or a timer.
736
276
  const readSnapshot = (context: Context) => {
737
277
  const key = snapshotKey(context);
738
278
  const cached = cachedSnapshots.get(key);
@@ -758,7 +298,6 @@ export function createDashboardApp(
758
298
  snapshotInFlight.set(key, inFlight);
759
299
  return inFlight;
760
300
  };
761
-
762
301
  app.use("*", async (context, next) => {
763
302
  if (!isAllowedDashboardAuthority(context.req.url, context.req.header("host"))) {
764
303
  return context.json({ error: "Host rejected" }, 403);
@@ -779,7 +318,6 @@ export function createDashboardApp(
779
318
  }
780
319
  return next();
781
320
  });
782
-
783
321
  app.use(`${API_PREFIX}/*`, async (context, next) => {
784
322
  const settingsWrite =
785
323
  context.req.method === "PUT" &&
@@ -824,7 +362,6 @@ export function createDashboardApp(
824
362
  }
825
363
  if (context.req.path === `${API_PREFIX}/health` || context.req.path === `${API_PREFIX}/ready`)
826
364
  return next();
827
-
828
365
  const origin = context.req.header("origin");
829
366
  if (origin !== undefined && !isAllowedOrigin(origin)) {
830
367
  return context.json({ error: "Origin rejected" }, 403);
@@ -853,7 +390,6 @@ export function createDashboardApp(
853
390
  return next();
854
391
  });
855
392
  registerBrowserLeaseRoute(app, API_PREFIX, options.launcherOwned === true);
856
-
857
393
  app.on(["GET", "HEAD"], `${API_PREFIX}/health`, async (context) => {
858
394
  try {
859
395
  const layout = await readLayout();
@@ -861,12 +397,13 @@ export function createDashboardApp(
861
397
  kind: RUNTIME_KIND,
862
398
  workspaceKey,
863
399
  service: "zaip-aios-dashboard",
864
- status: "ok",
400
+ status: projectionRefreshDiagnostics.healthStatus,
865
401
  api_version: 2,
866
402
  workspace_layout: layout.version,
867
403
  read_only: true,
868
404
  storage: isSqliteSnapshot ? filesystem.metadata() : { storage_mode: "filesystem" },
869
405
  snapshot_state: snapshotState,
406
+ projection_refresh_failure: projectionRefreshDiagnostics.failure,
870
407
  });
871
408
  } catch (error) {
872
409
  return headOrJson(
@@ -884,7 +421,6 @@ export function createDashboardApp(
884
421
  );
885
422
  }
886
423
  });
887
-
888
424
  // Launcher readiness proves a canonical projection can be read; health alone
889
425
  // intentionally remains a lightweight process/layout probe.
890
426
  app.on(["GET", "HEAD"], `${API_PREFIX}/ready`, async (context) => {
@@ -913,6 +449,7 @@ export function createDashboardApp(
913
449
  // /ready must never start a competing projection in the background.
914
450
  day_count: metadata.daily_aggregate_count,
915
451
  snapshot_state: snapshotState,
452
+ projection_refresh_failure: projectionRefreshDiagnostics.failure,
916
453
  snapshot_revision: revision,
917
454
  });
918
455
  }
@@ -939,321 +476,21 @@ export function createDashboardApp(
939
476
  );
940
477
  }
941
478
  });
942
-
943
479
  // Header identity is intentionally separate from Operations projections.
944
480
  // The quick first fold omits it, and resolving this small configuration/git
945
481
  // value must not read, refresh, or otherwise materialize a snapshot.
946
482
  app.on(["GET", "HEAD"], `${API_PREFIX}/dashboard-identity`, async (context) =>
947
483
  headOrJson(context, await dashboardIdentity(filesystem, workspaceRoot)),
948
484
  );
949
-
950
- app.on(["GET", "HEAD"], `${API_PREFIX}/operations`, async (context) => {
951
- try {
952
- const requestStartedAt = performance.now();
953
- const scope = context.req.query("scope");
954
- if (
955
- scope !== "overview" &&
956
- scope !== "quick" &&
957
- scope !== "detail" &&
958
- scope !== "board" &&
959
- scope !== "classification"
960
- )
961
- return headOrJson(context, (await readSnapshot(context)).operations);
962
- const layout = await readLayout();
963
- const layoutMs = performance.now() - requestStartedAt;
964
- const { from, to } = projectionPeriodFilter(context);
965
- if (scope === "board") {
966
- const stage = boardStage(context.req.query("stage"));
967
- const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
968
- const limit = Math.min(
969
- 50,
970
- Math.max(1, Number.parseInt(context.req.query("limit") ?? "10", 10) || 10),
971
- );
972
- const board = await readAggregate(
973
- `operations:board:${from ?? ""}:${to ?? ""}`,
974
- async () => {
975
- const projection = await buildFirstFoldQuickProjection(
976
- filesystem,
977
- layout,
978
- { from, to },
979
- { includeBoardTasks: true },
980
- );
981
- return {
982
- projection,
983
- priority: await readWorkPriority(canonicalFilesystem, knownWorkIds(projection)),
984
- };
985
- },
986
- );
987
- const filters = boardFilters(context);
988
- const pageKey = JSON.stringify({ from, to, stage, offset, limit, ...filters });
989
- const page = await readAggregate(`operations:board-page:${pageKey}`, async () =>
990
- boardPage(board.projection, stage, offset, limit, board.priority.item_ids, filters),
991
- );
992
- return headOrJson(context, page, 200, true);
993
- }
994
- if (scope === "classification") {
995
- const kind = context.req.query("kind");
996
- const classification = context.req.query("classification");
997
- if (kind !== "task" && kind !== "spec" && kind !== "prd")
998
- return headOrJson(context, { error: "A valid work kind is required" }, 400);
999
- if (
1000
- !["lite", "standard", "critical", "empty", "structured", "rework"].includes(
1001
- classification ?? "",
1002
- )
1003
- )
1004
- return headOrJson(context, { error: "A valid classification is required" }, 400);
1005
- const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1006
- const limit = Math.min(
1007
- 50,
1008
- Math.max(1, Number.parseInt(context.req.query("limit") ?? "50", 10) || 50),
1009
- );
1010
- const projection = await readAggregate(
1011
- `operations:classification:${from ?? ""}:${to ?? ""}`,
1012
- () =>
1013
- buildFirstFoldQuickProjection(
1014
- filesystem,
1015
- layout,
1016
- { from, to },
1017
- { includeBoardTasks: true },
1018
- ),
1019
- );
1020
- return headOrJson(
1021
- context,
1022
- classificationPage(
1023
- projection,
1024
- kind,
1025
- classification as BoardClassification,
1026
- offset,
1027
- limit,
1028
- ),
1029
- 200,
1030
- true,
1031
- );
1032
- }
1033
- if (scope === "quick") {
1034
- const projectionStartedAt = performance.now();
1035
- const quick = await readAggregate(
1036
- `operations:quick:${from ?? ""}:${to ?? ""}`,
1037
- () =>
1038
- buildFirstFoldQuickProjection(
1039
- filesystem,
1040
- layout,
1041
- { from, to },
1042
- {
1043
- includeTopTasks: from === null && to === null,
1044
- includeExtended: from === null && to === null,
1045
- },
1046
- ),
1047
- context.req.query("fresh") === "1",
1048
- );
1049
- if (
1050
- process.env.ZAIP_DASHBOARD_PROFILE_FIRST_FOLD === "1" &&
1051
- filesystem instanceof SqliteReadModelFileSystem
1052
- ) {
1053
- const timings = filesystem.firstFoldViewportProfile();
1054
- context.header(
1055
- "Server-Timing",
1056
- [
1057
- `layout;dur=${Math.round(layoutMs * 10) / 10}`,
1058
- `projection;dur=${Math.round((performance.now() - projectionStartedAt) * 10) / 10}`,
1059
- ...Object.entries(timings).map(([name, duration]) => `${name};dur=${duration}`),
1060
- ].join(", "),
1061
- );
1062
- }
1063
- return headOrJson(context, quick, 200, true);
1064
- }
1065
- const projection =
1066
- filesystem instanceof SqliteReadModelFileSystem
1067
- ? await buildFirstFoldQuickProjection(
1068
- filesystem,
1069
- layout,
1070
- { from, to },
1071
- {
1072
- includeBoardTasks: scope === "detail",
1073
- includeExtended: scope === "detail" || scope === "overview",
1074
- includePeriods: scope === "detail" || scope === "overview",
1075
- },
1076
- )
1077
- : await buildCanonicalOperationsProjection(
1078
- filesystem,
1079
- layout,
1080
- { from, to },
1081
- scope === "overview",
1082
- );
1083
- return headOrJson(context, projection, 200, scope === "overview");
1084
- } catch (error) {
1085
- if (error instanceof V2WorkspaceRequiredError) throw error;
1086
- return headOrJson(
1087
- context,
1088
- { error: error instanceof Error ? error.message : String(error) },
1089
- 400,
1090
- );
1091
- }
485
+ registerOperationsRoutes(app, {
486
+ filesystem,
487
+ canonicalFilesystem,
488
+ isSqliteSnapshot,
489
+ readLayout,
490
+ readSnapshot,
491
+ readAggregate,
492
+ clearSnapshotCache,
1092
493
  });
1093
-
1094
- registerOperationsRunsRoute(app, filesystem, readAggregate, headOrJson);
1095
-
1096
- // Explicit SQL read-model API for the comparison dashboard. It never falls
1097
- // back to filesystem pagination, making first-fold/entity query plans
1098
- // inspectable while preserving the established Operations contracts.
1099
- app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-read-models`, async (context) => {
1100
- if (!(filesystem instanceof SqliteReadModelFileSystem))
1101
- return headOrJson(context, { error: "SQLite read model is not active" }, 409);
1102
- const kind = context.req.query("kind");
1103
- const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1104
- const limit = Math.min(
1105
- 200,
1106
- Math.max(1, Number.parseInt(context.req.query("limit") ?? "20", 10) || 20),
1107
- );
1108
- if (kind === "overview") {
1109
- const period = context.req.query("period");
1110
- if (period !== "day" && period !== "month" && period !== "year")
1111
- return headOrJson(context, { error: "Unsupported SQLite overview period" }, 400);
1112
- return headOrJson(context, filesystem.overviewPage(period, offset, limit), 200, true);
1113
- }
1114
- if (kind !== "task" && kind !== "spec" && kind !== "prd")
1115
- return headOrJson(context, { error: "Unsupported SQLite read model" }, 400);
1116
- const { from, to } = projectionPeriodFilter(context);
1117
- const filters = operationsPageFilters(context);
1118
- return headOrJson(
1119
- context,
1120
- filesystem.summaryPage(
1121
- kind,
1122
- { from, to, ...filters },
1123
- offset,
1124
- limit,
1125
- context.req.query("cursor") ?? null,
1126
- ),
1127
- 200,
1128
- true,
1129
- );
1130
- });
1131
-
1132
- app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-views`, async (context) => {
1133
- if (!(filesystem instanceof SqliteReadModelFileSystem))
1134
- return headOrJson(context, { error: "SQLite read model is not active" }, 409);
1135
- const view = context.req.query("view");
1136
- if (view === undefined || !(view in sqliteViewMetadata))
1137
- return headOrJson(context, { error: "Unsupported SQLite view" }, 400);
1138
- const parsed = Number.parseInt(context.req.query("limit") ?? "", 10);
1139
- const defaultLimit = view.includes("agent_invocations")
1140
- ? 100
1141
- : view === "first_fold_top_tasks"
1142
- ? 5
1143
- : 50;
1144
- const limit = Math.min(200, Math.max(1, Number.isFinite(parsed) ? parsed : defaultLimit));
1145
- const { from, to } = projectionPeriodFilter(context);
1146
- const optional = (name: string) => {
1147
- const value = context.req.query(name);
1148
- return value === undefined ? {} : { [name]: value };
1149
- };
1150
- const workListKind = context.req.query("kind");
1151
- if (
1152
- view === "operations_work_list" &&
1153
- workListKind !== undefined &&
1154
- !["all", "task", "standalone-task", "prd", "spec"].includes(workListKind)
1155
- )
1156
- return headOrJson(context, { error: "Unsupported Operations work-list kind" }, 400);
1157
- return headOrJson(
1158
- context,
1159
- filesystem.viewPage(view as keyof typeof sqliteViewMetadata, {
1160
- from,
1161
- to,
1162
- ...optional("query"),
1163
- ...optional("status"),
1164
- ...optional("risk"),
1165
- ...optional("provider"),
1166
- ...optional("model"),
1167
- ...optional("agent"),
1168
- ...optional("phase"),
1169
- ...optional("origin"),
1170
- ...optional("workType"),
1171
- ...optional("priority"),
1172
- ...(workListKind === undefined
1173
- ? {}
1174
- : { kind: workListKind as "all" | "task" | "standalone-task" | "prd" | "spec" }),
1175
- offset: Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0),
1176
- limit,
1177
- }),
1178
- 200,
1179
- true,
1180
- );
1181
- });
1182
-
1183
- app.on(["GET", "HEAD"], `${API_PREFIX}/work-priority`, async (context) => {
1184
- const layout = await requireV2Layout(canonicalFilesystem);
1185
- const operations = await buildOperationsProjection(canonicalFilesystem, layout, {
1186
- from: null,
1187
- to: null,
1188
- });
1189
- return headOrJson(
1190
- context,
1191
- await readWorkPriority(canonicalFilesystem, knownWorkIds(operations)),
1192
- );
1193
- });
1194
-
1195
- app.on(["GET", "HEAD"], `${API_PREFIX}/operations/entities`, async (context) => {
1196
- const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1197
- const limit = Math.min(
1198
- 500,
1199
- Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
1200
- );
1201
- const kind = context.req.query("kind");
1202
- if (kind !== "changes" && kind !== "decisions")
1203
- return headOrJson(context, { error: "Unsupported Operations entity page" }, 400);
1204
- const { from, to } = projectionPeriodFilter(context);
1205
- const pageFilters = operationsPageFilters(context);
1206
- const payload = await readAggregate(
1207
- `operations:entities:${kind}:${from ?? ""}:${to ?? ""}:${offset}:${limit}:${pageFilters.query}:${pageFilters.status}:${pageFilters.risk}`,
1208
- async () => {
1209
- const layout = await readLayout();
1210
- const entities = await new LifecycleManifestStore(filesystem).readEntities(
1211
- kind === "changes" ? "change" : "decision",
1212
- );
1213
- const entityRows =
1214
- kind === "changes"
1215
- ? changeEntityPageRows(entities, layout, from, to)
1216
- : decisionEntityPageRows(entities, layout, from, to);
1217
- const rows = filterLifecyclePageRows(entityRows, pageFilters);
1218
- return {
1219
- data_source: "lifecycle_manifest_shards",
1220
- kind,
1221
- offset,
1222
- limit,
1223
- total: rows.length,
1224
- rows: rows.slice(offset, offset + limit),
1225
- };
1226
- },
1227
- );
1228
- return headOrJson(context, payload, 200, true);
1229
- });
1230
-
1231
- registerDailyReadModelRoute(app, filesystem, readAggregate, headOrJson);
1232
- app.put(`${API_PREFIX}/work-priority`, async (context) => {
1233
- const candidate = normalizeWorkPriority(await context.req.json());
1234
- // Priority is canonical queue configuration, never snapshot content.
1235
- // Keep every other settings mutation blocked in snapshot-serving mode.
1236
- await canonicalFilesystem.writeTextAtomic(
1237
- WORK_PRIORITY_PATH,
1238
- `${JSON.stringify(candidate, null, 2)}\n`,
1239
- );
1240
- clearSnapshotCache();
1241
- return context.json(candidate);
1242
- });
1243
-
1244
- app.on(["GET", "HEAD"], `${API_PREFIX}/snapshot`, async (context) => {
1245
- try {
1246
- return headOrJson(context, await readSnapshot(context));
1247
- } catch (error) {
1248
- if (error instanceof V2WorkspaceRequiredError) throw error;
1249
- return headOrJson(
1250
- context,
1251
- { error: error instanceof Error ? error.message : String(error) },
1252
- 400,
1253
- );
1254
- }
1255
- });
1256
-
1257
494
  app.post(`${API_PREFIX}/reconcile-open-work`, async (context) => {
1258
495
  try {
1259
496
  await readLayout();
@@ -1286,7 +523,6 @@ export function createDashboardApp(
1286
523
  return context.json({ error: error instanceof Error ? error.message : String(error) }, 400);
1287
524
  }
1288
525
  });
1289
-
1290
526
  app.post(`${API_PREFIX}/refresh-projection`, async (context) => {
1291
527
  try {
1292
528
  await readLayout();
@@ -1297,151 +533,15 @@ export function createDashboardApp(
1297
533
  return context.json({ error: error instanceof Error ? error.message : String(error) }, 400);
1298
534
  }
1299
535
  });
1300
-
1301
- app.on(["GET", "HEAD"], `${API_PREFIX}/cost-monitor`, async (context) => {
1302
- try {
1303
- const scope = context.req.query("scope") ?? "summary";
1304
- if (scope === "summary") {
1305
- const layout = await readLayout();
1306
- const { from, to } = projectionPeriodFilter(context);
1307
- const currency = selectedCurrency(context);
1308
- const aggregateStartedAt = performance.now();
1309
- const [source, rate] = await Promise.all([
1310
- firstFoldCostSource(filesystem, {
1311
- from,
1312
- to,
1313
- providers: queryValues(context, "provider"),
1314
- models: queryValues(context, "model"),
1315
- agents: queryValues(context, "agent"),
1316
- phases: queryValues(context, "phase"),
1317
- statuses: queryValues(context, "status"),
1318
- tasks: queryValues(context, "task"),
1319
- specs: queryValues(context, "spec"),
1320
- prds: queryValues(context, "prd"),
1321
- executions: queryValues(context, "execution"),
1322
- changes: queryValues(context, "change"),
1323
- }),
1324
- currency === "BRL" ? readCurrentFxRate(filesystem) : Promise.resolve(null),
1325
- ]);
1326
- // First fold receives only aggregate facts. Detailed Runs and entity
1327
- // arrays remain on their explicit paginated endpoints.
1328
- const aggregateMs = performance.now() - aggregateStartedAt;
1329
- const costOperations = {
1330
- schema_version: 1,
1331
- layout_version: layout.version,
1332
- readonly: true,
1333
- source: ".agents/records",
1334
- filters: { from, to },
1335
- summary: { task_count: source.task_count ?? 0 },
1336
- prds: [],
1337
- specs: [],
1338
- runs: [],
1339
- } as unknown as OperationsProjection;
1340
- const projectionStartedAt = performance.now();
1341
- const payload = buildCostSummaryProjection(
1342
- source,
1343
- costOperations,
1344
- currency,
1345
- rate?.rate ?? null,
1346
- );
1347
- context.header(
1348
- "Server-Timing",
1349
- `cost_sql;dur=${aggregateMs.toFixed(1)},cost_projection;dur=${(performance.now() - projectionStartedAt).toFixed(1)}`,
1350
- );
1351
- return headOrJson(context, payload, 200, true);
1352
- }
1353
- if (scope === "runs") {
1354
- const layout = await readLayout();
1355
- const { from, to } = projectionPeriodFilter(context);
1356
- const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
1357
- const limit = Math.min(
1358
- 500,
1359
- Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
1360
- );
1361
- return headOrJson(
1362
- context,
1363
- // The paged Cost adapter reads the lifecycle summary. That canonical
1364
- // artifact is intentionally not copied into SQLite; use its current
1365
- // filesystem alongside the immutable SQL facts rather than failing
1366
- // a filtered page with a synthetic missing-file error.
1367
- await buildManifestCostRunPage(
1368
- isSqliteSnapshot ? canonicalFilesystem : filesystem,
1369
- layout,
1370
- context,
1371
- { from, to },
1372
- offset,
1373
- limit,
1374
- ),
1375
- 200,
1376
- true,
1377
- );
1378
- }
1379
- if (isUnfilteredCostRequest(context))
1380
- return headOrJson(context, (await readSnapshot(context)).costs);
1381
- const layout = await readLayout();
1382
- const { from, to } = projectionPeriodFilter(context);
1383
- // Cost and Operations must start from the same manifest-corrected Run
1384
- // population; financial availability is represented on each row, never
1385
- // by dropping lifecycle Runs before the Cost projection.
1386
- const operations = await buildCanonicalOperationsProjection(filesystem, layout, { from, to });
1387
- return headOrJson(
1388
- context,
1389
- await buildManifestFirstCostProjection(filesystem, operations, context),
1390
- );
1391
- } catch (error) {
1392
- if (error instanceof V2WorkspaceRequiredError) throw error;
1393
- return headOrJson(
1394
- context,
1395
- { error: error instanceof Error ? error.message : String(error) },
1396
- 400,
1397
- );
1398
- }
1399
- });
1400
-
1401
- /** Compact factual attribution view used by both Cost Monitor and Operations.
1402
- * It is intentionally read-only and is available only from a coherent SQLite
1403
- * generation, never by reopening mutable JSONL streams on a request. */
1404
- app.on(["GET", "HEAD"], `${API_PREFIX}/developers`, async (context) => {
1405
- if (!(filesystem instanceof SqliteReadModelFileSystem))
1406
- return headOrJson(
1407
- context,
1408
- { error: "Developer attribution requires the SQLite read model" },
1409
- 409,
1410
- );
1411
- const { from, to } = projectionPeriodFilter(context);
1412
- return headOrJson(
1413
- context,
1414
- filesystem.developerCostAggregate({
1415
- from,
1416
- to,
1417
- accountIds: queryValues(context, "account_id"),
1418
- }),
1419
- 200,
1420
- true,
1421
- );
536
+ registerCostsRoutes(app, {
537
+ filesystem,
538
+ canonicalFilesystem,
539
+ isSqliteSnapshot,
540
+ readLayout,
541
+ readSnapshot,
542
+ readAggregate,
543
+ clearSnapshotCache,
1422
544
  });
1423
-
1424
- /** Operations keeps its developer facts separate from Cost Monitor columns. */
1425
- app.on(["GET", "HEAD"], `${API_PREFIX}/operations/developers`, async (context) => {
1426
- if (!(filesystem instanceof SqliteReadModelFileSystem))
1427
- return headOrJson(
1428
- context,
1429
- { error: "Developer attribution requires the SQLite read model" },
1430
- 409,
1431
- );
1432
- const { from, to } = projectionPeriodFilter(context);
1433
- return headOrJson(
1434
- context,
1435
- filesystem.operationsDeveloperAggregate({
1436
- from,
1437
- to,
1438
- accountIds: queryValues(context, "account_id"),
1439
- }),
1440
- 200,
1441
- true,
1442
- );
1443
- });
1444
-
1445
545
  app.on(["GET", "HEAD"], `${API_PREFIX}/pricing`, async (context) =>
1446
546
  headOrJson(
1447
547
  context,
@@ -1450,7 +550,6 @@ export function createDashboardApp(
1450
550
  ),
1451
551
  ),
1452
552
  );
1453
-
1454
553
  app.put(`${API_PREFIX}/pricing`, async (context) => {
1455
554
  const catalog = pricingCatalogSchema.parse({
1456
555
  ...(await context.req.json()),
@@ -1463,19 +562,16 @@ export function createDashboardApp(
1463
562
  clearSnapshotCache();
1464
563
  return context.json(catalog);
1465
564
  });
1466
-
1467
565
  app.on(["GET", "HEAD"], `${API_PREFIX}/fx-rate`, async (context) => {
1468
566
  const rate = await new PricingRepository(filesystem).readLatestFxRate();
1469
567
  return headOrJson(context, rate);
1470
568
  });
1471
-
1472
569
  app.put(`${API_PREFIX}/fx-rate`, async (context) => {
1473
570
  const input = fxRateSchema.pick({ rate: true }).parse(await context.req.json());
1474
571
  const rate = await new PricingRepository(filesystem).writeCurrentFxRate(input.rate);
1475
572
  clearSnapshotCache();
1476
573
  return context.json(rate);
1477
574
  });
1478
-
1479
575
  app.on(["GET", "HEAD"], `${API_PREFIX}/agent-catalog`, async (context) => {
1480
576
  try {
1481
577
  return headOrJson(context, await readAgentCatalogSettings(filesystem));
@@ -1487,7 +583,6 @@ export function createDashboardApp(
1487
583
  );
1488
584
  }
1489
585
  });
1490
-
1491
586
  app.get(`${API_PREFIX}/agent-catalog-assets/:agentId`, async (context) => {
1492
587
  const portrait = agentPortraitFiles[context.req.param("agentId")];
1493
588
  if (portrait === undefined) return context.json({ error: "Agent portrait not found" }, 404);
@@ -1504,7 +599,6 @@ export function createDashboardApp(
1504
599
  return context.json({ error: "Agent portrait could not be read" }, 500);
1505
600
  }
1506
601
  });
1507
-
1508
602
  app.put(`${API_PREFIX}/agent-catalog`, async (context) => {
1509
603
  const update = async () => {
1510
604
  try {
@@ -1529,7 +623,6 @@ export function createDashboardApp(
1529
623
  );
1530
624
  return result;
1531
625
  });
1532
-
1533
626
  app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings`, async (context) => {
1534
627
  try {
1535
628
  return headOrJson(context, await readProjectSettings(canonicalFilesystem));
@@ -1541,11 +634,9 @@ export function createDashboardApp(
1541
634
  );
1542
635
  }
1543
636
  });
1544
-
1545
637
  app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings/overview`, async (context) => {
1546
638
  return headOrJson(context, await readProjectOverview(canonicalFilesystem));
1547
639
  });
1548
-
1549
640
  app.put(`${API_PREFIX}/project-settings`, async (context) => {
1550
641
  try {
1551
642
  const persisted = await persistProjectSettings(canonicalFilesystem, await context.req.json());
@@ -1558,53 +649,15 @@ export function createDashboardApp(
1558
649
  );
1559
650
  }
1560
651
  });
1561
-
1562
- app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id`, async (context) => {
1563
- try {
1564
- const layout = await readLayout();
1565
- return headOrJson(context, await readTaskDetail(filesystem, layout, context.req.param("id")));
1566
- } catch (error) {
1567
- if (error instanceof V2WorkspaceRequiredError) throw error;
1568
- return headOrJson(context, { error: "Task not found" }, 404);
1569
- }
1570
- });
1571
-
1572
- app.on(["GET", "HEAD"], `${API_PREFIX}/prds/:id`, async (context) => {
1573
- try {
1574
- const layout = await readLayout();
1575
- return headOrJson(context, await readPrdDetail(filesystem, layout, context.req.param("id")));
1576
- } catch (error) {
1577
- if (error instanceof V2WorkspaceRequiredError) throw error;
1578
- return headOrJson(context, { error: "PRD not found" }, 404);
1579
- }
1580
- });
1581
-
1582
- app.on(["GET", "HEAD"], `${API_PREFIX}/specs/:id`, async (context) => {
1583
- try {
1584
- const layout = await readLayout();
1585
- return headOrJson(context, await readSpecDetail(filesystem, layout, context.req.param("id")));
1586
- } catch (error) {
1587
- if (error instanceof V2WorkspaceRequiredError) throw error;
1588
- return headOrJson(context, { error: "SPEC not found" }, 404);
1589
- }
1590
- });
1591
-
1592
- app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id/trace`, async (context) => {
1593
- try {
1594
- const layout = await readLayout();
1595
- const taskId = context.req.param("id");
1596
- return headOrJson(
1597
- context,
1598
- filesystem instanceof SqliteReadModelFileSystem
1599
- ? buildSqliteTaskTrace(filesystem, layout, taskId)
1600
- : await buildTaskTrace(filesystem, layout, taskId),
1601
- );
1602
- } catch (error) {
1603
- if (error instanceof V2WorkspaceRequiredError) throw error;
1604
- return headOrJson(context, { error: "Task trace not found" }, 404);
1605
- }
652
+ registerTraceRoutes(app, {
653
+ filesystem,
654
+ canonicalFilesystem,
655
+ isSqliteSnapshot,
656
+ readLayout,
657
+ readSnapshot,
658
+ readAggregate,
659
+ clearSnapshotCache,
1606
660
  });
1607
-
1608
661
  app.onError((error, context) => {
1609
662
  const status = error instanceof V2WorkspaceRequiredError ? 409 : 500;
1610
663
  return headOrJson(
@@ -1613,9 +666,7 @@ export function createDashboardApp(
1613
666
  status,
1614
667
  );
1615
668
  });
1616
-
1617
669
  app.all(`${API_PREFIX}/*`, (context) => headOrJson(context, { error: "Not found" }, 404));
1618
-
1619
670
  app.on(["GET", "HEAD"], "/", serveStatic({ path: `${uiRoot}/index.html` }));
1620
671
  app.use("/*", async (context, next) => {
1621
672
  if (!isSafeUiPath(context.req.path)) {
@@ -1628,45 +679,10 @@ export function createDashboardApp(
1628
679
  return app;
1629
680
  }
1630
681
 
1631
- async function dashboardSnapshot(
1632
- filesystem: WorkspaceFileSystem,
1633
- workspaceRoot: string,
1634
- context: Context,
1635
- cachedLayout?: WorkspaceLayout,
1636
- ) {
1637
- const layout = cachedLayout ?? (await requireV2Layout(filesystem));
1638
- const { from, to } = projectionPeriodFilter(context);
1639
- const currency = selectedCurrency(context);
1640
- const operations =
1641
- filesystem instanceof SqliteReadModelFileSystem
1642
- ? await buildFirstFoldQuickProjection(filesystem, layout, { from, to })
1643
- : await buildCanonicalOperationsProjection(filesystem, layout, { from, to });
1644
- const costs =
1645
- filesystem instanceof SqliteReadModelFileSystem
1646
- ? buildCostSummaryProjection(
1647
- await firstFoldCostSource(filesystem, { from, to }),
1648
- operations,
1649
- currency,
1650
- currency === "BRL" ? ((await readCurrentFxRate(filesystem))?.rate ?? null) : null,
1651
- )
1652
- : await buildCurrentCostProjection(filesystem, operations, currency);
1653
- return {
1654
- operations,
1655
- costs,
1656
- dashboard: await dashboardIdentity(filesystem, workspaceRoot),
1657
- projection: {
1658
- source_latest_at: operations.summary.source_latest_at,
1659
- served_at: new Date().toISOString(),
1660
- },
1661
- };
1662
- }
1663
-
1664
682
  const moduleStartedAt = performance.now();
1665
683
  const sessionToken = defaultSessionToken();
1666
684
  const runtimeWorkspaceRoot = process.env.ZAIP_WORKSPACE ?? process.cwd();
1667
-
1668
685
  let defaultApp: Hono | undefined;
1669
-
1670
686
  /**
1671
687
  * Compatibility export for direct module consumers. The executable must not
1672
688
  * construct this filesystem-backed app: `main` creates the SQLite app after
@@ -1676,14 +692,12 @@ export function defaultDashboardApp(): Hono {
1676
692
  defaultApp ??= createDashboardApp(runtimeWorkspaceRoot, sessionToken);
1677
693
  return defaultApp;
1678
694
  }
1679
-
1680
695
  export const app = new Proxy({} as Hono, {
1681
696
  get(_target, property) {
1682
697
  const value = Reflect.get(defaultDashboardApp(), property);
1683
698
  return typeof value === "function" ? value.bind(defaultDashboardApp()) : value;
1684
699
  },
1685
700
  });
1686
-
1687
701
  async function main(): Promise<void> {
1688
702
  const port = Number.parseInt(process.env.ZAIP_SERVER_PORT ?? "4317", 10);
1689
703
  writeStartupPhase(moduleStartedAt, "main_entered");
@@ -1706,7 +720,6 @@ async function main(): Promise<void> {
1706
720
  );
1707
721
  });
1708
722
  }
1709
-
1710
723
  if (
1711
724
  process.argv[1]?.endsWith("server.cjs") ||
1712
725
  process.argv[1]?.endsWith("server.js") ||