@zaip-ai/cli 1.3.8 → 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 (315) hide show
  1. package/README.md +58 -3
  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 +17 -6
  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 +2 -0
  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-plan.d.ts +3 -1
  42. package/dist/services/stack-plan.d.ts.map +1 -1
  43. package/dist/services/stack-plan.js +15 -1
  44. package/dist/services/stack-plan.js.map +1 -1
  45. package/dist/services/stack-terraform.d.ts +13 -0
  46. package/dist/services/stack-terraform.d.ts.map +1 -0
  47. package/dist/services/stack-terraform.js +274 -0
  48. package/dist/services/stack-terraform.js.map +1 -0
  49. package/dist/services/template-manifest.d.ts.map +1 -1
  50. package/dist/services/template-manifest.js +14 -1
  51. package/dist/services/template-manifest.js.map +1 -1
  52. package/dist/types.d.ts +1 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/templates/.agents/.env.example +34 -0
  56. package/templates/{README.md → .agents/README.md} +124 -60
  57. package/templates/.agents/config/agents/catalog.yaml +23 -23
  58. package/templates/.agents/config/clients/codex/agents/zaip-agent-analyst.toml +1 -0
  59. package/templates/.agents/config/clients/codex/agents/zaip-agent-auditor.toml +1 -0
  60. package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +5 -4
  61. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +1 -0
  62. package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +1 -0
  63. package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +10 -7
  64. package/templates/.agents/config/clients/codex/agents/zaip-agent-data-analyst.toml +1 -0
  65. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +1 -0
  66. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -0
  67. package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +5 -4
  68. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +1 -0
  69. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +1 -0
  70. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +1 -0
  71. package/templates/.agents/config/clients/codex/agents/zaip-agent-integrator.toml +1 -0
  72. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +5 -4
  73. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -0
  74. package/templates/.agents/config/clients/codex/agents/zaip-agent-refactoring-specialist.toml +1 -0
  75. package/templates/.agents/config/clients/codex/agents/zaip-agent-reviewer.toml +1 -0
  76. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +1 -0
  77. package/templates/.agents/config/clients/codex/agents/zaip-agent-security-auditor.toml +1 -0
  78. package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +1 -0
  79. package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +5 -4
  80. package/templates/.agents/config/clients/codex/agents/zaip-agent-writer.toml +1 -0
  81. package/templates/.agents/config/pricing/models.json +25 -1
  82. package/templates/.agents/config/project.yaml +2 -0
  83. package/templates/.agents/dashboard/.zaip-managed.json +200 -88
  84. package/templates/.agents/dashboard/DATA-DICTIONARY.md +1 -1
  85. package/templates/.agents/dashboard/README.md +58 -19
  86. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-DwVZOmMV.js → CostCalculationGuidePage-snUCQYTC.js} +1 -1
  87. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-VcLx9iOO.js → DashboardHelpDialog-oZVlf-l5.js} +72 -8
  88. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-CyjMXkm8.js → PricingSettingsDialog-BPtVUGgs.js} +1 -1
  89. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-3Awyaovx.js +3 -0
  90. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-DFZwIXSC.js → TraceabilityWorkspace-BYjVOHI_.js} +1 -1
  91. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DbaLbU2q.js +68 -0
  92. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DXBOtWNI.js → icons-vendor-TPUT2IUt.js} +1 -1
  93. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Cmlx8OYH.css +1 -0
  94. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ZJFXmK7m.js +2 -0
  95. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-DaNXQPyk.js → react-vendor-Ct48XHGL.js} +1 -1
  96. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-I4AKqVDX.js → traceability-BDe4_Ubl.js} +1 -1
  97. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  98. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +73 -67
  99. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +35 -7
  100. package/templates/.agents/dashboard/apps/frontend/src/AppWorkBoard.ts +55 -0
  101. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +11 -0
  102. package/templates/.agents/dashboard/apps/frontend/src/app-loading-boundary.css +98 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +5 -5
  104. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-reconciliation.ts +33 -0
  105. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +75 -0
  106. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +7 -8
  107. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +14 -2
  108. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +35 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +78 -2
  110. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +28 -0
  111. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +8 -5
  112. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +180 -1
  113. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +7 -4
  114. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +13 -7
  115. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +14 -0
  116. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableDisplay.ts +211 -0
  117. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +152 -157
  118. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +155 -142
  119. package/templates/.agents/dashboard/modules/cost-monitor/src/react/InvocationManifestQuery.test.ts +145 -0
  120. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +7 -5
  121. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +4 -1
  122. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +4 -1
  123. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +4 -1
  124. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +4 -4
  125. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +79 -1
  126. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +105 -11
  127. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +482 -332
  128. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +430 -285
  129. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +14 -2
  130. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +6 -26
  131. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +1 -1
  132. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-persistent-launch.mjs +268 -0
  133. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +482 -332
  134. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +90 -1
  135. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +112 -22
  136. package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-assets.ts +39 -0
  137. package/templates/.agents/dashboard/packages/local-runtime/src/server-browser-lease.ts +33 -0
  138. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +148 -11
  139. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +68 -0
  140. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.test.ts +19 -0
  141. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +39 -43
  142. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.test.ts +42 -0
  143. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.ts +26 -0
  144. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +34 -0
  145. package/templates/.agents/dashboard/packages/local-runtime/src/server-startup.ts +30 -0
  146. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +89 -0
  147. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +88 -0
  148. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +68 -7
  149. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +11 -5
  150. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +129 -99
  151. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.test.ts +111 -0
  152. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.ts +140 -0
  153. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +127 -4
  154. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +145 -5
  155. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +156 -23
  156. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +18 -9
  157. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-projection.ts +52 -0
  158. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +35 -13
  159. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-force-canonical.test.ts +56 -0
  160. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.test.ts +29 -0
  161. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.ts +136 -0
  162. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts +251 -0
  163. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +22 -7
  164. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +1 -1
  165. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical-parsing.ts +42 -0
  166. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +793 -0
  167. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-duration.test.ts +270 -0
  168. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +3 -2
  169. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +18 -4
  170. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.test.ts +87 -0
  171. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +105 -16
  172. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +131 -59
  173. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1000 -1115
  174. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +13 -92
  175. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-claims.test.mjs +79 -0
  176. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +27 -0
  177. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +6 -6
  178. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +1 -1
  179. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +2 -10
  180. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +142 -0
  181. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +231 -239
  182. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +18 -29
  183. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +6 -0
  184. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  185. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  186. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +47 -0
  187. package/templates/.agents/requirements/comparative-audit.md +1 -1
  188. package/templates/.agents/requirements/context-engine.md +1 -1
  189. package/templates/.agents/requirements/cost-observability.md +1 -1
  190. package/templates/.agents/requirements/dashboard-launch.md +2 -2
  191. package/templates/.agents/requirements/data-export.md +1 -1
  192. package/templates/.agents/requirements/design-system.md +1 -1
  193. package/templates/.agents/requirements/execution-engine.md +1 -1
  194. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  195. package/templates/.agents/requirements/model-orchestration.md +1 -1
  196. package/templates/.agents/requirements/public-distribution.md +1 -1
  197. package/templates/.agents/requirements/software-architecture.md +1 -1
  198. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  199. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  200. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  201. package/templates/.agents/rules/autonomy-decision.md +1 -1
  202. package/templates/.agents/rules/cloud-hostinger-autonomy.md +21 -0
  203. package/templates/.agents/rules/core.md +5 -4
  204. package/templates/.agents/rules/local-sqlite-storage.md +18 -0
  205. package/templates/.agents/rules/meta-mcp.md +21 -0
  206. package/templates/.agents/rules/workflow.md +1 -0
  207. package/templates/.agents/runtime/bin/zaip.mjs +305 -137
  208. package/templates/.agents/runtime/checksums.sha256 +2 -2
  209. package/templates/.agents/runtime/provenance.json +2 -2
  210. package/templates/.agents/skills/zaip-skill-api-to-mcp/SKILL.md +4 -0
  211. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +25 -9
  212. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +2 -2
  213. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +2 -2
  214. package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +15 -0
  215. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +13 -5
  216. package/templates/.agents/skills/zaip-skill-data-analyst/SKILL.md +10 -5
  217. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +5 -4
  218. package/templates/.agents/skills/zaip-skill-frontend-launcher/SKILL.md +4 -3
  219. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +5 -3
  220. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +5 -5
  221. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.mjs +78 -13
  222. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.test.mjs +59 -0
  223. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/yaml-parser.mjs +157 -0
  224. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +11 -6
  225. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/SKILL.md +17 -0
  226. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/agents/openai.yaml +7 -0
  227. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-cloud-inputs.example.json +41 -0
  228. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-runner.md +84 -0
  229. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/operational-contract.md +19 -0
  230. package/templates/.agents/skills/zaip-skill-local-credentials/SKILL.md +43 -0
  231. package/templates/.agents/skills/zaip-skill-local-credentials/agents/openai.yaml +6 -0
  232. package/templates/.agents/skills/zaip-skill-local-credentials/references/dotenv-resolution.md +47 -0
  233. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +8 -3
  234. package/templates/.agents/skills/zaip-skill-meta-ads/SKILL.md +16 -0
  235. package/templates/.agents/skills/zaip-skill-meta-ads/agents/openai.yaml +4 -0
  236. package/templates/.agents/skills/zaip-skill-meta-ads/references/setup.md +3 -0
  237. package/templates/.agents/skills/zaip-skill-meta-ads-setup/SKILL.md +24 -0
  238. package/templates/.agents/skills/zaip-skill-meta-ads-setup/agents/openai.yaml +4 -0
  239. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/diagnostics.md +13 -0
  240. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/official-sources.md +10 -0
  241. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/SKILL.md +12 -0
  242. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/agents/openai.yaml +4 -0
  243. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/LICENSE +21 -0
  244. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/upstream.md +77 -0
  245. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/SKILL.md +12 -0
  246. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/agents/openai.yaml +4 -0
  247. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/LICENSE +21 -0
  248. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/upstream.md +126 -0
  249. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/SKILL.md +12 -0
  250. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/agents/openai.yaml +4 -0
  251. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/LICENSE +21 -0
  252. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/upstream.md +67 -0
  253. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/SKILL.md +12 -0
  254. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/agents/openai.yaml +4 -0
  255. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/LICENSE +21 -0
  256. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/upstream.md +69 -0
  257. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/SKILL.md +12 -0
  258. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/agents/openai.yaml +4 -0
  259. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/LICENSE +21 -0
  260. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/upstream.md +79 -0
  261. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/SKILL.md +14 -0
  262. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/agents/openai.yaml +4 -0
  263. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/LICENSE +21 -0
  264. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/upstream.md +144 -0
  265. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/SKILL.md +12 -0
  266. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/agents/openai.yaml +4 -0
  267. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/LICENSE +21 -0
  268. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/upstream.md +83 -0
  269. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/SKILL.md +10 -0
  270. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/agents/openai.yaml +4 -0
  271. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/LICENSE +21 -0
  272. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/setup.md +11 -0
  273. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/upstream.md +55 -0
  274. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/SKILL.md +12 -0
  275. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/agents/openai.yaml +4 -0
  276. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/LICENSE +21 -0
  277. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/upstream.md +56 -0
  278. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +12 -7
  279. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +11 -4
  280. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +6 -4
  281. package/templates/.agents/skills/zaip-skill-tool-setup/references/codex-config.md +4 -2
  282. package/templates/.agents/skills/zaip-skill-tool-setup/references/secrets.md +1 -0
  283. package/templates/.agents/tools/catalog.yaml +6 -0
  284. package/templates/.agents/tools/cloud-runner-dns.mjs +28 -0
  285. package/templates/.agents/tools/cloud-runner-terraform.mjs +212 -0
  286. package/templates/.agents/tools/cloud-runner.integration.test.mjs +239 -0
  287. package/templates/.agents/tools/cloud-runner.mjs +786 -0
  288. package/templates/.agents/tools/cloud-runner.test.mjs +534 -0
  289. package/templates/.agents/tools/cloud-terraform-plan-policy.mjs +262 -0
  290. package/templates/.agents/tools/mcp/development/meta-devtools/README.md +14 -0
  291. package/templates/.agents/tools/mcp/development/meta-devtools/manifest.yaml +28 -0
  292. package/templates/.agents/tools/mcp/infra/cloudflare/manifest.yaml +2 -3
  293. package/templates/.agents/tools/mcp/infra/hostinger/manifest.yaml +1 -3
  294. package/templates/.agents/tools/mcp/marketing/meta-ads/README.md +15 -13
  295. package/templates/.agents/tools/mcp/marketing/meta-ads/manifest.yaml +15 -10
  296. package/templates/.agents/tools/profiles/development.yaml +3 -0
  297. package/templates/infra/terraform/.terraform.lock.hcl +42 -0
  298. package/templates/infra/terraform/README.md +31 -0
  299. package/templates/infra/terraform/backend.tf.json +13 -0
  300. package/templates/infra/terraform/deployment-inputs.example.yaml +22 -0
  301. package/templates/infra/terraform/fixtures/approved-targets.json +32 -0
  302. package/templates/infra/terraform/fixtures/plan-approved.json +67 -0
  303. package/templates/infra/terraform/main.tf +77 -0
  304. package/templates/infra/terraform/outputs.tf +20 -0
  305. package/templates/infra/terraform/terraform.tfvars.example +26 -0
  306. package/templates/infra/terraform/variables.tf +91 -0
  307. package/templates/infra/terraform/versions.tf +18 -0
  308. package/templates/scripts/validate-cloud-hostinger-contract.mjs +89 -0
  309. package/templates/scripts/validate-cloud-terraform-plan.mjs +1 -0
  310. package/templates/template-manifest.json +599 -167
  311. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BwnJpl02.js +0 -3
  312. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +0 -68
  313. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Dyo_wpL4.js +0 -2
  314. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +0 -1
  315. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +0 -50
