Context
Split out of #9537, which delivered every other item on its list. buildPlanDag / validatePlanDag / nextReadySteps and the step state machine exist twice:
src/services/plan-dag.ts — the Worker's copy, fully typed.
packages/loopover-mcp/bin/loopover-mcp.ts:287-340 — a hand-duplicated, untyped copy, whose own comment explains why: "this file resolves @loopover/engine through the published package, whose export map does not surface it."
They are the same algorithm, and a fix to one silently misses the other. packages/loopover-miner/lib/plan-store.ts holds a third partial view (the persisted step statuses), now at least sharing one vocabulary after #9537 corrected PLAN_STEP_STATUSES.
Why it did not land in #9537
The correct shared home is @loopover/engine — this is pure, deterministic, dependency-free logic, which is exactly the engine's charter, and the contract package cannot take it (zod-only leaf). But packages/loopover-mcp depends on @loopover/engine at ^3.15.2 and resolves it from the registry when the CLI is installed. Adding an export therefore needs the engine published before a @loopover/mcp release can import it, and a merge that lands both at once leaves a window where a fresh npm i -g @loopover/mcp resolves an engine without the export and crashes at startup.
That release ordering is the whole problem, and it deserves to be got right on its own rather than as a footnote to a 102-tool migration.
Requirements
- Move the implementation to
packages/loopover-engine/src/services/plan-dag.ts and export it from the engine's root entry, preserving behavior exactly (src/services/plan-dag.ts's own tests must pass unchanged against the moved module).
src/services/plan-dag.ts becomes a re-export. No second implementation remains in the repo.
- Bump
@loopover/engine's version and packages/loopover-miner/expected-engine.version together, and widen @loopover/mcp's dependency range to the new minimum, in the SAME PR — with the release-ordering constraint stated in the PR body and confirmed against test:mcp-pack, which packs the CLI and must not resolve an engine lacking the export.
- Delete the untyped copy in
packages/loopover-mcp/bin/loopover-mcp.ts (buildPlanDag, validatePlanDag, nextReadySteps, isPlanStepDone, mapPlanStep, planView's helpers) and import from @loopover/engine; the three plan tools' behavior is pinned by the existing mcp-cli-* suites plus test/unit/plan-templates.test.ts and test/unit/issue-plan-decomposition.test.ts.
- Full branch-counted patch coverage on anything that moves, per the house bar.
Non-goals
- Changing the plan-DAG algorithm, the tools' wire shapes, or the persisted plan-store schema. This is a relocation.
Deliverables
Expected outcome
grep -rn "function buildPlanDag" returns exactly one hit, and the stdio server's plan tools run the same code the Worker does.
References
Split from #9537 (requirement 6). The published-export constraint is the same one recorded in #6153.
Context
Split out of #9537, which delivered every other item on its list.
buildPlanDag/validatePlanDag/nextReadyStepsand the step state machine exist twice:src/services/plan-dag.ts— the Worker's copy, fully typed.packages/loopover-mcp/bin/loopover-mcp.ts:287-340— a hand-duplicated, untyped copy, whose own comment explains why: "this file resolves@loopover/enginethrough the published package, whose export map does not surface it."They are the same algorithm, and a fix to one silently misses the other.
packages/loopover-miner/lib/plan-store.tsholds a third partial view (the persisted step statuses), now at least sharing one vocabulary after #9537 correctedPLAN_STEP_STATUSES.Why it did not land in #9537
The correct shared home is
@loopover/engine— this is pure, deterministic, dependency-free logic, which is exactly the engine's charter, and the contract package cannot take it (zod-only leaf). Butpackages/loopover-mcpdepends on@loopover/engineat^3.15.2and resolves it from the registry when the CLI is installed. Adding an export therefore needs the engine published before a@loopover/mcprelease can import it, and a merge that lands both at once leaves a window where a freshnpm i -g @loopover/mcpresolves an engine without the export and crashes at startup.That release ordering is the whole problem, and it deserves to be got right on its own rather than as a footnote to a 102-tool migration.
Requirements
packages/loopover-engine/src/services/plan-dag.tsand export it from the engine's root entry, preserving behavior exactly (src/services/plan-dag.ts's own tests must pass unchanged against the moved module).src/services/plan-dag.tsbecomes a re-export. No second implementation remains in the repo.@loopover/engine's version andpackages/loopover-miner/expected-engine.versiontogether, and widen@loopover/mcp's dependency range to the new minimum, in the SAME PR — with the release-ordering constraint stated in the PR body and confirmed againsttest:mcp-pack, which packs the CLI and must not resolve an engine lacking the export.packages/loopover-mcp/bin/loopover-mcp.ts(buildPlanDag,validatePlanDag,nextReadySteps,isPlanStepDone,mapPlanStep,planView's helpers) and import from@loopover/engine; the three plan tools' behavior is pinned by the existingmcp-cli-*suites plustest/unit/plan-templates.test.tsandtest/unit/issue-plan-decomposition.test.ts.Non-goals
Deliverables
@loopover/engine, re-exported bysrc/services/plan-dag.tspackages/loopover-mcpduplicate deleted and importing from the engine@loopover/mcpdependency range updated together, release ordering statedtest:mcp-packgreen against the packed CLIExpected outcome
grep -rn "function buildPlanDag"returns exactly one hit, and the stdio server's plan tools run the same code the Worker does.References
Split from #9537 (requirement 6). The published-export constraint is the same one recorded in #6153.