@rtorcato/repo-tooling 3.31.2 → 3.32.0

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.
@@ -232,9 +232,11 @@ async function runBaseChecks(dir, lock, opts) {
232
232
  results.push(await checkAiSetup(dir));
233
233
  // User-global, not repo state — see checkClaudeSkills on why it never returns drift.
234
234
  results.push(await checkClaudeSkills(opts.skillsDir));
235
- // #533: gated on `aiLoop`, which is already the "this repo uses the pipeline"
235
+ // #533: gated on `aiLoop.agentUser`, which is the "this repo uses the pipeline"
236
236
  // signal, so a repo that doesn't gets no line at all rather than an empty one.
237
- if (lock?.rules?.aiLoop && lock.rules.requiredSkills?.length) {
237
+ // The key itself no longer says that — since #571 every repo is scaffolded with
238
+ // an empty `aiLoop`, and the skills have always read the login, not the key.
239
+ if (lock?.rules?.aiLoop?.agentUser && lock.rules.requiredSkills?.length) {
238
240
  results.push(await checkRequiredSkills(lock.rules.requiredSkills, opts.skillsDir));
239
241
  }
240
242
  // #534: advisory. Absent `mcp.recommended` means the repo has nothing to say
@@ -91,7 +91,14 @@ export async function generateConfigs(config, targetDir) {
91
91
  await ensureBuildApprovals(config, targetDir);
92
92
  // Family-wide pnpm settings (#314). Runs last of the workspace writers so it
93
93
  // merges into whatever they wrote rather than racing them for the file.
94
- await ensurePnpmSettings(targetDir, bundlerNeedsEsbuild(config), familyGlob(config.projectName));
94
+ //
95
+ // The scope is read back off the emitted package.json rather than taken from
96
+ // config.projectName: under `--preset` that name is only the directory
97
+ // basename, while generatePackageJson preserves an existing scoped `name`.
98
+ // doctor derives the glob from that same field, so reading the basename left
99
+ // the two disagreeing on every scoped repo (#573).
100
+ const { name } = (await fs.readJson(path.join(targetDir, 'package.json')));
101
+ await ensurePnpmSettings(targetDir, bundlerNeedsEsbuild(config), familyGlob(name));
95
102
  // Turborepo task pipeline (pnpm-workspace monorepos, when opted-in)
96
103
  if (config.turborepo) {
97
104
  await generateTurborepo(targetDir);
@@ -24,10 +24,16 @@ export const WORKSPACE_FILE = 'pnpm-workspace.yaml';
24
24
  *
25
25
  * One glob covers a whole scope: pnpm matches these entries with
26
26
  * `@pnpm/config.matcher`, so there's no package list to keep in sync.
27
+ *
28
+ * The scope is matched against npm's own name charset, not just "anything up
29
+ * to the slash": the name comes verbatim from a pre-existing `package.json`
30
+ * and is never validated as an npm name on the way here, so a scope of a lone
31
+ * wildcard would otherwise be taken as a glob and exempt every scoped package
32
+ * from the release-age delay. An unparseable scope gets no setting at all.
27
33
  */
28
34
  export function familyGlob(packageName) {
29
35
  const name = typeof packageName === 'string' ? packageName : '';
30
- const scope = /^(@[^/]+)\//.exec(name)?.[1];
36
+ const scope = /^(@[a-z0-9-][a-z0-9._-]*)\//i.exec(name)?.[1];
31
37
  return scope ? `${scope}/*` : null;
32
38
  }
33
39
  /** Bundlers that pull in esbuild, whose install script pnpm 11 refuses to run unapproved. */
@@ -27,6 +27,21 @@ const LOCKFILE_SCHEMA_URL = 'https://rtorcato.github.io/repo-tooling/schemas/loc
27
27
  * branches on it beyond printing it.
28
28
  */
29
29
  export const MCP_IMPORTANCE = ['nice-to-have', 'important', 'critical'];
30
+ /**
31
+ * The empty ruleset a repo with no stated rules gets (#571). `rules` used to
32
+ * appear only via v1–v3 migration, so the repos this tool creates were exactly
33
+ * the ones with nothing to edit and nothing to compare. Writing the containers
34
+ * — with `$schema` stamped alongside — makes the file teach its own shape.
35
+ *
36
+ * Values, never defaults: every entry is empty, because a populated one would
37
+ * be this tool asserting a rule on a repo whose humans have not stated any.
38
+ */
39
+ export const DEFAULT_RULES = {
40
+ aiLoop: {},
41
+ requiredSkills: [],
42
+ mcp: { recommended: [] },
43
+ exceptions: {},
44
+ };
30
45
  /**
31
46
  * JSON Schema for the lockfile, published with the docs site at the exact URL
32
47
  * every written lockfile's `$schema` points to (#529). The `satisfies` clauses
@@ -246,7 +261,8 @@ export async function writeLockfile(dir, config, assets) {
246
261
  writtenBy: `@rtorcato/repo-tooling@${await getToolVersion()}`,
247
262
  writtenAt: new Date().toISOString(),
248
263
  },
249
- ...(existing?.rules ? { rules: existing.rules } : {}),
264
+ // A repo that has stated no rules gets the empty scaffold, not nothing (#571).
265
+ rules: existing?.rules ?? DEFAULT_RULES,
250
266
  };
251
267
  await fs.writeJson(filepath, lockfile, { spaces: 2 });
252
268
  // Migrate a pre-rename repo to the new name: now that the canonical file is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtorcato/repo-tooling",
3
- "version": "3.31.2",
3
+ "version": "3.32.0",
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": [