Skip to content

Commit 91edd99

Browse files
committed
fix(cli): cap only included workspace manifests, and don't let update guess a migration
1 parent 0dac786 commit 91edd99

5 files changed

Lines changed: 19 additions & 15 deletions

File tree

‎.changeset/init-stale-cli-pins.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"@taskless/cli": patch
33
---
44

5-
`taskless init` names any `package.json` pin of `@taskless/cli` or `@taskless/cli-nightly` that would run an older CLI than the one that just ran (a dependency whose installed build or range is behind, or a script spelling out an older version), with the version to move it to, and offers the bump. Scripts, CI and git hooks run that pin, and a CLI older than the project's `.taskless/` refuses the layout. The install does not edit `package.json`. The workspace packages a project declares (`pnpm-workspace.yaml`, or the `workspaces` field) are read as well as the root, and a dependency is judged by the version that package actually runs: its own `node_modules` link, else the hoisted one. `init --json` and `info --json` both carry the pins as `pinnedCli`, each naming its `manifest`, and plain `info` lists them. The `init` (topic v4), `info` (topic v2) and `update` (topic v14) recipes tell an agent to offer the bump, and `update` now reads the pins from `info --json` rather than re-running `init`.
5+
`taskless init` names any `package.json` pin of `@taskless/cli` or `@taskless/cli-nightly` that would run an older CLI than the one that just ran (a dependency whose installed build or range is behind, or a script spelling out an older version), with the version to move it to, and offers the bump. Scripts, CI and git hooks run that pin, and a CLI older than the project's `.taskless/` refuses the layout. The install does not edit `package.json`. The workspace packages a project declares (`pnpm-workspace.yaml`, or the `workspaces` field) are read as well as the root, and a dependency is judged by the version that package actually runs: its own `node_modules` link, else the hoisted one. `init --json` and `info --json` both carry the pins as `pinnedCli`, each naming its `manifest`, and plain `info` lists them. The `init` (topic v4), `info` (topic v2) and `update` (topic v15) recipes tell an agent to offer the bump, and `update` now reads the pins from `info --json` rather than re-running `init`.

‎openspec/changes/archive/2026-10-05-stale-cli-pins-workspaces/proposal.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ command, and `update` already runs `info --json` in step 1.
3232
every line, the root included.
3333
- `info --json` carries `pinnedCli` in the same shape; plain `info` lists the
3434
pins.
35-
- Recipes: `update` topic v14 reads the pins from `info --json` in step 4.
35+
- Recipes: `update` topic v15 reads the pins from `info --json` in step 4.
3636
`info` topic v2 documents the field and a step to offer the bump. `init`
3737
topic v4 documents `manifest`.
3838
- The existing `init-stale-cli-pins` changeset is extended. #443 has not been

‎openspec/changes/archive/2026-10-05-stale-cli-pins-workspaces/tasks.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
## 4. Recipes and changeset
2121

22-
- [x] 4.1 `update` v14 reads pins from `info --json`; `info` v2; `init` v4.
22+
- [x] 4.1 `update` v15 reads pins from `info --json`; `info` v2; `init` v4.
2323
- [x] 4.2 Extend the `init-stale-cli-pins` changeset.
2424

2525
## 5. Tests

‎packages/cli/src/agent/update.md‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Topic: update (CLI v%(CLI_VERSION)s / topic v14)
1+
# Topic: update (CLI v%(CLI_VERSION)s / topic v15)
22

33
## You are here
44
This is `update`. It tells you what an upgrade changed for the rules
@@ -67,13 +67,16 @@ that you finished.
6767

6868
If the upgrade also migrated an existing `.taskless/` (`init` printed
6969
a migration, or `init --json` carried `migrated` with `from` above
70-
`0`; if you did not see that output, assume it did), this is not
71-
optional advice: a CLI that predates the new schema refuses the
72-
project with `SCAFFOLD_VERSION_MISMATCH`, so CI breaks on the push
73-
that carries the migrated files. The bump belongs in that same
74-
commit. Without a
75-
migration the pin still reads the layout, but checks rules against
76-
engines this walk has moved past.
70+
`0`), this is not optional advice: a CLI that predates the new
71+
schema refuses the project with `SCAFFOLD_VERSION_MISMATCH`, so CI
72+
breaks on the push that carries the migrated files. The bump belongs
73+
in that same commit. Without a migration the pin still reads the
74+
layout, but checks rules against engines this walk has moved past.
75+
76+
`info --json` does not say whether a migration ran. If you did not
77+
see `init`'s output yourself, treat the bump as required, but say
78+
that you cannot tell whether `.taskless/` was migrated; do not name
79+
schema versions you have not read.
7780

7881
Offer the user the bump to the installed version, along with
7982
reinstalling dependencies. Do not make it silently: a pin can be

‎packages/cli/src/install/pinned-cli.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,9 @@ async function listWorkspaceManifests(
208208
): Promise<string[]> {
209209
const included = new Set<string>();
210210
const excluded = new Set<string>();
211-
let read = 0;
211+
// Only inclusions count toward the cap: they are what gets read later. A
212+
// broad `!**/fixtures` costing the same budget would leave real packages
213+
// listed after it unread, and the silence would look like "nothing stale".
212214
for (const raw of await readWorkspacePatterns(cwd, root)) {
213215
const negated = raw.startsWith("!");
214216
const pattern = posix
@@ -226,10 +228,9 @@ async function listWorkspaceManifests(
226228
exclude: (path) => SKIPPED_DIRECTORIES.has(basename(path)),
227229
})) {
228230
(negated ? excluded : included).add(entry.split(sep).join("/"));
229-
read += 1;
230-
if (read >= MAX_WORKSPACE_MANIFESTS) break;
231+
if (included.size >= MAX_WORKSPACE_MANIFESTS) break;
231232
}
232-
if (read >= MAX_WORKSPACE_MANIFESTS) break;
233+
if (included.size >= MAX_WORKSPACE_MANIFESTS) break;
233234
}
234235
// A `.` pattern names the root, which is read on its own.
235236
included.delete("package.json");

0 commit comments

Comments
 (0)