@xdelivered/emberflow 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/bin/commands.test.ts +185 -0
  4. package/bin/commands.ts +306 -0
  5. package/bin/emberflow.mjs +38 -0
  6. package/bin/init.test.ts +165 -0
  7. package/bin/init.ts +297 -0
  8. package/bin/runtime.mjs +105 -0
  9. package/bin/runtime.test.ts +167 -0
  10. package/bin/skillTargets.test.ts +108 -0
  11. package/bin/skillTargets.ts +36 -0
  12. package/bin/skillTemplates.test.ts +37 -0
  13. package/bin/tsconfig.json +23 -0
  14. package/dist/bin/commands.d.ts +24 -0
  15. package/dist/bin/commands.js +252 -0
  16. package/dist/bin/init.d.ts +17 -0
  17. package/dist/bin/init.js +241 -0
  18. package/dist/bin/runtime.d.mts +39 -0
  19. package/dist/bin/runtime.mjs +96 -0
  20. package/dist/bin/skillTargets.d.ts +13 -0
  21. package/dist/bin/skillTargets.js +22 -0
  22. package/dist/server/agents/claudeAdapter.d.ts +8 -0
  23. package/dist/server/agents/claudeAdapter.js +190 -0
  24. package/dist/server/agents/claudeParse.d.ts +2 -0
  25. package/dist/server/agents/claudeParse.js +162 -0
  26. package/dist/server/agents/codexAdapter.d.ts +13 -0
  27. package/dist/server/agents/codexAdapter.js +174 -0
  28. package/dist/server/agents/codexParse.d.ts +2 -0
  29. package/dist/server/agents/codexParse.js +74 -0
  30. package/dist/server/agents/detect.d.ts +23 -0
  31. package/dist/server/agents/detect.js +35 -0
  32. package/dist/server/agents/gitScope.d.ts +24 -0
  33. package/dist/server/agents/gitScope.js +95 -0
  34. package/dist/server/agents/modelRejectionHint.d.ts +8 -0
  35. package/dist/server/agents/modelRejectionHint.js +12 -0
  36. package/dist/server/agents/prompt.d.ts +79 -0
  37. package/dist/server/agents/prompt.js +364 -0
  38. package/dist/server/agents/runManager.d.ts +90 -0
  39. package/dist/server/agents/runManager.js +259 -0
  40. package/dist/server/agents/types.d.ts +15 -0
  41. package/dist/server/agents/types.js +1 -0
  42. package/dist/server/apiStore.d.ts +60 -0
  43. package/dist/server/apiStore.js +249 -0
  44. package/dist/server/auth/enforce.d.ts +1 -0
  45. package/dist/server/auth/enforce.js +6 -0
  46. package/dist/server/auth/types.d.ts +1 -0
  47. package/dist/server/auth/types.js +1 -0
  48. package/dist/server/auth/verifiers.d.ts +1 -0
  49. package/dist/server/auth/verifiers.js +4 -0
  50. package/dist/server/authAttach.d.ts +19 -0
  51. package/dist/server/authAttach.js +79 -0
  52. package/dist/server/cli.d.ts +7 -0
  53. package/dist/server/cli.js +804 -0
  54. package/dist/server/client.d.ts +130 -0
  55. package/dist/server/client.js +247 -0
  56. package/dist/server/environments.d.ts +103 -0
  57. package/dist/server/environments.js +486 -0
  58. package/dist/server/flowStore.d.ts +37 -0
  59. package/dist/server/flowStore.js +117 -0
  60. package/dist/server/httpOperations.d.ts +32 -0
  61. package/dist/server/httpOperations.js +62 -0
  62. package/dist/server/index.d.ts +1 -0
  63. package/dist/server/index.js +1022 -0
  64. package/dist/server/infrastructure.d.ts +51 -0
  65. package/dist/server/infrastructure.js +116 -0
  66. package/dist/server/login.d.ts +21 -0
  67. package/dist/server/login.js +44 -0
  68. package/dist/server/mcp.d.ts +1 -0
  69. package/dist/server/mcp.js +182 -0
  70. package/dist/server/migrateFlows.d.ts +8 -0
  71. package/dist/server/migrateFlows.js +25 -0
  72. package/dist/server/mockHandler.d.ts +15 -0
  73. package/dist/server/mockHandler.js +35 -0
  74. package/dist/server/nodesPayload.d.ts +14 -0
  75. package/dist/server/nodesPayload.js +14 -0
  76. package/dist/server/normalizeFlow.d.ts +21 -0
  77. package/dist/server/normalizeFlow.js +48 -0
  78. package/dist/server/openBrowser.d.ts +8 -0
  79. package/dist/server/openBrowser.js +16 -0
  80. package/dist/server/operationResult.d.ts +12 -0
  81. package/dist/server/operationResult.js +26 -0
  82. package/dist/server/pathGuard.d.ts +1 -0
  83. package/dist/server/pathGuard.js +16 -0
  84. package/dist/server/pathSafety.d.ts +20 -0
  85. package/dist/server/pathSafety.js +35 -0
  86. package/dist/server/projectConfig.d.ts +47 -0
  87. package/dist/server/projectConfig.js +61 -0
  88. package/dist/server/projectMode.d.ts +26 -0
  89. package/dist/server/projectMode.js +44 -0
  90. package/dist/server/redact.d.ts +6 -0
  91. package/dist/server/redact.js +37 -0
  92. package/dist/server/runRegistry.d.ts +84 -0
  93. package/dist/server/runRegistry.js +200 -0
  94. package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
  95. package/dist/server/runtime/RuntimeAdapter.js +1 -0
  96. package/dist/server/runtime/expressAdapter.d.ts +19 -0
  97. package/dist/server/runtime/expressAdapter.js +56 -0
  98. package/dist/server/scenarioTest.d.ts +14 -0
  99. package/dist/server/scenarioTest.js +66 -0
  100. package/dist/server/subflowRunner.d.ts +32 -0
  101. package/dist/server/subflowRunner.js +72 -0
  102. package/dist/server/testRunner.d.ts +70 -0
  103. package/dist/server/testRunner.js +151 -0
  104. package/dist/src/config.d.ts +3 -0
  105. package/dist/src/config.js +2 -0
  106. package/dist/src/engine/authVerify.d.ts +33 -0
  107. package/dist/src/engine/authVerify.js +63 -0
  108. package/dist/src/engine/diagnostics.d.ts +62 -0
  109. package/dist/src/engine/diagnostics.js +148 -0
  110. package/dist/src/engine/executor.d.ts +151 -0
  111. package/dist/src/engine/executor.js +684 -0
  112. package/dist/src/engine/httpError.d.ts +7 -0
  113. package/dist/src/engine/httpError.js +14 -0
  114. package/dist/src/engine/index.d.ts +11 -0
  115. package/dist/src/engine/index.js +11 -0
  116. package/dist/src/engine/output.d.ts +11 -0
  117. package/dist/src/engine/output.js +20 -0
  118. package/dist/src/engine/publish.d.ts +30 -0
  119. package/dist/src/engine/publish.js +95 -0
  120. package/dist/src/engine/registry.d.ts +34 -0
  121. package/dist/src/engine/registry.js +67 -0
  122. package/dist/src/engine/scenarios.d.ts +12 -0
  123. package/dist/src/engine/scenarios.js +19 -0
  124. package/dist/src/engine/schemaCheck.d.ts +4 -0
  125. package/dist/src/engine/schemaCheck.js +34 -0
  126. package/dist/src/engine/trace.d.ts +12 -0
  127. package/dist/src/engine/trace.js +12 -0
  128. package/dist/src/engine/types.d.ts +308 -0
  129. package/dist/src/engine/types.js +1 -0
  130. package/dist/src/engine/validation.d.ts +26 -0
  131. package/dist/src/engine/validation.js +318 -0
  132. package/dist/src/flows/anomaly-flows.d.ts +23 -0
  133. package/dist/src/flows/anomaly-flows.js +151 -0
  134. package/dist/src/flows/city-sweep-flow.d.ts +10 -0
  135. package/dist/src/flows/city-sweep-flow.js +127 -0
  136. package/dist/src/flows/login-flow.d.ts +13 -0
  137. package/dist/src/flows/login-flow.js +145 -0
  138. package/dist/src/flows/pradar-flows.d.ts +5 -0
  139. package/dist/src/flows/pradar-flows.js +375 -0
  140. package/dist/src/flows/weather-flow.d.ts +12 -0
  141. package/dist/src/flows/weather-flow.js +150 -0
  142. package/dist/src/lib/pathParams.d.ts +3 -0
  143. package/dist/src/lib/pathParams.js +6 -0
  144. package/dist/src/nodes/anomaly.d.ts +2 -0
  145. package/dist/src/nodes/anomaly.js +556 -0
  146. package/dist/src/nodes/flow-control.d.ts +7 -0
  147. package/dist/src/nodes/flow-control.js +270 -0
  148. package/dist/src/nodes/index.d.ts +3 -0
  149. package/dist/src/nodes/index.js +20 -0
  150. package/dist/src/nodes/login.d.ts +7 -0
  151. package/dist/src/nodes/login.js +158 -0
  152. package/dist/src/nodes/loops.d.ts +11 -0
  153. package/dist/src/nodes/loops.js +60 -0
  154. package/dist/src/nodes/pradar/index.d.ts +7 -0
  155. package/dist/src/nodes/pradar/index.js +9 -0
  156. package/dist/src/nodes/pradar/logic.d.ts +189 -0
  157. package/dist/src/nodes/pradar/logic.js +370 -0
  158. package/dist/src/nodes/pradar/nodes.d.ts +42 -0
  159. package/dist/src/nodes/pradar/nodes.js +801 -0
  160. package/dist/src/nodes/requireAuth.d.ts +20 -0
  161. package/dist/src/nodes/requireAuth.js +48 -0
  162. package/dist/src/nodes/response.d.ts +8 -0
  163. package/dist/src/nodes/response.js +33 -0
  164. package/dist/src/nodes/weather.d.ts +33 -0
  165. package/dist/src/nodes/weather.js +260 -0
  166. package/package.json +92 -0
  167. package/server/agentRoute.test.ts +157 -0
  168. package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
  169. package/server/agents/__fixtures__/fake-claude.mjs +55 -0
  170. package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
  171. package/server/agents/__fixtures__/fake-codex.mjs +52 -0
  172. package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
  173. package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
  174. package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
  175. package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
  176. package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
  177. package/server/agents/claudeAdapter.test.ts +127 -0
  178. package/server/agents/claudeAdapter.ts +212 -0
  179. package/server/agents/claudeParse.ts +193 -0
  180. package/server/agents/codexAdapter.test.ts +125 -0
  181. package/server/agents/codexAdapter.ts +198 -0
  182. package/server/agents/codexParse.test.ts +58 -0
  183. package/server/agents/codexParse.ts +126 -0
  184. package/server/agents/detect.test.ts +65 -0
  185. package/server/agents/detect.ts +43 -0
  186. package/server/agents/gitScope.test.ts +129 -0
  187. package/server/agents/gitScope.ts +110 -0
  188. package/server/agents/modelRejectionHint.test.ts +30 -0
  189. package/server/agents/modelRejectionHint.ts +14 -0
  190. package/server/agents/prompt.test.ts +613 -0
  191. package/server/agents/prompt.ts +527 -0
  192. package/server/agents/runManager.test.ts +251 -0
  193. package/server/agents/runManager.ts +286 -0
  194. package/server/agents/types.ts +17 -0
  195. package/server/apiMount.test.ts +591 -0
  196. package/server/apiStore.test.ts +324 -0
  197. package/server/apiStore.ts +241 -0
  198. package/server/auth/enforce.test.ts +61 -0
  199. package/server/auth/enforce.ts +6 -0
  200. package/server/auth/types.ts +5 -0
  201. package/server/auth/verifiers.test.ts +37 -0
  202. package/server/auth/verifiers.ts +9 -0
  203. package/server/authAttach.test.ts +175 -0
  204. package/server/authAttach.ts +99 -0
  205. package/server/cli.ts +847 -0
  206. package/server/cliAuth.test.ts +229 -0
  207. package/server/cliCreate.test.ts +131 -0
  208. package/server/cliDoctor.test.ts +326 -0
  209. package/server/cliTest.test.ts +735 -0
  210. package/server/client.ts +314 -0
  211. package/server/demoProject.test.ts +29 -0
  212. package/server/diagnosticsRoute.test.ts +169 -0
  213. package/server/environments.test.ts +620 -0
  214. package/server/environments.ts +603 -0
  215. package/server/environmentsRoute.test.ts +385 -0
  216. package/server/errorWorkflow.test.ts +205 -0
  217. package/server/flowStore.test.ts +84 -0
  218. package/server/flowStore.ts +125 -0
  219. package/server/httpOperations.test.ts +377 -0
  220. package/server/httpOperations.ts +96 -0
  221. package/server/index.ts +1111 -0
  222. package/server/infrastructure.test.ts +154 -0
  223. package/server/infrastructure.ts +161 -0
  224. package/server/infrastructureRoute.test.ts +105 -0
  225. package/server/login.test.ts +153 -0
  226. package/server/login.ts +67 -0
  227. package/server/mcp.ts +278 -0
  228. package/server/migrateFlows.test.ts +55 -0
  229. package/server/migrateFlows.ts +23 -0
  230. package/server/mockHandler.test.ts +138 -0
  231. package/server/mockHandler.ts +52 -0
  232. package/server/mockRun.test.ts +367 -0
  233. package/server/mockServe.test.ts +467 -0
  234. package/server/nodesPayload.test.ts +29 -0
  235. package/server/nodesPayload.ts +26 -0
  236. package/server/normalizeFlow.test.ts +80 -0
  237. package/server/normalizeFlow.ts +59 -0
  238. package/server/openBrowser.test.ts +20 -0
  239. package/server/openBrowser.ts +22 -0
  240. package/server/operationResult.test.ts +67 -0
  241. package/server/operationResult.ts +27 -0
  242. package/server/pathGuard.test.ts +19 -0
  243. package/server/pathGuard.ts +12 -0
  244. package/server/pathSafety.test.ts +38 -0
  245. package/server/pathSafety.ts +33 -0
  246. package/server/projectConfig.test.ts +67 -0
  247. package/server/projectConfig.ts +101 -0
  248. package/server/projectMode.test.ts +106 -0
  249. package/server/projectMode.ts +59 -0
  250. package/server/redact.test.ts +88 -0
  251. package/server/redact.ts +35 -0
  252. package/server/redactEgress.test.ts +281 -0
  253. package/server/runRegistry.ts +270 -0
  254. package/server/runsAuth.test.ts +119 -0
  255. package/server/runtime/RuntimeAdapter.ts +24 -0
  256. package/server/runtime/expressAdapter.test.ts +38 -0
  257. package/server/runtime/expressAdapter.ts +70 -0
  258. package/server/scenarioTest.test.ts +148 -0
  259. package/server/scenarioTest.ts +88 -0
  260. package/server/setupStatus.test.ts +127 -0
  261. package/server/subflowRunner.ts +104 -0
  262. package/server/testRunner.ts +216 -0
  263. package/server/tsconfig.json +20 -0
  264. package/server/workflowTestRoute.test.ts +297 -0
  265. package/src/App.tsx +233 -0
  266. package/src/components/AgentConsole.tsx +461 -0
  267. package/src/components/BranchRulesEditor.tsx +156 -0
  268. package/src/components/CreateModal.tsx +275 -0
  269. package/src/components/Dock.tsx +487 -0
  270. package/src/components/EnvironmentDialog.tsx +569 -0
  271. package/src/components/EnvironmentPicker.tsx +357 -0
  272. package/src/components/EnvironmentsDialog.tsx +144 -0
  273. package/src/components/ExecutionPager.tsx +67 -0
  274. package/src/components/InfraPanel.test.tsx +103 -0
  275. package/src/components/InfraPanel.tsx +171 -0
  276. package/src/components/Inspector.tsx +1182 -0
  277. package/src/components/Json.tsx +50 -0
  278. package/src/components/JsonModal.tsx +52 -0
  279. package/src/components/LogMessage.tsx +61 -0
  280. package/src/components/NodeRunModal.tsx +213 -0
  281. package/src/components/RunLogPanel.tsx +397 -0
  282. package/src/components/RunbookView.tsx +1085 -0
  283. package/src/components/Scenarios.tsx +476 -0
  284. package/src/components/SettingsDialog.tsx +229 -0
  285. package/src/components/Sidebar.tsx +436 -0
  286. package/src/components/StatusBar.tsx +302 -0
  287. package/src/components/Toolbar.tsx +663 -0
  288. package/src/components/WelcomeDialog.test.tsx +107 -0
  289. package/src/components/WelcomeDialog.tsx +412 -0
  290. package/src/components/agentMarkdown.test.tsx +91 -0
  291. package/src/components/agentMarkdown.tsx +158 -0
  292. package/src/components/ui/badge.tsx +31 -0
  293. package/src/components/ui/button.tsx +46 -0
  294. package/src/components/ui/combobox.tsx +114 -0
  295. package/src/components/ui/command.tsx +82 -0
  296. package/src/components/ui/dialog.tsx +73 -0
  297. package/src/components/ui/input.tsx +20 -0
  298. package/src/components/ui/popover.tsx +32 -0
  299. package/src/components/ui/resizable.tsx +44 -0
  300. package/src/components/ui/tabs.tsx +36 -0
  301. package/src/config.ts +4 -0
  302. package/src/engine/authVerify.ts +81 -0
  303. package/src/engine/diagnostics.test.ts +437 -0
  304. package/src/engine/diagnostics.ts +211 -0
  305. package/src/engine/executor.test.ts +1339 -0
  306. package/src/engine/executor.ts +840 -0
  307. package/src/engine/httpError.test.ts +12 -0
  308. package/src/engine/httpError.ts +16 -0
  309. package/src/engine/index.ts +11 -0
  310. package/src/engine/output.ts +21 -0
  311. package/src/engine/publish.test.ts +176 -0
  312. package/src/engine/publish.ts +127 -0
  313. package/src/engine/registry.test.ts +64 -0
  314. package/src/engine/registry.ts +80 -0
  315. package/src/engine/scenarios.test.ts +55 -0
  316. package/src/engine/scenarios.ts +29 -0
  317. package/src/engine/schemaCheck.test.ts +21 -0
  318. package/src/engine/schemaCheck.ts +35 -0
  319. package/src/engine/trace.test.ts +19 -0
  320. package/src/engine/trace.ts +23 -0
  321. package/src/engine/types.test.ts +22 -0
  322. package/src/engine/types.ts +316 -0
  323. package/src/engine/validation.test.ts +314 -0
  324. package/src/engine/validation.ts +354 -0
  325. package/src/flows/anomaly-flows.test.ts +81 -0
  326. package/src/flows/anomaly-flows.ts +202 -0
  327. package/src/flows/city-sweep-flow.ts +131 -0
  328. package/src/flows/flows.test.ts +30 -0
  329. package/src/flows/login-flow.ts +150 -0
  330. package/src/flows/pradar-flows.test.ts +204 -0
  331. package/src/flows/pradar-flows.ts +419 -0
  332. package/src/flows/weather-flow.ts +154 -0
  333. package/src/globals.css +147 -0
  334. package/src/lib/pathParams.test.ts +24 -0
  335. package/src/lib/pathParams.ts +6 -0
  336. package/src/lib/registerLens.test.ts +79 -0
  337. package/src/lib/registerLens.ts +59 -0
  338. package/src/lib/runbookModel.test.ts +61 -0
  339. package/src/lib/runbookModel.ts +290 -0
  340. package/src/lib/runbookProjection.test.ts +417 -0
  341. package/src/lib/runbookProjection.ts +254 -0
  342. package/src/lib/useTailAnchor.ts +53 -0
  343. package/src/lib/utils.ts +7 -0
  344. package/src/main.tsx +13 -0
  345. package/src/nodes/anomaly.test.ts +98 -0
  346. package/src/nodes/anomaly.ts +637 -0
  347. package/src/nodes/effects-scan.test.ts +44 -0
  348. package/src/nodes/flow-control.test.ts +280 -0
  349. package/src/nodes/flow-control.ts +305 -0
  350. package/src/nodes/index.ts +22 -0
  351. package/src/nodes/login.test.ts +81 -0
  352. package/src/nodes/login.ts +186 -0
  353. package/src/nodes/loops.ts +69 -0
  354. package/src/nodes/pradar/index.ts +11 -0
  355. package/src/nodes/pradar/logic.test.ts +288 -0
  356. package/src/nodes/pradar/logic.ts +582 -0
  357. package/src/nodes/pradar/nodes.test.ts +146 -0
  358. package/src/nodes/pradar/nodes.ts +955 -0
  359. package/src/nodes/requireAuth.test.ts +75 -0
  360. package/src/nodes/requireAuth.ts +55 -0
  361. package/src/nodes/response.test.ts +33 -0
  362. package/src/nodes/response.ts +39 -0
  363. package/src/nodes/traceDetail.test.ts +39 -0
  364. package/src/nodes/weather.test.ts +65 -0
  365. package/src/nodes/weather.ts +306 -0
  366. package/src/store/agentClient.test.ts +103 -0
  367. package/src/store/agentClient.ts +131 -0
  368. package/src/store/apiTree.test.ts +17 -0
  369. package/src/store/apiTree.ts +73 -0
  370. package/src/store/builderStore.test.ts +1234 -0
  371. package/src/store/builderStore.ts +1879 -0
  372. package/src/store/infraClient.ts +63 -0
  373. package/src/store/nodeMeta.test.ts +24 -0
  374. package/src/store/nodeMeta.ts +21 -0
  375. package/src/store/persistence.test.ts +15 -0
  376. package/src/store/persistence.ts +54 -0
  377. package/src/store/serverRunner.ts +381 -0
  378. package/src/store/setupClient.ts +57 -0
  379. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  380. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  381. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  382. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  383. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  384. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  385. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  386. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  387. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  388. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  389. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  390. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  391. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  392. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  393. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  394. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  395. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  396. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  397. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  398. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  399. package/studio-dist/assets/index-DNJwW-hM.css +1 -0
  400. package/studio-dist/assets/index-O26dKRjW.js +65 -0
  401. package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  402. package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  403. package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  404. package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  405. package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  406. package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  407. package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  408. package/studio-dist/favicon.svg +1 -0
  409. package/studio-dist/icons.svg +24 -0
  410. package/studio-dist/index.html +14 -0
  411. package/templates/skills/emberflow-basics/SKILL.md +316 -0
  412. package/templates/skills/emberflow-model-process/SKILL.md +209 -0
  413. package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
  414. package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
