@rtorcato/repo-tooling 3.16.0 → 3.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/generators/linting.js +10 -49
- package/dist/languages/js/checks.js +16 -17
- package/package.json +20 -20
- package/skills/ai-issue-loop/SKILL.md +32 -11
- package/tooling/biome/biome.json +1 -1
|
@@ -35,55 +35,17 @@ export const BIOME_CONFIG = 'biome.json';
|
|
|
35
35
|
*/
|
|
36
36
|
export const BIOME_LEGACY_CONFIG = 'biome.jsonc';
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
return (await fs.readJson(filepath));
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* The `@biomejs/biome` actually installed under `targetDir`, or null when
|
|
53
|
-
* node_modules isn't populated. This is the binary that will parse the config
|
|
54
|
-
* and emit the schema-mismatch warning, so it is the only version worth
|
|
55
|
-
* comparing a written `$schema` against — a declared range like `^2.5.0` says
|
|
56
|
-
* nothing about which 2.x resolved (#424).
|
|
57
|
-
*/
|
|
58
|
-
export async function installedBiomeVersion(targetDir) {
|
|
59
|
-
const installed = await readJsonOrNull(path.join(targetDir, 'node_modules', '@biomejs', 'biome', 'package.json'));
|
|
60
|
-
const match = typeof installed?.version === 'string' ? /(\d+\.\d+\.\d+)/.exec(installed.version) : null;
|
|
61
|
-
return match?.[1] ?? null;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* The Biome version the scaffolded `$schema` URL should name (#363).
|
|
38
|
+
* Unversioned on purpose (#468). A `$schema` naming an exact version is only
|
|
39
|
+
* ever right until the next `@biomejs/biome` bump, at which point Biome prints
|
|
40
|
+
* "The configuration schema version does not match the CLI version" on every
|
|
41
|
+
* invocation and doctor reports drift — so every Dependabot bump needed a hand
|
|
42
|
+
* edit before it could go green. Biome accepts `latest` and says nothing about
|
|
43
|
+
* it (verified against 2.5.5), which removes the failure class outright.
|
|
65
44
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* does not match the CLI version 2.5.7") — noise on a file the consumer never
|
|
69
|
-
* wrote. The installed package wins because it is literally the binary that
|
|
70
|
-
* will parse the file and emit that warning; the declared range is the next
|
|
71
|
-
* best statement of intent when node_modules isn't populated yet.
|
|
45
|
+
* `latest` rather than dropping the key: editors still resolve a real schema
|
|
46
|
+
* for autocomplete and validation, which is the only reason the key is there.
|
|
72
47
|
*/
|
|
73
|
-
|
|
74
|
-
const installed = await installedBiomeVersion(targetDir);
|
|
75
|
-
if (installed)
|
|
76
|
-
return installed;
|
|
77
|
-
const pkg = await readJsonOrNull(path.join(targetDir, 'package.json'));
|
|
78
|
-
const deps = {
|
|
79
|
-
...(pkg?.dependencies ?? {}),
|
|
80
|
-
...(pkg?.devDependencies ?? {}),
|
|
81
|
-
};
|
|
82
|
-
// A range ("^2.5.0") carries the x.y.z we need just as an exact version does.
|
|
83
|
-
const declared = deps['@biomejs/biome'];
|
|
84
|
-
const match = typeof declared === 'string' ? /(\d+\.\d+\.\d+)/.exec(declared) : null;
|
|
85
|
-
return match?.[1] ?? BIOME_SCHEMA_FALLBACK;
|
|
86
|
-
}
|
|
48
|
+
const BIOME_SCHEMA_URL = 'https://biomejs.dev/schemas/latest/schema.json';
|
|
87
49
|
/**
|
|
88
50
|
* The thin pointer config — the same shape this repo dogfoods. `fix biome` used
|
|
89
51
|
* to copy the whole preset inline instead, which meant the scaffolded file
|
|
@@ -95,9 +57,8 @@ export async function generateBiomeConfig(targetDir) {
|
|
|
95
57
|
// file globs via `files.includes`; emitting the old 1.x `include`/`ignore`
|
|
96
58
|
// keys here forced consumers to run `biome migrate` before `biome check`
|
|
97
59
|
// would run at all.
|
|
98
|
-
const version = await resolveBiomeSchemaVersion(targetDir);
|
|
99
60
|
const biomeConfig = {
|
|
100
|
-
$schema:
|
|
61
|
+
$schema: BIOME_SCHEMA_URL,
|
|
101
62
|
extends: ['@rtorcato/repo-tooling/biome'],
|
|
102
63
|
};
|
|
103
64
|
await fs.writeJson(path.join(targetDir, BIOME_CONFIG), biomeConfig, { spaces: 2 });
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import { checkFile, hookHasUncommented } from '../../base/checks.js';
|
|
4
|
-
import { installedBiomeVersion } from '../../cli/generators/linting.js';
|
|
5
4
|
import { CLAUDE_SETTINGS_FILE, readClaudeSettings, workspaceSymlinkDirs, worktreeSymlinkDirs, } from '../../cli/generators/agent-rules.js';
|
|
6
5
|
import { WORKSPACE_FILE, dependsOnEsbuild, familyGlob, missingPnpmSettings, } from '../../cli/generators/pnpm-workspace.js';
|
|
7
6
|
const PACKAGE = '@rtorcato/repo-tooling';
|
|
@@ -163,38 +162,38 @@ export const BIOME_FILE_CHECK = {
|
|
|
163
162
|
};
|
|
164
163
|
/**
|
|
165
164
|
* The Biome check plus the one thing a contents-only matcher can't see: whether
|
|
166
|
-
* the `$schema` URL still
|
|
165
|
+
* the `$schema` URL still pins a version (#424, #468).
|
|
167
166
|
*
|
|
168
|
-
*
|
|
169
|
-
* nothing re-checked it
|
|
170
|
-
*
|
|
171
|
-
*
|
|
167
|
+
* It began as a drift check — the URL named the Biome resolved at scaffold
|
|
168
|
+
* time, nothing re-checked it, and once the repo's Biome moved on every single
|
|
169
|
+
* invocation (the pre-commit hook included) printed "The configuration schema
|
|
170
|
+
* version does not match the CLI version" while doctor stayed clean.
|
|
172
171
|
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
172
|
+
* Chasing the installed version was treating the symptom: an exact pin is
|
|
173
|
+
* *only ever* correct until the next bump, so every `@biomejs/biome` update
|
|
174
|
+
* failed the build until someone hand-edited a URL. `fix biome` now writes the
|
|
175
|
+
* unpinned `latest`, and this reads as a migration signal instead — any
|
|
176
|
+
* surviving pin is a repo that predates the unpinning, whatever version it
|
|
177
|
+
* names. That makes it independent of node_modules, so it also fires on a
|
|
178
|
+
* fresh clone, and it goes quiet for good once the repo is migrated.
|
|
177
179
|
*/
|
|
178
180
|
export async function checkBiome(dir) {
|
|
179
181
|
const result = await checkFile(dir, BIOME_FILE_CHECK);
|
|
180
182
|
if (result.status !== 'ok')
|
|
181
183
|
return result;
|
|
182
|
-
const installed = await installedBiomeVersion(dir);
|
|
183
|
-
if (!installed)
|
|
184
|
-
return result;
|
|
185
184
|
// The same candidate checkFile settled on — first one that exists wins.
|
|
186
185
|
for (const candidate of BIOME_FILE_CHECK.candidates) {
|
|
187
186
|
const filepath = path.join(dir, candidate);
|
|
188
187
|
if (!(await fs.pathExists(filepath)))
|
|
189
188
|
continue;
|
|
190
189
|
const url = readSchemaUrl(await fs.readFile(filepath, 'utf8'));
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
190
|
+
const pinned = url?.includes('biomejs.dev') ? schemaUrlVersion(url)?.join('.') : null;
|
|
191
|
+
if (pinned) {
|
|
193
192
|
return {
|
|
194
193
|
check: BIOME_FILE_CHECK.check,
|
|
195
194
|
status: 'drift',
|
|
196
|
-
detail: `${candidate}
|
|
197
|
-
hint: 'Run `npx @rtorcato/repo-tooling fix biome` to
|
|
195
|
+
detail: `${candidate} pins Biome ${pinned} in $schema — the next @biomejs/biome bump past it breaks every Biome run`,
|
|
196
|
+
hint: 'Run `npx @rtorcato/repo-tooling fix biome` to drop the version from the $schema URL',
|
|
198
197
|
};
|
|
199
198
|
}
|
|
200
199
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rtorcato/repo-tooling",
|
|
3
|
-
"version": "3.16.
|
|
3
|
+
"version": "3.16.2",
|
|
4
4
|
"description": "One CLI to scaffold, audit and fix your repo's whole toolchain — linting, tests, commits, releases & CI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -237,15 +237,15 @@
|
|
|
237
237
|
"inquirer": "^14.0.2"
|
|
238
238
|
},
|
|
239
239
|
"devDependencies": {
|
|
240
|
-
"@biomejs/biome": "^2.5.
|
|
240
|
+
"@biomejs/biome": "^2.5.8",
|
|
241
241
|
"@types/diff": "^8.0.0",
|
|
242
|
-
"@commitlint/cli": "^21.2.
|
|
243
|
-
"@commitlint/config-conventional": "^21.2.
|
|
242
|
+
"@commitlint/cli": "^21.2.2",
|
|
243
|
+
"@commitlint/config-conventional": "^21.2.2",
|
|
244
244
|
"@commitlint/types": "^21.2.0",
|
|
245
245
|
"@eslint/js": "^10.0.1",
|
|
246
246
|
"@ianvs/prettier-plugin-sort-imports": "^4.4.2",
|
|
247
|
-
"@next/eslint-plugin-next": "^16.
|
|
248
|
-
"@playwright/test": "^1.62.
|
|
247
|
+
"@next/eslint-plugin-next": "^16.3.0",
|
|
248
|
+
"@playwright/test": "^1.62.1",
|
|
249
249
|
"@rollup/plugin-typescript": "^12.0.0",
|
|
250
250
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
251
251
|
"@semantic-release/exec": "^7.1.0",
|
|
@@ -254,43 +254,43 @@
|
|
|
254
254
|
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
255
255
|
"@total-typescript/ts-reset": "0.6.1",
|
|
256
256
|
"@types/fs-extra": "^11.0.4",
|
|
257
|
-
"@types/node": "^26.
|
|
258
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
259
|
-
"@typescript-eslint/parser": "^8.
|
|
260
|
-
"@vitejs/plugin-react": "^6.0.
|
|
257
|
+
"@types/node": "^26.2.0",
|
|
258
|
+
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
|
259
|
+
"@typescript-eslint/parser": "^8.67.0",
|
|
260
|
+
"@vitejs/plugin-react": "^6.0.5",
|
|
261
261
|
"@vitest/coverage-v8": "^4.1.10",
|
|
262
262
|
"commitizen": "^4.3.1",
|
|
263
|
-
"conventional-changelog-conventionalcommits": "^10.
|
|
263
|
+
"conventional-changelog-conventionalcommits": "^10.3.0",
|
|
264
264
|
"cz-conventional-changelog": "^3.3.0",
|
|
265
|
-
"esbuild": "^0.28.
|
|
265
|
+
"esbuild": "^0.28.2",
|
|
266
266
|
"esbuild-node-externals": "^2.0.0",
|
|
267
|
-
"eslint": "10.8.
|
|
267
|
+
"eslint": "10.8.1",
|
|
268
268
|
"eslint-plugin-import": "^2.32.0",
|
|
269
|
-
"eslint-plugin-jest": "29.16.
|
|
269
|
+
"eslint-plugin-jest": "29.16.1",
|
|
270
270
|
"husky": "^9.1.7",
|
|
271
271
|
"is-ci": "^4.1.0",
|
|
272
272
|
"jest": "^30.4.2",
|
|
273
273
|
"jsdom": "^30.0.1",
|
|
274
|
-
"knip": "^6.
|
|
274
|
+
"knip": "^6.32.2",
|
|
275
275
|
"prettier": "^3.9.6",
|
|
276
276
|
"rimraf": "6.1.3",
|
|
277
|
-
"rollup": "^4.62.
|
|
278
|
-
"semantic-release": "^25.0.
|
|
277
|
+
"rollup": "^4.62.4",
|
|
278
|
+
"semantic-release": "^25.0.9",
|
|
279
279
|
"ts-jest": "^29.4.12",
|
|
280
280
|
"tslib": "^2.8.0",
|
|
281
281
|
"tsup": "8.5.1",
|
|
282
282
|
"typescript": "^7.0.2",
|
|
283
|
-
"typescript-eslint": "^8.
|
|
283
|
+
"typescript-eslint": "^8.67.0",
|
|
284
284
|
"vitest": "^4.1.10"
|
|
285
285
|
},
|
|
286
286
|
"peerDependencies": {
|
|
287
287
|
"@biomejs/biome": "^2.5.0",
|
|
288
288
|
"@commitlint/cli": "^20.0.0 || ^21.0.0",
|
|
289
|
-
"@commitlint/config-conventional": "^21.2.
|
|
289
|
+
"@commitlint/config-conventional": "^21.2.2",
|
|
290
290
|
"@commitlint/types": "^21.2.0",
|
|
291
291
|
"@eslint/js": "^9.0.0",
|
|
292
292
|
"@ianvs/prettier-plugin-sort-imports": "^4.0.0",
|
|
293
|
-
"@next/eslint-plugin-next": "^16.
|
|
293
|
+
"@next/eslint-plugin-next": "^16.3.0",
|
|
294
294
|
"@rollup/plugin-typescript": "^12.0.0",
|
|
295
295
|
"@semantic-release/changelog": "^6.0.0",
|
|
296
296
|
"@semantic-release/commit-analyzer": "^13.0.0",
|
|
@@ -70,9 +70,9 @@ drift with a second copy to maintain.
|
|
|
70
70
|
|
|
71
71
|
| Label | On | Meaning |
|
|
72
72
|
|---|---|---|
|
|
73
|
-
| `ai-ready` | issue | Eligible for an agent. The hard gate
|
|
74
|
-
| `ai-wip` | issue | Claimed; a worktree exists. |
|
|
75
|
-
| `ai-blocked` | issue | Agent gave up; needs a human. |
|
|
73
|
+
| `ai-ready` | issue | Eligible for an agent. The hard gate; **cleared on pickup**. |
|
|
74
|
+
| `ai-wip` | issue | Claimed; a worktree exists. Never rides alongside `ai-ready`. |
|
|
75
|
+
| `ai-blocked` | issue | Agent gave up; needs a human. Only a human re-adds `ai-ready`. |
|
|
76
76
|
| `ai-review` | PR | Awaiting agent review. |
|
|
77
77
|
| `ai-reviewing-code` | PR | `code-reviewer` claimed and running. Cleared with its verdict. |
|
|
78
78
|
| `ai-reviewing-sec` | PR | `security-expert` claimed and running. Cleared with its verdict. |
|
|
@@ -400,13 +400,21 @@ Only then:
|
|
|
400
400
|
git -C "$ROOT" worktree remove --force "$WT_DIR" # the path found above, not a rebuilt one
|
|
401
401
|
git -C "$ROOT" branch -D "$BRANCH" 2>/dev/null
|
|
402
402
|
gh issue edit <N> --remove-label ai-wip 2>/dev/null
|
|
403
|
+
# Still OPEN means the PR said only `Refs #N`; a `Closes #N` issue is already closed.
|
|
404
|
+
if [ "$(gh issue view <N> --json state -q .state)" = OPEN ]; then
|
|
405
|
+
gh issue edit <N> --add-assignee @me
|
|
406
|
+
fi
|
|
403
407
|
```
|
|
404
408
|
|
|
405
409
|
A closed-unmerged PR is the exception: there is no squash to find, so skip the
|
|
406
410
|
confirmation and remove — the work was abandoned deliberately.
|
|
407
411
|
|
|
408
|
-
The issue itself closes from the PR body's `Closes #N
|
|
409
|
-
|
|
412
|
+
The issue itself closes from the PR body's `Closes #N`, so both edits are normally
|
|
413
|
+
no-ops on a closed issue. A PR that said only `Refs #N` leaves it **open**, which is
|
|
414
|
+
what the state check catches. The work has landed, so it must not go back in
|
|
415
|
+
the queue; pickup already dropped `ai-ready`, and assigning it is what stops a
|
|
416
|
+
merged issue sitting unowned instead (#429 had to be moved to `holding` by hand).
|
|
417
|
+
This pass is what frees concurrency slots, so it must run before Pass 4.
|
|
410
418
|
|
|
411
419
|
**Then reap the stalled.** Nothing can time out an agent: the Agent tool takes no
|
|
412
420
|
timeout, and an agent whose session died leaves its labels behind with no process
|
|
@@ -432,8 +440,10 @@ work must never be reaped out from under itself.
|
|
|
432
440
|
The **no PR exists** condition on the first row is what makes reaping safe. An
|
|
433
441
|
agent that got as far as opening a PR has handed off to the label state machine
|
|
434
442
|
and is no longer the thing being waited on; only a run that produced nothing is
|
|
435
|
-
presumed dead.
|
|
436
|
-
|
|
443
|
+
presumed dead. Reaping deliberately does **not** restore `ai-ready` — `ai-blocked`
|
|
444
|
+
means a human decides when the issue re-enters the queue, and the removed worktree
|
|
445
|
+
means their re-label starts clean. The other two `ai-blocked` exits, Pass 3's
|
|
446
|
+
ping-pong stop and an implementer handing back, leave it off for the same reason.
|
|
437
447
|
|
|
438
448
|
**Every `ai-blocked` must say why, and land in front of a human.** So reaping always
|
|
439
449
|
does three things together — label, assign, comment — and the comment opens with
|
|
@@ -448,9 +458,12 @@ puts it in the statusline and fires a notification with a sound.
|
|
|
448
458
|
**Reaping is not always the right call — say so when it isn't.** The rule assumes a
|
|
449
459
|
dead agent, but a stale `ai-wip` can also come from a run that was cancelled
|
|
450
460
|
deliberately, in which case the work is fine and only the claim is stale. If you know
|
|
451
|
-
the cause and it is benign,
|
|
452
|
-
|
|
453
|
-
|
|
461
|
+
the cause and it is benign, **return it to the queue** — `gh issue edit <N> --add-label
|
|
462
|
+
ai-ready --remove-label ai-wip`, no `ai-blocked` — so Pass 4 picks it straight back up,
|
|
463
|
+
and say in the comment that you re-queued it, that you deviated, and why. Re-adding
|
|
464
|
+
`ai-ready` is not optional: pickup cleared it, so clearing `ai-wip` alone drops the
|
|
465
|
+
issue out of the queue silently, which is the worse failure. `ai-blocked` means *a
|
|
466
|
+
human must look*; do not spend it on a claim you already understand.
|
|
454
467
|
|
|
455
468
|
### Pass 3 — review
|
|
456
469
|
|
|
@@ -769,9 +782,17 @@ Take the first `slots` issues. For each, **claim it first** so a concurrent tick
|
|
|
769
782
|
can't double-pick:
|
|
770
783
|
|
|
771
784
|
```bash
|
|
772
|
-
gh issue edit <N> --add-label ai-wip
|
|
785
|
+
gh issue edit <N> --add-label ai-wip --remove-label ai-ready
|
|
773
786
|
```
|
|
774
787
|
|
|
788
|
+
Dropping `ai-ready` is half the claim, not tidiness — the diagram above is a
|
|
789
|
+
transition, not an accumulation. An issue left carrying both re-enters the queue
|
|
790
|
+
the instant `ai-wip` clears for any reason other than the PR closing it, and the
|
|
791
|
+
next tick spawns an agent to re-implement work already sitting in an open PR
|
|
792
|
+
(#458, #467, #461, #452, all in one session). Every path that legitimately returns
|
|
793
|
+
an issue to the queue therefore re-adds `ai-ready` explicitly; Pass 2's benign-stall
|
|
794
|
+
path is the only one, and a human does the rest.
|
|
795
|
+
|
|
775
796
|
**Then create the worktree yourself**, before spawning anything. `<slug>` is 3–4
|
|
776
797
|
kebab-case words from the title:
|
|
777
798
|
|
package/tooling/biome/biome.json
CHANGED