@zaip-ai/cli 1.3.7 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/README.md +86 -1
  2. package/dist/commands/init.d.ts +1 -0
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/init.js +26 -0
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/services/adapters.js +4 -4
  11. package/dist/services/adapters.js.map +1 -1
  12. package/dist/services/codex-config-template.d.ts +2 -2
  13. package/dist/services/codex-config-template.d.ts.map +1 -1
  14. package/dist/services/codex-config-template.js +3 -3
  15. package/dist/services/codex-config-template.js.map +1 -1
  16. package/dist/services/codex-rules.d.ts +1 -1
  17. package/dist/services/codex-rules.d.ts.map +1 -1
  18. package/dist/services/codex-rules.js +9 -24
  19. package/dist/services/codex-rules.js.map +1 -1
  20. package/dist/services/delivery-git-preflight.d.ts +23 -0
  21. package/dist/services/delivery-git-preflight.d.ts.map +1 -0
  22. package/dist/services/delivery-git-preflight.js +69 -0
  23. package/dist/services/delivery-git-preflight.js.map +1 -0
  24. package/dist/services/doctor.d.ts.map +1 -1
  25. package/dist/services/doctor.js +13 -1
  26. package/dist/services/doctor.js.map +1 -1
  27. package/dist/services/install-agents-template.d.ts.map +1 -1
  28. package/dist/services/install-agents-template.js +22 -28
  29. package/dist/services/install-agents-template.js.map +1 -1
  30. package/dist/services/install-profile.d.ts +8 -0
  31. package/dist/services/install-profile.d.ts.map +1 -0
  32. package/dist/services/install-profile.js +56 -0
  33. package/dist/services/install-profile.js.map +1 -0
  34. package/dist/services/logger.d.ts.map +1 -1
  35. package/dist/services/logger.js +6 -5
  36. package/dist/services/logger.js.map +1 -1
  37. package/dist/services/stack-git.d.ts +9 -0
  38. package/dist/services/stack-git.d.ts.map +1 -0
  39. package/dist/services/stack-git.js +188 -0
  40. package/dist/services/stack-git.js.map +1 -0
  41. package/dist/services/stack-install.d.ts +14 -0
  42. package/dist/services/stack-install.d.ts.map +1 -0
  43. package/dist/services/stack-install.js +176 -0
  44. package/dist/services/stack-install.js.map +1 -0
  45. package/dist/services/stack-node.d.ts +12 -0
  46. package/dist/services/stack-node.d.ts.map +1 -0
  47. package/dist/services/stack-node.js +150 -0
  48. package/dist/services/stack-node.js.map +1 -0
  49. package/dist/services/stack-package-manager.d.ts +12 -0
  50. package/dist/services/stack-package-manager.d.ts.map +1 -0
  51. package/dist/services/stack-package-manager.js +178 -0
  52. package/dist/services/stack-package-manager.js.map +1 -0
  53. package/dist/services/stack-plan.d.ts +35 -0
  54. package/dist/services/stack-plan.d.ts.map +1 -0
  55. package/dist/services/stack-plan.js +130 -0
  56. package/dist/services/stack-plan.js.map +1 -0
  57. package/dist/services/stack-terraform.d.ts +13 -0
  58. package/dist/services/stack-terraform.d.ts.map +1 -0
  59. package/dist/services/stack-terraform.js +274 -0
  60. package/dist/services/stack-terraform.js.map +1 -0
  61. package/dist/services/template-manifest.d.ts.map +1 -1
  62. package/dist/services/template-manifest.js +14 -1
  63. package/dist/services/template-manifest.js.map +1 -1
  64. package/dist/types.d.ts +4 -0
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +1 -1
  67. package/templates/.agents/.env.example +34 -0
  68. package/templates/{README.md → .agents/README.md} +124 -60
  69. package/templates/.agents/config/agents/catalog.yaml +23 -23
  70. package/templates/.agents/config/clients/codex/agents/zaip-agent-analyst.toml +1 -0
  71. package/templates/.agents/config/clients/codex/agents/zaip-agent-auditor.toml +1 -0
  72. package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +5 -4
  73. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +1 -0
  74. package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +1 -0
  75. package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +10 -7
  76. package/templates/.agents/config/clients/codex/agents/zaip-agent-data-analyst.toml +1 -0
  77. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +1 -0
  78. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -0
  79. package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +5 -4
  80. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +1 -0
  81. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +1 -0
  82. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +1 -0
  83. package/templates/.agents/config/clients/codex/agents/zaip-agent-integrator.toml +1 -0
  84. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +5 -4
  85. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -0
  86. package/templates/.agents/config/clients/codex/agents/zaip-agent-refactoring-specialist.toml +1 -0
  87. package/templates/.agents/config/clients/codex/agents/zaip-agent-reviewer.toml +1 -0
  88. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +1 -0
  89. package/templates/.agents/config/clients/codex/agents/zaip-agent-security-auditor.toml +1 -0
  90. package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +1 -0
  91. package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +5 -4
  92. package/templates/.agents/config/clients/codex/agents/zaip-agent-writer.toml +1 -0
  93. package/templates/.agents/config/pricing/models.json +25 -1
  94. package/templates/.agents/config/project.yaml +2 -0
  95. package/templates/.agents/dashboard/.zaip-managed.json +214 -94
  96. package/templates/.agents/dashboard/DATA-DICTIONARY.md +1 -1
  97. package/templates/.agents/dashboard/README.md +58 -19
  98. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-DwVZOmMV.js → CostCalculationGuidePage-snUCQYTC.js} +1 -1
  99. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog--czwAlcH.js → DashboardHelpDialog-oZVlf-l5.js} +787 -723
  100. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-CyjMXkm8.js → PricingSettingsDialog-BPtVUGgs.js} +1 -1
  101. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-3Awyaovx.js +3 -0
  102. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-BomxqCBU.css → ProjectSettingsPage-GgzpZ_PY.css} +1 -1
  103. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CAqYVin1.js → TraceabilityWorkspace-BYjVOHI_.js} +1 -1
  104. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DbaLbU2q.js +68 -0
  105. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DXBOtWNI.js → icons-vendor-TPUT2IUt.js} +1 -1
  106. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Cmlx8OYH.css +1 -0
  107. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ZJFXmK7m.js +2 -0
  108. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-DaNXQPyk.js → react-vendor-Ct48XHGL.js} +1 -1
  109. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-I4AKqVDX.js → traceability-BDe4_Ubl.js} +1 -1
  110. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  111. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +73 -67
  112. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +35 -7
  113. package/templates/.agents/dashboard/apps/frontend/src/AppWorkBoard.ts +55 -0
  114. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +15 -2
  115. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +33 -6
  116. package/templates/.agents/dashboard/apps/frontend/src/app-loading-boundary.css +98 -0
  117. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +5 -5
  118. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-reconciliation.ts +33 -0
  119. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +75 -0
  120. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +9 -8
  121. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +15 -3
  122. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +0 -104
  123. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +61 -0
  124. package/templates/.agents/dashboard/apps/frontend/src/project-stack-versions.css +117 -0
  125. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +78 -2
  126. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +28 -0
  127. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +8 -5
  128. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +180 -1
  129. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +7 -4
  130. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +13 -7
  131. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +14 -0
  132. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableDisplay.ts +211 -0
  133. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +152 -157
  134. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +155 -142
  135. package/templates/.agents/dashboard/modules/cost-monitor/src/react/InvocationManifestQuery.test.ts +145 -0
  136. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +7 -5
  137. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +4 -1
  138. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +4 -1
  139. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +4 -1
  140. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +4 -4
  141. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +79 -1
  142. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +105 -11
  143. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +477 -327
  144. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +430 -285
  145. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +14 -2
  146. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +6 -26
  147. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +1 -1
  148. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-persistent-launch.mjs +268 -0
  149. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +482 -332
  150. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +243 -1
  151. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +252 -41
  152. package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-assets.ts +39 -0
  153. package/templates/.agents/dashboard/packages/local-runtime/src/server-browser-lease.ts +33 -0
  154. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +148 -11
  155. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +68 -0
  156. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.test.ts +19 -0
  157. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +39 -43
  158. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.test.ts +42 -0
  159. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.ts +26 -0
  160. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +34 -0
  161. package/templates/.agents/dashboard/packages/local-runtime/src/server-startup.ts +30 -0
  162. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +89 -0
  163. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +88 -0
  164. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +68 -7
  165. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +11 -5
  166. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +129 -99
  167. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.test.ts +111 -0
  168. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.ts +140 -0
  169. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +127 -4
  170. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +145 -5
  171. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +156 -23
  172. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +18 -9
  173. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-projection.ts +52 -0
  174. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +35 -13
  175. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-force-canonical.test.ts +56 -0
  176. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.test.ts +29 -0
  177. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.ts +136 -0
  178. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts +251 -0
  179. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +22 -7
  180. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +1 -1
  181. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical-parsing.ts +42 -0
  182. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +793 -0
  183. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-duration.test.ts +270 -0
  184. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +3 -2
  185. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +18 -4
  186. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.test.ts +87 -0
  187. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +105 -16
  188. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +131 -59
  189. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1000 -1115
  190. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +13 -92
  191. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-claims.test.mjs +79 -0
  192. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +27 -0
  193. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +6 -6
  194. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +1 -1
  195. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +2 -10
  196. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +142 -0
  197. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +231 -239
  198. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +18 -29
  199. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +6 -0
  200. package/templates/.agents/dashboard/stack-dependencies.json +14 -0
  201. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  202. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  203. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +47 -0
  204. package/templates/.agents/requirements/comparative-audit.md +1 -1
  205. package/templates/.agents/requirements/context-engine.md +1 -1
  206. package/templates/.agents/requirements/cost-observability.md +1 -1
  207. package/templates/.agents/requirements/dashboard-launch.md +2 -2
  208. package/templates/.agents/requirements/data-export.md +1 -1
  209. package/templates/.agents/requirements/design-system.md +1 -1
  210. package/templates/.agents/requirements/execution-engine.md +1 -1
  211. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  212. package/templates/.agents/requirements/model-orchestration.md +1 -1
  213. package/templates/.agents/requirements/public-distribution.md +1 -1
  214. package/templates/.agents/requirements/software-architecture.md +1 -1
  215. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  216. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  217. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  218. package/templates/.agents/rules/autonomy-decision.md +1 -1
  219. package/templates/.agents/rules/cloud-hostinger-autonomy.md +21 -0
  220. package/templates/.agents/rules/core.md +5 -4
  221. package/templates/.agents/rules/local-sqlite-storage.md +18 -0
  222. package/templates/.agents/rules/meta-mcp.md +21 -0
  223. package/templates/.agents/rules/workflow.md +1 -0
  224. package/templates/.agents/runtime/bin/zaip.mjs +305 -137
  225. package/templates/.agents/runtime/checksums.sha256 +2 -2
  226. package/templates/.agents/runtime/provenance.json +2 -2
  227. package/templates/.agents/skills/zaip-skill-api-to-mcp/SKILL.md +4 -0
  228. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +25 -9
  229. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +2 -2
  230. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +2 -2
  231. package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +15 -0
  232. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +13 -5
  233. package/templates/.agents/skills/zaip-skill-data-analyst/SKILL.md +10 -5
  234. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +5 -4
  235. package/templates/.agents/skills/zaip-skill-frontend-launcher/SKILL.md +4 -3
  236. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +5 -3
  237. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +5 -5
  238. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.mjs +78 -13
  239. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.test.mjs +59 -0
  240. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/yaml-parser.mjs +157 -0
  241. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +11 -6
  242. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/SKILL.md +17 -0
  243. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/agents/openai.yaml +7 -0
  244. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-cloud-inputs.example.json +41 -0
  245. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-runner.md +84 -0
  246. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/operational-contract.md +19 -0
  247. package/templates/.agents/skills/zaip-skill-local-credentials/SKILL.md +43 -0
  248. package/templates/.agents/skills/zaip-skill-local-credentials/agents/openai.yaml +6 -0
  249. package/templates/.agents/skills/zaip-skill-local-credentials/references/dotenv-resolution.md +47 -0
  250. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +8 -3
  251. package/templates/.agents/skills/zaip-skill-meta-ads/SKILL.md +16 -0
  252. package/templates/.agents/skills/zaip-skill-meta-ads/agents/openai.yaml +4 -0
  253. package/templates/.agents/skills/zaip-skill-meta-ads/references/setup.md +3 -0
  254. package/templates/.agents/skills/zaip-skill-meta-ads-setup/SKILL.md +24 -0
  255. package/templates/.agents/skills/zaip-skill-meta-ads-setup/agents/openai.yaml +4 -0
  256. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/diagnostics.md +13 -0
  257. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/official-sources.md +10 -0
  258. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/SKILL.md +12 -0
  259. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/agents/openai.yaml +4 -0
  260. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/LICENSE +21 -0
  261. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/upstream.md +77 -0
  262. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/SKILL.md +12 -0
  263. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/agents/openai.yaml +4 -0
  264. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/LICENSE +21 -0
  265. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/upstream.md +126 -0
  266. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/SKILL.md +12 -0
  267. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/agents/openai.yaml +4 -0
  268. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/LICENSE +21 -0
  269. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/upstream.md +67 -0
  270. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/SKILL.md +12 -0
  271. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/agents/openai.yaml +4 -0
  272. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/LICENSE +21 -0
  273. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/upstream.md +69 -0
  274. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/SKILL.md +12 -0
  275. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/agents/openai.yaml +4 -0
  276. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/LICENSE +21 -0
  277. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/upstream.md +79 -0
  278. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/SKILL.md +14 -0
  279. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/agents/openai.yaml +4 -0
  280. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/LICENSE +21 -0
  281. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/upstream.md +144 -0
  282. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/SKILL.md +12 -0
  283. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/agents/openai.yaml +4 -0
  284. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/LICENSE +21 -0
  285. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/upstream.md +83 -0
  286. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/SKILL.md +10 -0
  287. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/agents/openai.yaml +4 -0
  288. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/LICENSE +21 -0
  289. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/setup.md +11 -0
  290. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/upstream.md +55 -0
  291. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/SKILL.md +12 -0
  292. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/agents/openai.yaml +4 -0
  293. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/LICENSE +21 -0
  294. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/upstream.md +56 -0
  295. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +12 -7
  296. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +11 -4
  297. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +6 -4
  298. package/templates/.agents/skills/zaip-skill-tool-setup/references/codex-config.md +4 -2
  299. package/templates/.agents/skills/zaip-skill-tool-setup/references/secrets.md +1 -0
  300. package/templates/.agents/tools/catalog.yaml +6 -0
  301. package/templates/.agents/tools/cloud-runner-dns.mjs +28 -0
  302. package/templates/.agents/tools/cloud-runner-terraform.mjs +212 -0
  303. package/templates/.agents/tools/cloud-runner.integration.test.mjs +239 -0
  304. package/templates/.agents/tools/cloud-runner.mjs +786 -0
  305. package/templates/.agents/tools/cloud-runner.test.mjs +534 -0
  306. package/templates/.agents/tools/cloud-terraform-plan-policy.mjs +262 -0
  307. package/templates/.agents/tools/mcp/development/meta-devtools/README.md +14 -0
  308. package/templates/.agents/tools/mcp/development/meta-devtools/manifest.yaml +28 -0
  309. package/templates/.agents/tools/mcp/infra/cloudflare/manifest.yaml +2 -3
  310. package/templates/.agents/tools/mcp/infra/hostinger/manifest.yaml +1 -3
  311. package/templates/.agents/tools/mcp/marketing/meta-ads/README.md +15 -13
  312. package/templates/.agents/tools/mcp/marketing/meta-ads/manifest.yaml +15 -10
  313. package/templates/.agents/tools/profiles/development.yaml +3 -0
  314. package/templates/infra/terraform/.terraform.lock.hcl +42 -0
  315. package/templates/infra/terraform/README.md +31 -0
  316. package/templates/infra/terraform/backend.tf.json +13 -0
  317. package/templates/infra/terraform/deployment-inputs.example.yaml +22 -0
  318. package/templates/infra/terraform/fixtures/approved-targets.json +32 -0
  319. package/templates/infra/terraform/fixtures/plan-approved.json +67 -0
  320. package/templates/infra/terraform/main.tf +77 -0
  321. package/templates/infra/terraform/outputs.tf +20 -0
  322. package/templates/infra/terraform/terraform.tfvars.example +26 -0
  323. package/templates/infra/terraform/variables.tf +91 -0
  324. package/templates/infra/terraform/versions.tf +18 -0
  325. package/templates/scripts/validate-cloud-hostinger-contract.mjs +89 -0
  326. package/templates/scripts/validate-cloud-terraform-plan.mjs +1 -0
  327. package/templates/template-manifest.json +613 -173
  328. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-B_Oj6Dlf.js +0 -3
  329. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +0 -68
  330. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CblPpexd.js +0 -2
  331. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +0 -1
  332. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +0 -50
