@superblocksteam/vite-plugin-file-sync 2.0.119 → 2.0.120-next.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 (345) hide show
  1. package/dist/ai-service/agent/middleware.d.ts.map +1 -1
  2. package/dist/ai-service/agent/middleware.js +2 -5
  3. package/dist/ai-service/agent/middleware.js.map +1 -1
  4. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
  5. package/dist/ai-service/agent/prompts/build-base-system-prompt.js +17 -11
  6. package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
  7. package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts +17 -0
  8. package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts.map +1 -0
  9. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +219 -0
  10. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -0
  11. package/dist/ai-service/agent/tool-message-utils.d.ts.map +1 -1
  12. package/dist/ai-service/agent/tool-message-utils.js +64 -6
  13. package/dist/ai-service/agent/tool-message-utils.js.map +1 -1
  14. package/dist/ai-service/agent/tools/apis/api-comparator.d.ts +36 -0
  15. package/dist/ai-service/agent/tools/apis/api-comparator.d.ts.map +1 -0
  16. package/dist/ai-service/agent/tools/apis/api-comparator.js +369 -0
  17. package/dist/ai-service/agent/tools/apis/api-comparator.js.map +1 -0
  18. package/dist/ai-service/agent/tools/apis/api-validation-orchestrator.d.ts +4 -0
  19. package/dist/ai-service/agent/tools/apis/api-validation-orchestrator.d.ts.map +1 -1
  20. package/dist/ai-service/agent/tools/apis/api-validation-orchestrator.js +16 -5
  21. package/dist/ai-service/agent/tools/apis/api-validation-orchestrator.js.map +1 -1
  22. package/dist/ai-service/agent/tools/apis/build-api-artifact.d.ts +1 -0
  23. package/dist/ai-service/agent/tools/apis/build-api-artifact.d.ts.map +1 -1
  24. package/dist/ai-service/agent/tools/apis/build-api-artifact.js +17 -2
  25. package/dist/ai-service/agent/tools/apis/build-api-artifact.js.map +1 -1
  26. package/dist/ai-service/agent/tools/apis/build-api.d.ts.map +1 -1
  27. package/dist/ai-service/agent/tools/apis/build-api.js +17 -15
  28. package/dist/ai-service/agent/tools/apis/build-api.js.map +1 -1
  29. package/dist/ai-service/agent/tools/apis/get-api-docs.d.ts +1 -1
  30. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts.map +1 -1
  31. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js +18 -13
  32. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js.map +1 -1
  33. package/dist/ai-service/agent/tools/apis/test-api.d.ts.map +1 -1
  34. package/dist/ai-service/agent/tools/apis/test-api.js +138 -2
  35. package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
  36. package/dist/ai-service/agent/tools/build-copy-directory.d.ts +12 -0
  37. package/dist/ai-service/agent/tools/build-copy-directory.d.ts.map +1 -0
  38. package/dist/ai-service/agent/tools/build-copy-directory.js +51 -0
  39. package/dist/ai-service/agent/tools/build-copy-directory.js.map +1 -0
  40. package/dist/ai-service/agent/tools/build-copy-file.d.ts +12 -0
  41. package/dist/ai-service/agent/tools/build-copy-file.d.ts.map +1 -0
  42. package/dist/ai-service/agent/tools/build-copy-file.js +52 -0
  43. package/dist/ai-service/agent/tools/build-copy-file.js.map +1 -0
  44. package/dist/ai-service/agent/tools/build-copy-utils.d.ts +57 -0
  45. package/dist/ai-service/agent/tools/build-copy-utils.d.ts.map +1 -0
  46. package/dist/ai-service/agent/tools/build-copy-utils.js +37 -0
  47. package/dist/ai-service/agent/tools/build-copy-utils.js.map +1 -0
  48. package/dist/ai-service/agent/tools/build-debug.d.ts.map +1 -1
  49. package/dist/ai-service/agent/tools/build-debug.js +17 -5
  50. package/dist/ai-service/agent/tools/build-debug.js.map +1 -1
  51. package/dist/ai-service/agent/tools/build-finalize.d.ts.map +1 -1
  52. package/dist/ai-service/agent/tools/build-finalize.js +42 -50
  53. package/dist/ai-service/agent/tools/build-finalize.js.map +1 -1
  54. package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +7 -5
  55. package/dist/ai-service/agent/tools/build-manage-checklist.d.ts.map +1 -1
  56. package/dist/ai-service/agent/tools/build-manage-checklist.js +54 -108
  57. package/dist/ai-service/agent/tools/build-manage-checklist.js.map +1 -1
  58. package/dist/ai-service/agent/tools/databases/dev-database.d.ts +103 -0
  59. package/dist/ai-service/agent/tools/databases/dev-database.d.ts.map +1 -0
  60. package/dist/ai-service/agent/tools/databases/dev-database.js +117 -0
  61. package/dist/ai-service/agent/tools/databases/dev-database.js.map +1 -0
  62. package/dist/ai-service/agent/tools/get-logs.d.ts +1 -1
  63. package/dist/ai-service/agent/tools/index.d.ts +4 -0
  64. package/dist/ai-service/agent/tools/index.d.ts.map +1 -1
  65. package/dist/ai-service/agent/tools/index.js +4 -0
  66. package/dist/ai-service/agent/tools/index.js.map +1 -1
  67. package/dist/ai-service/agent/tools/integrations/delete-integration.d.ts +18 -0
  68. package/dist/ai-service/agent/tools/integrations/delete-integration.d.ts.map +1 -0
  69. package/dist/ai-service/agent/tools/integrations/delete-integration.js +99 -0
  70. package/dist/ai-service/agent/tools/integrations/delete-integration.js.map +1 -0
  71. package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +1 -1
  72. package/dist/ai-service/agent/tools/integrations/index.d.ts +1 -0
  73. package/dist/ai-service/agent/tools/integrations/index.d.ts.map +1 -1
  74. package/dist/ai-service/agent/tools/integrations/index.js +1 -0
  75. package/dist/ai-service/agent/tools/integrations/index.js.map +1 -1
  76. package/dist/ai-service/agent/tools/integrations/metadata.d.ts.map +1 -1
  77. package/dist/ai-service/agent/tools/integrations/metadata.js +30 -4
  78. package/dist/ai-service/agent/tools/integrations/metadata.js.map +1 -1
  79. package/dist/ai-service/agent/tools/report-security-findings.d.ts +163 -0
  80. package/dist/ai-service/agent/tools/report-security-findings.d.ts.map +1 -0
  81. package/dist/ai-service/agent/tools/report-security-findings.js +52 -0
  82. package/dist/ai-service/agent/tools/report-security-findings.js.map +1 -0
  83. package/dist/ai-service/agent/tools.d.ts +2 -0
  84. package/dist/ai-service/agent/tools.d.ts.map +1 -1
  85. package/dist/ai-service/agent/tools.js +74 -6
  86. package/dist/ai-service/agent/tools.js.map +1 -1
  87. package/dist/ai-service/agent/tools2/example.js +1 -1
  88. package/dist/ai-service/agent/tools2/example.js.map +1 -1
  89. package/dist/ai-service/agent/tools2/tools/ask-multi-choice.d.ts +7 -0
  90. package/dist/ai-service/agent/tools2/tools/ask-multi-choice.d.ts.map +1 -1
  91. package/dist/ai-service/agent/tools2/tools/ask-multi-choice.js +11 -1
  92. package/dist/ai-service/agent/tools2/tools/ask-multi-choice.js.map +1 -1
  93. package/dist/ai-service/agent/tools2/tools/ask-searchable-dropdown.d.ts +7 -0
  94. package/dist/ai-service/agent/tools2/tools/ask-searchable-dropdown.d.ts.map +1 -1
  95. package/dist/ai-service/agent/tools2/tools/ask-searchable-dropdown.js +3 -1
  96. package/dist/ai-service/agent/tools2/tools/ask-searchable-dropdown.js.map +1 -1
  97. package/dist/ai-service/agent/tools2/tools/download-attachments.d.ts.map +1 -1
  98. package/dist/ai-service/agent/tools2/tools/download-attachments.js +4 -3
  99. package/dist/ai-service/agent/tools2/tools/download-attachments.js.map +1 -1
  100. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts +9 -0
  101. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
  102. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +15 -1
  103. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
  104. package/dist/ai-service/agent/tools2/tools/list-attachments.d.ts.map +1 -1
  105. package/dist/ai-service/agent/tools2/tools/list-attachments.js +8 -4
  106. package/dist/ai-service/agent/tools2/tools/list-attachments.js.map +1 -1
  107. package/dist/ai-service/agent/tools2/tools/spawn-coding-subagents.d.ts +21 -4
  108. package/dist/ai-service/agent/tools2/tools/spawn-coding-subagents.d.ts.map +1 -1
  109. package/dist/ai-service/agent/tools2/tools/spawn-coding-subagents.js +87 -11
  110. package/dist/ai-service/agent/tools2/tools/spawn-coding-subagents.js.map +1 -1
  111. package/dist/ai-service/agent/tools2/types.d.ts +10 -2
  112. package/dist/ai-service/agent/tools2/types.d.ts.map +1 -1
  113. package/dist/ai-service/agent/tools2/types.js.map +1 -1
  114. package/dist/ai-service/agent/utils.d.ts.map +1 -1
  115. package/dist/ai-service/agent/utils.js +2 -0
  116. package/dist/ai-service/agent/utils.js.map +1 -1
  117. package/dist/ai-service/app-interface/filesystem/draft-manager.d.ts +1 -1
  118. package/dist/ai-service/app-interface/filesystem/draft-manager.d.ts.map +1 -1
  119. package/dist/ai-service/app-interface/filesystem/draft-manager.js.map +1 -1
  120. package/dist/ai-service/app-interface/npm-registry.d.ts +137 -0
  121. package/dist/ai-service/app-interface/npm-registry.d.ts.map +1 -0
  122. package/dist/ai-service/app-interface/npm-registry.js +415 -0
  123. package/dist/ai-service/app-interface/npm-registry.js.map +1 -0
  124. package/dist/ai-service/app-interface/shell.d.ts +38 -0
  125. package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
  126. package/dist/ai-service/app-interface/shell.js +222 -1
  127. package/dist/ai-service/app-interface/shell.js.map +1 -1
  128. package/dist/ai-service/attachments/uploaded-content-part.d.ts +5 -0
  129. package/dist/ai-service/attachments/uploaded-content-part.d.ts.map +1 -1
  130. package/dist/ai-service/attachments/uploaded-content-part.js +31 -21
  131. package/dist/ai-service/attachments/uploaded-content-part.js.map +1 -1
  132. package/dist/ai-service/checklist/persisted-checklist-store.d.ts +105 -0
  133. package/dist/ai-service/checklist/persisted-checklist-store.d.ts.map +1 -0
  134. package/dist/ai-service/checklist/persisted-checklist-store.js +498 -0
  135. package/dist/ai-service/checklist/persisted-checklist-store.js.map +1 -0
  136. package/dist/ai-service/context-download.d.ts +14 -1
  137. package/dist/ai-service/context-download.d.ts.map +1 -1
  138. package/dist/ai-service/context-download.js +80 -0
  139. package/dist/ai-service/context-download.js.map +1 -1
  140. package/dist/ai-service/dev-database-client.d.ts +90 -0
  141. package/dist/ai-service/dev-database-client.d.ts.map +1 -0
  142. package/dist/ai-service/dev-database-client.js +166 -0
  143. package/dist/ai-service/dev-database-client.js.map +1 -0
  144. package/dist/ai-service/features.d.ts +16 -0
  145. package/dist/ai-service/features.d.ts.map +1 -1
  146. package/dist/ai-service/features.js +10 -0
  147. package/dist/ai-service/features.js.map +1 -1
  148. package/dist/ai-service/filter-disabled-tools-for-migration.d.ts +6 -0
  149. package/dist/ai-service/filter-disabled-tools-for-migration.d.ts.map +1 -0
  150. package/dist/ai-service/filter-disabled-tools-for-migration.js +35 -0
  151. package/dist/ai-service/filter-disabled-tools-for-migration.js.map +1 -0
  152. package/dist/ai-service/index.d.ts +11 -1
  153. package/dist/ai-service/index.d.ts.map +1 -1
  154. package/dist/ai-service/index.js +86 -37
  155. package/dist/ai-service/index.js.map +1 -1
  156. package/dist/ai-service/integrations/store.d.ts +5 -0
  157. package/dist/ai-service/integrations/store.d.ts.map +1 -1
  158. package/dist/ai-service/integrations/store.js +19 -2
  159. package/dist/ai-service/integrations/store.js.map +1 -1
  160. package/dist/ai-service/judge/tools/submit-feedback.d.ts +1 -1
  161. package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
  162. package/dist/ai-service/llm/context-v2/context.js +6 -2
  163. package/dist/ai-service/llm/context-v2/context.js.map +1 -1
  164. package/dist/ai-service/llm/context-v2/manager.d.ts +8 -1
  165. package/dist/ai-service/llm/context-v2/manager.d.ts.map +1 -1
  166. package/dist/ai-service/llm/context-v2/manager.js +17 -1
  167. package/dist/ai-service/llm/context-v2/manager.js.map +1 -1
  168. package/dist/ai-service/llm/context-v2/prompts/compaction.d.ts +1 -1
  169. package/dist/ai-service/llm/context-v2/prompts/compaction.d.ts.map +1 -1
  170. package/dist/ai-service/llm/context-v2/prompts/compaction.js +3 -3
  171. package/dist/ai-service/llm/context-v2/types.d.ts +7 -0
  172. package/dist/ai-service/llm/context-v2/types.d.ts.map +1 -1
  173. package/dist/ai-service/llm/context-v2/types.js +33 -0
  174. package/dist/ai-service/llm/context-v2/types.js.map +1 -1
  175. package/dist/ai-service/llm/impl/clark.d.ts.map +1 -1
  176. package/dist/ai-service/llm/impl/clark.js +3 -3
  177. package/dist/ai-service/llm/impl/clark.js.map +1 -1
  178. package/dist/ai-service/llm/provider.d.ts.map +1 -1
  179. package/dist/ai-service/llm/provider.js +22 -7
  180. package/dist/ai-service/llm/provider.js.map +1 -1
  181. package/dist/ai-service/llm/types.d.ts +14 -1
  182. package/dist/ai-service/llm/types.d.ts.map +1 -1
  183. package/dist/ai-service/llmobs/context-registry.d.ts +62 -0
  184. package/dist/ai-service/llmobs/context-registry.d.ts.map +1 -0
  185. package/dist/ai-service/llmobs/context-registry.js +115 -0
  186. package/dist/ai-service/llmobs/context-registry.js.map +1 -0
  187. package/dist/ai-service/llmobs/otel-exporter.d.ts +23 -0
  188. package/dist/ai-service/llmobs/otel-exporter.d.ts.map +1 -1
  189. package/dist/ai-service/llmobs/otel-exporter.js +112 -10
  190. package/dist/ai-service/llmobs/otel-exporter.js.map +1 -1
  191. package/dist/ai-service/llmobs/tracer.d.ts +7 -0
  192. package/dist/ai-service/llmobs/tracer.d.ts.map +1 -1
  193. package/dist/ai-service/llmobs/tracer.js +38 -0
  194. package/dist/ai-service/llmobs/tracer.js.map +1 -1
  195. package/dist/ai-service/skills/system/_registry.generated.d.ts.map +1 -1
  196. package/dist/ai-service/skills/system/_registry.generated.js +2 -0
  197. package/dist/ai-service/skills/system/_registry.generated.js.map +1 -1
  198. package/dist/ai-service/skills/system/superblocks-frontend/skill.generated.d.ts +1 -1
  199. package/dist/ai-service/skills/system/superblocks-frontend/skill.generated.d.ts.map +1 -1
  200. package/dist/ai-service/skills/system/superblocks-frontend/skill.generated.js +2 -0
  201. package/dist/ai-service/skills/system/superblocks-frontend/skill.generated.js.map +1 -1
  202. package/dist/ai-service/skills/system/superblocks-migration/references/focused-debug.generated.d.ts +1 -1
  203. package/dist/ai-service/skills/system/superblocks-migration/references/focused-debug.generated.d.ts.map +1 -1
  204. package/dist/ai-service/skills/system/superblocks-migration/references/focused-debug.generated.js +3 -1
  205. package/dist/ai-service/skills/system/superblocks-migration/references/focused-debug.generated.js.map +1 -1
  206. package/dist/ai-service/skills/system/superblocks-migration/references/yaml-block-mapping.generated.d.ts +1 -1
  207. package/dist/ai-service/skills/system/superblocks-migration/references/yaml-block-mapping.generated.d.ts.map +1 -1
  208. package/dist/ai-service/skills/system/superblocks-migration/references/yaml-block-mapping.generated.js +29 -0
  209. package/dist/ai-service/skills/system/superblocks-migration/references/yaml-block-mapping.generated.js.map +1 -1
  210. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts +1 -1
  211. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts.map +1 -1
  212. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js +139 -7
  213. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js.map +1 -1
  214. package/dist/ai-service/skills/system/third-party-migration/claude-design.generated.d.ts +2 -0
  215. package/dist/ai-service/skills/system/third-party-migration/claude-design.generated.d.ts.map +1 -0
  216. package/dist/ai-service/skills/system/third-party-migration/claude-design.generated.js +107 -0
  217. package/dist/ai-service/skills/system/third-party-migration/claude-design.generated.js.map +1 -0
  218. package/dist/ai-service/skills/system/third-party-migration/skill.generated.d.ts +1 -1
  219. package/dist/ai-service/skills/system/third-party-migration/skill.generated.d.ts.map +1 -1
  220. package/dist/ai-service/skills/system/third-party-migration/skill.generated.js +33 -3
  221. package/dist/ai-service/skills/system/third-party-migration/skill.generated.js.map +1 -1
  222. package/dist/ai-service/state-machine/clark-fsm.d.ts +21 -0
  223. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  224. package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
  225. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  226. package/dist/ai-service/state-machine/handlers/agent-planning.js +79 -6
  227. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  228. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts +10 -0
  229. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  230. package/dist/ai-service/state-machine/handlers/llm-generating.js +69 -41
  231. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  232. package/dist/ai-service/state-machine/helpers/peer.d.ts +35 -7
  233. package/dist/ai-service/state-machine/helpers/peer.d.ts.map +1 -1
  234. package/dist/ai-service/state-machine/helpers/peer.js +81 -15
  235. package/dist/ai-service/state-machine/helpers/peer.js.map +1 -1
  236. package/dist/ai-service/template-renderer.d.ts +14 -1
  237. package/dist/ai-service/template-renderer.d.ts.map +1 -1
  238. package/dist/ai-service/template-renderer.js +144 -41
  239. package/dist/ai-service/template-renderer.js.map +1 -1
  240. package/dist/ai-service/transform/api-builder/to-sdk-transformer.js +2 -2
  241. package/dist/ai-service/transform/api-builder/to-sdk-transformer.js.map +1 -1
  242. package/dist/ai-service/transform/api-builder/to-yaml-transformer.js +2 -2
  243. package/dist/ai-service/transform/api-builder/to-yaml-transformer.js.map +1 -1
  244. package/dist/draft-interface.d.ts +1 -1
  245. package/dist/draft-interface.d.ts.map +1 -1
  246. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  247. package/dist/file-sync-vite-plugin.js +34 -27
  248. package/dist/file-sync-vite-plugin.js.map +1 -1
  249. package/dist/file-system-helpers.d.ts +4 -0
  250. package/dist/file-system-helpers.d.ts.map +1 -1
  251. package/dist/file-system-helpers.js +13 -0
  252. package/dist/file-system-helpers.js.map +1 -1
  253. package/dist/inject-index-vite-plugin.d.ts.map +1 -1
  254. package/dist/inject-index-vite-plugin.js +15 -1
  255. package/dist/inject-index-vite-plugin.js.map +1 -1
  256. package/dist/injected-index.d.ts.map +1 -1
  257. package/dist/injected-index.js +15 -1
  258. package/dist/injected-index.js.map +1 -1
  259. package/dist/lock-service/index.d.ts.map +1 -1
  260. package/dist/lock-service/index.js +8 -10
  261. package/dist/lock-service/index.js.map +1 -1
  262. package/dist/migration/migration-checklist.d.ts +51 -2
  263. package/dist/migration/migration-checklist.d.ts.map +1 -1
  264. package/dist/migration/migration-checklist.js +79 -151
  265. package/dist/migration/migration-checklist.js.map +1 -1
  266. package/dist/migration/migration-routes.d.ts.map +1 -1
  267. package/dist/migration/migration-routes.js +290 -30
  268. package/dist/migration/migration-routes.js.map +1 -1
  269. package/dist/migration/migration-verification.d.ts +206 -0
  270. package/dist/migration/migration-verification.d.ts.map +1 -0
  271. package/dist/migration/migration-verification.js +1006 -0
  272. package/dist/migration/migration-verification.js.map +1 -0
  273. package/dist/migration/recommended-user-deps.d.ts +39 -0
  274. package/dist/migration/recommended-user-deps.d.ts.map +1 -0
  275. package/dist/migration/recommended-user-deps.js +209 -0
  276. package/dist/migration/recommended-user-deps.js.map +1 -0
  277. package/dist/migration/restructure.d.ts +29 -2
  278. package/dist/migration/restructure.d.ts.map +1 -1
  279. package/dist/migration/restructure.js +145 -6
  280. package/dist/migration/restructure.js.map +1 -1
  281. package/dist/migration/scan-imports.d.ts +7 -0
  282. package/dist/migration/scan-imports.d.ts.map +1 -0
  283. package/dist/migration/scan-imports.js +178 -0
  284. package/dist/migration/scan-imports.js.map +1 -0
  285. package/dist/migration/translation-prompt.d.ts.map +1 -1
  286. package/dist/migration/translation-prompt.js +2 -0
  287. package/dist/migration/translation-prompt.js.map +1 -1
  288. package/dist/migration/unsupported-integrations.d.ts.map +1 -1
  289. package/dist/migration/unsupported-integrations.js +9 -0
  290. package/dist/migration/unsupported-integrations.js.map +1 -1
  291. package/dist/migration/yaml-walk.d.ts +18 -0
  292. package/dist/migration/yaml-walk.d.ts.map +1 -0
  293. package/dist/migration/yaml-walk.js +45 -0
  294. package/dist/migration/yaml-walk.js.map +1 -0
  295. package/dist/migration-templates/app-fullstack/client/components/hooks/use-mobile.ts +1 -1
  296. package/dist/migration-templates/app-fullstack/client/components/ui/accordion.tsx +1 -1
  297. package/dist/migration-templates/app-fullstack/client/components/ui/avatar.tsx +1 -1
  298. package/dist/migration-templates/app-fullstack/client/components/ui/breadcrumb.tsx +1 -1
  299. package/dist/migration-templates/app-fullstack/client/components/ui/button.tsx +1 -1
  300. package/dist/migration-templates/app-fullstack/client/components/ui/calendar.tsx +1 -1
  301. package/dist/migration-templates/app-fullstack/client/components/ui/chart.tsx +1 -1
  302. package/dist/migration-templates/app-fullstack/client/components/ui/file-dropzone.tsx +1 -1
  303. package/dist/migration-templates/app-fullstack/client/components/ui/file-input.tsx +1 -1
  304. package/dist/migration-templates/app-fullstack/client/components/ui/hover-card.tsx +1 -1
  305. package/dist/migration-templates/app-fullstack/client/components/ui/image.tsx +1 -1
  306. package/dist/migration-templates/app-fullstack/client/components/ui/input.tsx +1 -1
  307. package/dist/migration-templates/app-fullstack/client/components/ui/label.tsx +1 -1
  308. package/dist/migration-templates/app-fullstack/client/components/ui/navigation-menu.tsx +1 -1
  309. package/dist/migration-templates/app-fullstack/client/components/ui/pagination.tsx +1 -1
  310. package/dist/migration-templates/app-fullstack/client/components/ui/popover.tsx +1 -1
  311. package/dist/migration-templates/app-fullstack/client/components/ui/progress.tsx +1 -1
  312. package/dist/migration-templates/app-fullstack/client/components/ui/select.tsx +1 -1
  313. package/dist/migration-templates/app-fullstack/client/components/ui/sheet.tsx +1 -1
  314. package/dist/migration-templates/app-fullstack/client/components/ui/sidebar.tsx +1 -1
  315. package/dist/migration-templates/app-fullstack/client/components/ui/slider.tsx +1 -1
  316. package/dist/migration-templates/app-fullstack/client/components/ui/switch.tsx +1 -1
  317. package/dist/migration-templates/app-fullstack/client/components/ui/table.tsx +1 -1
  318. package/dist/migration-templates/app-fullstack/client/components/ui/tabs.tsx +1 -1
  319. package/dist/migration-templates/app-fullstack/client/components/ui/toggle-group.tsx +1 -1
  320. package/dist/migration-templates/app-fullstack/client/components/ui/toggle.tsx +1 -1
  321. package/dist/migration-templates/app-fullstack/client/components/ui/tooltip.tsx +1 -1
  322. package/dist/socket-manager.d.ts.map +1 -1
  323. package/dist/socket-manager.js +8 -0
  324. package/dist/socket-manager.js.map +1 -1
  325. package/dist/sync-service/hash-dir-tree.d.ts +1 -1
  326. package/dist/sync-service/hash-dir-tree.d.ts.map +1 -1
  327. package/dist/sync-service/hash-dir-tree.js +3 -3
  328. package/dist/sync-service/hash-dir-tree.js.map +1 -1
  329. package/dist/sync-service/index.d.ts +0 -14
  330. package/dist/sync-service/index.d.ts.map +1 -1
  331. package/dist/sync-service/index.js +1 -44
  332. package/dist/sync-service/index.js.map +1 -1
  333. package/dist/sync-service/list-dir.d.ts +1 -1
  334. package/dist/sync-service/list-dir.d.ts.map +1 -1
  335. package/dist/sync-service/list-dir.js +36 -3
  336. package/dist/sync-service/list-dir.js.map +1 -1
  337. package/dist/sync-service/snapshot/take-snapshot.d.ts +1 -1
  338. package/dist/sync-service/snapshot/take-snapshot.d.ts.map +1 -1
  339. package/dist/sync-service/snapshot/take-snapshot.js +4 -8
  340. package/dist/sync-service/snapshot/take-snapshot.js.map +1 -1
  341. package/dist/util/log-sanitizer.d.ts +6 -5
  342. package/dist/util/log-sanitizer.d.ts.map +1 -1
  343. package/dist/util/log-sanitizer.js +21 -6
  344. package/dist/util/log-sanitizer.js.map +1 -1
  345. package/package.json +9 -8
