feat(M1): Baseline verification, quality tooling foundation, and current-state report

This commit is contained in:
Stefano Fiorini
2026-05-03 19:26:55 -05:00
parent 2deab1c1b4
commit 0443381aa0
19 changed files with 4100 additions and 2 deletions
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env node
/**
* run-check.mjs — aggregate quality check runner (M1, S-106)
*
* Runs every quality gate in sequence and reports a summary.
* All steps run even if earlier steps fail, so you get a complete
* picture of the repository health in one pass.
*
* Transitional contract (M1):
* This script may exit non-zero. Pre-existing failures are recorded in
* docs/CLEANUP-BASELINE.md. Only issues introduced by new changes (not
* listed in the baseline) constitute a regression.
*
* Usage:
* node scripts/lib/run-check.mjs # full check
* pnpm run check # same, via pnpm
*/
import { spawnSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, "../..");
// ── Steps ──────────────────────────────────────────────────────────────────
const STEPS = [
{ label: "lint", cmd: "pnpm", args: ["run", "lint"] },
{ label: "typecheck", cmd: "pnpm", args: ["run", "typecheck"] },
{ label: "test", cmd: "pnpm", args: ["run", "test"] },
{ label: "verify:pi", cmd: "pnpm", args: ["run", "verify:pi"] },
{ label: "verify:reviewers", cmd: "pnpm", args: ["run", "verify:reviewers"] },
{ label: "verify:docs", cmd: "pnpm", args: ["run", "verify:docs"] },
{ label: "verify:generated", cmd: "pnpm", args: ["run", "verify:generated"] },
];
// ── Runner ─────────────────────────────────────────────────────────────────
const RESET = "\x1b[0m";
const GREEN = "\x1b[32m";
const RED = "\x1b[31m";
const BOLD = "\x1b[1m";
const DIM = "\x1b[2m";
function colorize(color, text) {
// Respect NO_COLOR env variable
if (process.env.NO_COLOR || process.env.CI) return text;
return `${color}${text}${RESET}`;
}
const results = [];
for (const step of STEPS) {
process.stdout.write(`\n${colorize(BOLD, `=== ${step.label} ===`)}\n`);
const result = spawnSync(step.cmd, step.args, {
cwd: REPO_ROOT,
stdio: "inherit",
encoding: "utf8",
shell: false,
});
const ok = result.status === 0 && !result.error;
results.push({ label: step.label, ok, status: result.status ?? -1 });
}
// ── Summary ────────────────────────────────────────────────────────────────
process.stdout.write(`\n${colorize(BOLD, "=== check summary ===")}\n`);
const failures = [];
for (const r of results) {
if (r.ok) {
process.stdout.write(
` ${colorize(GREEN, "PASS")} ${r.label}\n`
);
} else {
process.stdout.write(
` ${colorize(RED, "FAIL")} ${r.label} ${colorize(DIM, `(exit ${r.status})`)} — see docs/CLEANUP-BASELINE.md if pre-existing\n`
);
failures.push(r.label);
}
}
process.stdout.write("\n");
if (failures.length === 0) {
process.stdout.write(colorize(GREEN, "All checks passed.\n"));
process.exit(0);
} else {
process.stdout.write(
colorize(
RED,
`${failures.length} check(s) failed: ${failures.join(", ")}\n`
)
);
process.exit(1);
}
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env node
/**
* run-link-check.mjs — markdown link-check runner (M1, S-104)
*
* Runs markdown-link-check across README.md, docs/, and every SKILL.md
* (excluding node_modules and generated agent-variant directories).
*
* Modes:
* --offline (default) — checks only repo-relative links and #anchor links.
* All http/https links are ignored. Safe for CI and local dev
* without network access.
* --online — checks all links, including external URLs, with timeouts
* and retries as configured in markdown-link-check.online.json.
*
* Exit codes:
* 0 — all checked links are alive (or ignored in offline mode)
* 1 — one or more broken links found
*/
import { spawnSync } from "node:child_process";
import { readdirSync, existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, "../..");
// ── CLI arguments ──────────────────────────────────────────────────────────
const online = process.argv.includes("--online");
const configFile = online
? path.join(REPO_ROOT, "markdown-link-check.online.json")
: path.join(REPO_ROOT, "markdown-link-check.json");
// ── File discovery ─────────────────────────────────────────────────────────
const SKIP_PATHS = new Set([
"skills/atlassian/codex",
"skills/atlassian/claude-code",
"skills/atlassian/cursor",
"skills/atlassian/opencode",
"skills/atlassian/pi",
"skills/web-automation/claude-code",
"skills/web-automation/cursor",
"skills/web-automation/opencode",
"skills/web-automation/pi",
"pi-package",
]);
function shouldSkip(absPath) {
const rel = path.relative(REPO_ROOT, absPath);
for (const skip of SKIP_PATHS) {
if (rel === skip || rel.startsWith(skip + path.sep)) return true;
}
const parts = rel.split(path.sep);
return parts.includes("node_modules");
}
function collectMarkdownFiles(dir) {
const found = [];
let entries;
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
return found;
}
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (shouldSkip(full)) continue;
if (entry.isDirectory()) {
found.push(...collectMarkdownFiles(full));
} else if (
entry.isFile() &&
(entry.name.endsWith(".md") || entry.name === "SKILL.md")
) {
found.push(full);
}
}
return found;
}
// ── Collect target files ───────────────────────────────────────────────────
const files = [
path.join(REPO_ROOT, "README.md"),
...collectMarkdownFiles(path.join(REPO_ROOT, "docs")),
...collectMarkdownFiles(path.join(REPO_ROOT, "skills")),
].filter(existsSync);
// De-duplicate (README.md could appear twice)
const uniqueFiles = [...new Set(files)];
if (uniqueFiles.length === 0) {
console.log("link-check: no markdown files found — nothing to check.");
process.exit(0);
}
console.log(
`link-check: checking ${uniqueFiles.length} file(s) ` +
`[mode: ${online ? "online" : "offline"}]…`
);
// ── Run markdown-link-check ────────────────────────────────────────────────
const mlcBin = path.join(
REPO_ROOT,
"node_modules",
".bin",
"markdown-link-check"
);
let failures = 0;
for (const file of uniqueFiles.sort()) {
const result = spawnSync(
mlcBin,
["--config", configFile, "--quiet", file],
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
);
const output = (result.stdout + result.stderr).trim();
const rel = path.relative(REPO_ROOT, file);
if (result.status !== 0) {
failures += 1;
console.error(`\n--- ${rel} ---`);
if (output) console.error(output);
}
}
if (failures > 0) {
console.error(
`\nlink-check: ${failures} file(s) have broken links. ` +
`See docs/CLEANUP-BASELINE.md for the as-is baseline.`
);
process.exit(1);
} else {
console.log("link-check: all links OK.");
process.exit(0);
}
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env node
/**
* run-shellcheck.mjs — shell script quality wrapper (M1, S-105)
*
* Discovers *.sh files under scripts/ and skills/ (excluding node_modules
* and generated agent-variant directories), then runs shellcheck on each.
*
* shellcheck is a REQUIRED prerequisite. The script fails immediately when
* shellcheck is not found on PATH. Install it with:
* macOS: brew install shellcheck
* Debian: apt-get install shellcheck
*
* Exit codes:
* 0 — all files passed shellcheck
* 1 — one or more files have shellcheck findings
* 2 — shellcheck is missing from PATH
*/
import { spawnSync } from "node:child_process";
import { readdirSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, "../..");
// ── Prerequisites check ────────────────────────────────────────────────────
function checkShellcheck() {
const result = spawnSync("shellcheck", ["--version"], { encoding: "utf8" });
if (result.error || result.status === null || result.status > 1) {
console.error(
[
"ERROR: shellcheck is not available on PATH.",
"",
"shellcheck is a required prerequisite for this repository.",
"Install it before running lint:",
"",
" macOS: brew install shellcheck",
" Debian/Ubuntu: sudo apt-get install shellcheck",
" Other: https://github.com/koalaman/shellcheck#installing",
].join("\n")
);
process.exit(2);
}
}
// ── File discovery ─────────────────────────────────────────────────────────
/**
* Directories to scan for *.sh files.
* Relative paths from REPO_ROOT.
*/
const SCAN_DIRS = ["scripts", "skills"];
/**
* Path segments that indicate a directory should be skipped entirely.
* Matches generated agent-variant bundles and npm/pnpm install artifacts.
*/
const SKIP_SEGMENTS = new Set([
"node_modules",
// Generated agent-variant directories (excluded per M1 workspace policy)
// skills/<skill>/codex — except web-automation/codex which is canonical
// We skip all codex variants to be safe; shellcheck only cares about .sh files
// and all variants share the same scripts anyway.
]);
/**
* Exact relative paths (from REPO_ROOT) to skip, matched after normalisation.
* These are the generated variants that duplicate canonical source.
*/
const SKIP_PATHS = new Set([
"skills/atlassian/codex",
"skills/atlassian/claude-code",
"skills/atlassian/cursor",
"skills/atlassian/opencode",
"skills/atlassian/pi",
"skills/web-automation/claude-code",
"skills/web-automation/cursor",
"skills/web-automation/opencode",
"skills/web-automation/pi",
"pi-package",
]);
function shouldSkip(absPath) {
const rel = path.relative(REPO_ROOT, absPath);
// Check exact-prefix matches (directory and its children)
for (const skip of SKIP_PATHS) {
if (rel === skip || rel.startsWith(skip + path.sep)) return true;
}
// Check path-segment matches (e.g. node_modules anywhere in path)
for (const seg of rel.split(path.sep)) {
if (SKIP_SEGMENTS.has(seg)) return true;
}
return false;
}
function collectShellFiles(dir) {
const found = [];
let entries;
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
return found; // directory does not exist — skip silently
}
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (shouldSkip(full)) continue;
if (entry.isDirectory()) {
found.push(...collectShellFiles(full));
} else if (entry.isFile() && entry.name.endsWith(".sh")) {
found.push(full);
}
}
return found;
}
// ── Main ───────────────────────────────────────────────────────────────────
checkShellcheck();
const files = SCAN_DIRS.flatMap((d) =>
collectShellFiles(path.join(REPO_ROOT, d))
);
if (files.length === 0) {
console.log("shellcheck: no .sh files found — nothing to check.");
process.exit(0);
}
console.log(`shellcheck: scanning ${files.length} file(s)…`);
let failures = 0;
for (const file of files.sort()) {
const result = spawnSync("shellcheck", [file], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
const output = (result.stdout + result.stderr).trim();
const rel = path.relative(REPO_ROOT, file);
if (result.status !== 0) {
failures += 1;
// Print findings prefixed with the relative path so output is reproducible
// regardless of cwd.
console.error(`\n--- ${rel} ---`);
if (output) console.error(output);
} else {
// Quiet on success — only show problems
}
}
if (failures > 0) {
console.error(
`\nshellcheck: ${failures} file(s) have findings. ` +
`See docs/CLEANUP-BASELINE.md for the as-is baseline.`
);
process.exit(1);
} else {
console.log("shellcheck: all files passed.");
process.exit(0);
}