@@ -0,0 +1,33 @@
1
+ import type { Hono } from "hono";
2
+ import { streamSSE } from "hono/streaming";
3
+
4
+ export function registerBrowserLeaseRoute(
5
+ app: Hono,
6
+ apiPrefix: string,
7
+ launcherOwned = false,
8
+ ): void {
9
+ const browserLeases = new Set<object>();
10
+ let browserLeaseDeadline: NodeJS.Timeout | undefined;
11
+ if (launcherOwned) {
12
+ browserLeaseDeadline = setTimeout(() => process.exit(0), 12_000);
13
+ browserLeaseDeadline.unref();
14
+ }
15
+ const releaseBrowserLease = (lease: object) => {
16
+ browserLeases.delete(lease);
17
+ if (!launcherOwned || browserLeases.size !== 0) return;
18
+ if (browserLeaseDeadline !== undefined) clearTimeout(browserLeaseDeadline);
19
+ browserLeaseDeadline = setTimeout(() => process.exit(0), 12_000);
20
+ browserLeaseDeadline.unref();
21
+ };
22
+ app.get(`${apiPrefix}/browser-lease`, (context) => {
23
+ const lease = {};
24
+ if (browserLeaseDeadline !== undefined) clearTimeout(browserLeaseDeadline);
25
+ browserLeaseDeadline = undefined;
26
+ browserLeases.add(lease);
27
+ return streamSSE(context, async (stream) => {
28
+ stream.onAbort(() => releaseBrowserLease(lease));
29
+ await stream.writeSSE({ event: "lease", data: "active" });
30
+ await new Promise(() => {});
31
+ });
32
+ });
33
+ }
@@ -1,12 +1,96 @@
1
- import { resolve } from "node:path";
1
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
2
3
  import { WorkspaceFileSystem, type WorkspaceLayout } from "@zaip-ai/core";
