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.
- package/AGENTS.md +41 -22
- package/DESIGN.md +494 -273
- package/README.md +109 -57
- package/SPEC.md +33 -24
- package/WORKFLOW.minimal.yaml +34 -0
- package/{WORKFLOW.template.md → WORKFLOW.template.yaml} +409 -256
- package/WORKFLOW.yaml +487 -0
- package/assets/skills/symphony-issues/SKILL.md +136 -0
- package/assets/symphony-mise.system.toml +68 -0
- package/dist/bin/symphony.js +22 -786
- package/dist/bin/symphony.js.map +1 -1
- package/dist/core/actions/context.js +109 -0
- package/dist/core/actions/context.js.map +1 -0
- package/dist/{actions/parsing.js → core/actions/parse.js} +33 -114
- package/dist/core/actions/parse.js.map +1 -0
- package/dist/core/actions/plan.js +197 -0
- package/dist/core/actions/plan.js.map +1 -0
- package/dist/core/actions/predicates.js +111 -0
- package/dist/core/actions/predicates.js.map +1 -0
- package/dist/core/actions/run-fold.js +248 -0
- package/dist/core/actions/run-fold.js.map +1 -0
- package/dist/core/actions/template.js +118 -0
- package/dist/core/actions/template.js.map +1 -0
- package/dist/core/cli/args.js +116 -0
- package/dist/core/cli/args.js.map +1 -0
- package/dist/core/coerce.js +75 -0
- package/dist/core/coerce.js.map +1 -0
- package/dist/core/credential/account-id.js +20 -0
- package/dist/core/credential/account-id.js.map +1 -0
- package/dist/core/credential/adapter-config.js +136 -0
- package/dist/core/credential/adapter-config.js.map +1 -0
- package/dist/core/credential/availability.js +98 -0
- package/dist/core/credential/availability.js.map +1 -0
- package/dist/core/credential/extract.js +228 -0
- package/dist/core/credential/extract.js.map +1 -0
- package/dist/core/credential/fake-creds.js +171 -0
- package/dist/core/credential/fake-creds.js.map +1 -0
- package/dist/core/credential/identity.js +125 -0
- package/dist/core/credential/identity.js.map +1 -0
- package/dist/core/credential/shape.js +230 -0
- package/dist/core/credential/shape.js.map +1 -0
- package/dist/core/credential/strings.js +15 -0
- package/dist/core/credential/strings.js.map +1 -0
- package/dist/core/doctor/checks.js +303 -0
- package/dist/core/doctor/checks.js.map +1 -0
- package/dist/core/git/result.js +107 -0
- package/dist/core/git/result.js.map +1 -0
- package/dist/core/http/decisions.js +225 -0
- package/dist/core/http/decisions.js.map +1 -0
- package/dist/{http.js → core/http/render.js} +472 -738
- package/dist/core/http/render.js.map +1 -0
- package/dist/{http-handlers.js → core/http/routes.js} +52 -87
- package/dist/core/http/routes.js.map +1 -0
- package/dist/core/http/views.js +181 -0
- package/dist/core/http/views.js.map +1 -0
- package/dist/core/image/managed-image.js +95 -0
- package/dist/core/image/managed-image.js.map +1 -0
- package/dist/core/issue/file.js +149 -0
- package/dist/core/issue/file.js.map +1 -0
- package/dist/core/issue/parse.js +210 -0
- package/dist/core/issue/parse.js.map +1 -0
- package/dist/core/mcp/dispatch.js +239 -0
- package/dist/core/mcp/dispatch.js.map +1 -0
- package/dist/core/mcp/post-move.js +92 -0
- package/dist/core/mcp/post-move.js.map +1 -0
- package/dist/core/mcp/protocol.js +293 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/url.js +162 -0
- package/dist/core/mcp/url.js.map +1 -0
- package/dist/core/path.js +63 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/reconcile/image-decide.js +48 -0
- package/dist/core/reconcile/image-decide.js.map +1 -0
- package/dist/core/reconcile/ledger.js +142 -0
- package/dist/core/reconcile/ledger.js.map +1 -0
- package/dist/core/reconcile/pr-classify.js +62 -0
- package/dist/core/reconcile/pr-classify.js.map +1 -0
- package/dist/{reconciler → core/reconcile}/pr-decide.js +25 -12
- package/dist/core/reconcile/pr-decide.js.map +1 -0
- package/dist/core/reconcile/pr-loop.js +161 -0
- package/dist/core/reconcile/pr-loop.js.map +1 -0
- package/dist/core/reconcile/pr-notes.js +35 -0
- package/dist/core/reconcile/pr-notes.js.map +1 -0
- package/dist/core/reconcile/vm-decide.js +70 -0
- package/dist/core/reconcile/vm-decide.js.map +1 -0
- package/dist/core/reconcile/vm-reap.js +207 -0
- package/dist/core/reconcile/vm-reap.js.map +1 -0
- package/dist/core/reconcile/workspace-decide.js +162 -0
- package/dist/core/reconcile/workspace-decide.js.map +1 -0
- package/dist/core/runlog/summary.js +231 -0
- package/dist/core/runlog/summary.js.map +1 -0
- package/dist/core/runner/dispatch-config.js +95 -0
- package/dist/core/runner/dispatch-config.js.map +1 -0
- package/dist/core/runner/injection.js +61 -0
- package/dist/core/runner/injection.js.map +1 -0
- package/dist/core/runner/mise.js +210 -0
- package/dist/core/runner/mise.js.map +1 -0
- package/dist/core/runner/prompt.js +720 -0
- package/dist/core/runner/prompt.js.map +1 -0
- package/dist/core/runner/turn.js +242 -0
- package/dist/core/runner/turn.js.map +1 -0
- package/dist/core/runner/vm-plan.js +390 -0
- package/dist/core/runner/vm-plan.js.map +1 -0
- package/dist/core/schedule/admission.js +123 -0
- package/dist/core/schedule/admission.js.map +1 -0
- package/dist/core/schedule/circuit-breaker.js +111 -0
- package/dist/core/schedule/circuit-breaker.js.map +1 -0
- package/dist/core/schedule/eligibility.js +83 -0
- package/dist/core/schedule/eligibility.js.map +1 -0
- package/dist/core/schedule/reconcile-issue.js +82 -0
- package/dist/core/schedule/reconcile-issue.js.map +1 -0
- package/dist/core/schedule/retry.js +96 -0
- package/dist/core/schedule/retry.js.map +1 -0
- package/dist/core/schedule/sleep-cycle.js +133 -0
- package/dist/core/schedule/sleep-cycle.js.map +1 -0
- package/dist/core/schedule/slots.js +124 -0
- package/dist/core/schedule/slots.js.map +1 -0
- package/dist/core/schedule/tick.js +553 -0
- package/dist/core/schedule/tick.js.map +1 -0
- package/dist/core/schedule/token-fold.js +181 -0
- package/dist/core/schedule/token-fold.js.map +1 -0
- package/dist/core/state-resolve.js +86 -0
- package/dist/core/state-resolve.js.map +1 -0
- package/dist/core/vm-guards.js +278 -0
- package/dist/core/vm-guards.js.map +1 -0
- package/dist/core/workflow/derive.js +107 -0
- package/dist/core/workflow/derive.js.map +1 -0
- package/dist/core/workflow/parse.js +687 -0
- package/dist/core/workflow/parse.js.map +1 -0
- package/dist/core/workflow/prompt-probe.js +78 -0
- package/dist/core/workflow/prompt-probe.js.map +1 -0
- package/dist/core/workflow/validate.js +189 -0
- package/dist/core/workflow/validate.js.map +1 -0
- package/dist/core/workspace-key.js +19 -0
- package/dist/core/workspace-key.js.map +1 -0
- package/dist/shell/actions-runner.js +356 -0
- package/dist/shell/actions-runner.js.map +1 -0
- package/dist/shell/adapter/adapter-registry.js +45 -0
- package/dist/shell/adapter/adapter-registry.js.map +1 -0
- package/dist/shell/adapter/clock-random.js +96 -0
- package/dist/shell/adapter/clock-random.js.map +1 -0
- package/dist/shell/adapter/gondolin-dispatch-helpers.js +158 -0
- package/dist/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
- package/dist/shell/adapter/gondolin-dispatch.js +385 -0
- package/dist/shell/adapter/gondolin-dispatch.js.map +1 -0
- package/dist/shell/adapter/gondolin-image-converter.js +233 -0
- package/dist/shell/adapter/gondolin-image-converter.js.map +1 -0
- package/dist/shell/adapter/gondolin-image-fetch.js +180 -0
- package/dist/shell/adapter/gondolin-image-fetch.js.map +1 -0
- package/dist/shell/adapter/launcher-asset.js +57 -0
- package/dist/shell/adapter/launcher-asset.js.map +1 -0
- package/dist/shell/adapter/mise-config-asset.js +65 -0
- package/dist/shell/adapter/mise-config-asset.js.map +1 -0
- package/dist/shell/adapter/workflow-loader.js +304 -0
- package/dist/shell/adapter/workflow-loader.js.map +1 -0
- package/dist/shell/cli/doctor.js +268 -0
- package/dist/shell/cli/doctor.js.map +1 -0
- package/dist/shell/effect-interpreter-families.js +314 -0
- package/dist/shell/effect-interpreter-families.js.map +1 -0
- package/dist/shell/effect-interpreter.js +29 -0
- package/dist/shell/effect-interpreter.js.map +1 -0
- package/dist/shell/interp/acp-frame.js +137 -0
- package/dist/shell/interp/acp-frame.js.map +1 -0
- package/dist/shell/interp/acp-ws-conn.js +320 -0
- package/dist/shell/interp/acp-ws-conn.js.map +1 -0
- package/dist/shell/interp/acp-ws-frames.js +159 -0
- package/dist/shell/interp/acp-ws-frames.js.map +1 -0
- package/dist/shell/interp/acp-ws.js +197 -0
- package/dist/shell/interp/acp-ws.js.map +1 -0
- package/dist/shell/interp/acp.js +319 -0
- package/dist/shell/interp/acp.js.map +1 -0
- package/dist/shell/interp/credential-defaults.js +128 -0
- package/dist/shell/interp/credential-defaults.js.map +1 -0
- package/dist/shell/interp/credential-hooks.js +149 -0
- package/dist/shell/interp/credential-hooks.js.map +1 -0
- package/dist/shell/interp/credential-registry.js +226 -0
- package/dist/shell/interp/credential-registry.js.map +1 -0
- package/dist/shell/interp/credential.js +103 -0
- package/dist/shell/interp/credential.js.map +1 -0
- package/dist/shell/interp/gh.js +163 -0
- package/dist/shell/interp/gh.js.map +1 -0
- package/dist/shell/interp/git.js +28 -0
- package/dist/shell/interp/git.js.map +1 -0
- package/dist/shell/interp/log.js +213 -0
- package/dist/shell/interp/log.js.map +1 -0
- package/dist/shell/interp/process.js +178 -0
- package/dist/shell/interp/process.js.map +1 -0
- package/dist/shell/interp/runlog.js +193 -0
- package/dist/shell/interp/runlog.js.map +1 -0
- package/dist/shell/interp/timer.js +64 -0
- package/dist/shell/interp/timer.js.map +1 -0
- package/dist/shell/interp/tracker-disk.js +99 -0
- package/dist/shell/interp/tracker-disk.js.map +1 -0
- package/dist/shell/interp/tracker-parse.js +71 -0
- package/dist/shell/interp/tracker-parse.js.map +1 -0
- package/dist/shell/interp/tracker-scan.js +238 -0
- package/dist/shell/interp/tracker-scan.js.map +1 -0
- package/dist/shell/interp/tracker-write.js +91 -0
- package/dist/shell/interp/tracker-write.js.map +1 -0
- package/dist/shell/interp/tracker.js +41 -0
- package/dist/shell/interp/tracker.js.map +1 -0
- package/dist/shell/interp/tty.js +48 -0
- package/dist/shell/interp/tty.js.map +1 -0
- package/dist/shell/interp/vm.js +199 -0
- package/dist/shell/interp/vm.js.map +1 -0
- package/dist/shell/interp/workspace.js +310 -0
- package/dist/shell/interp/workspace.js.map +1 -0
- package/dist/shell/main-acp.js +78 -0
- package/dist/shell/main-acp.js.map +1 -0
- package/dist/shell/main-adapters.js +222 -0
- package/dist/shell/main-adapters.js.map +1 -0
- package/dist/shell/main-credential.js +122 -0
- package/dist/shell/main-credential.js.map +1 -0
- package/dist/shell/main-doctor.js +22 -0
- package/dist/shell/main-doctor.js.map +1 -0
- package/dist/shell/main-entry.js +46 -0
- package/dist/shell/main-entry.js.map +1 -0
- package/dist/shell/main-http-csrf.js +45 -0
- package/dist/shell/main-http-csrf.js.map +1 -0
- package/dist/shell/main-http-handler.js +389 -0
- package/dist/shell/main-http-handler.js.map +1 -0
- package/dist/shell/main-http-mcp.js +122 -0
- package/dist/shell/main-http-mcp.js.map +1 -0
- package/dist/shell/main-http-views.js +253 -0
- package/dist/shell/main-http-views.js.map +1 -0
- package/dist/shell/main-http.js +76 -0
- package/dist/shell/main-http.js.map +1 -0
- package/dist/shell/main-loops.js +130 -0
- package/dist/shell/main-loops.js.map +1 -0
- package/dist/shell/main-mcp.js +129 -0
- package/dist/shell/main-mcp.js.map +1 -0
- package/dist/shell/main-orchestrator.js +120 -0
- package/dist/shell/main-orchestrator.js.map +1 -0
- package/dist/shell/main-preflight.js +43 -0
- package/dist/shell/main-preflight.js.map +1 -0
- package/dist/shell/main-reconcilers-helpers.js +244 -0
- package/dist/shell/main-reconcilers-helpers.js.map +1 -0
- package/dist/shell/main-reconcilers-pr.js +148 -0
- package/dist/shell/main-reconcilers-pr.js.map +1 -0
- package/dist/shell/main-reconcilers.js +225 -0
- package/dist/shell/main-reconcilers.js.map +1 -0
- package/dist/shell/main-runner.js +355 -0
- package/dist/shell/main-runner.js.map +1 -0
- package/dist/shell/main-scaffold.js +116 -0
- package/dist/shell/main-scaffold.js.map +1 -0
- package/dist/shell/main-shutdown.js +115 -0
- package/dist/shell/main-shutdown.js.map +1 -0
- package/dist/shell/main-startup.js +48 -0
- package/dist/shell/main-startup.js.map +1 -0
- package/dist/shell/main-substrates.js +43 -0
- package/dist/shell/main-substrates.js.map +1 -0
- package/dist/shell/main.js +385 -0
- package/dist/shell/main.js.map +1 -0
- package/dist/shell/orchestrator-feedback.js +69 -0
- package/dist/shell/orchestrator-feedback.js.map +1 -0
- package/dist/shell/orchestrator-image.js +167 -0
- package/dist/shell/orchestrator-image.js.map +1 -0
- package/dist/shell/orchestrator-loop.js +468 -0
- package/dist/shell/orchestrator-loop.js.map +1 -0
- package/dist/shell/orchestrator-reconcile.js +36 -0
- package/dist/shell/orchestrator-reconcile.js.map +1 -0
- package/dist/shell/reconciler-loop.js +228 -0
- package/dist/shell/reconciler-loop.js.map +1 -0
- package/dist/shell/runner-loop-turn.js +301 -0
- package/dist/shell/runner-loop-turn.js.map +1 -0
- package/dist/shell/runner-loop.js +338 -0
- package/dist/shell/runner-loop.js.map +1 -0
- package/dist/shell/server/http.js +208 -0
- package/dist/shell/server/http.js.map +1 -0
- package/dist/shell/server/mcp-runtime-effects.js +237 -0
- package/dist/shell/server/mcp-runtime-effects.js.map +1 -0
- package/dist/shell/server/mcp-runtime.js +99 -0
- package/dist/shell/server/mcp-runtime.js.map +1 -0
- package/dist/shell/workspace-key.js +14 -0
- package/dist/shell/workspace-key.js.map +1 -0
- package/dist/types/acp.js +8 -0
- package/dist/types/acp.js.map +1 -0
- package/dist/types/actions/plan.js +6 -0
- package/dist/types/actions/plan.js.map +1 -0
- package/dist/types/actions/predicates.js +6 -0
- package/dist/types/actions/predicates.js.map +1 -0
- package/dist/types/actions/run-fold.js +8 -0
- package/dist/types/actions/run-fold.js.map +1 -0
- package/dist/types/actions.js +7 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/adapter/clock-random.js +4 -0
- package/dist/types/adapter/clock-random.js.map +1 -0
- package/dist/types/adapter/gondolin-image-converter.js +5 -0
- package/dist/types/adapter/gondolin-image-converter.js.map +1 -0
- package/dist/types/adapter/gondolin-image-fetch.js +5 -0
- package/dist/types/adapter/gondolin-image-fetch.js.map +1 -0
- package/dist/types/adapter/workflow-loader.js +4 -0
- package/dist/types/adapter/workflow-loader.js.map +1 -0
- package/dist/types/cli/args.js +8 -0
- package/dist/types/cli/args.js.map +1 -0
- package/dist/types/config.js +8 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/credential-interp.js +6 -0
- package/dist/types/credential-interp.js.map +1 -0
- package/dist/types/credentials.js +10 -0
- package/dist/types/credentials.js.map +1 -0
- package/dist/types/doctor.js +7 -0
- package/dist/types/doctor.js.map +1 -0
- package/dist/types/domain.js +7 -0
- package/dist/types/domain.js.map +1 -0
- package/dist/types/effect.js +15 -0
- package/dist/types/effect.js.map +1 -0
- package/dist/types/errors.js +39 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/http/decisions.js +6 -0
- package/dist/types/http/decisions.js.map +1 -0
- package/dist/types/http/render.js +10 -0
- package/dist/types/http/render.js.map +1 -0
- package/dist/types/http/views.js +6 -0
- package/dist/types/http/views.js.map +1 -0
- package/dist/types/http.js +9 -0
- package/dist/types/http.js.map +1 -0
- package/dist/types/image/managed-image.js +7 -0
- package/dist/types/image/managed-image.js.map +1 -0
- package/dist/types/interp/effect-interpreter.js +8 -0
- package/dist/types/interp/effect-interpreter.js.map +1 -0
- package/dist/types/interp/tracker.js +7 -0
- package/dist/types/interp/tracker.js.map +1 -0
- package/dist/types/issue/file.js +6 -0
- package/dist/types/issue/file.js.map +1 -0
- package/dist/types/issue/parse.js +8 -0
- package/dist/types/issue/parse.js.map +1 -0
- package/dist/types/main-acp.js +13 -0
- package/dist/types/main-acp.js.map +1 -0
- package/dist/types/main-adapters.js +5 -0
- package/dist/types/main-adapters.js.map +1 -0
- package/dist/types/main-credential.js +21 -0
- package/dist/types/main-credential.js.map +1 -0
- package/dist/types/main-doctor.js +6 -0
- package/dist/types/main-doctor.js.map +1 -0
- package/dist/types/main-http-handler.js +12 -0
- package/dist/types/main-http-handler.js.map +1 -0
- package/dist/types/main-http.js +5 -0
- package/dist/types/main-http.js.map +1 -0
- package/dist/types/main-loops.js +5 -0
- package/dist/types/main-loops.js.map +1 -0
- package/dist/types/main-mcp.js +12 -0
- package/dist/types/main-mcp.js.map +1 -0
- package/dist/types/main-orchestrator.js +5 -0
- package/dist/types/main-orchestrator.js.map +1 -0
- package/dist/types/main-reconcilers.js +11 -0
- package/dist/types/main-reconcilers.js.map +1 -0
- package/dist/types/main-runner.js +13 -0
- package/dist/types/main-runner.js.map +1 -0
- package/dist/types/main-startup.js +5 -0
- package/dist/types/main-startup.js.map +1 -0
- package/dist/types/main-substrates.js +5 -0
- package/dist/types/main-substrates.js.map +1 -0
- package/dist/types/mcp/dispatch.js +4 -0
- package/dist/types/mcp/dispatch.js.map +1 -0
- package/dist/types/mcp/post-move.js +7 -0
- package/dist/types/mcp/post-move.js.map +1 -0
- package/dist/types/mcp.js +9 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/types/ports.js +12 -0
- package/dist/types/ports.js.map +1 -0
- package/dist/types/reconcile/image-decide.js +5 -0
- package/dist/types/reconcile/image-decide.js.map +1 -0
- package/dist/types/reconcile/ledger.js +7 -0
- package/dist/types/reconcile/ledger.js.map +1 -0
- package/dist/types/reconcile/pr-loop.js +8 -0
- package/dist/types/reconcile/pr-loop.js.map +1 -0
- package/dist/types/reconcile/vm-reap.js +8 -0
- package/dist/types/reconcile/vm-reap.js.map +1 -0
- package/dist/types/reconcile/workspace-decide.js +7 -0
- package/dist/types/reconcile/workspace-decide.js.map +1 -0
- package/dist/types/reconcile.js +9 -0
- package/dist/types/reconcile.js.map +1 -0
- package/dist/types/runlog.js +7 -0
- package/dist/types/runlog.js.map +1 -0
- package/dist/types/runner/actions-runner.js +12 -0
- package/dist/types/runner/actions-runner.js.map +1 -0
- package/dist/types/runner/gondolin-dispatch.js +5 -0
- package/dist/types/runner/gondolin-dispatch.js.map +1 -0
- package/dist/types/runner/injection.js +6 -0
- package/dist/types/runner/injection.js.map +1 -0
- package/dist/types/runner/runner-loop.js +5 -0
- package/dist/types/runner/runner-loop.js.map +1 -0
- package/dist/types/runner/turn.js +4 -0
- package/dist/types/runner/turn.js.map +1 -0
- package/dist/types/runner/vm-plan.js +4 -0
- package/dist/types/runner/vm-plan.js.map +1 -0
- package/dist/types/runtime.js +9 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/schedule/admission.js +7 -0
- package/dist/types/schedule/admission.js.map +1 -0
- package/dist/types/schedule/circuit-breaker.js +2 -0
- package/dist/types/schedule/circuit-breaker.js.map +1 -0
- package/dist/types/schedule/eligibility.js +9 -0
- package/dist/types/schedule/eligibility.js.map +1 -0
- package/dist/types/schedule/orchestrator-loop.js +10 -0
- package/dist/types/schedule/orchestrator-loop.js.map +1 -0
- package/dist/types/schedule/sleep-cycle.js +4 -0
- package/dist/types/schedule/sleep-cycle.js.map +1 -0
- package/dist/types/schedule/slots.js +8 -0
- package/dist/types/schedule/slots.js.map +1 -0
- package/dist/types/schedule/tick.js +9 -0
- package/dist/types/schedule/tick.js.map +1 -0
- package/dist/types/server/mcp-runtime.js +8 -0
- package/dist/types/server/mcp-runtime.js.map +1 -0
- package/dist/types/workflow/parse.js +4 -0
- package/dist/types/workflow/parse.js.map +1 -0
- package/package.json +22 -10
- package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
- package/prompts/Reflect.md +91 -0
- package/prompts/Review.md +97 -0
- package/prompts/Todo.md +96 -0
- package/prompts/_footer.md +41 -0
- package/prompts/_preamble.md +42 -0
- package/prompts-minimal/Todo.md +26 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/vm-agent.mjs +312 -90
- package/WORKFLOW.md +0 -744
- package/dist/acp-bridge.js +0 -324
- package/dist/acp-bridge.js.map +0 -1
- package/dist/actions/cache.js +0 -191
- package/dist/actions/cache.js.map +0 -1
- package/dist/actions/effects.js +0 -41
- package/dist/actions/effects.js.map +0 -1
- package/dist/actions/executor.js +0 -570
- package/dist/actions/executor.js.map +0 -1
- package/dist/actions/index.js +0 -13
- package/dist/actions/index.js.map +0 -1
- package/dist/actions/parsing.js.map +0 -1
- package/dist/actions/predicate-env.js +0 -27
- package/dist/actions/predicate-env.js.map +0 -1
- package/dist/actions/predicates.js +0 -49
- package/dist/actions/predicates.js.map +0 -1
- package/dist/actions/templating.js +0 -66
- package/dist/actions/templating.js.map +0 -1
- package/dist/actions/types.js +0 -15
- package/dist/actions/types.js.map +0 -1
- package/dist/agent/acp.js +0 -473
- package/dist/agent/acp.js.map +0 -1
- package/dist/agent/adapter-names.js +0 -159
- package/dist/agent/adapter-names.js.map +0 -1
- package/dist/agent/adapters.js +0 -511
- package/dist/agent/adapters.js.map +0 -1
- package/dist/agent/credential-extractors.js +0 -342
- package/dist/agent/credential-extractors.js.map +0 -1
- package/dist/agent/credential-secrets.js +0 -628
- package/dist/agent/credential-secrets.js.map +0 -1
- package/dist/agent/credential-ticker.js +0 -57
- package/dist/agent/credential-ticker.js.map +0 -1
- package/dist/agent/gondolin-creds-staging.js +0 -356
- package/dist/agent/gondolin-creds-staging.js.map +0 -1
- package/dist/agent/gondolin-dispatch.js +0 -375
- package/dist/agent/gondolin-dispatch.js.map +0 -1
- package/dist/agent/gondolin.js +0 -124
- package/dist/agent/gondolin.js.map +0 -1
- package/dist/agent/runner-decisions.js +0 -134
- package/dist/agent/runner-decisions.js.map +0 -1
- package/dist/agent/runner.js +0 -1456
- package/dist/agent/runner.js.map +0 -1
- package/dist/agent/tool-call-summary.js +0 -102
- package/dist/agent/tool-call-summary.js.map +0 -1
- package/dist/agent/vm-acp-mapping.js +0 -73
- package/dist/agent/vm-acp-mapping.js.map +0 -1
- package/dist/agent/vm-guards.js +0 -262
- package/dist/agent/vm-guards.js.map +0 -1
- package/dist/agent/vm-port.js +0 -22
- package/dist/agent/vm-port.js.map +0 -1
- package/dist/agent/vm-process-registry.js +0 -79
- package/dist/agent/vm-process-registry.js.map +0 -1
- package/dist/bin/cli-args.js +0 -105
- package/dist/bin/cli-args.js.map +0 -1
- package/dist/errors.js +0 -15
- package/dist/errors.js.map +0 -1
- package/dist/http-disk.js +0 -135
- package/dist/http-disk.js.map +0 -1
- package/dist/http-handlers.js.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/issues.js +0 -178
- package/dist/issues.js.map +0 -1
- package/dist/logging.js +0 -203
- package/dist/logging.js.map +0 -1
- package/dist/mcp.js +0 -706
- package/dist/mcp.js.map +0 -1
- package/dist/memory.js +0 -85
- package/dist/memory.js.map +0 -1
- package/dist/orchestrator-decisions.js +0 -331
- package/dist/orchestrator-decisions.js.map +0 -1
- package/dist/orchestrator.js +0 -1569
- package/dist/orchestrator.js.map +0 -1
- package/dist/prompt.js +0 -65
- package/dist/prompt.js.map +0 -1
- package/dist/reconciler/cache.js +0 -65
- package/dist/reconciler/cache.js.map +0 -1
- package/dist/reconciler/index.js +0 -448
- package/dist/reconciler/index.js.map +0 -1
- package/dist/reconciler/ledger.js +0 -131
- package/dist/reconciler/ledger.js.map +0 -1
- package/dist/reconciler/pr-adapters.js +0 -174
- package/dist/reconciler/pr-adapters.js.map +0 -1
- package/dist/reconciler/pr-decide.js.map +0 -1
- package/dist/reconciler/pr.js +0 -422
- package/dist/reconciler/pr.js.map +0 -1
- package/dist/reconciler/types.js +0 -12
- package/dist/reconciler/types.js.map +0 -1
- package/dist/reconciler/vm.js +0 -243
- package/dist/reconciler/vm.js.map +0 -1
- package/dist/reconciler/workspace-defaults.js +0 -83
- package/dist/reconciler/workspace-defaults.js.map +0 -1
- package/dist/reconciler/workspace.js +0 -272
- package/dist/reconciler/workspace.js.map +0 -1
- package/dist/runlog.js +0 -403
- package/dist/runlog.js.map +0 -1
- package/dist/scaffold.js +0 -165
- package/dist/scaffold.js.map +0 -1
- package/dist/trackers/local.js +0 -445
- package/dist/trackers/local.js.map +0 -1
- package/dist/trackers/types.js +0 -10
- package/dist/trackers/types.js.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/util/clock.js +0 -12
- package/dist/util/clock.js.map +0 -1
- package/dist/util/crypto.js +0 -25
- package/dist/util/crypto.js.map +0 -1
- package/dist/util/frontmatter.js +0 -70
- package/dist/util/frontmatter.js.map +0 -1
- package/dist/util/fs-issues.js +0 -22
- package/dist/util/fs-issues.js.map +0 -1
- package/dist/util/process.js +0 -152
- package/dist/util/process.js.map +0 -1
- package/dist/util/workspace-key.js +0 -10
- package/dist/util/workspace-key.js.map +0 -1
- package/dist/workflow-loader.js +0 -147
- package/dist/workflow-loader.js.map +0 -1
- package/dist/workflow.js +0 -822
- package/dist/workflow.js.map +0 -1
- package/dist/workspace-types.js +0 -8
- package/dist/workspace-types.js.map +0 -1
- package/dist/workspace.js +0 -443
- 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
|