From 2ab634468750b7baba13e00fcee792c4396fae33 Mon Sep 17 00:00:00 2001 From: Mike Stankavich Date: Mon, 31 Aug 2026 06:51:27 -0500 Subject: [PATCH 1/2] chore: add a checker for CONTRIBUTING.md references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CONTRIBUTING.md rotted in the other repos because nothing read it. No gate, no session, no CI, and evidently no human since the files it named stopped existing. Staleness announces itself when someone follows an instruction and it fails — but only if someone follows it. This is that someone. It parses CONTRIBUTING.md and asserts that every pnpm script and every repo path it names still resolves. Two false positives were found by running it rather than reasoning about it, and both would have made the gate untrustworthy. A branch-name example, `docs/tra-0000-update-getting-started`, matched a naive a/b path pattern; a path claim now has to end in a known file extension or a trailing slash, which branch examples do not. An API route, `/api/v1/`, matched too; a leading slash now marks a span as absolute rather than repo-relative. A check that fails on a correct file is a check someone turns off. Verified by planting a breakage rather than trusting a green run: a `pnpm nonexistent-script` reference and a `docs/not-a-real-file.md` path were both reported, exit 1, then removed. Refs: TRA-1219 --- scripts/check-contributing.mjs | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 scripts/check-contributing.mjs diff --git a/scripts/check-contributing.mjs b/scripts/check-contributing.mjs new file mode 100644 index 0000000..d8b4dd8 --- /dev/null +++ b/scripts/check-contributing.mjs @@ -0,0 +1,81 @@ +#!/usr/bin/env node +// Gives CONTRIBUTING.md a consumer. +// +// Nothing read that file, so it rotted: dead `cp` commands, branch conventions +// nobody followed, a rule contradicting CLAUDE.md. Staleness announces itself +// when someone follows an instruction and it fails — but only if someone does. +// This is that someone. Run from `pnpm lint`, so the validate gate catches drift. + +import { readFileSync, existsSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join, resolve } from "node:path"; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const docPath = join(repoRoot, "CONTRIBUTING.md"); + +// pnpm subcommands that are pnpm's own, not this repo's scripts. +const PNPM_BUILTINS = new Set([ + "install", + "add", + "remove", + "update", + "dlx", + "exec", + "run", + "why", + "outdated", + "audit", + "store", + "list", + "test", + "start", + "publish", +]); + +// Things that look like paths but are not repo files: URLs, placeholders, and +// absolute paths — a leading slash means an API route (`/api/v1/`), not a file. +const NOT_REPO_PATHS = /^(https?:|\/|<|YOUR_|\.\.?$|node_modules)/; + +const text = readFileSync(docPath, "utf8"); +const problems = []; + +// --- pnpm scripts ----------------------------------------------------------- +const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")); +const scripts = new Set(Object.keys(pkg.scripts ?? {})); + +for (const [, name] of text.matchAll(/\bpnpm\s+([a-z][a-z0-9:_-]*)/gi)) { + if (PNPM_BUILTINS.has(name) || scripts.has(name)) continue; + problems.push(`pnpm script "${name}" is referenced but not in package.json`); +} + +// --- repo-relative paths in inline code spans ------------------------------- +// Only inline spans: fenced blocks and prose carry examples, not claims. +const inlineCode = [...text.matchAll(/`([^`\n]+)`/g)].map((m) => m[1].trim()); + +// A span is a path claim only if it ends in a known file extension or a +// trailing slash. Anything looser also matches branch-name examples such as +// `docs/tra-0000-update-getting-started`, which are illustrations rather than +// claims — and a check that fails on a correct file is a check someone turns +// off. +const FILE_EXT = /\.(md|json|ts|tsx|js|mjs|yml|yaml|sql|sh)$/; + +for (const span of inlineCode) { + const looksLikePath = FILE_EXT.test(span) || span.endsWith("/"); + if (!looksLikePath || NOT_REPO_PATHS.test(span)) continue; + if (!existsSync(join(repoRoot, span))) { + problems.push(`path \`${span}\` is referenced but does not exist`); + } +} + +// --- report ----------------------------------------------------------------- +if (problems.length > 0) { + console.error("CONTRIBUTING.md references things that no longer exist:\n"); + for (const p of [...new Set(problems)]) console.error(` - ${p}`); + console.error( + "\nFix the file or the reference. This check exists because nothing else\n" + + "reads CONTRIBUTING.md, so drift here is otherwise silent.", + ); + process.exit(1); +} + +console.log("CONTRIBUTING.md: all referenced scripts and paths resolve."); From 4f044d9f077bbb6465f8be854447a58183e3bf71 Mon Sep 17 00:00:00 2001 From: Mike Stankavich Date: Mon, 31 Aug 2026 06:51:27 -0500 Subject: [PATCH 2/2] chore: run the CONTRIBUTING.md checker from pnpm lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A checker nothing invokes is the problem it was written to solve. Wiring it into lint puts it inside the validate command declared in .claude/csw.json, so drift fails the same gate everything else does. Also notes at the top of CONTRIBUTING.md that the file is checked, so the next person editing it learns that from the file rather than from a failing build. This is one repo's answer to the ticket's item 3 — give the file a consumer — and it does not settle the cross-repo question of whether the other repos want the same mechanism. Refs: TRA-1219 --- CONTRIBUTING.md | 3 +++ package.json | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d879086..4f95b6d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,9 @@ We love contributions! This guide will help you get started quickly. +> Editing this file? `pnpm lint` checks that every `pnpm` script and repo path +> named here still exists. + ## What is this project? TrakRF Docs is the documentation site for the TrakRF platform, built with [Docusaurus](https://docusaurus.io/). It covers product documentation, API references, guides, and tutorials. diff --git a/package.json b/package.json index 22bbbb5..53a343f 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,13 @@ "build": "docusaurus build", "serve": "docusaurus serve --host 0.0.0.0", "typecheck": "tsc", - "lint": "prettier --check .", + "lint": "prettier --check . && node scripts/check-contributing.mjs", "lint:fix": "prettier --write .", "clear": "docusaurus clear", "swizzle": "docusaurus swizzle", "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" + "write-heading-ids": "docusaurus write-heading-ids", + "check:contributing": "node scripts/check-contributing.mjs" }, "dependencies": { "@docusaurus/core": "3.9.2",