@@ -0,0 +1,270 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { expectFastCostPagesMatchCanonical } from "./sqlite-cost-fast-page-parity.test-support.js";
6
+ import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
7
+ import { prepareMultiProviderFixture } from "./sqlite-read-model-multi-provider-fixture.js";
8
+
9
+ const roots: string[] = [];
10
+ const sqliteTestTempRoot = join(
11
+ resolve(import.meta.dirname, "../../../../../.."),
12
+ ".tmp",
13
+ "sqlite-read-model-tests",
14
+ );
15
+ async function createSqliteTestRoot(): Promise<string> {
16
+ await mkdir(sqliteTestTempRoot, { recursive: true });
17
+ return mkdtemp(join(sqliteTestTempRoot, "fixture-"));
18
+ }
19
+ afterEach(async () => {
20
+ await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
21
+ });
22
+
23
+ describe("SQLite dashboard read model multi-provider", () => {
24
+ it("counts task participation per observed model while retaining canonical structural attribution", async () => {
25
+ const root = await createSqliteTestRoot();
26
+ roots.push(root);
27
+ const { started, usage, finished } = await prepareMultiProviderFixture(root);
28
+ await mkdir(join(root, ".agents", "records", "tasks", "2026", "08"), { recursive: true });
29
+ await mkdir(join(root, ".agents", "records", "specs", "2026", "08"), { recursive: true });
30
+ await mkdir(join(root, ".agents", "records", "prds", "2026", "08"), { recursive: true });
31
+ await mkdir(
32
+ join(
33
+ root,
34
+ ".agents",
35
+ "records",
36
+ "changes",
37
+ "2026",
38
+ "08",
39
+ "CHG-CREATED",
40
+ "artifacts",
41
+ "tests",
42
+ ),
43
+ { recursive: true },
44
+ );
45
+ await mkdir(
46
+ join(
47
+ root,
48
+ ".agents",
49
+ "records",
50
+ "changes",
51
+ "2026",
52
+ "08",
53
+ "CHG-CREATED",
54
+ "artifacts",
55
+ "evidence",
56
+ ),
57
+ { recursive: true },
58
+ );
59
+ const creationStarted = {
60
+ ...started,
61
+ event_id: "RUN-CREATION:1",
62
+ run_id: "RUN-CREATION",
63
+ model: "gpt-a",
64
+ provider: "openai",
65
+ agent_invocation_id: "AINV-CREATION",
66
+ root_agent_invocation_id: "AINV-CREATION",
67
+ task_id: "TASK-CREATED",
68
+ spec_id: null,
69
+ prd_id: null,
70
+ };
71
+ const creationUsage = {
72
+ ...usage(2, "openai", "gpt-a", 4, 2),
73
+ event_id: "RUN-CREATION:2",
74
+ run_id: "RUN-CREATION",
75
+ source_segment_key: "creation",
76
+ model_call_id: "MCALL-CREATION",
77
+ };
78
+ const creationFinished = {
79
+ ...finished,
80
+ event_id: "RUN-CREATION:3",
81
+ run_id: "RUN-CREATION",
82
+ sequence: 3,
83
+ };
84
+ await writeFile(
85
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-CREATION.jsonl"),
86
+ `${[creationStarted, creationUsage, creationFinished].map((row) => JSON.stringify(row)).join("\n")}\n`,
87
+ );
88
+ const created = (id: string) =>
89
+ `---\nid: ${id}\ntitle: Created\nstatus: completed\ncreation_run_id: RUN-CREATION\ncreated_at: 2026-08-04T12:00:00.000Z\n---\n`;
90
+ await writeFile(
91
+ join(root, ".agents", "records", "tasks", "2026", "08", "TASK-CREATED.md"),
92
+ created("TASK-CREATED"),
93
+ );
94
+ await writeFile(
95
+ join(root, ".agents", "records", "specs", "2026", "08", "SPEC-CREATED.md"),
96
+ created("SPEC-CREATED"),
97
+ );
98
+ await writeFile(
99
+ join(root, ".agents", "records", "prds", "2026", "08", "PRD-CREATED.md"),
100
+ created("PRD-CREATED"),
101
+ );
102
+ const artifact = (id: string, kind: string) =>
103
+ `---\nid: ${id}\nkind: ${kind}\ntask_id: TASK-CREATED\nrun_id: RUN-CREATION\nexecuted_at: 2026-08-04T12:02:00.000Z\nstatus: passed\n---\n`;
104
+ await writeFile(
105
+ join(
106
+ root,
107
+ ".agents",
108
+ "records",
109
+ "changes",
110
+ "2026",
111
+ "08",
112
+ "CHG-CREATED",
113
+ "artifacts",
114
+ "tests",
115
+ "TEST-CREATED.md",
116
+ ),
117
+ artifact("TEST-CREATED", "test"),
118
+ );
119
+ await writeFile(
120
+ join(
121
+ root,
122
+ ".agents",
123
+ "records",
124
+ "changes",
125
+ "2026",
126
+ "08",
127
+ "CHG-CREATED",
128
+ "artifacts",
129
+ "evidence",
130
+ "EVD-CREATED.md",
131
+ ),
132
+ artifact("EVD-CREATED", "evidence"),
133
+ );
134
+ const directConversationRun = (id: string, provider: string, model: string) => {
135
+ const directStarted = {
136
+ ...started,
137
+ event_id: `${id}:1`,
138
+ run_id: id,
139
+ conversation_id: "CONV-DIRECT-MULTI",
140
+ task_id: "TASK-LEGACY-ONLY",
141
+ provider,
142
+ model,
143
+ };
144
+ delete (directStarted as { agent_invocation_id?: string }).agent_invocation_id;
145
+ delete (directStarted as { root_agent_invocation_id?: string }).root_agent_invocation_id;
146
+ const directUsage = {
147
+ ...usage(2, provider, model, 3, 1),
148
+ event_id: `${id}:2`,
149
+ run_id: id,
150
+ };
151
+ const directFinished = { ...finished, event_id: `${id}:3`, run_id: id, sequence: 3 };
152
+ return [directStarted, directUsage, directFinished];
153
+ };
154
+ for (const [id, provider, model] of [
155
+ ["RUN-DIRECT-GPT", "openai", "gpt-a"],
156
+ ["RUN-DIRECT-CLAUDE", "anthropic", "claude-b"],
157
+ ] as const)
158
+ await writeFile(
159
+ join(root, ".agents", "records", "runs", "2026", "08", `${id}.jsonl`),
160
+ `${directConversationRun(id, provider, model)
161
+ .map((row) => JSON.stringify(row))
162
+ .join("\n")}\n`,
163
+ );
164
+ // An agent Run may have a residual unknown model while still exposing a
165
+ // factual conversation_id. It counts once in unavailable, never as Task.
166
+ const unknownConversationStarted = {
167
+ ...started,
168
+ event_id: "RUN-UNKNOWN-CONVERSATION:1",
169
+ run_id: "RUN-UNKNOWN-CONVERSATION",
170
+ conversation_id: "CONV-UNKNOWN-MODEL",
171
+ task_id: null,
172
+ entrypoint: "conversation",
173
+ provider: "openai",
174
+ model: "unavailable",
175
+ agent_invocation_id: "AINV-UNKNOWN-CONVERSATION",
176
+ root_agent_invocation_id: "AINV-UNKNOWN-CONVERSATION",
177
+ };
178
+ const unknownConversationUsage = {
179
+ ...usage(2, "openai", "unavailable", 1, 1),
180
+ event_id: "RUN-UNKNOWN-CONVERSATION:2",
181
+ run_id: "RUN-UNKNOWN-CONVERSATION",
182
+ source_segment_key: "unknown-conversation",
183
+ model_call_id: "MCALL-UNKNOWN-CONVERSATION",
184
+ };
185
+ await writeFile(
186
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-UNKNOWN-CONVERSATION.jsonl"),
187
+ `${[unknownConversationStarted, unknownConversationUsage, { ...finished, event_id: "RUN-UNKNOWN-CONVERSATION:3", run_id: "RUN-UNKNOWN-CONVERSATION", sequence: 3 }].map((row) => JSON.stringify(row)).join("\n")}\n`,
188
+ );
189
+ await rebuildSqliteReadModel(root);
190
+ const filesystem = new SqliteReadModelFileSystem(root);
191
+ try {
192
+ const models = filesystem.viewPage("cost_monitor_models", { limit: 20 });
193
+ expect(models.rows.find((row) => row.key === "gpt-a")).toMatchObject({
194
+ task_count: 2,
195
+ spec_count: 1,
196
+ prd_count: 1,
197
+ validation_count: 1,
198
+ evidence_count: 1,
199
+ });
200
+ // RUN-MULTI used both models for its linked Task. Model membership is
201
+ // consequently additive in the footer, while structural facts remain
202
+ // independently attributed through their creation Run.
203
+ expect(models.rows.find((row) => row.key === "claude-b")).toMatchObject({
204
+ task_count: 1,
205
+ spec_count: 0,
206
+ prd_count: 0,
207
+ validation_count: 0,
208
+ evidence_count: 0,
209
+ });
210
+ expect(models.rows.find((row) => row.key === "gpt-a")).toMatchObject({
211
+ conversation_count: 2,
212
+ });
213
+ expect(models.rows.find((row) => row.key === "claude-b")).toMatchObject({
214
+ conversation_count: 1,
215
+ });
216
+ expect(models.rows.map((row) => row.key)).not.toContain("unavailable");
217
+ expect(models.totals).toMatchObject({
218
+ task_count: 3,
219
+ spec_count: 1,
220
+ prd_count: 1,
221
+ validation_count: 1,
222
+ evidence_count: 1,
223
+ });
224
+ expect(models.totals?.task_count).toBe(
225
+ models.rows.reduce((sum, row) => sum + Number(row.task_count), 0),
226
+ );
227
+ expect((models.totals as Record<string, unknown> | undefined)?.conversation_count).toBe(3);
228
+ expect((models.totals as Record<string, unknown> | undefined)?.conversation_count).toBe(
229
+ models.rows.reduce((sum, row) => sum + Number(row.conversation_count), 0),
230
+ );
231
+ const periodModels = filesystem.viewPage("cost_monitor_models", {
232
+ from: "2026-08-04",
233
+ to: "2026-08-04",
234
+ limit: 20,
235
+ });
236
+ expect(periodModels.rows.map((row) => row.key)).not.toContain("unavailable");
237
+ expect((periodModels.totals as Record<string, unknown>).conversation_count).toBe(3);
238
+ expect(periodModels.rows.find((row) => row.key === "gpt-a")).toMatchObject({
239
+ task_count: 2,
240
+ spec_count: 1,
241
+ prd_count: 1,
242
+ validation_count: 1,
243
+ evidence_count: 1,
244
+ });
245
+ expect(periodModels.totals).toMatchObject({
246
+ task_count: 3,
247
+ spec_count: 1,
248
+ prd_count: 1,
249
+ validation_count: 1,
250
+ evidence_count: 1,
251
+ });
252
+ const filteredModels = filesystem.viewPage("cost_monitor_models", {
253
+ from: "2026-08-04",
254
+ to: "2026-08-04",
255
+ provider: "openai",
256
+ model: "gpt-a",
257
+ limit: 20,
258
+ });
259
+ expect(filteredModels.rows.find((row) => row.key === "gpt-a")).toMatchObject({
260
+ task_count: 2,
261
+ spec_count: 1,
262
+ prd_count: 1,
263
+ validation_count: 1,
264
+ evidence_count: 1,
265
+ });
266
+ } finally {
267
+ filesystem.close();
268
+ }
269
+ });
270
+ });
@@ -192,8 +192,9 @@ export async function prepareMultiProviderFixture(root: string) {
192
192
  join(root, ".agents", "records", "runs", "2026", "08", "RUN-UNAVAILABLE.jsonl"),
193
193
  `${[unavailableStarted, unavailableUsage, unavailableFinished].map((row) => JSON.stringify(row)).join("\n")}\n`,
194
194
  );
