Skip to content

feat(cli): run only as the recorded version, on rules reconciled for its engines - #562

Merged
theCodeDrift merged 10 commits into
mainfrom
cli-version-pin-gate
Oct 21, 2026
Merged

theCodeDrift merged 10 commits into
mainfrom
cli-version-pin-gate

Conversation

@theCodeDrift

@theCodeDrift theCodeDrift commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Stack (root → tip):

Engine upgrades no longer carry backward-compatibility weight: rule migrations rewrite whatever an ast-grep or Vale release breaks. What still went wrong was running a CLI against rules reconciled for other engines. Until now the only version gate was the scaffold layout number, and rule migrations deliberately leave that alone. So nothing refused either of these cases:

  • A new CLI ran its new engines against rules nobody had migrated. With the Haskell type_synomym rename, check reported no ast-grep findings at all.
  • An older pinned CLI ran rules that npx @taskless/cli@latest update --rules had already migrated past it.

What changes

  • Version-pin gate: a CLI that is not the version .taskless/taskless.json records for its package refuses every command except init, info, auth and feedback, with CLI_VERSION_MISMATCH. This applies however the CLI was started: npx, dlx, node_modules or the launcher. "Recorded" means what that package's launcher runs (recordedLauncherVersion), so the gate and the launcher can't disagree. init is the one command that moves the record, which makes it the way to upgrade. update is gated, because it moves the rules' record, not the CLI's.
  • Engine gate: check and verify refuse with RULES_ENGINE_MISMATCH when rules.engines differs from the bundled ast-grep or Vale. This only applies to an engine the project has rules for, and only when engines were recorded at all. When the rules are behind, the message names update then update --rules. When they are ahead, it names the recorded CLI. validate is not gated, because an agent runs it during the walk.
  • --allow-version-mismatches skips both gates, and TASKLESS_ALLOW_VERSION_MISMATCHES=1 is its environment form. The root pnpm cli script sets it, since this repo's manifest pins 0.11.2 with engines recorded at 0.45.2/3.18.0, and pnpm lint would otherwise refuse its own fresh build.
  • update recipe (topic v19): the walk passes the override to any mid-walk check, and the pinned-CLI bump in step 4 is now required whether or not a migration ran. It also adds the new codes to the error table and a ledger note under 0.13.3.
  • npx is only for installing and upgrading (the bare wizard, or init). Everything else goes through ./.taskless/bin/cli.mjs:
    • The docs-npx-cli Vale rule used to rewrite docs toward npx @taskless/cli <command>. It now refuses an npx/dlx/bunx run of any subcommand but init and auth (a login is a per-repo token, and auth runs on any version) in READMEs and dotagents.md, and still bans pnpm cli in READMEs. It lists subcommands explicitly so prose like "run npx @taskless/cli to install" passes.
    • The root, package and example READMEs move to the launcher. The .taskless/README.md template runs check through the launcher too.
    • init's upgrade trailer says ./.taskless/bin/cli.mjs update, not the npx invocation that ran it. The bare non-interactive notice names the launcher for recipes when the project has one.
    • The auth recipe (topic v4) says auth runs on any version, and that logging in does not clear a CLI_VERSION_MISMATCH from another command.

Reviewer notes

  • CLIs already published don't carry the gate, so the engine gate's "ahead" branch only protects CLIs released from here on.

  • After an upgrade with init, CI stays red on check until someone runs update --rules. That's intended, and the refusal names both commands.

  • Two test fixtures leaked a recorded version. reconcile-marker copied this repo's scaffold, and onboard hard-coded 0.7.0. Both now record no version or the built CLI's version.

  • Error remedies (auth login, rule restore, …) still repeat however the CLI was started. Under the gate, an npx run that gets far enough to print one is already the recorded version, so the remedy works, and changing ~20 call sites would change nothing anyone sees.

  • This repo's own .taskless/ (pinned 0.11.2) moves in a separate PR. Its README is outside the Vale rule's scope.

OpenSpec: openspec/changes/archive/2026-10-20-cli-version-pin-gate/ and openspec/changes/archive/2026-10-21-npx-install-only/ (ADDED-only deltas to cli, both archived in this PR).

…its engines

