@rtorcato/repo-tooling 3.18.0 → 3.19.1
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/base/checks.js +14 -5
- package/dist/base/fixers.js +2 -2
- package/dist/cli/commands/doctor.js +8 -3
- package/dist/cli/commands/fix.js +3 -1
- package/dist/cli/generators/claude-skills.js +14 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/utils/copy-preset.js +3 -1
- package/dist/languages/js/checks.js +2 -2
- package/package.json +3 -3
- package/tooling/biome/{biome.json → preset.json} +0 -1
package/dist/base/checks.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import { BLOCK_START, hasStaleAgentBlock } from '../cli/generators/agent-rules.js';
|
|
4
4
|
import { BADGE_START, hasPublicOnlyBadges } from '../cli/generators/badges.js';
|
|
5
|
-
import { claudeSkillStatus, SHIPPED_SKILL } from '../cli/generators/claude-skills.js';
|
|
5
|
+
import { claudeSkillStatus, SHIPPED_SKILL, skillDiffCommand, } from '../cli/generators/claude-skills.js';
|
|
6
6
|
import { DEPENDABOT_CONFIG_PATHS, dependabotIgnoreRules } from '../cli/generators/security.js';
|
|
7
7
|
import { detectNestedLanguages } from '../cli/utils/detect-language.js';
|
|
8
8
|
/**
|
|
@@ -469,11 +469,16 @@ export async function checkAiSetup(dir) {
|
|
|
469
469
|
* is not a defect in this repo, and either of those statuses would fail its CI
|
|
470
470
|
* over a file CI has never seen. `optional-missing` is the honest verdict — an
|
|
471
471
|
* opt-in workflow tool that isn't configured — and it leaves the exit code alone.
|
|
472
|
+
*
|
|
473
|
+
* `skillsDir` is doctor's `--skills-dir`. Without it this reported against
|
|
474
|
+
* `~/.claude/skills` whatever directory the repo actually installs into, so a
|
|
475
|
+
* consumer who passes the flag to `fix` saw a permanent false `optional-missing`
|
|
476
|
+
* for a skill they have (#485).
|
|
472
477
|
*/
|
|
473
|
-
export async function checkClaudeSkills() {
|
|
478
|
+
export async function checkClaudeSkills(skillsDir) {
|
|
474
479
|
const check = 'Claude skills';
|
|
475
480
|
const hint = `Run \`npx @rtorcato/repo-tooling fix claude-skills\` to install the ${SHIPPED_SKILL} skill (writes outside the repo; opt-in, so \`fix\` alone skips it)`;
|
|
476
|
-
const status = await claudeSkillStatus();
|
|
481
|
+
const status = await claudeSkillStatus(SHIPPED_SKILL, skillsDir);
|
|
477
482
|
if (!status.installed) {
|
|
478
483
|
return {
|
|
479
484
|
check,
|
|
@@ -495,7 +500,9 @@ export async function checkClaudeSkills() {
|
|
|
495
500
|
// A local fork is `ok` for the same reason a modified copied asset is (#448):
|
|
496
501
|
// it is somebody's deliberate work, so it is named once and never nagged as
|
|
497
502
|
// fixable — pointing at a `fix` that would refuse is worse than saying nothing.
|
|
498
|
-
|
|
503
|
+
// `realFile` is set whenever `contentState` is — both mean something is
|
|
504
|
+
// installed. The extra test is TypeScript's, not a real condition.
|
|
505
|
+
if (status.realFile && status.contentState && status.contentState !== 'pristine') {
|
|
499
506
|
const why = status.contentState === 'modified'
|
|
500
507
|
? `has local changes since ${status.installedVersion}`
|
|
501
508
|
: 'carries no content record, so a fork cannot be told from a stale copy';
|
|
@@ -503,7 +510,9 @@ export async function checkClaudeSkills() {
|
|
|
503
510
|
check,
|
|
504
511
|
status: 'ok',
|
|
505
512
|
detail: `${SHIPPED_SKILL} skill at ${status.file} ${why}; this package ships ${status.shippedVersion} and will not overwrite it`,
|
|
506
|
-
|
|
513
|
+
// Name both paths: "diff it against the shipped copy" left the reader
|
|
514
|
+
// with nothing to diff, in the one case they most want to look (#484).
|
|
515
|
+
hint: `Diff it against the shipped copy — \`${skillDiffCommand({ realFile: status.realFile, shippedFile: status.shippedFile })}\` — then run \`npx @rtorcato/repo-tooling fix claude-skills --force-skills\` to take the shipped version`,
|
|
507
516
|
};
|
|
508
517
|
}
|
|
509
518
|
return {
|
package/dist/base/fixers.js
CHANGED
|
@@ -16,7 +16,7 @@ import path from 'node:path';
|
|
|
16
16
|
import chalk from 'chalk';
|
|
17
17
|
import inquirer from 'inquirer';
|
|
18
18
|
import { installAgentRules, installAiSetup } from '../cli/generators/agent-rules.js';
|
|
19
|
-
import { installClaudeSkill, resolveSkillsDir, SHIPPED_SKILL, } from '../cli/generators/claude-skills.js';
|
|
19
|
+
import { installClaudeSkill, resolveSkillsDir, SHIPPED_SKILL, skillDiffCommand, } from '../cli/generators/claude-skills.js';
|
|
20
20
|
import { generateBrand } from '../cli/generators/brand.js';
|
|
21
21
|
import { generateCommunityHealth } from '../cli/generators/community-health.js';
|
|
22
22
|
import { generateCommitlintConfig } from '../cli/generators/git.js';
|
|
@@ -89,7 +89,7 @@ function describeSkillFork(result) {
|
|
|
89
89
|
return [
|
|
90
90
|
`skipped — ${SHIPPED_SKILL} was not overwritten with ${result.shippedVersion}: ${why}`,
|
|
91
91
|
` ${target}`,
|
|
92
|
-
` compare:
|
|
92
|
+
` compare: ${skillDiffCommand(result)}`,
|
|
93
93
|
' overwrite anyway: fix claude-skills --force-skills',
|
|
94
94
|
];
|
|
95
95
|
}
|
|
@@ -179,12 +179,12 @@ async function runBaseChecks(dir, lock, opts) {
|
|
|
179
179
|
results.push(await checkBrand(dir));
|
|
180
180
|
results.push(await checkAiSetup(dir));
|
|
181
181
|
// User-global, not repo state — see checkClaudeSkills on why it never returns drift.
|
|
182
|
-
results.push(await checkClaudeSkills());
|
|
182
|
+
results.push(await checkClaudeSkills(opts.skillsDir));
|
|
183
183
|
results.push(await checkReadmeBadges(dir, opts.badges.audience, opts.badges.fixTarget));
|
|
184
184
|
results.push(await checkCoverageUpload(dir));
|
|
185
185
|
return results;
|
|
186
186
|
}
|
|
187
|
-
export async function runDoctor(dir) {
|
|
187
|
+
export async function runDoctor(dir, skillsDir) {
|
|
188
188
|
const targetDir = path.resolve(dir);
|
|
189
189
|
const lock = await readLockfile(targetDir);
|
|
190
190
|
// Per-module dispatch (#285): the base checks (repo hygiene, CI, security,
|
|
@@ -212,6 +212,7 @@ export async function runDoctor(dir) {
|
|
|
212
212
|
presetWorkflow: null,
|
|
213
213
|
language,
|
|
214
214
|
codeqlLanguages: languageModule.codeqlLanguages,
|
|
215
|
+
skillsDir,
|
|
215
216
|
})),
|
|
216
217
|
];
|
|
217
218
|
return demoteDeclined(results, lock);
|
|
@@ -234,6 +235,7 @@ export async function runDoctor(dir) {
|
|
|
234
235
|
presetWorkflow: renderSwiftWorkflow(await readSwiftPackage(targetDir)),
|
|
235
236
|
language,
|
|
236
237
|
codeqlLanguages: languageModule.codeqlLanguages,
|
|
238
|
+
skillsDir,
|
|
237
239
|
})),
|
|
238
240
|
...(await runSwiftChecks(targetDir)),
|
|
239
241
|
];
|
|
@@ -257,6 +259,7 @@ export async function runDoctor(dir) {
|
|
|
257
259
|
presetWorkflow: renderPythonWorkflow(await readPyproject(targetDir)),
|
|
258
260
|
language,
|
|
259
261
|
codeqlLanguages: languageModule.codeqlLanguages,
|
|
262
|
+
skillsDir,
|
|
260
263
|
})),
|
|
261
264
|
...(await runPythonChecks(targetDir)),
|
|
262
265
|
];
|
|
@@ -282,6 +285,7 @@ export async function runDoctor(dir) {
|
|
|
282
285
|
presetWorkflow: renderPerlWorkflow(await readPerlProject(targetDir)),
|
|
283
286
|
language,
|
|
284
287
|
codeqlLanguages: languageModule.codeqlLanguages,
|
|
288
|
+
skillsDir,
|
|
285
289
|
})),
|
|
286
290
|
...(await runPerlChecks(targetDir)),
|
|
287
291
|
];
|
|
@@ -342,6 +346,7 @@ export async function runDoctor(dir) {
|
|
|
342
346
|
presetWorkflow: renderGitHubWorkflow(githubJobs(inferProjectConfig(pkg), { scripts: scriptsOf(pkg) })),
|
|
343
347
|
language,
|
|
344
348
|
codeqlLanguages: languageModule.codeqlLanguages,
|
|
349
|
+
skillsDir,
|
|
345
350
|
})));
|
|
346
351
|
return demoteDeclined(results, lock);
|
|
347
352
|
}
|
|
@@ -397,7 +402,7 @@ export function summarize(results) {
|
|
|
397
402
|
}
|
|
398
403
|
export async function doctorCommand(options = {}) {
|
|
399
404
|
const dir = options.directory ?? process.cwd();
|
|
400
|
-
const results = await runDoctor(dir);
|
|
405
|
+
const results = await runDoctor(dir, options.skillsDir);
|
|
401
406
|
if (options.json) {
|
|
402
407
|
console.log(JSON.stringify({ directory: path.resolve(dir), results }, null, 2));
|
|
403
408
|
}
|
package/dist/cli/commands/fix.js
CHANGED
|
@@ -333,7 +333,9 @@ export async function fixCommand(target, options = {}) {
|
|
|
333
333
|
const pkg = await readPackageJson(targetDir);
|
|
334
334
|
const lock = await readLockfile(targetDir);
|
|
335
335
|
const fixers = fixersForLanguage(await detectLanguage(targetDir));
|
|
336
|
-
|
|
336
|
+
// Same --skills-dir the claude-skills fixer writes to, so the diagnosis fix
|
|
337
|
+
// acts on and the install it performs agree on one directory (#485).
|
|
338
|
+
const results = await runDoctor(targetDir, options.skillsDir);
|
|
337
339
|
const actions = [];
|
|
338
340
|
const noteLockConflict = (check) => {
|
|
339
341
|
if (!lock)
|
|
@@ -129,6 +129,16 @@ export async function readShippedSkill(name = SHIPPED_SKILL) {
|
|
|
129
129
|
const pkg = await fs.readJson(path.join(root, 'package.json'));
|
|
130
130
|
return { content, version: String(pkg.version), file };
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* The command a human runs to see what their fork changed, before deciding
|
|
134
|
+
* whether to take the shipped copy. One builder, because `doctor` and `fix`
|
|
135
|
+
* both print it and two independently-built strings drift (#484). Always
|
|
136
|
+
* `realFile`: through a stow symlink the nominal path is the link, and the
|
|
137
|
+
* bytes worth reading are in the dotfiles checkout it points at.
|
|
138
|
+
*/
|
|
139
|
+
export function skillDiffCommand(paths) {
|
|
140
|
+
return `diff "${paths.realFile}" "${paths.shippedFile}"`;
|
|
141
|
+
}
|
|
132
142
|
/** Whether `file` is itself a symlink, as opposed to merely resolving through one. */
|
|
133
143
|
async function isSymlink(file) {
|
|
134
144
|
return await fs
|
|
@@ -185,6 +195,8 @@ export async function claudeSkillStatus(name = SHIPPED_SKILL, explicit) {
|
|
|
185
195
|
const shipped = await readShippedSkill(name);
|
|
186
196
|
const { dir } = await resolveSkillsDir(explicit);
|
|
187
197
|
const absent = {
|
|
198
|
+
realFile: null,
|
|
199
|
+
shippedFile: shipped.file,
|
|
188
200
|
installed: false,
|
|
189
201
|
installedVersion: null,
|
|
190
202
|
shippedVersion: shipped.version,
|
|
@@ -202,6 +214,8 @@ export async function claudeSkillStatus(name = SHIPPED_SKILL, explicit) {
|
|
|
202
214
|
const behind = installedVersion === null || isNewerVersion(shipped.version, installedVersion);
|
|
203
215
|
return {
|
|
204
216
|
file,
|
|
217
|
+
realFile: (await isSymlink(file)) ? await fs.realpath(file) : file,
|
|
218
|
+
shippedFile: shipped.file,
|
|
205
219
|
installed: true,
|
|
206
220
|
installedVersion,
|
|
207
221
|
shippedVersion: shipped.version,
|
package/dist/cli/index.js
CHANGED
|
@@ -317,6 +317,9 @@ program
|
|
|
317
317
|
.description('🩺 Diagnose project alignment with @rtorcato/repo-tooling presets')
|
|
318
318
|
.option('-d, --directory <path>', 'Target directory to diagnose', process.cwd())
|
|
319
319
|
.option('--json', 'Emit machine-readable JSON output')
|
|
320
|
+
// The read side of `fix --skills-dir` (#485). No --yes/--json requirement
|
|
321
|
+
// here: doctor never writes, so an unresolved directory is just reported.
|
|
322
|
+
.option('--skills-dir <path>', 'Where `fix claude-skills` installs user-global agent skills (default: ~/.claude/skills). Pass the same path `fix` was given, or the skill reports as not installed')
|
|
320
323
|
.action(doctorCommand);
|
|
321
324
|
program
|
|
322
325
|
.command('fix [target]')
|
|
@@ -4,7 +4,9 @@ import fs from 'fs-extra';
|
|
|
4
4
|
import { recordAssetHash } from './lockfile.js';
|
|
5
5
|
export const PRESETS = {
|
|
6
6
|
biome: {
|
|
7
|
-
|
|
7
|
+
// Not named `biome.json`: Biome auto-discovers any file by that name, so
|
|
8
|
+
// shipping one inside this package made it a second root config here (#486).
|
|
9
|
+
source: 'tooling/biome/preset.json',
|
|
8
10
|
target: 'biome.json',
|
|
9
11
|
desc: 'Biome formatter and linter configuration',
|
|
10
12
|
},
|
|
@@ -84,7 +84,7 @@ function matchesBiomeConfig(contents) {
|
|
|
84
84
|
if (BIOME_PRESET_REF.test(JSON.stringify(config.extends ?? '')))
|
|
85
85
|
return true;
|
|
86
86
|
// The inlined preset: biome's own `$schema` plus the `linter.rules.preset`
|
|
87
|
-
// key the shipped `tooling/biome/
|
|
87
|
+
// key the shipped `tooling/biome/preset.json` sets.
|
|
88
88
|
const schema = config.$schema;
|
|
89
89
|
return (typeof schema === 'string' &&
|
|
90
90
|
schema.includes('biomejs.dev/schemas/') &&
|
|
@@ -1373,7 +1373,7 @@ function readSchemaUrl(contents) {
|
|
|
1373
1373
|
* of which version the config targets, so comparing it against the declared
|
|
1374
1374
|
* dependency floor is exact — no heuristics, no false positives.
|
|
1375
1375
|
*
|
|
1376
|
-
* This is the #330 defect precisely: `tooling/biome/
|
|
1376
|
+
* This is the #330 defect precisely: `tooling/biome/preset.json` carries
|
|
1377
1377
|
* `$schema` 2.5.0 and uses `linter.rules.preset`, a 2.5 key, while
|
|
1378
1378
|
* `peerDependencies` advertised `@biomejs/biome: ^2.0.0`. Consumers on 2.0–2.4
|
|
1379
1379
|
* got `Found an unknown key \`preset\`` with nothing pointing at the range.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rtorcato/repo-tooling",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.19.1",
|
|
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": [
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"tooling/docusaurus/docs-helpers.mjs",
|
|
91
91
|
"tooling/docusaurus/theme-tokens.css",
|
|
92
92
|
"tooling/docusaurus/theme.css",
|
|
93
|
-
"tooling/biome/
|
|
93
|
+
"tooling/biome/preset.json",
|
|
94
94
|
"tooling/bun/bunfig.toml",
|
|
95
95
|
"tooling/nx/nx.json",
|
|
96
96
|
"tooling/changesets/config.json",
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
"types": "./tooling/docusaurus/index.d.mts",
|
|
202
202
|
"import": "./tooling/docusaurus/index.mjs"
|
|
203
203
|
},
|
|
204
|
-
"./biome": "./tooling/biome/
|
|
204
|
+
"./biome": "./tooling/biome/preset.json",
|
|
205
205
|
"./nx": "./tooling/nx/nx.json",
|
|
206
206
|
"./changesets": "./tooling/changesets/config.json",
|
|
207
207
|
"./release-please": "./tooling/release-please/release-please-config.json",
|