3
- import { describe, expect, it, vi } from "vitest";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
5
  import { createDashboardApp, reconciliationNeedsRefresh } from "./server.js";
6
+ import { ReconciliationCommandTimeoutError, reconciliationResult } from "./server-workspace.js";
7
+ import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
5
8
 
6
9
  const workspaceRoot = resolve(import.meta.dirname, "../../../../..");
7
10
  const v2Layout = { version: 3 } as WorkspaceLayout;
11
+ const fixtureRoots: string[] = [];
12
+ const fixtureBase = join(workspaceRoot, ".tmp", "server-reconciliation-refresh-tests");
13
+
14
+ async function createSqliteFixture(): Promise<string> {
15
+ await mkdir(fixtureBase, { recursive: true });
16
+ const root = await mkdtemp(join(fixtureBase, "fixture-"));
17
+ fixtureRoots.push(root);
18
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
19
+ await Promise.all([
20
+ readFile(join(workspaceRoot, ".agents", "config", "project.yaml"), "utf8").then((content) =>
21
+ writeFile(join(root, ".agents", "config", "project.yaml"), content),
22
+ ),
23
+ readFile(join(workspaceRoot, ".agents", "config", "framework.lock.yaml"), "utf8").then(
24
+ (content) => writeFile(join(root, ".agents", "config", "framework.lock.yaml"), content),
25
+ ),
26
+ ]);
27
+ await rebuildSqliteReadModel(root);
28
+ return root;
29
+ }
30
+
31
+ afterEach(async () => {
32
+ await Promise.all(
33
+ fixtureRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
34
+ );
35
+ });
8
36
 
