smol-symphony 0.2.0 → 0.3.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 (716) hide show
  1. package/AGENTS.md +41 -22
  2. package/DESIGN.md +494 -273
  3. package/README.md +109 -57
  4. package/SPEC.md +33 -24
  5. package/WORKFLOW.minimal.yaml +34 -0
  6. package/{WORKFLOW.template.md → WORKFLOW.template.yaml} +409 -256
  7. package/WORKFLOW.yaml +487 -0
  8. package/assets/skills/symphony-issues/SKILL.md +136 -0
  9. package/assets/symphony-mise.system.toml +68 -0
  10. package/dist/src/bin/symphony.js +30 -0
  11. package/dist/src/bin/symphony.js.map +1 -0
  12. package/dist/src/core/actions/context.js +109 -0
  13. package/dist/src/core/actions/context.js.map +1 -0
  14. package/dist/{actions/parsing.js → src/core/actions/parse.js} +33 -114
  15. package/dist/src/core/actions/parse.js.map +1 -0
  16. package/dist/src/core/actions/plan.js +197 -0
  17. package/dist/src/core/actions/plan.js.map +1 -0
  18. package/dist/src/core/actions/predicates.js +111 -0
  19. package/dist/src/core/actions/predicates.js.map +1 -0
  20. package/dist/src/core/actions/run-fold.js +248 -0
  21. package/dist/src/core/actions/run-fold.js.map +1 -0
  22. package/dist/src/core/actions/template.js +118 -0
  23. package/dist/src/core/actions/template.js.map +1 -0
  24. package/dist/src/core/cli/args.js +116 -0
  25. package/dist/src/core/cli/args.js.map +1 -0
  26. package/dist/src/core/coerce.js +75 -0
  27. package/dist/src/core/coerce.js.map +1 -0
  28. package/dist/src/core/credential/account-id.js +20 -0
  29. package/dist/src/core/credential/account-id.js.map +1 -0
  30. package/dist/src/core/credential/adapter-config.js +136 -0
  31. package/dist/src/core/credential/adapter-config.js.map +1 -0
  32. package/dist/src/core/credential/availability.js +98 -0
  33. package/dist/src/core/credential/availability.js.map +1 -0
  34. package/dist/src/core/credential/extract.js +228 -0
  35. package/dist/src/core/credential/extract.js.map +1 -0
  36. package/dist/src/core/credential/fake-creds.js +171 -0
  37. package/dist/src/core/credential/fake-creds.js.map +1 -0
  38. package/dist/src/core/credential/identity.js +125 -0
  39. package/dist/src/core/credential/identity.js.map +1 -0
  40. package/dist/src/core/credential/shape.js +230 -0
  41. package/dist/src/core/credential/shape.js.map +1 -0
  42. package/dist/src/core/credential/strings.js +15 -0
  43. package/dist/src/core/credential/strings.js.map +1 -0
  44. package/dist/src/core/doctor/checks.js +303 -0
  45. package/dist/src/core/doctor/checks.js.map +1 -0
  46. package/dist/src/core/git/result.js +107 -0
  47. package/dist/src/core/git/result.js.map +1 -0
  48. package/dist/src/core/http/decisions.js +225 -0
  49. package/dist/src/core/http/decisions.js.map +1 -0
  50. package/dist/{http.js → src/core/http/render.js} +472 -738
  51. package/dist/src/core/http/render.js.map +1 -0
  52. package/dist/{http-handlers.js → src/core/http/routes.js} +52 -87
  53. package/dist/src/core/http/routes.js.map +1 -0
  54. package/dist/src/core/http/views.js +181 -0
  55. package/dist/src/core/http/views.js.map +1 -0
  56. package/dist/src/core/image/managed-image.js +95 -0
  57. package/dist/src/core/image/managed-image.js.map +1 -0
  58. package/dist/src/core/issue/file.js +149 -0
  59. package/dist/src/core/issue/file.js.map +1 -0
  60. package/dist/src/core/issue/parse.js +210 -0
  61. package/dist/src/core/issue/parse.js.map +1 -0
  62. package/dist/src/core/mcp/dispatch.js +239 -0
  63. package/dist/src/core/mcp/dispatch.js.map +1 -0
  64. package/dist/src/core/mcp/post-move.js +92 -0
  65. package/dist/src/core/mcp/post-move.js.map +1 -0
  66. package/dist/src/core/mcp/protocol.js +293 -0
  67. package/dist/src/core/mcp/protocol.js.map +1 -0
  68. package/dist/src/core/mcp/url.js +162 -0
  69. package/dist/src/core/mcp/url.js.map +1 -0
  70. package/dist/src/core/path.js +63 -0
  71. package/dist/src/core/path.js.map +1 -0
  72. package/dist/src/core/reconcile/image-decide.js +48 -0
  73. package/dist/src/core/reconcile/image-decide.js.map +1 -0
  74. package/dist/src/core/reconcile/ledger.js +142 -0
  75. package/dist/src/core/reconcile/ledger.js.map +1 -0
  76. package/dist/src/core/reconcile/pr-classify.js +62 -0
  77. package/dist/src/core/reconcile/pr-classify.js.map +1 -0
  78. package/dist/{reconciler → src/core/reconcile}/pr-decide.js +25 -12
  79. package/dist/src/core/reconcile/pr-decide.js.map +1 -0
  80. package/dist/src/core/reconcile/pr-loop.js +161 -0
  81. package/dist/src/core/reconcile/pr-loop.js.map +1 -0
  82. package/dist/src/core/reconcile/pr-notes.js +35 -0
  83. package/dist/src/core/reconcile/pr-notes.js.map +1 -0
  84. package/dist/src/core/reconcile/vm-decide.js +70 -0
  85. package/dist/src/core/reconcile/vm-decide.js.map +1 -0
  86. package/dist/src/core/reconcile/vm-reap.js +207 -0
  87. package/dist/src/core/reconcile/vm-reap.js.map +1 -0
  88. package/dist/src/core/reconcile/workspace-decide.js +162 -0
  89. package/dist/src/core/reconcile/workspace-decide.js.map +1 -0
  90. package/dist/src/core/runlog/summary.js +231 -0
  91. package/dist/src/core/runlog/summary.js.map +1 -0
  92. package/dist/src/core/runner/dispatch-config.js +95 -0
  93. package/dist/src/core/runner/dispatch-config.js.map +1 -0
  94. package/dist/src/core/runner/injection.js +61 -0
  95. package/dist/src/core/runner/injection.js.map +1 -0
  96. package/dist/src/core/runner/mise.js +210 -0
  97. package/dist/src/core/runner/mise.js.map +1 -0
  98. package/dist/src/core/runner/prompt.js +720 -0
  99. package/dist/src/core/runner/prompt.js.map +1 -0
  100. package/dist/src/core/runner/turn.js +242 -0
  101. package/dist/src/core/runner/turn.js.map +1 -0
  102. package/dist/src/core/runner/vm-plan.js +390 -0
  103. package/dist/src/core/runner/vm-plan.js.map +1 -0
  104. package/dist/src/core/schedule/admission.js +123 -0
  105. package/dist/src/core/schedule/admission.js.map +1 -0
  106. package/dist/src/core/schedule/circuit-breaker.js +111 -0
  107. package/dist/src/core/schedule/circuit-breaker.js.map +1 -0
  108. package/dist/src/core/schedule/eligibility.js +83 -0
  109. package/dist/src/core/schedule/eligibility.js.map +1 -0
  110. package/dist/src/core/schedule/reconcile-issue.js +82 -0
  111. package/dist/src/core/schedule/reconcile-issue.js.map +1 -0
  112. package/dist/src/core/schedule/retry.js +96 -0
  113. package/dist/src/core/schedule/retry.js.map +1 -0
  114. package/dist/src/core/schedule/sleep-cycle.js +133 -0
  115. package/dist/src/core/schedule/sleep-cycle.js.map +1 -0
  116. package/dist/src/core/schedule/slots.js +124 -0
  117. package/dist/src/core/schedule/slots.js.map +1 -0
  118. package/dist/src/core/schedule/tick.js +553 -0
  119. package/dist/src/core/schedule/tick.js.map +1 -0
  120. package/dist/src/core/schedule/token-fold.js +181 -0
  121. package/dist/src/core/schedule/token-fold.js.map +1 -0
  122. package/dist/src/core/state-resolve.js +86 -0
  123. package/dist/src/core/state-resolve.js.map +1 -0
  124. package/dist/src/core/vm-guards.js +278 -0
  125. package/dist/src/core/vm-guards.js.map +1 -0
  126. package/dist/src/core/workflow/derive.js +107 -0
  127. package/dist/src/core/workflow/derive.js.map +1 -0
  128. package/dist/src/core/workflow/parse.js +687 -0
  129. package/dist/src/core/workflow/parse.js.map +1 -0
  130. package/dist/src/core/workflow/prompt-probe.js +78 -0
  131. package/dist/src/core/workflow/prompt-probe.js.map +1 -0
  132. package/dist/src/core/workflow/validate.js +189 -0
  133. package/dist/src/core/workflow/validate.js.map +1 -0
  134. package/dist/src/core/workspace-key.js +19 -0
  135. package/dist/src/core/workspace-key.js.map +1 -0
  136. package/dist/src/shell/actions-runner.js +356 -0
  137. package/dist/src/shell/actions-runner.js.map +1 -0
  138. package/dist/src/shell/adapter/adapter-registry.js +45 -0
  139. package/dist/src/shell/adapter/adapter-registry.js.map +1 -0
  140. package/dist/src/shell/adapter/clock-random.js +96 -0
  141. package/dist/src/shell/adapter/clock-random.js.map +1 -0
  142. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js +158 -0
  143. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
  144. package/dist/src/shell/adapter/gondolin-dispatch.js +385 -0
  145. package/dist/src/shell/adapter/gondolin-dispatch.js.map +1 -0
  146. package/dist/src/shell/adapter/gondolin-image-converter.js +233 -0
  147. package/dist/src/shell/adapter/gondolin-image-converter.js.map +1 -0
  148. package/dist/src/shell/adapter/gondolin-image-fetch.js +180 -0
  149. package/dist/src/shell/adapter/gondolin-image-fetch.js.map +1 -0
  150. package/dist/src/shell/adapter/launcher-asset.js +57 -0
  151. package/dist/src/shell/adapter/launcher-asset.js.map +1 -0
  152. package/dist/src/shell/adapter/mise-config-asset.js +65 -0
  153. package/dist/src/shell/adapter/mise-config-asset.js.map +1 -0
  154. package/dist/src/shell/adapter/workflow-loader.js +304 -0
  155. package/dist/src/shell/adapter/workflow-loader.js.map +1 -0
  156. package/dist/src/shell/cli/doctor.js +268 -0
  157. package/dist/src/shell/cli/doctor.js.map +1 -0
  158. package/dist/src/shell/effect-interpreter-families.js +314 -0
  159. package/dist/src/shell/effect-interpreter-families.js.map +1 -0
  160. package/dist/src/shell/effect-interpreter.js +29 -0
  161. package/dist/src/shell/effect-interpreter.js.map +1 -0
  162. package/dist/src/shell/interp/acp-frame.js +137 -0
  163. package/dist/src/shell/interp/acp-frame.js.map +1 -0
  164. package/dist/src/shell/interp/acp-ws-conn.js +320 -0
  165. package/dist/src/shell/interp/acp-ws-conn.js.map +1 -0
  166. package/dist/src/shell/interp/acp-ws-frames.js +159 -0
  167. package/dist/src/shell/interp/acp-ws-frames.js.map +1 -0
  168. package/dist/src/shell/interp/acp-ws.js +197 -0
  169. package/dist/src/shell/interp/acp-ws.js.map +1 -0
  170. package/dist/src/shell/interp/acp.js +319 -0
  171. package/dist/src/shell/interp/acp.js.map +1 -0
  172. package/dist/src/shell/interp/credential-defaults.js +128 -0
  173. package/dist/src/shell/interp/credential-defaults.js.map +1 -0
  174. package/dist/src/shell/interp/credential-hooks.js +149 -0
  175. package/dist/src/shell/interp/credential-hooks.js.map +1 -0
  176. package/dist/src/shell/interp/credential-registry.js +226 -0
  177. package/dist/src/shell/interp/credential-registry.js.map +1 -0
  178. package/dist/src/shell/interp/credential.js +103 -0
  179. package/dist/src/shell/interp/credential.js.map +1 -0
  180. package/dist/src/shell/interp/gh.js +163 -0
  181. package/dist/src/shell/interp/gh.js.map +1 -0
  182. package/dist/src/shell/interp/git.js +28 -0
  183. package/dist/src/shell/interp/git.js.map +1 -0
  184. package/dist/src/shell/interp/log.js +213 -0
  185. package/dist/src/shell/interp/log.js.map +1 -0
  186. package/dist/src/shell/interp/process.js +178 -0
  187. package/dist/src/shell/interp/process.js.map +1 -0
  188. package/dist/src/shell/interp/runlog.js +193 -0
  189. package/dist/src/shell/interp/runlog.js.map +1 -0
  190. package/dist/src/shell/interp/timer.js +64 -0
  191. package/dist/src/shell/interp/timer.js.map +1 -0
  192. package/dist/src/shell/interp/tracker-disk.js +99 -0
  193. package/dist/src/shell/interp/tracker-disk.js.map +1 -0
  194. package/dist/src/shell/interp/tracker-parse.js +71 -0
  195. package/dist/src/shell/interp/tracker-parse.js.map +1 -0
  196. package/dist/src/shell/interp/tracker-scan.js +238 -0
  197. package/dist/src/shell/interp/tracker-scan.js.map +1 -0
  198. package/dist/src/shell/interp/tracker-write.js +91 -0
  199. package/dist/src/shell/interp/tracker-write.js.map +1 -0
  200. package/dist/src/shell/interp/tracker.js +41 -0
  201. package/dist/src/shell/interp/tracker.js.map +1 -0
  202. package/dist/src/shell/interp/tty.js +48 -0
  203. package/dist/src/shell/interp/tty.js.map +1 -0
  204. package/dist/src/shell/interp/vm.js +199 -0
  205. package/dist/src/shell/interp/vm.js.map +1 -0
  206. package/dist/src/shell/interp/workspace.js +310 -0
  207. package/dist/src/shell/interp/workspace.js.map +1 -0
  208. package/dist/src/shell/main-acp.js +78 -0
  209. package/dist/src/shell/main-acp.js.map +1 -0
  210. package/dist/src/shell/main-adapters.js +222 -0
  211. package/dist/src/shell/main-adapters.js.map +1 -0
  212. package/dist/src/shell/main-credential.js +122 -0
  213. package/dist/src/shell/main-credential.js.map +1 -0
  214. package/dist/src/shell/main-doctor.js +22 -0
  215. package/dist/src/shell/main-doctor.js.map +1 -0
  216. package/dist/src/shell/main-entry.js +46 -0
  217. package/dist/src/shell/main-entry.js.map +1 -0
  218. package/dist/src/shell/main-http-csrf.js +45 -0
  219. package/dist/src/shell/main-http-csrf.js.map +1 -0
  220. package/dist/src/shell/main-http-handler.js +389 -0
  221. package/dist/src/shell/main-http-handler.js.map +1 -0
  222. package/dist/src/shell/main-http-mcp.js +122 -0
  223. package/dist/src/shell/main-http-mcp.js.map +1 -0
  224. package/dist/src/shell/main-http-views.js +253 -0
  225. package/dist/src/shell/main-http-views.js.map +1 -0
  226. package/dist/src/shell/main-http.js +76 -0
  227. package/dist/src/shell/main-http.js.map +1 -0
  228. package/dist/src/shell/main-loops.js +130 -0
  229. package/dist/src/shell/main-loops.js.map +1 -0
  230. package/dist/src/shell/main-mcp.js +129 -0
  231. package/dist/src/shell/main-mcp.js.map +1 -0
  232. package/dist/src/shell/main-orchestrator.js +120 -0
  233. package/dist/src/shell/main-orchestrator.js.map +1 -0
  234. package/dist/src/shell/main-preflight.js +43 -0
  235. package/dist/src/shell/main-preflight.js.map +1 -0
  236. package/dist/src/shell/main-reconcilers-helpers.js +244 -0
  237. package/dist/src/shell/main-reconcilers-helpers.js.map +1 -0
  238. package/dist/src/shell/main-reconcilers-pr.js +148 -0
  239. package/dist/src/shell/main-reconcilers-pr.js.map +1 -0
  240. package/dist/src/shell/main-reconcilers.js +225 -0
  241. package/dist/src/shell/main-reconcilers.js.map +1 -0
  242. package/dist/src/shell/main-runner.js +355 -0
  243. package/dist/src/shell/main-runner.js.map +1 -0
  244. package/dist/src/shell/main-scaffold.js +116 -0
  245. package/dist/src/shell/main-scaffold.js.map +1 -0
  246. package/dist/src/shell/main-shutdown.js +115 -0
  247. package/dist/src/shell/main-shutdown.js.map +1 -0
  248. package/dist/src/shell/main-startup.js +48 -0
  249. package/dist/src/shell/main-startup.js.map +1 -0
  250. package/dist/src/shell/main-substrates.js +43 -0
  251. package/dist/src/shell/main-substrates.js.map +1 -0
  252. package/dist/src/shell/main.js +385 -0
  253. package/dist/src/shell/main.js.map +1 -0
  254. package/dist/src/shell/orchestrator-feedback.js +69 -0
  255. package/dist/src/shell/orchestrator-feedback.js.map +1 -0
  256. package/dist/src/shell/orchestrator-image.js +167 -0
  257. package/dist/src/shell/orchestrator-image.js.map +1 -0
  258. package/dist/src/shell/orchestrator-loop.js +468 -0
  259. package/dist/src/shell/orchestrator-loop.js.map +1 -0
  260. package/dist/src/shell/orchestrator-reconcile.js +36 -0
  261. package/dist/src/shell/orchestrator-reconcile.js.map +1 -0
  262. package/dist/src/shell/reconciler-loop.js +228 -0
  263. package/dist/src/shell/reconciler-loop.js.map +1 -0
  264. package/dist/src/shell/runner-loop-turn.js +301 -0
  265. package/dist/src/shell/runner-loop-turn.js.map +1 -0
  266. package/dist/src/shell/runner-loop.js +338 -0
  267. package/dist/src/shell/runner-loop.js.map +1 -0
  268. package/dist/src/shell/server/http.js +208 -0
  269. package/dist/src/shell/server/http.js.map +1 -0
  270. package/dist/src/shell/server/mcp-runtime-effects.js +237 -0
  271. package/dist/src/shell/server/mcp-runtime-effects.js.map +1 -0
  272. package/dist/src/shell/server/mcp-runtime.js +99 -0
  273. package/dist/src/shell/server/mcp-runtime.js.map +1 -0
  274. package/dist/src/shell/workspace-key.js +14 -0
  275. package/dist/src/shell/workspace-key.js.map +1 -0
  276. package/dist/src/types/acp.js +8 -0
  277. package/dist/src/types/acp.js.map +1 -0
  278. package/dist/src/types/actions/plan.js +6 -0
  279. package/dist/src/types/actions/plan.js.map +1 -0
  280. package/dist/src/types/actions/predicates.js +6 -0
  281. package/dist/src/types/actions/predicates.js.map +1 -0
  282. package/dist/src/types/actions/run-fold.js +8 -0
  283. package/dist/src/types/actions/run-fold.js.map +1 -0
  284. package/dist/src/types/actions.js +7 -0
  285. package/dist/src/types/actions.js.map +1 -0
  286. package/dist/src/types/adapter/clock-random.js +4 -0
  287. package/dist/src/types/adapter/clock-random.js.map +1 -0
  288. package/dist/src/types/adapter/gondolin-image-converter.js +5 -0
  289. package/dist/src/types/adapter/gondolin-image-converter.js.map +1 -0
  290. package/dist/src/types/adapter/gondolin-image-fetch.js +5 -0
  291. package/dist/src/types/adapter/gondolin-image-fetch.js.map +1 -0
  292. package/dist/src/types/adapter/workflow-loader.js +4 -0
  293. package/dist/src/types/adapter/workflow-loader.js.map +1 -0
  294. package/dist/src/types/cli/args.js +8 -0
  295. package/dist/src/types/cli/args.js.map +1 -0
  296. package/dist/src/types/config.js +8 -0
  297. package/dist/src/types/config.js.map +1 -0
  298. package/dist/src/types/credential-interp.js +6 -0
  299. package/dist/src/types/credential-interp.js.map +1 -0
  300. package/dist/src/types/credentials.js +10 -0
  301. package/dist/src/types/credentials.js.map +1 -0
  302. package/dist/src/types/doctor.js +7 -0
  303. package/dist/src/types/doctor.js.map +1 -0
  304. package/dist/src/types/domain.js +7 -0
  305. package/dist/src/types/domain.js.map +1 -0
  306. package/dist/src/types/effect.js +15 -0
  307. package/dist/src/types/effect.js.map +1 -0
  308. package/dist/src/types/errors.js +39 -0
  309. package/dist/src/types/errors.js.map +1 -0
  310. package/dist/src/types/http/decisions.js +6 -0
  311. package/dist/src/types/http/decisions.js.map +1 -0
  312. package/dist/src/types/http/render.js +10 -0
  313. package/dist/src/types/http/render.js.map +1 -0
  314. package/dist/src/types/http/views.js +6 -0
  315. package/dist/src/types/http/views.js.map +1 -0
  316. package/dist/src/types/http.js +9 -0
  317. package/dist/src/types/http.js.map +1 -0
  318. package/dist/src/types/image/managed-image.js +7 -0
  319. package/dist/src/types/image/managed-image.js.map +1 -0
  320. package/dist/src/types/interp/effect-interpreter.js +8 -0
  321. package/dist/src/types/interp/effect-interpreter.js.map +1 -0
  322. package/dist/src/types/interp/tracker.js +7 -0
  323. package/dist/src/types/interp/tracker.js.map +1 -0
  324. package/dist/src/types/issue/file.js +6 -0
  325. package/dist/src/types/issue/file.js.map +1 -0
  326. package/dist/src/types/issue/parse.js +8 -0
  327. package/dist/src/types/issue/parse.js.map +1 -0
  328. package/dist/src/types/main-acp.js +13 -0
  329. package/dist/src/types/main-acp.js.map +1 -0
  330. package/dist/src/types/main-adapters.js +5 -0
  331. package/dist/src/types/main-adapters.js.map +1 -0
  332. package/dist/src/types/main-credential.js +21 -0
  333. package/dist/src/types/main-credential.js.map +1 -0
  334. package/dist/src/types/main-doctor.js +6 -0
  335. package/dist/src/types/main-doctor.js.map +1 -0
  336. package/dist/src/types/main-http-handler.js +12 -0
  337. package/dist/src/types/main-http-handler.js.map +1 -0
  338. package/dist/src/types/main-http.js +5 -0
  339. package/dist/src/types/main-http.js.map +1 -0
  340. package/dist/src/types/main-loops.js +5 -0
  341. package/dist/src/types/main-loops.js.map +1 -0
  342. package/dist/src/types/main-mcp.js +12 -0
  343. package/dist/src/types/main-mcp.js.map +1 -0
  344. package/dist/src/types/main-orchestrator.js +5 -0
  345. package/dist/src/types/main-orchestrator.js.map +1 -0
  346. package/dist/src/types/main-reconcilers.js +11 -0
  347. package/dist/src/types/main-reconcilers.js.map +1 -0
  348. package/dist/src/types/main-runner.js +13 -0
  349. package/dist/src/types/main-runner.js.map +1 -0
  350. package/dist/src/types/main-startup.js +5 -0
  351. package/dist/src/types/main-startup.js.map +1 -0
  352. package/dist/src/types/main-substrates.js +5 -0
  353. package/dist/src/types/main-substrates.js.map +1 -0
  354. package/dist/src/types/mcp/dispatch.js +4 -0
  355. package/dist/src/types/mcp/dispatch.js.map +1 -0
  356. package/dist/src/types/mcp/post-move.js +7 -0
  357. package/dist/src/types/mcp/post-move.js.map +1 -0
  358. package/dist/src/types/mcp.js +9 -0
  359. package/dist/src/types/mcp.js.map +1 -0
  360. package/dist/src/types/ports.js +12 -0
  361. package/dist/src/types/ports.js.map +1 -0
  362. package/dist/src/types/reconcile/image-decide.js +5 -0
  363. package/dist/src/types/reconcile/image-decide.js.map +1 -0
  364. package/dist/src/types/reconcile/ledger.js +7 -0
  365. package/dist/src/types/reconcile/ledger.js.map +1 -0
  366. package/dist/src/types/reconcile/pr-loop.js +8 -0
  367. package/dist/src/types/reconcile/pr-loop.js.map +1 -0
  368. package/dist/src/types/reconcile/vm-reap.js +8 -0
  369. package/dist/src/types/reconcile/vm-reap.js.map +1 -0
  370. package/dist/src/types/reconcile/workspace-decide.js +7 -0
  371. package/dist/src/types/reconcile/workspace-decide.js.map +1 -0
  372. package/dist/src/types/reconcile.js +9 -0
  373. package/dist/src/types/reconcile.js.map +1 -0
  374. package/dist/src/types/runlog.js +7 -0
  375. package/dist/src/types/runlog.js.map +1 -0
  376. package/dist/src/types/runner/actions-runner.js +12 -0
  377. package/dist/src/types/runner/actions-runner.js.map +1 -0
  378. package/dist/src/types/runner/gondolin-dispatch.js +5 -0
  379. package/dist/src/types/runner/gondolin-dispatch.js.map +1 -0
  380. package/dist/src/types/runner/injection.js +6 -0
  381. package/dist/src/types/runner/injection.js.map +1 -0
  382. package/dist/src/types/runner/runner-loop.js +5 -0
  383. package/dist/src/types/runner/runner-loop.js.map +1 -0
  384. package/dist/src/types/runner/turn.js +4 -0
  385. package/dist/src/types/runner/turn.js.map +1 -0
  386. package/dist/src/types/runner/vm-plan.js +4 -0
  387. package/dist/src/types/runner/vm-plan.js.map +1 -0
  388. package/dist/src/types/runtime.js +9 -0
  389. package/dist/src/types/runtime.js.map +1 -0
  390. package/dist/src/types/schedule/admission.js +7 -0
  391. package/dist/src/types/schedule/admission.js.map +1 -0
  392. package/dist/src/types/schedule/circuit-breaker.js +2 -0
  393. package/dist/src/types/schedule/circuit-breaker.js.map +1 -0
  394. package/dist/src/types/schedule/eligibility.js +9 -0
  395. package/dist/src/types/schedule/eligibility.js.map +1 -0
  396. package/dist/src/types/schedule/orchestrator-loop.js +10 -0
  397. package/dist/src/types/schedule/orchestrator-loop.js.map +1 -0
  398. package/dist/src/types/schedule/sleep-cycle.js +4 -0
  399. package/dist/src/types/schedule/sleep-cycle.js.map +1 -0
  400. package/dist/src/types/schedule/slots.js +8 -0
  401. package/dist/src/types/schedule/slots.js.map +1 -0
  402. package/dist/src/types/schedule/tick.js +9 -0
  403. package/dist/src/types/schedule/tick.js.map +1 -0
  404. package/dist/src/types/server/mcp-runtime.js +8 -0
  405. package/dist/src/types/server/mcp-runtime.js.map +1 -0
  406. package/dist/src/types/workflow/parse.js +4 -0
  407. package/dist/src/types/workflow/parse.js.map +1 -0
  408. package/dist/tests/core/account-id.test.js +35 -0
  409. package/dist/tests/core/account-id.test.js.map +1 -0
  410. package/dist/tests/core/actions-parse.test.js +176 -0
  411. package/dist/tests/core/actions-parse.test.js.map +1 -0
  412. package/dist/tests/core/adapter-config.test.js +133 -0
  413. package/dist/tests/core/adapter-config.test.js.map +1 -0
  414. package/dist/tests/core/admission.test.js +215 -0
  415. package/dist/tests/core/admission.test.js.map +1 -0
  416. package/dist/tests/core/args.test.js +132 -0
  417. package/dist/tests/core/args.test.js.map +1 -0
  418. package/dist/tests/core/availability.test.js +62 -0
  419. package/dist/tests/core/availability.test.js.map +1 -0
  420. package/dist/tests/core/checks.test.js +395 -0
  421. package/dist/tests/core/checks.test.js.map +1 -0
  422. package/dist/tests/core/circuit-breaker.test.js +172 -0
  423. package/dist/tests/core/circuit-breaker.test.js.map +1 -0
  424. package/dist/tests/core/coerce.test.js +87 -0
  425. package/dist/tests/core/coerce.test.js.map +1 -0
  426. package/dist/tests/core/context.test.js +228 -0
  427. package/dist/tests/core/context.test.js.map +1 -0
  428. package/dist/tests/core/decisions.test.js +310 -0
  429. package/dist/tests/core/decisions.test.js.map +1 -0
  430. package/dist/tests/core/derive.test.js +205 -0
  431. package/dist/tests/core/derive.test.js.map +1 -0
  432. package/dist/tests/core/dispatch-config.test.js +164 -0
  433. package/dist/tests/core/dispatch-config.test.js.map +1 -0
  434. package/dist/tests/core/dispatch.test.js +302 -0
  435. package/dist/tests/core/dispatch.test.js.map +1 -0
  436. package/dist/tests/core/eligibility.test.js +163 -0
  437. package/dist/tests/core/eligibility.test.js.map +1 -0
  438. package/dist/tests/core/extract.test.js +139 -0
  439. package/dist/tests/core/extract.test.js.map +1 -0
  440. package/dist/tests/core/fake-creds.test.js +134 -0
  441. package/dist/tests/core/fake-creds.test.js.map +1 -0
  442. package/dist/tests/core/file.test.js +197 -0
  443. package/dist/tests/core/file.test.js.map +1 -0
  444. package/dist/tests/core/git-result.test.js +113 -0
  445. package/dist/tests/core/git-result.test.js.map +1 -0
  446. package/dist/tests/core/identity.test.js +180 -0
  447. package/dist/tests/core/identity.test.js.map +1 -0
  448. package/dist/tests/core/image-decide.test.js +59 -0
  449. package/dist/tests/core/image-decide.test.js.map +1 -0
  450. package/dist/tests/core/injection.test.js +163 -0
  451. package/dist/tests/core/injection.test.js.map +1 -0
  452. package/dist/tests/core/ledger.test.js +218 -0
  453. package/dist/tests/core/ledger.test.js.map +1 -0
  454. package/dist/tests/core/managed-image.test.js +68 -0
  455. package/dist/tests/core/managed-image.test.js.map +1 -0
  456. package/dist/tests/core/mise.test.js +138 -0
  457. package/dist/tests/core/mise.test.js.map +1 -0
  458. package/dist/tests/core/parse.test.js +174 -0
  459. package/dist/tests/core/parse.test.js.map +1 -0
  460. package/dist/tests/core/path.test.js +50 -0
  461. package/dist/tests/core/path.test.js.map +1 -0
  462. package/dist/tests/core/plan.test.js +218 -0
  463. package/dist/tests/core/plan.test.js.map +1 -0
  464. package/dist/tests/core/post-move.test.js +162 -0
  465. package/dist/tests/core/post-move.test.js.map +1 -0
  466. package/dist/tests/core/pr-classify.test.js +117 -0
  467. package/dist/tests/core/pr-classify.test.js.map +1 -0
  468. package/dist/tests/core/pr-decide.test.js +298 -0
  469. package/dist/tests/core/pr-decide.test.js.map +1 -0
  470. package/dist/tests/core/pr-loop.test.js +301 -0
  471. package/dist/tests/core/pr-loop.test.js.map +1 -0
  472. package/dist/tests/core/pr-notes.test.js +165 -0
  473. package/dist/tests/core/pr-notes.test.js.map +1 -0
  474. package/dist/tests/core/predicates.test.js +154 -0
  475. package/dist/tests/core/predicates.test.js.map +1 -0
  476. package/dist/tests/core/prompt.test.js +189 -0
  477. package/dist/tests/core/prompt.test.js.map +1 -0
  478. package/dist/tests/core/protocol.test.js +195 -0
  479. package/dist/tests/core/protocol.test.js.map +1 -0
  480. package/dist/tests/core/reconcile-issue.test.js +116 -0
  481. package/dist/tests/core/reconcile-issue.test.js.map +1 -0
  482. package/dist/tests/core/render.test.js +549 -0
  483. package/dist/tests/core/render.test.js.map +1 -0
  484. package/dist/tests/core/retry.test.js +186 -0
  485. package/dist/tests/core/retry.test.js.map +1 -0
  486. package/dist/tests/core/routes.test.js +247 -0
  487. package/dist/tests/core/routes.test.js.map +1 -0
  488. package/dist/tests/core/run-fold.test.js +299 -0
  489. package/dist/tests/core/run-fold.test.js.map +1 -0
  490. package/dist/tests/core/shape.test.js +185 -0
  491. package/dist/tests/core/shape.test.js.map +1 -0
  492. package/dist/tests/core/sleep-cycle.test.js +150 -0
  493. package/dist/tests/core/sleep-cycle.test.js.map +1 -0
  494. package/dist/tests/core/slots.test.js +201 -0
  495. package/dist/tests/core/slots.test.js.map +1 -0
  496. package/dist/tests/core/state-resolve.test.js +80 -0
  497. package/dist/tests/core/state-resolve.test.js.map +1 -0
  498. package/dist/tests/core/summary.test.js +200 -0
  499. package/dist/tests/core/summary.test.js.map +1 -0
  500. package/dist/tests/core/template.test.js +116 -0
  501. package/dist/tests/core/template.test.js.map +1 -0
  502. package/dist/tests/core/tick.test.js +558 -0
  503. package/dist/tests/core/tick.test.js.map +1 -0
  504. package/dist/tests/core/token-fold.test.js +176 -0
  505. package/dist/tests/core/token-fold.test.js.map +1 -0
  506. package/dist/tests/core/turn.test.js +388 -0
  507. package/dist/tests/core/turn.test.js.map +1 -0
  508. package/dist/tests/core/url.test.js +118 -0
  509. package/dist/tests/core/url.test.js.map +1 -0
  510. package/dist/tests/core/validate.test.js +247 -0
  511. package/dist/tests/core/validate.test.js.map +1 -0
  512. package/dist/tests/core/views.test.js +252 -0
  513. package/dist/tests/core/views.test.js.map +1 -0
  514. package/dist/tests/core/vm-decide.test.js +110 -0
  515. package/dist/tests/core/vm-decide.test.js.map +1 -0
  516. package/dist/tests/core/vm-guards.test.js +153 -0
  517. package/dist/tests/core/vm-guards.test.js.map +1 -0
  518. package/dist/tests/core/vm-plan.test.js +332 -0
  519. package/dist/tests/core/vm-plan.test.js.map +1 -0
  520. package/dist/tests/core/vm-reap.test.js +196 -0
  521. package/dist/tests/core/vm-reap.test.js.map +1 -0
  522. package/dist/tests/core/workflow-parse.test.js +493 -0
  523. package/dist/tests/core/workflow-parse.test.js.map +1 -0
  524. package/dist/tests/core/workspace-decide.test.js +236 -0
  525. package/dist/tests/core/workspace-decide.test.js.map +1 -0
  526. package/dist/tests/helpers/fixtures.js +167 -0
  527. package/dist/tests/helpers/fixtures.js.map +1 -0
  528. package/dist/tests/shell/acp-substrate.test.js +101 -0
  529. package/dist/tests/shell/acp-substrate.test.js.map +1 -0
  530. package/dist/tests/shell/actions-runner-push.test.js +203 -0
  531. package/dist/tests/shell/actions-runner-push.test.js.map +1 -0
  532. package/dist/tests/shell/credential-hooks.test.js +36 -0
  533. package/dist/tests/shell/credential-hooks.test.js.map +1 -0
  534. package/dist/tests/shell/credential-registry.test.js +165 -0
  535. package/dist/tests/shell/credential-registry.test.js.map +1 -0
  536. package/dist/tests/shell/credential-substrate.test.js +179 -0
  537. package/dist/tests/shell/credential-substrate.test.js.map +1 -0
  538. package/dist/tests/shell/dockerfile-mise-pin.test.js +51 -0
  539. package/dist/tests/shell/dockerfile-mise-pin.test.js.map +1 -0
  540. package/dist/tests/shell/doctor.test.js +101 -0
  541. package/dist/tests/shell/doctor.test.js.map +1 -0
  542. package/dist/tests/shell/effect-vm-create.test.js +52 -0
  543. package/dist/tests/shell/effect-vm-create.test.js.map +1 -0
  544. package/dist/tests/shell/gh-port.test.js +63 -0
  545. package/dist/tests/shell/gh-port.test.js.map +1 -0
  546. package/dist/tests/shell/gondolin-dispatch-guard.test.js +144 -0
  547. package/dist/tests/shell/gondolin-dispatch-guard.test.js.map +1 -0
  548. package/dist/tests/shell/gondolin-dispatch-shquote.test.js +168 -0
  549. package/dist/tests/shell/gondolin-dispatch-shquote.test.js.map +1 -0
  550. package/dist/tests/shell/gondolin-image-converter.test.js +208 -0
  551. package/dist/tests/shell/gondolin-image-converter.test.js.map +1 -0
  552. package/dist/tests/shell/gondolin-image-fetch.test.js +93 -0
  553. package/dist/tests/shell/gondolin-image-fetch.test.js.map +1 -0
  554. package/dist/tests/shell/http-handler.test.js +608 -0
  555. package/dist/tests/shell/http-handler.test.js.map +1 -0
  556. package/dist/tests/shell/http-server.test.js +53 -0
  557. package/dist/tests/shell/http-server.test.js.map +1 -0
  558. package/dist/tests/shell/mcp-runtime.test.js +366 -0
  559. package/dist/tests/shell/mcp-runtime.test.js.map +1 -0
  560. package/dist/tests/shell/mise-config-asset.test.js +87 -0
  561. package/dist/tests/shell/mise-config-asset.test.js.map +1 -0
  562. package/dist/tests/shell/orchestrator-loop.test.js +583 -0
  563. package/dist/tests/shell/orchestrator-loop.test.js.map +1 -0
  564. package/dist/tests/shell/reconciler-passes.test.js +314 -0
  565. package/dist/tests/shell/reconciler-passes.test.js.map +1 -0
  566. package/dist/tests/shell/runner-loop-turn.test.js +97 -0
  567. package/dist/tests/shell/runner-loop-turn.test.js.map +1 -0
  568. package/dist/tests/shell/runner-slice.test.js +536 -0
  569. package/dist/tests/shell/runner-slice.test.js.map +1 -0
  570. package/dist/tests/shell/scaffold.test.js +65 -0
  571. package/dist/tests/shell/scaffold.test.js.map +1 -0
  572. package/dist/tests/shell/tick-config.test.js +83 -0
  573. package/dist/tests/shell/tick-config.test.js.map +1 -0
  574. package/dist/tests/shell/tracker-parse-dates.test.js +44 -0
  575. package/dist/tests/shell/tracker-parse-dates.test.js.map +1 -0
  576. package/dist/tests/shell/tracker-write-issue.test.js +154 -0
  577. package/dist/tests/shell/tracker-write-issue.test.js.map +1 -0
  578. package/dist/tests/shell/workflow-prompt-split.test.js +208 -0
  579. package/dist/tests/shell/workflow-prompt-split.test.js.map +1 -0
  580. package/dist/tests/shell/workspace-live-config.test.js +140 -0
  581. package/dist/tests/shell/workspace-live-config.test.js.map +1 -0
  582. package/package.json +21 -9
  583. package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
  584. package/prompts/Reflect.md +91 -0
  585. package/prompts/Review.md +97 -0
  586. package/prompts/Todo.md +96 -0
  587. package/prompts/_footer.md +41 -0
  588. package/prompts/_preamble.md +42 -0
  589. package/prompts-minimal/Todo.md +26 -0
  590. package/scripts/postinstall.mjs +63 -0
  591. package/scripts/vm-agent.mjs +312 -90
  592. package/WORKFLOW.md +0 -744
  593. package/dist/acp-bridge.js +0 -324
  594. package/dist/acp-bridge.js.map +0 -1
  595. package/dist/actions/cache.js +0 -191
  596. package/dist/actions/cache.js.map +0 -1
  597. package/dist/actions/effects.js +0 -41
  598. package/dist/actions/effects.js.map +0 -1
  599. package/dist/actions/executor.js +0 -570
  600. package/dist/actions/executor.js.map +0 -1
  601. package/dist/actions/index.js +0 -13
  602. package/dist/actions/index.js.map +0 -1
  603. package/dist/actions/parsing.js.map +0 -1
  604. package/dist/actions/predicate-env.js +0 -27
  605. package/dist/actions/predicate-env.js.map +0 -1
  606. package/dist/actions/predicates.js +0 -49
  607. package/dist/actions/predicates.js.map +0 -1
  608. package/dist/actions/templating.js +0 -66
  609. package/dist/actions/templating.js.map +0 -1
  610. package/dist/actions/types.js +0 -15
  611. package/dist/actions/types.js.map +0 -1
  612. package/dist/agent/acp.js +0 -473
  613. package/dist/agent/acp.js.map +0 -1
  614. package/dist/agent/adapter-names.js +0 -159
  615. package/dist/agent/adapter-names.js.map +0 -1
  616. package/dist/agent/adapters.js +0 -511
  617. package/dist/agent/adapters.js.map +0 -1
  618. package/dist/agent/credential-extractors.js +0 -342
  619. package/dist/agent/credential-extractors.js.map +0 -1
  620. package/dist/agent/credential-secrets.js +0 -628
  621. package/dist/agent/credential-secrets.js.map +0 -1
  622. package/dist/agent/credential-ticker.js +0 -57
  623. package/dist/agent/credential-ticker.js.map +0 -1
  624. package/dist/agent/gondolin-creds-staging.js +0 -356
  625. package/dist/agent/gondolin-creds-staging.js.map +0 -1
  626. package/dist/agent/gondolin-dispatch.js +0 -375
  627. package/dist/agent/gondolin-dispatch.js.map +0 -1
  628. package/dist/agent/gondolin.js +0 -124
  629. package/dist/agent/gondolin.js.map +0 -1
  630. package/dist/agent/runner-decisions.js +0 -134
  631. package/dist/agent/runner-decisions.js.map +0 -1
  632. package/dist/agent/runner.js +0 -1456
  633. package/dist/agent/runner.js.map +0 -1
  634. package/dist/agent/tool-call-summary.js +0 -102
  635. package/dist/agent/tool-call-summary.js.map +0 -1
  636. package/dist/agent/vm-acp-mapping.js +0 -73
  637. package/dist/agent/vm-acp-mapping.js.map +0 -1
  638. package/dist/agent/vm-guards.js +0 -262
  639. package/dist/agent/vm-guards.js.map +0 -1
  640. package/dist/agent/vm-port.js +0 -22
  641. package/dist/agent/vm-port.js.map +0 -1
  642. package/dist/agent/vm-process-registry.js +0 -79
  643. package/dist/agent/vm-process-registry.js.map +0 -1
  644. package/dist/bin/cli-args.js +0 -105
  645. package/dist/bin/cli-args.js.map +0 -1
  646. package/dist/bin/symphony.js +0 -794
  647. package/dist/bin/symphony.js.map +0 -1
  648. package/dist/errors.js +0 -15
  649. package/dist/errors.js.map +0 -1
  650. package/dist/http-disk.js +0 -135
  651. package/dist/http-disk.js.map +0 -1
  652. package/dist/http-handlers.js.map +0 -1
  653. package/dist/http.js.map +0 -1
  654. package/dist/issues.js +0 -178
  655. package/dist/issues.js.map +0 -1
  656. package/dist/logging.js +0 -203
  657. package/dist/logging.js.map +0 -1
  658. package/dist/mcp.js +0 -706
  659. package/dist/mcp.js.map +0 -1
  660. package/dist/memory.js +0 -85
  661. package/dist/memory.js.map +0 -1
  662. package/dist/orchestrator-decisions.js +0 -331
  663. package/dist/orchestrator-decisions.js.map +0 -1
  664. package/dist/orchestrator.js +0 -1569
  665. package/dist/orchestrator.js.map +0 -1
  666. package/dist/prompt.js +0 -65
  667. package/dist/prompt.js.map +0 -1
  668. package/dist/reconciler/cache.js +0 -65
  669. package/dist/reconciler/cache.js.map +0 -1
  670. package/dist/reconciler/index.js +0 -448
  671. package/dist/reconciler/index.js.map +0 -1
  672. package/dist/reconciler/ledger.js +0 -131
  673. package/dist/reconciler/ledger.js.map +0 -1
  674. package/dist/reconciler/pr-adapters.js +0 -174
  675. package/dist/reconciler/pr-adapters.js.map +0 -1
  676. package/dist/reconciler/pr-decide.js.map +0 -1
  677. package/dist/reconciler/pr.js +0 -422
  678. package/dist/reconciler/pr.js.map +0 -1
  679. package/dist/reconciler/types.js +0 -12
  680. package/dist/reconciler/types.js.map +0 -1
  681. package/dist/reconciler/vm.js +0 -243
  682. package/dist/reconciler/vm.js.map +0 -1
  683. package/dist/reconciler/workspace-defaults.js +0 -83
  684. package/dist/reconciler/workspace-defaults.js.map +0 -1
  685. package/dist/reconciler/workspace.js +0 -272
  686. package/dist/reconciler/workspace.js.map +0 -1
  687. package/dist/runlog.js +0 -403
  688. package/dist/runlog.js.map +0 -1
  689. package/dist/scaffold.js +0 -165
  690. package/dist/scaffold.js.map +0 -1
  691. package/dist/trackers/local.js +0 -445
  692. package/dist/trackers/local.js.map +0 -1
  693. package/dist/trackers/types.js +0 -10
  694. package/dist/trackers/types.js.map +0 -1
  695. package/dist/types.js +0 -3
  696. package/dist/types.js.map +0 -1
  697. package/dist/util/clock.js +0 -12
  698. package/dist/util/clock.js.map +0 -1
  699. package/dist/util/crypto.js +0 -25
  700. package/dist/util/crypto.js.map +0 -1
  701. package/dist/util/frontmatter.js +0 -70
  702. package/dist/util/frontmatter.js.map +0 -1
  703. package/dist/util/fs-issues.js +0 -22
  704. package/dist/util/fs-issues.js.map +0 -1
  705. package/dist/util/process.js +0 -152
  706. package/dist/util/process.js.map +0 -1
  707. package/dist/util/workspace-key.js +0 -10
  708. package/dist/util/workspace-key.js.map +0 -1
  709. package/dist/workflow-loader.js +0 -147
  710. package/dist/workflow-loader.js.map +0 -1
  711. package/dist/workflow.js +0 -822
  712. package/dist/workflow.js.map +0 -1
  713. package/dist/workspace-types.js +0 -8
  714. package/dist/workspace-types.js.map +0 -1
  715. package/dist/workspace.js +0 -443
  716. package/dist/workspace.js.map +0 -1