A CLI that is not the version .taskless/taskless.json records for its
package now refuses every command but init, info, auth and feedback
(CLI_VERSION_MISMATCH), however it was started. check and verify refuse
rules whose recorded rules.engines differ from the bundled ast-grep or
Vale, for an engine the project has rules for (RULES_ENGINE_MISMATCH).
--allow-version-mismatches, now also TASKLESS_ALLOW_VERSION_MISMATCHES,
skips both; the repo's pnpm cli sets it.
The docs-npx-cli Vale rule now refuses an npx or dlx run of any subcommand
but init, instead of rewriting docs toward npx @taskless/cli <command>.
init's upgrade trailer and the bare non-interactive notice sent readers to
npx for update and agent, a CLI the version-pin gate refuses.
…any version

A login is a per-repo token, not a version, so auth is exempt from the
version-pin gate. docs-npx-cli now allows npx and dlx runs of auth.
@theCodeDrift

Copy link
Copy Markdown
Member Author

@claude /review

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @theCodeDrift's task in 3m 11s —— View job


Review: feat(cli): run only as the recorded version, on rules reconciled for its engines

Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json had no comments, reviews, or review threads, so this is the first review of this PR; the whole diff is assessed below.

Todo

  • Read .prior-review.json (no prior findings)
  • Fetch PR diff and metadata
  • Review version-gate.ts and engine-gate.ts core logic
  • Review index.ts / check.ts / verify.ts / init.ts integration
  • Review migrate.ts / 0001-init.ts changes
  • Review docs/Vale rule changes
  • Review tests
  • Post findings and final summary

Assessment

This is a well-structured change with thorough unit and spawned-CLI test coverage (version-gate.test.ts, version-gate-cli.test.ts, engine-gate.test.ts, reconcile-marker.test.ts). I traced the gate wiring end-to-end:

  • The pin gate runs once in index.ts before runCommand, keyed on positionals[0], with exemptions matching the spec (init, info, auth, feedback, bare/--help/--version).
  • findVersionMismatch/versionMismatchMessage in packages/cli/src/install/version-gate.ts correctly mirror recordedLauncherVersion's own resolution (checked against install/launcher.ts), so the gate and launcher can't disagree, as claimed.
  • The engine gate (packages/cli/src/rules/engine-gate.ts) is wired into check and into verify (but not validate, via the label === "verify" guard), and requireReconciledEngines is a separate function from requireCurrentSchema so update --rules isn't refused by the gate it exists to clear — matches recordReconciliation in reconcile-marker.ts, which always stamps sg/vale atomically from the same running CLI, so the "behind vs. ahead" message branch (which treats a mixed set as "ahead" by checking behind.length === mismatches.length) can't actually produce a mixed-direction set in practice.
  • hasVersionMismatchOverride's new env parameter defaults to process.env at every existing call site (migrate.ts:243,287, engine-gate.ts:111), so the signature change is backward compatible.
  • Checked the repo's own READMEs, CONTRIBUTING.md, and dotagents.md against the new docs-npx-cli Vale rule by hand: no leftover npx @taskless/cli <non-init-subcommand> invocations remain anywhere in scope, so the rule doesn't self-break on landing.

One minor, non-blocking observation:

  • [New] .taskless/rules/vale/docs-npx-cli/docs-npx-cli.yml lines 114–115: the subcommand alternation includes demo, help, and test, none of which are registered subcommands (packages/cli/src/commands/names.ts lists only agent, auth, check, detect, feedback, info, init, onboard, report, rule, share, update, validate, verify). This is harmless — it only makes the rule flag invocations that could never legitimately appear in docs — but it undercuts the design note that the list is "exact" and "a new subcommand is a one-line addition"; worth pruning or confirming those three are intentional placeholders.

Nothing else stood out as a correctness, security, or test-adequacy gap — the gate's directional messaging (newer vs. older vs. other-channel), the "no manifest / unreadable manifest / self build / unknown version" exemptions, and the --json envelope rendering path (including the reported: true re-throw in index.ts that prevents double-printing) all match their spec scenarios and have corresponding tests.

demo, help and test are not in commands/names.ts.
@theCodeDrift

Copy link
Copy Markdown
Member Author

Re: @github-actions — "Claude finished @theCodeDrift's task in 3m 11s…"
#562 (comment)

Pruned demo, help and test from the docs-npx-cli subcommand list in fdd7b66. It now matches packages/cli/src/commands/names.ts exactly, minus init and auth, and the rule comment points at that file.

— AI Coding Agent

@theCodeDrift
theCodeDrift merged commit 52fdd64 into main Oct 21, 2026
4 checks passed
@theCodeDrift
theCodeDrift deleted the cli-version-pin-gate branch October 21, 2026 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant