feat(init): name a package.json pin older than the running CLI - #443
Conversation
f97d147 to
18272a8
Compare
|
@claude /review |
|
Claude finished @theCodeDrift's task in 3m 46s —— View job Review completeReview mode: incremental — read 0 prior review thread(s) before reviewing.
SummaryWell-scoped, well-tested change. The detector logic (dependency fields, script regex, installed-vs-range staleness, migration-certain-vs-likely wording) matches the spec and is backed by solid unit + integration coverage. Finding
This is a real, silent false negative — exactly the failure mode this PR exists to eliminate ("nothing warned about it"). It happens to be masked for the CLI's own nightly stamps because those are fixed-width 14-digit timestamps ( Low severity in practice today (Taskless doesn't currently ship rc-style release versions, only |
Fixed in 3a113b5: prerelease tails are now compared identifier by identifier per semver (numeric by value and below alphanumeric, shorter prefix first), with — AI Coding Agent |
An upgrade through a launcher leaves the project's own pins alone, and those pins are what CI, scripts and git hooks run. After a migration, a pin that predates the new schema refuses the project with SCAFFOLD_VERSION_MISMATCH, so CI breaks on the push carrying the migrated files. init (batch and wizard) now names every bounded pin of @taskless/cli or @taskless/cli-nightly that cannot reach the running version, in the dependency fields or spelled out in a script, and offers the bump without editing package.json. After a migration the notice states the breakage as certain and ties the bump to the same commit as .taskless/. init --json carries the pins as pinnedCli, and the update recipe (topic v11) tells an agent to offer the bump.
Review fixes for the stale-pin notice: - read the version installed under node_modules as well as the range; ^0.11.0 admits 0.11.2 but CI runs the locked 0.11.0 - compare exact and installed versions with semver precedence, so an older nightly of the same base is stale - name each pin's target on the package that publishes that version - a migration from schema 0 is a fresh install, not an upgrade - init recipe topic v3: pinnedCli in the envelope, the stop rule, and a bump step; update recipe points at re-running init --json - script regex: left boundary, punctuation-terminated versions, one report per repeated pin - tests: ordering guard, wizard, fresh install, nightly ordering
compareSemver compared the prerelease tail as one string, which orders rc.9 after rc.10. Compare dot-separated identifiers instead: numeric ones by value and below alphanumeric ones, a shorter prefix list first. Nightly stamps were unaffected, since their timestamp is fixed-width.
3a113b5 to
1d41534
Compare
An upgrade is usually run through a launcher (
npx @taskless/cli@latest init), which leaves the project's own pins alone. Those pins, adevDependenciesentry or a script spelling out@taskless/cli@0.10.2, are what CI, scripts and git hooks actually run. When the upgrade also migrated.taskless/, a pin that predates the new schema refuses the project withSCAFFOLD_VERSION_MISMATCH, so CI breaks on the push that carries the migrated files. Nothing about the upgrade itself fails, so nothing warned about it.What changes
init(batch and wizard) readspackage.jsonand names every pin of@taskless/cli/@taskless/cli-nightlythat would run an older CLI than the one that just ran:node_modules/<name>/package.json) is older.pnpm add -Dwrites^0.11.0and locks 0.11.0; the range admits 0.11.2, but CI runs 0.11.0;^/~range whose ceiling is below it (pre-1.0 caret holds the minor).latest,*,>=,workspace:and URLs are skipped rather than guessed at.0.12.0-<stamp>x<sha>), so it sorts before that release, and two nightlies of one base sort by build time.@taskless/cli-nightly@0.11.2, so a nightly pin under a release CLI moves to@taskless/cli, and the reverse.SCAFFOLD_VERSION_MISMATCH, says CI will break, and puts the bump in the same commit as.taskless/. A fresh install (migration from schema 0) is not called an upgrade, and like a run with no migration says "likely fail".package.json. A pin can be deliberate, and bumping it changes the lockfile.init --jsoncarriespinnedCli: [{ location, name, spec, installed }], always present.init→ topic v3 (envelope example and field list carrypinnedCli; "stop whenchangedis false" now also needs no stale pins; a bump step).update→ topic v13 (a step offering the bump, and saying plainly after a migration that CI will fail without it).compareVersionsmoved fromreconcile-marker.tstoutil/version-compare.tsunchanged, beside a newcompareSemver.Notes for review
// ast-grep-ignore: no-unrouted-cli-invocationonPACKAGE_NAMESinpinned-cli.ts. That rule assumes every@taskless/cliliteral insrc/is emitted text; these are lookup keys into someone else'spackage.json. Suppressed with the reason rather than splitting the literal to evade it.Deliberately left for follow-up
package.jsonis read. In a pnpm workspace the pin usually lives in a workspace package.pinnedClioninfo --json, soupdatecan read the pins without re-runninginit.Delivery shape
Single PR. OpenSpec change
init-stale-cli-pinsis archived here (one ADDED requirement oncli-init; 102 → 112 scenarios after rebasing onto main, nothing dropped).patchchangeset.Verification
pnpm typecheckandpnpm lint(including the house rules) clean; full CLI suite 1989 passed, including integration tests that run a real migration, a fresh install, and the wizard against a stale pin.