9
37
  describe("reconciliation snapshot refresh", () => {
38
+ it("publishes a stable readiness revision and changes it after a status-only snapshot commit", async () => {
39
+ const fixtureRoot = await createSqliteFixture();
40
+ const taskPath = join(fixtureRoot, ".agents/records/tasks/TASK-REVISION.md");
41
+ await mkdir(join(fixtureRoot, ".agents/records/tasks"), { recursive: true });
42
+ const task = (status: string) => `---
43
+ schema_version: 2
44
+ id: TASK-REVISION
45
+ title: Snapshot status revision
46
+ status: ${status}
47
+ created_at: 2026-09-05T10:00:00.000Z
48
+ ---
49
+ # Task
50
+ `;
51
+ await writeFile(taskPath, task("executing"));
52
+ await rebuildSqliteReadModel(fixtureRoot);
53
+ const filesystem = new SqliteReadModelFileSystem(fixtureRoot);
54
+ const refresh = vi.fn(async () => undefined);
55
+ const app = createDashboardApp(fixtureRoot, "test-session", undefined, {
56
+ snapshotReadOnly: true,
57
+ filesystem,
58
+ refreshReadModel: refresh,
59
+ layout: v2Layout,
60
+ });
61
+ const ready = async () => {
62
+ const response = await app.request("/api/v2/ready", {
63
+ headers: { "x-zaip-session": "test-session" },
64
+ });
65
+ expect(response.status).toBe(200);
66
+ return response.json() as Promise<{
67
+ snapshot_revision: string;
68
+ activity_count: number;
69
+ day_count: number;
70
+ }>;
71
+ };
72
+ try {
73
+ const before = await ready();
74
+ expect(before.snapshot_revision).toMatch(/^[a-f0-9]{64}$/);
75
+ await expect(ready()).resolves.toEqual(before);
76
+ await writeFile(taskPath, task("completed"));
77
+ // Canonical edits do not pretend that the opened snapshot was promoted.
78
+ await expect(ready()).resolves.toEqual(before);
79
+ // Use the production refresh boundary, which releases Windows readers
80
+ // before replacing an older snapshot generation.
81
+ await filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(fixtureRoot));
82
+ const after = await ready();
83
+ expect(after.snapshot_revision).not.toBe(before.snapshot_revision);
84
+ expect(after.activity_count).toBe(before.activity_count);
85
+ expect(after.day_count).toBe(before.day_count);
86
+ await filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(fixtureRoot));
87
+ await expect(ready()).resolves.toEqual(after);
88
+ expect(refresh).not.toHaveBeenCalled();
89
+ } finally {
90
+ filesystem.close();
91
+ }
92
+ });
93
+
10
94
  it("refreshes only when reconciliation changed canonical work", () => {
11
95
  expect(
12
96
  reconciliationNeedsRefresh({ reconciled_ids: [], blocked: [], not_reconciled: [] }),
@@ -25,9 +109,35 @@ describe("reconciliation snapshot refresh", () => {
25
109
  not_reconciled: [],
26
110
  }),
27
111
  ).toBe(true);
112
+ expect(
113
+ reconciliationNeedsRefresh(
114
+ reconciliationResult({
115
+ reconciliations: [
116
+ {
117
+ status: "closed",
118
+ specs: [{ id: "SPEC-DELIVERED", status: "delivered" }],
119
+ prds: [{ id: "PRD-DELIVERED", status: "delivered" }],
120
+ },
121
+ ],
122
+ }),
123
+ ),
124
+ ).toBe(true);
28
125
  });
29
126
 
