import { readFileSync, readdirSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { describe, expect, test } from "vitest";
const ROOTS = ["src", "tests", "benchmarks", "scripts", "dashboard/src"].map((r) =>
join(process.cwd(), r),
);
function walk(dir: string, out: string[] = []): string[] {
for (const name of readdirSync(dir)) {
const p = join(dir, name);
if (statSync(p).isDirectory()) walk(p, out);
else if (p.endsWith(".ts") && !p.endsWith(".d.ts")) out.push(p);
}
return out;
}
const FILES = ROOTS.flatMap((root) => {
try {
return walk(root);
} catch {
return [];
}
}).map((p) => ({
path: p,
rel: relative(process.cwd(), p),
src: readFileSync(p, "utf8"),
}));
function blockComments(src: string): Array<{ start: number; lines: number; body: string }> {
const out: Array<{ start: number; lines: number; body: string }> = [];
const lines = src.split("\n");
let i = 0;
while (i < lines.length) {
const line = lines[i];
const open = line.indexOf("/*");
const before = open === -1 ? "" : line.slice(0, open);
if (open !== -1 && before.indexOf("//") === -1 && !/["'`]/.test(before)) {
const startLine = i + 1;
const buf: string[] = [line.slice(open)];
const closeOnSame = line.indexOf("*/", open + 2);
if (closeOnSame !== -1) {
out.push({ start: startLine, lines: 1, body: line.slice(open, closeOnSame + 2) });
i++;
continue;
}
let j = i + 1;
while (j < lines.length && lines[j].indexOf("*/") === -1) {
buf.push(lines[j]);
j++;
}
if (j < lines.length) buf.push(lines[j]);
out.push({ start: startLine, lines: j - i + 1, body: buf.join("\n") });
i = j + 1;
continue;
}
i++;
}
return out;
}
const VERSION_RE = /\bv\d+\.\d+(?:\.\d+)?\b/;
const PHASE_RE = /\bPhase\s+\d+\b/i;
const BANNER_RE = /\/\/\s*[─=\-*#]{3,}/;
const INCIDENT_RE =
/\b(user reported|screenshot showed|incident|hotfix for|fix for #?\d|introduced in v|regression from v|pre-v\d|legacy v)/i;
const BARE_TODO_RE = /(?:^|[^A-Za-z0-9_])(TODO|HACK)(?!\(#\d+\))/;
const FIXME_RE = /(?:^|[^A-Za-z0-9_])FIXME\b/;
const TRANSLATOR_NOTE_RE =
/\b(translator(?:'s)? note|english version|chinese version|英文版|中文版)\b/i;
describe("comment policy (CLAUDE.md)", () => {
test("module-essay headers ≤ 2 lines", () => {
const offenders: string[] = [];
for (const { rel, src } of FILES) {
const trimmed = src.replace(/^\uFEFF/, "").trimStart();
if (!trimmed.startsWith("/*")) continue;
const blocks = blockComments(src);
const head = blocks[0];
if (!head) continue;
const beforeHead = src.slice(0, src.indexOf(head.body)).trim();
if (beforeHead.length > 0) continue;
if (head.lines > 2) offenders.push(`${rel}:${head.start} — header is ${head.lines} lines`);
}
expect(offenders, format(offenders, "Module headers must be ≤ 2 lines.")).toEqual([]);
});
test("no Phase N narrative", () => {
const offenders = scan(FILES, (line) => PHASE_RE.test(line));
expect(offenders, format(offenders, 'No "Phase N" narrative in source comments.')).toEqual([]);
});
test("no version-number narrative in comments", () => {
const offenders: string[] = [];
for (const { rel, src } of FILES) {
const lines = src.split("\n");
lines.forEach((line, idx) => {
const c = commentText(line);
if (c && VERSION_RE.test(c))
offenders.push(`${rel}:${idx + 1} — ${line.trim().slice(0, 100)}`);
});
}
expect(
offenders,
format(offenders, "No version refs in comments — put them in commit/PR text."),
).toEqual([]);
});
test("no incident / conversation narrative", () => {
const offenders = scan(FILES, (line) => {
const c = commentText(line);
return c ? INCIDENT_RE.test(c) : false;
});
expect(offenders, format(offenders, "No incident/conversation history in comments.")).toEqual(
[],
);
});
test("no banner separator comments", () => {
const offenders = scan(FILES, (line) => BANNER_RE.test(line));
expect(offenders, format(offenders, "No section-banner separator comments.")).toEqual([]);
});
test("TODO / HACK markers must carry a (#nnn) issue anchor", () => {
const offenders: string[] = [];
for (const { rel, src } of FILES) {
const lines = src.split("\n");
lines.forEach((line, idx) => {
const c = commentText(line);
if (c && BARE_TODO_RE.test(c))
offenders.push(`${rel}:${idx + 1} — ${line.trim().slice(0, 100)}`);
});
}
expect(
offenders,
format(offenders, "Bare TODO/HACK is debt leakage — write `TODO(#nnn): ...`."),
).toEqual([]);
});
test("no FIXME markers — fix it or open an issue + TODO", () => {
const offenders: string[] = [];
for (const { rel, src } of FILES) {
const lines = src.split("\n");
lines.forEach((line, idx) => {
const c = commentText(line);
if (c && FIXME_RE.test(c))
offenders.push(`${rel}:${idx + 1} — ${line.trim().slice(0, 100)}`);
});
}
expect(offenders, format(offenders, "FIXME is banned — use TODO(#nnn) or fix now.")).toEqual(
[],
);
});
test("no translator-note comments", () => {
const offenders: string[] = [];
for (const { rel, src } of FILES) {
const lines = src.split("\n");
lines.forEach((line, idx) => {
const c = commentText(line);
if (c && TRANSLATOR_NOTE_RE.test(c))
offenders.push(`${rel}:${idx + 1} — ${line.trim().slice(0, 100)}`);
});
}
expect(
offenders,
format(offenders, "i18n strings are self-documenting — no translator notes."),
).toEqual([]);
});
test("no multi-line block comments > 3 lines", () => {
const offenders: string[] = [];
for (const { rel, src } of FILES) {
for (const b of blockComments(src)) {
if (b.lines > 3) offenders.push(`${rel}:${b.start} — ${b.lines}-line block`);
}
}
expect(
offenders,
format(offenders, "Block comments capped at 3 lines (one-line preferred)."),
).toEqual([]);
});
});
function commentText(line: string): string | null {
const m = line.match(/(?:^|\s)\/\/(.*)$/);
if (m) return m[1];
const m2 = line.match(/\/\*+(.*?)\*?\/?$/);
if (m2) return m2[1];
const m3 = line.match(/^\s*\*\s?(.*)$/);
if (m3) return m3[1];
return null;
}
function scan(files: typeof FILES, pred: (line: string) => boolean): string[] {
const out: string[] = [];
for (const { rel, src } of files) {
src.split("\n").forEach((line, idx) => {
if (pred(line)) out.push(`${rel}:${idx + 1} — ${line.trim().slice(0, 100)}`);
});
}
return out;
}
function format(offenders: string[], rule: string): string {
if (offenders.length === 0) return "";
const head = `${rule} ${offenders.length} violation(s):`;
const list = offenders
.slice(0, 30)
.map((o) => ` ${o}`)
.join("\n");
const tail = offenders.length > 30 ? `\n … +${offenders.length - 30} more` : "";
return `\n${head}\n${list}${tail}\n`;
}