smol-symphony 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/src/bin/symphony.js +30 -0
- package/dist/src/bin/symphony.js.map +1 -0
- package/dist/src/core/actions/context.js +109 -0
- package/dist/src/core/actions/context.js.map +1 -0
- package/dist/{actions/parsing.js → src/core/actions/parse.js} +33 -114
- package/dist/src/core/actions/parse.js.map +1 -0
- package/dist/src/core/actions/plan.js +197 -0
- package/dist/src/core/actions/plan.js.map +1 -0
- package/dist/src/core/actions/predicates.js +111 -0
- package/dist/src/core/actions/predicates.js.map +1 -0
- package/dist/src/core/actions/run-fold.js +248 -0
- package/dist/src/core/actions/run-fold.js.map +1 -0
- package/dist/src/core/actions/template.js +118 -0
- package/dist/src/core/actions/template.js.map +1 -0
- package/dist/src/core/cli/args.js +116 -0
- package/dist/src/core/cli/args.js.map +1 -0
- package/dist/src/core/coerce.js +75 -0
- package/dist/src/core/coerce.js.map +1 -0
- package/dist/src/core/credential/account-id.js +20 -0
- package/dist/src/core/credential/account-id.js.map +1 -0
- package/dist/src/core/credential/adapter-config.js +136 -0
- package/dist/src/core/credential/adapter-config.js.map +1 -0
- package/dist/src/core/credential/availability.js +98 -0
- package/dist/src/core/credential/availability.js.map +1 -0
- package/dist/src/core/credential/extract.js +228 -0
- package/dist/src/core/credential/extract.js.map +1 -0
- package/dist/src/core/credential/fake-creds.js +171 -0
- package/dist/src/core/credential/fake-creds.js.map +1 -0
- package/dist/src/core/credential/identity.js +125 -0
- package/dist/src/core/credential/identity.js.map +1 -0
- package/dist/src/core/credential/shape.js +230 -0
- package/dist/src/core/credential/shape.js.map +1 -0
- package/dist/src/core/credential/strings.js +15 -0
- package/dist/src/core/credential/strings.js.map +1 -0
- package/dist/src/core/doctor/checks.js +303 -0
- package/dist/src/core/doctor/checks.js.map +1 -0
- package/dist/src/core/git/result.js +107 -0
- package/dist/src/core/git/result.js.map +1 -0
- package/dist/src/core/http/decisions.js +225 -0
- package/dist/src/core/http/decisions.js.map +1 -0
- package/dist/{http.js → src/core/http/render.js} +472 -738
- package/dist/src/core/http/render.js.map +1 -0
- package/dist/{http-handlers.js → src/core/http/routes.js} +52 -87
- package/dist/src/core/http/routes.js.map +1 -0
- package/dist/src/core/http/views.js +181 -0
- package/dist/src/core/http/views.js.map +1 -0
- package/dist/src/core/image/managed-image.js +95 -0
- package/dist/src/core/image/managed-image.js.map +1 -0
- package/dist/src/core/issue/file.js +149 -0
- package/dist/src/core/issue/file.js.map +1 -0
- package/dist/src/core/issue/parse.js +210 -0
- package/dist/src/core/issue/parse.js.map +1 -0
- package/dist/src/core/mcp/dispatch.js +239 -0
- package/dist/src/core/mcp/dispatch.js.map +1 -0
- package/dist/src/core/mcp/post-move.js +92 -0
- package/dist/src/core/mcp/post-move.js.map +1 -0
- package/dist/src/core/mcp/protocol.js +293 -0
- package/dist/src/core/mcp/protocol.js.map +1 -0
- package/dist/src/core/mcp/url.js +162 -0
- package/dist/src/core/mcp/url.js.map +1 -0
- package/dist/src/core/path.js +63 -0
- package/dist/src/core/path.js.map +1 -0
- package/dist/src/core/reconcile/image-decide.js +48 -0
- package/dist/src/core/reconcile/image-decide.js.map +1 -0
- package/dist/src/core/reconcile/ledger.js +142 -0
- package/dist/src/core/reconcile/ledger.js.map +1 -0
- package/dist/src/core/reconcile/pr-classify.js +62 -0
- package/dist/src/core/reconcile/pr-classify.js.map +1 -0
- package/dist/{reconciler → src/core/reconcile}/pr-decide.js +25 -12
- package/dist/src/core/reconcile/pr-decide.js.map +1 -0
- package/dist/src/core/reconcile/pr-loop.js +161 -0
- package/dist/src/core/reconcile/pr-loop.js.map +1 -0
- package/dist/src/core/reconcile/pr-notes.js +35 -0
- package/dist/src/core/reconcile/pr-notes.js.map +1 -0
- package/dist/src/core/reconcile/vm-decide.js +70 -0
- package/dist/src/core/reconcile/vm-decide.js.map +1 -0
- package/dist/src/core/reconcile/vm-reap.js +207 -0
- package/dist/src/core/reconcile/vm-reap.js.map +1 -0
- package/dist/src/core/reconcile/workspace-decide.js +162 -0
- package/dist/src/core/reconcile/workspace-decide.js.map +1 -0
- package/dist/src/core/runlog/summary.js +231 -0
- package/dist/src/core/runlog/summary.js.map +1 -0
- package/dist/src/core/runner/dispatch-config.js +95 -0
- package/dist/src/core/runner/dispatch-config.js.map +1 -0
- package/dist/src/core/runner/injection.js +61 -0
- package/dist/src/core/runner/injection.js.map +1 -0
- package/dist/src/core/runner/mise.js +210 -0
- package/dist/src/core/runner/mise.js.map +1 -0
- package/dist/src/core/runner/prompt.js +720 -0
- package/dist/src/core/runner/prompt.js.map +1 -0
- package/dist/src/core/runner/turn.js +242 -0
- package/dist/src/core/runner/turn.js.map +1 -0
- package/dist/src/core/runner/vm-plan.js +390 -0
- package/dist/src/core/runner/vm-plan.js.map +1 -0
- package/dist/src/core/schedule/admission.js +123 -0
- package/dist/src/core/schedule/admission.js.map +1 -0
- package/dist/src/core/schedule/circuit-breaker.js +111 -0
- package/dist/src/core/schedule/circuit-breaker.js.map +1 -0
- package/dist/src/core/schedule/eligibility.js +83 -0
- package/dist/src/core/schedule/eligibility.js.map +1 -0
- package/dist/src/core/schedule/reconcile-issue.js +82 -0
- package/dist/src/core/schedule/reconcile-issue.js.map +1 -0
- package/dist/src/core/schedule/retry.js +96 -0
- package/dist/src/core/schedule/retry.js.map +1 -0
- package/dist/src/core/schedule/sleep-cycle.js +133 -0
- package/dist/src/core/schedule/sleep-cycle.js.map +1 -0
- package/dist/src/core/schedule/slots.js +124 -0
- package/dist/src/core/schedule/slots.js.map +1 -0
- package/dist/src/core/schedule/tick.js +553 -0
- package/dist/src/core/schedule/tick.js.map +1 -0
- package/dist/src/core/schedule/token-fold.js +181 -0
- package/dist/src/core/schedule/token-fold.js.map +1 -0
- package/dist/src/core/state-resolve.js +86 -0
- package/dist/src/core/state-resolve.js.map +1 -0
- package/dist/src/core/vm-guards.js +278 -0
- package/dist/src/core/vm-guards.js.map +1 -0
- package/dist/src/core/workflow/derive.js +107 -0
- package/dist/src/core/workflow/derive.js.map +1 -0
- package/dist/src/core/workflow/parse.js +687 -0
- package/dist/src/core/workflow/parse.js.map +1 -0
- package/dist/src/core/workflow/prompt-probe.js +78 -0
- package/dist/src/core/workflow/prompt-probe.js.map +1 -0
- package/dist/src/core/workflow/validate.js +189 -0
- package/dist/src/core/workflow/validate.js.map +1 -0
- package/dist/src/core/workspace-key.js +19 -0
- package/dist/src/core/workspace-key.js.map +1 -0
- package/dist/src/shell/actions-runner.js +356 -0
- package/dist/src/shell/actions-runner.js.map +1 -0
- package/dist/src/shell/adapter/adapter-registry.js +45 -0
- package/dist/src/shell/adapter/adapter-registry.js.map +1 -0
- package/dist/src/shell/adapter/clock-random.js +96 -0
- package/dist/src/shell/adapter/clock-random.js.map +1 -0
- package/dist/src/shell/adapter/gondolin-dispatch-helpers.js +158 -0
- package/dist/src/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
- package/dist/src/shell/adapter/gondolin-dispatch.js +385 -0
- package/dist/src/shell/adapter/gondolin-dispatch.js.map +1 -0
- package/dist/src/shell/adapter/gondolin-image-converter.js +233 -0
- package/dist/src/shell/adapter/gondolin-image-converter.js.map +1 -0
- package/dist/src/shell/adapter/gondolin-image-fetch.js +180 -0
- package/dist/src/shell/adapter/gondolin-image-fetch.js.map +1 -0
- package/dist/src/shell/adapter/launcher-asset.js +57 -0
- package/dist/src/shell/adapter/launcher-asset.js.map +1 -0
- package/dist/src/shell/adapter/mise-config-asset.js +65 -0
- package/dist/src/shell/adapter/mise-config-asset.js.map +1 -0
- package/dist/src/shell/adapter/workflow-loader.js +304 -0
- package/dist/src/shell/adapter/workflow-loader.js.map +1 -0
- package/dist/src/shell/cli/doctor.js +268 -0
- package/dist/src/shell/cli/doctor.js.map +1 -0
- package/dist/src/shell/effect-interpreter-families.js +314 -0
- package/dist/src/shell/effect-interpreter-families.js.map +1 -0
- package/dist/src/shell/effect-interpreter.js +29 -0
- package/dist/src/shell/effect-interpreter.js.map +1 -0
- package/dist/src/shell/interp/acp-frame.js +137 -0
- package/dist/src/shell/interp/acp-frame.js.map +1 -0
- package/dist/src/shell/interp/acp-ws-conn.js +320 -0
- package/dist/src/shell/interp/acp-ws-conn.js.map +1 -0
- package/dist/src/shell/interp/acp-ws-frames.js +159 -0
- package/dist/src/shell/interp/acp-ws-frames.js.map +1 -0
- package/dist/src/shell/interp/acp-ws.js +197 -0
- package/dist/src/shell/interp/acp-ws.js.map +1 -0
- package/dist/src/shell/interp/acp.js +319 -0
- package/dist/src/shell/interp/acp.js.map +1 -0
- package/dist/src/shell/interp/credential-defaults.js +128 -0
- package/dist/src/shell/interp/credential-defaults.js.map +1 -0
- package/dist/src/shell/interp/credential-hooks.js +149 -0
- package/dist/src/shell/interp/credential-hooks.js.map +1 -0
- package/dist/src/shell/interp/credential-registry.js +226 -0
- package/dist/src/shell/interp/credential-registry.js.map +1 -0
- package/dist/src/shell/interp/credential.js +103 -0
- package/dist/src/shell/interp/credential.js.map +1 -0
- package/dist/src/shell/interp/gh.js +163 -0
- package/dist/src/shell/interp/gh.js.map +1 -0
- package/dist/src/shell/interp/git.js +28 -0
- package/dist/src/shell/interp/git.js.map +1 -0
- package/dist/src/shell/interp/log.js +213 -0
- package/dist/src/shell/interp/log.js.map +1 -0
- package/dist/src/shell/interp/process.js +178 -0
- package/dist/src/shell/interp/process.js.map +1 -0
- package/dist/src/shell/interp/runlog.js +193 -0
- package/dist/src/shell/interp/runlog.js.map +1 -0
- package/dist/src/shell/interp/timer.js +64 -0
- package/dist/src/shell/interp/timer.js.map +1 -0
- package/dist/src/shell/interp/tracker-disk.js +99 -0
- package/dist/src/shell/interp/tracker-disk.js.map +1 -0
- package/dist/src/shell/interp/tracker-parse.js +71 -0
- package/dist/src/shell/interp/tracker-parse.js.map +1 -0
- package/dist/src/shell/interp/tracker-scan.js +238 -0
- package/dist/src/shell/interp/tracker-scan.js.map +1 -0
- package/dist/src/shell/interp/tracker-write.js +91 -0
- package/dist/src/shell/interp/tracker-write.js.map +1 -0
- package/dist/src/shell/interp/tracker.js +41 -0
- package/dist/src/shell/interp/tracker.js.map +1 -0
- package/dist/src/shell/interp/tty.js +48 -0
- package/dist/src/shell/interp/tty.js.map +1 -0
- package/dist/src/shell/interp/vm.js +199 -0
- package/dist/src/shell/interp/vm.js.map +1 -0
- package/dist/src/shell/interp/workspace.js +310 -0
- package/dist/src/shell/interp/workspace.js.map +1 -0
- package/dist/src/shell/main-acp.js +78 -0
- package/dist/src/shell/main-acp.js.map +1 -0
- package/dist/src/shell/main-adapters.js +222 -0
- package/dist/src/shell/main-adapters.js.map +1 -0
- package/dist/src/shell/main-credential.js +122 -0
- package/dist/src/shell/main-credential.js.map +1 -0
- package/dist/src/shell/main-doctor.js +22 -0
- package/dist/src/shell/main-doctor.js.map +1 -0
- package/dist/src/shell/main-entry.js +46 -0
- package/dist/src/shell/main-entry.js.map +1 -0
- package/dist/src/shell/main-http-csrf.js +45 -0
- package/dist/src/shell/main-http-csrf.js.map +1 -0
- package/dist/src/shell/main-http-handler.js +389 -0
- package/dist/src/shell/main-http-handler.js.map +1 -0
- package/dist/src/shell/main-http-mcp.js +122 -0
- package/dist/src/shell/main-http-mcp.js.map +1 -0
- package/dist/src/shell/main-http-views.js +253 -0
- package/dist/src/shell/main-http-views.js.map +1 -0
- package/dist/src/shell/main-http.js +76 -0
- package/dist/src/shell/main-http.js.map +1 -0
- package/dist/src/shell/main-loops.js +130 -0
- package/dist/src/shell/main-loops.js.map +1 -0
- package/dist/src/shell/main-mcp.js +129 -0
- package/dist/src/shell/main-mcp.js.map +1 -0
- package/dist/src/shell/main-orchestrator.js +120 -0
- package/dist/src/shell/main-orchestrator.js.map +1 -0
- package/dist/src/shell/main-preflight.js +43 -0
- package/dist/src/shell/main-preflight.js.map +1 -0
- package/dist/src/shell/main-reconcilers-helpers.js +244 -0
- package/dist/src/shell/main-reconcilers-helpers.js.map +1 -0
- package/dist/src/shell/main-reconcilers-pr.js +148 -0
- package/dist/src/shell/main-reconcilers-pr.js.map +1 -0
- package/dist/src/shell/main-reconcilers.js +225 -0
- package/dist/src/shell/main-reconcilers.js.map +1 -0
- package/dist/src/shell/main-runner.js +355 -0
- package/dist/src/shell/main-runner.js.map +1 -0
- package/dist/src/shell/main-scaffold.js +116 -0
- package/dist/src/shell/main-scaffold.js.map +1 -0
- package/dist/src/shell/main-shutdown.js +115 -0
- package/dist/src/shell/main-shutdown.js.map +1 -0
- package/dist/src/shell/main-startup.js +48 -0
- package/dist/src/shell/main-startup.js.map +1 -0
- package/dist/src/shell/main-substrates.js +43 -0
- package/dist/src/shell/main-substrates.js.map +1 -0
- package/dist/src/shell/main.js +385 -0
- package/dist/src/shell/main.js.map +1 -0
- package/dist/src/shell/orchestrator-feedback.js +69 -0
- package/dist/src/shell/orchestrator-feedback.js.map +1 -0
- package/dist/src/shell/orchestrator-image.js +167 -0
- package/dist/src/shell/orchestrator-image.js.map +1 -0
- package/dist/src/shell/orchestrator-loop.js +468 -0
- package/dist/src/shell/orchestrator-loop.js.map +1 -0
- package/dist/src/shell/orchestrator-reconcile.js +36 -0
- package/dist/src/shell/orchestrator-reconcile.js.map +1 -0
- package/dist/src/shell/reconciler-loop.js +228 -0
- package/dist/src/shell/reconciler-loop.js.map +1 -0
- package/dist/src/shell/runner-loop-turn.js +301 -0
- package/dist/src/shell/runner-loop-turn.js.map +1 -0
- package/dist/src/shell/runner-loop.js +338 -0
- package/dist/src/shell/runner-loop.js.map +1 -0
- package/dist/src/shell/server/http.js +208 -0
- package/dist/src/shell/server/http.js.map +1 -0
- package/dist/src/shell/server/mcp-runtime-effects.js +237 -0
- package/dist/src/shell/server/mcp-runtime-effects.js.map +1 -0
- package/dist/src/shell/server/mcp-runtime.js +99 -0
- package/dist/src/shell/server/mcp-runtime.js.map +1 -0
- package/dist/src/shell/workspace-key.js +14 -0
- package/dist/src/shell/workspace-key.js.map +1 -0
- package/dist/src/types/acp.js +8 -0
- package/dist/src/types/acp.js.map +1 -0
- package/dist/src/types/actions/plan.js +6 -0
- package/dist/src/types/actions/plan.js.map +1 -0
- package/dist/src/types/actions/predicates.js +6 -0
- package/dist/src/types/actions/predicates.js.map +1 -0
- package/dist/src/types/actions/run-fold.js +8 -0
- package/dist/src/types/actions/run-fold.js.map +1 -0
- package/dist/src/types/actions.js +7 -0
- package/dist/src/types/actions.js.map +1 -0
- package/dist/src/types/adapter/clock-random.js +4 -0
- package/dist/src/types/adapter/clock-random.js.map +1 -0
- package/dist/src/types/adapter/gondolin-image-converter.js +5 -0
- package/dist/src/types/adapter/gondolin-image-converter.js.map +1 -0
- package/dist/src/types/adapter/gondolin-image-fetch.js +5 -0
- package/dist/src/types/adapter/gondolin-image-fetch.js.map +1 -0
- package/dist/src/types/adapter/workflow-loader.js +4 -0
- package/dist/src/types/adapter/workflow-loader.js.map +1 -0
- package/dist/src/types/cli/args.js +8 -0
- package/dist/src/types/cli/args.js.map +1 -0
- package/dist/src/types/config.js +8 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/credential-interp.js +6 -0
- package/dist/src/types/credential-interp.js.map +1 -0
- package/dist/src/types/credentials.js +10 -0
- package/dist/src/types/credentials.js.map +1 -0
- package/dist/src/types/doctor.js +7 -0
- package/dist/src/types/doctor.js.map +1 -0
- package/dist/src/types/domain.js +7 -0
- package/dist/src/types/domain.js.map +1 -0
- package/dist/src/types/effect.js +15 -0
- package/dist/src/types/effect.js.map +1 -0
- package/dist/src/types/errors.js +39 -0
- package/dist/src/types/errors.js.map +1 -0
- package/dist/src/types/http/decisions.js +6 -0
- package/dist/src/types/http/decisions.js.map +1 -0
- package/dist/src/types/http/render.js +10 -0
- package/dist/src/types/http/render.js.map +1 -0
- package/dist/src/types/http/views.js +6 -0
- package/dist/src/types/http/views.js.map +1 -0
- package/dist/src/types/http.js +9 -0
- package/dist/src/types/http.js.map +1 -0
- package/dist/src/types/image/managed-image.js +7 -0
- package/dist/src/types/image/managed-image.js.map +1 -0
- package/dist/src/types/interp/effect-interpreter.js +8 -0
- package/dist/src/types/interp/effect-interpreter.js.map +1 -0
- package/dist/src/types/interp/tracker.js +7 -0
- package/dist/src/types/interp/tracker.js.map +1 -0
- package/dist/src/types/issue/file.js +6 -0
- package/dist/src/types/issue/file.js.map +1 -0
- package/dist/src/types/issue/parse.js +8 -0
- package/dist/src/types/issue/parse.js.map +1 -0
- package/dist/src/types/main-acp.js +13 -0
- package/dist/src/types/main-acp.js.map +1 -0
- package/dist/src/types/main-adapters.js +5 -0
- package/dist/src/types/main-adapters.js.map +1 -0
- package/dist/src/types/main-credential.js +21 -0
- package/dist/src/types/main-credential.js.map +1 -0
- package/dist/src/types/main-doctor.js +6 -0
- package/dist/src/types/main-doctor.js.map +1 -0
- package/dist/src/types/main-http-handler.js +12 -0
- package/dist/src/types/main-http-handler.js.map +1 -0
- package/dist/src/types/main-http.js +5 -0
- package/dist/src/types/main-http.js.map +1 -0
- package/dist/src/types/main-loops.js +5 -0
- package/dist/src/types/main-loops.js.map +1 -0
- package/dist/src/types/main-mcp.js +12 -0
- package/dist/src/types/main-mcp.js.map +1 -0
- package/dist/src/types/main-orchestrator.js +5 -0
- package/dist/src/types/main-orchestrator.js.map +1 -0
- package/dist/src/types/main-reconcilers.js +11 -0
- package/dist/src/types/main-reconcilers.js.map +1 -0
- package/dist/src/types/main-runner.js +13 -0
- package/dist/src/types/main-runner.js.map +1 -0
- package/dist/src/types/main-startup.js +5 -0
- package/dist/src/types/main-startup.js.map +1 -0
- package/dist/src/types/main-substrates.js +5 -0
- package/dist/src/types/main-substrates.js.map +1 -0
- package/dist/src/types/mcp/dispatch.js +4 -0
- package/dist/src/types/mcp/dispatch.js.map +1 -0
- package/dist/src/types/mcp/post-move.js +7 -0
- package/dist/src/types/mcp/post-move.js.map +1 -0
- package/dist/src/types/mcp.js +9 -0
- package/dist/src/types/mcp.js.map +1 -0
- package/dist/src/types/ports.js +12 -0
- package/dist/src/types/ports.js.map +1 -0
- package/dist/src/types/reconcile/image-decide.js +5 -0
- package/dist/src/types/reconcile/image-decide.js.map +1 -0
- package/dist/src/types/reconcile/ledger.js +7 -0
- package/dist/src/types/reconcile/ledger.js.map +1 -0
- package/dist/src/types/reconcile/pr-loop.js +8 -0
- package/dist/src/types/reconcile/pr-loop.js.map +1 -0
- package/dist/src/types/reconcile/vm-reap.js +8 -0
- package/dist/src/types/reconcile/vm-reap.js.map +1 -0
- package/dist/src/types/reconcile/workspace-decide.js +7 -0
- package/dist/src/types/reconcile/workspace-decide.js.map +1 -0
- package/dist/src/types/reconcile.js +9 -0
- package/dist/src/types/reconcile.js.map +1 -0
- package/dist/src/types/runlog.js +7 -0
- package/dist/src/types/runlog.js.map +1 -0
- package/dist/src/types/runner/actions-runner.js +12 -0
- package/dist/src/types/runner/actions-runner.js.map +1 -0
- package/dist/src/types/runner/gondolin-dispatch.js +5 -0
- package/dist/src/types/runner/gondolin-dispatch.js.map +1 -0
- package/dist/src/types/runner/injection.js +6 -0
- package/dist/src/types/runner/injection.js.map +1 -0
- package/dist/src/types/runner/runner-loop.js +5 -0
- package/dist/src/types/runner/runner-loop.js.map +1 -0
- package/dist/src/types/runner/turn.js +4 -0
- package/dist/src/types/runner/turn.js.map +1 -0
- package/dist/src/types/runner/vm-plan.js +4 -0
- package/dist/src/types/runner/vm-plan.js.map +1 -0
- package/dist/src/types/runtime.js +9 -0
- package/dist/src/types/runtime.js.map +1 -0
- package/dist/src/types/schedule/admission.js +7 -0
- package/dist/src/types/schedule/admission.js.map +1 -0
- package/dist/src/types/schedule/circuit-breaker.js +2 -0
- package/dist/src/types/schedule/circuit-breaker.js.map +1 -0
- package/dist/src/types/schedule/eligibility.js +9 -0
- package/dist/src/types/schedule/eligibility.js.map +1 -0
- package/dist/src/types/schedule/orchestrator-loop.js +10 -0
- package/dist/src/types/schedule/orchestrator-loop.js.map +1 -0
- package/dist/src/types/schedule/sleep-cycle.js +4 -0
- package/dist/src/types/schedule/sleep-cycle.js.map +1 -0
- package/dist/src/types/schedule/slots.js +8 -0
- package/dist/src/types/schedule/slots.js.map +1 -0
- package/dist/src/types/schedule/tick.js +9 -0
- package/dist/src/types/schedule/tick.js.map +1 -0
- package/dist/src/types/server/mcp-runtime.js +8 -0
- package/dist/src/types/server/mcp-runtime.js.map +1 -0
- package/dist/src/types/workflow/parse.js +4 -0
- package/dist/src/types/workflow/parse.js.map +1 -0
- package/dist/tests/core/account-id.test.js +35 -0
- package/dist/tests/core/account-id.test.js.map +1 -0
- package/dist/tests/core/actions-parse.test.js +176 -0
- package/dist/tests/core/actions-parse.test.js.map +1 -0
- package/dist/tests/core/adapter-config.test.js +133 -0
- package/dist/tests/core/adapter-config.test.js.map +1 -0
- package/dist/tests/core/admission.test.js +215 -0
- package/dist/tests/core/admission.test.js.map +1 -0
- package/dist/tests/core/args.test.js +132 -0
- package/dist/tests/core/args.test.js.map +1 -0
- package/dist/tests/core/availability.test.js +62 -0
- package/dist/tests/core/availability.test.js.map +1 -0
- package/dist/tests/core/checks.test.js +395 -0
- package/dist/tests/core/checks.test.js.map +1 -0
- package/dist/tests/core/circuit-breaker.test.js +172 -0
- package/dist/tests/core/circuit-breaker.test.js.map +1 -0
- package/dist/tests/core/coerce.test.js +87 -0
- package/dist/tests/core/coerce.test.js.map +1 -0
- package/dist/tests/core/context.test.js +228 -0
- package/dist/tests/core/context.test.js.map +1 -0
- package/dist/tests/core/decisions.test.js +310 -0
- package/dist/tests/core/decisions.test.js.map +1 -0
- package/dist/tests/core/derive.test.js +205 -0
- package/dist/tests/core/derive.test.js.map +1 -0
- package/dist/tests/core/dispatch-config.test.js +164 -0
- package/dist/tests/core/dispatch-config.test.js.map +1 -0
- package/dist/tests/core/dispatch.test.js +302 -0
- package/dist/tests/core/dispatch.test.js.map +1 -0
- package/dist/tests/core/eligibility.test.js +163 -0
- package/dist/tests/core/eligibility.test.js.map +1 -0
- package/dist/tests/core/extract.test.js +139 -0
- package/dist/tests/core/extract.test.js.map +1 -0
- package/dist/tests/core/fake-creds.test.js +134 -0
- package/dist/tests/core/fake-creds.test.js.map +1 -0
- package/dist/tests/core/file.test.js +197 -0
- package/dist/tests/core/file.test.js.map +1 -0
- package/dist/tests/core/git-result.test.js +113 -0
- package/dist/tests/core/git-result.test.js.map +1 -0
- package/dist/tests/core/identity.test.js +180 -0
- package/dist/tests/core/identity.test.js.map +1 -0
- package/dist/tests/core/image-decide.test.js +59 -0
- package/dist/tests/core/image-decide.test.js.map +1 -0
- package/dist/tests/core/injection.test.js +163 -0
- package/dist/tests/core/injection.test.js.map +1 -0
- package/dist/tests/core/ledger.test.js +218 -0
- package/dist/tests/core/ledger.test.js.map +1 -0
- package/dist/tests/core/managed-image.test.js +68 -0
- package/dist/tests/core/managed-image.test.js.map +1 -0
- package/dist/tests/core/mise.test.js +138 -0
- package/dist/tests/core/mise.test.js.map +1 -0
- package/dist/tests/core/parse.test.js +174 -0
- package/dist/tests/core/parse.test.js.map +1 -0
- package/dist/tests/core/path.test.js +50 -0
- package/dist/tests/core/path.test.js.map +1 -0
- package/dist/tests/core/plan.test.js +218 -0
- package/dist/tests/core/plan.test.js.map +1 -0
- package/dist/tests/core/post-move.test.js +162 -0
- package/dist/tests/core/post-move.test.js.map +1 -0
- package/dist/tests/core/pr-classify.test.js +117 -0
- package/dist/tests/core/pr-classify.test.js.map +1 -0
- package/dist/tests/core/pr-decide.test.js +298 -0
- package/dist/tests/core/pr-decide.test.js.map +1 -0
- package/dist/tests/core/pr-loop.test.js +301 -0
- package/dist/tests/core/pr-loop.test.js.map +1 -0
- package/dist/tests/core/pr-notes.test.js +165 -0
- package/dist/tests/core/pr-notes.test.js.map +1 -0
- package/dist/tests/core/predicates.test.js +154 -0
- package/dist/tests/core/predicates.test.js.map +1 -0
- package/dist/tests/core/prompt.test.js +189 -0
- package/dist/tests/core/prompt.test.js.map +1 -0
- package/dist/tests/core/protocol.test.js +195 -0
- package/dist/tests/core/protocol.test.js.map +1 -0
- package/dist/tests/core/reconcile-issue.test.js +116 -0
- package/dist/tests/core/reconcile-issue.test.js.map +1 -0
- package/dist/tests/core/render.test.js +549 -0
- package/dist/tests/core/render.test.js.map +1 -0
- package/dist/tests/core/retry.test.js +186 -0
- package/dist/tests/core/retry.test.js.map +1 -0
- package/dist/tests/core/routes.test.js +247 -0
- package/dist/tests/core/routes.test.js.map +1 -0
- package/dist/tests/core/run-fold.test.js +299 -0
- package/dist/tests/core/run-fold.test.js.map +1 -0
- package/dist/tests/core/shape.test.js +185 -0
- package/dist/tests/core/shape.test.js.map +1 -0
- package/dist/tests/core/sleep-cycle.test.js +150 -0
- package/dist/tests/core/sleep-cycle.test.js.map +1 -0
- package/dist/tests/core/slots.test.js +201 -0
- package/dist/tests/core/slots.test.js.map +1 -0
- package/dist/tests/core/state-resolve.test.js +80 -0
- package/dist/tests/core/state-resolve.test.js.map +1 -0
- package/dist/tests/core/summary.test.js +200 -0
- package/dist/tests/core/summary.test.js.map +1 -0
- package/dist/tests/core/template.test.js +116 -0
- package/dist/tests/core/template.test.js.map +1 -0
- package/dist/tests/core/tick.test.js +558 -0
- package/dist/tests/core/tick.test.js.map +1 -0
- package/dist/tests/core/token-fold.test.js +176 -0
- package/dist/tests/core/token-fold.test.js.map +1 -0
- package/dist/tests/core/turn.test.js +388 -0
- package/dist/tests/core/turn.test.js.map +1 -0
- package/dist/tests/core/url.test.js +118 -0
- package/dist/tests/core/url.test.js.map +1 -0
- package/dist/tests/core/validate.test.js +247 -0
- package/dist/tests/core/validate.test.js.map +1 -0
- package/dist/tests/core/views.test.js +252 -0
- package/dist/tests/core/views.test.js.map +1 -0
- package/dist/tests/core/vm-decide.test.js +110 -0
- package/dist/tests/core/vm-decide.test.js.map +1 -0
- package/dist/tests/core/vm-guards.test.js +153 -0
- package/dist/tests/core/vm-guards.test.js.map +1 -0
- package/dist/tests/core/vm-plan.test.js +332 -0
- package/dist/tests/core/vm-plan.test.js.map +1 -0
- package/dist/tests/core/vm-reap.test.js +196 -0
- package/dist/tests/core/vm-reap.test.js.map +1 -0
- package/dist/tests/core/workflow-parse.test.js +493 -0
- package/dist/tests/core/workflow-parse.test.js.map +1 -0
- package/dist/tests/core/workspace-decide.test.js +236 -0
- package/dist/tests/core/workspace-decide.test.js.map +1 -0
- package/dist/tests/helpers/fixtures.js +167 -0
- package/dist/tests/helpers/fixtures.js.map +1 -0
- package/dist/tests/shell/acp-substrate.test.js +101 -0
- package/dist/tests/shell/acp-substrate.test.js.map +1 -0
- package/dist/tests/shell/actions-runner-push.test.js +203 -0
- package/dist/tests/shell/actions-runner-push.test.js.map +1 -0
- package/dist/tests/shell/credential-hooks.test.js +36 -0
- package/dist/tests/shell/credential-hooks.test.js.map +1 -0
- package/dist/tests/shell/credential-registry.test.js +165 -0
- package/dist/tests/shell/credential-registry.test.js.map +1 -0
- package/dist/tests/shell/credential-substrate.test.js +179 -0
- package/dist/tests/shell/credential-substrate.test.js.map +1 -0
- package/dist/tests/shell/dockerfile-mise-pin.test.js +51 -0
- package/dist/tests/shell/dockerfile-mise-pin.test.js.map +1 -0
- package/dist/tests/shell/doctor.test.js +101 -0
- package/dist/tests/shell/doctor.test.js.map +1 -0
- package/dist/tests/shell/effect-vm-create.test.js +52 -0
- package/dist/tests/shell/effect-vm-create.test.js.map +1 -0
- package/dist/tests/shell/gh-port.test.js +63 -0
- package/dist/tests/shell/gh-port.test.js.map +1 -0
- package/dist/tests/shell/gondolin-dispatch-guard.test.js +144 -0
- package/dist/tests/shell/gondolin-dispatch-guard.test.js.map +1 -0
- package/dist/tests/shell/gondolin-dispatch-shquote.test.js +168 -0
- package/dist/tests/shell/gondolin-dispatch-shquote.test.js.map +1 -0
- package/dist/tests/shell/gondolin-image-converter.test.js +208 -0
- package/dist/tests/shell/gondolin-image-converter.test.js.map +1 -0
- package/dist/tests/shell/gondolin-image-fetch.test.js +93 -0
- package/dist/tests/shell/gondolin-image-fetch.test.js.map +1 -0
- package/dist/tests/shell/http-handler.test.js +608 -0
- package/dist/tests/shell/http-handler.test.js.map +1 -0
- package/dist/tests/shell/http-server.test.js +53 -0
- package/dist/tests/shell/http-server.test.js.map +1 -0
- package/dist/tests/shell/mcp-runtime.test.js +366 -0
- package/dist/tests/shell/mcp-runtime.test.js.map +1 -0
- package/dist/tests/shell/mise-config-asset.test.js +87 -0
- package/dist/tests/shell/mise-config-asset.test.js.map +1 -0
- package/dist/tests/shell/orchestrator-loop.test.js +583 -0
- package/dist/tests/shell/orchestrator-loop.test.js.map +1 -0
- package/dist/tests/shell/reconciler-passes.test.js +314 -0
- package/dist/tests/shell/reconciler-passes.test.js.map +1 -0
- package/dist/tests/shell/runner-loop-turn.test.js +97 -0
- package/dist/tests/shell/runner-loop-turn.test.js.map +1 -0
- package/dist/tests/shell/runner-slice.test.js +536 -0
- package/dist/tests/shell/runner-slice.test.js.map +1 -0
- package/dist/tests/shell/scaffold.test.js +65 -0
- package/dist/tests/shell/scaffold.test.js.map +1 -0
- package/dist/tests/shell/tick-config.test.js +83 -0
- package/dist/tests/shell/tick-config.test.js.map +1 -0
- package/dist/tests/shell/tracker-parse-dates.test.js +44 -0
- package/dist/tests/shell/tracker-parse-dates.test.js.map +1 -0
- package/dist/tests/shell/tracker-write-issue.test.js +154 -0
- package/dist/tests/shell/tracker-write-issue.test.js.map +1 -0
- package/dist/tests/shell/workflow-prompt-split.test.js +208 -0
- package/dist/tests/shell/workflow-prompt-split.test.js.map +1 -0
- package/dist/tests/shell/workspace-live-config.test.js +140 -0
- package/dist/tests/shell/workspace-live-config.test.js.map +1 -0
- package/package.json +21 -9
- 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/bin/symphony.js +0 -794
- package/dist/bin/symphony.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,238 @@
|
|
|
1
|
+
// Deps-bound tracker IO split out of tracker.ts (kind: adapter): scan/fetch/
|
|
2
|
+
// move/write + the per-effect router. Takes a small context (live config + log)
|
|
3
|
+
// so the factory in tracker.ts stays a thin wiring shell.
|
|
4
|
+
//
|
|
5
|
+
// Fidelity: src/trackers/local.ts (scan/normalize/move) + src/http-disk.ts.
|
|
6
|
+
import { mkdir, readdir, readFile, rename, stat } from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { TrackerError } from '../../types/errors.js';
|
|
9
|
+
import { splitFrontMatter } from './tracker-parse.js';
|
|
10
|
+
import { listIssues, readIssue } from './tracker-disk.js';
|
|
11
|
+
import { assertTargetFree, buildNotesBody, durablyCreateExclusive, durablyReplaceInPlace } from './tracker-write.js';
|
|
12
|
+
const warn = (ctx, msg, fields) => ctx.log?.emit('warn', msg, fields);
|
|
13
|
+
function rootOf(ctx) {
|
|
14
|
+
const r = ctx.config().root;
|
|
15
|
+
if (!r)
|
|
16
|
+
throw new TrackerError('local_no_root', 'tracker.root is required');
|
|
17
|
+
return r;
|
|
18
|
+
}
|
|
19
|
+
// Parse every .md under one declared state dir. Per-file failures skip with a
|
|
20
|
+
// warning so one bad file doesn't poison the scan (faithful to local.ts).
|
|
21
|
+
async function scanStateDir(ctx, dirPath, stateDir, out) {
|
|
22
|
+
let files;
|
|
23
|
+
try {
|
|
24
|
+
files = await readdir(dirPath);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const fileName of files) {
|
|
30
|
+
if (!fileName.endsWith('.md'))
|
|
31
|
+
continue;
|
|
32
|
+
const filePath = path.join(dirPath, fileName);
|
|
33
|
+
try {
|
|
34
|
+
if (!(await stat(filePath)).isFile())
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
let text;
|
|
41
|
+
try {
|
|
42
|
+
text = await readFile(filePath, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
warn(ctx, 'skipping unreadable issue file', { file: filePath, error: err.message });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const { fields, body } = splitFrontMatter(text);
|
|
50
|
+
out.push({ filePath, identifier: fileName.slice(0, -3), state: stateDir, frontMatter: fields, description: body });
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
warn(ctx, 'skipping malformed issue file', { file: filePath, error: err.message });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Scan every declared state dir under `root`; undeclared dirs are warned + skipped.
|
|
58
|
+
async function scanAllAt(ctx, root) {
|
|
59
|
+
let entries;
|
|
60
|
+
try {
|
|
61
|
+
entries = await readdir(root);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// A not-yet-created root is an EMPTY tracker, not an error: a fresh
|
|
65
|
+
// `scaffold → run` has no issues until the first one is filed (the write path
|
|
66
|
+
// mkdir's the state dir on create), so the first poll must NOT crash. Mirror
|
|
67
|
+
// scanStateDir's tolerance of a missing state dir. Any OTHER read failure
|
|
68
|
+
// (EACCES, ENOTDIR, …) is a real misconfiguration → surface it loudly.
|
|
69
|
+
if (err.code === 'ENOENT')
|
|
70
|
+
return [];
|
|
71
|
+
throw new TrackerError('local_root_read_error', `cannot read tracker.root ${root}: ${err.message}`);
|
|
72
|
+
}
|
|
73
|
+
const declared = new Set(Object.keys(ctx.config().states).map((n) => n.toLowerCase()));
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const dirEntry of entries) {
|
|
76
|
+
const dirPath = path.join(root, dirEntry);
|
|
77
|
+
try {
|
|
78
|
+
if (!(await stat(dirPath)).isDirectory())
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!declared.has(dirEntry.toLowerCase())) {
|
|
85
|
+
warn(ctx, 'skipping undeclared state directory', { dir: dirPath });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
await scanStateDir(ctx, dirPath, dirEntry, out);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The ONE tracker read: scan every declared state dir into raw decoded
|
|
94
|
+
* front-matter (no filtering, no normalization). Selection/normalization is the
|
|
95
|
+
* pure core's job — the shell hands it this raw list and lets it filter down
|
|
96
|
+
* (core/issue/parse.ts:selectCandidateIssues/selectIssuesByIds/...). `root` is
|
|
97
|
+
* captured here so the orchestrator's atomic candidate-fetch can report it.
|
|
98
|
+
*/
|
|
99
|
+
export async function loadAllIssues(ctx) {
|
|
100
|
+
const root = rootOf(ctx);
|
|
101
|
+
return { raw: await scanAllAt(ctx, root), root };
|
|
102
|
+
}
|
|
103
|
+
// Append-notes-then-atomic-rename: write notes into a same-dir tmp+fsync, rename
|
|
104
|
+
// in place, THEN cross-dir rename into the target state. A crash between the two
|
|
105
|
+
// renames leaves notes persisted in the source dir (faithful to local.ts).
|
|
106
|
+
export async function moveIssueToState(ctx, issueId, toState, opts) {
|
|
107
|
+
const root = opts?.fromRoot ?? rootOf(ctx);
|
|
108
|
+
// Match resolution (filter-by-id + duplicate disambiguation) is pure core logic
|
|
109
|
+
// injected through ScanCtx; the shell only does the readdir + the rename IO.
|
|
110
|
+
const match = ctx.resolveMatch(await scanAllAt(ctx, root), issueId, opts?.fromState);
|
|
111
|
+
if (match.state.toLowerCase() === toState.toLowerCase()) {
|
|
112
|
+
return { fromState: match.state, toState: match.state, newPath: match.filePath };
|
|
113
|
+
}
|
|
114
|
+
await appendNotes(match, toState, opts);
|
|
115
|
+
const newPath = path.join(root, toState, path.basename(match.filePath));
|
|
116
|
+
await mkdir(path.join(root, toState), { recursive: true });
|
|
117
|
+
await assertTargetFree(newPath);
|
|
118
|
+
try {
|
|
119
|
+
await rename(match.filePath, newPath);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
throw new TrackerError('local_issue_move_error', `failed to move ${match.filePath} -> ${newPath}: ${err.message}`);
|
|
123
|
+
}
|
|
124
|
+
ctx.log?.emit('info', 'issue transitioned', { issue_id: issueId, from_state: match.state, to_state: toState, path: newPath });
|
|
125
|
+
return { fromState: match.state, toState, newPath };
|
|
126
|
+
}
|
|
127
|
+
async function appendNotes(match, toState, opts) {
|
|
128
|
+
const notes = typeof opts?.notes === 'string' ? opts.notes : '';
|
|
129
|
+
if (notes.length === 0)
|
|
130
|
+
return;
|
|
131
|
+
let original;
|
|
132
|
+
try {
|
|
133
|
+
original = await readFile(match.filePath, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
throw new TrackerError('local_issue_notes_read_error', `failed to read ${match.filePath} for notes append: ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
await durablyReplaceInPlace(match.filePath, buildNotesBody(original, match.state, toState, notes, opts?.actor));
|
|
139
|
+
}
|
|
140
|
+
export async function start(ctx) {
|
|
141
|
+
const root = rootOf(ctx);
|
|
142
|
+
for (const name of Object.keys(ctx.config().states)) {
|
|
143
|
+
await mkdir(path.join(root, name), { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Collect every existing identifier (filename stem) across ALL state dirs under
|
|
147
|
+
// `root` — the readdir half of the original `collectExistingIdentifiers`. Counts
|
|
148
|
+
// stems from EVERY state so a `Done/3.md` can't silently shadow a freshly-picked
|
|
149
|
+
// `Holding/3.md`. The pure next-free-numeric pick (core) consumes this set.
|
|
150
|
+
async function collectExistingIdentifiers(ctx, root) {
|
|
151
|
+
const existing = new Set();
|
|
152
|
+
for (const raw of await scanAllAt(ctx, root))
|
|
153
|
+
existing.add(raw.identifier);
|
|
154
|
+
return existing;
|
|
155
|
+
}
|
|
156
|
+
// write_issue: durable-write a NEW issue file. Every caller is a CREATE path
|
|
157
|
+
// (the HTTP create-issue handler, the MCP propose_issue write) — none is an
|
|
158
|
+
// in-place UPDATE — so an explicit identifier whose target file is already
|
|
159
|
+
// occupied is a collision, NOT a silent overwrite. The serialization
|
|
160
|
+
// (identifier sanitize, YAML front-matter render, body framing) is the PURE
|
|
161
|
+
// core `composeIssueFile`, injected through the ScanCtx.
|
|
162
|
+
//
|
|
163
|
+
// Identifier resolution (both branches scan the EXISTING ids across ALL state
|
|
164
|
+
// dirs so the create-409 contract holds either way):
|
|
165
|
+
// • effect.identifier PRESENT (HTTP create / triage path) → fed back in
|
|
166
|
+
// verbatim; composeIssueFile sanitizes it (idempotent) and rejects with
|
|
167
|
+
// `identifier_exists` when the sanitized id is already taken — that rejection
|
|
168
|
+
// surfaces here as a `local_issue_target_exists` TrackerError the HTTP layer
|
|
169
|
+
// maps to 409, instead of `durablyReplaceInPlace` overwriting the live file.
|
|
170
|
+
// • effect.identifier ABSENT (MCP propose_issue path) → the injected core
|
|
171
|
+
// `pickNextNumericIdentifier` chooses the next free numeric id over the SAME
|
|
172
|
+
// existing set. This replaces the old `?? '1'` default that overwrote
|
|
173
|
+
// `<holding>/1.md` on every proposal.
|
|
174
|
+
export async function writeIssue(ctx, effect) {
|
|
175
|
+
// Scan once across every state dir: the explicit-identifier branch needs the set
|
|
176
|
+
// to detect a collision (the create-409 contract); the no-identifier branch needs
|
|
177
|
+
// it to pick the next free numeric id. (Pre-fix the explicit branch passed an
|
|
178
|
+
// EMPTY set, so composeIssueFile's collision guard never fired and an occupied
|
|
179
|
+
// target was silently overwritten via durablyReplaceInPlace.)
|
|
180
|
+
const existingIdentifiers = await collectExistingIdentifiers(ctx, effect.trackerRoot);
|
|
181
|
+
const identifierIn = effect.identifier ?? ctx.pickNextNumericIdentifier(existingIdentifiers);
|
|
182
|
+
const composed = ctx.composeIssueFile({
|
|
183
|
+
trackerRoot: effect.trackerRoot,
|
|
184
|
+
state: effect.state,
|
|
185
|
+
title: effect.title,
|
|
186
|
+
identifier: identifierIn,
|
|
187
|
+
...(effect.description != null ? { description: effect.description } : {}),
|
|
188
|
+
...(effect.priority != null ? { priority: effect.priority } : {}),
|
|
189
|
+
...(effect.labels !== undefined ? { labels: effect.labels } : {}),
|
|
190
|
+
...(effect.blocked_by !== undefined ? { blocked_by: effect.blocked_by } : {}),
|
|
191
|
+
...(effect.extraFrontMatter !== undefined ? { extra_front_matter: effect.extraFrontMatter } : {}),
|
|
192
|
+
existingIdentifiers,
|
|
193
|
+
nowIso: new Date().toISOString(),
|
|
194
|
+
});
|
|
195
|
+
if (!composed.ok) {
|
|
196
|
+
// An explicit identifier already occupied → honour the create-409 contract:
|
|
197
|
+
// surface a `local_issue_target_exists` (the same code assertTargetFree uses)
|
|
198
|
+
// so the HTTP create handler maps it to 409 rather than overwriting the file.
|
|
199
|
+
if (composed.error.code === 'identifier_exists') {
|
|
200
|
+
throw new TrackerError('local_issue_target_exists', composed.error.message);
|
|
201
|
+
}
|
|
202
|
+
throw new TrackerError('local_issue_write_error', `failed to compose issue file: ${composed.error.message}`);
|
|
203
|
+
}
|
|
204
|
+
const { path: filePath, identifier } = composed.result;
|
|
205
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
206
|
+
// CREATE via an EXCLUSIVE (O_EXCL) open, NOT an overwriting tmp+rename: the
|
|
207
|
+
// scan-based collision check above is a TOCTOU window, so two concurrent creates
|
|
208
|
+
// of the SAME identifier could both pass it. The exclusive create makes the kernel
|
|
209
|
+
// the arbiter — exactly one wins; the loser's EEXIST surfaces as
|
|
210
|
+
// `local_issue_target_exists` (→ HTTP 409), with no overwrite/corruption.
|
|
211
|
+
await durablyCreateExclusive(filePath, composed.result.content);
|
|
212
|
+
return { kind: 'write_issue', path: filePath, identifier, state: effect.state };
|
|
213
|
+
}
|
|
214
|
+
async function interpretMove(ctx, effect) {
|
|
215
|
+
const r = await moveIssueToState(ctx, effect.issueId, effect.toState, {
|
|
216
|
+
fromRoot: effect.fromRoot ?? undefined,
|
|
217
|
+
fromState: effect.fromState ?? undefined,
|
|
218
|
+
notes: effect.notes,
|
|
219
|
+
actor: effect.actor ?? undefined,
|
|
220
|
+
});
|
|
221
|
+
return { kind: 'move_state', fromState: r.fromState, toState: r.toState, newPath: r.newPath };
|
|
222
|
+
}
|
|
223
|
+
// Per-effect router (the central interpreter delegates the tracker family).
|
|
224
|
+
export async function interpret(ctx, effect) {
|
|
225
|
+
switch (effect.kind) {
|
|
226
|
+
case 'current_root':
|
|
227
|
+
return { kind: 'tracker_root', root: ctx.config().root };
|
|
228
|
+
case 'move_state':
|
|
229
|
+
return interpretMove(ctx, effect);
|
|
230
|
+
case 'write_issue':
|
|
231
|
+
return writeIssue(ctx, effect);
|
|
232
|
+
case 'list_issues_from_disk':
|
|
233
|
+
return { kind: 'disk_issues', issues: await listIssues(effect.root) };
|
|
234
|
+
case 'read_issue_from_disk':
|
|
235
|
+
return { kind: 'disk_issue_detail', detail: await readIssue(effect.root, effect.identifier) };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=tracker-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker-scan.js","sourceRoot":"","sources":["../../../../src/shell/interp/tracker-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Durable-write + move primitives split out of tracker.ts (kind: adapter).
|
|
2
|
+
// Stateless (no closure deps). Faithful to src/trackers/local.ts (atomic
|
|
3
|
+
// rename, fsync, notes append). Match resolution (filter-by-id + duplicate
|
|
4
|
+
// disambiguation) is the pure core's job (core/issue/parse.ts:matchingCandidates
|
|
5
|
+
// +resolveMatch), injected into the move path through ScanCtx.
|
|
6
|
+
import { open, rename, stat } from 'node:fs/promises';
|
|
7
|
+
import { TrackerError } from '../../types/errors.js';
|
|
8
|
+
export function buildNotesBody(original, fromState, toState, notes, actor) {
|
|
9
|
+
const who = actor && actor.length > 0 ? actor : 'unknown';
|
|
10
|
+
const header = `## ${who} — ${new Date().toISOString()} — ${fromState} → ${toState}`;
|
|
11
|
+
const sep = original.endsWith('\n\n') ? '' : original.endsWith('\n') ? '\n' : '\n\n';
|
|
12
|
+
return `${original}${sep}${header}\n\n${notes}\n`;
|
|
13
|
+
}
|
|
14
|
+
// Same-dir tmp+fsync then atomic rename in place. UPDATE semantics: the rename
|
|
15
|
+
// OVERWRITES the target, so this is for in-place edits of an EXISTING file (notes
|
|
16
|
+
// append, move-in-place). New-issue CREATE must use durablyCreateExclusive — its
|
|
17
|
+
// overwriting rename cannot enforce the create-409 contract under concurrency.
|
|
18
|
+
export async function durablyReplaceInPlace(targetPath, contents) {
|
|
19
|
+
const tmpPath = targetPath + '.tmp';
|
|
20
|
+
let fh;
|
|
21
|
+
try {
|
|
22
|
+
fh = await open(tmpPath, 'w', 0o644);
|
|
23
|
+
await fh.writeFile(contents, 'utf8');
|
|
24
|
+
await fh.sync();
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
try {
|
|
28
|
+
if (fh)
|
|
29
|
+
await fh.close();
|
|
30
|
+
}
|
|
31
|
+
catch { /* best-effort */ }
|
|
32
|
+
throw new TrackerError('local_issue_notes_write_error', `failed to write notes tmp file ${tmpPath}: ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
await fh.close();
|
|
36
|
+
}
|
|
37
|
+
catch { /* on disk + fsync'd; close is not load-bearing */ }
|
|
38
|
+
try {
|
|
39
|
+
await rename(tmpPath, targetPath);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
throw new TrackerError('local_issue_notes_rename_error', `failed to atomic-rename ${tmpPath} -> ${targetPath}: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Durable CREATE of a NEW issue file. The scan-based 409 check in writeIssue is a
|
|
46
|
+
// TOCTOU window — two concurrent creates of the same identifier can both pass it,
|
|
47
|
+
// then a deterministic-tmp+overwriting-rename (durablyReplaceInPlace) would let
|
|
48
|
+
// the second silently clobber the first. Instead open the FINAL path with O_EXCL
|
|
49
|
+
// ('wx'): the kernel guarantees exactly one creator wins; the loser gets EEXIST,
|
|
50
|
+
// which we map to the same `local_issue_target_exists` TrackerError the create-409
|
|
51
|
+
// contract already uses (→ HTTP 409). fsync keeps durability. No tmp+rename: the
|
|
52
|
+
// excl open IS the atomic claim, so there is no shared temp path to race on.
|
|
53
|
+
export async function durablyCreateExclusive(targetPath, contents) {
|
|
54
|
+
let fh;
|
|
55
|
+
try {
|
|
56
|
+
fh = await open(targetPath, 'wx', 0o644);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (err.code === 'EEXIST') {
|
|
60
|
+
throw new TrackerError('local_issue_target_exists', `refusing to overwrite existing file at ${targetPath}`);
|
|
61
|
+
}
|
|
62
|
+
throw new TrackerError('local_issue_write_error', `failed to create issue file ${targetPath}: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await fh.writeFile(contents, 'utf8');
|
|
66
|
+
await fh.sync();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
try {
|
|
70
|
+
await fh.close();
|
|
71
|
+
}
|
|
72
|
+
catch { /* best-effort */ }
|
|
73
|
+
throw new TrackerError('local_issue_write_error', `failed to write issue file ${targetPath}: ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
await fh.close();
|
|
77
|
+
}
|
|
78
|
+
catch { /* on disk + fsync'd; close is not load-bearing */ }
|
|
79
|
+
}
|
|
80
|
+
export async function assertTargetFree(newPath) {
|
|
81
|
+
try {
|
|
82
|
+
await stat(newPath);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err.code === 'ENOENT')
|
|
86
|
+
return;
|
|
87
|
+
throw new TrackerError('local_issue_move_error', `failed to stat target ${newPath}: ${err.message}`);
|
|
88
|
+
}
|
|
89
|
+
throw new TrackerError('local_issue_target_exists', `refusing to overwrite existing file at ${newPath}`);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=tracker-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker-write.js","sourceRoot":"","sources":["../../../../src/shell/interp/tracker-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// FCIS rewrite — tracker effect interpreter (kind: adapter).
|
|
2
|
+
//
|
|
3
|
+
// Interprets every `TrackerEffect` variant against the filesystem-backed
|
|
4
|
+
// LocalMarkdownTracker layout `<tracker.root>/<state>/<identifier>.md`:
|
|
5
|
+
// • current_root — snapshot the live tracker root
|
|
6
|
+
// • move_state — append-notes-then-atomic-rename across state dirs
|
|
7
|
+
// • write_issue — render + durable write a new issue file
|
|
8
|
+
// • list/read_issue_from_disk — the dashboard's direct-disk reads (critic gap)
|
|
9
|
+
// Selection reads (candidates / by-id) go through loadAllIssues + the pure core
|
|
10
|
+
// selectors at the composition root, not through an effect.
|
|
11
|
+
//
|
|
12
|
+
// Fidelity anchors (read-only reference, NOT modified):
|
|
13
|
+
// • src/trackers/local.ts (LocalMarkdownTracker: scan/normalize/move)
|
|
14
|
+
// • src/http-disk.ts (listIssuesFromDisk / readIssueFromDisk)
|
|
15
|
+
//
|
|
16
|
+
// This file is the thin wiring shell: it binds the live config + log into a
|
|
17
|
+
// ScanCtx and exposes the adapter surface. The stateless halves (front-matter
|
|
18
|
+
// decode, normalize/coerce, YAML render, durable-write/move primitives, disk
|
|
19
|
+
// dashboard reads) and the deps-bound scan/fetch/move/write IO live in siblings.
|
|
20
|
+
import * as scan from './tracker-scan.js';
|
|
21
|
+
import { listIssues, readIssue } from './tracker-disk.js';
|
|
22
|
+
export function createTrackerAdapter(deps) {
|
|
23
|
+
const ctx = {
|
|
24
|
+
config: deps.config,
|
|
25
|
+
log: deps.log,
|
|
26
|
+
resolveMatch: deps.resolveMatch,
|
|
27
|
+
composeIssueFile: deps.composeIssueFile,
|
|
28
|
+
pickNextNumericIdentifier: deps.pickNextNumericIdentifier,
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
loadAllIssues: () => scan.loadAllIssues(ctx),
|
|
32
|
+
moveIssueToState: (id, to, opts) => scan.moveIssueToState(ctx, id, to, opts),
|
|
33
|
+
currentRoot: () => deps.config().root,
|
|
34
|
+
start: () => scan.start(ctx),
|
|
35
|
+
listIssues,
|
|
36
|
+
readIssue,
|
|
37
|
+
writeIssue: (effect) => scan.writeIssue(ctx, effect),
|
|
38
|
+
interpret: (effect) => scan.interpret(ctx, effect),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../../../src/shell/interp/tracker.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,yEAAyE;AACzE,wEAAwE;AACxE,oDAAoD;AACpD,qEAAqE;AACrE,4DAA4D;AAC5D,iFAAiF;AACjF,gFAAgF;AAChF,4DAA4D;AAC5D,EAAE;AACF,wDAAwD;AACxD,wEAAwE;AACxE,gEAAgE;AAChE,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,iFAAiF;AAGjF,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAI1D,MAAM,UAAU,oBAAoB,CAAC,IAAwB;IAC3D,MAAM,GAAG,GAAiB;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,yBAAyB,EAAE,IAAI,CAAC,yBAAyB;KAC1D,CAAC;IACF,OAAO;QACL,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;QAC5C,gBAAgB,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC;QAC5E,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI;QACrC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;QAC5B,UAAU;QACV,SAAS;QACT,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;QACpD,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC;KACnD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// FCIS rewrite — shell adapter: interactive TTY (kind: adapter).
|
|
2
|
+
//
|
|
3
|
+
// Backs the scaffold-on-missing-workflow preflight: read one line of operator
|
|
4
|
+
// input, gate on an interactive terminal, and parse the yes/no accept decision.
|
|
5
|
+
//
|
|
6
|
+
// Fidelity anchors (REF main branch src/bin/symphony.ts, read-only):
|
|
7
|
+
// • promptLine — the readline interface, closed before resolving so the
|
|
8
|
+
// process can exit cleanly afterwards.
|
|
9
|
+
// • maybeScaffoldMissingWorkflow — the `!isTTY` gate and the
|
|
10
|
+
// `'' | 'y' | 'yes'` (trimmed, lower-cased) default-accept parse, kept inline
|
|
11
|
+
// in the shell exactly as the original did.
|
|
12
|
+
//
|
|
13
|
+
// Shell rule: imports from src/types/** ONLY (this adapter needs none).
|
|
14
|
+
import readline from 'node:readline';
|
|
15
|
+
import process from 'node:process';
|
|
16
|
+
/**
|
|
17
|
+
* Read a single line from stdin with the given prompt. The readline interface
|
|
18
|
+
* is closed before resolving so the process can exit cleanly afterwards.
|
|
19
|
+
* Ported byte-for-byte from REF promptLine.
|
|
20
|
+
*/
|
|
21
|
+
export function promptLine(message) {
|
|
22
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
rl.question(message, (answer) => {
|
|
25
|
+
rl.close();
|
|
26
|
+
resolve(answer);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* True only when BOTH stdin and stdout are attached to an interactive terminal.
|
|
32
|
+
* The scaffold offer is suppressed otherwise (cron jobs, CI, container
|
|
33
|
+
* ENTRYPOINTs). Ported from the gate at the top of REF maybeScaffoldMissingWorkflow.
|
|
34
|
+
*/
|
|
35
|
+
export function isInteractive() {
|
|
36
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Prompt the operator with a yes/no question and resolve to the accept decision.
|
|
40
|
+
* Default-accept: bare enter, "y", or "yes" (case-insensitive, trimmed); anything
|
|
41
|
+
* else rejects. Ported from the parse in REF maybeScaffoldMissingWorkflow. Callers
|
|
42
|
+
* gate on {@link isInteractive} first, as the original did.
|
|
43
|
+
*/
|
|
44
|
+
export async function promptYesNo(message) {
|
|
45
|
+
const normalized = (await promptLine(message)).trim().toLowerCase();
|
|
46
|
+
return normalized === '' || normalized === 'y' || normalized === 'yes';
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=tty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tty.js","sourceRoot":"","sources":["../../../../src/shell/interp/tty.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,EAAE;AACF,qEAAqE;AACrE,0EAA0E;AAC1E,2CAA2C;AAC3C,+DAA+D;AAC/D,kFAAkF;AAClF,gDAAgD;AAChD,EAAE;AACF,wEAAwE;AAExE,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;QACrC,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE;YAC9B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAe;IAC/C,MAAM,UAAU,GAAG,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpE,OAAO,UAAU,KAAK,EAAE,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// FCIS rewrite — VM-substrate adapter (kind: adapter) implementing the VmClient
|
|
2
|
+
// port over `@earendil-works/gondolin`.
|
|
3
|
+
//
|
|
4
|
+
// Thin by design: `createVm` → `VM.create` (host mounts become RealFSProvider /
|
|
5
|
+
// ReadonlyProvider VFS entries), the handle's `exec` → `vm.exec` (piped stdio + an
|
|
6
|
+
// AbortController standing in for the port's `kill()`), `close` → `vm.close`, and
|
|
7
|
+
// `listSessions` / `gc` → Gondolin's session registry. A tiny in-process
|
|
8
|
+
// VmProcessRegistry tracks each live VM's backing qemu host pid so a forced exit
|
|
9
|
+
// can SIGKILL them synchronously before `process.exit` (issue 152): VMs are
|
|
10
|
+
// created in-process so each qemu is a DIRECT CHILD of the orchestrator, and the
|
|
11
|
+
// session registry only records OUR pid — `getHostPid()` is the sole reliable
|
|
12
|
+
// handle on the backing process.
|
|
13
|
+
//
|
|
14
|
+
// Shell rule: imports from src/types/** ONLY (plus the real Gondolin SDK). ZERO
|
|
15
|
+
// decisions live here. NodeNext ESM with .js extensions on relative imports.
|
|
16
|
+
import { mkdir } from 'node:fs/promises';
|
|
17
|
+
import { VM, RealFSProvider, ReadonlyProvider, listSessions as gondolinListSessions, gcSessions, } from '@earendil-works/gondolin';
|
|
18
|
+
/**
|
|
19
|
+
* Pure: reduce candidate VM host pids (some unknown/null) to the distinct, safe
|
|
20
|
+
* set to SIGKILL — positive safe integers, never the caller's own pid (which
|
|
21
|
+
* `process.kill` would aim at the orchestrator) and never `<= 0` (which would
|
|
22
|
+
* signal a whole process group).
|
|
23
|
+
*/
|
|
24
|
+
function safeKillTargets(hostPids, selfPid) {
|
|
25
|
+
const out = new Set();
|
|
26
|
+
for (const pid of hostPids) {
|
|
27
|
+
if (pid == null || !Number.isSafeInteger(pid) || pid <= 0 || pid === selfPid)
|
|
28
|
+
continue;
|
|
29
|
+
out.add(pid);
|
|
30
|
+
}
|
|
31
|
+
return [...out];
|
|
32
|
+
}
|
|
33
|
+
class VmProcessRegistry {
|
|
34
|
+
live = new Set();
|
|
35
|
+
kill;
|
|
36
|
+
selfPid;
|
|
37
|
+
constructor(opts = {}) {
|
|
38
|
+
this.kill = opts.kill ?? ((pid, signal) => process.kill(pid, signal));
|
|
39
|
+
this.selfPid = opts.selfPid ?? process.pid;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Register a live VM. Returns a deregister callback to invoke after a clean
|
|
43
|
+
* `close()` so the set stays bounded. Correctness does NOT depend on it: once a
|
|
44
|
+
* VM's runner exits, `getHostPid()` returns null and {@link killAll} skips it.
|
|
45
|
+
*/
|
|
46
|
+
track(vm) {
|
|
47
|
+
this.live.add(vm);
|
|
48
|
+
return () => void this.live.delete(vm);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* SIGKILL the backing host process of every tracked VM that still has a live
|
|
52
|
+
* runner pid. Synchronous and bounded (one signal per tracked VM, no IO), so
|
|
53
|
+
* the forced-shutdown path can call it and `process.exit` immediately. Best
|
|
54
|
+
* effort: ESRCH / EPERM / etc. are swallowed. Returns the count targeted.
|
|
55
|
+
*/
|
|
56
|
+
killAll() {
|
|
57
|
+
const pids = safeKillTargets([...this.live].map((vm) => vm.getHostPid()), this.selfPid);
|
|
58
|
+
for (const pid of pids) {
|
|
59
|
+
try {
|
|
60
|
+
this.kill(pid, 'SIGKILL');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ESRCH (already gone) / EPERM / etc. — best-effort on the way out.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return pids.length;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ─── VM handle (one launched Gondolin VM) ──────────────────────────────────
|
|
70
|
+
class GondolinVmHandle {
|
|
71
|
+
vm;
|
|
72
|
+
onClose;
|
|
73
|
+
/**
|
|
74
|
+
* `onClose` deregisters this VM from the client's process registry after a
|
|
75
|
+
* clean `close()` (qemu already torn down). Defaults to a no-op so the handle
|
|
76
|
+
* can be constructed without a registry in tests.
|
|
77
|
+
*/
|
|
78
|
+
constructor(vm, onClose = () => undefined) {
|
|
79
|
+
this.vm = vm;
|
|
80
|
+
this.onClose = onClose;
|
|
81
|
+
}
|
|
82
|
+
get id() {
|
|
83
|
+
return this.vm.id;
|
|
84
|
+
}
|
|
85
|
+
exec(opts) {
|
|
86
|
+
// Gondolin has no `proc.kill()`; aborting the signal passed to `exec` is the
|
|
87
|
+
// teardown path, so the handle owns the controller.
|
|
88
|
+
const ac = new AbortController();
|
|
89
|
+
const proc = this.vm.exec(opts.command, {
|
|
90
|
+
cwd: opts.workdir ?? undefined,
|
|
91
|
+
env: opts.env,
|
|
92
|
+
stdin: true,
|
|
93
|
+
stdout: 'pipe',
|
|
94
|
+
stderr: 'pipe',
|
|
95
|
+
signal: ac.signal,
|
|
96
|
+
});
|
|
97
|
+
const { stdout, stderr } = proc;
|
|
98
|
+
if (!stdout || !stderr) {
|
|
99
|
+
throw new Error('gondolin exec did not expose piped stdout/stderr');
|
|
100
|
+
}
|
|
101
|
+
let killed = false;
|
|
102
|
+
return {
|
|
103
|
+
stdin: { write: (chunk) => proc.write(chunk), end: () => proc.end() },
|
|
104
|
+
stdout,
|
|
105
|
+
stderr,
|
|
106
|
+
pid: this.vm.getHostPid() ?? undefined,
|
|
107
|
+
exit: proc.result.then((r) => ({ code: r.exitCode, signal: r.signal ?? null }), (err) => {
|
|
108
|
+
// An intentional kill() aborts the AbortController, which makes Gondolin reject
|
|
109
|
+
// the exec result with Error("exec aborted"). That abort IS the teardown signal,
|
|
110
|
+
// not a failure — resolve it to a killed sentinel. Otherwise a caller that does
|
|
111
|
+
// not observe `exit` (the launch agent's exec is a bare process tether: its
|
|
112
|
+
// lifecycle rides the /acp WebSocket, nothing awaits `exit`) would turn the
|
|
113
|
+
// teardown abort into an unhandled rejection that crashes the orchestrator.
|
|
114
|
+
// Genuine (non-kill) errors still reject so observing callers see them.
|
|
115
|
+
if (killed)
|
|
116
|
+
return { code: null, signal: null };
|
|
117
|
+
throw err;
|
|
118
|
+
}),
|
|
119
|
+
kill: () => {
|
|
120
|
+
if (killed)
|
|
121
|
+
return;
|
|
122
|
+
killed = true;
|
|
123
|
+
try {
|
|
124
|
+
ac.abort();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// idempotent — already aborted / process gone
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async close() {
|
|
133
|
+
// Deregister only after a clean close. If `close()` throws, the VM stays
|
|
134
|
+
// tracked so the force-quit backstop (`killAllVms`) can still SIGKILL its
|
|
135
|
+
// backing qemu — a teardown that failed must not leave an orphan.
|
|
136
|
+
await this.vm.close();
|
|
137
|
+
this.onClose();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ─── VM client (the VmClient port) ──────────────────────────────────────────
|
|
141
|
+
export class GondolinVmClient {
|
|
142
|
+
procRegistry;
|
|
143
|
+
constructor(opts = {}) {
|
|
144
|
+
this.procRegistry = new VmProcessRegistry(opts);
|
|
145
|
+
}
|
|
146
|
+
async createVm(opts) {
|
|
147
|
+
const mounts = {};
|
|
148
|
+
for (const m of opts.mounts) {
|
|
149
|
+
// Provision symphony-managed RW mount hosts before mounting (issue 209): the mise
|
|
150
|
+
// download-cache dir may not exist on a fresh host. Idempotent; only flagged
|
|
151
|
+
// mounts (never the workspace / user volumes) are created.
|
|
152
|
+
if (m.ensureHostDir)
|
|
153
|
+
await mkdir(m.host, { recursive: true });
|
|
154
|
+
const provider = new RealFSProvider(m.host);
|
|
155
|
+
mounts[m.guest] = m.readonly ? new ReadonlyProvider(provider) : provider;
|
|
156
|
+
}
|
|
157
|
+
// The port keeps httpHooks/tcp/dns opaque (`unknown`) to avoid leaking
|
|
158
|
+
// Gondolin infra types upward; the composition root produces the real shapes
|
|
159
|
+
// (createHttpHooks / tcp.hosts tunnel) and passes them straight through. The
|
|
160
|
+
// cast at this single-adapter seam is deliberate and cheap. Per-guest file
|
|
161
|
+
// staging (creds + launcher) is the dispatcher's job over `exec` AFTER create,
|
|
162
|
+
// not the createVm port surface — matching the reference gondolin.ts adapter.
|
|
163
|
+
const options = {
|
|
164
|
+
sandbox: { imagePath: opts.imagePath },
|
|
165
|
+
cpus: opts.cpus,
|
|
166
|
+
memory: `${opts.memMib}M`,
|
|
167
|
+
// Grow the guest rootfs so the ephemeral mise toolchain install fits (issue 222);
|
|
168
|
+
// qemu size syntax (e.g. "3G"). Omitted ⇒ the substrate's default rootfs size.
|
|
169
|
+
...(opts.rootfsSize ? { rootfs: { size: opts.rootfsSize } } : {}),
|
|
170
|
+
env: opts.env,
|
|
171
|
+
vfs: { mounts },
|
|
172
|
+
httpHooks: opts.httpHooks,
|
|
173
|
+
tcp: opts.tcp,
|
|
174
|
+
dns: opts.dns,
|
|
175
|
+
allowWebSockets: opts.allowWebSockets,
|
|
176
|
+
sessionLabel: opts.sessionLabel,
|
|
177
|
+
};
|
|
178
|
+
const vm = await VM.create(options);
|
|
179
|
+
return new GondolinVmHandle(vm, this.procRegistry.track(vm));
|
|
180
|
+
}
|
|
181
|
+
/** @see VmClient.killAllVms — synchronous, best-effort qemu reap on forced exit. */
|
|
182
|
+
killAllVms() {
|
|
183
|
+
return this.procRegistry.killAll();
|
|
184
|
+
}
|
|
185
|
+
async listSessions() {
|
|
186
|
+
const sessions = await gondolinListSessions();
|
|
187
|
+
return sessions.map((s) => ({
|
|
188
|
+
id: s.id,
|
|
189
|
+
pid: s.pid,
|
|
190
|
+
label: s.label,
|
|
191
|
+
alive: s.alive,
|
|
192
|
+
createdAt: s.createdAt,
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
async gc() {
|
|
196
|
+
return gcSessions();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=vm.js.map
|