@@ -0,0 +1,720 @@
1
+ // FCIS rewrite — PURE functional-core prompt construction for the runner.
2
+ //
3
+ // Responsibility (ported from the original src/prompt.ts + the prompt-building
4
+ // helpers inlined in the original src/agent/runner.ts):
5
+ // - renderPrompt: strict Liquid render of a state's prompt template
6
+ // against the issue scope (initial / first turn).
7
+ // - continuationPrompt: the bare follow-up turn prompt (MCP on/off).
8
+ // - buildSteeringReplyPrompt: the human-in-the-loop reply prompt.
9
+ // - selectPromptKind: pick which of the three to use this iteration.
10
+ // - composePrompt: one entry the shell calls per turn.
11
+ // - stageDonePrBodyEnv: the Done-state PR-body/title staging env-map shape.
12
+ //
13
+ // 100% SYNCHRONOUS, zero IO, zero node: imports. Imports ONLY from src/types.
14
+ //
15
+ // The REF renders with liquidjs in async mode (`ENGINE.render`). liquidjs is a
16
+ // third-party engine and importing it is forbidden here (core may import only
17
+ // src/types). So this module ships a small, SELF-CONTAINED, *synchronous* strict
18
+ // Liquid interpreter covering exactly the constructs the real workflow prompt
19
+ // templates use today: `{{ output | filters }}`, `{% if/unless %}`, `{% for %}`
20
+ // (+ `forloop.last`), `{% case/when/else %}`, dotted/`.size` access, whitespace
21
+ // control (`-%}` / `{%-`), and the strictVariables / strictFilters /
22
+ // ownPropertyOnly semantics (unknown var or unknown filter → PromptError).
23
+ //
24
+ // Note: this is a faithful SUBSET, not the full liquidjs surface. A construct
25
+ // outside the set above (e.g. `assign`, `capture`, `tablerow`, arithmetic
26
+ // filters) hits the unknown-tag path and throws PromptError('template_parse_error',
27
+ // …), so any such gap fails loudly rather than silently mis-rendering — at which
28
+ // point either extend this interpreter or move the render into the shell as an
29
+ // Effect executed via liquidjs.
30
+ import { PromptError } from '../../types/errors.js';
31
+ /** Convert an Issue into the template scope. Preserves nested arrays/maps. */
32
+ export function issueToScope(issue) {
33
+ return {
34
+ id: issue.id,
35
+ identifier: issue.identifier,
36
+ title: issue.title,
37
+ description: issue.description,
38
+ priority: issue.priority,
39
+ state: issue.state,
40
+ branch_name: issue.branch_name,
41
+ url: issue.url,
42
+ labels: issue.labels.slice(),
43
+ blocked_by: issue.blocked_by.map((b) => ({
44
+ id: b.id,
45
+ identifier: b.identifier,
46
+ state: b.state,
47
+ })),
48
+ created_at: issue.created_at,
49
+ updated_at: issue.updated_at,
50
+ };
51
+ }
52
+ /** §4.4 fallback when the workflow body is empty. */
53
+ export const FALLBACK_PROMPT = 'You are working on an issue.';
54
+ /**
55
+ * Strict Liquid render of the initial prompt. Empty body → FALLBACK_PROMPT.
56
+ * Unknown variable / filter / tag → PromptError (parse vs render code).
57
+ */
58
+ export function renderPrompt(input) {
59
+ const tpl = input.template.trim();
60
+ if (tpl.length === 0)
61
+ return FALLBACK_PROMPT;
62
+ const scope = {
63
+ issue: issueToScope(input.issue),
64
+ attempt: input.attempt,
65
+ };
66
+ return renderLiquid(tpl, scope);
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // continuationPrompt (REF runner.ts CONTINUATION_PROMPT_* + continuationPrompt)
70
+ // ---------------------------------------------------------------------------
71
+ export const CONTINUATION_PROMPT_WITH_MCP = 'Continue working on the same issue. Pick up where the prior turn left off and proceed with the next concrete action. If the work is fully complete, summarize what changed and call the symphony.transition tool to hand off to the next state.';
72
+ export const CONTINUATION_PROMPT_NO_MCP = 'Continue working on the same issue. Pick up where the prior turn left off and proceed with the next concrete action. If the work is fully complete, summarize what changed and stop.';
73
+ /** The bare follow-up turn prompt; MCP-on routes the agent to symphony.transition. */
74
+ export function continuationPrompt(mcpEnabled) {
75
+ return mcpEnabled ? CONTINUATION_PROMPT_WITH_MCP : CONTINUATION_PROMPT_NO_MCP;
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // buildSteeringReplyPrompt (REF runner.ts `buildSteeringReplyPrompt`)
79
+ // ---------------------------------------------------------------------------
80
+ /** Compose the human-in-the-loop reply prompt. Collapses 3+ blank lines to 2. */
81
+ export function buildSteeringReplyPrompt(question, context, reply) {
82
+ const ctxBlock = context && context.length > 0 ? `\n\nContext you provided:\n${context}` : '';
83
+ return [
84
+ 'The human operator has responded to your steering request.',
85
+ '',
86
+ 'Your question was:',
87
+ question,
88
+ ctxBlock,
89
+ '',
90
+ 'The human responded:',
91
+ reply,
92
+ '',
93
+ 'Continue work on the issue, taking the human response into account. If the work is fully complete, call symphony.transition to hand off to the next state. If you need to ask another question, call symphony.request_human_steering again.',
94
+ ]
95
+ .join('\n')
96
+ .replace(/\n{3,}/g, '\n\n');
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // selectPromptKind + composePrompt (REF runner.ts selectPromptKind / composeTurnPrompt)
100
+ // ---------------------------------------------------------------------------
101
+ /**
102
+ * Pick the prompt template for the next iteration. Pending steering trumps
103
+ * everything (human in the loop); then the first-turn full prompt; then the
104
+ * bare continuation prompt.
105
+ */
106
+ export function selectPromptKind(input) {
107
+ if (input.pendingSteering)
108
+ return 'steering';
109
+ if (input.firstTurn)
110
+ return 'initial';
111
+ return 'continuation';
112
+ }
113
+ /**
114
+ * Single pure entry the shell calls per turn (mirrors REF `composeTurnPrompt`).
115
+ * Throws PromptError for an unknown var/filter/tag in the initial template.
116
+ */
117
+ export function composePrompt(input) {
118
+ const kind = selectPromptKind({
119
+ pendingSteering: input.pendingSteering !== null,
120
+ firstTurn: input.firstTurn,
121
+ });
122
+ if (kind === 'steering') {
123
+ const ps = input.pendingSteering;
124
+ return { kind, prompt: buildSteeringReplyPrompt(ps.question, ps.context, ps.reply) };
125
+ }
126
+ if (kind === 'initial') {
127
+ return {
128
+ kind,
129
+ prompt: renderPrompt({ template: input.template, issue: input.issue, attempt: input.attempt }),
130
+ };
131
+ }
132
+ return { kind, prompt: continuationPrompt(input.mcpEnabled) };
133
+ }
134
+ /**
135
+ * The SYMPHONY_* env map the Done-state `actions:` block (push_branch +
136
+ * create_pr_if_missing) consumes. Pure: no clock, no fs, no randomness.
137
+ * PR title is `<id>: <title>` when the title is non-empty after trimming,
138
+ * else just `<id>` (REF parity).
139
+ */
140
+ export function stageDonePrBodyEnv(input) {
141
+ const branch = `agent/${input.identifier}`;
142
+ const title = input.issue_title.trim();
143
+ return {
144
+ SYMPHONY_ISSUE_ID: input.issue_id,
145
+ SYMPHONY_BRANCH: branch,
146
+ SYMPHONY_BASE_BRANCH: input.base_branch,
147
+ SYMPHONY_PR_TITLE: title.length > 0 ? `${input.issue_id}: ${title}` : input.issue_id,
148
+ SYMPHONY_PR_BODY_FILE: input.pr_body_file,
149
+ };
150
+ }
151
+ const UNDEF = Symbol('undefined-liquid-value');
152
+ function tokenize(src) {
153
+ const tokens = [];
154
+ let i = 0;
155
+ const n = src.length;
156
+ while (i < n) {
157
+ const open = src.indexOf('{', i);
158
+ if (open === -1 || open + 1 >= n) {
159
+ tokens.push({ type: 'text', value: src.slice(i) });
160
+ break;
161
+ }
162
+ const marker = src[open + 1];
163
+ if (marker !== '{' && marker !== '%') {
164
+ // a lone '{' — emit up to and including it as text, keep scanning.
165
+ tokens.push({ type: 'text', value: src.slice(i, open + 1) });
166
+ i = open + 1;
167
+ continue;
168
+ }
169
+ if (open > i)
170
+ tokens.push({ type: 'text', value: src.slice(i, open) });
171
+ const isOutput = marker === '{';
172
+ const closeSeq = isOutput ? '}}' : '%}';
173
+ const close = src.indexOf(closeSeq, open + 2);
174
+ if (close === -1) {
175
+ throw new PromptError('template_parse_error', `unterminated ${isOutput ? 'output' : 'tag'}`);
176
+ }
177
+ let inner = src.slice(open + 2, close);
178
+ let trimLeft = false;
179
+ let trimRight = false;
180
+ if (inner.startsWith('-')) {
181
+ trimLeft = true;
182
+ inner = inner.slice(1);
183
+ }
184
+ if (inner.endsWith('-')) {
185
+ trimRight = true;
186
+ inner = inner.slice(0, -1);
187
+ }
188
+ inner = inner.trim();
189
+ tokens.push(isOutput
190
+ ? { type: 'output', value: inner, trimLeft, trimRight }
191
+ : { type: 'tag', value: inner, trimLeft, trimRight });
192
+ i = close + 2;
193
+ }
194
+ return applyWhitespaceControl(tokens);
195
+ }
196
+ /** Honor `-%}` / `{%-` whitespace stripping on adjacent text tokens. */
197
+ function applyWhitespaceControl(tokens) {
198
+ for (let k = 0; k < tokens.length; k++) {
199
+ const t = tokens[k];
200
+ if (t.type === 'text')
201
+ continue;
202
+ if (t.trimLeft) {
203
+ const prev = tokens[k - 1];
204
+ if (prev && prev.type === 'text')
205
+ prev.value = prev.value.replace(/\s+$/, '');
206
+ }
207
+ if (t.trimRight) {
208
+ const next = tokens[k + 1];
209
+ if (next && next.type === 'text')
210
+ next.value = next.value.replace(/^\s+/, '');
211
+ }
212
+ }
213
+ return tokens;
214
+ }
215
+ function parse(tokens) {
216
+ let pos = 0;
217
+ function parseUntil(stopTags) {
218
+ const nodes = [];
219
+ while (pos < tokens.length) {
220
+ const t = tokens[pos];
221
+ if (t.type === 'text') {
222
+ if (t.value.length > 0)
223
+ nodes.push({ type: 'text', value: t.value });
224
+ pos++;
225
+ continue;
226
+ }
227
+ if (t.type === 'output') {
228
+ nodes.push({ type: 'output', expr: t.value });
229
+ pos++;
230
+ continue;
231
+ }
232
+ // tag
233
+ const tagName = t.value.split(/\s+/)[0];
234
+ if (stopTags.includes(tagName)) {
235
+ return { nodes, stoppedAt: tagName };
236
+ }
237
+ pos++;
238
+ if (tagName === 'if' || tagName === 'unless') {
239
+ nodes.push(parseConditional(t.value, tagName));
240
+ }
241
+ else if (tagName === 'for') {
242
+ nodes.push(parseFor(t.value));
243
+ }
244
+ else if (tagName === 'case') {
245
+ nodes.push(parseCase(t.value));
246
+ }
247
+ else {
248
+ throw new PromptError('template_parse_error', `unknown tag "${tagName}"`);
249
+ }
250
+ }
251
+ return { nodes, stoppedAt: null };
252
+ }
253
+ function parseConditional(openValue, kind) {
254
+ const rest = openValue.slice(kind.length).trim();
255
+ const branches = [];
256
+ const endTag = kind === 'if' ? 'endif' : 'endunless';
257
+ // `unless X` is `if not X`; encode by wrapping the condition.
258
+ const firstCond = kind === 'unless' ? `__unless__ ${rest}` : rest;
259
+ let curCond = firstCond;
260
+ for (;;) {
261
+ const { nodes, stoppedAt } = parseUntil(['elsif', 'else', endTag]);
262
+ branches.push({ cond: curCond, body: nodes });
263
+ if (stoppedAt === endTag || stoppedAt === null) {
264
+ expectClose(endTag);
265
+ break;
266
+ }
267
+ const stopTok = tokens[pos];
268
+ pos++;
269
+ if (stoppedAt === 'else') {
270
+ const elseBlock = parseUntil([endTag]);
271
+ branches.push({ cond: null, body: elseBlock.nodes });
272
+ expectClose(endTag);
273
+ break;
274
+ }
275
+ // elsif
276
+ curCond = stopTok.value.slice('elsif'.length).trim();
277
+ }
278
+ return { type: 'if', branches };
279
+ }
280
+ function parseFor(openValue) {
281
+ // `for <var> in <collection>`
282
+ const m = /^for\s+(\w+)\s+in\s+(.+)$/.exec(openValue);
283
+ if (!m)
284
+ throw new PromptError('template_parse_error', `malformed for tag: ${openValue}`);
285
+ const varName = m[1];
286
+ const collExpr = m[2].trim();
287
+ const { nodes, stoppedAt } = parseUntil(['endfor']);
288
+ if (stoppedAt !== 'endfor')
289
+ throw new PromptError('template_parse_error', 'unterminated for');
290
+ expectClose('endfor');
291
+ return { type: 'for', varName, collExpr, body: nodes };
292
+ }
293
+ function parseCase(openValue) {
294
+ const subject = openValue.slice('case'.length).trim();
295
+ const whens = [];
296
+ let elseBody = [];
297
+ // Skip any text/whitespace before the first when/else/endcase.
298
+ for (;;) {
299
+ const { stoppedAt } = parseUntil(['when', 'else', 'endcase']);
300
+ if (stoppedAt === null)
301
+ throw new PromptError('template_parse_error', 'unterminated case');
302
+ const tok = tokens[pos];
303
+ pos++;
304
+ if (stoppedAt === 'endcase') {
305
+ expectClose('endcase');
306
+ break;
307
+ }
308
+ if (stoppedAt === 'else') {
309
+ const block = parseUntil(['endcase']);
310
+ elseBody = block.nodes;
311
+ expectClose('endcase');
312
+ break;
313
+ }
314
+ // when — collect comma/`or` separated literals, then its body.
315
+ const matchSpec = tok.value.slice('when'.length).trim();
316
+ const matches = matchSpec.split(/\s*,\s*|\s+or\s+/).map((s) => s.trim());
317
+ const block = parseUntil(['when', 'else', 'endcase']);
318
+ whens.push({ matches, body: block.nodes });
319
+ // Do NOT consume the next when/else/endcase here; loop re-enters parseUntil
320
+ // which will immediately stop on it. Rewind one parseUntil iteration by
321
+ // continuing the outer loop (pos already points at the stop tag).
322
+ }
323
+ return { type: 'case', subject, whens, elseBody };
324
+ }
325
+ function expectClose(endTag) {
326
+ const t = tokens[pos];
327
+ if (!t || t.type !== 'tag' || t.value.split(/\s+/)[0] !== endTag) {
328
+ throw new PromptError('template_parse_error', `expected ${endTag}`);
329
+ }
330
+ pos++;
331
+ }
332
+ const { nodes, stoppedAt } = parseUntil([]);
333
+ if (stoppedAt !== null) {
334
+ throw new PromptError('template_parse_error', `unexpected ${stoppedAt}`);
335
+ }
336
+ return nodes;
337
+ }
338
+ // --- Renderer (node tree + scope → string) --------------------------------
339
+ function renderNodes(nodes, scope) {
340
+ let out = '';
341
+ for (const node of nodes) {
342
+ out += renderNode(node, scope);
343
+ }
344
+ return out;
345
+ }
346
+ function renderNode(node, scope) {
347
+ switch (node.type) {
348
+ case 'text':
349
+ return node.value;
350
+ case 'output':
351
+ return stringify(evalOutput(node.expr, scope));
352
+ case 'if': {
353
+ for (const branch of node.branches) {
354
+ if (branch.cond === null || evalCondition(branch.cond, scope)) {
355
+ return renderNodes(branch.body, scope);
356
+ }
357
+ }
358
+ return '';
359
+ }
360
+ case 'for': {
361
+ const coll = evalExpr(node.collExpr, scope);
362
+ const items = Array.isArray(coll) ? coll : [];
363
+ let out = '';
364
+ const len = items.length;
365
+ for (let idx = 0; idx < len; idx++) {
366
+ const childScope = {
367
+ ...scope,
368
+ [node.varName]: items[idx],
369
+ forloop: {
370
+ index: idx + 1,
371
+ index0: idx,
372
+ first: idx === 0,
373
+ last: idx === len - 1,
374
+ length: len,
375
+ },
376
+ };
377
+ out += renderNodes(node.body, childScope);
378
+ }
379
+ return out;
380
+ }
381
+ case 'case': {
382
+ const subject = evalExpr(node.subject, scope);
383
+ for (const when of node.whens) {
384
+ for (const m of when.matches) {
385
+ if (looseEq(subject, evalLiteralOrVar(m, scope))) {
386
+ return renderNodes(when.body, scope);
387
+ }
388
+ }
389
+ }
390
+ return renderNodes(node.elseBody, scope);
391
+ }
392
+ }
393
+ }
394
+ // --- Expression evaluation -------------------------------------------------
395
+ /** Evaluate an output expression: `value | filter: arg | filter2`. */
396
+ function evalOutput(expr, scope) {
397
+ const parts = splitPipes(expr);
398
+ let value = evalExpr(parts[0].trim(), scope);
399
+ for (let k = 1; k < parts.length; k++) {
400
+ value = applyFilter(parts[k].trim(), value, scope);
401
+ }
402
+ return value;
403
+ }
404
+ /** Split on `|` that are not inside quotes. */
405
+ function splitPipes(expr) {
406
+ const out = [];
407
+ let cur = '';
408
+ let quote = null;
409
+ for (const ch of expr) {
410
+ if (quote) {
411
+ cur += ch;
412
+ if (ch === quote)
413
+ quote = null;
414
+ continue;
415
+ }
416
+ if (ch === '"' || ch === "'") {
417
+ quote = ch;
418
+ cur += ch;
419
+ continue;
420
+ }
421
+ if (ch === '|') {
422
+ out.push(cur);
423
+ cur = '';
424
+ continue;
425
+ }
426
+ cur += ch;
427
+ }
428
+ out.push(cur);
429
+ return out;
430
+ }
431
+ /**
432
+ * Evaluate a primary expression: a literal, or a dotted/bracket variable path.
433
+ * Strict: an unknown variable throws PromptError('template_render_error').
434
+ */
435
+ function evalExpr(expr, scope) {
436
+ const lit = parseLiteral(expr);
437
+ if (lit !== UNDEF)
438
+ return lit;
439
+ const v = resolvePath(expr, scope);
440
+ if (v === UNDEF) {
441
+ throw new PromptError('template_render_error', `undefined variable: ${expr}`);
442
+ }
443
+ return v;
444
+ }
445
+ /** Like evalExpr but a bare word that is not a known var is treated as a string literal (case/when RHS). */
446
+ function evalLiteralOrVar(expr, scope) {
447
+ const lit = parseLiteral(expr);
448
+ if (lit !== UNDEF)
449
+ return lit;
450
+ const v = resolvePath(expr, scope);
451
+ return v === UNDEF ? expr : v;
452
+ }
453
+ function parseLiteral(expr) {
454
+ const e = expr.trim();
455
+ if ((e.startsWith('"') && e.endsWith('"') && e.length >= 2) ||
456
+ (e.startsWith("'") && e.endsWith("'") && e.length >= 2)) {
457
+ return e.slice(1, -1);
458
+ }
459
+ if (e === 'true')
460
+ return true;
461
+ if (e === 'false')
462
+ return false;
463
+ if (e === 'nil' || e === 'null' || e === 'empty')
464
+ return null;
465
+ if (/^-?\d+(\.\d+)?$/.test(e))
466
+ return Number(e);
467
+ return UNDEF;
468
+ }
469
+ /**
470
+ * Resolve a dotted / `.size` / bracket path against the scope using
471
+ * own-property-only semantics. Returns UNDEF when any hop is missing.
472
+ */
473
+ function resolvePath(path, scope) {
474
+ const segs = splitPath(path);
475
+ if (segs.length === 0)
476
+ return UNDEF;
477
+ let cur = scope;
478
+ let first = true;
479
+ for (const seg of segs) {
480
+ if (first) {
481
+ // top-level scope lookup is always own-prop on the scope object
482
+ if (!hasOwn(scope, seg))
483
+ return UNDEF;
484
+ cur = scope[seg];
485
+ first = false;
486
+ continue;
487
+ }
488
+ if (cur == null)
489
+ return UNDEF;
490
+ if (seg === 'size') {
491
+ if (Array.isArray(cur))
492
+ cur = cur.length;
493
+ else if (typeof cur === 'string')
494
+ cur = cur.length;
495
+ else
496
+ return UNDEF;
497
+ continue;
498
+ }
499
+ if (Array.isArray(cur)) {
500
+ const n = Number(seg);
501
+ if (!Number.isInteger(n))
502
+ return UNDEF;
503
+ cur = cur[n];
504
+ continue;
505
+ }
506
+ if (typeof cur === 'object') {
507
+ if (!hasOwn(cur, seg))
508
+ return UNDEF;
509
+ cur = cur[seg];
510
+ continue;
511
+ }
512
+ return UNDEF;
513
+ }
514
+ return cur;
515
+ }
516
+ function splitPath(path) {
517
+ // Support `a.b.c` and `a[0].b`; brackets become path segments.
518
+ const normalized = path.replace(/\[\s*['"]?([^'"\]]+)['"]?\s*\]/g, '.$1');
519
+ return normalized
520
+ .split('.')
521
+ .map((s) => s.trim())
522
+ .filter((s) => s.length > 0);
523
+ }
524
+ function hasOwn(obj, key) {
525
+ return Object.prototype.hasOwnProperty.call(obj, key);
526
+ }
527
+ // --- Conditions ------------------------------------------------------------
528
+ /** Evaluate an `if`/`elsif`/`unless` condition (supports and/or, comparisons). */
529
+ function evalCondition(cond, scope) {
530
+ let negate = false;
531
+ let c = cond.trim();
532
+ if (c.startsWith('__unless__')) {
533
+ negate = true;
534
+ c = c.slice('__unless__'.length).trim();
535
+ }
536
+ const result = evalBoolExpr(c, scope);
537
+ return negate ? !result : result;
538
+ }
539
+ function evalBoolExpr(expr, scope) {
540
+ // `or` binds looser than `and` (Liquid evaluates right-to-left but the
541
+ // boolean result is the same for our supported operators).
542
+ const orParts = splitOnKeyword(expr, 'or');
543
+ if (orParts.length > 1) {
544
+ return orParts.some((p) => evalBoolExpr(p, scope));
545
+ }
546
+ const andParts = splitOnKeyword(expr, 'and');
547
+ if (andParts.length > 1) {
548
+ return andParts.every((p) => evalBoolExpr(p, scope));
549
+ }
550
+ return evalComparison(expr.trim(), scope);
551
+ }
552
+ /** Split `expr` on a whitespace-delimited `and`/`or` keyword, ignoring quotes. */
553
+ function splitOnKeyword(expr, keyword) {
554
+ const out = [];
555
+ let quote = null;
556
+ let cur = '';
557
+ for (const t of expr.split(/(\s+)/)) {
558
+ if (quote) {
559
+ cur += t;
560
+ if (t.includes(quote))
561
+ quote = null;
562
+ }
563
+ else if (/^['"]/.test(t) && !t.endsWith(t[0])) {
564
+ quote = t[0];
565
+ cur += t;
566
+ }
567
+ else if (t === keyword) {
568
+ out.push(cur);
569
+ cur = '';
570
+ }
571
+ else {
572
+ cur += t;
573
+ }
574
+ }
575
+ out.push(cur);
576
+ return out.length === 1 ? [expr] : out.map((s) => s.trim()).filter((s) => s.length > 0);
577
+ }
578
+ const COMPARATORS = [
579
+ ['==', (a, b) => looseEq(a, b)],
580
+ ['!=', (a, b) => !looseEq(a, b)],
581
+ ['>=', (a, b) => Number(a) >= Number(b)],
582
+ ['<=', (a, b) => Number(a) <= Number(b)],
583
+ ['>', (a, b) => Number(a) > Number(b)],
584
+ ['<', (a, b) => Number(a) < Number(b)],
585
+ ];
586
+ function evalComparison(expr, scope) {
587
+ for (const [op, fn] of COMPARATORS) {
588
+ const idx = findOp(expr, op);
589
+ if (idx !== -1) {
590
+ const lhs = expr.slice(0, idx).trim();
591
+ const rhs = expr.slice(idx + op.length).trim();
592
+ return fn(evalSoft(lhs, scope), evalSoft(rhs, scope));
593
+ }
594
+ }
595
+ // Bare truthiness test (`{% if attempt %}` / `{% if issue.description %}`).
596
+ return truthy(evalSoft(expr, scope));
597
+ }
598
+ /** Find a comparison operator outside quotes; returns -1 if absent. */
599
+ function findOp(expr, op) {
600
+ let quote = null;
601
+ for (let i = 0; i + op.length <= expr.length; i++) {
602
+ const ch = expr[i];
603
+ if (quote) {
604
+ if (ch === quote)
605
+ quote = null;
606
+ continue;
607
+ }
608
+ if (ch === '"' || ch === "'") {
609
+ quote = ch;
610
+ continue;
611
+ }
612
+ // Don't match '>'/'<' that are really the start of '>='/'<=' (longer ops
613
+ // are tried first, so a bare '>' here is genuine only if no '=' follows).
614
+ if (expr.startsWith(op, i)) {
615
+ if ((op === '>' || op === '<') && expr[i + 1] === '=')
616
+ continue;
617
+ return i;
618
+ }
619
+ }
620
+ return -1;
621
+ }
622
+ /** Soft evaluation: literal, else variable path that yields undefined→null (no throw). */
623
+ function evalSoft(expr, scope) {
624
+ const lit = parseLiteral(expr);
625
+ if (lit !== UNDEF)
626
+ return lit;
627
+ const v = resolvePath(expr, scope);
628
+ return v === UNDEF ? null : v;
629
+ }
630
+ function looseEq(a, b) {
631
+ if (a === b)
632
+ return true;
633
+ if (a == null && b == null)
634
+ return true;
635
+ // Liquid treats `nil`/`empty` specially; for our use string vs string is enough.
636
+ if (typeof a === 'string' && typeof b === 'string')
637
+ return a === b;
638
+ return false;
639
+ }
640
+ function truthy(v) {
641
+ // Liquid: only nil and false are falsy; 0 and '' are truthy.
642
+ return v !== null && v !== undefined && v !== false;
643
+ }
644
+ // --- Filters ---------------------------------------------------------------
645
+ /**
646
+ * Apply a filter `name: arg1, arg2`. Strict: an unknown filter name throws
647
+ * PromptError('template_render_error') (REF strictFilters: true).
648
+ */
649
+ function applyFilter(spec, value, scope) {
650
+ const colon = spec.indexOf(':');
651
+ const name = (colon === -1 ? spec : spec.slice(0, colon)).trim();
652
+ const argStr = colon === -1 ? '' : spec.slice(colon + 1);
653
+ const args = argStr
654
+ .split(',')
655
+ .map((a) => a.trim())
656
+ .filter((a) => a.length > 0)
657
+ .map((a) => evalSoft(a, scope));
658
+ const fn = FILTERS[name];
659
+ if (!fn) {
660
+ throw new PromptError('template_render_error', `unknown filter: ${name}`);
661
+ }
662
+ return fn(value, args);
663
+ }
664
+ const FILTERS = {
665
+ upcase: (v) => stringify(v).toUpperCase(),
666
+ downcase: (v) => stringify(v).toLowerCase(),
667
+ capitalize: (v) => {
668
+ const s = stringify(v);
669
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
670
+ },
671
+ strip: (v) => stringify(v).trim(),
672
+ size: (v) => (Array.isArray(v) ? v.length : stringify(v).length),
673
+ default: (v, args) => (truthy(v) && stringify(v).length > 0 ? v : (args[0] ?? '')),
674
+ join: (v, args) => (Array.isArray(v) ? v.map(stringify).join(stringify(args[0] ?? ' ')) : stringify(v)),
675
+ first: (v) => (Array.isArray(v) ? v[0] : stringify(v)[0]),
676
+ last: (v) => (Array.isArray(v) ? v[v.length - 1] : stringify(v).slice(-1)),
677
+ append: (v, args) => stringify(v) + stringify(args[0] ?? ''),
678
+ prepend: (v, args) => stringify(args[0] ?? '') + stringify(v),
679
+ replace: (v, args) => stringify(v).split(stringify(args[0] ?? '')).join(stringify(args[1] ?? '')),
680
+ truncate: (v, args) => {
681
+ const s = stringify(v);
682
+ const n = typeof args[0] === 'number' ? args[0] : Number(args[0] ?? 50);
683
+ return s.length <= n ? s : s.slice(0, Math.max(0, n - 3)) + '...';
684
+ },
685
+ };
686
+ // --- Output stringification ------------------------------------------------
687
+ function stringify(v) {
688
+ if (v === null || v === undefined)
689
+ return '';
690
+ if (typeof v === 'string')
691
+ return v;
692
+ if (typeof v === 'number' || typeof v === 'boolean')
693
+ return String(v);
694
+ if (Array.isArray(v))
695
+ return v.map(stringify).join('');
696
+ return String(v);
697
+ }
698
+ // --- Top-level render ------------------------------------------------------
699
+ function renderLiquid(template, scope) {
700
+ let tokens;
701
+ let nodes;
702
+ try {
703
+ tokens = tokenize(template);
704
+ nodes = parse(tokens);
705
+ }
706
+ catch (err) {
707
+ if (err instanceof PromptError && err.code === 'template_parse_error')
708
+ throw err;
709
+ throw new PromptError('template_parse_error', err.message);
710
+ }
711
+ try {
712
+ return renderNodes(nodes, scope);
713
+ }
714
+ catch (err) {
715
+ if (err instanceof PromptError)
716
+ throw err;
717
+ throw new PromptError('template_render_error', err.message);
718
+ }
719
+ }
720
+ //# sourceMappingURL=prompt.js.map