patchwork-os 0.2.0-beta.9.canary.92 → 1.1.0-beta.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 (739) hide show
  1. package/README.bridge.md +15 -16
  2. package/README.md +46 -458
  3. package/dist/activationMetrics.js +2 -3
  4. package/dist/activationMetrics.js.map +1 -1
  5. package/dist/activityLog.d.ts +1 -0
  6. package/dist/activityLog.js +8 -3
  7. package/dist/activityLog.js.map +1 -1
  8. package/dist/adapters/gemini.js +2 -2
  9. package/dist/adapters/gemini.js.map +1 -1
  10. package/dist/adapters/grok.js +6 -1
  11. package/dist/adapters/grok.js.map +1 -1
  12. package/dist/adapters/openai.js +4 -0
  13. package/dist/adapters/openai.js.map +1 -1
  14. package/dist/approvalHttp.d.ts +9 -0
  15. package/dist/approvalHttp.js +210 -171
  16. package/dist/approvalHttp.js.map +1 -1
  17. package/dist/approvalKpi.d.ts +102 -0
  18. package/dist/approvalKpi.js +282 -0
  19. package/dist/approvalKpi.js.map +1 -0
  20. package/dist/approvalQueue.d.ts +1 -1
  21. package/dist/approvalQueue.js.map +1 -1
  22. package/dist/automation.d.ts +45 -4
  23. package/dist/automation.js +123 -15
  24. package/dist/automation.js.map +1 -1
  25. package/dist/bridge.d.ts +10 -0
  26. package/dist/bridge.js +225 -46
  27. package/dist/bridge.js.map +1 -1
  28. package/dist/bridgeLockDiscovery.d.ts +16 -1
  29. package/dist/bridgeLockDiscovery.js +38 -4
  30. package/dist/bridgeLockDiscovery.js.map +1 -1
  31. package/dist/bridgeToolsRules.js +3 -18
  32. package/dist/bridgeToolsRules.js.map +1 -1
  33. package/dist/claudeAuthHttp.d.ts +25 -0
  34. package/dist/claudeAuthHttp.js +219 -0
  35. package/dist/claudeAuthHttp.js.map +1 -0
  36. package/dist/claudeDriver.js +4 -1
  37. package/dist/claudeDriver.js.map +1 -1
  38. package/dist/claudeOrchestrator.d.ts +15 -0
  39. package/dist/claudeOrchestrator.js +44 -0
  40. package/dist/claudeOrchestrator.js.map +1 -1
  41. package/dist/commands/connect.d.ts +47 -0
  42. package/dist/commands/connect.js +419 -0
  43. package/dist/commands/connect.js.map +1 -0
  44. package/dist/commands/install.js +3 -10
  45. package/dist/commands/install.js.map +1 -1
  46. package/dist/commands/launchd.d.ts +7 -0
  47. package/dist/commands/launchd.js +20 -2
  48. package/dist/commands/launchd.js.map +1 -1
  49. package/dist/commands/patchworkInit.d.ts +7 -0
  50. package/dist/commands/patchworkInit.js +26 -0
  51. package/dist/commands/patchworkInit.js.map +1 -1
  52. package/dist/commands/recipe.d.ts +59 -4
  53. package/dist/commands/recipe.js +206 -5
  54. package/dist/commands/recipe.js.map +1 -1
  55. package/dist/commands/recipeInstall.d.ts +9 -0
  56. package/dist/commands/recipeInstall.js +56 -3
  57. package/dist/commands/recipeInstall.js.map +1 -1
  58. package/dist/commands/task.d.ts +25 -0
  59. package/dist/commands/task.js +61 -41
  60. package/dist/commands/task.js.map +1 -1
  61. package/dist/commands/tokenEfficiency.d.ts +4 -0
  62. package/dist/commands/tokenEfficiency.js +23 -26
  63. package/dist/commands/tokenEfficiency.js.map +1 -1
  64. package/dist/commands/tracesExport.d.ts +15 -1
  65. package/dist/commands/tracesExport.js +39 -5
  66. package/dist/commands/tracesExport.js.map +1 -1
  67. package/dist/config.js +16 -0
  68. package/dist/config.js.map +1 -1
  69. package/dist/connectorRoutes.js +613 -62
  70. package/dist/connectorRoutes.js.map +1 -1
  71. package/dist/connectors/asana.js +9 -3
  72. package/dist/connectors/asana.js.map +1 -1
  73. package/dist/connectors/baseConnector.d.ts +16 -0
  74. package/dist/connectors/baseConnector.js +2 -0
  75. package/dist/connectors/baseConnector.js.map +1 -1
  76. package/dist/connectors/caldiy.js +34 -0
  77. package/dist/connectors/caldiy.js.map +1 -1
  78. package/dist/connectors/confluence.js +9 -2
  79. package/dist/connectors/confluence.js.map +1 -1
  80. package/dist/connectors/connectorActivity.d.ts +24 -0
  81. package/dist/connectors/connectorActivity.js +32 -0
  82. package/dist/connectors/connectorActivity.js.map +1 -0
  83. package/dist/connectors/connectorRedirectUri.js +22 -2
  84. package/dist/connectors/connectorRedirectUri.js.map +1 -1
  85. package/dist/connectors/connectorRegistry.d.ts +0 -3
  86. package/dist/connectors/connectorRegistry.js +8 -8
  87. package/dist/connectors/connectorRegistry.js.map +1 -1
  88. package/dist/connectors/datadog.js +8 -1
  89. package/dist/connectors/datadog.js.map +1 -1
  90. package/dist/connectors/discord.js +4 -3
  91. package/dist/connectors/discord.js.map +1 -1
  92. package/dist/connectors/elasticsearch.js +22 -0
  93. package/dist/connectors/elasticsearch.js.map +1 -1
  94. package/dist/connectors/github.d.ts +38 -0
  95. package/dist/connectors/github.js +91 -5
  96. package/dist/connectors/github.js.map +1 -1
  97. package/dist/connectors/gitlab.js +19 -16
  98. package/dist/connectors/gitlab.js.map +1 -1
  99. package/dist/connectors/gmail.d.ts +5 -0
  100. package/dist/connectors/gmail.js +53 -11
  101. package/dist/connectors/gmail.js.map +1 -1
  102. package/dist/connectors/googleCalendar.js +5 -4
  103. package/dist/connectors/googleCalendar.js.map +1 -1
  104. package/dist/connectors/googleDocs.js +7 -6
  105. package/dist/connectors/googleDocs.js.map +1 -1
  106. package/dist/connectors/googleDrive.js +15 -8
  107. package/dist/connectors/googleDrive.js.map +1 -1
  108. package/dist/connectors/grafana.js +39 -3
  109. package/dist/connectors/grafana.js.map +1 -1
  110. package/dist/connectors/jira.d.ts +1 -1
  111. package/dist/connectors/jira.js +19 -4
  112. package/dist/connectors/jira.js.map +1 -1
  113. package/dist/connectors/linear.js +2 -2
  114. package/dist/connectors/linear.js.map +1 -1
  115. package/dist/connectors/mcpClient.d.ts +29 -1
  116. package/dist/connectors/mcpClient.js +114 -44
  117. package/dist/connectors/mcpClient.js.map +1 -1
  118. package/dist/connectors/mcpOAuth.d.ts +1 -0
  119. package/dist/connectors/mcpOAuth.js +49 -4
  120. package/dist/connectors/mcpOAuth.js.map +1 -1
  121. package/dist/connectors/monday.js +5 -4
  122. package/dist/connectors/monday.js.map +1 -1
  123. package/dist/connectors/mongodb.js +32 -5
  124. package/dist/connectors/mongodb.js.map +1 -1
  125. package/dist/connectors/notion.d.ts +1 -1
  126. package/dist/connectors/notion.js +22 -6
  127. package/dist/connectors/notion.js.map +1 -1
  128. package/dist/connectors/oauthError.d.ts +17 -0
  129. package/dist/connectors/oauthError.js +50 -0
  130. package/dist/connectors/oauthError.js.map +1 -0
  131. package/dist/connectors/postgres.d.ts +7 -0
  132. package/dist/connectors/postgres.js +32 -2
  133. package/dist/connectors/postgres.js.map +1 -1
  134. package/dist/connectors/posthog.js +26 -0
  135. package/dist/connectors/posthog.js.map +1 -1
  136. package/dist/connectors/redis.d.ts +1 -0
  137. package/dist/connectors/redis.js +47 -20
  138. package/dist/connectors/redis.js.map +1 -1
  139. package/dist/connectors/salesforce.js +66 -5
  140. package/dist/connectors/salesforce.js.map +1 -1
  141. package/dist/connectors/sentry.js +2 -2
  142. package/dist/connectors/sentry.js.map +1 -1
  143. package/dist/connectors/slack.js +37 -14
  144. package/dist/connectors/slack.js.map +1 -1
  145. package/dist/connectors/snowflake.js +24 -0
  146. package/dist/connectors/snowflake.js.map +1 -1
  147. package/dist/connectors/stripe.js +9 -2
  148. package/dist/connectors/stripe.js.map +1 -1
  149. package/dist/connectors/supabase.js +34 -0
  150. package/dist/connectors/supabase.js.map +1 -1
  151. package/dist/connectors/tokenStorage.d.ts +20 -0
  152. package/dist/connectors/tokenStorage.js +196 -64
  153. package/dist/connectors/tokenStorage.js.map +1 -1
  154. package/dist/connectors/woocommerce.js +34 -0
  155. package/dist/connectors/woocommerce.js.map +1 -1
  156. package/dist/copilot/parseIntent.d.ts +58 -0
  157. package/dist/copilot/parseIntent.js +149 -0
  158. package/dist/copilot/parseIntent.js.map +1 -0
  159. package/dist/decisionReplay.d.ts +8 -6
  160. package/dist/decisionReplay.js +35 -11
  161. package/dist/decisionReplay.js.map +1 -1
  162. package/dist/decisionTraceLog.d.ts +9 -0
  163. package/dist/decisionTraceLog.js +6 -0
  164. package/dist/decisionTraceLog.js.map +1 -1
  165. package/dist/drivers/claude/api.js +49 -7
  166. package/dist/drivers/claude/api.js.map +1 -1
  167. package/dist/drivers/claude/envSanitizer.d.ts +9 -1
  168. package/dist/drivers/claude/envSanitizer.js +41 -3
  169. package/dist/drivers/claude/envSanitizer.js.map +1 -1
  170. package/dist/drivers/claude/streamParser.d.ts +13 -0
  171. package/dist/drivers/claude/streamParser.js.map +1 -1
  172. package/dist/drivers/claude/subprocess.js +131 -16
  173. package/dist/drivers/claude/subprocess.js.map +1 -1
  174. package/dist/drivers/claude/subprocessSettings.d.ts +1 -1
  175. package/dist/drivers/claude/subprocessSettings.js +18 -4
  176. package/dist/drivers/claude/subprocessSettings.js.map +1 -1
  177. package/dist/drivers/gemini/index.d.ts +13 -0
  178. package/dist/drivers/gemini/index.js +211 -67
  179. package/dist/drivers/gemini/index.js.map +1 -1
  180. package/dist/drivers/local/index.d.ts +2 -17
  181. package/dist/drivers/local/index.js +5 -92
  182. package/dist/drivers/local/index.js.map +1 -1
  183. package/dist/drivers/openai/index.js +69 -9
  184. package/dist/drivers/openai/index.js.map +1 -1
  185. package/dist/drivers/outputCap.d.ts +27 -0
  186. package/dist/drivers/outputCap.js +50 -0
  187. package/dist/drivers/outputCap.js.map +1 -0
  188. package/dist/embeddings/cosine.d.ts +23 -0
  189. package/dist/embeddings/cosine.js +59 -0
  190. package/dist/embeddings/cosine.js.map +1 -0
  191. package/dist/embeddings/index.d.ts +19 -0
  192. package/dist/embeddings/index.js +35 -0
  193. package/dist/embeddings/index.js.map +1 -0
  194. package/dist/embeddings/localEmbeddings.d.ts +29 -0
  195. package/dist/embeddings/localEmbeddings.js +103 -0
  196. package/dist/embeddings/localEmbeddings.js.map +1 -0
  197. package/dist/embeddings/localEndpoints.d.ts +24 -0
  198. package/dist/embeddings/localEndpoints.js +37 -0
  199. package/dist/embeddings/localEndpoints.js.map +1 -0
  200. package/dist/embeddings/types.d.ts +32 -0
  201. package/dist/embeddings/types.js +12 -0
  202. package/dist/embeddings/types.js.map +1 -0
  203. package/dist/featureFlags.d.ts +18 -0
  204. package/dist/featureFlags.js +32 -0
  205. package/dist/featureFlags.js.map +1 -1
  206. package/dist/fp/automationInterpreter.js +118 -39
  207. package/dist/fp/automationInterpreter.js.map +1 -1
  208. package/dist/fp/automationProgram.d.ts +10 -0
  209. package/dist/fp/automationProgram.js.map +1 -1
  210. package/dist/fp/automationState.d.ts +6 -0
  211. package/dist/fp/automationState.js +38 -0
  212. package/dist/fp/automationState.js.map +1 -1
  213. package/dist/fp/commandDescription.d.ts +6 -0
  214. package/dist/fp/commandDescription.js +18 -5
  215. package/dist/fp/commandDescription.js.map +1 -1
  216. package/dist/fp/interpreterContext.d.ts +16 -5
  217. package/dist/fp/interpreterContext.js +59 -3
  218. package/dist/fp/interpreterContext.js.map +1 -1
  219. package/dist/fp/policyParser.js +18 -8
  220. package/dist/fp/policyParser.js.map +1 -1
  221. package/dist/haltPushDispatch.d.ts +4 -0
  222. package/dist/haltPushDispatch.js +7 -22
  223. package/dist/haltPushDispatch.js.map +1 -1
  224. package/dist/inboxRoutes.js +2 -2
  225. package/dist/inboxRoutes.js.map +1 -1
  226. package/dist/index.js +672 -135
  227. package/dist/index.js.map +1 -1
  228. package/dist/installGuard.js +6 -0
  229. package/dist/installGuard.js.map +1 -1
  230. package/dist/localEndpointGuard.d.ts +25 -0
  231. package/dist/localEndpointGuard.js +101 -0
  232. package/dist/localEndpointGuard.js.map +1 -0
  233. package/dist/mcpRoutes.js +1 -1
  234. package/dist/mcpRoutes.js.map +1 -1
  235. package/dist/oauth.d.ts +13 -0
  236. package/dist/oauth.js +53 -9
  237. package/dist/oauth.js.map +1 -1
  238. package/dist/orchestrator/childBridgeClient.d.ts +7 -0
  239. package/dist/orchestrator/childBridgeClient.js +42 -1
  240. package/dist/orchestrator/childBridgeClient.js.map +1 -1
  241. package/dist/orchestrator/orchestratorBridge.d.ts +33 -0
  242. package/dist/orchestrator/orchestratorBridge.js +75 -9
  243. package/dist/orchestrator/orchestratorBridge.js.map +1 -1
  244. package/dist/patchworkConfig.d.ts +4 -0
  245. package/dist/patchworkConfig.js +33 -4
  246. package/dist/patchworkConfig.js.map +1 -1
  247. package/dist/probe.js +39 -5
  248. package/dist/probe.js.map +1 -1
  249. package/dist/recipeOrchestration.d.ts +59 -0
  250. package/dist/recipeOrchestration.js +314 -60
  251. package/dist/recipeOrchestration.js.map +1 -1
  252. package/dist/recipeRoutes.d.ts +93 -0
  253. package/dist/recipeRoutes.js +664 -27
  254. package/dist/recipeRoutes.js.map +1 -1
  255. package/dist/recipes/RecipeOrchestrator.d.ts +14 -0
  256. package/dist/recipes/RecipeOrchestrator.js +42 -2
  257. package/dist/recipes/RecipeOrchestrator.js.map +1 -1
  258. package/dist/recipes/agentExecutor.d.ts +51 -2
  259. package/dist/recipes/agentExecutor.js +86 -13
  260. package/dist/recipes/agentExecutor.js.map +1 -1
  261. package/dist/recipes/chainedRunner.d.ts +111 -3
  262. package/dist/recipes/chainedRunner.js +440 -45
  263. package/dist/recipes/chainedRunner.js.map +1 -1
  264. package/dist/recipes/compiler.js +42 -29
  265. package/dist/recipes/compiler.js.map +1 -1
  266. package/dist/recipes/connectorPreflight.js +31 -0
  267. package/dist/recipes/connectorPreflight.js.map +1 -1
  268. package/dist/recipes/dependencyGraph.d.ts +16 -0
  269. package/dist/recipes/dependencyGraph.js +37 -2
  270. package/dist/recipes/dependencyGraph.js.map +1 -1
  271. package/dist/recipes/eventTriggerPrograms.d.ts +36 -0
  272. package/dist/recipes/eventTriggerPrograms.js +158 -0
  273. package/dist/recipes/eventTriggerPrograms.js.map +1 -0
  274. package/dist/recipes/githubInstallSource.d.ts +9 -2
  275. package/dist/recipes/githubInstallSource.js +36 -6
  276. package/dist/recipes/githubInstallSource.js.map +1 -1
  277. package/dist/recipes/haltCategory.d.ts +10 -0
  278. package/dist/recipes/haltCategory.js +11 -1
  279. package/dist/recipes/haltCategory.js.map +1 -1
  280. package/dist/recipes/idempotencyKey.d.ts +30 -3
  281. package/dist/recipes/idempotencyKey.js +89 -5
  282. package/dist/recipes/idempotencyKey.js.map +1 -1
  283. package/dist/recipes/installer.js +12 -35
  284. package/dist/recipes/installer.js.map +1 -1
  285. package/dist/recipes/judgeVerdict.d.ts +11 -1
  286. package/dist/recipes/judgeVerdict.js +47 -7
  287. package/dist/recipes/judgeVerdict.js.map +1 -1
  288. package/dist/recipes/names.d.ts +5 -0
  289. package/dist/recipes/names.js +10 -5
  290. package/dist/recipes/names.js.map +1 -1
  291. package/dist/recipes/parser.js +74 -10
  292. package/dist/recipes/parser.js.map +1 -1
  293. package/dist/recipes/pricing/costRouter.d.ts +43 -0
  294. package/dist/recipes/pricing/costRouter.js +44 -0
  295. package/dist/recipes/pricing/costRouter.js.map +1 -0
  296. package/dist/recipes/pricing/priceTable.d.ts +76 -0
  297. package/dist/recipes/pricing/priceTable.js +144 -0
  298. package/dist/recipes/pricing/priceTable.js.map +1 -0
  299. package/dist/recipes/replayRun.js +5 -2
  300. package/dist/recipes/replayRun.js.map +1 -1
  301. package/dist/recipes/resolveRecipePath.js +15 -1
  302. package/dist/recipes/resolveRecipePath.js.map +1 -1
  303. package/dist/recipes/runBudget.d.ts +93 -32
  304. package/dist/recipes/runBudget.js +213 -49
  305. package/dist/recipes/runBudget.js.map +1 -1
  306. package/dist/recipes/runRegistry.d.ts +32 -0
  307. package/dist/recipes/runRegistry.js +54 -0
  308. package/dist/recipes/runRegistry.js.map +1 -0
  309. package/dist/recipes/scheduler.js +6 -2
  310. package/dist/recipes/scheduler.js.map +1 -1
  311. package/dist/recipes/schema.d.ts +59 -6
  312. package/dist/recipes/schemaGenerator.d.ts +9 -0
  313. package/dist/recipes/schemaGenerator.js +446 -77
  314. package/dist/recipes/schemaGenerator.js.map +1 -1
  315. package/dist/recipes/simulation/aggregateRunRisk.d.ts +46 -0
  316. package/dist/recipes/simulation/aggregateRunRisk.js +120 -0
  317. package/dist/recipes/simulation/aggregateRunRisk.js.map +1 -0
  318. package/dist/recipes/simulation/costProjector.d.ts +32 -0
  319. package/dist/recipes/simulation/costProjector.js +194 -0
  320. package/dist/recipes/simulation/costProjector.js.map +1 -0
  321. package/dist/recipes/simulation/sideEffects.d.ts +32 -0
  322. package/dist/recipes/simulation/sideEffects.js +62 -0
  323. package/dist/recipes/simulation/sideEffects.js.map +1 -0
  324. package/dist/recipes/simulation/simulate.d.ts +36 -0
  325. package/dist/recipes/simulation/simulate.js +264 -0
  326. package/dist/recipes/simulation/simulate.js.map +1 -0
  327. package/dist/recipes/simulation/simulateMockedRun.d.ts +52 -0
  328. package/dist/recipes/simulation/simulateMockedRun.js +84 -0
  329. package/dist/recipes/simulation/simulateMockedRun.js.map +1 -0
  330. package/dist/recipes/simulation/synthesizeMockedOutputs.d.ts +31 -0
  331. package/dist/recipes/simulation/synthesizeMockedOutputs.js +50 -0
  332. package/dist/recipes/simulation/synthesizeMockedOutputs.js.map +1 -0
  333. package/dist/recipes/simulation/types.d.ts +198 -0
  334. package/dist/recipes/simulation/types.js +30 -0
  335. package/dist/recipes/simulation/types.js.map +1 -0
  336. package/dist/recipes/stepObservation.d.ts +12 -0
  337. package/dist/recipes/stepObservation.js +21 -1
  338. package/dist/recipes/stepObservation.js.map +1 -1
  339. package/dist/recipes/toolRegistry.js +61 -24
  340. package/dist/recipes/toolRegistry.js.map +1 -1
  341. package/dist/recipes/tools/airtable.d.ts +15 -0
  342. package/dist/recipes/tools/airtable.js +240 -0
  343. package/dist/recipes/tools/airtable.js.map +1 -0
  344. package/dist/recipes/tools/caldiy.d.ts +13 -0
  345. package/dist/recipes/tools/caldiy.js +215 -0
  346. package/dist/recipes/tools/caldiy.js.map +1 -0
  347. package/dist/recipes/tools/circleci.d.ts +10 -0
  348. package/dist/recipes/tools/circleci.js +204 -0
  349. package/dist/recipes/tools/circleci.js.map +1 -0
  350. package/dist/recipes/tools/cloudflare.d.ts +13 -0
  351. package/dist/recipes/tools/cloudflare.js +211 -0
  352. package/dist/recipes/tools/cloudflare.js.map +1 -0
  353. package/dist/recipes/tools/confluence.js +11 -10
  354. package/dist/recipes/tools/confluence.js.map +1 -1
  355. package/dist/recipes/tools/datadog.js +13 -12
  356. package/dist/recipes/tools/datadog.js.map +1 -1
  357. package/dist/recipes/tools/docs.d.ts +18 -0
  358. package/dist/recipes/tools/docs.js +95 -0
  359. package/dist/recipes/tools/docs.js.map +1 -0
  360. package/dist/recipes/tools/elasticsearch.d.ts +11 -0
  361. package/dist/recipes/tools/elasticsearch.js +157 -0
  362. package/dist/recipes/tools/elasticsearch.js.map +1 -0
  363. package/dist/recipes/tools/fanOut.js +4 -4
  364. package/dist/recipes/tools/fanOut.js.map +1 -1
  365. package/dist/recipes/tools/figma.d.ts +12 -0
  366. package/dist/recipes/tools/figma.js +195 -0
  367. package/dist/recipes/tools/figma.js.map +1 -0
  368. package/dist/recipes/tools/file.js +43 -22
  369. package/dist/recipes/tools/file.js.map +1 -1
  370. package/dist/recipes/tools/github.js +162 -5
  371. package/dist/recipes/tools/github.js.map +1 -1
  372. package/dist/recipes/tools/gmail.js +45 -33
  373. package/dist/recipes/tools/gmail.js.map +1 -1
  374. package/dist/recipes/tools/grafana.d.ts +11 -0
  375. package/dist/recipes/tools/grafana.js +216 -0
  376. package/dist/recipes/tools/grafana.js.map +1 -0
  377. package/dist/recipes/tools/http.d.ts +1 -1
  378. package/dist/recipes/tools/http.js +10 -28
  379. package/dist/recipes/tools/http.js.map +1 -1
  380. package/dist/recipes/tools/hubspot.js +13 -12
  381. package/dist/recipes/tools/hubspot.js.map +1 -1
  382. package/dist/recipes/tools/index.d.ts +28 -0
  383. package/dist/recipes/tools/index.js +28 -0
  384. package/dist/recipes/tools/index.js.map +1 -1
  385. package/dist/recipes/tools/intercom.js +11 -10
  386. package/dist/recipes/tools/intercom.js.map +1 -1
  387. package/dist/recipes/tools/jira.js +13 -0
  388. package/dist/recipes/tools/jira.js.map +1 -1
  389. package/dist/recipes/tools/meetingNotes.js +2 -2
  390. package/dist/recipes/tools/monday.d.ts +22 -0
  391. package/dist/recipes/tools/monday.js +242 -0
  392. package/dist/recipes/tools/monday.js.map +1 -0
  393. package/dist/recipes/tools/mongodb.d.ts +19 -0
  394. package/dist/recipes/tools/mongodb.js +194 -0
  395. package/dist/recipes/tools/mongodb.js.map +1 -0
  396. package/dist/recipes/tools/notion.js +11 -10
  397. package/dist/recipes/tools/notion.js.map +1 -1
  398. package/dist/recipes/tools/obsidian.d.ts +15 -0
  399. package/dist/recipes/tools/obsidian.js +173 -0
  400. package/dist/recipes/tools/obsidian.js.map +1 -0
  401. package/dist/recipes/tools/outcomes.d.ts +15 -0
  402. package/dist/recipes/tools/outcomes.js +111 -0
  403. package/dist/recipes/tools/outcomes.js.map +1 -0
  404. package/dist/recipes/tools/paystack.d.ts +11 -0
  405. package/dist/recipes/tools/paystack.js +212 -0
  406. package/dist/recipes/tools/paystack.js.map +1 -0
  407. package/dist/recipes/tools/pipedrive.d.ts +16 -0
  408. package/dist/recipes/tools/pipedrive.js +234 -0
  409. package/dist/recipes/tools/pipedrive.js.map +1 -0
  410. package/dist/recipes/tools/postgres.d.ts +15 -0
  411. package/dist/recipes/tools/postgres.js +186 -0
  412. package/dist/recipes/tools/postgres.js.map +1 -0
  413. package/dist/recipes/tools/posthog.d.ts +16 -0
  414. package/dist/recipes/tools/posthog.js +219 -0
  415. package/dist/recipes/tools/posthog.js.map +1 -0
  416. package/dist/recipes/tools/redis.d.ts +9 -0
  417. package/dist/recipes/tools/redis.js +141 -0
  418. package/dist/recipes/tools/redis.js.map +1 -0
  419. package/dist/recipes/tools/resend.d.ts +8 -0
  420. package/dist/recipes/tools/resend.js +153 -0
  421. package/dist/recipes/tools/resend.js.map +1 -0
  422. package/dist/recipes/tools/salesforce.d.ts +16 -0
  423. package/dist/recipes/tools/salesforce.js +184 -0
  424. package/dist/recipes/tools/salesforce.js.map +1 -0
  425. package/dist/recipes/tools/sendgrid.d.ts +9 -0
  426. package/dist/recipes/tools/sendgrid.js +175 -0
  427. package/dist/recipes/tools/sendgrid.js.map +1 -0
  428. package/dist/recipes/tools/shopify.d.ts +16 -0
  429. package/dist/recipes/tools/shopify.js +266 -0
  430. package/dist/recipes/tools/shopify.js.map +1 -0
  431. package/dist/recipes/tools/snowflake.d.ts +16 -0
  432. package/dist/recipes/tools/snowflake.js +173 -0
  433. package/dist/recipes/tools/snowflake.js.map +1 -0
  434. package/dist/recipes/tools/stripe.js +13 -12
  435. package/dist/recipes/tools/stripe.js.map +1 -1
  436. package/dist/recipes/tools/supabase.d.ts +9 -0
  437. package/dist/recipes/tools/supabase.js +133 -0
  438. package/dist/recipes/tools/supabase.js.map +1 -0
  439. package/dist/recipes/tools/todoist.d.ts +15 -0
  440. package/dist/recipes/tools/todoist.js +228 -0
  441. package/dist/recipes/tools/todoist.js.map +1 -0
  442. package/dist/recipes/tools/twilio.d.ts +11 -0
  443. package/dist/recipes/tools/twilio.js +180 -0
  444. package/dist/recipes/tools/twilio.js.map +1 -0
  445. package/dist/recipes/tools/vercel.d.ts +9 -0
  446. package/dist/recipes/tools/vercel.js +146 -0
  447. package/dist/recipes/tools/vercel.js.map +1 -0
  448. package/dist/recipes/tools/webflow.d.ts +11 -0
  449. package/dist/recipes/tools/webflow.js +244 -0
  450. package/dist/recipes/tools/webflow.js.map +1 -0
  451. package/dist/recipes/tools/woocommerce.d.ts +18 -0
  452. package/dist/recipes/tools/woocommerce.js +260 -0
  453. package/dist/recipes/tools/woocommerce.js.map +1 -0
  454. package/dist/recipes/tools/wrapConnectorExecute.d.ts +25 -0
  455. package/dist/recipes/tools/wrapConnectorExecute.js +37 -0
  456. package/dist/recipes/tools/wrapConnectorExecute.js.map +1 -0
  457. package/dist/recipes/tools/zendesk.js +11 -10
  458. package/dist/recipes/tools/zendesk.js.map +1 -1
  459. package/dist/recipes/triggerVars.d.ts +15 -0
  460. package/dist/recipes/triggerVars.js +57 -0
  461. package/dist/recipes/triggerVars.js.map +1 -0
  462. package/dist/recipes/validation.d.ts +1 -1
  463. package/dist/recipes/validation.js +536 -12
  464. package/dist/recipes/validation.js.map +1 -1
  465. package/dist/recipes/yamlPositions.d.ts +1 -1
  466. package/dist/recipes/yamlPositions.js +7 -2
  467. package/dist/recipes/yamlPositions.js.map +1 -1
  468. package/dist/recipes/yamlRunner.d.ts +216 -4
  469. package/dist/recipes/yamlRunner.js +1030 -96
  470. package/dist/recipes/yamlRunner.js.map +1 -1
  471. package/dist/recipesHttp.d.ts +5 -0
  472. package/dist/recipesHttp.js +68 -6
  473. package/dist/recipesHttp.js.map +1 -1
  474. package/dist/resources.js +14 -1
  475. package/dist/resources.js.map +1 -1
  476. package/dist/riskSignals.d.ts +42 -0
  477. package/dist/riskSignals.js +197 -0
  478. package/dist/riskSignals.js.map +1 -0
  479. package/dist/riskTier.d.ts +9 -0
  480. package/dist/riskTier.js +48 -0
  481. package/dist/riskTier.js.map +1 -1
  482. package/dist/runLog.d.ts +53 -0
  483. package/dist/runLog.js +75 -16
  484. package/dist/runLog.js.map +1 -1
  485. package/dist/schemas/recipe.v1.json +360 -16
  486. package/dist/server.d.ts +60 -0
  487. package/dist/server.js +192 -31
  488. package/dist/server.js.map +1 -1
  489. package/dist/sessionCheckpoint.js +4 -1
  490. package/dist/sessionCheckpoint.js.map +1 -1
  491. package/dist/sessionDetail.d.ts +43 -0
  492. package/dist/sessionDetail.js +59 -0
  493. package/dist/sessionDetail.js.map +1 -0
  494. package/dist/settingsEnv.d.ts +47 -0
  495. package/dist/settingsEnv.js +88 -0
  496. package/dist/settingsEnv.js.map +1 -0
  497. package/dist/streamableHttp.js +44 -10
  498. package/dist/streamableHttp.js.map +1 -1
  499. package/dist/ta/backtest/abnday.d.ts +53 -0
  500. package/dist/ta/backtest/abnday.js +123 -0
  501. package/dist/ta/backtest/abnday.js.map +1 -0
  502. package/dist/ta/backtest/directional.d.ts +48 -0
  503. package/dist/ta/backtest/directional.js +103 -0
  504. package/dist/ta/backtest/directional.js.map +1 -0
  505. package/dist/ta/backtest/fundx.d.ts +67 -0
  506. package/dist/ta/backtest/fundx.js +264 -0
  507. package/dist/ta/backtest/fundx.js.map +1 -0
  508. package/dist/ta/backtest/fvg.d.ts +62 -0
  509. package/dist/ta/backtest/fvg.js +236 -0
  510. package/dist/ta/backtest/fvg.js.map +1 -0
  511. package/dist/ta/backtest/ichimoku.d.ts +41 -0
  512. package/dist/ta/backtest/ichimoku.js +146 -0
  513. package/dist/ta/backtest/ichimoku.js.map +1 -0
  514. package/dist/ta/backtest/ingest.d.ts +20 -0
  515. package/dist/ta/backtest/ingest.js +104 -0
  516. package/dist/ta/backtest/ingest.js.map +1 -0
  517. package/dist/ta/backtest/msb.d.ts +49 -0
  518. package/dist/ta/backtest/msb.js +162 -0
  519. package/dist/ta/backtest/msb.js.map +1 -0
  520. package/dist/ta/backtest/scoring.d.ts +33 -0
  521. package/dist/ta/backtest/scoring.js +82 -0
  522. package/dist/ta/backtest/scoring.js.map +1 -0
  523. package/dist/ta/backtest/summary.d.ts +20 -0
  524. package/dist/ta/backtest/summary.js +42 -0
  525. package/dist/ta/backtest/summary.js.map +1 -0
  526. package/dist/ta/backtest/tsmom.d.ts +72 -0
  527. package/dist/ta/backtest/tsmom.js +202 -0
  528. package/dist/ta/backtest/tsmom.js.map +1 -0
  529. package/dist/ta/backtest/volsq.d.ts +34 -0
  530. package/dist/ta/backtest/volsq.js +154 -0
  531. package/dist/ta/backtest/volsq.js.map +1 -0
  532. package/dist/ta/backtest/walkForward.d.ts +45 -0
  533. package/dist/ta/backtest/walkForward.js +136 -0
  534. package/dist/ta/backtest/walkForward.js.map +1 -0
  535. package/dist/ta/cycles.d.ts +15 -0
  536. package/dist/ta/cycles.js +31 -0
  537. package/dist/ta/cycles.js.map +1 -0
  538. package/dist/ta/desk/accrualEmitter.d.ts +59 -0
  539. package/dist/ta/desk/accrualEmitter.js +136 -0
  540. package/dist/ta/desk/accrualEmitter.js.map +1 -0
  541. package/dist/ta/desk/cellBacktest.d.ts +179 -0
  542. package/dist/ta/desk/cellBacktest.js +514 -0
  543. package/dist/ta/desk/cellBacktest.js.map +1 -0
  544. package/dist/ta/desk/cells/wpMaRejection.d.ts +49 -0
  545. package/dist/ta/desk/cells/wpMaRejection.js +141 -0
  546. package/dist/ta/desk/cells/wpMaRejection.js.map +1 -0
  547. package/dist/ta/desk/cells/wpVolumeClimax.d.ts +57 -0
  548. package/dist/ta/desk/cells/wpVolumeClimax.js +193 -0
  549. package/dist/ta/desk/cells/wpVolumeClimax.js.map +1 -0
  550. package/dist/ta/desk/collectors.d.ts +18 -0
  551. package/dist/ta/desk/collectors.js +450 -0
  552. package/dist/ta/desk/collectors.js.map +1 -0
  553. package/dist/ta/desk/contract.d.ts +51 -0
  554. package/dist/ta/desk/contract.js +261 -0
  555. package/dist/ta/desk/contract.js.map +1 -0
  556. package/dist/ta/desk/deskLedger.d.ts +36 -0
  557. package/dist/ta/desk/deskLedger.js +239 -0
  558. package/dist/ta/desk/deskLedger.js.map +1 -0
  559. package/dist/ta/desk/liqTape.d.ts +37 -0
  560. package/dist/ta/desk/liqTape.js +99 -0
  561. package/dist/ta/desk/liqTape.js.map +1 -0
  562. package/dist/ta/desk/post.d.ts +20 -0
  563. package/dist/ta/desk/post.js +65 -0
  564. package/dist/ta/desk/post.js.map +1 -0
  565. package/dist/ta/desk/render.d.ts +25 -0
  566. package/dist/ta/desk/render.js +398 -0
  567. package/dist/ta/desk/render.js.map +1 -0
  568. package/dist/ta/desk/surfaces.d.ts +216 -0
  569. package/dist/ta/desk/surfaces.js +472 -0
  570. package/dist/ta/desk/surfaces.js.map +1 -0
  571. package/dist/ta/desk/types.d.ts +174 -0
  572. package/dist/ta/desk/types.js +104 -0
  573. package/dist/ta/desk/types.js.map +1 -0
  574. package/dist/ta/index.d.ts +23 -0
  575. package/dist/ta/index.js +54 -0
  576. package/dist/ta/index.js.map +1 -0
  577. package/dist/ta/ledger.d.ts +44 -0
  578. package/dist/ta/ledger.js +95 -0
  579. package/dist/ta/ledger.js.map +1 -0
  580. package/dist/ta/levels.d.ts +21 -0
  581. package/dist/ta/levels.js +41 -0
  582. package/dist/ta/levels.js.map +1 -0
  583. package/dist/ta/swings.d.ts +30 -0
  584. package/dist/ta/swings.js +83 -0
  585. package/dist/ta/swings.js.map +1 -0
  586. package/dist/ta/types.d.ts +81 -0
  587. package/dist/ta/types.js +20 -0
  588. package/dist/ta/types.js.map +1 -0
  589. package/dist/telemetry.js +20 -9
  590. package/dist/telemetry.js.map +1 -1
  591. package/dist/tokenUsageTracker.d.ts +2 -1
  592. package/dist/tokenUsageTracker.js +38 -20
  593. package/dist/tokenUsageTracker.js.map +1 -1
  594. package/dist/tools/contextBundle.js +13 -9
  595. package/dist/tools/contextBundle.js.map +1 -1
  596. package/dist/tools/ctxGetTaskContext.js +8 -3
  597. package/dist/tools/ctxGetTaskContext.js.map +1 -1
  598. package/dist/tools/ctxQueryTraces.d.ts +18 -1
  599. package/dist/tools/ctxQueryTraces.js +176 -27
  600. package/dist/tools/ctxQueryTraces.js.map +1 -1
  601. package/dist/tools/enrichCommit.js +5 -2
  602. package/dist/tools/enrichCommit.js.map +1 -1
  603. package/dist/tools/getDependencyTree.js +2 -2
  604. package/dist/tools/getDependencyTree.js.map +1 -1
  605. package/dist/tools/getDiagnostics.js +3 -0
  606. package/dist/tools/getDiagnostics.js.map +1 -1
  607. package/dist/tools/getDocumentSymbols.js +5 -1
  608. package/dist/tools/getDocumentSymbols.js.map +1 -1
  609. package/dist/tools/getGitHotspots.js +12 -1
  610. package/dist/tools/getGitHotspots.js.map +1 -1
  611. package/dist/tools/getGitLog.js +5 -2
  612. package/dist/tools/getGitLog.js.map +1 -1
  613. package/dist/tools/getGitStatus.js +6 -1
  614. package/dist/tools/getGitStatus.js.map +1 -1
  615. package/dist/tools/getProjectContext.js +3 -1
  616. package/dist/tools/getProjectContext.js.map +1 -1
  617. package/dist/tools/getToolCapabilities.js +39 -4
  618. package/dist/tools/getToolCapabilities.js.map +1 -1
  619. package/dist/tools/gitWrite.js +12 -3
  620. package/dist/tools/gitWrite.js.map +1 -1
  621. package/dist/tools/github/composite.d.ts +1 -1
  622. package/dist/tools/github/composite.js +7 -7
  623. package/dist/tools/github/composite.js.map +1 -1
  624. package/dist/tools/github/pr.d.ts +6 -6
  625. package/dist/tools/github/pr.js +18 -11
  626. package/dist/tools/github/pr.js.map +1 -1
  627. package/dist/tools/httpClient.js +55 -3
  628. package/dist/tools/httpClient.js.map +1 -1
  629. package/dist/tools/index.d.ts +2 -1
  630. package/dist/tools/index.js +19 -12
  631. package/dist/tools/index.js.map +1 -1
  632. package/dist/tools/lsp.d.ts +6 -0
  633. package/dist/tools/lsp.js +81 -12
  634. package/dist/tools/lsp.js.map +1 -1
  635. package/dist/tools/previewEdit.js +16 -1
  636. package/dist/tools/previewEdit.js.map +1 -1
  637. package/dist/tools/resumeClaudeTask.js +17 -0
  638. package/dist/tools/resumeClaudeTask.js.map +1 -1
  639. package/dist/tools/searchAndReplace.js +9 -5
  640. package/dist/tools/searchAndReplace.js.map +1 -1
  641. package/dist/tools/searchTools.d.ts +26 -0
  642. package/dist/tools/searchTools.js +42 -2
  643. package/dist/tools/searchTools.js.map +1 -1
  644. package/dist/tools/terminal.js +23 -51
  645. package/dist/tools/terminal.js.map +1 -1
  646. package/dist/tools/transaction.d.ts +9 -1
  647. package/dist/tools/transaction.js +59 -4
  648. package/dist/tools/transaction.js.map +1 -1
  649. package/dist/tools/utils.d.ts +16 -0
  650. package/dist/tools/utils.js +77 -7
  651. package/dist/tools/utils.js.map +1 -1
  652. package/dist/transport.d.ts +31 -0
  653. package/dist/transport.js +117 -14
  654. package/dist/transport.js.map +1 -1
  655. package/dist/wireHaltPushDispatch.js +12 -0
  656. package/dist/wireHaltPushDispatch.js.map +1 -1
  657. package/dist/workerGateDecisionLog.d.ts +141 -0
  658. package/dist/workerGateDecisionLog.js +361 -0
  659. package/dist/workerGateDecisionLog.js.map +1 -0
  660. package/dist/workers/actionClass.d.ts +38 -0
  661. package/dist/workers/actionClass.js +155 -0
  662. package/dist/workers/actionClass.js.map +1 -0
  663. package/dist/workers/backtest.d.ts +68 -0
  664. package/dist/workers/backtest.js +113 -0
  665. package/dist/workers/backtest.js.map +1 -0
  666. package/dist/workers/contextRisk.d.ts +35 -0
  667. package/dist/workers/contextRisk.js +20 -0
  668. package/dist/workers/contextRisk.js.map +1 -0
  669. package/dist/workers/contextRiskScorer.d.ts +48 -0
  670. package/dist/workers/contextRiskScorer.js +94 -0
  671. package/dist/workers/contextRiskScorer.js.map +1 -0
  672. package/dist/workers/graduation.d.ts +61 -0
  673. package/dist/workers/graduation.js +112 -0
  674. package/dist/workers/graduation.js.map +1 -0
  675. package/dist/workers/outcomeStore.d.ts +98 -0
  676. package/dist/workers/outcomeStore.js +146 -0
  677. package/dist/workers/outcomeStore.js.map +1 -0
  678. package/dist/workers/outcomesCli.d.ts +34 -0
  679. package/dist/workers/outcomesCli.js +91 -0
  680. package/dist/workers/outcomesCli.js.map +1 -0
  681. package/dist/workers/runWorkerShadow.d.ts +90 -0
  682. package/dist/workers/runWorkerShadow.js +323 -0
  683. package/dist/workers/runWorkerShadow.js.map +1 -0
  684. package/dist/workers/shadowGate.d.ts +30 -0
  685. package/dist/workers/shadowGate.js +58 -0
  686. package/dist/workers/shadowGate.js.map +1 -0
  687. package/dist/workers/shadowObserver.d.ts +165 -0
  688. package/dist/workers/shadowObserver.js +203 -0
  689. package/dist/workers/shadowObserver.js.map +1 -0
  690. package/dist/workers/shadowReport.d.ts +16 -0
  691. package/dist/workers/shadowReport.js +61 -0
  692. package/dist/workers/shadowReport.js.map +1 -0
  693. package/dist/workers/shadowRun.d.ts +38 -0
  694. package/dist/workers/shadowRun.js +44 -0
  695. package/dist/workers/shadowRun.js.map +1 -0
  696. package/dist/workers/trustLevel.d.ts +82 -0
  697. package/dist/workers/trustLevel.js +100 -0
  698. package/dist/workers/trustLevel.js.map +1 -0
  699. package/dist/workers/worker.d.ts +49 -0
  700. package/dist/workers/worker.js +88 -0
  701. package/dist/workers/worker.js.map +1 -0
  702. package/dist/workers/workerGate.d.ts +101 -0
  703. package/dist/workers/workerGate.js +210 -0
  704. package/dist/workers/workerGate.js.map +1 -0
  705. package/dist/workers/workerLevelStore.d.ts +33 -0
  706. package/dist/workers/workerLevelStore.js +87 -0
  707. package/dist/workers/workerLevelStore.js.map +1 -0
  708. package/dist/workers/workerLoader.d.ts +7 -0
  709. package/dist/workers/workerLoader.js +31 -0
  710. package/dist/workers/workerLoader.js.map +1 -0
  711. package/dist/writeFileAtomic.js +40 -13
  712. package/dist/writeFileAtomic.js.map +1 -1
  713. package/package.json +14 -6
  714. package/scripts/mcp-stdio-shim.cjs +247 -87
  715. package/templates/CLAUDE.bridge.md +1 -1
  716. package/templates/recipes/dependency-bump.yaml +33 -0
  717. package/templates/recipes/incident-to-pr.yaml +187 -0
  718. package/templates/recipes/outcome-ingester.yaml +53 -0
  719. package/templates/recipes/release-notes.yaml +30 -0
  720. package/templates/recipes/triage-failing-tests-autofile.yaml +152 -0
  721. package/templates/recipes/triage-failing-tests.yaml +38 -0
  722. package/templates/workers/dependency-upkeep.worker.yaml +26 -0
  723. package/templates/workers/release-notes.worker.yaml +17 -0
  724. package/templates/workers/test-guardian.worker.yaml +24 -0
  725. package/deploy/README.md +0 -172
  726. package/deploy/bootstrap-new-vps.sh +0 -364
  727. package/deploy/bootstrap-vps.sh +0 -192
  728. package/deploy/claude-ide-bridge.service.template +0 -67
  729. package/deploy/claude-ide-bridge@.service +0 -31
  730. package/deploy/deploy-dashboard.sh +0 -198
  731. package/deploy/deploy-landing.sh +0 -136
  732. package/deploy/ecosystem.config.js.example +0 -36
  733. package/deploy/install-vps-service.sh +0 -240
  734. package/deploy/macos/README.md +0 -153
  735. package/deploy/macos/com.patchwork.bridge.plist.template +0 -54
  736. package/deploy/macos/com.patchwork.tunnel.plist.template +0 -76
  737. package/deploy/macos/install-mac-bridge.sh +0 -244
  738. package/deploy/macos/uninstall-mac-bridge.sh +0 -22
  739. package/deploy/nginx-claude-bridge.conf.template +0 -129