@@ -0,0 +1,1006 @@
1
+ import crypto from "node:crypto";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import yaml from "yaml";
5
+ import z from "zod";
6
+ import { AiEntityType, AiPermissionType, } from "@superblocksteam/library-shared/types";
7
+ import { LanguagePluginID } from "@superblocksteam/shared";
8
+ import { extractIntegrationIdsFromSource, extractMutations, } from "../ai-service/agent/tools/apis/analysis.js";
9
+ import { compareApiResults, } from "../ai-service/agent/tools/apis/api-comparator.js";
10
+ import { ApiExecutor, } from "../ai-service/agent/tools/apis/api-executor.js";
11
+ import { createToolFactory, PermissionLevel, ToolCategory, } from "../ai-service/agent/tools2/types.js";
12
+ import { updateChecklistItem } from "../ai-service/checklist/persisted-checklist-store.js";
13
+ import { sanitizeLogMessage } from "../util/log-sanitizer.js";
14
+ import { getLogger } from "../util/logger.js";
15
+ import { OperationQueue } from "../util/operation-queue.js";
16
+ import { classifyYamlStepMutation } from "./migration-classifier.js";
17
+ import { forEachYamlStep } from "./yaml-walk.js";
18
+ const SCRATCH_DIR = "scratch";
19
+ const VERIFICATION_DIR = "migration-verification";
20
+ const V2_BACKUP_DIR = "v2-backup";
21
+ const DIFF_SIZE_CAP = 1024 * 1024; // 1 MB
22
+ const PIPELINE_TIMEOUT_MS = 30_000;
23
+ /**
24
+ * Hard cap on verification attempts per API per migration. The agent does NOT
25
+ * decide when to stop iterating — this counter is computed from on-disk
26
+ * history and enforced inside `runMigrationVerification`. An LLM agent asked
27
+ * to self-assess "am I converging?" reliably picks the socially acceptable
28
+ * answer ("yes"); the cap removes that judgment from the loop.
29
+ */
30
+ export const MAX_VERIFICATION_ATTEMPTS = 10;
31
+ const LANGUAGE_PLUGIN_IDS = new Set(Object.values(LanguagePluginID));
32
+ /**
33
+ * Verify a single API by running both the v2 YAML and v3 TS versions with the
34
+ * supplied inputs and comparing their outputs.
35
+ *
36
+ * Wraps `computeVerificationOutcome` with on-disk history bookkeeping:
37
+ * - Reads `scratch/migration-verification/<apiName>.history.json` and refuses
38
+ * to run beyond `MAX_VERIFICATION_ATTEMPTS`, returning `kind: "exhausted"`.
39
+ * - For `diverged` outcomes, computes `progress` (`first_attempt` |
40
+ * `converging` | `stalled`) by hashing (path, kind) tuples and comparing to
41
+ * the prior diverged attempt's hash.
42
+ * - Appends a history entry on every run so the next call has accurate state.
43
+ *
44
+ * History persistence is best-effort: bookkeeping I/O failures are logged but
45
+ * do not surface as verification errors.
46
+ */
47
+ export async function runMigrationVerification(params) {
48
+ // Serialize the entire read-cap-check + pipeline + append for a given API
49
+ // through the per-API queue. Two concurrent calls for the same API used to
50
+ // both observe `attempts.length = MAX-1`, both pass the cap check, and
51
+ // both run the pipeline + append — silently exceeding the documented hard
52
+ // cap by N. Wrapping end-to-end (rather than serializing only the append)
53
+ // costs us same-API parallelism — but parallel verifications for the same
54
+ // API are wasteful work anyway (the second result clobbers the first or
55
+ // hits the cap), so the cost is purely on a degenerate path. Different
56
+ // APIs use different queues and remain fully parallel.
57
+ return getVerificationHistoryQueue(params.appRootDir, params.apiName).enqueue(async () => {
58
+ const history = await readHistory(params.appRootDir, params.apiName);
59
+ if (history.attempts.length >= MAX_VERIFICATION_ATTEMPTS) {
60
+ return {
61
+ kind: "exhausted",
62
+ attempts: history.attempts.length,
63
+ lastDivergence: await readLastDivergedDiff(params.appRootDir, params.apiName),
64
+ };
65
+ }
66
+ const raw = await computeVerificationOutcome(params);
67
+ let outcome;
68
+ let divergenceHashForHistory;
69
+ if (raw.kind === "diverged") {
70
+ const currentHash = divergenceShapeHash(raw.diff.summaryForAgent);
71
+ divergenceHashForHistory = currentHash;
72
+ const lastDivergedHash = findLastDivergedShapeHash(history);
73
+ const progress = lastDivergedHash === undefined
74
+ ? "first_attempt"
75
+ : lastDivergedHash === currentHash
76
+ ? "stalled"
77
+ : "converging";
78
+ outcome = {
79
+ kind: "diverged",
80
+ diff: raw.diff,
81
+ attempt: history.attempts.length + 1,
82
+ progress,
83
+ };
84
+ }
85
+ else {
86
+ outcome = raw;
87
+ }
88
+ // Single decision-point log so an operator chasing "Clark says diverged
89
+ // but I see passed" has a stdout breadcrumb to correlate. Logged here at
90
+ // the single return path of `runMigrationVerification` rather than in
91
+ // each tool-side branch — wraps every outcome (passed, diverged,
92
+ // both_failed, exhausted, skipped_mutation, v2_unrunnable, error) with a
93
+ // consistent shape. Apply name only; raw failure strings stay scrubbed.
94
+ getLogger().info(`[migration-verify] outcome`, {
95
+ apiName: params.apiName,
96
+ attempt: history.attempts.length + 1,
97
+ maxAttempts: MAX_VERIFICATION_ATTEMPTS,
98
+ kind: outcome.kind,
99
+ progress: outcome.kind === "diverged" ? outcome.progress : undefined,
100
+ divergenceCount: outcome.kind === "diverged"
101
+ ? outcome.diff.summaryForAgent.length
102
+ : undefined,
103
+ });
104
+ // Direct (non-enqueued) write — we already hold this API's queue, so
105
+ // re-entering it would deadlock.
106
+ await writeHistoryEntry(params.appRootDir, params.apiName, {
107
+ ts: new Date().toISOString(),
108
+ kind: outcome.kind,
109
+ divergenceShapeHash: divergenceHashForHistory,
110
+ divergenceCount: outcome.kind === "diverged"
111
+ ? outcome.diff.summaryForAgent.length
112
+ : undefined,
113
+ });
114
+ return outcome;
115
+ });
116
+ }
117
+ async function computeVerificationOutcome({ apiName, appRootDir, inputs, runV2Pipeline, runV3Sdk, allowAskPolicy, }) {
118
+ const verificationInputs = inputs ?? {};
119
+ // 1. Detect mutations in the v3 TS source before running anything.
120
+ const apiTsPath = join(appRootDir, "server", "apis", apiName, "api.ts");
121
+ let source;
122
+ try {
123
+ source = await readFile(apiTsPath, "utf-8");
124
+ }
125
+ catch (error) {
126
+ return {
127
+ kind: "error",
128
+ reason: `Cannot read v3 source at ${apiTsPath}: ${error instanceof Error ? error.message : String(error)}`,
129
+ };
130
+ }
131
+ // Extract integration IDs up-front so they can be passed to runV3Sdk for
132
+ // the auth preflight (matching test-api.ts). Falls back to undefined on AST
133
+ // parse failure — runV3Sdk will then rely on the Redux store as before.
134
+ let integrationIds;
135
+ try {
136
+ const ids = extractIntegrationIdsFromSource(source);
137
+ if (ids.length > 0)
138
+ integrationIds = ids;
139
+ }
140
+ catch {
141
+ // AST parse failure: leave undefined.
142
+ }
143
+ let mutations;
144
+ try {
145
+ mutations = extractMutations(source);
146
+ }
147
+ catch (error) {
148
+ // AST parse failure: we cannot prove the source is mutation-free, so fall
149
+ // back to the same policy gate as if mutations were detected. The user
150
+ // can still get the API verified by allow-and-retry or manually verified.
151
+ return {
152
+ kind: "skipped_mutation",
153
+ mutationDetails: [
154
+ `unable to analyze source for mutations: ${error instanceof Error ? error.message : String(error)}`,
155
+ ],
156
+ skipPolicy: "ask",
157
+ };
158
+ }
159
+ const v2BackupPath = join(appRootDir, SCRATCH_DIR, V2_BACKUP_DIR, "apis", apiName, "api.yaml");
160
+ // Also check the v2 backup for writes — a half-migrated API where v3 looks
161
+ // read-only but the original v2 still mutates would otherwise bypass the
162
+ // policy gate and run the v2 pipeline against real integrations.
163
+ const v2Analysis = await analyzeV2BackupMutations(v2BackupPath);
164
+ const mutationDetails = [
165
+ ...mutations.map((m) => `v3 ${m.type} "${m.name}": ${m.details}`),
166
+ ...v2Analysis.mutationDetails,
167
+ ];
168
+ if (mutationDetails.length > 0) {
169
+ const { effective } = await getEffectiveMigrationWritePolicy({
170
+ appRootDir,
171
+ apiName,
172
+ integrationIds,
173
+ });
174
+ if (effective === "deny" || (effective === "ask" && !allowAskPolicy)) {
175
+ return {
176
+ kind: "skipped_mutation",
177
+ mutationDetails,
178
+ skipPolicy: effective,
179
+ };
180
+ }
181
+ // effective === "allow": fall through to run both pipelines
182
+ }
183
+ // 2. Run v2 YAML pipeline.
184
+ let v2Result;
185
+ try {
186
+ const pipelineResult = await withTimeout(runV2Pipeline(v2BackupPath, verificationInputs), PIPELINE_TIMEOUT_MS, "v2 pipeline");
187
+ if (!pipelineResult.executionResult) {
188
+ return {
189
+ kind: "v2_unrunnable",
190
+ reason: "v2 pipeline returned no execution result",
191
+ };
192
+ }
193
+ v2Result = pipelineResult.executionResult;
194
+ }
195
+ catch (error) {
196
+ return {
197
+ kind: "v2_unrunnable",
198
+ reason: error instanceof Error ? error.message : String(error),
199
+ };
200
+ }
201
+ // 3. Run v3 SDK.
202
+ let v3Result;
203
+ try {
204
+ v3Result = await withTimeout(runV3Sdk(integrationIds, verificationInputs), PIPELINE_TIMEOUT_MS, "v3 SDK");
205
+ }
206
+ catch (error) {
207
+ return {
208
+ kind: "error",
209
+ reason: error instanceof Error ? error.message : String(error),
210
+ };
211
+ }
212
+ // 3.5. If both executions returned failure, surface that explicitly rather
213
+ // than letting compareApiResults produce a vacuous "diff.empty" pass.
214
+ if (!v2Result.success && !v3Result.success) {
215
+ return {
216
+ kind: "both_failed",
217
+ v2Error: extractFailureMessage(v2Result, "v2"),
218
+ v3Error: extractFailureMessage(v3Result, "v3"),
219
+ };
220
+ }
221
+ // 3.6. One-sided failure: if one side succeeded and the other failed, that is
222
+ // a divergence regardless of what the output diff says. Without this
223
+ // guard, a v3 that returns `success: false` with empty `outputs` would
224
+ // compare equal to a v2 with empty `outputs`, producing a false "passed".
225
+ if (v2Result.success !== v3Result.success) {
226
+ const failedSide = v2Result.success ? "v3" : "v2";
227
+ const failureMessage = extractFailureMessage(v2Result.success ? v3Result : v2Result, failedSide);
228
+ // Server-side log so divergence shows up in the dev-server log stream
229
+ // (and DataDog when the EE forwards stdout). The actual failure text is
230
+ // intentionally NOT logged here — extractFailureMessage may include
231
+ // upstream response bodies, query strings, or credentials echoed back by
232
+ // the integration. The agent already gets failureMessage via
233
+ // summaryForAgent, and the user sees full detail in the verification UI.
234
+ // What we want from the server log is a "this happened" marker:
235
+ // apiName, which side failed, and the error type/source classification.
236
+ // Logger.warn accepts free-form key/value pairs; .error has a stricter
237
+ // ErrorMeta shape that doesn't fit this diagnostic record.
238
+ getLogger().warn(`Migration verification diverged for ${apiName}`, {
239
+ apiName,
240
+ failedSide,
241
+ v2: {
242
+ success: v2Result.success,
243
+ hasSystemError: Boolean(v2Result.systemError),
244
+ firstErrorType: v2Result.errors?.[0]?.type,
245
+ },
246
+ v3: { success: v3Result.success },
247
+ });
248
+ // The agent-facing `failureMessage` is scrubbed via `redactForAgent`
249
+ // because it ends up in LLM context (and from there into chat
250
+ // transcripts persisted by the client). The UI-facing `valuesForUi.reason`
251
+ // intentionally keeps the raw text — that channel renders to the
252
+ // user's own browser, not into the model context.
253
+ return {
254
+ kind: "diverged",
255
+ diff: {
256
+ empty: false,
257
+ summaryForAgent: [
258
+ {
259
+ path: "success",
260
+ kind: "value_mismatch",
261
+ expectedHash: String(v2Result.success),
262
+ actualHash: String(v3Result.success),
263
+ failureMessage: redactForAgent(failureMessage),
264
+ },
265
+ ],
266
+ valuesForUi: [
267
+ {
268
+ path: "success",
269
+ expected: v2Result.success,
270
+ actual: v3Result.success,
271
+ reason: `${failedSide} execution failed while the other succeeded: ${failureMessage}`,
272
+ },
273
+ ],
274
+ },
275
+ };
276
+ }
277
+ // 4. Compare.
278
+ const diff = compareApiResults(v2Result, v3Result);
279
+ if (diff.empty) {
280
+ return { kind: "passed" };
281
+ }
282
+ // 5. Persist the diff to disk for the UI to render (valuesForUi only).
283
+ const verificationDir = join(appRootDir, SCRATCH_DIR, VERIFICATION_DIR);
284
+ await mkdir(verificationDir, { recursive: true });
285
+ const diffPath = join(verificationDir, `${apiName}.diff.json`);
286
+ const diffJson = JSON.stringify({ valuesForUi: diff.valuesForUi }, null, 2);
287
+ const payload = diffJson.length > DIFF_SIZE_CAP
288
+ ? JSON.stringify({ valuesForUi: diff.valuesForUi.slice(0, 50), truncated: true }, null, 2)
289
+ : diffJson;
290
+ await writeFile(diffPath, payload, "utf-8");
291
+ return { kind: "diverged", diff };
292
+ }
293
+ /** Relative path (from appRootDir) for a diff JSON file. */
294
+ export function diffRelativePath(apiName) {
295
+ return `${SCRATCH_DIR}/${VERIFICATION_DIR}/${apiName}.diff.json`;
296
+ }
297
+ export const migrationVerificationToolFactory = createToolFactory("runMigrationVerification", ({ clark, services, }) => ({
298
+ category: ToolCategory.DEBUG,
299
+ getRequiredPermissions: async (input) => {
300
+ const apiName = input?.apiName;
301
+ if (!apiName)
302
+ return [];
303
+ let source;
304
+ try {
305
+ source = await services.appShell.readFile(`server/apis/${apiName}/api.ts`);
306
+ }
307
+ catch {
308
+ return [];
309
+ }
310
+ let mutations;
311
+ try {
312
+ mutations = extractMutations(source);
313
+ }
314
+ catch {
315
+ return [];
316
+ }
317
+ if (mutations.length === 0)
318
+ return [];
319
+ let integrationIds;
320
+ try {
321
+ const ids = extractIntegrationIdsFromSource(source);
322
+ if (ids.length > 0)
323
+ integrationIds = ids;
324
+ }
325
+ catch {
326
+ integrationIds = undefined;
327
+ }
328
+ const { effective, sourceIds } = await getEffectiveMigrationWritePolicy({
329
+ appRootDir: services.appRootDirPath,
330
+ apiName,
331
+ integrationIds,
332
+ });
333
+ if (effective === "allow" || sourceIds.length === 0) {
334
+ return [];
335
+ }
336
+ return sourceIds.map((entityId) => ({
337
+ entityType: AiEntityType.INTEGRATION,
338
+ entityId,
339
+ permissionType: AiPermissionType.WRITE,
340
+ }));
341
+ },
342
+ getActionName: (input) => {
343
+ const name = input?.apiName ?? "API";
344
+ return {
345
+ future: `verify ${name}`,
346
+ present: `verifying ${name}`,
347
+ past: `verified ${name}`,
348
+ };
349
+ },
350
+ description: "Verify a migrated API by running both the v2 YAML and v3 TS versions " +
351
+ "and comparing their outputs. Pass `inputs` to inject test parameters " +
352
+ "for any bindings the API references (the same way `testApi` does — " +
353
+ "`Input1.value`, `Table1.selectedRow`, etc.); both v2 and v3 receive " +
354
+ "the same inputs so the comparison is apples-to-apples. Omit `inputs` " +
355
+ "(or pass `{}`) only when the API takes no inputs. Each pipeline has a " +
356
+ `${PIPELINE_TIMEOUT_MS / 1000}s reporting timeout (reject-only — the ` +
357
+ "underlying execution can't be cancelled, so allow-policy mutations " +
358
+ "may continue server-side after this returns). Returns a structured outcome: " +
359
+ "'passed', 'diverged' (with summaryForAgent), 'both_failed' (both " +
360
+ "executions errored — cannot determine correctness), " +
361
+ "'skipped_mutation', 'v2_unrunnable', or 'error'. On 'diverged' use " +
362
+ "`summaryForAgent` to revise `server/apis/<apiName>/api.ts` and re-run " +
363
+ "the tool — the attempt counter is tracked server-side from on-disk " +
364
+ "history; the response always includes the current `attempt` and " +
365
+ "`maxAttempts` so the agent doesn't have to thread it.",
366
+ inputSchema: z.object({
367
+ apiName: z
368
+ .string()
369
+ .describe("The API name to verify (must match the directory under " +
370
+ "server/apis/, may contain only letters, digits, hyphens, or " +
371
+ "underscores). Bare .string() — `.regex()` emits a JSON Schema " +
372
+ "`pattern` keyword that Snowflake Cortex rejects; the format is " +
373
+ "validated below at runtime.")
374
+ .refine((v) => /^[a-zA-Z0-9_-]+$/.test(v), {
375
+ message: "apiName must contain only letters, digits, hyphens, or underscores",
376
+ }),
377
+ inputs: z
378
+ .record(z.string(), z.any())
379
+ .optional()
380
+ .describe("Test input values for the API's bindings, applied to BOTH the " +
381
+ "v2 YAML and v3 TS executions so the diff is meaningful. Use the " +
382
+ "same approach as `testApi`: a key per binding the API references " +
383
+ "(component values like `Input1.value`, table selections like " +
384
+ "`Table1.selectedRow`, state variables, workflow params, etc.) " +
385
+ "with realistic mock values that match the binding's expected " +
386
+ "type. Omit (defaults to `{}`) only when the API takes no inputs " +
387
+ "— passing `{}` for an API that needs inputs typically produces " +
388
+ "a vacuous 'both_failed' result."),
389
+ // `attemptCount` was previously declared here for the agent to thread
390
+ // through retries, but `execute` never read it — the real attempt
391
+ // count is computed from on-disk history inside
392
+ // `runMigrationVerification` so an agent that lies about its attempt
393
+ // index can't bypass `MAX_VERIFICATION_ATTEMPTS`. The field is dropped
394
+ // to match what the tool actually uses; Zod's default object policy
395
+ // strips unknown keys so any agent still passing it in-flight is a
396
+ // no-op rather than an error.
397
+ }),
398
+ async execute({ apiName, inputs }) {
399
+ const appRootDir = services.appRootDirPath;
400
+ const outcome = await runMigrationVerification({
401
+ apiName,
402
+ appRootDir,
403
+ inputs,
404
+ allowAskPolicy: true,
405
+ runV2Pipeline: async (sourcePath, pipelineInputs) => {
406
+ // runApiValidationPipeline's sourcePath only controls metadata
407
+ // loading (validateMetadata: false here), not execution — it always
408
+ // calls aiExecuteV2Api({ type: "fetch", apiName }) which resolves the
409
+ // API by name in the editor. After migration that name points to the
410
+ // v3 TS version, so the v2 backup YAML was never actually run.
411
+ // Load the backup YAML directly and execute inline instead.
412
+ const yamlContent = await readFile(sourcePath, "utf-8");
413
+ const apiDefinition = yaml.parse(yamlContent);
414
+ const editorClient = clark.context.peer;
415
+ if (!editorClient)
416
+ throw new Error("No editor client available");
417
+ const executor = new ApiExecutor(editorClient, services.integrationStore);
418
+ const executionResult = await executor.execute({ type: "inline", definition: apiDefinition }, pipelineInputs, { includeStepOutputs: false });
419
+ return { success: executionResult.success, executionResult };
420
+ },
421
+ runV3Sdk: async (integrationIds, sdkInputs) => {
422
+ const editorClient = clark.context.peer;
423
+ if (!editorClient)
424
+ throw new Error("No editor client available");
425
+ const sdkResult = await editorClient.call.aiExecuteSdkApi({
426
+ apiName,
427
+ input: sdkInputs,
428
+ entryPoint: `server/apis/${apiName}/api.ts`,
429
+ integrationIds,
430
+ });
431
+ // SdkApiExecutionResult uses `output` (singular) while
432
+ // ApiExecutionResult uses `outputs` (plural) — normalize here.
433
+ // Also forward the SDK error so divergence diagnostics can show the
434
+ // real reason instead of a bare success: false.
435
+ const errors = sdkResult.error
436
+ ? [
437
+ {
438
+ type: "execution",
439
+ message: sdkResult.error.message,
440
+ },
441
+ ]
442
+ : undefined;
443
+ return {
444
+ success: sdkResult.success,
445
+ outputs: sdkResult.output,
446
+ errors,
447
+ };
448
+ },
449
+ });
450
+ // Update checklist with verification metadata. Each branch explicitly
451
+ // clears fields from the previous run that no longer apply (null = clear).
452
+ if (outcome.kind === "passed") {
453
+ await updateChecklistItem({
454
+ appRootDirPath: appRootDir,
455
+ itemId: `api_${apiName}`,
456
+ verificationOutcome: "auto_passed",
457
+ verificationSkippedReason: null,
458
+ lastVerificationAt: new Date().toISOString(),
459
+ });
460
+ }
461
+ else if (outcome.kind === "skipped_mutation") {
462
+ await updateChecklistItem({
463
+ appRootDirPath: appRootDir,
464
+ itemId: `api_${apiName}`,
465
+ verificationSkippedReason: outcome.skipPolicy === "ask" ? "mutation_ask" : "mutation",
466
+ verificationOutcome: null,
467
+ lastVerificationAt: new Date().toISOString(),
468
+ });
469
+ }
470
+ else if (outcome.kind === "v2_unrunnable") {
471
+ await updateChecklistItem({
472
+ appRootDirPath: appRootDir,
473
+ itemId: `api_${apiName}`,
474
+ verificationSkippedReason: "v2_unrunnable",
475
+ verificationOutcome: null,
476
+ lastVerificationAt: new Date().toISOString(),
477
+ });
478
+ }
479
+ else if (outcome.kind === "both_failed") {
480
+ // Both v2 and v3 errored — most often because the API reads bindings
481
+ // we didn't supply. Per the migration skill, the agent should
482
+ // synthesize realistic inputs and retry rather than treat this as a
483
+ // terminal outcome, so we deliberately do NOT mark `status: completed`
484
+ // here (a previous version did, which contradicted the skill and left
485
+ // the item stuck `completed` even after the agent kept iterating).
486
+ // We still record `verificationSkippedReason: "both_failed"` so the
487
+ // UI can surface the transient state; subsequent diverged/passed
488
+ // attempts clear it via `verificationSkippedReason: null`.
489
+ await updateChecklistItem({
490
+ appRootDirPath: appRootDir,
491
+ itemId: `api_${apiName}`,
492
+ verificationSkippedReason: "both_failed",
493
+ verificationOutcome: null,
494
+ lastVerificationAt: new Date().toISOString(),
495
+ });
496
+ }
497
+ else if (outcome.kind === "diverged") {
498
+ await updateChecklistItem({
499
+ appRootDirPath: appRootDir,
500
+ itemId: `api_${apiName}`,
501
+ verificationOutcome: null,
502
+ verificationSkippedReason: null,
503
+ lastVerificationAt: new Date().toISOString(),
504
+ lastVerificationDiffPath: diffRelativePath(apiName),
505
+ });
506
+ }
507
+ else if (outcome.kind === "exhausted") {
508
+ // Hard cap reached. Mark the item failed so classifyItem routes it to
509
+ // the manual-verification lane and the user knows automatic
510
+ // reconciliation gave up. The agent must NOT keep calling
511
+ // runMigrationVerification for this API in this migration run; the
512
+ // tool will return `exhausted` again until the migration is reset.
513
+ await updateChecklistItem({
514
+ appRootDirPath: appRootDir,
515
+ itemId: `api_${apiName}`,
516
+ status: "failed",
517
+ failureReason: `verification_exhausted: hit MAX_VERIFICATION_ATTEMPTS (${MAX_VERIFICATION_ATTEMPTS}) without converging. See scratch/migration-verification/${apiName}.history.json for the per-attempt log.`,
518
+ verificationOutcome: null,
519
+ verificationSkippedReason: null,
520
+ lastVerificationAt: new Date().toISOString(),
521
+ ...(outcome.lastDivergence
522
+ ? { lastVerificationDiffPath: diffRelativePath(apiName) }
523
+ : {}),
524
+ });
525
+ }
526
+ else {
527
+ // outcome.kind === "error": clear stale fields so classifyItem doesn't
528
+ // misclassify this item as verified or skipped.
529
+ await updateChecklistItem({
530
+ appRootDirPath: appRootDir,
531
+ itemId: `api_${apiName}`,
532
+ verificationOutcome: null,
533
+ verificationSkippedReason: null,
534
+ lastVerificationAt: new Date().toISOString(),
535
+ });
536
+ }
537
+ if (outcome.kind === "diverged") {
538
+ return {
539
+ kind: "diverged",
540
+ summaryForAgent: outcome.diff.summaryForAgent,
541
+ diffPath: diffRelativePath(apiName),
542
+ attempt: outcome.attempt,
543
+ maxAttempts: MAX_VERIFICATION_ATTEMPTS,
544
+ progress: outcome.progress,
545
+ guidance: progressGuidance(outcome.progress, outcome.attempt),
546
+ };
547
+ }
548
+ if (outcome.kind === "exhausted") {
549
+ // Deliberately do NOT include the persisted diff payload (which holds
550
+ // raw response values) in the agent-facing tool result — those values
551
+ // belong to the user's data plane and stay scoped to local scratch
552
+ // and the UI render path. The checklist item already carries
553
+ // `lastVerificationDiffPath` for the UI to read directly from disk.
554
+ return {
555
+ kind: "exhausted",
556
+ attempts: outcome.attempts,
557
+ summary: `\`${apiName}\` hit the hard cap of ${MAX_VERIFICATION_ATTEMPTS} verification attempts without converging. ` +
558
+ `The checklist item has been marked failed; do NOT call runMigrationVerification again for this API in this migration run. ` +
559
+ `Continue to the next API.`,
560
+ };
561
+ }
562
+ if (outcome.kind === "skipped_mutation") {
563
+ return {
564
+ kind: "skipped_mutation",
565
+ mutationDetails: outcome.mutationDetails,
566
+ summary: `\`${apiName}\` makes writes — skipped automatic verification (policy: ${outcome.skipPolicy}). Mark as manually verified once you have confirmed the API behaves correctly.`,
567
+ };
568
+ }
569
+ if (outcome.kind === "passed") {
570
+ return {
571
+ kind: "passed",
572
+ summary: `\`${apiName}\` verified — v2 and v3 outputs matched exactly.`,
573
+ };
574
+ }
575
+ if (outcome.kind === "v2_unrunnable") {
576
+ return {
577
+ kind: "v2_unrunnable",
578
+ summary: `The original pre-migration version of \`${apiName}\` couldn't be run for comparison (${outcome.reason}), ` +
579
+ `so automatic verification was skipped. The API has been marked complete — ` +
580
+ `test it in the app to confirm it works as expected.`,
581
+ };
582
+ }
583
+ if (outcome.kind === "both_failed") {
584
+ // Scrub raw upstream error strings before surfacing them to the
585
+ // model — same threat model as the diverged branch's
586
+ // `redactForAgent(failureMessage)`. The persisted server log already
587
+ // omits the raw message; the agent-facing channel must too.
588
+ return {
589
+ kind: "both_failed",
590
+ summary: `Both the original and migrated versions of \`${apiName}\` returned errors ` +
591
+ `(typically because the API reads bindings — Input1.value, Table1.selectedRow, ` +
592
+ `workflow params, etc. — that the verification call did not supply). The item ` +
593
+ `is NOT marked complete; per the migration skill, synthesize realistic mock ` +
594
+ `inputs that satisfy the bindings the API references and call ` +
595
+ `runMigrationVerification again. Only treat this as terminal if the failure is ` +
596
+ `clearly environmental (auth/credentials/integration unavailable). ` +
597
+ `Original error: ${redactForAgent(outcome.v2Error)}. ` +
598
+ `Migrated error: ${redactForAgent(outcome.v3Error)}.`,
599
+ };
600
+ }
601
+ // outcome.kind === "error" — the only remaining case
602
+ return {
603
+ kind: "error",
604
+ reason: outcome.reason,
605
+ };
606
+ },
607
+ }));
608
+ /**
609
+ * Soft UX timeout for the verification pipeline.
610
+ *
611
+ * IMPORTANT: this is a *reporting* timeout, not an execution boundary. The
612
+ * underlying work — `aiExecuteV2Api` and `aiExecuteSdkApi` editor-client RPCs —
613
+ * has no cancellation channel (the socket `MethodSchema` is a plain
614
+ * `(params) => Promise<Response>`; there is no AbortSignal plumbed through
615
+ * the RPC, the browser-side handler, or the SDK runtime). When this rejects,
616
+ * the in-flight execution keeps running until the server completes or the
617
+ * socket disconnects.
618
+ *
619
+ * What this does protect: the verification UI doesn't stall indefinitely on a
620
+ * slow API; the agent gets a deterministic outcome to act on.
621
+ *
622
+ * What this does NOT protect: write-capable side effects already in flight.
623
+ * Mutation safety is provided by the `integrationWritePolicy` check that runs
624
+ * BEFORE this timeout race ever fires — `runMigrationVerification` inspects
625
+ * BOTH the v3 source (via `extractMutations`) and the v2 backup YAML (via
626
+ * `analyzeV2BackupMutations`); if either side has any write or unclassified
627
+ * step, the pipelines are skipped entirely unless the user has explicitly
628
+ * marked every involved integration as "allow". By the time this `withTimeout`
629
+ * runs, any side effects in flight have already been opted into. Verification
630
+ * is not a sandbox; it relies on the policy gate, not on cancellation.
631
+ *
632
+ * Adding real cancellation would require extending `MethodSchema` with abort
633
+ * signaling and propagating it through the editorClient RPC layer. Tracked
634
+ * separately; out of scope for this verification helper.
635
+ */
636
+ function withTimeout(promise, ms, label) {
637
+ let timer;
638
+ return Promise.race([
639
+ promise.finally(() => clearTimeout(timer)),
640
+ new Promise((_, reject) => {
641
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms / 1000}s`)), ms);
642
+ }),
643
+ ]);
644
+ }
645
+ /**
646
+ * Inspect a v2 backup YAML for mutating steps and the integrations they touch.
647
+ *
648
+ * Returns empty results when the backup is missing or unparseable — the
649
+ * verification flow handles that downstream as `v2_unrunnable`. The goal here
650
+ * is only to feed the policy gate so v2-only writes don't bypass it.
651
+ *
652
+ * Walks the whole tree via `forEachYamlStep` so writes nested inside
653
+ * control-flow blocks (`tryCatch`, `conditional`, `foreach`, `parallel`) are
654
+ * caught — a flat scan over `parsed.blocks` would let nested v2 writes bypass
655
+ * the policy gate, especially now that the language-plugin filter has removed
656
+ * the accidental safety net where javascript's missing policy used to block
657
+ * all verification.
658
+ *
659
+ * Treats `unknown` step classifications (dynamic SQL bodies, plugins we don't
660
+ * recognize) as mutations: better to ask the user than silently run a write.
661
+ */
662
+ export async function analyzeV2BackupMutations(v2BackupPath) {
663
+ let raw;
664
+ try {
665
+ raw = await readFile(v2BackupPath, "utf-8");
666
+ }
667
+ catch {
668
+ return { mutationDetails: [], integrationIds: [] };
669
+ }
670
+ let parsed;
671
+ try {
672
+ parsed = yaml.parse(raw);
673
+ }
674
+ catch {
675
+ return { mutationDetails: [], integrationIds: [] };
676
+ }
677
+ const integrationIds = new Set();
678
+ const mutationDetails = [];
679
+ forEachYamlStep(parsed, (step) => {
680
+ const integrationId = step["integration"];
681
+ if (typeof integrationId === "string" && integrationId.length > 0) {
682
+ integrationIds.add(integrationId);
683
+ }
684
+ const classification = classifyYamlStepMutation(step);
685
+ if (classification === "write" || classification === "unknown") {
686
+ const stepName = typeof step["name"] === "string" ? step["name"] : "<unnamed>";
687
+ mutationDetails.push(`v2 step "${stepName}" classified as ${classification}`);
688
+ }
689
+ });
690
+ return {
691
+ mutationDetails,
692
+ integrationIds: Array.from(integrationIds),
693
+ };
694
+ }
695
+ async function readIntegrationWritePolicy(appRootDir) {
696
+ try {
697
+ const raw = await readFile(join(appRootDir, SCRATCH_DIR, "migration-state.json"), "utf-8");
698
+ const parsed = JSON.parse(raw);
699
+ const pol = parsed.integrationWritePolicy;
700
+ if (pol !== null && typeof pol === "object" && !Array.isArray(pol)) {
701
+ return pol;
702
+ }
703
+ }
704
+ catch {
705
+ // missing or unreadable — default to ask
706
+ }
707
+ return {};
708
+ }
709
+ function mostRestrictivePolicy(policies) {
710
+ if (policies.includes("deny"))
711
+ return "deny";
712
+ if (policies.includes("ask"))
713
+ return "ask";
714
+ return "allow";
715
+ }
716
+ async function getEffectiveMigrationWritePolicy({ appRootDir, apiName, integrationIds, }) {
717
+ const policy = await readIntegrationWritePolicy(appRootDir);
718
+ const v2BackupPath = join(appRootDir, SCRATCH_DIR, V2_BACKUP_DIR, "apis", apiName, "api.yaml");
719
+ // Use IDs from source — not Object.keys(policy) — so a UUID present in
720
+ // source but absent from the policy map gets a real "ask" vote rather
721
+ // than being ignored. Otherwise an "allow" on one integration would be
722
+ // taken as blanket allow for everything the API touches. Union v2 and v3
723
+ // integrations so the gate sees every integration the verification will
724
+ // touch, not just the ones still referenced by v3.
725
+ //
726
+ // Language-runtime "integrations" (`javascript`, `python`) are inline
727
+ // user code, not data integrations; the modal hides them and they have
728
+ // no real write-policy decision attached. Excluding them here keeps a
729
+ // mixed API (e.g. postgres + javascript) from being blocked just because
730
+ // javascript has no policy entry.
731
+ const v2Analysis = await analyzeV2BackupMutations(v2BackupPath);
732
+ const sourceIds = Array.from(new Set([...(integrationIds ?? []), ...v2Analysis.integrationIds])).filter((id) => !LANGUAGE_PLUGIN_IDS.has(id));
733
+ const effective = sourceIds.length > 0
734
+ ? mostRestrictivePolicy(sourceIds.map((id) => policy[id] ?? "ask"))
735
+ : "ask";
736
+ return { effective, sourceIds };
737
+ }
738
+ export const checkRunMigrationVerificationPermissions = async (services, input) => {
739
+ if (!input || !input.apiName)
740
+ return PermissionLevel.PROMPT;
741
+ const apiName = input.apiName;
742
+ let source;
743
+ try {
744
+ source = await services.appShell.readFile(`server/apis/${apiName}/api.ts`);
745
+ }
746
+ catch {
747
+ return PermissionLevel.PROMPT;
748
+ }
749
+ let mutations;
750
+ try {
751
+ mutations = extractMutations(source);
752
+ }
753
+ catch {
754
+ return PermissionLevel.PROMPT;
755
+ }
756
+ if (mutations.length === 0)
757
+ return PermissionLevel.ALLOW;
758
+ let integrationIds;
759
+ try {
760
+ const ids = extractIntegrationIdsFromSource(source);
761
+ if (ids.length > 0)
762
+ integrationIds = ids;
763
+ }
764
+ catch {
765
+ integrationIds = undefined;
766
+ }
767
+ const { effective } = await getEffectiveMigrationWritePolicy({
768
+ appRootDir: services.appRootDirPath,
769
+ apiName,
770
+ integrationIds,
771
+ });
772
+ if (effective === "allow")
773
+ return PermissionLevel.ALLOW;
774
+ if (effective === "deny")
775
+ return PermissionLevel.BLOCK;
776
+ return PermissionLevel.PROMPT;
777
+ };
778
+ /**
779
+ * Extract the most specific failure message from a Pick<ApiExecutionResult>.
780
+ * Tries errors[0].message first, then systemError, then a placeholder.
781
+ */
782
+ function extractFailureMessage(result, side) {
783
+ return (result.errors?.[0]?.message ??
784
+ result.systemError ??
785
+ `${side} execution returned success: false with no error details`);
786
+ }
787
+ const REDACTED_AGENT_FAILURE_MAX_LEN = 512;
788
+ /**
789
+ * Scrub raw upstream-error strings before they enter LLM context. Upstream
790
+ * integrations can echo back bearer tokens, JWTs, or `api_key=...` query
791
+ * strings in error messages — the existing `sanitizeLogMessage` helper in
792
+ * `util/log-sanitizer.ts` is the canonical scrubber for those patterns and
793
+ * is what the rest of the vite plugin already uses for log lines. We
794
+ * additionally cap the length so a single misbehaving upstream can't crowd
795
+ * out the rest of the LLM context window.
796
+ *
797
+ * Used on the agent-facing channels (`summaryForAgent.failureMessage` for
798
+ * diverged outcomes, the `summary` string for `both_failed`) — the
799
+ * comment at the `getLogger().warn` call site for "Migration verification
800
+ * diverged" already keeps the server log clean of these strings; this
801
+ * applies the same treatment to the durable LLM channel.
802
+ */
803
+ function redactForAgent(msg, maxLen = REDACTED_AGENT_FAILURE_MAX_LEN) {
804
+ if (msg === undefined || msg === null)
805
+ return "";
806
+ const sanitized = sanitizeLogMessage(String(msg));
807
+ if (sanitized.length <= maxLen)
808
+ return sanitized;
809
+ return sanitized.slice(0, maxLen) + "… [truncated]";
810
+ }
811
+ /**
812
+ * Reject any `apiName` that could escape `scratch/migration-verification/`.
813
+ * Checklist items are persisted to disk and parsed back in without a strict
814
+ * shape guard, so a poisoned `apiName` containing path separators or `..`
815
+ * segments would let `historyPath`/`diffPath` (and therefore the `rm` calls
816
+ * in `clearVerificationHistory`) target arbitrary files.
817
+ *
818
+ * v2 → v3 migration API names are derived from server YAML/SDK identifiers
819
+ * which are restricted to `[A-Za-z0-9_-]`, so this check is conservative
820
+ * for legitimate inputs.
821
+ */
822
+ function assertSafeApiName(apiName) {
823
+ if (!/^[A-Za-z0-9_-]+$/.test(apiName)) {
824
+ throw new Error(`Refusing to operate on verification artifacts for unsafe apiName: ${JSON.stringify(apiName)}`);
825
+ }
826
+ }
827
+ function historyPath(appRootDir, apiName) {
828
+ assertSafeApiName(apiName);
829
+ return join(appRootDir, SCRATCH_DIR, VERIFICATION_DIR, `${apiName}.history.json`);
830
+ }
831
+ function diffPath(appRootDir, apiName) {
832
+ assertSafeApiName(apiName);
833
+ return join(appRootDir, SCRATCH_DIR, VERIFICATION_DIR, `${apiName}.diff.json`);
834
+ }
835
+ /**
836
+ * Delete the per-API verification history (and stale diff snapshot) so the
837
+ * next `runMigrationVerification` call starts from a clean slate. Called by
838
+ * the "Fix with Clark" retry endpoints — without this, an item that hit the
839
+ * `MAX_VERIFICATION_ATTEMPTS` cap on the first run would immediately return
840
+ * `kind: "exhausted"` on retry, making the retry a no-op.
841
+ *
842
+ * Routed through the same per-API `verificationHistoryQueues` queue used by
843
+ * `runMigrationVerification`. Without this, a clear could race with an
844
+ * in-flight verification: the verification reads history, runs the pipeline,
845
+ * the clear `rm()`s the file, then the verification's append re-creates the
846
+ * file with a stale attempt count — defeating the clear and immediately
847
+ * returning `exhausted` on the user's next retry.
848
+ *
849
+ * Best-effort: missing files are not an error. The safety check on
850
+ * `apiName` (via `historyPath`/`diffPath`) runs *before* enqueueing so a
851
+ * poisoned name throws synchronously, never holding the queue.
852
+ */
853
+ export async function clearVerificationHistory(appRootDir, apiName) {
854
+ const histPath = historyPath(appRootDir, apiName);
855
+ const dPath = diffPath(appRootDir, apiName);
856
+ await getVerificationHistoryQueue(appRootDir, apiName).enqueue(async () => {
857
+ await Promise.all([
858
+ rm(histPath, { force: true }),
859
+ rm(dPath, { force: true }),
860
+ ]);
861
+ });
862
+ }
863
+ /**
864
+ * Static guidance string keyed off the deterministic `progress` signal — the
865
+ * agent doesn't have to decide whether to keep iterating, the tool tells it.
866
+ */
867
+ function progressGuidance(progress, attempt) {
868
+ if (progress === "first_attempt") {
869
+ return `First diverged attempt for this API. Use the summaryForAgent entries to revise server/apis/<apiName>/api.ts and call runMigrationVerification again.`;
870
+ }
871
+ if (progress === "converging") {
872
+ return `Converging — the set of diverging (path, kind) pairs changed since the last attempt (#${attempt - 1}). Your previous edit had structural effect. Keep iterating: pick the next summaryForAgent entry, fix it, re-run.`;
873
+ }
874
+ // stalled
875
+ return `STALLED — the set of diverging (path, kind) pairs is identical to attempt #${attempt - 1}. Your last edit had no structural effect on the divergence. Either you edited the wrong block, the edit was a no-op, or this divergence is structurally irreconcilable (v2 uses a YAML construct with no v3 equivalent; v2 reads runtime state v3 can't observe). On your next attempt: try a different fix, or — if you are confident the divergence cannot be reconciled — mark the item failed with failureReason starting with "verification_diverged_irreconcilable:" and continue. Do NOT submit the identical edit again.`;
876
+ }
877
+ function divergenceShapeHash(entries) {
878
+ // Sort tuples so the hash is order-independent — the comparator's emit
879
+ // order is structural-walk based, but small refactors to compareApiResults
880
+ // shouldn't accidentally invalidate the convergence signal.
881
+ const tuples = entries
882
+ .map((e) => `${e.path}::${e.kind}`)
883
+ .sort()
884
+ .join("\n");
885
+ return crypto.createHash("sha256").update(tuples).digest("hex").slice(0, 16);
886
+ }
887
+ function findLastDivergedShapeHash(history) {
888
+ for (let i = history.attempts.length - 1; i >= 0; i--) {
889
+ const entry = history.attempts[i];
890
+ if (entry && entry.kind === "diverged" && entry.divergenceShapeHash) {
891
+ return entry.divergenceShapeHash;
892
+ }
893
+ }
894
+ return undefined;
895
+ }
896
+ async function readHistory(appRootDir, apiName) {
897
+ let raw;
898
+ try {
899
+ raw = await readFile(historyPath(appRootDir, apiName), "utf-8");
900
+ }
901
+ catch {
902
+ // Missing — fresh API, no history yet. Distinct from "present but
903
+ // unparseable", which we want to surface as a warning so an operator
904
+ // chasing a stuck migration can tell amnesia apart from no-prior-run.
905
+ return { attempts: [] };
906
+ }
907
+ try {
908
+ const parsed = JSON.parse(raw);
909
+ if (parsed &&
910
+ typeof parsed === "object" &&
911
+ "attempts" in parsed &&
912
+ Array.isArray(parsed.attempts)) {
913
+ return parsed;
914
+ }
915
+ getLogger().warn(`Verification history for ${apiName} parsed but had unexpected shape; treating as empty`, { apiName });
916
+ }
917
+ catch (error) {
918
+ getLogger().warn(`Verification history for ${apiName} was unreadable JSON (likely a partial-write crash); treating as empty`, {
919
+ apiName,
920
+ error: error instanceof Error ? error.message : String(error),
921
+ });
922
+ }
923
+ return { attempts: [] };
924
+ }
925
+ /**
926
+ * Per-(appRootDir, apiName) serialization of the read-modify-write on the
927
+ * `<apiName>.history.json` file. Without this, two parallel
928
+ * `runMigrationVerification` calls for the same API can both observe
929
+ * `attempts.length = MAX-1`, both pass the cap check, and both write — the
930
+ * later clobbers the earlier and the cap is silently bypassed. We re-use the
931
+ * same `OperationQueue` primitive `migration-checklist.ts` uses for the
932
+ * shared checklist mutations.
933
+ */
934
+ const verificationHistoryQueues = new Map();
935
+ function getVerificationHistoryQueue(appRootDir, apiName) {
936
+ const key = `${appRootDir}\0${apiName}`;
937
+ let queue = verificationHistoryQueues.get(key);
938
+ if (!queue) {
939
+ queue = new OperationQueue();
940
+ verificationHistoryQueues.set(key, queue);
941
+ }
942
+ return queue;
943
+ }
944
+ /**
945
+ * Append a history entry without enqueueing — caller MUST already hold the
946
+ * per-API verification queue. Used from `runMigrationVerification`'s queued
947
+ * body, where re-entering the same queue would deadlock.
948
+ */
949
+ async function writeHistoryEntry(appRootDir, apiName, entry) {
950
+ try {
951
+ const history = await readHistory(appRootDir, apiName);
952
+ history.attempts.push(entry);
953
+ const dir = join(appRootDir, SCRATCH_DIR, VERIFICATION_DIR);
954
+ await mkdir(dir, { recursive: true });
955
+ // Atomic write via tmp + rename: a crash mid-write would otherwise
956
+ // leave the file truncated, and `readHistory`'s catch would silently
957
+ // amnesia-reset the cap to zero. POSIX `rename` is atomic on the same
958
+ // filesystem, so a reader either sees the full prior file or the full
959
+ // new file — never a partial write.
960
+ const finalPath = historyPath(appRootDir, apiName);
961
+ const tmpPath = `${finalPath}.tmp`;
962
+ await writeFile(tmpPath, JSON.stringify(history, null, 2), "utf-8");
963
+ await rename(tmpPath, finalPath);
964
+ }
965
+ catch (error) {
966
+ // History persistence is best-effort: a write failure here should not
967
+ // surface as a verification error to the agent (the verification
968
+ // result itself is still valid). Worst case: the next attempt sees
969
+ // stale history and computes `progress: "first_attempt"`, which is a
970
+ // softer failure mode than throwing here.
971
+ getLogger().warn(`Failed to persist verification history for ${apiName}`, {
972
+ apiName,
973
+ error: error instanceof Error ? error.message : String(error),
974
+ });
975
+ }
976
+ }
977
+ /**
978
+ * Read the on-disk diff JSON written by the most recent diverged run, if any.
979
+ * Used by the `exhausted` outcome to surface "what did we get stuck on" to
980
+ * the agent without re-running the pipeline.
981
+ */
982
+ async function readLastDivergedDiff(appRootDir, apiName) {
983
+ try {
984
+ const raw = await readFile(diffPath(appRootDir, apiName), "utf-8");
985
+ const parsed = JSON.parse(raw);
986
+ if (parsed &&
987
+ typeof parsed === "object" &&
988
+ "valuesForUi" in parsed &&
989
+ Array.isArray(parsed.valuesForUi)) {
990
+ // We only persist `valuesForUi` (see writeFile call in
991
+ // computeVerificationOutcome), not summaryForAgent — synthesize a
992
+ // minimal ApiComparisonDiff so the exhausted outcome carries
993
+ // something the UI/agent can render.
994
+ return {
995
+ empty: false,
996
+ valuesForUi: parsed.valuesForUi,
997
+ summaryForAgent: [],
998
+ };
999
+ }
1000
+ }
1001
+ catch {
1002
+ // No prior diff or unreadable — fine, return undefined.
1003
+ }
1004
+ return undefined;
1005
+ }
1006
+ //# sourceMappingURL=migration-verification.js.map