@@ -0,0 +1,613 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildPrompt, type AgentIntent } from './prompt';
3
+
4
+ describe('buildPrompt', () => {
5
+ const apisDir = 'flows';
6
+ const relPath = 'hello';
7
+
8
+ it('new-scenario: names the flow file and scenarios file, includes instruction verbatim and a skill reference', () => {
9
+ const intent: AgentIntent = {
10
+ action: 'new-scenario',
11
+ flowId: 'hello',
12
+ instruction: 'Add a scenario where the user says "hi" and the bot replies "hello back".',
13
+ };
14
+
15
+ const prompt = buildPrompt(intent, apisDir, relPath);
16
+
17
+ expect(prompt).toContain(intent.instruction);
18
+ expect(prompt).toContain('flows/hello.json');
19
+ expect(prompt).toContain('flows/hello.scenarios.json');
20
+ expect(prompt).toMatch(/emberflow-/);
21
+ expect(prompt).toMatch(/only edit/i);
22
+ expect(prompt).toMatch(/expect/i);
23
+ expect(prompt).toMatch(/executedNodes/);
24
+ expect(prompt).toMatch(/test hello/);
25
+ });
26
+
27
+ it('edit-node: names the flow file, the node id, includes instruction verbatim and a skill reference', () => {
28
+ const intent: AgentIntent = {
29
+ action: 'edit-node',
30
+ flowId: 'hello',
31
+ nodeId: 'greet-node',
32
+ instruction: 'Make the greeting node use a friendlier tone.',
33
+ };
34
+
35
+ const prompt = buildPrompt(intent, apisDir, relPath);
36
+
37
+ expect(prompt).toContain(intent.instruction);
38
+ expect(prompt).toContain('flows/hello.json');
39
+ expect(prompt).toContain('greet-node');
40
+ expect(prompt).toMatch(/emberflow-/);
41
+ expect(prompt).toMatch(/only edit/i);
42
+ });
43
+
44
+ it('edit-flow: names the flow file, includes instruction verbatim, a skill reference, and mentions self-review', () => {
45
+ const intent: AgentIntent = {
46
+ action: 'edit-flow',
47
+ flowId: 'hello',
48
+ instruction: 'Rewire the flow so the fallback node runs before the closing node.',
49
+ };
50
+
51
+ const prompt = buildPrompt(intent, apisDir, relPath);
52
+
53
+ expect(prompt).toContain(intent.instruction);
54
+ expect(prompt).toContain('flows/hello.json');
55
+ expect(prompt).toMatch(/emberflow-/);
56
+ expect(prompt).toMatch(/emberflow-review-workflow/);
57
+ expect(prompt).toMatch(/registerNodes/); // may author a node while editing
58
+ });
59
+
60
+ it('does not name the scenarios file for edit-node or edit-flow', () => {
61
+ const editNode = buildPrompt(
62
+ { action: 'edit-node', flowId: 'hello', nodeId: 'n1', instruction: 'x' },
63
+ apisDir,
64
+ relPath,
65
+ );
66
+ const editFlow = buildPrompt({ action: 'edit-flow', flowId: 'hello', instruction: 'x' }, apisDir, relPath);
67
+
68
+ expect(editNode).not.toContain('.scenarios.json');
69
+ expect(editFlow).not.toContain('.scenarios.json');
70
+ });
71
+
72
+ it('throws on an unrecognized intent.action instead of silently building a near-empty prompt', () => {
73
+ const bogus = { action: 'delete-everything', flowId: 'hello', instruction: 'x' } as unknown as AgentIntent;
74
+ expect(() => buildPrompt(bogus, apisDir, relPath)).toThrow(/unknown intent action/i);
75
+ });
76
+
77
+ it('new-operation: names the target apis/<location>/ dir, expresses the user goal, and asks for a single new operation with an http trigger and a Response node', () => {
78
+ const intent: AgentIntent = {
79
+ action: 'new-operation',
80
+ location: 'billing',
81
+ instruction: 'Let a customer request a refund for an order and notify billing ops.',
82
+ };
83
+
84
+ const prompt = buildPrompt(intent, 'flows', 'billing');
85
+
86
+ expect(prompt).toContain(intent.instruction);
87
+ expect(prompt).toContain('flows/billing');
88
+ expect(prompt).toMatch(/emberflow-/);
89
+ expect(prompt).toMatch(/create a brand-new operation/i);
90
+ expect(prompt).toMatch(/http/i);
91
+ expect(prompt).toMatch(/Response/);
92
+ expect(prompt).toMatch(/Input/);
93
+ // New-operation now leads with creating the shell via the CLI (name + method
94
+ // + path), then building it out.
95
+ expect(prompt).toMatch(/create the shell/i);
96
+ expect(prompt).toMatch(/ create <id>/i);
97
+ });
98
+
99
+ it('new-operation: resolves the default location to the "default" apis dir', () => {
100
+ const prompt = buildPrompt(
101
+ { action: 'new-operation', location: '', instruction: 'Send a welcome email on signup.' },
102
+ 'flows',
103
+ 'default',
104
+ );
105
+
106
+ expect(prompt).toContain('flows/default');
107
+ });
108
+
109
+ it('names the operation file by its relative path, not its id', () => {
110
+ const prompt = buildPrompt(
111
+ { action: 'edit-flow', flowId: 'triage', instruction: 'rename it' },
112
+ '/proj/emberflow/apis',
113
+ 'default/triage',
114
+ );
115
+ expect(prompt).toContain('/proj/emberflow/apis/default/triage.json');
116
+ expect(prompt).not.toContain('/proj/emberflow/apis/triage.json');
117
+ });
118
+
119
+ it('includes the available node types and the register-before-reference guardrail, for every intent action', () => {
120
+ const availableNodes = [
121
+ { type: 'Input' },
122
+ { type: 'Response' },
123
+ { type: 'ValidateCredentials', label: 'Validate Credentials', description: 'Checks a username/password pair.' },
124
+ { type: 'FetchUser', label: 'Fetch User' },
125
+ ];
126
+
127
+ const intents: AgentIntent[] = [
128
+ { action: 'new-scenario', flowId: 'hello', instruction: 'x' },
129
+ { action: 'edit-node', flowId: 'hello', nodeId: 'n1', instruction: 'x' },
130
+ { action: 'edit-flow', flowId: 'hello', instruction: 'x' },
131
+ { action: 'new-operation', location: 'billing', instruction: 'x' },
132
+ ];
133
+
134
+ for (const intent of intents) {
135
+ const prompt = buildPrompt(intent, apisDir, relPath, availableNodes);
136
+ expect(prompt).toMatch(/reuse/i); // reuse existing before authoring
137
+ expect(prompt).toMatch(/register its implementation/i); // the guardrail: register before reference
138
+ expect(prompt).toContain('Input');
139
+ expect(prompt).toContain('Response');
140
+ expect(prompt).toContain('ValidateCredentials');
141
+ expect(prompt).toContain('Validate Credentials');
142
+ expect(prompt).toContain('Checks a username/password pair.');
143
+ expect(prompt).toContain('FetchUser');
144
+ }
145
+ });
146
+
147
+ it('with no available nodes reported, tells the agent to author the nodes it needs', () => {
148
+ const prompt = buildPrompt({ action: 'edit-flow', flowId: 'hello', instruction: 'x' }, apisDir, relPath);
149
+ expect(prompt).toMatch(/author the nodes/i);
150
+ });
151
+
152
+ it('forbids the agent from switching serving mode itself — a real incident: an agent flipped to real mid-task and later runs hit the database', () => {
153
+ const prompt = buildPrompt({ action: 'edit-flow', flowId: 'hello', instruction: 'x' }, apisDir, relPath);
154
+ expect(prompt).toMatch(/NEVER switch this yourself/);
155
+ expect(prompt).toMatch(/say so in your final message instead of flipping the mode/);
156
+ });
157
+
158
+ it('includes the login-environment CLI command and the AUTH guidance section', () => {
159
+ const prompt = buildPrompt({ action: 'edit-flow', flowId: 'hello', instruction: 'x' }, apisDir, relPath);
160
+
161
+ expect(prompt).toContain(
162
+ '- login-environment <name> — perform the environment\'s configured login and store its credential runner-side; run this if operations fail 401/unauthorized and list-environments shows auth configured but not authenticated.',
163
+ );
164
+ expect(prompt).toMatch(/«secret:KEY»/);
165
+ expect(prompt).toMatch(/emberflow\.environments\.json/);
166
+ expect(prompt).toMatch(/login-environment <name>/);
167
+ });
168
+
169
+ it('setup-auth: names the environment, includes the instruction verbatim, the apply command, and both NEVER rules', () => {
170
+ const intent: AgentIntent = {
171
+ action: 'setup-auth',
172
+ environment: 'dev',
173
+ instruction: "curl -X POST https://api.example.com/login -d '{...}' returns a Set-Cookie session token",
174
+ };
175
+
176
+ const prompt = buildPrompt(intent, 'flows', 'default');
177
+
178
+ expect(prompt).toContain(intent.instruction);
179
+ expect(prompt).toContain('dev');
180
+ expect(prompt).toMatch(/set-environment-auth dev --json/);
181
+ expect(prompt).toMatch(/NEVER read or edit emberflow\.environments\.json directly/i);
182
+ expect(prompt).toMatch(/NEVER ask for or handle credential values/i);
183
+ expect(prompt).toMatch(/login-environment dev/);
184
+ expect(prompt).toMatch(/list-environments/);
185
+ });
186
+
187
+ it('setup-environments: includes the instruction verbatim, environments-file path guidance, the protected-for-prod rule, the never-write-secret-values rule, and a Manage-dialog/login pointer', () => {
188
+ const intent: AgentIntent = {
189
+ action: 'setup-environments',
190
+ instruction: 'We have a dev environment at https://dev.api.example.com and a prod environment at https://api.example.com, prod needs an API key.',
191
+ };
192
+
193
+ const prompt = buildPrompt(intent, apisDir, relPath);
194
+
195
+ expect(prompt).toContain(intent.instruction);
196
+ expect(prompt).toMatch(/emberflow\.environments\.json/);
197
+ expect(prompt).toMatch(/"protected"\s*:\s*true/);
198
+ expect(prompt).toMatch(/NEVER (invent|write) .*secret|credential.*value/i);
199
+ expect(prompt).toMatch(/Manage Environment dialog/i);
200
+ expect(prompt).toMatch(/login-environment/);
201
+ });
202
+
203
+ it('setup-environments: fences the agent to the user description — no code/infra/MCP investigation, placeholders for unknowns', () => {
204
+ const prompt = buildPrompt(
205
+ { action: 'setup-environments', instruction: 'dev and prod please' },
206
+ apisDir,
207
+ relPath,
208
+ );
209
+ expect(prompt).toMatch(/description ALONE/);
210
+ expect(prompt).toMatch(/do NOT explore .*deployment providers.*MCP/i);
211
+ expect(prompt).toMatch(/placeholder var/);
212
+ });
213
+
214
+ it('setup-environments does NOT get the doctor global rule (it edits emberflow.environments.json, not an operation)', () => {
215
+ const prompt = buildPrompt(
216
+ { action: 'setup-environments', instruction: 'Set up a dev environment.' },
217
+ apisDir,
218
+ relPath,
219
+ );
220
+ expect(prompt).not.toMatch(/run doctor/i);
221
+ expect(prompt).not.toMatch(/resolve its findings/i);
222
+ });
223
+
224
+ it('cover-operation: names the flow + scenarios files, includes instruction verbatim, the branch-coverage instruction, the never-weaken rule, and the test verification command', () => {
225
+ const intent: AgentIntent = {
226
+ action: 'cover-operation',
227
+ flowId: 'hello',
228
+ instruction: 'Cover every branch of the operation with scenarios.',
229
+ };
230
+
231
+ const prompt = buildPrompt(intent, apisDir, relPath);
232
+
233
+ expect(prompt).toContain(intent.instruction);
234
+ expect(prompt).toContain('flows/hello.json');
235
+ expect(prompt).toContain('flows/hello.scenarios.json');
236
+ expect(prompt).toMatch(/every Route\/Conditional branch/i);
237
+ expect(prompt).toMatch(/terminal Response\/Result/i);
238
+ expect(prompt).toMatch(/executedNodes/);
239
+ expect(prompt).toMatch(/NEVER weaken or delete an existing expect/i);
240
+ expect(prompt).toMatch(/test hello/);
241
+ // The mechanical "every scenario must supply every :param" law now lives in
242
+ // `doctor`'s findings; the prompt keeps only the real-id taste guidance.
243
+ expect(prompt).toMatch(/real id from the project's data/i);
244
+ });
245
+
246
+ it('cover-operation: carries the mocks-map guidance (op-level + per-scenario shape, infra traceKind, realistic output) and the never-secrets-in-mocks rule', () => {
247
+ const intent: AgentIntent = {
248
+ action: 'cover-operation',
249
+ flowId: 'hello',
250
+ instruction: 'Cover every branch of the operation with scenarios.',
251
+ };
252
+
253
+ const prompt = buildPrompt(intent, apisDir, relPath);
254
+
255
+ expect(prompt).toMatch(/traceKind.*"db".*"http".*"llm"/i);
256
+ expect(prompt).toMatch(/"mocks"/);
257
+ expect(prompt).toMatch(/nodeId/);
258
+ expect(prompt).toMatch(/op-level.*mocks.*plain Run|plain Run.*op-level.*mocks/i);
259
+ expect(prompt).toMatch(/per-scenario.*mocks.*overrides/i);
260
+ expect(prompt).toMatch(/read (the )?node'?s? implementation/i);
261
+ expect(prompt).toMatch(/never put secret values in (a |the )?mocks/i);
262
+ });
263
+
264
+ it('new-scenario: carries the mocks-map guidance and the never-secrets-in-mocks rule', () => {
265
+ const intent: AgentIntent = {
266
+ action: 'new-scenario',
267
+ flowId: 'hello',
268
+ instruction: 'Add a scenario for the timeout branch.',
269
+ };
270
+
271
+ const prompt = buildPrompt(intent, apisDir, relPath);
272
+
273
+ expect(prompt).toMatch(/traceKind.*"db".*"http".*"llm"/i);
274
+ expect(prompt).toMatch(/"mocks"/);
275
+ expect(prompt).toMatch(/never put secret values in (a |the )?mocks/i);
276
+ });
277
+
278
+ it('edit-flow and new-operation carry the traceKind-honesty node-authoring nudge', () => {
279
+ const editFlow = buildPrompt({ action: 'edit-flow', flowId: 'hello', instruction: 'x' }, apisDir, relPath);
280
+ const scaffold = buildPrompt(
281
+ { action: 'edit-flow', flowId: 'placeholder-name', instruction: 'x', scaffold: true },
282
+ apisDir,
283
+ relPath,
284
+ );
285
+ const newOperation = buildPrompt({ action: 'new-operation', location: 'billing', instruction: 'x' }, apisDir, relPath);
286
+
287
+ for (const prompt of [editFlow, scaffold, newOperation]) {
288
+ expect(prompt).toMatch(/set (its |the )?traceKind honestly/i);
289
+ expect(prompt).toMatch(/mock-mode infrastructure boundary/i);
290
+ }
291
+ });
292
+
293
+ it('setup-environments does NOT gain the mocks or traceKind-honesty guidance', () => {
294
+ const prompt = buildPrompt({ action: 'setup-environments', instruction: 'dev and prod please' }, apisDir, relPath);
295
+
296
+ expect(prompt).not.toMatch(/traceKind/i);
297
+ expect(prompt).not.toMatch(/"mocks"/);
298
+ expect(prompt).not.toMatch(/never put secret values in (a |the )?mocks/i);
299
+ });
300
+
301
+ it('CLI catalog lists the doctor command', () => {
302
+ const intent: AgentIntent = { action: 'edit-flow', flowId: 'hello', instruction: 'x' };
303
+ const prompt = buildPrompt(intent, apisDir, relPath);
304
+ expect(prompt).toContain(
305
+ '- doctor [<id>] [--fix] — report operation diagnostics (missing param defaults, uncovered path params, missing expects); --fix auto-seeds missing param defaults. Run it on any operation you created or edited and resolve every finding before you finish.',
306
+ );
307
+ });
308
+
309
+ it('every flow-mutating intent tells the agent to run doctor and resolve its findings before finishing', () => {
310
+ const intents: AgentIntent[] = [
311
+ { action: 'new-scenario', flowId: 'hello', instruction: 'x' },
312
+ { action: 'edit-node', flowId: 'hello', nodeId: 'n1', instruction: 'x' },
313
+ { action: 'edit-flow', flowId: 'hello', instruction: 'x' },
314
+ { action: 'new-operation', location: 'billing', instruction: 'x' },
315
+ { action: 'cover-operation', flowId: 'hello', instruction: 'x' },
316
+ ];
317
+
318
+ for (const intent of intents) {
319
+ const prompt = buildPrompt(intent, apisDir, relPath);
320
+ expect(prompt).toMatch(/run doctor/i);
321
+ expect(prompt).toMatch(/resolve its findings/i);
322
+ expect(prompt).toMatch(/doctor --fix seeds missing param defaults/i);
323
+ }
324
+ });
325
+
326
+ it('edit-flow scaffold path also tells the agent to run doctor', () => {
327
+ const prompt = buildPrompt(
328
+ { action: 'edit-flow', flowId: 'placeholder-name', instruction: 'x', scaffold: true },
329
+ apisDir,
330
+ relPath,
331
+ );
332
+ expect(prompt).toMatch(/run doctor/i);
333
+ });
334
+
335
+ it('setup-auth does NOT get the doctor global rule (it edits emberflow.environments.json, not an operation) — the CLI catalog entry is still listed', () => {
336
+ const prompt = buildPrompt(
337
+ { action: 'setup-auth', environment: 'dev', instruction: 'x' },
338
+ apisDir,
339
+ relPath,
340
+ );
341
+ expect(prompt).not.toMatch(/run doctor/i);
342
+ expect(prompt).not.toMatch(/resolve its findings/i);
343
+ // The shared CLI catalog (listed for every intent) still names the command.
344
+ expect(prompt).toMatch(/- doctor \[<id>\]/);
345
+ });
346
+
347
+ it('ask (with a flow): names the flow file, includes the question verbatim, enforces the no-edit rule, and carries the inspection order', () => {
348
+ const intent: AgentIntent = {
349
+ action: 'ask',
350
+ flowId: 'hello',
351
+ instruction: 'Which node decides whether the request is authorized?',
352
+ };
353
+
354
+ const prompt = buildPrompt(intent, apisDir, relPath);
355
+
356
+ expect(prompt).toContain(intent.instruction);
357
+ expect(prompt).toContain('flows/hello.json');
358
+ // No-edit rule: this is a question, answer it and change nothing.
359
+ expect(prompt).toMatch(/QUESTION, not an edit/i);
360
+ expect(prompt).toMatch(/change NOTHING/i);
361
+ expect(prompt).toMatch(/Do NOT write, create, save, edit, delete, or rename/i);
362
+ // Inspection order (shared preamble) is present.
363
+ expect(prompt).toMatch(/INSPECTION ORDER/);
364
+ expect(prompt).toMatch(/list-workflows.*get-workflow.*get-node.*node-schema.*samples/);
365
+ // Answer-format guidance.
366
+ expect(prompt).toMatch(/LEAD with the direct answer/i);
367
+ });
368
+
369
+ it('ask (without a flow): frames it as a project-wide question, still no-edit + inspection order', () => {
370
+ const intent: AgentIntent = {
371
+ action: 'ask',
372
+ instruction: 'How many operations touch the database?',
373
+ };
374
+
375
+ // For a flow-less ask the caller passes the apis root (empty relPath).
376
+ const prompt = buildPrompt(intent, apisDir, '');
377
+
378
+ expect(prompt).toContain(intent.instruction);
379
+ expect(prompt).toMatch(/question about this Emberflow project/i);
380
+ expect(prompt).not.toMatch(/\.json\b.*question|question.*flows\/hello\.json/);
381
+ expect(prompt).toMatch(/QUESTION, not an edit/i);
382
+ expect(prompt).toMatch(/change NOTHING/i);
383
+ expect(prompt).toMatch(/INSPECTION ORDER/);
384
+ expect(prompt).toMatch(/list-workflows.*get-workflow.*get-node.*node-schema.*samples/);
385
+ });
386
+
387
+ it('ask does NOT get the doctor global rule (it changes nothing)', () => {
388
+ const prompt = buildPrompt({ action: 'ask', flowId: 'hello', instruction: 'x' }, apisDir, relPath);
389
+ expect(prompt).not.toMatch(/run doctor .*resolve its findings/i);
390
+ });
391
+
392
+ it('CLI catalog lists the get-node command and the inspection order (every intent)', () => {
393
+ const prompt = buildPrompt({ action: 'edit-flow', flowId: 'hello', instruction: 'x' }, apisDir, relPath);
394
+ expect(prompt).toMatch(/- get-node <id> <nodeId>/);
395
+ expect(prompt).toMatch(/INSPECTION ORDER/);
396
+ });
397
+
398
+ it('CLI catalog lists the test command', () => {
399
+ const intent: AgentIntent = { action: 'edit-flow', flowId: 'hello', instruction: 'x' };
400
+ const prompt = buildPrompt(intent, apisDir, relPath);
401
+ expect(prompt).toMatch(/- test <id> — run the operation's scenario expectations in-process/);
402
+ });
403
+
404
+ it('shared test-as-you-build guidance keeps the run+nodeStates debugging step and adds scenario expects + a final test run', () => {
405
+ for (const intent of [
406
+ { action: 'edit-flow', flowId: 'hello', instruction: 'x' } as AgentIntent,
407
+ { action: 'new-operation', location: 'billing', instruction: 'x' } as AgentIntent,
408
+ ]) {
409
+ const prompt = buildPrompt(intent, apisDir, relPath);
410
+ expect(prompt).toMatch(/read.{0,20}nodeStates/i); // existing run-and-read guidance stays
411
+ expect(prompt).toMatch(/scenarios sidecar.*expect/i);
412
+ expect(prompt).toMatch(/node .*test <id>/);
413
+ }
414
+ });
415
+
416
+ it('defaults to typescript-driven guidance when projectLanguage is omitted', () => {
417
+ const intent: AgentIntent = { action: 'edit-flow', flowId: 'hello', instruction: 'x' };
418
+ const prompt = buildPrompt(intent, apisDir, relPath);
419
+ expect(prompt).toMatch(/This project is typescript-driven.*TypeScript \(\.ts, typed\).*do not introduce JavaScript files into it/);
420
+ });
421
+
422
+ it('states javascript-driven guidance when projectLanguage is "javascript"', () => {
423
+ const intent: AgentIntent = { action: 'edit-flow', flowId: 'hello', instruction: 'x' };
424
+ const prompt = buildPrompt(intent, apisDir, relPath, [], 'javascript');
425
+ expect(prompt).toMatch(/This project is javascript-driven.*JavaScript \(\.mjs\/\.js, JSDoc types\).*do not introduce TypeScript files into it/);
426
+ expect(prompt).not.toMatch(/This project is typescript-driven/);
427
+ });
428
+
429
+ it('states typescript-driven guidance when projectLanguage is explicitly "typescript"', () => {
430
+ const intent: AgentIntent = { action: 'edit-flow', flowId: 'hello', instruction: 'x' };
431
+ const prompt = buildPrompt(intent, apisDir, relPath, [], 'typescript');
432
+ expect(prompt).toMatch(/This project is typescript-driven.*TypeScript \(\.ts, typed\).*do not introduce JavaScript files into it/);
433
+ expect(prompt).not.toMatch(/This project is javascript-driven/);
434
+ });
435
+
436
+ describe('scout-infrastructure', () => {
437
+ const scout: AgentIntent = {
438
+ action: 'scout-infrastructure',
439
+ instruction: 'Scan this project for infrastructure it already uses.',
440
+ };
441
+
442
+ it('states the JOB is to read the project and INVERTS the setup-environments no-explore rule', () => {
443
+ const prompt = buildPrompt(scout, apisDir, relPath);
444
+ expect(prompt).toContain(scout.instruction);
445
+ expect(prompt).toMatch(/your JOB.*is to READ the project/i);
446
+ expect(prompt).toMatch(/INVERT/i);
447
+ expect(prompt).toMatch(/do NOT explore/); // references the rule it inverts
448
+ });
449
+
450
+ it('enumerates deps/config/ORM/env-var refs/HTTP clients/routes and the manifest kinds', () => {
451
+ const prompt = buildPrompt(scout, apisDir, relPath);
452
+ expect(prompt).toMatch(/package\.json/);
453
+ expect(prompt).toMatch(/requirements\.txt/);
454
+ expect(prompt).toMatch(/go\.mod/);
455
+ expect(prompt).toMatch(/prisma\/schema\.prisma/);
456
+ expect(prompt).toMatch(/process\.env/);
457
+ expect(prompt).toMatch(/HTTP clients/i);
458
+ expect(prompt).toMatch(/route/i);
459
+ expect(prompt).toMatch(/database, http-api, queue, cache, email, llm, auth, framework, storage, other/);
460
+ });
461
+
462
+ it('tells the agent to WRITE emberflow/infrastructure.json in the manifest shape', () => {
463
+ const prompt = buildPrompt(scout, apisDir, relPath);
464
+ expect(prompt).toMatch(/WRITE the manifest to emberflow\/infrastructure\.json/);
465
+ expect(prompt).toMatch(/create the emberflow\/ directory/i);
466
+ expect(prompt).toMatch(/"scannedAt"/);
467
+ expect(prompt).toMatch(/"greenfield"/);
468
+ expect(prompt).toMatch(/"suggestedSecretRefs"/);
469
+ expect(prompt).toMatch(/"evidence"/);
470
+ });
471
+
472
+ it('forbids copying secret VALUES — env-var NAMES only — and states it twice', () => {
473
+ const prompt = buildPrompt(scout, apisDir, relPath);
474
+ const nameOnlyMatches = prompt.match(/NAMES ONLY|only names|no secret values/gi) ?? [];
475
+ // Stated at least twice (the hard rule + the closing double-check reminder).
476
+ expect(nameOnlyMatches.length).toBeGreaterThanOrEqual(2);
477
+ expect(prompt).toMatch(/NEVER copy a secret VALUE/i);
478
+ expect(prompt).toMatch(/stated twice on purpose/i);
479
+ });
480
+
481
+ it('covers greenfield handling and the closing summary + open questions', () => {
482
+ const prompt = buildPrompt(scout, apisDir, relPath);
483
+ expect(prompt).toMatch(/greenfield.*true/i);
484
+ expect(prompt).toMatch(/open questions/i);
485
+ });
486
+
487
+ it('does NOT get the doctor rule, the mocks guidance, or the traceKind nudge (it edits no operation)', () => {
488
+ const prompt = buildPrompt(scout, apisDir, relPath);
489
+ expect(prompt).not.toMatch(/run doctor/i);
490
+ expect(prompt).not.toMatch(/"mocks"/);
491
+ expect(prompt).not.toMatch(/set (its |the )?traceKind honestly/i);
492
+ });
493
+
494
+ it('does NOT receive the known-infrastructure preamble even when a manifest is passed (it writes it)', () => {
495
+ const manifest = {
496
+ version: 1,
497
+ greenfield: false,
498
+ items: [
499
+ { id: 'pg', kind: 'database' as const, name: 'Postgres', evidence: [], suggestedSecretRefs: ['DATABASE_URL'], suggestedVars: [] },
500
+ ],
501
+ };
502
+ const prompt = buildPrompt(scout, apisDir, relPath, [], 'typescript', manifest);
503
+ expect(prompt).not.toMatch(/Known project infrastructure \(from/);
504
+ });
505
+ });
506
+
507
+ describe('known-infrastructure preamble injection', () => {
508
+ const editFlow: AgentIntent = { action: 'edit-flow', flowId: 'hello', instruction: 'x' };
509
+ const manifest = {
510
+ version: 1,
511
+ scannedAt: '2026-07-12T00:00:00Z',
512
+ greenfield: false,
513
+ summary: 'Express + Postgres + Stripe.',
514
+ items: [
515
+ {
516
+ id: 'postgres-main',
517
+ kind: 'database' as const,
518
+ name: 'Postgres (Prisma)',
519
+ evidence: [{ file: 'prisma/schema.prisma', note: 'datasource db' }],
520
+ suggestedSecretRefs: ['DATABASE_URL'],
521
+ suggestedVars: [],
522
+ },
523
+ {
524
+ id: 'stripe',
525
+ kind: 'http-api' as const,
526
+ name: 'Stripe',
527
+ evidence: [{ file: 'package.json', note: 'stripe dep' }],
528
+ suggestedSecretRefs: ['STRIPE_SECRET_KEY'],
529
+ suggestedVars: [],
530
+ },
531
+ ],
532
+ };
533
+
534
+ it('with a manifest: lists each item (name/kind — secretRefs — evidence file) and the REUSE rule', () => {
535
+ const prompt = buildPrompt(editFlow, apisDir, relPath, [], 'typescript', manifest);
536
+ expect(prompt).toMatch(/Known project infrastructure \(from emberflow\/infrastructure\.json/);
537
+ expect(prompt).toContain('Postgres (Prisma) (database)');
538
+ expect(prompt).toContain('secretRefs: DATABASE_URL');
539
+ expect(prompt).toContain('see prisma/schema.prisma');
540
+ expect(prompt).toContain('Stripe (http-api)');
541
+ expect(prompt).toContain('secretRefs: STRIPE_SECRET_KEY');
542
+ expect(prompt).toMatch(/REUSE rule.*REUSE it/i);
543
+ });
544
+
545
+ it('without a manifest: one line noting none on record and suggesting the scout', () => {
546
+ const prompt = buildPrompt(editFlow, apisDir, relPath, [], 'typescript', null);
547
+ expect(prompt).toMatch(/Known project infrastructure: none on record/i);
548
+ expect(prompt).toMatch(/infrastructure scout/i);
549
+ expect(prompt).not.toMatch(/Known project infrastructure \(from/);
550
+ });
551
+
552
+ it('greenfield manifest: says greenfield rather than listing items', () => {
553
+ const prompt = buildPrompt(editFlow, apisDir, relPath, [], 'typescript', {
554
+ version: 1,
555
+ greenfield: true,
556
+ items: [],
557
+ });
558
+ expect(prompt).toMatch(/marks this project greenfield/i);
559
+ });
560
+
561
+ it('caps the item listing at 30 and appends a "+N more" line pointing at the manifest/scout', () => {
562
+ const items = Array.from({ length: 35 }, (_, i) => ({
563
+ id: `item-${i}`,
564
+ kind: 'http-api' as const,
565
+ name: `Service ${i}`,
566
+ evidence: [],
567
+ suggestedSecretRefs: [],
568
+ suggestedVars: [],
569
+ }));
570
+ const prompt = buildPrompt(editFlow, apisDir, relPath, [], 'typescript', {
571
+ version: 1,
572
+ greenfield: false,
573
+ items,
574
+ });
575
+ expect(prompt).toContain('Service 0 (http-api)');
576
+ expect(prompt).toContain('Service 29 (http-api)');
577
+ expect(prompt).not.toContain('Service 30 (http-api)');
578
+ expect(prompt).not.toContain('Service 34 (http-api)');
579
+ expect(prompt).toMatch(/\(\+5 more — see emberflow\/infrastructure\.json or re-run the scout\)/);
580
+ });
581
+
582
+ it('does not append a "+N more" line when items are at or under the cap', () => {
583
+ const prompt = buildPrompt(editFlow, apisDir, relPath, [], 'typescript', manifest);
584
+ expect(prompt).not.toMatch(/more — see emberflow\/infrastructure\.json/);
585
+ });
586
+
587
+ it('non-greenfield manifest present but with no items: distinct "exists but lists no items" wording', () => {
588
+ const prompt = buildPrompt(editFlow, apisDir, relPath, [], 'typescript', {
589
+ version: 1,
590
+ greenfield: false,
591
+ items: [],
592
+ });
593
+ expect(prompt).toMatch(/infrastructure manifest exists but lists no items/i);
594
+ expect(prompt).toMatch(/re-run the scout/i);
595
+ // Distinct from both the "none on record" and "greenfield" branches.
596
+ expect(prompt).not.toMatch(/Known project infrastructure: none on record/i);
597
+ expect(prompt).not.toMatch(/marks this project greenfield/i);
598
+ });
599
+
600
+ it('preamble reaches new-operation too (the intent most likely to need reuse)', () => {
601
+ const prompt = buildPrompt(
602
+ { action: 'new-operation', location: 'billing', instruction: 'charge a card' },
603
+ apisDir,
604
+ relPath,
605
+ [],
606
+ 'typescript',
607
+ manifest,
608
+ );
609
+ expect(prompt).toContain('Stripe (http-api)');
610
+ expect(prompt).toMatch(/REUSE rule/i);
611
+ });
612
+ });
613
+ });