30
- it("serves health while a changed reconciliation refreshes in a worker", async () => {
127
+ it("reports a closed Run as a snapshot-changing reconciliation without a Task graph", () => {
128
+ expect(
129
+ reconciliationNeedsRefresh(
130
+ reconciliationResult({
131
+ runs: [{ run: { run_id: "RUN-CONVERSATION-CLOSED" } }],
132
+ reconciliations: [],
133
+ }),
134
+ ),
135
+ ).toBe(true);
136
+ });
137
+
138
+ it("returns a compact receipt while a changed reconciliation refreshes in a worker", async () => {
139
+ const fixtureRoot = await createSqliteFixture();
140
+ const filesystem = new SqliteReadModelFileSystem(fixtureRoot);
31
141
  let rejectRefresh: ((error: Error) => void) | undefined;
32
142
  const refresh = vi.fn(
33
143
  () =>
@@ -35,9 +145,10 @@ describe("reconciliation snapshot refresh", () => {
35
145
  rejectRefresh = reject;
36
146
  }),
37
147
  );
38
- const app = createDashboardApp(workspaceRoot, "test-session", undefined, {
148
+ const app = createDashboardApp(fixtureRoot, "test-session", undefined, {
39
149
  snapshotReadOnly: true,
40
- canonicalFilesystem: new WorkspaceFileSystem(workspaceRoot),
150
+ filesystem,
151
+ canonicalFilesystem: new WorkspaceFileSystem(fixtureRoot),
41
152
  reconcileOpenWork: async () => ({
42
153
  reconciled_ids: ["TASK-CHANGED"],
43
154
  blocked: [],
@@ -50,17 +161,43 @@ describe("reconciliation snapshot refresh", () => {
50
161
  method: "POST",
51
162
  headers: { "x-zaip-session": "test-session" },
52
163
  });
164
+ const response = await reconciliation;
165
+ expect(response.status, await response.clone().text()).toBe(200);
166
+ await expect(response.json()).resolves.toMatchObject({
167
+ snapshot_state: "updating",
168
+ projection_refresh_completed: false,
169
+ reconciliation: { reconciled_ids: ["TASK-CHANGED"] },
170
+ });
53
171
  await vi.waitFor(() => expect(refresh).toHaveBeenCalledOnce());
54
172
  await expect(
55
173
  app.request("/api/v2/health", { headers: { "x-zaip-session": "test-session" } }),
56
- ).resolves.toMatchObject({
57
- status: 200,
58
- });
174
+ ).resolves.toMatchObject({ status: 200 });
59
175
  rejectRefresh?.(new Error("simulated worker refresh failure"));
60
- const response = await reconciliation;
61
- expect(response.status).toBe(400);
176
+ await vi.waitFor(() => expect(refresh).toHaveBeenCalledOnce());
177
+ filesystem.close();
178
+ });
179
+
180
+ it("returns a finite partial-reconciliation error and refreshes the projection", async () => {
181
+ const refresh = vi.fn(async () => undefined);
182
+ const app = createDashboardApp(workspaceRoot, "test-session", undefined, {
183
+ snapshotReadOnly: true,
184
+ canonicalFilesystem: new WorkspaceFileSystem(workspaceRoot),
185
+ reconcileOpenWork: async () => {
186
+ throw new ReconciliationCommandTimeoutError();
187
+ },
188
+ refreshReadModel: refresh,
189
+ layout: v2Layout,
190
+ });
191
+
192
+ const response = await app.request("/api/v2/reconcile-open-work", {
193
+ method: "POST",
194
+ headers: { "x-zaip-session": "test-session" },
195
+ });
196
+ expect(response.status).toBe(408);
62
197
  await expect(response.json()).resolves.toMatchObject({
63
- error: "simulated worker refresh failure",
198
+ reconciliation_may_be_partial: true,
199
+ projection_refresh_started: true,
64
200
  });
201
+ await vi.waitFor(() => expect(refresh).toHaveBeenCalledOnce());
65
202
  });
66
203
  });
@@ -0,0 +1,68 @@
1
+ import type { WorkspaceFileSystem } from "@zaip-ai/core";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { reconcileOpenWork, reconciliationResult } from "./server-workspace.js";
4
+
5
+ describe("manual reconciliation report", () => {
6
+ it("reports delivered SPECs and PRDs even when no Task or Run changed", () => {
7
+ expect(
8
+ reconciliationResult({
9
+ reconciliations: [
10
+ {
11
+ status: "closed",
12
+ specs: [{ id: "SPEC-DONE", status: "delivered" }],
13
+ prds: [{ id: "PRD-DONE", status: "delivered" }],
14
+ },
15
+ ],
16
+ }),
17
+ ).toEqual({ reconciled_ids: ["PRD-DONE", "SPEC-DONE"], blocked: [], not_reconciled: [] });
18
+ });
19
+
20
+ it("reports the canonical owners of an orphan with their persisted blocking reasons", () => {
21
+ expect(
22
+ reconciliationResult({
23
+ orphaned_executions: ["EXEC-ORPHAN"],
24
+ orphaned_execution_entities: [
25
+ {
26
+ id: "EXEC-ORPHAN",
27
+ status: "blocked",
28
+ status_reason: "Host terminated without delivery evidence.",
29
+ },
30
+ ],
31
+ orphaned_owner_entities: [
32
+ { id: "TASK-ORPHAN", status: "blocked", status_reason: "Host interrupted." },
33
+ { id: "SPEC-ORPHAN", status: "blocked", status_reason: "Child attempt stopped." },
34
+ { id: "PRD-ORPHAN", status: "blocked", status_reason: "Child attempt stopped." },
35
+ ],
36
+ unresolved_running_run_ids: ["RUN-LIVE"],
37
+ }),
38
+ ).toEqual({
39
+ reconciled_ids: ["EXEC-ORPHAN", "PRD-ORPHAN", "SPEC-ORPHAN", "TASK-ORPHAN"],
40
+ blocked: [
41
+ { id: "EXEC-ORPHAN", reason: "Host terminated without delivery evidence." },
42
+ { id: "PRD-ORPHAN", reason: "Child attempt stopped." },
43
+ { id: "SPEC-ORPHAN", reason: "Child attempt stopped." },
44
+ { id: "TASK-ORPHAN", reason: "Host interrupted." },
45
+ ],
46
+ not_reconciled: [{ id: "RUN-LIVE", reason: "Invocation is still running." }],
47
+ });
48
+ });
49
+
50
+ it("does not advertise untouched draft, ready or active entities as reconciled", () => {
51
+ expect(
52
+ reconciliationResult({
53
+ orphaned_owner_entities: [
54
+ { id: "PRD-DRAFT", status: "draft" },
55
+ { id: "SPEC-READY", status: "ready" },
56
+ { id: "TASK-LIVE", status: "executing" },
57
+ ],
58
+ }),
59
+ ).toEqual({ reconciled_ids: [], blocked: [], not_reconciled: [] });
60
+ });
61
+
62
+ it("reports unavailable reconciliation instead of returning a successful empty result", async () => {
63
+ const filesystem = { exists: vi.fn(async () => false) } as unknown as WorkspaceFileSystem;
64
+ await expect(reconcileOpenWork(filesystem, "test-workspace")).rejects.toThrow(
65
+ "could not be reconciled",
66
+ );
67
+ });
68
+ });
@@ -0,0 +1,19 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { describe, expect, it } from "vitest";
3
+ import { attachRefreshWorkerCleanup } from "./server-snapshot-refresh.js";
4
+
5
+ describe("snapshot refresh shutdown", () => {
6
+ it("terminates an active refresh worker on runtime shutdown and removes its listener on completion", () => {
7
+ const processRef = new EventEmitter();
8
+ const calls: string[] = [];
9
+ const cleanup = attachRefreshWorkerCleanup(
10
+ { kill: (signal) => (calls.push(String(signal)), true) },
11
+ processRef,
12
+ );
13
+ processRef.emit("exit");
14
+ expect(calls).toEqual(["SIGTERM"]);
15
+ cleanup();
16
+ processRef.emit("exit");
17
+ expect(calls).toEqual(["SIGTERM"]);
18
+ });
19
+ });
@@ -1,25 +1,13 @@
1
1
  import { spawn } from "node:child_process";
2
- import { randomBytes } from "node:crypto";
3
2
  import { existsSync } from "node:fs";
4
- import { rm } from "node:fs/promises";
5
- import { dirname, resolve } from "node:path";
3
+ import { resolve } from "node:path";
6
4
  import {
7
5
  type SqliteReadModelFileSystem,
8
6
  sqliteReadModelPath,
9
7
  validateSqliteReadModelSchema,
10
8
  } from "./sqlite-read-model.js";
11
- import { activateStagedSqliteSnapshot, stageSqliteSnapshot } from "./sqlite-snapshot-install.js";
12
9
 
13
- /** Rebuilds an isolated SQLite generation so the serving event loop stays available. */
14
- export async function rebuildSnapshotInWorker(workspaceRoot: string, currentPath: string) {
15
- const target = resolve(
16
- dirname(currentPath),
17
- `refresh-server-${process.pid}-${randomBytes(4).toString("hex")}.sqlite`,
18
- );
19
- // A temporary/canonical workspace contains data, not necessarily a second
20
- // dashboard runtime checkout. The explicit candidates work for a launcher
21
- // opened at the workspace root and for fixture tests launched from the
22
- // runtime's source checkout, without relying on import.meta in the CJS build.
10
+ async function rebuildPersistentDatabaseInWorker(workspaceRoot: string, destination: string) {
23
11
  const scriptCandidates = [
24
12
  resolve(
25
13
  workspaceRoot,
@@ -40,58 +28,66 @@ export async function rebuildSnapshotInWorker(workspaceRoot: string, currentPath
40
28
  cwd: workspaceRoot,
41
29
  env: {
42
30
  ...process.env,
43
- // The script prioritizes ZAIP_WORKSPACE over cwd. Do not inherit a
44
- // launcher's root when this refresh was requested for another
45
- // canonical workspace (notably an isolated test fixture).
46
31
  ZAIP_WORKSPACE: workspaceRoot,
47
- ZAIP_SQLITE_DB_PATH: target,
32
+ ZAIP_SQLITE_DB_PATH: destination,
48
33
  ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "0",
49
34
  ZAIP_DASHBOARD_LAUNCH_PROGRESS: "0",
50
35
  },
51
36
  stdio: ["ignore", "ignore", "pipe"],
52
37
  windowsHide: true,
53
38
  });
39
+ // Lease shutdown may end the serving parent while a slow snapshot rebuild
40
+ // is still writing. Terminate the direct child synchronously during parent
41
+ // exit so the next launcher cannot overlap the persistent SQLite writer.
42
+ const cleanupWorker = attachRefreshWorkerCleanup(worker);
54
43
  worker.stderr?.setEncoding("utf8");
55
44
  worker.stderr?.on("data", (chunk: string) => {
56
- stderr += chunk;
45
+ stderr = `${stderr}${chunk}`.slice(-8_000);
57
46
  });
58
- worker.once("error", rejectWorker);
59
- worker.once("exit", (code) =>
60
- code === 0
47
+ worker.once("error", (error) => {
48
+ cleanupWorker();
49
+ rejectWorker(error);
50
+ });
51
+ worker.once("exit", (code) => {
52
+ cleanupWorker();
53
+ return code === 0
61
54
  ? resolveWorker()
62
55
  : rejectWorker(
63
56
  new Error(`SQLite refresh worker failed (${code}): ${stderr.trim() || "no stderr"}`),
64
- ),
65
- );
57
+ );
58
+ });
66
59
  });
67
- return target;
68
60
  }
69
61
 
70
- export async function removeRefreshSnapshot(path: string) {
71
- await rm(path, { force: true }).catch(() => {});
62
+ export function attachRefreshWorkerCleanup(
63
+ worker: { kill(signal?: NodeJS.Signals): boolean },
64
+ processRef: {
65
+ once(event: string, listener: () => void): unknown;
66
+ removeListener(event: string, listener: () => void): unknown;
67
+ } = process,
68
+ ): () => void {
69
+ const stopWorkerOnParentExit = () => {
70
+ try {
71
+ worker.kill("SIGTERM");
72
+ } catch {
73
+ /* already terminal */
74
+ }
75
+ };
76
+ processRef.once("exit", stopWorkerOnParentExit);
77
+ return () => processRef.removeListener("exit", stopWorkerOnParentExit);
72
78
  }
73
79
 
74
80
  /**
75
- * Builds and validates a replacement outside the serving process, then asks
76
- * the read-model filesystem to perform only the short close/rename/reopen
77
- * handoff. The current immutable generation remains readable while staging.
81
+ * The child process writes the single persistent database in place. WAL keeps
82
+ * the server reader usable until the worker commits; no staged SQLite file is
83
+ * created. On an error the worker rolls back and this reader stays untouched.
78
84
  */
79
85
  export async function refreshSqliteSnapshotInWorker(
80
86
  workspaceRoot: string,
81
87
  filesystem: SqliteReadModelFileSystem,
82
88
  ) {
83
89
  const destination = sqliteReadModelPath(workspaceRoot);
84
- const refreshedPath = await rebuildSnapshotInWorker(workspaceRoot, destination);
85
- try {
86
- const stagedPath = await stageSqliteSnapshot(
87
- refreshedPath,
88
- destination,
89
- validateSqliteReadModelSchema,
90
- );
91
- await filesystem.rebuildAndRefresh(async () => {
92
- activateStagedSqliteSnapshot(stagedPath, destination);
93
- });
94
- } finally {
95
- await removeRefreshSnapshot(refreshedPath);
96
- }
90
+ await rebuildPersistentDatabaseInWorker(workspaceRoot, destination);
91
+ validateSqliteReadModelSchema(destination);
92
+ filesystem.refresh();
97
93
  }
@@ -0,0 +1,42 @@
1
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join, resolve, sep } from "node:path";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ import { expect, it } from "vitest";
6
+ import { readCommittedSnapshotRevision } from "./server-snapshot-revision.js";
7
+ import { sqliteReadModelPath } from "./sqlite-read-model.js";
8
+
9
+ it("publishes only committed metadata, preserving the revision through a rolled-back write", async () => {
10
+ const base = resolve(tmpdir());
11
+ const root = await mkdtemp(join(base, "zaip-revision-"));
12
+ if (!resolve(root).startsWith(base + sep))
13
+ throw new Error("Fixture cleanup outside temporary directory");
14
+ const databasePath = sqliteReadModelPath(root);
15
+ await mkdir(dirname(databasePath), { recursive: true });
16
+ const writer = new DatabaseSync(databasePath);
17
+ try {
18
+ writer.exec(`
19
+ PRAGMA journal_mode=WAL;
20
+ CREATE TABLE read_model_metadata (key TEXT PRIMARY KEY, value TEXT);
21
+ CREATE VIEW zaip_vw_read_model_metadata AS SELECT * FROM read_model_metadata;
22
+ INSERT INTO read_model_metadata VALUES ('schema_version', '1'), ('source_fingerprint', 'first');
23
+ `);
24
+ const original = readCommittedSnapshotRevision(root);
25
+ expect(original).toMatch(/^[a-f0-9]{64}$/);
26
+ writer.exec(
27
+ "BEGIN IMMEDIATE; UPDATE read_model_metadata SET value='second' WHERE key='source_fingerprint';",
28
+ );
29
+ expect(readCommittedSnapshotRevision(root)).toBe(original);
30
+ writer.exec("ROLLBACK;");
31
+ expect(readCommittedSnapshotRevision(root)).toBe(original);
32
+ writer.exec(
33
+ "BEGIN IMMEDIATE; UPDATE read_model_metadata SET value='second' WHERE key='source_fingerprint'; COMMIT;",
34
+ );
35
+ const committed = readCommittedSnapshotRevision(root);
36
+ expect(committed).not.toBe(original);
37
+ expect(readCommittedSnapshotRevision(root)).toBe(committed);
38
+ } finally {
39
+ writer.close();
40
+ await rm(root, { recursive: true, force: true });
41
+ }
42
+ });
@@ -0,0 +1,26 @@
1
+ import { createHash } from "node:crypto";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ import { sqliteReadModelPath } from "./sqlite-read-model.js";
4
+
5
+ /** Read committed inputs only. Canonical edits and rolled-back writes cannot advance the revision. */
6
+ export function readCommittedSnapshotRevision(workspaceRoot: string): string {
7
+ const database = new DatabaseSync(sqliteReadModelPath(workspaceRoot), { readOnly: true });
8
+ try {
9
+ const rows = database
10
+ .prepare(
11
+ "SELECT key, value FROM zaip_vw_read_model_metadata WHERE key IN ('schema_version', 'source_fingerprint')",
12
+ )
13
+ .all() as Array<{ key: string; value: string }>;
14
+ const metadata = new Map(rows.map((row) => [row.key, row.value]));
15
+ if (!metadata.has("schema_version") || !metadata.has("source_fingerprint")) {
16
+ throw new Error("Committed SQLite snapshot metadata is incomplete");
17
+ }
18
+ return createHash("sha256")
19
+ .update(metadata.get("schema_version") as string)
20
+ .update(":")
21
+ .update(metadata.get("source_fingerprint") as string)
22
+ .digest("hex");
23
+ } finally {
24
+ database.close();
25
+ }
26
+ }
@@ -0,0 +1,34 @@
1
+ import { watch } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ export function startSnapshotSynchronization(input: {
5
+ workspaceRoot: string;
6
+ staleSeed: boolean;
7
+ watchSources: boolean;
8
+ refresh: () => Promise<void>;
9
+ }): void {
10
+ if (input.staleSeed) void input.refresh().catch(() => undefined);
11
+ if (!input.watchSources || process.env.ZAIP_DASHBOARD_WATCH_SOURCES === "0") return;
12
+ let pendingRefresh: NodeJS.Timeout | undefined;
13
+ const scheduleRefresh = () => {
14
+ if (pendingRefresh !== undefined) clearTimeout(pendingRefresh);
15
+ pendingRefresh = setTimeout(() => {
16
+ pendingRefresh = undefined;
17
+ void input.refresh().catch(() => undefined);
18
+ }, 300);
19
+ pendingRefresh.unref();
20
+ };
21
+ for (const sourceRoot of [
22
+ join(input.workspaceRoot, ".agents", "records"),
23
+ join(input.workspaceRoot, ".agents", "config"),
24
+ ]) {
25
+ try {
26
+ const watcher = watch(sourceRoot, { recursive: true }, scheduleRefresh);
27
+ watcher.unref();
28
+ } catch {
29
+ // Reuse-triggered refresh remains the portable fallback.
30
+ }
31
+ }
32
+ const freshnessTimer = setInterval(scheduleRefresh, 60_000);
33
+ freshnessTimer.unref();
34
+ }
@@ -0,0 +1,30 @@
1
+ import type { rebuildSqliteReadModel } from "./sqlite-read-model.js";
2
+
3
+ export function writeStartupPhase(startedAt: number, phase: string): void {
4
+ process.stderr.write(
5
+ `${JSON.stringify({
6
+ event: "dashboard_sqlite_startup_phase",
7
+ phase,
8
+ elapsed_ms: Math.round(performance.now() - startedAt),
9
+ })}\n`,
10
+ );
11
+ }
12
+
13
+ export function writeReadModelStartup(
14
+ startedAt: number,
15
+ readModel: Awaited<ReturnType<typeof rebuildSqliteReadModel>>,
16
+ ): void {
17
+ // The parent launcher captures stderr even when stdout is intentionally
18
+ // hidden. This makes seed hit/miss and private-copy cost observable without
19
+ // exposing a shared handle or workspace payload.
20
+ process.stderr.write(
21
+ `${JSON.stringify({
22
+ event: "dashboard_sqlite_read_model",
23
+ elapsed_ms: Math.round(performance.now() - startedAt),
24
+ cache: readModel.cache,
25
+ fingerprint_ms: readModel.fingerprint_ms,
26
+ materialize_ms: readModel.materialize_ms,
27
+ source_files: readModel.files,
28
+ })}\n`,
29
+ );
30
+ }
@@ -0,0 +1,89 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import {
3
+ createWorkReconciliation,
4
+ startHostTerminalReconciliation,
5
+ waitForWorkReconciliation,
6
+ } from "./server-work-reconciliation.js";
7
+ import { type ReconciliationResult, reconciliationCommandTimeoutMs } from "./server-workspace.js";
8
+
9
+ const clean: ReconciliationResult = { reconciled_ids: [], blocked: [], not_reconciled: [] };
10
+ const changed: ReconciliationResult = {
11
+ ...clean,
12
+ reconciled_ids: ["TASK-TERMINAL", "SPEC-DELIVERED", "PRD-DELIVERED"],
13
+ };
14
+ afterEach(() => vi.useRealTimers());
15
+
16
+ describe("canonical host recovery", () => {
17
+ it("serializes manual recovery after host-only work and deduplicates concurrent clicks", async () => {
18
+ let release!: (value: ReconciliationResult) => void;
19
+ const first = new Promise<ReconciliationResult>((resolve) => {
20
+ release = resolve;
21
+ });
22
+ const reconcile = vi.fn().mockReturnValueOnce(first).mockResolvedValue(changed);
23
+ const refresh = vi.fn().mockResolvedValue(undefined);
24
+ const run = createWorkReconciliation({ reconcile, refresh });
25
+ const automatic = run("host-terminal");
26
+ await Promise.resolve();
27
+ const manual = run("manual");
28
+ expect(run("manual")).toBe(manual);
29
+ expect(run("host-terminal")).toBe(manual);
30
+ expect(reconcile.mock.calls).toEqual([["host-terminal"]]);
31
+ release(clean);
32
+ await automatic;
33
+ await expect(manual).resolves.toEqual(changed);
34
+ expect(reconcile.mock.calls).toEqual([["host-terminal"], ["manual"]]);
35
+ expect(refresh).toHaveBeenCalledOnce();
36
+ });
37
+
38
+ it("promotes partial changes after a failure and allows the next attempt", async () => {
39
+ const reconcile = vi
40
+ .fn()
41
+ .mockRejectedValueOnce(new Error("writer interrupted"))
42
+ .mockResolvedValue(clean);
43
+ const refresh = vi.fn().mockResolvedValue(undefined);
44
+ const run = createWorkReconciliation({ reconcile, refresh });
45
+ await expect(run("manual")).rejects.toThrow("writer interrupted");
46
+ await expect(run("manual")).resolves.toEqual(clean);
47
+ expect(refresh).toHaveBeenCalledOnce();
48
+ });
49
+
50
+ it("recovers at startup and retries after failure without overlapping work", async () => {
51
+ vi.useFakeTimers();
52
+ let release!: () => void;
53
+ const reconcile = vi
54
+ .fn()
55
+ .mockImplementationOnce(
56
+ () =>
57
+ new Promise<void>((resolve) => {
58
+ release = resolve;
59
+ }),
60
+ )
61
+ .mockRejectedValueOnce(new Error("busy"))
62
+ .mockResolvedValue(undefined);
63
+ const stop = startHostTerminalReconciliation(reconcile);
64
+ await vi.advanceTimersByTimeAsync(1_000);
65
+ await vi.advanceTimersByTimeAsync(90_000);
66
+ expect(reconcile).toHaveBeenCalledOnce();
67
+ release();
68
+ await vi.advanceTimersByTimeAsync(30_000);
69
+ expect(reconcile).toHaveBeenCalledTimes(2);
70
+ await vi.advanceTimersByTimeAsync(30_000);
71
+ expect(reconcile).toHaveBeenCalledTimes(3);
72
+ stop();
73
+ await vi.advanceTimersByTimeAsync(60_000);
74
+ expect(reconcile).toHaveBeenCalledTimes(3);
75
+ });
76
+
77
+ it("bounds total request time even while the canonical writer is queued", async () => {
78
+ vi.useFakeTimers();
79
+ let release!: (value: ReconciliationResult) => void;
80
+ const pending = new Promise<ReconciliationResult>((resolve) => {
81
+ release = resolve;
82
+ });
83
+ const bounded = expect(waitForWorkReconciliation(pending)).rejects.toThrow("exceeded");
84
+ await vi.advanceTimersByTimeAsync(reconciliationCommandTimeoutMs);
85
+ await bounded;
86
+ release(changed);
87
+ await expect(pending).resolves.toEqual(changed);
88
+ });
89
+ });