unguard 0.16.0 → 0.16.2

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/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, relative, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport pc from \"picocolors\";\nimport { executeScan, type FailOn, type RulePolicyEntry, type RulePolicySeverity, type Severity } from \"./engine.ts\";\nimport { BASELINE_FILENAME, buildBaseline, loadBaseline, writeBaseline, type BaselineData } from \"./scan/baseline.ts\";\nimport { isFailOn, isRulePolicySeverity, isSeverity, toRulePolicyEntries } from \"./scan/config.ts\";\nimport { computeExitCode } from \"./scan/policy.ts\";\nimport type { RulePolicy, RuleOverride } from \"./scan/types.ts\";\nimport type { Diagnostic } from \"./rules/types.ts\";\n\ninterface UnguardConfig {\n paths?: string[];\n ignore?: string[];\n rules?: RulePolicy;\n overrides?: RuleOverride[];\n failOn?: FailOn;\n severity?: Severity[];\n concurrency?: number;\n cache?: boolean;\n}\n\nconst COMMANDS = new Set([\"scan\", \"audit\", \"baseline\"]);\n\n// @unguard unused-export CLI entry point\nexport async function main(argv: string[]): Promise<number> {\n const rawArgs = argv.slice(2);\n const first = rawArgs[0];\n const command = first !== undefined && COMMANDS.has(first) ? first : \"scan\";\n const userArgs = first !== undefined && COMMANDS.has(first) ? rawArgs.slice(1) : rawArgs;\n\n const { values, positionals: cliPaths } = parseArgs({\n args: userArgs,\n options: {\n strict: { type: \"boolean\", default: false },\n filter: { type: \"string\" },\n fix: { type: \"boolean\", default: false },\n format: { type: \"string\", default: \"grouped\" },\n severity: { type: \"string\", multiple: true, default: [] },\n ignore: { type: \"string\", multiple: true, default: [] },\n rule: { type: \"string\", multiple: true, default: [] },\n config: { type: \"string\" },\n \"fail-on\": { type: \"string\" },\n concurrency: { type: \"string\" },\n \"no-baseline\": { type: \"boolean\", default: false },\n \"no-cache\": { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n allowPositionals: true,\n });\n\n if (values.help) {\n printHelp();\n return 0;\n }\n\n if (values.format !== \"grouped\" && values.format !== \"flat\" && values.format !== \"json\") {\n console.error(pc.red(`Invalid --format value \"${values.format}\". Use grouped, flat, or json.`));\n return 1;\n }\n\n let config: UnguardConfig | null = null;\n try {\n const configPath = findConfigPath(values.config);\n config = configPath ? loadConfig(configPath) : null;\n } catch (err) {\n return printErrorAndFail(err);\n }\n\n const rulePolicyResult = parseRulePolicyArgs(values.rule);\n if (!rulePolicyResult.ok) {\n console.error(pc.red(rulePolicyResult.message));\n return 1;\n }\n\n const severityFiltersResult = parseSeverityFilters(values.severity);\n if (!severityFiltersResult.ok) {\n console.error(pc.red(severityFiltersResult.message));\n return 1;\n }\n\n // Only an explicit --fail-on makes audit exit nonzero; config failOn\n // applies to scan only.\n const failOnResult = command === \"audit\"\n ? resolveFailOn(values[\"fail-on\"], \"none\")\n : resolveFailOn(values[\"fail-on\"], \"info\", config?.failOn);\n if (!failOnResult.ok) {\n console.error(pc.red(failOnResult.message));\n return 1;\n }\n\n const concurrencyResult = resolveConcurrency(values.concurrency, config?.concurrency);\n if (!concurrencyResult.ok) {\n console.error(pc.red(concurrencyResult.message));\n return 1;\n }\n\n const paths = cliPaths.length > 0 ? cliPaths : config?.paths ?? [];\n const ignore = [...(config?.ignore ?? []), ...values.ignore];\n const rulePolicy = [\n ...toRulePolicyEntries(config?.rules),\n ...rulePolicyResult.value,\n ];\n\n const cacheEnabled = values[\"no-cache\"] ? false : config?.cache ?? true;\n\n // The baseline subcommand regenerates the file, so the existing one must\n // not suppress anything during its scan.\n let baseline: BaselineData | null = null;\n if (command === \"scan\" && !values[\"no-baseline\"]) {\n try {\n baseline = loadBaseline(process.cwd());\n } catch (err) {\n return printErrorAndFail(err);\n }\n }\n\n const execution = await executeScan({\n paths,\n mode: command === \"audit\" ? \"audit\" : \"scan\",\n strict: values.strict,\n rules: values.filter ? [values.filter] : undefined,\n ignore: ignore.length > 0 ? ignore : undefined,\n rulePolicy: rulePolicy.length > 0 ? rulePolicy : undefined,\n overrides: config?.overrides,\n showSeverities: severityFiltersResult.value.length > 0 ? severityFiltersResult.value : config?.severity,\n failOn: failOnResult.value,\n concurrency: concurrencyResult.value,\n cache: cacheEnabled,\n baseline: baseline ?? undefined,\n });\n\n if (command === \"baseline\") {\n const data = buildBaseline(execution.visibleDiagnostics, process.cwd());\n writeBaseline(data, process.cwd());\n console.log(\n `Baseline written to ${BASELINE_FILENAME}: ${plural(execution.visibleDiagnostics.length, \"known issue\")} across ${plural(Object.keys(data.rules).length, \"file\")}.`,\n );\n return 0;\n }\n\n let diagnostics = execution.visibleDiagnostics;\n let exitCode = execution.exitCode;\n let fixedCount = 0;\n let fixedFileCount = 0;\n\n if (values.fix) {\n const result = applyFixes(diagnostics);\n fixedCount = result.applied;\n fixedFileCount = result.fileCount;\n diagnostics = diagnostics.filter((d) => !result.fixed.has(d));\n exitCode = computeExitCode(diagnostics, failOnResult.value);\n }\n\n if (values.format === \"json\") {\n console.log(JSON.stringify(buildJsonReport(diagnostics, execution.fileCount, exitCode, values.fix ? fixedCount : null), null, 2));\n return exitCode;\n }\n\n if (baseline !== null) {\n console.log(pc.dim(`Using ${BASELINE_FILENAME} (run \"unguard baseline\" to regenerate, --no-baseline to ignore)`));\n }\n if (values.fix && fixedCount > 0) {\n console.log(pc.green(`Applied ${fixedCount} fix${fixedCount === 1 ? \"\" : \"es\"} in ${fixedFileCount} file${fixedFileCount === 1 ? \"\" : \"s\"}. Re-run unguard to verify.`));\n }\n\n if (diagnostics.length === 0) {\n console.log(pc.green(`No issues found in ${plural(execution.fileCount, \"file\")}.`));\n return exitCode;\n }\n\n if (values.format === \"flat\") {\n printDiagnosticsFlat(diagnostics);\n } else {\n printDiagnostics(diagnostics);\n }\n printSummary(diagnostics, execution.fileCount);\n\n return exitCode;\n}\n\ninterface JsonDiagnostic {\n file: string;\n line: number;\n column: number;\n severity: Severity;\n ruleId: string;\n message: string;\n annotation?: string;\n fixable: boolean;\n}\n\nfunction buildJsonReport(\n diagnostics: Diagnostic[],\n fileCount: number,\n exitCode: number,\n fixedCount: number | null,\n): { diagnostics: JsonDiagnostic[]; fileCount: number; exitCode: number; fixedCount?: number } {\n return {\n diagnostics: diagnostics.map((d) => ({\n file: d.file,\n line: d.line,\n column: d.column,\n severity: d.severity,\n ruleId: d.ruleId,\n message: d.message,\n ...(d.annotation !== undefined ? { annotation: d.annotation } : {}),\n fixable: d.fix !== undefined,\n })),\n fileCount,\n exitCode,\n ...(fixedCount !== null ? { fixedCount } : {}),\n };\n}\n\n/**\n * Apply fix edits, last-to-first per file so earlier offsets stay valid.\n * Overlapping edits in one pass are skipped — the re-run picks them up.\n */\nfunction applyFixes(diagnostics: Diagnostic[]): { applied: number; fileCount: number; fixed: Set<Diagnostic> } {\n const byFile = groupByFile(diagnostics.filter((d) => d.fix !== undefined));\n\n const fixed = new Set<Diagnostic>();\n for (const [file, fileDiagnostics] of byFile) {\n const ordered = [...fileDiagnostics].sort((a, b) => (b.fix?.start ?? 0) - (a.fix?.start ?? 0));\n let content = readFileSync(file, \"utf8\");\n let lastAppliedStart = Number.POSITIVE_INFINITY;\n for (const diagnostic of ordered) {\n const fix = diagnostic.fix;\n if (fix === undefined) continue;\n if (fix.end > lastAppliedStart) continue;\n content = content.slice(0, fix.start) + fix.text + content.slice(fix.end);\n lastAppliedStart = fix.start;\n fixed.add(diagnostic);\n }\n writeFileSync(file, content, \"utf8\");\n }\n\n return { applied: fixed.size, fileCount: byFile.size, fixed };\n}\n\nfunction groupByFile(diagnostics: Diagnostic[]): Map<string, Diagnostic[]> {\n const byFile = new Map<string, Diagnostic[]>();\n for (const diagnostic of diagnostics) {\n let list = byFile.get(diagnostic.file);\n if (list === undefined) {\n list = [];\n byFile.set(diagnostic.file, list);\n }\n list.push(diagnostic);\n }\n return byFile;\n}\n\n/** Surface a failure as output + exit code 1 instead of a thrown error. */\nfunction printErrorAndFail(err: unknown): number {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(message));\n return 1;\n}\n\nfunction printHelp() {\n console.log(`unguard: data-shape static analyzer\n\nUsage:\n unguard scan [paths...] [options]\n unguard audit [paths...] [options]\n unguard baseline [paths...] [options]\n unguard [paths...] [options]\n\nCommands:\n scan Run proven rules: every finding demands a fix (default)\n audit Run heuristic rules: findings surface for review, exit\n code is 0 unless --fail-on is passed explicitly\n baseline Record current issues in unguard.baseline.json; later\n scans suppress a (file, rule) group until its count\n grows past the recorded number\n\nOptions:\n --config <path> Path to unguard config file (defaults to ./unguard.config.json)\n --strict Treat all diagnostics as errors\n --filter <rule> Run only the specified rule (in either command)\n --fix Apply available auto-fixes, then report what remains\n --rule <sel=sev> Override rule severity (supports id, *, category:<name>,\n tag:<name>, confidence:<proven|heuristic>)\n --ignore <glob> Ignore path glob. Repeatable.\n --severity <levels> Filter output by severity (comma-separated and/or repeatable)\n --fail-on <level> Exit threshold: none, error, warning, info (default: info)\n --format <mode> Output format: grouped (default), flat, or json\n --concurrency <n> Worker threads for tsconfig groups (default: auto, 1 disables)\n --no-baseline Ignore unguard.baseline.json for this scan\n --no-cache Disable on-disk diagnostic cache (node_modules/.cache/unguard)\n -h, --help Show this help\n\nExit codes:\n 0 Clean, or does not meet --fail-on threshold\n 1 Meets threshold without errors\n 2 Meets threshold with at least one error\n\nExamples:\n unguard scan src\n unguard audit src\n unguard scan src --fix\n unguard scan src --format=json\n unguard baseline src\n unguard scan src --rule duplicate-*=warning\n unguard scan src --rule category:cross-file=warning\n unguard scan src --rule tag:safety=error\n unguard scan src --severity=error,warning\n unguard scan src --fail-on=error`);\n}\n\nfunction printDiagnostics(diagnostics: Diagnostic[]) {\n const byFile = groupByFile(diagnostics);\n\n const cwd = `${process.cwd()}/`;\n const cwdBase = process.cwd();\n\n // \" \" + location column + \" \" + severity column + \" \"\n const prefixWidth = 2 + 10 + 1 + 7 + 2;\n const columns = process.stdout.columns;\n const wrapWidth = columns !== undefined && columns > prefixWidth + 20 ? columns - prefixWidth : null;\n\n for (const [file, fileDiagnostics] of byFile) {\n const rel = relative(cwdBase, file);\n console.log(pc.underline(rel));\n\n for (const diagnostic of fileDiagnostics) {\n const loc = `${diagnostic.line}:${diagnostic.column}`;\n const sev = diagnostic.severity === \"error\"\n ? pc.red(\"error\".padEnd(7))\n : diagnostic.severity === \"warning\"\n ? pc.yellow(\"warning\")\n : pc.blue(\"info\".padEnd(7));\n const msg = diagnostic.message.replaceAll(cwd, \"\");\n const head = ` ${pc.dim(loc.padEnd(10))} ${sev} `;\n const rule = pc.dim(diagnostic.ruleId);\n const annotation = diagnostic.annotation !== undefined ? ` ${pc.cyan(diagnostic.annotation)}` : \"\";\n\n if (wrapWidth === null) {\n console.log(`${head}${msg} ${rule}${annotation}`);\n continue;\n }\n\n const lines = wrapWords(msg, wrapWidth);\n const tailWidth = diagnostic.ruleId.length + (diagnostic.annotation !== undefined ? 2 + diagnostic.annotation.length : 0);\n const last = lines.length - 1;\n const tailFits = (lines[last]?.length ?? 0) + 2 + tailWidth <= wrapWidth;\n const indent = \" \".repeat(prefixWidth);\n for (let i = 0; i < lines.length; i++) {\n const start = i === 0 ? head : indent;\n const tail = i === last && tailFits ? ` ${rule}${annotation}` : \"\";\n console.log(`${start}${lines[i]}${tail}`);\n }\n if (!tailFits) console.log(`${indent}${rule}${annotation}`);\n }\n\n console.log();\n }\n}\n\n/** Greedy word wrap; words longer than the width get their own line unbroken. */\nfunction wrapWords(text: string, width: number): string[] {\n const lines: string[] = [];\n let line = \"\";\n for (const word of text.split(\" \")) {\n if (line === \"\") {\n line = word;\n } else if (line.length + 1 + word.length > width) {\n lines.push(line);\n line = word;\n } else {\n line += ` ${word}`;\n }\n }\n if (line !== \"\") lines.push(line);\n return lines;\n}\n\nfunction printDiagnosticsFlat(diagnostics: Diagnostic[]) {\n const cwdBase = process.cwd();\n const cwd = `${cwdBase}/`;\n\n for (const diagnostic of diagnostics) {\n const rel = relative(cwdBase, diagnostic.file);\n const msg = diagnostic.message.replaceAll(cwd, \"\");\n console.log(`${rel}:${diagnostic.line}:${diagnostic.column} ${diagnostic.severity} [${diagnostic.ruleId}] ${msg}`);\n }\n}\n\nfunction printSummary(diagnostics: Diagnostic[], fileCount: number) {\n const counts = new Map<string, number>();\n for (const diagnostic of diagnostics) {\n const prev = counts.get(diagnostic.ruleId);\n counts.set(diagnostic.ruleId, (prev === undefined ? 0 : prev) + 1);\n }\n\n const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]);\n const maxRule = Math.max(...sorted.map(([rule]) => rule.length));\n\n console.log(pc.bold(\"Summary\"));\n for (const [rule, count] of sorted) {\n console.log(` ${rule.padEnd(maxRule)} ${pc.bold(String(count))}`);\n }\n\n console.log(\n `\\n${pc.bold(String(diagnostics.length))} issue${diagnostics.length === 1 ? \"\" : \"s\"} in ${plural(fileCount, \"file\")}.`,\n );\n}\n\nfunction plural(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? \"\" : \"s\"}`;\n}\n\nfunction parseSeverityFilters(values: string[]): { ok: true; value: Severity[] } | { ok: false; message: string } {\n const levels = splitCsv(values);\n const parsed: Severity[] = [];\n for (const value of levels) {\n if (!isSeverity(value)) {\n return { ok: false, message: `Invalid --severity value \"${value}\". Use error, warning, info.` };\n }\n parsed.push(value);\n }\n return { ok: true, value: parsed };\n}\n\nfunction parseRulePolicyArgs(values: string[]): { ok: true; value: RulePolicyEntry[] } | { ok: false; message: string } {\n const entries: RulePolicyEntry[] = [];\n\n for (const arg of values) {\n const separator = resolveRuleSeparator(arg);\n if (separator === null) {\n return { ok: false, message: `Invalid --rule value \"${arg}\". Use <selector>=<off|info|warning|error>.` };\n }\n\n const selector = arg.slice(0, separator).trim();\n const severity = arg.slice(separator + 1).trim();\n if (!selector) {\n return { ok: false, message: `Invalid --rule value \"${arg}\". Rule selector is required.` };\n }\n if (!isRulePolicySeverity(severity)) {\n return { ok: false, message: `Invalid --rule value \"${arg}\". Severity must be off, info, warning, or error.` };\n }\n\n entries.push({ selector, severity });\n }\n\n return { ok: true, value: entries };\n}\n\nfunction resolveRuleSeparator(value: string): number | null {\n const equalIndex = value.indexOf(\"=\");\n if (equalIndex >= 0) return equalIndex;\n const colonIndex = value.lastIndexOf(\":\");\n if (colonIndex <= 0) return null;\n return colonIndex;\n}\n\nfunction splitCsv(values: string[]): string[] {\n return values\n .flatMap((value) => value.split(\",\"))\n .map((value) => value.trim())\n .filter((value) => value.length > 0);\n}\n\nfunction findConfigPath(cliValue: string | undefined): string | null {\n if (cliValue) {\n const explicitPath = resolve(process.cwd(), cliValue);\n if (!existsSync(explicitPath)) throw new Error(`Config file not found: ${cliValue}`);\n return explicitPath;\n }\n\n const candidates = [\"unguard.config.json\", \".unguardrc.json\"];\n for (const candidate of candidates) {\n const candidatePath = resolve(process.cwd(), candidate);\n if (existsSync(candidatePath)) return candidatePath;\n }\n return null;\n}\n\nfunction loadConfig(path: string): UnguardConfig {\n const text = readFileSync(path, \"utf8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid JSON in ${basename(path)}: ${message}`);\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(`Invalid config in ${basename(path)}: expected a JSON object.`);\n }\n\n const raw = parsed as Record<string, unknown>;\n const config: UnguardConfig = {};\n\n if (raw.paths !== undefined) {\n if (!isStringArray(raw.paths)) throw new Error(`Invalid config in ${basename(path)}: \"paths\" must be a string array.`);\n config.paths = raw.paths;\n }\n\n if (raw.ignore !== undefined) {\n if (!isStringArray(raw.ignore)) throw new Error(`Invalid config in ${basename(path)}: \"ignore\" must be a string array.`);\n config.ignore = raw.ignore;\n }\n\n if (raw.failOn !== undefined) {\n if (typeof raw.failOn !== \"string\" || !isFailOn(raw.failOn)) {\n throw new Error(`Invalid config in ${basename(path)}: \"failOn\" must be one of none, error, warning, info.`);\n }\n config.failOn = raw.failOn;\n }\n\n if (raw.severity !== undefined) {\n if (!isStringArray(raw.severity) || !raw.severity.every(isSeverity)) {\n throw new Error(`Invalid config in ${basename(path)}: \"severity\" must be an array of error, warning, info.`);\n }\n config.severity = raw.severity;\n }\n\n if (raw.concurrency !== undefined) {\n if (typeof raw.concurrency !== \"number\" || !Number.isInteger(raw.concurrency) || raw.concurrency < 1) {\n throw new Error(`Invalid config in ${basename(path)}: \"concurrency\" must be a positive integer.`);\n }\n config.concurrency = raw.concurrency;\n }\n\n if (raw.cache !== undefined) {\n if (typeof raw.cache !== \"boolean\") {\n throw new Error(`Invalid config in ${basename(path)}: \"cache\" must be a boolean.`);\n }\n config.cache = raw.cache;\n }\n\n if (raw.rules !== undefined) {\n config.rules = parseRulesObject(raw.rules, `\"rules\"`, basename(path));\n }\n\n if (raw.overrides !== undefined) {\n if (!Array.isArray(raw.overrides)) {\n throw new Error(`Invalid config in ${basename(path)}: \"overrides\" must be an array.`);\n }\n config.overrides = raw.overrides.map((entry: unknown, index) => {\n if (typeof entry !== \"object\" || entry === null || Array.isArray(entry)) {\n throw new Error(`Invalid config in ${basename(path)}: overrides[${index}] must be an object.`);\n }\n const override = entry as Record<string, unknown>;\n if (!isStringArray(override.files) || override.files.length === 0) {\n throw new Error(`Invalid config in ${basename(path)}: overrides[${index}].files must be a non-empty string array.`);\n }\n return {\n files: override.files,\n rules: parseRulesObject(override.rules, `overrides[${index}].rules`, basename(path)),\n };\n });\n }\n\n return config;\n}\n\nfunction parseRulesObject(value: unknown, label: string, configName: string): Record<string, RulePolicySeverity> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`Invalid config in ${configName}: ${label} must be an object.`);\n }\n\n const rules: Record<string, RulePolicySeverity> = {};\n for (const [selector, severity] of Object.entries(value as Record<string, unknown>)) {\n if (typeof severity !== \"string\" || !isRulePolicySeverity(severity)) {\n throw new Error(`Invalid config in ${configName}: rule \"${selector}\" in ${label} must be off, info, warning, or error.`);\n }\n rules[selector] = severity;\n }\n return rules;\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === \"string\");\n}\n\nfunction resolveFailOn(\n cliValue: string | undefined,\n fallback: FailOn,\n configValue?: FailOn,\n): { ok: true; value: FailOn } | { ok: false; message: string } {\n const selected = cliValue ?? configValue ?? fallback;\n if (!isFailOn(selected)) {\n return { ok: false, message: `Invalid --fail-on value \"${selected}\". Use none, error, warning, or info.` };\n }\n return { ok: true, value: selected };\n}\n\nfunction resolveConcurrency(\n cliValue: string | undefined,\n configValue: number | undefined,\n): { ok: true; value: number | undefined } | { ok: false; message: string } {\n if (cliValue !== undefined) {\n const parsed = Number(cliValue);\n if (!Number.isInteger(parsed) || parsed < 1) {\n return { ok: false, message: `Invalid --concurrency value \"${cliValue}\". Use a positive integer.` };\n }\n return { ok: true, value: parsed };\n }\n if (configValue !== undefined) {\n if (!Number.isInteger(configValue) || configValue < 1) {\n return { ok: false, message: \"Invalid concurrency in config: must be a positive integer.\" };\n }\n return { ok: true, value: configValue };\n }\n return { ok: true, value: undefined };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, relative, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport pc from \"picocolors\";\nimport { executeScan, type FailOn, type RulePolicyEntry, type RulePolicySeverity, type Severity } from \"./engine.ts\";\nimport { BASELINE_FILENAME, buildBaseline, loadBaseline, writeBaseline, type BaselineData } from \"./scan/baseline.ts\";\nimport { isFailOn, isRulePolicySeverity, isSeverity, toRulePolicyEntries } from \"./scan/config.ts\";\nimport { computeExitCode } from \"./scan/policy.ts\";\nimport type { RulePolicy, RuleOverride } from \"./scan/types.ts\";\nimport type { Diagnostic } from \"./rules/types.ts\";\n\ninterface UnguardConfig {\n paths?: string[];\n ignore?: string[];\n rules?: RulePolicy;\n overrides?: RuleOverride[];\n failOn?: FailOn;\n severity?: Severity[];\n concurrency?: number;\n cache?: boolean;\n}\n\nconst COMMANDS = new Set([\"scan\", \"audit\", \"baseline\"]);\n\n// @unguard unused-export CLI entry point\nexport async function main(argv: string[]): Promise<number> {\n const rawArgs = argv.slice(2);\n const first = rawArgs[0];\n const command = first !== undefined && COMMANDS.has(first) ? first : \"scan\";\n const userArgs = first !== undefined && COMMANDS.has(first) ? rawArgs.slice(1) : rawArgs;\n\n const { values, positionals: cliPaths } = parseArgs({\n args: userArgs,\n options: {\n strict: { type: \"boolean\", default: false },\n filter: { type: \"string\" },\n fix: { type: \"boolean\", default: false },\n format: { type: \"string\", default: \"grouped\" },\n severity: { type: \"string\", multiple: true, default: [] },\n ignore: { type: \"string\", multiple: true, default: [] },\n rule: { type: \"string\", multiple: true, default: [] },\n config: { type: \"string\" },\n \"fail-on\": { type: \"string\" },\n concurrency: { type: \"string\" },\n \"no-baseline\": { type: \"boolean\", default: false },\n \"no-cache\": { type: \"boolean\", default: false },\n version: { type: \"boolean\", short: \"v\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n allowPositionals: true,\n });\n\n if (values.version) {\n console.log(readVersion());\n return 0;\n }\n\n if (values.help) {\n printHelp();\n return 0;\n }\n\n if (values.format !== \"grouped\" && values.format !== \"flat\" && values.format !== \"json\") {\n console.error(pc.red(`Invalid --format value \"${values.format}\". Use grouped, flat, or json.`));\n return 1;\n }\n\n let config: UnguardConfig | null = null;\n try {\n const configPath = findConfigPath(values.config);\n config = configPath ? loadConfig(configPath) : null;\n } catch (err) {\n return printErrorAndFail(err);\n }\n\n const rulePolicyResult = parseRulePolicyArgs(values.rule);\n if (!rulePolicyResult.ok) {\n console.error(pc.red(rulePolicyResult.message));\n return 1;\n }\n\n const severityFiltersResult = parseSeverityFilters(values.severity);\n if (!severityFiltersResult.ok) {\n console.error(pc.red(severityFiltersResult.message));\n return 1;\n }\n\n // Only an explicit --fail-on makes audit exit nonzero; config failOn\n // applies to scan only.\n const failOnResult = command === \"audit\"\n ? resolveFailOn(values[\"fail-on\"], \"none\")\n : resolveFailOn(values[\"fail-on\"], \"info\", config?.failOn);\n if (!failOnResult.ok) {\n console.error(pc.red(failOnResult.message));\n return 1;\n }\n\n const concurrencyResult = resolveConcurrency(values.concurrency, config?.concurrency);\n if (!concurrencyResult.ok) {\n console.error(pc.red(concurrencyResult.message));\n return 1;\n }\n\n const paths = cliPaths.length > 0 ? cliPaths : config?.paths ?? [];\n const ignore = [...(config?.ignore ?? []), ...values.ignore];\n const rulePolicy = [\n ...toRulePolicyEntries(config?.rules),\n ...rulePolicyResult.value,\n ];\n\n const cacheEnabled = values[\"no-cache\"] ? false : config?.cache ?? true;\n\n // The baseline subcommand regenerates the file, so the existing one must\n // not suppress anything during its scan.\n let baseline: BaselineData | null = null;\n if (command === \"scan\" && !values[\"no-baseline\"]) {\n try {\n baseline = loadBaseline(process.cwd());\n } catch (err) {\n return printErrorAndFail(err);\n }\n }\n\n const execution = await executeScan({\n paths,\n mode: command === \"audit\" ? \"audit\" : \"scan\",\n strict: values.strict,\n rules: values.filter ? [values.filter] : undefined,\n ignore: ignore.length > 0 ? ignore : undefined,\n rulePolicy: rulePolicy.length > 0 ? rulePolicy : undefined,\n overrides: config?.overrides,\n showSeverities: severityFiltersResult.value.length > 0 ? severityFiltersResult.value : config?.severity,\n failOn: failOnResult.value,\n concurrency: concurrencyResult.value,\n cache: cacheEnabled,\n baseline: baseline ?? undefined,\n });\n\n if (command === \"baseline\") {\n const data = buildBaseline(execution.visibleDiagnostics, process.cwd());\n writeBaseline(data, process.cwd());\n console.log(\n `Baseline written to ${BASELINE_FILENAME}: ${plural(execution.visibleDiagnostics.length, \"known issue\")} across ${plural(Object.keys(data.rules).length, \"file\")}.`,\n );\n return 0;\n }\n\n let diagnostics = execution.visibleDiagnostics;\n let exitCode = execution.exitCode;\n let fixedCount = 0;\n let fixedFileCount = 0;\n\n if (values.fix) {\n const result = applyFixes(diagnostics);\n fixedCount = result.applied;\n fixedFileCount = result.fileCount;\n diagnostics = diagnostics.filter((d) => !result.fixed.has(d));\n exitCode = computeExitCode(diagnostics, failOnResult.value);\n }\n\n if (values.format === \"json\") {\n console.log(JSON.stringify(buildJsonReport(diagnostics, execution.fileCount, exitCode, values.fix ? fixedCount : null), null, 2));\n return exitCode;\n }\n\n if (baseline !== null) {\n console.log(pc.dim(`Using ${BASELINE_FILENAME} (run \"unguard baseline\" to regenerate, --no-baseline to ignore)`));\n }\n if (values.fix && fixedCount > 0) {\n console.log(pc.green(`Applied ${fixedCount} fix${fixedCount === 1 ? \"\" : \"es\"} in ${fixedFileCount} file${fixedFileCount === 1 ? \"\" : \"s\"}. Re-run unguard to verify.`));\n }\n\n if (diagnostics.length === 0) {\n console.log(pc.green(`No issues found in ${plural(execution.fileCount, \"file\")}.`));\n return exitCode;\n }\n\n if (values.format === \"flat\") {\n printDiagnosticsFlat(diagnostics);\n } else {\n printDiagnostics(diagnostics);\n }\n printSummary(diagnostics, execution.fileCount);\n\n return exitCode;\n}\n\ninterface JsonDiagnostic {\n file: string;\n line: number;\n column: number;\n severity: Severity;\n ruleId: string;\n message: string;\n annotation?: string;\n fixable: boolean;\n}\n\nfunction buildJsonReport(\n diagnostics: Diagnostic[],\n fileCount: number,\n exitCode: number,\n fixedCount: number | null,\n): { diagnostics: JsonDiagnostic[]; fileCount: number; exitCode: number; fixedCount?: number } {\n return {\n diagnostics: diagnostics.map((d) => ({\n file: d.file,\n line: d.line,\n column: d.column,\n severity: d.severity,\n ruleId: d.ruleId,\n message: d.message,\n ...(d.annotation !== undefined ? { annotation: d.annotation } : {}),\n fixable: d.fix !== undefined,\n })),\n fileCount,\n exitCode,\n ...(fixedCount !== null ? { fixedCount } : {}),\n };\n}\n\n/**\n * Apply fix edits, last-to-first per file so earlier offsets stay valid.\n * Overlapping edits in one pass are skipped — the re-run picks them up.\n */\nfunction applyFixes(diagnostics: Diagnostic[]): { applied: number; fileCount: number; fixed: Set<Diagnostic> } {\n const byFile = groupByFile(diagnostics.filter((d) => d.fix !== undefined));\n\n const fixed = new Set<Diagnostic>();\n for (const [file, fileDiagnostics] of byFile) {\n const ordered = [...fileDiagnostics].sort((a, b) => (b.fix?.start ?? 0) - (a.fix?.start ?? 0));\n let content = readFileSync(file, \"utf8\");\n let lastAppliedStart = Number.POSITIVE_INFINITY;\n for (const diagnostic of ordered) {\n const fix = diagnostic.fix;\n if (fix === undefined) continue;\n if (fix.end > lastAppliedStart) continue;\n content = content.slice(0, fix.start) + fix.text + content.slice(fix.end);\n lastAppliedStart = fix.start;\n fixed.add(diagnostic);\n }\n writeFileSync(file, content, \"utf8\");\n }\n\n return { applied: fixed.size, fileCount: byFile.size, fixed };\n}\n\nfunction groupByFile(diagnostics: Diagnostic[]): Map<string, Diagnostic[]> {\n const byFile = new Map<string, Diagnostic[]>();\n for (const diagnostic of diagnostics) {\n let list = byFile.get(diagnostic.file);\n if (list === undefined) {\n list = [];\n byFile.set(diagnostic.file, list);\n }\n list.push(diagnostic);\n }\n return byFile;\n}\n\n/** Surface a failure as output + exit code 1 instead of a thrown error. */\nfunction printErrorAndFail(err: unknown): number {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(message));\n return 1;\n}\n\nfunction readVersion(): string {\n const pkg = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"));\n const version: unknown = pkg.version;\n return typeof version === \"string\" ? version : \"unknown\";\n}\n\nfunction printHelp() {\n console.log(`unguard: data-shape static analyzer\n\nUsage:\n unguard scan [paths...] [options]\n unguard audit [paths...] [options]\n unguard baseline [paths...] [options]\n unguard [paths...] [options]\n\nCommands:\n scan Run proven rules: every finding demands a fix (default)\n audit Run heuristic rules: findings surface for review, exit\n code is 0 unless --fail-on is passed explicitly\n baseline Record current issues in unguard.baseline.json; later\n scans suppress a (file, rule) group until its count\n grows past the recorded number\n\nOptions:\n --config <path> Path to unguard config file (defaults to ./unguard.config.json)\n --strict Treat all diagnostics as errors\n --filter <rule> Run only the specified rule (in either command)\n --fix Apply available auto-fixes, then report what remains\n --rule <sel=sev> Override rule severity (supports id, *, category:<name>,\n tag:<name>, confidence:<proven|heuristic>)\n --ignore <glob> Ignore path glob. Repeatable.\n --severity <levels> Filter output by severity (comma-separated and/or repeatable)\n --fail-on <level> Exit threshold: none, error, warning, info (default: info)\n --format <mode> Output format: grouped (default), flat, or json\n --concurrency <n> Worker threads for tsconfig groups (default: auto, 1 disables)\n --no-baseline Ignore unguard.baseline.json for this scan\n --no-cache Disable on-disk diagnostic cache (node_modules/.cache/unguard)\n -v, --version Print version and exit\n -h, --help Show this help\n\nExit codes:\n 0 Clean, or does not meet --fail-on threshold\n 1 Meets threshold without errors\n 2 Meets threshold with at least one error\n\nExamples:\n unguard scan src\n unguard audit src\n unguard scan src --fix\n unguard scan src --format=json\n unguard baseline src\n unguard scan src --rule duplicate-*=warning\n unguard scan src --rule category:cross-file=warning\n unguard scan src --rule tag:safety=error\n unguard scan src --severity=error,warning\n unguard scan src --fail-on=error`);\n}\n\nfunction printDiagnostics(diagnostics: Diagnostic[]) {\n const byFile = groupByFile(diagnostics);\n\n const cwd = `${process.cwd()}/`;\n const cwdBase = process.cwd();\n\n // \" \" + location column + \" \" + severity column + \" \"\n const prefixWidth = 2 + 10 + 1 + 7 + 2;\n const columns = process.stdout.columns;\n const wrapWidth = columns !== undefined && columns > prefixWidth + 20 ? columns - prefixWidth : null;\n\n for (const [file, fileDiagnostics] of byFile) {\n const rel = relative(cwdBase, file);\n console.log(pc.underline(rel));\n\n for (const diagnostic of fileDiagnostics) {\n const loc = `${diagnostic.line}:${diagnostic.column}`;\n const sev = diagnostic.severity === \"error\"\n ? pc.red(\"error\".padEnd(7))\n : diagnostic.severity === \"warning\"\n ? pc.yellow(\"warning\")\n : pc.blue(\"info\".padEnd(7));\n const msg = diagnostic.message.replaceAll(cwd, \"\");\n const head = ` ${pc.dim(loc.padEnd(10))} ${sev} `;\n const rule = pc.dim(diagnostic.ruleId);\n const annotation = diagnostic.annotation !== undefined ? ` ${pc.cyan(diagnostic.annotation)}` : \"\";\n\n if (wrapWidth === null) {\n console.log(`${head}${msg} ${rule}${annotation}`);\n continue;\n }\n\n const lines = wrapWords(msg, wrapWidth);\n const tailWidth = diagnostic.ruleId.length + (diagnostic.annotation !== undefined ? 2 + diagnostic.annotation.length : 0);\n const last = lines.length - 1;\n const tailFits = (lines[last]?.length ?? 0) + 2 + tailWidth <= wrapWidth;\n const indent = \" \".repeat(prefixWidth);\n for (let i = 0; i < lines.length; i++) {\n const start = i === 0 ? head : indent;\n const tail = i === last && tailFits ? ` ${rule}${annotation}` : \"\";\n console.log(`${start}${lines[i]}${tail}`);\n }\n if (!tailFits) console.log(`${indent}${rule}${annotation}`);\n }\n\n console.log();\n }\n}\n\n/** Greedy word wrap; words longer than the width get their own line unbroken. */\nfunction wrapWords(text: string, width: number): string[] {\n const lines: string[] = [];\n let line = \"\";\n for (const word of text.split(\" \")) {\n if (line === \"\") {\n line = word;\n } else if (line.length + 1 + word.length > width) {\n lines.push(line);\n line = word;\n } else {\n line += ` ${word}`;\n }\n }\n if (line !== \"\") lines.push(line);\n return lines;\n}\n\nfunction printDiagnosticsFlat(diagnostics: Diagnostic[]) {\n const cwdBase = process.cwd();\n const cwd = `${cwdBase}/`;\n\n for (const diagnostic of diagnostics) {\n const rel = relative(cwdBase, diagnostic.file);\n const msg = diagnostic.message.replaceAll(cwd, \"\");\n console.log(`${rel}:${diagnostic.line}:${diagnostic.column} ${diagnostic.severity} [${diagnostic.ruleId}] ${msg}`);\n }\n}\n\nfunction printSummary(diagnostics: Diagnostic[], fileCount: number) {\n const counts = new Map<string, number>();\n for (const diagnostic of diagnostics) {\n const prev = counts.get(diagnostic.ruleId);\n counts.set(diagnostic.ruleId, (prev === undefined ? 0 : prev) + 1);\n }\n\n const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]);\n const maxRule = Math.max(...sorted.map(([rule]) => rule.length));\n\n console.log(pc.bold(\"Summary\"));\n for (const [rule, count] of sorted) {\n console.log(` ${rule.padEnd(maxRule)} ${pc.bold(String(count))}`);\n }\n\n console.log(\n `\\n${pc.bold(String(diagnostics.length))} issue${diagnostics.length === 1 ? \"\" : \"s\"} in ${plural(fileCount, \"file\")}.`,\n );\n}\n\nfunction plural(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? \"\" : \"s\"}`;\n}\n\nfunction parseSeverityFilters(values: string[]): { ok: true; value: Severity[] } | { ok: false; message: string } {\n const levels = splitCsv(values);\n const parsed: Severity[] = [];\n for (const value of levels) {\n if (!isSeverity(value)) {\n return { ok: false, message: `Invalid --severity value \"${value}\". Use error, warning, info.` };\n }\n parsed.push(value);\n }\n return { ok: true, value: parsed };\n}\n\nfunction parseRulePolicyArgs(values: string[]): { ok: true; value: RulePolicyEntry[] } | { ok: false; message: string } {\n const entries: RulePolicyEntry[] = [];\n\n for (const arg of values) {\n const separator = resolveRuleSeparator(arg);\n if (separator === null) {\n return { ok: false, message: `Invalid --rule value \"${arg}\". Use <selector>=<off|info|warning|error>.` };\n }\n\n const selector = arg.slice(0, separator).trim();\n const severity = arg.slice(separator + 1).trim();\n if (!selector) {\n return { ok: false, message: `Invalid --rule value \"${arg}\". Rule selector is required.` };\n }\n if (!isRulePolicySeverity(severity)) {\n return { ok: false, message: `Invalid --rule value \"${arg}\". Severity must be off, info, warning, or error.` };\n }\n\n entries.push({ selector, severity });\n }\n\n return { ok: true, value: entries };\n}\n\nfunction resolveRuleSeparator(value: string): number | null {\n const equalIndex = value.indexOf(\"=\");\n if (equalIndex >= 0) return equalIndex;\n const colonIndex = value.lastIndexOf(\":\");\n if (colonIndex <= 0) return null;\n return colonIndex;\n}\n\nfunction splitCsv(values: string[]): string[] {\n return values\n .flatMap((value) => value.split(\",\"))\n .map((value) => value.trim())\n .filter((value) => value.length > 0);\n}\n\nfunction findConfigPath(cliValue: string | undefined): string | null {\n if (cliValue) {\n const explicitPath = resolve(process.cwd(), cliValue);\n if (!existsSync(explicitPath)) throw new Error(`Config file not found: ${cliValue}`);\n return explicitPath;\n }\n\n const candidates = [\"unguard.config.json\", \".unguardrc.json\"];\n for (const candidate of candidates) {\n const candidatePath = resolve(process.cwd(), candidate);\n if (existsSync(candidatePath)) return candidatePath;\n }\n return null;\n}\n\nfunction loadConfig(path: string): UnguardConfig {\n const text = readFileSync(path, \"utf8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid JSON in ${basename(path)}: ${message}`);\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(`Invalid config in ${basename(path)}: expected a JSON object.`);\n }\n\n const raw = parsed as Record<string, unknown>;\n const config: UnguardConfig = {};\n\n if (raw.paths !== undefined) {\n if (!isStringArray(raw.paths)) throw new Error(`Invalid config in ${basename(path)}: \"paths\" must be a string array.`);\n config.paths = raw.paths;\n }\n\n if (raw.ignore !== undefined) {\n if (!isStringArray(raw.ignore)) throw new Error(`Invalid config in ${basename(path)}: \"ignore\" must be a string array.`);\n config.ignore = raw.ignore;\n }\n\n if (raw.failOn !== undefined) {\n if (typeof raw.failOn !== \"string\" || !isFailOn(raw.failOn)) {\n throw new Error(`Invalid config in ${basename(path)}: \"failOn\" must be one of none, error, warning, info.`);\n }\n config.failOn = raw.failOn;\n }\n\n if (raw.severity !== undefined) {\n if (!isStringArray(raw.severity) || !raw.severity.every(isSeverity)) {\n throw new Error(`Invalid config in ${basename(path)}: \"severity\" must be an array of error, warning, info.`);\n }\n config.severity = raw.severity;\n }\n\n if (raw.concurrency !== undefined) {\n if (typeof raw.concurrency !== \"number\" || !Number.isInteger(raw.concurrency) || raw.concurrency < 1) {\n throw new Error(`Invalid config in ${basename(path)}: \"concurrency\" must be a positive integer.`);\n }\n config.concurrency = raw.concurrency;\n }\n\n if (raw.cache !== undefined) {\n if (typeof raw.cache !== \"boolean\") {\n throw new Error(`Invalid config in ${basename(path)}: \"cache\" must be a boolean.`);\n }\n config.cache = raw.cache;\n }\n\n if (raw.rules !== undefined) {\n config.rules = parseRulesObject(raw.rules, `\"rules\"`, basename(path));\n }\n\n if (raw.overrides !== undefined) {\n if (!Array.isArray(raw.overrides)) {\n throw new Error(`Invalid config in ${basename(path)}: \"overrides\" must be an array.`);\n }\n config.overrides = raw.overrides.map((entry: unknown, index) => {\n if (typeof entry !== \"object\" || entry === null || Array.isArray(entry)) {\n throw new Error(`Invalid config in ${basename(path)}: overrides[${index}] must be an object.`);\n }\n const override = entry as Record<string, unknown>;\n if (!isStringArray(override.files) || override.files.length === 0) {\n throw new Error(`Invalid config in ${basename(path)}: overrides[${index}].files must be a non-empty string array.`);\n }\n return {\n files: override.files,\n rules: parseRulesObject(override.rules, `overrides[${index}].rules`, basename(path)),\n };\n });\n }\n\n return config;\n}\n\nfunction parseRulesObject(value: unknown, label: string, configName: string): Record<string, RulePolicySeverity> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`Invalid config in ${configName}: ${label} must be an object.`);\n }\n\n const rules: Record<string, RulePolicySeverity> = {};\n for (const [selector, severity] of Object.entries(value as Record<string, unknown>)) {\n if (typeof severity !== \"string\" || !isRulePolicySeverity(severity)) {\n throw new Error(`Invalid config in ${configName}: rule \"${selector}\" in ${label} must be off, info, warning, or error.`);\n }\n rules[selector] = severity;\n }\n return rules;\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === \"string\");\n}\n\nfunction resolveFailOn(\n cliValue: string | undefined,\n fallback: FailOn,\n configValue?: FailOn,\n): { ok: true; value: FailOn } | { ok: false; message: string } {\n const selected = cliValue ?? configValue ?? fallback;\n if (!isFailOn(selected)) {\n return { ok: false, message: `Invalid --fail-on value \"${selected}\". Use none, error, warning, or info.` };\n }\n return { ok: true, value: selected };\n}\n\nfunction resolveConcurrency(\n cliValue: string | undefined,\n configValue: number | undefined,\n): { ok: true; value: number | undefined } | { ok: false; message: string } {\n if (cliValue !== undefined) {\n const parsed = Number(cliValue);\n if (!Number.isInteger(parsed) || parsed < 1) {\n return { ok: false, message: `Invalid --concurrency value \"${cliValue}\". Use a positive integer.` };\n }\n return { ok: true, value: parsed };\n }\n if (configValue !== undefined) {\n if (!Number.isInteger(configValue) || configValue < 1) {\n return { ok: false, message: \"Invalid concurrency in config: must be a positive integer.\" };\n }\n return { ok: true, value: configValue };\n }\n return { ok: true, value: undefined };\n}\n"],"mappings":"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"}
@@ -0,0 +1,479 @@
1
+ import { i as getRuleMetadata, r as allRules, t as analyzeFiles } from "./analyze-Cccs9TDD.js";
2
+ import { createRequire } from "node:module";
3
+ import { availableParallelism } from "node:os";
4
+ import { join, relative, resolve, sep } from "node:path";
5
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
6
+ import { createHash } from "node:crypto";
7
+ import fg from "fast-glob";
8
+ import ignore from "ignore";
9
+ //#region src/scan/cache.ts
10
+ const CACHE_VERSION = 1;
11
+ const CACHE_FILENAME = "scan-cache.json";
12
+ /** Walk up from `startDir` to the nearest `node_modules`, returning its `.cache/unguard/v<N>` subdir. */
13
+ function resolveCacheDir(startDir) {
14
+ let dir = startDir;
15
+ for (;;) {
16
+ const nm = join(dir, "node_modules");
17
+ if (existsSync(nm)) return join(nm, ".cache", "unguard", `v${CACHE_VERSION}`);
18
+ const parent = join(dir, "..");
19
+ if (parent === dir) return null;
20
+ dir = parent;
21
+ }
22
+ }
23
+ function computeScanKey(input) {
24
+ const ruleSig = [...input.rules].map((r) => `${r.id}=${r.severity}`).sort().join(",");
25
+ const payload = JSON.stringify({
26
+ unguardVersion: input.unguardVersion,
27
+ ruleSig,
28
+ paths: [...input.paths].sort(),
29
+ ignore: [...input.ignore].sort(),
30
+ strict: input.strict,
31
+ failOn: input.failOn,
32
+ showSeverities: input.showSeverities ? [...input.showSeverities].sort() : null
33
+ });
34
+ return createHash("sha256").update(payload).digest("hex").slice(0, 16);
35
+ }
36
+ function hashFile(path, prevFingerprint) {
37
+ const s = statSync(path, { bigint: true });
38
+ const statTag = `${s.size}:${s.mtimeNs.toString()}`;
39
+ if (prevFingerprint !== void 0) {
40
+ if (prevFingerprint.split("@", 1)[0] === statTag) return prevFingerprint;
41
+ }
42
+ const buf = readFileSync(path);
43
+ return `${statTag}@${createHash("sha1").update(buf).digest("hex").slice(0, 16)}`;
44
+ }
45
+ function hashFiles(paths, prev) {
46
+ const result = {};
47
+ for (const path of paths) result[path] = hashFile(path, prev?.[path]);
48
+ return result;
49
+ }
50
+ function readScanCache(dir) {
51
+ const path = join(dir, CACHE_FILENAME);
52
+ if (!existsSync(path)) return null;
53
+ const text = trySync(() => readFileSync(path, "utf8"), "read failed");
54
+ if (text === null) return null;
55
+ const raw = trySync(() => JSON.parse(text), "parse failed");
56
+ if (raw === null) return null;
57
+ return validateScanCache(raw);
58
+ }
59
+ function writeScanCache(dir, entry) {
60
+ trySync(() => {
61
+ mkdirSync(dir, { recursive: true });
62
+ writeFileSync(join(dir, CACHE_FILENAME), JSON.stringify(entry));
63
+ return true;
64
+ }, "write failed");
65
+ }
66
+ /** Cache hit iff scan config matches and every file's content hash matches. Ignores stat. */
67
+ function cacheCovers(cached, context, currentHashes) {
68
+ if (cached.unguardVersion !== context.unguardVersion) return false;
69
+ if (cached.scanKey !== context.scanKey) return false;
70
+ const cachedFiles = Object.keys(cached.fileHashes);
71
+ const currentFiles = Object.keys(currentHashes);
72
+ if (cachedFiles.length !== currentFiles.length) return false;
73
+ for (const file of currentFiles) {
74
+ const prev = cached.fileHashes[file];
75
+ const cur = currentHashes[file];
76
+ if (prev === void 0 || cur === void 0) return false;
77
+ if (contentHashOf(prev) !== contentHashOf(cur)) return false;
78
+ }
79
+ return true;
80
+ }
81
+ function contentHashOf(fingerprint) {
82
+ const idx = fingerprint.indexOf("@");
83
+ return idx >= 0 ? fingerprint.slice(idx + 1) : fingerprint;
84
+ }
85
+ function validateScanCache(raw) {
86
+ if (typeof raw !== "object" || raw === null) return null;
87
+ const entry = raw;
88
+ if (entry.version !== CACHE_VERSION) return null;
89
+ if (typeof entry.scanKey !== "string") return null;
90
+ if (typeof entry.unguardVersion !== "string") return null;
91
+ if (typeof entry.fileCount !== "number") return null;
92
+ const fileHashes = validateFileHashes(entry.fileHashes);
93
+ if (fileHashes === null) return null;
94
+ const diagnostics = validateDiagnostics(entry.diagnostics);
95
+ if (diagnostics === null) return null;
96
+ return {
97
+ version: entry.version,
98
+ unguardVersion: entry.unguardVersion,
99
+ scanKey: entry.scanKey,
100
+ fileHashes,
101
+ diagnostics,
102
+ fileCount: entry.fileCount
103
+ };
104
+ }
105
+ function validateFileHashes(raw) {
106
+ if (typeof raw !== "object" || raw === null) return null;
107
+ const result = {};
108
+ for (const [key, value] of Object.entries(raw)) {
109
+ if (typeof value !== "string") return null;
110
+ result[key] = value;
111
+ }
112
+ return result;
113
+ }
114
+ function validateDiagnostics(raw) {
115
+ if (!Array.isArray(raw)) return null;
116
+ const list = [];
117
+ for (const item of raw) {
118
+ const diag = validateDiagnostic(item);
119
+ if (diag === null) return null;
120
+ list.push(diag);
121
+ }
122
+ return list;
123
+ }
124
+ function validateDiagnostic(item) {
125
+ if (typeof item !== "object" || item === null) return null;
126
+ if (!("file" in item) || typeof item.file !== "string") return null;
127
+ if (!("ruleId" in item) || typeof item.ruleId !== "string") return null;
128
+ if (!("line" in item) || typeof item.line !== "number") return null;
129
+ if (!("column" in item) || typeof item.column !== "number") return null;
130
+ if (!("severity" in item)) return null;
131
+ if (item.severity !== "error" && item.severity !== "warning" && item.severity !== "info") return null;
132
+ if (!("message" in item) || typeof item.message !== "string") return null;
133
+ const annotation = "annotation" in item ? item.annotation : void 0;
134
+ if (annotation !== void 0 && typeof annotation !== "string") return null;
135
+ return {
136
+ file: item.file,
137
+ ruleId: item.ruleId,
138
+ line: item.line,
139
+ column: item.column,
140
+ severity: item.severity,
141
+ message: item.message,
142
+ annotation
143
+ };
144
+ }
145
+ function trySync(fn, label) {
146
+ try {
147
+ return fn();
148
+ } catch (err) {
149
+ return debugLog(label, err);
150
+ }
151
+ }
152
+ function debugLog(label, err) {
153
+ if (process.env.UNGUARD_DEBUG !== void 0) console.error(`[unguard] cache ${label}:`, err);
154
+ return null;
155
+ }
156
+ //#endregion
157
+ //#region src/scan/config.ts
158
+ const BUILTIN_IGNORE = [
159
+ "**/node_modules/**",
160
+ "**/dist/**",
161
+ "**/.git/**",
162
+ "**/*.d.ts",
163
+ "**/*.d.cts",
164
+ "**/*.d.mts"
165
+ ];
166
+ const GENERATED_IGNORE = ["**/*.gen.*", "**/*.generated.*"];
167
+ const DEFAULT_FAIL_ON = "info";
168
+ function resolveScanConfig(options) {
169
+ const paths = options.paths.length > 0 ? options.paths : ["."];
170
+ const ignore = [
171
+ ...BUILTIN_IGNORE,
172
+ ...GENERATED_IGNORE,
173
+ ...options.ignore ?? []
174
+ ];
175
+ return {
176
+ paths,
177
+ mode: options.mode ?? "scan",
178
+ strict: options.strict ?? false,
179
+ rules: options.rules ? [...options.rules] : null,
180
+ ignore,
181
+ rulePolicy: toRulePolicyEntries(options.rulePolicy),
182
+ overrides: toResolvedOverrides(options.overrides),
183
+ showSeverities: normalizeSeveritySet(options.showSeverities),
184
+ failOn: options.failOn ?? DEFAULT_FAIL_ON,
185
+ useGitIgnore: options.useGitIgnore ?? true,
186
+ concurrency: options.concurrency,
187
+ cache: options.cache ?? true,
188
+ baseline: options.baseline ?? null
189
+ };
190
+ }
191
+ function toResolvedOverrides(overrides) {
192
+ if (overrides === void 0) return [];
193
+ return overrides.map((entry) => ({
194
+ files: [...entry.files],
195
+ rulePolicy: toRulePolicyEntries(entry.rules)
196
+ }));
197
+ }
198
+ function toRulePolicyEntries(policy) {
199
+ if (policy === void 0) return [];
200
+ if (Array.isArray(policy)) return [...policy];
201
+ const entries = [];
202
+ for (const [selector, severity] of Object.entries(policy)) entries.push({
203
+ selector,
204
+ severity
205
+ });
206
+ return entries;
207
+ }
208
+ function normalizeSeveritySet(levels) {
209
+ if (levels === void 0 || levels.length === 0) return null;
210
+ return new Set(levels);
211
+ }
212
+ function isRulePolicySeverity(value) {
213
+ return value === "off" || value === "info" || value === "warning" || value === "error";
214
+ }
215
+ function isSeverity(value) {
216
+ return value === "info" || value === "warning" || value === "error";
217
+ }
218
+ function isFailOn(value) {
219
+ return value === "none" || isSeverity(value);
220
+ }
221
+ //#endregion
222
+ //#region src/scan/discover.ts
223
+ async function discoverFiles(config) {
224
+ const discoveredFiles = await fg(expandGlobs(config.paths), {
225
+ ignore: config.ignore,
226
+ absolute: true
227
+ });
228
+ if (!config.useGitIgnore) return discoveredFiles;
229
+ return applyGitIgnore(discoveredFiles);
230
+ }
231
+ function expandGlobs(paths) {
232
+ return paths.map((p) => {
233
+ if (p === ".") return "./**/*.{ts,cts,mts,tsx}";
234
+ if (p.endsWith("/")) return `${p}**/*.{ts,cts,mts,tsx}`;
235
+ if (!p.includes("*") && !p.endsWith(".ts") && !p.endsWith(".tsx") && !p.endsWith(".cts") && !p.endsWith(".mts")) return `${p}/**/*.{ts,cts,mts,tsx}`;
236
+ return p;
237
+ });
238
+ }
239
+ function applyGitIgnore(files) {
240
+ const gitIgnorePath = resolve(process.cwd(), ".gitignore");
241
+ if (!existsSync(gitIgnorePath)) return files;
242
+ const matcher = ignore().add(readFileSync(gitIgnorePath, "utf8"));
243
+ return files.filter((file) => {
244
+ const rel = relative(process.cwd(), file);
245
+ if (rel.startsWith("..")) return true;
246
+ const normalized = rel.split(sep).join("/");
247
+ return !matcher.ignores(normalized);
248
+ });
249
+ }
250
+ //#endregion
251
+ //#region src/scan/baseline.ts
252
+ const BASELINE_FILENAME = "unguard.baseline.json";
253
+ function buildBaseline(diagnostics, cwd) {
254
+ const rules = {};
255
+ for (const diagnostic of diagnostics) {
256
+ const file = relative(cwd, diagnostic.file);
257
+ rules[file] ??= {};
258
+ const forFile = rules[file];
259
+ forFile[diagnostic.ruleId] = (forFile[diagnostic.ruleId] ?? 0) + 1;
260
+ }
261
+ return {
262
+ version: 1,
263
+ rules
264
+ };
265
+ }
266
+ function writeBaseline(baseline, cwd) {
267
+ const path = resolve(cwd, BASELINE_FILENAME);
268
+ writeFileSync(path, `${JSON.stringify(baseline, null, 2)}\n`, "utf8");
269
+ return path;
270
+ }
271
+ function loadBaseline(cwd) {
272
+ const path = resolve(cwd, BASELINE_FILENAME);
273
+ if (!existsSync(path)) return null;
274
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
275
+ if (!isBaselineData(parsed)) throw new Error(`Invalid baseline in ${BASELINE_FILENAME}: expected {"version": 1, "rules": {<file>: {<ruleId>: count}}}.`);
276
+ return parsed;
277
+ }
278
+ function isBaselineData(value) {
279
+ if (typeof value !== "object" || value === null) return false;
280
+ if (!("version" in value) || value.version !== 1) return false;
281
+ if (!("rules" in value) || typeof value.rules !== "object" || value.rules === null) return false;
282
+ return Object.values(value.rules).every((forFile) => typeof forFile === "object" && forFile !== null && Object.values(forFile).every((count) => typeof count === "number"));
283
+ }
284
+ /** Drop diagnostic groups still within their baselined count. */
285
+ function applyBaseline(diagnostics, baseline, cwd) {
286
+ const groups = /* @__PURE__ */ new Map();
287
+ for (const diagnostic of diagnostics) {
288
+ const key = `${relative(cwd, diagnostic.file)}\0${diagnostic.ruleId}`;
289
+ let list = groups.get(key);
290
+ if (list === void 0) {
291
+ list = [];
292
+ groups.set(key, list);
293
+ }
294
+ list.push(diagnostic);
295
+ }
296
+ const kept = [];
297
+ for (const [key, group] of groups) {
298
+ const [file, ruleId] = key.split("\0");
299
+ const allowed = baseline.rules[file]?.[ruleId] ?? 0;
300
+ if (group.length <= allowed) continue;
301
+ kept.push(...group);
302
+ }
303
+ return kept;
304
+ }
305
+ //#endregion
306
+ //#region src/scan/policy.ts
307
+ function buildRuleDescriptors(rules) {
308
+ return rules.map((rule) => {
309
+ const metadata = getRuleMetadata(rule.id);
310
+ return {
311
+ rule,
312
+ category: metadata.category,
313
+ tags: metadata.tags,
314
+ confidence: metadata.confidence
315
+ };
316
+ });
317
+ }
318
+ function resolveActiveRules(descriptors, config) {
319
+ const selectedRules = config.rules ? new Set(config.rules) : null;
320
+ const wantedConfidence = config.mode === "audit" ? "heuristic" : "proven";
321
+ const active = [];
322
+ for (const descriptor of descriptors) {
323
+ if (selectedRules) {
324
+ if (!selectedRules.has(descriptor.rule.id)) continue;
325
+ } else if (descriptor.confidence !== wantedConfidence) continue;
326
+ const resolvedSeverity = resolveRuleSeverity(descriptor, config);
327
+ if (resolvedSeverity === "off") continue;
328
+ if (descriptor.rule.severity === resolvedSeverity) {
329
+ active.push(descriptor.rule);
330
+ continue;
331
+ }
332
+ active.push({
333
+ ...descriptor.rule,
334
+ severity: resolvedSeverity
335
+ });
336
+ }
337
+ return active;
338
+ }
339
+ function resolveRuleSeverity(descriptor, config) {
340
+ let severity = descriptor.rule.severity;
341
+ for (const entry of config.rulePolicy) {
342
+ if (!matchesSelector(descriptor, entry.selector)) continue;
343
+ severity = entry.severity;
344
+ }
345
+ if (severity === "off") return "off";
346
+ if (config.strict) return "error";
347
+ return severity;
348
+ }
349
+ function matchesSelector(descriptor, selector) {
350
+ if (selector.startsWith("category:")) return descriptor.category === selector.slice(9);
351
+ if (selector.startsWith("tag:")) return descriptor.tags.includes(selector.slice(4));
352
+ if (selector.startsWith("confidence:")) return descriptor.confidence === selector.slice(11);
353
+ if (selector === descriptor.rule.id) return true;
354
+ if (!selector.includes("*")) return false;
355
+ return new RegExp(`^${escapeRegex(selector).replaceAll("\\*", ".*")}$`).test(descriptor.rule.id);
356
+ }
357
+ function escapeRegex(value) {
358
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
359
+ }
360
+ function finalizeScanResult(diagnostics, fileCount, config) {
361
+ const afterOverrides = applyOverrides(diagnostics, config.overrides, process.cwd());
362
+ const effective = config.baseline ? applyBaseline(afterOverrides, config.baseline, process.cwd()) : afterOverrides;
363
+ return {
364
+ diagnostics,
365
+ visibleDiagnostics: config.showSeverities ? effective.filter((d) => config.showSeverities?.has(d.severity)) : effective,
366
+ fileCount,
367
+ exitCode: computeExitCode(effective, config.failOn)
368
+ };
369
+ }
370
+ /**
371
+ * Re-resolve severities per file: each override whose globs (gitignore
372
+ * syntax, matched against the cwd-relative path) cover the diagnostic's file
373
+ * applies its rule policy on top of the already-resolved severity. `off`
374
+ * drops the diagnostic.
375
+ */
376
+ function applyOverrides(diagnostics, overrides, cwd) {
377
+ if (overrides.length === 0) return diagnostics;
378
+ const matchers = overrides.map((entry) => ({
379
+ matcher: ignore().add(entry.files),
380
+ rulePolicy: entry.rulePolicy
381
+ }));
382
+ const result = [];
383
+ for (const diagnostic of diagnostics) {
384
+ const relPath = relative(cwd, diagnostic.file).replaceAll("\\", "/");
385
+ const metadata = getRuleMetadata(diagnostic.ruleId);
386
+ const descriptor = {
387
+ rule: { id: diagnostic.ruleId },
388
+ category: metadata.category,
389
+ tags: metadata.tags,
390
+ confidence: metadata.confidence
391
+ };
392
+ let severity = diagnostic.severity;
393
+ for (const { matcher, rulePolicy } of matchers) {
394
+ if (relPath.startsWith("..") || !matcher.ignores(relPath)) continue;
395
+ for (const entry of rulePolicy) {
396
+ if (!matchesSelector(descriptor, entry.selector)) continue;
397
+ severity = entry.severity;
398
+ }
399
+ }
400
+ if (severity === "off") continue;
401
+ result.push(severity === diagnostic.severity ? diagnostic : {
402
+ ...diagnostic,
403
+ severity
404
+ });
405
+ }
406
+ return result;
407
+ }
408
+ function toScanResult(execution) {
409
+ return {
410
+ diagnostics: execution.diagnostics,
411
+ fileCount: execution.fileCount
412
+ };
413
+ }
414
+ function computeExitCode(diagnostics, failOn) {
415
+ if (failOn === "none") return 0;
416
+ const hasError = diagnostics.some((d) => d.severity === "error");
417
+ const hasWarning = diagnostics.some((d) => d.severity === "warning");
418
+ const hasInfo = diagnostics.some((d) => d.severity === "info");
419
+ if (failOn === "error") return hasError ? 2 : 0;
420
+ if (failOn === "warning") {
421
+ if (hasError) return 2;
422
+ return hasWarning ? 1 : 0;
423
+ }
424
+ if (hasError) return 2;
425
+ if (hasWarning || hasInfo) return 1;
426
+ return 0;
427
+ }
428
+ //#endregion
429
+ //#region src/engine.ts
430
+ const UNGUARD_VERSION = createRequire(import.meta.url)("../package.json").version;
431
+ async function executeScan(options) {
432
+ const config = resolveScanConfig(options);
433
+ const files = await discoverFiles(config);
434
+ const activeRules = resolveActiveRules(buildRuleDescriptors(allRules), config);
435
+ const cacheDir = config.cache ? resolveCacheDir(process.cwd()) : null;
436
+ if (cacheDir !== null) {
437
+ const scanKey = computeScanKey({
438
+ unguardVersion: UNGUARD_VERSION,
439
+ rules: activeRules,
440
+ paths: config.paths,
441
+ ignore: config.ignore,
442
+ strict: config.strict,
443
+ failOn: config.failOn,
444
+ showSeverities: config.showSeverities
445
+ });
446
+ const cached = readScanCache(cacheDir);
447
+ const currentHashes = hashFiles([...files].sort(), cached?.fileHashes ?? null);
448
+ if (cached !== null && cacheCovers(cached, {
449
+ unguardVersion: UNGUARD_VERSION,
450
+ scanKey
451
+ }, currentHashes)) return finalizeScanResult(cached.diagnostics, cached.fileCount, config);
452
+ const diagnostics = await analyzeFiles(files, activeRules, { concurrency: resolveDefaultConcurrency(config.concurrency) });
453
+ writeScanCache(cacheDir, {
454
+ version: 1,
455
+ unguardVersion: UNGUARD_VERSION,
456
+ scanKey,
457
+ fileHashes: currentHashes,
458
+ diagnostics,
459
+ fileCount: files.length
460
+ });
461
+ return finalizeScanResult(diagnostics, files.length, config);
462
+ }
463
+ return finalizeScanResult(await analyzeFiles(files, activeRules, { concurrency: resolveDefaultConcurrency(config.concurrency) }), files.length, config);
464
+ }
465
+ function resolveDefaultConcurrency(requested) {
466
+ if (requested !== void 0) {
467
+ if (!Number.isFinite(requested) || requested < 1) return 1;
468
+ return Math.floor(requested);
469
+ }
470
+ const cores = availableParallelism();
471
+ return Math.min(8, Math.max(1, Math.ceil(cores / 2)));
472
+ }
473
+ async function scan(options) {
474
+ return toScanResult(await executeScan(options));
475
+ }
476
+ //#endregion
477
+ export { buildBaseline as a, isFailOn as c, toRulePolicyEntries as d, BASELINE_FILENAME as i, isRulePolicySeverity as l, scan as n, loadBaseline as o, computeExitCode as r, writeBaseline as s, executeScan as t, isSeverity as u };
478
+
479
+ //# sourceMappingURL=engine-C4KGqeYw.js.map