@rtorcato/repo-tooling 3.8.6 → 3.8.7

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.
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'fs-extra';
3
+ import { BLOCK_START, hasStaleAgentBlock } from '../cli/generators/agent-rules.js';
3
4
  import { BADGE_START, hasPublicOnlyBadges } from '../cli/generators/badges.js';
4
5
  import { detectNestedLanguages } from '../cli/utils/detect-language.js';
5
6
  /**
@@ -335,19 +336,37 @@ export async function checkCoverageUpload(dir) {
335
336
  };
336
337
  }
337
338
  export async function checkAiSetup(dir) {
338
- // Consider AI setup present if AGENTS.md carries the js-tooling block or the
339
- // Claude skill is installed — the two primary markers `fix ai` writes.
340
- const agentsPath = path.join(dir, 'AGENTS.md');
341
- const hasAgentsBlock = (await fs.pathExists(agentsPath)) &&
342
- (await fs.readFile(agentsPath, 'utf8')).includes('<!-- js-tooling:start -->');
339
+ // Consider AI setup present if one of the block-managed files carries the
340
+ // agent block or the Claude skill is installed — the markers `fix ai` writes.
341
+ let blockFile = null;
342
+ const stale = [];
343
+ for (const rel of ['AGENTS.md', 'CLAUDE.md', path.join('.github', 'copilot-instructions.md')]) {
344
+ const filepath = path.join(dir, rel);
345
+ if (!(await fs.pathExists(filepath)))
346
+ continue;
347
+ const content = await fs.readFile(filepath, 'utf8');
348
+ if (!content.includes(BLOCK_START))
349
+ continue;
350
+ blockFile ??= rel;
351
+ if (hasStaleAgentBlock(content))
352
+ stale.push(rel);
353
+ }
343
354
  const hasSkill = (await fs.pathExists(path.join(dir, '.claude', 'skills', 'repo-tooling.md'))) ||
344
355
  // Pre-rename name — still counts as present until `fix` migrates it.
345
356
  (await fs.pathExists(path.join(dir, '.claude', 'skills', 'js-tooling.md')));
346
- if (hasAgentsBlock || hasSkill) {
357
+ if (stale.length > 0) {
358
+ return {
359
+ check: 'AI setup',
360
+ status: 'drift',
361
+ detail: `${stale.join(', ')} still point agents at the dead \`js-tooling\` package/bin`,
362
+ hint: 'Run `npx @rtorcato/repo-tooling fix ai` to refresh the agent instructions in place',
363
+ };
364
+ }
365
+ if (blockFile || hasSkill) {
347
366
  return {
348
367
  check: 'AI setup',
349
368
  status: 'ok',
350
- detail: hasAgentsBlock ? 'AGENTS.md has the js-tooling block' : '.claude skill installed',
369
+ detail: blockFile ? `${blockFile} has the agent block` : '.claude skill installed',
351
370
  };
352
371
  }
353
372
  return {
@@ -352,6 +352,14 @@ const PROTECTION_BODY = JSON.stringify({
352
352
  // enforce_admins off so the App/RELEASE_TOKEN can bypass to push release commits.
353
353
  enforce_admins: false,
354
354
  // Required human review would deadlock solo Dependabot auto-merge.
355
+ //
356
+ // Note this is *applied*, not merely tolerated: checkBranchProtection reports
357
+ // any required_pull_request_reviews as drift, so `fix github-settings` PUTs it
358
+ // back to null. A repo that deliberately requires approvals will have that
359
+ // silently reverted by the next unrelated fix run, with nothing in the output
360
+ // naming the rule that was removed. Known downstream case: the ai-issue-loop
361
+ // pipeline works around it with approval labels precisely because of this.
362
+ // Loosen the standard here first if a repo ever genuinely needs review gating.
355
363
  required_pull_request_reviews: null,
356
364
  restrictions: null,
357
365
  allow_force_pushes: false,
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'fs-extra';
3
3
  import { copyPreset, getPackageRoot } from '../utils/copy-preset.js';
4
+ import { LEGACY_TOOL_NAME } from '../utils/lockfile.js';
4
5
  import { installSkillsInstallDocs } from './skills-install.js';
5
6
  /**
6
7
  * All agent rule files are generated from one source of truth — the shipped
@@ -8,8 +9,25 @@ import { installSkillsInstallDocs } from './skills-install.js';
8
9
  * location and the frontmatter differ per agent.
9
10
  */
10
11
  const SOURCE = 'tooling/claude/repo-tooling.md';
11
- const BLOCK_START = '<!-- js-tooling:start -->';
12
- const BLOCK_END = '<!-- js-tooling:end -->';
12
+ // The markers keep the pre-rename text on purpose: changing them would orphan
13
+ // every block already written and append a second one on the next fix.
14
+ export const BLOCK_START = '<!-- js-tooling:start -->';
15
+ export const BLOCK_END = '<!-- js-tooling:end -->';
16
+ /**
17
+ * True when a managed block still names the pre-rename package or bin — a repo
18
+ * scaffolded before the rename tells agents to run `js-tooling fix ai`, which
19
+ * is not a runnable command (#393). Only the block *body* is inspected: the
20
+ * markers carry the legacy name by design, so matching on the whole file would
21
+ * report every repo as permanently stale.
22
+ */
23
+ export function hasStaleAgentBlock(content) {
24
+ const start = content.indexOf(BLOCK_START);
25
+ if (start === -1)
26
+ return false;
27
+ const bodyStart = start + BLOCK_START.length;
28
+ const end = content.indexOf(BLOCK_END, bodyStart);
29
+ return content.slice(bodyStart, end === -1 ? undefined : end).includes(LEGACY_TOOL_NAME);
30
+ }
13
31
  /** Read the shipped skill and split its frontmatter from the markdown body. */
14
32
  async function readSkill() {
15
33
  const raw = await fs.readFile(path.join(getPackageRoot(), SOURCE), 'utf8');
@@ -3,10 +3,14 @@ import fs from 'fs-extra';
3
3
  import packageJson from '../../../package.json' with { type: 'json' };
4
4
  import { validateProjectConfig } from '../commands/setup-presets.js';
5
5
  export const LOCKFILE_NAME = '.repo-tooling.json';
6
- // Name used before the js-tooling→repo-tooling rename (#272). Repos set up on an
7
- // older version still have it: readLockfile falls back to it, and writeLockfile
8
- // migrates to the new name (removing the old file) on the next write.
9
- export const LEGACY_LOCKFILE_NAME = '.js-tooling.json';
6
+ // Package and bin name used before the js-tooling→repo-tooling rename (#272).
7
+ // The bin no longer exists and the package is 404 on the registry, so any
8
+ // generated file still naming it is stale (#393).
9
+ export const LEGACY_TOOL_NAME = 'js-tooling';
10
+ // Lockfile name from the same era. Repos set up on an older version still have
11
+ // it: readLockfile falls back to it, and writeLockfile migrates to the new name
12
+ // (removing the old file) on the next write.
13
+ export const LEGACY_LOCKFILE_NAME = `.${LEGACY_TOOL_NAME}.json`;
10
14
  // v2 added ProjectConfig.language (multi-language seam, #140). v1 files are
11
15
  // migrated to v2 on read, defaulting language to 'js'.
12
16
  export const LOCKFILE_VERSION = 2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtorcato/repo-tooling",
3
- "version": "3.8.6",
3
+ "version": "3.8.7",
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": [