You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Mentioning pacquet on the pnpm website and/or in the readme.
Stage 1 — Headless installer
Make pacquet install --frozen-lockfile feature-complete with pnpm install --frozen-lockfile.
Pacquet does not resolve dependencies in this stage. The user runs pnpm install (or pnpm install --lockfile-only) to produce pnpm-lock.yaml, then pacquet install --frozen-lockfile materializes node_modules from it. The lockfile is the contract between the two tools.
This mirrors pnpm's internal @pnpm/headless boundary, so the seam is natural.
Frozen-lockfile freshness check (Update ts-node to version 1.6.1 🚀 #447). Error when package.json dependencies don't match the lockfile importer entries, mirroring upstream's allProjectsAreUpToDate. Today pacquet installs regardless of staleness, which silently masks lockfile/manifest drift in CI.
Tier 2 — Real-world install enablers. Each unlocks a large slice of real pnpm repos and is largely independent of the others.
Tier 3 — New resolution types and layout modes. Each is a self-contained extension to the install pipeline; the ordering inside this tier is mostly arbitrary.
.modules.yaml validation triggering re-install on layout change. Upstream's validateModules forces a full re-import when nodeLinker / hoistPattern / publicHoistPattern / etc. differ between runs. Implemented as modules_consistent_with in crates/package-manager/src/install.rs, which gates the frozen-lockfile no-op fast path on layoutVersion / nodeLinker / hoistPattern / publicHoistPattern / virtualStoreDirMaxLength / storeDir / virtualStoreDir / included matching.
Integration milestone (between Stage 1 and Stage 2)
Once the headless installer is solid, integrate pacquet as an opt-in install backend for pnpm itself — for example via an install-backend=pacquet setting, or a Node N-API addon hooked at the @pnpm/headless seam. This ships Stage 1 value to every pnpm user, not just those who install pacquet directly, and provides a feedback firehose for parity validation before Stage 2 is built.
Stage 2 — Implementing resolution
Make pacquet install feature-complete with pnpm install (no --frozen-lockfile). Pacquet reads package.json + pnpm-workspace.yaml, resolves the dependency graph, writes a pnpm-lock.yaml that round-trips byte-identically with pnpm's, and materializes node_modules. This unlocks the install subcommands that today only work via pnpm: install (default), install --lockfile-only, add, update, remove, outdated, why.
Pacquet already has scaffolding from Stage 1: crates/registry, crates/resolving-npm-resolver (metadata fetch + cache, named registries, mirroring, trust checks), crates/resolving-resolver-base, and a full lockfile writer (crates/lockfile/save_lockfile). Stage 2 fills in the parts that turn a manifest into a resolved graph.
TODO
Ordered by implementation dependency — items lower in the list generally build on items above.
Tier 1 — Foundations. Nothing downstream is sound until these land.
Resolution graph & recursion engine. Port installing/deps-resolver/src/resolveDependencyTree.ts and resolveDependencies.ts (~2.3k LoC combined). Owns dedupe by name (pkgIdsByName), preferredVersions, direct-vs-transitive context, resolutionsByDepPath cache, pendingNodes, linked-workspace tracking. Heart of this stage. Landed in crates/resolving-deps-resolver (resolve_workspace / resolve_importer / resolve_dependency_tree).
Verify lockfile writer parity for newly-resolved entries. Pacquet's save_lockfile round-trips Stage 1 inputs; confirm it serializes new packages / snapshots / importers / catalogs / overrides / patchedDependencies byte-identically with pnpm so pnpm install after pacquet install produces no diff. crates/lockfile/save_lockfile now writes all of these sections from a fresh resolution (env-document prefix preserved).
Tier 2 — Per-protocol resolvers. Each unlocks a class of spec. Largely independent.
npm resolver — version picking. Port pickPackage.ts / pickPackageFromMeta.ts: semver matching, tag handling, allowedDeprecatedVersions, minimumReleaseAge. Metadata fetch already exists in resolving-npm-resolver.
Git resolver. Port resolving/git-resolver: git+ssh, github:owner/repo#ref, #semver:^1 selectors, GitHub-tarball preference. Pairs with the Stage-1 git-fetcher crate.
Tarball resolver. Port resolving/tarball-resolver: direct https://…/foo.tgz resolution + integrity verify.
Local-path / link / file resolver. Port resolving/local-resolver: file:../foo, link:../foo, injected deps. The Stage-1 directory-fetcher crate handles the fetch side.
JSR specifier parser. Port resolving/jsr-specifier-parser; wires into the JSR path already drafted in resolving-npm-resolver.
Runtime resolver. Pick versions for node@runtime: / deno@runtime: / bun@runtime:. Stage 1 already loads them from the lockfile; resolution still has to choose a version and emit the entry.
Catalog protocol. Resolve catalog:default / catalog:react18 through pnpm-workspace.yaml's catalog(s):, then dispatch to npm. Snapshot the selections into the lockfile's catalogs: block.
Overrides. pnpm.overrides from pnpm-workspace.yaml. Apply at parseWantedDependency time; record in the lockfile's overrides: block.
allowedDeprecatedVersions + deprecation warning emission. crates/resolving-deps-resolver now emits pnpm:deprecation events (emit_deprecation_if_needed / DeprecationLogFn), @pnpm/cli.default-reporter formats them (on_deprecation), and the parsed allowedDeprecatedVersions map suppresses matched versions.
blockExoticSubdeps. Reject git/tarball/file specs in transitive deps.
patchedDependencies resolution-side. Hash the patch and attach pkgIdWithPatchHash to dep paths so the installer picks the patched store entry. crates/lockfile/pkg_id_with_patch_hash.rs already exists.
allowNonAppliedPatches + verifyPatches. verify_patches is now invoked from the fresh-lockfile install flow (crates/package-manager/src/install_with_fresh_lockfile.rs), gated on config.allow_unused_patches (the current allowUnusedPatches setting; upstream dropped the allowNonAppliedPatches name), and emits the ERR_PNPM_UNUSED_PATCH diagnostic.
minimumReleaseAge at resolve time. Today it gates global installs only; extend it so new transitive versions are also rejected, mirroring upstream's resolve-path enforcement.
Tier 4 — Hooks & custom resolution. Both require running user JavaScript inside pacquet — design call needed.
.pnpmfile.cjs / pnpmfile.cjs execution. Port the readPackage, afterAllResolved, preResolution, filterLog hook surface from hooks/pnpmfile. Implemented in crates/hooks by shelling out to node via a worker-pool runtime (node_runtime.rs); readPackage / preResolution / afterAllResolved / updateConfig are wired into the resolution pipeline. (filterLog trait method exists but is not yet consumed by the reporter.)
Custom resolvers (hooks.types.CustomResolver). Extracted from the pnpmfile and prepended to the resolver chain via CustomResolverAdapter; covered by crates/cli/tests/custom_resolvers.rs.
Tier 5 — Commands unlocked by resolution. Stage 1 only delivers pacquet install --frozen-lockfile. Once resolution lands, each of these is a CLI surface port that follows the underlying capability.
Tier 6 — Consistency, polish, auxiliary. Close the parity gap once the structural work is in place.
Round-trip stability. A re-resolve that picks the same versions must produce a byte-identical pnpm-lock.yaml (key ordering, anchor reuse, no spurious churn).
E2E port. Mirror pnpm/test/install.ts, add.ts, update.ts, remove.ts, outdated.ts into pacquet's test layout as each capability lands. Each command now has a crates/cli/tests/*.rs suite (install/add/update/remove/outdated/why + ~30 more); ongoing parity backfill tracked in pacquet/plans/TEST_PORTING.md.
Integration milestone (between Stage 2 and Stage 3)
Once resolution is solid, extend the Stage-1 → Stage-2 opt-in seam (install-backend=pacquet / N-API) to route everyday pnpm install calls through pacquet end-to-end — not just --frozen-lockfile. Stage 3 then targets the non-install command surface (publish, audit, dlx, exec, run, store).
Stage 3 — Non-install command surface
Stages 1–2 cover the dependency-management surface: install (incl. --frozen-lockfile / --lockfile-only), add, update, remove, outdated, why. Stage 3 tracks every remaining pnpm command. Pacquet's crates/cli already implements a handful of these beyond install; the rest are unported.
Status legend: [x] = a handler exists in pacquet's CLI today (a parity audit against pnpm's flags/output is still required before it can be called complete); [ ] = not ported.
Tracking
Grouped by the pnpm package that owns each command (see the imports at the top of pnpm/src/cmd/index.ts). Aliases are listed after the primary name.
Script & process — @pnpm/exec.commands
run / run-script (incl. test, start script aliases)
exec
dlx
create taken
restart (+ stop)
Store & cache — @pnpm/store.commands, @pnpm/cache.commands
Not tracked: access, edit, issues, prefix, profile, team, token, xmas — these are stubs in pnpm itself (they throw NOT_IMPLEMENTED and defer to the npm CLI), so there is nothing to port.
Stage 3 tracking section drafted by an agent (Claude Code, claude-opus-4-8).
Stage 2 plan drafted by an agent (Claude Code, claude-opus-4-7).
Stage 0
Stage 1 — Headless installer
Make
pacquet install --frozen-lockfilefeature-complete withpnpm install --frozen-lockfile.Pacquet does not resolve dependencies in this stage. The user runs
pnpm install(orpnpm install --lockfile-only) to producepnpm-lock.yaml, thenpacquet install --frozen-lockfilematerializesnode_modulesfrom it. The lockfile is the contract between the two tools.This mirrors pnpm's internal
@pnpm/headlessboundary, so the seam is natural.Completed
.modules.yamlwrite and verify pacquet#331 (.modules.yamlwrite and verify)@pnpm/core-loggersschema (Run post install scripts after all dependencies were installed #345). Visual parity via@pnpm/cli.default-reporteris tracked under Tier 4 below (EACCESS error on runas #344).TODO
Ordered by implementation dependency — items lower in the list generally build on items above.
Tier 1 — Foundations. Correct snapshot filtering and lockfile loading; nothing downstream is sound until these are in place.
optionalDependenciessupport (umbrella). Without correctos/cpu/libcfiltering, every install over-fetches and may break on platforms other than the build host. Subsumes Install respects every snapshot — no os/cpu/libc filter for optional deps pacquet#266.crates/lockfile/src/resolution.rsuses#[serde(deny_unknown_fields)]and is missingTarballResolution.gitHosted,BinaryResolution, andVariationsResolution. A real v11pnpm-lock.yamlcontaining a github-tarball, a git-hosted package, or a runtime entry fails serde before the install dispatcher runs. Types covered piecemeal in Install git-hosted packages frompnpm-lock.yaml(frozen-lockfile) pacquet#436 and Install runtime dependencies (node@runtime:,deno@runtime:,bun@runtime:) frompnpm-lock.yamlpacquet#437; land them as a single unblocker so loading is correct before the install logic for each shape ships.package.jsondependencies don't match the lockfile importer entries, mirroring upstream'sallProjectsAreUpToDate. Today pacquet installs regardless of staleness, which silently masks lockfile/manifest drift in CI.Tier 2 — Real-world install enablers. Each unlocks a large slice of real pnpm repos and is largely independent of the others.
pacquet install --frozen-lockfilepacquet#431 — Support workspaces. Most real pnpm repos are workspaces; until this lands, pacquet can't install them at all.--frozen-lockfile: read+writenode_modules/.pnpm/lock.yamland skip unchanged snapshots pacquet#433 — Partial install with--frozen-lockfile: read+writenode_modules/.pnpm/lock.yamland skip unchanged snapshots. Closes the warm-install perf gap.hoistPatternandpublicHoistPatternpacquet#435 — Hoisting (hoistPattern,publicHoistPattern). Needed for ecosystem packages that rely on phantom dependencies and for editor tooling (eslint/prettier).Tier 3 — New resolution types and layout modes. Each is a self-contained extension to the install pipeline; the ordering inside this tier is mostly arbitrary.
pacquet install --frozen-lockfilepacquet#432 — Support the global virtual store dir (Stage 1: frozen-lockfile install + per-importer registry). Follow-ups: Implementpacquet store prunefor the global virtual store (#432 follow-up) pacquet#458 (prune sweep), Engine-agnostic GVS hash gating viaallowBuilds-drivenbuilt_dep_paths(#432 follow-up) pacquet#459 (engine-agnostic hash gating); CI auto-detect / CONFIG_CONFLICT validation / e2eglobalVirtualStore.tsport remain as bullets on Add global virtual store support topacquet install --frozen-lockfilepacquet#432.pnpm-lock.yaml(frozen-lockfile) pacquet#436 — Install git-hosted packages frompnpm-lock.yaml(frozen-lockfile).node@runtime:,deno@runtime:,bun@runtime:) frompnpm-lock.yamlpacquet#437 — Install runtime dependencies (node@runtime:,deno@runtime:,bun@runtime:).nodeLinker: 'hoisted'(umbrella). Alternative installer; orthogonal to Add hoisting support:hoistPatternandpublicHoistPatternpacquet#435. Landed via thereal-hoistcrate +link_hoisted_modules.rs; popularity-based ident preference completed in feat(pacquet): cover the gap innodeLinker: 'hoisted'#12510.Tier 4 — Consistency, polish, auxiliary. Quality-of-life fixes that close the parity gap once the structural work is in place.
.pnpm/that no longer appear in the lockfile (upstream'spruneVirtualStore). Sister to Partial install with--frozen-lockfile: read+writenode_modules/.pnpm/lock.yamland skip unchanged snapshots pacquet#433 — partial install decides what to add; this decides what to remove. Implemented as the mark-and-sweepStoreDir::pruneincrates/store-dir/src/prune.rs(reporter wiring still tracked under Use pnpm's @pnpm/cli.default-reporter for terminal output (via NDJSON) pacquet#344)..modules.yamlvalidation triggering re-install on layout change. Upstream'svalidateModulesforces a full re-import whennodeLinker/hoistPattern/publicHoistPattern/ etc. differ between runs. Implemented asmodules_consistent_withincrates/package-manager/src/install.rs, which gates the frozen-lockfile no-op fast path onlayoutVersion/nodeLinker/hoistPattern/publicHoistPattern/virtualStoreDirMaxLength/storeDir/virtualStoreDir/includedmatching.@pnpm/cli.default-reporterfor terminal output (via NDJSON). Brings visual parity withpnpm installon top of the reporting engine landed in feat(reporter): implement the reporting engine (NDJSON + Reporter trait) pacquet#345.Integration milestone (between Stage 1 and Stage 2)
Once the headless installer is solid, integrate pacquet as an opt-in install backend for pnpm itself — for example via an
install-backend=pacquetsetting, or a Node N-API addon hooked at the@pnpm/headlessseam. This ships Stage 1 value to every pnpm user, not just those who install pacquet directly, and provides a feedback firehose for parity validation before Stage 2 is built.Stage 2 — Implementing resolution
Make
pacquet installfeature-complete withpnpm install(no--frozen-lockfile). Pacquet readspackage.json+pnpm-workspace.yaml, resolves the dependency graph, writes apnpm-lock.yamlthat round-trips byte-identically with pnpm's, and materializesnode_modules. This unlocks the install subcommands that today only work via pnpm:install(default),install --lockfile-only,add,update,remove,outdated,why.Pacquet already has scaffolding from Stage 1:
crates/registry,crates/resolving-npm-resolver(metadata fetch + cache, named registries, mirroring, trust checks),crates/resolving-resolver-base, and a full lockfile writer (crates/lockfile/save_lockfile). Stage 2 fills in the parts that turn a manifest into a resolved graph.TODO
Ordered by implementation dependency — items lower in the list generally build on items above.
Tier 1 — Foundations. Nothing downstream is sound until these land.
@pnpm/resolving.parse-wanted-dependency(npm aliaspnpm:foo@npm:bar, scoped names, tag vs. semver) and the protocol-routing dispatcher at the top ofresolving/default-resolver/src/index.ts. Slots into the existingresolving-resolver-basecrate.installing/deps-resolver/src/resolveDependencyTree.tsandresolveDependencies.ts(~2.3k LoC combined). Owns dedupe by name (pkgIdsByName),preferredVersions, direct-vs-transitive context,resolutionsByDepPathcache,pendingNodes, linked-workspace tracking. Heart of this stage. Landed incrates/resolving-deps-resolver(resolve_workspace/resolve_importer/resolve_dependency_tree).save_lockfileround-trips Stage 1 inputs; confirm it serializes newpackages/snapshots/importers/catalogs/overrides/patchedDependenciesbyte-identically with pnpm sopnpm installafterpacquet installproduces no diff.crates/lockfile/save_lockfilenow writes all of these sections from a fresh resolution (env-document prefix preserved).Tier 2 — Per-protocol resolvers. Each unlocks a class of spec. Largely independent.
pickPackage.ts/pickPackageFromMeta.ts: semver matching, tag handling,allowedDeprecatedVersions,minimumReleaseAge. Metadata fetch already exists inresolving-npm-resolver.resolving/git-resolver:git+ssh,github:owner/repo#ref,#semver:^1selectors, GitHub-tarball preference. Pairs with the Stage-1 git-fetcher crate.resolving/tarball-resolver: directhttps://…/foo.tgzresolution + integrity verify.resolving/local-resolver:file:../foo,link:../foo, injected deps. The Stage-1directory-fetchercrate handles the fetch side.resolving/jsr-specifier-parser; wires into the JSR path already drafted inresolving-npm-resolver.node@runtime:/deno@runtime:/bun@runtime:. Stage 1 already loads them from the lockfile; resolution still has to choose a version and emit the entry.catalog:default/catalog:react18throughpnpm-workspace.yaml'scatalog(s):, then dispatch to npm. Snapshot the selections into the lockfile'scatalogs:block.workspace:*/workspace:^/workspace:~/workspace:1.2.3→link:lockfile entry + publish-time spec rewrite. Pacquet already loads workspaces (Add workspace support topacquet install --frozen-lockfilepacquet#431).Tier 3 — Resolution policies & constraints. Behaviors layered on top of the recursion that change which version wins or whether the install proceeds.
resolvePeers.ts(~1k LoC). Produces dep paths with peer hashes ([email protected]([email protected])). Heaviest single port in this stage.pnpm.overridesfrompnpm-workspace.yaml. Apply atparseWantedDependencytime; record in the lockfile'soverrides:block.allowedDeprecatedVersions+ deprecation warning emission.crates/resolving-deps-resolvernow emitspnpm:deprecationevents (emit_deprecation_if_needed/DeprecationLogFn),@pnpm/cli.default-reporterformats them (on_deprecation), and the parsedallowedDeprecatedVersionsmap suppresses matched versions.blockExoticSubdeps. Reject git/tarball/file specs in transitive deps.patchedDependenciesresolution-side. Hash the patch and attachpkgIdWithPatchHashto dep paths so the installer picks the patched store entry.crates/lockfile/pkg_id_with_patch_hash.rsalready exists.allowNonAppliedPatches+verifyPatches.verify_patchesis now invoked from the fresh-lockfile install flow (crates/package-manager/src/install_with_fresh_lockfile.rs), gated onconfig.allow_unused_patches(the currentallowUnusedPatchessetting; upstream dropped theallowNonAppliedPatchesname), and emits theERR_PNPM_UNUSED_PATCHdiagnostic.minimumReleaseAgeat resolve time. Today it gates global installs only; extend it so new transitive versions are also rejected, mirroring upstream's resolve-path enforcement.Tier 4 — Hooks & custom resolution. Both require running user JavaScript inside pacquet — design call needed.
.pnpmfile.cjs/pnpmfile.cjsexecution. Port thereadPackage,afterAllResolved,preResolution,filterLoghook surface fromhooks/pnpmfile. Implemented incrates/hooksby shelling out tonodevia a worker-pool runtime (node_runtime.rs);readPackage/preResolution/afterAllResolved/updateConfigare wired into the resolution pipeline. (filterLogtrait method exists but is not yet consumed by the reporter.)hooks.types.CustomResolver). Extracted from the pnpmfile and prepended to the resolver chain viaCustomResolverAdapter; covered bycrates/cli/tests/custom_resolvers.rs.Tier 5 — Commands unlocked by resolution. Stage 1 only delivers
pacquet install --frozen-lockfile. Once resolution lands, each of these is a CLI surface port that follows the underlying capability.pacquet install(default, non-frozen).pacquet install --lockfile-only.pacquet add(incl.-D,--save-peer,--save-exact,--save-prefix).pacquet update(incl.--latest,updateMatching,-i).pacquet remove.pacquet outdated.pacquet why.Tier 6 — Consistency, polish, auxiliary. Close the parity gap once the structural work is in place.
pnpm-lock.yaml(key ordering, anchor reuse, no spurious churn).add/remove/update. PortupdateProjectManifest.ts: respectsave-prefix,save-exact,save-workspace-protocol.PackageManifestadd/remove/update +crates/workspace-manifest-writer(format-preserving catalog writes) handle this.afterAllResolved/preResolutionlog-event parity so@pnpm/cli.default-reporterformats them identically.readPackage/afterAllResolvedcontext.log()calls emitpnpm:hookevents, andpreResolution'sinfo/warnlogging now routes throughpnpm:hooktoo (pre_resolution_log_fn→HookLog { from: "pnpmfile", hook: "preResolution" }), matching pnpm'shookLoggerpayload.pnpm/test/install.ts,add.ts,update.ts,remove.ts,outdated.tsinto pacquet's test layout as each capability lands. Each command now has acrates/cli/tests/*.rssuite (install/add/update/remove/outdated/why + ~30 more); ongoing parity backfill tracked inpacquet/plans/TEST_PORTING.md.Integration milestone (between Stage 2 and Stage 3)
Once resolution is solid, extend the Stage-1 → Stage-2 opt-in seam (
install-backend=pacquet/ N-API) to route everydaypnpm installcalls through pacquet end-to-end — not just--frozen-lockfile. Stage 3 then targets the non-install command surface (publish,audit,dlx,exec,run,store).Stage 3 — Non-install command surface
Stages 1–2 cover the dependency-management surface:
install(incl.--frozen-lockfile/--lockfile-only),add,update,remove,outdated,why. Stage 3 tracks every remaining pnpm command. Pacquet'scrates/clialready implements a handful of these beyond install; the rest are unported.Status legend:
[x]= a handler exists in pacquet's CLI today (a parity audit against pnpm's flags/output is still required before it can be called complete);[ ]= not ported.Tracking
Grouped by the pnpm package that owns each command (see the imports at the top of
pnpm/src/cmd/index.ts). Aliases are listed after the primary name.Script & process —
@pnpm/exec.commandsrun/run-script(incl.test,startscript aliases)execdlxcreatetakenrestart(+stop)Store & cache —
@pnpm/store.commands,@pnpm/cache.commandsstore(+ subcommands)cat-filecat-indexfind-hashcacheProject scaffolding —
@pnpm/workspace.commandsinitWorkspace install helpers —
@pnpm/installing.commandsimportdedupeprunefetchlink/lnunlink/dislinkInspection & query —
@pnpm/deps.inspection.commandslist/lsll/lapeersview/info/show/v@Safortbugsrepodocs/homeCompliance —
@pnpm/deps.compliance.commandsaudit@zkochanlicensessbomBuild / lifecycle —
@pnpm/building.commandsrebuild/rb@zkochanapprove-builds@zkochanignored-builds@zkochanPatching —
@pnpm/patching.commands(taken by @spencer17x)patchpatch-commitpatch-removePublishing & releasing —
@pnpm/releasing.commandspublish(taken by @KSXGitHub)pack(taken by @KSXGitHub)pack-app@zkochandeploy@zkochanstage@zkochanversion(taken by @Safort)Registry access —
@pnpm/registry-access.commands,@pnpm/auth.commandslogin/adduser(taken by @KSXGitHub)logout(taken by @KSXGitHub)whoami(taken by @Safort)ping@Safortsearch/s/se/finddeprecateundeprecateunpublishdist-tag/dist-tags@zkochanowner/ownersstarstarsunstarConfig & manifest —
@pnpm/config.commands,@pnpm/pkg-manifest.commandsconfig/c(+get,set) @zkochanpkgset-script/ss(taken by @Safort)Engine & self-management —
@pnpm/engine.pm.commands,@pnpm/engine.runtime.commandsself-update@zkochansetup@zkochanwith@zkochanruntime/rt@zkochanCompletion —
@pnpm/cli.commandscompletion@spencer17xpnpm CLI built-ins —
pnpm/src/cmdbin(taken by @exidniy)root(taken by @exidniy)clean/purgeci/clean-install/ic/install-cleaninstall-test/itrecursive/multi/mNot tracked:
access,edit,issues,prefix,profile,team,token,xmas— these are stubs in pnpm itself (they throwNOT_IMPLEMENTEDand defer to the npm CLI), so there is nothing to port.Stage 3 tracking section drafted by an agent (Claude Code, claude-opus-4-8).
Stage 2 plan drafted by an agent (Claude Code, claude-opus-4-7).