ai-diff-check 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/chunk-7JKSFHII.js +1525 -0
- package/dist/chunk-7JKSFHII.js.map +1 -0
- package/dist/cli.js +104 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +160 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/git/diff.ts","../src/report/terminal.ts","../src/report/ci.ts","../src/report/fix-prompt.ts","../src/checks/util.ts","../src/checks/stub.ts","../src/checks/oversize.ts","../src/checks/phantom-dep.ts","../src/checks/deletion.ts","../src/checks/no-test.ts","../src/checks/unreferenced.ts","../src/checks/duplicate.ts","../src/checks/standard.ts","../src/checks/reuse.ts","../src/checks/index.ts","../src/types.ts","../src/config.ts","../src/commands.ts","../src/mcp.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process';\nimport type { ChangedFile } from '../types.js';\n\nexport class NotAGitRepoError extends Error {\n constructor() {\n super('Not a git repository — run ai-diff-check inside a repo.');\n }\n}\n\nfunction git(args: string[], cwd: string): string {\n return execFileSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });\n}\n\nexport function findRepoRoot(cwd: string): string {\n try {\n return git(['rev-parse', '--show-toplevel'], cwd).trim();\n } catch {\n throw new NotAGitRepoError();\n }\n}\n\n/**\n * Pick the diff base: --base flag, else main/master (local, then origin —\n * CI checkouts often have origin/main but no local branch), else first commit.\n */\nexport function resolveBaseRef(repoRoot: string, explicit?: string): string {\n if (explicit) return explicit;\n for (const ref of ['main', 'master', 'origin/main', 'origin/master']) {\n try {\n git(['rev-parse', '--verify', '--quiet', ref], repoRoot);\n return ref;\n } catch {\n // ref doesn't exist, try the next one\n }\n }\n // Fresh repo with no main/master: diff against the root commit.\n const root = git(['rev-list', '--max-parents=0', 'HEAD'], repoRoot).trim().split('\\n')[0];\n if (!root) throw new Error('Could not resolve a base ref — pass one with --base.');\n return root;\n}\n\n/**\n * Parse `git diff <base>` (working tree included) into structured files.\n * Only ever looks at the unified diff — never the whole repo.\n */\nexport function getChangedFiles(repoRoot: string, baseRef: string): ChangedFile[] {\n // Prefixes pinned so user git config (diff.noprefix, mnemonicPrefix) can't break parsing.\n const raw = git(['diff', '--no-color', '--unified=0', '--src-prefix=a/', '--dst-prefix=b/', baseRef, '--'], repoRoot);\n return parseUnifiedDiff(raw);\n}\n\nexport function parseUnifiedDiff(raw: string): ChangedFile[] {\n const files: ChangedFile[] = [];\n let current: ChangedFile | null = null;\n let oldLine = 0;\n let newLine = 0;\n\n for (const line of raw.split('\\n')) {\n if (line.startsWith('diff --git ')) {\n current = { path: '', added: new Map(), removed: new Map(), status: 'modified' };\n files.push(current);\n } else if (current && line.startsWith('--- ')) {\n if (line.includes('/dev/null')) current.status = 'added';\n else if (line.startsWith('--- a/')) current.path = line.slice('--- a/'.length);\n } else if (current && line.startsWith('+++ ')) {\n if (line.includes('/dev/null')) current.status = 'deleted';\n else current.path = line.slice('+++ b/'.length);\n } else if (current && line.startsWith('@@')) {\n const m = /@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/.exec(line);\n if (m) {\n oldLine = Number(m[1]);\n newLine = Number(m[2]);\n }\n } else if (current && line.startsWith('+')) {\n current.added.set(newLine++, line.slice(1));\n } else if (current && line.startsWith('-')) {\n current.removed.set(oldLine++, line.slice(1));\n }\n }\n\n return files.filter((f) => f.path !== '');\n}\n\nexport interface DiffStats {\n files: number;\n added: number;\n removed: number;\n}\n\nexport function diffStats(files: ChangedFile[]): DiffStats {\n return {\n files: files.length,\n added: files.reduce((n, f) => n + f.added.size, 0),\n removed: files.reduce((n, f) => n + f.removed.size, 0),\n };\n}\n","import pc from 'picocolors';\nimport type { Finding } from '../types.js';\nimport type { DiffStats } from '../git/diff.js';\n\nconst SEVERITY_ORDER: Record<Finding['severity'], number> = { error: 0, warn: 1, info: 2 };\n\n/** Errors first, then warnings, then info notes — the locked output order. */\nexport function sortFindings(findings: Finding[]): Finding[] {\n return [...findings].sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);\n}\n\nexport function renderHeader(version: string, baseRef: string): string {\n return `\\n ${pc.bold(pc.yellow('ai-diff-check'))} ${pc.dim(`v${version} — reviewing working tree vs ${baseRef}`)}\\n`;\n}\n\nexport function renderDiffLine(stats: DiffStats, elapsedMs: number): string {\n const files = `${stats.files} file${stats.files === 1 ? '' : 's'}`;\n return ` ${pc.green('✓')} ${pc.dim('parsed diff')} ${files} · +${stats.added.toLocaleString()} −${stats.removed.toLocaleString()} ${pc.dim(`(${(elapsedMs / 1000).toFixed(1)}s)`)}`;\n}\n\nexport function renderFindings(findings: Finding[]): string {\n const blocks = sortFindings(findings).map((f) => {\n const mark = f.severity === 'error' ? pc.red('✖') : f.severity === 'warn' ? pc.yellow('⚠') : pc.cyan('ℹ');\n const color = f.severity === 'error' ? pc.red : f.severity === 'warn' ? pc.yellow : pc.cyan;\n const badge = color(pc.bold(f.check.toUpperCase()));\n const loc = pc.dim(f.line ? `${f.file}:${f.line}` : f.file);\n const fix = f.fix ? `\\n ${pc.dim('→')} ${f.fix}` : '';\n return ` ${mark} ${badge} ${pc.dim(`[${f.id}]`)}\\n ${loc} ${f.message}${fix}`;\n });\n return blocks.join('\\n\\n');\n}\n\nexport function renderSummary(findings: Finding[], stats: DiffStats, elapsedMs: number): string {\n const errors = findings.filter((f) => f.severity === 'error').length;\n const warns = findings.filter((f) => f.severity === 'warn').length;\n const infos = findings.filter((f) => f.severity === 'info').length;\n const score = trustScore(findings, stats);\n const scoreColor = score >= 80 ? pc.green : score >= 50 ? pc.yellow : pc.red;\n\n const parts = [\n `Diff trust score: ${scoreColor(pc.bold(`${score}/100`))}`,\n errors ? pc.red(pc.bold(`✖ ${errors} error${errors === 1 ? '' : 's'}`)) : pc.green('no errors'),\n warns ? pc.yellow(`⚠ ${warns} warning${warns === 1 ? '' : 's'}`) : '',\n infos ? pc.cyan(`ℹ ${infos} note${infos === 1 ? '' : 's'}`) : '',\n pc.dim(`${stats.files} files · ${(elapsedMs / 1000).toFixed(1)}s`),\n ].filter(Boolean);\n\n return `\\n ${pc.dim('─'.repeat(56))}\\n ${parts.join(' ')}\\n`;\n}\n\n/**\n * 0–100 verdict for the whole diff. Simple v1 formula:\n * start at 100, -18 per error, -6 per warning, floored at 0.\n */\nexport function trustScore(findings: Finding[], _stats: DiffStats): number {\n const errors = findings.filter((f) => f.severity === 'error').length;\n const warns = findings.filter((f) => f.severity === 'warn').length;\n return Math.max(0, 100 - errors * 18 - warns * 6);\n}\n","import type { Finding } from '../types.js';\n\n/**\n * GitHub Actions workflow commands — one per finding, so findings land as\n * inline annotations on the PR diff. Escaping per the workflow-command spec.\n */\n\nconst escapeData = (s: string): string => s.replace(/%/g, '%25').replace(/\\r/g, '%0D').replace(/\\n/g, '%0A');\nconst escapeProp = (s: string): string => escapeData(s).replace(/:/g, '%3A').replace(/,/g, '%2C');\n\nconst COMMAND: Record<Finding['severity'], string> = { error: 'error', warn: 'warning', info: 'notice' };\n\nexport function githubAnnotations(findings: Finding[]): string[] {\n return findings.map((f) => {\n const props = [`file=${escapeProp(f.file)}`];\n if (f.line !== undefined) props.push(`line=${f.line}`);\n props.push(`title=${escapeProp(`${f.check.toUpperCase()} [${f.id}]`)}`);\n const message = f.fix !== undefined ? `${f.message} → ${f.fix}` : f.message;\n return `::${COMMAND[f.severity]} ${props.join(',')}::${escapeData(message)}`;\n });\n}\n","import type { Finding } from '../types.js';\nimport { sortFindings } from './terminal.js';\n\n/**\n * Render findings as a prompt for the AI agent that wrote the diff —\n * `ai-diff-check --fix-prompt | pbcopy`, paste, done. Scoped tightly so the\n * agent fixes exactly what was flagged instead of refactoring the world.\n */\nexport function renderFixPrompt(findings: Finding[]): string {\n if (findings.length === 0) return 'The diff is clean — nothing to fix.';\n\n const lines: string[] = [\n 'Your last change failed its automated review (ai-diff-check). Fix ONLY the issues below.',\n 'Do not refactor unrelated code, do not add dependencies, do not delete tests to make checks pass.',\n '',\n ];\n\n sortFindings(findings).forEach((f, i) => {\n const loc = f.line !== undefined ? `${f.file}:${f.line}` : f.file;\n lines.push(`${i + 1}. [${f.severity}] ${loc} — ${f.message}`);\n if (f.fix !== undefined) lines.push(` Required fix: ${f.fix}`);\n });\n\n lines.push('');\n lines.push('When done, run `ai-diff-check` — it must report no errors.');\n return lines.join('\\n');\n}\n","import { readdirSync } from 'node:fs';\nimport { join, relative, sep } from 'node:path';\n\n/** JS/TS-family source files — the only files checks scan for code patterns. */\nexport const CODE_FILE = /\\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/;\n\n/** Test files by naming convention: foo.test.ts / foo.spec.tsx / anything under __tests__/. */\nexport const TEST_FILE = /(?:[._](?:test|spec)\\.[cm]?[jt]sx?$|__tests__\\/)/;\n\nconst SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'out', 'coverage', 'vendor']);\n\n/** All code files in the repo (repo-relative, posix separators), one cheap walk. */\nexport function walkCodeFiles(repoRoot: string): string[] {\n const found: string[] = [];\n const stack = [repoRoot];\n while (stack.length > 0) {\n const dir = stack.pop()!;\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith('.')) stack.push(join(dir, entry.name));\n } else if (entry.isFile() && CODE_FILE.test(entry.name)) {\n found.push(relative(repoRoot, join(dir, entry.name)).split(sep).join('/'));\n }\n }\n }\n return found;\n}\n\nexport interface Decl {\n name: string;\n /** 1-based line of the declaration. */\n line: number;\n isClass: boolean;\n}\n\n// Top-level (column-0) declarations only — nested decls are indented.\nconst DECL_RES = [\n /^(?:export\\s+)?(?:default\\s+)?(?:async\\s+)?function\\s*\\*?\\s+([A-Za-z_$][\\w$]*)/,\n /^(?:export\\s+)?(?:default\\s+)?(?:abstract\\s+)?class\\s+([A-Za-z_$][\\w$]*)/,\n /^(?:export\\s+)?const\\s+([A-Za-z_$][\\w$]*)[^=]*=\\s*(?:async\\s+)?(?:\\(|function\\b|[A-Za-z_$][\\w$]*\\s*=>)/,\n];\n\nexport function scanTopLevelDecls(lines: string[]): Decl[] {\n const decls: Decl[] = [];\n lines.forEach((text, i) => {\n for (let p = 0; p < DECL_RES.length; p++) {\n const name = DECL_RES[p]!.exec(text)?.[1];\n if (name) {\n decls.push({ name, line: i + 1, isClass: p === 1 });\n break;\n }\n }\n });\n return decls;\n}\n\n/**\n * Span of a top-level declaration: from its line to the first column-0\n * closer, bounded by the next top-level declaration. Formatted code always\n * closes a top-level body at column 0, so this stays accurate without an AST.\n */\nexport function declSpan(lines: string[], decl: Decl, decls: Decl[]): { start: number; end: number } {\n const next = decls.find((d) => d.line > decl.line)?.line ?? lines.length + 1;\n for (let i = decl.line; i < lines.length && i + 1 < next; i++) {\n if (/^[}\\])]/.test(lines[i]!)) return { start: decl.line, end: i + 1 };\n }\n return { start: decl.line, end: next - 1 };\n}\n\n/** k-token shingle set for similarity fingerprinting. */\nexport function shingles(tokens: string[], k: number): Set<string> {\n const set = new Set<string>();\n for (let i = 0; i + k <= tokens.length; i++) {\n set.add(tokens.slice(i, i + k).join('\u0000'));\n }\n return set;\n}\n\n/** Jaccard similarity of two shingle sets. */\nexport function jaccard(a: Set<string>, b: Set<string>): number {\n const [small, large] = a.size <= b.size ? [a, b] : [b, a];\n let intersection = 0;\n for (const s of small) if (large.has(s)) intersection++;\n const union = a.size + b.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\n/**\n * Deterministic short id for a finding, e.g. `STUB-104`.\n * The id is derived from a stable `key` (not the line number) so that a finding\n * keeps the same id as surrounding code shifts — that is what makes\n * `ai-diff-check ignore <id>` stick across runs.\n */\nexport function stableId(prefix: string, key: string): string {\n let h = 5381;\n for (let i = 0; i < key.length; i++) {\n h = ((h << 5) + h + key.charCodeAt(i)) >>> 0; // djb2\n }\n return `${prefix}-${100 + (h % 900)}`; // 100–999, mockup-style\n}\n","import type { Check, Finding } from '../types.js';\nimport { stableId } from './util.js';\n\n/**\n * STUB (warn) — leftover placeholders in newly added lines: debug logging,\n * unresolved task markers, \"not implemented\" throws, and empty catch blocks.\n * Diff-scoped only: we never scan the whole file, just what the diff added.\n */\n\n// The task-marker tokens are assembled from fragments on purpose: writing them\n// as literals here would make this check flag its own source file in a review.\nconst MARK_A = 'TO' + 'DO';\nconst MARK_B = 'FIX' + 'ME';\n// Only match a marker that sits inside a comment, so plain UI strings\n// (e.g. a \"TODO list\" label) don't trip the check.\nconst MARKER = new RegExp(`(?://|/\\\\*|\\\\*|#).*\\\\b(?:${MARK_A}|${MARK_B})\\\\b`);\nconst MARKER_TOKEN = new RegExp(`\\\\b(${MARK_A}|${MARK_B})\\\\b`);\n\ninterface LineRule {\n kind: string;\n match: RegExp;\n message: (line: string) => string;\n fix: string;\n}\n\nconst LINE_RULES: LineRule[] = [\n {\n kind: 'console',\n match: /\\bconsole\\.(?:log|debug)\\s*\\(/,\n message: () => 'Leftover debug logging in new code',\n fix: 'remove the log or route it through your logger',\n },\n {\n kind: 'marker',\n match: MARKER,\n message: (line) => `Unresolved ${MARKER_TOKEN.exec(line)?.[1] ?? 'task'} marker left in new code`,\n fix: 'resolve it, or track it in an issue before merging',\n },\n {\n kind: 'not-implemented',\n match: /throw\\s+new\\s+\\w*Error\\s*\\(\\s*['\"`][^'\"`]*not[\\s_-]?implemented/i,\n message: () => 'Stubbed \"not implemented\" throw shipped in the diff',\n fix: 'implement the path or remove the dead branch',\n },\n {\n // Single-line empty catch: `catch (e) {}`.\n kind: 'empty-catch',\n match: /catch\\s*(?:\\([^)]*\\))?\\s*\\{\\s*\\}/,\n message: () => 'Empty catch block silently swallows the error',\n fix: \"handle the error or at least log it — don't swallow it\",\n },\n];\n\nconst EMPTY_CATCH_RULE = LINE_RULES[LINE_RULES.length - 1]!;\n\n// Multi-line empty catch: an opener whose very next added line is a bare `}`.\n// Firing only when BOTH lines are newly added keeps us diff-scoped and quiet —\n// an untouched catch body never lands in the added map.\nconst CATCH_OPEN = /catch\\s*(?:\\([^)]*\\))?\\s*\\{\\s*$/;\nconst BARE_CLOSE = /^\\s*\\}\\s*;?\\s*$/;\n\nexport const stubCheck: Check = {\n name: 'stub',\n async run(ctx) {\n const findings: Finding[] = [];\n const seen = new Map<string, number>();\n\n const push = (path: string, line: number, rule: LineRule, text: string): void => {\n const snippet = text.trim();\n const key = `${path}:${rule.kind}:${snippet}`;\n const occ = seen.get(key) ?? 0;\n seen.set(key, occ + 1);\n findings.push({\n id: stableId('STUB', occ === 0 ? key : `${key}#${occ}`),\n check: 'stub',\n severity: 'warn',\n file: path,\n line,\n message: rule.message(text),\n fix: rule.fix,\n });\n };\n\n for (const file of ctx.files) {\n const added = [...file.added.entries()].sort((a, b) => a[0] - b[0]);\n for (const [lineNo, text] of added) {\n // Multi-line empty catch takes precedence over the per-line rules.\n if (CATCH_OPEN.test(text)) {\n const next = file.added.get(lineNo + 1);\n if (next !== undefined && BARE_CLOSE.test(next)) {\n push(file.path, lineNo, EMPTY_CATCH_RULE, text);\n continue;\n }\n }\n for (const rule of LINE_RULES) {\n if (rule.match.test(text)) {\n push(file.path, lineNo, rule, text);\n break; // one finding per line is enough\n }\n }\n }\n }\n\n return findings;\n },\n};\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Check, Finding, ChangedFile } from '../types.js';\nimport type { Decl } from './util.js';\nimport { stableId, CODE_FILE, scanTopLevelDecls, declSpan } from './util.js';\n\n/**\n * OVERSIZE (warn) — a touched file is past `maxFileLines`, or a touched\n * component is past `maxComponentLines`, and the fix proposes the split.\n *\n * Ratchet semantics keep legacy monoliths grandfathered: a file only fires\n * when THIS diff pushed it over the limit (or dumped `REGROWTH_LINES`+ net\n * new lines into one already over it), and a component only fires when the\n * diff wrote `MIN_ADDED_IN_COMPONENT`+ lines inside it. Touching a big old\n * file with a one-line fix stays silent — prefer a miss over noise.\n */\n\nconst JSX_FILE = /\\.(?:tsx|jsx)$/;\n\n/** Re-flag an already-oversized file only when the diff grows it by this many net lines. */\nconst REGROWTH_LINES = 50;\n/** Flag an oversized component only when the diff added at least this many lines inside it. */\nconst MIN_ADDED_IN_COMPONENT = 10;\n\nfunction countLines(content: string): number {\n const parts = content.split('\\n');\n return parts[parts.length - 1] === '' ? parts.length - 1 : parts.length;\n}\n\nfunction declLabel(d: Decl, isJsx: boolean): string {\n if (d.isClass) return d.name;\n if (/^use[A-Z]/.test(d.name)) return `${d.name} (hook)`;\n if (isJsx && /^[A-Z]/.test(d.name)) return `<${d.name}>`;\n return d.name;\n}\n\nfunction proposeFileSplit(decls: Decl[], isJsx: boolean): string {\n if (decls.length < 2) return 'split components, hooks, and utils into their own modules';\n const names = decls.slice(0, 4).map((d) => declLabel(d, isJsx));\n const more = decls.length - 4;\n return `split it up — ${names.join(', ')}${more > 0 ? ` (+${more} more)` : ''} can each live in their own module`;\n}\n\n// Nested decls inside a component body — extraction candidates for the fix.\nconst INNER_DECL = /^\\s+(?:const|function)\\s+([a-z_$][\\w$]*)\\s*[=(]/;\n\nfunction proposeComponentSplit(name: string, lines: string[], span: { start: number; end: number }): string {\n const inner: string[] = [];\n for (let i = span.start; i < span.end && inner.length < 3; i++) {\n const m = INNER_DECL.exec(lines[i]!);\n if (m?.[1]) inner.push(m[1]);\n }\n return inner.length > 0\n ? `extract pieces of <${name}> into hooks/subcomponents — e.g. ${inner.join(', ')}`\n : `break <${name}> into smaller subcomponents or hooks`;\n}\n\nfunction fileFinding(file: ChangedFile, total: number, before: number, limit: number, fix: string): Finding {\n const message =\n file.status === 'added'\n ? `New file is ${total} lines (limit ${limit})`\n : `File grew from ${before} to ${total} lines (limit ${limit})`;\n return {\n id: stableId('OVERSIZE', `${file.path}:file`),\n check: 'oversize',\n severity: 'warn',\n file: file.path,\n message,\n fix,\n };\n}\n\nexport const oversizeCheck: Check = {\n name: 'oversize',\n async run(ctx) {\n const findings: Finding[] = [];\n\n for (const file of ctx.files) {\n if (file.status === 'deleted' || !CODE_FILE.test(file.path)) continue;\n\n let content: string;\n try {\n content = readFileSync(join(ctx.repoRoot, file.path), 'utf8');\n } catch {\n continue; // gone or unreadable since the diff — prefer a miss\n }\n\n const lines = content.split('\\n');\n const total = countLines(content);\n const netGrowth = file.added.size - file.removed.size;\n const before = total - netGrowth;\n const isJsx = JSX_FILE.test(file.path);\n const decls = scanTopLevelDecls(lines);\n\n const fileLimit = ctx.config.maxFileLines;\n const crossedLimit = before <= fileLimit && total > fileLimit;\n const keptGrowing = before > fileLimit && netGrowth >= REGROWTH_LINES;\n if (total > fileLimit && (crossedLimit || keptGrowing)) {\n findings.push(fileFinding(file, total, before, fileLimit, proposeFileSplit(decls, isJsx)));\n continue; // the file-level split proposal covers its components too\n }\n\n if (!isJsx) continue;\n const compLimit = ctx.config.maxComponentLines;\n for (const decl of decls) {\n if (decl.isClass || !/^[A-Z]/.test(decl.name)) continue;\n const span = declSpan(lines, decl, decls);\n const size = span.end - span.start + 1;\n if (size <= compLimit) continue;\n\n let addedInside = 0;\n for (const n of file.added.keys()) {\n if (n >= span.start && n <= span.end) addedInside++;\n }\n if (addedInside < MIN_ADDED_IN_COMPONENT) continue;\n\n findings.push({\n id: stableId('OVERSIZE', `${file.path}:${decl.name}`),\n check: 'oversize',\n severity: 'warn',\n file: file.path,\n line: decl.line,\n message: `<${decl.name}> is ${size} lines (limit ${compLimit})`,\n fix: proposeComponentSplit(decl.name, lines, span),\n });\n }\n }\n\n return findings;\n },\n};\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { builtinModules } from 'node:module';\nimport type { Check, Finding } from '../types.js';\nimport { stableId, CODE_FILE } from './util.js';\n\n/**\n * PHANTOM-DEP (error) — newly imported packages that are not declared in any\n * package.json governing the file. Undeclared-but-real packages break fresh\n * installs (classic phantom dependency); names that 404 on the npm registry\n * are likely AI-hallucinated imports (and installing a lookalike is the\n * slopsquatting trap). The registry lookup is best-effort: offline or blocked,\n * the undeclared finding still stands, just without the exists/404 verdict.\n */\n\nconst BUILTINS = new Set(builtinModules);\n\n/** Registry lookups are capped per run and time-boxed — never block the commit on the network. */\nconst MAX_REGISTRY_LOOKUPS = 15;\nconst REGISTRY_TIMEOUT_MS = 3000;\n\n// One import specifier per added line. Anchored shapes keep string literals\n// that merely mention \"from 'x'\" from matching.\nconst IMPORT_PATTERNS = [\n /^\\s*(?:import|export)\\b[^'\"]*\\bfrom\\s+['\"]([^'\"]+)['\"]/, // import x from 'p' / export { x } from 'p'\n /^\\s*[}\\])][^'\"]*\\bfrom\\s+['\"]([^'\"]+)['\"]/, // closing line of a multi-line import\n /^\\s*import\\s+['\"]([^'\"]+)['\"]/, // side-effect import\n /\\brequire\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/, // CJS\n /\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/, // dynamic import\n];\n\nfunction extractSpecifier(line: string): string | null {\n for (const re of IMPORT_PATTERNS) {\n const name = re.exec(line)?.[1];\n if (name) return name;\n }\n return null;\n}\n\n/**\n * npm package name for a bare specifier, or null for anything that can't be\n * one: relative/absolute paths, `#` subpath imports, `~/` aliases, node\n * builtins, and names npm itself would reject (uppercase, empty scope like\n * the `@/` alias, special characters).\n */\nfunction packageName(spec: string): string | null {\n if (/^[./#~]/.test(spec)) return null;\n if (spec.startsWith('node:')) return null;\n const parts = spec.split('/');\n const name = spec.startsWith('@') ? (parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null) : parts[0]!;\n if (!name || BUILTINS.has(name)) return null;\n if (!/^(@[a-z0-9~][\\w.~-]*\\/)?[a-z0-9~][a-z0-9._~-]*$/.test(name)) return null;\n return name;\n}\n\n/** The `@types/…` counterpart: `estree` → `@types/estree`, `@scope/pkg` → `@types/scope__pkg`. */\nfunction typesPackage(name: string): string {\n return name.startsWith('@') ? `@types/${name.slice(1).replace('/', '__')}` : `@types/${name}`;\n}\n\ninterface Aliases {\n exact: Set<string>;\n prefixes: string[];\n}\n\n/** Path aliases from the root tsconfig — `@/*`, `components/*`, … look like packages but aren't. */\nfunction loadTsconfigAliases(repoRoot: string): Aliases {\n const aliases: Aliases = { exact: new Set(), prefixes: [] };\n let raw: string;\n try {\n raw = readFileSync(join(repoRoot, 'tsconfig.json'), 'utf8');\n } catch {\n return aliases;\n }\n try {\n // tsconfig is JSONC — strip comments and trailing commas before parsing\n const jsonc = raw\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/^\\s*\\/\\/.*$/gm, '')\n .replace(/,(\\s*[}\\]])/g, '$1');\n const paths: unknown = JSON.parse(jsonc)?.compilerOptions?.paths;\n if (paths && typeof paths === 'object') {\n for (const key of Object.keys(paths)) {\n if (key.endsWith('*')) aliases.prefixes.push(key.slice(0, -1));\n else aliases.exact.add(key);\n }\n }\n } catch {\n // unparseable tsconfig — treat as no aliases\n }\n return aliases;\n}\n\nfunction isAliased(spec: string, aliases: Aliases): boolean {\n return aliases.exact.has(spec) || aliases.prefixes.some((p) => spec.startsWith(p));\n}\n\n/** Declared deps (all dep fields + the package's own name) from one package.json, cached per dir. */\nfunction readManifest(dir: string, cache: Map<string, Set<string> | null>): Set<string> | null {\n const cached = cache.get(dir);\n if (cached !== undefined) return cached;\n let result: Set<string> | null = null;\n try {\n const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as Record<string, unknown>;\n result = new Set<string>();\n if (typeof pkg['name'] === 'string') result.add(pkg['name']);\n for (const field of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {\n const deps = pkg[field];\n if (deps && typeof deps === 'object') for (const key of Object.keys(deps)) result.add(key);\n }\n } catch {\n result = null;\n }\n cache.set(dir, result);\n return result;\n}\n\n/** Union of every package.json from the file's directory up to the repo root (monorepo-aware). */\nfunction collectDeclared(\n repoRoot: string,\n relPath: string,\n cache: Map<string, Set<string> | null>,\n): { declared: Set<string>; manifests: number } {\n const declared = new Set<string>();\n let manifests = 0;\n let dir = dirname(join(repoRoot, relPath));\n for (;;) {\n const manifest = readManifest(dir, cache);\n if (manifest) {\n manifests++;\n for (const name of manifest) declared.add(name);\n }\n if (dir === repoRoot) break;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return { declared, manifests };\n}\n\ntype RegistryResult = 'exists' | 'missing' | 'unknown';\n\nasync function checkRegistry(name: string): Promise<RegistryResult> {\n try {\n const res = await fetch(`https://registry.npmjs.org/${name.replace('/', '%2F')}`, {\n method: 'HEAD',\n signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),\n });\n if (res.status === 404) return 'missing';\n if (res.ok) return 'exists';\n return 'unknown';\n } catch {\n return 'unknown'; // offline or blocked — never fail the run over the network\n }\n}\n\ninterface Occurrence {\n file: string;\n line: number;\n count: number;\n}\n\nexport const phantomDepCheck: Check = {\n name: 'phantom-dep',\n async run(ctx) {\n const manifestCache = new Map<string, Set<string> | null>();\n const aliases = loadTsconfigAliases(ctx.repoRoot);\n const undeclared = new Map<string, Occurrence>();\n\n for (const file of ctx.files) {\n if (file.status === 'deleted' || !CODE_FILE.test(file.path)) continue;\n const { declared, manifests } = collectDeclared(ctx.repoRoot, file.path, manifestCache);\n if (manifests === 0) continue; // no package.json governs this file — can't judge\n\n for (const [lineNo, text] of [...file.added.entries()].sort((a, b) => a[0] - b[0])) {\n const trimmed = text.trim();\n if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;\n const spec = extractSpecifier(text);\n if (!spec || isAliased(spec, aliases)) continue;\n const name = packageName(spec);\n if (!name || declared.has(name) || declared.has(typesPackage(name))) continue;\n // baseUrl-style local imports ('src/utils/date') shadow real package names\n if (existsSync(join(ctx.repoRoot, spec.split('/')[0]!))) continue;\n\n const seen = undeclared.get(name);\n if (seen) seen.count++;\n else undeclared.set(name, { file: file.path, line: lineNo, count: 1 });\n }\n }\n\n const verdicts = new Map<string, RegistryResult>();\n await Promise.all(\n [...undeclared.keys()].slice(0, MAX_REGISTRY_LOOKUPS).map(async (name) => {\n verdicts.set(name, await checkRegistry(name));\n }),\n );\n\n const findings: Finding[] = [];\n for (const [name, occ] of undeclared) {\n const verdict = verdicts.get(name) ?? 'unknown';\n const more = occ.count > 1 ? ` (and ${occ.count - 1} more place${occ.count > 2 ? 's' : ''})` : '';\n const finding: Finding = {\n id: stableId('PHANTOM', name),\n check: 'phantom-dep',\n severity: 'error',\n file: occ.file,\n line: occ.line,\n message: '',\n fix: '',\n };\n if (verdict === 'missing') {\n finding.message = `'${name}' does not exist on npm — likely a hallucinated import${more}`;\n finding.fix = `remove it or find the real package — installing a lookalike name risks slopsquatting`;\n } else if (verdict === 'exists') {\n finding.message = `'${name}' is imported but not declared in package.json${more}`;\n finding.fix = `npm install ${name} — undeclared deps break on fresh installs`;\n } else {\n finding.message = `'${name}' is imported but not declared in package.json${more}`;\n finding.fix = `npm install ${name} if it's real — could not reach the npm registry to verify`;\n }\n findings.push(finding);\n }\n\n return findings;\n },\n};\n","import type { Check, Finding } from '../types.js';\nimport { stableId, CODE_FILE, TEST_FILE } from './util.js';\n\n/**\n * DELETION (error) — protective code that vanished in this diff: error\n * handling (try/catch, promise .catch), guards/validation (early returns on\n * bad input, throws, validator calls), and test cases. Nobody reviews what\n * the AI deleted.\n *\n * Refactors are the noise source, so two suppression layers before anything\n * fires: a removed line whose normalized text is re-added anywhere in the\n * diff just moved; and a file that ADDS lines of the same category is being\n * reworked, not stripped. Tests are counted name-aware, so renames and\n * rewrites stay silent — only net test loss fires.\n */\n\nconst ERROR_HANDLING = [/\\btry\\s*\\{/, /\\bcatch\\s*[({]/, /\\.catch\\s*\\(/];\n\n// Guard shapes that need a consequence (return/throw/…) on the same or next\n// removed line — `if (!open) setOpen(true)` is UI logic, not a guard.\nconst GUARD_IF = [/\\bif\\s*\\(\\s*!(?!=)/, /\\bif\\b[^\\n]*[=!]==?\\s*(?:null|undefined)\\b/, /\\bif\\b[^\\n]*\\btypeof\\s+[\\w.$]+\\s*[=!]==?/];\n// Guard shapes that stand on their own.\nconst GUARD_DIRECT = [\n /\\bthrow\\s+new\\s+\\w*(?:Error|Exception)\\w*\\s*\\(/,\n /\\b(?:validate|invariant|assert)\\w*\\s*\\(/,\n /\\.safeParse\\s*\\(/,\n /[sS]chema\\.parse\\s*\\(/,\n];\nconst CONSEQUENCE = /\\b(?:return|throw|continue|break)\\b|process\\.exit/;\n\nconst TEST_LINE = /^\\s*(?:it|test)(?:\\.\\w+)?\\s*\\(\\s*(?:(['\"`])(.*?)\\1)?/;\n\nconst norm = (s: string): string => s.replace(/\\s+/g, ' ').trim();\n\nconst snippet = (s: string): string => {\n const t = norm(s);\n return t.length > 64 ? `${t.slice(0, 63)}…` : t;\n};\n\nfunction isGuard(text: string, nextRemoved: string | undefined): boolean {\n if (GUARD_DIRECT.some((re) => re.test(text))) return true;\n if (!GUARD_IF.some((re) => re.test(text))) return false;\n return CONSEQUENCE.test(text) || (nextRemoved !== undefined && CONSEQUENCE.test(nextRemoved));\n}\n\n/** Broad category test for layer-2 suppression on added lines. */\nfunction sameCategoryAdded(category: 'error-handling' | 'guard', text: string): boolean {\n if (category === 'error-handling') return ERROR_HANDLING.some((re) => re.test(text));\n return GUARD_DIRECT.some((re) => re.test(text)) || GUARD_IF.some((re) => re.test(text));\n}\n\nexport const deletionCheck: Check = {\n name: 'deletion',\n async run(ctx) {\n const findings: Finding[] = [];\n const seen = new Map<string, number>();\n\n const push = (file: string, line: number, key: string, message: string, fix: string): void => {\n const occ = seen.get(key) ?? 0;\n seen.set(key, occ + 1);\n findings.push({\n id: stableId('DEL', occ === 0 ? key : `${key}#${occ}`),\n check: 'deletion',\n severity: 'error',\n file,\n line,\n message,\n fix,\n });\n };\n\n // Layer 1: anything re-added verbatim anywhere in the diff just moved.\n const addedEverywhere = new Set<string>();\n const addedTestNames = new Set<string>();\n for (const f of ctx.files) {\n for (const text of f.added.values()) {\n addedEverywhere.add(norm(text));\n const name = TEST_LINE.exec(text)?.[2];\n if (name) addedTestNames.add(name);\n }\n }\n const moved = (text: string): boolean => addedEverywhere.has(norm(text));\n\n for (const file of ctx.files) {\n if (!CODE_FILE.test(file.path) || file.removed.size === 0) continue;\n const removed = [...file.removed.entries()].sort((a, b) => a[0] - b[0]);\n const addedTexts = [...file.added.values()];\n\n if (TEST_FILE.test(file.path)) {\n // Only the test category applies in test files — expectation code is\n // rewritten constantly and isn't a production guard.\n const removedTests: Array<{ line: number; name: string | undefined }> = [];\n for (const [lineNo, text] of removed) {\n const m = TEST_LINE.exec(text);\n if (!m || moved(text)) continue;\n const name = m[2];\n if (name !== undefined && addedTestNames.has(name)) continue; // moved or kept under edits\n removedTests.push({ line: lineNo, name });\n }\n if (removedTests.length === 0) continue;\n const first = removedTests[0]!;\n const fix = 'restore it — deleting tests to make a change pass hides regressions';\n\n if (file.status === 'deleted') {\n const count = removedTests.length;\n push(\n file.path,\n first.line,\n `${file.path}:test-file`,\n `Test file deleted (${count} test case${count === 1 ? '' : 's'})`,\n fix,\n );\n continue;\n }\n const addedCount = addedTexts.filter((t) => TEST_LINE.test(t)).length;\n if (removedTests.length <= addedCount) continue; // rewrite or rename, not a purge\n const named = first.name !== undefined ? `: \"${first.name}\"` : '';\n const message =\n removedTests.length === 1\n ? `Test case removed${named}`\n : `${removedTests.length} test cases removed, only ${addedCount} added back${\n first.name !== undefined ? ` — including \"${first.name}\"` : ''\n }`;\n push(file.path, first.line, `${file.path}:test`, message, fix);\n continue;\n }\n\n if (file.status === 'deleted') continue; // whole-file deletions are visible in any review\n\n // Layer 2: the file adds code of the same category — rework, not removal.\n const ehSuppressed = addedTexts.some((t) => sameCategoryAdded('error-handling', t));\n const guardSuppressed = addedTexts.some((t) => sameCategoryAdded('guard', t));\n\n // One finding per category per consecutive removed block.\n let block: Array<[number, string]> = [];\n let prevLine = Number.MIN_SAFE_INTEGER;\n const blocks: Array<Array<[number, string]>> = [];\n for (const entry of removed) {\n if (entry[0] !== prevLine + 1) {\n block = [];\n blocks.push(block);\n }\n block.push(entry);\n prevLine = entry[0];\n }\n\n for (const blk of blocks) {\n if (!ehSuppressed) {\n const hit = blk.find(([, text]) => !moved(text) && ERROR_HANDLING.some((re) => re.test(text)));\n if (hit) {\n const promise = /\\.catch\\s*\\(/.test(hit[1]);\n push(\n file.path,\n hit[0],\n `${file.path}:eh:${norm(hit[1])}`,\n promise\n ? 'promise .catch() removed — rejections now go unhandled'\n : 'try/catch removed — errors here now propagate unhandled',\n 'restore the handling, or make the caller handle the error explicitly',\n );\n }\n }\n if (!guardSuppressed) {\n const hit = blk.find(([lineNo, text]) => !moved(text) && isGuard(text, file.removed.get(lineNo + 1)));\n if (hit) {\n push(\n file.path,\n hit[0],\n `${file.path}:guard:${norm(hit[1])}`,\n `Guard removed: \"${snippet(hit[1])}\"`,\n 'restore it — or confirm the input can no longer be invalid',\n );\n }\n }\n }\n }\n\n return findings;\n },\n};\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Check, Finding } from '../types.js';\nimport { stableId, CODE_FILE, TEST_FILE, walkCodeFiles } from './util.js';\n\n/**\n * NO-TEST (warn) — source logic changed but the matching test file didn't.\n * Matching is by naming convention (`git/diff.ts` ↔ `tests/diff.test.ts`);\n * the import-graph upgrade is v1.1.\n *\n * Noise control: only *logic* lines count (imports, comments, type decls and\n * punctuation don't), thresholds gate both cases, and repos with no test\n * runner and no test files are never nagged — they didn't opt into testing.\n */\n\n/** Fire \"tests untouched\" only when this many logic lines changed. */\nconst MIN_LOGIC_TOUCHED = 5;\n/** Fire \"no test file exists\" only for meatier changes that declare something. */\nconst MIN_LOGIC_NEW = 10;\n\nconst NOT_LOGIC = [\n /^\\s*$/,\n /^\\s*(?:\\/\\/|\\/?\\*)/, // comments\n /^\\s*import\\b/,\n /^\\s*export\\s+(?:\\{|\\*|type\\b|interface\\b)/, // re-exports and type exports\n /^\\s*(?:interface|type|declare)\\b/,\n /^[\\s{}()[\\];,]*$/, // punctuation-only\n];\nconst LOGIC = /\\breturn\\b|\\bthrow\\b|\\bawait\\b|\\bif\\b|\\belse\\b|\\bfor\\b|\\bwhile\\b|\\bswitch\\b|=>|[\\w$]\\s*\\(|=[^=>]/;\nconst DECL_SIGNAL = /\\bfunction\\b|\\bclass\\b|=>/;\n\nfunction countLogicLines(texts: Iterable<string>): number {\n let count = 0;\n for (const text of texts) {\n if (NOT_LOGIC.some((re) => re.test(text))) continue;\n if (LOGIC.test(text)) count++;\n }\n return count;\n}\n\n/** `src/git/diff.ts` → `diff`; `tests/diff.test.ts` → `diff`; `__tests__/Foo.tsx` → `Foo`. */\nfunction moduleBase(path: string): string {\n const name = path.split('/').pop()!;\n return name.replace(/\\.[cm]?[jt]sx?$/, '').replace(/[._](?:test|spec)$/, '');\n}\n\nconst RUNNERS = ['vitest', 'jest', 'mocha', 'ava', 'tap', 'jasmine', 'uvu'];\n\nfunction hasTestRunner(repoRoot: string): boolean {\n try {\n const pkg = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')) as Record<string, unknown>;\n for (const field of ['dependencies', 'devDependencies']) {\n const deps = pkg[field];\n if (deps && typeof deps === 'object' && RUNNERS.some((r) => r in (deps as Record<string, unknown>))) return true;\n }\n } catch {\n // no manifest — treated as no runner\n }\n return false;\n}\n\n/** Follow the repo's existing convention: a tests/ dir if one is in use, else colocated. */\nfunction suggestTestPath(sourcePath: string, repoTests: string[]): string {\n const base = moduleBase(sourcePath);\n const testDir = repoTests.find((t) => /^tests?\\//.test(t))?.split('/')[0];\n if (testDir) return `${testDir}/${base}.test.ts`;\n const dir = sourcePath.split('/').slice(0, -1).join('/');\n return `${dir === '' ? '' : `${dir}/`}${base}.test.ts`;\n}\n\nexport const noTestCheck: Check = {\n name: 'no-test',\n async run(ctx) {\n const findings: Finding[] = [];\n const sourceChanged = ctx.files.filter(\n (f) => f.status !== 'deleted' && CODE_FILE.test(f.path) && !TEST_FILE.test(f.path),\n );\n if (sourceChanged.length === 0) return findings;\n\n const repoTests = walkCodeFiles(ctx.repoRoot).filter((p) => TEST_FILE.test(p));\n if (repoTests.length === 0 && !hasTestRunner(ctx.repoRoot)) return findings; // repo doesn't do tests\n\n const touchedTestBases = new Set(\n ctx.files.filter((f) => TEST_FILE.test(f.path) && f.status !== 'deleted').map((f) => moduleBase(f.path)),\n );\n const repoTestByBase = new Map<string, string>();\n for (const t of repoTests) {\n const base = moduleBase(t);\n if (!repoTestByBase.has(base)) repoTestByBase.set(base, t);\n }\n\n for (const file of sourceChanged) {\n const base = moduleBase(file.path);\n if (touchedTestBases.has(base)) continue; // tests moved with the change\n\n const logic = countLogicLines(file.added.values());\n const testPath = repoTestByBase.get(base);\n\n if (testPath !== undefined) {\n if (logic < MIN_LOGIC_TOUCHED) continue;\n findings.push({\n id: stableId('NOTEST', `${file.path}:stale`),\n check: 'no-test',\n severity: 'warn',\n file: file.path,\n message: `Logic changed (${logic} lines) but ${testPath} wasn't touched`,\n fix: `update ${testPath} to cover the changed behavior`,\n });\n } else {\n if (logic < MIN_LOGIC_NEW) continue;\n const texts = [...file.added.values()];\n if (!texts.some((t) => DECL_SIGNAL.test(t))) continue;\n findings.push({\n id: stableId('NOTEST', `${file.path}:missing`),\n check: 'no-test',\n severity: 'warn',\n file: file.path,\n message: `${logic} lines of logic changed with no test file for this module`,\n fix: `add ${suggestTestPath(file.path, repoTests)} to lock the behavior in`,\n });\n }\n }\n\n return findings;\n },\n};\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Check, Finding } from '../types.js';\nimport { stableId, TEST_FILE, walkCodeFiles } from './util.js';\n\n/**\n * UNREFERENCED (warn) — a newly added export that nothing references:\n * generated \"just in case\", it invites drift. Reference search is textual\n * (word-boundary match across repo code files) rather than AST-based: a name\n * mentioned anywhere — even a comment — counts as referenced, so the failure\n * mode is a miss, never noise. Value exports only; exported types are API\n * surface and stay out of scope.\n */\n\n// Files whose exports are consumed by something we can't see.\nconst ENTRY_FILE = /(?:^|\\/)(?:index|main|cli)\\.[cm]?[jt]sx?$/;\nconst FRAMEWORK_FILE = /(?:^|\\/)(?:pages|app|routes)\\/|\\.(?:config|stories)\\.[cm]?[jt]sx?$|\\.d\\.ts$/;\n\n// Names frameworks read by convention (Next.js, Remix, …).\nconst MAGIC_NAMES = new Set([\n 'metadata',\n 'revalidate',\n 'dynamic',\n 'config',\n 'runtime',\n 'viewport',\n 'getServerSideProps',\n 'getStaticProps',\n 'getStaticPaths',\n 'generateMetadata',\n 'generateStaticParams',\n 'loader',\n 'action',\n 'links',\n 'meta',\n 'headers',\n 'handle',\n 'ErrorBoundary',\n 'middleware',\n]);\n\nconst EXPORT_DECL = /^\\s*export\\s+(?:async\\s+)?(?:function\\*?|class|abstract\\s+class|const|let|var)\\s+([A-Za-z_$][\\w$]*)/;\n\nconst escapeRegex = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const unreferencedCheck: Check = {\n name: 'unreferenced',\n async run(ctx) {\n const findings: Finding[] = [];\n\n // 1. New export declarations introduced by this diff.\n const candidates: Array<{ file: string; line: number; name: string }> = [];\n for (const file of ctx.files) {\n if (file.status === 'deleted') continue;\n if (!/\\.[cm]?[jt]sx?$/.test(file.path)) continue;\n if (ENTRY_FILE.test(file.path) || FRAMEWORK_FILE.test(file.path) || TEST_FILE.test(file.path)) continue;\n for (const [lineNo, text] of file.added) {\n const name = EXPORT_DECL.exec(text)?.[1];\n if (name === undefined || MAGIC_NAMES.has(name)) continue;\n candidates.push({ file: file.path, line: lineNo, name });\n }\n }\n if (candidates.length === 0) return findings;\n\n // 2. Word-boundary reference search across the repo, own file last.\n const repoFiles = walkCodeFiles(ctx.repoRoot);\n const onDisk = new Set(repoFiles);\n const contentCache = new Map<string, string>();\n const read = (path: string): string => {\n let content = contentCache.get(path);\n if (content === undefined) {\n try {\n content = readFileSync(join(ctx.repoRoot, path), 'utf8');\n } catch {\n content = '';\n }\n contentCache.set(path, content);\n }\n return content;\n };\n\n for (const { file, line, name } of candidates) {\n if (!onDisk.has(file)) continue; // can't read the file — can't judge, stay silent\n const word = new RegExp(`\\\\b${escapeRegex(name)}\\\\b`);\n const referencedElsewhere = repoFiles.some((other) => other !== file && word.test(read(other)));\n if (referencedElsewhere) continue;\n\n // Own-file matches beyond the declaration itself = internal use.\n const ownMatches = read(file).match(new RegExp(`\\\\b${escapeRegex(name)}\\\\b`, 'g'))?.length ?? 0;\n const internalOnly = ownMatches >= 2;\n findings.push({\n id: stableId('UNREF', `${file}:${name}`),\n check: 'unreferenced',\n severity: 'warn',\n file,\n line,\n message: internalOnly\n ? `export ${name} is only used inside this file`\n : `export ${name} is never referenced`,\n fix: internalOnly\n ? 'drop the export keyword — keep the module surface honest'\n : 'remove it, or wire it up — dead exports invite drift',\n });\n }\n\n return findings;\n },\n};\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Check, Finding } from '../types.js';\nimport { stableId, CODE_FILE, TEST_FILE, walkCodeFiles, scanTopLevelDecls, declSpan, shingles, jaccard } from './util.js';\n\n/**\n * DUPLICATE (error) — a function this diff adds that already exists in the\n * codebase. Fingerprinting is normalize → token shingles → Jaccard:\n * identifiers, strings and numbers collapse to classes, so a renamed copy\n * still matches; keywords and operators keep the structure. Only functions\n * with real mass are compared (tiny helpers are naturally similar), test\n * files are excluded on both sides, and each new function reports only its\n * best match.\n */\n\n/** Report a duplicate at or above this similarity. */\nconst SIMILARITY_THRESHOLD = 0.8;\n/** Ignore functions smaller than this many normalized tokens. */\nconst MIN_TOKENS = 40;\n/** Compare bodies alone only when both have this many tokens — tiny bodies match trivially. */\nconst BODY_MIN_TOKENS = 30;\n/** Shingle width in tokens. */\nconst SHINGLE_K = 6;\n\nconst KEYWORDS = new Set([\n 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'return', 'const', 'let', 'var',\n 'function', 'class', 'new', 'try', 'catch', 'finally', 'throw', 'await', 'async', 'yield',\n 'typeof', 'instanceof', 'in', 'of', 'break', 'continue', 'default', 'import', 'export',\n 'from', 'extends', 'this', 'super', 'null', 'undefined', 'true', 'false', 'void', 'delete',\n 'static', 'get', 'set',\n]);\n\nconst TOKEN_RE =\n /(['\"`])(?:\\\\.|(?!\\1).)*\\1|\\d+(?:\\.\\d+)?|[A-Za-z_$][\\w$]*|=>|===|!==|==|!=|<=|>=|&&|\\|\\||\\?\\?|\\?\\.|\\.\\.\\.|[+\\-*/%<>=!&|^~?:;,.(){}[\\]]/g;\n\nfunction tokenize(code: string): string[] {\n const stripped = code.replace(/\\/\\*[\\s\\S]*?\\*\\//g, ' ').replace(/\\/\\/[^\\n]*/g, ' ');\n const tokens: string[] = [];\n for (const m of stripped.matchAll(TOKEN_RE)) {\n const t = m[0];\n if (t[0] === '\"' || t[0] === \"'\" || t[0] === '`') tokens.push('S');\n else if (/^\\d/.test(t)) tokens.push('N');\n else if (/^[A-Za-z_$]/.test(t)) tokens.push(KEYWORDS.has(t) ? t : 'ID');\n else tokens.push(t);\n }\n return tokens;\n}\n\ninterface Fn {\n file: string;\n name: string;\n start: number;\n end: number;\n shingles: Set<string>;\n /** Shingles of the body alone (declaration line dropped) — null when too small to trust. */\n bodyShingles: Set<string> | null;\n}\n\n/** Extract sizeable top-level functions from a file's content. */\nfunction extractFunctions(file: string, content: string): Fn[] {\n const lines = content.split('\\n');\n const decls = scanTopLevelDecls(lines);\n const fns: Fn[] = [];\n for (const decl of decls) {\n const span = declSpan(lines, decl, decls);\n const tokens = tokenize(lines.slice(span.start - 1, span.end).join('\\n'));\n if (tokens.length < MIN_TOKENS) continue;\n // Signatures carry type annotations; bodies carry the logic. Scoring on\n // both lets a typed original still match an untyped (or retyped) copy.\n const bodyTokens = tokenize(lines.slice(span.start, span.end).join('\\n'));\n fns.push({\n file,\n name: decl.name,\n start: span.start,\n end: span.end,\n shingles: shingles(tokens, SHINGLE_K),\n bodyShingles: bodyTokens.length >= BODY_MIN_TOKENS ? shingles(bodyTokens, SHINGLE_K) : null,\n });\n }\n return fns;\n}\n\nfunction similarity(a: Fn, b: Fn): number {\n const full = jaccard(a.shingles, b.shingles);\n const body = a.bodyShingles !== null && b.bodyShingles !== null ? jaccard(a.bodyShingles, b.bodyShingles) : 0;\n return Math.max(full, body);\n}\n\nexport const duplicateCheck: Check = {\n name: 'duplicate',\n async run(ctx) {\n const findings: Finding[] = [];\n const contentCache = new Map<string, string | null>();\n const read = (path: string): string | null => {\n let content = contentCache.get(path);\n if (content === undefined) {\n try {\n content = readFileSync(join(ctx.repoRoot, path), 'utf8');\n } catch {\n content = null;\n }\n contentCache.set(path, content);\n }\n return content;\n };\n\n // 1. Candidates: brand-new top-level functions (their decl line was added).\n const candidates: Fn[] = [];\n for (const file of ctx.files) {\n if (file.status === 'deleted' || !CODE_FILE.test(file.path) || TEST_FILE.test(file.path)) continue;\n if (file.added.size === 0) continue;\n const content = read(file.path);\n if (content === null) continue;\n for (const fn of extractFunctions(file.path, content)) {\n if (file.added.has(fn.start)) candidates.push(fn);\n }\n }\n if (candidates.length === 0) return findings;\n\n // 2. Index the codebase (lazy: only now that something needs comparing).\n const index: Fn[] = [];\n for (const path of walkCodeFiles(ctx.repoRoot)) {\n if (TEST_FILE.test(path)) continue;\n const content = read(path);\n if (content !== null) index.push(...extractFunctions(path, content));\n }\n\n // 3. Best match per candidate, excluding itself. When two new functions\n // match each other, the pair reports once, not once from each side.\n const reportedPairs = new Set<string>();\n for (const candidate of candidates) {\n let best: Fn | null = null;\n let bestScore = 0;\n for (const existing of index) {\n if (existing.file === candidate.file && existing.start === candidate.start) continue; // itself\n const score = similarity(candidate, existing);\n if (score > bestScore) {\n bestScore = score;\n best = existing;\n }\n }\n if (best === null || bestScore < SIMILARITY_THRESHOLD) continue;\n const pairKey = [`${candidate.file}:${candidate.start}`, `${best.file}:${best.start}`].sort().join('|');\n if (reportedPairs.has(pairKey)) continue;\n reportedPairs.add(pairKey);\n const pct = Math.round(bestScore * 100);\n findings.push({\n id: stableId('DUP', `${candidate.file}:${candidate.name}`),\n check: 'duplicate',\n severity: 'error',\n file: candidate.file,\n line: candidate.start,\n message: `${candidate.name}() is ${pct}% similar to ${best.name}() in ${best.file}:${best.start}`,\n fix: `reuse ${best.name} from ${best.file} — extend it there if the behavior differs`,\n });\n }\n\n return findings;\n },\n};\n","import type { Check, Finding, StandardRule } from '../types.js';\nimport { stableId, CODE_FILE } from './util.js';\n\n/**\n * STANDARD (severity per rule, default warn) — the user's own house rules\n * from aidiff.config.json, applied to ADDED lines only. That diff scoping is\n * the ratchet: legacy violations are grandfathered, new code is held to the\n * standard, and nobody has to write an ESLint plugin to get there.\n */\n\n/**\n * Convert a glob to a path regex. Supports `**` (any depth), `*` (within a\n * segment), `?`; a pattern without `/` matches in any directory. Covers the\n * config `in` field without a glob dependency — we only match paths, never\n * walk the filesystem.\n */\nexport function globToRegex(glob: string): RegExp {\n const pattern = glob.includes('/') ? glob : `**/${glob}`;\n const segments = pattern.split('/');\n let out = '^';\n segments.forEach((segment, i) => {\n const last = i === segments.length - 1;\n if (segment === '**') {\n out += last ? '.*' : '(?:[^/]+/)*';\n } else {\n out +=\n segment\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '[^/]*')\n .replace(/\\?/g, '[^/]') + (last ? '' : '/');\n }\n });\n return new RegExp(`${out}$`);\n}\n\ninterface CompiledRule {\n rule: StandardRule;\n /** What the finding quotes — the ban string or the pattern source. */\n key: string;\n matches: (line: string) => boolean;\n scope: RegExp | null;\n}\n\nfunction compile(rule: StandardRule): CompiledRule {\n const pattern = rule.pattern !== undefined ? new RegExp(rule.pattern) : null;\n const ban = rule.ban ?? null;\n return {\n rule,\n key: ban ?? rule.pattern ?? '',\n matches: (line) => (ban !== null && line.includes(ban)) || (pattern !== null && pattern.test(line)),\n scope: rule.in !== undefined ? globToRegex(rule.in) : null,\n };\n}\n\nfunction ruleFix(rule: StandardRule): string | undefined {\n if (rule.use !== undefined && rule.docs !== undefined) return `use ${rule.use} (see ${rule.docs})`;\n if (rule.use !== undefined) return `use ${rule.use}`;\n if (rule.docs !== undefined) return `see ${rule.docs}`;\n return undefined;\n}\n\nexport const standardCheck: Check = {\n name: 'standard',\n async run(ctx) {\n const rules = ctx.config.standards;\n if (rules === undefined || rules.length === 0) return [];\n const compiled = rules.map(compile);\n const findings: Finding[] = [];\n\n for (const file of ctx.files) {\n if (file.status === 'deleted' || file.added.size === 0) continue;\n for (const { rule, key, matches, scope } of compiled) {\n // A rule with `in` decides its own scope; without it, code files only.\n if (scope !== null ? !scope.test(file.path) : !CODE_FILE.test(file.path)) continue;\n\n let firstLine: number | null = null;\n let count = 0;\n for (const [lineNo, text] of file.added) {\n const trimmed = text.trim();\n if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;\n if (!matches(text)) continue;\n count++;\n if (firstLine === null || lineNo < firstLine) firstLine = lineNo;\n }\n if (firstLine === null) continue;\n\n findings.push({\n id: stableId('STD', `${key}:${file.path}`),\n check: 'standard',\n severity: rule.severity ?? 'warn',\n file: file.path,\n line: firstLine,\n message: `\"${key}\" is banned in new code${count > 1 ? ` (${count}× in this file)` : ''}`,\n fix: ruleFix(rule),\n });\n }\n }\n\n return findings;\n },\n};\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Check, Finding } from '../types.js';\nimport { stableId, TEST_FILE, walkCodeFiles, scanTopLevelDecls, declSpan, shingles, jaccard } from './util.js';\n\n/**\n * REUSE (warn) — a new component whose MARKUP near-duplicates an existing\n * component in the repo. Where DUPLICATE fingerprints all logic tokens, this\n * fingerprints only JSX structure — tag names and attribute names, with\n * values and text dropped — so \"same markup, different data wiring\" still\n * matches. The fix names the existing component to extend instead of forking.\n */\n\nconst JSX_FILE = /\\.(?:tsx|jsx)$/;\n/** Report at or above this structural similarity. */\nconst SIMILARITY_THRESHOLD = 0.8;\n/** Ignore components with less JSX structure than this — tiny wrappers all look alike. */\nconst MIN_JSX_TOKENS = 25;\nconst SHINGLE_K = 4;\n\n// Tags (`<div`, `</Card`), self-closers, and JSX attribute names — an attr is\n// `name=` immediately followed by a quote or brace, which JS assignments\n// (spaced by any formatter) don't produce.\nconst JSX_BITS = /<\\/?[A-Za-z][\\w.]*|\\/>|([A-Za-z][\\w-]*)=(?=[\"'{])/g;\n\nexport function jsxTokens(code: string): string[] {\n const out: string[] = [];\n for (const m of code.matchAll(JSX_BITS)) {\n out.push(m[1] !== undefined ? `${m[1]}=` : m[0]);\n }\n return out;\n}\n\ninterface Comp {\n file: string;\n name: string;\n start: number;\n shingles: Set<string>;\n}\n\n/** Top-level function components with enough JSX structure to compare. */\nfunction extractComponents(file: string, content: string): Comp[] {\n const lines = content.split('\\n');\n const decls = scanTopLevelDecls(lines);\n const comps: Comp[] = [];\n for (const decl of decls) {\n if (decl.isClass || !/^[A-Z]/.test(decl.name)) continue;\n const span = declSpan(lines, decl, decls);\n const tokens = jsxTokens(lines.slice(span.start - 1, span.end).join('\\n'));\n if (tokens.length < MIN_JSX_TOKENS) continue;\n comps.push({ file, name: decl.name, start: span.start, shingles: shingles(tokens, SHINGLE_K) });\n }\n return comps;\n}\n\nexport const reuseCheck: Check = {\n name: 'reuse',\n async run(ctx) {\n const findings: Finding[] = [];\n const contentCache = new Map<string, string | null>();\n const read = (path: string): string | null => {\n let content = contentCache.get(path);\n if (content === undefined) {\n try {\n content = readFileSync(join(ctx.repoRoot, path), 'utf8');\n } catch {\n content = null;\n }\n contentCache.set(path, content);\n }\n return content;\n };\n\n // Candidates: components this diff added (decl line in the added map).\n const candidates: Comp[] = [];\n for (const file of ctx.files) {\n if (file.status === 'deleted' || !JSX_FILE.test(file.path) || TEST_FILE.test(file.path)) continue;\n if (file.added.size === 0) continue;\n const content = read(file.path);\n if (content === null) continue;\n for (const comp of extractComponents(file.path, content)) {\n if (file.added.has(comp.start)) candidates.push(comp);\n }\n }\n if (candidates.length === 0) return findings;\n\n const index: Comp[] = [];\n for (const path of walkCodeFiles(ctx.repoRoot)) {\n if (!JSX_FILE.test(path) || TEST_FILE.test(path)) continue;\n const content = read(path);\n if (content !== null) index.push(...extractComponents(path, content));\n }\n\n const reportedPairs = new Set<string>();\n for (const candidate of candidates) {\n let best: Comp | null = null;\n let bestScore = 0;\n for (const existing of index) {\n if (existing.file === candidate.file && existing.start === candidate.start) continue; // itself\n const score = jaccard(candidate.shingles, existing.shingles);\n if (score > bestScore) {\n bestScore = score;\n best = existing;\n }\n }\n if (best === null || bestScore < SIMILARITY_THRESHOLD) continue;\n const pairKey = [`${candidate.file}:${candidate.start}`, `${best.file}:${best.start}`].sort().join('|');\n if (reportedPairs.has(pairKey)) continue;\n reportedPairs.add(pairKey);\n\n findings.push({\n id: stableId('REUSE', `${candidate.file}:${candidate.name}`),\n check: 'reuse',\n severity: 'warn',\n file: candidate.file,\n line: candidate.start,\n message: `<${candidate.name}> markup is ${Math.round(bestScore * 100)}% similar to <${best.name}> (${best.file}:${best.start})`,\n fix: `reuse <${best.name}> from ${best.file} — extend it with props instead of forking the markup`,\n });\n }\n\n return findings;\n },\n};\n","import type { Check, CheckContext, Finding } from '../types.js';\nimport { stubCheck } from './stub.js';\nimport { oversizeCheck } from './oversize.js';\nimport { phantomDepCheck } from './phantom-dep.js';\nimport { deletionCheck } from './deletion.js';\nimport { noTestCheck } from './no-test.js';\nimport { unreferencedCheck } from './unreferenced.js';\nimport { duplicateCheck } from './duplicate.js';\nimport { standardCheck } from './standard.js';\nimport { reuseCheck } from './reuse.js';\n\n/** Every check the CLI knows about. Order matters: on same-line overlap the earlier check wins ties. */\nexport const checks: Check[] = [\n phantomDepCheck,\n deletionCheck,\n duplicateCheck,\n reuseCheck,\n stubCheck,\n oversizeCheck,\n noTestCheck,\n unreferencedCheck,\n standardCheck,\n];\n\nconst SEVERITY_RANK: Record<Finding['severity'], number> = { error: 0, warn: 1, info: 2 };\n\n/**\n * Run every registered check against the diff and return the findings, with\n * ignored ids filtered out and same-location overlap deduplicated: when two\n * checks flag the same file:line (e.g. DUPLICATE and REUSE on a copied\n * component), only the most severe finding survives — first registered wins\n * ties. One line, one finding.\n */\nexport async function runChecks(ctx: CheckContext): Promise<Finding[]> {\n const ignored = new Set(ctx.config.ignore);\n const findings: Finding[] = [];\n for (const check of checks) {\n const results = await check.run(ctx);\n for (const f of results) {\n if (!ignored.has(f.id)) findings.push(f);\n }\n }\n\n const byLocation = new Map<string, Finding>();\n const kept: Finding[] = [];\n for (const f of findings) {\n if (f.line === undefined) {\n kept.push(f);\n continue;\n }\n const key = `${f.file}:${f.line}`;\n const prev = byLocation.get(key);\n if (prev === undefined) {\n byLocation.set(key, f);\n kept.push(f);\n } else if (SEVERITY_RANK[f.severity] < SEVERITY_RANK[prev.severity]) {\n kept[kept.indexOf(prev)] = f;\n byLocation.set(key, f);\n }\n }\n return kept;\n}\n","/** Severity decides the exit code: any `error` finding fails the run. */\nexport type Severity = 'error' | 'warn' | 'info';\n\n/** One reviewable problem found in the diff. */\nexport interface Finding {\n /** Stable id like DUP-104, used by `ai-diff-check ignore <id>`. */\n id: string;\n /** Which check produced this, e.g. \"duplicate\", \"stub\". */\n check: CheckName;\n severity: Severity;\n file: string;\n line?: number;\n /** What is wrong, in one sentence. */\n message: string;\n /** What the project does instead — rendered after \"→\". */\n fix?: string;\n}\n\nexport type CheckName =\n | 'phantom-dep'\n | 'duplicate'\n | 'deletion'\n | 'unreferenced'\n | 'stub'\n | 'no-test'\n | 'oversize'\n | 'standard'\n | 'reuse';\n\n/** A single changed file in the diff, with its added/removed lines. */\nexport interface ChangedFile {\n path: string;\n /** Lines added, keyed by new-file line number. */\n added: Map<number, string>;\n /** Lines removed, keyed by old-file line number. */\n removed: Map<number, string>;\n status: 'added' | 'modified' | 'deleted' | 'renamed';\n}\n\n/** Everything a check gets access to. Checks never touch git directly. */\nexport interface CheckContext {\n repoRoot: string;\n baseRef: string;\n files: ChangedFile[];\n config: ResolvedConfig;\n}\n\nexport interface Check {\n name: CheckName;\n run(ctx: CheckContext): Promise<Finding[]>;\n}\n\n/** Shape of aidiff.config.json (all optional for the user). */\nexport interface UserConfig {\n /** Component/file size limits for the oversize check. */\n maxFileLines?: number;\n maxComponentLines?: number;\n /** Declarative house rules for the standard check. */\n standards?: StandardRule[];\n /** Finding ids to skip, managed by `ai-diff-check ignore`. */\n ignore?: string[];\n /** Treat warnings as failures. */\n strict?: boolean;\n}\n\nexport interface StandardRule {\n /** Substring or pattern to ban in new code, e.g. \"fetch(\". */\n ban?: string;\n /** Regex alternative to `ban`. */\n pattern?: string;\n /** Glob restricting where the rule applies, e.g. \"src/**\". */\n in?: string;\n /** The approved alternative, shown as the fix. */\n use?: string;\n /** Path to the standards doc this rule comes from. */\n docs?: string;\n severity?: Severity;\n}\n\nexport type ResolvedConfig = Required<Pick<UserConfig, 'maxFileLines' | 'maxComponentLines' | 'strict'>> &\n Pick<UserConfig, 'standards'> & { ignore: string[] };\n\nexport const DEFAULT_CONFIG: ResolvedConfig = {\n maxFileLines: 400,\n maxComponentLines: 200,\n strict: false,\n standards: undefined,\n ignore: [],\n};\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig, Severity, StandardRule, UserConfig } from './types.js';\nimport { DEFAULT_CONFIG } from './types.js';\n\nexport const CONFIG_FILE = 'aidiff.config.json';\n\n/** Bad config is a usage error: the CLI reports it and exits 2. */\nexport class ConfigError extends Error {}\n\nfunction fail(message: string): never {\n throw new ConfigError(`${CONFIG_FILE}: ${message}`);\n}\n\nconst SEVERITIES: readonly Severity[] = ['error', 'warn', 'info'];\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nfunction readPositiveInt(o: Record<string, unknown>, key: string): number | undefined {\n const v = o[key];\n if (v === undefined) return undefined;\n if (typeof v !== 'number' || !Number.isInteger(v) || v < 1) fail(`\"${key}\" must be a positive integer`);\n return v;\n}\n\nfunction readStringArray(o: Record<string, unknown>, key: string): string[] | undefined {\n const v = o[key];\n if (v === undefined) return undefined;\n if (!Array.isArray(v) || v.some((s) => typeof s !== 'string')) fail(`\"${key}\" must be an array of strings`);\n return v as string[];\n}\n\nfunction readStandards(o: Record<string, unknown>): StandardRule[] | undefined {\n const v = o['standards'];\n if (v === undefined) return undefined;\n if (!Array.isArray(v)) fail('\"standards\" must be an array of rules');\n\n return v.map((raw, i) => {\n const at = `standards[${i}]`;\n if (!isPlainObject(raw)) fail(`${at} must be an object`);\n const rule: StandardRule = {};\n for (const key of ['ban', 'pattern', 'in', 'use', 'docs'] as const) {\n const field = raw[key];\n if (field === undefined) continue;\n if (typeof field !== 'string' || field === '') fail(`${at}.${key} must be a non-empty string`);\n rule[key] = field;\n }\n if (rule.ban === undefined && rule.pattern === undefined) fail(`${at} needs a \"ban\" or a \"pattern\"`);\n if (rule.pattern !== undefined) {\n try {\n new RegExp(rule.pattern);\n } catch {\n fail(`${at}.pattern is not a valid regex: ${rule.pattern}`);\n }\n }\n const severity = raw['severity'];\n if (severity !== undefined) {\n if (typeof severity !== 'string' || !SEVERITIES.includes(severity as Severity)) {\n fail(`${at}.severity must be one of ${SEVERITIES.join(' | ')}`);\n }\n rule.severity = severity as Severity;\n }\n return rule;\n });\n}\n\n/**\n * Load `aidiff.config.json` from the repo root and resolve it over defaults.\n * No file → defaults. Malformed JSON or a wrong-typed known key → ConfigError,\n * never a silent fallback (a typo'd config that silently no-ops is worse than\n * an error). Unknown keys pass through untouched for forward compatibility.\n */\nexport function loadConfig(repoRoot: string): ResolvedConfig {\n let raw: string;\n try {\n raw = readFileSync(join(repoRoot, CONFIG_FILE), 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return { ...DEFAULT_CONFIG };\n fail(`could not be read: ${(err as Error).message}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n fail(`is not valid JSON — ${(err as Error).message}`);\n }\n if (!isPlainObject(parsed)) fail('must be a JSON object');\n\n const strict = parsed['strict'];\n if (strict !== undefined && typeof strict !== 'boolean') fail('\"strict\" must be a boolean');\n\n const user: UserConfig = {\n maxFileLines: readPositiveInt(parsed, 'maxFileLines'),\n maxComponentLines: readPositiveInt(parsed, 'maxComponentLines'),\n strict,\n ignore: readStringArray(parsed, 'ignore'),\n standards: readStandards(parsed),\n };\n\n return {\n maxFileLines: user.maxFileLines ?? DEFAULT_CONFIG.maxFileLines,\n maxComponentLines: user.maxComponentLines ?? DEFAULT_CONFIG.maxComponentLines,\n strict: user.strict ?? DEFAULT_CONFIG.strict,\n standards: user.standards,\n ignore: user.ignore ?? [],\n };\n}\n","import { existsSync, readFileSync, writeFileSync, appendFileSync, chmodSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { CONFIG_FILE, ConfigError } from './config.js';\n\n/** `init` and `ignore` subcommands — kept CLI-free so tests can drive them directly. */\n\nconst STARTER_CONFIG = `{\n \"maxFileLines\": 400,\n \"maxComponentLines\": 200,\n \"strict\": false,\n \"ignore\": [],\n \"standards\": []\n}\n`;\n\nconst HOOK_LINE = 'npx ai-diff-check';\n\n/**\n * Write a starter config and wire the pre-commit hook: husky's\n * .husky/pre-commit when the repo uses husky, plain .git/hooks/pre-commit\n * otherwise. Never overwrites anything — existing files are appended to or\n * left alone. Returns human-readable lines describing what happened.\n */\nexport function runInit(repoRoot: string): string[] {\n const out: string[] = [];\n\n const configPath = join(repoRoot, CONFIG_FILE);\n if (existsSync(configPath)) {\n out.push(`${CONFIG_FILE} already exists — left untouched`);\n } else {\n writeFileSync(configPath, STARTER_CONFIG);\n out.push(`wrote ${CONFIG_FILE}`);\n }\n\n const usesHusky = existsSync(join(repoRoot, '.husky'));\n const hooksDir = usesHusky ? join(repoRoot, '.husky') : join(repoRoot, '.git', 'hooks');\n const label = usesHusky ? '.husky/pre-commit' : '.git/hooks/pre-commit';\n if (!existsSync(hooksDir)) {\n out.push(`no .husky/ or .git/hooks/ found — add \"${HOOK_LINE}\" to your pre-commit hook manually`);\n return out;\n }\n\n const hookPath = join(hooksDir, 'pre-commit');\n if (!existsSync(hookPath)) {\n writeFileSync(hookPath, `#!/bin/sh\\n${HOOK_LINE}\\n`);\n chmodSync(hookPath, 0o755);\n out.push(`created ${label} — every commit now runs the vibe check`);\n } else if (readFileSync(hookPath, 'utf8').includes('ai-diff-check')) {\n out.push(`${label} already runs ai-diff-check — left untouched`);\n } else {\n appendFileSync(hookPath, `\\n${HOOK_LINE}\\n`);\n out.push(`added \"${HOOK_LINE}\" to your existing ${label}`);\n }\n return out;\n}\n\n/**\n * Add a finding id to the config's ignore list (creating the config if\n * needed), preserving any other keys the user has set.\n */\nexport function addIgnore(repoRoot: string, id: string): string {\n const configPath = join(repoRoot, CONFIG_FILE);\n let config: Record<string, unknown> = {};\n if (existsSync(configPath)) {\n try {\n config = JSON.parse(readFileSync(configPath, 'utf8')) as Record<string, unknown>;\n } catch {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON — fix it before adding ignores`);\n }\n }\n const ignore = Array.isArray(config['ignore']) ? (config['ignore'] as unknown[]) : [];\n if (ignore.includes(id)) return `${id} is already on the ignore list`;\n config['ignore'] = [...ignore, id];\n writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`);\n return `${id} added to the ignore list in ${CONFIG_FILE}`;\n}\n","import { createInterface } from 'node:readline';\nimport { findRepoRoot, resolveBaseRef, getChangedFiles, diffStats, NotAGitRepoError } from './git/diff.js';\nimport { loadConfig, ConfigError } from './config.js';\nimport { runChecks } from './checks/index.js';\nimport { trustScore, sortFindings } from './report/terminal.js';\nimport { renderFixPrompt } from './report/fix-prompt.js';\n\n/**\n * MCP server mode — coding agents vibe-check their own diffs.\n *\n * A deliberately minimal Model Context Protocol server over stdio: JSON-RPC\n * 2.0, newline-delimited, supporting initialize / tools/list / tools/call /\n * ping. Hand-rolled instead of pulling the SDK: the surface we need is tiny\n * and zero-deps is part of the brand. Nothing but protocol messages may touch\n * stdout here.\n */\n\nconst PROTOCOL_FALLBACK = '2025-03-26';\n\nconst VIBE_CHECK_TOOL = {\n name: 'vibe_check',\n description:\n 'Review the current git diff (uncommitted/branch changes only — never the whole repo) for AI-generated-code issues: ' +\n 'hallucinated or undeclared imports, functions duplicating existing code, deleted error handling/guards/tests, ' +\n 'leftover stubs (console.log, TODO, empty catch), oversized files/components, logic changes with untouched tests, ' +\n 'unreferenced new exports, and violations of the project\\'s own house rules. ' +\n 'Call this after making code changes and fix everything it reports before declaring the task done.',\n inputSchema: {\n type: 'object',\n properties: {\n base: { type: 'string', description: 'git ref to diff against (default: main/master, then origin/*)' },\n repo: { type: 'string', description: 'path inside the repository to check (default: server working directory)' },\n },\n additionalProperties: false,\n },\n};\n\nasync function vibeCheck(args: { base?: string; repo?: string }): Promise<string> {\n const repoRoot = findRepoRoot(args.repo ?? process.cwd());\n const config = loadConfig(repoRoot);\n const baseRef = resolveBaseRef(repoRoot, args.base);\n const files = getChangedFiles(repoRoot, baseRef);\n const findings = sortFindings(await runChecks({ repoRoot, baseRef, files, config }));\n const stats = diffStats(files);\n\n const errors = findings.filter((f) => f.severity === 'error').length;\n const warns = findings.filter((f) => f.severity === 'warn').length;\n const header =\n `Trust score ${trustScore(findings, stats)}/100 — ${errors} error(s), ${warns} warning(s) ` +\n `across ${stats.files} changed file(s) (diff vs ${baseRef}).`;\n return `${header}\\n\\n${renderFixPrompt(findings)}`;\n}\n\ntype JsonRpcId = string | number | null;\n\ninterface JsonRpcMessage {\n jsonrpc?: string;\n id?: JsonRpcId;\n method?: string;\n params?: Record<string, unknown>;\n}\n\nexport function runMcpServer(version: string): void {\n const write = (msg: object): void => {\n process.stdout.write(`${JSON.stringify(msg)}\\n`);\n };\n const reply = (id: JsonRpcId, result: object): void => write({ jsonrpc: '2.0', id, result });\n const replyError = (id: JsonRpcId, code: number, message: string): void =>\n write({ jsonrpc: '2.0', id, error: { code, message } });\n\n const handle = async (msg: JsonRpcMessage): Promise<void> => {\n const { id, method, params } = msg;\n if (method === undefined) return;\n if (id === undefined) return; // notification (e.g. notifications/initialized) — no response\n\n switch (method) {\n case 'initialize': {\n const requested = params?.['protocolVersion'];\n reply(id, {\n protocolVersion: typeof requested === 'string' ? requested : PROTOCOL_FALLBACK,\n capabilities: { tools: {} },\n serverInfo: { name: 'ai-diff-check', version },\n });\n return;\n }\n case 'ping':\n reply(id, {});\n return;\n case 'tools/list':\n reply(id, { tools: [VIBE_CHECK_TOOL] });\n return;\n case 'tools/call': {\n if (params?.['name'] !== VIBE_CHECK_TOOL.name) {\n replyError(id, -32602, `Unknown tool: ${String(params?.['name'])}`);\n return;\n }\n try {\n const text = await vibeCheck((params?.['arguments'] ?? {}) as { base?: string; repo?: string });\n reply(id, { content: [{ type: 'text', text }], isError: false });\n } catch (err) {\n const known = err instanceof NotAGitRepoError || err instanceof ConfigError;\n const text = known ? (err as Error).message : `vibe_check failed: ${(err as Error).message}`;\n reply(id, { content: [{ type: 'text', text }], isError: true });\n }\n return;\n }\n default:\n replyError(id, -32601, `Method not found: ${method}`);\n }\n };\n\n const rl = createInterface({ input: process.stdin });\n rl.on('line', (line) => {\n const trimmed = line.trim();\n if (trimmed === '') return;\n let msg: JsonRpcMessage;\n try {\n msg = JSON.parse(trimmed) as JsonRpcMessage;\n } catch {\n replyError(null, -32700, 'Parse error');\n return;\n }\n void handle(msg);\n });\n rl.on('close', () => process.exit(0));\n}\n"],"mappings":"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","names":["snippet","join","join","readFileSync","join","readFileSync","join","readFileSync","join","readFileSync","join","readFileSync","join","readFileSync","join","readFileSync","join","readFileSync","join","readFileSync","join","JSX_FILE","SIMILARITY_THRESHOLD","SHINGLE_K","readFileSync","join","readFileSync","join","readFileSync","join","existsSync","readFileSync","join","join","existsSync","readFileSync"]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ConfigError,
|
|
4
|
+
NotAGitRepoError,
|
|
5
|
+
addIgnore,
|
|
6
|
+
diffStats,
|
|
7
|
+
findRepoRoot,
|
|
8
|
+
getChangedFiles,
|
|
9
|
+
githubAnnotations,
|
|
10
|
+
loadConfig,
|
|
11
|
+
renderDiffLine,
|
|
12
|
+
renderFindings,
|
|
13
|
+
renderFixPrompt,
|
|
14
|
+
renderHeader,
|
|
15
|
+
renderSummary,
|
|
16
|
+
resolveBaseRef,
|
|
17
|
+
runChecks,
|
|
18
|
+
runInit,
|
|
19
|
+
runMcpServer,
|
|
20
|
+
sortFindings
|
|
21
|
+
} from "./chunk-7JKSFHII.js";
|
|
22
|
+
|
|
23
|
+
// src/cli.ts
|
|
24
|
+
import { Command } from "commander";
|
|
25
|
+
import pc from "picocolors";
|
|
26
|
+
import { createRequire } from "module";
|
|
27
|
+
var require2 = createRequire(import.meta.url);
|
|
28
|
+
var { version } = require2("../package.json");
|
|
29
|
+
var print = (line = "") => {
|
|
30
|
+
process.stdout.write(`${line}
|
|
31
|
+
`);
|
|
32
|
+
};
|
|
33
|
+
var usageError = (message) => {
|
|
34
|
+
console.error(pc.red(`
|
|
35
|
+
\u2716 ${message}
|
|
36
|
+
`));
|
|
37
|
+
process.exit(2);
|
|
38
|
+
};
|
|
39
|
+
function repoRootOrExit() {
|
|
40
|
+
try {
|
|
41
|
+
return findRepoRoot(process.cwd());
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err instanceof NotAGitRepoError) usageError(err.message);
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function configOrExit(repoRoot) {
|
|
48
|
+
try {
|
|
49
|
+
return loadConfig(repoRoot);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (err instanceof ConfigError) usageError(err.message);
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
var program = new Command();
|
|
56
|
+
program.name("ai-diff-check").description("The vibe check for AI-written code \u2014 reviews your git diff before you commit.").version(version).option("-b, --base <ref>", "git ref to diff against (default: main/master)").option("--ci", "machine-readable output (JSON + GitHub annotations)").option("--fix-prompt", "print a prompt for the AI agent that wrote the diff to fix the findings").action(async (opts) => {
|
|
57
|
+
const started = Date.now();
|
|
58
|
+
const repoRoot = repoRootOrExit();
|
|
59
|
+
const config = configOrExit(repoRoot);
|
|
60
|
+
const baseRef = resolveBaseRef(repoRoot, opts.base);
|
|
61
|
+
const files = getChangedFiles(repoRoot, baseRef);
|
|
62
|
+
const stats = diffStats(files);
|
|
63
|
+
const findings = sortFindings(await runChecks({ repoRoot, baseRef, files, config }));
|
|
64
|
+
if (opts.fixPrompt) {
|
|
65
|
+
print(renderFixPrompt(findings));
|
|
66
|
+
} else if (opts.ci) {
|
|
67
|
+
for (const line of githubAnnotations(findings)) print(line);
|
|
68
|
+
print(JSON.stringify({ version, baseRef, stats, findings }, null, 2));
|
|
69
|
+
} else {
|
|
70
|
+
print(renderHeader(version, baseRef));
|
|
71
|
+
print(renderDiffLine(stats, Date.now() - started));
|
|
72
|
+
if (stats.files === 0) {
|
|
73
|
+
print(pc.dim("\n nothing to review \u2014 working tree matches base\n"));
|
|
74
|
+
} else {
|
|
75
|
+
if (findings.length > 0) print(`
|
|
76
|
+
${renderFindings(findings)}`);
|
|
77
|
+
print(renderSummary(findings, stats, Date.now() - started));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const failed = findings.some((f) => f.severity === "error" || config.strict && f.severity === "warn");
|
|
81
|
+
process.exit(failed ? 1 : 0);
|
|
82
|
+
});
|
|
83
|
+
program.command("init").description("write a starter aidiff.config.json and wire the pre-commit hook").action(() => {
|
|
84
|
+
const repoRoot = repoRootOrExit();
|
|
85
|
+
print();
|
|
86
|
+
for (const line of runInit(repoRoot)) print(` ${pc.green("\u2713")} ${line}`);
|
|
87
|
+
print();
|
|
88
|
+
});
|
|
89
|
+
program.command("ignore").argument("<id>", "finding id to silence, e.g. STUB-441").description("permanently silence a finding id (stored in aidiff.config.json)").action((id) => {
|
|
90
|
+
const repoRoot = repoRootOrExit();
|
|
91
|
+
try {
|
|
92
|
+
print(`
|
|
93
|
+
${pc.green("\u2713")} ${addIgnore(repoRoot, id)}
|
|
94
|
+
`);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err instanceof ConfigError) usageError(err.message);
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
program.command("mcp").description("run as an MCP server so coding agents can vibe_check their own diffs").action(() => {
|
|
101
|
+
runMcpServer(version);
|
|
102
|
+
});
|
|
103
|
+
program.parse();
|
|
104
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport pc from 'picocolors';\nimport { createRequire } from 'node:module';\nimport { findRepoRoot, resolveBaseRef, getChangedFiles, diffStats, NotAGitRepoError } from './git/diff.js';\nimport { renderHeader, renderDiffLine, renderFindings, renderSummary, sortFindings } from './report/terminal.js';\nimport { githubAnnotations } from './report/ci.js';\nimport { renderFixPrompt } from './report/fix-prompt.js';\nimport { runChecks } from './checks/index.js';\nimport { loadConfig, ConfigError } from './config.js';\nimport { runInit, addIgnore } from './commands.js';\nimport { runMcpServer } from './mcp.js';\nimport type { ResolvedConfig } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json') as { version: string };\n\nconst print = (line = ''): void => {\n process.stdout.write(`${line}\\n`);\n};\n\nconst usageError = (message: string): never => {\n console.error(pc.red(`\\n ✖ ${message}\\n`));\n process.exit(2);\n};\n\nfunction repoRootOrExit(): string {\n try {\n return findRepoRoot(process.cwd());\n } catch (err) {\n if (err instanceof NotAGitRepoError) usageError(err.message);\n throw err;\n }\n}\n\nfunction configOrExit(repoRoot: string): ResolvedConfig {\n try {\n return loadConfig(repoRoot);\n } catch (err) {\n if (err instanceof ConfigError) usageError(err.message);\n throw err;\n }\n}\n\nconst program = new Command();\n\nprogram\n .name('ai-diff-check')\n .description('The vibe check for AI-written code — reviews your git diff before you commit.')\n .version(version)\n .option('-b, --base <ref>', 'git ref to diff against (default: main/master)')\n .option('--ci', 'machine-readable output (JSON + GitHub annotations)')\n .option('--fix-prompt', 'print a prompt for the AI agent that wrote the diff to fix the findings')\n .action(async (opts: { base?: string; ci?: boolean; fixPrompt?: boolean }) => {\n const started = Date.now();\n const repoRoot = repoRootOrExit();\n const config = configOrExit(repoRoot);\n\n const baseRef = resolveBaseRef(repoRoot, opts.base);\n const files = getChangedFiles(repoRoot, baseRef);\n const stats = diffStats(files);\n\n const findings = sortFindings(await runChecks({ repoRoot, baseRef, files, config }));\n\n if (opts.fixPrompt) {\n print(renderFixPrompt(findings));\n } else if (opts.ci) {\n for (const line of githubAnnotations(findings)) print(line);\n print(JSON.stringify({ version, baseRef, stats, findings }, null, 2));\n } else {\n print(renderHeader(version, baseRef));\n print(renderDiffLine(stats, Date.now() - started));\n if (stats.files === 0) {\n print(pc.dim('\\n nothing to review — working tree matches base\\n'));\n } else {\n if (findings.length > 0) print(`\\n${renderFindings(findings)}`);\n print(renderSummary(findings, stats, Date.now() - started));\n }\n }\n\n // UX contract: exit 1 on any error, or on any warning when strict is on.\n const failed = findings.some((f) => f.severity === 'error' || (config.strict && f.severity === 'warn'));\n process.exit(failed ? 1 : 0);\n });\n\nprogram\n .command('init')\n .description('write a starter aidiff.config.json and wire the pre-commit hook')\n .action(() => {\n const repoRoot = repoRootOrExit();\n print();\n for (const line of runInit(repoRoot)) print(` ${pc.green('✓')} ${line}`);\n print();\n });\n\nprogram\n .command('ignore')\n .argument('<id>', 'finding id to silence, e.g. STUB-441')\n .description('permanently silence a finding id (stored in aidiff.config.json)')\n .action((id: string) => {\n const repoRoot = repoRootOrExit();\n try {\n print(`\\n ${pc.green('✓')} ${addIgnore(repoRoot, id)}\\n`);\n } catch (err) {\n if (err instanceof ConfigError) usageError(err.message);\n throw err;\n }\n });\n\nprogram\n .command('mcp')\n .description('run as an MCP server so coding agents can vibe_check their own diffs')\n .action(() => {\n runMcpServer(version);\n });\n\nprogram.parse();\n"],"mappings":"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","names":["require"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/** Severity decides the exit code: any `error` finding fails the run. */
|
|
2
|
+
type Severity = 'error' | 'warn' | 'info';
|
|
3
|
+
/** One reviewable problem found in the diff. */
|
|
4
|
+
interface Finding {
|
|
5
|
+
/** Stable id like DUP-104, used by `ai-diff-check ignore <id>`. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Which check produced this, e.g. "duplicate", "stub". */
|
|
8
|
+
check: CheckName;
|
|
9
|
+
severity: Severity;
|
|
10
|
+
file: string;
|
|
11
|
+
line?: number;
|
|
12
|
+
/** What is wrong, in one sentence. */
|
|
13
|
+
message: string;
|
|
14
|
+
/** What the project does instead — rendered after "→". */
|
|
15
|
+
fix?: string;
|
|
16
|
+
}
|
|
17
|
+
type CheckName = 'phantom-dep' | 'duplicate' | 'deletion' | 'unreferenced' | 'stub' | 'no-test' | 'oversize' | 'standard' | 'reuse';
|
|
18
|
+
/** A single changed file in the diff, with its added/removed lines. */
|
|
19
|
+
interface ChangedFile {
|
|
20
|
+
path: string;
|
|
21
|
+
/** Lines added, keyed by new-file line number. */
|
|
22
|
+
added: Map<number, string>;
|
|
23
|
+
/** Lines removed, keyed by old-file line number. */
|
|
24
|
+
removed: Map<number, string>;
|
|
25
|
+
status: 'added' | 'modified' | 'deleted' | 'renamed';
|
|
26
|
+
}
|
|
27
|
+
/** Everything a check gets access to. Checks never touch git directly. */
|
|
28
|
+
interface CheckContext {
|
|
29
|
+
repoRoot: string;
|
|
30
|
+
baseRef: string;
|
|
31
|
+
files: ChangedFile[];
|
|
32
|
+
config: ResolvedConfig;
|
|
33
|
+
}
|
|
34
|
+
interface Check {
|
|
35
|
+
name: CheckName;
|
|
36
|
+
run(ctx: CheckContext): Promise<Finding[]>;
|
|
37
|
+
}
|
|
38
|
+
/** Shape of aidiff.config.json (all optional for the user). */
|
|
39
|
+
interface UserConfig {
|
|
40
|
+
/** Component/file size limits for the oversize check. */
|
|
41
|
+
maxFileLines?: number;
|
|
42
|
+
maxComponentLines?: number;
|
|
43
|
+
/** Declarative house rules for the standard check. */
|
|
44
|
+
standards?: StandardRule[];
|
|
45
|
+
/** Finding ids to skip, managed by `ai-diff-check ignore`. */
|
|
46
|
+
ignore?: string[];
|
|
47
|
+
/** Treat warnings as failures. */
|
|
48
|
+
strict?: boolean;
|
|
49
|
+
}
|
|
50
|
+
interface StandardRule {
|
|
51
|
+
/** Substring or pattern to ban in new code, e.g. "fetch(". */
|
|
52
|
+
ban?: string;
|
|
53
|
+
/** Regex alternative to `ban`. */
|
|
54
|
+
pattern?: string;
|
|
55
|
+
/** Glob restricting where the rule applies, e.g. "src/**". */
|
|
56
|
+
in?: string;
|
|
57
|
+
/** The approved alternative, shown as the fix. */
|
|
58
|
+
use?: string;
|
|
59
|
+
/** Path to the standards doc this rule comes from. */
|
|
60
|
+
docs?: string;
|
|
61
|
+
severity?: Severity;
|
|
62
|
+
}
|
|
63
|
+
type ResolvedConfig = Required<Pick<UserConfig, 'maxFileLines' | 'maxComponentLines' | 'strict'>> & Pick<UserConfig, 'standards'> & {
|
|
64
|
+
ignore: string[];
|
|
65
|
+
};
|
|
66
|
+
declare const DEFAULT_CONFIG: ResolvedConfig;
|
|
67
|
+
|
|
68
|
+
declare function findRepoRoot(cwd: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Pick the diff base: --base flag, else main/master (local, then origin —
|
|
71
|
+
* CI checkouts often have origin/main but no local branch), else first commit.
|
|
72
|
+
*/
|
|
73
|
+
declare function resolveBaseRef(repoRoot: string, explicit?: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Parse `git diff <base>` (working tree included) into structured files.
|
|
76
|
+
* Only ever looks at the unified diff — never the whole repo.
|
|
77
|
+
*/
|
|
78
|
+
declare function getChangedFiles(repoRoot: string, baseRef: string): ChangedFile[];
|
|
79
|
+
declare function parseUnifiedDiff(raw: string): ChangedFile[];
|
|
80
|
+
interface DiffStats {
|
|
81
|
+
files: number;
|
|
82
|
+
added: number;
|
|
83
|
+
removed: number;
|
|
84
|
+
}
|
|
85
|
+
declare function diffStats(files: ChangedFile[]): DiffStats;
|
|
86
|
+
|
|
87
|
+
/** Errors first, then warnings, then info notes — the locked output order. */
|
|
88
|
+
declare function sortFindings(findings: Finding[]): Finding[];
|
|
89
|
+
/**
|
|
90
|
+
* 0–100 verdict for the whole diff. Simple v1 formula:
|
|
91
|
+
* start at 100, -18 per error, -6 per warning, floored at 0.
|
|
92
|
+
*/
|
|
93
|
+
declare function trustScore(findings: Finding[], _stats: DiffStats): number;
|
|
94
|
+
|
|
95
|
+
declare function githubAnnotations(findings: Finding[]): string[];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Render findings as a prompt for the AI agent that wrote the diff —
|
|
99
|
+
* `ai-diff-check --fix-prompt | pbcopy`, paste, done. Scoped tightly so the
|
|
100
|
+
* agent fixes exactly what was flagged instead of refactoring the world.
|
|
101
|
+
*/
|
|
102
|
+
declare function renderFixPrompt(findings: Finding[]): string;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Write a starter config and wire the pre-commit hook: husky's
|
|
106
|
+
* .husky/pre-commit when the repo uses husky, plain .git/hooks/pre-commit
|
|
107
|
+
* otherwise. Never overwrites anything — existing files are appended to or
|
|
108
|
+
* left alone. Returns human-readable lines describing what happened.
|
|
109
|
+
*/
|
|
110
|
+
declare function runInit(repoRoot: string): string[];
|
|
111
|
+
/**
|
|
112
|
+
* Add a finding id to the config's ignore list (creating the config if
|
|
113
|
+
* needed), preserving any other keys the user has set.
|
|
114
|
+
*/
|
|
115
|
+
declare function addIgnore(repoRoot: string, id: string): string;
|
|
116
|
+
|
|
117
|
+
declare function runMcpServer(version: string): void;
|
|
118
|
+
|
|
119
|
+
declare const CONFIG_FILE = "aidiff.config.json";
|
|
120
|
+
/** Bad config is a usage error: the CLI reports it and exits 2. */
|
|
121
|
+
declare class ConfigError extends Error {
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Load `aidiff.config.json` from the repo root and resolve it over defaults.
|
|
125
|
+
* No file → defaults. Malformed JSON or a wrong-typed known key → ConfigError,
|
|
126
|
+
* never a silent fallback (a typo'd config that silently no-ops is worse than
|
|
127
|
+
* an error). Unknown keys pass through untouched for forward compatibility.
|
|
128
|
+
*/
|
|
129
|
+
declare function loadConfig(repoRoot: string): ResolvedConfig;
|
|
130
|
+
|
|
131
|
+
/** Every check the CLI knows about. Order matters: on same-line overlap the earlier check wins ties. */
|
|
132
|
+
declare const checks: Check[];
|
|
133
|
+
/**
|
|
134
|
+
* Run every registered check against the diff and return the findings, with
|
|
135
|
+
* ignored ids filtered out and same-location overlap deduplicated: when two
|
|
136
|
+
* checks flag the same file:line (e.g. DUPLICATE and REUSE on a copied
|
|
137
|
+
* component), only the most severe finding survives — first registered wins
|
|
138
|
+
* ties. One line, one finding.
|
|
139
|
+
*/
|
|
140
|
+
declare function runChecks(ctx: CheckContext): Promise<Finding[]>;
|
|
141
|
+
|
|
142
|
+
declare const stubCheck: Check;
|
|
143
|
+
|
|
144
|
+
declare const oversizeCheck: Check;
|
|
145
|
+
|
|
146
|
+
declare const phantomDepCheck: Check;
|
|
147
|
+
|
|
148
|
+
declare const deletionCheck: Check;
|
|
149
|
+
|
|
150
|
+
declare const noTestCheck: Check;
|
|
151
|
+
|
|
152
|
+
declare const unreferencedCheck: Check;
|
|
153
|
+
|
|
154
|
+
declare const duplicateCheck: Check;
|
|
155
|
+
|
|
156
|
+
declare const standardCheck: Check;
|
|
157
|
+
|
|
158
|
+
declare const reuseCheck: Check;
|
|
159
|
+
|
|
160
|
+
export { CONFIG_FILE, type ChangedFile, type Check, type CheckContext, type CheckName, ConfigError, DEFAULT_CONFIG, type Finding, type ResolvedConfig, type Severity, type StandardRule, type UserConfig, addIgnore, checks, deletionCheck, diffStats, duplicateCheck, findRepoRoot, getChangedFiles, githubAnnotations, loadConfig, noTestCheck, oversizeCheck, parseUnifiedDiff, phantomDepCheck, renderFixPrompt, resolveBaseRef, reuseCheck, runChecks, runInit, runMcpServer, sortFindings, standardCheck, stubCheck, trustScore, unreferencedCheck };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFIG_FILE,
|
|
3
|
+
ConfigError,
|
|
4
|
+
DEFAULT_CONFIG,
|
|
5
|
+
addIgnore,
|
|
6
|
+
checks,
|
|
7
|
+
deletionCheck,
|
|
8
|
+
diffStats,
|
|
9
|
+
duplicateCheck,
|
|
10
|
+
findRepoRoot,
|
|
11
|
+
getChangedFiles,
|
|
12
|
+
githubAnnotations,
|
|
13
|
+
loadConfig,
|
|
14
|
+
noTestCheck,
|
|
15
|
+
oversizeCheck,
|
|
16
|
+
parseUnifiedDiff,
|
|
17
|
+
phantomDepCheck,
|
|
18
|
+
renderFixPrompt,
|
|
19
|
+
resolveBaseRef,
|
|
20
|
+
reuseCheck,
|
|
21
|
+
runChecks,
|
|
22
|
+
runInit,
|
|
23
|
+
runMcpServer,
|
|
24
|
+
sortFindings,
|
|
25
|
+
standardCheck,
|
|
26
|
+
stubCheck,
|
|
27
|
+
trustScore,
|
|
28
|
+
unreferencedCheck
|
|
29
|
+
} from "./chunk-7JKSFHII.js";
|
|
30
|
+
export {
|
|
31
|
+
CONFIG_FILE,
|
|
32
|
+
ConfigError,
|
|
33
|
+
DEFAULT_CONFIG,
|
|
34
|
+
addIgnore,
|
|
35
|
+
checks,
|
|
36
|
+
deletionCheck,
|
|
37
|
+
diffStats,
|
|
38
|
+
duplicateCheck,
|
|
39
|
+
findRepoRoot,
|
|
40
|
+
getChangedFiles,
|
|
41
|
+
githubAnnotations,
|
|
42
|
+
loadConfig,
|
|
43
|
+
noTestCheck,
|
|
44
|
+
oversizeCheck,
|
|
45
|
+
parseUnifiedDiff,
|
|
46
|
+
phantomDepCheck,
|
|
47
|
+
renderFixPrompt,
|
|
48
|
+
resolveBaseRef,
|
|
49
|
+
reuseCheck,
|
|
50
|
+
runChecks,
|
|
51
|
+
runInit,
|
|
52
|
+
runMcpServer,
|
|
53
|
+
sortFindings,
|
|
54
|
+
standardCheck,
|
|
55
|
+
stubCheck,
|
|
56
|
+
trustScore,
|
|
57
|
+
unreferencedCheck
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=index.js.map
|