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
package/WORKFLOW.yaml
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# WORKFLOW.yaml — symphony dispatched against smol-symphony itself.
|
|
2
|
+
#
|
|
3
|
+
# Run with:
|
|
4
|
+
#
|
|
5
|
+
# npx symphony WORKFLOW.yaml
|
|
6
|
+
#
|
|
7
|
+
# The per-issue workspace clones from this repo's `.git` directory and the agent
|
|
8
|
+
# has no network credentials. The remote PR flow is configured in-file via
|
|
9
|
+
# `workspace.github_repo` below (dizk/smol-symphony): on a terminal transition
|
|
10
|
+
# the Done-state actions push the per-issue branch and open a PR. `gh` on the
|
|
11
|
+
# host must be authenticated (`gh auth status` clean); the token never enters
|
|
12
|
+
# the VM.
|
|
13
|
+
#
|
|
14
|
+
# For a fully local setup (branch left in the workspace until cleanup, nothing
|
|
15
|
+
# pushed), set `workspace.github_repo: none`. The SYMPHONY_REPO env var still
|
|
16
|
+
# overrides the in-file value when exported.
|
|
17
|
+
#
|
|
18
|
+
# Every section and option is documented in WORKFLOW.template.yaml.
|
|
19
|
+
|
|
20
|
+
# Declared workflow states. Drives dispatch eligibility (role: active),
|
|
21
|
+
# terminal cleanup (role: terminal), and the propose_issue landing directory
|
|
22
|
+
# (role: holding). This map is the single source of truth — there are no
|
|
23
|
+
# separate active/terminal lists to keep in sync.
|
|
24
|
+
#
|
|
25
|
+
# Per-state `adapter` / `model` / `max_turns` override the workflow-level
|
|
26
|
+
# `acp.*` and `agent.max_turns` defaults at dispatch time, and `max_concurrent`
|
|
27
|
+
# caps how many agents run at once in this state (the global
|
|
28
|
+
# `agent.max_concurrent_agents` stays the cross-state host ceiling).
|
|
29
|
+
# `allowed_transitions` narrows the targets the agent can pass to
|
|
30
|
+
# `symphony.transition` while operating in this state (omit for "any declared
|
|
31
|
+
# state is reachable").
|
|
32
|
+
states:
|
|
33
|
+
Todo:
|
|
34
|
+
role: active
|
|
35
|
+
# Per-state prompt body. The shell loader wraps this file in the shared
|
|
36
|
+
# `prompt.preamble_file` + `prompt.footer_file` (below) and renders it for
|
|
37
|
+
# Todo dispatches — replacing the old inline `{% case issue.state %}` branch.
|
|
38
|
+
prompt_file: prompts/Todo.md
|
|
39
|
+
adapter: claude
|
|
40
|
+
# Opus 4.8, 1M-context variant. The plain `claude-opus-4-7` is the 200K
|
|
41
|
+
# variant — a γ-class refactor dispatch hit two mid-turn compactions before
|
|
42
|
+
# reaching the edit phase; `[1m]` gives ~30x headroom and removes the stall.
|
|
43
|
+
# The suffix is Claude Code's model-selection convention, forwarded to the
|
|
44
|
+
# adapter via ANTHROPIC_MODEL. Review stays on codex (cross-model review).
|
|
45
|
+
model: claude-opus-4-8[1m]
|
|
46
|
+
max_turns: 10
|
|
47
|
+
# Per-state concurrency cap (issue 137): at most one implementer agent at a
|
|
48
|
+
# time. Lives on the state now, symmetric with max_turns; the global
|
|
49
|
+
# `agent.max_concurrent_agents` below remains the cross-state host ceiling.
|
|
50
|
+
max_concurrent: 1
|
|
51
|
+
Review:
|
|
52
|
+
# Codex picks up the implementer's branch and approves or rejects. On
|
|
53
|
+
# approval it transitions the issue to Done with PR-body notes; on
|
|
54
|
+
# rejection it transitions back to Todo with rework instructions.
|
|
55
|
+
role: active
|
|
56
|
+
prompt_file: prompts/Review.md
|
|
57
|
+
adapter: codex
|
|
58
|
+
# codex-acp accepts the model via `-c model="..."` argv (TOML); see
|
|
59
|
+
# src/agent/adapters.ts. `gpt-5-codex` was historically rejected with the
|
|
60
|
+
# ChatGPT-account user, so leave `model` unset and let codex-acp pick its
|
|
61
|
+
# own default code-review model. Operators with an API-key Codex setup can
|
|
62
|
+
# pin a specific model here once that's known-good.
|
|
63
|
+
max_turns: 6
|
|
64
|
+
allowed_transitions: [Todo, Done]
|
|
65
|
+
Reflect:
|
|
66
|
+
# Sleep cycle (issue 122) as a recurring STATE that SPAWNS ephemeral
|
|
67
|
+
# reflection issues — not one immortal ticket. When the `spawn:` trigger below
|
|
68
|
+
# fires, the orchestrator MINTS a fresh issue into Reflect; it runs once and
|
|
69
|
+
# terminates in `Reflected` (a per-cycle audit trail), like every other
|
|
70
|
+
# ticket. eval_mode binds the read-only /symphony/issues (all state dirs,
|
|
71
|
+
# including the Done/*.md handoff transcripts) and /symphony/logs (per-issue
|
|
72
|
+
# JSONL run logs) mounts so the agent can mine finished work for *recurring*
|
|
73
|
+
# harness friction, distil lessons, and file improvement proposals via
|
|
74
|
+
# propose_issue (which land in Triage — the human gate). It reflects on *how
|
|
75
|
+
# symphony runs work* (the WORKFLOW.yaml `prompt_file` bodies under prompts/,
|
|
76
|
+
# per-state model/max_turns/effort/actions, the gondolin image config,
|
|
77
|
+
# acceptance criteria, timeouts), NOT the product code under review. See the
|
|
78
|
+
# Reflect prompt file (prompts/Reflect.md) for the read → distil → propose
|
|
79
|
+
# loop and the guardrails. Cadence: the operator / an external cron / a real
|
|
80
|
+
# dashboard "reflect now" button can mint a cycle by creating a Reflect issue,
|
|
81
|
+
# and the orchestrator auto-spawns on idle or after N terminal transitions
|
|
82
|
+
# (see this state's `spawn:` block below).
|
|
83
|
+
role: active
|
|
84
|
+
prompt_file: prompts/Reflect.md
|
|
85
|
+
adapter: claude
|
|
86
|
+
# 1M-context Opus: a reflection turn reads many Done/*.md transcripts plus
|
|
87
|
+
# the relevant logs/<id>.jsonl, so the large-context variant avoids mid-turn
|
|
88
|
+
# compaction (same rationale as the Todo state).
|
|
89
|
+
model: claude-opus-4-8[1m]
|
|
90
|
+
# Higher than Todo/Review: reading the history, distilling patterns, and
|
|
91
|
+
# filing one proposal per lesson takes more turns than a single edit/review.
|
|
92
|
+
max_turns: 20
|
|
93
|
+
# Bind the read-only /symphony/issues + /symphony/logs mounts for this state.
|
|
94
|
+
eval_mode: true
|
|
95
|
+
# The reflector may ONLY terminate in Reflected — it cannot route itself into
|
|
96
|
+
# Todo/Review/Done. A guardrail on this self-modifying loop; filing
|
|
97
|
+
# improvements happens through propose_issue (→ Triage), which is
|
|
98
|
+
# independent of allowed_transitions. Reflected is a DEDICATED terminal (NOT
|
|
99
|
+
# Done): Done carries the PR autopilot actions, and a minted reflection lands
|
|
100
|
+
# on an empty branch — terminating in Done would try to open a PR off it.
|
|
101
|
+
allowed_transitions: [Reflected]
|
|
102
|
+
# Recurring spawn trigger. This active state mints a FRESH ephemeral
|
|
103
|
+
# reflection issue into itself: `on_idle` when the orchestrator is idle and
|
|
104
|
+
# >=1 issue reached a terminal state since the last cycle, and `after_terminal`
|
|
105
|
+
# as a backstop once that many issues have reached a terminal state since the
|
|
106
|
+
# last cycle. The terminal-transition counter resets to 0 on a successful mint.
|
|
107
|
+
# `max_in_flight: 1` is load-bearing — it replaces the immortal ticket's
|
|
108
|
+
# built-in "one location = one reflection" mutex, so a busy stretch never mints
|
|
109
|
+
# a second cycle while one is still live. `title` stamps the mint time
|
|
110
|
+
# (`{{ stamp }}`); the generated body carries the trigger + counter provenance.
|
|
111
|
+
# GUARDRAILS (carried over from issue 122): spawning ONLY mints the reflection
|
|
112
|
+
# — the proposals it files still land in Triage and still require human
|
|
113
|
+
# approve/discard, so this does not bypass the human gate.
|
|
114
|
+
spawn:
|
|
115
|
+
on_idle: true
|
|
116
|
+
after_terminal: 10
|
|
117
|
+
title: "Reflection {{ stamp }}"
|
|
118
|
+
max_in_flight: 1
|
|
119
|
+
Done:
|
|
120
|
+
role: terminal
|
|
121
|
+
# PR autopilot routing (issue 38; moved onto the state in issue 139). Done
|
|
122
|
+
# is the merge state: a MERGEABLE Done-state PR has GitHub auto-merge armed
|
|
123
|
+
# with `squash` (matches the repo's `NN: title (#PR)` history); a
|
|
124
|
+
# CONFLICTING one is routed back to `on_conflict.route_to` (Todo) for the
|
|
125
|
+
# dispatched agent to rebase. The host-global on/off switch + poll TTL live
|
|
126
|
+
# in the top-level `pr:` block below; the merge/close/route targets are
|
|
127
|
+
# derived by scanning states for this `pr:` field (no named-string sibling
|
|
128
|
+
# block). While the engine is enabled, transitions into Done no longer fire
|
|
129
|
+
# the standard terminal workspace cleanup — the pr resource owns the
|
|
130
|
+
# workspace until its PR merges or closes.
|
|
131
|
+
pr:
|
|
132
|
+
auto_merge: squash
|
|
133
|
+
on_conflict:
|
|
134
|
+
route_to: Todo
|
|
135
|
+
# Issue 36 (reconciler v2 / typed action DAG): the legacy `after_run`
|
|
136
|
+
# shell that pushed the branch and opened a PR is replaced by two typed
|
|
137
|
+
# actions. The host pre-stages SYMPHONY_PR_TITLE / SYMPHONY_PR_BODY_FILE /
|
|
138
|
+
# SYMPHONY_BRANCH (the same values the old shell read); the action
|
|
139
|
+
# executor exposes them as $pr_title / $pr_body_file / $branch /
|
|
140
|
+
# $base_branch / $repo in the fixed template namespace
|
|
141
|
+
# (src/actions/types.ts → ActionContext). The `if: $repo` predicate
|
|
142
|
+
# matches the old `[ -n "${SYMPHONY_REPO:-}" ] || exit 0` short-circuit
|
|
143
|
+
# so the local-only mode is still a no-op. Per-action retry/snapshot
|
|
144
|
+
# plumbing replaces the opaque shell-exit-code surface; on rate-limit
|
|
145
|
+
# the create_pr_if_missing action shows "retrying in 60s" on the
|
|
146
|
+
# dashboard instead of a silent failure.
|
|
147
|
+
#
|
|
148
|
+
# on_error route (issue 235): a Done handoff that fails — `push_branch`
|
|
149
|
+
# server-side-declined (e.g. a `.github/workflows/` edit the Workflows:write-less
|
|
150
|
+
# PAT can't push), or `create_pr_if_missing` erroring after its retries — must
|
|
151
|
+
# NOT dead-end silently in terminal Done with no branch on the remote and no PR.
|
|
152
|
+
# Each action reroutes the issue OUT of Done into the `HandoffFailed` holding
|
|
153
|
+
# state (below) with the failure reason appended, so the stranded handoff is
|
|
154
|
+
# operator-visible on the dashboard instead of surfacing only as a log line. The
|
|
155
|
+
# per-issue workspace is retained (issue 234's handoff-pending marker) so the
|
|
156
|
+
# completed commit survives for a manual push. The default retry (count 3) still
|
|
157
|
+
# applies first — a transient `create_pr_if_missing` rate-limit recovers without
|
|
158
|
+
# routing; only a still-failing handoff reroutes.
|
|
159
|
+
actions:
|
|
160
|
+
- kind: push_branch
|
|
161
|
+
name: push-branch
|
|
162
|
+
remote: origin
|
|
163
|
+
ref: $branch
|
|
164
|
+
if: $repo
|
|
165
|
+
on_error:
|
|
166
|
+
then: { route_to: HandoffFailed }
|
|
167
|
+
- kind: create_pr_if_missing
|
|
168
|
+
name: open-pr
|
|
169
|
+
base: $base_branch
|
|
170
|
+
head: $branch
|
|
171
|
+
title_from: $pr_title
|
|
172
|
+
body_from: $pr_body_file
|
|
173
|
+
if: $repo
|
|
174
|
+
on_error:
|
|
175
|
+
then: { route_to: HandoffFailed }
|
|
176
|
+
Cancelled:
|
|
177
|
+
role: terminal
|
|
178
|
+
# Cancelled means the work was abandoned; no patch, no PR. The workspace is
|
|
179
|
+
# cleaned up after the run unwinds and the commits are discarded with it.
|
|
180
|
+
# PR autopilot close state (issue 139): when the engine is enabled, an open
|
|
181
|
+
# PR for a Cancelled issue is closed without merge and its remote branch is
|
|
182
|
+
# best-effort-deleted. Unlike the merge state, the close path needs no
|
|
183
|
+
# workspace, so standard terminal cleanup still runs on transition in.
|
|
184
|
+
pr:
|
|
185
|
+
close: true
|
|
186
|
+
Reflected:
|
|
187
|
+
# Dedicated terminal for a completed sleep-cycle reflection (replaces the old
|
|
188
|
+
# Dormant parking state). Each minted reflection issue lands here after filing
|
|
189
|
+
# its proposals, so the column is a browsable per-cycle audit trail — one issue
|
|
190
|
+
# per run — instead of one ever-growing immortal ticket. Deliberately carries
|
|
191
|
+
# NO `pr:` and NO `actions:`: a reflection runs on an empty branch (it only
|
|
192
|
+
# files propose_issue → Triage), so the Done-state PR autopilot must NOT fire
|
|
193
|
+
# — that's the whole reason this is a separate terminal from Done. Standard
|
|
194
|
+
# terminal workspace cleanup runs on transition in.
|
|
195
|
+
role: terminal
|
|
196
|
+
Triage:
|
|
197
|
+
# Landing directory for `symphony.propose_issue`. Never dispatched; the
|
|
198
|
+
# operator approves or discards from the dashboard. Declared FIRST among
|
|
199
|
+
# holding states so it stays the `propose_issue` landing + triage target
|
|
200
|
+
# (both resolve the first declared holding state).
|
|
201
|
+
role: holding
|
|
202
|
+
HandoffFailed:
|
|
203
|
+
# Landing state for a Done handoff that FAILED (issue 235). The agent already
|
|
204
|
+
# transitioned the issue to Done, but the Done `actions:` handoff (push_branch /
|
|
205
|
+
# create_pr_if_missing) couldn't land — most commonly a `.github/workflows/`
|
|
206
|
+
# edit the shared fine-grained PAT can't push (Workflows:write is intentionally
|
|
207
|
+
# withheld; that diff is a deliberate manual SSH-push step). Rather than dead-end
|
|
208
|
+
# invisibly in terminal Done with no branch on the remote and no PR, the failing
|
|
209
|
+
# action's `on_error.then.route_to` (states.Done.actions, above) reroutes the
|
|
210
|
+
# issue here with the failure reason appended to its body. Holding → never
|
|
211
|
+
# dispatched: it parks for an operator to push the `agent/<id>` branch by hand
|
|
212
|
+
# (the per-issue workspace is RETAINED by issue 234's handoff-pending marker, so
|
|
213
|
+
# the completed commit survives) and then move the issue back to Done, or to
|
|
214
|
+
# Cancelled. Declared AFTER Triage so Triage stays the `propose_issue` landing
|
|
215
|
+
# (the first declared holding state).
|
|
216
|
+
role: holding
|
|
217
|
+
|
|
218
|
+
# Shared prompt-assembly files (per-state prompt split). Each active state above
|
|
219
|
+
# names its own `prompt_file`; the loader wraps that body in this shared header
|
|
220
|
+
# and footer and renders the result for that state — so the config above reads as
|
|
221
|
+
# the shape of the workflow, and each prompt is edited in its own file under
|
|
222
|
+
# `prompts/` instead of one 800-line `{% case issue.state %}`. Both paths are
|
|
223
|
+
# resolved relative to this file. There is no inline prompt body anymore —
|
|
224
|
+
# workflow files are pure YAML. A dispatched state that declares no `prompt_file`
|
|
225
|
+
# (or omitting this block) falls back to a generic built-in prompt
|
|
226
|
+
# ("You are working on an issue."), so every active state should name one.
|
|
227
|
+
prompt:
|
|
228
|
+
preamble_file: prompts/_preamble.md
|
|
229
|
+
footer_file: prompts/_footer.md
|
|
230
|
+
|
|
231
|
+
tracker:
|
|
232
|
+
kind: local
|
|
233
|
+
# Operator-scoped tracker root (outside the repo). State transitions and
|
|
234
|
+
# propose_issue writes don't dirty the codebase's git status. Symphony
|
|
235
|
+
# auto-mkdirs every declared state directory under this root on startup.
|
|
236
|
+
root: ~/.symphony/trackers/smol-symphony
|
|
237
|
+
|
|
238
|
+
# PR autopilot engine toggle (issue 38, simplified by issue 101; routing moved
|
|
239
|
+
# onto states in issue 139). This is the slim host-global half only — the
|
|
240
|
+
# on/off switch and the per-PR `gh pr view` cache TTL. The merge/close/route
|
|
241
|
+
# targets and the auto-merge strategy now live ON the states they describe:
|
|
242
|
+
# `states.Done.pr` (merge state — auto_merge + on_conflict.route_to) and
|
|
243
|
+
# `states.Cancelled.pr` (close state — close: true), above. The reconciler
|
|
244
|
+
# derives those by scanning states; there is no named-string sibling block.
|
|
245
|
+
#
|
|
246
|
+
# Enabled 2026-05-25 so MERGEABLE Done-state PRs have GitHub auto-merge armed
|
|
247
|
+
# and CONFLICTING ones are routed back to Todo for the dispatched agent to
|
|
248
|
+
# rebase (the host runs `git fetch origin <base>` before each dispatch so
|
|
249
|
+
# `origin/<base>` is current, and the Todo prompt's first step is
|
|
250
|
+
# `git rebase origin/<base>`). There is no autopilot-side rebase machinery and
|
|
251
|
+
# no consecutive-failure circuit breaker.
|
|
252
|
+
#
|
|
253
|
+
# PREREQUISITE: `gh pr merge --auto` requires at least one branch-protection
|
|
254
|
+
# rule on `main`, or arming auto-merge errors. Ensure one exists in the repo's
|
|
255
|
+
# GitHub settings. To disable, set `enabled: false` (the resource is then never
|
|
256
|
+
# constructed and Done-state behavior reverts to the actions-block PR-create +
|
|
257
|
+
# operator merge).
|
|
258
|
+
pr:
|
|
259
|
+
enabled: true
|
|
260
|
+
poll_interval_ms: 30000
|
|
261
|
+
|
|
262
|
+
polling:
|
|
263
|
+
interval_ms: 5000
|
|
264
|
+
|
|
265
|
+
# The canonical clone + base-branch checkout + `agent/<id>` branch cut +
|
|
266
|
+
# origin/identity setup is owned by the orchestrator's TypeScript
|
|
267
|
+
# `setupWorkspaceDir` action (issue 34 / reconciler stage 3) — there is no
|
|
268
|
+
# shell `hooks:` surface. The per-issue workspace arrives at the dispatched
|
|
269
|
+
# agent with:
|
|
270
|
+
#
|
|
271
|
+
# • a hardlinked `git clone --local` of the source repo on the base branch
|
|
272
|
+
# (`workspace.base_branch`, or the SYMPHONY_BASE_BRANCH env override,
|
|
273
|
+
# default `main`) at the source repo's current local base SHA
|
|
274
|
+
# • all network remotes stripped (in-VM `git push`/`git fetch` fail closed)
|
|
275
|
+
# • when `workspace.github_repo` (or the SYMPHONY_REPO env override) is set:
|
|
276
|
+
# `origin` restored to the canonical HTTPS URL so the host's Done-state
|
|
277
|
+
# `push_branch` action can push (`gh auth setup-git` runs best-effort on the
|
|
278
|
+
# host so the push has credentials; the token never enters the VM)
|
|
279
|
+
# • `user.name = symphony-agent` / `user.email = agent@symphony.local`
|
|
280
|
+
# • `agent/<id>` checked out
|
|
281
|
+
#
|
|
282
|
+
# The source repo's local `<base>` is the single source of truth for the
|
|
283
|
+
# workspace's base ref. To pick up a new base, update the source repo
|
|
284
|
+
# (`git pull` / `git fetch && git checkout <base>`) before the next dispatch;
|
|
285
|
+
# symphony does not implicitly fetch from `origin/<base>` at setup time.
|
|
286
|
+
#
|
|
287
|
+
# Need extra per-VM tooling on top of that? Bake it into the agent image
|
|
288
|
+
# (`images/agents/`), or run arbitrary guest commands from a state's `actions:`
|
|
289
|
+
# via `run_in_vm`. The post-attempt push + PR-create handoff is the Done
|
|
290
|
+
# state's `actions:` block above.
|
|
291
|
+
workspace:
|
|
292
|
+
# Explicit in-repo workspace root (the new unset default is
|
|
293
|
+
# ~/.symphony/workspaces/<project>). Kept explicit here so in-flight per-issue
|
|
294
|
+
# working trees aren't relocated mid-burndown; drop this line to inherit home.
|
|
295
|
+
root: ./.symphony/workspaces
|
|
296
|
+
# PR/push target for the dogfood (symphony-on-symphony) setup: the Done-state
|
|
297
|
+
# actions push the per-issue branch + open a PR against this repo. Set to
|
|
298
|
+
# `none` for local-only (branch left in the workspace). The SYMPHONY_REPO env
|
|
299
|
+
# var still overrides this if exported.
|
|
300
|
+
github_repo: dizk/smol-symphony
|
|
301
|
+
# Branch the per-issue workspace clones from + targets as the PR base. The
|
|
302
|
+
# SYMPHONY_BASE_BRANCH env var still overrides this if exported.
|
|
303
|
+
base_branch: main
|
|
304
|
+
|
|
305
|
+
# Per-issue JSONL run logs plus an orchestrator-side `symphony.log` mirror.
|
|
306
|
+
# One JSONL file per issue, appended across attempts and process restarts;
|
|
307
|
+
# captures every ACP JSON-RPC frame to/from the VM, raw adapter stderr,
|
|
308
|
+
# typed-action output, and orchestrator lifecycle events — intended for
|
|
309
|
+
# later evaluation by another agent. The sibling `symphony.log` captures the
|
|
310
|
+
# orchestrator's structured log (dispatch, actions, reconciler, shutdown) in the
|
|
311
|
+
# same `key=value` format so a post-hoc review has both surfaces in one
|
|
312
|
+
# directory. While the file sink is active the console shows only the startup
|
|
313
|
+
# banner; `tail -f symphony.log` follows the detail, and `--verbose` mirrors it
|
|
314
|
+
# back to the console. See WORKFLOW.template.yaml for the full schema.
|
|
315
|
+
logs:
|
|
316
|
+
# Operator-scoped run-log root (outside the repo), matching the home default
|
|
317
|
+
# (~/.symphony/logs/<project>) and the operator-scoped tracker.root above.
|
|
318
|
+
# Durable, expensive-to-rebuild run logs survive a delete / re-clone of the repo
|
|
319
|
+
# instead of living inside the working tree. (Leaving this unset would inherit
|
|
320
|
+
# the same path, since github_repo derives <project> = smol-symphony.)
|
|
321
|
+
root: ~/.symphony/logs/smol-symphony
|
|
322
|
+
|
|
323
|
+
agent:
|
|
324
|
+
# SERIALIZED to 1 (2026-05-27) to stop the FC/IS burn-down conflict storm:
|
|
325
|
+
# every burn-down PR edits the same policy files (package.json --max-warnings
|
|
326
|
+
# ratchet, .dependency-cruiser.cjs, eslint.config.js), so any two in flight
|
|
327
|
+
# conflict by construction. Serial dispatch makes each PR rebase on the prior
|
|
328
|
+
# merge. Revert to 2 once the arch-burndown queue drains.
|
|
329
|
+
max_concurrent_agents: 1
|
|
330
|
+
max_turns: 6
|
|
331
|
+
max_retry_backoff_ms: 120000
|
|
332
|
+
|
|
333
|
+
acp:
|
|
334
|
+
# Selecting "claude" is enough: symphony probes ~/.claude/.credentials.json
|
|
335
|
+
# on the host at startup and auto-generates a launch command for the in-VM
|
|
336
|
+
# agent. There is no `command` escape hatch under the ACP WebSocket transport —
|
|
337
|
+
# the launch shape is fixed; fork scripts/vm-agent.mjs if you need to customize
|
|
338
|
+
# what the agent spawns.
|
|
339
|
+
adapter: claude
|
|
340
|
+
# Credentials never enter the VM (issue 113; codex generalized in 116). The
|
|
341
|
+
# guest holds only a token-shaped placeholder; the host substitutes the real
|
|
342
|
+
# upstream credential into the outbound request at Gondolin egress (TLS-MITM):
|
|
343
|
+
# for claude, the Anthropic OAuth access token; for codex (the Review state's
|
|
344
|
+
# adapter), the OpenAI credential read from ~/.codex/auth.json (access token or
|
|
345
|
+
# OPENAI_API_KEY, never the refresh token). Every credential-bearing var is
|
|
346
|
+
# stripped from the forwarded VM boot env, so no real credential lands in the VM.
|
|
347
|
+
# Reasoning effort forwarded to claude-agent-acp via a staged settings.json
|
|
348
|
+
# (`{"effortLevel": "xhigh"}`) copied into /root/.claude/settings.json before the
|
|
349
|
+
# adapter starts. xhigh is the second-highest tier under Opus 4.7 (max is the top
|
|
350
|
+
# but is meaningfully slower); operators on a Haiku-backed model must drop this
|
|
351
|
+
# because Haiku rejects xhigh at adapter startup. Valid set is `low|medium|high|xhigh|max`,
|
|
352
|
+
# model-gated by claude-agent-acp's `supportedEffortLevels`.
|
|
353
|
+
effort: xhigh
|
|
354
|
+
shell: bash
|
|
355
|
+
# Hard cap on a single session/prompt regardless of activity. Raised from 30min to
|
|
356
|
+
# 60min (the code default) because a heavy refactor turn at effort=xhigh can run a
|
|
357
|
+
# single uninterrupted turn past 30min — issue 103's healthy attempt was killed
|
|
358
|
+
# mid-edit at the old 1800000 cap with turns_completed:0. Distinct from
|
|
359
|
+
# stall_timeout_ms below (which only trips on NO activity).
|
|
360
|
+
prompt_timeout_ms: 3600000
|
|
361
|
+
read_timeout_ms: 30000
|
|
362
|
+
# ACP rides the unified HTTP server: the in-VM agent (the injected launcher at
|
|
363
|
+
# `/opt/symphony/vm-agent.mjs` — see the BYO-image contract under `gondolin:`) dials
|
|
364
|
+
# back the `/acp` WebSocket through the SAME `tcp.hosts` tunnel MCP uses
|
|
365
|
+
# (`ws://symphony-mcp:7001/acp`) and authenticates with a per-dispatch bearer sent as the
|
|
366
|
+
# first WebSocket message. No separate ACP listener / bind port — the raw-TCP bridge is
|
|
367
|
+
# retired. `connect_timeout_ms` bounds how long to wait for that dial-back (default 30000).
|
|
368
|
+
# connect_timeout_ms: 30000
|
|
369
|
+
# ACP-over-WebSocket liveness heartbeat. Both ends of the `/acp` socket send an
|
|
370
|
+
# application-level ping every `heartbeat_interval_ms` and tear the socket down (a typed
|
|
371
|
+
# `transport_error`, which feeds the consecutive circuit breaker — NOT a refusal) if no
|
|
372
|
+
# inbound traffic arrives for `heartbeat_timeout_ms`. This surfaces a half-open socket in
|
|
373
|
+
# seconds instead of waiting for `prompt_timeout_ms` or the stall reaper. The heartbeat is
|
|
374
|
+
# INDEPENDENT of ACP frames, so a long silent tool call stays alive; the conservative
|
|
375
|
+
# defaults (interval 15000 / timeout 45000 ≈ 3 intervals) keep a slow KVM host from
|
|
376
|
+
# false-positiving a live-but-quiet peer mid-turn. Raise the timeout if a heavily loaded
|
|
377
|
+
# host legitimately pauses the guest event loop past 45s.
|
|
378
|
+
# heartbeat_interval_ms: 15000
|
|
379
|
+
# heartbeat_timeout_ms: 45000
|
|
380
|
+
# Time between any ACP event from the adapter before symphony kills the attempt as stalled.
|
|
381
|
+
# Raised from the 5-minute default because Opus 4.7 at effort=xhigh can take many minutes to
|
|
382
|
+
# produce its first thought chunk on a heavy prompt. If a real wedge happens, attempts will
|
|
383
|
+
# die at this longer threshold; if the agent is just thinking, we let it finish.
|
|
384
|
+
stall_timeout_ms: 1800000
|
|
385
|
+
|
|
386
|
+
gondolin:
|
|
387
|
+
# Per-issue microVM (Gondolin substrate). `image` is the agent rootfs the VM
|
|
388
|
+
# boots. The value is a Gondolin image selector: a content-addressed build id, a
|
|
389
|
+
# `name:tag` ref like `symphony-agents:latest`, or a path to an exported asset
|
|
390
|
+
# directory. Build the reference image with `npm run build:image` (see images/agents/).
|
|
391
|
+
#
|
|
392
|
+
# IMAGE CONTRACT (issue 209 — mise-only base): the image ships ONLY the `mise`
|
|
393
|
+
# binary (+ glibc essentials). node + the agent CLIs are NOT baked — they come from a
|
|
394
|
+
# mise SYSTEM config symphony STAGES at /etc/mise/config.toml at dispatch
|
|
395
|
+
# (assets/symphony-mise.system.toml), installed via `mise install` into a
|
|
396
|
+
# warm-cached data dir (see `mise:` below). The in-VM launcher (`scripts/vm-agent.mjs`)
|
|
397
|
+
# is likewise INJECTED at dispatch (read off the host's own disk, SHA-256 logged), so
|
|
398
|
+
# neither a CLI bump nor a launcher/transport change needs an image rebuild + repin.
|
|
399
|
+
# A pre-launch probe fails fast with a precise error (not an opaque ENOENT) if unmet.
|
|
400
|
+
#
|
|
401
|
+
# The image MUST provide ONLY:
|
|
402
|
+
# 1. the `mise` binary on PATH (node + the agent CLIs are mise-installed; the agent
|
|
403
|
+
# runtime node is resolved system-scoped via `mise which node`, kept ≥ 21 for the
|
|
404
|
+
# launcher's global WebSocket). mise provisioning is the ONLY toolchain path
|
|
405
|
+
# (issue 233) — there is no opt-out, so the base image need not bake node;
|
|
406
|
+
# 2. /bin/sh + base64 + mkdir/chmod (coreutils — present in any Debian/Alpine base)
|
|
407
|
+
# for the staging write;
|
|
408
|
+
# 3. a writable rootfs overlay (Gondolin default `cow`; a deliberately
|
|
409
|
+
# readonly-manifest image transparently falls back to a /tmp tmpfs path);
|
|
410
|
+
# 4. distro CA files left intact (Gondolin injects its MITM CA at boot — the image
|
|
411
|
+
# must not shadow /etc/ssl).
|
|
412
|
+
# Symphony injects everything else: the mise SYSTEM config, the launcher, the
|
|
413
|
+
# bind-mount-trust gitconfig, the fake-cred placeholder files, and all SYMPHONY_* /
|
|
414
|
+
# PATH / GIT_CONFIG_GLOBAL launch env.
|
|
415
|
+
#
|
|
416
|
+
# The agent-CLI pins live in assets/symphony-mise.system.toml (claude-code,
|
|
417
|
+
# codex, claude-agent-acp, codex-acp) — bump THERE + restart, no rebuild.
|
|
418
|
+
# mise-only base (issue 209). #2 (PR #211) installs the agent toolchain onto the guest
|
|
419
|
+
# ROOTFS at dispatch — sandboxfs silently drops chmod, so an executable can't be born on
|
|
420
|
+
# the bind mount; the persistent download cache stays on the bind mount (data only).
|
|
421
|
+
# `rootfs_size` grows the ephemeral install fs past the ~593 MB default. Bump an agent CLI
|
|
422
|
+
# in assets/symphony-mise.system.toml + restart — no image rebuild (only a base/mise bump needs
|
|
423
|
+
# `npm run build:image` + a repin here). NB the codex reviewer egress needed the
|
|
424
|
+
# chatgpt-backend route swap scoped to the platform host (credential-hooks.ts) to reach
|
|
425
|
+
# npm/nodejs — see docs/research/codex-route-hook-host-rewrite-rca.md.
|
|
426
|
+
rootfs_size: 3G
|
|
427
|
+
image: e8a1562b-ea57-54c6-9f0a-ea9aa6f1d40d
|
|
428
|
+
cpus: 2
|
|
429
|
+
mem_mib: 4096
|
|
430
|
+
# Absolute guest path the injected launcher is staged + exec'd at. Default
|
|
431
|
+
# `/opt/symphony/vm-agent.mjs` (the historical bake path). The same value is the write
|
|
432
|
+
# target AND the exec arg, so they can never drift; a readonly-rootfs image falls back
|
|
433
|
+
# to a /tmp path automatically. Override only for an unusual image layout.
|
|
434
|
+
# guest_agent_path: /opt/symphony/vm-agent.mjs
|
|
435
|
+
# The node binary the launcher is exec'd with. Default bare `node` (the pre-launch
|
|
436
|
+
# probe resolves it to an absolute path so the exec is PATH-independent). Set this for
|
|
437
|
+
# an image that ships node under a non-PATH name (e.g. /opt/node/bin/node).
|
|
438
|
+
# node_bin: node
|
|
439
|
+
# No runtime bind-mounts. The launcher is INJECTED (staged per-dispatch), not mounted,
|
|
440
|
+
# so it needs no `volumes` entry. Keeping `volumes` empty leaves room for an eval_mode
|
|
441
|
+
# state's two read-only mounts (/symphony/issues + /symphony/logs) on top of the
|
|
442
|
+
# auto-mounted workspace. Credentials never mount: the host substitutes the real token
|
|
443
|
+
# at Gondolin egress; the tracker is reached via the symphony MCP server (or the
|
|
444
|
+
# eval_mode mount).
|
|
445
|
+
volumes: []
|
|
446
|
+
# forward_env is a generic passthrough into the VM boot env, but the runner
|
|
447
|
+
# strips EVERY credential-bearing var before boot (the guest holds only a
|
|
448
|
+
# placeholder Gondolin substitutes at egress) — so listing OPENAI_API_KEY here
|
|
449
|
+
# does NOT plant the real key in a VM.
|
|
450
|
+
forward_env:
|
|
451
|
+
- OPENAI_API_KEY
|
|
452
|
+
- ANTHROPIC_API_KEY
|
|
453
|
+
|
|
454
|
+
egress:
|
|
455
|
+
# General dev-tooling firewall for the in-VM agent. Gondolin denies guest egress
|
|
456
|
+
# by default; the agent can always reach its own inference host (handled by the
|
|
457
|
+
# credential layer), and these hosts are additionally opened so gates can run
|
|
458
|
+
# (`npm install`, git-based deps, release binaries). SECURITY: nothing here ever
|
|
459
|
+
# gets a real token substituted — listing a host grants plain network egress
|
|
460
|
+
# only. The real upstream token is substituted solely on each adapter's inference
|
|
461
|
+
# host (see src/agent/credential-secrets.ts).
|
|
462
|
+
allowed_hosts:
|
|
463
|
+
- nodejs.org # mise node prebuilts (issue 209)
|
|
464
|
+
- registry.npmjs.org # npm install + mise npm: backend (the agent CLIs)
|
|
465
|
+
- mise.jdx.dev # mise registry redirect (issue 209)
|
|
466
|
+
- mise-versions.jdx.dev # mise precompiled tool version lists (node@N, etc.; issue 223)
|
|
467
|
+
- github.com # git-based deps / release pages
|
|
468
|
+
- codeload.github.com # GitHub tarball fetch
|
|
469
|
+
- objects.githubusercontent.com # release-binary downloads
|
|
470
|
+
|
|
471
|
+
server:
|
|
472
|
+
# Pinned to 8787 for stable local/tailscale access (bookmarks, forwards,
|
|
473
|
+
# scripts). #160 made the port ephemeral by default — so two instances never
|
|
474
|
+
# collide on the HTTP port — and banners the bound port. This project runs a
|
|
475
|
+
# single instance, so a fixed, predictable port is preferable. Omit `port:`
|
|
476
|
+
# (or pass `--port N`) to return to ephemeral. Takes effect on next restart.
|
|
477
|
+
port: 8787
|
|
478
|
+
# Bound to all interfaces because access is gated by tailscale, not by the
|
|
479
|
+
# HTTP server itself. The endpoint has no auth; only expose it inside a
|
|
480
|
+
# trusted network boundary.
|
|
481
|
+
host: 0.0.0.0
|
|
482
|
+
|
|
483
|
+
mcp:
|
|
484
|
+
# Gondolin maps a synthetic guest host to the host's loopback (`tcp.hosts`), so
|
|
485
|
+
# 127.0.0.1 from inside the VM hits the host's listener. Override only if
|
|
486
|
+
# your VMM has a different host alias.
|
|
487
|
+
host: 127.0.0.1
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: symphony-issues
|
|
3
|
+
description: Author well-formed issues for a symphony tracker, and decompose a large task into a blocked_by DAG of small one-PR issues. Trigger when asked to "file a symphony issue", "open/create a tracker issue", "break this task into issues", or "decompose into a DAG of issues".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Filing symphony issues
|
|
7
|
+
|
|
8
|
+
This project is driven by **symphony**: a coding agent picks up one tracker
|
|
9
|
+
issue, works it on a per-issue branch, and opens exactly one PR. **The issue
|
|
10
|
+
body IS the dispatched agent's prompt** — write every issue for that reader.
|
|
11
|
+
|
|
12
|
+
Anything project-specific (the HTTP port, the tracker root, the state names)
|
|
13
|
+
lives in this repo's `WORKFLOW.yaml`. Read it from there; this skill never
|
|
14
|
+
hardcodes it.
|
|
15
|
+
|
|
16
|
+
## The four-section issue body
|
|
17
|
+
|
|
18
|
+
Every issue body uses the same four sections, in this order:
|
|
19
|
+
|
|
20
|
+
- **Problem** — what's wrong and why now. The motivation the agent needs to
|
|
21
|
+
make the judgement calls the Change leaves open.
|
|
22
|
+
- **Change** — the concrete edits. Name file paths and line ranges where you
|
|
23
|
+
know them; describe the *shape* of the change, not just the goal.
|
|
24
|
+
- **allowed_paths** — the workspace scope the dispatched agent is permitted to
|
|
25
|
+
touch, one path per line.
|
|
26
|
+
- **Acceptance** — the checks that must pass before the agent transitions the
|
|
27
|
+
issue to a terminal state. Usually the build/test/lint gate plus the
|
|
28
|
+
issue-specific assertions that prove this change works.
|
|
29
|
+
|
|
30
|
+
Why this shape, plainly: **the body is the prompt, `allowed_paths` is the
|
|
31
|
+
leash, and Acceptance is the definition of done.** A vague body yields a vague
|
|
32
|
+
PR; a missing `allowed_paths` lets the agent wander into unrelated files; a
|
|
33
|
+
missing Acceptance leaves "done" undefined and the reviewer with nothing to
|
|
34
|
+
check against.
|
|
35
|
+
|
|
36
|
+
## How to file
|
|
37
|
+
|
|
38
|
+
Find `server.port` in this repo's `WORKFLOW.yaml`, then POST JSON to the running
|
|
39
|
+
symphony HTTP server:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
curl -s -X POST http://127.0.0.1:<server.port>/api/v1/issues \
|
|
43
|
+
-H "content-type: application/json" \
|
|
44
|
+
--data-binary @/tmp/issue.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Body shape:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"title": "required, non-empty",
|
|
52
|
+
"state": "Todo",
|
|
53
|
+
"identifier": "108",
|
|
54
|
+
"description": "markdown body — the four sections above, below the front matter",
|
|
55
|
+
"priority": 2,
|
|
56
|
+
"labels": ["refactor"],
|
|
57
|
+
"blocked_by": ["107"]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `title` is the only required field.
|
|
62
|
+
- `state` is optional — it defaults to the **first active state** declared in
|
|
63
|
+
`WORKFLOW.yaml`. Any declared **non-terminal** state is a valid explicit target:
|
|
64
|
+
both active states and holding states (e.g. a Triage holding state) can be
|
|
65
|
+
created into. Only **terminal** states are closed to direct creation.
|
|
66
|
+
- `identifier` is optional — the server allocates the next free integer.
|
|
67
|
+
- `priority`, `labels`, and `blocked_by` are optional.
|
|
68
|
+
- The server stamps `created_at` / `updated_at` and returns `201` with
|
|
69
|
+
`{ path, identifier, state }`. Duplicate ids / bad states come back `4xx`.
|
|
70
|
+
|
|
71
|
+
**Disk fallback** (server not running): hand-write a file at
|
|
72
|
+
`<tracker.root>/<State>/<n>.md` — read `tracker.root` and the state names from
|
|
73
|
+
your `WORKFLOW.yaml` — with the same YAML front matter (`title`, optional
|
|
74
|
+
`priority` / `labels` / `blocked_by`) and the four-section body below it. The
|
|
75
|
+
**disk is canonical for reading**; the API is just the filing front door, so
|
|
76
|
+
look at neighbouring files under the tracker root for the exact front-matter
|
|
77
|
+
shape this project uses.
|
|
78
|
+
|
|
79
|
+
## Decomposing a large task into a `blocked_by` DAG
|
|
80
|
+
|
|
81
|
+
This is the high-leverage move, and the reason to reach for this skill. Don't
|
|
82
|
+
file one giant issue — model the task as a **dependency graph of small issues,
|
|
83
|
+
one PR per node.**
|
|
84
|
+
|
|
85
|
+
- `blocked_by: ["<id>", …]` makes an issue **ineligible to dispatch** until
|
|
86
|
+
every blocker has reached a **terminal** tracker state.
|
|
87
|
+
- **Serialize work that shares a file or artifact.** Two agents editing the
|
|
88
|
+
same file in parallel collide and invalidate each other's branch — chain
|
|
89
|
+
those nodes `A ← B ← C` (B `blocked_by` A, C `blocked_by` B) so they land in
|
|
90
|
+
order.
|
|
91
|
+
- **Parallelize only genuinely independent work** (disjoint files / artifacts).
|
|
92
|
+
File those with no `blocked_by` so they fan out across the concurrency cap and
|
|
93
|
+
run at once.
|
|
94
|
+
|
|
95
|
+
### Two gotchas to design around
|
|
96
|
+
|
|
97
|
+
1. **Terminal ≠ merged.** `blocked_by` unblocks the moment a blocker hits a
|
|
98
|
+
*terminal tracker state*, which can **precede the blocker's PR actually
|
|
99
|
+
merging**. A downstream node may therefore dispatch off a base branch that
|
|
100
|
+
still lacks the upstream change. Fast-forward / rebase the base between
|
|
101
|
+
merges so each node starts from its blockers' landed code.
|
|
102
|
+
2. **Keep downstream specs at intent altitude.** Don't pin line numbers in a
|
|
103
|
+
node that won't dispatch until several upstream PRs have landed — they'll
|
|
104
|
+
have drifted by then. Say *"locate by name"* or *"run the relevant check for
|
|
105
|
+
the current count"* instead of `src/foo.ts:412`.
|
|
106
|
+
|
|
107
|
+
### Worked example: rename an API across many call sites
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
discover (enumerate every call site)
|
|
111
|
+
├── transform-call-site-A ─┐
|
|
112
|
+
├── transform-call-site-B ─┤ (parallel — disjoint files)
|
|
113
|
+
└── transform-call-site-C ─┘
|
|
114
|
+
└── verify (typecheck + test the whole tree)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
1. **`discover`** — one issue that enumerates every call site and lists them in
|
|
118
|
+
its handoff notes. No `blocked_by` — it dispatches immediately.
|
|
119
|
+
2. **N transform issues** — one per call site, each `blocked_by` the discover
|
|
120
|
+
issue and each touching **disjoint files**, so they run in parallel:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{ "title": "Rename API in module A",
|
|
124
|
+
"blocked_by": ["<discover-id>"], "labels": ["refactor"] }
|
|
125
|
+
```
|
|
126
|
+
3. **`verify`** — one issue blocked on *all* the transforms, asserting the whole
|
|
127
|
+
tree still typechecks and tests after every call site moved:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{ "title": "Verify API rename across the tree",
|
|
131
|
+
"blocked_by": ["<A-id>", "<B-id>", "<C-id>"] }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The transforms fan out once `discover` is terminal; `verify` waits for all
|
|
135
|
+
three. Keep `verify`'s spec at intent altitude (gotcha 2) — it dispatches last,
|
|
136
|
+
after the others have drifted the line numbers it would otherwise have pinned.
|