71 lines
2.8 KiB
JavaScript
71 lines
2.8 KiB
JavaScript
// Integrity checks for .github/areas.json -- the single source of truth for both
|
|
// issue triage and PR reviewer assignment. Run offline: `node .github/workflows/areas.test.js`
|
|
// (cwd = repo root). No network. Guards the invariants the two workflows rely on.
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const areas = JSON.parse(fs.readFileSync(path.resolve(".github/areas.json"), "utf8")).areas;
|
|
const maint = new Set(
|
|
fs.readFileSync(path.resolve(".github/MAINTAINER"), "utf8")
|
|
.split("\n").map((l) => l.replace(/#.*/, "").trim().toLowerCase()).filter(Boolean)
|
|
);
|
|
|
|
// The 8 comp:* labels that exist in the repo (gh cannot add a label that does not
|
|
// exist, and there is no label-sync). Every area label must be one of these.
|
|
const ALLOWED_LABELS = new Set([
|
|
"comp:server", "comp:runner", "comp:repr", "comp:web-ui",
|
|
"comp:tui", "comp:policies", "comp:harnesses", "comp:infra",
|
|
]);
|
|
|
|
let failures = 0;
|
|
function assert(name, cond, detail) {
|
|
console.log(`${cond ? "PASS" : "FAIL"} ${name}${detail ? " -- " + detail : ""}`);
|
|
if (!cond) failures++;
|
|
}
|
|
|
|
// Every owner is a known maintainer.
|
|
for (const a of areas)
|
|
for (const o of a.owners || [])
|
|
assert(`owner @${o} (area ${a.key}) is in MAINTAINER`, maint.has(o.toLowerCase()));
|
|
|
|
// Every label is one of the real comp:* labels.
|
|
for (const a of areas)
|
|
assert(`area ${a.key} label ${a.label} is a real comp:*`, ALLOWED_LABELS.has(a.label));
|
|
|
|
// Every area has >= 2 owners (the 2+ codeowner requirement).
|
|
for (const a of areas) {
|
|
const n = (a.owners || []).length;
|
|
assert(`area ${a.key} has >= 2 owners`, n >= 2, `${n} owner(s)`);
|
|
}
|
|
|
|
// Every area has a definition and at least one path.
|
|
for (const a of areas) {
|
|
assert(`area ${a.key} has a definition`, typeof a.definition === "string" && a.definition.length > 0);
|
|
assert(`area ${a.key} has paths`, Array.isArray(a.paths) && a.paths.length > 0);
|
|
}
|
|
|
|
// Path resolution (last-match-wins startsWith) sends representative files to the
|
|
// expected area -- especially the web/ carve-out ordering and harness prefixes.
|
|
function resolve(fn) {
|
|
let match = null;
|
|
for (const a of areas) for (const p of a.paths) if (fn.startsWith(p)) match = a;
|
|
return match;
|
|
}
|
|
const cases = [
|
|
["omnigent/inner/foo.py", "inner"],
|
|
["omnigent/inner/claude_sdk_executor.py", "harness-claude"],
|
|
["omnigent/inner/kimi_executor.py", "harness-kimi"],
|
|
["omnigent/inner/kiro_native_harness.py", "harness-kiro"],
|
|
["web/src/main.tsx", "web"],
|
|
["web/ios/App.swift", "mobile-app"],
|
|
["web/electron/main.ts", "desktop-app"],
|
|
["omnigent/server/api.py", "server"],
|
|
];
|
|
for (const [fn, key] of cases) {
|
|
const m = resolve(fn);
|
|
assert(`${fn} -> ${key}`, m && m.key === key, m ? m.key : "(unmatched)");
|
|
}
|
|
|
|
console.log(failures ? `\n${failures} FAILURE(S)` : "\nAll areas.json integrity checks passed.");
|
|
process.exitCode = failures ? 1 : 0;
|