feat(checks): unify inspection scope in checks/scope.ts

Single source of truth for what pygienium inspects (implementation-code
extensions, exclude dirs, .d.ts/.min.* exclusions) shared by recon,
dead-code, deep-modules, defensive-guards, comments, and complexity.
Every scan task and agent prompt injects the shared scope rules instead
of copy-pasted per-check lists.
This commit is contained in:
2026-08-09 16:45:29 -04:00
parent 581436ed23
commit 2caeb2f790
10 changed files with 268 additions and 112 deletions

View File

@@ -34,6 +34,11 @@ import {
type CheckDefinition,
type CheckScope,
} from "./registry.js";
import {
isScopeSource,
SCOPE_EXCLUDE_DIRS,
scopeRulesMarkdown,
} from "./scope.js";
/** Dead-code categories recognised by the rubric. */
export type DeadCategory = "export" | "file" | "shim" | "dep";
@@ -82,43 +87,6 @@ export const CATEGORY_LABELS: Record<DeadCategory, string> = {
dep: "Unused dependencies",
};
/** Source extensions worth scanning for import graphs. */
const SOURCE_EXTENSIONS = new Set([
".ts",
".tsx",
".js",
".jsx",
".mjs",
".cjs",
".py",
".rb",
".go",
".rs",
".java",
".kt",
".swift",
".php",
".cs",
".lua",
]);
/** Directories never scanned (build output, deps, tooling). */
const IGNORED_DIRS = new Set([
"node_modules",
".git",
".pygienium",
"dist",
"build",
"out",
"coverage",
".next",
".nuxt",
".venv",
"__pycache__",
"vendor",
".ralpi",
]);
/** Extensions tried when resolving a bare import specifier. */
const RESOLVE_EXTENSIONS = [
".ts",
@@ -131,11 +99,6 @@ const RESOLVE_EXTENSIONS = [
".cts",
];
const EXT = (name: string): string => {
const i = name.lastIndexOf(".");
return i === -1 ? "" : name.slice(i).toLowerCase();
};
/** Filename signals of a compat/deprecated/migration shim. */
const SHIM_NAME_RE =
/(?:compat|legacy|deprecated|obsolete|obsoleted|migration|migrate|backcompat|back-compat|fallback|shim)/i;
@@ -161,7 +124,7 @@ function depStem(spec: string): string {
async function walkSourceFiles(root: string): Promise<string[]> {
const st = await stat(root).catch(() => undefined);
if (!st) return [];
if (st.isFile()) return SOURCE_EXTENSIONS.has(EXT(root)) ? [root] : [];
if (st.isFile()) return isScopeSource(root) ? [root] : [];
const out: string[] = [];
const stack = [root];
while (stack.length > 0) {
@@ -175,9 +138,9 @@ async function walkSourceFiles(root: string): Promise<string[]> {
for (const entry of entries) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
if (IGNORED_DIRS.has(entry.name)) continue;
if (SCOPE_EXCLUDE_DIRS.has(entry.name)) continue;
stack.push(full);
} else if (entry.isFile() && SOURCE_EXTENSIONS.has(EXT(entry.name))) {
} else if (entry.isFile() && isScopeSource(entry.name)) {
out.push(full);
}
}
@@ -1069,6 +1032,8 @@ export async function buildDeadCodeScanTask(
"```",
``,
`Target root: ${scope.target}. Work dir: ${cwd}.`,
``,
scopeRulesMarkdown(),
].join("\n");
}