smol-symphony 0.2.0 → 0.3.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 (540) 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/bin/symphony.js +22 -786
  11. package/dist/bin/symphony.js.map +1 -1
  12. package/dist/core/actions/context.js +109 -0
  13. package/dist/core/actions/context.js.map +1 -0
  14. package/dist/{actions/parsing.js → core/actions/parse.js} +33 -114
  15. package/dist/core/actions/parse.js.map +1 -0
  16. package/dist/core/actions/plan.js +197 -0
  17. package/dist/core/actions/plan.js.map +1 -0
  18. package/dist/core/actions/predicates.js +111 -0
  19. package/dist/core/actions/predicates.js.map +1 -0
  20. package/dist/core/actions/run-fold.js +248 -0
  21. package/dist/core/actions/run-fold.js.map +1 -0
  22. package/dist/core/actions/template.js +118 -0
  23. package/dist/core/actions/template.js.map +1 -0
  24. package/dist/core/cli/args.js +116 -0
  25. package/dist/core/cli/args.js.map +1 -0
  26. package/dist/core/coerce.js +75 -0
  27. package/dist/core/coerce.js.map +1 -0
  28. package/dist/core/credential/account-id.js +20 -0
  29. package/dist/core/credential/account-id.js.map +1 -0
  30. package/dist/core/credential/adapter-config.js +136 -0
  31. package/dist/core/credential/adapter-config.js.map +1 -0
  32. package/dist/core/credential/availability.js +98 -0
  33. package/dist/core/credential/availability.js.map +1 -0
  34. package/dist/core/credential/extract.js +228 -0
  35. package/dist/core/credential/extract.js.map +1 -0
  36. package/dist/core/credential/fake-creds.js +171 -0
  37. package/dist/core/credential/fake-creds.js.map +1 -0
  38. package/dist/core/credential/identity.js +125 -0
  39. package/dist/core/credential/identity.js.map +1 -0
  40. package/dist/core/credential/shape.js +230 -0
  41. package/dist/core/credential/shape.js.map +1 -0
  42. package/dist/core/credential/strings.js +15 -0
  43. package/dist/core/credential/strings.js.map +1 -0
  44. package/dist/core/doctor/checks.js +303 -0
  45. package/dist/core/doctor/checks.js.map +1 -0
  46. package/dist/core/git/result.js +107 -0
  47. package/dist/core/git/result.js.map +1 -0
  48. package/dist/core/http/decisions.js +225 -0
  49. package/dist/core/http/decisions.js.map +1 -0
  50. package/dist/{http.js → core/http/render.js} +472 -738
  51. package/dist/core/http/render.js.map +1 -0
  52. package/dist/{http-handlers.js → core/http/routes.js} +52 -87
  53. package/dist/core/http/routes.js.map +1 -0
  54. package/dist/core/http/views.js +181 -0
  55. package/dist/core/http/views.js.map +1 -0
  56. package/dist/core/image/managed-image.js +95 -0
  57. package/dist/core/image/managed-image.js.map +1 -0
  58. package/dist/core/issue/file.js +149 -0
  59. package/dist/core/issue/file.js.map +1 -0
  60. package/dist/core/issue/parse.js +210 -0
  61. package/dist/core/issue/parse.js.map +1 -0
  62. package/dist/core/mcp/dispatch.js +239 -0
  63. package/dist/core/mcp/dispatch.js.map +1 -0
  64. package/dist/core/mcp/post-move.js +92 -0
  65. package/dist/core/mcp/post-move.js.map +1 -0
  66. package/dist/core/mcp/protocol.js +293 -0
  67. package/dist/core/mcp/protocol.js.map +1 -0
  68. package/dist/core/mcp/url.js +162 -0
  69. package/dist/core/mcp/url.js.map +1 -0
  70. package/dist/core/path.js +63 -0
  71. package/dist/core/path.js.map +1 -0
  72. package/dist/core/reconcile/image-decide.js +48 -0
  73. package/dist/core/reconcile/image-decide.js.map +1 -0
  74. package/dist/core/reconcile/ledger.js +142 -0
  75. package/dist/core/reconcile/ledger.js.map +1 -0
  76. package/dist/core/reconcile/pr-classify.js +62 -0
  77. package/dist/core/reconcile/pr-classify.js.map +1 -0
  78. package/dist/{reconciler → core/reconcile}/pr-decide.js +25 -12
  79. package/dist/core/reconcile/pr-decide.js.map +1 -0
  80. package/dist/core/reconcile/pr-loop.js +161 -0
  81. package/dist/core/reconcile/pr-loop.js.map +1 -0
  82. package/dist/core/reconcile/pr-notes.js +35 -0
  83. package/dist/core/reconcile/pr-notes.js.map +1 -0
  84. package/dist/core/reconcile/vm-decide.js +70 -0
  85. package/dist/core/reconcile/vm-decide.js.map +1 -0
  86. package/dist/core/reconcile/vm-reap.js +207 -0
  87. package/dist/core/reconcile/vm-reap.js.map +1 -0
  88. package/dist/core/reconcile/workspace-decide.js +162 -0
  89. package/dist/core/reconcile/workspace-decide.js.map +1 -0
  90. package/dist/core/runlog/summary.js +231 -0
  91. package/dist/core/runlog/summary.js.map +1 -0
  92. package/dist/core/runner/dispatch-config.js +95 -0
  93. package/dist/core/runner/dispatch-config.js.map +1 -0
  94. package/dist/core/runner/injection.js +61 -0
  95. package/dist/core/runner/injection.js.map +1 -0
  96. package/dist/core/runner/mise.js +210 -0
  97. package/dist/core/runner/mise.js.map +1 -0
  98. package/dist/core/runner/prompt.js +720 -0
  99. package/dist/core/runner/prompt.js.map +1 -0
  100. package/dist/core/runner/turn.js +242 -0
  101. package/dist/core/runner/turn.js.map +1 -0
  102. package/dist/core/runner/vm-plan.js +390 -0
  103. package/dist/core/runner/vm-plan.js.map +1 -0
  104. package/dist/core/schedule/admission.js +123 -0
  105. package/dist/core/schedule/admission.js.map +1 -0
  106. package/dist/core/schedule/circuit-breaker.js +111 -0
  107. package/dist/core/schedule/circuit-breaker.js.map +1 -0
  108. package/dist/core/schedule/eligibility.js +83 -0
  109. package/dist/core/schedule/eligibility.js.map +1 -0
  110. package/dist/core/schedule/reconcile-issue.js +82 -0
  111. package/dist/core/schedule/reconcile-issue.js.map +1 -0
  112. package/dist/core/schedule/retry.js +96 -0
  113. package/dist/core/schedule/retry.js.map +1 -0
  114. package/dist/core/schedule/sleep-cycle.js +133 -0
  115. package/dist/core/schedule/sleep-cycle.js.map +1 -0
  116. package/dist/core/schedule/slots.js +124 -0
  117. package/dist/core/schedule/slots.js.map +1 -0
  118. package/dist/core/schedule/tick.js +553 -0
  119. package/dist/core/schedule/tick.js.map +1 -0
  120. package/dist/core/schedule/token-fold.js +181 -0
  121. package/dist/core/schedule/token-fold.js.map +1 -0
  122. package/dist/core/state-resolve.js +86 -0
  123. package/dist/core/state-resolve.js.map +1 -0
  124. package/dist/core/vm-guards.js +278 -0
  125. package/dist/core/vm-guards.js.map +1 -0
  126. package/dist/core/workflow/derive.js +107 -0
  127. package/dist/core/workflow/derive.js.map +1 -0
  128. package/dist/core/workflow/parse.js +687 -0
  129. package/dist/core/workflow/parse.js.map +1 -0
  130. package/dist/core/workflow/prompt-probe.js +78 -0
  131. package/dist/core/workflow/prompt-probe.js.map +1 -0
  132. package/dist/core/workflow/validate.js +189 -0
  133. package/dist/core/workflow/validate.js.map +1 -0
  134. package/dist/core/workspace-key.js +19 -0
  135. package/dist/core/workspace-key.js.map +1 -0
  136. package/dist/shell/actions-runner.js +356 -0
  137. package/dist/shell/actions-runner.js.map +1 -0
  138. package/dist/shell/adapter/adapter-registry.js +45 -0
  139. package/dist/shell/adapter/adapter-registry.js.map +1 -0
  140. package/dist/shell/adapter/clock-random.js +96 -0
  141. package/dist/shell/adapter/clock-random.js.map +1 -0
  142. package/dist/shell/adapter/gondolin-dispatch-helpers.js +158 -0
  143. package/dist/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
  144. package/dist/shell/adapter/gondolin-dispatch.js +385 -0
  145. package/dist/shell/adapter/gondolin-dispatch.js.map +1 -0
  146. package/dist/shell/adapter/gondolin-image-converter.js +233 -0
  147. package/dist/shell/adapter/gondolin-image-converter.js.map +1 -0
  148. package/dist/shell/adapter/gondolin-image-fetch.js +180 -0
  149. package/dist/shell/adapter/gondolin-image-fetch.js.map +1 -0
  150. package/dist/shell/adapter/launcher-asset.js +57 -0
  151. package/dist/shell/adapter/launcher-asset.js.map +1 -0
  152. package/dist/shell/adapter/mise-config-asset.js +65 -0
  153. package/dist/shell/adapter/mise-config-asset.js.map +1 -0
  154. package/dist/shell/adapter/workflow-loader.js +304 -0
  155. package/dist/shell/adapter/workflow-loader.js.map +1 -0
  156. package/dist/shell/cli/doctor.js +268 -0
  157. package/dist/shell/cli/doctor.js.map +1 -0
  158. package/dist/shell/effect-interpreter-families.js +314 -0
  159. package/dist/shell/effect-interpreter-families.js.map +1 -0
  160. package/dist/shell/effect-interpreter.js +29 -0
  161. package/dist/shell/effect-interpreter.js.map +1 -0
  162. package/dist/shell/interp/acp-frame.js +137 -0
  163. package/dist/shell/interp/acp-frame.js.map +1 -0
  164. package/dist/shell/interp/acp-ws-conn.js +320 -0
  165. package/dist/shell/interp/acp-ws-conn.js.map +1 -0
  166. package/dist/shell/interp/acp-ws-frames.js +159 -0
  167. package/dist/shell/interp/acp-ws-frames.js.map +1 -0
  168. package/dist/shell/interp/acp-ws.js +197 -0
  169. package/dist/shell/interp/acp-ws.js.map +1 -0
  170. package/dist/shell/interp/acp.js +319 -0
  171. package/dist/shell/interp/acp.js.map +1 -0
  172. package/dist/shell/interp/credential-defaults.js +128 -0
  173. package/dist/shell/interp/credential-defaults.js.map +1 -0
  174. package/dist/shell/interp/credential-hooks.js +149 -0
  175. package/dist/shell/interp/credential-hooks.js.map +1 -0
  176. package/dist/shell/interp/credential-registry.js +226 -0
  177. package/dist/shell/interp/credential-registry.js.map +1 -0
  178. package/dist/shell/interp/credential.js +103 -0
  179. package/dist/shell/interp/credential.js.map +1 -0
  180. package/dist/shell/interp/gh.js +163 -0
  181. package/dist/shell/interp/gh.js.map +1 -0
  182. package/dist/shell/interp/git.js +28 -0
  183. package/dist/shell/interp/git.js.map +1 -0
  184. package/dist/shell/interp/log.js +213 -0
  185. package/dist/shell/interp/log.js.map +1 -0
  186. package/dist/shell/interp/process.js +178 -0
  187. package/dist/shell/interp/process.js.map +1 -0
  188. package/dist/shell/interp/runlog.js +193 -0
  189. package/dist/shell/interp/runlog.js.map +1 -0
  190. package/dist/shell/interp/timer.js +64 -0
  191. package/dist/shell/interp/timer.js.map +1 -0
  192. package/dist/shell/interp/tracker-disk.js +99 -0
  193. package/dist/shell/interp/tracker-disk.js.map +1 -0
  194. package/dist/shell/interp/tracker-parse.js +71 -0
  195. package/dist/shell/interp/tracker-parse.js.map +1 -0
  196. package/dist/shell/interp/tracker-scan.js +238 -0
  197. package/dist/shell/interp/tracker-scan.js.map +1 -0
  198. package/dist/shell/interp/tracker-write.js +91 -0
  199. package/dist/shell/interp/tracker-write.js.map +1 -0
  200. package/dist/shell/interp/tracker.js +41 -0
  201. package/dist/shell/interp/tracker.js.map +1 -0
  202. package/dist/shell/interp/tty.js +48 -0
  203. package/dist/shell/interp/tty.js.map +1 -0
  204. package/dist/shell/interp/vm.js +199 -0
  205. package/dist/shell/interp/vm.js.map +1 -0
  206. package/dist/shell/interp/workspace.js +310 -0
  207. package/dist/shell/interp/workspace.js.map +1 -0
  208. package/dist/shell/main-acp.js +78 -0
  209. package/dist/shell/main-acp.js.map +1 -0
  210. package/dist/shell/main-adapters.js +222 -0
  211. package/dist/shell/main-adapters.js.map +1 -0
  212. package/dist/shell/main-credential.js +122 -0
  213. package/dist/shell/main-credential.js.map +1 -0
  214. package/dist/shell/main-doctor.js +22 -0
  215. package/dist/shell/main-doctor.js.map +1 -0
  216. package/dist/shell/main-entry.js +46 -0
  217. package/dist/shell/main-entry.js.map +1 -0
  218. package/dist/shell/main-http-csrf.js +45 -0
  219. package/dist/shell/main-http-csrf.js.map +1 -0
  220. package/dist/shell/main-http-handler.js +389 -0
  221. package/dist/shell/main-http-handler.js.map +1 -0
  222. package/dist/shell/main-http-mcp.js +122 -0
  223. package/dist/shell/main-http-mcp.js.map +1 -0
  224. package/dist/shell/main-http-views.js +253 -0
  225. package/dist/shell/main-http-views.js.map +1 -0
  226. package/dist/shell/main-http.js +76 -0
  227. package/dist/shell/main-http.js.map +1 -0
  228. package/dist/shell/main-loops.js +130 -0
  229. package/dist/shell/main-loops.js.map +1 -0
  230. package/dist/shell/main-mcp.js +129 -0
  231. package/dist/shell/main-mcp.js.map +1 -0
  232. package/dist/shell/main-orchestrator.js +120 -0
  233. package/dist/shell/main-orchestrator.js.map +1 -0
  234. package/dist/shell/main-preflight.js +43 -0
  235. package/dist/shell/main-preflight.js.map +1 -0
  236. package/dist/shell/main-reconcilers-helpers.js +244 -0
  237. package/dist/shell/main-reconcilers-helpers.js.map +1 -0
  238. package/dist/shell/main-reconcilers-pr.js +148 -0
  239. package/dist/shell/main-reconcilers-pr.js.map +1 -0
  240. package/dist/shell/main-reconcilers.js +225 -0
  241. package/dist/shell/main-reconcilers.js.map +1 -0
  242. package/dist/shell/main-runner.js +355 -0
  243. package/dist/shell/main-runner.js.map +1 -0
  244. package/dist/shell/main-scaffold.js +116 -0
  245. package/dist/shell/main-scaffold.js.map +1 -0
  246. package/dist/shell/main-shutdown.js +115 -0
  247. package/dist/shell/main-shutdown.js.map +1 -0
  248. package/dist/shell/main-startup.js +48 -0
  249. package/dist/shell/main-startup.js.map +1 -0
  250. package/dist/shell/main-substrates.js +43 -0
  251. package/dist/shell/main-substrates.js.map +1 -0
  252. package/dist/shell/main.js +385 -0
  253. package/dist/shell/main.js.map +1 -0
  254. package/dist/shell/orchestrator-feedback.js +69 -0
  255. package/dist/shell/orchestrator-feedback.js.map +1 -0
  256. package/dist/shell/orchestrator-image.js +167 -0
  257. package/dist/shell/orchestrator-image.js.map +1 -0
  258. package/dist/shell/orchestrator-loop.js +468 -0
  259. package/dist/shell/orchestrator-loop.js.map +1 -0
  260. package/dist/shell/orchestrator-reconcile.js +36 -0
  261. package/dist/shell/orchestrator-reconcile.js.map +1 -0
  262. package/dist/shell/reconciler-loop.js +228 -0
  263. package/dist/shell/reconciler-loop.js.map +1 -0
  264. package/dist/shell/runner-loop-turn.js +301 -0
  265. package/dist/shell/runner-loop-turn.js.map +1 -0
  266. package/dist/shell/runner-loop.js +338 -0
  267. package/dist/shell/runner-loop.js.map +1 -0
  268. package/dist/shell/server/http.js +208 -0
  269. package/dist/shell/server/http.js.map +1 -0
  270. package/dist/shell/server/mcp-runtime-effects.js +237 -0
  271. package/dist/shell/server/mcp-runtime-effects.js.map +1 -0
  272. package/dist/shell/server/mcp-runtime.js +99 -0
  273. package/dist/shell/server/mcp-runtime.js.map +1 -0
  274. package/dist/shell/workspace-key.js +14 -0
  275. package/dist/shell/workspace-key.js.map +1 -0
  276. package/dist/types/acp.js +8 -0
  277. package/dist/types/acp.js.map +1 -0
  278. package/dist/types/actions/plan.js +6 -0
  279. package/dist/types/actions/plan.js.map +1 -0
  280. package/dist/types/actions/predicates.js +6 -0
  281. package/dist/types/actions/predicates.js.map +1 -0
  282. package/dist/types/actions/run-fold.js +8 -0
  283. package/dist/types/actions/run-fold.js.map +1 -0
  284. package/dist/types/actions.js +7 -0
  285. package/dist/types/actions.js.map +1 -0
  286. package/dist/types/adapter/clock-random.js +4 -0
  287. package/dist/types/adapter/clock-random.js.map +1 -0
  288. package/dist/types/adapter/gondolin-image-converter.js +5 -0
  289. package/dist/types/adapter/gondolin-image-converter.js.map +1 -0
  290. package/dist/types/adapter/gondolin-image-fetch.js +5 -0
  291. package/dist/types/adapter/gondolin-image-fetch.js.map +1 -0
  292. package/dist/types/adapter/workflow-loader.js +4 -0
  293. package/dist/types/adapter/workflow-loader.js.map +1 -0
  294. package/dist/types/cli/args.js +8 -0
  295. package/dist/types/cli/args.js.map +1 -0
  296. package/dist/types/config.js +8 -0
  297. package/dist/types/config.js.map +1 -0
  298. package/dist/types/credential-interp.js +6 -0
  299. package/dist/types/credential-interp.js.map +1 -0
  300. package/dist/types/credentials.js +10 -0
  301. package/dist/types/credentials.js.map +1 -0
  302. package/dist/types/doctor.js +7 -0
  303. package/dist/types/doctor.js.map +1 -0
  304. package/dist/types/domain.js +7 -0
  305. package/dist/types/domain.js.map +1 -0
  306. package/dist/types/effect.js +15 -0
  307. package/dist/types/effect.js.map +1 -0
  308. package/dist/types/errors.js +39 -0
  309. package/dist/types/errors.js.map +1 -0
  310. package/dist/types/http/decisions.js +6 -0
  311. package/dist/types/http/decisions.js.map +1 -0
  312. package/dist/types/http/render.js +10 -0
  313. package/dist/types/http/render.js.map +1 -0
  314. package/dist/types/http/views.js +6 -0
  315. package/dist/types/http/views.js.map +1 -0
  316. package/dist/types/http.js +9 -0
  317. package/dist/types/http.js.map +1 -0
  318. package/dist/types/image/managed-image.js +7 -0
  319. package/dist/types/image/managed-image.js.map +1 -0
  320. package/dist/types/interp/effect-interpreter.js +8 -0
  321. package/dist/types/interp/effect-interpreter.js.map +1 -0
  322. package/dist/types/interp/tracker.js +7 -0
  323. package/dist/types/interp/tracker.js.map +1 -0
  324. package/dist/types/issue/file.js +6 -0
  325. package/dist/types/issue/file.js.map +1 -0
  326. package/dist/types/issue/parse.js +8 -0
  327. package/dist/types/issue/parse.js.map +1 -0
  328. package/dist/types/main-acp.js +13 -0
  329. package/dist/types/main-acp.js.map +1 -0
  330. package/dist/types/main-adapters.js +5 -0
  331. package/dist/types/main-adapters.js.map +1 -0
  332. package/dist/types/main-credential.js +21 -0
  333. package/dist/types/main-credential.js.map +1 -0
  334. package/dist/types/main-doctor.js +6 -0
  335. package/dist/types/main-doctor.js.map +1 -0
  336. package/dist/types/main-http-handler.js +12 -0
  337. package/dist/types/main-http-handler.js.map +1 -0
  338. package/dist/types/main-http.js +5 -0
  339. package/dist/types/main-http.js.map +1 -0
  340. package/dist/types/main-loops.js +5 -0
  341. package/dist/types/main-loops.js.map +1 -0
  342. package/dist/types/main-mcp.js +12 -0
  343. package/dist/types/main-mcp.js.map +1 -0
  344. package/dist/types/main-orchestrator.js +5 -0
  345. package/dist/types/main-orchestrator.js.map +1 -0
  346. package/dist/types/main-reconcilers.js +11 -0
  347. package/dist/types/main-reconcilers.js.map +1 -0
  348. package/dist/types/main-runner.js +13 -0
  349. package/dist/types/main-runner.js.map +1 -0
  350. package/dist/types/main-startup.js +5 -0
  351. package/dist/types/main-startup.js.map +1 -0
  352. package/dist/types/main-substrates.js +5 -0
  353. package/dist/types/main-substrates.js.map +1 -0
  354. package/dist/types/mcp/dispatch.js +4 -0
  355. package/dist/types/mcp/dispatch.js.map +1 -0
  356. package/dist/types/mcp/post-move.js +7 -0
  357. package/dist/types/mcp/post-move.js.map +1 -0
  358. package/dist/types/mcp.js +9 -0
  359. package/dist/types/mcp.js.map +1 -0
  360. package/dist/types/ports.js +12 -0
  361. package/dist/types/ports.js.map +1 -0
  362. package/dist/types/reconcile/image-decide.js +5 -0
  363. package/dist/types/reconcile/image-decide.js.map +1 -0
  364. package/dist/types/reconcile/ledger.js +7 -0
  365. package/dist/types/reconcile/ledger.js.map +1 -0
  366. package/dist/types/reconcile/pr-loop.js +8 -0
  367. package/dist/types/reconcile/pr-loop.js.map +1 -0
  368. package/dist/types/reconcile/vm-reap.js +8 -0
  369. package/dist/types/reconcile/vm-reap.js.map +1 -0
  370. package/dist/types/reconcile/workspace-decide.js +7 -0
  371. package/dist/types/reconcile/workspace-decide.js.map +1 -0
  372. package/dist/types/reconcile.js +9 -0
  373. package/dist/types/reconcile.js.map +1 -0
  374. package/dist/types/runlog.js +7 -0
  375. package/dist/types/runlog.js.map +1 -0
  376. package/dist/types/runner/actions-runner.js +12 -0
  377. package/dist/types/runner/actions-runner.js.map +1 -0
  378. package/dist/types/runner/gondolin-dispatch.js +5 -0
  379. package/dist/types/runner/gondolin-dispatch.js.map +1 -0
  380. package/dist/types/runner/injection.js +6 -0
  381. package/dist/types/runner/injection.js.map +1 -0
  382. package/dist/types/runner/runner-loop.js +5 -0
  383. package/dist/types/runner/runner-loop.js.map +1 -0
  384. package/dist/types/runner/turn.js +4 -0
  385. package/dist/types/runner/turn.js.map +1 -0
  386. package/dist/types/runner/vm-plan.js +4 -0
  387. package/dist/types/runner/vm-plan.js.map +1 -0
  388. package/dist/types/runtime.js +9 -0
  389. package/dist/types/runtime.js.map +1 -0
  390. package/dist/types/schedule/admission.js +7 -0
  391. package/dist/types/schedule/admission.js.map +1 -0
  392. package/dist/types/schedule/circuit-breaker.js +2 -0
  393. package/dist/types/schedule/circuit-breaker.js.map +1 -0
  394. package/dist/types/schedule/eligibility.js +9 -0
  395. package/dist/types/schedule/eligibility.js.map +1 -0
  396. package/dist/types/schedule/orchestrator-loop.js +10 -0
  397. package/dist/types/schedule/orchestrator-loop.js.map +1 -0
  398. package/dist/types/schedule/sleep-cycle.js +4 -0
  399. package/dist/types/schedule/sleep-cycle.js.map +1 -0
  400. package/dist/types/schedule/slots.js +8 -0
  401. package/dist/types/schedule/slots.js.map +1 -0
  402. package/dist/types/schedule/tick.js +9 -0
  403. package/dist/types/schedule/tick.js.map +1 -0
  404. package/dist/types/server/mcp-runtime.js +8 -0
  405. package/dist/types/server/mcp-runtime.js.map +1 -0
  406. package/dist/types/workflow/parse.js +4 -0
  407. package/dist/types/workflow/parse.js.map +1 -0
  408. package/package.json +22 -10
  409. package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
  410. package/prompts/Reflect.md +91 -0
  411. package/prompts/Review.md +97 -0
  412. package/prompts/Todo.md +96 -0
  413. package/prompts/_footer.md +41 -0
  414. package/prompts/_preamble.md +42 -0
  415. package/prompts-minimal/Todo.md +26 -0
  416. package/scripts/postinstall.mjs +63 -0
  417. package/scripts/vm-agent.mjs +312 -90
  418. package/WORKFLOW.md +0 -744
  419. package/dist/acp-bridge.js +0 -324
  420. package/dist/acp-bridge.js.map +0 -1
  421. package/dist/actions/cache.js +0 -191
  422. package/dist/actions/cache.js.map +0 -1
  423. package/dist/actions/effects.js +0 -41
  424. package/dist/actions/effects.js.map +0 -1
  425. package/dist/actions/executor.js +0 -570
  426. package/dist/actions/executor.js.map +0 -1
  427. package/dist/actions/index.js +0 -13
  428. package/dist/actions/index.js.map +0 -1
  429. package/dist/actions/parsing.js.map +0 -1
  430. package/dist/actions/predicate-env.js +0 -27
  431. package/dist/actions/predicate-env.js.map +0 -1
  432. package/dist/actions/predicates.js +0 -49
  433. package/dist/actions/predicates.js.map +0 -1
  434. package/dist/actions/templating.js +0 -66
  435. package/dist/actions/templating.js.map +0 -1
  436. package/dist/actions/types.js +0 -15
  437. package/dist/actions/types.js.map +0 -1
  438. package/dist/agent/acp.js +0 -473
  439. package/dist/agent/acp.js.map +0 -1
  440. package/dist/agent/adapter-names.js +0 -159
  441. package/dist/agent/adapter-names.js.map +0 -1
  442. package/dist/agent/adapters.js +0 -511
  443. package/dist/agent/adapters.js.map +0 -1
  444. package/dist/agent/credential-extractors.js +0 -342
  445. package/dist/agent/credential-extractors.js.map +0 -1
  446. package/dist/agent/credential-secrets.js +0 -628
  447. package/dist/agent/credential-secrets.js.map +0 -1
  448. package/dist/agent/credential-ticker.js +0 -57
  449. package/dist/agent/credential-ticker.js.map +0 -1
  450. package/dist/agent/gondolin-creds-staging.js +0 -356
  451. package/dist/agent/gondolin-creds-staging.js.map +0 -1
  452. package/dist/agent/gondolin-dispatch.js +0 -375
  453. package/dist/agent/gondolin-dispatch.js.map +0 -1
  454. package/dist/agent/gondolin.js +0 -124
  455. package/dist/agent/gondolin.js.map +0 -1
  456. package/dist/agent/runner-decisions.js +0 -134
  457. package/dist/agent/runner-decisions.js.map +0 -1
  458. package/dist/agent/runner.js +0 -1456
  459. package/dist/agent/runner.js.map +0 -1
  460. package/dist/agent/tool-call-summary.js +0 -102
  461. package/dist/agent/tool-call-summary.js.map +0 -1
  462. package/dist/agent/vm-acp-mapping.js +0 -73
  463. package/dist/agent/vm-acp-mapping.js.map +0 -1
  464. package/dist/agent/vm-guards.js +0 -262
  465. package/dist/agent/vm-guards.js.map +0 -1
  466. package/dist/agent/vm-port.js +0 -22
  467. package/dist/agent/vm-port.js.map +0 -1
  468. package/dist/agent/vm-process-registry.js +0 -79
  469. package/dist/agent/vm-process-registry.js.map +0 -1
  470. package/dist/bin/cli-args.js +0 -105
  471. package/dist/bin/cli-args.js.map +0 -1
  472. package/dist/errors.js +0 -15
  473. package/dist/errors.js.map +0 -1
  474. package/dist/http-disk.js +0 -135
  475. package/dist/http-disk.js.map +0 -1
  476. package/dist/http-handlers.js.map +0 -1
  477. package/dist/http.js.map +0 -1
  478. package/dist/issues.js +0 -178
  479. package/dist/issues.js.map +0 -1
  480. package/dist/logging.js +0 -203
  481. package/dist/logging.js.map +0 -1
  482. package/dist/mcp.js +0 -706
  483. package/dist/mcp.js.map +0 -1
  484. package/dist/memory.js +0 -85
  485. package/dist/memory.js.map +0 -1
  486. package/dist/orchestrator-decisions.js +0 -331
  487. package/dist/orchestrator-decisions.js.map +0 -1
  488. package/dist/orchestrator.js +0 -1569
  489. package/dist/orchestrator.js.map +0 -1
  490. package/dist/prompt.js +0 -65
  491. package/dist/prompt.js.map +0 -1
  492. package/dist/reconciler/cache.js +0 -65
  493. package/dist/reconciler/cache.js.map +0 -1
  494. package/dist/reconciler/index.js +0 -448
  495. package/dist/reconciler/index.js.map +0 -1
  496. package/dist/reconciler/ledger.js +0 -131
  497. package/dist/reconciler/ledger.js.map +0 -1
  498. package/dist/reconciler/pr-adapters.js +0 -174
  499. package/dist/reconciler/pr-adapters.js.map +0 -1
  500. package/dist/reconciler/pr-decide.js.map +0 -1
  501. package/dist/reconciler/pr.js +0 -422
  502. package/dist/reconciler/pr.js.map +0 -1
  503. package/dist/reconciler/types.js +0 -12
  504. package/dist/reconciler/types.js.map +0 -1
  505. package/dist/reconciler/vm.js +0 -243
  506. package/dist/reconciler/vm.js.map +0 -1
  507. package/dist/reconciler/workspace-defaults.js +0 -83
  508. package/dist/reconciler/workspace-defaults.js.map +0 -1
  509. package/dist/reconciler/workspace.js +0 -272
  510. package/dist/reconciler/workspace.js.map +0 -1
  511. package/dist/runlog.js +0 -403
  512. package/dist/runlog.js.map +0 -1
  513. package/dist/scaffold.js +0 -165
  514. package/dist/scaffold.js.map +0 -1
  515. package/dist/trackers/local.js +0 -445
  516. package/dist/trackers/local.js.map +0 -1
  517. package/dist/trackers/types.js +0 -10
  518. package/dist/trackers/types.js.map +0 -1
  519. package/dist/types.js +0 -3
  520. package/dist/types.js.map +0 -1
  521. package/dist/util/clock.js +0 -12
  522. package/dist/util/clock.js.map +0 -1
  523. package/dist/util/crypto.js +0 -25
  524. package/dist/util/crypto.js.map +0 -1
  525. package/dist/util/frontmatter.js +0 -70
  526. package/dist/util/frontmatter.js.map +0 -1
  527. package/dist/util/fs-issues.js +0 -22
  528. package/dist/util/fs-issues.js.map +0 -1
  529. package/dist/util/process.js +0 -152
  530. package/dist/util/process.js.map +0 -1
  531. package/dist/util/workspace-key.js +0 -10
  532. package/dist/util/workspace-key.js.map +0 -1
  533. package/dist/workflow-loader.js +0 -147
  534. package/dist/workflow-loader.js.map +0 -1
  535. package/dist/workflow.js +0 -822
  536. package/dist/workflow.js.map +0 -1
  537. package/dist/workspace-types.js +0 -8
  538. package/dist/workspace-types.js.map +0 -1
  539. package/dist/workspace.js +0 -443
  540. 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