@@ -5,6 +5,68 @@ import { defaultDeprecationWarn, normalizeRecipeForRuntime, } from "./migrations
5
5
  import { RECIPE_NAME_RE, RECIPE_VAR_NAME_RE, RESERVED_VAR_NAMES, } from "./names.js";
6
6
  import { generateSchemaSet } from "./schemaGenerator.js";
7
7
  import { listToolOutputContextKeys } from "./toolRegistry.js";
8
+ /** Driver ids a `downshift` candidate may name (mirrors the JSON-schema enum). */
9
+ const DOWNSHIFT_KNOWN_DRIVERS = new Set([
10
+ "claude",
11
+ "claude-code",
12
+ "api",
13
+ "openai",
14
+ "grok",
15
+ "gemini",
16
+ "anthropic",
17
+ "local",
18
+ ]);
19
+ /**
20
+ * Validate an ordered route-candidate list (`downshift` / `escalate`): an array
21
+ * of `{driver?, model?}` where each entry sets at least one field and any
22
+ * `driver` is dispatch-known. Shared by both routing fields so they stay in
23
+ * lockstep. Codes are field-prefixed (`downshift-type`, `escalate-type`, …).
24
+ * (quality-aware-escalation)
25
+ */
26
+ function validateRouteCandidateList(field, value, stepIndex, issues) {
27
+ if (value === undefined)
28
+ return;
29
+ const base = `steps.${stepIndex}.agent.${field}`;
30
+ if (!Array.isArray(value)) {
31
+ issues.push({
32
+ level: "error",
33
+ message: `Step ${stepIndex + 1}: '${field}' must be an array of {driver?, model?} candidates`,
34
+ code: `${field}-type`,
35
+ path: base,
36
+ });
37
+ return;
38
+ }
39
+ value.forEach((entry, di) => {
40
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
41
+ issues.push({
42
+ level: "error",
43
+ message: `Step ${stepIndex + 1}: ${field}[${di}] must be an object with 'driver' and/or 'model'`,
44
+ code: `${field}-entry-type`,
45
+ path: `${base}.${di}`,
46
+ });
47
+ return;
48
+ }
49
+ const e = entry;
50
+ const hasDriver = typeof e.driver === "string";
51
+ const hasModel = typeof e.model === "string";
52
+ if (!hasDriver && !hasModel) {
53
+ issues.push({
54
+ level: "error",
55
+ message: `Step ${stepIndex + 1}: ${field}[${di}] must set at least one of 'driver' or 'model'`,
56
+ code: `${field}-entry-empty`,
57
+ path: `${base}.${di}`,
58
+ });
59
+ }
60
+ if (hasDriver && !DOWNSHIFT_KNOWN_DRIVERS.has(e.driver)) {
61
+ issues.push({
62
+ level: "error",
63
+ message: `Step ${stepIndex + 1}: ${field}[${di}].driver '${String(e.driver)}' is not a known driver`,
64
+ code: `${field}-driver-enum`,
65
+ path: `${base}.${di}.driver`,
66
+ });
67
+ }
68
+ });
69
+ }
8
70
  export function validateRecipeDefinition(recipe) {
9
71
  const issues = [];
10
72
  const normalizedRecipe = normalizeRecipeForValidation(recipe);
@@ -13,6 +75,17 @@ export function validateRecipeDefinition(recipe) {
13
75
  }
14
76
  else {
15
77
  const r = normalizedRecipe;
78
+ // Root-level `vars:` is silently dropped at runtime — the runner reads
79
+ // only `trigger.vars` / `trigger.inputs` (PR#259 trap). Warn so the
80
+ // author moves it under `trigger:` instead of debugging empty {{vars}}.
81
+ if (r.vars !== undefined) {
82
+ issues.push({
83
+ level: "warning",
84
+ message: "Top-level 'vars' is ignored at runtime — only 'trigger.vars' / 'trigger.inputs' are read. Move declared variables under 'trigger:'.",
85
+ code: "root-vars-ignored",
86
+ path: "vars",
87
+ });
88
+ }
16
89
  if (!r.name || typeof r.name !== "string") {
17
90
  issues.push({
18
91
  level: "error",
@@ -69,14 +142,16 @@ export function validateRecipeDefinition(recipe) {
69
142
  // src/recipes/scheduler.ts:parseSchedule.
70
143
  const at = trigger.at.trim();
71
144
  const isInterval = /^@every\s+[1-9]\d*\s*(ms|s|m|h)$/i.test(at);
72
- const isCron5 = /^\S+\s+\S+\s+\S+\s+\S+\s+\S+$/.test(at);
73
- if (!isInterval && !isCron5) {
145
+ // Accept 5-field and 6-field cron (M24: node-cron supports 6 fields
146
+ // with a seconds column; reject only if fewer than 5 fields).
147
+ const isCronExpr = /^\S+(?:\s+\S+){4,5}$/.test(at);
148
+ if (!isInterval && !isCronExpr) {
74
149
  issues.push({
75
150
  level: "error",
76
- message: `trigger.at "${at}" is not a valid schedule — expected 5-field cron (e.g. "0 9 * * 1-5") or "@every Ns|Nm|Nh"`,
151
+ message: `trigger.at "${at}" is not a valid schedule — expected 5- or 6-field cron (e.g. "0 9 * * 1-5") or "@every Ns|Nm|Nh"`,
77
152
  });
78
153
  }
79
- else if (isCron5 && !cron.validate(at)) {
154
+ else if (isCronExpr && !cron.validate(at)) {
80
155
  // node-cron catches range/step typos a field-count check
81
156
  // misses — e.g. "0 25 * * *", "* / 5 * * *".
82
157
  issues.push({
@@ -88,6 +163,35 @@ export function validateRecipeDefinition(recipe) {
88
163
  validateTriggerVarsList(trigger.vars, "vars", issues);
89
164
  validateTriggerVarsList(trigger.inputs, "inputs", issues);
90
165
  }
166
+ // M26: parallel:{each} map-reduce is not implemented in chained recipes.
167
+ // Check raw recipe.steps (before flattening) so the parallel wrapper is visible.
168
+ const rawRecipe = recipe && typeof recipe === "object" && !Array.isArray(recipe)
169
+ ? recipe
170
+ : null;
171
+ const isChainedRecipe = rawRecipe?.trigger &&
172
+ typeof rawRecipe.trigger === "object" &&
173
+ !Array.isArray(rawRecipe.trigger) &&
174
+ rawRecipe.trigger.type === "chained";
175
+ if (isChainedRecipe && Array.isArray(rawRecipe?.steps)) {
176
+ const rawSteps = rawRecipe.steps;
177
+ for (let i = 0; i < rawSteps.length; i++) {
178
+ const rawStep = rawSteps[i];
179
+ if (rawStep && typeof rawStep === "object" && !Array.isArray(rawStep)) {
180
+ const rs = rawStep;
181
+ if (rs.parallel &&
182
+ typeof rs.parallel === "object" &&
183
+ !Array.isArray(rs.parallel) &&
184
+ "each" in rs.parallel) {
185
+ issues.push({
186
+ level: "error",
187
+ message: `Step ${i + 1}: parallel:{each} map-reduce is not supported in chained recipes. Use the \`fan_out\` tool step for tool-only loops.`,
188
+ path: `steps.${i}.parallel`,
189
+ code: "chained-parallel-each-unsupported",
190
+ });
191
+ }
192
+ }
193
+ }
194
+ }
91
195
  if (!Array.isArray(r.steps) || r.steps.length === 0) {
92
196
  issues.push({
93
197
  level: "error",
@@ -95,8 +199,25 @@ export function validateRecipeDefinition(recipe) {
95
199
  });
96
200
  }
97
201
  else {
202
+ const seenStepIds = new Set();
98
203
  for (let i = 0; i < r.steps.length; i++) {
99
204
  const step = r.steps[i];
205
+ // Duplicate step ids break dependency wiring (`awaits:`, `into:`
206
+ // overwrite, output keying) — the runner keys outputs by id and a
207
+ // second step with the same id silently clobbers the first. Reject.
208
+ if (typeof step.id === "string") {
209
+ if (seenStepIds.has(step.id)) {
210
+ issues.push({
211
+ level: "error",
212
+ message: `Step ${i + 1}: duplicate step id '${step.id}' — step ids must be unique`,
213
+ code: "duplicate-step-id",
214
+ path: `steps.${i}.id`,
215
+ });
216
+ }
217
+ else {
218
+ seenStepIds.add(step.id);
219
+ }
220
+ }
100
221
  const hasTool = typeof step.tool === "string";
101
222
  const hasAgent = !!step.agent;
102
223
  const hasNestedRecipe = typeof step.recipe === "string" || typeof step.chain === "string";
@@ -106,6 +227,20 @@ export function validateRecipeDefinition(recipe) {
106
227
  message: `Step ${i + 1}: Must have 'tool', 'agent', 'recipe', or 'chain' field`,
107
228
  });
108
229
  }
230
+ // M31: step-level retry: -1 silently skips tool steps (loop body
231
+ // iterates 0 times when retryCount < 0). Reject at lint time.
232
+ if (step.retry !== undefined) {
233
+ const retryVal = step.retry;
234
+ if (typeof retryVal !== "number" ||
235
+ !Number.isInteger(retryVal) ||
236
+ retryVal < 0) {
237
+ issues.push({
238
+ level: "error",
239
+ message: `Step ${i + 1}: retry must be a non-negative integer (got ${JSON.stringify(retryVal)})`,
240
+ path: `steps.${i}.retry`,
241
+ });
242
+ }
243
+ }
109
244
  if (step.agent && typeof step.agent === "object") {
110
245
  const agent = step.agent;
111
246
  if (!agent.prompt || typeof agent.prompt !== "string") {
@@ -114,10 +249,121 @@ export function validateRecipeDefinition(recipe) {
114
249
  message: `Step ${i + 1}: Agent step missing 'prompt'`,
115
250
  });
116
251
  }
252
+ // Tier-1 #8 (audit 2026-06-22): the judge→refine loop is implemented
253
+ // ONLY in the flat runner. A chained recipe (trigger.type:"chained")
254
+ // treats a `kind: judge` step as a plain agent step and silently
255
+ // ignores `max_revisions` — the refinement never runs. Reject at lint
256
+ // time so users aren't misled into thinking it works here.
257
+ if (isChainedRecipe) {
258
+ const mrChained = agent.max_revisions;
259
+ const judgeRequested = agent.kind === "judge" ||
260
+ (typeof mrChained === "number" && mrChained > 0);
261
+ if (judgeRequested) {
262
+ issues.push({
263
+ level: "error",
264
+ message: `Step ${i + 1}: judge→refine (kind: judge / max_revisions) is not supported in chained recipes (trigger.type: chained) — it only runs in the flat runner and would silently no-op here.`,
265
+ code: "chained-judge-unsupported",
266
+ path: `steps.${i}.agent`,
267
+ });
268
+ }
269
+ }
270
+ // `driver: claude|anthropic` routes to the Anthropic API (needs
271
+ // ANTHROPIC_API_KEY), NOT the Claude Code subscription — a common
272
+ // trap. Warn at lint time when the key is absent so the step
273
+ // doesn't silently no-op ("[agent step skipped: ANTHROPIC_API_KEY
274
+ // not set]") at run time. Use `driver: subprocess`/`claude-code`
275
+ // for the subscription path.
276
+ if ((agent.driver === "claude" || agent.driver === "anthropic") &&
277
+ !process.env.ANTHROPIC_API_KEY) {
278
+ issues.push({
279
+ level: "warning",
280
+ message: `Step ${i + 1}: driver '${String(agent.driver)}' uses the Anthropic API but ANTHROPIC_API_KEY is not set — the step will be skipped at run time. Set ANTHROPIC_API_KEY, or use 'driver: subprocess' for the Claude Code subscription.`,
281
+ code: "driver-api-key-required",
282
+ path: `steps.${i}.agent.driver`,
283
+ });
284
+ }
285
+ // OPT-IN judge→refine loop fields. Both `max_revisions` and
286
+ // `on_exhausted` are meaningless without a `kind: "judge"` step
287
+ // that points at an upstream step via `reviews:` — a refine loop
288
+ // needs an agent output to re-run. Flag misuse as an error so
289
+ // `recipe lint` / `doctor` catch it before any run.
290
+ const hasRefineField = agent.max_revisions !== undefined ||
291
+ agent.on_exhausted !== undefined;
292
+ if (hasRefineField) {
293
+ if (agent.kind !== "judge") {
294
+ issues.push({
295
+ level: "error",
296
+ message: `Step ${i + 1}: 'max_revisions'/'on_exhausted' require 'kind: judge' (the judge→refine loop only applies to judge steps)`,
297
+ code: "refine-requires-judge",
298
+ path: `steps.${i}.agent`,
299
+ });
300
+ }
301
+ else if (typeof agent.reviews !== "string" ||
302
+ agent.reviews.length === 0) {
303
+ issues.push({
304
+ level: "error",
305
+ message: `Step ${i + 1}: 'max_revisions'/'on_exhausted' require 'reviews' to be set (the loop re-runs the reviewed step)`,
306
+ code: "refine-requires-reviews",
307
+ path: `steps.${i}.agent`,
308
+ });
309
+ }
310
+ }
311
+ if (agent.max_revisions !== undefined) {
312
+ const mr = agent.max_revisions;
313
+ if (typeof mr !== "number" || !Number.isInteger(mr) || mr < 0) {
314
+ issues.push({
315
+ level: "error",
316
+ message: `Step ${i + 1}: 'max_revisions' must be a non-negative integer (got ${JSON.stringify(mr)})`,
317
+ code: "refine-max-revisions-invalid",
318
+ path: `steps.${i}.agent.max_revisions`,
319
+ });
320
+ }
321
+ }
322
+ if (agent.on_exhausted !== undefined &&
323
+ agent.on_exhausted !== "halt" &&
324
+ agent.on_exhausted !== "proceed") {
325
+ issues.push({
326
+ level: "error",
327
+ message: `Step ${i + 1}: invalid 'on_exhausted' '${String(agent.on_exhausted)}' — must be 'halt' or 'proceed'`,
328
+ code: "refine-on-exhausted-enum",
329
+ path: `steps.${i}.agent.on_exhausted`,
330
+ });
331
+ }
332
+ // OPT-IN routing fallbacks, both ordered {driver?, model?} lists:
333
+ // `downshift` (cost-aware, Phase 4 — go cheaper as budget depletes)
334
+ // and `escalate` (quality-aware — go more capable on judge rejection).
335
+ validateRouteCandidateList("downshift", agent.downshift, i, issues);
336
+ validateRouteCandidateList("escalate", agent.escalate, i, issues);
337
+ }
338
+ // Unconditional risk-enum check. `risk` was previously never
339
+ // validated here (the parser casts it; the JSON-schema enum is
340
+ // gated behind FLAG_SCHEMA_LINT, which defaults off). A typo like
341
+ // `risk: "hgh"` silently fell through the compiler's risk bucketer
342
+ // to `allow` — fail-open. Flag any out-of-enum value as an error so
343
+ // `recipe lint` / `doctor` catch it before a run. Reads risk from
344
+ // the top-level step AND the nested agent object (both forms occur).
345
+ const stepAgent = step.agent && typeof step.agent === "object"
346
+ ? step.agent
347
+ : undefined;
348
+ const riskValue = step.risk ?? stepAgent?.risk;
349
+ if (riskValue !== undefined &&
350
+ riskValue !== "low" &&
351
+ riskValue !== "medium" &&
352
+ riskValue !== "high") {
353
+ issues.push({
354
+ level: "error",
355
+ message: `Step ${i + 1}: invalid risk '${String(riskValue)}' — must be one of: low, medium, high`,
356
+ code: "risk-enum",
357
+ path: `steps.${i}.risk`,
358
+ });
117
359
  }
118
360
  }
361
+ validateAwaitsTargets(r, recipe, issues);
362
+ validateJudgeReviewsTargets(r, issues);
119
363
  validateTemplateReferences(r, issues, collectParallelEachKeys(recipe));
120
364
  }
365
+ validateRecipeBudget(r, issues);
366
+ validateRecipeErrorPolicy(r, issues);
121
367
  }
122
368
  if (isEnabled(FLAG_SCHEMA_LINT)) {
123
369
  issues.push(...validateRecipeSchema(normalizedRecipe));
@@ -136,6 +382,251 @@ export function validateRecipeDefinition(recipe) {
136
382
  errors,
137
383
  };
138
384
  }
385
+ /**
386
+ * Validate the optional recipe `budget` block. Closes a real gap: before
387
+ * cost-routing Phase 3 there was NO budget validation, so `tokensMax: -5` or
388
+ * `tokensMax: "lots"` passed lint silently. A present cap must be a positive
389
+ * number; `onBreach` must be the halt|warn enum.
390
+ */
391
+ function validateRecipeBudget(r, issues) {
392
+ const budget = r.budget;
393
+ if (budget === undefined)
394
+ return;
395
+ if (typeof budget !== "object" || budget === null || Array.isArray(budget)) {
396
+ issues.push({
397
+ level: "error",
398
+ message: "'budget' must be an object",
399
+ path: "budget",
400
+ code: "budget-type",
401
+ });
402
+ return;
403
+ }
404
+ const b = budget;
405
+ for (const key of ["tokensMax", "usdMax"]) {
406
+ const value = b[key];
407
+ if (value === undefined)
408
+ continue;
409
+ if (typeof value !== "number" || !(value > 0)) {
410
+ issues.push({
411
+ level: "error",
412
+ message: `budget.${key} must be a positive number`,
413
+ path: `budget.${key}`,
414
+ code: "budget-positive",
415
+ });
416
+ }
417
+ }
418
+ if (b.onBreach !== undefined &&
419
+ b.onBreach !== "halt" &&
420
+ b.onBreach !== "warn") {
421
+ issues.push({
422
+ level: "error",
423
+ message: "budget.onBreach must be 'halt' or 'warn'",
424
+ path: "budget.onBreach",
425
+ code: "budget-onbreach-enum",
426
+ });
427
+ }
428
+ if (b.estimateUnmeasured !== undefined &&
429
+ typeof b.estimateUnmeasured !== "boolean") {
430
+ issues.push({
431
+ level: "error",
432
+ message: "budget.estimateUnmeasured must be a boolean",
433
+ path: "budget.estimateUnmeasured",
434
+ code: "budget-estimate-type",
435
+ });
436
+ }
437
+ }
438
+ /**
439
+ * Validate the optional recipe `on_error` policy block. Audit 2026-06-03
440
+ * (MEDIUM): the hand-rolled lint never checked it, so a typo'd `fallback`
441
+ * enum, a non-numeric `retry`, or a negative `retry` (which the runtime
442
+ * now clamps — see chainedRunner.withRetry, audit HIGH #8) passed lint
443
+ * silently. Mirrors validateRecipeBudget. `retry` is a non-negative integer;
444
+ * `retryDelay` a non-negative number (ms); `fallback` the documented enum.
445
+ */
446
+ function validateRecipeErrorPolicy(r, issues) {
447
+ const onError = r.on_error;
448
+ if (onError === undefined)
449
+ return;
450
+ if (typeof onError !== "object" ||
451
+ onError === null ||
452
+ Array.isArray(onError)) {
453
+ issues.push({
454
+ level: "error",
455
+ message: "'on_error' must be an object",
456
+ path: "on_error",
457
+ code: "on-error-type",
458
+ });
459
+ return;
460
+ }
461
+ const e = onError;
462
+ if (e.retry !== undefined) {
463
+ if (typeof e.retry !== "number" ||
464
+ !Number.isInteger(e.retry) ||
465
+ e.retry < 0) {
466
+ issues.push({
467
+ level: "error",
468
+ message: "on_error.retry must be a non-negative integer",
469
+ path: "on_error.retry",
470
+ code: "on-error-retry",
471
+ });
472
+ }
473
+ }
474
+ if (e.retryDelay !== undefined) {
475
+ if (typeof e.retryDelay !== "number" ||
476
+ !Number.isFinite(e.retryDelay) ||
477
+ e.retryDelay < 0) {
478
+ issues.push({
479
+ level: "error",
480
+ message: "on_error.retryDelay must be a non-negative number (ms)",
481
+ path: "on_error.retryDelay",
482
+ code: "on-error-retrydelay",
483
+ });
484
+ }
485
+ }
486
+ if (e.fallback !== undefined &&
487
+ e.fallback !== "log_only" &&
488
+ e.fallback !== "abort" &&
489
+ e.fallback !== "deliver_original") {
490
+ issues.push({
491
+ level: "error",
492
+ message: "on_error.fallback must be 'log_only', 'abort', or 'deliver_original'",
493
+ path: "on_error.fallback",
494
+ code: "on-error-fallback-enum",
495
+ });
496
+ }
497
+ }
498
+ /**
499
+ * Validate step `awaits:` targets against the set of declared step ids.
500
+ *
501
+ * An `awaits:` target that matches no real step is a recipe-authoring bug
502
+ * that the runtime can't recover from: the cycle-detection DFS skips the
503
+ * phantom edge (so `hasCycles` stays false), but Kahn's algorithm counts it
504
+ * in the awaiting step's in-degree and never decrements it — so the
505
+ * awaiting step AND all its transitive dependents silently drop out of the
506
+ * topological order, never run, and the run STILL reports success. Flag it
507
+ * statically so `recipe lint` / `doctor` catch it before any run.
508
+ *
509
+ * Known-id collection runs over BOTH:
510
+ * - the NORMALIZED recipe (`normalizedRecipe.steps`) — `awaits:` clauses
511
+ * live here, and `flattenValidationSteps` has already hoisted parallel
512
+ * children to top level (so child ids are present), AND
513
+ * - the RAW recipe (`rawRecipe.steps`) — still carries the `parallel:`
514
+ * CONTAINER ids that flattening drops.
515
+ *
516
+ * The union matters because `awaits: [<parallel-group-id>]` is VALID at
517
+ * runtime: `chainedRunner.expandParallelSteps` rewrites a group-id await to
518
+ * the expanded child ids. Collecting from the raw steps keeps those
519
+ * container ids in `knownIds` so a legitimate group-await isn't flagged,
520
+ * while a genuine typo (`gather2`) — present in neither list — still errors.
521
+ */
522
+ /**
523
+ * Cross-check each judge step's `reviews:` target against the set of resolvable
524
+ * step keys. The runtime resolves `reviews` against ctx (keyed by `into`) first,
525
+ * then falls back to step `id` — a reference matching neither silently reviews
526
+ * nothing. Flag as error when a refine loop is configured (max_revisions set),
527
+ * warning otherwise (the judge still runs; it just has no prior output to review).
528
+ */
529
+ function validateJudgeReviewsTargets(r, issues) {
530
+ const steps = Array.isArray(r.steps)
531
+ ? r.steps
532
+ : [];
533
+ // Collect all resolvable keys: every step's `into` value + every step `id`.
534
+ const resolvable = new Set();
535
+ for (const step of steps) {
536
+ const agent = step.agent && typeof step.agent === "object"
537
+ ? step.agent
538
+ : undefined;
539
+ if (typeof agent?.into === "string")
540
+ resolvable.add(agent.into);
541
+ if (typeof step.id === "string")
542
+ resolvable.add(step.id);
543
+ }
544
+ for (let i = 0; i < steps.length; i++) {
545
+ const step = steps[i];
546
+ const agent = step.agent && typeof step.agent === "object"
547
+ ? step.agent
548
+ : undefined;
549
+ if (!agent || agent.kind !== "judge")
550
+ continue;
551
+ const reviews = agent.reviews;
552
+ if (typeof reviews !== "string" || reviews.length === 0)
553
+ continue;
554
+ if (resolvable.has(reviews))
555
+ continue;
556
+ const hasRefineLoop = agent.max_revisions !== undefined || agent.on_exhausted !== undefined;
557
+ issues.push({
558
+ level: hasRefineLoop ? "error" : "warning",
559
+ message: `Step ${i + 1}: 'reviews: ${reviews}' does not match any step into key or step id — the judge would review nothing at run time`,
560
+ code: "judge-reviews-unresolved",
561
+ path: `steps.${i}.agent.reviews`,
562
+ });
563
+ }
564
+ }
565
+ function validateAwaitsTargets(normalizedRecipe, rawRecipe, issues) {
566
+ const steps = Array.isArray(normalizedRecipe.steps)
567
+ ? normalizedRecipe.steps
568
+ : [];
569
+ if (steps.length === 0)
570
+ return;
571
+ const knownIds = new Set();
572
+ const collectIds = (list) => {
573
+ for (const step of list) {
574
+ if (!step || typeof step !== "object")
575
+ continue;
576
+ if (typeof step.id === "string")
577
+ knownIds.add(step.id);
578
+ if (Array.isArray(step.parallel)) {
579
+ collectIds(step.parallel);
580
+ }
581
+ else if (step.parallel &&
582
+ typeof step.parallel === "object" &&
583
+ Array.isArray(step.parallel.steps)) {
584
+ collectIds(step.parallel.steps);
585
+ }
586
+ }
587
+ };
588
+ // Collect from the flattened (normalized) list first — child ids + any
589
+ // top-level ids.
590
+ collectIds(steps);
591
+ // Then collect from the RAW recipe steps so parallel-group CONTAINER ids
592
+ // (which `flattenValidationSteps` strips) are recognised as valid await
593
+ // targets. `collectIds` recurses into `parallel:` arrays / map-reduce
594
+ // `{steps}` blocks, picking up both the container id and its children.
595
+ if (rawRecipe &&
596
+ typeof rawRecipe === "object" &&
597
+ !Array.isArray(rawRecipe) &&
598
+ Array.isArray(rawRecipe.steps)) {
599
+ collectIds(rawRecipe.steps);
600
+ }
601
+ const checkAwaits = (list, label) => {
602
+ for (let i = 0; i < list.length; i++) {
603
+ const step = list[i];
604
+ if (!step || typeof step !== "object")
605
+ continue;
606
+ if (Array.isArray(step.awaits)) {
607
+ for (const target of step.awaits) {
608
+ if (typeof target === "string" && !knownIds.has(target)) {
609
+ issues.push({
610
+ level: "error",
611
+ message: `${label(i)}: awaits unknown step '${target}' — no step with that id exists. The step (and everything depending on it) would silently never run.`,
612
+ code: "unknown-awaits-target",
613
+ path: `steps.${i}.awaits`,
614
+ });
615
+ }
616
+ }
617
+ }
618
+ if (Array.isArray(step.parallel)) {
619
+ checkAwaits(step.parallel, (j) => `${label(i)}.parallel[${j}]`);
620
+ }
621
+ else if (step.parallel &&
622
+ typeof step.parallel === "object" &&
623
+ Array.isArray(step.parallel.steps)) {
624
+ checkAwaits(step.parallel.steps, (j) => `${label(i)}.parallel.steps[${j}]`);
625
+ }
626
+ }
627
+ };
628
+ checkAwaits(steps, (i) => `Step ${i + 1}`);
629
+ }
139
630
  /**
140
631
  * Validate `trigger.vars` / `trigger.inputs` array entries. Catches names
141
632
  * the runtime template engine can't resolve as `{{var}}` (e.g. spaces,
@@ -271,7 +762,17 @@ function flattenValidationStep(step) {
271
762
  if (otherwiseStep &&
272
763
  typeof otherwiseStep === "object" &&
273
764
  !Array.isArray(otherwiseStep)) {
765
+ // Validate the `otherwise` block.
274
766
  branchSteps.push(...flattenValidationStep(otherwiseStep));
767
+ // Also validate the co-located conditional step fields (if any) — strip
768
+ // `otherwise` so the validator sees only the branch-step shape.
769
+ // Only do this when the entry has keys beyond `otherwise`; a standalone
770
+ // `{ otherwise: {...} }` entry has nothing else to validate and passing
771
+ // an empty object `{}` to the schema validator produces bogus errors.
772
+ const { otherwise: _omit, ...branchWithoutOtherwise } = branchRecord;
773
+ if (Object.keys(branchWithoutOtherwise).length > 0) {
774
+ branchSteps.push(...flattenValidationStep(branchWithoutOtherwise));
775
+ }
275
776
  continue;
276
777
  }
277
778
  branchSteps.push(...flattenValidationStep(branchRecord));
@@ -395,15 +896,29 @@ function collectParallelEachKeys(recipe) {
395
896
  if (!recipe || typeof recipe !== "object" || Array.isArray(recipe))
396
897
  return keys;
397
898
  const steps = recipe.steps;
899
+ collectParallelEachKeysFromSteps(steps, keys);
900
+ return keys;
901
+ }
902
+ /**
903
+ * Walk a steps array, collecting parallel-each loop variable keys. Recurses
904
+ * into `parallel: [...]` group arrays so a nested map-reduce step inside a
905
+ * parallel group still contributes its loop variable (`as`) — otherwise
906
+ * `{{item}}` references in those child steps were flagged as unknown template
907
+ * references, blocking valid recipes (audit 2026-06-10 recipe-validation-4).
908
+ */
909
+ function collectParallelEachKeysFromSteps(steps, keys) {
398
910
  if (!Array.isArray(steps))
399
- return keys;
911
+ return;
400
912
  for (const step of steps) {
401
913
  if (!step || typeof step !== "object" || Array.isArray(step))
402
914
  continue;
403
915
  const s = step;
404
- if (s.parallel &&
405
- typeof s.parallel === "object" &&
406
- !Array.isArray(s.parallel)) {
916
+ if (Array.isArray(s.parallel)) {
917
+ // Parallel GROUP array — recurse into its child steps.
918
+ collectParallelEachKeysFromSteps(s.parallel, keys);
919
+ }
920
+ else if (s.parallel && typeof s.parallel === "object") {
921
+ // Parallel map-reduce (each/as) object.
407
922
  const par = s.parallel;
408
923
  if (typeof par.as === "string")
409
924
  keys.add(par.as);
@@ -411,24 +926,33 @@ function collectParallelEachKeys(recipe) {
411
926
  keys.add(s.id);
412
927
  keys.add(`${s.id}.results`);
413
928
  }
929
+ // Recurse into the map-reduce body so deeper nesting is also covered.
930
+ collectParallelEachKeysFromSteps(par.steps, keys);
414
931
  }
415
932
  // Step-level each: "{{items}}" as: item — loop variable
416
933
  if (typeof s.as === "string")
417
934
  keys.add(s.as);
418
935
  }
419
- return keys;
420
936
  }
421
937
  function validateTemplateReferences(recipe, issues, extraParallelKeys) {
422
938
  const builtinKeys = new Set([
939
+ // Date/time tokens injected at runtime: `date`/`time` on the flat path and
940
+ // `DATE`/`TIME` on the chained dispatch path, plus the YYYY / YYYY-MM /
941
+ // YYYY-MM-DD / ISO_NOW / HH / MM / SS family — now injected on BOTH paths
942
+ // (yamlRunner.ts ctx + dispatchRecipe env), so {{YYYY-MM-DD}} etc. render
943
+ // real values AND pass lint. Keep in sync with the yamlRunner.ts injection
944
+ // sites. (audit 2026-06-10 recipe-validation-1)
423
945
  "date",
424
946
  "time",
947
+ "DATE",
948
+ "TIME",
425
949
  "YYYY",
426
950
  "YYYY-MM",
427
951
  "YYYY-MM-DD",
428
952
  "ISO_NOW",
429
- "HH", // time component in datetime format strings
430
- "MM", // time component in datetime format strings
431
- "SS", // time component in datetime format strings
953
+ "HH",
954
+ "MM",
955
+ "SS",
432
956
  "this", // Handlebars loop current-item reference
433
957
  ]);
434
958
  const availableKeys = new Set(builtinKeys);