195
- // It is a canonical activity but not an Agent Invocation. Cost Monitor
196
- // must not blend this population into its token/cost/session rate.
195
+ // It is a canonical unassisted turn, not an Agent Invocation. Its legacy
196
+ // task_id is correlation metadata: Models counts its conversation, never a
197
+ // second Task participation.
197
198
  const {
198
199
  agent_invocation_id: _invocation,
199
200
  root_agent_invocation_id: _rootInvocation,
@@ -42,7 +42,7 @@ describe("SQLite dashboard read model multi-provider", () => {
42
42
  const { started, usage, finished } = await prepareMultiProviderFixture(root);
43
43
  await rebuildSqliteReadModel(root);
44
44
  const aggregateDatabase = new DatabaseSync(
45
- resolve(root, ".local/dashboard-sqlite/read-model.sqlite"),
45
+ resolve(root, ".local/sqlite/zaip-aios-dashboard.sqlite"),
46
46
  {
47
47
  readOnly: true,
48
48
  },
@@ -296,7 +296,7 @@ describe("SQLite dashboard read model multi-provider", () => {
296
296
  expect.objectContaining({ key: "claude-b", total_tokens: 20, estimated_brl: 10 }),
297
297
  ]),
298
298
  );
299
- expect(modelGroups.rows.map((row) => row.key)).toEqual(expect.arrayContaining(["unavailable"]));
299
+ expect(modelGroups.rows.map((row) => row.key)).not.toContain("unavailable");
300
300
  const modelTotals = modelGroups.rows.reduce<{
301
301
  invocations: number;
302
302
  tasks: number;
@@ -314,12 +314,26 @@ describe("SQLite dashboard read model multi-provider", () => {
314
314
  { invocations: 0, tasks: 0, duration: 0, tokens: 0, brl: 0 },
315
315
  );
316
316
  expect(modelTotals).toEqual({
317
- invocations: 2,
318
- tasks: 3,
317
+ // The unavailable residual is retained in canonical facts and Provider
318
+ // rows, but excluded from the displayed Models population.
319
+ invocations: 1,
320
+ // The remaining legacy unassisted turn carries task_id only as
321
+ // correlation metadata. It is a Conversation, not another Task.
322
+ tasks: 2,
319
323
  duration: 60_000,
320
324
  tokens: 1030,
321
325
  brl: 515,
322
326
  });
327
+ // RUN-UNASSISTED retains its legacy task_id for correlation, but its
328
+ // observed conversation belongs to gpt-a without adding a Task.
329
+ expect(modelGroups.rows.find((row) => row.key === "gpt-a")).toMatchObject({
330
+ task_count: 1,
331
+ conversation_count: 1,
332
+ });
333
+ expect(Number((modelGroups.totals as Record<string, unknown>).conversation_count)).toBe(1);
334
+ expect(
335
+ modelGroups.rows.reduce((sum, row) => sum + Number(row.conversation_count ?? 0), 0),
336
+ ).toBe(1);
323
337
  expect(filesystem.viewPage("cost_monitor_models", { query: "gpt-a" }).rows).toHaveLength(1);
324
338
  expect(
325
339
  filesystem.viewPage("cost_monitor_models", { provider: "openai", model: "gpt-a" }).rows[0],
@@ -0,0 +1,87 @@
1
+ import { mkdir, mkdtemp, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import {
5
+ type SqliteReadModelSourceCandidate,
6
+ sourceFingerprint,
7
+ } from "./sqlite-read-model-source-fingerprint.js";
8
+
9
+ const roots: string[] = [];
10
+ async function fixture() {
11
+ const directory = resolve(import.meta.dirname, "../../../../..", ".local/sqlite/tests");
12
+ await mkdir(directory, { recursive: true });
13
+ const root = await mkdtemp(join(directory, "fingerprint-"));
14
+ roots.push(root);
15
+ await mkdir(join(root, ".agents/records/tasks"), { recursive: true });
16
+ const path = join(root, ".agents/records/tasks/task.md");
17
+ await writeFile(path, "# first\n");
18
+ return { root, path };
19
+ }
20
+ const checkpoints = (candidates: SqliteReadModelSourceCandidate[]) =>
21
+ new Map(candidates.map((candidate) => [candidate.source, candidate]));
22
+
23
+ afterEach(async () => {
24
+ await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
25
+ });
26
+
27
+ describe("persistent source fingerprint checkpoints", () => {
28
+ it("reuses historical content hashes across serialized checkpoints and detects additions and removals", async () => {
29
+ const { root, path } = await fixture();
30
+ const initial = await sourceFingerprint(root);
31
+ const previous = checkpoints(JSON.parse(JSON.stringify(initial.candidates)));
32
+ expect(initial.hashedFiles).toBe(1);
33
+ const unchanged = await sourceFingerprint(root, { previous });
34
+ expect(unchanged).toMatchObject({
35
+ fingerprint: initial.fingerprint,
36
+ hashedFiles: 0,
37
+ reusedFiles: 1,
38
+ });
39
+ await writeFile(join(root, ".agents/records/tasks/new.md"), "# new\n");
40
+ const added = await sourceFingerprint(root, { previous });
41
+ expect(added).toMatchObject({ hashedFiles: 1, reusedFiles: 1 });
42
+ expect(added.candidates).toHaveLength(2);
43
+ await rm(path);
44
+ const removed = await sourceFingerprint(root, { previous: checkpoints(added.candidates) });
45
+ expect(removed).toMatchObject({ hashedFiles: 0, reusedFiles: 1 });
46
+ expect(removed.candidates.map((candidate) => candidate.source)).toEqual([
47
+ ".agents/records/tasks/new.md",
48
+ ]);
49
+ expect(removed.fingerprint).not.toBe(added.fingerprint);
50
+ });
51
+
52
+ it("detects same-size content changes when the previous modification time is restored", async () => {
53
+ const { root, path } = await fixture();
54
+ // Exact second precision makes the preserved mtime assertion portable.
55
+ const timestamp = new Date("2026-01-01T00:00:00Z");
56
+ await utimes(path, timestamp, timestamp);
57
+ const initial = await sourceFingerprint(root);
58
+ await writeFile(path, "# other\n");
59
+ await utimes(path, timestamp, timestamp);
60
+ const changed = await sourceFingerprint(root, { previous: checkpoints(initial.candidates) });
61
+ expect(changed.candidates[0]?.mtimeMs).toBe(initial.candidates[0]?.mtimeMs);
62
+ expect(changed.candidates[0]?.size).toBe(initial.candidates[0]?.size);
63
+ expect(changed.candidates[0]?.contentHash).not.toBe(initial.candidates[0]?.contentHash);
64
+ expect(changed).toMatchObject({ hashedFiles: 1, reusedFiles: 0 });
65
+ });
66
+
67
+ it("detects atomic file replacement and allows explicit complete content verification", async () => {
68
+ const { root, path } = await fixture();
69
+ const initial = await sourceFingerprint(root);
70
+ const metadata = await stat(path);
71
+ const replacement = `${path}.replacement`;
72
+ await writeFile(replacement, "# other\n");
73
+ await utimes(replacement, metadata.atime, metadata.mtime);
74
+ await rename(replacement, path);
75
+ const changed = await sourceFingerprint(root, { previous: checkpoints(initial.candidates) });
76
+ expect(changed.fingerprint).not.toBe(initial.fingerprint);
77
+ const verified = await sourceFingerprint(root, {
78
+ previous: checkpoints(changed.candidates),
79
+ verifyContent: true,
80
+ });
81
+ expect(verified).toMatchObject({
82
+ fingerprint: changed.fingerprint,
83
+ hashedFiles: 1,
84
+ reusedFiles: 0,
85
+ });
86
+ });
87
+ });
@@ -1,4 +1,5 @@
1
- import { readdir, stat } from "node:fs/promises";
1
+ import { createHash } from "node:crypto";
2
+ import { readdir, readFile, stat } from "node:fs/promises";
2
3
  import { join, relative, resolve } from "node:path";
3
4
 
4
5
  const textExtensions = new Set([".json", ".jsonl", ".md", ".yaml", ".yml", ".txt"]);
@@ -32,14 +33,41 @@ export type SqliteReadModelSourceCandidate = {
32
33
  source: string;
33
34
  mtimeMs: number;
34
35
  size: number;
36
+ contentHash: string;
37
+ ctimeMs: number;
38
+ fileIdentity: string;
39
+ };
40
+
41
+ export type SqliteReadModelSourceCheckpoint = Omit<SqliteReadModelSourceCandidate, "path">;
42
+
43
+ type FingerprintOptions = {
44
+ previous?: ReadonlyMap<string, SqliteReadModelSourceCheckpoint>;
45
+ verifyContent?: boolean;
35
46
  };
36
47
 
37
48
  export async function sourceFingerprint(
38
49
  workspaceRoot: string,
39
- ): Promise<{ fingerprint: string; candidates: SqliteReadModelSourceCandidate[] }> {
50
+ options: FingerprintOptions = {},
51
+ ): Promise<{
52
+ fingerprint: string;
53
+ candidates: SqliteReadModelSourceCandidate[];
54
+ hashedFiles: number;
55
+ reusedFiles: number;
56
+ }> {
40
57
  const candidates = (
41
58
  await Promise.all(
42
- [".agents/records", ".agents/config"].map((root) => listFiles(resolve(workspaceRoot, root))),
59
+ [
60
+ ".agents/config",
61
+ ...[
62
+ "tasks",
63
+ "specs",
64
+ "prds",
65
+ "runs",
66
+ "executions",
67
+ "changes",
68
+ "lifecycle/historical-developer-attribution",
69
+ ].map((kind) => `.agents/records/${kind}`),
70
+ ].map((root) => listFiles(resolve(workspaceRoot, root))),
43
71
  )
44
72
  )
45
73
  .flat()
@@ -49,27 +77,88 @@ export async function sourceFingerprint(
49
77
  isDashboardCanonicalSource(relative(workspaceRoot, path).replaceAll("\\", "/")),
50
78
  )
51
79
  .sort();
52
- const candidatesWithMetadata = await Promise.all(
53
- candidates.map(async (candidate) => {
54
- const metadata = await stat(candidate);
55
- return {
56
- path: candidate,
57
- source: relative(workspaceRoot, candidate).replaceAll("\\", "/"),
58
- mtimeMs: Math.trunc(metadata.mtimeMs),
59
- size: metadata.size,
60
- };
61
- }),
62
- );
80
+ const candidatesWithMetadata: SqliteReadModelSourceCandidate[] = [];
81
+ let hashedFiles = 0;
82
+ let reusedFiles = 0;
83
+ // Persisted checkpoints avoid rereading unchanged history across workers.
84
+ // ctime and identity detect same-size edits with preserved mtime and atomic
85
+ // replacement. Explicit verification bypasses this metadata shortcut.
86
+ for (let offset = 0; offset < candidates.length; offset += 32) {
87
+ candidatesWithMetadata.push(
88
+ ...(
89
+ await Promise.all(
90
+ candidates.slice(offset, offset + 32).map(async (candidate) => {
91
+ const source = relative(workspaceRoot, candidate).replaceAll("\\", "/");
92
+ try {
93
+ for (let attempt = 0; attempt < 3; attempt += 1) {
94
+ const metadata = await stat(candidate);
95
+ const current = {
96
+ path: candidate,
97
+ source,
98
+ mtimeMs: metadata.mtimeMs,
99
+ ctimeMs: metadata.ctimeMs,
100
+ size: metadata.size,
101
+ fileIdentity: `${metadata.dev}:${metadata.ino}:${metadata.birthtimeMs}`,
102
+ };
103
+ const previous = options.previous?.get(source);
104
+ if (
105
+ !options.verifyContent &&
106
+ previous !== undefined &&
107
+ previous.mtimeMs === current.mtimeMs &&
108
+ previous.ctimeMs === current.ctimeMs &&
109
+ previous.size === current.size &&
110
+ previous.fileIdentity === current.fileIdentity
111
+ ) {
112
+ reusedFiles += 1;
113
+ return { ...current, contentHash: previous.contentHash };
114
+ }
115
+ const contentHash = createHash("sha256")
116
+ .update(await readFile(candidate))
117
+ .digest("hex");
118
+ const after = await stat(candidate);
119
+ if (
120
+ metadata.mtimeMs !== after.mtimeMs ||
121
+ metadata.ctimeMs !== after.ctimeMs ||
122
+ metadata.size !== after.size ||
123
+ metadata.ino !== after.ino ||
124
+ metadata.dev !== after.dev ||
125
+ metadata.birthtimeMs !== after.birthtimeMs
126
+ )
127
+ continue;
128
+ hashedFiles += 1;
129
+ return { ...current, contentHash };
130
+ }
131
+ throw new Error(`Canonical source changed repeatedly during refresh: ${source}`);
132
+ } catch (error) {
133
+ // Concurrent removals are represented by absence in this snapshot.
134
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
135
+ throw error;
136
+ }
137
+ }),
138
+ )
139
+ ).filter((candidate): candidate is SqliteReadModelSourceCandidate => candidate !== null),
140
+ );
141
+ }
63
142
  return {
64
143
  fingerprint: candidatesWithMetadata
65
- .map((candidate) => `${candidate.source}:${candidate.mtimeMs}:${candidate.size}`)
144
+ .map(
145
+ (candidate) =>
146
+ `${candidate.source}:${candidate.mtimeMs}:${candidate.size}:${candidate.contentHash}`,
147
+ )
66
148
  .join("|"),
67
149
  candidates: candidatesWithMetadata,
150
+ hashedFiles,
151
+ reusedFiles,
68
152
  };
69
153
  }
70
154
 
71
155
  async function listFiles(directory: string): Promise<string[]> {
72
- const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
156
+ const entries = await readdir(directory, { withFileTypes: true }).catch(
157
+ (error: NodeJS.ErrnoException) => {
158
+ if (error.code === "ENOENT") return [];
159
+ throw error;
160
+ },
161
+ );
73
162
  return (
74
163
  await Promise.all(
75
164
  entries.map(async (entry) => {