madr-lint 0.4.2 → 0.4.3

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","../src/commands/init.ts","../src/core/config.ts","../src/configs/recommended.ts","../src/core/discover.ts","../src/core/init.ts","../src/core/parser.ts","../src/core/null-proto-map.ts","../src/core/lint.ts","../src/core/baseline.ts","../src/core/types.ts","../src/core/cache.ts","../src/core/fix.ts","../src/core/runner.ts","../src/core/interpolate.ts","../src/core/suppression.ts","../src/rules/index.ts","../src/rules/date-iso8601/schema.json","../src/rules/date-iso8601/index.ts","../src/rules/filename-format/index.ts","../src/core/regex-safety.ts","../src/rules/filename-format/schema.json","../src/rules/no-broken-links/index.ts","../src/rules/no-broken-links/schema.json","../src/rules/no-duplicate-numbering/index.ts","../src/rules/no-duplicate-numbering/schema.json","../src/rules/no-numbering-gap/index.ts","../src/rules/no-numbering-gap/schema.json","../src/rules/required-sections/index.ts","../src/rules/required-sections/schema.json","../src/rules/status-enum/schema.json","../src/rules/status-enum/index.ts","../src/rules/supersedes-bidirectional/index.ts","../src/rules/supersedes-bidirectional/schema.json","../src/core/ignore.ts","../src/core/reporter.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { relative, resolve, sep } from 'node:path';\nimport { defineCommand, runMain } from 'citty';\nimport { initCommand } from './commands/init.js';\nimport {\n baselineHiddenSummary,\n baselineMalformedWarning,\n baselinePath,\n baselineWriteSummary,\n buildBaseline,\n loadBaseline,\n writeBaseline,\n} from './core/baseline.js';\nimport { computeConfigHash } from './core/cache.js';\nimport {\n ConfigFileNotFoundError,\n loadConfig,\n loadConfigFromPath,\n resolveExtends,\n type ResolvedConfig,\n} from './core/config.js';\nimport { findAdrFiles } from './core/discover.js';\nimport { unifiedDiff } from './core/fix.js';\nimport { shouldIgnore } from './core/ignore.js';\nimport { lintAndFix, lintFiles, type CacheConfig } from './core/lint.js';\nimport { reporters, type ReporterFormat } from './core/reporter.js';\nimport { RuleOptionsError } from './core/runner.js';\nimport type { AnyRule, Diagnostic } from './core/types.js';\nimport * as builtinRules from './rules/index.js';\n\nfunction toPosix(p: string): string {\n if (sep === '/') return p;\n return p.split(sep).join('/');\n}\n\nconst pkg = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8'),\n) as { version: string };\n\nconst main = defineCommand({\n meta: {\n name: 'madr-lint',\n version: pkg.version,\n description: 'A linter for MADR (Markdown Architectural Decision Records)',\n },\n args: {\n paths: {\n type: 'positional',\n description:\n 'One or more paths (files or directories). Defaults to config.adrDir',\n required: false,\n },\n cache: {\n type: 'boolean',\n description:\n 'Use per-file content-hash cache (use --no-cache to disable)',\n default: true,\n },\n 'cache-dir': {\n type: 'string',\n description: 'Cache directory (default: .madr-lint/cache)',\n required: false,\n },\n format: {\n type: 'string',\n description: 'Reporter format: text (default), json, sarif, or github',\n default: 'text',\n },\n baseline: {\n type: 'boolean',\n description:\n 'Subtract .madr-lint/baseline.json when present (use --no-baseline to ignore it)',\n default: true,\n },\n 'update-baseline': {\n type: 'boolean',\n description:\n 'Rewrite .madr-lint/baseline.json from a full lint, then exit 0',\n default: false,\n },\n quiet: {\n type: 'boolean',\n description: 'Report errors only; suppress warnings from output',\n default: false,\n },\n 'max-warnings': {\n type: 'string',\n description:\n 'Exit 1 when warning count exceeds n. n=0 means any warning fails. Negative = no limit.',\n required: false,\n },\n config: {\n type: 'string',\n description: 'Path to config file, bypassing auto-discovery',\n required: false,\n },\n fix: {\n type: 'boolean',\n description: 'Apply autofixes in place; exit code reflects remaining problems',\n default: false,\n },\n 'fix-dry-run': {\n type: 'boolean',\n description: 'Show a diff of the fixes that --fix would apply; write nothing',\n default: false,\n },\n },\n run({ args }) {\n const cwd = process.cwd();\n\n // Parse --max-warnings. Require an integer (including negative); float or\n // non-numeric is a usage error (exit 2), not a lint failure (exit 1).\n // Number('') === 0, so an empty value (e.g. --max-warnings \"$UNSET_VAR\")\n // would silently become the strictest limit — reject it explicitly.\n let maxWarnings = -1;\n const rawMaxWarnings = args['max-warnings'] as string | undefined;\n if (rawMaxWarnings !== undefined) {\n const parsed = Number(rawMaxWarnings);\n if (rawMaxWarnings.trim() === '' || !Number.isInteger(parsed)) {\n console.error(\n `Invalid --max-warnings \"${rawMaxWarnings}\": must be an integer.`,\n );\n process.exit(2);\n }\n maxWarnings = parsed;\n }\n\n // --config bypasses the config discovery walk. When the user explicitly\n // points to a config file we skip the recommended fallback — they chose\n // the config intentionally (even if it has no rules).\n let config: ResolvedConfig;\n let configExplicit = false;\n const configArg = args.config as string | undefined;\n if (configArg) {\n const absConfigPath = resolve(cwd, configArg);\n try {\n config = loadConfigFromPath(absConfigPath);\n configExplicit = true;\n } catch (err) {\n if (err instanceof ConfigFileNotFoundError) {\n console.error(err.message);\n } else {\n console.error(\n `Failed to load config \"${configArg}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n process.exit(2);\n }\n } else {\n config = loadConfig(cwd);\n }\n\n // When no rules are configured and no explicit config was provided, fall\n // back to `recommended` so users get useful output without authoring a\n // config first.\n if (!configExplicit && Object.keys(config.rules).length === 0) {\n config = resolveExtends({ extends: ['madr-lint:recommended'] });\n }\n\n // citty: positionals collect into `args._` (array of strings). If\n // empty, fall back to the config's adrDir.\n const positionals = (args._ as string[] | undefined) ?? [];\n const inputs = positionals.length > 0 ? positionals : [config.adrDir];\n\n const seen = new Set<string>();\n const allFiles: string[] = [];\n for (const input of inputs) {\n for (const file of findAdrFiles(input)) {\n if (!seen.has(file)) {\n seen.add(file);\n allFiles.push(file);\n }\n }\n }\n\n // Apply ignorePatterns from config (e.g. README.md, template.md, 9999-*).\n // POSIX-normalize so path-suffix patterns work cross-platform on Windows.\n const files = allFiles.filter(\n (absPath) =>\n !shouldIgnore(toPosix(relative(cwd, absPath)), config.ignorePatterns),\n );\n\n if (files.length === 0) {\n const skipped = allFiles.length - files.length;\n const note = skipped > 0 ? ` (${skipped} ignored by config)` : '';\n const where = inputs.length === 1 ? inputs[0] : inputs.join(', ');\n console.log(`No .md files to lint in ${where}${note}`);\n process.exit(0);\n }\n\n // CLI flag wins over config — `--no-cache` sets args.cache=false,\n // overriding `cache: true` from .madrlintrc.json.\n const cacheEnabled = (args.cache ?? config.cache) === true;\n const cacheDir = (args['cache-dir'] ?? config.cacheLocation) as string;\n const cacheConfig: CacheConfig | null = cacheEnabled\n ? {\n dir: resolve(cwd, cacheDir),\n configHash: computeConfigHash({\n rules: config.rules,\n ignorePatterns: config.ignorePatterns,\n madrVersion: config.madrVersion,\n }),\n pkgVersion: pkg.version,\n }\n : null;\n\n // --update-baseline runs a full lint IGNORING any existing baseline, so we\n // subtract nothing here and (below) rebuild the file from the raw output.\n const baselineFile = baselinePath(cwd);\n const updateBaseline = args['update-baseline'] === true;\n const useBaseline = !updateBaseline && args.baseline !== false;\n const baseline = useBaseline ? loadBaseline(baselineFile) : null;\n // Absent → silent no-op. Present but unusable → warn: silently ignoring a\n // corrupt baseline would flip CI red with zero explanation.\n if (useBaseline && baseline === null && existsSync(baselineFile)) {\n console.error(baselineMalformedWarning());\n }\n\n const allRulesArray: AnyRule[] = Object.values(builtinRules);\n const format = (args.format ?? 'text') as ReporterFormat;\n\n // Shared \"report the resulting diagnostics and exit\" tail. `fixedCount` is\n // undefined on a plain lint (no fix footer / summary), a number when a fix\n // pass ran. `fixDiffs` carries dry-run diffs for the json reporter to embed\n // (undefined otherwise). Diagnostics are POST-baseline, so baselined\n // warnings never count toward --max-warnings — inherited debt does not\n // fail CI.\n const reportAndExit = (\n diagnostics: readonly Diagnostic[],\n baselineHidden: number,\n fixedCount: number | undefined,\n dryRun: boolean,\n fixDiffs?: readonly { path: string; diff: string }[],\n ): never => {\n const rulesByName = new Map<string, AnyRule>(\n allRulesArray.map((r) => [r.meta.name, r]),\n );\n const reporter = reporters[format];\n if (!reporter) {\n console.error(\n `Unknown --format \"${format}\". Available: ${Object.keys(reporters).join(', ')}`,\n );\n process.exit(2);\n }\n const warningCount = diagnostics.filter((d) => d.severity === 'warn').length;\n const errorCount = diagnostics.filter((d) => d.severity === 'error').length;\n const overWarningLimit = maxWarnings >= 0 && warningCount > maxWarnings;\n\n // --quiet filters warnings from OUTPUT but the original warning count is\n // still used for --max-warnings (mirrors ESLint). The threshold verdict\n // goes to stderr so `--quiet --max-warnings 0` still fails loudly.\n const reported = args.quiet\n ? diagnostics.filter((d) => d.severity !== 'warn')\n : diagnostics;\n\n // Text-only: a \"✓ All clear.\" banner beside exit 1 would lie, so suppress\n // it when the run fails purely on the warning threshold.\n const suppressAllClear =\n format === 'text' && reported.length === 0 && overWarningLimit;\n if (!suppressAllClear) {\n console.log(\n reporter.format(reported, rulesByName, {\n baselineHidden,\n ...(fixedCount !== undefined ? { fixed: fixedCount } : {}),\n ...(fixDiffs !== undefined ? { fixDiffs } : {}),\n }),\n );\n }\n if (format === 'text' && baselineHidden > 0) {\n console.log(baselineHiddenSummary(baselineHidden));\n }\n // Text-only autofix footer (json carries summary.fixed instead).\n if (format === 'text' && fixedCount !== undefined && fixedCount > 0) {\n const noun = fixedCount === 1 ? 'problem' : 'problems';\n console.log(\n dryRun\n ? `${fixedCount} ${noun} fixable (dry run; no files written)`\n : `Fixed ${fixedCount} ${noun}`,\n );\n }\n if (overWarningLimit) {\n console.error(\n `madr-lint: ${warningCount} warning(s) found, exceeds --max-warnings ${maxWarnings}`,\n );\n }\n process.exit(errorCount > 0 || overWarningLimit ? 1 : 0);\n };\n\n // ── Autofix path (--fix / --fix-dry-run) ─────────────────────────\n const fixMode = args.fix === true || args['fix-dry-run'] === true;\n if (fixMode && updateBaseline) {\n // Ambiguous intent: rewrite files vs snapshot the current violations.\n console.error(\n 'madr-lint: --update-baseline cannot be combined with --fix or --fix-dry-run',\n );\n process.exit(2);\n }\n if (fixMode) {\n // --fix-dry-run wins if both are given: never write on a dry run.\n const dryRun = args['fix-dry-run'] === true;\n let fixResult: ReturnType<typeof lintAndFix>;\n try {\n // The cache is intentionally bypassed while fixing (fixes need live\n // thunks); fixed files re-enter the normal pipeline on the next run.\n fixResult = lintAndFix({\n rules: allRulesArray,\n ruleSeverity: config.rules,\n files,\n cwd,\n baseline,\n });\n } catch (err) {\n if (err instanceof RuleOptionsError) {\n console.error(`Invalid rule options in config: ${err.message}`);\n process.exit(2);\n }\n throw err;\n }\n\n // Dry-run diff routing: raw diff on stdout is only safe for the human\n // `text` format. json embeds it (top-level `diffs`, via reportAndExit);\n // sarif/github get it on stderr so their stdout stays machine-parseable.\n let fixDiffs: { path: string; diff: string }[] | undefined;\n if (dryRun) {\n const changed = fixResult.files.filter((f) => f.changed);\n if (format === 'text') {\n for (const f of changed) {\n process.stdout.write(unifiedDiff(f.path, f.original, f.fixed));\n }\n } else if (format === 'json') {\n fixDiffs = changed.map((f) => ({\n path: f.path,\n diff: unifiedDiff(f.path, f.original, f.fixed),\n }));\n } else {\n for (const f of changed) {\n process.stderr.write(unifiedDiff(f.path, f.original, f.fixed));\n }\n }\n } else {\n for (const f of fixResult.files) {\n if (f.changed) writeFileSync(f.absPath, f.fixed, 'utf8');\n }\n }\n reportAndExit(\n fixResult.diagnostics,\n fixResult.baselineHidden,\n fixResult.fixed,\n dryRun,\n fixDiffs,\n );\n }\n\n // ── Normal lint path ─────────────────────────────────────────────\n let result: ReturnType<typeof lintFiles>;\n try {\n result = lintFiles({\n rules: allRulesArray,\n ruleSeverity: config.rules,\n files,\n cwd,\n cache: cacheConfig,\n baseline,\n });\n } catch (err) {\n // Invalid rule options in the user's config fail AJV validation now that\n // options actually reach the rules. Surface a clear message instead of a\n // stack trace, and exit 2 (config error, distinct from lint failures).\n if (err instanceof RuleOptionsError) {\n console.error(`Invalid rule options in config: ${err.message}`);\n process.exit(2);\n }\n throw err;\n }\n\n // Rebuild the baseline from the full (pre-baseline) diagnostics and exit.\n if (updateBaseline) {\n const built = buildBaseline(result.diagnostics);\n writeBaseline(baselineFile, built);\n console.log(baselineWriteSummary(built));\n process.exit(0);\n }\n\n reportAndExit(result.diagnostics, result.baselineHidden, undefined, false);\n },\n});\n\n// Manual subcommand dispatch. citty's `subCommands` field cannot coexist with\n// the free-form `paths` positional: runCommand() treats ANY leading non-flag\n// token as a subcommand name once `subCommands` is set, so `madr-lint docs/adr`\n// would die with \"Unknown command docs/adr\" (verified against citty 0.2.2).\n// Only the literal first raw arg `init` selects the subcommand; every other\n// invocation flows to the default lint command exactly as before.\nconst rawArgs = process.argv.slice(2);\nif (rawArgs[0] === 'init') {\n await runMain(initCommand, { rawArgs: rawArgs.slice(1) });\n} else {\n await runMain(main);\n}\n","import { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { defineCommand } from 'citty';\nimport { CONFIG_FILES, resolveExtends } from '../core/config.js';\nimport { findAdrFiles } from '../core/discover.js';\nimport {\n CONFIG_FILENAME_BY_FORMAT,\n GETTING_STARTED_URL,\n buildEpilogue,\n detectAdrDir,\n detectConfigFormat,\n detectMadrVersion,\n findExistingConfigFile,\n renderConfig,\n type AdrDirSource,\n} from '../core/init.js';\nimport { lintFiles } from '../core/lint.js';\nimport type { AnyRule } from '../core/types.js';\nimport * as builtinRules from '../rules/index.js';\n\n/**\n * `madr-lint init` — scaffold a config file (issue #30).\n *\n * Non-interactive by design: every decision is a filesystem heuristic or a\n * flag, so it works in CI and behind pipes. Detects the ADR directory, the\n * dominant MADR version, and whether to write TS or JSON; refuses to clobber\n * an existing config unless `--force`.\n */\nexport const initCommand = defineCommand({\n meta: {\n name: 'init',\n description:\n 'Scaffold a madr-lint config file (detects ADR directory, MADR version, and TS vs JSON)',\n },\n args: {\n force: {\n type: 'boolean',\n description: 'Overwrite an existing config file',\n default: false,\n },\n dir: {\n type: 'string',\n description: 'ADR directory to write into the config, overriding detection',\n required: false,\n },\n json: {\n type: 'boolean',\n description: 'Emit a machine-readable JSON summary instead of text',\n default: false,\n },\n },\n run({ args }) {\n const cwd = process.cwd();\n\n // Usage errors first. An empty --dir is falsy, so without this guard it\n // would silently fall through to auto-detection (e.g. --dir \"$UNSET_VAR\")\n // — the same footgun class --max-warnings \"\" guards against in cli.ts.\n const rawDir = args.dir as string | undefined;\n if (rawDir !== undefined && rawDir.trim() === '') {\n console.error(`Invalid --dir \"${rawDir}\": must be a non-empty path.`);\n process.exit(2);\n }\n\n const existing = findExistingConfigFile(cwd);\n if (existing && args.force !== true) {\n console.error(\n `A madr-lint config already exists: ${existing}. Re-run with --force to overwrite.`,\n );\n process.exit(2);\n }\n\n // ── Detection ────────────────────────────────────────────────────\n let adrDir: string;\n let adrDirSource: AdrDirSource;\n if (rawDir !== undefined) {\n adrDir = rawDir;\n adrDirSource = 'override';\n } else {\n const detected = detectAdrDir(cwd);\n adrDir = detected.adrDir;\n adrDirSource = detected.source;\n }\n const madrVersion = detectMadrVersion(cwd, adrDir);\n const configFormat = detectConfigFormat(cwd);\n const configPath = CONFIG_FILENAME_BY_FORMAT[configFormat];\n\n // ── Write ────────────────────────────────────────────────────────\n writeFileSync(\n resolve(cwd, configPath),\n renderConfig(configFormat, { adrDir, madrVersion }),\n );\n // --force replaces the canonical file for the DETECTED format. A\n // pre-existing config under a different name would still win (or lose)\n // discovery order silently — never leave that unsaid, and state the\n // ACTUAL direction: CONFIG_FILES index order decides which file wins.\n if (existing && existing !== configPath) {\n const oldWins =\n CONFIG_FILES.indexOf(existing) < CONFIG_FILES.indexOf(configPath);\n console.error(\n oldWins\n ? `Note: ${existing} still exists and precedes ${configPath} in config discovery — remove it to make the new config take effect.`\n : `Note: the leftover ${existing} is now shadowed by ${configPath}, which precedes it in config discovery — you can delete it.`,\n );\n }\n\n // ── Initial in-process lint (cheap) ──────────────────────────────\n // Counts violations of the freshly scaffolded config so the epilogue can\n // suggest --update-baseline on legacy repos. Never fails init: a lint\n // crash is reported but the scaffold already succeeded.\n const files = findAdrFiles(resolve(cwd, adrDir));\n let errors = 0;\n let warnings = 0;\n if (files.length > 0) {\n try {\n const config = resolveExtends({ extends: ['madr-lint:recommended'] });\n const allRules: AnyRule[] = Object.values(builtinRules);\n const result = lintFiles({\n rules: allRules,\n ruleSeverity: config.rules,\n files,\n cwd,\n cache: null,\n baseline: null,\n });\n for (const d of result.diagnostics) {\n if (d.severity === 'error') errors++;\n else warnings++;\n }\n } catch (err) {\n console.error(\n `Note: the initial lint failed (${err instanceof Error ? err.message : String(err)}); the config was still written.`,\n );\n }\n }\n\n // ── Report ───────────────────────────────────────────────────────\n const filesChecked = files.length;\n const suggestUpdateBaseline = filesChecked > 0 && errors + warnings > 0;\n if (args.json === true) {\n console.log(\n JSON.stringify(\n {\n written: true,\n configPath,\n configFormat,\n adrDir,\n adrDirSource,\n madrVersion,\n filesChecked,\n errors,\n warnings,\n suggestUpdateBaseline,\n docsUrl: GETTING_STARTED_URL,\n },\n null,\n 2,\n ),\n );\n } else {\n console.log(\n buildEpilogue({\n adrDir,\n adrDirSource,\n configPath,\n filesChecked,\n errors,\n warnings,\n }),\n );\n }\n process.exit(0);\n },\n});\n","import { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createJiti } from 'jiti';\nimport { recommended } from '../configs/recommended.js';\nimport type { MadrLintConfig } from '../index.js';\nimport type { MadrVersion, RuleSeverity } from './types.js';\n\nexport interface ResolvedConfig {\n /** ADR directory, relative to cwd or absolute. Default: 'docs/adr'. */\n adrDir: string;\n /** Per-rule severity / options map. Empty when no preset extended. */\n rules: Record<string, RuleSeverity>;\n /** Glob patterns to skip. */\n ignorePatterns: string[];\n /** MADR version target. Default: 'auto'. */\n madrVersion: MadrVersion | 'auto';\n /** Whether to use per-file content-hash cache. Default: true. */\n cache: boolean;\n /** Cache directory (relative to cwd). Default: '.madr-lint/cache'. */\n cacheLocation: string;\n}\n\n/**\n * Config file resolution order. First match wins.\n *\n * `.json` is parsed directly (no module loader), so it stays the fastest\n * and dependency-free path. The other extensions go through `jiti`,\n * which transpiles TS/ESM/CJS on the fly.\n */\nexport const CONFIG_FILES = [\n '.madrlintrc.json',\n '.madrlintrc.ts',\n '.madrlintrc.mts',\n '.madrlintrc.js',\n '.madrlintrc.mjs',\n '.madrlintrc.cjs',\n 'madr-lint.config.ts',\n 'madr-lint.config.mts',\n 'madr-lint.config.js',\n 'madr-lint.config.mjs',\n 'madr-lint.config.cjs',\n];\n\nexport class ConfigFileNotFoundError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigFileNotFoundError';\n }\n}\n\n/**\n * Load and resolve a config from an explicit file path, bypassing discovery.\n * Throws `ConfigFileNotFoundError` if the file does not exist.\n * Throws `SyntaxError` or other load errors if the file is malformed.\n */\nexport function loadConfigFromPath(filePath: string): ResolvedConfig {\n // Race-free error classification: fs check-APIs (existsSync/statSync) before\n // a use are TOCTOU by construction (CodeQL js/file-system-race). Reading IS\n // the use, so classify its errors instead: ENOENT/EISDIR carry exactly the\n // two cases we distinguish, with no check-then-use window.\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n throw new ConfigFileNotFoundError(`Config file not found: ${filePath}`);\n }\n // A directory exists on disk but jiti's failure reads like a missing\n // npm package (\"Cannot find module\") — fail with an honest message instead.\n if (code === 'EISDIR') {\n throw new ConfigFileNotFoundError(\n `Config path is a directory, not a file: ${filePath}`,\n );\n }\n throw err;\n }\n if (filePath.endsWith('.json')) {\n const parsed = JSON.parse(raw) as MadrLintConfig;\n return resolveExtends(parsed);\n }\n const jiti = createJiti(import.meta.url, { interopDefault: true });\n const loaded = jiti(filePath) as MadrLintConfig | { default: MadrLintConfig };\n const config: MadrLintConfig =\n typeof loaded === 'object' && loaded !== null && 'default' in loaded\n ? (loaded.default as MadrLintConfig)\n : (loaded as MadrLintConfig);\n return resolveExtends(config);\n}\n\n/**\n * Loads and resolves a config from `cwd`. If no config file is found,\n * returns the defaults (no rules enabled — caller can opt into the\n * recommended preset by extending).\n *\n * `.json` is read synchronously and parsed inline. All other extensions\n * are loaded via `jiti`, supporting TypeScript and both module systems.\n */\nexport function loadConfig(cwd: string): ResolvedConfig {\n for (const name of CONFIG_FILES) {\n const path = resolve(cwd, name);\n if (!existsSync(path)) continue;\n\n if (name.endsWith('.json')) {\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as MadrLintConfig;\n return resolveExtends(parsed);\n }\n\n const jiti = createJiti(import.meta.url, { interopDefault: true });\n const loaded = jiti(path) as MadrLintConfig | { default: MadrLintConfig };\n const config: MadrLintConfig =\n typeof loaded === 'object' && loaded !== null && 'default' in loaded\n ? (loaded.default as MadrLintConfig)\n : (loaded as MadrLintConfig);\n return resolveExtends(config);\n }\n return resolveExtends({});\n}\n\n/**\n * Pure function: turns a raw `MadrLintConfig` into a `ResolvedConfig`\n * with defaults filled in and `extends` presets merged.\n */\nexport function resolveExtends(config: MadrLintConfig): ResolvedConfig {\n const baseRules: Record<string, RuleSeverity> = config.extends?.includes(\n 'madr-lint:recommended',\n )\n ? { ...recommended }\n : {};\n\n return {\n adrDir: config.adrDir ?? 'docs/adr',\n rules: { ...baseRules, ...config.rules },\n ignorePatterns: config.ignorePatterns ?? [],\n madrVersion: config.madrVersion ?? 'auto',\n cache: config.cache ?? true,\n cacheLocation: config.cacheLocation ?? '.madr-lint/cache',\n };\n}\n","import type { RuleSeverity } from '../core/types.js';\n\n// The `recommended` preset enables rules whose default behavior is broadly\n// useful and grounded in the MADR spec. New rules append here via the\n// add-rule skill, with severity decided per-rule (default `error` for\n// spec-grounded rules, `warn` for convention-only rules).\n//\n// Both bare-string and tuple forms are supported here:\n// 'madr/foo': 'error' — enable with defaults\n// 'madr/foo': ['error', { pattern: '^X' }] — enable with override\n\nexport const recommended: Record<string, RuleSeverity> = {\n 'madr/date-iso8601': 'error',\n 'madr/filename-format': 'error',\n 'madr/no-broken-links': 'error',\n 'madr/no-duplicate-numbering': 'error',\n // Convention-only — opt in if your team treats ADR numbering as a\n // contiguous sequence. See https://knktkc.github.io/madr-lint/rules/no-numbering-gap/.\n 'madr/no-numbering-gap': 'off',\n 'madr/required-sections': 'error',\n 'madr/status-enum': 'error',\n 'madr/supersedes-bidirectional': 'error',\n};\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Resolves a target path to a list of `.md` files to lint.\n *\n * - If the target is a directory: recursively walks it and returns every `.md`\n * file, sorted lexicographically.\n * - If the target is a `.md` file: returns just that file.\n * - If the target is a non-`.md` file or does not exist: returns `[]`.\n *\n * The empty-array fallback lets the CLI print \"No .md files found\" rather\n * than crashing — the caller decides whether that is an error.\n */\nexport function findAdrFiles(target: string): string[] {\n if (!existsSync(target)) return [];\n\n const stat = statSync(target);\n if (stat.isFile()) {\n return target.endsWith('.md') ? [target] : [];\n }\n if (!stat.isDirectory()) return [];\n\n const entries = readdirSync(target, { recursive: true, withFileTypes: true });\n return entries\n .filter((e) => e.isFile() && e.name.endsWith('.md'))\n .map((e) => join(e.parentPath, e.name))\n .toSorted();\n}\n","import { existsSync, readFileSync, readdirSync, type Dirent } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { CONFIG_FILES } from './config.js';\nimport { parseFile, type ParsedFile } from './parser.js';\nimport type { MadrVersion } from './types.js';\n\n/** Docs quickstart, surfaced in the init epilogue and --json payload. */\nexport const GETTING_STARTED_URL =\n 'https://knktkc.github.io/madr-lint/guides/getting-started/';\n\n/**\n * ADR directory candidates, scanned in priority order. A candidate qualifies\n * when its top level contains at least one `NNNN-*.md` file.\n */\nexport const ADR_DIR_CANDIDATES = [\n 'docs/adr',\n 'docs/decisions',\n 'doc/adr',\n 'adr',\n 'docs/architecture/decisions',\n];\n\n/** Filenames shaped like an ADR: four digits, a hyphen, `.md`. */\nconst ADR_FILE_PATTERN = /^\\d{4}-.*\\.md$/;\n\n/** How the ADR directory ended up in the config. */\nexport type AdrDirSource = 'detected' | 'fallback' | 'override';\n\nexport interface AdrDirDetection {\n adrDir: string;\n source: 'detected' | 'fallback';\n}\n\n/**\n * Detect the ADR directory by scanning the candidates (in order) for at\n * least one top-level `NNNN-*.md` file. Falls back to `docs/adr` — the\n * linter's default — when nothing qualifies.\n */\nexport function detectAdrDir(cwd: string): AdrDirDetection {\n for (const candidate of ADR_DIR_CANDIDATES) {\n let entries: Dirent[];\n try {\n entries = readdirSync(resolve(cwd, candidate), { withFileTypes: true });\n } catch {\n continue; // absent or unreadable — not a candidate\n }\n if (entries.some((e) => e.isFile() && ADR_FILE_PATTERN.test(e.name))) {\n return { adrDir: candidate, source: 'detected' };\n }\n }\n return { adrDir: 'docs/adr', source: 'fallback' };\n}\n\n/**\n * Per-file MADR version detection, reusing the parser's metadata bridge\n * (ADR-0006): YAML frontmatter means v3/v4 — v4 when the v4-renamed\n * `decision-makers` key is present, v3 otherwise; a v2 body-list metadata\n * block means v2. A file with neither casts no vote.\n */\nfunction detectFileVersion(content: string): MadrVersion | null {\n let parsed: ParsedFile;\n try {\n parsed = parseFile(content);\n } catch {\n return null; // unparseable (e.g. malformed YAML) — no vote\n }\n if (parsed.frontmatter) {\n return 'decision-makers' in parsed.frontmatter ? 'v4' : 'v3';\n }\n if (parsed.listMetadata) return 'v2';\n return null;\n}\n\n/**\n * Detect the dominant MADR version of an ADR directory by sampling up to 20\n * `NNNN-*.md` files (lexicographically first, for determinism) and letting\n * the majority win. An empty directory, no recognizable metadata, or an\n * exact tie yields `'auto'` — the config default, safe for mixed vintages.\n */\nexport function detectMadrVersion(\n cwd: string,\n adrDir: string,\n): MadrVersion | 'auto' {\n const dir = resolve(cwd, adrDir);\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return 'auto';\n }\n\n const sample = entries\n .filter((e) => e.isFile() && ADR_FILE_PATTERN.test(e.name))\n .map((e) => e.name)\n .toSorted()\n .slice(0, 20);\n\n const votes: Record<MadrVersion, number> = { v2: 0, v3: 0, v4: 0 };\n for (const name of sample) {\n let content: string;\n try {\n content = readFileSync(join(dir, name), 'utf8');\n } catch {\n continue;\n }\n const version = detectFileVersion(content);\n if (version) votes[version]++;\n }\n\n const max = Math.max(votes.v2, votes.v3, votes.v4);\n if (max === 0) return 'auto';\n const winners = (['v2', 'v3', 'v4'] as const).filter(\n (v) => votes[v] === max,\n );\n return winners.length === 1 ? winners[0] : 'auto';\n}\n\n/** Which config file format to scaffold. */\nexport type ConfigFormat = 'ts' | 'json';\n\n/** Canonical config filename per scaffolded format. */\nexport const CONFIG_FILENAME_BY_FORMAT: Record<ConfigFormat, string> = {\n ts: 'madr-lint.config.ts',\n json: '.madrlintrc.json',\n};\n\n/**\n * Pick the config format: TypeScript when the project looks TS-ish\n * (a `tsconfig.json`, or `typescript` among package.json dependencies /\n * devDependencies), JSON otherwise. A missing or malformed package.json\n * never throws — it just means \"not TS-ish\".\n */\nexport function detectConfigFormat(cwd: string): ConfigFormat {\n if (existsSync(resolve(cwd, 'tsconfig.json'))) return 'ts';\n try {\n const pkg = JSON.parse(\n readFileSync(resolve(cwd, 'package.json'), 'utf8'),\n ) as {\n dependencies?: Record<string, unknown>;\n devDependencies?: Record<string, unknown>;\n };\n if (\n pkg.dependencies?.['typescript'] !== undefined ||\n pkg.devDependencies?.['typescript'] !== undefined\n ) {\n return 'ts';\n }\n } catch {\n // absent or malformed package.json — fall through to json\n }\n return 'json';\n}\n\n/**\n * First existing config file in `cwd`, in the loader's CONFIG_FILES\n * resolution order, or null when the project has none. Used by `init` to\n * refuse clobbering an existing config without `--force`.\n */\nexport function findExistingConfigFile(cwd: string): string | null {\n for (const name of CONFIG_FILES) {\n if (existsSync(resolve(cwd, name))) return name;\n }\n return null;\n}\n\nexport interface ConfigRenderOptions {\n adrDir: string;\n madrVersion: MadrVersion | 'auto';\n}\n\n/** Escape a value for interpolation into a single-quoted TS string literal. */\nfunction tsQuote(value: string): string {\n return `'${value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")}'`;\n}\n\n/**\n * Render the scaffolded config file content. `madrVersion: 'auto'` is the\n * default, so it is omitted rather than written redundantly.\n */\nexport function renderConfig(\n format: ConfigFormat,\n options: ConfigRenderOptions,\n): string {\n if (format === 'json') {\n const config: Record<string, unknown> = {\n extends: ['madr-lint:recommended'],\n adrDir: options.adrDir,\n };\n if (options.madrVersion !== 'auto') config.madrVersion = options.madrVersion;\n return `${JSON.stringify(config, null, 2)}\\n`;\n }\n\n const versionLine =\n options.madrVersion !== 'auto'\n ? `\\n madrVersion: ${tsQuote(options.madrVersion)},`\n : '';\n return `import { defineConfig } from 'madr-lint';\n\nexport default defineConfig({\n extends: ['madr-lint:recommended'],\n adrDir: ${tsQuote(options.adrDir)},${versionLine}\n});\n`;\n}\n\nexport interface EpilogueInput {\n adrDir: string;\n adrDirSource: AdrDirSource;\n configPath: string;\n /** Files covered by the initial in-process lint (0 = nothing to lint). */\n filesChecked: number;\n errors: number;\n warnings: number;\n}\n\n/**\n * Human-readable summary + next steps printed by `madr-lint init`. When the\n * initial lint of the detected directory found violations, it points at\n * `--update-baseline` so legacy debt does not block adoption.\n */\nexport function buildEpilogue(input: EpilogueInput): string {\n // The fallback note may only claim emptiness when the initial lint saw\n // nothing: detection scans candidate top levels, but the lint is recursive,\n // so a nested-only ADR tree is 'fallback' WITH filesChecked > 0 — claiming\n // \"created nothing yet\" beside real findings would contradict itself.\n const dirNote =\n input.adrDirSource === 'detected'\n ? '(detected)'\n : input.adrDirSource === 'override'\n ? '(from --dir)'\n : input.filesChecked === 0\n ? '(default — no existing ADRs found, so init created nothing yet; add your first ADR here)'\n : '(default — no NNNN-*.md at its top level, though the initial lint found Markdown files nested inside)';\n\n const lines = [\n `Wrote ${input.configPath}`,\n '',\n ` ADR directory: ${input.adrDir} ${dirNote}`,\n '',\n 'Next steps:',\n ' - Lint your ADRs: npx madr-lint',\n ];\n\n const total = input.errors + input.warnings;\n if (input.filesChecked > 0 && total > 0) {\n lines.push(\n ` - The initial lint found ${input.errors} error(s) and ${input.warnings} warning(s) across ${input.filesChecked} file(s).`,\n ' Adopting on a legacy repo? Snapshot the debt so only new violations fail the build:',\n ' npx madr-lint --update-baseline',\n );\n }\n\n lines.push(` - Docs: ${GETTING_STARTED_URL}`);\n return lines.join('\\n');\n}\n","import type { List, ListItem, Paragraph, Root } from 'mdast';\nimport { fromMarkdown } from 'mdast-util-from-markdown';\nimport { toString } from 'mdast-util-to-string';\nimport grayMatter from 'gray-matter';\nimport { nullProtoMap } from './null-proto-map.js';\n\n/** Source position of a metadata list item, in body coordinates. */\nexport interface MetadataPosition {\n line: number;\n column: number;\n}\n\nexport interface ParsedFile {\n /** Parsed YAML frontmatter (v3/v4), or null if absent. */\n frontmatter: Record<string, unknown> | null;\n /**\n * v2 body-list metadata extracted from the leading metadata block, or null if\n * absent. The block is the leading RUN of lists, joined across HTML comments\n * (which render as nothing). Covers both the bold-key (`- **Status**:`) and\n * the canonical MADR v2.1.2 plain-key (`* Status:`) shapes. See ADR-0006.\n */\n listMetadata: Record<string, unknown> | null;\n /**\n * Combined metadata: frontmatter merged with bold-list. Frontmatter wins\n * on key conflict. null only when both are absent. See ADR-0006.\n */\n metadata: Record<string, unknown> | null;\n /**\n * Body-coordinate positions (list item start) for metadata keys whose\n * EFFECTIVE value came from the v2 leading list. Keys won by defined\n * frontmatter values are absent: frontmatter is stripped before mdast\n * parsing, so it has no body line — and inline suppression directives\n * (which live in the body) could not target it anyway. null when no\n * effective value is list-sourced.\n */\n metadataLoc: Record<string, MetadataPosition> | null;\n /**\n * Body-coordinate offset RANGE of the effective value for keys whose value\n * came from the v2 leading list AND was a single contiguous text token (no\n * inline markup), verified to slice back to the exact value. Autofix targets\n * the value precisely with this. Frontmatter-won keys are absent (frontmatter\n * is stripped before parsing → no body offset). null when nothing qualifies.\n */\n metadataValueLoc: Record<string, { start: number; end: number }> | null;\n /** mdast root of the body (frontmatter stripped). */\n ast: Root;\n /** Body content with frontmatter stripped. */\n body: string;\n}\n\n/**\n * Passed to EVERY gray-matter call. gray-matter memoizes a parse only when it\n * is called with no options, and its cache is a plain object keyed by the whole\n * document — so a document that is exactly the name of an `Object.prototype`\n * member (`constructor`, `toString`, `hasOwnProperty`) reads that inherited\n * member back as a hit and yields `content: undefined` (#122). The cache also\n * retains every parsed document for the process lifetime, which for a\n * thousand-ADR repo is the whole corpus held live. Frozen because the single\n * instance is shared across all calls.\n */\nconst GRAY_MATTER_OPTIONS: NonNullable<Parameters<typeof grayMatter>[1]> =\n Object.freeze({});\n\n/**\n * Character length of the frontmatter block that gray-matter strips from the\n * front of `content` — i.e. the offset at which the body begins in the whole\n * file. mdast positions are body-relative, so `fileOffset = bodyOffset +\n * frontmatterOffset(content)`. gray-matter never mutates the body, so the body\n * is always an exact suffix and this length is exact (verified across CRLF and\n * leading-newline inputs). Used by the autofix fixer. See ADR-0008.\n */\nexport function frontmatterOffset(content: string): number {\n return content.length - grayMatter(content, GRAY_MATTER_OPTIONS).content.length;\n}\n\n/**\n * Parse a Markdown file: extract YAML frontmatter via gray-matter, then\n * feed the body into mdast-util-from-markdown directly (per ADR-0002,\n * we skip the unified+remark pipeline overhead). Additionally, extract\n * v2-style list metadata from the body's leading metadata block — both\n * bold-key and canonical plain-key shapes (see ADR-0006).\n */\nexport function parseFile(content: string): ParsedFile {\n const matter = grayMatter(content, GRAY_MATTER_OPTIONS);\n const data = matter.data as Record<string, unknown>;\n const frontmatter = Object.keys(data).length > 0 ? data : null;\n const ast = fromMarkdown(matter.content);\n const listResult = extractListMetadataWithLoc(ast, matter.content);\n const listMetadata = listResult?.values ?? null;\n\n let metadata: Record<string, unknown> | null = null;\n let metadataLoc: Record<string, MetadataPosition> | null =\n listResult && Object.keys(listResult.loc).length > 0\n ? { ...listResult.loc }\n : null;\n let metadataValueLoc: Record<string, { start: number; end: number }> | null =\n listResult && Object.keys(listResult.valueOffsets).length > 0\n ? { ...listResult.valueOffsets }\n : null;\n if (frontmatter && listMetadata) {\n // Frontmatter wins on conflict, BUT explicit null/undefined are\n // skipped so that `status: ~` in YAML doesn't blank a present\n // list value (counterintuitive UX otherwise).\n // Null prototype: YAML keys are document-controlled and gray-matter hands\n // `__proto__` back as an own key, which a plain object would feed to the\n // inherited setter — silently dropping it (#56).\n const frontmatterDefined = nullProtoMap<unknown>();\n for (const [k, v] of Object.entries(frontmatter)) {\n if (v !== null && v !== undefined) frontmatterDefined[k] = v;\n }\n metadata = { ...listMetadata, ...frontmatterDefined };\n // A frontmatter-won key's effective value is not in the body, so it\n // must not advertise the (shadowed) list item's position or value range.\n if (metadataLoc) {\n for (const k of Object.keys(frontmatterDefined)) delete metadataLoc[k];\n if (Object.keys(metadataLoc).length === 0) metadataLoc = null;\n }\n if (metadataValueLoc) {\n for (const k of Object.keys(frontmatterDefined)) delete metadataValueLoc[k];\n if (Object.keys(metadataValueLoc).length === 0) metadataValueLoc = null;\n }\n } else if (frontmatter) {\n metadata = frontmatter;\n } else if (listMetadata) {\n metadata = listMetadata;\n }\n\n return {\n frontmatter,\n listMetadata,\n metadata,\n metadataLoc,\n metadataValueLoc,\n ast,\n body: matter.content,\n };\n}\n\n// Canonical MADR metadata field names across v2/v3/v4 (after key\n// normalization). Used to distinguish a genuine metadata block from an\n// ordinary leading list: the plain-key shape (`Foo: bar`) is syntactically\n// identical to prose bullets, so we only promote the block to metadata when it\n// carries at least one of these keys. The gate runs once over the merged\n// segments, not per list. The set is deliberately the canonical fields — a\n// block keyed only by non-standard fields (e.g. `Tags`, `Author`) is treated\n// as prose, not metadata. See the dogfooding notes in ADR-0006.\nconst RECOGNIZED_METADATA_KEYS = new Set([\n 'status',\n 'date',\n 'deciders',\n 'decision-makers',\n 'consulted',\n 'informed',\n]);\n\n/**\n * Extract v2-style list metadata from an mdast Root. The metadata block leads\n * the body: only H1 headings and HTML nodes may precede it — an H2-or-deeper\n * heading, or any other block before the first list, means there is no\n * metadata block. The block itself is the leading RUN of lists, joined across\n * HTML comments — a comment ends the CommonMark list yet renders as nothing,\n * so a field below it is still metadata (#73). A second list with nothing\n * between it and the first (a bullet-marker change, or an ordered list) joins\n * the run when EVERY one of its items reads as a `Key: value` pair (#115); a\n * list carrying any other item is prose and ends the block. Once the run has\n * started, visible HTML, a paragraph, a code fence, a thematic break, a\n * blockquote, and a heading of ANY depth all end the block. Each `listItem` is\n * read as one `Key: value` pair in either of two shapes:\n *\n * - Bold key: `- **Status**: accepted` (some MADR v2 authors)\n * - Plain key: `* Status: accepted` (official MADR v2.1.2 template)\n *\n * Key normalization: trim → lowercase → spaces become hyphens\n * (matches v4 frontmatter convention, e.g. \"Decision Makers\" →\n * \"decision-makers\"). On duplicate keys, first occurrence wins — across\n * segments too, so an earlier segment always shadows a later one.\n *\n * Returns null unless the block carries a recognized MADR key, so ordinary\n * leading prose lists are not mistaken for metadata. (A leading list keyed by\n * a recognized field is still treated as metadata even if its value reads\n * prose-like, e.g. `Status: under discussion` — that position + key is\n * overwhelmingly metadata; this is an intentional, bounded tradeoff.)\n *\n * Because the result is gated on MADR field names, callers using this as a\n * generic list reader will get null for purely non-MADR lists.\n */\nexport function extractListMetadata(\n ast: Root,\n): Record<string, unknown> | null {\n return extractListMetadataWithLoc(ast)?.values ?? null;\n}\n\n/**\n * True when an `html` node is nothing but HTML comments — one or more, with\n * only whitespace between them — i.e. invisible in the rendered document.\n * Deliberately independent of suppression.ts, which rejects several of these\n * shapes as directives: the question here is \"does a reader see this?\", not\n * \"is this a madr-lint directive?\".\n *\n * Consuming the comments in a run rather than testing the first and last four\n * characters is what keeps `<!-- a --> visible <!-- b -->` out — CommonMark\n * ends an HTML block on the first line containing `-->`, so that whole line is\n * ONE node whose visible text a reader does see.\n */\nfunction isHtmlComment(value: string): boolean {\n let rest = value.trim();\n if (rest.length === 0) return false;\n while (rest.length > 0) {\n if (!rest.startsWith('<!--')) return false;\n // From index 2, not 4: CommonMark admits `<!-->` and `<!--->` as empty\n // comments, where the closing `-->` overlaps the opening `<!--`.\n const end = rest.indexOf('-->', 2);\n if (end === -1) return false;\n rest = rest.slice(end + 3).trim();\n }\n return true;\n}\n\n/**\n * Position-carrying variant used by parseFile: alongside the values, records\n * each key's list-item start position (body coordinates) so diagnostics on\n * list-sourced metadata can carry a line — which is what makes inline\n * suppression directives able to target them.\n */\nfunction extractListMetadataWithLoc(\n ast: Root,\n body?: string,\n): {\n values: Record<string, unknown>;\n loc: Record<string, MetadataPosition>;\n valueOffsets: Record<string, { start: number; end: number }>;\n} | null {\n const segments: List[] = [];\n let bridged = false;\n for (const child of ast.children) {\n if (segments.length === 0) {\n if (child.type === 'heading') {\n if (child.depth >= 2) break; // reached the first H2 — no metadata block\n continue; // skip the H1 title (and any further H1s)\n }\n // Leading HTML comments / markers don't displace the metadata block.\n if (child.type === 'html') continue;\n if (child.type === 'list') {\n segments.push(child);\n continue;\n }\n // Any other leading block (paragraph, code, thematic break, …) means the\n // metadata block — which must lead — is absent; the list, if any, is prose.\n break;\n }\n // An HTML comment between two items ends the CommonMark list but renders as\n // nothing, so the block a reader sees is still one block: a field below it\n // must not vanish from metadata (#73). Only pure comments bridge — visible\n // HTML is real content. A heading of ANY depth ends the block here; the H1\n // branch above must stay unreachable once a segment exists, or\n // `list | comment | # Other | list` would merge across a section boundary.\n if (child.type === 'html' && isHtmlComment(child.value)) {\n bridged = true;\n continue;\n }\n // A bullet-marker change (`* Status:` then `- Date:`) also splits the block\n // into two CommonMark lists, but with NO node between them for a comment to\n // bridge (#115). Shape is all the scan has to go on there, so such a list\n // joins the block only when EVERY item reads as a `Key: value` pair — which\n // keeps an ordinary prose list out, at the cost of merging a prose list\n // that happens to be KV-shaped throughout (same class as the #73 tradeoff).\n // The length guard matters because `[].every()` is true: no Markdown\n // produces an empty list, but this function is public and takes any Root.\n if (\n child.type === 'list' &&\n (bridged ||\n (child.children.length > 0 &&\n child.children.every((item) => extractListItemKV(item))))\n ) {\n segments.push(child);\n bridged = false;\n continue;\n }\n break;\n }\n\n if (segments.length === 0) return null;\n\n // Keys come from the document. `Object.hasOwn` below is the fix for #56:\n // `in` sees inherited members, so `* Constructor: x` read as a duplicate of\n // a key that was never set and was dropped. The null prototype is defense in\n // depth — with it, an unset key can never read back an inherited value.\n const values = nullProtoMap<unknown>();\n const loc = nullProtoMap<MetadataPosition>();\n const valueOffsets = nullProtoMap<{ start: number; end: number }>();\n for (const list of segments) {\n for (const item of list.children) {\n const pair = extractListItemKV(item);\n if (pair && !Object.hasOwn(values, pair.key)) {\n values[pair.key] = pair.value;\n const start = item.position?.start;\n if (start) loc[pair.key] = { line: start.line, column: start.column };\n // Record the value offset only when it slices back to the exact value —\n // this rejects any misalignment from escapes / entities, keeping autofix\n // edits precise. Only available when the body text is supplied.\n if (\n body !== undefined &&\n pair.valueOffset &&\n body.slice(pair.valueOffset.start, pair.valueOffset.end) === pair.value\n ) {\n valueOffsets[pair.key] = pair.valueOffset;\n }\n }\n }\n }\n\n const hasRecognizedKey = Object.keys(values).some((k) =>\n RECOGNIZED_METADATA_KEYS.has(k),\n );\n // `values` is handed back as a PLAIN copy: it reaches the public\n // `extractListMetadata` / `ParsedFile.listMetadata` surface verbatim, where\n // a consumer may call `hasOwnProperty` on it. Spreading uses\n // CreateDataProperty, so a `constructor` key stays an own data property.\n // `loc` / `valueOffsets` need no copy here — this function is private and\n // parseFile already spreads them into fresh objects before exposing them.\n return hasRecognizedKey ? { values: { ...values }, loc, valueOffsets } : null;\n}\n\nconst KEY_PATTERN = /^[A-Za-z][A-Za-z0-9 \\-_]*$/;\nconst SEPARATOR_PATTERN = /^\\s*:\\s*/;\n\nfunction normalizeKey(rawKey: string): string {\n return rawKey.trim().toLowerCase().replace(/\\s+/g, '-');\n}\n\n/**\n * Read a single `Key: value` pair from one list item. Returns null for any\n * item not shaped as metadata. Two shapes are supported:\n *\n * - Bold key: paragraph starts with `strong` (the Key), followed by a\n * `: value` text node and optional inline tail.\n * - Plain key: paragraph starts with a `text` node holding `Key: value`,\n * followed by optional inline tail.\n *\n * In both shapes the value's inline tail (links, code, emphasis) is\n * flattened via mdast-util-to-string so values keep their text.\n */\nfunction extractListItemKV(\n item: ListItem,\n): {\n key: string;\n value: string;\n /** Body offset range of a single-text-token value (no inline tail). */\n valueOffset?: { start: number; end: number };\n} | null {\n const para = item.children[0];\n if (para?.type !== 'paragraph') return null;\n\n const paragraph = para as Paragraph;\n const first = paragraph.children[0];\n if (!first) return null;\n\n // Bold key: `**Key**` then a separator text node carrying `: value`.\n if (first.type === 'strong') {\n const keyNode = first.children[0];\n if (keyNode?.type !== 'text' || !KEY_PATTERN.test(keyNode.value)) {\n return null;\n }\n\n const sep = paragraph.children[1];\n if (sep?.type !== 'text' || !SEPARATOR_PATTERN.test(sep.value)) return null;\n\n const valueHead = sep.value.replace(SEPARATOR_PATTERN, '');\n const valueTail = paragraph.children\n .slice(2)\n .map((n) => toString(n))\n .join('');\n const value = (valueHead + valueTail).trim();\n if (value === '') return null;\n\n // Precise offset only for a tail-free value (`**Key**: value` and nothing\n // else) — the value then sits contiguously inside `sep`.\n let valueOffset: { start: number; end: number } | undefined;\n const sepStart = sep.position?.start.offset;\n if (\n paragraph.children.length === 2 &&\n typeof sepStart === 'number' &&\n valueHead !== ''\n ) {\n const sepMatch = SEPARATOR_PATTERN.exec(sep.value);\n const vStart = sepStart + (sepMatch ? sepMatch[0].length : 0);\n valueOffset = { start: vStart, end: vStart + value.length };\n }\n\n return { key: normalizeKey(keyNode.value), value, valueOffset };\n }\n\n // Plain key (canonical MADR v2.1.2): first text node holds `Key: value`.\n if (first.type === 'text') {\n const colonIdx = first.value.indexOf(':');\n if (colonIdx === -1) return null;\n\n const rawKey = first.value.slice(0, colonIdx);\n if (!KEY_PATTERN.test(rawKey)) return null;\n\n const afterColon = first.value.slice(colonIdx + 1);\n const valueHead = afterColon.replace(/^\\s+/, '');\n const valueTail = paragraph.children\n .slice(1)\n .map((n) => toString(n))\n .join('');\n const value = (valueHead + valueTail).trim();\n if (value === '') return null;\n\n // Precise offset only for a tail-free value (`Key: value` in one text node).\n let valueOffset: { start: number; end: number } | undefined;\n const nodeStart = first.position?.start.offset;\n if (\n paragraph.children.length === 1 &&\n typeof nodeStart === 'number' &&\n valueHead !== ''\n ) {\n const leadingWs = afterColon.length - valueHead.length;\n const vStart = nodeStart + colonIdx + 1 + leadingWs;\n valueOffset = { start: vStart, end: vStart + value.length };\n }\n\n return { key: normalizeKey(rawKey), value, valueOffset };\n }\n\n return null;\n}\n","/**\n * Null-prototype map. Keys across this codebase derive from untrusted input —\n * on-disk filenames, rule metadata, MADR body-list field names — so a key like\n * '__proto__' or 'constructor' must behave as ordinary data. On a plain object\n * it would read/write THROUGH Object.prototype instead (prototype pollution,\n * silently dropped entries, an inherited value returned for a key that was\n * never set). With no prototype there is nothing to inherit or pollute.\n * JSON.stringify output is identical.\n */\nexport function nullProtoMap<T>(): Record<string, T> {\n return Object.create(null) as Record<string, T>;\n}\n","import { readFileSync, statSync } from 'node:fs';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport { applyBaseline, type Baseline } from './baseline.js';\nimport {\n computeContentHash,\n loadManifest,\n manifestPath,\n saveManifest,\n CACHE_SCHEMA_VERSION,\n type CacheEntry,\n type CacheManifest,\n} from './cache.js';\nimport {\n applyEditsCounted,\n collectProjectFixes,\n fixFileContent,\n MAX_FIX_PASSES,\n} from './fix.js';\nimport { nullProtoMap } from './null-proto-map.js';\nimport {\n buildProjectFile,\n runRulesOnFile,\n runRulesOnProject,\n} from './runner.js';\nimport {\n collectDirectives,\n isSuppressed,\n DIRECTIVE_PREFIX,\n type DirectiveIndex,\n} from './suppression.js';\n\n/**\n * Normalize a relative path to POSIX form (forward-slashes). On Windows,\n * `relative()` returns backslash-separated paths, but project rules\n * (e.g. `no-broken-links`) resolve URLs using `posix.normalize`. Mixing\n * separators causes silent false positives — so the orchestrator owns\n * the normalization.\n */\nfunction toPosix(p: string): string {\n if (sep === '/') return p;\n return p.split(sep).join('/');\n}\nimport {\n INTERNAL_ERROR_RULE_NAME,\n isProjectRule,\n type AnyRule,\n type Diagnostic,\n type ProjectFile,\n type ProjectRule,\n type Rule,\n type RuleSeverity,\n type Severity,\n} from './types.js';\n\nexport interface CacheConfig {\n /** Directory under which manifest.json is read/written. */\n dir: string;\n /** Stable hash of the resolved config. Mismatch invalidates the cache. */\n configHash: string;\n /** madr-lint package version. Mismatch invalidates the cache. */\n pkgVersion: string;\n}\n\nexport interface LintOptions {\n /** All available rules (per-file or project). The orchestrator picks\n * enabled ones via ruleSeverity and dispatches to the right runner. */\n rules: readonly AnyRule[];\n /** User-configured severity per rule name. Missing entry === 'off'. */\n ruleSeverity: Record<string, RuleSeverity>;\n /** Files to lint, absolute or cwd-relative paths. */\n files: readonly string[];\n /** Working directory. Diagnostics' `path` is reported relative to this. */\n cwd: string;\n /**\n * If set, persist per-file diagnostics keyed by content hash to enable\n * fast re-runs of unchanged files. Project (cross-file) rules always\n * re-run regardless of the cache. Pass `null` to disable.\n */\n cache?: CacheConfig | null;\n /**\n * Parsed baseline to subtract, or null/undefined to disable. Subtraction\n * runs AFTER inline-suppression filtering and is NEVER written to the cache\n * (the cache keeps pre-baseline diagnostics, so editing or deleting the\n * baseline file takes effect without cache invalidation). See ADR-0007.\n */\n baseline?: Baseline | null;\n}\n\nexport interface LintResult {\n filesChecked: number;\n filesFromCache: number;\n diagnostics: Diagnostic[];\n /** How many diagnostics the baseline absorbed (0 when no baseline). */\n baselineHidden: number;\n}\n\n/**\n * Lint a list of files against per-file AND project rules.\n *\n * Per-file rules go through `runRulesOnFile` (single-pass AST per file).\n * Project rules go through `runRulesOnProject` (eager parse all files,\n * single check call per rule). See ADR-0005.\n *\n * File contents are read once and shared between both passes.\n */\nexport function lintFiles(opts: LintOptions): LintResult {\n const allDiagnostics: Diagnostic[] = [];\n\n // Read all file contents once — shared between per-file and project passes.\n // POSIX-normalize the relative path so cross-platform diagnostics and\n // path-based rules (e.g. no-broken-links) work consistently on Windows.\n const fileEntries = opts.files.map((absolutePath) => ({\n relativePath: toPosix(relative(opts.cwd, absolutePath)),\n content: readFileSync(absolutePath, 'utf8'),\n }));\n\n // Partition rules by kind.\n const perFileRules: Rule[] = [];\n const projectRules: ProjectRule[] = [];\n for (const rule of opts.rules) {\n if (isProjectRule(rule)) projectRules.push(rule);\n else perFileRules.push(rule);\n }\n\n // Per-rule options from config tuples (`['error', {...}]`), threaded into\n // both passes so rule options actually take effect (not just defaults).\n const optionsByRule = extractOptionsByRule(opts.ruleSeverity);\n\n // ── Cache setup ──────────────────────────────────────────────────\n let manifest: CacheManifest | null = null;\n let filesFromCache = 0;\n if (opts.cache) {\n const path = manifestPath(opts.cache.dir);\n const loaded = loadManifest(path);\n if (\n loaded &&\n loaded.version === opts.cache.pkgVersion &&\n loaded.configHash === opts.cache.configHash\n ) {\n manifest = loaded;\n } else {\n manifest = {\n schemaVersion: CACHE_SCHEMA_VERSION,\n version: opts.cache.pkgVersion,\n configHash: opts.cache.configHash,\n files: nullProtoMap<CacheEntry>(),\n };\n }\n }\n\n // ── Per-file pass ────────────────────────────────────────────────\n for (const file of fileEntries) {\n if (manifest) {\n const contentHash = computeContentHash(file.content);\n const entry = manifest.files[file.relativePath];\n if (entry && entry.contentHash === contentHash) {\n allDiagnostics.push(...entry.perFileDiagnostics);\n filesFromCache++;\n continue;\n }\n // Cache miss — run rules and store result.\n const fileDiagnostics = runPerFileRulesForFile(\n perFileRules,\n opts.ruleSeverity,\n optionsByRule,\n file,\n );\n allDiagnostics.push(...fileDiagnostics);\n const next: CacheEntry = {\n contentHash,\n perFileDiagnostics: fileDiagnostics,\n };\n manifest.files[file.relativePath] = next;\n } else {\n allDiagnostics.push(\n ...runPerFileRulesForFile(\n perFileRules,\n opts.ruleSeverity,\n optionsByRule,\n file,\n ),\n );\n }\n }\n\n // ── Project pass ─────────────────────────────────────────────────\n const enabledProjectRules = projectRules.filter(\n (rule) => resolveSeverity(opts.ruleSeverity[rule.meta.name]) !== 'off',\n );\n if (enabledProjectRules.length > 0) {\n const projectFiles = fileEntries.map((f) =>\n buildProjectFile({ path: f.relativePath, content: f.content }),\n );\n allDiagnostics.push(\n ...runProjectPass(\n projectFiles,\n enabledProjectRules,\n opts.ruleSeverity,\n optionsByRule,\n opts.cwd,\n ),\n );\n }\n\n // ── Persist cache ────────────────────────────────────────────────\n // Cache the PRE-baseline diagnostics: subtraction happens below, after the\n // save, so the on-disk cache is independent of the baseline file.\n if (manifest && opts.cache) {\n saveManifest(manifestPath(opts.cache.dir), manifest);\n }\n\n // ── Baseline subtraction ─────────────────────────────────────────\n // Zero work when no baseline is provided (the common hot path).\n if (!opts.baseline) {\n return {\n filesChecked: opts.files.length,\n filesFromCache,\n diagnostics: allDiagnostics,\n baselineHidden: 0,\n };\n }\n const applied = applyBaseline(allDiagnostics, opts.baseline);\n return {\n filesChecked: opts.files.length,\n filesFromCache,\n diagnostics: applied.kept,\n baselineHidden: applied.hidden,\n };\n}\n\n// ──────────────────────────────────────────────────────────────────\n// Autofix orchestration (issue #28). Kept OUT of lintFiles so the linting\n// hot path gains no branch. The cache is intentionally not consulted here:\n// fixes need live `fix` thunks, which cache-hydrated diagnostics lack, and a\n// fixed file re-enters the normal pipeline on the next run with a fresh hash.\n// ──────────────────────────────────────────────────────────────────\n\nexport interface FixedFile {\n /** POSIX relative path (diagnostics + diff header). */\n path: string;\n /** Absolute path (for writing). */\n absPath: string;\n /** Original on-disk content. */\n original: string;\n /** Content after the fixpoint loop. */\n fixed: string;\n /** Whether `fixed` differs from `original`. */\n changed: boolean;\n}\n\nexport interface LintAndFixResult {\n files: FixedFile[];\n /** Remaining diagnostics on the FIXED contents (per-file + project). */\n diagnostics: Diagnostic[];\n /** Count of edits applied across every file/pass. */\n fixed: number;\n /** Diagnostics the baseline absorbed on the final contents. */\n baselineHidden: number;\n}\n\nexport interface LintAndFixOptions {\n rules: readonly AnyRule[];\n ruleSeverity: Record<string, RuleSeverity>;\n files: readonly string[];\n cwd: string;\n baseline?: Baseline | null;\n /** Fixpoint pass cap; defaults to the applier's MAX_FIX_PASSES. */\n maxPasses?: number;\n}\n\n/**\n * Lint AND autofix: run the per-file fixpoint on every file (fixes are\n * collected from REPORTED diagnostics only — suppression + baseline already\n * subtracted, so suppressed/baselined problems are never rewritten), then run\n * the project pass on the FIXED contents for the final diagnostic set. Files\n * are returned with their fixed content; the caller writes them (or prints a\n * diff for a dry run). Per-file only — project-rule fixes are #29.\n */\nexport function lintAndFix(opts: LintAndFixOptions): LintAndFixResult {\n const perFileRules: Rule[] = [];\n const projectRules: ProjectRule[] = [];\n for (const rule of opts.rules) {\n if (isProjectRule(rule)) projectRules.push(rule);\n else perFileRules.push(rule);\n }\n const optionsByRule = extractOptionsByRule(opts.ruleSeverity);\n const baseline = opts.baseline ?? null;\n\n const files: FixedFile[] = [];\n const remaining: Diagnostic[] = [];\n let fixed = 0;\n let baselineHidden = 0;\n\n for (const absPath of opts.files) {\n const relativePath = toPosix(relative(opts.cwd, absPath));\n const original = readFileSync(absPath, 'utf8');\n\n // `lint` returns the REPORTED (post-suppression, post-baseline) diagnostics\n // for the given content, carrying live fix thunks. `lastHidden` tracks the\n // final pass's baseline absorption for the summary.\n let lastHidden = 0;\n const lint = (content: string): Diagnostic[] => {\n const raw = runPerFileRulesForFile(\n perFileRules,\n opts.ruleSeverity,\n optionsByRule,\n { relativePath, content },\n );\n if (!baseline) {\n lastHidden = 0;\n return raw;\n }\n const sub = applyBaseline(raw, baseline);\n lastHidden = sub.hidden;\n return sub.kept;\n };\n\n const res = fixFileContent(original, lint, opts.maxPasses);\n files.push({\n path: relativePath,\n absPath,\n original,\n fixed: res.fixedContent,\n changed: res.changed,\n });\n remaining.push(...res.remaining);\n fixed += res.applied;\n baselineHidden += lastHidden;\n }\n\n // ── Project pass + cross-file fix fixpoint on the FIXED contents ──\n // Cross-file (project-rule) fixes reuse the applier through their own per-file\n // edit collection (the #29 seam). The per-file fixpoint has already settled;\n // project fixes only insert whole-file frontmatter keys (supersedes /\n // superseded-by) that no per-file rule reads, so the per-file `remaining`\n // captured above stays valid. See ADR-0008.\n const enabledProjectRules = projectRules.filter(\n (rule) => resolveSeverity(opts.ruleSeverity[rule.meta.name]) !== 'off',\n );\n if (enabledProjectRules.length > 0) {\n // Mutable per-path content, seeded from the per-file-fixed files.\n const contentByPath = new Map(files.map((f) => [f.path, f.fixed]));\n const maxPasses = opts.maxPasses ?? MAX_FIX_PASSES;\n\n let projectDiags: Diagnostic[] = [];\n let passes = 0;\n for (;;) {\n const projectFiles = files.map((f) =>\n buildProjectFile({ path: f.path, content: contentByPath.get(f.path) ?? f.fixed }),\n );\n // runProjectPass returns suppression-filtered, PRE-baseline diagnostics.\n // Subtract the baseline BEFORE collecting fixes so a baselined problem is\n // never rewritten (parity with the per-file path). Only the pass we stop on\n // contributes to `baselineHidden` — the loop recomputes it each iteration.\n let diags = runProjectPass(\n projectFiles,\n enabledProjectRules,\n opts.ruleSeverity,\n optionsByRule,\n opts.cwd,\n );\n let hiddenThisPass = 0;\n if (baseline) {\n const sub = applyBaseline(diags, baseline);\n diags = sub.kept;\n hiddenThisPass = sub.hidden;\n }\n projectDiags = diags;\n\n if (passes >= maxPasses) {\n baselineHidden += hiddenThisPass;\n break;\n }\n const fixesByPath = collectProjectFixes(diags, (p) => contentByPath.get(p));\n let progressed = false;\n for (const [path, edits] of fixesByPath) {\n const before = contentByPath.get(path);\n if (before === undefined) continue;\n const { text, applied } = applyEditsCounted(before, edits);\n if (text !== before) {\n contentByPath.set(path, text);\n fixed += applied;\n progressed = true;\n }\n }\n if (!progressed) {\n baselineHidden += hiddenThisPass;\n break;\n }\n passes++;\n }\n\n // Fold the project-fixed contents back into the returned files.\n for (const f of files) {\n const c = contentByPath.get(f.path);\n if (c !== undefined && c !== f.fixed) {\n f.fixed = c;\n f.changed = f.fixed !== f.original;\n }\n }\n remaining.push(...projectDiags);\n }\n\n return { files, diagnostics: remaining, fixed, baselineHidden };\n}\n\nfunction runPerFileRulesForFile(\n perFileRules: readonly Rule[],\n ruleSeverity: Record<string, RuleSeverity>,\n optionsByRule: Record<string, Record<string, unknown>>,\n file: { relativePath: string; content: string },\n): Diagnostic[] {\n const fileContext = {\n content: file.content,\n path: file.relativePath,\n };\n\n const errorRules: Rule[] = [];\n const warnRules: Rule[] = [];\n for (const rule of perFileRules) {\n const sev = resolveSeverity(ruleSeverity[rule.meta.name]);\n if (sev === 'off') continue;\n if (sev === 'error') errorRules.push(rule);\n else warnRules.push(rule);\n }\n\n const diagnostics: Diagnostic[] = [];\n if (errorRules.length > 0) {\n diagnostics.push(\n ...runRulesOnFile(errorRules, fileContext, {\n severity: 'error',\n optionsByRule,\n }),\n );\n }\n if (warnRules.length > 0) {\n diagnostics.push(\n ...runRulesOnFile(warnRules, fileContext, {\n severity: 'warn',\n optionsByRule,\n }),\n );\n }\n return diagnostics;\n}\n\n/**\n * Build the `fileExists` predicate exposed to project rules. Does a path exist\n * as a regular file WITHIN the project root? Lets project rules verify link\n * targets not in the linted .md set (non-Markdown assets, files outside the\n * scanned paths). Contract:\n * - a target that resolves at or above the root escapes the project →\n * false (path traversal cannot reach above the root);\n * - directories are not files → false (a link must point at a file);\n * - results are memoized per lint run (repeated targets cost one stat).\n * The containment test compares the resolved absolute path to the root rather\n * than a lexical prefix on `resolvedPath`, which may be un-normalized (e.g. a\n * `/`-rooted link resolving to `foo/../../x`). NOTE: statSync follows symlinks,\n * so a symlink that lives inside the root but points outside it is accepted —\n * it is a real entry in the project tree. `resolvedPath` is POSIX; node's\n * path.resolve accepts forward slashes on all platforms.\n */\nfunction makeFileExists(cwd: string): (resolvedPath: string) => boolean {\n const projectRoot = resolve(cwd);\n const existsCache = new Map<string, boolean>();\n return (resolvedPath: string): boolean => {\n const cached = existsCache.get(resolvedPath);\n if (cached !== undefined) return cached;\n const abs = resolve(projectRoot, resolvedPath);\n const rel = relative(projectRoot, abs);\n const escapesRoot =\n rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);\n let result: boolean;\n if (escapesRoot) {\n result = false;\n } else {\n try {\n result = statSync(abs, { throwIfNoEntry: false })?.isFile() ?? false;\n } catch {\n // ELOOP / EACCES / invalid path → cannot confirm a regular file.\n result = false;\n }\n }\n existsCache.set(resolvedPath, result);\n return result;\n };\n}\n\n/**\n * Run enabled project rules over the given (already-built) project files and\n * return suppression-filtered, PRE-baseline diagnostics. Shared by `lintFiles`\n * (files as read) and `lintAndFix` (files after autofix). Baseline subtraction\n * is the caller's job.\n */\nfunction runProjectPass(\n projectFiles: readonly ProjectFile[],\n enabledProjectRules: readonly ProjectRule[],\n ruleSeverity: Record<string, RuleSeverity>,\n optionsByRule: Record<string, Record<string, unknown>>,\n cwd: string,\n): Diagnostic[] {\n const fileExists = makeFileExists(cwd);\n const errorRules: ProjectRule[] = [];\n const warnRules: ProjectRule[] = [];\n for (const rule of enabledProjectRules) {\n const sev = resolveSeverity(ruleSeverity[rule.meta.name]);\n if (sev === 'error') errorRules.push(rule);\n else if (sev === 'warn') warnRules.push(rule);\n }\n\n const projectDiagnostics: Diagnostic[] = [];\n if (errorRules.length > 0) {\n projectDiagnostics.push(\n ...runRulesOnProject(errorRules, projectFiles, {\n severity: 'error',\n fileExists,\n optionsByRule,\n }),\n );\n }\n if (warnRules.length > 0) {\n projectDiagnostics.push(\n ...runRulesOnProject(warnRules, projectFiles, {\n severity: 'warn',\n fileExists,\n optionsByRule,\n }),\n );\n }\n\n // Inline suppression for project diagnostics (issue #23): a directive lives\n // in the file the diagnostic is attributed to (project rules report an\n // explicit `path`). File-scoped directives (disable-file, or an open-ended\n // disable) suppress line-less project diagnostics; line-scoped directives\n // apply when the diagnostic carries a line.\n return filterSuppressedProjectDiagnostics(projectDiagnostics, projectFiles);\n}\n\n/**\n * Filter suppressed project-rule diagnostics. Each diagnostic is matched\n * against the directives of the file it is attributed to (by `path`).\n * Directive indexes are collected lazily and memoized so a file with no\n * suppressed diagnostics is walked at most once. `core/internal-error`\n * diagnostics (and any diagnostic whose path has no source file, e.g. the\n * `<project>` sentinel) are never suppressed.\n */\nfunction filterSuppressedProjectDiagnostics(\n diagnostics: readonly Diagnostic[],\n projectFiles: readonly ProjectFile[],\n): Diagnostic[] {\n if (diagnostics.length === 0) return [];\n\n const fileByPath = new Map(projectFiles.map((f) => [f.path, f]));\n const indexByPath = new Map<string, DirectiveIndex | null>();\n const indexFor = (path: string): DirectiveIndex | null => {\n const cached = indexByPath.get(path);\n if (cached !== undefined) return cached;\n const file = fileByPath.get(path);\n // Unlike the per-file path, no parse can be forced here — project files\n // are eager-parsed by buildProjectFile before rules run. The prefix\n // check just skips the AST walk for directive-free files (same\n // soundness argument: no 'madr-lint-' in content ⇒ no directives).\n const index =\n file && file.content.includes(DIRECTIVE_PREFIX)\n ? collectDirectives(file.ast, file.body)\n : null;\n indexByPath.set(path, index);\n return index;\n };\n\n return diagnostics.filter((d) => {\n if (d.ruleName === INTERNAL_ERROR_RULE_NAME) return true;\n const index = indexFor(d.path);\n if (!index) return true;\n return !isSuppressed(index, d.ruleName, d.loc?.line);\n });\n}\n\nfunction resolveSeverity(config: RuleSeverity | undefined): Severity | 'off' {\n if (config === undefined) return 'off';\n if (typeof config === 'string') return config;\n return config[0];\n}\n\n/**\n * Build a ruleName → options map from config tuples (`['error', {...}]`).\n * Bare-string severities carry no options and are omitted, so rules without\n * configured options fall back to their `meta.defaultOptions` in the runner.\n */\nfunction extractOptionsByRule(\n ruleSeverity: Record<string, RuleSeverity>,\n): Record<string, Record<string, unknown>> {\n const map: Record<string, Record<string, unknown>> = {};\n for (const [name, config] of Object.entries(ruleSeverity)) {\n // Only tuples carry options; guard config[1] so a malformed 1-element\n // `['error']` doesn't put `undefined` into the map.\n if (Array.isArray(config) && config[1]) map[name] = config[1];\n }\n return map;\n}\n","// Baseline file for gradual adoption (issue #24).\n//\n// FINGERPRINT MODEL: a violation is identified by `(relative posix path,\n// ruleName, messageId)` mapped to an allowed COUNT. No line numbers, no\n// message text, no interpolation data — so the baseline survives unrelated\n// edits by construction (a rule that fires the same number of times on the\n// same file+message is fully absorbed regardless of where the lines moved).\n// This is the tsc-baseline-style count model; see ADR-0007 for the rationale\n// and the rejected line-based / content-hash alternatives.\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { nullProtoMap } from './null-proto-map.js';\nimport { INTERNAL_ERROR_RULE_NAME, type Diagnostic } from './types.js';\n\n/** Bumped only on a breaking change to the on-disk shape. */\nexport const BASELINE_VERSION = 1;\n\n/** Fixed, project-root-relative location. Also used verbatim in summaries. */\nexport const BASELINE_DISPLAY_PATH = '.madr-lint/baseline.json';\n\n/**\n * On-disk baseline. `entries` nests path → ruleName → messageId → count.\n * The nesting (rather than a flat hash) is what yields clean, reviewable git\n * diffs once keys are sorted at serialization time.\n */\nexport interface Baseline {\n version: number;\n entries: Record<string, Record<string, Record<string, number>>>;\n}\n\nexport interface BaselineApplyResult {\n /** Diagnostics that survive subtraction (new / over-count / never baselined). */\n kept: Diagnostic[];\n /** How many diagnostics the baseline absorbed. */\n hidden: number;\n}\n\n/** Absolute path to the baseline file for a given project root. */\nexport function baselinePath(cwd: string): string {\n return join(cwd, '.madr-lint', 'baseline.json');\n}\n\n/**\n * Aggregate diagnostics into a baseline by counting each\n * `(path, ruleName, messageId)`. `core/internal-error` is never recorded —\n * it signals a rule bug and must never be silenced (same contract as inline\n * suppression).\n */\nexport function buildBaseline(diagnostics: readonly Diagnostic[]): Baseline {\n const entries = nullProtoMap<Record<string, Record<string, number>>>();\n for (const d of diagnostics) {\n if (d.ruleName === INTERNAL_ERROR_RULE_NAME) continue;\n const byRule = (entries[d.path] ??= nullProtoMap());\n const byMessage = (byRule[d.ruleName] ??= nullProtoMap());\n byMessage[d.messageId] = (byMessage[d.messageId] ?? 0) + 1;\n }\n return { version: BASELINE_VERSION, entries };\n}\n\n/**\n * Deterministic serialization: keys sorted at every level (path, then rule,\n * then messageId), 2-space indent, trailing newline. Same logical baseline\n * always produces byte-identical output → minimal git diffs.\n */\nexport function serializeBaseline(baseline: Baseline): string {\n // Null-proto targets: on a plain object, assigning a '__proto__' key goes\n // through the inherited setter and the entry silently vanishes from the\n // JSON output.\n const sortedEntries = nullProtoMap<Record<string, Record<string, number>>>();\n for (const path of Object.keys(baseline.entries).toSorted()) {\n const byRule = baseline.entries[path] ?? {};\n const sortedByRule = nullProtoMap<Record<string, number>>();\n for (const rule of Object.keys(byRule).toSorted()) {\n const byMessage = byRule[rule] ?? {};\n const sortedByMessage = nullProtoMap<number>();\n for (const messageId of Object.keys(byMessage).toSorted()) {\n sortedByMessage[messageId] = byMessage[messageId] ?? 0;\n }\n sortedByRule[rule] = sortedByMessage;\n }\n sortedEntries[path] = sortedByRule;\n }\n const ordered = { version: baseline.version, entries: sortedEntries };\n return JSON.stringify(ordered, null, 2) + '\\n';\n}\n\n/**\n * Read a baseline file. Returns null when absent or unreadable/malformed —\n * a missing or corrupt baseline is treated as \"no baseline\" (a no-op),\n * never a hard error.\n */\nexport function loadBaseline(path: string): Baseline | null {\n if (!existsSync(path)) return null;\n try {\n const data = JSON.parse(readFileSync(path, 'utf8')) as Partial<Baseline>;\n if (typeof data.entries !== 'object' || data.entries === null) return null;\n return {\n version: typeof data.version === 'number' ? data.version : BASELINE_VERSION,\n entries: rebuildEntries(data.entries as Record<string, unknown>),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Copy parsed JSON into null-proto maps, keeping only well-shaped levels\n * (objects) and leaves (numbers). JSON.parse itself creates '__proto__' as\n * a safe own property, but handing proto-ful objects downstream would\n * re-expose every consumer write to the pollution hazard — and a malformed\n * inner level (e.g. `\"a.md\": null`) would crash applyBaseline.\n */\nfunction rebuildEntries(raw: Record<string, unknown>): Baseline['entries'] {\n const entries = nullProtoMap<Record<string, Record<string, number>>>();\n for (const [path, rawByRule] of Object.entries(raw)) {\n if (typeof rawByRule !== 'object' || rawByRule === null) continue;\n const byRule = nullProtoMap<Record<string, number>>();\n for (const [rule, rawByMessage] of Object.entries(rawByRule)) {\n if (typeof rawByMessage !== 'object' || rawByMessage === null) continue;\n const byMessage = nullProtoMap<number>();\n for (const [messageId, count] of Object.entries(\n rawByMessage as Record<string, unknown>,\n )) {\n if (typeof count === 'number') byMessage[messageId] = count;\n }\n byRule[rule] = byMessage;\n }\n entries[path] = byRule;\n }\n return entries;\n}\n\n/** Write the baseline deterministically, creating `.madr-lint/` if needed. */\nexport function writeBaseline(path: string, baseline: Baseline): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, serializeBaseline(baseline), 'utf8');\n}\n\n// NUL cannot appear in paths, rule names, or messageIds, so joined keys\n// can never collide. Written as an escape (not a raw byte) to keep this\n// file text for git diff/blame.\nconst KEY_SEP = '\\u0000';\n\nfunction fingerprint(d: Diagnostic): string {\n return d.path + KEY_SEP + d.ruleName + KEY_SEP + d.messageId;\n}\n\n/**\n * Subtract a baseline from a diagnostic list. For each fingerprint the first\n * N matching diagnostics (N = baselined count) are absorbed; any extras are\n * reported as new. `core/internal-error` is never absorbed. Stale entries\n * (count exceeding current matches) are simply inert — no error is raised.\n *\n * O(1) per diagnostic: a mutable per-fingerprint allowance map is built once,\n * then each diagnostic is a single map lookup + decrement.\n */\nexport function applyBaseline(\n diagnostics: readonly Diagnostic[],\n baseline: Baseline,\n): BaselineApplyResult {\n const remaining = new Map<string, number>();\n for (const [path, byRule] of Object.entries(baseline.entries)) {\n for (const [rule, byMessage] of Object.entries(byRule)) {\n if (rule === INTERNAL_ERROR_RULE_NAME) continue;\n for (const [messageId, count] of Object.entries(byMessage)) {\n remaining.set(path + KEY_SEP + rule + KEY_SEP + messageId, count);\n }\n }\n }\n\n const kept: Diagnostic[] = [];\n let hidden = 0;\n for (const d of diagnostics) {\n if (d.ruleName === INTERNAL_ERROR_RULE_NAME) {\n kept.push(d);\n continue;\n }\n const key = fingerprint(d);\n const left = remaining.get(key);\n if (left !== undefined && left > 0) {\n remaining.set(key, left - 1);\n hidden++;\n } else {\n kept.push(d);\n }\n }\n return { kept, hidden };\n}\n\nfunction totalCount(baseline: Baseline): number {\n let total = 0;\n for (const byRule of Object.values(baseline.entries)) {\n for (const byMessage of Object.values(byRule)) {\n for (const count of Object.values(byMessage)) total += count;\n }\n }\n return total;\n}\n\n/**\n * One-line summary after `--update-baseline`. Counts the SUM of allowed\n * counts (\"violations\") — not fingerprint keys, and deliberately not the\n * word \"entries\", which names the on-disk map.\n */\nexport function baselineWriteSummary(baseline: Baseline): string {\n const violations = totalCount(baseline);\n const files = Object.keys(baseline.entries).length;\n const v = `${violations} ${violations === 1 ? 'violation' : 'violations'}`;\n const f = `${files} ${files === 1 ? 'file' : 'files'}`;\n return `Wrote ${v} across ${f} to ${BASELINE_DISPLAY_PATH}`;\n}\n\n/**\n * Stderr warning for a baseline file that EXISTS but could not be used.\n * A missing file is silent by design; a present-but-broken one must not be —\n * silently ignoring it would flip CI red with zero explanation.\n */\nexport function baselineMalformedWarning(): string {\n return `madr-lint: warning: ${BASELINE_DISPLAY_PATH} is malformed; ignoring it (run --update-baseline to regenerate)`;\n}\n\n/** One-line summary appended to text output when the baseline hid diagnostics. */\nexport function baselineHiddenSummary(hidden: number): string {\n return `${hidden} ${hidden === 1 ? 'problem' : 'problems'} hidden by baseline (${BASELINE_DISPLAY_PATH})`;\n}\n","// MADR linter core types.\n// Per ADR-0002, the runner uses a single-pass visitor registry over mdast.\n// The Rule type below supports BOTH:\n// - Filename / metadata-only rules: create() returns void\n// - AST-based rules: create() returns RuleListeners with enter/exit handlers\n// keyed by mdast node type. The runner walks each file's tree once and\n// dispatches to all subscribed rules.\n\nimport type { AnySchemaObject } from 'ajv';\nimport type { Nodes, Root } from 'mdast';\n// Note: imports of project-internal modules use the `.js` extension per\n// Node ESM convention. tsc resolves these to the .ts source files.\n\n// All mdast node types rules can subscribe to. `'root'` is the top-level\n// document node — useful for \"after all children visited\" reporting in\n// exit handlers.\nexport type MdastNodeType = Root['type'] | Nodes['type'];\n\n// The argument type for visitor handlers — covers root + all descendants.\nexport type MdastNode = Root | Nodes;\n\n/** Severity levels supported by every rule. */\nexport type Severity = 'error' | 'warn';\n\n// ──────────────────────────────────────────────────────────────────\n// Autofix (see ADR-0008). Fixes are raw-text offset edits, never AST\n// serialization — round-trips lose formatting.\n// ──────────────────────────────────────────────────────────────────\n\n/**\n * A raw-text edit: replace `content.slice(range[0], range[1])` with `text`.\n * Offsets are into the ORIGINAL WHOLE-FILE content (frontmatter included) —\n * the applier's coordinate space. Rules never build these directly; they call\n * the `Fixer` helpers, which translate body (mdast) offsets to whole-file\n * offsets. See ADR-0008.\n */\nexport interface TextEdit {\n range: readonly [number, number];\n text: string;\n}\n\n/**\n * Helpers handed to a rule's `fix` thunk. Range / offset arguments are in\n * BODY (mdast) coordinates — the same space as `node.position.*.offset` — and\n * the fixer adds the stripped-frontmatter length so the returned `TextEdit`\n * carries whole-file offsets. Keep the surface minimal: replace, insert, remove.\n */\nexport interface Fixer {\n /** Replace the body range with `text`. */\n replaceRange(range: readonly [number, number], text: string): TextEdit;\n /** Insert `text` at a body offset (zero-width edit). */\n insertAt(offset: number, text: string): TextEdit;\n /** Delete the body range. */\n remove(range: readonly [number, number]): TextEdit;\n}\n\n/**\n * A lazy fix producer. Invoked ONLY when fixing is requested (zero cost on a\n * normal lint). Returns one edit, several, or `null` to decline (e.g. the\n * concrete fix is not mechanically safe for this occurrence). A rule may only\n * attach a fix when its `meta.fixable === 'code'`.\n */\nexport type FixFn = (fixer: Fixer) => TextEdit | TextEdit[] | null;\n\n/**\n * The reserved rule name used for internal-error diagnostics. A real rule\n * cannot register with this name (the registry should reject collisions).\n * Lives here (not in runner.ts) so leaf modules like the suppression layer\n * can reference it without importing the runner.\n */\nexport const INTERNAL_ERROR_RULE_NAME = 'core/internal-error';\n\n/**\n * User-facing severity declaration in config files.\n * - bare string: enable/disable with default options\n * - tuple: enable with explicit options\n */\nexport type RuleSeverity =\n | Severity\n | 'off'\n | readonly [Severity, Record<string, unknown>];\n\nexport type MadrVersion = 'v2' | 'v3' | 'v4';\n\nexport interface RuleMeta<TOptions = Record<string, unknown>> {\n /** Rule identifier, e.g. 'madr/filename-format'. */\n name: string;\n /** Whether the rule operates per-file or across the project. */\n type: 'perFile' | 'project';\n /** Which MADR versions this rule applies to. */\n versionCompat: readonly MadrVersion[];\n docs: {\n description: string;\n url?: string;\n recommended: boolean;\n };\n /** messageId -> template string with {{placeholders}}. */\n messages: Record<string, string>;\n /**\n * messageId -> machine-actionable remediation template, parallel to\n * `messages` and interpolated the same way. Partial: declare an entry only\n * where a concrete, mechanical fix exists; omit where the fix is inherently\n * contextual. The runner resolves the entry for a reported messageId into\n * `Diagnostic.suggestion` (null when absent). See issue #67.\n */\n suggestions?: Record<string, string>;\n /**\n * Whether this rule can produce autofixes. `'code'` = the rule may attach a\n * `fix` thunk to `context.report(...)`; the runner honors it only for\n * fixable rules and routes a fix from a non-fixable rule through\n * `core/internal-error` (a rule bug). Defaults to `false`. See ADR-0008.\n */\n fixable?: 'code' | false;\n /** Default options merged with user config. */\n defaultOptions: TOptions;\n /** AJV JSON Schema for options. Validated at runRule time. */\n schema?: AnySchemaObject;\n}\n\nexport interface FileContext {\n /** File path (typically basename or repo-relative). */\n path: string;\n /** Raw file content. */\n content: string;\n}\n\nexport interface Diagnostic {\n /** Rule that emitted this diagnostic. */\n ruleName: string;\n /** messageId from rule.meta.messages. */\n messageId: string;\n /** Severity resolved against the user config. */\n severity: Severity;\n /** Path of the offending file. */\n path: string;\n /** Optional source range. Filename rules omit this. */\n loc?: {\n line: number;\n column: number;\n };\n /** Data for placeholder interpolation in messages[messageId]. */\n data?: Record<string, unknown>;\n /**\n * Machine-actionable remediation, resolved by the runner at report time from\n * `rule.meta.suggestions[messageId]` (interpolated with `data`, exactly like\n * the message). `null` when the rule declares no suggestion for this\n * messageId. Every runner-produced diagnostic sets this. See issue #67.\n */\n suggestion: string | null;\n /**\n * The rule's documentation URL (`rule.meta.docs.url`), resolved by the runner\n * so consumers need not reconstruct it from `ruleName`. `core/internal-error`\n * diagnostics point at the repository; a rule without a docs URL yields `''`.\n */\n docsUrl: string;\n /**\n * Whether an autofix is available for THIS diagnostic — the rule is\n * `meta.fixable: 'code'` AND attached a `fix` thunk here. Durable (serialized\n * to the cache and json output; reporters render a 🔧 marker). Every\n * runner-produced diagnostic sets this. See ADR-0008 / issue #28.\n */\n fixable: boolean;\n /**\n * The live fix producer, present only when `fixable` is true. TRANSIENT — a\n * closure, so it is dropped by JSON serialization (cache / json output) and\n * absent on cache-hydrated diagnostics. Consumed by the autofix applier\n * (`src/core/fix.ts`) after suppression + baseline filtering. See ADR-0008.\n */\n fix?: FixFn;\n}\n\nexport interface RuleContext<TOptions = Record<string, unknown>> {\n /** The file being linted. */\n file: FileContext;\n /**\n * Parsed YAML frontmatter (v3/v4 only), or null if absent.\n * Use this when the rule must validate strict YAML frontmatter shape.\n */\n frontmatter: Record<string, unknown> | null;\n /**\n * Combined metadata: YAML frontmatter merged with v2 bold-list extracted\n * from the body. Frontmatter wins on key conflict. null only when both\n * are absent. See ADR-0006. Use this for format-agnostic field reads.\n */\n metadata: Record<string, unknown> | null;\n /**\n * Body-coordinate positions for `metadata` keys whose effective value came\n * from the v2 leading list (the list item's start). Frontmatter-sourced\n * keys are absent — frontmatter is stripped before mdast parsing, so it\n * has no body line. Rules attach this as `loc` on metadata-value\n * diagnostics so inline suppression directives can target them by line.\n */\n metadataLoc: Record<string, { line: number; column: number }> | null;\n /**\n * Body-coordinate offset RANGE of the effective value for `metadata` keys\n * whose value came from the v2 leading list AND was a single contiguous text\n * token (no inline markup). Autofix uses this to target the value precisely\n * (e.g. rewrite the status value in place). Frontmatter-sourced keys are\n * absent — frontmatter is stripped before mdast parsing, so it has no body\n * offset and needs YAML-aware rewriting instead. null when nothing qualifies.\n * See ADR-0008.\n */\n metadataValueLoc: Record<string, { start: number; end: number }> | null;\n /** User-merged options for this rule (validated against rule.meta.schema). */\n options: TOptions;\n /**\n * Emit a diagnostic. `suggestion` / `docsUrl` / `fixable` are resolved by the\n * runner. A rule may pass a `fix` thunk ONLY when its `meta.fixable` is\n * `'code'` (otherwise the runner routes it through `core/internal-error`).\n */\n report(\n diagnostic: Omit<\n Diagnostic,\n 'ruleName' | 'severity' | 'path' | 'suggestion' | 'docsUrl' | 'fixable'\n >,\n ): void;\n}\n\n/**\n * Visitor map dispatched during single-pass mdast traversal.\n * Rules subscribe to specific node types; the runner walks the tree once\n * and calls the matching enter/exit handler for each node.\n */\nexport interface RuleListeners {\n enter?: Partial<Record<MdastNodeType, (node: MdastNode) => void>>;\n exit?: Partial<Record<MdastNodeType, (node: MdastNode) => void>>;\n}\n\n/**\n * A lint rule.\n *\n * - Filename / metadata-only rules: `create` reports diagnostics directly,\n * then returns void.\n * - AST-based rules: `create` returns RuleListeners; the runner invokes\n * them during single-pass traversal of the file's mdast tree.\n */\nexport interface Rule<TOptions = Record<string, unknown>> {\n meta: RuleMeta<TOptions>;\n create(context: RuleContext<TOptions>): RuleListeners | void;\n}\n\n// ──────────────────────────────────────────────────────────────────\n// Project rules (cross-file). See ADR-0005.\n// ──────────────────────────────────────────────────────────────────\n\n/**\n * A pre-parsed file passed to project rules. Built once per\n * `runRulesOnProject` invocation.\n */\nexport interface ProjectFile {\n /** File path relative to the project root. */\n path: string;\n /** Raw file content (unparsed). */\n content: string;\n /**\n * Body content with frontmatter stripped — the same coordinate space as\n * `ast` positions. Used by the suppression layer for line-level directive\n * targeting.\n */\n body: string;\n /** Parsed YAML frontmatter (v3/v4 only), or null if absent. */\n frontmatter: Record<string, unknown> | null;\n /**\n * Combined metadata: frontmatter merged with v2 bold-list extracted\n * from the body. Frontmatter wins on key conflict. See ADR-0006.\n */\n metadata: Record<string, unknown> | null;\n /** mdast root of the body (frontmatter stripped). */\n ast: Root;\n}\n\nexport interface ProjectRuleContext<TOptions = Record<string, unknown>> {\n /** All files in the project (eager-parsed). */\n files: readonly ProjectFile[];\n /** User-merged options for this rule. */\n options: TOptions;\n /**\n * Predicate: does a project-root-relative POSIX path exist as a regular\n * file WITHIN the project root? Injected by the orchestrator so rules (e.g.\n * no-broken-links) can verify link targets that are NOT in the linted `.md`\n * set — non-Markdown assets or files outside the scanned paths. Returns\n * false for directories and for any target that resolves at or above the\n * project root (i.e. escapes it). Undefined when no filesystem is available\n * (e.g. in-memory unit tests), in which case rules fall back to the `files`\n * set alone.\n */\n fileExists?: (resolvedPath: string) => boolean;\n /**\n * Emit a diagnostic. Unlike per-file rules, project rules MUST set\n * `path` explicitly — the runner cannot infer which file the\n * diagnostic relates to. `suggestion` / `docsUrl` are resolved by the runner.\n */\n report(\n diagnostic: Omit<\n Diagnostic,\n 'ruleName' | 'severity' | 'suggestion' | 'docsUrl' | 'fixable'\n >,\n ): void;\n}\n\n/**\n * A cross-file rule. Receives all parsed files at once and reports\n * diagnostics with explicit `path` per file.\n *\n * Project rules use a different runner function (`runRulesOnProject`)\n * and a distinct context shape from per-file rules. See ADR-0005 for\n * the rationale behind keeping the two APIs separate.\n */\nexport interface ProjectRule<TOptions = Record<string, unknown>> {\n meta: RuleMeta<TOptions>;\n check(context: ProjectRuleContext<TOptions>): void;\n}\n\n/** Either a per-file or a project rule. */\nexport type AnyRule<TOptions = Record<string, unknown>> =\n | Rule<TOptions>\n | ProjectRule<TOptions>;\n\n/** Type guard distinguishing project rules from per-file rules. */\nexport function isProjectRule(rule: AnyRule): rule is ProjectRule {\n return 'check' in rule;\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { nullProtoMap } from './null-proto-map.js';\nimport type { Diagnostic } from './types.js';\n\n/**\n * Per-file cache entry: the file content's sha1 hash plus the per-file\n * diagnostics that the rule pipeline emitted last time.\n *\n * Note: project (cross-file) diagnostics are NOT cached — they depend on\n * the entire file set and would invalidate on any add/remove/rename.\n */\nexport interface CacheEntry {\n contentHash: string;\n perFileDiagnostics: Diagnostic[];\n}\n\n/**\n * On-disk manifest schema version. Bump whenever the shape of what we CACHE\n * changes — in particular the `Diagnostic` shape inside `perFileDiagnostics`.\n * pkgVersion invalidation only saves npm upgraders; repo devs and\n * same-version CI caches would otherwise be served stale-shape entries\n * verbatim (e.g. diagnostics missing the #67 `suggestion`/`docsUrl` keys,\n * silently dropped from json output). History: 3 = Diagnostic gained the\n * `fixable` boolean (#28); 2 = Diagnostic gained `suggestion` + `docsUrl`\n * (#67); 1 (implicit) = pre-schema manifests, which carry no `schemaVersion`\n * field at all.\n */\nexport const CACHE_SCHEMA_VERSION = 3;\n\nexport interface CacheManifest {\n /** Manifest schema version. Missing or mismatched ⇒ the cache is cold. */\n schemaVersion: number;\n /** madr-lint version when this manifest was written. Mismatch invalidates the cache. */\n version: string;\n /** Stable hash of the resolved config. Mismatch invalidates the cache. */\n configHash: string;\n /** Per-file cached entries, keyed by POSIX-normalized relative path. */\n files: Record<string, CacheEntry>;\n}\n\nexport function computeContentHash(content: string): string {\n return createHash('sha1').update(content, 'utf8').digest('hex');\n}\n\nexport function computeConfigHash(config: unknown): string {\n return createHash('sha1').update(stableStringify(config), 'utf8').digest('hex');\n}\n\n/**\n * Stable JSON stringify: object keys are sorted so the same logical\n * config always produces the same hash regardless of property insertion\n * order. Cycles are not handled — config objects are tree-shaped.\n */\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value);\n }\n if (Array.isArray(value)) {\n return '[' + value.map(stableStringify).join(',') + ']';\n }\n const entries = Object.entries(value as Record<string, unknown>).toSorted(\n ([a], [b]) => (a < b ? -1 : a > b ? 1 : 0),\n );\n return (\n '{' +\n entries\n .map(([k, v]) => JSON.stringify(k) + ':' + stableStringify(v))\n .join(',') +\n '}'\n );\n}\n\nexport function manifestPath(cacheDir: string): string {\n return join(cacheDir, 'manifest.json');\n}\n\nexport function loadManifest(path: string): CacheManifest | null {\n if (!existsSync(path)) return null;\n try {\n const data = JSON.parse(readFileSync(path, 'utf8')) as Partial<CacheManifest>;\n if (\n // Single invalidation point for schema drift: an unknown (or absent)\n // schema version nulls here, so every consumer sees a cold cache — no\n // scattered re-hydration of stale-shape entries downstream.\n data.schemaVersion !== CACHE_SCHEMA_VERSION ||\n typeof data.version !== 'string' ||\n typeof data.configHash !== 'string' ||\n typeof data.files !== 'object' ||\n data.files === null\n ) {\n return null;\n }\n // Rehydrate onto a null prototype: path keys come from the filesystem, so\n // a file named `__proto__` must behave as ordinary data on both read and\n // write (#56). JSON.parse gives it as an OWN property; copying it onto a\n // plain object would hand it back to the inherited setter.\n const files = nullProtoMap<CacheEntry>();\n for (const [key, entry] of Object.entries(\n data.files as Record<string, CacheEntry>,\n )) {\n files[key] = entry;\n }\n return { ...(data as CacheManifest), files };\n } catch {\n return null;\n }\n}\n\nexport function saveManifest(path: string, manifest: CacheManifest): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(manifest), 'utf8');\n}\n","// Autofix applier (issue #28, ADR-0008).\n//\n// Fixes are RAW-TEXT offset edits, never AST serialization — round-trips lose\n// formatting. A rule's `fix` thunk works in BODY (mdast) coordinates; the\n// `Fixer` translates those to whole-file offsets by adding the stripped\n// frontmatter length. The applier then:\n// 1. collects edits from REPORTED diagnostics only (the caller has already\n// filtered suppression + baseline — those must never be silently rewritten),\n// 2. sorts by start offset and drops any edit overlapping an earlier one\n// (first-by-position wins, deterministic),\n// 3. applies them in a single pass, then RE-LINTS and repeats to a fixpoint\n// (max 10 passes, ESLint parity).\n//\n// Scope: per-file edit sets. Project-rule (cross-file) fixes are #29 — they\n// reuse `applyEdits` with their own per-file edit collection (the clean seam).\n\nimport { frontmatterOffset } from './parser.js';\nimport type { Diagnostic, Fixer, TextEdit } from './types.js';\n\n/** Fixpoint iteration cap (matches ESLint's autofix passes). */\nexport const MAX_FIX_PASSES = 10;\n\n/**\n * Build a `Fixer` whose helpers take BODY offsets and emit whole-file\n * `TextEdit`s by adding `frontmatterOffset` (the length gray-matter stripped).\n */\nexport function makeFixer(base: number): Fixer {\n const shift = (n: number): number => n + base;\n return {\n replaceRange(range, text) {\n return { range: [shift(range[0]), shift(range[1])], text };\n },\n insertAt(offset, text) {\n const at = shift(offset);\n return { range: [at, at], text };\n },\n remove(range) {\n return { range: [shift(range[0]), shift(range[1])], text: '' };\n },\n };\n}\n\n/**\n * Apply text edits to `content` in a single pass. Edits are sorted by start\n * offset; any edit overlapping a previously-applied one is dropped (first wins).\n * Invalid ranges (non-integer, negative, inverted, out of bounds) are dropped.\n * Whole-file offsets in, new string out.\n */\nexport function applyEdits(content: string, edits: readonly TextEdit[]): string {\n return applyEditsCounted(content, edits).text;\n}\n\n/**\n * Counting variant of `applyEdits`: `applied` is the number of edits that\n * actually LANDED (post overlap/bounds filtering) — the honest source for\n * `summary.fixed`, which must not count dropped edits. Exported for the\n * cross-file fixpoint (`lintAndFix`), which applies project-rule edits per file\n * and needs the landed count.\n */\nexport function applyEditsCounted(\n content: string,\n edits: readonly TextEdit[],\n): { text: string; applied: number } {\n const valid = edits.filter((e) => {\n const [start, end] = e.range;\n return (\n Number.isInteger(start) &&\n Number.isInteger(end) &&\n start >= 0 &&\n end >= start &&\n end <= content.length\n );\n });\n // Stable-ish deterministic order: by start, then by end.\n const sorted = valid.toSorted(\n (a, b) => a.range[0] - b.range[0] || a.range[1] - b.range[1],\n );\n\n let out = '';\n let cursor = 0;\n let applied = 0;\n for (const edit of sorted) {\n const [start, end] = edit.range;\n if (start < cursor) continue; // overlaps an already-applied edit — drop\n out += content.slice(cursor, start) + edit.text;\n cursor = end;\n applied++;\n }\n return { text: out + content.slice(cursor), applied };\n}\n\n/**\n * Invoke each diagnostic's `fix` thunk (if any) with a `Fixer` built for\n * `content`, flattening arrays and skipping thunks that decline (return null).\n * The frontmatter offset is derived from `content` so translation always\n * matches the exact bytes being edited.\n */\nexport function collectFixes(\n diagnostics: readonly Diagnostic[],\n content: string,\n): TextEdit[] {\n const fixer = makeFixer(frontmatterOffset(content));\n const edits: TextEdit[] = [];\n for (const d of diagnostics) {\n if (!d.fix) continue;\n const result = d.fix(fixer);\n if (result === null) continue;\n if (Array.isArray(result)) edits.push(...result);\n else edits.push(result);\n }\n return edits;\n}\n\n/**\n * Collect CROSS-FILE (project-rule) fixes, grouped by the file each edits.\n * A project diagnostic names the file it targets via `diagnostic.path`; its fix\n * emits an edit for THAT file. Unlike per-file fixes, project fixes operate in\n * WHOLE-FILE coordinates: they may edit YAML frontmatter, which body (mdast)\n * coordinates strip, so there is no frontmatter shift to add — the fixer's base\n * is 0. See #29 (ADR-0008's per-file edit-set seam).\n *\n * `contentByPath(path)` returns the current content of a target file, or\n * undefined if it is not in the fix set (its edits are then skipped). At most\n * ONE fix contributes per file per pass — a second insertion at the same spot\n * would duplicate a key; the fixpoint re-runs the project pass so the runners-up\n * re-evaluate against the now-edited file (and typically decline).\n */\nexport function collectProjectFixes(\n diagnostics: readonly Diagnostic[],\n contentByPath: (path: string) => string | undefined,\n): Map<string, TextEdit[]> {\n const fixer = makeFixer(0);\n const byPath = new Map<string, TextEdit[]>();\n for (const d of diagnostics) {\n if (!d.fix) continue;\n if (byPath.has(d.path)) continue; // one file, one fix per pass\n if (contentByPath(d.path) === undefined) continue; // target not in the set\n const result = d.fix(fixer);\n if (result === null) continue;\n byPath.set(d.path, Array.isArray(result) ? [...result] : [result]);\n }\n return byPath;\n}\n\nexport interface FixFileResult {\n /** Content after the fixpoint loop (unchanged if nothing applied). */\n fixedContent: string;\n /** Diagnostics of the FINAL content (post-suppression / -baseline via `lint`). */\n remaining: Diagnostic[];\n /** Whether any edit actually changed the content. */\n changed: boolean;\n /** Number of applied passes (0 when nothing was fixed). */\n passes: number;\n /** Edits that actually LANDED across all passes (dropped edits excluded). */\n applied: number;\n}\n\n/**\n * Drive the autofix fixpoint for ONE file. `lint(content)` must return the\n * REPORTED diagnostics for `content` — already suppression- and\n * baseline-filtered — carrying live `fix` thunks. The loop lints at the top of\n * every iteration, so `remaining` always reflects the returned `fixedContent`.\n * Stops when: no edits remain, an edit makes no progress, or `maxPasses` is hit.\n */\nexport function fixFileContent(\n originalContent: string,\n lint: (content: string) => Diagnostic[],\n maxPasses: number = MAX_FIX_PASSES,\n): FixFileResult {\n let content = originalContent;\n let passes = 0;\n let applied = 0;\n\n for (;;) {\n const diagnostics = lint(content);\n const done = (): FixFileResult => ({\n fixedContent: content,\n remaining: diagnostics,\n changed: content !== originalContent,\n passes,\n applied,\n });\n\n if (passes >= maxPasses) return done();\n\n const edits = collectFixes(diagnostics, content);\n if (edits.length === 0) return done();\n\n const { text: next, applied: landed } = applyEditsCounted(content, edits);\n if (next === content) return done(); // no progress — avoid an infinite loop\n\n // Count edits that LANDED, not edits collected — overlap-dropped and\n // out-of-bounds edits never touched the content.\n applied += landed;\n content = next;\n passes++;\n }\n}\n\n// ──────────────────────────────────────────────────────────────────\n// Unified diff (for --fix-dry-run). Line-based LCS with 3 lines of\n// context, grouped into hunks. A preview, not a patch — the \"\\ No newline\n// at end of file\" marker is intentionally omitted.\n// ──────────────────────────────────────────────────────────────────\n\nconst CONTEXT = 3;\n\ntype Op = { kind: 'eq' | 'del' | 'ins'; text: string };\n\n/**\n * Produce a unified diff between `before` and `after` for display. Returns an\n * empty string when the two are identical.\n */\nexport function unifiedDiff(path: string, before: string, after: string): string {\n if (before === after) return '';\n\n const a = splitLines(before);\n const b = splitLines(after);\n const ops = diffLines(a, b);\n const hunks = groupHunks(ops);\n if (hunks.length === 0) return '';\n\n const lines: string[] = [`--- a/${path}`, `+++ b/${path}`];\n for (const hunk of hunks) lines.push(...renderHunk(hunk));\n return lines.join('\\n') + '\\n';\n}\n\n/** Split into lines WITHOUT terminators; a trailing newline yields no empty tail. */\nfunction splitLines(text: string): string[] {\n if (text === '') return [];\n const parts = text.split('\\n');\n if (parts[parts.length - 1] === '') parts.pop();\n return parts;\n}\n\n/** LCS-based line diff → a flat op sequence (eq / del / ins). */\nfunction diffLines(a: readonly string[], b: readonly string[]): Op[] {\n const n = a.length;\n const m = b.length;\n // lcs[i][j] = length of LCS of a[i:] and b[j:].\n const lcs: number[][] = Array.from({ length: n + 1 }, () =>\n Array.from<number>({ length: m + 1 }).fill(0),\n );\n for (let i = n - 1; i >= 0; i--) {\n for (let j = m - 1; j >= 0; j--) {\n lcs[i]![j] =\n a[i] === b[j]\n ? lcs[i + 1]![j + 1]! + 1\n : Math.max(lcs[i + 1]![j]!, lcs[i]![j + 1]!);\n }\n }\n\n const ops: Op[] = [];\n let i = 0;\n let j = 0;\n while (i < n && j < m) {\n if (a[i] === b[j]) {\n ops.push({ kind: 'eq', text: a[i]! });\n i++;\n j++;\n } else if (lcs[i + 1]![j]! >= lcs[i]![j + 1]!) {\n ops.push({ kind: 'del', text: a[i]! });\n i++;\n } else {\n ops.push({ kind: 'ins', text: b[j]! });\n j++;\n }\n }\n while (i < n) ops.push({ kind: 'del', text: a[i++]! });\n while (j < m) ops.push({ kind: 'ins', text: b[j++]! });\n return ops;\n}\n\ninterface Hunk {\n aStart: number;\n aCount: number;\n bStart: number;\n bCount: number;\n ops: Op[];\n}\n\n/** Group the op sequence into hunks, keeping up to CONTEXT equal lines around changes. */\nfunction groupHunks(ops: readonly Op[]): Hunk[] {\n // Index of each op onto its (1-based) source/target line for hunk headers.\n const changeIdx: number[] = [];\n ops.forEach((op, idx) => {\n if (op.kind !== 'eq') changeIdx.push(idx);\n });\n if (changeIdx.length === 0) return [];\n\n // Build [start, end] op-index windows padded by CONTEXT, then merge overlaps.\n const windows: Array<[number, number]> = [];\n for (const idx of changeIdx) {\n const start = Math.max(0, idx - CONTEXT);\n const end = Math.min(ops.length - 1, idx + CONTEXT);\n const last = windows[windows.length - 1];\n if (last && start <= last[1] + 1) last[1] = Math.max(last[1], end);\n else windows.push([start, end]);\n }\n\n const hunks: Hunk[] = [];\n for (const [start, end] of windows) {\n let aLine = 1;\n let bLine = 1;\n for (let k = 0; k < start; k++) {\n if (ops[k]!.kind !== 'ins') aLine++;\n if (ops[k]!.kind !== 'del') bLine++;\n }\n const hunkOps = ops.slice(start, end + 1);\n let aCount = 0;\n let bCount = 0;\n for (const op of hunkOps) {\n if (op.kind !== 'ins') aCount++;\n if (op.kind !== 'del') bCount++;\n }\n hunks.push({ aStart: aLine, aCount, bStart: bLine, bCount, ops: hunkOps });\n }\n return hunks;\n}\n\nfunction renderHunk(hunk: Hunk): string[] {\n const header = `@@ -${hunk.aStart},${hunk.aCount} +${hunk.bStart},${hunk.bCount} @@`;\n const body = hunk.ops.map((op) => {\n const prefix = op.kind === 'eq' ? ' ' : op.kind === 'del' ? '-' : '+';\n return `${prefix}${op.text}`;\n });\n return [header, ...body];\n}\n","import Ajv, { type AnySchemaObject, type ValidateFunction } from 'ajv';\nimport {\n INTERNAL_ERROR_RULE_NAME,\n type Diagnostic,\n type FileContext,\n type MdastNode,\n type ProjectFile,\n type ProjectRule,\n type ProjectRuleContext,\n type Rule,\n type RuleContext,\n type RuleListeners,\n type Severity,\n} from './types.js';\nimport { interpolate } from './interpolate.js';\nimport { parseFile, type ParsedFile } from './parser.js';\nimport {\n collectDirectives,\n filterSuppressed,\n DIRECTIVE_PREFIX,\n} from './suppression.js';\n\n// strict: true catches typo'd schema keywords at compile time.\n// allErrors: true so RuleOptionsError surfaces every issue at once.\nconst ajv = new Ajv({ strict: true, allErrors: true });\n\n// Cache compiled validators per schema object reference.\n// Schemas are module-level static on rules, so the cache key never goes stale.\n// (If a rule mutates `meta.schema` post-load — don't — the cache will desync.)\nconst validatorCache = new WeakMap<AnySchemaObject, ValidateFunction>();\n\nfunction getValidator(schema: AnySchemaObject): ValidateFunction {\n let validator = validatorCache.get(schema);\n if (!validator) {\n validator = ajv.compile(schema);\n validatorCache.set(schema, validator);\n }\n return validator;\n}\n\n// Defined in types.ts (leaf module) so the suppression layer can reference\n// it without a runner import cycle; re-exported here for API stability.\nexport { INTERNAL_ERROR_RULE_NAME };\n\n// core/internal-error diagnostics carry no rule docs URL of their own — a rule\n// bug is a repository issue, so point consumers at the repo. See issue #67.\nconst INTERNAL_ERROR_DOCS_URL = 'https://github.com/knktkc/madr-lint';\n\n/**\n * Thrown when a rule's merged options fail AJV validation against\n * `rule.meta.schema`. Distinct from generic `Error` so callers can\n * `instanceof` to skip rules with bad config and continue.\n */\nexport class RuleOptionsError extends Error {\n constructor(\n public readonly ruleName: string,\n public readonly ajvErrors: string,\n ) {\n super(`Invalid options for rule ${ruleName}: ${ajvErrors}`);\n this.name = 'RuleOptionsError';\n }\n}\n\nexport interface RunRuleOptions {\n /** Per-rule options to merge over rule.meta.defaultOptions. */\n options?: Record<string, unknown>;\n /**\n * Options keyed by rule name, for multi-rule calls where each rule needs\n * its own options (e.g. config tuples `['error', {...}]`). For a given rule,\n * an entry here takes precedence over `options`; rules absent from the map\n * fall back to `options` (if any), then `meta.defaultOptions`.\n */\n optionsByRule?: Record<string, Record<string, unknown>>;\n /** Severity to attach to emitted diagnostics. Defaults to 'error'. */\n severity?: Severity;\n /** Skip AJV options validation. Default false. */\n skipValidation?: boolean;\n /**\n * Filesystem-existence predicate exposed to project rules as\n * `context.fileExists`. Per-file runs ignore this. See\n * ProjectRuleContext.fileExists.\n */\n fileExists?: (resolvedPath: string) => boolean;\n}\n\n/**\n * Per-rule, per-file rule context. The lazy-parse getters live on the class\n * PROTOTYPE — object literals with getters force expensive per-call shape\n * setup, which the filename-format micro-bench (~400ns/op) measured as a\n * double-digit % regression when a third getter was added (PR #53).\n * Prototype accessors are set up once; instantiation is near-free. `report`\n * stays a per-instance closure (it captures the rule name and severity, and\n * this keeps it safely destructurable by rule authors).\n */\nclass PerFileRuleContext implements RuleContext {\n readonly file: FileContext;\n readonly options: Record<string, unknown>;\n readonly report: RuleContext['report'];\n private readonly ensureParsed: () => ParsedFile;\n\n constructor(\n file: FileContext,\n options: Record<string, unknown>,\n ensureParsed: () => ParsedFile,\n report: RuleContext['report'],\n ) {\n this.file = file;\n this.options = options;\n this.ensureParsed = ensureParsed;\n this.report = report;\n }\n\n get frontmatter(): Record<string, unknown> | null {\n return this.ensureParsed().frontmatter;\n }\n\n get metadata(): Record<string, unknown> | null {\n return this.ensureParsed().metadata;\n }\n\n get metadataLoc(): Record<string, { line: number; column: number }> | null {\n return this.ensureParsed().metadataLoc;\n }\n\n get metadataValueLoc(): Record<string, { start: number; end: number }> | null {\n return this.ensureParsed().metadataValueLoc;\n }\n}\n\n/**\n * Run a single rule against a single file. Sugar over runRulesOnFile\n * for the common one-rule case (used heavily by tests).\n *\n * Note: inline suppression directives (`<!-- madr-lint-disable... -->`)\n * present in `file.content` ARE honored — matching diagnostics are filtered.\n */\nexport function runRule<TOptions extends Record<string, unknown>>(\n rule: Rule<TOptions>,\n file: FileContext,\n runtime: RunRuleOptions = {},\n): Diagnostic[] {\n return runRulesOnFile([rule as Rule], file, runtime);\n}\n\n/**\n * Run multiple rules against a single file with a single mdast traversal.\n *\n * Steps:\n * 1. Validate each rule's merged options against its schema (AJV).\n * Throws RuleOptionsError on failure (caller decides whether to skip).\n * 2. Call each rule's create(). If a rule throws, the error is caught\n * and reported as a `core/internal-error` diagnostic — other rules\n * continue. The same isolation wraps every enter/exit handler so a\n * single buggy rule cannot abort the whole lint run.\n * 3. If at least one rule returned RuleListeners, call parseFile()\n * ONCE (gray-matter + mdast-util-from-markdown together; there is\n * no separate cheap \"frontmatter only\" path) and walk the tree,\n * dispatching enter/exit handlers per node type.\n *\n * Frontmatter is exposed lazily via context.frontmatter — the getter\n * triggers parseFile() on first access. Filename-style rules that never\n * touch frontmatter and return void from create() pay zero parse cost.\n *\n * Note: inline suppression directives (`<!-- madr-lint-disable... -->`)\n * present in `file.content` ARE honored — matching diagnostics are filtered\n * centrally after all rules have reported (see suppression.ts).\n */\nexport function runRulesOnFile(\n rules: readonly Rule[],\n file: FileContext,\n runtime: RunRuleOptions = {},\n): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const severity = runtime.severity ?? 'error';\n\n let parsed: ParsedFile | null = null;\n const ensureParsed = (): ParsedFile => {\n parsed ??= parseFile(file.content);\n return parsed;\n };\n\n const enterMap: Record<string, Array<(node: MdastNode) => void>> = {};\n const exitMap: Record<string, Array<(node: MdastNode) => void>> = {};\n let hasASTRule = false;\n\n for (const rule of rules) {\n const perRuleOptions =\n runtime.optionsByRule?.[rule.meta.name] ?? runtime.options;\n const mergedOptions = {\n ...rule.meta.defaultOptions,\n ...perRuleOptions,\n };\n\n if (rule.meta.schema && !runtime.skipValidation) {\n const validator = getValidator(rule.meta.schema);\n if (!validator(mergedOptions)) {\n throw new RuleOptionsError(rule.meta.name, ajv.errorsText(validator.errors));\n }\n }\n\n // Per-rule constants hoisted OUT of the report closure: resolving them per\n // report() call (helper call + rule.meta.docs.url chain + an absent\n // `suggestions` property walk) measured a confirmed -19% on the CI x64\n // no-numbering-gap dirty-path bench, though Apple-silicon runs masked it.\n // The literal below also keeps the base construction pattern — boilerplate\n // head then a single trailing spread — so no post-spread shape transitions.\n const ruleName = rule.meta.name;\n const suggestions = rule.meta.suggestions;\n const docsUrl = rule.meta.docs.url ?? '';\n // Autofix (#28): only a `meta.fixable: 'code'` rule may attach a fix. The\n // flag is captured per rule (a const) so the report closure stays hot —\n // `hasFix` short-circuits to false for the non-fixable common case.\n const fixableRule = rule.meta.fixable === 'code';\n const context: RuleContext = new PerFileRuleContext(\n file,\n mergedOptions,\n ensureParsed,\n (d) => {\n const template = suggestions?.[d.messageId];\n // A fix from a non-fixable rule is a rule bug: throw so the create() /\n // handler isolation converts it to core/internal-error (the misused\n // diagnostic never surfaces). After this guard, `hasFix ⟹ fixableRule`,\n // so `fixable` equals `hasFix`.\n const hasFix = d.fix !== undefined;\n if (hasFix && !fixableRule) {\n throw new Error(\n `Rule \"${ruleName}\" attached a fix but meta.fixable is not 'code'`,\n );\n }\n diagnostics.push({\n ruleName,\n severity,\n path: file.path,\n suggestion: template === undefined ? null : interpolate(template, d.data ?? {}),\n docsUrl,\n fixable: hasFix,\n ...d,\n });\n },\n );\n\n let listeners: RuleListeners | void;\n try {\n listeners = rule.create(context);\n } catch (err) {\n // RuleOptionsError signals the user's config is wrong (e.g. unsafe\n // regex). Propagate it so the CLI can render a clear \"fix your\n // .madrlintrc\" message rather than burying it as a per-file\n // internal-error diagnostic.\n if (err instanceof RuleOptionsError) throw err;\n diagnostics.push(\n internalErrorDiagnostic(rule.meta.name, 'create', err, file.path),\n );\n continue;\n }\n\n if (listeners) {\n hasASTRule = true;\n collectListeners(rule, listeners, enterMap, exitMap, diagnostics, file);\n }\n }\n\n if (hasASTRule) {\n walk(ensureParsed().ast, enterMap, exitMap);\n }\n\n // Central, post-report suppression (issue #23). Rules stay unaware; the\n // runner filters here using directives collected from the file's `html`\n // comment nodes. We only pay the parse + directive walk when there is\n // something to potentially suppress — a clean file short-circuits, so\n // filename-only rules on passing files keep their zero-parse fast path.\n if (diagnostics.length === 0) return diagnostics;\n // Perf guard (PR #53): every directive form contains the literal\n // 'madr-lint-', and the parsed body is a substring of the raw content, so\n // its absence PROVES no directives exist — filtering can be skipped\n // without parsing. Without this, a Shape-A filename diagnostic on an\n // otherwise-unparsed file forced a full gray-matter+mdast parse here\n // (measured -99% on the filename-format invalid-path bench). The substring\n // scan is ~µs; files that do contain the literal proceed exactly as before.\n if (!file.content.includes(DIRECTIVE_PREFIX)) return diagnostics;\n const { ast, body } = ensureParsed();\n const directives = collectDirectives(ast, body);\n return directives ? filterSuppressed(diagnostics, directives) : diagnostics;\n}\n\n// Internal-error diagnostics are ALWAYS severity 'error' regardless of the\n// runtime-passed severity for the rule that threw — a rule misbehaving is\n// never something the user wants to silence as a warning.\nfunction internalErrorDiagnostic(\n ruleName: string,\n operation: 'create' | 'enter' | 'exit' | 'check',\n err: unknown,\n path: string,\n): Diagnostic {\n return {\n ruleName: INTERNAL_ERROR_RULE_NAME,\n messageId: 'ruleThrew',\n severity: 'error',\n path,\n suggestion: null,\n docsUrl: INTERNAL_ERROR_DOCS_URL,\n fixable: false,\n data: {\n rule: ruleName,\n operation,\n error: err instanceof Error ? err.message : String(err),\n },\n };\n}\n\nfunction collectListeners(\n rule: Rule,\n listeners: RuleListeners,\n enterMap: Record<string, Array<(node: MdastNode) => void>>,\n exitMap: Record<string, Array<(node: MdastNode) => void>>,\n diagnostics: Diagnostic[],\n file: FileContext,\n): void {\n const wrap =\n (op: 'enter' | 'exit') => (handler: (node: MdastNode) => void) => (node: MdastNode) => {\n try {\n handler(node);\n } catch (err) {\n diagnostics.push(\n internalErrorDiagnostic(rule.meta.name, op, err, file.path),\n );\n }\n };\n\n if (listeners.enter) {\n const wrapEnter = wrap('enter');\n for (const [type, handler] of Object.entries(listeners.enter)) {\n if (handler) (enterMap[type] ??= []).push(wrapEnter(handler));\n }\n }\n if (listeners.exit) {\n const wrapExit = wrap('exit');\n for (const [type, handler] of Object.entries(listeners.exit)) {\n if (handler) (exitMap[type] ??= []).push(wrapExit(handler));\n }\n }\n}\n\nfunction walk(\n node: MdastNode,\n enterMap: Record<string, Array<(node: MdastNode) => void>>,\n exitMap: Record<string, Array<(node: MdastNode) => void>>,\n): void {\n const enterHandlers = enterMap[node.type];\n if (enterHandlers) {\n for (const handler of enterHandlers) handler(node);\n }\n if ('children' in node && Array.isArray(node.children)) {\n for (const child of node.children) {\n walk(child as MdastNode, enterMap, exitMap);\n }\n }\n const exitHandlers = exitMap[node.type];\n if (exitHandlers) {\n for (const handler of exitHandlers) handler(node);\n }\n}\n\n/**\n * Build a `ProjectFile` (path + content + parsed frontmatter + AST) from\n * a raw file. Used by `runRulesOnProject` and by the lint orchestrator\n * to share parsing between per-file and project passes when needed.\n */\nexport function buildProjectFile(file: FileContext): ProjectFile {\n const parsed: ParsedFile = parseFile(file.content);\n return {\n path: file.path,\n content: file.content,\n body: parsed.body,\n frontmatter: parsed.frontmatter,\n metadata: parsed.metadata,\n ast: parsed.ast,\n };\n}\n\n/**\n * Run multiple project rules across all parsed files. Each rule's\n * `check()` is called once with the full file array. Throws are\n * captured as `core/internal-error` diagnostics (attached to the\n * first file's path, since project rules have no file context).\n *\n * See ADR-0005 for the API rationale.\n */\nexport function runRulesOnProject(\n rules: readonly ProjectRule[],\n files: readonly ProjectFile[],\n runtime: RunRuleOptions = {},\n): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const severity = runtime.severity ?? 'error';\n\n for (const rule of rules) {\n const perRuleOptions =\n runtime.optionsByRule?.[rule.meta.name] ?? runtime.options;\n const mergedOptions = {\n ...rule.meta.defaultOptions,\n ...perRuleOptions,\n };\n\n if (rule.meta.schema && !runtime.skipValidation) {\n const validator = getValidator(rule.meta.schema);\n if (!validator(mergedOptions)) {\n throw new RuleOptionsError(\n rule.meta.name,\n ajv.errorsText(validator.errors),\n );\n }\n }\n\n // Same hoisting as the per-file closure — see the comment there (CI x64\n // bench regression when these resolved per report call).\n const ruleName = rule.meta.name;\n const suggestions = rule.meta.suggestions;\n const docsUrl = rule.meta.docs.url ?? '';\n // Cross-file autofix (#29): a `meta.fixable: 'code'` project rule may attach\n // a fix thunk, resolved to a durable `fixable` boolean and carried (transient)\n // for the cross-file applier. Same gate + misuse-throw as the per-file path.\n const fixableRule = rule.meta.fixable === 'code';\n const context: ProjectRuleContext = {\n files,\n options: mergedOptions,\n fileExists: runtime.fileExists,\n report(d) {\n const template = suggestions?.[d.messageId];\n const hasFix = d.fix !== undefined;\n if (hasFix && !fixableRule) {\n throw new Error(\n `Rule \"${ruleName}\" attached a fix but meta.fixable is not 'code'`,\n );\n }\n diagnostics.push({\n ruleName,\n severity,\n suggestion: template === undefined ? null : interpolate(template, d.data ?? {}),\n docsUrl,\n fixable: hasFix,\n ...d,\n });\n },\n };\n\n try {\n rule.check(context);\n } catch (err) {\n // Project rules have no current-file context — attribute the\n // internal-error to a sentinel path rather than mis-attributing\n // to whatever file happens to be first in the array.\n diagnostics.push(\n internalErrorDiagnostic(rule.meta.name, 'check', err, '<project>'),\n );\n }\n }\n\n return diagnostics;\n}\n","// Shared {{placeholder}} interpolation for message AND suggestion templates.\n// Lives in a leaf module so both the runner (which resolves a diagnostic's\n// `suggestion` at report time) and the reporter (which renders the `message`\n// from its template) use one implementation — never two that can drift.\n\n/**\n * Substitute `{{key}}` placeholders in `template` with the matching value from\n * `data`, stringified. An unknown key is left intact (`{{key}}`) so a missing\n * interpolation is a visible bug surface rather than a silent empty string.\n */\nexport function interpolate(\n template: string,\n data: Record<string, unknown>,\n): string {\n // Fast-path placeholder-free templates (e.g. static suggestions): skip the\n // regex scan + callback allocation entirely. This is on the report hot path\n // — resolved once per emitted diagnostic — so the substring guard matters for\n // the sub-microsecond filename-format micro-bench.\n if (!template.includes('{{')) return template;\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key: string) => {\n const v = data[key];\n return v !== undefined ? String(v) : `{{${key}}}`;\n });\n}\n","// Inline suppression directives (see issue #23).\n//\n// Rules stay completely unaware of suppression: they emit diagnostics as\n// usual, and the runner / lint layer filters them centrally *after* reporting,\n// using directives collected from the file's mdast `html` comment nodes.\n//\n// COORDINATE SYSTEM: directives are read from mdast `html` node positions.\n// Because `parseFile` strips YAML frontmatter with gray-matter BEFORE feeding\n// the body to `mdast-util-from-markdown`, every mdast position — and therefore\n// every directive line AND every AST-derived diagnostic `loc.line` — lives in\n// the same body coordinate space (frontmatter height removed). We deliberately\n// do NOT scan the raw file text, which would put directive lines in raw-file\n// coordinates and silently mis-align them with diagnostic lines.\n\nimport type { Root } from 'mdast';\nimport {\n INTERNAL_ERROR_RULE_NAME,\n type Diagnostic,\n type MdastNode,\n} from './types.js';\n\ntype DirectiveKind = 'disable-file' | 'disable' | 'enable' | 'disable-next-line';\n\n// Only these exact keywords are directives. An unknown suffix (e.g.\n// `madr-lint-disable-line`) is a full non-matching token and is ignored.\nconst DIRECTIVE_KINDS = new Set<DirectiveKind>([\n 'disable-file',\n 'disable',\n 'enable',\n 'disable-next-line',\n]);\n\n/**\n * Every directive form contains this literal. Callers use it as a cheap\n * raw-content pre-filter: since the parsed body is a substring of the raw\n * content, `!content.includes(DIRECTIVE_PREFIX)` PROVES the file contains\n * no directives — so directive collection, and any parse it would otherwise\n * force, can be skipped entirely.\n */\nexport const DIRECTIVE_PREFIX = 'madr-lint-';\n\ninterface Directive {\n kind: DirectiveKind;\n /** null = applies to all rules; otherwise the explicit rule-id list. */\n rules: readonly string[] | null;\n /** 1-based START line of the comment, in body (frontmatter-stripped) coordinates. */\n line: number;\n /**\n * 1-based END line of the comment, same coordinates. Equals `line` for a\n * single-line comment; for a multi-line `<!--` … `-->` block it is the line\n * carrying `-->`, which is what `disable-next-line` counts from (#116).\n */\n endLine: number;\n}\n\ninterface NextLineEntry {\n all: boolean;\n rules: Set<string>;\n}\n\nexport interface DirectiveIndex {\n /** An unscoped `disable-file` disables every rule for the whole file. */\n fileDisableAll: boolean;\n /** Rules disabled file-wide via `disable-file <rules>`. */\n fileDisableRules: Set<string>;\n /** `disable` / `enable` directives, sorted by START line ascending. */\n ranged: Directive[];\n /**\n * disable-next-line: target line → affected rules. The target is the first\n * NON-BLANK line after the comment's END line, not `line + 1`.\n */\n nextLine: Map<number, NextLineEntry>;\n}\n\n/** Parse one `<!-- ... -->` html node value into a directive, or null. */\nfunction parseDirective(\n value: string,\n line: number,\n endLine: number,\n): Directive | null {\n const trimmed = value.trim();\n if (!trimmed.startsWith('<!--') || !trimmed.endsWith('-->')) return null;\n\n const inner = trimmed.slice(4, -3).trim();\n // Two comments coalesced into one html node (e.g. `<!-- a --><!-- b -->`\n // on one line) pass the startsWith/endsWith guard but would sweep\n // `--><!--` garbage into the rule list. A directive comment must stand\n // alone: reject any candidate whose interior still carries comment\n // delimiters.\n if (inner.includes('<!--') || inner.includes('-->')) return null;\n if (!inner.startsWith(DIRECTIVE_PREFIX)) return null;\n\n const afterPrefix = inner.slice(DIRECTIVE_PREFIX.length);\n // The keyword token ends at the first whitespace; the remainder is the\n // optional rule list. Matching the FULL token against the exact keyword set\n // is what makes `madr-lint-disable-line` (token `disable-line`) a non-match.\n const wsIndex = afterPrefix.search(/\\s/);\n const keyword = wsIndex === -1 ? afterPrefix : afterPrefix.slice(0, wsIndex);\n if (!DIRECTIVE_KINDS.has(keyword as DirectiveKind)) return null;\n\n const rest = wsIndex === -1 ? '' : afterPrefix.slice(wsIndex);\n return {\n kind: keyword as DirectiveKind,\n rules: parseRuleList(rest),\n line,\n endLine,\n };\n}\n\nfunction parseRuleList(rest: string): readonly string[] | null {\n const trimmed = rest.trim();\n if (trimmed === '') return null; // no list ⇒ all rules\n const rules = trimmed\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s !== '');\n return rules.length > 0 ? rules : null;\n}\n\nfunction walkHtml(node: MdastNode, out: Directive[]): void {\n if (node.type === 'html') {\n const line = node.position?.start.line;\n const endLine = node.position?.end.line;\n if (typeof line === 'number' && typeof endLine === 'number') {\n const directive = parseDirective(node.value, line, endLine);\n if (directive) out.push(directive);\n }\n }\n if ('children' in node && Array.isArray(node.children)) {\n for (const child of node.children) walkHtml(child as MdastNode, out);\n }\n}\n\n/**\n * Collect suppression directives from a file's mdast tree in one cheap walk.\n * Returns null when the file carries no directives, so callers can skip\n * filtering entirely (fast path). Both block and inline `html` comment nodes\n * are inspected.\n *\n * `body` is the frontmatter-stripped body text — the SAME coordinate space\n * as the mdast positions. It is only consulted (and only split into lines)\n * when a `disable-next-line` directive needs to resolve its target.\n */\nexport function collectDirectives(\n ast: Root,\n body: string,\n): DirectiveIndex | null {\n const all: Directive[] = [];\n walkHtml(ast as MdastNode, all);\n if (all.length === 0) return null;\n\n // disable-next-line targets the next NON-BLANK line, not the literal next\n // line. This deviates from ESLint deliberately: Markdown authors\n // idiomatically leave a blank line after a comment block, and a directive\n // that silently misses across it would be a footgun.\n let lines: string[] | null = null;\n const nextNonBlankLine = (after: number): number | null => {\n lines ??= body.split('\\n');\n for (let ln = after + 1; ln <= lines.length; ln++) {\n if ((lines[ln - 1] ?? '').trim() !== '') return ln;\n }\n return null; // directive at EOF with nothing after it — no target\n };\n\n const index: DirectiveIndex = {\n fileDisableAll: false,\n fileDisableRules: new Set(),\n ranged: [],\n nextLine: new Map(),\n };\n\n for (const directive of all) {\n switch (directive.kind) {\n case 'disable-file': {\n if (directive.rules === null) index.fileDisableAll = true;\n else for (const rule of directive.rules) index.fileDisableRules.add(rule);\n break;\n }\n case 'disable':\n case 'enable': {\n index.ranged.push(directive);\n break;\n }\n case 'disable-next-line': {\n // Counted from the comment's END line: a `<!--` … `-->` block written\n // across several lines would otherwise target its own interior\n // instead of the author's content below `-->` (#116). A single-line\n // comment has endLine === line, so its behavior is unchanged.\n const target = nextNonBlankLine(directive.endLine);\n if (target === null) break;\n let entry = index.nextLine.get(target);\n if (!entry) {\n entry = { all: false, rules: new Set() };\n index.nextLine.set(target, entry);\n }\n if (directive.rules === null) entry.all = true;\n else for (const rule of directive.rules) entry.rules.add(rule);\n break;\n }\n }\n }\n\n // Ranges stay keyed to the comment's START line, which is what preserves\n // pre-#116 behavior: a range opens where the author wrote it. The choice is\n // observable, not cosmetic — an INLINE multi-line comment shares its start\n // line with the content beside it (`[gone](./nope.md) <!--` … `-->` spans\n // three lines while the link is reported on the first), so counting from\n // `endLine` here would stop such a `disable` from covering its own line.\n index.ranged.sort((a, b) => a.line - b.line);\n return index;\n}\n\nfunction directiveApplies(directive: Directive, ruleName: string): boolean {\n return directive.rules === null || directive.rules.includes(ruleName);\n}\n\n/**\n * Is a single diagnostic suppressed by the file's directives?\n *\n * `core/internal-error` is handled by the caller and never reaches here.\n *\n * Line-less diagnostics (no `loc`) are treated as spanning the whole file:\n * only `disable-file` OR an OPEN-ENDED `disable` (a `disable` with no later\n * matching `enable`) suppresses them. This is exactly the \"file-scoped\"\n * contract the issue specifies for project-rule diagnostics.\n */\nexport function isSuppressed(\n index: DirectiveIndex,\n ruleName: string,\n line: number | undefined,\n): boolean {\n if (index.fileDisableAll || index.fileDisableRules.has(ruleName)) return true;\n\n if (line !== undefined) {\n const next = index.nextLine.get(line);\n if (next && (next.all || next.rules.has(ruleName))) return true;\n }\n\n // Replay disable/enable in line order; the last one at or before the\n // diagnostic line wins. A line-less diagnostic uses +Infinity as its line,\n // so every directive is replayed and only an unclosed final `disable`\n // (open to EOF) leaves it suppressed. This same replay yields ESLint-style\n // carve-outs, e.g. an unscoped `disable` followed by a scoped `enable`.\n const ceiling = line ?? Number.POSITIVE_INFINITY;\n let disabled = false;\n for (const directive of index.ranged) {\n if (directive.line > ceiling) break; // ranged is sorted ascending\n if (directiveApplies(directive, ruleName)) {\n disabled = directive.kind === 'disable';\n }\n }\n return disabled;\n}\n\n/**\n * Filter suppressed diagnostics. `core/internal-error` diagnostics are NEVER\n * suppressible: they signal a rule bug (always severity 'error'), and letting\n * a `disable`/`disable-file` hide them would mask defects. This is the\n * simplest behavior consistent with the runner already forcing internal-error\n * severity regardless of user config.\n */\nexport function filterSuppressed(\n diagnostics: readonly Diagnostic[],\n index: DirectiveIndex,\n): Diagnostic[] {\n return diagnostics.filter((d) => {\n if (d.ruleName === INTERNAL_ERROR_RULE_NAME) return true;\n return !isSuppressed(index, d.ruleName, d.loc?.line);\n });\n}\n","// Registry of all built-in madr-lint rules.\n// New rules append here via the add-rule skill.\n\nexport { default as dateIso8601 } from './date-iso8601/index.js';\nexport { default as filenameFormat } from './filename-format/index.js';\nexport { default as noBrokenLinks } from './no-broken-links/index.js';\nexport { default as noDuplicateNumbering } from './no-duplicate-numbering/index.js';\nexport { default as noNumberingGap } from './no-numbering-gap/index.js';\nexport { default as requiredSections } from './required-sections/index.js';\nexport { default as statusEnum } from './status-enum/index.js';\nexport { default as supersedesBidirectional } from './supersedes-bidirectional/index.js';\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"field\": {\n \"type\": \"string\",\n \"description\": \"Frontmatter key to read. Default: 'date'.\"\n }\n }\n}\n","import type { Fixer, Rule, TextEdit } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface DateIso8601Options extends Record<string, unknown> {\n field: string;\n}\n\nfunction normalizeDate(value: unknown): string | null {\n if (typeof value === 'string') return value;\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null;\n return value.toISOString().slice(0, 10);\n }\n return null;\n}\n\nfunction isValidIso8601Date(s: string): boolean {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(s)) return false;\n const parts = s.split('-').map(Number);\n const [year, month, day] = parts;\n if (year === undefined || month === undefined || day === undefined) return false;\n const d = new Date(Date.UTC(year, month - 1, day));\n return (\n d.getUTCFullYear() === year &&\n d.getUTCMonth() === month - 1 &&\n d.getUTCDate() === day\n );\n}\n\n// English month names → month number. An explicit table (no `Date.parse`, which\n// is locale/timezone-dependent) so autofix only touches names it recognizes\n// unambiguously; any other spelling declines. See #29.\nconst MONTHS: Record<string, number> = {\n jan: 1, january: 1,\n feb: 2, february: 2,\n mar: 3, march: 3,\n apr: 4, april: 4,\n may: 5,\n jun: 6, june: 6,\n jul: 7, july: 7,\n aug: 8, august: 8,\n sep: 9, sept: 9, september: 9,\n oct: 10, october: 10,\n nov: 11, november: 11,\n dec: 12, december: 12,\n};\n\n/** Zero-pad a positive integer to `width` digits. */\nfunction pad(n: number, width: number): string {\n return String(n).padStart(width, '0');\n}\n\n/**\n * Build a canonical `YYYY-MM-DD` string from numeric parts, or null when the\n * result is not a real calendar date. Never turns an invalid date into a\n * *different* valid one (e.g. `2026/2/30` has no fix).\n */\nfunction buildIso(year: number, month: number, day: number): string | null {\n if (month < 1 || month > 12 || day < 1 || day > 31) return null;\n const iso = `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`;\n return isValidIso8601Date(iso) ? iso : null;\n}\n\n/**\n * Normalize `raw` to `YYYY-MM-DD` ONLY when the mapping is unambiguous:\n * - year-first numeric with matching separators: `YYYY/M/D`, `YYYY.M.D`,\n * `YYYY-M-D` (month ≤ 12, day ≤ 31, real calendar date);\n * - named-month forms with an English month: `D Mon YYYY`, `Mon D, YYYY`.\n * Day/month-order-ambiguous inputs (`03/07/2026`), short years, and unknown\n * month names return null — the rule then reports without offering a fix.\n */\nfunction normalizeToIso(raw: string): string | null {\n const s = raw.trim();\n\n // Year-first numeric — the ONE unambiguous numeric shape. The `\\2` backref\n // forces a single separator style, so `2026/07-03` never matches.\n const numeric = /^(\\d{4})([-/.])(\\d{1,2})\\2(\\d{1,2})$/.exec(s);\n if (numeric) {\n return buildIso(Number(numeric[1]), Number(numeric[3]), Number(numeric[4]));\n }\n\n // Day-first named month: `3 Jul 2026`, `03 July 2026` (optional trailing dot).\n const dMonY = /^(\\d{1,2})\\s+([A-Za-z]+)\\.?\\s+(\\d{4})$/.exec(s);\n if (dMonY) {\n const month = MONTHS[dMonY[2]!.toLowerCase()];\n return month ? buildIso(Number(dMonY[3]), month, Number(dMonY[1])) : null;\n }\n\n // Month-first named month: `Jul 3, 2026`, `July 03, 2026` (comma optional).\n const monDY = /^([A-Za-z]+)\\.?\\s+(\\d{1,2}),?\\s+(\\d{4})$/.exec(s);\n if (monDY) {\n const month = MONTHS[monDY[1]!.toLowerCase()];\n return month ? buildIso(Number(monDY[3]), month, Number(monDY[2])) : null;\n }\n\n return null;\n}\n\nconst rule: Rule<DateIso8601Options> = {\n meta: {\n name: 'madr/date-iso8601',\n type: 'perFile',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'Validate that ADR `date` (frontmatter or v2 bold-list) is a valid ISO 8601 calendar date (YYYY-MM-DD)',\n url: 'https://knktkc.github.io/madr-lint/rules/date-iso8601/',\n recommended: true,\n },\n messages: {\n missingDate: 'Metadata does not contain a \"{{field}}\" field (checked frontmatter and v2 bold-list)',\n invalidDate: 'Date \"{{date}}\" is not a valid ISO 8601 calendar date (YYYY-MM-DD)',\n },\n suggestions: {\n missingDate:\n 'add a \"{{field}}\" field in YYYY-MM-DD format to the frontmatter (for MADR v2, a \"* Date: ...\" list item)',\n invalidDate: 'use the YYYY-MM-DD calendar-date format, e.g. 2025-03-14',\n },\n // Autofix (#29): normalize UNAMBIGUOUS shapes of a v2 list-sourced value —\n // year-first numeric and English named-month forms. Ambiguous day/month\n // order, invalid calendar dates, and frontmatter-sourced values get no fix.\n fixable: 'code',\n defaultOptions: {\n field: 'date',\n },\n schema,\n },\n create(context) {\n const meta = context.metadata;\n const fieldName = context.options.field;\n\n if (!meta) {\n context.report({\n messageId: 'missingDate',\n data: { field: fieldName },\n });\n return;\n }\n\n const raw = meta[fieldName];\n const dateStr = normalizeDate(raw);\n if (dateStr === null) {\n context.report({\n messageId: 'missingDate',\n data: { field: fieldName },\n });\n return;\n }\n\n if (!isValidIso8601Date(dateStr)) {\n // loc only exists for v2 list-sourced values: frontmatter is stripped\n // before mdast parsing, so a frontmatter-sourced date has no body line\n // to point at — inline suppression directives (which live in the body)\n // can only silence it file-wide, never per line.\n const loc = context.metadataLoc?.[fieldName];\n // Autofix (#29): offer a fix ONLY for a v2 list-sourced value (we then have\n // its exact body offset range) whose shape normalizes unambiguously.\n // Frontmatter-sourced values have no body offset and need YAML-aware\n // rewriting — out of scope, so no fix is attached there.\n const valueRange = context.metadataValueLoc?.[fieldName];\n const normalized = valueRange ? normalizeToIso(dateStr) : null;\n const fix =\n normalized !== null && normalized !== dateStr && valueRange\n ? (fixer: Fixer): TextEdit =>\n fixer.replaceRange([valueRange.start, valueRange.end], normalized)\n : undefined;\n context.report({\n messageId: 'invalidDate',\n ...(loc ? { loc } : {}),\n data: { date: dateStr },\n ...(fix ? { fix } : {}),\n });\n }\n },\n};\n\nexport default rule;\n","import { basename } from 'node:path';\nimport { assertSafeRegex } from '../../core/regex-safety.js';\nimport type { Rule } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface FilenameFormatOptions extends Record<string, unknown> {\n pattern: string;\n}\n\nconst rule: Rule<FilenameFormatOptions> = {\n meta: {\n name: 'madr/filename-format',\n type: 'perFile',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'Enforce ADR filename convention NNNN-kebab-case.md',\n url: 'https://knktkc.github.io/madr-lint/rules/filename-format/',\n recommended: true,\n },\n messages: {\n invalidFilename:\n 'Filename \"{{filename}}\" does not match expected pattern \"{{expected}}\"',\n },\n suggestions: {\n // The message already states the expected pattern; the suggestion adds a\n // concrete, conforming example of the NNNN-kebab-case.md convention.\n invalidFilename:\n 'rename the file to the NNNN-kebab-case.md convention, e.g. 0001-record-architecture-decisions.md',\n },\n defaultOptions: {\n pattern: '^[0-9]{4}-[a-z0-9-]+\\\\.md$',\n },\n schema,\n },\n create(context) {\n const { pattern } = context.options;\n const regex = assertSafeRegex(pattern, 'madr/filename-format', 'pattern');\n const filename = basename(context.file.path);\n if (!regex.test(filename)) {\n context.report({\n messageId: 'invalidFilename',\n data: { filename, expected: pattern },\n });\n }\n },\n};\n\nexport default rule;\n","import safeRegex from 'safe-regex2';\nimport { RuleOptionsError } from './runner.js';\n\nconst SAFE_REGEX_LIMIT = 25;\n\n// Pattern → compiled RegExp cache. Only safe patterns are cached; unsafe\n// or syntactically invalid ones throw on every call (callers should fix\n// their config rather than burn cycles re-validating the same bad input).\n//\n// Cache scope: module-global. Keys are pattern strings, which are static\n// per rule.defaultOptions or per user config. Bounded in practice — a\n// long-running daemon would still see at most O(rules × user-overrides)\n// distinct patterns. If memory becomes a concern in pathological cases\n// (e.g. dynamically-generated patterns), the cache can be swapped for an\n// LRU. As of v0.1, a plain Map is sufficient.\nconst cache = new Map<string, RegExp>();\n\nexport function assertSafeRegex(\n pattern: string,\n ruleName: string,\n optionPath: string,\n): RegExp {\n const cached = cache.get(pattern);\n if (cached) return cached;\n\n let regex: RegExp;\n try {\n regex = new RegExp(pattern);\n } catch (err) {\n throw new RuleOptionsError(\n ruleName,\n `option \"${optionPath}\" is not a valid regular expression: ${(err as Error).message}`,\n );\n }\n if (!safeRegex(regex, { limit: SAFE_REGEX_LIMIT })) {\n throw new RuleOptionsError(\n ruleName,\n `option \"${optionPath}\" (${pattern}) appears unsafe (catastrophic backtracking). Reject before runtime.`,\n );\n }\n cache.set(pattern, regex);\n return regex;\n}\n\n/**\n * Test-only helper to drop the cache between unit tests so that\n * \"unsafe pattern\" assertions don't accidentally short-circuit on a\n * previously-cached safe entry. Not exported from src/index.ts.\n */\nexport function clearRegexCacheForTesting(): void {\n cache.clear();\n}\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"pattern\": {\n \"type\": \"string\",\n \"description\": \"Regex (as string) the basename must match. Defaults to ^[0-9]{4}-[a-z0-9-]+\\\\.md$\"\n }\n }\n}\n","import { dirname, posix } from 'node:path';\nimport type { MdastNode, ProjectRule } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface NoBrokenLinksOptions extends Record<string, unknown> {\n // No options at v0.1.\n}\n\nconst PROTOCOL_REGEX = /^[a-z][a-z0-9+.-]*:/i;\n\ninterface LinkLikeNode {\n url: string;\n /** unist position — start carries the body-coordinate line/column. */\n position?: { start: { line: number; column: number } };\n}\n\nfunction isLinkNode(node: MdastNode): node is MdastNode & LinkLikeNode {\n return (\n node.type === 'link' &&\n 'url' in node &&\n typeof (node as { url: unknown }).url === 'string'\n );\n}\n\nfunction isExternalOrAnchor(url: string): boolean {\n if (url === '' || url.startsWith('#')) return true;\n return PROTOCOL_REGEX.test(url);\n}\n\nfunction stripAnchorAndQuery(url: string): string {\n const hash = url.indexOf('#');\n const query = url.indexOf('?');\n let cut = hash;\n if (query !== -1 && (cut === -1 || query < cut)) cut = query;\n return cut === -1 ? url : url.slice(0, cut);\n}\n\n// Markdown link destinations may be percent-encoded (e.g. `my%20file.md`).\n// Decode to the real on-disk path for resolution; leave malformed encoding\n// (e.g. a bare `%`) untouched rather than throwing. Fast-path the common case\n// of no percent-encoding to keep per-link cost minimal (decodeURIComponent is\n// comparatively expensive).\nfunction safeDecode(path: string): string {\n if (!path.includes('%')) return path;\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\nfunction resolveRelative(fromDir: string, url: string): string {\n // Treat absolute (`/foo`) as project-rooted by stripping the leading slash,\n // so `[x](/0042-y.md)` resolves to `0042-y.md` rather than the OS root.\n if (url.startsWith('/')) return url.slice(1);\n return posix.normalize(posix.join(fromDir, url));\n}\n\nfunction collectLinks(node: MdastNode, out: LinkLikeNode[]): void {\n if (isLinkNode(node)) out.push(node);\n if ('children' in node && Array.isArray(node.children)) {\n for (const child of node.children) {\n collectLinks(child as MdastNode, out);\n }\n }\n}\n\nconst rule: ProjectRule<NoBrokenLinksOptions> = {\n meta: {\n name: 'madr/no-broken-links',\n type: 'project',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description:\n 'Relative-path Markdown links must resolve to an existing file in the project',\n url: 'https://knktkc.github.io/madr-lint/rules/no-broken-links/',\n recommended: true,\n },\n messages: {\n brokenLink:\n 'Link to \"{{url}}\" resolves to \"{{resolvedPath}}\", which does not exist in the project',\n },\n suggestions: {\n brokenLink:\n \"fix the link target or remove the link; relative paths resolve from the ADR file's own directory\",\n },\n defaultOptions: {},\n schema,\n },\n check(context) {\n const knownPaths = new Set<string>();\n for (const file of context.files) {\n knownPaths.add(file.path);\n }\n\n for (const file of context.files) {\n const links: LinkLikeNode[] = [];\n collectLinks(file.ast, links);\n\n for (const link of links) {\n const url = link.url;\n if (isExternalOrAnchor(url)) continue;\n\n const pathOnly = safeDecode(stripAnchorAndQuery(url));\n if (pathOnly === '') continue;\n\n const fileDir = dirname(file.path);\n const resolvedPath = resolveRelative(fileDir, pathOnly);\n\n // A target counts as present if it is one of the linted .md files\n // OR exists on disk (covers non-Markdown assets and files outside\n // the scanned paths). fileExists is undefined in pure in-memory\n // runs, where knownPaths is the only source of truth. NOTE: the\n // on-disk check inherits the host filesystem's case-sensitivity —\n // a wrong-case link may pass on macOS/Windows yet fail on Linux/CI.\n const exists =\n knownPaths.has(resolvedPath) ||\n (context.fileExists?.(resolvedPath) ?? false);\n\n if (!exists) {\n // Link position is in body coordinates (frontmatter stripped) —\n // the same space suppression directives use, so disable-next-line\n // can target this diagnostic.\n const start = link.position?.start;\n context.report({\n messageId: 'brokenLink',\n path: file.path,\n ...(start\n ? { loc: { line: start.line, column: start.column } }\n : {}),\n data: { url, resolvedPath },\n });\n }\n }\n }\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {}\n}\n","import { basename } from 'node:path';\nimport type { ProjectRule } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface NoDuplicateNumberingOptions extends Record<string, unknown> {\n // No options at v0.1.\n}\n\nconst NUMBER_REGEX = /^(\\d{4})-/;\n\nconst rule: ProjectRule<NoDuplicateNumberingOptions> = {\n meta: {\n name: 'madr/no-duplicate-numbering',\n type: 'project',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'No two ADRs share the same NNNN- number prefix',\n url: 'https://knktkc.github.io/madr-lint/rules/no-duplicate-numbering/',\n recommended: true,\n },\n messages: {\n duplicateNumber: 'ADR number {{number}} is used by multiple files: {{paths}}',\n },\n suggestions: {\n duplicateNumber:\n 'renumber all but one of these files so each ADR has a unique NNNN- prefix',\n },\n defaultOptions: {},\n schema,\n },\n check(context) {\n const byNumber = new Map<string, string[]>();\n for (const file of context.files) {\n const match = NUMBER_REGEX.exec(basename(file.path));\n if (!match) continue;\n const number = match[1];\n if (number === undefined) continue;\n const group = byNumber.get(number) ?? [];\n group.push(file.path);\n byNumber.set(number, group);\n }\n\n for (const [number, paths] of byNumber) {\n if (paths.length > 1) {\n const pathList = paths.join(', ');\n for (const path of paths) {\n context.report({\n messageId: 'duplicateNumber',\n path,\n data: { number, paths: pathList },\n });\n }\n }\n }\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {}\n}\n","import { basename } from 'node:path';\nimport type { ProjectRule } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface NoNumberingGapOptions extends Record<string, unknown> {\n // No options at v0.1.\n}\n\nconst NUMBER_REGEX = /^(\\d{4})-/;\n\nfunction format4(n: number): string {\n return n.toString().padStart(4, '0');\n}\n\nconst rule: ProjectRule<NoNumberingGapOptions> = {\n meta: {\n name: 'madr/no-numbering-gap',\n type: 'project',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description:\n 'Detect gaps in ADR numbering (e.g. 0001 and 0003 exist but 0002 is missing). Convention-only; default off.',\n url: 'https://knktkc.github.io/madr-lint/rules/no-numbering-gap/',\n recommended: false,\n },\n messages: {\n numberingGap:\n 'Numbering gap: missing {{missing}} between ADR-{{from}} and ADR-{{to}}',\n },\n // No `suggestions`: a gap can be intentional (a rejected or deleted ADR),\n // and closing it may mean either adding the missing ADR OR renumbering —\n // the right remediation is contextual, so we omit a suggestion. See #67.\n defaultOptions: {},\n schema,\n },\n check(context) {\n // Map ADR number → file path. Files without a NNNN- prefix are ignored.\n const numberToPath = new Map<number, string>();\n for (const file of context.files) {\n const match = NUMBER_REGEX.exec(basename(file.path));\n if (!match?.[1]) continue;\n const n = Number.parseInt(match[1], 10);\n if (!Number.isNaN(n)) numberToPath.set(n, file.path);\n }\n if (numberToPath.size < 2) return;\n\n const sortedNumbers = Array.from(numberToPath.keys()).toSorted(\n (a, b) => a - b,\n );\n\n for (let i = 1; i < sortedNumbers.length; i++) {\n const prev = sortedNumbers[i - 1];\n const curr = sortedNumbers[i];\n if (prev === undefined || curr === undefined) continue;\n if (curr === prev + 1) continue;\n\n const missingNumbers: string[] = [];\n for (let n = prev + 1; n < curr; n++) {\n missingNumbers.push(format4(n));\n }\n\n const currPath = numberToPath.get(curr);\n if (!currPath) continue;\n\n context.report({\n messageId: 'numberingGap',\n path: currPath,\n data: {\n from: format4(prev),\n to: format4(curr),\n missing: missingNumbers.join(', '),\n },\n });\n }\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {}\n}\n","import { toString } from 'mdast-util-to-string';\nimport type { Rule, RuleListeners } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface RequiredSectionsOptions extends Record<string, unknown> {\n sections: string[];\n matchMode: 'exact' | 'startsWith';\n}\n\nconst rule: Rule<RequiredSectionsOptions> = {\n meta: {\n name: 'madr/required-sections',\n type: 'perFile',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'Enforce that ADRs contain required heading sections',\n url: 'https://knktkc.github.io/madr-lint/rules/required-sections/',\n recommended: true,\n },\n messages: {\n missingSection: 'Missing required section: \"{{section}}\"',\n },\n suggestions: {\n missingSection:\n 'add a \"## {{section}}\" heading to the document body',\n },\n defaultOptions: {\n sections: [\n 'Context and Problem Statement',\n 'Decision Outcome',\n 'Consequences',\n ],\n matchMode: 'exact',\n },\n schema,\n },\n create(context): RuleListeners {\n const seen: string[] = [];\n return {\n enter: {\n heading(node) {\n if (node.type !== 'heading') return;\n seen.push(toString(node).trim());\n },\n },\n exit: {\n root() {\n const { sections, matchMode } = context.options;\n for (const section of sections) {\n const matched = seen.some((heading) =>\n matchMode === 'startsWith'\n ? heading.startsWith(section)\n : heading === section,\n );\n if (!matched) {\n context.report({\n messageId: 'missingSection',\n data: { section, found: [...seen] },\n });\n }\n }\n },\n },\n };\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"sections\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"Required heading texts. Order does not matter.\"\n },\n \"matchMode\": {\n \"type\": \"string\",\n \"enum\": [\"exact\", \"startsWith\"],\n \"description\": \"Comparison mode against heading text.\"\n }\n }\n}\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"Exact-match allowed status values.\"\n },\n \"prefixValues\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"startsWith-match allowed prefixes (e.g. 'superseded by').\"\n },\n \"caseSensitive\": {\n \"type\": \"boolean\",\n \"description\": \"When false, comparisons are case-insensitive.\"\n }\n }\n}\n","import type { Fixer, Rule, TextEdit } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface StatusEnumOptions extends Record<string, unknown> {\n values: string[];\n prefixValues: string[];\n caseSensitive: boolean;\n}\n\n// A tiny, curated misspelling → canonical-spelling map, applied token-by-token.\n// Deliberately minimal and length-agnostic; a correction only lands if the\n// repaired string matches exactly ONE configured value/prefix (below), so this\n// table can never introduce an out-of-enum value. See #29.\nconst TYPO_CORRECTIONS: Record<string, string> = {\n superceded: 'superseded',\n supercedes: 'supersedes',\n depricated: 'deprecated',\n};\n\n/** Repair known misspelled tokens in a lowercased status string. */\nfunction repairTypos(lower: string): string {\n return lower\n .split(' ')\n .map((tok) => TYPO_CORRECTIONS[tok] ?? tok)\n .join(' ');\n}\n\n/** The substring of `s` after skipping its first `n` whitespace-delimited words. */\nfunction tailAfterWords(s: string, n: number): string {\n let i = 0;\n for (let w = 0; w < n; w++) {\n while (i < s.length && /\\s/.test(s[i]!)) i++; // skip separators\n while (i < s.length && !/\\s/.test(s[i]!)) i++; // skip the word\n }\n return s.slice(i);\n}\n\n/**\n * The unambiguous canonical replacement for an invalid `status`, or undefined\n * when no safe one-to-one correction exists. Corrections are validated against\n * the CONFIGURED enum, so autofix never guesses:\n * 1. exact value — case-fold (+ token typo repair) onto exactly one `values`;\n * 2. prefix value — repaired string starts with exactly one `prefixValues`\n * entry; the canonical prefix is spliced in and the original tail kept.\n * A mapping that lands on two candidates (case collision, two prefixes) declines.\n */\nfunction correctStatus(\n status: string,\n values: string[],\n prefixValues: string[],\n): string | undefined {\n const repaired = repairTypos(status.toLowerCase());\n\n // 1. Exact value.\n const exact = [...new Set(values.filter((v) => v.toLowerCase() === repaired))];\n if (exact.length === 1) {\n return exact[0] !== status ? exact[0] : undefined;\n }\n if (exact.length > 1) return undefined; // ambiguous\n\n // 2. Prefix value (canonical prefix + verbatim tail).\n const prefixes = [\n ...new Set(\n prefixValues.filter((p) => {\n const pl = p.toLowerCase();\n return repaired === pl || repaired.startsWith(`${pl} `);\n }),\n ),\n ];\n if (prefixes.length !== 1) return undefined;\n const prefix = prefixes[0]!;\n const tail = tailAfterWords(status, prefix.split(' ').length);\n const replacement = `${prefix}${tail}`;\n return replacement !== status ? replacement : undefined;\n}\n\nconst rule: Rule<StatusEnumOptions> = {\n meta: {\n name: 'madr/status-enum',\n type: 'perFile',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'Validate that ADR `status` (frontmatter or v2 bold-list) is in the allowed enum',\n url: 'https://knktkc.github.io/madr-lint/rules/status-enum/',\n recommended: true,\n },\n messages: {\n missingStatus: 'Metadata does not contain a \"status\" field (checked frontmatter and v2 bold-list)',\n invalidStatus: 'Status \"{{status}}\" is not one of: {{allowed}}',\n },\n // No suggestion for invalidStatus: the message already lists the allowed\n // values (the expected-vs-actual is self-contained), so a suggestion would\n // only restate it. See issue #67.\n suggestions: {\n missingStatus:\n 'add a \"status\" field to the frontmatter (for MADR v2, a \"* Status: ...\" list item) — allowed values: {{allowed}}',\n },\n // Autofix (#28/#29): normalize a v2 list-sourced value to the unique\n // canonical enum entry — case difference, a curated misspelling, or a prefix\n // case/typo — preserving any tail (see create()). Ambiguous or\n // frontmatter-sourced values get no fix.\n fixable: 'code',\n defaultOptions: {\n values: ['proposed', 'rejected', 'accepted', 'deprecated'],\n prefixValues: ['superseded by'],\n caseSensitive: false,\n },\n schema,\n },\n create(context) {\n const { values, prefixValues, caseSensitive } = context.options;\n // The allowed-value list is shared by both the invalidStatus message and\n // the missingStatus suggestion, so build it once up front.\n const allowed = [...values, ...prefixValues.map((p) => `${p} ...`)];\n\n const meta = context.metadata;\n if (!meta || typeof meta.status !== 'string') {\n context.report({ messageId: 'missingStatus', data: { allowed } });\n return;\n }\n\n const status = meta.status;\n const compareStatus = caseSensitive ? status : status.toLowerCase();\n const compareValues = caseSensitive\n ? values\n : values.map((v) => v.toLowerCase());\n const comparePrefixes = caseSensitive\n ? prefixValues\n : prefixValues.map((p) => p.toLowerCase());\n\n const exactMatch = compareValues.includes(compareStatus);\n const prefixMatch = comparePrefixes.some((p) =>\n compareStatus.startsWith(p),\n );\n\n if (!exactMatch && !prefixMatch) {\n // loc only exists for v2 list-sourced values: frontmatter is stripped\n // before mdast parsing, so a frontmatter-sourced status has no body\n // line to point at — inline suppression directives (which live in the\n // body) can only silence it file-wide, never per line.\n const loc = context.metadataLoc?.status;\n // Autofix (#28/#29): offer a fix ONLY for a v2 list-sourced value (we then\n // have its exact body offset range) that maps unambiguously onto the\n // CONFIGURED enum. Frontmatter-sourced values have no body offset and need\n // YAML-aware rewriting — out of scope, so no fix is attached there.\n const canonical = correctStatus(status, values, prefixValues);\n const valueRange = context.metadataValueLoc?.status;\n const fix =\n canonical !== undefined && canonical !== status && valueRange\n ? (fixer: Fixer): TextEdit =>\n fixer.replaceRange([valueRange.start, valueRange.end], canonical)\n : undefined;\n context.report({\n messageId: 'invalidStatus',\n ...(loc ? { loc } : {}),\n data: { status, allowed },\n ...(fix ? { fix } : {}),\n });\n }\n },\n};\n\nexport default rule;\n","import { basename } from 'node:path';\nimport { frontmatterOffset } from '../../core/parser.js';\nimport type { Fixer, ProjectFile, ProjectRule, TextEdit } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface SupersedesBidirectionalOptions extends Record<string, unknown> {\n // No options at v0.1.\n}\n\nconst ADR_NUMBER_REGEX = /^(\\d{4})-/;\nconst FORWARD_FIELD = 'supersedes';\nconst BACKWARD_FIELD = 'superseded-by';\n\nfunction extractAdrNumber(path: string): string | null {\n const match = ADR_NUMBER_REGEX.exec(basename(path));\n return match?.[1] ?? null;\n}\n\nfunction normalizeRefs(value: unknown): string[] {\n if (typeof value === 'string') return [value];\n if (Array.isArray(value)) {\n return value.filter((v): v is string => typeof v === 'string');\n }\n return [];\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Compute a whole-file insertion of `key: value` immediately before the closing\n * `---` of the target's EXISTING YAML frontmatter, or null when the fix is not\n * mechanically safe. Frontmatter is treated as opaque lines — NO YAML parsing or\n * serialization (which would reflow the block). Guard rails:\n * - no frontmatter block (v2 list-sourced or bare body) → null;\n * - `key` already present as a top-level frontmatter key → null (rewriting a\n * wrong value, or appending to an existing key, is out of scope). Matched\n * CASE-INSENSITIVELY: inserting `superseded-by:` next to an existing\n * `Superseded-By:` would be valid YAML but reads as contradictory\n * duplicates to a human;\n * - the byte slice at the computed offset must be the closing fence.\n * The file's newline style (LF vs CRLF) is preserved. See #29 / ADR-0008.\n */\nfunction frontmatterInsertEdit(\n content: string,\n key: string,\n value: string,\n): { offset: number; text: string } | null {\n const fmEnd = frontmatterOffset(content);\n if (fmEnd === 0) return null; // no frontmatter block\n\n const fmBlock = content.slice(0, fmEnd);\n // The closing fence is the final `---` line of the block (its own newline is\n // part of fmBlock, so anchor at end). Capture the fence line to derive EOL.\n const closeMatch = /\\r?\\n(---[ \\t]*\\r?\\n)$/.exec(fmBlock);\n if (!closeMatch) return null;\n const fenceLine = closeMatch[1]!;\n const offset = closeMatch.index + (closeMatch[0].length - fenceLine.length);\n\n // Slice-verification: the computed offset must sit exactly on the `---` fence.\n if (content.slice(offset, offset + 3) !== '---') return null;\n\n // The key must not already exist as a top-level frontmatter key (any case\n // variant counts — see the guard-rail note above). `content` up to the fence\n // is `---<eol>...keys...`; the opening fence cannot match a `key:` line, so\n // scanning the whole prefix is safe.\n const keyPattern = new RegExp(`(^|\\\\r?\\\\n)${escapeRegExp(key)}[ \\\\t]*:`, 'i');\n if (keyPattern.test(content.slice(0, offset))) return null;\n\n const eol = fenceLine.endsWith('\\r\\n') ? '\\r\\n' : '\\n';\n return { offset, text: `${key}: ${value}${eol}` };\n}\n\n/**\n * A fix that inserts `key: value` into `target`'s frontmatter, or undefined when\n * no mechanically-safe insertion exists. The guard rails are evaluated EAGERLY\n * (at report time) so the diagnostic's durable `fixable` flag is accurate — a\n * thunk that merely returns null at apply time would still mark the diagnostic\n * fixable. The cross-file fixer works in WHOLE-FILE coordinates (project fixes\n * touch frontmatter, which body coordinates strip), so the offset is used as-is.\n */\nfunction backReferenceFix(\n target: ProjectFile,\n key: string,\n value: string,\n): ((fixer: Fixer) => TextEdit) | undefined {\n const edit = frontmatterInsertEdit(target.content, key, value);\n if (!edit) return undefined;\n return (fixer) => fixer.insertAt(edit.offset, edit.text);\n}\n\nconst rule: ProjectRule<SupersedesBidirectionalOptions> = {\n meta: {\n name: 'madr/supersedes-bidirectional',\n type: 'project',\n versionCompat: ['v3', 'v4'],\n docs: {\n description:\n 'Frontmatter `supersedes` and `superseded-by` must reference each other bidirectionally',\n url: 'https://knktkc.github.io/madr-lint/rules/supersedes-bidirectional/',\n recommended: true,\n },\n messages: {\n unknownReference:\n 'Frontmatter `{{direction}}: {{ref}}` references an ADR that does not exist',\n // `declared` is the field the SOURCE file actually declares;\n // `direction` is the field THIS file (the diagnostic's path) must add.\n // They are opposites — rendering `direction` as the declared field\n // would mislabel the source's frontmatter in both directions.\n missingBackReference:\n '`{{source}}` declares `{{declared}}: {{ref}}`, but `{{ref}}` (this file) does not back-reference it via `{{direction}}: {{expected}}`',\n },\n suggestions: {\n unknownReference:\n 'correct \"{{ref}}\" to an existing ADR number, or remove \"{{direction}}: {{ref}}\" from the frontmatter',\n missingBackReference:\n 'add \"{{direction}}: {{expected}}\" to the frontmatter of this file',\n },\n // Cross-file autofix (#29): `missingBackReference` inserts the reciprocal\n // `<direction>: <expected>` line into the target's EXISTING frontmatter.\n // Targets without frontmatter, or where the key already exists, decline;\n // `unknownReference` is contextual and never fixable.\n fixable: 'code',\n defaultOptions: {},\n schema,\n },\n check(context) {\n // Build ADR-NNNN → file index. Files without a NNNN- prefix are\n // not addressable via `supersedes`/`superseded-by` and thus skipped.\n const adrIndex = new Map<string, ProjectFile>();\n for (const file of context.files) {\n const num = extractAdrNumber(file.path);\n if (num) adrIndex.set(`ADR-${num}`, file);\n }\n\n // Walk every file with a frontmatter and check both directions.\n for (const file of context.files) {\n const fm = file.frontmatter;\n if (!fm) continue;\n const fileNumber = extractAdrNumber(file.path);\n if (!fileNumber) continue;\n const fileRef = `ADR-${fileNumber}`;\n\n const forwardRefs = normalizeRefs(fm[FORWARD_FIELD]);\n const backwardRefs = normalizeRefs(fm[BACKWARD_FIELD]);\n\n // Forward direction: this file `supersedes` something — verify\n // the target back-references this file via `superseded-by`.\n for (const ref of forwardRefs) {\n const target = adrIndex.get(ref);\n if (!target) {\n context.report({\n messageId: 'unknownReference',\n path: file.path,\n data: { ref, direction: FORWARD_FIELD },\n });\n continue;\n }\n const targetBack = normalizeRefs(target.frontmatter?.[BACKWARD_FIELD]);\n if (!targetBack.includes(fileRef)) {\n const fix = backReferenceFix(target, BACKWARD_FIELD, fileRef);\n context.report({\n messageId: 'missingBackReference',\n path: target.path,\n data: {\n ref,\n source: file.path,\n declared: FORWARD_FIELD,\n direction: BACKWARD_FIELD,\n expected: fileRef,\n },\n ...(fix ? { fix } : {}),\n });\n }\n }\n\n // Backward direction: this file is `superseded-by` something —\n // verify the target forward-references this file via `supersedes`.\n for (const ref of backwardRefs) {\n const target = adrIndex.get(ref);\n if (!target) {\n context.report({\n messageId: 'unknownReference',\n path: file.path,\n data: { ref, direction: BACKWARD_FIELD },\n });\n continue;\n }\n const targetFwd = normalizeRefs(target.frontmatter?.[FORWARD_FIELD]);\n if (!targetFwd.includes(fileRef)) {\n const fix = backReferenceFix(target, FORWARD_FIELD, fileRef);\n context.report({\n messageId: 'missingBackReference',\n path: target.path,\n data: {\n ref,\n source: file.path,\n declared: BACKWARD_FIELD,\n direction: FORWARD_FIELD,\n expected: fileRef,\n },\n ...(fix ? { fix } : {}),\n });\n }\n }\n }\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {}\n}\n","import { basename } from 'node:path';\nimport picomatch from 'picomatch';\n\n/**\n * Should the given relative path be excluded from linting?\n *\n * Patterns are tested in three forms (any match wins):\n * - **Full glob via picomatch** against the relative POSIX path:\n * `docs/**\\/draft-*.md`, `**\\/template.md`, `9999-*.md`\n * - Full glob via picomatch against the file's basename, so patterns\n * like `README.md` or `9999-*` work without a `**\\/` prefix.\n * - Exact relative-path match (defensive — picomatch with default\n * options also handles this, but we keep the explicit branch for\n * clarity and to avoid surprises if picomatch ever changes default\n * `dot`/`bash` semantics).\n *\n * Picomatch is invoked with `{ dot: true }` so dotfiles (rare for ADRs\n * but possible — e.g. `.archived/*.md`) are matched naturally.\n */\nexport function shouldIgnore(\n relativePath: string,\n patterns: readonly string[],\n): boolean {\n if (patterns.length === 0) return false;\n const base = basename(relativePath);\n for (const pattern of patterns) {\n if (matchesPattern(relativePath, base, pattern)) return true;\n }\n return false;\n}\n\nfunction matchesPattern(\n relativePath: string,\n base: string,\n pattern: string,\n): boolean {\n if (relativePath === pattern) return true;\n const matcher = picomatch(pattern, { dot: true });\n if (matcher(relativePath)) return true;\n if (matcher(base)) return true;\n // Back-compat shortcut: a pattern like `adr/template.md` (sub-path\n // without leading `**/`) should match `docs/adr/template.md`. Also\n // handles `adr/9999-*` against `docs/adr/9999-x.md` via picomatch\n // applied to the suffix.\n if (pattern.includes('/') && !pattern.startsWith('**/')) {\n const suffixMatcher = picomatch(`**/${pattern}`, { dot: true });\n if (suffixMatcher(relativePath)) return true;\n }\n return false;\n}\n","import pc from 'picocolors';\nimport { interpolate } from './interpolate.js';\nimport type { AnyRule, Diagnostic } from './types.js';\n\n/**\n * Run-level facts a reporter cannot derive from the diagnostics themselves.\n * Optional third argument so existing two-arg callers keep working.\n */\nexport interface ReporterMeta {\n /** Diagnostics absorbed by .madr-lint/baseline.json. Absent ⇒ 0. */\n baselineHidden?: number;\n /**\n * How many fixes were applied this run (autofix, #28). Present only when a\n * fix pass ran (`--fix` / `--fix-dry-run`); absent on a plain lint. json\n * surfaces it as `summary.fixed`.\n */\n fixed?: number;\n /**\n * Per-file unified diffs of a dry run (`--fix-dry-run`). Present only then.\n * json embeds them as a top-level `diffs` array so machine consumers get the\n * dry run's output in-band — the raw diff must never pollute a structured\n * format's stdout. Other reporters ignore this (the CLI prints/routes the\n * raw diff itself for text / sarif / github).\n */\n fixDiffs?: readonly { path: string; diff: string }[];\n}\n\nexport interface Reporter {\n format(\n diagnostics: readonly Diagnostic[],\n rulesByName: Map<string, AnyRule>,\n meta?: ReporterMeta,\n ): string;\n}\n\nexport type ReporterFormat = 'text' | 'json' | 'sarif' | 'github';\n\n/**\n * Human-readable reporter for terminal output. Groups diagnostics by file,\n * interpolates {{placeholders}} from `data` against the rule's message\n * template, and ends with a count summary.\n *\n * Coloring uses picocolors which honors NO_COLOR / FORCE_COLOR env vars.\n */\nexport const textReporter: Reporter = {\n // Deliberately 2-param (meta unused): the CLI prints baselineHiddenSummary()\n // itself for text format, so the footer can outlive a suppressed banner.\n format(diagnostics, rulesByName) {\n if (diagnostics.length === 0) {\n return pc.green('✓ All clear.');\n }\n\n const byFile = groupByFile(diagnostics);\n const lines: string[] = [];\n\n // Aligns a suggestion / docs line under the message column:\n // severity gutter (9) + padded ruleId (28) + the 2-space separator.\n const gutter = ' '.repeat(9 + 28 + 2);\n\n for (const [file, fileDiagnostics] of byFile) {\n lines.push(pc.underline(file));\n // ruleName -> docs URL, first-seen order, one entry per rule in the group.\n const docsByRule = new Map<string, string>();\n for (const d of fileDiagnostics) {\n const sev =\n d.severity === 'error'\n ? pc.red(' error ')\n : pc.yellow(' warn ');\n const ruleId = pc.dim(d.ruleName.padEnd(28));\n const message = renderMessage(d, rulesByName);\n // Autofix (#28): a dim marker tells the reader `--fix` can repair this.\n const fixTag = d.fixable ? pc.dim(' 🔧 fixable') : '';\n lines.push(`${sev}${ruleId} ${message}${fixTag}`);\n if (d.suggestion) {\n lines.push(`${gutter}${pc.cyan(`→ ${d.suggestion}`)}`);\n }\n if (d.docsUrl && !docsByRule.has(d.ruleName)) {\n docsByRule.set(d.ruleName, d.docsUrl);\n }\n }\n // Compact footer: the docs URL once per rule per file group, never per\n // diagnostic — self-contained without repeating the link. See issue #67.\n for (const [ruleName, url] of docsByRule) {\n lines.push(pc.dim(` ${ruleName} ${url}`));\n }\n lines.push('');\n }\n\n const errors = diagnostics.filter((d) => d.severity === 'error').length;\n const warnings = diagnostics.filter((d) => d.severity === 'warn').length;\n lines.push(pc.bold(formatSummary(errors, warnings)));\n\n return lines.join('\\n');\n },\n};\n\nfunction groupByFile(diagnostics: readonly Diagnostic[]): Map<string, Diagnostic[]> {\n const map = new Map<string, Diagnostic[]>();\n for (const d of diagnostics) {\n if (!map.has(d.path)) map.set(d.path, []);\n map.get(d.path)?.push(d);\n }\n return map;\n}\n\nfunction renderMessage(\n d: Diagnostic,\n rulesByName: Map<string, AnyRule>,\n): string {\n const rule = rulesByName.get(d.ruleName);\n const template = rule?.meta.messages[d.messageId] ?? d.messageId;\n return interpolate(template, d.data ?? {});\n}\n\nfunction formatSummary(errors: number, warnings: number): string {\n const parts: string[] = [];\n if (errors > 0) parts.push(`${errors} ${errors === 1 ? 'error' : 'errors'}`);\n if (warnings > 0) parts.push(`${warnings} ${warnings === 1 ? 'warning' : 'warnings'}`);\n return parts.join(', ');\n}\n\n/**\n * Machine-readable JSON reporter. Stable shape — additive changes only,\n * tagged via `version`. Tools can `JSON.parse` stdout and consume `results`\n * directly without scraping the human-readable formatter.\n */\nexport const jsonReporter: Reporter = {\n format(diagnostics, rulesByName, meta) {\n const errors = diagnostics.filter((d) => d.severity === 'error').length;\n const warnings = diagnostics.filter((d) => d.severity === 'warn').length;\n const payload = {\n version: 1,\n summary: {\n total: diagnostics.length,\n errors,\n warnings,\n // Always present (0 when no baseline) so CI consumers can detect an\n // active baseline without probing for the key. SARIF stays untouched.\n baselineHidden: meta?.baselineHidden ?? 0,\n // `fixed` appears ONLY when a fix pass ran (#28) — a plain lint keeps\n // the summary shape it always had.\n ...(meta?.fixed !== undefined ? { fixed: meta.fixed } : {}),\n },\n // Dry-run diffs (#28): embedded so `--fix-dry-run --format json` keeps\n // stdout pure JSON while still delivering the diff. Absent otherwise.\n ...(meta?.fixDiffs !== undefined ? { diffs: meta.fixDiffs } : {}),\n results: diagnostics.map((d) => ({\n path: d.path,\n ruleName: d.ruleName,\n messageId: d.messageId,\n severity: d.severity,\n message: renderMessage(d, rulesByName),\n // Self-contained diagnostics (#67): always present so consumers can\n // rely on the keys — `suggestion` is null when the rule declares none.\n suggestion: d.suggestion,\n docsUrl: d.docsUrl,\n // Autofix (#28): whether `--fix` can repair this diagnostic.\n fixable: d.fixable,\n data: d.data ?? {},\n })),\n };\n return JSON.stringify(payload, null, 2);\n },\n};\n\n/**\n * SARIF 2.1.0 reporter. Emits a minimal-but-valid Static Analysis Results\n * Interchange Format document so GitHub Code Scanning, VS Code SARIF\n * Viewer, and other consumers can ingest madr-lint results.\n *\n * Spec: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html\n */\nexport const sarifReporter: Reporter = {\n format(diagnostics, rulesByName) {\n const ruleNames = [...new Set(diagnostics.map((d) => d.ruleName))];\n const ruleIndex = new Map(ruleNames.map((name, i) => [name, i]));\n\n const sarifRules = ruleNames.map((name) => {\n const rule = rulesByName.get(name);\n return {\n id: name,\n name: name.replace(/[^A-Za-z0-9]/g, '_'),\n shortDescription: { text: rule?.meta.docs.description ?? name },\n helpUri: rule?.meta.docs.url,\n };\n });\n\n const sarifResults = diagnostics.map((d) => ({\n ruleId: d.ruleName,\n ruleIndex: ruleIndex.get(d.ruleName) ?? 0,\n level: d.severity === 'error' ? 'error' : 'warning',\n message: { text: renderMessage(d, rulesByName) },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: d.path, uriBaseId: '%SRCROOT%' },\n region: { startLine: 1 },\n },\n },\n ],\n }));\n\n const sarif = {\n $schema:\n 'https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n tool: {\n driver: {\n name: 'madr-lint',\n informationUri: 'https://github.com/knktkc/madr-lint',\n rules: sarifRules,\n },\n },\n results: sarifResults,\n },\n ],\n };\n return JSON.stringify(sarif, null, 2);\n },\n};\n\n/** Escape a workflow-command message value (data after `::`). */\nfunction escapeGhMessage(s: string): string {\n return s.replace(/%/g, '%25').replace(/\\r/g, '%0D').replace(/\\n/g, '%0A');\n}\n\n/** Escape a workflow-command property value (e.g. file=, title=). */\nfunction escapeGhProperty(s: string): string {\n return escapeGhMessage(s)\n .replace(/,/g, '%2C')\n .replace(/:/g, '%3A');\n}\n\n/**\n * GitHub Actions annotation reporter. Emits one `::error` / `::warning`\n * workflow command per diagnostic so GitHub renders them as PR diff annotations,\n * then a one-line human summary on stdout.\n *\n * Escape rules from https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions\n */\nexport const githubReporter: Reporter = {\n format(diagnostics, rulesByName, meta) {\n const lines: string[] = [];\n\n for (const d of diagnostics) {\n const level = d.severity === 'error' ? 'error' : 'warning';\n const file = escapeGhProperty(d.path);\n const title = escapeGhProperty(d.ruleName);\n // Self-contained diagnostics (#67): fold the suggestion into the single\n // annotation message (annotations carry no separate remediation field),\n // escaping the combined string once. Properties are unchanged.\n const rendered = renderMessage(d, rulesByName);\n const message = escapeGhMessage(\n d.suggestion ? `${rendered} — ${d.suggestion}` : rendered,\n );\n\n const linePart = d.loc ? `,line=${d.loc.line}` : '';\n lines.push(`::${level} file=${file}${linePart},title=${title}::${message}`);\n }\n\n const errors = diagnostics.filter((d) => d.severity === 'error').length;\n const warnings = diagnostics.filter((d) => d.severity === 'warn').length;\n const summaryParts: string[] = [];\n if (errors > 0) summaryParts.push(`${errors} ${errors === 1 ? 'error' : 'errors'}`);\n if (warnings > 0) summaryParts.push(`${warnings} ${warnings === 1 ? 'warning' : 'warnings'}`);\n // A clean run still prints counts — a silent log line reads as a broken CI step.\n if (summaryParts.length === 0) summaryParts.push('0 errors, 0 warnings');\n const baselineHidden = meta?.baselineHidden ?? 0;\n if (baselineHidden > 0) summaryParts.push(`${baselineHidden} hidden by baseline`);\n lines.push(summaryParts.join(', '));\n\n return lines.join('\\n');\n },\n};\n\nexport const reporters: Record<ReporterFormat, Reporter> = {\n text: textReporter,\n json: jsonReporter,\n sarif: sarifReporter,\n github: githubReporter,\n};\n"],"mappings":";;;;;;;;AAAA,SAAS,cAAAA,aAAY,gBAAAC,eAAc,iBAAAC,sBAAqB;AACxD,SAAS,YAAAC,WAAU,WAAAC,UAAS,OAAAC,YAAW;AACvC,SAAS,iBAAAC,gBAAe,eAAe;;;ACFvC,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,WAAAC,gBAAe;AACxB,SAAS,qBAAqB;;;ACF9B,SAAS,YAAY,oBAAoB;AACzC,SAAS,eAAe;AACxB,SAAS,kBAAkB;;;ACSpB,IAAM,cAA4C;AAAA,EACvD,qBAAqB;AAAA,EACrB,wBAAwB;AAAA,EACxB,wBAAwB;AAAA,EACxB,+BAA+B;AAAA;AAAA;AAAA,EAG/B,yBAAyB;AAAA,EACzB,0BAA0B;AAAA,EAC1B,oBAAoB;AAAA,EACpB,iCAAiC;AACnC;;;ADOO,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EACjD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAOO,SAAS,mBAAmB,UAAkC;AAKnE,MAAI;AACJ,MAAI;AACF,UAAM,aAAa,UAAU,MAAM;AAAA,EACrC,SAAS,KAAK;AACZ,UAAM,OAAQ,IAA8B;AAC5C,QAAI,SAAS,UAAU;AACrB,YAAM,IAAI,wBAAwB,0BAA0B,QAAQ,EAAE;AAAA,IACxE;AAGA,QAAI,SAAS,UAAU;AACrB,YAAM,IAAI;AAAA,QACR,2CAA2C,QAAQ;AAAA,MACrD;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACA,MAAI,SAAS,SAAS,OAAO,GAAG;AAC9B,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,eAAe,MAAM;AAAA,EAC9B;AACA,QAAM,OAAO,WAAW,YAAY,KAAK,EAAE,gBAAgB,KAAK,CAAC;AACjE,QAAM,SAAS,KAAK,QAAQ;AAC5B,QAAM,SACJ,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,SACzD,OAAO,UACP;AACP,SAAO,eAAe,MAAM;AAC9B;AAUO,SAAS,WAAW,KAA6B;AACtD,aAAW,QAAQ,cAAc;AAC/B,UAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,QAAI,CAAC,WAAW,IAAI,EAAG;AAEvB,QAAI,KAAK,SAAS,OAAO,GAAG;AAC1B,YAAM,SAAS,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AACpD,aAAO,eAAe,MAAM;AAAA,IAC9B;AAEA,UAAM,OAAO,WAAW,YAAY,KAAK,EAAE,gBAAgB,KAAK,CAAC;AACjE,UAAM,SAAS,KAAK,IAAI;AACxB,UAAM,SACJ,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,SACzD,OAAO,UACP;AACP,WAAO,eAAe,MAAM;AAAA,EAC9B;AACA,SAAO,eAAe,CAAC,CAAC;AAC1B;AAMO,SAAS,eAAe,QAAwC;AACrE,QAAM,YAA0C,OAAO,SAAS;AAAA,IAC9D;AAAA,EACF,IACI,EAAE,GAAG,YAAY,IACjB,CAAC;AAEL,SAAO;AAAA,IACL,QAAQ,OAAO,UAAU;AAAA,IACzB,OAAO,EAAE,GAAG,WAAW,GAAG,OAAO,MAAM;AAAA,IACvC,gBAAgB,OAAO,kBAAkB,CAAC;AAAA,IAC1C,aAAa,OAAO,eAAe;AAAA,IACnC,OAAO,OAAO,SAAS;AAAA,IACvB,eAAe,OAAO,iBAAiB;AAAA,EACzC;AACF;;;AE1IA,SAAS,cAAAC,aAAY,aAAa,gBAAgB;AAClD,SAAS,YAAY;AAad,SAAS,aAAa,QAA0B;AACrD,MAAI,CAACA,YAAW,MAAM,EAAG,QAAO,CAAC;AAEjC,QAAM,OAAO,SAAS,MAAM;AAC5B,MAAI,KAAK,OAAO,GAAG;AACjB,WAAO,OAAO,SAAS,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC;AAAA,EAC9C;AACA,MAAI,CAAC,KAAK,YAAY,EAAG,QAAO,CAAC;AAEjC,QAAM,UAAU,YAAY,QAAQ,EAAE,WAAW,MAAM,eAAe,KAAK,CAAC;AAC5E,SAAO,QACJ,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE,KAAK,SAAS,KAAK,CAAC,EAClD,IAAI,CAAC,MAAM,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,EACrC,SAAS;AACd;;;AC5BA,SAAS,cAAAC,aAAY,gBAAAC,eAAc,eAAAC,oBAAgC;AACnE,SAAS,QAAAC,OAAM,WAAAC,gBAAe;;;ACA9B,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AACzB,OAAO,gBAAgB;;;ACMhB,SAAS,eAAqC;AACnD,SAAO,uBAAO,OAAO,IAAI;AAC3B;;;ADiDA,IAAM,sBACJ,OAAO,OAAO,CAAC,CAAC;AAUX,SAAS,kBAAkB,SAAyB;AACzD,SAAO,QAAQ,SAAS,WAAW,SAAS,mBAAmB,EAAE,QAAQ;AAC3E;AASO,SAAS,UAAU,SAA6B;AACrD,QAAM,SAAS,WAAW,SAAS,mBAAmB;AACtD,QAAM,OAAO,OAAO;AACpB,QAAM,cAAc,OAAO,KAAK,IAAI,EAAE,SAAS,IAAI,OAAO;AAC1D,QAAM,MAAM,aAAa,OAAO,OAAO;AACvC,QAAM,aAAa,2BAA2B,KAAK,OAAO,OAAO;AACjE,QAAM,eAAe,YAAY,UAAU;AAE3C,MAAI,WAA2C;AAC/C,MAAI,cACF,cAAc,OAAO,KAAK,WAAW,GAAG,EAAE,SAAS,IAC/C,EAAE,GAAG,WAAW,IAAI,IACpB;AACN,MAAI,mBACF,cAAc,OAAO,KAAK,WAAW,YAAY,EAAE,SAAS,IACxD,EAAE,GAAG,WAAW,aAAa,IAC7B;AACN,MAAI,eAAe,cAAc;AAO/B,UAAM,qBAAqB,aAAsB;AACjD,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,WAAW,GAAG;AAChD,UAAI,MAAM,QAAQ,MAAM,OAAW,oBAAmB,CAAC,IAAI;AAAA,IAC7D;AACA,eAAW,EAAE,GAAG,cAAc,GAAG,mBAAmB;AAGpD,QAAI,aAAa;AACf,iBAAW,KAAK,OAAO,KAAK,kBAAkB,EAAG,QAAO,YAAY,CAAC;AACrE,UAAI,OAAO,KAAK,WAAW,EAAE,WAAW,EAAG,eAAc;AAAA,IAC3D;AACA,QAAI,kBAAkB;AACpB,iBAAW,KAAK,OAAO,KAAK,kBAAkB,EAAG,QAAO,iBAAiB,CAAC;AAC1E,UAAI,OAAO,KAAK,gBAAgB,EAAE,WAAW,EAAG,oBAAmB;AAAA,IACrE;AAAA,EACF,WAAW,aAAa;AACtB,eAAW;AAAA,EACb,WAAW,cAAc;AACvB,eAAW;AAAA,EACb;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,OAAO;AAAA,EACf;AACF;AAUA,IAAM,2BAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAmDD,SAAS,cAAc,OAAwB;AAC7C,MAAI,OAAO,MAAM,KAAK;AACtB,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,SAAO,KAAK,SAAS,GAAG;AACtB,QAAI,CAAC,KAAK,WAAW,MAAM,EAAG,QAAO;AAGrC,UAAM,MAAM,KAAK,QAAQ,OAAO,CAAC;AACjC,QAAI,QAAQ,GAAI,QAAO;AACvB,WAAO,KAAK,MAAM,MAAM,CAAC,EAAE,KAAK;AAAA,EAClC;AACA,SAAO;AACT;AAQA,SAAS,2BACP,KACA,MAKO;AACP,QAAM,WAAmB,CAAC;AAC1B,MAAI,UAAU;AACd,aAAW,SAAS,IAAI,UAAU;AAChC,QAAI,SAAS,WAAW,GAAG;AACzB,UAAI,MAAM,SAAS,WAAW;AAC5B,YAAI,MAAM,SAAS,EAAG;AACtB;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,OAAQ;AAC3B,UAAI,MAAM,SAAS,QAAQ;AACzB,iBAAS,KAAK,KAAK;AACnB;AAAA,MACF;AAGA;AAAA,IACF;AAOA,QAAI,MAAM,SAAS,UAAU,cAAc,MAAM,KAAK,GAAG;AACvD,gBAAU;AACV;AAAA,IACF;AASA,QACE,MAAM,SAAS,WACd,WACE,MAAM,SAAS,SAAS,KACvB,MAAM,SAAS,MAAM,CAAC,SAAS,kBAAkB,IAAI,CAAC,IAC1D;AACA,eAAS,KAAK,KAAK;AACnB,gBAAU;AACV;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,SAAS,WAAW,EAAG,QAAO;AAMlC,QAAM,SAAS,aAAsB;AACrC,QAAM,MAAM,aAA+B;AAC3C,QAAM,eAAe,aAA6C;AAClE,aAAW,QAAQ,UAAU;AAC3B,eAAW,QAAQ,KAAK,UAAU;AAChC,YAAM,OAAO,kBAAkB,IAAI;AACnC,UAAI,QAAQ,CAAC,OAAO,OAAO,QAAQ,KAAK,GAAG,GAAG;AAC5C,eAAO,KAAK,GAAG,IAAI,KAAK;AACxB,cAAM,QAAQ,KAAK,UAAU;AAC7B,YAAI,MAAO,KAAI,KAAK,GAAG,IAAI,EAAE,MAAM,MAAM,MAAM,QAAQ,MAAM,OAAO;AAIpE,YACE,SAAS,UACT,KAAK,eACL,KAAK,MAAM,KAAK,YAAY,OAAO,KAAK,YAAY,GAAG,MAAM,KAAK,OAClE;AACA,uBAAa,KAAK,GAAG,IAAI,KAAK;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,mBAAmB,OAAO,KAAK,MAAM,EAAE;AAAA,IAAK,CAAC,MACjD,yBAAyB,IAAI,CAAC;AAAA,EAChC;AAOA,SAAO,mBAAmB,EAAE,QAAQ,EAAE,GAAG,OAAO,GAAG,KAAK,aAAa,IAAI;AAC3E;AAEA,IAAM,cAAc;AACpB,IAAM,oBAAoB;AAE1B,SAAS,aAAa,QAAwB;AAC5C,SAAO,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,QAAQ,GAAG;AACxD;AAcA,SAAS,kBACP,MAMO;AACP,QAAM,OAAO,KAAK,SAAS,CAAC;AAC5B,MAAI,MAAM,SAAS,YAAa,QAAO;AAEvC,QAAM,YAAY;AAClB,QAAM,QAAQ,UAAU,SAAS,CAAC;AAClC,MAAI,CAAC,MAAO,QAAO;AAGnB,MAAI,MAAM,SAAS,UAAU;AAC3B,UAAM,UAAU,MAAM,SAAS,CAAC;AAChC,QAAI,SAAS,SAAS,UAAU,CAAC,YAAY,KAAK,QAAQ,KAAK,GAAG;AAChE,aAAO;AAAA,IACT;AAEA,UAAMC,OAAM,UAAU,SAAS,CAAC;AAChC,QAAIA,MAAK,SAAS,UAAU,CAAC,kBAAkB,KAAKA,KAAI,KAAK,EAAG,QAAO;AAEvE,UAAM,YAAYA,KAAI,MAAM,QAAQ,mBAAmB,EAAE;AACzD,UAAM,YAAY,UAAU,SACzB,MAAM,CAAC,EACP,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC,EACtB,KAAK,EAAE;AACV,UAAM,SAAS,YAAY,WAAW,KAAK;AAC3C,QAAI,UAAU,GAAI,QAAO;AAIzB,QAAI;AACJ,UAAM,WAAWA,KAAI,UAAU,MAAM;AACrC,QACE,UAAU,SAAS,WAAW,KAC9B,OAAO,aAAa,YACpB,cAAc,IACd;AACA,YAAM,WAAW,kBAAkB,KAAKA,KAAI,KAAK;AACjD,YAAM,SAAS,YAAY,WAAW,SAAS,CAAC,EAAE,SAAS;AAC3D,oBAAc,EAAE,OAAO,QAAQ,KAAK,SAAS,MAAM,OAAO;AAAA,IAC5D;AAEA,WAAO,EAAE,KAAK,aAAa,QAAQ,KAAK,GAAG,OAAO,YAAY;AAAA,EAChE;AAGA,MAAI,MAAM,SAAS,QAAQ;AACzB,UAAM,WAAW,MAAM,MAAM,QAAQ,GAAG;AACxC,QAAI,aAAa,GAAI,QAAO;AAE5B,UAAM,SAAS,MAAM,MAAM,MAAM,GAAG,QAAQ;AAC5C,QAAI,CAAC,YAAY,KAAK,MAAM,EAAG,QAAO;AAEtC,UAAM,aAAa,MAAM,MAAM,MAAM,WAAW,CAAC;AACjD,UAAM,YAAY,WAAW,QAAQ,QAAQ,EAAE;AAC/C,UAAM,YAAY,UAAU,SACzB,MAAM,CAAC,EACP,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC,EACtB,KAAK,EAAE;AACV,UAAM,SAAS,YAAY,WAAW,KAAK;AAC3C,QAAI,UAAU,GAAI,QAAO;AAGzB,QAAI;AACJ,UAAM,YAAY,MAAM,UAAU,MAAM;AACxC,QACE,UAAU,SAAS,WAAW,KAC9B,OAAO,cAAc,YACrB,cAAc,IACd;AACA,YAAM,YAAY,WAAW,SAAS,UAAU;AAChD,YAAM,SAAS,YAAY,WAAW,IAAI;AAC1C,oBAAc,EAAE,OAAO,QAAQ,KAAK,SAAS,MAAM,OAAO;AAAA,IAC5D;AAEA,WAAO,EAAE,KAAK,aAAa,MAAM,GAAG,OAAO,YAAY;AAAA,EACzD;AAEA,SAAO;AACT;;;ADnaO,IAAM,sBACX;AAMK,IAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,mBAAmB;AAelB,SAAS,aAAa,KAA8B;AACzD,aAAW,aAAa,oBAAoB;AAC1C,QAAI;AACJ,QAAI;AACF,gBAAUC,aAAYC,SAAQ,KAAK,SAAS,GAAG,EAAE,eAAe,KAAK,CAAC;AAAA,IACxE,QAAQ;AACN;AAAA,IACF;AACA,QAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,iBAAiB,KAAK,EAAE,IAAI,CAAC,GAAG;AACpE,aAAO,EAAE,QAAQ,WAAW,QAAQ,WAAW;AAAA,IACjD;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,YAAY,QAAQ,WAAW;AAClD;AAQA,SAAS,kBAAkB,SAAqC;AAC9D,MAAI;AACJ,MAAI;AACF,aAAS,UAAU,OAAO;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,OAAO,aAAa;AACtB,WAAO,qBAAqB,OAAO,cAAc,OAAO;AAAA,EAC1D;AACA,MAAI,OAAO,aAAc,QAAO;AAChC,SAAO;AACT;AAQO,SAAS,kBACd,KACA,QACsB;AACtB,QAAM,MAAMA,SAAQ,KAAK,MAAM;AAC/B,MAAI;AACJ,MAAI;AACF,cAAUD,aAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,QACZ,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,iBAAiB,KAAK,EAAE,IAAI,CAAC,EACzD,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,SAAS,EACT,MAAM,GAAG,EAAE;AAEd,QAAM,QAAqC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE;AACjE,aAAW,QAAQ,QAAQ;AACzB,QAAI;AACJ,QAAI;AACF,gBAAUE,cAAaC,MAAK,KAAK,IAAI,GAAG,MAAM;AAAA,IAChD,QAAQ;AACN;AAAA,IACF;AACA,UAAM,UAAU,kBAAkB,OAAO;AACzC,QAAI,QAAS,OAAM,OAAO;AAAA,EAC5B;AAEA,QAAM,MAAM,KAAK,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,EAAE;AACjD,MAAI,QAAQ,EAAG,QAAO;AACtB,QAAM,UAAW,CAAC,MAAM,MAAM,IAAI,EAAY;AAAA,IAC5C,CAAC,MAAM,MAAM,CAAC,MAAM;AAAA,EACtB;AACA,SAAO,QAAQ,WAAW,IAAI,QAAQ,CAAC,IAAI;AAC7C;AAMO,IAAM,4BAA0D;AAAA,EACrE,IAAI;AAAA,EACJ,MAAM;AACR;AAQO,SAAS,mBAAmB,KAA2B;AAC5D,MAAIC,YAAWH,SAAQ,KAAK,eAAe,CAAC,EAAG,QAAO;AACtD,MAAI;AACF,UAAMI,OAAM,KAAK;AAAA,MACfH,cAAaD,SAAQ,KAAK,cAAc,GAAG,MAAM;AAAA,IACnD;AAIA,QACEI,KAAI,eAAe,YAAY,MAAM,UACrCA,KAAI,kBAAkB,YAAY,MAAM,QACxC;AACA,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAOO,SAAS,uBAAuB,KAA4B;AACjE,aAAW,QAAQ,cAAc;AAC/B,QAAID,YAAWH,SAAQ,KAAK,IAAI,CAAC,EAAG,QAAO;AAAA,EAC7C;AACA,SAAO;AACT;AAQA,SAAS,QAAQ,OAAuB;AACtC,SAAO,IAAI,MAAM,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK,CAAC;AAC9D;AAMO,SAAS,aACd,QACA,SACQ;AACR,MAAI,WAAW,QAAQ;AACrB,UAAM,SAAkC;AAAA,MACtC,SAAS,CAAC,uBAAuB;AAAA,MACjC,QAAQ,QAAQ;AAAA,IAClB;AACA,QAAI,QAAQ,gBAAgB,OAAQ,QAAO,cAAc,QAAQ;AACjE,WAAO,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA;AAAA,EAC3C;AAEA,QAAM,cACJ,QAAQ,gBAAgB,SACpB;AAAA,iBAAoB,QAAQ,QAAQ,WAAW,CAAC,MAChD;AACN,SAAO;AAAA;AAAA;AAAA;AAAA,YAIG,QAAQ,QAAQ,MAAM,CAAC,IAAI,WAAW;AAAA;AAAA;AAGlD;AAiBO,SAAS,cAAc,OAA8B;AAK1D,QAAM,UACJ,MAAM,iBAAiB,aACnB,eACA,MAAM,iBAAiB,aACrB,iBACA,MAAM,iBAAiB,IACrB,kGACA;AAEV,QAAM,QAAQ;AAAA,IACZ,SAAS,MAAM,UAAU;AAAA,IACzB;AAAA,IACA,oBAAoB,MAAM,MAAM,IAAI,OAAO;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM,SAAS,MAAM;AACnC,MAAI,MAAM,eAAe,KAAK,QAAQ,GAAG;AACvC,UAAM;AAAA,MACJ,8BAA8B,MAAM,MAAM,iBAAiB,MAAM,QAAQ,sBAAsB,MAAM,YAAY;AAAA,MACjH;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,aAAa,mBAAmB,EAAE;AAC7C,SAAO,MAAM,KAAK,IAAI;AACxB;;;AG9PA,SAAS,gBAAAK,eAAc,YAAAC,iBAAgB;AACvC,SAAS,YAAY,UAAU,WAAAC,UAAS,WAAW;;;ACSnD,SAAS,cAAAC,aAAY,WAAW,gBAAAC,eAAc,qBAAqB;AACnE,SAAS,SAAS,QAAAC,aAAY;;;AC2DvB,IAAM,2BAA2B;AAyPjC,SAAS,cAAcC,OAAoC;AAChE,SAAO,WAAWA;AACpB;;;ADjTO,IAAM,mBAAmB;AAGzB,IAAM,wBAAwB;AAoB9B,SAAS,aAAa,KAAqB;AAChD,SAAOC,MAAK,KAAK,cAAc,eAAe;AAChD;AAQO,SAAS,cAAc,aAA8C;AAC1E,QAAM,UAAU,aAAqD;AACrE,aAAW,KAAK,aAAa;AAC3B,QAAI,EAAE,aAAa,yBAA0B;AAC7C,UAAM,SAAU,QAAQ,EAAE,IAAI,MAAM,aAAa;AACjD,UAAM,YAAa,OAAO,EAAE,QAAQ,MAAM,aAAa;AACvD,cAAU,EAAE,SAAS,KAAK,UAAU,EAAE,SAAS,KAAK,KAAK;AAAA,EAC3D;AACA,SAAO,EAAE,SAAS,kBAAkB,QAAQ;AAC9C;AAOO,SAAS,kBAAkB,UAA4B;AAI5D,QAAM,gBAAgB,aAAqD;AAC3E,aAAW,QAAQ,OAAO,KAAK,SAAS,OAAO,EAAE,SAAS,GAAG;AAC3D,UAAM,SAAS,SAAS,QAAQ,IAAI,KAAK,CAAC;AAC1C,UAAM,eAAe,aAAqC;AAC1D,eAAWC,SAAQ,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AACjD,YAAM,YAAY,OAAOA,KAAI,KAAK,CAAC;AACnC,YAAM,kBAAkB,aAAqB;AAC7C,iBAAW,aAAa,OAAO,KAAK,SAAS,EAAE,SAAS,GAAG;AACzD,wBAAgB,SAAS,IAAI,UAAU,SAAS,KAAK;AAAA,MACvD;AACA,mBAAaA,KAAI,IAAI;AAAA,IACvB;AACA,kBAAc,IAAI,IAAI;AAAA,EACxB;AACA,QAAM,UAAU,EAAE,SAAS,SAAS,SAAS,SAAS,cAAc;AACpE,SAAO,KAAK,UAAU,SAAS,MAAM,CAAC,IAAI;AAC5C;AAOO,SAAS,aAAa,MAA+B;AAC1D,MAAI,CAACC,YAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,UAAM,OAAO,KAAK,MAAMC,cAAa,MAAM,MAAM,CAAC;AAClD,QAAI,OAAO,KAAK,YAAY,YAAY,KAAK,YAAY,KAAM,QAAO;AACtE,WAAO;AAAA,MACL,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAAA,MAC3D,SAAS,eAAe,KAAK,OAAkC;AAAA,IACjE;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,SAAS,eAAe,KAAmD;AACzE,QAAM,UAAU,aAAqD;AACrE,aAAW,CAAC,MAAM,SAAS,KAAK,OAAO,QAAQ,GAAG,GAAG;AACnD,QAAI,OAAO,cAAc,YAAY,cAAc,KAAM;AACzD,UAAM,SAAS,aAAqC;AACpD,eAAW,CAACF,OAAM,YAAY,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC5D,UAAI,OAAO,iBAAiB,YAAY,iBAAiB,KAAM;AAC/D,YAAM,YAAY,aAAqB;AACvC,iBAAW,CAAC,WAAW,KAAK,KAAK,OAAO;AAAA,QACtC;AAAA,MACF,GAAG;AACD,YAAI,OAAO,UAAU,SAAU,WAAU,SAAS,IAAI;AAAA,MACxD;AACA,aAAOA,KAAI,IAAI;AAAA,IACjB;AACA,YAAQ,IAAI,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAGO,SAAS,cAAc,MAAc,UAA0B;AACpE,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,kBAAkB,QAAQ,GAAG,MAAM;AACzD;AAKA,IAAM,UAAU;AAEhB,SAAS,YAAY,GAAuB;AAC1C,SAAO,EAAE,OAAO,UAAU,EAAE,WAAW,UAAU,EAAE;AACrD;AAWO,SAAS,cACd,aACA,UACqB;AACrB,QAAM,YAAY,oBAAI,IAAoB;AAC1C,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,SAAS,OAAO,GAAG;AAC7D,eAAW,CAACA,OAAM,SAAS,KAAK,OAAO,QAAQ,MAAM,GAAG;AACtD,UAAIA,UAAS,yBAA0B;AACvC,iBAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC1D,kBAAU,IAAI,OAAO,UAAUA,QAAO,UAAU,WAAW,KAAK;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAqB,CAAC;AAC5B,MAAI,SAAS;AACb,aAAW,KAAK,aAAa;AAC3B,QAAI,EAAE,aAAa,0BAA0B;AAC3C,WAAK,KAAK,CAAC;AACX;AAAA,IACF;AACA,UAAM,MAAM,YAAY,CAAC;AACzB,UAAM,OAAO,UAAU,IAAI,GAAG;AAC9B,QAAI,SAAS,UAAa,OAAO,GAAG;AAClC,gBAAU,IAAI,KAAK,OAAO,CAAC;AAC3B;AAAA,IACF,OAAO;AACL,WAAK,KAAK,CAAC;AAAA,IACb;AAAA,EACF;AACA,SAAO,EAAE,MAAM,OAAO;AACxB;AAEA,SAAS,WAAW,UAA4B;AAC9C,MAAI,QAAQ;AACZ,aAAW,UAAU,OAAO,OAAO,SAAS,OAAO,GAAG;AACpD,eAAW,aAAa,OAAO,OAAO,MAAM,GAAG;AAC7C,iBAAW,SAAS,OAAO,OAAO,SAAS,EAAG,UAAS;AAAA,IACzD;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,qBAAqB,UAA4B;AAC/D,QAAM,aAAa,WAAW,QAAQ;AACtC,QAAM,QAAQ,OAAO,KAAK,SAAS,OAAO,EAAE;AAC5C,QAAM,IAAI,GAAG,UAAU,IAAI,eAAe,IAAI,cAAc,YAAY;AACxE,QAAM,IAAI,GAAG,KAAK,IAAI,UAAU,IAAI,SAAS,OAAO;AACpD,SAAO,SAAS,CAAC,WAAW,CAAC,OAAO,qBAAqB;AAC3D;AAOO,SAAS,2BAAmC;AACjD,SAAO,uBAAuB,qBAAqB;AACrD;AAGO,SAAS,sBAAsB,QAAwB;AAC5D,SAAO,GAAG,MAAM,IAAI,WAAW,IAAI,YAAY,UAAU,wBAAwB,qBAAqB;AACxG;;;AEjOA,SAAS,kBAAkB;AAC3B,SAAS,cAAAG,aAAY,aAAAC,YAAW,gBAAAC,eAAc,iBAAAC,sBAAqB;AACnE,SAAS,WAAAC,UAAS,QAAAC,aAAY;AA2BvB,IAAM,uBAAuB;AAa7B,SAAS,mBAAmB,SAAyB;AAC1D,SAAO,WAAW,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,OAAO,KAAK;AAChE;AAEO,SAAS,kBAAkB,QAAyB;AACzD,SAAO,WAAW,MAAM,EAAE,OAAO,gBAAgB,MAAM,GAAG,MAAM,EAAE,OAAO,KAAK;AAChF;AAOA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,MAAM,IAAI,eAAe,EAAE,KAAK,GAAG,IAAI;AAAA,EACtD;AACA,QAAM,UAAU,OAAO,QAAQ,KAAgC,EAAE;AAAA,IAC/D,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;AAAA,EAC1C;AACA,SACE,MACA,QACG,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI,MAAM,gBAAgB,CAAC,CAAC,EAC5D,KAAK,GAAG,IACX;AAEJ;AAEO,SAAS,aAAa,UAA0B;AACrD,SAAOC,MAAK,UAAU,eAAe;AACvC;AAEO,SAAS,aAAa,MAAoC;AAC/D,MAAI,CAACC,YAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,UAAM,OAAO,KAAK,MAAMC,cAAa,MAAM,MAAM,CAAC;AAClD;AAAA;AAAA;AAAA;AAAA,MAIE,KAAK,kBAAkB,wBACvB,OAAO,KAAK,YAAY,YACxB,OAAO,KAAK,eAAe,YAC3B,OAAO,KAAK,UAAU,YACtB,KAAK,UAAU;AAAA,MACf;AACA,aAAO;AAAA,IACT;AAKA,UAAM,QAAQ,aAAyB;AACvC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO;AAAA,MAChC,KAAK;AAAA,IACP,GAAG;AACD,YAAM,GAAG,IAAI;AAAA,IACf;AACA,WAAO,EAAE,GAAI,MAAwB,MAAM;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,MAAc,UAA+B;AACxE,EAAAC,WAAUC,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,EAAAC,eAAc,MAAM,KAAK,UAAU,QAAQ,GAAG,MAAM;AACtD;;;AC7FO,IAAM,iBAAiB;AAMvB,SAAS,UAAU,MAAqB;AAC7C,QAAM,QAAQ,CAAC,MAAsB,IAAI;AACzC,SAAO;AAAA,IACL,aAAa,OAAO,MAAM;AACxB,aAAO,EAAE,OAAO,CAAC,MAAM,MAAM,CAAC,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK;AAAA,IAC3D;AAAA,IACA,SAAS,QAAQ,MAAM;AACrB,YAAM,KAAK,MAAM,MAAM;AACvB,aAAO,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,KAAK;AAAA,IACjC;AAAA,IACA,OAAO,OAAO;AACZ,aAAO,EAAE,OAAO,CAAC,MAAM,MAAM,CAAC,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG;AAAA,IAC/D;AAAA,EACF;AACF;AAmBO,SAAS,kBACd,SACA,OACmC;AACnC,QAAM,QAAQ,MAAM,OAAO,CAAC,MAAM;AAChC,UAAM,CAAC,OAAO,GAAG,IAAI,EAAE;AACvB,WACE,OAAO,UAAU,KAAK,KACtB,OAAO,UAAU,GAAG,KACpB,SAAS,KACT,OAAO,SACP,OAAO,QAAQ;AAAA,EAEnB,CAAC;AAED,QAAM,SAAS,MAAM;AAAA,IACnB,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,EAC7D;AAEA,MAAI,MAAM;AACV,MAAI,SAAS;AACb,MAAI,UAAU;AACd,aAAW,QAAQ,QAAQ;AACzB,UAAM,CAAC,OAAO,GAAG,IAAI,KAAK;AAC1B,QAAI,QAAQ,OAAQ;AACpB,WAAO,QAAQ,MAAM,QAAQ,KAAK,IAAI,KAAK;AAC3C,aAAS;AACT;AAAA,EACF;AACA,SAAO,EAAE,MAAM,MAAM,QAAQ,MAAM,MAAM,GAAG,QAAQ;AACtD;AAQO,SAAS,aACd,aACA,SACY;AACZ,QAAM,QAAQ,UAAU,kBAAkB,OAAO,CAAC;AAClD,QAAM,QAAoB,CAAC;AAC3B,aAAW,KAAK,aAAa;AAC3B,QAAI,CAAC,EAAE,IAAK;AACZ,UAAM,SAAS,EAAE,IAAI,KAAK;AAC1B,QAAI,WAAW,KAAM;AACrB,QAAI,MAAM,QAAQ,MAAM,EAAG,OAAM,KAAK,GAAG,MAAM;AAAA,QAC1C,OAAM,KAAK,MAAM;AAAA,EACxB;AACA,SAAO;AACT;AAgBO,SAAS,oBACd,aACA,eACyB;AACzB,QAAM,QAAQ,UAAU,CAAC;AACzB,QAAM,SAAS,oBAAI,IAAwB;AAC3C,aAAW,KAAK,aAAa;AAC3B,QAAI,CAAC,EAAE,IAAK;AACZ,QAAI,OAAO,IAAI,EAAE,IAAI,EAAG;AACxB,QAAI,cAAc,EAAE,IAAI,MAAM,OAAW;AACzC,UAAM,SAAS,EAAE,IAAI,KAAK;AAC1B,QAAI,WAAW,KAAM;AACrB,WAAO,IAAI,EAAE,MAAM,MAAM,QAAQ,MAAM,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC;AAAA,EACnE;AACA,SAAO;AACT;AAsBO,SAAS,eACd,iBACA,MACA,YAAoB,gBACL;AACf,MAAI,UAAU;AACd,MAAI,SAAS;AACb,MAAI,UAAU;AAEd,aAAS;AACP,UAAM,cAAc,KAAK,OAAO;AAChC,UAAM,OAAO,OAAsB;AAAA,MACjC,cAAc;AAAA,MACd,WAAW;AAAA,MACX,SAAS,YAAY;AAAA,MACrB;AAAA,MACA;AAAA,IACF;AAEA,QAAI,UAAU,UAAW,QAAO,KAAK;AAErC,UAAM,QAAQ,aAAa,aAAa,OAAO;AAC/C,QAAI,MAAM,WAAW,EAAG,QAAO,KAAK;AAEpC,UAAM,EAAE,MAAM,MAAM,SAAS,OAAO,IAAI,kBAAkB,SAAS,KAAK;AACxE,QAAI,SAAS,QAAS,QAAO,KAAK;AAIlC,eAAW;AACX,cAAU;AACV;AAAA,EACF;AACF;AAQA,IAAM,UAAU;AAQT,SAAS,YAAY,MAAc,QAAgB,OAAuB;AAC/E,MAAI,WAAW,MAAO,QAAO;AAE7B,QAAM,IAAI,WAAW,MAAM;AAC3B,QAAM,IAAI,WAAW,KAAK;AAC1B,QAAM,MAAM,UAAU,GAAG,CAAC;AAC1B,QAAM,QAAQ,WAAW,GAAG;AAC5B,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,QAAkB,CAAC,SAAS,IAAI,IAAI,SAAS,IAAI,EAAE;AACzD,aAAW,QAAQ,MAAO,OAAM,KAAK,GAAG,WAAW,IAAI,CAAC;AACxD,SAAO,MAAM,KAAK,IAAI,IAAI;AAC5B;AAGA,SAAS,WAAW,MAAwB;AAC1C,MAAI,SAAS,GAAI,QAAO,CAAC;AACzB,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,MAAI,MAAM,MAAM,SAAS,CAAC,MAAM,GAAI,OAAM,IAAI;AAC9C,SAAO;AACT;AAGA,SAAS,UAAU,GAAsB,GAA4B;AACnE,QAAM,IAAI,EAAE;AACZ,QAAM,IAAI,EAAE;AAEZ,QAAM,MAAkB,MAAM;AAAA,IAAK,EAAE,QAAQ,IAAI,EAAE;AAAA,IAAG,MACpD,MAAM,KAAa,EAAE,QAAQ,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC;AAAA,EAC9C;AACA,WAASC,KAAI,IAAI,GAAGA,MAAK,GAAGA,MAAK;AAC/B,aAASC,KAAI,IAAI,GAAGA,MAAK,GAAGA,MAAK;AAC/B,UAAID,EAAC,EAAGC,EAAC,IACP,EAAED,EAAC,MAAM,EAAEC,EAAC,IACR,IAAID,KAAI,CAAC,EAAGC,KAAI,CAAC,IAAK,IACtB,KAAK,IAAI,IAAID,KAAI,CAAC,EAAGC,EAAC,GAAI,IAAID,EAAC,EAAGC,KAAI,CAAC,CAAE;AAAA,IACjD;AAAA,EACF;AAEA,QAAM,MAAY,CAAC;AACnB,MAAI,IAAI;AACR,MAAI,IAAI;AACR,SAAO,IAAI,KAAK,IAAI,GAAG;AACrB,QAAI,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;AACjB,UAAI,KAAK,EAAE,MAAM,MAAM,MAAM,EAAE,CAAC,EAAG,CAAC;AACpC;AACA;AAAA,IACF,WAAW,IAAI,IAAI,CAAC,EAAG,CAAC,KAAM,IAAI,CAAC,EAAG,IAAI,CAAC,GAAI;AAC7C,UAAI,KAAK,EAAE,MAAM,OAAO,MAAM,EAAE,CAAC,EAAG,CAAC;AACrC;AAAA,IACF,OAAO;AACL,UAAI,KAAK,EAAE,MAAM,OAAO,MAAM,EAAE,CAAC,EAAG,CAAC;AACrC;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI,EAAG,KAAI,KAAK,EAAE,MAAM,OAAO,MAAM,EAAE,GAAG,EAAG,CAAC;AACrD,SAAO,IAAI,EAAG,KAAI,KAAK,EAAE,MAAM,OAAO,MAAM,EAAE,GAAG,EAAG,CAAC;AACrD,SAAO;AACT;AAWA,SAAS,WAAW,KAA4B;AAE9C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ,CAAC,IAAI,QAAQ;AACvB,QAAI,GAAG,SAAS,KAAM,WAAU,KAAK,GAAG;AAAA,EAC1C,CAAC;AACD,MAAI,UAAU,WAAW,EAAG,QAAO,CAAC;AAGpC,QAAM,UAAmC,CAAC;AAC1C,aAAW,OAAO,WAAW;AAC3B,UAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,OAAO;AACvC,UAAM,MAAM,KAAK,IAAI,IAAI,SAAS,GAAG,MAAM,OAAO;AAClD,UAAM,OAAO,QAAQ,QAAQ,SAAS,CAAC;AACvC,QAAI,QAAQ,SAAS,KAAK,CAAC,IAAI,EAAG,MAAK,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,GAAG;AAAA,QAC5D,SAAQ,KAAK,CAAC,OAAO,GAAG,CAAC;AAAA,EAChC;AAEA,QAAM,QAAgB,CAAC;AACvB,aAAW,CAAC,OAAO,GAAG,KAAK,SAAS;AAClC,QAAI,QAAQ;AACZ,QAAI,QAAQ;AACZ,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAI,IAAI,CAAC,EAAG,SAAS,MAAO;AAC5B,UAAI,IAAI,CAAC,EAAG,SAAS,MAAO;AAAA,IAC9B;AACA,UAAM,UAAU,IAAI,MAAM,OAAO,MAAM,CAAC;AACxC,QAAI,SAAS;AACb,QAAI,SAAS;AACb,eAAW,MAAM,SAAS;AACxB,UAAI,GAAG,SAAS,MAAO;AACvB,UAAI,GAAG,SAAS,MAAO;AAAA,IACzB;AACA,UAAM,KAAK,EAAE,QAAQ,OAAO,QAAQ,QAAQ,OAAO,QAAQ,KAAK,QAAQ,CAAC;AAAA,EAC3E;AACA,SAAO;AACT;AAEA,SAAS,WAAW,MAAsB;AACxC,QAAM,SAAS,OAAO,KAAK,MAAM,IAAI,KAAK,MAAM,KAAK,KAAK,MAAM,IAAI,KAAK,MAAM;AAC/E,QAAM,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO;AAChC,UAAM,SAAS,GAAG,SAAS,OAAO,MAAM,GAAG,SAAS,QAAQ,MAAM;AAClE,WAAO,GAAG,MAAM,GAAG,GAAG,IAAI;AAAA,EAC5B,CAAC;AACD,SAAO,CAAC,QAAQ,GAAG,IAAI;AACzB;;;ACvUA,OAAO,SAA0D;;;ACU1D,SAAS,YACd,UACA,MACQ;AAKR,MAAI,CAAC,SAAS,SAAS,IAAI,EAAG,QAAO;AACrC,SAAO,SAAS,QAAQ,kBAAkB,CAAC,GAAG,QAAgB;AAC5D,UAAM,IAAI,KAAK,GAAG;AAClB,WAAO,MAAM,SAAY,OAAO,CAAC,IAAI,KAAK,GAAG;AAAA,EAC/C,CAAC;AACH;;;ACEA,IAAM,kBAAkB,oBAAI,IAAmB;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AASM,IAAM,mBAAmB;AAoChC,SAAS,eACP,OACA,MACA,SACkB;AAClB,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,WAAW,MAAM,KAAK,CAAC,QAAQ,SAAS,KAAK,EAAG,QAAO;AAEpE,QAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,EAAE,KAAK;AAMxC,MAAI,MAAM,SAAS,MAAM,KAAK,MAAM,SAAS,KAAK,EAAG,QAAO;AAC5D,MAAI,CAAC,MAAM,WAAW,gBAAgB,EAAG,QAAO;AAEhD,QAAM,cAAc,MAAM,MAAM,iBAAiB,MAAM;AAIvD,QAAM,UAAU,YAAY,OAAO,IAAI;AACvC,QAAM,UAAU,YAAY,KAAK,cAAc,YAAY,MAAM,GAAG,OAAO;AAC3E,MAAI,CAAC,gBAAgB,IAAI,OAAwB,EAAG,QAAO;AAE3D,QAAM,OAAO,YAAY,KAAK,KAAK,YAAY,MAAM,OAAO;AAC5D,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,cAAc,IAAI;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,cAAc,MAAwC;AAC7D,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,YAAY,GAAI,QAAO;AAC3B,QAAM,QAAQ,QACX,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,MAAM,EAAE;AACzB,SAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;AAEA,SAAS,SAAS,MAAiB,KAAwB;AACzD,MAAI,KAAK,SAAS,QAAQ;AACxB,UAAM,OAAO,KAAK,UAAU,MAAM;AAClC,UAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAI,OAAO,SAAS,YAAY,OAAO,YAAY,UAAU;AAC3D,YAAM,YAAY,eAAe,KAAK,OAAO,MAAM,OAAO;AAC1D,UAAI,UAAW,KAAI,KAAK,SAAS;AAAA,IACnC;AAAA,EACF;AACA,MAAI,cAAc,QAAQ,MAAM,QAAQ,KAAK,QAAQ,GAAG;AACtD,eAAW,SAAS,KAAK,SAAU,UAAS,OAAoB,GAAG;AAAA,EACrE;AACF;AAYO,SAAS,kBACd,KACA,MACuB;AACvB,QAAM,MAAmB,CAAC;AAC1B,WAAS,KAAkB,GAAG;AAC9B,MAAI,IAAI,WAAW,EAAG,QAAO;AAM7B,MAAI,QAAyB;AAC7B,QAAM,mBAAmB,CAAC,UAAiC;AACzD,cAAU,KAAK,MAAM,IAAI;AACzB,aAAS,KAAK,QAAQ,GAAG,MAAM,MAAM,QAAQ,MAAM;AACjD,WAAK,MAAM,KAAK,CAAC,KAAK,IAAI,KAAK,MAAM,GAAI,QAAO;AAAA,IAClD;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAwB;AAAA,IAC5B,gBAAgB;AAAA,IAChB,kBAAkB,oBAAI,IAAI;AAAA,IAC1B,QAAQ,CAAC;AAAA,IACT,UAAU,oBAAI,IAAI;AAAA,EACpB;AAEA,aAAW,aAAa,KAAK;AAC3B,YAAQ,UAAU,MAAM;AAAA,MACtB,KAAK,gBAAgB;AACnB,YAAI,UAAU,UAAU,KAAM,OAAM,iBAAiB;AAAA,YAChD,YAAWC,SAAQ,UAAU,MAAO,OAAM,iBAAiB,IAAIA,KAAI;AACxE;AAAA,MACF;AAAA,MACA,KAAK;AAAA,MACL,KAAK,UAAU;AACb,cAAM,OAAO,KAAK,SAAS;AAC3B;AAAA,MACF;AAAA,MACA,KAAK,qBAAqB;AAKxB,cAAM,SAAS,iBAAiB,UAAU,OAAO;AACjD,YAAI,WAAW,KAAM;AACrB,YAAI,QAAQ,MAAM,SAAS,IAAI,MAAM;AACrC,YAAI,CAAC,OAAO;AACV,kBAAQ,EAAE,KAAK,OAAO,OAAO,oBAAI,IAAI,EAAE;AACvC,gBAAM,SAAS,IAAI,QAAQ,KAAK;AAAA,QAClC;AACA,YAAI,UAAU,UAAU,KAAM,OAAM,MAAM;AAAA,YACrC,YAAWA,SAAQ,UAAU,MAAO,OAAM,MAAM,IAAIA,KAAI;AAC7D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAQA,QAAM,OAAO,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AAC3C,SAAO;AACT;AAEA,SAAS,iBAAiB,WAAsB,UAA2B;AACzE,SAAO,UAAU,UAAU,QAAQ,UAAU,MAAM,SAAS,QAAQ;AACtE;AAYO,SAAS,aACd,OACA,UACA,MACS;AACT,MAAI,MAAM,kBAAkB,MAAM,iBAAiB,IAAI,QAAQ,EAAG,QAAO;AAEzE,MAAI,SAAS,QAAW;AACtB,UAAM,OAAO,MAAM,SAAS,IAAI,IAAI;AACpC,QAAI,SAAS,KAAK,OAAO,KAAK,MAAM,IAAI,QAAQ,GAAI,QAAO;AAAA,EAC7D;AAOA,QAAM,UAAU,QAAQ,OAAO;AAC/B,MAAI,WAAW;AACf,aAAW,aAAa,MAAM,QAAQ;AACpC,QAAI,UAAU,OAAO,QAAS;AAC9B,QAAI,iBAAiB,WAAW,QAAQ,GAAG;AACzC,iBAAW,UAAU,SAAS;AAAA,IAChC;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,iBACd,aACA,OACc;AACd,SAAO,YAAY,OAAO,CAAC,MAAM;AAC/B,QAAI,EAAE,aAAa,yBAA0B,QAAO;AACpD,WAAO,CAAC,aAAa,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI;AAAA,EACrD,CAAC;AACH;;;AFrPA,IAAM,MAAM,IAAI,IAAI,EAAE,QAAQ,MAAM,WAAW,KAAK,CAAC;AAKrD,IAAM,iBAAiB,oBAAI,QAA2C;AAEtE,SAAS,aAAa,QAA2C;AAC/D,MAAI,YAAY,eAAe,IAAI,MAAM;AACzC,MAAI,CAAC,WAAW;AACd,gBAAY,IAAI,QAAQ,MAAM;AAC9B,mBAAe,IAAI,QAAQ,SAAS;AAAA,EACtC;AACA,SAAO;AACT;AAQA,IAAM,0BAA0B;AAOzB,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YACkB,UACA,WAChB;AACA,UAAM,4BAA4B,QAAQ,KAAK,SAAS,EAAE;AAH1C;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;AAiCA,IAAM,qBAAN,MAAgD;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACQ;AAAA,EAEjB,YACE,MACA,SACA,cACA,QACA;AACA,SAAK,OAAO;AACZ,SAAK,UAAU;AACf,SAAK,eAAe;AACpB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,cAA8C;AAChD,WAAO,KAAK,aAAa,EAAE;AAAA,EAC7B;AAAA,EAEA,IAAI,WAA2C;AAC7C,WAAO,KAAK,aAAa,EAAE;AAAA,EAC7B;AAAA,EAEA,IAAI,cAAuE;AACzE,WAAO,KAAK,aAAa,EAAE;AAAA,EAC7B;AAAA,EAEA,IAAI,mBAA0E;AAC5E,WAAO,KAAK,aAAa,EAAE;AAAA,EAC7B;AACF;AAwCO,SAAS,eACd,OACA,MACA,UAA0B,CAAC,GACb;AACd,QAAM,cAA4B,CAAC;AACnC,QAAM,WAAW,QAAQ,YAAY;AAErC,MAAI,SAA4B;AAChC,QAAM,eAAe,MAAkB;AACrC,eAAW,UAAU,KAAK,OAAO;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,WAA6D,CAAC;AACpE,QAAM,UAA4D,CAAC;AACnE,MAAI,aAAa;AAEjB,aAAWC,SAAQ,OAAO;AACxB,UAAM,iBACJ,QAAQ,gBAAgBA,MAAK,KAAK,IAAI,KAAK,QAAQ;AACrD,UAAM,gBAAgB;AAAA,MACpB,GAAGA,MAAK,KAAK;AAAA,MACb,GAAG;AAAA,IACL;AAEA,QAAIA,MAAK,KAAK,UAAU,CAAC,QAAQ,gBAAgB;AAC/C,YAAM,YAAY,aAAaA,MAAK,KAAK,MAAM;AAC/C,UAAI,CAAC,UAAU,aAAa,GAAG;AAC7B,cAAM,IAAI,iBAAiBA,MAAK,KAAK,MAAM,IAAI,WAAW,UAAU,MAAM,CAAC;AAAA,MAC7E;AAAA,IACF;AAQA,UAAM,WAAWA,MAAK,KAAK;AAC3B,UAAM,cAAcA,MAAK,KAAK;AAC9B,UAAM,UAAUA,MAAK,KAAK,KAAK,OAAO;AAItC,UAAM,cAAcA,MAAK,KAAK,YAAY;AAC1C,UAAM,UAAuB,IAAI;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAC,MAAM;AACL,cAAM,WAAW,cAAc,EAAE,SAAS;AAK1C,cAAM,SAAS,EAAE,QAAQ;AACzB,YAAI,UAAU,CAAC,aAAa;AAC1B,gBAAM,IAAI;AAAA,YACR,SAAS,QAAQ;AAAA,UACnB;AAAA,QACF;AACA,oBAAY,KAAK;AAAA,UACf;AAAA,UACA;AAAA,UACA,MAAM,KAAK;AAAA,UACX,YAAY,aAAa,SAAY,OAAO,YAAY,UAAU,EAAE,QAAQ,CAAC,CAAC;AAAA,UAC9E;AAAA,UACA,SAAS;AAAA,UACT,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,kBAAYA,MAAK,OAAO,OAAO;AAAA,IACjC,SAAS,KAAK;AAKZ,UAAI,eAAe,iBAAkB,OAAM;AAC3C,kBAAY;AAAA,QACV,wBAAwBA,MAAK,KAAK,MAAM,UAAU,KAAK,KAAK,IAAI;AAAA,MAClE;AACA;AAAA,IACF;AAEA,QAAI,WAAW;AACb,mBAAa;AACb,uBAAiBA,OAAM,WAAW,UAAU,SAAS,aAAa,IAAI;AAAA,IACxE;AAAA,EACF;AAEA,MAAI,YAAY;AACd,SAAK,aAAa,EAAE,KAAK,UAAU,OAAO;AAAA,EAC5C;AAOA,MAAI,YAAY,WAAW,EAAG,QAAO;AAQrC,MAAI,CAAC,KAAK,QAAQ,SAAS,gBAAgB,EAAG,QAAO;AACrD,QAAM,EAAE,KAAK,KAAK,IAAI,aAAa;AACnC,QAAM,aAAa,kBAAkB,KAAK,IAAI;AAC9C,SAAO,aAAa,iBAAiB,aAAa,UAAU,IAAI;AAClE;AAKA,SAAS,wBACP,UACA,WACA,KACA,MACY;AACZ,SAAO;AAAA,IACL,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,IACA,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD;AAAA,EACF;AACF;AAEA,SAAS,iBACPA,OACA,WACA,UACA,SACA,aACA,MACM;AACN,QAAM,OACJ,CAAC,OAAyB,CAAC,YAAuC,CAAC,SAAoB;AACrF,QAAI;AACF,cAAQ,IAAI;AAAA,IACd,SAAS,KAAK;AACZ,kBAAY;AAAA,QACV,wBAAwBA,MAAK,KAAK,MAAM,IAAI,KAAK,KAAK,IAAI;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAEF,MAAI,UAAU,OAAO;AACnB,UAAM,YAAY,KAAK,OAAO;AAC9B,eAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,UAAU,KAAK,GAAG;AAC7D,UAAI,QAAS,EAAC,SAAS,IAAI,MAAM,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC;AAAA,IAC9D;AAAA,EACF;AACA,MAAI,UAAU,MAAM;AAClB,UAAM,WAAW,KAAK,MAAM;AAC5B,eAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,UAAU,IAAI,GAAG;AAC5D,UAAI,QAAS,EAAC,QAAQ,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC;AAAA,IAC5D;AAAA,EACF;AACF;AAEA,SAAS,KACP,MACA,UACA,SACM;AACN,QAAM,gBAAgB,SAAS,KAAK,IAAI;AACxC,MAAI,eAAe;AACjB,eAAW,WAAW,cAAe,SAAQ,IAAI;AAAA,EACnD;AACA,MAAI,cAAc,QAAQ,MAAM,QAAQ,KAAK,QAAQ,GAAG;AACtD,eAAW,SAAS,KAAK,UAAU;AACjC,WAAK,OAAoB,UAAU,OAAO;AAAA,IAC5C;AAAA,EACF;AACA,QAAM,eAAe,QAAQ,KAAK,IAAI;AACtC,MAAI,cAAc;AAChB,eAAW,WAAW,aAAc,SAAQ,IAAI;AAAA,EAClD;AACF;AAOO,SAAS,iBAAiB,MAAgC;AAC/D,QAAM,SAAqB,UAAU,KAAK,OAAO;AACjD,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,MAAM,OAAO;AAAA,IACb,aAAa,OAAO;AAAA,IACpB,UAAU,OAAO;AAAA,IACjB,KAAK,OAAO;AAAA,EACd;AACF;AAUO,SAAS,kBACd,OACA,OACA,UAA0B,CAAC,GACb;AACd,QAAM,cAA4B,CAAC;AACnC,QAAM,WAAW,QAAQ,YAAY;AAErC,aAAWA,SAAQ,OAAO;AACxB,UAAM,iBACJ,QAAQ,gBAAgBA,MAAK,KAAK,IAAI,KAAK,QAAQ;AACrD,UAAM,gBAAgB;AAAA,MACpB,GAAGA,MAAK,KAAK;AAAA,MACb,GAAG;AAAA,IACL;AAEA,QAAIA,MAAK,KAAK,UAAU,CAAC,QAAQ,gBAAgB;AAC/C,YAAM,YAAY,aAAaA,MAAK,KAAK,MAAM;AAC/C,UAAI,CAAC,UAAU,aAAa,GAAG;AAC7B,cAAM,IAAI;AAAA,UACRA,MAAK,KAAK;AAAA,UACV,IAAI,WAAW,UAAU,MAAM;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AAIA,UAAM,WAAWA,MAAK,KAAK;AAC3B,UAAM,cAAcA,MAAK,KAAK;AAC9B,UAAM,UAAUA,MAAK,KAAK,KAAK,OAAO;AAItC,UAAM,cAAcA,MAAK,KAAK,YAAY;AAC1C,UAAM,UAA8B;AAAA,MAClC;AAAA,MACA,SAAS;AAAA,MACT,YAAY,QAAQ;AAAA,MACpB,OAAO,GAAG;AACR,cAAM,WAAW,cAAc,EAAE,SAAS;AAC1C,cAAM,SAAS,EAAE,QAAQ;AACzB,YAAI,UAAU,CAAC,aAAa;AAC1B,gBAAM,IAAI;AAAA,YACR,SAAS,QAAQ;AAAA,UACnB;AAAA,QACF;AACA,oBAAY,KAAK;AAAA,UACf;AAAA,UACA;AAAA,UACA,YAAY,aAAa,SAAY,OAAO,YAAY,UAAU,EAAE,QAAQ,CAAC,CAAC;AAAA,UAC9E;AAAA,UACA,SAAS;AAAA,UACT,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI;AACF,MAAAA,MAAK,MAAM,OAAO;AAAA,IACpB,SAAS,KAAK;AAIZ,kBAAY;AAAA,QACV,wBAAwBA,MAAK,KAAK,MAAM,SAAS,KAAK,WAAW;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;ALraA,SAAS,QAAQ,GAAmB;AAClC,MAAI,QAAQ,IAAK,QAAO;AACxB,SAAO,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG;AAC9B;AAgEO,SAAS,UAAU,MAA+B;AACvD,QAAM,iBAA+B,CAAC;AAKtC,QAAM,cAAc,KAAK,MAAM,IAAI,CAAC,kBAAkB;AAAA,IACpD,cAAc,QAAQ,SAAS,KAAK,KAAK,YAAY,CAAC;AAAA,IACtD,SAASC,cAAa,cAAc,MAAM;AAAA,EAC5C,EAAE;AAGF,QAAM,eAAuB,CAAC;AAC9B,QAAM,eAA8B,CAAC;AACrC,aAAWC,SAAQ,KAAK,OAAO;AAC7B,QAAI,cAAcA,KAAI,EAAG,cAAa,KAAKA,KAAI;AAAA,QAC1C,cAAa,KAAKA,KAAI;AAAA,EAC7B;AAIA,QAAM,gBAAgB,qBAAqB,KAAK,YAAY;AAG5D,MAAI,WAAiC;AACrC,MAAI,iBAAiB;AACrB,MAAI,KAAK,OAAO;AACd,UAAM,OAAO,aAAa,KAAK,MAAM,GAAG;AACxC,UAAM,SAAS,aAAa,IAAI;AAChC,QACE,UACA,OAAO,YAAY,KAAK,MAAM,cAC9B,OAAO,eAAe,KAAK,MAAM,YACjC;AACA,iBAAW;AAAA,IACb,OAAO;AACL,iBAAW;AAAA,QACT,eAAe;AAAA,QACf,SAAS,KAAK,MAAM;AAAA,QACpB,YAAY,KAAK,MAAM;AAAA,QACvB,OAAO,aAAyB;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAGA,aAAW,QAAQ,aAAa;AAC9B,QAAI,UAAU;AACZ,YAAM,cAAc,mBAAmB,KAAK,OAAO;AACnD,YAAM,QAAQ,SAAS,MAAM,KAAK,YAAY;AAC9C,UAAI,SAAS,MAAM,gBAAgB,aAAa;AAC9C,uBAAe,KAAK,GAAG,MAAM,kBAAkB;AAC/C;AACA;AAAA,MACF;AAEA,YAAM,kBAAkB;AAAA,QACtB;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MACF;AACA,qBAAe,KAAK,GAAG,eAAe;AACtC,YAAM,OAAmB;AAAA,QACvB;AAAA,QACA,oBAAoB;AAAA,MACtB;AACA,eAAS,MAAM,KAAK,YAAY,IAAI;AAAA,IACtC,OAAO;AACL,qBAAe;AAAA,QACb,GAAG;AAAA,UACD;AAAA,UACA,KAAK;AAAA,UACL;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,sBAAsB,aAAa;AAAA,IACvC,CAACA,UAAS,gBAAgB,KAAK,aAAaA,MAAK,KAAK,IAAI,CAAC,MAAM;AAAA,EACnE;AACA,MAAI,oBAAoB,SAAS,GAAG;AAClC,UAAM,eAAe,YAAY;AAAA,MAAI,CAAC,MACpC,iBAAiB,EAAE,MAAM,EAAE,cAAc,SAAS,EAAE,QAAQ,CAAC;AAAA,IAC/D;AACA,mBAAe;AAAA,MACb,GAAG;AAAA,QACD;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA,KAAK;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAKA,MAAI,YAAY,KAAK,OAAO;AAC1B,iBAAa,aAAa,KAAK,MAAM,GAAG,GAAG,QAAQ;AAAA,EACrD;AAIA,MAAI,CAAC,KAAK,UAAU;AAClB,WAAO;AAAA,MACL,cAAc,KAAK,MAAM;AAAA,MACzB;AAAA,MACA,aAAa;AAAA,MACb,gBAAgB;AAAA,IAClB;AAAA,EACF;AACA,QAAM,UAAU,cAAc,gBAAgB,KAAK,QAAQ;AAC3D,SAAO;AAAA,IACL,cAAc,KAAK,MAAM;AAAA,IACzB;AAAA,IACA,aAAa,QAAQ;AAAA,IACrB,gBAAgB,QAAQ;AAAA,EAC1B;AACF;AAkDO,SAAS,WAAW,MAA2C;AACpE,QAAM,eAAuB,CAAC;AAC9B,QAAM,eAA8B,CAAC;AACrC,aAAWA,SAAQ,KAAK,OAAO;AAC7B,QAAI,cAAcA,KAAI,EAAG,cAAa,KAAKA,KAAI;AAAA,QAC1C,cAAa,KAAKA,KAAI;AAAA,EAC7B;AACA,QAAM,gBAAgB,qBAAqB,KAAK,YAAY;AAC5D,QAAM,WAAW,KAAK,YAAY;AAElC,QAAM,QAAqB,CAAC;AAC5B,QAAM,YAA0B,CAAC;AACjC,MAAI,QAAQ;AACZ,MAAI,iBAAiB;AAErB,aAAW,WAAW,KAAK,OAAO;AAChC,UAAM,eAAe,QAAQ,SAAS,KAAK,KAAK,OAAO,CAAC;AACxD,UAAM,WAAWD,cAAa,SAAS,MAAM;AAK7C,QAAI,aAAa;AACjB,UAAM,OAAO,CAAC,YAAkC;AAC9C,YAAM,MAAM;AAAA,QACV;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA,EAAE,cAAc,QAAQ;AAAA,MAC1B;AACA,UAAI,CAAC,UAAU;AACb,qBAAa;AACb,eAAO;AAAA,MACT;AACA,YAAM,MAAM,cAAc,KAAK,QAAQ;AACvC,mBAAa,IAAI;AACjB,aAAO,IAAI;AAAA,IACb;AAEA,UAAM,MAAM,eAAe,UAAU,MAAM,KAAK,SAAS;AACzD,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,OAAO,IAAI;AAAA,MACX,SAAS,IAAI;AAAA,IACf,CAAC;AACD,cAAU,KAAK,GAAG,IAAI,SAAS;AAC/B,aAAS,IAAI;AACb,sBAAkB;AAAA,EACpB;AAQA,QAAM,sBAAsB,aAAa;AAAA,IACvC,CAACC,UAAS,gBAAgB,KAAK,aAAaA,MAAK,KAAK,IAAI,CAAC,MAAM;AAAA,EACnE;AACA,MAAI,oBAAoB,SAAS,GAAG;AAElC,UAAM,gBAAgB,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AACjE,UAAM,YAAY,KAAK,aAAa;AAEpC,QAAI,eAA6B,CAAC;AAClC,QAAI,SAAS;AACb,eAAS;AACP,YAAM,eAAe,MAAM;AAAA,QAAI,CAAC,MAC9B,iBAAiB,EAAE,MAAM,EAAE,MAAM,SAAS,cAAc,IAAI,EAAE,IAAI,KAAK,EAAE,MAAM,CAAC;AAAA,MAClF;AAKA,UAAI,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA,KAAK;AAAA,MACP;AACA,UAAI,iBAAiB;AACrB,UAAI,UAAU;AACZ,cAAM,MAAM,cAAc,OAAO,QAAQ;AACzC,gBAAQ,IAAI;AACZ,yBAAiB,IAAI;AAAA,MACvB;AACA,qBAAe;AAEf,UAAI,UAAU,WAAW;AACvB,0BAAkB;AAClB;AAAA,MACF;AACA,YAAM,cAAc,oBAAoB,OAAO,CAAC,MAAM,cAAc,IAAI,CAAC,CAAC;AAC1E,UAAI,aAAa;AACjB,iBAAW,CAAC,MAAM,KAAK,KAAK,aAAa;AACvC,cAAM,SAAS,cAAc,IAAI,IAAI;AACrC,YAAI,WAAW,OAAW;AAC1B,cAAM,EAAE,MAAM,QAAQ,IAAI,kBAAkB,QAAQ,KAAK;AACzD,YAAI,SAAS,QAAQ;AACnB,wBAAc,IAAI,MAAM,IAAI;AAC5B,mBAAS;AACT,uBAAa;AAAA,QACf;AAAA,MACF;AACA,UAAI,CAAC,YAAY;AACf,0BAAkB;AAClB;AAAA,MACF;AACA;AAAA,IACF;AAGA,eAAW,KAAK,OAAO;AACrB,YAAM,IAAI,cAAc,IAAI,EAAE,IAAI;AAClC,UAAI,MAAM,UAAa,MAAM,EAAE,OAAO;AACpC,UAAE,QAAQ;AACV,UAAE,UAAU,EAAE,UAAU,EAAE;AAAA,MAC5B;AAAA,IACF;AACA,cAAU,KAAK,GAAG,YAAY;AAAA,EAChC;AAEA,SAAO,EAAE,OAAO,aAAa,WAAW,OAAO,eAAe;AAChE;AAEA,SAAS,uBACP,cACA,cACA,eACA,MACc;AACd,QAAM,cAAc;AAAA,IAClB,SAAS,KAAK;AAAA,IACd,MAAM,KAAK;AAAA,EACb;AAEA,QAAM,aAAqB,CAAC;AAC5B,QAAM,YAAoB,CAAC;AAC3B,aAAWA,SAAQ,cAAc;AAC/B,UAAM,MAAM,gBAAgB,aAAaA,MAAK,KAAK,IAAI,CAAC;AACxD,QAAI,QAAQ,MAAO;AACnB,QAAI,QAAQ,QAAS,YAAW,KAAKA,KAAI;AAAA,QACpC,WAAU,KAAKA,KAAI;AAAA,EAC1B;AAEA,QAAM,cAA4B,CAAC;AACnC,MAAI,WAAW,SAAS,GAAG;AACzB,gBAAY;AAAA,MACV,GAAG,eAAe,YAAY,aAAa;AAAA,QACzC,UAAU;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,UAAU,SAAS,GAAG;AACxB,gBAAY;AAAA,MACV,GAAG,eAAe,WAAW,aAAa;AAAA,QACxC,UAAU;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAkBA,SAAS,eAAe,KAAgD;AACtE,QAAM,cAAcC,SAAQ,GAAG;AAC/B,QAAM,cAAc,oBAAI,IAAqB;AAC7C,SAAO,CAAC,iBAAkC;AACxC,UAAM,SAAS,YAAY,IAAI,YAAY;AAC3C,QAAI,WAAW,OAAW,QAAO;AACjC,UAAM,MAAMA,SAAQ,aAAa,YAAY;AAC7C,UAAM,MAAM,SAAS,aAAa,GAAG;AACrC,UAAM,cACJ,QAAQ,MAAM,QAAQ,QAAQ,IAAI,WAAW,KAAK,GAAG,EAAE,KAAK,WAAW,GAAG;AAC5E,QAAI;AACJ,QAAI,aAAa;AACf,eAAS;AAAA,IACX,OAAO;AACL,UAAI;AACF,iBAASC,UAAS,KAAK,EAAE,gBAAgB,MAAM,CAAC,GAAG,OAAO,KAAK;AAAA,MACjE,QAAQ;AAEN,iBAAS;AAAA,MACX;AAAA,IACF;AACA,gBAAY,IAAI,cAAc,MAAM;AACpC,WAAO;AAAA,EACT;AACF;AAQA,SAAS,eACP,cACA,qBACA,cACA,eACA,KACc;AACd,QAAM,aAAa,eAAe,GAAG;AACrC,QAAM,aAA4B,CAAC;AACnC,QAAM,YAA2B,CAAC;AAClC,aAAWF,SAAQ,qBAAqB;AACtC,UAAM,MAAM,gBAAgB,aAAaA,MAAK,KAAK,IAAI,CAAC;AACxD,QAAI,QAAQ,QAAS,YAAW,KAAKA,KAAI;AAAA,aAChC,QAAQ,OAAQ,WAAU,KAAKA,KAAI;AAAA,EAC9C;AAEA,QAAM,qBAAmC,CAAC;AAC1C,MAAI,WAAW,SAAS,GAAG;AACzB,uBAAmB;AAAA,MACjB,GAAG,kBAAkB,YAAY,cAAc;AAAA,QAC7C,UAAU;AAAA,QACV;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,UAAU,SAAS,GAAG;AACxB,uBAAmB;AAAA,MACjB,GAAG,kBAAkB,WAAW,cAAc;AAAA,QAC5C,UAAU;AAAA,QACV;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAOA,SAAO,mCAAmC,oBAAoB,YAAY;AAC5E;AAUA,SAAS,mCACP,aACA,cACc;AACd,MAAI,YAAY,WAAW,EAAG,QAAO,CAAC;AAEtC,QAAM,aAAa,IAAI,IAAI,aAAa,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAC/D,QAAM,cAAc,oBAAI,IAAmC;AAC3D,QAAM,WAAW,CAAC,SAAwC;AACxD,UAAM,SAAS,YAAY,IAAI,IAAI;AACnC,QAAI,WAAW,OAAW,QAAO;AACjC,UAAM,OAAO,WAAW,IAAI,IAAI;AAKhC,UAAM,QACJ,QAAQ,KAAK,QAAQ,SAAS,gBAAgB,IAC1C,kBAAkB,KAAK,KAAK,KAAK,IAAI,IACrC;AACN,gBAAY,IAAI,MAAM,KAAK;AAC3B,WAAO;AAAA,EACT;AAEA,SAAO,YAAY,OAAO,CAAC,MAAM;AAC/B,QAAI,EAAE,aAAa,yBAA0B,QAAO;AACpD,UAAM,QAAQ,SAAS,EAAE,IAAI;AAC7B,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,CAAC,aAAa,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI;AAAA,EACrD,CAAC;AACH;AAEA,SAAS,gBAAgB,QAAoD;AAC3E,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,SAAO,OAAO,CAAC;AACjB;AAOA,SAAS,qBACP,cACyC;AACzC,QAAM,MAA+C,CAAC;AACtD,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,YAAY,GAAG;AAGzD,QAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,CAAC,EAAG,KAAI,IAAI,IAAI,OAAO,CAAC;AAAA,EAC9D;AACA,SAAO;AACT;;;AQvlBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA,EACE,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,sBAAwB;AAAA,EACxB,YAAc;AAAA,IACZ,OAAS;AAAA,MACP,MAAQ;AAAA,MACR,aAAe;AAAA,IACjB;AAAA,EACF;AACF;;;ACHA,SAAS,cAAc,OAA+B;AACpD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,iBAAiB,MAAM;AACzB,QAAI,OAAO,MAAM,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC1C,WAAO,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EACxC;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,GAAoB;AAC9C,MAAI,CAAC,sBAAsB,KAAK,CAAC,EAAG,QAAO;AAC3C,QAAM,QAAQ,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AACrC,QAAM,CAAC,MAAM,OAAO,GAAG,IAAI;AAC3B,MAAI,SAAS,UAAa,UAAU,UAAa,QAAQ,OAAW,QAAO;AAC3E,QAAM,IAAI,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;AACjD,SACE,EAAE,eAAe,MAAM,QACvB,EAAE,YAAY,MAAM,QAAQ,KAC5B,EAAE,WAAW,MAAM;AAEvB;AAKA,IAAM,SAAiC;AAAA,EACrC,KAAK;AAAA,EAAG,SAAS;AAAA,EACjB,KAAK;AAAA,EAAG,UAAU;AAAA,EAClB,KAAK;AAAA,EAAG,OAAO;AAAA,EACf,KAAK;AAAA,EAAG,OAAO;AAAA,EACf,KAAK;AAAA,EACL,KAAK;AAAA,EAAG,MAAM;AAAA,EACd,KAAK;AAAA,EAAG,MAAM;AAAA,EACd,KAAK;AAAA,EAAG,QAAQ;AAAA,EAChB,KAAK;AAAA,EAAG,MAAM;AAAA,EAAG,WAAW;AAAA,EAC5B,KAAK;AAAA,EAAI,SAAS;AAAA,EAClB,KAAK;AAAA,EAAI,UAAU;AAAA,EACnB,KAAK;AAAA,EAAI,UAAU;AACrB;AAGA,SAAS,IAAI,GAAW,OAAuB;AAC7C,SAAO,OAAO,CAAC,EAAE,SAAS,OAAO,GAAG;AACtC;AAOA,SAAS,SAAS,MAAc,OAAe,KAA4B;AACzE,MAAI,QAAQ,KAAK,QAAQ,MAAM,MAAM,KAAK,MAAM,GAAI,QAAO;AAC3D,QAAM,MAAM,GAAG,IAAI,MAAM,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC;AAC3D,SAAO,mBAAmB,GAAG,IAAI,MAAM;AACzC;AAUA,SAAS,eAAe,KAA4B;AAClD,QAAM,IAAI,IAAI,KAAK;AAInB,QAAM,UAAU,uCAAuC,KAAK,CAAC;AAC7D,MAAI,SAAS;AACX,WAAO,SAAS,OAAO,QAAQ,CAAC,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CAAC;AAAA,EAC5E;AAGA,QAAM,QAAQ,yCAAyC,KAAK,CAAC;AAC7D,MAAI,OAAO;AACT,UAAM,QAAQ,OAAO,MAAM,CAAC,EAAG,YAAY,CAAC;AAC5C,WAAO,QAAQ,SAAS,OAAO,MAAM,CAAC,CAAC,GAAG,OAAO,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI;AAAA,EACvE;AAGA,QAAM,QAAQ,2CAA2C,KAAK,CAAC;AAC/D,MAAI,OAAO;AACT,UAAM,QAAQ,OAAO,MAAM,CAAC,EAAG,YAAY,CAAC;AAC5C,WAAO,QAAQ,SAAS,OAAO,MAAM,CAAC,CAAC,GAAG,OAAO,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI;AAAA,EACvE;AAEA,SAAO;AACT;AAEA,IAAM,OAAiC;AAAA,EACrC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe,CAAC,MAAM,MAAM,IAAI;AAAA,IAChC,MAAM;AAAA,MACJ,aAAa;AAAA,MACb,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,aAAa;AAAA,MACb,aAAa;AAAA,IACf;AAAA,IACA,aAAa;AAAA,MACX,aACE;AAAA,MACF,aAAa;AAAA,IACf;AAAA;AAAA;AAAA;AAAA,IAIA,SAAS;AAAA,IACT,gBAAgB;AAAA,MACd,OAAO;AAAA,IACT;AAAA,IACA;AAAA,EACF;AAAA,EACA,OAAO,SAAS;AACd,UAAM,OAAO,QAAQ;AACrB,UAAM,YAAY,QAAQ,QAAQ;AAElC,QAAI,CAAC,MAAM;AACT,cAAQ,OAAO;AAAA,QACb,WAAW;AAAA,QACX,MAAM,EAAE,OAAO,UAAU;AAAA,MAC3B,CAAC;AACD;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,SAAS;AAC1B,UAAM,UAAU,cAAc,GAAG;AACjC,QAAI,YAAY,MAAM;AACpB,cAAQ,OAAO;AAAA,QACb,WAAW;AAAA,QACX,MAAM,EAAE,OAAO,UAAU;AAAA,MAC3B,CAAC;AACD;AAAA,IACF;AAEA,QAAI,CAAC,mBAAmB,OAAO,GAAG;AAKhC,YAAM,MAAM,QAAQ,cAAc,SAAS;AAK3C,YAAM,aAAa,QAAQ,mBAAmB,SAAS;AACvD,YAAM,aAAa,aAAa,eAAe,OAAO,IAAI;AAC1D,YAAM,MACJ,eAAe,QAAQ,eAAe,WAAW,aAC7C,CAAC,UACC,MAAM,aAAa,CAAC,WAAW,OAAO,WAAW,GAAG,GAAG,UAAU,IACnE;AACN,cAAQ,OAAO;AAAA,QACb,WAAW;AAAA,QACX,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,QACrB,MAAM,EAAE,MAAM,QAAQ;AAAA,QACtB,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAO,uBAAQ;;;AC/Kf,SAAS,gBAAgB;;;ACAzB,OAAO,eAAe;AAGtB,IAAM,mBAAmB;AAYzB,IAAM,QAAQ,oBAAI,IAAoB;AAE/B,SAAS,gBACd,SACA,UACA,YACQ;AACR,QAAM,SAAS,MAAM,IAAI,OAAO;AAChC,MAAI,OAAQ,QAAO;AAEnB,MAAI;AACJ,MAAI;AACF,YAAQ,IAAI,OAAO,OAAO;AAAA,EAC5B,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,MACA,WAAW,UAAU,wCAAyC,IAAc,OAAO;AAAA,IACrF;AAAA,EACF;AACA,MAAI,CAAC,UAAU,OAAO,EAAE,OAAO,iBAAiB,CAAC,GAAG;AAClD,UAAM,IAAI;AAAA,MACR;AAAA,MACA,WAAW,UAAU,MAAM,OAAO;AAAA,IACpC;AAAA,EACF;AACA,QAAM,IAAI,SAAS,KAAK;AACxB,SAAO;AACT;;;AC1CA,IAAAG,kBAAA;AAAA,EACE,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,sBAAwB;AAAA,EACxB,YAAc;AAAA,IACZ,SAAW;AAAA,MACT,MAAQ;AAAA,MACR,aAAe;AAAA,IACjB;AAAA,EACF;AACF;;;AFDA,IAAMC,QAAoC;AAAA,EACxC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe,CAAC,MAAM,MAAM,IAAI;AAAA,IAChC,MAAM;AAAA,MACJ,aAAa;AAAA,MACb,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,iBACE;AAAA,IACJ;AAAA,IACA,aAAa;AAAA;AAAA;AAAA,MAGX,iBACE;AAAA,IACJ;AAAA,IACA,gBAAgB;AAAA,MACd,SAAS;AAAA,IACX;AAAA,IACA,QAAAC;AAAA,EACF;AAAA,EACA,OAAO,SAAS;AACd,UAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,UAAM,QAAQ,gBAAgB,SAAS,wBAAwB,SAAS;AACxE,UAAM,WAAW,SAAS,QAAQ,KAAK,IAAI;AAC3C,QAAI,CAAC,MAAM,KAAK,QAAQ,GAAG;AACzB,cAAQ,OAAO;AAAA,QACb,WAAW;AAAA,QACX,MAAM,EAAE,UAAU,UAAU,QAAQ;AAAA,MACtC,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAO,0BAAQD;;;AG/Cf,SAAS,WAAAE,UAAS,aAAa;;;ACA/B,IAAAC,kBAAA;AAAA,EACE,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,sBAAwB;AAAA,EACxB,YAAc,CAAC;AACjB;;;ADGA,IAAM,iBAAiB;AAQvB,SAAS,WAAW,MAAmD;AACrE,SACE,KAAK,SAAS,UACd,SAAS,QACT,OAAQ,KAA0B,QAAQ;AAE9C;AAEA,SAAS,mBAAmB,KAAsB;AAChD,MAAI,QAAQ,MAAM,IAAI,WAAW,GAAG,EAAG,QAAO;AAC9C,SAAO,eAAe,KAAK,GAAG;AAChC;AAEA,SAAS,oBAAoB,KAAqB;AAChD,QAAM,OAAO,IAAI,QAAQ,GAAG;AAC5B,QAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7B,MAAI,MAAM;AACV,MAAI,UAAU,OAAO,QAAQ,MAAM,QAAQ,KAAM,OAAM;AACvD,SAAO,QAAQ,KAAK,MAAM,IAAI,MAAM,GAAG,GAAG;AAC5C;AAOA,SAAS,WAAW,MAAsB;AACxC,MAAI,CAAC,KAAK,SAAS,GAAG,EAAG,QAAO;AAChC,MAAI;AACF,WAAO,mBAAmB,IAAI;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gBAAgB,SAAiB,KAAqB;AAG7D,MAAI,IAAI,WAAW,GAAG,EAAG,QAAO,IAAI,MAAM,CAAC;AAC3C,SAAO,MAAM,UAAU,MAAM,KAAK,SAAS,GAAG,CAAC;AACjD;AAEA,SAAS,aAAa,MAAiB,KAA2B;AAChE,MAAI,WAAW,IAAI,EAAG,KAAI,KAAK,IAAI;AACnC,MAAI,cAAc,QAAQ,MAAM,QAAQ,KAAK,QAAQ,GAAG;AACtD,eAAW,SAAS,KAAK,UAAU;AACjC,mBAAa,OAAoB,GAAG;AAAA,IACtC;AAAA,EACF;AACF;AAEA,IAAMC,QAA0C;AAAA,EAC9C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe,CAAC,MAAM,MAAM,IAAI;AAAA,IAChC,MAAM;AAAA,MACJ,aACE;AAAA,MACF,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,YACE;AAAA,IACJ;AAAA,IACA,aAAa;AAAA,MACX,YACE;AAAA,IACJ;AAAA,IACA,gBAAgB,CAAC;AAAA,IACjB,QAAAC;AAAA,EACF;AAAA,EACA,MAAM,SAAS;AACb,UAAM,aAAa,oBAAI,IAAY;AACnC,eAAW,QAAQ,QAAQ,OAAO;AAChC,iBAAW,IAAI,KAAK,IAAI;AAAA,IAC1B;AAEA,eAAW,QAAQ,QAAQ,OAAO;AAChC,YAAM,QAAwB,CAAC;AAC/B,mBAAa,KAAK,KAAK,KAAK;AAE5B,iBAAW,QAAQ,OAAO;AACxB,cAAM,MAAM,KAAK;AACjB,YAAI,mBAAmB,GAAG,EAAG;AAE7B,cAAM,WAAW,WAAW,oBAAoB,GAAG,CAAC;AACpD,YAAI,aAAa,GAAI;AAErB,cAAM,UAAUC,SAAQ,KAAK,IAAI;AACjC,cAAM,eAAe,gBAAgB,SAAS,QAAQ;AAQtD,cAAM,SACJ,WAAW,IAAI,YAAY,MAC1B,QAAQ,aAAa,YAAY,KAAK;AAEzC,YAAI,CAAC,QAAQ;AAIX,gBAAM,QAAQ,KAAK,UAAU;AAC7B,kBAAQ,OAAO;AAAA,YACb,WAAW;AAAA,YACX,MAAM,KAAK;AAAA,YACX,GAAI,QACA,EAAE,KAAK,EAAE,MAAM,MAAM,MAAM,QAAQ,MAAM,OAAO,EAAE,IAClD,CAAC;AAAA,YACL,MAAM,EAAE,KAAK,aAAa;AAAA,UAC5B,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,0BAAQF;;;AE1If,SAAS,YAAAG,iBAAgB;;;ACAzB,IAAAC,kBAAA;AAAA,EACE,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,sBAAwB;AAAA,EACxB,YAAc,CAAC;AACjB;;;ADGA,IAAM,eAAe;AAErB,IAAMC,QAAiD;AAAA,EACrD,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe,CAAC,MAAM,MAAM,IAAI;AAAA,IAChC,MAAM;AAAA,MACJ,aAAa;AAAA,MACb,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,iBAAiB;AAAA,IACnB;AAAA,IACA,aAAa;AAAA,MACX,iBACE;AAAA,IACJ;AAAA,IACA,gBAAgB,CAAC;AAAA,IACjB,QAAAC;AAAA,EACF;AAAA,EACA,MAAM,SAAS;AACb,UAAM,WAAW,oBAAI,IAAsB;AAC3C,eAAW,QAAQ,QAAQ,OAAO;AAChC,YAAM,QAAQ,aAAa,KAAKC,UAAS,KAAK,IAAI,CAAC;AACnD,UAAI,CAAC,MAAO;AACZ,YAAM,SAAS,MAAM,CAAC;AACtB,UAAI,WAAW,OAAW;AAC1B,YAAM,QAAQ,SAAS,IAAI,MAAM,KAAK,CAAC;AACvC,YAAM,KAAK,KAAK,IAAI;AACpB,eAAS,IAAI,QAAQ,KAAK;AAAA,IAC5B;AAEA,eAAW,CAAC,QAAQ,KAAK,KAAK,UAAU;AACtC,UAAI,MAAM,SAAS,GAAG;AACpB,cAAM,WAAW,MAAM,KAAK,IAAI;AAChC,mBAAW,QAAQ,OAAO;AACxB,kBAAQ,OAAO;AAAA,YACb,WAAW;AAAA,YACX;AAAA,YACA,MAAM,EAAE,QAAQ,OAAO,SAAS;AAAA,UAClC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,iCAAQF;;;AEzDf,SAAS,YAAAG,iBAAgB;;;ACAzB,IAAAC,kBAAA;AAAA,EACE,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,sBAAwB;AAAA,EACxB,YAAc,CAAC;AACjB;;;ADGA,IAAMC,gBAAe;AAErB,SAAS,QAAQ,GAAmB;AAClC,SAAO,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACrC;AAEA,IAAMC,QAA2C;AAAA,EAC/C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe,CAAC,MAAM,MAAM,IAAI;AAAA,IAChC,MAAM;AAAA,MACJ,aACE;AAAA,MACF,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,cACE;AAAA,IACJ;AAAA;AAAA;AAAA;AAAA,IAIA,gBAAgB,CAAC;AAAA,IACjB,QAAAC;AAAA,EACF;AAAA,EACA,MAAM,SAAS;AAEb,UAAM,eAAe,oBAAI,IAAoB;AAC7C,eAAW,QAAQ,QAAQ,OAAO;AAChC,YAAM,QAAQF,cAAa,KAAKG,UAAS,KAAK,IAAI,CAAC;AACnD,UAAI,CAAC,QAAQ,CAAC,EAAG;AACjB,YAAM,IAAI,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AACtC,UAAI,CAAC,OAAO,MAAM,CAAC,EAAG,cAAa,IAAI,GAAG,KAAK,IAAI;AAAA,IACrD;AACA,QAAI,aAAa,OAAO,EAAG;AAE3B,UAAM,gBAAgB,MAAM,KAAK,aAAa,KAAK,CAAC,EAAE;AAAA,MACpD,CAAC,GAAG,MAAM,IAAI;AAAA,IAChB;AAEA,aAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,YAAM,OAAO,cAAc,IAAI,CAAC;AAChC,YAAM,OAAO,cAAc,CAAC;AAC5B,UAAI,SAAS,UAAa,SAAS,OAAW;AAC9C,UAAI,SAAS,OAAO,EAAG;AAEvB,YAAM,iBAA2B,CAAC;AAClC,eAAS,IAAI,OAAO,GAAG,IAAI,MAAM,KAAK;AACpC,uBAAe,KAAK,QAAQ,CAAC,CAAC;AAAA,MAChC;AAEA,YAAM,WAAW,aAAa,IAAI,IAAI;AACtC,UAAI,CAAC,SAAU;AAEf,cAAQ,OAAO;AAAA,QACb,WAAW;AAAA,QACX,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,MAAM,QAAQ,IAAI;AAAA,UAClB,IAAI,QAAQ,IAAI;AAAA,UAChB,SAAS,eAAe,KAAK,IAAI;AAAA,QACnC;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAO,2BAAQF;;;AE7Ef,SAAS,YAAAG,iBAAgB;;;ACAzB,IAAAC,kBAAA;AAAA,EACE,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,sBAAwB;AAAA,EACxB,YAAc;AAAA,IACZ,UAAY;AAAA,MACV,MAAQ;AAAA,MACR,OAAS,EAAE,MAAQ,SAAS;AAAA,MAC5B,aAAe;AAAA,IACjB;AAAA,IACA,WAAa;AAAA,MACX,MAAQ;AAAA,MACR,MAAQ,CAAC,SAAS,YAAY;AAAA,MAC9B,aAAe;AAAA,IACjB;AAAA,EACF;AACF;;;ADPA,IAAMC,QAAsC;AAAA,EAC1C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe,CAAC,MAAM,MAAM,IAAI;AAAA,IAChC,MAAM;AAAA,MACJ,aAAa;AAAA,MACb,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,gBAAgB;AAAA,IAClB;AAAA,IACA,aAAa;AAAA,MACX,gBACE;AAAA,IACJ;AAAA,IACA,gBAAgB;AAAA,MACd,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,WAAW;AAAA,IACb;AAAA,IACA,QAAAC;AAAA,EACF;AAAA,EACA,OAAO,SAAwB;AAC7B,UAAM,OAAiB,CAAC;AACxB,WAAO;AAAA,MACL,OAAO;AAAA,QACL,QAAQ,MAAM;AACZ,cAAI,KAAK,SAAS,UAAW;AAC7B,eAAK,KAAKC,UAAS,IAAI,EAAE,KAAK,CAAC;AAAA,QACjC;AAAA,MACF;AAAA,MACA,MAAM;AAAA,QACJ,OAAO;AACL,gBAAM,EAAE,UAAU,UAAU,IAAI,QAAQ;AACxC,qBAAW,WAAW,UAAU;AAC9B,kBAAM,UAAU,KAAK;AAAA,cAAK,CAAC,YACzB,cAAc,eACV,QAAQ,WAAW,OAAO,IAC1B,YAAY;AAAA,YAClB;AACA,gBAAI,CAAC,SAAS;AACZ,sBAAQ,OAAO;AAAA,gBACb,WAAW;AAAA,gBACX,MAAM,EAAE,SAAS,OAAO,CAAC,GAAG,IAAI,EAAE;AAAA,cACpC,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,4BAAQF;;;AEnEf,IAAAG,kBAAA;AAAA,EACE,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,sBAAwB;AAAA,EACxB,YAAc;AAAA,IACZ,QAAU;AAAA,MACR,MAAQ;AAAA,MACR,OAAS,EAAE,MAAQ,SAAS;AAAA,MAC5B,aAAe;AAAA,IACjB;AAAA,IACA,cAAgB;AAAA,MACd,MAAQ;AAAA,MACR,OAAS,EAAE,MAAQ,SAAS;AAAA,MAC5B,aAAe;AAAA,IACjB;AAAA,IACA,eAAiB;AAAA,MACf,MAAQ;AAAA,MACR,aAAe;AAAA,IACjB;AAAA,EACF;AACF;;;ACPA,IAAM,mBAA2C;AAAA,EAC/C,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,YAAY;AACd;AAGA,SAAS,YAAY,OAAuB;AAC1C,SAAO,MACJ,MAAM,GAAG,EACT,IAAI,CAAC,QAAQ,iBAAiB,GAAG,KAAK,GAAG,EACzC,KAAK,GAAG;AACb;AAGA,SAAS,eAAe,GAAW,GAAmB;AACpD,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,WAAO,IAAI,EAAE,UAAU,KAAK,KAAK,EAAE,CAAC,CAAE,EAAG;AACzC,WAAO,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,EAAE,CAAC,CAAE,EAAG;AAAA,EAC5C;AACA,SAAO,EAAE,MAAM,CAAC;AAClB;AAWA,SAAS,cACP,QACA,QACA,cACoB;AACpB,QAAM,WAAW,YAAY,OAAO,YAAY,CAAC;AAGjD,QAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,CAAC,MAAM,EAAE,YAAY,MAAM,QAAQ,CAAC,CAAC;AAC7E,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,IAAI;AAAA,EAC1C;AACA,MAAI,MAAM,SAAS,EAAG,QAAO;AAG7B,QAAM,WAAW;AAAA,IACf,GAAG,IAAI;AAAA,MACL,aAAa,OAAO,CAAC,MAAM;AACzB,cAAM,KAAK,EAAE,YAAY;AACzB,eAAO,aAAa,MAAM,SAAS,WAAW,GAAG,EAAE,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AACA,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,SAAS,SAAS,CAAC;AACzB,QAAM,OAAO,eAAe,QAAQ,OAAO,MAAM,GAAG,EAAE,MAAM;AAC5D,QAAM,cAAc,GAAG,MAAM,GAAG,IAAI;AACpC,SAAO,gBAAgB,SAAS,cAAc;AAChD;AAEA,IAAMC,QAAgC;AAAA,EACpC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe,CAAC,MAAM,MAAM,IAAI;AAAA,IAChC,MAAM;AAAA,MACJ,aAAa;AAAA,MACb,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,eAAe;AAAA,MACf,eAAe;AAAA,IACjB;AAAA;AAAA;AAAA;AAAA,IAIA,aAAa;AAAA,MACX,eACE;AAAA,IACJ;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,SAAS;AAAA,IACT,gBAAgB;AAAA,MACd,QAAQ,CAAC,YAAY,YAAY,YAAY,YAAY;AAAA,MACzD,cAAc,CAAC,eAAe;AAAA,MAC9B,eAAe;AAAA,IACjB;AAAA,IACA,QAAAC;AAAA,EACF;AAAA,EACA,OAAO,SAAS;AACd,UAAM,EAAE,QAAQ,cAAc,cAAc,IAAI,QAAQ;AAGxD,UAAM,UAAU,CAAC,GAAG,QAAQ,GAAG,aAAa,IAAI,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC;AAElE,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,QAAQ,OAAO,KAAK,WAAW,UAAU;AAC5C,cAAQ,OAAO,EAAE,WAAW,iBAAiB,MAAM,EAAE,QAAQ,EAAE,CAAC;AAChE;AAAA,IACF;AAEA,UAAM,SAAS,KAAK;AACpB,UAAM,gBAAgB,gBAAgB,SAAS,OAAO,YAAY;AAClE,UAAM,gBAAgB,gBAClB,SACA,OAAO,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AACrC,UAAM,kBAAkB,gBACpB,eACA,aAAa,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AAE3C,UAAM,aAAa,cAAc,SAAS,aAAa;AACvD,UAAM,cAAc,gBAAgB;AAAA,MAAK,CAAC,MACxC,cAAc,WAAW,CAAC;AAAA,IAC5B;AAEA,QAAI,CAAC,cAAc,CAAC,aAAa;AAK/B,YAAM,MAAM,QAAQ,aAAa;AAKjC,YAAM,YAAY,cAAc,QAAQ,QAAQ,YAAY;AAC5D,YAAM,aAAa,QAAQ,kBAAkB;AAC7C,YAAM,MACJ,cAAc,UAAa,cAAc,UAAU,aAC/C,CAAC,UACC,MAAM,aAAa,CAAC,WAAW,OAAO,WAAW,GAAG,GAAG,SAAS,IAClE;AACN,cAAQ,OAAO;AAAA,QACb,WAAW;AAAA,QACX,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,QACrB,MAAM,EAAE,QAAQ,QAAQ;AAAA,QACxB,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,IAAO,sBAAQD;;;AClKf,SAAS,YAAAE,iBAAgB;;;ACAzB,IAAAC,kBAAA;AAAA,EACE,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,sBAAwB;AAAA,EACxB,YAAc,CAAC;AACjB;;;ADIA,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AACtB,IAAM,iBAAiB;AAEvB,SAAS,iBAAiB,MAA6B;AACrD,QAAM,QAAQ,iBAAiB,KAAKC,UAAS,IAAI,CAAC;AAClD,SAAO,QAAQ,CAAC,KAAK;AACvB;AAEA,SAAS,cAAc,OAA0B;AAC/C,MAAI,OAAO,UAAU,SAAU,QAAO,CAAC,KAAK;AAC5C,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;AAAA,EAC/D;AACA,SAAO,CAAC;AACV;AAEA,SAAS,aAAa,GAAmB;AACvC,SAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;AAgBA,SAAS,sBACP,SACA,KACA,OACyC;AACzC,QAAM,QAAQ,kBAAkB,OAAO;AACvC,MAAI,UAAU,EAAG,QAAO;AAExB,QAAM,UAAU,QAAQ,MAAM,GAAG,KAAK;AAGtC,QAAM,aAAa,yBAAyB,KAAK,OAAO;AACxD,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,YAAY,WAAW,CAAC;AAC9B,QAAM,SAAS,WAAW,SAAS,WAAW,CAAC,EAAE,SAAS,UAAU;AAGpE,MAAI,QAAQ,MAAM,QAAQ,SAAS,CAAC,MAAM,MAAO,QAAO;AAMxD,QAAM,aAAa,IAAI,OAAO,cAAc,aAAa,GAAG,CAAC,YAAY,GAAG;AAC5E,MAAI,WAAW,KAAK,QAAQ,MAAM,GAAG,MAAM,CAAC,EAAG,QAAO;AAEtD,QAAM,MAAM,UAAU,SAAS,MAAM,IAAI,SAAS;AAClD,SAAO,EAAE,QAAQ,MAAM,GAAG,GAAG,KAAK,KAAK,GAAG,GAAG,GAAG;AAClD;AAUA,SAAS,iBACP,QACA,KACA,OAC0C;AAC1C,QAAM,OAAO,sBAAsB,OAAO,SAAS,KAAK,KAAK;AAC7D,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,CAAC,UAAU,MAAM,SAAS,KAAK,QAAQ,KAAK,IAAI;AACzD;AAEA,IAAMC,QAAoD;AAAA,EACxD,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe,CAAC,MAAM,IAAI;AAAA,IAC1B,MAAM;AAAA,MACJ,aACE;AAAA,MACF,KAAK;AAAA,MACL,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,kBACE;AAAA;AAAA;AAAA;AAAA;AAAA,MAKF,sBACE;AAAA,IACJ;AAAA,IACA,aAAa;AAAA,MACX,kBACE;AAAA,MACF,sBACE;AAAA,IACJ;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,SAAS;AAAA,IACT,gBAAgB,CAAC;AAAA,IACjB,QAAAC;AAAA,EACF;AAAA,EACA,MAAM,SAAS;AAGb,UAAM,WAAW,oBAAI,IAAyB;AAC9C,eAAW,QAAQ,QAAQ,OAAO;AAChC,YAAM,MAAM,iBAAiB,KAAK,IAAI;AACtC,UAAI,IAAK,UAAS,IAAI,OAAO,GAAG,IAAI,IAAI;AAAA,IAC1C;AAGA,eAAW,QAAQ,QAAQ,OAAO;AAChC,YAAM,KAAK,KAAK;AAChB,UAAI,CAAC,GAAI;AACT,YAAM,aAAa,iBAAiB,KAAK,IAAI;AAC7C,UAAI,CAAC,WAAY;AACjB,YAAM,UAAU,OAAO,UAAU;AAEjC,YAAM,cAAc,cAAc,GAAG,aAAa,CAAC;AACnD,YAAM,eAAe,cAAc,GAAG,cAAc,CAAC;AAIrD,iBAAW,OAAO,aAAa;AAC7B,cAAM,SAAS,SAAS,IAAI,GAAG;AAC/B,YAAI,CAAC,QAAQ;AACX,kBAAQ,OAAO;AAAA,YACb,WAAW;AAAA,YACX,MAAM,KAAK;AAAA,YACX,MAAM,EAAE,KAAK,WAAW,cAAc;AAAA,UACxC,CAAC;AACD;AAAA,QACF;AACA,cAAM,aAAa,cAAc,OAAO,cAAc,cAAc,CAAC;AACrE,YAAI,CAAC,WAAW,SAAS,OAAO,GAAG;AACjC,gBAAM,MAAM,iBAAiB,QAAQ,gBAAgB,OAAO;AAC5D,kBAAQ,OAAO;AAAA,YACb,WAAW;AAAA,YACX,MAAM,OAAO;AAAA,YACb,MAAM;AAAA,cACJ;AAAA,cACA,QAAQ,KAAK;AAAA,cACb,UAAU;AAAA,cACV,WAAW;AAAA,cACX,UAAU;AAAA,YACZ;AAAA,YACA,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,UACvB,CAAC;AAAA,QACH;AAAA,MACF;AAIA,iBAAW,OAAO,cAAc;AAC9B,cAAM,SAAS,SAAS,IAAI,GAAG;AAC/B,YAAI,CAAC,QAAQ;AACX,kBAAQ,OAAO;AAAA,YACb,WAAW;AAAA,YACX,MAAM,KAAK;AAAA,YACX,MAAM,EAAE,KAAK,WAAW,eAAe;AAAA,UACzC,CAAC;AACD;AAAA,QACF;AACA,cAAM,YAAY,cAAc,OAAO,cAAc,aAAa,CAAC;AACnE,YAAI,CAAC,UAAU,SAAS,OAAO,GAAG;AAChC,gBAAM,MAAM,iBAAiB,QAAQ,eAAe,OAAO;AAC3D,kBAAQ,OAAO;AAAA,YACb,WAAW;AAAA,YACX,MAAM,OAAO;AAAA,YACb,MAAM;AAAA,cACJ;AAAA,cACA,QAAQ,KAAK;AAAA,cACb,UAAU;AAAA,cACV,WAAW;AAAA,cACX,UAAU;AAAA,YACZ;AAAA,YACA,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,UACvB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,mCAAQD;;;A/BtLR,IAAM,cAAc,cAAc;AAAA,EACvC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,IAAI,EAAE,KAAK,GAAG;AACZ,UAAM,MAAM,QAAQ,IAAI;AAKxB,UAAM,SAAS,KAAK;AACpB,QAAI,WAAW,UAAa,OAAO,KAAK,MAAM,IAAI;AAChD,cAAQ,MAAM,kBAAkB,MAAM,8BAA8B;AACpE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,WAAW,uBAAuB,GAAG;AAC3C,QAAI,YAAY,KAAK,UAAU,MAAM;AACnC,cAAQ;AAAA,QACN,sCAAsC,QAAQ;AAAA,MAChD;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI;AACJ,QAAI;AACJ,QAAI,WAAW,QAAW;AACxB,eAAS;AACT,qBAAe;AAAA,IACjB,OAAO;AACL,YAAM,WAAW,aAAa,GAAG;AACjC,eAAS,SAAS;AAClB,qBAAe,SAAS;AAAA,IAC1B;AACA,UAAM,cAAc,kBAAkB,KAAK,MAAM;AACjD,UAAM,eAAe,mBAAmB,GAAG;AAC3C,UAAM,aAAa,0BAA0B,YAAY;AAGzD,IAAAE;AAAA,MACEC,SAAQ,KAAK,UAAU;AAAA,MACvB,aAAa,cAAc,EAAE,QAAQ,YAAY,CAAC;AAAA,IACpD;AAKA,QAAI,YAAY,aAAa,YAAY;AACvC,YAAM,UACJ,aAAa,QAAQ,QAAQ,IAAI,aAAa,QAAQ,UAAU;AAClE,cAAQ;AAAA,QACN,UACI,SAAS,QAAQ,8BAA8B,UAAU,8EACzD,sBAAsB,QAAQ,uBAAuB,UAAU;AAAA,MACrE;AAAA,IACF;AAMA,UAAM,QAAQ,aAAaA,SAAQ,KAAK,MAAM,CAAC;AAC/C,QAAI,SAAS;AACb,QAAI,WAAW;AACf,QAAI,MAAM,SAAS,GAAG;AACpB,UAAI;AACF,cAAM,SAAS,eAAe,EAAE,SAAS,CAAC,uBAAuB,EAAE,CAAC;AACpE,cAAM,WAAsB,OAAO,OAAO,aAAY;AACtD,cAAM,SAAS,UAAU;AAAA,UACvB,OAAO;AAAA,UACP,cAAc,OAAO;AAAA,UACrB;AAAA,UACA;AAAA,UACA,OAAO;AAAA,UACP,UAAU;AAAA,QACZ,CAAC;AACD,mBAAW,KAAK,OAAO,aAAa;AAClC,cAAI,EAAE,aAAa,QAAS;AAAA,cACvB;AAAA,QACP;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ;AAAA,UACN,kCAAkC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QACpF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,eAAe,MAAM;AAC3B,UAAM,wBAAwB,eAAe,KAAK,SAAS,WAAW;AACtE,QAAI,KAAK,SAAS,MAAM;AACtB,cAAQ;AAAA,QACN,KAAK;AAAA,UACH;AAAA,YACE,SAAS;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS;AAAA,UACX;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN,cAAc;AAAA,UACZ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;;;AiC5KD,SAAS,YAAAC,iBAAgB;AACzB,OAAO,eAAe;AAkBf,SAAS,aACd,cACA,UACS;AACT,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,OAAOA,UAAS,YAAY;AAClC,aAAW,WAAW,UAAU;AAC9B,QAAI,eAAe,cAAc,MAAM,OAAO,EAAG,QAAO;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,SAAS,eACP,cACA,MACA,SACS;AACT,MAAI,iBAAiB,QAAS,QAAO;AACrC,QAAM,UAAU,UAAU,SAAS,EAAE,KAAK,KAAK,CAAC;AAChD,MAAI,QAAQ,YAAY,EAAG,QAAO;AAClC,MAAI,QAAQ,IAAI,EAAG,QAAO;AAK1B,MAAI,QAAQ,SAAS,GAAG,KAAK,CAAC,QAAQ,WAAW,KAAK,GAAG;AACvD,UAAM,gBAAgB,UAAU,MAAM,OAAO,IAAI,EAAE,KAAK,KAAK,CAAC;AAC9D,QAAI,cAAc,YAAY,EAAG,QAAO;AAAA,EAC1C;AACA,SAAO;AACT;;;ACjDA,OAAO,QAAQ;AA4CR,IAAM,eAAyB;AAAA;AAAA;AAAA,EAGpC,OAAO,aAAa,aAAa;AAC/B,QAAI,YAAY,WAAW,GAAG;AAC5B,aAAO,GAAG,MAAM,mBAAc;AAAA,IAChC;AAEA,UAAM,SAAS,YAAY,WAAW;AACtC,UAAM,QAAkB,CAAC;AAIzB,UAAM,SAAS,IAAI,OAAO,IAAI,KAAK,CAAC;AAEpC,eAAW,CAAC,MAAM,eAAe,KAAK,QAAQ;AAC5C,YAAM,KAAK,GAAG,UAAU,IAAI,CAAC;AAE7B,YAAM,aAAa,oBAAI,IAAoB;AAC3C,iBAAW,KAAK,iBAAiB;AAC/B,cAAM,MACJ,EAAE,aAAa,UACX,GAAG,IAAI,WAAW,IAClB,GAAG,OAAO,WAAW;AAC3B,cAAM,SAAS,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;AAC3C,cAAM,UAAU,cAAc,GAAG,WAAW;AAE5C,cAAM,SAAS,EAAE,UAAU,GAAG,IAAI,oBAAa,IAAI;AACnD,cAAM,KAAK,GAAG,GAAG,GAAG,MAAM,KAAK,OAAO,GAAG,MAAM,EAAE;AACjD,YAAI,EAAE,YAAY;AAChB,gBAAM,KAAK,GAAG,MAAM,GAAG,GAAG,KAAK,UAAK,EAAE,UAAU,EAAE,CAAC,EAAE;AAAA,QACvD;AACA,YAAI,EAAE,WAAW,CAAC,WAAW,IAAI,EAAE,QAAQ,GAAG;AAC5C,qBAAW,IAAI,EAAE,UAAU,EAAE,OAAO;AAAA,QACtC;AAAA,MACF;AAGA,iBAAW,CAAC,UAAU,GAAG,KAAK,YAAY;AACxC,cAAM,KAAK,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG,EAAE,CAAC;AAAA,MAC5C;AACA,YAAM,KAAK,EAAE;AAAA,IACf;AAEA,UAAM,SAAS,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AACjE,UAAM,WAAW,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAClE,UAAM,KAAK,GAAG,KAAK,cAAc,QAAQ,QAAQ,CAAC,CAAC;AAEnD,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AACF;AAEA,SAAS,YAAY,aAA+D;AAClF,QAAM,MAAM,oBAAI,IAA0B;AAC1C,aAAW,KAAK,aAAa;AAC3B,QAAI,CAAC,IAAI,IAAI,EAAE,IAAI,EAAG,KAAI,IAAI,EAAE,MAAM,CAAC,CAAC;AACxC,QAAI,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC;AAAA,EACzB;AACA,SAAO;AACT;AAEA,SAAS,cACP,GACA,aACQ;AACR,QAAMC,QAAO,YAAY,IAAI,EAAE,QAAQ;AACvC,QAAM,WAAWA,OAAM,KAAK,SAAS,EAAE,SAAS,KAAK,EAAE;AACvD,SAAO,YAAY,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC3C;AAEA,SAAS,cAAc,QAAgB,UAA0B;AAC/D,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS,EAAG,OAAM,KAAK,GAAG,MAAM,IAAI,WAAW,IAAI,UAAU,QAAQ,EAAE;AAC3E,MAAI,WAAW,EAAG,OAAM,KAAK,GAAG,QAAQ,IAAI,aAAa,IAAI,YAAY,UAAU,EAAE;AACrF,SAAO,MAAM,KAAK,IAAI;AACxB;AAOO,IAAM,eAAyB;AAAA,EACpC,OAAO,aAAa,aAAa,MAAM;AACrC,UAAM,SAAS,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AACjE,UAAM,WAAW,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAClE,UAAM,UAAU;AAAA,MACd,SAAS;AAAA,MACT,SAAS;AAAA,QACP,OAAO,YAAY;AAAA,QACnB;AAAA,QACA;AAAA;AAAA;AAAA,QAGA,gBAAgB,MAAM,kBAAkB;AAAA;AAAA;AAAA,QAGxC,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,MAC3D;AAAA;AAAA;AAAA,MAGA,GAAI,MAAM,aAAa,SAAY,EAAE,OAAO,KAAK,SAAS,IAAI,CAAC;AAAA,MAC/D,SAAS,YAAY,IAAI,CAAC,OAAO;AAAA,QAC/B,MAAM,EAAE;AAAA,QACR,UAAU,EAAE;AAAA,QACZ,WAAW,EAAE;AAAA,QACb,UAAU,EAAE;AAAA,QACZ,SAAS,cAAc,GAAG,WAAW;AAAA;AAAA;AAAA,QAGrC,YAAY,EAAE;AAAA,QACd,SAAS,EAAE;AAAA;AAAA,QAEX,SAAS,EAAE;AAAA,QACX,MAAM,EAAE,QAAQ,CAAC;AAAA,MACnB,EAAE;AAAA,IACJ;AACA,WAAO,KAAK,UAAU,SAAS,MAAM,CAAC;AAAA,EACxC;AACF;AASO,IAAM,gBAA0B;AAAA,EACrC,OAAO,aAAa,aAAa;AAC/B,UAAM,YAAY,CAAC,GAAG,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACjE,UAAM,YAAY,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAE/D,UAAM,aAAa,UAAU,IAAI,CAAC,SAAS;AACzC,YAAMA,QAAO,YAAY,IAAI,IAAI;AACjC,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,KAAK,QAAQ,iBAAiB,GAAG;AAAA,QACvC,kBAAkB,EAAE,MAAMA,OAAM,KAAK,KAAK,eAAe,KAAK;AAAA,QAC9D,SAASA,OAAM,KAAK,KAAK;AAAA,MAC3B;AAAA,IACF,CAAC;AAED,UAAM,eAAe,YAAY,IAAI,CAAC,OAAO;AAAA,MAC3C,QAAQ,EAAE;AAAA,MACV,WAAW,UAAU,IAAI,EAAE,QAAQ,KAAK;AAAA,MACxC,OAAO,EAAE,aAAa,UAAU,UAAU;AAAA,MAC1C,SAAS,EAAE,MAAM,cAAc,GAAG,WAAW,EAAE;AAAA,MAC/C,WAAW;AAAA,QACT;AAAA,UACE,kBAAkB;AAAA,YAChB,kBAAkB,EAAE,KAAK,EAAE,MAAM,WAAW,YAAY;AAAA,YACxD,QAAQ,EAAE,WAAW,EAAE;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,IACF,EAAE;AAEF,UAAM,QAAQ;AAAA,MACZ,SACE;AAAA,MACF,SAAS;AAAA,MACT,MAAM;AAAA,QACJ;AAAA,UACE,MAAM;AAAA,YACJ,QAAQ;AAAA,cACN,MAAM;AAAA,cACN,gBAAgB;AAAA,cAChB,OAAO;AAAA,YACT;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,UAAU,OAAO,MAAM,CAAC;AAAA,EACtC;AACF;AAGA,SAAS,gBAAgB,GAAmB;AAC1C,SAAO,EAAE,QAAQ,MAAM,KAAK,EAAE,QAAQ,OAAO,KAAK,EAAE,QAAQ,OAAO,KAAK;AAC1E;AAGA,SAAS,iBAAiB,GAAmB;AAC3C,SAAO,gBAAgB,CAAC,EACrB,QAAQ,MAAM,KAAK,EACnB,QAAQ,MAAM,KAAK;AACxB;AASO,IAAM,iBAA2B;AAAA,EACtC,OAAO,aAAa,aAAa,MAAM;AACrC,UAAM,QAAkB,CAAC;AAEzB,eAAW,KAAK,aAAa;AAC3B,YAAM,QAAQ,EAAE,aAAa,UAAU,UAAU;AACjD,YAAM,OAAO,iBAAiB,EAAE,IAAI;AACpC,YAAM,QAAQ,iBAAiB,EAAE,QAAQ;AAIzC,YAAM,WAAW,cAAc,GAAG,WAAW;AAC7C,YAAM,UAAU;AAAA,QACd,EAAE,aAAa,GAAG,QAAQ,WAAM,EAAE,UAAU,KAAK;AAAA,MACnD;AAEA,YAAM,WAAW,EAAE,MAAM,SAAS,EAAE,IAAI,IAAI,KAAK;AACjD,YAAM,KAAK,KAAK,KAAK,SAAS,IAAI,GAAG,QAAQ,UAAU,KAAK,KAAK,OAAO,EAAE;AAAA,IAC5E;AAEA,UAAM,SAAS,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AACjE,UAAM,WAAW,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAClE,UAAM,eAAyB,CAAC;AAChC,QAAI,SAAS,EAAG,cAAa,KAAK,GAAG,MAAM,IAAI,WAAW,IAAI,UAAU,QAAQ,EAAE;AAClF,QAAI,WAAW,EAAG,cAAa,KAAK,GAAG,QAAQ,IAAI,aAAa,IAAI,YAAY,UAAU,EAAE;AAE5F,QAAI,aAAa,WAAW,EAAG,cAAa,KAAK,sBAAsB;AACvE,UAAM,iBAAiB,MAAM,kBAAkB;AAC/C,QAAI,iBAAiB,EAAG,cAAa,KAAK,GAAG,cAAc,qBAAqB;AAChF,UAAM,KAAK,aAAa,KAAK,IAAI,CAAC;AAElC,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AACF;AAEO,IAAM,YAA8C;AAAA,EACzD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AACV;;;AnC5PA,SAASC,SAAQ,GAAmB;AAClC,MAAIC,SAAQ,IAAK,QAAO;AACxB,SAAO,EAAE,MAAMA,IAAG,EAAE,KAAK,GAAG;AAC9B;AAEA,IAAM,MAAM,KAAK;AAAA,EACfC,cAAa,IAAI,IAAI,mBAAmB,YAAY,GAAG,GAAG,MAAM;AAClE;AAEA,IAAM,OAAOC,eAAc;AAAA,EACzB,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,SAAS,IAAI;AAAA,IACb,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aACE;AAAA,MACF,UAAU;AAAA,IACZ;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aACE;AAAA,MACF,SAAS;AAAA,IACX;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,aACE;AAAA,MACF,SAAS;AAAA,IACX;AAAA,IACA,mBAAmB;AAAA,MACjB,MAAM;AAAA,MACN,aACE;AAAA,MACF,SAAS;AAAA,IACX;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,gBAAgB;AAAA,MACd,MAAM;AAAA,MACN,aACE;AAAA,MACF,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,eAAe;AAAA,MACb,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,IAAI,EAAE,KAAK,GAAG;AACZ,UAAM,MAAM,QAAQ,IAAI;AAMxB,QAAI,cAAc;AAClB,UAAM,iBAAiB,KAAK,cAAc;AAC1C,QAAI,mBAAmB,QAAW;AAChC,YAAM,SAAS,OAAO,cAAc;AACpC,UAAI,eAAe,KAAK,MAAM,MAAM,CAAC,OAAO,UAAU,MAAM,GAAG;AAC7D,gBAAQ;AAAA,UACN,2BAA2B,cAAc;AAAA,QAC3C;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,oBAAc;AAAA,IAChB;AAKA,QAAI;AACJ,QAAI,iBAAiB;AACrB,UAAM,YAAY,KAAK;AACvB,QAAI,WAAW;AACb,YAAM,gBAAgBC,SAAQ,KAAK,SAAS;AAC5C,UAAI;AACF,iBAAS,mBAAmB,aAAa;AACzC,yBAAiB;AAAA,MACnB,SAAS,KAAK;AACZ,YAAI,eAAe,yBAAyB;AAC1C,kBAAQ,MAAM,IAAI,OAAO;AAAA,QAC3B,OAAO;AACL,kBAAQ;AAAA,YACN,0BAA0B,SAAS,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,UAC3F;AAAA,QACF;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,OAAO;AACL,eAAS,WAAW,GAAG;AAAA,IACzB;AAKA,QAAI,CAAC,kBAAkB,OAAO,KAAK,OAAO,KAAK,EAAE,WAAW,GAAG;AAC7D,eAAS,eAAe,EAAE,SAAS,CAAC,uBAAuB,EAAE,CAAC;AAAA,IAChE;AAIA,UAAM,cAAe,KAAK,KAA8B,CAAC;AACzD,UAAM,SAAS,YAAY,SAAS,IAAI,cAAc,CAAC,OAAO,MAAM;AAEpE,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,WAAqB,CAAC;AAC5B,eAAW,SAAS,QAAQ;AAC1B,iBAAW,QAAQ,aAAa,KAAK,GAAG;AACtC,YAAI,CAAC,KAAK,IAAI,IAAI,GAAG;AACnB,eAAK,IAAI,IAAI;AACb,mBAAS,KAAK,IAAI;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAIA,UAAM,QAAQ,SAAS;AAAA,MACrB,CAAC,YACC,CAAC,aAAaJ,SAAQK,UAAS,KAAK,OAAO,CAAC,GAAG,OAAO,cAAc;AAAA,IACxE;AAEA,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,UAAU,SAAS,SAAS,MAAM;AACxC,YAAM,OAAO,UAAU,IAAI,KAAK,OAAO,wBAAwB;AAC/D,YAAM,QAAQ,OAAO,WAAW,IAAI,OAAO,CAAC,IAAI,OAAO,KAAK,IAAI;AAChE,cAAQ,IAAI,2BAA2B,KAAK,GAAG,IAAI,EAAE;AACrD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAIA,UAAM,gBAAgB,KAAK,SAAS,OAAO,WAAW;AACtD,UAAM,WAAY,KAAK,WAAW,KAAK,OAAO;AAC9C,UAAM,cAAkC,eACpC;AAAA,MACE,KAAKD,SAAQ,KAAK,QAAQ;AAAA,MAC1B,YAAY,kBAAkB;AAAA,QAC5B,OAAO,OAAO;AAAA,QACd,gBAAgB,OAAO;AAAA,QACvB,aAAa,OAAO;AAAA,MACtB,CAAC;AAAA,MACD,YAAY,IAAI;AAAA,IAClB,IACA;AAIJ,UAAM,eAAe,aAAa,GAAG;AACrC,UAAM,iBAAiB,KAAK,iBAAiB,MAAM;AACnD,UAAM,cAAc,CAAC,kBAAkB,KAAK,aAAa;AACzD,UAAM,WAAW,cAAc,aAAa,YAAY,IAAI;AAG5D,QAAI,eAAe,aAAa,QAAQE,YAAW,YAAY,GAAG;AAChE,cAAQ,MAAM,yBAAyB,CAAC;AAAA,IAC1C;AAEA,UAAM,gBAA2B,OAAO,OAAO,aAAY;AAC3D,UAAM,SAAU,KAAK,UAAU;AAQ/B,UAAM,gBAAgB,CACpB,aACA,gBACA,YACA,QACA,aACU;AACV,YAAM,cAAc,IAAI;AAAA,QACtB,cAAc,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;AAAA,MAC3C;AACA,YAAM,WAAW,UAAU,MAAM;AACjC,UAAI,CAAC,UAAU;AACb,gBAAQ;AAAA,UACN,qBAAqB,MAAM,iBAAiB,OAAO,KAAK,SAAS,EAAE,KAAK,IAAI,CAAC;AAAA,QAC/E;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,YAAM,eAAe,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AACtE,YAAM,aAAa,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AACrE,YAAM,mBAAmB,eAAe,KAAK,eAAe;AAK5D,YAAM,WAAW,KAAK,QAClB,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,IAC/C;AAIJ,YAAM,mBACJ,WAAW,UAAU,SAAS,WAAW,KAAK;AAChD,UAAI,CAAC,kBAAkB;AACrB,gBAAQ;AAAA,UACN,SAAS,OAAO,UAAU,aAAa;AAAA,YACrC;AAAA,YACA,GAAI,eAAe,SAAY,EAAE,OAAO,WAAW,IAAI,CAAC;AAAA,YACxD,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,UAC/C,CAAC;AAAA,QACH;AAAA,MACF;AACA,UAAI,WAAW,UAAU,iBAAiB,GAAG;AAC3C,gBAAQ,IAAI,sBAAsB,cAAc,CAAC;AAAA,MACnD;AAEA,UAAI,WAAW,UAAU,eAAe,UAAa,aAAa,GAAG;AACnE,cAAM,OAAO,eAAe,IAAI,YAAY;AAC5C,gBAAQ;AAAA,UACN,SACI,GAAG,UAAU,IAAI,IAAI,yCACrB,SAAS,UAAU,IAAI,IAAI;AAAA,QACjC;AAAA,MACF;AACA,UAAI,kBAAkB;AACpB,gBAAQ;AAAA,UACN,cAAc,YAAY,6CAA6C,WAAW;AAAA,QACpF;AAAA,MACF;AACA,cAAQ,KAAK,aAAa,KAAK,mBAAmB,IAAI,CAAC;AAAA,IACzD;AAGA,UAAM,UAAU,KAAK,QAAQ,QAAQ,KAAK,aAAa,MAAM;AAC7D,QAAI,WAAW,gBAAgB;AAE7B,cAAQ;AAAA,QACN;AAAA,MACF;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,QAAI,SAAS;AAEX,YAAM,SAAS,KAAK,aAAa,MAAM;AACvC,UAAI;AACJ,UAAI;AAGF,oBAAY,WAAW;AAAA,UACrB,OAAO;AAAA,UACP,cAAc,OAAO;AAAA,UACrB;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,YAAI,eAAe,kBAAkB;AACnC,kBAAQ,MAAM,mCAAmC,IAAI,OAAO,EAAE;AAC9D,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM;AAAA,MACR;AAKA,UAAI;AACJ,UAAI,QAAQ;AACV,cAAM,UAAU,UAAU,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO;AACvD,YAAI,WAAW,QAAQ;AACrB,qBAAW,KAAK,SAAS;AACvB,oBAAQ,OAAO,MAAM,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC;AAAA,UAC/D;AAAA,QACF,WAAW,WAAW,QAAQ;AAC5B,qBAAW,QAAQ,IAAI,CAAC,OAAO;AAAA,YAC7B,MAAM,EAAE;AAAA,YACR,MAAM,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK;AAAA,UAC/C,EAAE;AAAA,QACJ,OAAO;AACL,qBAAW,KAAK,SAAS;AACvB,oBAAQ,OAAO,MAAM,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC;AAAA,UAC/D;AAAA,QACF;AAAA,MACF,OAAO;AACL,mBAAW,KAAK,UAAU,OAAO;AAC/B,cAAI,EAAE,QAAS,CAAAC,eAAc,EAAE,SAAS,EAAE,OAAO,MAAM;AAAA,QACzD;AAAA,MACF;AACA;AAAA,QACE,UAAU;AAAA,QACV,UAAU;AAAA,QACV,UAAU;AAAA,QACV;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,QAAI;AACJ,QAAI;AACF,eAAS,UAAU;AAAA,QACjB,OAAO;AAAA,QACP,cAAc,OAAO;AAAA,QACrB;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AAIZ,UAAI,eAAe,kBAAkB;AACnC,gBAAQ,MAAM,mCAAmC,IAAI,OAAO,EAAE;AAC9D,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,YAAM;AAAA,IACR;AAGA,QAAI,gBAAgB;AAClB,YAAM,QAAQ,cAAc,OAAO,WAAW;AAC9C,oBAAc,cAAc,KAAK;AACjC,cAAQ,IAAI,qBAAqB,KAAK,CAAC;AACvC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,kBAAc,OAAO,aAAa,OAAO,gBAAgB,QAAW,KAAK;AAAA,EAC3E;AACF,CAAC;AAQD,IAAM,UAAU,QAAQ,KAAK,MAAM,CAAC;AACpC,IAAI,QAAQ,CAAC,MAAM,QAAQ;AACzB,QAAM,QAAQ,aAAa,EAAE,SAAS,QAAQ,MAAM,CAAC,EAAE,CAAC;AAC1D,OAAO;AACL,QAAM,QAAQ,IAAI;AACpB;","names":["existsSync","readFileSync","writeFileSync","relative","resolve","sep","defineCommand","writeFileSync","resolve","existsSync","existsSync","readFileSync","readdirSync","join","resolve","sep","readdirSync","resolve","readFileSync","join","existsSync","pkg","readFileSync","statSync","resolve","existsSync","readFileSync","join","rule","join","rule","existsSync","readFileSync","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","join","join","existsSync","readFileSync","mkdirSync","dirname","writeFileSync","i","j","rule","rule","readFileSync","rule","resolve","statSync","schema_default","rule","schema_default","dirname","schema_default","rule","schema_default","dirname","basename","schema_default","rule","schema_default","basename","basename","schema_default","NUMBER_REGEX","rule","schema_default","basename","toString","schema_default","rule","schema_default","toString","schema_default","rule","schema_default","basename","schema_default","basename","rule","schema_default","writeFileSync","resolve","basename","rule","toPosix","sep","readFileSync","defineCommand","resolve","relative","existsSync","writeFileSync"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/commands/init.ts","../src/core/config.ts","../src/configs/recommended.ts","../src/core/discover.ts","../src/core/init.ts","../src/core/parser.ts","../src/core/null-proto-map.ts","../src/core/lint.ts","../src/core/baseline.ts","../src/core/types.ts","../src/core/cache.ts","../src/core/fix.ts","../src/core/runner.ts","../src/core/interpolate.ts","../src/core/suppression.ts","../src/rules/index.ts","../src/rules/date-iso8601/schema.json","../src/rules/date-iso8601/index.ts","../src/rules/filename-format/index.ts","../src/core/regex-safety.ts","../src/rules/filename-format/schema.json","../src/rules/no-broken-links/index.ts","../src/rules/no-broken-links/schema.json","../src/rules/no-duplicate-numbering/index.ts","../src/rules/no-duplicate-numbering/schema.json","../src/rules/no-numbering-gap/index.ts","../src/rules/no-numbering-gap/schema.json","../src/rules/required-sections/index.ts","../src/rules/required-sections/schema.json","../src/rules/status-enum/schema.json","../src/rules/status-enum/index.ts","../src/rules/supersedes-bidirectional/index.ts","../src/rules/supersedes-bidirectional/schema.json","../src/core/ignore.ts","../src/core/reporter.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { relative, resolve, sep } from 'node:path';\nimport { defineCommand, runMain } from 'citty';\nimport { initCommand } from './commands/init.js';\nimport {\n baselineHiddenSummary,\n baselineMalformedWarning,\n baselinePath,\n baselineWriteSummary,\n buildBaseline,\n loadBaseline,\n writeBaseline,\n} from './core/baseline.js';\nimport { computeConfigHash } from './core/cache.js';\nimport {\n ConfigFileNotFoundError,\n loadConfig,\n loadConfigFromPath,\n resolveExtends,\n type ResolvedConfig,\n} from './core/config.js';\nimport { findAdrFiles } from './core/discover.js';\nimport { unifiedDiff } from './core/fix.js';\nimport { shouldIgnore } from './core/ignore.js';\nimport { lintAndFix, lintFiles, type CacheConfig } from './core/lint.js';\nimport { reporters, type ReporterFormat } from './core/reporter.js';\nimport { RuleOptionsError } from './core/runner.js';\nimport type { AnyRule, Diagnostic } from './core/types.js';\nimport * as builtinRules from './rules/index.js';\n\nfunction toPosix(p: string): string {\n if (sep === '/') return p;\n return p.split(sep).join('/');\n}\n\nconst pkg = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8'),\n) as { version: string };\n\nconst main = defineCommand({\n meta: {\n name: 'madr-lint',\n version: pkg.version,\n description: 'A linter for MADR (Markdown Architectural Decision Records)',\n },\n args: {\n paths: {\n type: 'positional',\n description:\n 'One or more paths (files or directories). Defaults to config.adrDir',\n required: false,\n },\n cache: {\n type: 'boolean',\n description:\n 'Use per-file content-hash cache (use --no-cache to disable)',\n default: true,\n },\n 'cache-dir': {\n type: 'string',\n description: 'Cache directory (default: .madr-lint/cache)',\n required: false,\n },\n format: {\n type: 'string',\n description: 'Reporter format: text (default), json, sarif, or github',\n default: 'text',\n },\n baseline: {\n type: 'boolean',\n description:\n 'Subtract .madr-lint/baseline.json when present (use --no-baseline to ignore it)',\n default: true,\n },\n 'update-baseline': {\n type: 'boolean',\n description:\n 'Rewrite .madr-lint/baseline.json from a full lint, then exit 0',\n default: false,\n },\n quiet: {\n type: 'boolean',\n description: 'Report errors only; suppress warnings from output',\n default: false,\n },\n 'max-warnings': {\n type: 'string',\n description:\n 'Exit 1 when warning count exceeds n. n=0 means any warning fails. Negative = no limit.',\n required: false,\n },\n config: {\n type: 'string',\n description: 'Path to config file, bypassing auto-discovery',\n required: false,\n },\n fix: {\n type: 'boolean',\n description: 'Apply autofixes in place; exit code reflects remaining problems',\n default: false,\n },\n 'fix-dry-run': {\n type: 'boolean',\n description: 'Show a diff of the fixes that --fix would apply; write nothing',\n default: false,\n },\n },\n run({ args }) {\n const cwd = process.cwd();\n\n // Parse --max-warnings. Require an integer (including negative); float or\n // non-numeric is a usage error (exit 2), not a lint failure (exit 1).\n // Number('') === 0, so an empty value (e.g. --max-warnings \"$UNSET_VAR\")\n // would silently become the strictest limit — reject it explicitly.\n let maxWarnings = -1;\n const rawMaxWarnings = args['max-warnings'] as string | undefined;\n if (rawMaxWarnings !== undefined) {\n const parsed = Number(rawMaxWarnings);\n if (rawMaxWarnings.trim() === '' || !Number.isInteger(parsed)) {\n console.error(\n `Invalid --max-warnings \"${rawMaxWarnings}\": must be an integer.`,\n );\n process.exit(2);\n }\n maxWarnings = parsed;\n }\n\n // --config bypasses the config discovery walk. When the user explicitly\n // points to a config file we skip the recommended fallback — they chose\n // the config intentionally (even if it has no rules).\n let config: ResolvedConfig;\n let configExplicit = false;\n const configArg = args.config as string | undefined;\n if (configArg) {\n const absConfigPath = resolve(cwd, configArg);\n try {\n config = loadConfigFromPath(absConfigPath);\n configExplicit = true;\n } catch (err) {\n if (err instanceof ConfigFileNotFoundError) {\n console.error(err.message);\n } else {\n console.error(\n `Failed to load config \"${configArg}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n process.exit(2);\n }\n } else {\n config = loadConfig(cwd);\n }\n\n // When no rules are configured and no explicit config was provided, fall\n // back to `recommended` so users get useful output without authoring a\n // config first.\n if (!configExplicit && Object.keys(config.rules).length === 0) {\n config = resolveExtends({ extends: ['madr-lint:recommended'] });\n }\n\n // citty: positionals collect into `args._` (array of strings). If\n // empty, fall back to the config's adrDir.\n const positionals = (args._ as string[] | undefined) ?? [];\n const inputs = positionals.length > 0 ? positionals : [config.adrDir];\n\n const seen = new Set<string>();\n const allFiles: string[] = [];\n for (const input of inputs) {\n for (const file of findAdrFiles(input)) {\n if (!seen.has(file)) {\n seen.add(file);\n allFiles.push(file);\n }\n }\n }\n\n // Apply ignorePatterns from config (e.g. README.md, template.md, 9999-*).\n // POSIX-normalize so path-suffix patterns work cross-platform on Windows.\n const files = allFiles.filter(\n (absPath) =>\n !shouldIgnore(toPosix(relative(cwd, absPath)), config.ignorePatterns),\n );\n\n if (files.length === 0) {\n const skipped = allFiles.length - files.length;\n const note = skipped > 0 ? ` (${skipped} ignored by config)` : '';\n const where = inputs.length === 1 ? inputs[0] : inputs.join(', ');\n console.log(`No .md files to lint in ${where}${note}`);\n process.exit(0);\n }\n\n // CLI flag wins over config — `--no-cache` sets args.cache=false,\n // overriding `cache: true` from .madrlintrc.json.\n const cacheEnabled = (args.cache ?? config.cache) === true;\n const cacheDir = (args['cache-dir'] ?? config.cacheLocation) as string;\n const cacheConfig: CacheConfig | null = cacheEnabled\n ? {\n dir: resolve(cwd, cacheDir),\n configHash: computeConfigHash({\n rules: config.rules,\n ignorePatterns: config.ignorePatterns,\n madrVersion: config.madrVersion,\n }),\n pkgVersion: pkg.version,\n }\n : null;\n\n // --update-baseline runs a full lint IGNORING any existing baseline, so we\n // subtract nothing here and (below) rebuild the file from the raw output.\n const baselineFile = baselinePath(cwd);\n const updateBaseline = args['update-baseline'] === true;\n const useBaseline = !updateBaseline && args.baseline !== false;\n const baseline = useBaseline ? loadBaseline(baselineFile) : null;\n // Absent → silent no-op. Present but unusable → warn: silently ignoring a\n // corrupt baseline would flip CI red with zero explanation.\n if (useBaseline && baseline === null && existsSync(baselineFile)) {\n console.error(baselineMalformedWarning());\n }\n\n const allRulesArray: AnyRule[] = Object.values(builtinRules);\n const format = (args.format ?? 'text') as ReporterFormat;\n\n // Shared \"report the resulting diagnostics and exit\" tail. `fixedCount` is\n // undefined on a plain lint (no fix footer / summary), a number when a fix\n // pass ran. `fixDiffs` carries dry-run diffs for the json reporter to embed\n // (undefined otherwise). Diagnostics are POST-baseline, so baselined\n // warnings never count toward --max-warnings — inherited debt does not\n // fail CI.\n const reportAndExit = (\n diagnostics: readonly Diagnostic[],\n baselineHidden: number,\n fixedCount: number | undefined,\n dryRun: boolean,\n fixDiffs?: readonly { path: string; diff: string }[],\n ): never => {\n const rulesByName = new Map<string, AnyRule>(\n allRulesArray.map((r) => [r.meta.name, r]),\n );\n const reporter = reporters[format];\n if (!reporter) {\n console.error(\n `Unknown --format \"${format}\". Available: ${Object.keys(reporters).join(', ')}`,\n );\n process.exit(2);\n }\n const warningCount = diagnostics.filter((d) => d.severity === 'warn').length;\n const errorCount = diagnostics.filter((d) => d.severity === 'error').length;\n const overWarningLimit = maxWarnings >= 0 && warningCount > maxWarnings;\n\n // --quiet filters warnings from OUTPUT but the original warning count is\n // still used for --max-warnings (mirrors ESLint). The threshold verdict\n // goes to stderr so `--quiet --max-warnings 0` still fails loudly.\n const reported = args.quiet\n ? diagnostics.filter((d) => d.severity !== 'warn')\n : diagnostics;\n\n // Text-only: a \"✓ All clear.\" banner beside exit 1 would lie, so suppress\n // it when the run fails purely on the warning threshold.\n const suppressAllClear =\n format === 'text' && reported.length === 0 && overWarningLimit;\n if (!suppressAllClear) {\n console.log(\n reporter.format(reported, rulesByName, {\n baselineHidden,\n ...(fixedCount !== undefined ? { fixed: fixedCount } : {}),\n ...(fixDiffs !== undefined ? { fixDiffs } : {}),\n }),\n );\n }\n if (format === 'text' && baselineHidden > 0) {\n console.log(baselineHiddenSummary(baselineHidden));\n }\n // Text-only autofix footer (json carries summary.fixed instead).\n if (format === 'text' && fixedCount !== undefined && fixedCount > 0) {\n const noun = fixedCount === 1 ? 'problem' : 'problems';\n console.log(\n dryRun\n ? `${fixedCount} ${noun} fixable (dry run; no files written)`\n : `Fixed ${fixedCount} ${noun}`,\n );\n }\n if (overWarningLimit) {\n console.error(\n `madr-lint: ${warningCount} warning(s) found, exceeds --max-warnings ${maxWarnings}`,\n );\n }\n process.exit(errorCount > 0 || overWarningLimit ? 1 : 0);\n };\n\n // ── Autofix path (--fix / --fix-dry-run) ─────────────────────────\n const fixMode = args.fix === true || args['fix-dry-run'] === true;\n if (fixMode && updateBaseline) {\n // Ambiguous intent: rewrite files vs snapshot the current violations.\n console.error(\n 'madr-lint: --update-baseline cannot be combined with --fix or --fix-dry-run',\n );\n process.exit(2);\n }\n if (fixMode) {\n // --fix-dry-run wins if both are given: never write on a dry run.\n const dryRun = args['fix-dry-run'] === true;\n let fixResult: ReturnType<typeof lintAndFix>;\n try {\n // The cache is intentionally bypassed while fixing (fixes need live\n // thunks); fixed files re-enter the normal pipeline on the next run.\n fixResult = lintAndFix({\n rules: allRulesArray,\n ruleSeverity: config.rules,\n files,\n cwd,\n baseline,\n });\n } catch (err) {\n if (err instanceof RuleOptionsError) {\n console.error(`Invalid rule options in config: ${err.message}`);\n process.exit(2);\n }\n throw err;\n }\n\n // Dry-run diff routing: raw diff on stdout is only safe for the human\n // `text` format. json embeds it (top-level `diffs`, via reportAndExit);\n // sarif/github get it on stderr so their stdout stays machine-parseable.\n let fixDiffs: { path: string; diff: string }[] | undefined;\n if (dryRun) {\n const changed = fixResult.files.filter((f) => f.changed);\n if (format === 'text') {\n for (const f of changed) {\n process.stdout.write(unifiedDiff(f.path, f.original, f.fixed));\n }\n } else if (format === 'json') {\n fixDiffs = changed.map((f) => ({\n path: f.path,\n diff: unifiedDiff(f.path, f.original, f.fixed),\n }));\n } else {\n for (const f of changed) {\n process.stderr.write(unifiedDiff(f.path, f.original, f.fixed));\n }\n }\n } else {\n for (const f of fixResult.files) {\n if (f.changed) writeFileSync(f.absPath, f.fixed, 'utf8');\n }\n }\n reportAndExit(\n fixResult.diagnostics,\n fixResult.baselineHidden,\n fixResult.fixed,\n dryRun,\n fixDiffs,\n );\n }\n\n // ── Normal lint path ─────────────────────────────────────────────\n let result: ReturnType<typeof lintFiles>;\n try {\n result = lintFiles({\n rules: allRulesArray,\n ruleSeverity: config.rules,\n files,\n cwd,\n cache: cacheConfig,\n baseline,\n });\n } catch (err) {\n // Invalid rule options in the user's config fail AJV validation now that\n // options actually reach the rules. Surface a clear message instead of a\n // stack trace, and exit 2 (config error, distinct from lint failures).\n if (err instanceof RuleOptionsError) {\n console.error(`Invalid rule options in config: ${err.message}`);\n process.exit(2);\n }\n throw err;\n }\n\n // Rebuild the baseline from the full (pre-baseline) diagnostics and exit.\n if (updateBaseline) {\n const built = buildBaseline(result.diagnostics);\n writeBaseline(baselineFile, built);\n console.log(baselineWriteSummary(built));\n process.exit(0);\n }\n\n reportAndExit(result.diagnostics, result.baselineHidden, undefined, false);\n },\n});\n\n// Manual subcommand dispatch. citty's `subCommands` field cannot coexist with\n// the free-form `paths` positional: runCommand() treats ANY leading non-flag\n// token as a subcommand name once `subCommands` is set, so `madr-lint docs/adr`\n// would die with \"Unknown command docs/adr\" (verified against citty 0.2.2).\n// Only the literal first raw arg `init` selects the subcommand; every other\n// invocation flows to the default lint command exactly as before.\nconst rawArgs = process.argv.slice(2);\nif (rawArgs[0] === 'init') {\n await runMain(initCommand, { rawArgs: rawArgs.slice(1) });\n} else {\n await runMain(main);\n}\n","import { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { defineCommand } from 'citty';\nimport { CONFIG_FILES, resolveExtends } from '../core/config.js';\nimport { findAdrFiles } from '../core/discover.js';\nimport {\n CONFIG_FILENAME_BY_FORMAT,\n GETTING_STARTED_URL,\n buildEpilogue,\n detectAdrDir,\n detectConfigFormat,\n detectMadrVersion,\n findExistingConfigFile,\n renderConfig,\n type AdrDirSource,\n} from '../core/init.js';\nimport { lintFiles } from '../core/lint.js';\nimport type { AnyRule } from '../core/types.js';\nimport * as builtinRules from '../rules/index.js';\n\n/**\n * `madr-lint init` — scaffold a config file (issue #30).\n *\n * Non-interactive by design: every decision is a filesystem heuristic or a\n * flag, so it works in CI and behind pipes. Detects the ADR directory, the\n * dominant MADR version, and whether to write TS or JSON; refuses to clobber\n * an existing config unless `--force`.\n */\nexport const initCommand = defineCommand({\n meta: {\n name: 'init',\n description:\n 'Scaffold a madr-lint config file (detects ADR directory, MADR version, and TS vs JSON)',\n },\n args: {\n force: {\n type: 'boolean',\n description: 'Overwrite an existing config file',\n default: false,\n },\n dir: {\n type: 'string',\n description: 'ADR directory to write into the config, overriding detection',\n required: false,\n },\n json: {\n type: 'boolean',\n description: 'Emit a machine-readable JSON summary instead of text',\n default: false,\n },\n },\n run({ args }) {\n const cwd = process.cwd();\n\n // Usage errors first. An empty --dir is falsy, so without this guard it\n // would silently fall through to auto-detection (e.g. --dir \"$UNSET_VAR\")\n // — the same footgun class --max-warnings \"\" guards against in cli.ts.\n const rawDir = args.dir as string | undefined;\n if (rawDir !== undefined && rawDir.trim() === '') {\n console.error(`Invalid --dir \"${rawDir}\": must be a non-empty path.`);\n process.exit(2);\n }\n\n const existing = findExistingConfigFile(cwd);\n if (existing && args.force !== true) {\n console.error(\n `A madr-lint config already exists: ${existing}. Re-run with --force to overwrite.`,\n );\n process.exit(2);\n }\n\n // ── Detection ────────────────────────────────────────────────────\n let adrDir: string;\n let adrDirSource: AdrDirSource;\n if (rawDir !== undefined) {\n adrDir = rawDir;\n adrDirSource = 'override';\n } else {\n const detected = detectAdrDir(cwd);\n adrDir = detected.adrDir;\n adrDirSource = detected.source;\n }\n const madrVersion = detectMadrVersion(cwd, adrDir);\n const configFormat = detectConfigFormat(cwd);\n const configPath = CONFIG_FILENAME_BY_FORMAT[configFormat];\n\n // ── Write ────────────────────────────────────────────────────────\n writeFileSync(\n resolve(cwd, configPath),\n renderConfig(configFormat, { adrDir, madrVersion }),\n );\n // --force replaces the canonical file for the DETECTED format. A\n // pre-existing config under a different name would still win (or lose)\n // discovery order silently — never leave that unsaid, and state the\n // ACTUAL direction: CONFIG_FILES index order decides which file wins.\n if (existing && existing !== configPath) {\n const oldWins =\n CONFIG_FILES.indexOf(existing) < CONFIG_FILES.indexOf(configPath);\n console.error(\n oldWins\n ? `Note: ${existing} still exists and precedes ${configPath} in config discovery — remove it to make the new config take effect.`\n : `Note: the leftover ${existing} is now shadowed by ${configPath}, which precedes it in config discovery — you can delete it.`,\n );\n }\n\n // ── Initial in-process lint (cheap) ──────────────────────────────\n // Counts violations of the freshly scaffolded config so the epilogue can\n // suggest --update-baseline on legacy repos. Never fails init: a lint\n // crash is reported but the scaffold already succeeded.\n const files = findAdrFiles(resolve(cwd, adrDir));\n let errors = 0;\n let warnings = 0;\n if (files.length > 0) {\n try {\n const config = resolveExtends({ extends: ['madr-lint:recommended'] });\n const allRules: AnyRule[] = Object.values(builtinRules);\n const result = lintFiles({\n rules: allRules,\n ruleSeverity: config.rules,\n files,\n cwd,\n cache: null,\n baseline: null,\n });\n for (const d of result.diagnostics) {\n if (d.severity === 'error') errors++;\n else warnings++;\n }\n } catch (err) {\n console.error(\n `Note: the initial lint failed (${err instanceof Error ? err.message : String(err)}); the config was still written.`,\n );\n }\n }\n\n // ── Report ───────────────────────────────────────────────────────\n const filesChecked = files.length;\n const suggestUpdateBaseline = filesChecked > 0 && errors + warnings > 0;\n if (args.json === true) {\n console.log(\n JSON.stringify(\n {\n written: true,\n configPath,\n configFormat,\n adrDir,\n adrDirSource,\n madrVersion,\n filesChecked,\n errors,\n warnings,\n suggestUpdateBaseline,\n docsUrl: GETTING_STARTED_URL,\n },\n null,\n 2,\n ),\n );\n } else {\n console.log(\n buildEpilogue({\n adrDir,\n adrDirSource,\n configPath,\n filesChecked,\n errors,\n warnings,\n }),\n );\n }\n process.exit(0);\n },\n});\n","import { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createJiti } from 'jiti';\nimport { recommended } from '../configs/recommended.js';\nimport type { MadrLintConfig } from '../index.js';\nimport type { MadrVersion, RuleSeverity } from './types.js';\n\nexport interface ResolvedConfig {\n /** ADR directory, relative to cwd or absolute. Default: 'docs/adr'. */\n adrDir: string;\n /** Per-rule severity / options map. Empty when no preset extended. */\n rules: Record<string, RuleSeverity>;\n /** Glob patterns to skip. */\n ignorePatterns: string[];\n /** MADR version target. Default: 'auto'. */\n madrVersion: MadrVersion | 'auto';\n /** Whether to use per-file content-hash cache. Default: true. */\n cache: boolean;\n /** Cache directory (relative to cwd). Default: '.madr-lint/cache'. */\n cacheLocation: string;\n}\n\n/**\n * Config file resolution order. First match wins.\n *\n * `.json` is parsed directly (no module loader), so it stays the fastest\n * and dependency-free path. The other extensions go through `jiti`,\n * which transpiles TS/ESM/CJS on the fly.\n */\nexport const CONFIG_FILES = [\n '.madrlintrc.json',\n '.madrlintrc.ts',\n '.madrlintrc.mts',\n '.madrlintrc.js',\n '.madrlintrc.mjs',\n '.madrlintrc.cjs',\n 'madr-lint.config.ts',\n 'madr-lint.config.mts',\n 'madr-lint.config.js',\n 'madr-lint.config.mjs',\n 'madr-lint.config.cjs',\n];\n\nexport class ConfigFileNotFoundError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigFileNotFoundError';\n }\n}\n\n/**\n * Load and resolve a config from an explicit file path, bypassing discovery.\n * Throws `ConfigFileNotFoundError` if the file does not exist.\n * Throws `SyntaxError` or other load errors if the file is malformed.\n */\nexport function loadConfigFromPath(filePath: string): ResolvedConfig {\n // Race-free error classification: fs check-APIs (existsSync/statSync) before\n // a use are TOCTOU by construction (CodeQL js/file-system-race). Reading IS\n // the use, so classify its errors instead: ENOENT/EISDIR carry exactly the\n // two cases we distinguish, with no check-then-use window.\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n throw new ConfigFileNotFoundError(`Config file not found: ${filePath}`);\n }\n // A directory exists on disk but jiti's failure reads like a missing\n // npm package (\"Cannot find module\") — fail with an honest message instead.\n if (code === 'EISDIR') {\n throw new ConfigFileNotFoundError(\n `Config path is a directory, not a file: ${filePath}`,\n );\n }\n throw err;\n }\n if (filePath.endsWith('.json')) {\n const parsed = JSON.parse(raw) as MadrLintConfig;\n return resolveExtends(parsed);\n }\n const jiti = createJiti(import.meta.url, { interopDefault: true });\n const loaded = jiti(filePath) as MadrLintConfig | { default: MadrLintConfig };\n const config: MadrLintConfig =\n typeof loaded === 'object' && loaded !== null && 'default' in loaded\n ? (loaded.default as MadrLintConfig)\n : (loaded as MadrLintConfig);\n return resolveExtends(config);\n}\n\n/**\n * Loads and resolves a config from `cwd`. If no config file is found,\n * returns the defaults (no rules enabled — caller can opt into the\n * recommended preset by extending).\n *\n * `.json` is read synchronously and parsed inline. All other extensions\n * are loaded via `jiti`, supporting TypeScript and both module systems.\n */\nexport function loadConfig(cwd: string): ResolvedConfig {\n for (const name of CONFIG_FILES) {\n const path = resolve(cwd, name);\n if (!existsSync(path)) continue;\n\n if (name.endsWith('.json')) {\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as MadrLintConfig;\n return resolveExtends(parsed);\n }\n\n const jiti = createJiti(import.meta.url, { interopDefault: true });\n const loaded = jiti(path) as MadrLintConfig | { default: MadrLintConfig };\n const config: MadrLintConfig =\n typeof loaded === 'object' && loaded !== null && 'default' in loaded\n ? (loaded.default as MadrLintConfig)\n : (loaded as MadrLintConfig);\n return resolveExtends(config);\n }\n return resolveExtends({});\n}\n\n/**\n * Pure function: turns a raw `MadrLintConfig` into a `ResolvedConfig`\n * with defaults filled in and `extends` presets merged.\n */\nexport function resolveExtends(config: MadrLintConfig): ResolvedConfig {\n const baseRules: Record<string, RuleSeverity> = config.extends?.includes(\n 'madr-lint:recommended',\n )\n ? { ...recommended }\n : {};\n\n return {\n adrDir: config.adrDir ?? 'docs/adr',\n rules: { ...baseRules, ...config.rules },\n ignorePatterns: config.ignorePatterns ?? [],\n madrVersion: config.madrVersion ?? 'auto',\n cache: config.cache ?? true,\n cacheLocation: config.cacheLocation ?? '.madr-lint/cache',\n };\n}\n","import type { RuleSeverity } from '../core/types.js';\n\n// The `recommended` preset enables rules whose default behavior is broadly\n// useful and grounded in the MADR spec. New rules append here via the\n// add-rule skill, with severity decided per-rule (default `error` for\n// spec-grounded rules, `warn` for convention-only rules).\n//\n// Both bare-string and tuple forms are supported here:\n// 'madr/foo': 'error' — enable with defaults\n// 'madr/foo': ['error', { pattern: '^X' }] — enable with override\n\nexport const recommended: Record<string, RuleSeverity> = {\n 'madr/date-iso8601': 'error',\n 'madr/filename-format': 'error',\n 'madr/no-broken-links': 'error',\n 'madr/no-duplicate-numbering': 'error',\n // Convention-only — opt in if your team treats ADR numbering as a\n // contiguous sequence. See https://knktkc.github.io/madr-lint/rules/no-numbering-gap/.\n 'madr/no-numbering-gap': 'off',\n 'madr/required-sections': 'error',\n 'madr/status-enum': 'error',\n 'madr/supersedes-bidirectional': 'error',\n};\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Resolves a target path to a list of `.md` files to lint.\n *\n * - If the target is a directory: recursively walks it and returns every `.md`\n * file, sorted lexicographically.\n * - If the target is a `.md` file: returns just that file.\n * - If the target is a non-`.md` file or does not exist: returns `[]`.\n *\n * The empty-array fallback lets the CLI print \"No .md files found\" rather\n * than crashing — the caller decides whether that is an error.\n */\nexport function findAdrFiles(target: string): string[] {\n if (!existsSync(target)) return [];\n\n const stat = statSync(target);\n if (stat.isFile()) {\n return target.endsWith('.md') ? [target] : [];\n }\n if (!stat.isDirectory()) return [];\n\n const entries = readdirSync(target, { recursive: true, withFileTypes: true });\n return entries\n .filter((e) => e.isFile() && e.name.endsWith('.md'))\n .map((e) => join(e.parentPath, e.name))\n .toSorted();\n}\n","import { existsSync, readFileSync, readdirSync, type Dirent } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { CONFIG_FILES } from './config.js';\nimport { parseFile, type ParsedFile } from './parser.js';\nimport type { MadrVersion } from './types.js';\n\n/** Docs quickstart, surfaced in the init epilogue and --json payload. */\nexport const GETTING_STARTED_URL =\n 'https://knktkc.github.io/madr-lint/guides/getting-started/';\n\n/**\n * ADR directory candidates, scanned in priority order. A candidate qualifies\n * when its top level contains at least one `NNNN-*.md` file.\n */\nexport const ADR_DIR_CANDIDATES = [\n 'docs/adr',\n 'docs/decisions',\n 'doc/adr',\n 'adr',\n 'docs/architecture/decisions',\n];\n\n/** Filenames shaped like an ADR: four digits, a hyphen, `.md`. */\nconst ADR_FILE_PATTERN = /^\\d{4}-.*\\.md$/;\n\n/** How the ADR directory ended up in the config. */\nexport type AdrDirSource = 'detected' | 'fallback' | 'override';\n\nexport interface AdrDirDetection {\n adrDir: string;\n source: 'detected' | 'fallback';\n}\n\n/**\n * Detect the ADR directory by scanning the candidates (in order) for at\n * least one top-level `NNNN-*.md` file. Falls back to `docs/adr` — the\n * linter's default — when nothing qualifies.\n */\nexport function detectAdrDir(cwd: string): AdrDirDetection {\n for (const candidate of ADR_DIR_CANDIDATES) {\n let entries: Dirent[];\n try {\n entries = readdirSync(resolve(cwd, candidate), { withFileTypes: true });\n } catch {\n continue; // absent or unreadable — not a candidate\n }\n if (entries.some((e) => e.isFile() && ADR_FILE_PATTERN.test(e.name))) {\n return { adrDir: candidate, source: 'detected' };\n }\n }\n return { adrDir: 'docs/adr', source: 'fallback' };\n}\n\n/**\n * Per-file MADR version detection, reusing the parser's metadata bridge\n * (ADR-0006): YAML frontmatter means v3/v4 — v4 when the v4-renamed\n * `decision-makers` key is present, v3 otherwise; a v2 body-list metadata\n * block means v2. A file with neither casts no vote.\n */\nfunction detectFileVersion(content: string): MadrVersion | null {\n let parsed: ParsedFile;\n try {\n parsed = parseFile(content);\n } catch {\n return null; // unparseable (e.g. malformed YAML) — no vote\n }\n if (parsed.frontmatter) {\n return 'decision-makers' in parsed.frontmatter ? 'v4' : 'v3';\n }\n if (parsed.listMetadata) return 'v2';\n return null;\n}\n\n/**\n * Detect the dominant MADR version of an ADR directory by sampling up to 20\n * `NNNN-*.md` files (lexicographically first, for determinism) and letting\n * the majority win. An empty directory, no recognizable metadata, or an\n * exact tie yields `'auto'` — the config default, safe for mixed vintages.\n */\nexport function detectMadrVersion(\n cwd: string,\n adrDir: string,\n): MadrVersion | 'auto' {\n const dir = resolve(cwd, adrDir);\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return 'auto';\n }\n\n const sample = entries\n .filter((e) => e.isFile() && ADR_FILE_PATTERN.test(e.name))\n .map((e) => e.name)\n .toSorted()\n .slice(0, 20);\n\n const votes: Record<MadrVersion, number> = { v2: 0, v3: 0, v4: 0 };\n for (const name of sample) {\n let content: string;\n try {\n content = readFileSync(join(dir, name), 'utf8');\n } catch {\n continue;\n }\n const version = detectFileVersion(content);\n if (version) votes[version]++;\n }\n\n const max = Math.max(votes.v2, votes.v3, votes.v4);\n if (max === 0) return 'auto';\n const winners = (['v2', 'v3', 'v4'] as const).filter(\n (v) => votes[v] === max,\n );\n return winners.length === 1 ? winners[0] : 'auto';\n}\n\n/** Which config file format to scaffold. */\nexport type ConfigFormat = 'ts' | 'json';\n\n/** Canonical config filename per scaffolded format. */\nexport const CONFIG_FILENAME_BY_FORMAT: Record<ConfigFormat, string> = {\n ts: 'madr-lint.config.ts',\n json: '.madrlintrc.json',\n};\n\n/**\n * Pick the config format: TypeScript when the project looks TS-ish\n * (a `tsconfig.json`, or `typescript` among package.json dependencies /\n * devDependencies), JSON otherwise. A missing or malformed package.json\n * never throws — it just means \"not TS-ish\".\n */\nexport function detectConfigFormat(cwd: string): ConfigFormat {\n if (existsSync(resolve(cwd, 'tsconfig.json'))) return 'ts';\n try {\n const pkg = JSON.parse(\n readFileSync(resolve(cwd, 'package.json'), 'utf8'),\n ) as {\n dependencies?: Record<string, unknown>;\n devDependencies?: Record<string, unknown>;\n };\n if (\n pkg.dependencies?.['typescript'] !== undefined ||\n pkg.devDependencies?.['typescript'] !== undefined\n ) {\n return 'ts';\n }\n } catch {\n // absent or malformed package.json — fall through to json\n }\n return 'json';\n}\n\n/**\n * First existing config file in `cwd`, in the loader's CONFIG_FILES\n * resolution order, or null when the project has none. Used by `init` to\n * refuse clobbering an existing config without `--force`.\n */\nexport function findExistingConfigFile(cwd: string): string | null {\n for (const name of CONFIG_FILES) {\n if (existsSync(resolve(cwd, name))) return name;\n }\n return null;\n}\n\nexport interface ConfigRenderOptions {\n adrDir: string;\n madrVersion: MadrVersion | 'auto';\n}\n\n/** Escape a value for interpolation into a single-quoted TS string literal. */\nfunction tsQuote(value: string): string {\n return `'${value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")}'`;\n}\n\n/**\n * Render the scaffolded config file content. `madrVersion: 'auto'` is the\n * default, so it is omitted rather than written redundantly.\n */\nexport function renderConfig(\n format: ConfigFormat,\n options: ConfigRenderOptions,\n): string {\n if (format === 'json') {\n const config: Record<string, unknown> = {\n extends: ['madr-lint:recommended'],\n adrDir: options.adrDir,\n };\n if (options.madrVersion !== 'auto') config.madrVersion = options.madrVersion;\n return `${JSON.stringify(config, null, 2)}\\n`;\n }\n\n const versionLine =\n options.madrVersion !== 'auto'\n ? `\\n madrVersion: ${tsQuote(options.madrVersion)},`\n : '';\n return `import { defineConfig } from 'madr-lint';\n\nexport default defineConfig({\n extends: ['madr-lint:recommended'],\n adrDir: ${tsQuote(options.adrDir)},${versionLine}\n});\n`;\n}\n\nexport interface EpilogueInput {\n adrDir: string;\n adrDirSource: AdrDirSource;\n configPath: string;\n /** Files covered by the initial in-process lint (0 = nothing to lint). */\n filesChecked: number;\n errors: number;\n warnings: number;\n}\n\n/**\n * Human-readable summary + next steps printed by `madr-lint init`. When the\n * initial lint of the detected directory found violations, it points at\n * `--update-baseline` so legacy debt does not block adoption.\n */\nexport function buildEpilogue(input: EpilogueInput): string {\n // The fallback note may only claim emptiness when the initial lint saw\n // nothing: detection scans candidate top levels, but the lint is recursive,\n // so a nested-only ADR tree is 'fallback' WITH filesChecked > 0 — claiming\n // \"created nothing yet\" beside real findings would contradict itself.\n const dirNote =\n input.adrDirSource === 'detected'\n ? '(detected)'\n : input.adrDirSource === 'override'\n ? '(from --dir)'\n : input.filesChecked === 0\n ? '(default — no existing ADRs found, so init created nothing yet; add your first ADR here)'\n : '(default — no NNNN-*.md at its top level, though the initial lint found Markdown files nested inside)';\n\n const lines = [\n `Wrote ${input.configPath}`,\n '',\n ` ADR directory: ${input.adrDir} ${dirNote}`,\n '',\n 'Next steps:',\n ' - Lint your ADRs: npx madr-lint',\n ];\n\n const total = input.errors + input.warnings;\n if (input.filesChecked > 0 && total > 0) {\n lines.push(\n ` - The initial lint found ${input.errors} error(s) and ${input.warnings} warning(s) across ${input.filesChecked} file(s).`,\n ' Adopting on a legacy repo? Snapshot the debt so only new violations fail the build:',\n ' npx madr-lint --update-baseline',\n );\n }\n\n lines.push(` - Docs: ${GETTING_STARTED_URL}`);\n return lines.join('\\n');\n}\n","import type { List, ListItem, Paragraph, Root } from 'mdast';\nimport { fromMarkdown } from 'mdast-util-from-markdown';\nimport { toString } from 'mdast-util-to-string';\nimport grayMatter from 'gray-matter';\nimport { nullProtoMap } from './null-proto-map.js';\n\n/** Source position of a metadata list item, in body coordinates. */\nexport interface MetadataPosition {\n line: number;\n column: number;\n}\n\nexport interface ParsedFile {\n /** Parsed YAML frontmatter (v3/v4), or null if absent. */\n frontmatter: Record<string, unknown> | null;\n /**\n * v2 body-list metadata extracted from the leading metadata block, or null if\n * absent. The block is the leading RUN of lists, joined across HTML comments\n * (which render as nothing). Covers both the bold-key (`- **Status**:`) and\n * the canonical MADR v2.1.2 plain-key (`* Status:`) shapes. See ADR-0006.\n */\n listMetadata: Record<string, unknown> | null;\n /**\n * Combined metadata: frontmatter merged with bold-list. Frontmatter wins\n * on key conflict. null only when both are absent. See ADR-0006.\n */\n metadata: Record<string, unknown> | null;\n /**\n * Body-coordinate positions (list item start) for metadata keys whose\n * EFFECTIVE value came from the v2 leading list. Keys won by defined\n * frontmatter values are absent: frontmatter is stripped before mdast\n * parsing, so it has no body line — and inline suppression directives\n * (which live in the body) could not target it anyway. null when no\n * effective value is list-sourced.\n */\n metadataLoc: Record<string, MetadataPosition> | null;\n /**\n * Body-coordinate offset RANGE of the effective value for keys whose value\n * came from the v2 leading list AND was a single contiguous text token (no\n * inline markup), verified to slice back to the exact value. Autofix targets\n * the value precisely with this. Frontmatter-won keys are absent (frontmatter\n * is stripped before parsing → no body offset). null when nothing qualifies.\n */\n metadataValueLoc: Record<string, { start: number; end: number }> | null;\n /** mdast root of the body (frontmatter stripped). */\n ast: Root;\n /** Body content with frontmatter stripped. */\n body: string;\n}\n\n/**\n * Passed to EVERY gray-matter call. gray-matter memoizes a parse only when it\n * is called with no options, and its cache is a plain object keyed by the whole\n * document — so a document that is exactly the name of an `Object.prototype`\n * member (`constructor`, `toString`, `hasOwnProperty`) reads that inherited\n * member back as a hit and yields `content: undefined` (#122). The cache also\n * retains every parsed document for the process lifetime, which for a\n * thousand-ADR repo is the whole corpus held live. Frozen because the single\n * instance is shared across all calls.\n *\n * `engines.javascript` replaces gray-matter's default, which `eval`s the block\n * when a document opens with `---js`. ADR files are untrusted input, and\n * `language: 'yaml'` cannot prevent this: the delimiter's language wins.\n */\nconst GRAY_MATTER_OPTIONS: NonNullable<Parameters<typeof grayMatter>[1]> =\n Object.freeze({\n engines: {\n javascript: () => {\n throw new Error(\n 'JavaScript frontmatter (---js) is not supported. Use YAML frontmatter.',\n );\n },\n },\n });\n\n/**\n * Character length of the frontmatter block that gray-matter strips from the\n * front of `content` — i.e. the offset at which the body begins in the whole\n * file. mdast positions are body-relative, so `fileOffset = bodyOffset +\n * frontmatterOffset(content)`. gray-matter never mutates the body, so the body\n * is always an exact suffix and this length is exact (verified across CRLF and\n * leading-newline inputs). Used by the autofix fixer. See ADR-0008.\n */\nexport function frontmatterOffset(content: string): number {\n return content.length - grayMatter(content, GRAY_MATTER_OPTIONS).content.length;\n}\n\n/**\n * Parse a Markdown file: extract YAML frontmatter via gray-matter, then\n * feed the body into mdast-util-from-markdown directly (per ADR-0002,\n * we skip the unified+remark pipeline overhead). Additionally, extract\n * v2-style list metadata from the body's leading metadata block — both\n * bold-key and canonical plain-key shapes (see ADR-0006).\n */\nexport function parseFile(content: string): ParsedFile {\n const matter = grayMatter(content, GRAY_MATTER_OPTIONS);\n const data = matter.data as Record<string, unknown>;\n const frontmatter = Object.keys(data).length > 0 ? data : null;\n const ast = fromMarkdown(matter.content);\n const listResult = extractListMetadataWithLoc(ast, matter.content);\n const listMetadata = listResult?.values ?? null;\n\n let metadata: Record<string, unknown> | null = null;\n let metadataLoc: Record<string, MetadataPosition> | null =\n listResult && Object.keys(listResult.loc).length > 0\n ? { ...listResult.loc }\n : null;\n let metadataValueLoc: Record<string, { start: number; end: number }> | null =\n listResult && Object.keys(listResult.valueOffsets).length > 0\n ? { ...listResult.valueOffsets }\n : null;\n if (frontmatter && listMetadata) {\n // Frontmatter wins on conflict, BUT explicit null/undefined are\n // skipped so that `status: ~` in YAML doesn't blank a present\n // list value (counterintuitive UX otherwise).\n // Null prototype: YAML keys are document-controlled and gray-matter hands\n // `__proto__` back as an own key, which a plain object would feed to the\n // inherited setter — silently dropping it (#56).\n const frontmatterDefined = nullProtoMap<unknown>();\n for (const [k, v] of Object.entries(frontmatter)) {\n if (v !== null && v !== undefined) frontmatterDefined[k] = v;\n }\n metadata = { ...listMetadata, ...frontmatterDefined };\n // A frontmatter-won key's effective value is not in the body, so it\n // must not advertise the (shadowed) list item's position or value range.\n if (metadataLoc) {\n for (const k of Object.keys(frontmatterDefined)) delete metadataLoc[k];\n if (Object.keys(metadataLoc).length === 0) metadataLoc = null;\n }\n if (metadataValueLoc) {\n for (const k of Object.keys(frontmatterDefined)) delete metadataValueLoc[k];\n if (Object.keys(metadataValueLoc).length === 0) metadataValueLoc = null;\n }\n } else if (frontmatter) {\n metadata = frontmatter;\n } else if (listMetadata) {\n metadata = listMetadata;\n }\n\n return {\n frontmatter,\n listMetadata,\n metadata,\n metadataLoc,\n metadataValueLoc,\n ast,\n body: matter.content,\n };\n}\n\n// Canonical MADR metadata field names across v2/v3/v4 (after key\n// normalization). Used to distinguish a genuine metadata block from an\n// ordinary leading list: the plain-key shape (`Foo: bar`) is syntactically\n// identical to prose bullets, so we only promote the block to metadata when it\n// carries at least one of these keys. The gate runs once over the merged\n// segments, not per list. The set is deliberately the canonical fields — a\n// block keyed only by non-standard fields (e.g. `Tags`, `Author`) is treated\n// as prose, not metadata. See the dogfooding notes in ADR-0006.\nconst RECOGNIZED_METADATA_KEYS = new Set([\n 'status',\n 'date',\n 'deciders',\n 'decision-makers',\n 'consulted',\n 'informed',\n]);\n\n/**\n * Extract v2-style list metadata from an mdast Root. The metadata block leads\n * the body: only H1 headings and HTML nodes may precede it — an H2-or-deeper\n * heading, or any other block before the first list, means there is no\n * metadata block. The block itself is the leading RUN of lists, joined across\n * HTML comments — a comment ends the CommonMark list yet renders as nothing,\n * so a field below it is still metadata (#73). A second list with nothing\n * between it and the first (a bullet-marker change, or an ordered list) joins\n * the run when EVERY one of its items reads as a `Key: value` pair (#115); a\n * list carrying any other item is prose and ends the block. Once the run has\n * started, visible HTML, a paragraph, a code fence, a thematic break, a\n * blockquote, and a heading of ANY depth all end the block. Each `listItem` is\n * read as one `Key: value` pair in either of two shapes:\n *\n * - Bold key: `- **Status**: accepted` (some MADR v2 authors)\n * - Plain key: `* Status: accepted` (official MADR v2.1.2 template)\n *\n * Key normalization: trim → lowercase → spaces become hyphens\n * (matches v4 frontmatter convention, e.g. \"Decision Makers\" →\n * \"decision-makers\"). On duplicate keys, first occurrence wins — across\n * segments too, so an earlier segment always shadows a later one.\n *\n * Returns null unless the block carries a recognized MADR key, so ordinary\n * leading prose lists are not mistaken for metadata. (A leading list keyed by\n * a recognized field is still treated as metadata even if its value reads\n * prose-like, e.g. `Status: under discussion` — that position + key is\n * overwhelmingly metadata; this is an intentional, bounded tradeoff.)\n *\n * Because the result is gated on MADR field names, callers using this as a\n * generic list reader will get null for purely non-MADR lists.\n */\nexport function extractListMetadata(\n ast: Root,\n): Record<string, unknown> | null {\n return extractListMetadataWithLoc(ast)?.values ?? null;\n}\n\n/**\n * True when an `html` node is nothing but HTML comments — one or more, with\n * only whitespace between them — i.e. invisible in the rendered document.\n * Deliberately independent of suppression.ts, which rejects several of these\n * shapes as directives: the question here is \"does a reader see this?\", not\n * \"is this a madr-lint directive?\".\n *\n * Consuming the comments in a run rather than testing the first and last four\n * characters is what keeps `<!-- a --> visible <!-- b -->` out — CommonMark\n * ends an HTML block on the first line containing `-->`, so that whole line is\n * ONE node whose visible text a reader does see.\n */\nfunction isHtmlComment(value: string): boolean {\n let rest = value.trim();\n if (rest.length === 0) return false;\n while (rest.length > 0) {\n if (!rest.startsWith('<!--')) return false;\n // From index 2, not 4: CommonMark admits `<!-->` and `<!--->` as empty\n // comments, where the closing `-->` overlaps the opening `<!--`.\n const end = rest.indexOf('-->', 2);\n if (end === -1) return false;\n rest = rest.slice(end + 3).trim();\n }\n return true;\n}\n\n/**\n * Position-carrying variant used by parseFile: alongside the values, records\n * each key's list-item start position (body coordinates) so diagnostics on\n * list-sourced metadata can carry a line — which is what makes inline\n * suppression directives able to target them.\n */\nfunction extractListMetadataWithLoc(\n ast: Root,\n body?: string,\n): {\n values: Record<string, unknown>;\n loc: Record<string, MetadataPosition>;\n valueOffsets: Record<string, { start: number; end: number }>;\n} | null {\n const segments: List[] = [];\n let bridged = false;\n for (const child of ast.children) {\n if (segments.length === 0) {\n if (child.type === 'heading') {\n if (child.depth >= 2) break; // reached the first H2 — no metadata block\n continue; // skip the H1 title (and any further H1s)\n }\n // Leading HTML comments / markers don't displace the metadata block.\n if (child.type === 'html') continue;\n if (child.type === 'list') {\n segments.push(child);\n continue;\n }\n // Any other leading block (paragraph, code, thematic break, …) means the\n // metadata block — which must lead — is absent; the list, if any, is prose.\n break;\n }\n // An HTML comment between two items ends the CommonMark list but renders as\n // nothing, so the block a reader sees is still one block: a field below it\n // must not vanish from metadata (#73). Only pure comments bridge — visible\n // HTML is real content. A heading of ANY depth ends the block here; the H1\n // branch above must stay unreachable once a segment exists, or\n // `list | comment | # Other | list` would merge across a section boundary.\n if (child.type === 'html' && isHtmlComment(child.value)) {\n bridged = true;\n continue;\n }\n // A bullet-marker change (`* Status:` then `- Date:`) also splits the block\n // into two CommonMark lists, but with NO node between them for a comment to\n // bridge (#115). Shape is all the scan has to go on there, so such a list\n // joins the block only when EVERY item reads as a `Key: value` pair — which\n // keeps an ordinary prose list out, at the cost of merging a prose list\n // that happens to be KV-shaped throughout (same class as the #73 tradeoff).\n // The length guard matters because `[].every()` is true: no Markdown\n // produces an empty list, but this function is public and takes any Root.\n if (\n child.type === 'list' &&\n (bridged ||\n (child.children.length > 0 &&\n child.children.every((item) => extractListItemKV(item))))\n ) {\n segments.push(child);\n bridged = false;\n continue;\n }\n break;\n }\n\n if (segments.length === 0) return null;\n\n // Keys come from the document. `Object.hasOwn` below is the fix for #56:\n // `in` sees inherited members, so `* Constructor: x` read as a duplicate of\n // a key that was never set and was dropped. The null prototype is defense in\n // depth — with it, an unset key can never read back an inherited value.\n const values = nullProtoMap<unknown>();\n const loc = nullProtoMap<MetadataPosition>();\n const valueOffsets = nullProtoMap<{ start: number; end: number }>();\n for (const list of segments) {\n for (const item of list.children) {\n const pair = extractListItemKV(item);\n if (pair && !Object.hasOwn(values, pair.key)) {\n values[pair.key] = pair.value;\n const start = item.position?.start;\n if (start) loc[pair.key] = { line: start.line, column: start.column };\n // Record the value offset only when it slices back to the exact value —\n // this rejects any misalignment from escapes / entities, keeping autofix\n // edits precise. Only available when the body text is supplied.\n if (\n body !== undefined &&\n pair.valueOffset &&\n body.slice(pair.valueOffset.start, pair.valueOffset.end) === pair.value\n ) {\n valueOffsets[pair.key] = pair.valueOffset;\n }\n }\n }\n }\n\n const hasRecognizedKey = Object.keys(values).some((k) =>\n RECOGNIZED_METADATA_KEYS.has(k),\n );\n // `values` is handed back as a PLAIN copy: it reaches the public\n // `extractListMetadata` / `ParsedFile.listMetadata` surface verbatim, where\n // a consumer may call `hasOwnProperty` on it. Spreading uses\n // CreateDataProperty, so a `constructor` key stays an own data property.\n // `loc` / `valueOffsets` need no copy here — this function is private and\n // parseFile already spreads them into fresh objects before exposing them.\n return hasRecognizedKey ? { values: { ...values }, loc, valueOffsets } : null;\n}\n\nconst KEY_PATTERN = /^[A-Za-z][A-Za-z0-9 \\-_]*$/;\nconst SEPARATOR_PATTERN = /^\\s*:\\s*/;\n\nfunction normalizeKey(rawKey: string): string {\n return rawKey.trim().toLowerCase().replace(/\\s+/g, '-');\n}\n\n/**\n * Read a single `Key: value` pair from one list item. Returns null for any\n * item not shaped as metadata. Two shapes are supported:\n *\n * - Bold key: paragraph starts with `strong` (the Key), followed by a\n * `: value` text node and optional inline tail.\n * - Plain key: paragraph starts with a `text` node holding `Key: value`,\n * followed by optional inline tail.\n *\n * In both shapes the value's inline tail (links, code, emphasis) is\n * flattened via mdast-util-to-string so values keep their text.\n */\nfunction extractListItemKV(\n item: ListItem,\n): {\n key: string;\n value: string;\n /** Body offset range of a single-text-token value (no inline tail). */\n valueOffset?: { start: number; end: number };\n} | null {\n const para = item.children[0];\n if (para?.type !== 'paragraph') return null;\n\n const paragraph = para as Paragraph;\n const first = paragraph.children[0];\n if (!first) return null;\n\n // Bold key: `**Key**` then a separator text node carrying `: value`.\n if (first.type === 'strong') {\n const keyNode = first.children[0];\n if (keyNode?.type !== 'text' || !KEY_PATTERN.test(keyNode.value)) {\n return null;\n }\n\n const sep = paragraph.children[1];\n if (sep?.type !== 'text' || !SEPARATOR_PATTERN.test(sep.value)) return null;\n\n const valueHead = sep.value.replace(SEPARATOR_PATTERN, '');\n const valueTail = paragraph.children\n .slice(2)\n .map((n) => toString(n))\n .join('');\n const value = (valueHead + valueTail).trim();\n if (value === '') return null;\n\n // Precise offset only for a tail-free value (`**Key**: value` and nothing\n // else) — the value then sits contiguously inside `sep`.\n let valueOffset: { start: number; end: number } | undefined;\n const sepStart = sep.position?.start.offset;\n if (\n paragraph.children.length === 2 &&\n typeof sepStart === 'number' &&\n valueHead !== ''\n ) {\n const sepMatch = SEPARATOR_PATTERN.exec(sep.value);\n const vStart = sepStart + (sepMatch ? sepMatch[0].length : 0);\n valueOffset = { start: vStart, end: vStart + value.length };\n }\n\n return { key: normalizeKey(keyNode.value), value, valueOffset };\n }\n\n // Plain key (canonical MADR v2.1.2): first text node holds `Key: value`.\n if (first.type === 'text') {\n const colonIdx = first.value.indexOf(':');\n if (colonIdx === -1) return null;\n\n const rawKey = first.value.slice(0, colonIdx);\n if (!KEY_PATTERN.test(rawKey)) return null;\n\n const afterColon = first.value.slice(colonIdx + 1);\n const valueHead = afterColon.replace(/^\\s+/, '');\n const valueTail = paragraph.children\n .slice(1)\n .map((n) => toString(n))\n .join('');\n const value = (valueHead + valueTail).trim();\n if (value === '') return null;\n\n // Precise offset only for a tail-free value (`Key: value` in one text node).\n let valueOffset: { start: number; end: number } | undefined;\n const nodeStart = first.position?.start.offset;\n if (\n paragraph.children.length === 1 &&\n typeof nodeStart === 'number' &&\n valueHead !== ''\n ) {\n const leadingWs = afterColon.length - valueHead.length;\n const vStart = nodeStart + colonIdx + 1 + leadingWs;\n valueOffset = { start: vStart, end: vStart + value.length };\n }\n\n return { key: normalizeKey(rawKey), value, valueOffset };\n }\n\n return null;\n}\n","/**\n * Null-prototype map. Keys across this codebase derive from untrusted input —\n * on-disk filenames, rule metadata, MADR body-list field names — so a key like\n * '__proto__' or 'constructor' must behave as ordinary data. On a plain object\n * it would read/write THROUGH Object.prototype instead (prototype pollution,\n * silently dropped entries, an inherited value returned for a key that was\n * never set). With no prototype there is nothing to inherit or pollute.\n * JSON.stringify output is identical.\n */\nexport function nullProtoMap<T>(): Record<string, T> {\n return Object.create(null) as Record<string, T>;\n}\n","import { readFileSync, statSync } from 'node:fs';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport { applyBaseline, type Baseline } from './baseline.js';\nimport {\n computeContentHash,\n loadManifest,\n manifestPath,\n saveManifest,\n CACHE_SCHEMA_VERSION,\n type CacheEntry,\n type CacheManifest,\n} from './cache.js';\nimport {\n applyEditsCounted,\n collectProjectFixes,\n fixFileContent,\n MAX_FIX_PASSES,\n} from './fix.js';\nimport { nullProtoMap } from './null-proto-map.js';\nimport {\n buildProjectFile,\n runRulesOnFile,\n runRulesOnProject,\n} from './runner.js';\nimport {\n collectDirectives,\n isSuppressed,\n DIRECTIVE_PREFIX,\n type DirectiveIndex,\n} from './suppression.js';\n\n/**\n * Normalize a relative path to POSIX form (forward-slashes). On Windows,\n * `relative()` returns backslash-separated paths, but project rules\n * (e.g. `no-broken-links`) resolve URLs using `posix.normalize`. Mixing\n * separators causes silent false positives — so the orchestrator owns\n * the normalization.\n */\nfunction toPosix(p: string): string {\n if (sep === '/') return p;\n return p.split(sep).join('/');\n}\nimport {\n INTERNAL_ERROR_RULE_NAME,\n isProjectRule,\n type AnyRule,\n type Diagnostic,\n type ProjectFile,\n type ProjectRule,\n type Rule,\n type RuleSeverity,\n type Severity,\n} from './types.js';\n\nexport interface CacheConfig {\n /** Directory under which manifest.json is read/written. */\n dir: string;\n /** Stable hash of the resolved config. Mismatch invalidates the cache. */\n configHash: string;\n /** madr-lint package version. Mismatch invalidates the cache. */\n pkgVersion: string;\n}\n\nexport interface LintOptions {\n /** All available rules (per-file or project). The orchestrator picks\n * enabled ones via ruleSeverity and dispatches to the right runner. */\n rules: readonly AnyRule[];\n /** User-configured severity per rule name. Missing entry === 'off'. */\n ruleSeverity: Record<string, RuleSeverity>;\n /** Files to lint, absolute or cwd-relative paths. */\n files: readonly string[];\n /** Working directory. Diagnostics' `path` is reported relative to this. */\n cwd: string;\n /**\n * If set, persist per-file diagnostics keyed by content hash to enable\n * fast re-runs of unchanged files. Project (cross-file) rules always\n * re-run regardless of the cache. Pass `null` to disable.\n */\n cache?: CacheConfig | null;\n /**\n * Parsed baseline to subtract, or null/undefined to disable. Subtraction\n * runs AFTER inline-suppression filtering and is NEVER written to the cache\n * (the cache keeps pre-baseline diagnostics, so editing or deleting the\n * baseline file takes effect without cache invalidation). See ADR-0007.\n */\n baseline?: Baseline | null;\n}\n\nexport interface LintResult {\n filesChecked: number;\n filesFromCache: number;\n diagnostics: Diagnostic[];\n /** How many diagnostics the baseline absorbed (0 when no baseline). */\n baselineHidden: number;\n}\n\n/**\n * Lint a list of files against per-file AND project rules.\n *\n * Per-file rules go through `runRulesOnFile` (single-pass AST per file).\n * Project rules go through `runRulesOnProject` (eager parse all files,\n * single check call per rule). See ADR-0005.\n *\n * File contents are read once and shared between both passes.\n */\nexport function lintFiles(opts: LintOptions): LintResult {\n const allDiagnostics: Diagnostic[] = [];\n\n // Read all file contents once — shared between per-file and project passes.\n // POSIX-normalize the relative path so cross-platform diagnostics and\n // path-based rules (e.g. no-broken-links) work consistently on Windows.\n const fileEntries = opts.files.map((absolutePath) => ({\n relativePath: toPosix(relative(opts.cwd, absolutePath)),\n content: readFileSync(absolutePath, 'utf8'),\n }));\n\n // Partition rules by kind.\n const perFileRules: Rule[] = [];\n const projectRules: ProjectRule[] = [];\n for (const rule of opts.rules) {\n if (isProjectRule(rule)) projectRules.push(rule);\n else perFileRules.push(rule);\n }\n\n // Per-rule options from config tuples (`['error', {...}]`), threaded into\n // both passes so rule options actually take effect (not just defaults).\n const optionsByRule = extractOptionsByRule(opts.ruleSeverity);\n\n // ── Cache setup ──────────────────────────────────────────────────\n let manifest: CacheManifest | null = null;\n let filesFromCache = 0;\n if (opts.cache) {\n const path = manifestPath(opts.cache.dir);\n const loaded = loadManifest(path);\n if (\n loaded &&\n loaded.version === opts.cache.pkgVersion &&\n loaded.configHash === opts.cache.configHash\n ) {\n manifest = loaded;\n } else {\n manifest = {\n schemaVersion: CACHE_SCHEMA_VERSION,\n version: opts.cache.pkgVersion,\n configHash: opts.cache.configHash,\n files: nullProtoMap<CacheEntry>(),\n };\n }\n }\n\n // ── Per-file pass ────────────────────────────────────────────────\n for (const file of fileEntries) {\n if (manifest) {\n const contentHash = computeContentHash(file.content);\n const entry = manifest.files[file.relativePath];\n if (entry && entry.contentHash === contentHash) {\n allDiagnostics.push(...entry.perFileDiagnostics);\n filesFromCache++;\n continue;\n }\n // Cache miss — run rules and store result.\n const fileDiagnostics = runPerFileRulesForFile(\n perFileRules,\n opts.ruleSeverity,\n optionsByRule,\n file,\n );\n allDiagnostics.push(...fileDiagnostics);\n const next: CacheEntry = {\n contentHash,\n perFileDiagnostics: fileDiagnostics,\n };\n manifest.files[file.relativePath] = next;\n } else {\n allDiagnostics.push(\n ...runPerFileRulesForFile(\n perFileRules,\n opts.ruleSeverity,\n optionsByRule,\n file,\n ),\n );\n }\n }\n\n // ── Project pass ─────────────────────────────────────────────────\n const enabledProjectRules = projectRules.filter(\n (rule) => resolveSeverity(opts.ruleSeverity[rule.meta.name]) !== 'off',\n );\n if (enabledProjectRules.length > 0) {\n const projectFiles = fileEntries.map((f) =>\n buildProjectFile({ path: f.relativePath, content: f.content }),\n );\n allDiagnostics.push(\n ...runProjectPass(\n projectFiles,\n enabledProjectRules,\n opts.ruleSeverity,\n optionsByRule,\n opts.cwd,\n ),\n );\n }\n\n // ── Persist cache ────────────────────────────────────────────────\n // Cache the PRE-baseline diagnostics: subtraction happens below, after the\n // save, so the on-disk cache is independent of the baseline file.\n if (manifest && opts.cache) {\n saveManifest(manifestPath(opts.cache.dir), manifest);\n }\n\n // ── Baseline subtraction ─────────────────────────────────────────\n // Zero work when no baseline is provided (the common hot path).\n if (!opts.baseline) {\n return {\n filesChecked: opts.files.length,\n filesFromCache,\n diagnostics: allDiagnostics,\n baselineHidden: 0,\n };\n }\n const applied = applyBaseline(allDiagnostics, opts.baseline);\n return {\n filesChecked: opts.files.length,\n filesFromCache,\n diagnostics: applied.kept,\n baselineHidden: applied.hidden,\n };\n}\n\n// ──────────────────────────────────────────────────────────────────\n// Autofix orchestration (issue #28). Kept OUT of lintFiles so the linting\n// hot path gains no branch. The cache is intentionally not consulted here:\n// fixes need live `fix` thunks, which cache-hydrated diagnostics lack, and a\n// fixed file re-enters the normal pipeline on the next run with a fresh hash.\n// ──────────────────────────────────────────────────────────────────\n\nexport interface FixedFile {\n /** POSIX relative path (diagnostics + diff header). */\n path: string;\n /** Absolute path (for writing). */\n absPath: string;\n /** Original on-disk content. */\n original: string;\n /** Content after the fixpoint loop. */\n fixed: string;\n /** Whether `fixed` differs from `original`. */\n changed: boolean;\n}\n\nexport interface LintAndFixResult {\n files: FixedFile[];\n /** Remaining diagnostics on the FIXED contents (per-file + project). */\n diagnostics: Diagnostic[];\n /** Count of edits applied across every file/pass. */\n fixed: number;\n /** Diagnostics the baseline absorbed on the final contents. */\n baselineHidden: number;\n}\n\nexport interface LintAndFixOptions {\n rules: readonly AnyRule[];\n ruleSeverity: Record<string, RuleSeverity>;\n files: readonly string[];\n cwd: string;\n baseline?: Baseline | null;\n /** Fixpoint pass cap; defaults to the applier's MAX_FIX_PASSES. */\n maxPasses?: number;\n}\n\n/**\n * Lint AND autofix: run the per-file fixpoint on every file (fixes are\n * collected from REPORTED diagnostics only — suppression + baseline already\n * subtracted, so suppressed/baselined problems are never rewritten), then run\n * the project pass on the FIXED contents for the final diagnostic set. Files\n * are returned with their fixed content; the caller writes them (or prints a\n * diff for a dry run). Per-file only — project-rule fixes are #29.\n */\nexport function lintAndFix(opts: LintAndFixOptions): LintAndFixResult {\n const perFileRules: Rule[] = [];\n const projectRules: ProjectRule[] = [];\n for (const rule of opts.rules) {\n if (isProjectRule(rule)) projectRules.push(rule);\n else perFileRules.push(rule);\n }\n const optionsByRule = extractOptionsByRule(opts.ruleSeverity);\n const baseline = opts.baseline ?? null;\n\n const files: FixedFile[] = [];\n const remaining: Diagnostic[] = [];\n let fixed = 0;\n let baselineHidden = 0;\n\n for (const absPath of opts.files) {\n const relativePath = toPosix(relative(opts.cwd, absPath));\n const original = readFileSync(absPath, 'utf8');\n\n // `lint` returns the REPORTED (post-suppression, post-baseline) diagnostics\n // for the given content, carrying live fix thunks. `lastHidden` tracks the\n // final pass's baseline absorption for the summary.\n let lastHidden = 0;\n const lint = (content: string): Diagnostic[] => {\n const raw = runPerFileRulesForFile(\n perFileRules,\n opts.ruleSeverity,\n optionsByRule,\n { relativePath, content },\n );\n if (!baseline) {\n lastHidden = 0;\n return raw;\n }\n const sub = applyBaseline(raw, baseline);\n lastHidden = sub.hidden;\n return sub.kept;\n };\n\n const res = fixFileContent(original, lint, opts.maxPasses);\n files.push({\n path: relativePath,\n absPath,\n original,\n fixed: res.fixedContent,\n changed: res.changed,\n });\n remaining.push(...res.remaining);\n fixed += res.applied;\n baselineHidden += lastHidden;\n }\n\n // ── Project pass + cross-file fix fixpoint on the FIXED contents ──\n // Cross-file (project-rule) fixes reuse the applier through their own per-file\n // edit collection (the #29 seam). The per-file fixpoint has already settled;\n // project fixes only insert whole-file frontmatter keys (supersedes /\n // superseded-by) that no per-file rule reads, so the per-file `remaining`\n // captured above stays valid. See ADR-0008.\n const enabledProjectRules = projectRules.filter(\n (rule) => resolveSeverity(opts.ruleSeverity[rule.meta.name]) !== 'off',\n );\n if (enabledProjectRules.length > 0) {\n // Mutable per-path content, seeded from the per-file-fixed files.\n const contentByPath = new Map(files.map((f) => [f.path, f.fixed]));\n const maxPasses = opts.maxPasses ?? MAX_FIX_PASSES;\n\n let projectDiags: Diagnostic[] = [];\n let passes = 0;\n for (;;) {\n const projectFiles = files.map((f) =>\n buildProjectFile({ path: f.path, content: contentByPath.get(f.path) ?? f.fixed }),\n );\n // runProjectPass returns suppression-filtered, PRE-baseline diagnostics.\n // Subtract the baseline BEFORE collecting fixes so a baselined problem is\n // never rewritten (parity with the per-file path). Only the pass we stop on\n // contributes to `baselineHidden` — the loop recomputes it each iteration.\n let diags = runProjectPass(\n projectFiles,\n enabledProjectRules,\n opts.ruleSeverity,\n optionsByRule,\n opts.cwd,\n );\n let hiddenThisPass = 0;\n if (baseline) {\n const sub = applyBaseline(diags, baseline);\n diags = sub.kept;\n hiddenThisPass = sub.hidden;\n }\n projectDiags = diags;\n\n if (passes >= maxPasses) {\n baselineHidden += hiddenThisPass;\n break;\n }\n const fixesByPath = collectProjectFixes(diags, (p) => contentByPath.get(p));\n let progressed = false;\n for (const [path, edits] of fixesByPath) {\n const before = contentByPath.get(path);\n if (before === undefined) continue;\n const { text, applied } = applyEditsCounted(before, edits);\n if (text !== before) {\n contentByPath.set(path, text);\n fixed += applied;\n progressed = true;\n }\n }\n if (!progressed) {\n baselineHidden += hiddenThisPass;\n break;\n }\n passes++;\n }\n\n // Fold the project-fixed contents back into the returned files.\n for (const f of files) {\n const c = contentByPath.get(f.path);\n if (c !== undefined && c !== f.fixed) {\n f.fixed = c;\n f.changed = f.fixed !== f.original;\n }\n }\n remaining.push(...projectDiags);\n }\n\n return { files, diagnostics: remaining, fixed, baselineHidden };\n}\n\nfunction runPerFileRulesForFile(\n perFileRules: readonly Rule[],\n ruleSeverity: Record<string, RuleSeverity>,\n optionsByRule: Record<string, Record<string, unknown>>,\n file: { relativePath: string; content: string },\n): Diagnostic[] {\n const fileContext = {\n content: file.content,\n path: file.relativePath,\n };\n\n const errorRules: Rule[] = [];\n const warnRules: Rule[] = [];\n for (const rule of perFileRules) {\n const sev = resolveSeverity(ruleSeverity[rule.meta.name]);\n if (sev === 'off') continue;\n if (sev === 'error') errorRules.push(rule);\n else warnRules.push(rule);\n }\n\n const diagnostics: Diagnostic[] = [];\n if (errorRules.length > 0) {\n diagnostics.push(\n ...runRulesOnFile(errorRules, fileContext, {\n severity: 'error',\n optionsByRule,\n }),\n );\n }\n if (warnRules.length > 0) {\n diagnostics.push(\n ...runRulesOnFile(warnRules, fileContext, {\n severity: 'warn',\n optionsByRule,\n }),\n );\n }\n return diagnostics;\n}\n\n/**\n * Build the `fileExists` predicate exposed to project rules. Does a path exist\n * as a regular file WITHIN the project root? Lets project rules verify link\n * targets not in the linted .md set (non-Markdown assets, files outside the\n * scanned paths). Contract:\n * - a target that resolves at or above the root escapes the project →\n * false (path traversal cannot reach above the root);\n * - directories are not files → false (a link must point at a file);\n * - results are memoized per lint run (repeated targets cost one stat).\n * The containment test compares the resolved absolute path to the root rather\n * than a lexical prefix on `resolvedPath`, which may be un-normalized (e.g. a\n * `/`-rooted link resolving to `foo/../../x`). NOTE: statSync follows symlinks,\n * so a symlink that lives inside the root but points outside it is accepted —\n * it is a real entry in the project tree. `resolvedPath` is POSIX; node's\n * path.resolve accepts forward slashes on all platforms.\n */\nfunction makeFileExists(cwd: string): (resolvedPath: string) => boolean {\n const projectRoot = resolve(cwd);\n const existsCache = new Map<string, boolean>();\n return (resolvedPath: string): boolean => {\n const cached = existsCache.get(resolvedPath);\n if (cached !== undefined) return cached;\n const abs = resolve(projectRoot, resolvedPath);\n const rel = relative(projectRoot, abs);\n const escapesRoot =\n rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);\n let result: boolean;\n if (escapesRoot) {\n result = false;\n } else {\n try {\n result = statSync(abs, { throwIfNoEntry: false })?.isFile() ?? false;\n } catch {\n // ELOOP / EACCES / invalid path → cannot confirm a regular file.\n result = false;\n }\n }\n existsCache.set(resolvedPath, result);\n return result;\n };\n}\n\n/**\n * Run enabled project rules over the given (already-built) project files and\n * return suppression-filtered, PRE-baseline diagnostics. Shared by `lintFiles`\n * (files as read) and `lintAndFix` (files after autofix). Baseline subtraction\n * is the caller's job.\n */\nfunction runProjectPass(\n projectFiles: readonly ProjectFile[],\n enabledProjectRules: readonly ProjectRule[],\n ruleSeverity: Record<string, RuleSeverity>,\n optionsByRule: Record<string, Record<string, unknown>>,\n cwd: string,\n): Diagnostic[] {\n const fileExists = makeFileExists(cwd);\n const errorRules: ProjectRule[] = [];\n const warnRules: ProjectRule[] = [];\n for (const rule of enabledProjectRules) {\n const sev = resolveSeverity(ruleSeverity[rule.meta.name]);\n if (sev === 'error') errorRules.push(rule);\n else if (sev === 'warn') warnRules.push(rule);\n }\n\n const projectDiagnostics: Diagnostic[] = [];\n if (errorRules.length > 0) {\n projectDiagnostics.push(\n ...runRulesOnProject(errorRules, projectFiles, {\n severity: 'error',\n fileExists,\n optionsByRule,\n }),\n );\n }\n if (warnRules.length > 0) {\n projectDiagnostics.push(\n ...runRulesOnProject(warnRules, projectFiles, {\n severity: 'warn',\n fileExists,\n optionsByRule,\n }),\n );\n }\n\n // Inline suppression for project diagnostics (issue #23): a directive lives\n // in the file the diagnostic is attributed to (project rules report an\n // explicit `path`). File-scoped directives (disable-file, or an open-ended\n // disable) suppress line-less project diagnostics; line-scoped directives\n // apply when the diagnostic carries a line.\n return filterSuppressedProjectDiagnostics(projectDiagnostics, projectFiles);\n}\n\n/**\n * Filter suppressed project-rule diagnostics. Each diagnostic is matched\n * against the directives of the file it is attributed to (by `path`).\n * Directive indexes are collected lazily and memoized so a file with no\n * suppressed diagnostics is walked at most once. `core/internal-error`\n * diagnostics (and any diagnostic whose path has no source file, e.g. the\n * `<project>` sentinel) are never suppressed.\n */\nfunction filterSuppressedProjectDiagnostics(\n diagnostics: readonly Diagnostic[],\n projectFiles: readonly ProjectFile[],\n): Diagnostic[] {\n if (diagnostics.length === 0) return [];\n\n const fileByPath = new Map(projectFiles.map((f) => [f.path, f]));\n const indexByPath = new Map<string, DirectiveIndex | null>();\n const indexFor = (path: string): DirectiveIndex | null => {\n const cached = indexByPath.get(path);\n if (cached !== undefined) return cached;\n const file = fileByPath.get(path);\n // Unlike the per-file path, no parse can be forced here — project files\n // are eager-parsed by buildProjectFile before rules run. The prefix\n // check just skips the AST walk for directive-free files (same\n // soundness argument: no 'madr-lint-' in content ⇒ no directives).\n const index =\n file && file.content.includes(DIRECTIVE_PREFIX)\n ? collectDirectives(file.ast, file.body)\n : null;\n indexByPath.set(path, index);\n return index;\n };\n\n return diagnostics.filter((d) => {\n if (d.ruleName === INTERNAL_ERROR_RULE_NAME) return true;\n const index = indexFor(d.path);\n if (!index) return true;\n return !isSuppressed(index, d.ruleName, d.loc?.line);\n });\n}\n\nfunction resolveSeverity(config: RuleSeverity | undefined): Severity | 'off' {\n if (config === undefined) return 'off';\n if (typeof config === 'string') return config;\n return config[0];\n}\n\n/**\n * Build a ruleName → options map from config tuples (`['error', {...}]`).\n * Bare-string severities carry no options and are omitted, so rules without\n * configured options fall back to their `meta.defaultOptions` in the runner.\n */\nfunction extractOptionsByRule(\n ruleSeverity: Record<string, RuleSeverity>,\n): Record<string, Record<string, unknown>> {\n const map: Record<string, Record<string, unknown>> = {};\n for (const [name, config] of Object.entries(ruleSeverity)) {\n // Only tuples carry options; guard config[1] so a malformed 1-element\n // `['error']` doesn't put `undefined` into the map.\n if (Array.isArray(config) && config[1]) map[name] = config[1];\n }\n return map;\n}\n","// Baseline file for gradual adoption (issue #24).\n//\n// FINGERPRINT MODEL: a violation is identified by `(relative posix path,\n// ruleName, messageId)` mapped to an allowed COUNT. No line numbers, no\n// message text, no interpolation data — so the baseline survives unrelated\n// edits by construction (a rule that fires the same number of times on the\n// same file+message is fully absorbed regardless of where the lines moved).\n// This is the tsc-baseline-style count model; see ADR-0007 for the rationale\n// and the rejected line-based / content-hash alternatives.\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { nullProtoMap } from './null-proto-map.js';\nimport { INTERNAL_ERROR_RULE_NAME, type Diagnostic } from './types.js';\n\n/** Bumped only on a breaking change to the on-disk shape. */\nexport const BASELINE_VERSION = 1;\n\n/** Fixed, project-root-relative location. Also used verbatim in summaries. */\nexport const BASELINE_DISPLAY_PATH = '.madr-lint/baseline.json';\n\n/**\n * On-disk baseline. `entries` nests path → ruleName → messageId → count.\n * The nesting (rather than a flat hash) is what yields clean, reviewable git\n * diffs once keys are sorted at serialization time.\n */\nexport interface Baseline {\n version: number;\n entries: Record<string, Record<string, Record<string, number>>>;\n}\n\nexport interface BaselineApplyResult {\n /** Diagnostics that survive subtraction (new / over-count / never baselined). */\n kept: Diagnostic[];\n /** How many diagnostics the baseline absorbed. */\n hidden: number;\n}\n\n/** Absolute path to the baseline file for a given project root. */\nexport function baselinePath(cwd: string): string {\n return join(cwd, '.madr-lint', 'baseline.json');\n}\n\n/**\n * Aggregate diagnostics into a baseline by counting each\n * `(path, ruleName, messageId)`. `core/internal-error` is never recorded —\n * it signals a rule bug and must never be silenced (same contract as inline\n * suppression).\n */\nexport function buildBaseline(diagnostics: readonly Diagnostic[]): Baseline {\n const entries = nullProtoMap<Record<string, Record<string, number>>>();\n for (const d of diagnostics) {\n if (d.ruleName === INTERNAL_ERROR_RULE_NAME) continue;\n const byRule = (entries[d.path] ??= nullProtoMap());\n const byMessage = (byRule[d.ruleName] ??= nullProtoMap());\n byMessage[d.messageId] = (byMessage[d.messageId] ?? 0) + 1;\n }\n return { version: BASELINE_VERSION, entries };\n}\n\n/**\n * Deterministic serialization: keys sorted at every level (path, then rule,\n * then messageId), 2-space indent, trailing newline. Same logical baseline\n * always produces byte-identical output → minimal git diffs.\n */\nexport function serializeBaseline(baseline: Baseline): string {\n // Null-proto targets: on a plain object, assigning a '__proto__' key goes\n // through the inherited setter and the entry silently vanishes from the\n // JSON output.\n const sortedEntries = nullProtoMap<Record<string, Record<string, number>>>();\n for (const path of Object.keys(baseline.entries).toSorted()) {\n const byRule = baseline.entries[path] ?? {};\n const sortedByRule = nullProtoMap<Record<string, number>>();\n for (const rule of Object.keys(byRule).toSorted()) {\n const byMessage = byRule[rule] ?? {};\n const sortedByMessage = nullProtoMap<number>();\n for (const messageId of Object.keys(byMessage).toSorted()) {\n sortedByMessage[messageId] = byMessage[messageId] ?? 0;\n }\n sortedByRule[rule] = sortedByMessage;\n }\n sortedEntries[path] = sortedByRule;\n }\n const ordered = { version: baseline.version, entries: sortedEntries };\n return JSON.stringify(ordered, null, 2) + '\\n';\n}\n\n/**\n * Read a baseline file. Returns null when absent or unreadable/malformed —\n * a missing or corrupt baseline is treated as \"no baseline\" (a no-op),\n * never a hard error.\n */\nexport function loadBaseline(path: string): Baseline | null {\n if (!existsSync(path)) return null;\n try {\n const data = JSON.parse(readFileSync(path, 'utf8')) as Partial<Baseline>;\n if (typeof data.entries !== 'object' || data.entries === null) return null;\n return {\n version: typeof data.version === 'number' ? data.version : BASELINE_VERSION,\n entries: rebuildEntries(data.entries as Record<string, unknown>),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Copy parsed JSON into null-proto maps, keeping only well-shaped levels\n * (objects) and leaves (numbers). JSON.parse itself creates '__proto__' as\n * a safe own property, but handing proto-ful objects downstream would\n * re-expose every consumer write to the pollution hazard — and a malformed\n * inner level (e.g. `\"a.md\": null`) would crash applyBaseline.\n */\nfunction rebuildEntries(raw: Record<string, unknown>): Baseline['entries'] {\n const entries = nullProtoMap<Record<string, Record<string, number>>>();\n for (const [path, rawByRule] of Object.entries(raw)) {\n if (typeof rawByRule !== 'object' || rawByRule === null) continue;\n const byRule = nullProtoMap<Record<string, number>>();\n for (const [rule, rawByMessage] of Object.entries(rawByRule)) {\n if (typeof rawByMessage !== 'object' || rawByMessage === null) continue;\n const byMessage = nullProtoMap<number>();\n for (const [messageId, count] of Object.entries(\n rawByMessage as Record<string, unknown>,\n )) {\n if (typeof count === 'number') byMessage[messageId] = count;\n }\n byRule[rule] = byMessage;\n }\n entries[path] = byRule;\n }\n return entries;\n}\n\n/** Write the baseline deterministically, creating `.madr-lint/` if needed. */\nexport function writeBaseline(path: string, baseline: Baseline): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, serializeBaseline(baseline), 'utf8');\n}\n\n// NUL cannot appear in paths, rule names, or messageIds, so joined keys\n// can never collide. Written as an escape (not a raw byte) to keep this\n// file text for git diff/blame.\nconst KEY_SEP = '\\u0000';\n\nfunction fingerprint(d: Diagnostic): string {\n return d.path + KEY_SEP + d.ruleName + KEY_SEP + d.messageId;\n}\n\n/**\n * Subtract a baseline from a diagnostic list. For each fingerprint the first\n * N matching diagnostics (N = baselined count) are absorbed; any extras are\n * reported as new. `core/internal-error` is never absorbed. Stale entries\n * (count exceeding current matches) are simply inert — no error is raised.\n *\n * O(1) per diagnostic: a mutable per-fingerprint allowance map is built once,\n * then each diagnostic is a single map lookup + decrement.\n */\nexport function applyBaseline(\n diagnostics: readonly Diagnostic[],\n baseline: Baseline,\n): BaselineApplyResult {\n const remaining = new Map<string, number>();\n for (const [path, byRule] of Object.entries(baseline.entries)) {\n for (const [rule, byMessage] of Object.entries(byRule)) {\n if (rule === INTERNAL_ERROR_RULE_NAME) continue;\n for (const [messageId, count] of Object.entries(byMessage)) {\n remaining.set(path + KEY_SEP + rule + KEY_SEP + messageId, count);\n }\n }\n }\n\n const kept: Diagnostic[] = [];\n let hidden = 0;\n for (const d of diagnostics) {\n if (d.ruleName === INTERNAL_ERROR_RULE_NAME) {\n kept.push(d);\n continue;\n }\n const key = fingerprint(d);\n const left = remaining.get(key);\n if (left !== undefined && left > 0) {\n remaining.set(key, left - 1);\n hidden++;\n } else {\n kept.push(d);\n }\n }\n return { kept, hidden };\n}\n\nfunction totalCount(baseline: Baseline): number {\n let total = 0;\n for (const byRule of Object.values(baseline.entries)) {\n for (const byMessage of Object.values(byRule)) {\n for (const count of Object.values(byMessage)) total += count;\n }\n }\n return total;\n}\n\n/**\n * One-line summary after `--update-baseline`. Counts the SUM of allowed\n * counts (\"violations\") — not fingerprint keys, and deliberately not the\n * word \"entries\", which names the on-disk map.\n */\nexport function baselineWriteSummary(baseline: Baseline): string {\n const violations = totalCount(baseline);\n const files = Object.keys(baseline.entries).length;\n const v = `${violations} ${violations === 1 ? 'violation' : 'violations'}`;\n const f = `${files} ${files === 1 ? 'file' : 'files'}`;\n return `Wrote ${v} across ${f} to ${BASELINE_DISPLAY_PATH}`;\n}\n\n/**\n * Stderr warning for a baseline file that EXISTS but could not be used.\n * A missing file is silent by design; a present-but-broken one must not be —\n * silently ignoring it would flip CI red with zero explanation.\n */\nexport function baselineMalformedWarning(): string {\n return `madr-lint: warning: ${BASELINE_DISPLAY_PATH} is malformed; ignoring it (run --update-baseline to regenerate)`;\n}\n\n/** One-line summary appended to text output when the baseline hid diagnostics. */\nexport function baselineHiddenSummary(hidden: number): string {\n return `${hidden} ${hidden === 1 ? 'problem' : 'problems'} hidden by baseline (${BASELINE_DISPLAY_PATH})`;\n}\n","// MADR linter core types.\n// Per ADR-0002, the runner uses a single-pass visitor registry over mdast.\n// The Rule type below supports BOTH:\n// - Filename / metadata-only rules: create() returns void\n// - AST-based rules: create() returns RuleListeners with enter/exit handlers\n// keyed by mdast node type. The runner walks each file's tree once and\n// dispatches to all subscribed rules.\n\nimport type { AnySchemaObject } from 'ajv';\nimport type { Nodes, Root } from 'mdast';\n// Note: imports of project-internal modules use the `.js` extension per\n// Node ESM convention. tsc resolves these to the .ts source files.\n\n// All mdast node types rules can subscribe to. `'root'` is the top-level\n// document node — useful for \"after all children visited\" reporting in\n// exit handlers.\nexport type MdastNodeType = Root['type'] | Nodes['type'];\n\n// The argument type for visitor handlers — covers root + all descendants.\nexport type MdastNode = Root | Nodes;\n\n/** Severity levels supported by every rule. */\nexport type Severity = 'error' | 'warn';\n\n// ──────────────────────────────────────────────────────────────────\n// Autofix (see ADR-0008). Fixes are raw-text offset edits, never AST\n// serialization — round-trips lose formatting.\n// ──────────────────────────────────────────────────────────────────\n\n/**\n * A raw-text edit: replace `content.slice(range[0], range[1])` with `text`.\n * Offsets are into the ORIGINAL WHOLE-FILE content (frontmatter included) —\n * the applier's coordinate space. Rules never build these directly; they call\n * the `Fixer` helpers, which translate body (mdast) offsets to whole-file\n * offsets. See ADR-0008.\n */\nexport interface TextEdit {\n range: readonly [number, number];\n text: string;\n}\n\n/**\n * Helpers handed to a rule's `fix` thunk. Range / offset arguments are in\n * BODY (mdast) coordinates — the same space as `node.position.*.offset` — and\n * the fixer adds the stripped-frontmatter length so the returned `TextEdit`\n * carries whole-file offsets. Keep the surface minimal: replace, insert, remove.\n */\nexport interface Fixer {\n /** Replace the body range with `text`. */\n replaceRange(range: readonly [number, number], text: string): TextEdit;\n /** Insert `text` at a body offset (zero-width edit). */\n insertAt(offset: number, text: string): TextEdit;\n /** Delete the body range. */\n remove(range: readonly [number, number]): TextEdit;\n}\n\n/**\n * A lazy fix producer. Invoked ONLY when fixing is requested (zero cost on a\n * normal lint). Returns one edit, several, or `null` to decline (e.g. the\n * concrete fix is not mechanically safe for this occurrence). A rule may only\n * attach a fix when its `meta.fixable === 'code'`.\n */\nexport type FixFn = (fixer: Fixer) => TextEdit | TextEdit[] | null;\n\n/**\n * The reserved rule name used for internal-error diagnostics. A real rule\n * cannot register with this name (the registry should reject collisions).\n * Lives here (not in runner.ts) so leaf modules like the suppression layer\n * can reference it without importing the runner.\n */\nexport const INTERNAL_ERROR_RULE_NAME = 'core/internal-error';\n\n/**\n * User-facing severity declaration in config files.\n * - bare string: enable/disable with default options\n * - tuple: enable with explicit options\n */\nexport type RuleSeverity =\n | Severity\n | 'off'\n | readonly [Severity, Record<string, unknown>];\n\nexport type MadrVersion = 'v2' | 'v3' | 'v4';\n\nexport interface RuleMeta<TOptions = Record<string, unknown>> {\n /** Rule identifier, e.g. 'madr/filename-format'. */\n name: string;\n /** Whether the rule operates per-file or across the project. */\n type: 'perFile' | 'project';\n /** Which MADR versions this rule applies to. */\n versionCompat: readonly MadrVersion[];\n docs: {\n description: string;\n url?: string;\n recommended: boolean;\n };\n /** messageId -> template string with {{placeholders}}. */\n messages: Record<string, string>;\n /**\n * messageId -> machine-actionable remediation template, parallel to\n * `messages` and interpolated the same way. Partial: declare an entry only\n * where a concrete, mechanical fix exists; omit where the fix is inherently\n * contextual. The runner resolves the entry for a reported messageId into\n * `Diagnostic.suggestion` (null when absent). See issue #67.\n */\n suggestions?: Record<string, string>;\n /**\n * Whether this rule can produce autofixes. `'code'` = the rule may attach a\n * `fix` thunk to `context.report(...)`; the runner honors it only for\n * fixable rules and routes a fix from a non-fixable rule through\n * `core/internal-error` (a rule bug). Defaults to `false`. See ADR-0008.\n */\n fixable?: 'code' | false;\n /** Default options merged with user config. */\n defaultOptions: TOptions;\n /** AJV JSON Schema for options. Validated at runRule time. */\n schema?: AnySchemaObject;\n}\n\nexport interface FileContext {\n /** File path (typically basename or repo-relative). */\n path: string;\n /** Raw file content. */\n content: string;\n}\n\nexport interface Diagnostic {\n /** Rule that emitted this diagnostic. */\n ruleName: string;\n /** messageId from rule.meta.messages. */\n messageId: string;\n /** Severity resolved against the user config. */\n severity: Severity;\n /** Path of the offending file. */\n path: string;\n /** Optional source range. Filename rules omit this. */\n loc?: {\n line: number;\n column: number;\n };\n /** Data for placeholder interpolation in messages[messageId]. */\n data?: Record<string, unknown>;\n /**\n * Machine-actionable remediation, resolved by the runner at report time from\n * `rule.meta.suggestions[messageId]` (interpolated with `data`, exactly like\n * the message). `null` when the rule declares no suggestion for this\n * messageId. Every runner-produced diagnostic sets this. See issue #67.\n */\n suggestion: string | null;\n /**\n * The rule's documentation URL (`rule.meta.docs.url`), resolved by the runner\n * so consumers need not reconstruct it from `ruleName`. `core/internal-error`\n * diagnostics point at the repository; a rule without a docs URL yields `''`.\n */\n docsUrl: string;\n /**\n * Whether an autofix is available for THIS diagnostic — the rule is\n * `meta.fixable: 'code'` AND attached a `fix` thunk here. Durable (serialized\n * to the cache and json output; reporters render a 🔧 marker). Every\n * runner-produced diagnostic sets this. See ADR-0008 / issue #28.\n */\n fixable: boolean;\n /**\n * The live fix producer, present only when `fixable` is true. TRANSIENT — a\n * closure, so it is dropped by JSON serialization (cache / json output) and\n * absent on cache-hydrated diagnostics. Consumed by the autofix applier\n * (`src/core/fix.ts`) after suppression + baseline filtering. See ADR-0008.\n */\n fix?: FixFn;\n}\n\nexport interface RuleContext<TOptions = Record<string, unknown>> {\n /** The file being linted. */\n file: FileContext;\n /**\n * Parsed YAML frontmatter (v3/v4 only), or null if absent.\n * Use this when the rule must validate strict YAML frontmatter shape.\n */\n frontmatter: Record<string, unknown> | null;\n /**\n * Combined metadata: YAML frontmatter merged with v2 bold-list extracted\n * from the body. Frontmatter wins on key conflict. null only when both\n * are absent. See ADR-0006. Use this for format-agnostic field reads.\n */\n metadata: Record<string, unknown> | null;\n /**\n * Body-coordinate positions for `metadata` keys whose effective value came\n * from the v2 leading list (the list item's start). Frontmatter-sourced\n * keys are absent — frontmatter is stripped before mdast parsing, so it\n * has no body line. Rules attach this as `loc` on metadata-value\n * diagnostics so inline suppression directives can target them by line.\n */\n metadataLoc: Record<string, { line: number; column: number }> | null;\n /**\n * Body-coordinate offset RANGE of the effective value for `metadata` keys\n * whose value came from the v2 leading list AND was a single contiguous text\n * token (no inline markup). Autofix uses this to target the value precisely\n * (e.g. rewrite the status value in place). Frontmatter-sourced keys are\n * absent — frontmatter is stripped before mdast parsing, so it has no body\n * offset and needs YAML-aware rewriting instead. null when nothing qualifies.\n * See ADR-0008.\n */\n metadataValueLoc: Record<string, { start: number; end: number }> | null;\n /** User-merged options for this rule (validated against rule.meta.schema). */\n options: TOptions;\n /**\n * Emit a diagnostic. `suggestion` / `docsUrl` / `fixable` are resolved by the\n * runner. A rule may pass a `fix` thunk ONLY when its `meta.fixable` is\n * `'code'` (otherwise the runner routes it through `core/internal-error`).\n */\n report(\n diagnostic: Omit<\n Diagnostic,\n 'ruleName' | 'severity' | 'path' | 'suggestion' | 'docsUrl' | 'fixable'\n >,\n ): void;\n}\n\n/**\n * Visitor map dispatched during single-pass mdast traversal.\n * Rules subscribe to specific node types; the runner walks the tree once\n * and calls the matching enter/exit handler for each node.\n */\nexport interface RuleListeners {\n enter?: Partial<Record<MdastNodeType, (node: MdastNode) => void>>;\n exit?: Partial<Record<MdastNodeType, (node: MdastNode) => void>>;\n}\n\n/**\n * A lint rule.\n *\n * - Filename / metadata-only rules: `create` reports diagnostics directly,\n * then returns void.\n * - AST-based rules: `create` returns RuleListeners; the runner invokes\n * them during single-pass traversal of the file's mdast tree.\n */\nexport interface Rule<TOptions = Record<string, unknown>> {\n meta: RuleMeta<TOptions>;\n create(context: RuleContext<TOptions>): RuleListeners | void;\n}\n\n// ──────────────────────────────────────────────────────────────────\n// Project rules (cross-file). See ADR-0005.\n// ──────────────────────────────────────────────────────────────────\n\n/**\n * A pre-parsed file passed to project rules. Built once per\n * `runRulesOnProject` invocation.\n */\nexport interface ProjectFile {\n /** File path relative to the project root. */\n path: string;\n /** Raw file content (unparsed). */\n content: string;\n /**\n * Body content with frontmatter stripped — the same coordinate space as\n * `ast` positions. Used by the suppression layer for line-level directive\n * targeting.\n */\n body: string;\n /** Parsed YAML frontmatter (v3/v4 only), or null if absent. */\n frontmatter: Record<string, unknown> | null;\n /**\n * Combined metadata: frontmatter merged with v2 bold-list extracted\n * from the body. Frontmatter wins on key conflict. See ADR-0006.\n */\n metadata: Record<string, unknown> | null;\n /** mdast root of the body (frontmatter stripped). */\n ast: Root;\n}\n\nexport interface ProjectRuleContext<TOptions = Record<string, unknown>> {\n /** All files in the project (eager-parsed). */\n files: readonly ProjectFile[];\n /** User-merged options for this rule. */\n options: TOptions;\n /**\n * Predicate: does a project-root-relative POSIX path exist as a regular\n * file WITHIN the project root? Injected by the orchestrator so rules (e.g.\n * no-broken-links) can verify link targets that are NOT in the linted `.md`\n * set — non-Markdown assets or files outside the scanned paths. Returns\n * false for directories and for any target that resolves at or above the\n * project root (i.e. escapes it). Undefined when no filesystem is available\n * (e.g. in-memory unit tests), in which case rules fall back to the `files`\n * set alone.\n */\n fileExists?: (resolvedPath: string) => boolean;\n /**\n * Emit a diagnostic. Unlike per-file rules, project rules MUST set\n * `path` explicitly — the runner cannot infer which file the\n * diagnostic relates to. `suggestion` / `docsUrl` are resolved by the runner.\n */\n report(\n diagnostic: Omit<\n Diagnostic,\n 'ruleName' | 'severity' | 'suggestion' | 'docsUrl' | 'fixable'\n >,\n ): void;\n}\n\n/**\n * A cross-file rule. Receives all parsed files at once and reports\n * diagnostics with explicit `path` per file.\n *\n * Project rules use a different runner function (`runRulesOnProject`)\n * and a distinct context shape from per-file rules. See ADR-0005 for\n * the rationale behind keeping the two APIs separate.\n */\nexport interface ProjectRule<TOptions = Record<string, unknown>> {\n meta: RuleMeta<TOptions>;\n check(context: ProjectRuleContext<TOptions>): void;\n}\n\n/** Either a per-file or a project rule. */\nexport type AnyRule<TOptions = Record<string, unknown>> =\n | Rule<TOptions>\n | ProjectRule<TOptions>;\n\n/** Type guard distinguishing project rules from per-file rules. */\nexport function isProjectRule(rule: AnyRule): rule is ProjectRule {\n return 'check' in rule;\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { nullProtoMap } from './null-proto-map.js';\nimport type { Diagnostic } from './types.js';\n\n/**\n * Per-file cache entry: the file content's sha1 hash plus the per-file\n * diagnostics that the rule pipeline emitted last time.\n *\n * Note: project (cross-file) diagnostics are NOT cached — they depend on\n * the entire file set and would invalidate on any add/remove/rename.\n */\nexport interface CacheEntry {\n contentHash: string;\n perFileDiagnostics: Diagnostic[];\n}\n\n/**\n * On-disk manifest schema version. Bump whenever the shape of what we CACHE\n * changes — in particular the `Diagnostic` shape inside `perFileDiagnostics`.\n * pkgVersion invalidation only saves npm upgraders; repo devs and\n * same-version CI caches would otherwise be served stale-shape entries\n * verbatim (e.g. diagnostics missing the #67 `suggestion`/`docsUrl` keys,\n * silently dropped from json output). History: 3 = Diagnostic gained the\n * `fixable` boolean (#28); 2 = Diagnostic gained `suggestion` + `docsUrl`\n * (#67); 1 (implicit) = pre-schema manifests, which carry no `schemaVersion`\n * field at all.\n */\nexport const CACHE_SCHEMA_VERSION = 3;\n\nexport interface CacheManifest {\n /** Manifest schema version. Missing or mismatched ⇒ the cache is cold. */\n schemaVersion: number;\n /** madr-lint version when this manifest was written. Mismatch invalidates the cache. */\n version: string;\n /** Stable hash of the resolved config. Mismatch invalidates the cache. */\n configHash: string;\n /** Per-file cached entries, keyed by POSIX-normalized relative path. */\n files: Record<string, CacheEntry>;\n}\n\nexport function computeContentHash(content: string): string {\n return createHash('sha1').update(content, 'utf8').digest('hex');\n}\n\nexport function computeConfigHash(config: unknown): string {\n return createHash('sha1').update(stableStringify(config), 'utf8').digest('hex');\n}\n\n/**\n * Stable JSON stringify: object keys are sorted so the same logical\n * config always produces the same hash regardless of property insertion\n * order. Cycles are not handled — config objects are tree-shaped.\n */\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value);\n }\n if (Array.isArray(value)) {\n return '[' + value.map(stableStringify).join(',') + ']';\n }\n const entries = Object.entries(value as Record<string, unknown>).toSorted(\n ([a], [b]) => (a < b ? -1 : a > b ? 1 : 0),\n );\n return (\n '{' +\n entries\n .map(([k, v]) => JSON.stringify(k) + ':' + stableStringify(v))\n .join(',') +\n '}'\n );\n}\n\nexport function manifestPath(cacheDir: string): string {\n return join(cacheDir, 'manifest.json');\n}\n\nexport function loadManifest(path: string): CacheManifest | null {\n if (!existsSync(path)) return null;\n try {\n const data = JSON.parse(readFileSync(path, 'utf8')) as Partial<CacheManifest>;\n if (\n // Single invalidation point for schema drift: an unknown (or absent)\n // schema version nulls here, so every consumer sees a cold cache — no\n // scattered re-hydration of stale-shape entries downstream.\n data.schemaVersion !== CACHE_SCHEMA_VERSION ||\n typeof data.version !== 'string' ||\n typeof data.configHash !== 'string' ||\n typeof data.files !== 'object' ||\n data.files === null\n ) {\n return null;\n }\n // Rehydrate onto a null prototype: path keys come from the filesystem, so\n // a file named `__proto__` must behave as ordinary data on both read and\n // write (#56). JSON.parse gives it as an OWN property; copying it onto a\n // plain object would hand it back to the inherited setter.\n const files = nullProtoMap<CacheEntry>();\n for (const [key, entry] of Object.entries(\n data.files as Record<string, CacheEntry>,\n )) {\n files[key] = entry;\n }\n return { ...(data as CacheManifest), files };\n } catch {\n return null;\n }\n}\n\nexport function saveManifest(path: string, manifest: CacheManifest): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(manifest), 'utf8');\n}\n","// Autofix applier (issue #28, ADR-0008).\n//\n// Fixes are RAW-TEXT offset edits, never AST serialization — round-trips lose\n// formatting. A rule's `fix` thunk works in BODY (mdast) coordinates; the\n// `Fixer` translates those to whole-file offsets by adding the stripped\n// frontmatter length. The applier then:\n// 1. collects edits from REPORTED diagnostics only (the caller has already\n// filtered suppression + baseline — those must never be silently rewritten),\n// 2. sorts by start offset and drops any edit overlapping an earlier one\n// (first-by-position wins, deterministic),\n// 3. applies them in a single pass, then RE-LINTS and repeats to a fixpoint\n// (max 10 passes, ESLint parity).\n//\n// Scope: per-file edit sets. Project-rule (cross-file) fixes are #29 — they\n// reuse `applyEdits` with their own per-file edit collection (the clean seam).\n\nimport { frontmatterOffset } from './parser.js';\nimport type { Diagnostic, Fixer, TextEdit } from './types.js';\n\n/** Fixpoint iteration cap (matches ESLint's autofix passes). */\nexport const MAX_FIX_PASSES = 10;\n\n/**\n * Build a `Fixer` whose helpers take BODY offsets and emit whole-file\n * `TextEdit`s by adding `frontmatterOffset` (the length gray-matter stripped).\n */\nexport function makeFixer(base: number): Fixer {\n const shift = (n: number): number => n + base;\n return {\n replaceRange(range, text) {\n return { range: [shift(range[0]), shift(range[1])], text };\n },\n insertAt(offset, text) {\n const at = shift(offset);\n return { range: [at, at], text };\n },\n remove(range) {\n return { range: [shift(range[0]), shift(range[1])], text: '' };\n },\n };\n}\n\n/**\n * Apply text edits to `content` in a single pass. Edits are sorted by start\n * offset; any edit overlapping a previously-applied one is dropped (first wins).\n * Invalid ranges (non-integer, negative, inverted, out of bounds) are dropped.\n * Whole-file offsets in, new string out.\n */\nexport function applyEdits(content: string, edits: readonly TextEdit[]): string {\n return applyEditsCounted(content, edits).text;\n}\n\n/**\n * Counting variant of `applyEdits`: `applied` is the number of edits that\n * actually LANDED (post overlap/bounds filtering) — the honest source for\n * `summary.fixed`, which must not count dropped edits. Exported for the\n * cross-file fixpoint (`lintAndFix`), which applies project-rule edits per file\n * and needs the landed count.\n */\nexport function applyEditsCounted(\n content: string,\n edits: readonly TextEdit[],\n): { text: string; applied: number } {\n const valid = edits.filter((e) => {\n const [start, end] = e.range;\n return (\n Number.isInteger(start) &&\n Number.isInteger(end) &&\n start >= 0 &&\n end >= start &&\n end <= content.length\n );\n });\n // Stable-ish deterministic order: by start, then by end.\n const sorted = valid.toSorted(\n (a, b) => a.range[0] - b.range[0] || a.range[1] - b.range[1],\n );\n\n let out = '';\n let cursor = 0;\n let applied = 0;\n for (const edit of sorted) {\n const [start, end] = edit.range;\n if (start < cursor) continue; // overlaps an already-applied edit — drop\n out += content.slice(cursor, start) + edit.text;\n cursor = end;\n applied++;\n }\n return { text: out + content.slice(cursor), applied };\n}\n\n/**\n * Invoke each diagnostic's `fix` thunk (if any) with a `Fixer` built for\n * `content`, flattening arrays and skipping thunks that decline (return null).\n * The frontmatter offset is derived from `content` so translation always\n * matches the exact bytes being edited.\n */\nexport function collectFixes(\n diagnostics: readonly Diagnostic[],\n content: string,\n): TextEdit[] {\n const fixer = makeFixer(frontmatterOffset(content));\n const edits: TextEdit[] = [];\n for (const d of diagnostics) {\n if (!d.fix) continue;\n const result = d.fix(fixer);\n if (result === null) continue;\n if (Array.isArray(result)) edits.push(...result);\n else edits.push(result);\n }\n return edits;\n}\n\n/**\n * Collect CROSS-FILE (project-rule) fixes, grouped by the file each edits.\n * A project diagnostic names the file it targets via `diagnostic.path`; its fix\n * emits an edit for THAT file. Unlike per-file fixes, project fixes operate in\n * WHOLE-FILE coordinates: they may edit YAML frontmatter, which body (mdast)\n * coordinates strip, so there is no frontmatter shift to add — the fixer's base\n * is 0. See #29 (ADR-0008's per-file edit-set seam).\n *\n * `contentByPath(path)` returns the current content of a target file, or\n * undefined if it is not in the fix set (its edits are then skipped). At most\n * ONE fix contributes per file per pass — a second insertion at the same spot\n * would duplicate a key; the fixpoint re-runs the project pass so the runners-up\n * re-evaluate against the now-edited file (and typically decline).\n */\nexport function collectProjectFixes(\n diagnostics: readonly Diagnostic[],\n contentByPath: (path: string) => string | undefined,\n): Map<string, TextEdit[]> {\n const fixer = makeFixer(0);\n const byPath = new Map<string, TextEdit[]>();\n for (const d of diagnostics) {\n if (!d.fix) continue;\n if (byPath.has(d.path)) continue; // one file, one fix per pass\n if (contentByPath(d.path) === undefined) continue; // target not in the set\n const result = d.fix(fixer);\n if (result === null) continue;\n byPath.set(d.path, Array.isArray(result) ? [...result] : [result]);\n }\n return byPath;\n}\n\nexport interface FixFileResult {\n /** Content after the fixpoint loop (unchanged if nothing applied). */\n fixedContent: string;\n /** Diagnostics of the FINAL content (post-suppression / -baseline via `lint`). */\n remaining: Diagnostic[];\n /** Whether any edit actually changed the content. */\n changed: boolean;\n /** Number of applied passes (0 when nothing was fixed). */\n passes: number;\n /** Edits that actually LANDED across all passes (dropped edits excluded). */\n applied: number;\n}\n\n/**\n * Drive the autofix fixpoint for ONE file. `lint(content)` must return the\n * REPORTED diagnostics for `content` — already suppression- and\n * baseline-filtered — carrying live `fix` thunks. The loop lints at the top of\n * every iteration, so `remaining` always reflects the returned `fixedContent`.\n * Stops when: no edits remain, an edit makes no progress, or `maxPasses` is hit.\n */\nexport function fixFileContent(\n originalContent: string,\n lint: (content: string) => Diagnostic[],\n maxPasses: number = MAX_FIX_PASSES,\n): FixFileResult {\n let content = originalContent;\n let passes = 0;\n let applied = 0;\n\n for (;;) {\n const diagnostics = lint(content);\n const done = (): FixFileResult => ({\n fixedContent: content,\n remaining: diagnostics,\n changed: content !== originalContent,\n passes,\n applied,\n });\n\n if (passes >= maxPasses) return done();\n\n const edits = collectFixes(diagnostics, content);\n if (edits.length === 0) return done();\n\n const { text: next, applied: landed } = applyEditsCounted(content, edits);\n if (next === content) return done(); // no progress — avoid an infinite loop\n\n // Count edits that LANDED, not edits collected — overlap-dropped and\n // out-of-bounds edits never touched the content.\n applied += landed;\n content = next;\n passes++;\n }\n}\n\n// ──────────────────────────────────────────────────────────────────\n// Unified diff (for --fix-dry-run). Line-based LCS with 3 lines of\n// context, grouped into hunks. A preview, not a patch — the \"\\ No newline\n// at end of file\" marker is intentionally omitted.\n// ──────────────────────────────────────────────────────────────────\n\nconst CONTEXT = 3;\n\ntype Op = { kind: 'eq' | 'del' | 'ins'; text: string };\n\n/**\n * Produce a unified diff between `before` and `after` for display. Returns an\n * empty string when the two are identical.\n */\nexport function unifiedDiff(path: string, before: string, after: string): string {\n if (before === after) return '';\n\n const a = splitLines(before);\n const b = splitLines(after);\n const ops = diffLines(a, b);\n const hunks = groupHunks(ops);\n if (hunks.length === 0) return '';\n\n const lines: string[] = [`--- a/${path}`, `+++ b/${path}`];\n for (const hunk of hunks) lines.push(...renderHunk(hunk));\n return lines.join('\\n') + '\\n';\n}\n\n/** Split into lines WITHOUT terminators; a trailing newline yields no empty tail. */\nfunction splitLines(text: string): string[] {\n if (text === '') return [];\n const parts = text.split('\\n');\n if (parts[parts.length - 1] === '') parts.pop();\n return parts;\n}\n\n/** LCS-based line diff → a flat op sequence (eq / del / ins). */\nfunction diffLines(a: readonly string[], b: readonly string[]): Op[] {\n const n = a.length;\n const m = b.length;\n // lcs[i][j] = length of LCS of a[i:] and b[j:].\n const lcs: number[][] = Array.from({ length: n + 1 }, () =>\n Array.from<number>({ length: m + 1 }).fill(0),\n );\n for (let i = n - 1; i >= 0; i--) {\n for (let j = m - 1; j >= 0; j--) {\n lcs[i]![j] =\n a[i] === b[j]\n ? lcs[i + 1]![j + 1]! + 1\n : Math.max(lcs[i + 1]![j]!, lcs[i]![j + 1]!);\n }\n }\n\n const ops: Op[] = [];\n let i = 0;\n let j = 0;\n while (i < n && j < m) {\n if (a[i] === b[j]) {\n ops.push({ kind: 'eq', text: a[i]! });\n i++;\n j++;\n } else if (lcs[i + 1]![j]! >= lcs[i]![j + 1]!) {\n ops.push({ kind: 'del', text: a[i]! });\n i++;\n } else {\n ops.push({ kind: 'ins', text: b[j]! });\n j++;\n }\n }\n while (i < n) ops.push({ kind: 'del', text: a[i++]! });\n while (j < m) ops.push({ kind: 'ins', text: b[j++]! });\n return ops;\n}\n\ninterface Hunk {\n aStart: number;\n aCount: number;\n bStart: number;\n bCount: number;\n ops: Op[];\n}\n\n/** Group the op sequence into hunks, keeping up to CONTEXT equal lines around changes. */\nfunction groupHunks(ops: readonly Op[]): Hunk[] {\n // Index of each op onto its (1-based) source/target line for hunk headers.\n const changeIdx: number[] = [];\n ops.forEach((op, idx) => {\n if (op.kind !== 'eq') changeIdx.push(idx);\n });\n if (changeIdx.length === 0) return [];\n\n // Build [start, end] op-index windows padded by CONTEXT, then merge overlaps.\n const windows: Array<[number, number]> = [];\n for (const idx of changeIdx) {\n const start = Math.max(0, idx - CONTEXT);\n const end = Math.min(ops.length - 1, idx + CONTEXT);\n const last = windows[windows.length - 1];\n if (last && start <= last[1] + 1) last[1] = Math.max(last[1], end);\n else windows.push([start, end]);\n }\n\n const hunks: Hunk[] = [];\n for (const [start, end] of windows) {\n let aLine = 1;\n let bLine = 1;\n for (let k = 0; k < start; k++) {\n if (ops[k]!.kind !== 'ins') aLine++;\n if (ops[k]!.kind !== 'del') bLine++;\n }\n const hunkOps = ops.slice(start, end + 1);\n let aCount = 0;\n let bCount = 0;\n for (const op of hunkOps) {\n if (op.kind !== 'ins') aCount++;\n if (op.kind !== 'del') bCount++;\n }\n hunks.push({ aStart: aLine, aCount, bStart: bLine, bCount, ops: hunkOps });\n }\n return hunks;\n}\n\nfunction renderHunk(hunk: Hunk): string[] {\n const header = `@@ -${hunk.aStart},${hunk.aCount} +${hunk.bStart},${hunk.bCount} @@`;\n const body = hunk.ops.map((op) => {\n const prefix = op.kind === 'eq' ? ' ' : op.kind === 'del' ? '-' : '+';\n return `${prefix}${op.text}`;\n });\n return [header, ...body];\n}\n","import Ajv, { type AnySchemaObject, type ValidateFunction } from 'ajv';\nimport {\n INTERNAL_ERROR_RULE_NAME,\n type Diagnostic,\n type FileContext,\n type MdastNode,\n type ProjectFile,\n type ProjectRule,\n type ProjectRuleContext,\n type Rule,\n type RuleContext,\n type RuleListeners,\n type Severity,\n} from './types.js';\nimport { interpolate } from './interpolate.js';\nimport { parseFile, type ParsedFile } from './parser.js';\nimport {\n collectDirectives,\n filterSuppressed,\n DIRECTIVE_PREFIX,\n} from './suppression.js';\n\n// strict: true catches typo'd schema keywords at compile time.\n// allErrors: true so RuleOptionsError surfaces every issue at once.\nconst ajv = new Ajv({ strict: true, allErrors: true });\n\n// Cache compiled validators per schema object reference.\n// Schemas are module-level static on rules, so the cache key never goes stale.\n// (If a rule mutates `meta.schema` post-load — don't — the cache will desync.)\nconst validatorCache = new WeakMap<AnySchemaObject, ValidateFunction>();\n\nfunction getValidator(schema: AnySchemaObject): ValidateFunction {\n let validator = validatorCache.get(schema);\n if (!validator) {\n validator = ajv.compile(schema);\n validatorCache.set(schema, validator);\n }\n return validator;\n}\n\n// Defined in types.ts (leaf module) so the suppression layer can reference\n// it without a runner import cycle; re-exported here for API stability.\nexport { INTERNAL_ERROR_RULE_NAME };\n\n// core/internal-error diagnostics carry no rule docs URL of their own — a rule\n// bug is a repository issue, so point consumers at the repo. See issue #67.\nconst INTERNAL_ERROR_DOCS_URL = 'https://github.com/knktkc/madr-lint';\n\n/**\n * Thrown when a rule's merged options fail AJV validation against\n * `rule.meta.schema`. Distinct from generic `Error` so callers can\n * `instanceof` to skip rules with bad config and continue.\n */\nexport class RuleOptionsError extends Error {\n constructor(\n public readonly ruleName: string,\n public readonly ajvErrors: string,\n ) {\n super(`Invalid options for rule ${ruleName}: ${ajvErrors}`);\n this.name = 'RuleOptionsError';\n }\n}\n\nexport interface RunRuleOptions {\n /** Per-rule options to merge over rule.meta.defaultOptions. */\n options?: Record<string, unknown>;\n /**\n * Options keyed by rule name, for multi-rule calls where each rule needs\n * its own options (e.g. config tuples `['error', {...}]`). For a given rule,\n * an entry here takes precedence over `options`; rules absent from the map\n * fall back to `options` (if any), then `meta.defaultOptions`.\n */\n optionsByRule?: Record<string, Record<string, unknown>>;\n /** Severity to attach to emitted diagnostics. Defaults to 'error'. */\n severity?: Severity;\n /** Skip AJV options validation. Default false. */\n skipValidation?: boolean;\n /**\n * Filesystem-existence predicate exposed to project rules as\n * `context.fileExists`. Per-file runs ignore this. See\n * ProjectRuleContext.fileExists.\n */\n fileExists?: (resolvedPath: string) => boolean;\n}\n\n/**\n * Per-rule, per-file rule context. The lazy-parse getters live on the class\n * PROTOTYPE — object literals with getters force expensive per-call shape\n * setup, which the filename-format micro-bench (~400ns/op) measured as a\n * double-digit % regression when a third getter was added (PR #53).\n * Prototype accessors are set up once; instantiation is near-free. `report`\n * stays a per-instance closure (it captures the rule name and severity, and\n * this keeps it safely destructurable by rule authors).\n */\nclass PerFileRuleContext implements RuleContext {\n readonly file: FileContext;\n readonly options: Record<string, unknown>;\n readonly report: RuleContext['report'];\n private readonly ensureParsed: () => ParsedFile;\n\n constructor(\n file: FileContext,\n options: Record<string, unknown>,\n ensureParsed: () => ParsedFile,\n report: RuleContext['report'],\n ) {\n this.file = file;\n this.options = options;\n this.ensureParsed = ensureParsed;\n this.report = report;\n }\n\n get frontmatter(): Record<string, unknown> | null {\n return this.ensureParsed().frontmatter;\n }\n\n get metadata(): Record<string, unknown> | null {\n return this.ensureParsed().metadata;\n }\n\n get metadataLoc(): Record<string, { line: number; column: number }> | null {\n return this.ensureParsed().metadataLoc;\n }\n\n get metadataValueLoc(): Record<string, { start: number; end: number }> | null {\n return this.ensureParsed().metadataValueLoc;\n }\n}\n\n/**\n * Run a single rule against a single file. Sugar over runRulesOnFile\n * for the common one-rule case (used heavily by tests).\n *\n * Note: inline suppression directives (`<!-- madr-lint-disable... -->`)\n * present in `file.content` ARE honored — matching diagnostics are filtered.\n */\nexport function runRule<TOptions extends Record<string, unknown>>(\n rule: Rule<TOptions>,\n file: FileContext,\n runtime: RunRuleOptions = {},\n): Diagnostic[] {\n return runRulesOnFile([rule as Rule], file, runtime);\n}\n\n/**\n * Run multiple rules against a single file with a single mdast traversal.\n *\n * Steps:\n * 1. Validate each rule's merged options against its schema (AJV).\n * Throws RuleOptionsError on failure (caller decides whether to skip).\n * 2. Call each rule's create(). If a rule throws, the error is caught\n * and reported as a `core/internal-error` diagnostic — other rules\n * continue. The same isolation wraps every enter/exit handler so a\n * single buggy rule cannot abort the whole lint run.\n * 3. If at least one rule returned RuleListeners, call parseFile()\n * ONCE (gray-matter + mdast-util-from-markdown together; there is\n * no separate cheap \"frontmatter only\" path) and walk the tree,\n * dispatching enter/exit handlers per node type.\n *\n * Frontmatter is exposed lazily via context.frontmatter — the getter\n * triggers parseFile() on first access. Filename-style rules that never\n * touch frontmatter and return void from create() pay zero parse cost.\n *\n * Note: inline suppression directives (`<!-- madr-lint-disable... -->`)\n * present in `file.content` ARE honored — matching diagnostics are filtered\n * centrally after all rules have reported (see suppression.ts).\n */\nexport function runRulesOnFile(\n rules: readonly Rule[],\n file: FileContext,\n runtime: RunRuleOptions = {},\n): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const severity = runtime.severity ?? 'error';\n\n let parsed: ParsedFile | null = null;\n const ensureParsed = (): ParsedFile => {\n parsed ??= parseFile(file.content);\n return parsed;\n };\n\n const enterMap: Record<string, Array<(node: MdastNode) => void>> = {};\n const exitMap: Record<string, Array<(node: MdastNode) => void>> = {};\n let hasASTRule = false;\n\n for (const rule of rules) {\n const perRuleOptions =\n runtime.optionsByRule?.[rule.meta.name] ?? runtime.options;\n const mergedOptions = {\n ...rule.meta.defaultOptions,\n ...perRuleOptions,\n };\n\n if (rule.meta.schema && !runtime.skipValidation) {\n const validator = getValidator(rule.meta.schema);\n if (!validator(mergedOptions)) {\n throw new RuleOptionsError(rule.meta.name, ajv.errorsText(validator.errors));\n }\n }\n\n // Per-rule constants hoisted OUT of the report closure: resolving them per\n // report() call (helper call + rule.meta.docs.url chain + an absent\n // `suggestions` property walk) measured a confirmed -19% on the CI x64\n // no-numbering-gap dirty-path bench, though Apple-silicon runs masked it.\n // The literal below also keeps the base construction pattern — boilerplate\n // head then a single trailing spread — so no post-spread shape transitions.\n const ruleName = rule.meta.name;\n const suggestions = rule.meta.suggestions;\n const docsUrl = rule.meta.docs.url ?? '';\n // Autofix (#28): only a `meta.fixable: 'code'` rule may attach a fix. The\n // flag is captured per rule (a const) so the report closure stays hot —\n // `hasFix` short-circuits to false for the non-fixable common case.\n const fixableRule = rule.meta.fixable === 'code';\n const context: RuleContext = new PerFileRuleContext(\n file,\n mergedOptions,\n ensureParsed,\n (d) => {\n const template = suggestions?.[d.messageId];\n // A fix from a non-fixable rule is a rule bug: throw so the create() /\n // handler isolation converts it to core/internal-error (the misused\n // diagnostic never surfaces). After this guard, `hasFix ⟹ fixableRule`,\n // so `fixable` equals `hasFix`.\n const hasFix = d.fix !== undefined;\n if (hasFix && !fixableRule) {\n throw new Error(\n `Rule \"${ruleName}\" attached a fix but meta.fixable is not 'code'`,\n );\n }\n diagnostics.push({\n ruleName,\n severity,\n path: file.path,\n suggestion: template === undefined ? null : interpolate(template, d.data ?? {}),\n docsUrl,\n fixable: hasFix,\n ...d,\n });\n },\n );\n\n let listeners: RuleListeners | void;\n try {\n listeners = rule.create(context);\n } catch (err) {\n // RuleOptionsError signals the user's config is wrong (e.g. unsafe\n // regex). Propagate it so the CLI can render a clear \"fix your\n // .madrlintrc\" message rather than burying it as a per-file\n // internal-error diagnostic.\n if (err instanceof RuleOptionsError) throw err;\n diagnostics.push(\n internalErrorDiagnostic(rule.meta.name, 'create', err, file.path),\n );\n continue;\n }\n\n if (listeners) {\n hasASTRule = true;\n collectListeners(rule, listeners, enterMap, exitMap, diagnostics, file);\n }\n }\n\n if (hasASTRule) {\n walk(ensureParsed().ast, enterMap, exitMap);\n }\n\n // Central, post-report suppression (issue #23). Rules stay unaware; the\n // runner filters here using directives collected from the file's `html`\n // comment nodes. We only pay the parse + directive walk when there is\n // something to potentially suppress — a clean file short-circuits, so\n // filename-only rules on passing files keep their zero-parse fast path.\n if (diagnostics.length === 0) return diagnostics;\n // Perf guard (PR #53): every directive form contains the literal\n // 'madr-lint-', and the parsed body is a substring of the raw content, so\n // its absence PROVES no directives exist — filtering can be skipped\n // without parsing. Without this, a Shape-A filename diagnostic on an\n // otherwise-unparsed file forced a full gray-matter+mdast parse here\n // (measured -99% on the filename-format invalid-path bench). The substring\n // scan is ~µs; files that do contain the literal proceed exactly as before.\n if (!file.content.includes(DIRECTIVE_PREFIX)) return diagnostics;\n const { ast, body } = ensureParsed();\n const directives = collectDirectives(ast, body);\n return directives ? filterSuppressed(diagnostics, directives) : diagnostics;\n}\n\n// Internal-error diagnostics are ALWAYS severity 'error' regardless of the\n// runtime-passed severity for the rule that threw — a rule misbehaving is\n// never something the user wants to silence as a warning.\nfunction internalErrorDiagnostic(\n ruleName: string,\n operation: 'create' | 'enter' | 'exit' | 'check',\n err: unknown,\n path: string,\n): Diagnostic {\n return {\n ruleName: INTERNAL_ERROR_RULE_NAME,\n messageId: 'ruleThrew',\n severity: 'error',\n path,\n suggestion: null,\n docsUrl: INTERNAL_ERROR_DOCS_URL,\n fixable: false,\n data: {\n rule: ruleName,\n operation,\n error: err instanceof Error ? err.message : String(err),\n },\n };\n}\n\nfunction collectListeners(\n rule: Rule,\n listeners: RuleListeners,\n enterMap: Record<string, Array<(node: MdastNode) => void>>,\n exitMap: Record<string, Array<(node: MdastNode) => void>>,\n diagnostics: Diagnostic[],\n file: FileContext,\n): void {\n const wrap =\n (op: 'enter' | 'exit') => (handler: (node: MdastNode) => void) => (node: MdastNode) => {\n try {\n handler(node);\n } catch (err) {\n diagnostics.push(\n internalErrorDiagnostic(rule.meta.name, op, err, file.path),\n );\n }\n };\n\n if (listeners.enter) {\n const wrapEnter = wrap('enter');\n for (const [type, handler] of Object.entries(listeners.enter)) {\n if (handler) (enterMap[type] ??= []).push(wrapEnter(handler));\n }\n }\n if (listeners.exit) {\n const wrapExit = wrap('exit');\n for (const [type, handler] of Object.entries(listeners.exit)) {\n if (handler) (exitMap[type] ??= []).push(wrapExit(handler));\n }\n }\n}\n\nfunction walk(\n node: MdastNode,\n enterMap: Record<string, Array<(node: MdastNode) => void>>,\n exitMap: Record<string, Array<(node: MdastNode) => void>>,\n): void {\n const enterHandlers = enterMap[node.type];\n if (enterHandlers) {\n for (const handler of enterHandlers) handler(node);\n }\n if ('children' in node && Array.isArray(node.children)) {\n for (const child of node.children) {\n walk(child as MdastNode, enterMap, exitMap);\n }\n }\n const exitHandlers = exitMap[node.type];\n if (exitHandlers) {\n for (const handler of exitHandlers) handler(node);\n }\n}\n\n/**\n * Build a `ProjectFile` (path + content + parsed frontmatter + AST) from\n * a raw file. Used by `runRulesOnProject` and by the lint orchestrator\n * to share parsing between per-file and project passes when needed.\n */\nexport function buildProjectFile(file: FileContext): ProjectFile {\n const parsed: ParsedFile = parseFile(file.content);\n return {\n path: file.path,\n content: file.content,\n body: parsed.body,\n frontmatter: parsed.frontmatter,\n metadata: parsed.metadata,\n ast: parsed.ast,\n };\n}\n\n/**\n * Run multiple project rules across all parsed files. Each rule's\n * `check()` is called once with the full file array. Throws are\n * captured as `core/internal-error` diagnostics (attached to the\n * first file's path, since project rules have no file context).\n *\n * See ADR-0005 for the API rationale.\n */\nexport function runRulesOnProject(\n rules: readonly ProjectRule[],\n files: readonly ProjectFile[],\n runtime: RunRuleOptions = {},\n): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const severity = runtime.severity ?? 'error';\n\n for (const rule of rules) {\n const perRuleOptions =\n runtime.optionsByRule?.[rule.meta.name] ?? runtime.options;\n const mergedOptions = {\n ...rule.meta.defaultOptions,\n ...perRuleOptions,\n };\n\n if (rule.meta.schema && !runtime.skipValidation) {\n const validator = getValidator(rule.meta.schema);\n if (!validator(mergedOptions)) {\n throw new RuleOptionsError(\n rule.meta.name,\n ajv.errorsText(validator.errors),\n );\n }\n }\n\n // Same hoisting as the per-file closure — see the comment there (CI x64\n // bench regression when these resolved per report call).\n const ruleName = rule.meta.name;\n const suggestions = rule.meta.suggestions;\n const docsUrl = rule.meta.docs.url ?? '';\n // Cross-file autofix (#29): a `meta.fixable: 'code'` project rule may attach\n // a fix thunk, resolved to a durable `fixable` boolean and carried (transient)\n // for the cross-file applier. Same gate + misuse-throw as the per-file path.\n const fixableRule = rule.meta.fixable === 'code';\n const context: ProjectRuleContext = {\n files,\n options: mergedOptions,\n fileExists: runtime.fileExists,\n report(d) {\n const template = suggestions?.[d.messageId];\n const hasFix = d.fix !== undefined;\n if (hasFix && !fixableRule) {\n throw new Error(\n `Rule \"${ruleName}\" attached a fix but meta.fixable is not 'code'`,\n );\n }\n diagnostics.push({\n ruleName,\n severity,\n suggestion: template === undefined ? null : interpolate(template, d.data ?? {}),\n docsUrl,\n fixable: hasFix,\n ...d,\n });\n },\n };\n\n try {\n rule.check(context);\n } catch (err) {\n // Project rules have no current-file context — attribute the\n // internal-error to a sentinel path rather than mis-attributing\n // to whatever file happens to be first in the array.\n diagnostics.push(\n internalErrorDiagnostic(rule.meta.name, 'check', err, '<project>'),\n );\n }\n }\n\n return diagnostics;\n}\n","// Shared {{placeholder}} interpolation for message AND suggestion templates.\n// Lives in a leaf module so both the runner (which resolves a diagnostic's\n// `suggestion` at report time) and the reporter (which renders the `message`\n// from its template) use one implementation — never two that can drift.\n\n/**\n * Substitute `{{key}}` placeholders in `template` with the matching value from\n * `data`, stringified. An unknown key is left intact (`{{key}}`) so a missing\n * interpolation is a visible bug surface rather than a silent empty string.\n */\nexport function interpolate(\n template: string,\n data: Record<string, unknown>,\n): string {\n // Fast-path placeholder-free templates (e.g. static suggestions): skip the\n // regex scan + callback allocation entirely. This is on the report hot path\n // — resolved once per emitted diagnostic — so the substring guard matters for\n // the sub-microsecond filename-format micro-bench.\n if (!template.includes('{{')) return template;\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key: string) => {\n const v = data[key];\n return v !== undefined ? String(v) : `{{${key}}}`;\n });\n}\n","// Inline suppression directives (see issue #23).\n//\n// Rules stay completely unaware of suppression: they emit diagnostics as\n// usual, and the runner / lint layer filters them centrally *after* reporting,\n// using directives collected from the file's mdast `html` comment nodes.\n//\n// COORDINATE SYSTEM: directives are read from mdast `html` node positions.\n// Because `parseFile` strips YAML frontmatter with gray-matter BEFORE feeding\n// the body to `mdast-util-from-markdown`, every mdast position — and therefore\n// every directive line AND every AST-derived diagnostic `loc.line` — lives in\n// the same body coordinate space (frontmatter height removed). We deliberately\n// do NOT scan the raw file text, which would put directive lines in raw-file\n// coordinates and silently mis-align them with diagnostic lines.\n\nimport type { Root } from 'mdast';\nimport {\n INTERNAL_ERROR_RULE_NAME,\n type Diagnostic,\n type MdastNode,\n} from './types.js';\n\ntype DirectiveKind = 'disable-file' | 'disable' | 'enable' | 'disable-next-line';\n\n// Only these exact keywords are directives. An unknown suffix (e.g.\n// `madr-lint-disable-line`) is a full non-matching token and is ignored.\nconst DIRECTIVE_KINDS = new Set<DirectiveKind>([\n 'disable-file',\n 'disable',\n 'enable',\n 'disable-next-line',\n]);\n\n/**\n * Every directive form contains this literal. Callers use it as a cheap\n * raw-content pre-filter: since the parsed body is a substring of the raw\n * content, `!content.includes(DIRECTIVE_PREFIX)` PROVES the file contains\n * no directives — so directive collection, and any parse it would otherwise\n * force, can be skipped entirely.\n */\nexport const DIRECTIVE_PREFIX = 'madr-lint-';\n\ninterface Directive {\n kind: DirectiveKind;\n /** null = applies to all rules; otherwise the explicit rule-id list. */\n rules: readonly string[] | null;\n /** 1-based START line of the comment, in body (frontmatter-stripped) coordinates. */\n line: number;\n /**\n * 1-based END line of the comment, same coordinates. Equals `line` for a\n * single-line comment; for a multi-line `<!--` … `-->` block it is the line\n * carrying `-->`, which is what `disable-next-line` counts from (#116).\n */\n endLine: number;\n}\n\ninterface NextLineEntry {\n all: boolean;\n rules: Set<string>;\n}\n\nexport interface DirectiveIndex {\n /** An unscoped `disable-file` disables every rule for the whole file. */\n fileDisableAll: boolean;\n /** Rules disabled file-wide via `disable-file <rules>`. */\n fileDisableRules: Set<string>;\n /** `disable` / `enable` directives, sorted by START line ascending. */\n ranged: Directive[];\n /**\n * disable-next-line: target line → affected rules. The target is the first\n * NON-BLANK line after the comment's END line, not `line + 1`.\n */\n nextLine: Map<number, NextLineEntry>;\n}\n\n/** Parse one `<!-- ... -->` html node value into a directive, or null. */\nfunction parseDirective(\n value: string,\n line: number,\n endLine: number,\n): Directive | null {\n const trimmed = value.trim();\n if (!trimmed.startsWith('<!--') || !trimmed.endsWith('-->')) return null;\n\n const inner = trimmed.slice(4, -3).trim();\n // Two comments coalesced into one html node (e.g. `<!-- a --><!-- b -->`\n // on one line) pass the startsWith/endsWith guard but would sweep\n // `--><!--` garbage into the rule list. A directive comment must stand\n // alone: reject any candidate whose interior still carries comment\n // delimiters.\n if (inner.includes('<!--') || inner.includes('-->')) return null;\n if (!inner.startsWith(DIRECTIVE_PREFIX)) return null;\n\n const afterPrefix = inner.slice(DIRECTIVE_PREFIX.length);\n // The keyword token ends at the first whitespace; the remainder is the\n // optional rule list. Matching the FULL token against the exact keyword set\n // is what makes `madr-lint-disable-line` (token `disable-line`) a non-match.\n const wsIndex = afterPrefix.search(/\\s/);\n const keyword = wsIndex === -1 ? afterPrefix : afterPrefix.slice(0, wsIndex);\n if (!DIRECTIVE_KINDS.has(keyword as DirectiveKind)) return null;\n\n const rest = wsIndex === -1 ? '' : afterPrefix.slice(wsIndex);\n return {\n kind: keyword as DirectiveKind,\n rules: parseRuleList(rest),\n line,\n endLine,\n };\n}\n\nfunction parseRuleList(rest: string): readonly string[] | null {\n const trimmed = rest.trim();\n if (trimmed === '') return null; // no list ⇒ all rules\n const rules = trimmed\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s !== '');\n return rules.length > 0 ? rules : null;\n}\n\nfunction walkHtml(node: MdastNode, out: Directive[]): void {\n if (node.type === 'html') {\n const line = node.position?.start.line;\n const endLine = node.position?.end.line;\n if (typeof line === 'number' && typeof endLine === 'number') {\n const directive = parseDirective(node.value, line, endLine);\n if (directive) out.push(directive);\n }\n }\n if ('children' in node && Array.isArray(node.children)) {\n for (const child of node.children) walkHtml(child as MdastNode, out);\n }\n}\n\n/**\n * Collect suppression directives from a file's mdast tree in one cheap walk.\n * Returns null when the file carries no directives, so callers can skip\n * filtering entirely (fast path). Both block and inline `html` comment nodes\n * are inspected.\n *\n * `body` is the frontmatter-stripped body text — the SAME coordinate space\n * as the mdast positions. It is only consulted (and only split into lines)\n * when a `disable-next-line` directive needs to resolve its target.\n */\nexport function collectDirectives(\n ast: Root,\n body: string,\n): DirectiveIndex | null {\n const all: Directive[] = [];\n walkHtml(ast as MdastNode, all);\n if (all.length === 0) return null;\n\n // disable-next-line targets the next NON-BLANK line, not the literal next\n // line. This deviates from ESLint deliberately: Markdown authors\n // idiomatically leave a blank line after a comment block, and a directive\n // that silently misses across it would be a footgun.\n let lines: string[] | null = null;\n const nextNonBlankLine = (after: number): number | null => {\n lines ??= body.split('\\n');\n for (let ln = after + 1; ln <= lines.length; ln++) {\n if ((lines[ln - 1] ?? '').trim() !== '') return ln;\n }\n return null; // directive at EOF with nothing after it — no target\n };\n\n const index: DirectiveIndex = {\n fileDisableAll: false,\n fileDisableRules: new Set(),\n ranged: [],\n nextLine: new Map(),\n };\n\n for (const directive of all) {\n switch (directive.kind) {\n case 'disable-file': {\n if (directive.rules === null) index.fileDisableAll = true;\n else for (const rule of directive.rules) index.fileDisableRules.add(rule);\n break;\n }\n case 'disable':\n case 'enable': {\n index.ranged.push(directive);\n break;\n }\n case 'disable-next-line': {\n // Counted from the comment's END line: a `<!--` … `-->` block written\n // across several lines would otherwise target its own interior\n // instead of the author's content below `-->` (#116). A single-line\n // comment has endLine === line, so its behavior is unchanged.\n const target = nextNonBlankLine(directive.endLine);\n if (target === null) break;\n let entry = index.nextLine.get(target);\n if (!entry) {\n entry = { all: false, rules: new Set() };\n index.nextLine.set(target, entry);\n }\n if (directive.rules === null) entry.all = true;\n else for (const rule of directive.rules) entry.rules.add(rule);\n break;\n }\n }\n }\n\n // Ranges stay keyed to the comment's START line, which is what preserves\n // pre-#116 behavior: a range opens where the author wrote it. The choice is\n // observable, not cosmetic — an INLINE multi-line comment shares its start\n // line with the content beside it (`[gone](./nope.md) <!--` … `-->` spans\n // three lines while the link is reported on the first), so counting from\n // `endLine` here would stop such a `disable` from covering its own line.\n index.ranged.sort((a, b) => a.line - b.line);\n return index;\n}\n\nfunction directiveApplies(directive: Directive, ruleName: string): boolean {\n return directive.rules === null || directive.rules.includes(ruleName);\n}\n\n/**\n * Is a single diagnostic suppressed by the file's directives?\n *\n * `core/internal-error` is handled by the caller and never reaches here.\n *\n * Line-less diagnostics (no `loc`) are treated as spanning the whole file:\n * only `disable-file` OR an OPEN-ENDED `disable` (a `disable` with no later\n * matching `enable`) suppresses them. This is exactly the \"file-scoped\"\n * contract the issue specifies for project-rule diagnostics.\n */\nexport function isSuppressed(\n index: DirectiveIndex,\n ruleName: string,\n line: number | undefined,\n): boolean {\n if (index.fileDisableAll || index.fileDisableRules.has(ruleName)) return true;\n\n if (line !== undefined) {\n const next = index.nextLine.get(line);\n if (next && (next.all || next.rules.has(ruleName))) return true;\n }\n\n // Replay disable/enable in line order; the last one at or before the\n // diagnostic line wins. A line-less diagnostic uses +Infinity as its line,\n // so every directive is replayed and only an unclosed final `disable`\n // (open to EOF) leaves it suppressed. This same replay yields ESLint-style\n // carve-outs, e.g. an unscoped `disable` followed by a scoped `enable`.\n const ceiling = line ?? Number.POSITIVE_INFINITY;\n let disabled = false;\n for (const directive of index.ranged) {\n if (directive.line > ceiling) break; // ranged is sorted ascending\n if (directiveApplies(directive, ruleName)) {\n disabled = directive.kind === 'disable';\n }\n }\n return disabled;\n}\n\n/**\n * Filter suppressed diagnostics. `core/internal-error` diagnostics are NEVER\n * suppressible: they signal a rule bug (always severity 'error'), and letting\n * a `disable`/`disable-file` hide them would mask defects. This is the\n * simplest behavior consistent with the runner already forcing internal-error\n * severity regardless of user config.\n */\nexport function filterSuppressed(\n diagnostics: readonly Diagnostic[],\n index: DirectiveIndex,\n): Diagnostic[] {\n return diagnostics.filter((d) => {\n if (d.ruleName === INTERNAL_ERROR_RULE_NAME) return true;\n return !isSuppressed(index, d.ruleName, d.loc?.line);\n });\n}\n","// Registry of all built-in madr-lint rules.\n// New rules append here via the add-rule skill.\n\nexport { default as dateIso8601 } from './date-iso8601/index.js';\nexport { default as filenameFormat } from './filename-format/index.js';\nexport { default as noBrokenLinks } from './no-broken-links/index.js';\nexport { default as noDuplicateNumbering } from './no-duplicate-numbering/index.js';\nexport { default as noNumberingGap } from './no-numbering-gap/index.js';\nexport { default as requiredSections } from './required-sections/index.js';\nexport { default as statusEnum } from './status-enum/index.js';\nexport { default as supersedesBidirectional } from './supersedes-bidirectional/index.js';\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"field\": {\n \"type\": \"string\",\n \"description\": \"Frontmatter key to read. Default: 'date'.\"\n }\n }\n}\n","import type { Fixer, Rule, TextEdit } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface DateIso8601Options extends Record<string, unknown> {\n field: string;\n}\n\nfunction normalizeDate(value: unknown): string | null {\n if (typeof value === 'string') return value;\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null;\n return value.toISOString().slice(0, 10);\n }\n return null;\n}\n\nfunction isValidIso8601Date(s: string): boolean {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(s)) return false;\n const parts = s.split('-').map(Number);\n const [year, month, day] = parts;\n if (year === undefined || month === undefined || day === undefined) return false;\n const d = new Date(Date.UTC(year, month - 1, day));\n return (\n d.getUTCFullYear() === year &&\n d.getUTCMonth() === month - 1 &&\n d.getUTCDate() === day\n );\n}\n\n// English month names → month number. An explicit table (no `Date.parse`, which\n// is locale/timezone-dependent) so autofix only touches names it recognizes\n// unambiguously; any other spelling declines. See #29.\nconst MONTHS: Record<string, number> = {\n jan: 1, january: 1,\n feb: 2, february: 2,\n mar: 3, march: 3,\n apr: 4, april: 4,\n may: 5,\n jun: 6, june: 6,\n jul: 7, july: 7,\n aug: 8, august: 8,\n sep: 9, sept: 9, september: 9,\n oct: 10, october: 10,\n nov: 11, november: 11,\n dec: 12, december: 12,\n};\n\n/** Zero-pad a positive integer to `width` digits. */\nfunction pad(n: number, width: number): string {\n return String(n).padStart(width, '0');\n}\n\n/**\n * Build a canonical `YYYY-MM-DD` string from numeric parts, or null when the\n * result is not a real calendar date. Never turns an invalid date into a\n * *different* valid one (e.g. `2026/2/30` has no fix).\n */\nfunction buildIso(year: number, month: number, day: number): string | null {\n if (month < 1 || month > 12 || day < 1 || day > 31) return null;\n const iso = `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`;\n return isValidIso8601Date(iso) ? iso : null;\n}\n\n/**\n * Normalize `raw` to `YYYY-MM-DD` ONLY when the mapping is unambiguous:\n * - year-first numeric with matching separators: `YYYY/M/D`, `YYYY.M.D`,\n * `YYYY-M-D` (month ≤ 12, day ≤ 31, real calendar date);\n * - named-month forms with an English month: `D Mon YYYY`, `Mon D, YYYY`.\n * Day/month-order-ambiguous inputs (`03/07/2026`), short years, and unknown\n * month names return null — the rule then reports without offering a fix.\n */\nfunction normalizeToIso(raw: string): string | null {\n const s = raw.trim();\n\n // Year-first numeric — the ONE unambiguous numeric shape. The `\\2` backref\n // forces a single separator style, so `2026/07-03` never matches.\n const numeric = /^(\\d{4})([-/.])(\\d{1,2})\\2(\\d{1,2})$/.exec(s);\n if (numeric) {\n return buildIso(Number(numeric[1]), Number(numeric[3]), Number(numeric[4]));\n }\n\n // Day-first named month: `3 Jul 2026`, `03 July 2026` (optional trailing dot).\n const dMonY = /^(\\d{1,2})\\s+([A-Za-z]+)\\.?\\s+(\\d{4})$/.exec(s);\n if (dMonY) {\n const month = MONTHS[dMonY[2]!.toLowerCase()];\n return month ? buildIso(Number(dMonY[3]), month, Number(dMonY[1])) : null;\n }\n\n // Month-first named month: `Jul 3, 2026`, `July 03, 2026` (comma optional).\n const monDY = /^([A-Za-z]+)\\.?\\s+(\\d{1,2}),?\\s+(\\d{4})$/.exec(s);\n if (monDY) {\n const month = MONTHS[monDY[1]!.toLowerCase()];\n return month ? buildIso(Number(monDY[3]), month, Number(monDY[2])) : null;\n }\n\n return null;\n}\n\nconst rule: Rule<DateIso8601Options> = {\n meta: {\n name: 'madr/date-iso8601',\n type: 'perFile',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'Validate that ADR `date` (frontmatter or v2 bold-list) is a valid ISO 8601 calendar date (YYYY-MM-DD)',\n url: 'https://knktkc.github.io/madr-lint/rules/date-iso8601/',\n recommended: true,\n },\n messages: {\n missingDate: 'Metadata does not contain a \"{{field}}\" field (checked frontmatter and v2 bold-list)',\n invalidDate: 'Date \"{{date}}\" is not a valid ISO 8601 calendar date (YYYY-MM-DD)',\n },\n suggestions: {\n missingDate:\n 'add a \"{{field}}\" field in YYYY-MM-DD format to the frontmatter (for MADR v2, a \"* Date: ...\" list item)',\n invalidDate: 'use the YYYY-MM-DD calendar-date format, e.g. 2025-03-14',\n },\n // Autofix (#29): normalize UNAMBIGUOUS shapes of a v2 list-sourced value —\n // year-first numeric and English named-month forms. Ambiguous day/month\n // order, invalid calendar dates, and frontmatter-sourced values get no fix.\n fixable: 'code',\n defaultOptions: {\n field: 'date',\n },\n schema,\n },\n create(context) {\n const meta = context.metadata;\n const fieldName = context.options.field;\n\n if (!meta) {\n context.report({\n messageId: 'missingDate',\n data: { field: fieldName },\n });\n return;\n }\n\n const raw = meta[fieldName];\n const dateStr = normalizeDate(raw);\n if (dateStr === null) {\n context.report({\n messageId: 'missingDate',\n data: { field: fieldName },\n });\n return;\n }\n\n if (!isValidIso8601Date(dateStr)) {\n // loc only exists for v2 list-sourced values: frontmatter is stripped\n // before mdast parsing, so a frontmatter-sourced date has no body line\n // to point at — inline suppression directives (which live in the body)\n // can only silence it file-wide, never per line.\n const loc = context.metadataLoc?.[fieldName];\n // Autofix (#29): offer a fix ONLY for a v2 list-sourced value (we then have\n // its exact body offset range) whose shape normalizes unambiguously.\n // Frontmatter-sourced values have no body offset and need YAML-aware\n // rewriting — out of scope, so no fix is attached there.\n const valueRange = context.metadataValueLoc?.[fieldName];\n const normalized = valueRange ? normalizeToIso(dateStr) : null;\n const fix =\n normalized !== null && normalized !== dateStr && valueRange\n ? (fixer: Fixer): TextEdit =>\n fixer.replaceRange([valueRange.start, valueRange.end], normalized)\n : undefined;\n context.report({\n messageId: 'invalidDate',\n ...(loc ? { loc } : {}),\n data: { date: dateStr },\n ...(fix ? { fix } : {}),\n });\n }\n },\n};\n\nexport default rule;\n","import { basename } from 'node:path';\nimport { assertSafeRegex } from '../../core/regex-safety.js';\nimport type { Rule } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface FilenameFormatOptions extends Record<string, unknown> {\n pattern: string;\n}\n\nconst rule: Rule<FilenameFormatOptions> = {\n meta: {\n name: 'madr/filename-format',\n type: 'perFile',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'Enforce ADR filename convention NNNN-kebab-case.md',\n url: 'https://knktkc.github.io/madr-lint/rules/filename-format/',\n recommended: true,\n },\n messages: {\n invalidFilename:\n 'Filename \"{{filename}}\" does not match expected pattern \"{{expected}}\"',\n },\n suggestions: {\n // The message already states the expected pattern; the suggestion adds a\n // concrete, conforming example of the NNNN-kebab-case.md convention.\n invalidFilename:\n 'rename the file to the NNNN-kebab-case.md convention, e.g. 0001-record-architecture-decisions.md',\n },\n defaultOptions: {\n pattern: '^[0-9]{4}-[a-z0-9-]+\\\\.md$',\n },\n schema,\n },\n create(context) {\n const { pattern } = context.options;\n const regex = assertSafeRegex(pattern, 'madr/filename-format', 'pattern');\n const filename = basename(context.file.path);\n if (!regex.test(filename)) {\n context.report({\n messageId: 'invalidFilename',\n data: { filename, expected: pattern },\n });\n }\n },\n};\n\nexport default rule;\n","import safeRegex from 'safe-regex2';\nimport { RuleOptionsError } from './runner.js';\n\nconst SAFE_REGEX_LIMIT = 25;\n\n// Pattern → compiled RegExp cache. Only safe patterns are cached; unsafe\n// or syntactically invalid ones throw on every call (callers should fix\n// their config rather than burn cycles re-validating the same bad input).\n//\n// Cache scope: module-global. Keys are pattern strings, which are static\n// per rule.defaultOptions or per user config. Bounded in practice — a\n// long-running daemon would still see at most O(rules × user-overrides)\n// distinct patterns. If memory becomes a concern in pathological cases\n// (e.g. dynamically-generated patterns), the cache can be swapped for an\n// LRU. As of v0.1, a plain Map is sufficient.\nconst cache = new Map<string, RegExp>();\n\nexport function assertSafeRegex(\n pattern: string,\n ruleName: string,\n optionPath: string,\n): RegExp {\n const cached = cache.get(pattern);\n if (cached) return cached;\n\n let regex: RegExp;\n try {\n regex = new RegExp(pattern);\n } catch (err) {\n throw new RuleOptionsError(\n ruleName,\n `option \"${optionPath}\" is not a valid regular expression: ${(err as Error).message}`,\n );\n }\n if (!safeRegex(regex, { limit: SAFE_REGEX_LIMIT })) {\n throw new RuleOptionsError(\n ruleName,\n `option \"${optionPath}\" (${pattern}) appears unsafe (catastrophic backtracking). Reject before runtime.`,\n );\n }\n cache.set(pattern, regex);\n return regex;\n}\n\n/**\n * Test-only helper to drop the cache between unit tests so that\n * \"unsafe pattern\" assertions don't accidentally short-circuit on a\n * previously-cached safe entry. Not exported from src/index.ts.\n */\nexport function clearRegexCacheForTesting(): void {\n cache.clear();\n}\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"pattern\": {\n \"type\": \"string\",\n \"description\": \"Regex (as string) the basename must match. Defaults to ^[0-9]{4}-[a-z0-9-]+\\\\.md$\"\n }\n }\n}\n","import { dirname, posix } from 'node:path';\nimport type { MdastNode, ProjectRule } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface NoBrokenLinksOptions extends Record<string, unknown> {\n // No options at v0.1.\n}\n\nconst PROTOCOL_REGEX = /^[a-z][a-z0-9+.-]*:/i;\n\ninterface LinkLikeNode {\n url: string;\n /** unist position — start carries the body-coordinate line/column. */\n position?: { start: { line: number; column: number } };\n}\n\nfunction isLinkNode(node: MdastNode): node is MdastNode & LinkLikeNode {\n return (\n node.type === 'link' &&\n 'url' in node &&\n typeof (node as { url: unknown }).url === 'string'\n );\n}\n\nfunction isExternalOrAnchor(url: string): boolean {\n if (url === '' || url.startsWith('#')) return true;\n return PROTOCOL_REGEX.test(url);\n}\n\nfunction stripAnchorAndQuery(url: string): string {\n const hash = url.indexOf('#');\n const query = url.indexOf('?');\n let cut = hash;\n if (query !== -1 && (cut === -1 || query < cut)) cut = query;\n return cut === -1 ? url : url.slice(0, cut);\n}\n\n// Markdown link destinations may be percent-encoded (e.g. `my%20file.md`).\n// Decode to the real on-disk path for resolution; leave malformed encoding\n// (e.g. a bare `%`) untouched rather than throwing. Fast-path the common case\n// of no percent-encoding to keep per-link cost minimal (decodeURIComponent is\n// comparatively expensive).\nfunction safeDecode(path: string): string {\n if (!path.includes('%')) return path;\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\nfunction resolveRelative(fromDir: string, url: string): string {\n // Treat absolute (`/foo`) as project-rooted by stripping the leading slash,\n // so `[x](/0042-y.md)` resolves to `0042-y.md` rather than the OS root.\n if (url.startsWith('/')) return url.slice(1);\n return posix.normalize(posix.join(fromDir, url));\n}\n\nfunction collectLinks(node: MdastNode, out: LinkLikeNode[]): void {\n if (isLinkNode(node)) out.push(node);\n if ('children' in node && Array.isArray(node.children)) {\n for (const child of node.children) {\n collectLinks(child as MdastNode, out);\n }\n }\n}\n\nconst rule: ProjectRule<NoBrokenLinksOptions> = {\n meta: {\n name: 'madr/no-broken-links',\n type: 'project',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description:\n 'Relative-path Markdown links must resolve to an existing file in the project',\n url: 'https://knktkc.github.io/madr-lint/rules/no-broken-links/',\n recommended: true,\n },\n messages: {\n brokenLink:\n 'Link to \"{{url}}\" resolves to \"{{resolvedPath}}\", which does not exist in the project',\n },\n suggestions: {\n brokenLink:\n \"fix the link target or remove the link; relative paths resolve from the ADR file's own directory\",\n },\n defaultOptions: {},\n schema,\n },\n check(context) {\n const knownPaths = new Set<string>();\n for (const file of context.files) {\n knownPaths.add(file.path);\n }\n\n for (const file of context.files) {\n const links: LinkLikeNode[] = [];\n collectLinks(file.ast, links);\n\n for (const link of links) {\n const url = link.url;\n if (isExternalOrAnchor(url)) continue;\n\n const pathOnly = safeDecode(stripAnchorAndQuery(url));\n if (pathOnly === '') continue;\n\n const fileDir = dirname(file.path);\n const resolvedPath = resolveRelative(fileDir, pathOnly);\n\n // A target counts as present if it is one of the linted .md files\n // OR exists on disk (covers non-Markdown assets and files outside\n // the scanned paths). fileExists is undefined in pure in-memory\n // runs, where knownPaths is the only source of truth. NOTE: the\n // on-disk check inherits the host filesystem's case-sensitivity —\n // a wrong-case link may pass on macOS/Windows yet fail on Linux/CI.\n const exists =\n knownPaths.has(resolvedPath) ||\n (context.fileExists?.(resolvedPath) ?? false);\n\n if (!exists) {\n // Link position is in body coordinates (frontmatter stripped) —\n // the same space suppression directives use, so disable-next-line\n // can target this diagnostic.\n const start = link.position?.start;\n context.report({\n messageId: 'brokenLink',\n path: file.path,\n ...(start\n ? { loc: { line: start.line, column: start.column } }\n : {}),\n data: { url, resolvedPath },\n });\n }\n }\n }\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {}\n}\n","import { basename } from 'node:path';\nimport type { ProjectRule } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface NoDuplicateNumberingOptions extends Record<string, unknown> {\n // No options at v0.1.\n}\n\nconst NUMBER_REGEX = /^(\\d{4})-/;\n\nconst rule: ProjectRule<NoDuplicateNumberingOptions> = {\n meta: {\n name: 'madr/no-duplicate-numbering',\n type: 'project',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'No two ADRs share the same NNNN- number prefix',\n url: 'https://knktkc.github.io/madr-lint/rules/no-duplicate-numbering/',\n recommended: true,\n },\n messages: {\n duplicateNumber: 'ADR number {{number}} is used by multiple files: {{paths}}',\n },\n suggestions: {\n duplicateNumber:\n 'renumber all but one of these files so each ADR has a unique NNNN- prefix',\n },\n defaultOptions: {},\n schema,\n },\n check(context) {\n const byNumber = new Map<string, string[]>();\n for (const file of context.files) {\n const match = NUMBER_REGEX.exec(basename(file.path));\n if (!match) continue;\n const number = match[1];\n if (number === undefined) continue;\n const group = byNumber.get(number) ?? [];\n group.push(file.path);\n byNumber.set(number, group);\n }\n\n for (const [number, paths] of byNumber) {\n if (paths.length > 1) {\n const pathList = paths.join(', ');\n for (const path of paths) {\n context.report({\n messageId: 'duplicateNumber',\n path,\n data: { number, paths: pathList },\n });\n }\n }\n }\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {}\n}\n","import { basename } from 'node:path';\nimport type { ProjectRule } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface NoNumberingGapOptions extends Record<string, unknown> {\n // No options at v0.1.\n}\n\nconst NUMBER_REGEX = /^(\\d{4})-/;\n\nfunction format4(n: number): string {\n return n.toString().padStart(4, '0');\n}\n\nconst rule: ProjectRule<NoNumberingGapOptions> = {\n meta: {\n name: 'madr/no-numbering-gap',\n type: 'project',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description:\n 'Detect gaps in ADR numbering (e.g. 0001 and 0003 exist but 0002 is missing). Convention-only; default off.',\n url: 'https://knktkc.github.io/madr-lint/rules/no-numbering-gap/',\n recommended: false,\n },\n messages: {\n numberingGap:\n 'Numbering gap: missing {{missing}} between ADR-{{from}} and ADR-{{to}}',\n },\n // No `suggestions`: a gap can be intentional (a rejected or deleted ADR),\n // and closing it may mean either adding the missing ADR OR renumbering —\n // the right remediation is contextual, so we omit a suggestion. See #67.\n defaultOptions: {},\n schema,\n },\n check(context) {\n // Map ADR number → file path. Files without a NNNN- prefix are ignored.\n const numberToPath = new Map<number, string>();\n for (const file of context.files) {\n const match = NUMBER_REGEX.exec(basename(file.path));\n if (!match?.[1]) continue;\n const n = Number.parseInt(match[1], 10);\n if (!Number.isNaN(n)) numberToPath.set(n, file.path);\n }\n if (numberToPath.size < 2) return;\n\n const sortedNumbers = Array.from(numberToPath.keys()).toSorted(\n (a, b) => a - b,\n );\n\n for (let i = 1; i < sortedNumbers.length; i++) {\n const prev = sortedNumbers[i - 1];\n const curr = sortedNumbers[i];\n if (prev === undefined || curr === undefined) continue;\n if (curr === prev + 1) continue;\n\n const missingNumbers: string[] = [];\n for (let n = prev + 1; n < curr; n++) {\n missingNumbers.push(format4(n));\n }\n\n const currPath = numberToPath.get(curr);\n if (!currPath) continue;\n\n context.report({\n messageId: 'numberingGap',\n path: currPath,\n data: {\n from: format4(prev),\n to: format4(curr),\n missing: missingNumbers.join(', '),\n },\n });\n }\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {}\n}\n","import { toString } from 'mdast-util-to-string';\nimport type { Rule, RuleListeners } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface RequiredSectionsOptions extends Record<string, unknown> {\n sections: string[];\n matchMode: 'exact' | 'startsWith';\n}\n\nconst rule: Rule<RequiredSectionsOptions> = {\n meta: {\n name: 'madr/required-sections',\n type: 'perFile',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'Enforce that ADRs contain required heading sections',\n url: 'https://knktkc.github.io/madr-lint/rules/required-sections/',\n recommended: true,\n },\n messages: {\n missingSection: 'Missing required section: \"{{section}}\"',\n },\n suggestions: {\n missingSection:\n 'add a \"## {{section}}\" heading to the document body',\n },\n defaultOptions: {\n sections: [\n 'Context and Problem Statement',\n 'Decision Outcome',\n 'Consequences',\n ],\n matchMode: 'exact',\n },\n schema,\n },\n create(context): RuleListeners {\n const seen: string[] = [];\n return {\n enter: {\n heading(node) {\n if (node.type !== 'heading') return;\n seen.push(toString(node).trim());\n },\n },\n exit: {\n root() {\n const { sections, matchMode } = context.options;\n for (const section of sections) {\n const matched = seen.some((heading) =>\n matchMode === 'startsWith'\n ? heading.startsWith(section)\n : heading === section,\n );\n if (!matched) {\n context.report({\n messageId: 'missingSection',\n data: { section, found: [...seen] },\n });\n }\n }\n },\n },\n };\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"sections\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"Required heading texts. Order does not matter.\"\n },\n \"matchMode\": {\n \"type\": \"string\",\n \"enum\": [\"exact\", \"startsWith\"],\n \"description\": \"Comparison mode against heading text.\"\n }\n }\n}\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"Exact-match allowed status values.\"\n },\n \"prefixValues\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" },\n \"description\": \"startsWith-match allowed prefixes (e.g. 'superseded by').\"\n },\n \"caseSensitive\": {\n \"type\": \"boolean\",\n \"description\": \"When false, comparisons are case-insensitive.\"\n }\n }\n}\n","import type { Fixer, Rule, TextEdit } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface StatusEnumOptions extends Record<string, unknown> {\n values: string[];\n prefixValues: string[];\n caseSensitive: boolean;\n}\n\n// A tiny, curated misspelling → canonical-spelling map, applied token-by-token.\n// Deliberately minimal and length-agnostic; a correction only lands if the\n// repaired string matches exactly ONE configured value/prefix (below), so this\n// table can never introduce an out-of-enum value. See #29.\nconst TYPO_CORRECTIONS: Record<string, string> = {\n superceded: 'superseded',\n supercedes: 'supersedes',\n depricated: 'deprecated',\n};\n\n/** Repair known misspelled tokens in a lowercased status string. */\nfunction repairTypos(lower: string): string {\n return lower\n .split(' ')\n .map((tok) => TYPO_CORRECTIONS[tok] ?? tok)\n .join(' ');\n}\n\n/** The substring of `s` after skipping its first `n` whitespace-delimited words. */\nfunction tailAfterWords(s: string, n: number): string {\n let i = 0;\n for (let w = 0; w < n; w++) {\n while (i < s.length && /\\s/.test(s[i]!)) i++; // skip separators\n while (i < s.length && !/\\s/.test(s[i]!)) i++; // skip the word\n }\n return s.slice(i);\n}\n\n/**\n * The unambiguous canonical replacement for an invalid `status`, or undefined\n * when no safe one-to-one correction exists. Corrections are validated against\n * the CONFIGURED enum, so autofix never guesses:\n * 1. exact value — case-fold (+ token typo repair) onto exactly one `values`;\n * 2. prefix value — repaired string starts with exactly one `prefixValues`\n * entry; the canonical prefix is spliced in and the original tail kept.\n * A mapping that lands on two candidates (case collision, two prefixes) declines.\n */\nfunction correctStatus(\n status: string,\n values: string[],\n prefixValues: string[],\n): string | undefined {\n const repaired = repairTypos(status.toLowerCase());\n\n // 1. Exact value.\n const exact = [...new Set(values.filter((v) => v.toLowerCase() === repaired))];\n if (exact.length === 1) {\n return exact[0] !== status ? exact[0] : undefined;\n }\n if (exact.length > 1) return undefined; // ambiguous\n\n // 2. Prefix value (canonical prefix + verbatim tail).\n const prefixes = [\n ...new Set(\n prefixValues.filter((p) => {\n const pl = p.toLowerCase();\n return repaired === pl || repaired.startsWith(`${pl} `);\n }),\n ),\n ];\n if (prefixes.length !== 1) return undefined;\n const prefix = prefixes[0]!;\n const tail = tailAfterWords(status, prefix.split(' ').length);\n const replacement = `${prefix}${tail}`;\n return replacement !== status ? replacement : undefined;\n}\n\nconst rule: Rule<StatusEnumOptions> = {\n meta: {\n name: 'madr/status-enum',\n type: 'perFile',\n versionCompat: ['v2', 'v3', 'v4'],\n docs: {\n description: 'Validate that ADR `status` (frontmatter or v2 bold-list) is in the allowed enum',\n url: 'https://knktkc.github.io/madr-lint/rules/status-enum/',\n recommended: true,\n },\n messages: {\n missingStatus: 'Metadata does not contain a \"status\" field (checked frontmatter and v2 bold-list)',\n invalidStatus: 'Status \"{{status}}\" is not one of: {{allowed}}',\n },\n // No suggestion for invalidStatus: the message already lists the allowed\n // values (the expected-vs-actual is self-contained), so a suggestion would\n // only restate it. See issue #67.\n suggestions: {\n missingStatus:\n 'add a \"status\" field to the frontmatter (for MADR v2, a \"* Status: ...\" list item) — allowed values: {{allowed}}',\n },\n // Autofix (#28/#29): normalize a v2 list-sourced value to the unique\n // canonical enum entry — case difference, a curated misspelling, or a prefix\n // case/typo — preserving any tail (see create()). Ambiguous or\n // frontmatter-sourced values get no fix.\n fixable: 'code',\n defaultOptions: {\n values: ['proposed', 'rejected', 'accepted', 'deprecated'],\n prefixValues: ['superseded by'],\n caseSensitive: false,\n },\n schema,\n },\n create(context) {\n const { values, prefixValues, caseSensitive } = context.options;\n // The allowed-value list is shared by both the invalidStatus message and\n // the missingStatus suggestion, so build it once up front.\n const allowed = [...values, ...prefixValues.map((p) => `${p} ...`)];\n\n const meta = context.metadata;\n if (!meta || typeof meta.status !== 'string') {\n context.report({ messageId: 'missingStatus', data: { allowed } });\n return;\n }\n\n const status = meta.status;\n const compareStatus = caseSensitive ? status : status.toLowerCase();\n const compareValues = caseSensitive\n ? values\n : values.map((v) => v.toLowerCase());\n const comparePrefixes = caseSensitive\n ? prefixValues\n : prefixValues.map((p) => p.toLowerCase());\n\n const exactMatch = compareValues.includes(compareStatus);\n const prefixMatch = comparePrefixes.some((p) =>\n compareStatus.startsWith(p),\n );\n\n if (!exactMatch && !prefixMatch) {\n // loc only exists for v2 list-sourced values: frontmatter is stripped\n // before mdast parsing, so a frontmatter-sourced status has no body\n // line to point at — inline suppression directives (which live in the\n // body) can only silence it file-wide, never per line.\n const loc = context.metadataLoc?.status;\n // Autofix (#28/#29): offer a fix ONLY for a v2 list-sourced value (we then\n // have its exact body offset range) that maps unambiguously onto the\n // CONFIGURED enum. Frontmatter-sourced values have no body offset and need\n // YAML-aware rewriting — out of scope, so no fix is attached there.\n const canonical = correctStatus(status, values, prefixValues);\n const valueRange = context.metadataValueLoc?.status;\n const fix =\n canonical !== undefined && canonical !== status && valueRange\n ? (fixer: Fixer): TextEdit =>\n fixer.replaceRange([valueRange.start, valueRange.end], canonical)\n : undefined;\n context.report({\n messageId: 'invalidStatus',\n ...(loc ? { loc } : {}),\n data: { status, allowed },\n ...(fix ? { fix } : {}),\n });\n }\n },\n};\n\nexport default rule;\n","import { basename } from 'node:path';\nimport { frontmatterOffset } from '../../core/parser.js';\nimport type { Fixer, ProjectFile, ProjectRule, TextEdit } from '../../core/types.js';\nimport schema from './schema.json' with { type: 'json' };\n\ninterface SupersedesBidirectionalOptions extends Record<string, unknown> {\n // No options at v0.1.\n}\n\nconst ADR_NUMBER_REGEX = /^(\\d{4})-/;\nconst FORWARD_FIELD = 'supersedes';\nconst BACKWARD_FIELD = 'superseded-by';\n\nfunction extractAdrNumber(path: string): string | null {\n const match = ADR_NUMBER_REGEX.exec(basename(path));\n return match?.[1] ?? null;\n}\n\nfunction normalizeRefs(value: unknown): string[] {\n if (typeof value === 'string') return [value];\n if (Array.isArray(value)) {\n return value.filter((v): v is string => typeof v === 'string');\n }\n return [];\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Compute a whole-file insertion of `key: value` immediately before the closing\n * `---` of the target's EXISTING YAML frontmatter, or null when the fix is not\n * mechanically safe. Frontmatter is treated as opaque lines — NO YAML parsing or\n * serialization (which would reflow the block). Guard rails:\n * - no frontmatter block (v2 list-sourced or bare body) → null;\n * - `key` already present as a top-level frontmatter key → null (rewriting a\n * wrong value, or appending to an existing key, is out of scope). Matched\n * CASE-INSENSITIVELY: inserting `superseded-by:` next to an existing\n * `Superseded-By:` would be valid YAML but reads as contradictory\n * duplicates to a human;\n * - the byte slice at the computed offset must be the closing fence.\n * The file's newline style (LF vs CRLF) is preserved. See #29 / ADR-0008.\n */\nfunction frontmatterInsertEdit(\n content: string,\n key: string,\n value: string,\n): { offset: number; text: string } | null {\n const fmEnd = frontmatterOffset(content);\n if (fmEnd === 0) return null; // no frontmatter block\n\n const fmBlock = content.slice(0, fmEnd);\n // The closing fence is the final `---` line of the block (its own newline is\n // part of fmBlock, so anchor at end). Capture the fence line to derive EOL.\n const closeMatch = /\\r?\\n(---[ \\t]*\\r?\\n)$/.exec(fmBlock);\n if (!closeMatch) return null;\n const fenceLine = closeMatch[1]!;\n const offset = closeMatch.index + (closeMatch[0].length - fenceLine.length);\n\n // Slice-verification: the computed offset must sit exactly on the `---` fence.\n if (content.slice(offset, offset + 3) !== '---') return null;\n\n // The key must not already exist as a top-level frontmatter key (any case\n // variant counts — see the guard-rail note above). `content` up to the fence\n // is `---<eol>...keys...`; the opening fence cannot match a `key:` line, so\n // scanning the whole prefix is safe.\n const keyPattern = new RegExp(`(^|\\\\r?\\\\n)${escapeRegExp(key)}[ \\\\t]*:`, 'i');\n if (keyPattern.test(content.slice(0, offset))) return null;\n\n const eol = fenceLine.endsWith('\\r\\n') ? '\\r\\n' : '\\n';\n return { offset, text: `${key}: ${value}${eol}` };\n}\n\n/**\n * A fix that inserts `key: value` into `target`'s frontmatter, or undefined when\n * no mechanically-safe insertion exists. The guard rails are evaluated EAGERLY\n * (at report time) so the diagnostic's durable `fixable` flag is accurate — a\n * thunk that merely returns null at apply time would still mark the diagnostic\n * fixable. The cross-file fixer works in WHOLE-FILE coordinates (project fixes\n * touch frontmatter, which body coordinates strip), so the offset is used as-is.\n */\nfunction backReferenceFix(\n target: ProjectFile,\n key: string,\n value: string,\n): ((fixer: Fixer) => TextEdit) | undefined {\n const edit = frontmatterInsertEdit(target.content, key, value);\n if (!edit) return undefined;\n return (fixer) => fixer.insertAt(edit.offset, edit.text);\n}\n\nconst rule: ProjectRule<SupersedesBidirectionalOptions> = {\n meta: {\n name: 'madr/supersedes-bidirectional',\n type: 'project',\n versionCompat: ['v3', 'v4'],\n docs: {\n description:\n 'Frontmatter `supersedes` and `superseded-by` must reference each other bidirectionally',\n url: 'https://knktkc.github.io/madr-lint/rules/supersedes-bidirectional/',\n recommended: true,\n },\n messages: {\n unknownReference:\n 'Frontmatter `{{direction}}: {{ref}}` references an ADR that does not exist',\n // `declared` is the field the SOURCE file actually declares;\n // `direction` is the field THIS file (the diagnostic's path) must add.\n // They are opposites — rendering `direction` as the declared field\n // would mislabel the source's frontmatter in both directions.\n missingBackReference:\n '`{{source}}` declares `{{declared}}: {{ref}}`, but `{{ref}}` (this file) does not back-reference it via `{{direction}}: {{expected}}`',\n },\n suggestions: {\n unknownReference:\n 'correct \"{{ref}}\" to an existing ADR number, or remove \"{{direction}}: {{ref}}\" from the frontmatter',\n missingBackReference:\n 'add \"{{direction}}: {{expected}}\" to the frontmatter of this file',\n },\n // Cross-file autofix (#29): `missingBackReference` inserts the reciprocal\n // `<direction>: <expected>` line into the target's EXISTING frontmatter.\n // Targets without frontmatter, or where the key already exists, decline;\n // `unknownReference` is contextual and never fixable.\n fixable: 'code',\n defaultOptions: {},\n schema,\n },\n check(context) {\n // Build ADR-NNNN → file index. Files without a NNNN- prefix are\n // not addressable via `supersedes`/`superseded-by` and thus skipped.\n const adrIndex = new Map<string, ProjectFile>();\n for (const file of context.files) {\n const num = extractAdrNumber(file.path);\n if (num) adrIndex.set(`ADR-${num}`, file);\n }\n\n // Walk every file with a frontmatter and check both directions.\n for (const file of context.files) {\n const fm = file.frontmatter;\n if (!fm) continue;\n const fileNumber = extractAdrNumber(file.path);\n if (!fileNumber) continue;\n const fileRef = `ADR-${fileNumber}`;\n\n const forwardRefs = normalizeRefs(fm[FORWARD_FIELD]);\n const backwardRefs = normalizeRefs(fm[BACKWARD_FIELD]);\n\n // Forward direction: this file `supersedes` something — verify\n // the target back-references this file via `superseded-by`.\n for (const ref of forwardRefs) {\n const target = adrIndex.get(ref);\n if (!target) {\n context.report({\n messageId: 'unknownReference',\n path: file.path,\n data: { ref, direction: FORWARD_FIELD },\n });\n continue;\n }\n const targetBack = normalizeRefs(target.frontmatter?.[BACKWARD_FIELD]);\n if (!targetBack.includes(fileRef)) {\n const fix = backReferenceFix(target, BACKWARD_FIELD, fileRef);\n context.report({\n messageId: 'missingBackReference',\n path: target.path,\n data: {\n ref,\n source: file.path,\n declared: FORWARD_FIELD,\n direction: BACKWARD_FIELD,\n expected: fileRef,\n },\n ...(fix ? { fix } : {}),\n });\n }\n }\n\n // Backward direction: this file is `superseded-by` something —\n // verify the target forward-references this file via `supersedes`.\n for (const ref of backwardRefs) {\n const target = adrIndex.get(ref);\n if (!target) {\n context.report({\n messageId: 'unknownReference',\n path: file.path,\n data: { ref, direction: BACKWARD_FIELD },\n });\n continue;\n }\n const targetFwd = normalizeRefs(target.frontmatter?.[FORWARD_FIELD]);\n if (!targetFwd.includes(fileRef)) {\n const fix = backReferenceFix(target, FORWARD_FIELD, fileRef);\n context.report({\n messageId: 'missingBackReference',\n path: target.path,\n data: {\n ref,\n source: file.path,\n declared: BACKWARD_FIELD,\n direction: FORWARD_FIELD,\n expected: fileRef,\n },\n ...(fix ? { fix } : {}),\n });\n }\n }\n }\n },\n};\n\nexport default rule;\n","{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {}\n}\n","import { basename } from 'node:path';\nimport picomatch from 'picomatch';\n\n/**\n * Should the given relative path be excluded from linting?\n *\n * Patterns are tested in three forms (any match wins):\n * - **Full glob via picomatch** against the relative POSIX path:\n * `docs/**\\/draft-*.md`, `**\\/template.md`, `9999-*.md`\n * - Full glob via picomatch against the file's basename, so patterns\n * like `README.md` or `9999-*` work without a `**\\/` prefix.\n * - Exact relative-path match (defensive — picomatch with default\n * options also handles this, but we keep the explicit branch for\n * clarity and to avoid surprises if picomatch ever changes default\n * `dot`/`bash` semantics).\n *\n * Picomatch is invoked with `{ dot: true }` so dotfiles (rare for ADRs\n * but possible — e.g. `.archived/*.md`) are matched naturally.\n */\nexport function shouldIgnore(\n relativePath: string,\n patterns: readonly string[],\n): boolean {\n if (patterns.length === 0) return false;\n const base = basename(relativePath);\n for (const pattern of patterns) {\n if (matchesPattern(relativePath, base, pattern)) return true;\n }\n return false;\n}\n\nfunction matchesPattern(\n relativePath: string,\n base: string,\n pattern: string,\n): boolean {\n if (relativePath === pattern) return true;\n const matcher = picomatch(pattern, { dot: true });\n if (matcher(relativePath)) return true;\n if (matcher(base)) return true;\n // Back-compat shortcut: a pattern like `adr/template.md` (sub-path\n // without leading `**/`) should match `docs/adr/template.md`. Also\n // handles `adr/9999-*` against `docs/adr/9999-x.md` via picomatch\n // applied to the suffix.\n if (pattern.includes('/') && !pattern.startsWith('**/')) {\n const suffixMatcher = picomatch(`**/${pattern}`, { dot: true });\n if (suffixMatcher(relativePath)) return true;\n }\n return false;\n}\n","import pc from 'picocolors';\nimport { interpolate } from './interpolate.js';\nimport type { AnyRule, Diagnostic } from './types.js';\n\n/**\n * Run-level facts a reporter cannot derive from the diagnostics themselves.\n * Optional third argument so existing two-arg callers keep working.\n */\nexport interface ReporterMeta {\n /** Diagnostics absorbed by .madr-lint/baseline.json. Absent ⇒ 0. */\n baselineHidden?: number;\n /**\n * How many fixes were applied this run (autofix, #28). Present only when a\n * fix pass ran (`--fix` / `--fix-dry-run`); absent on a plain lint. json\n * surfaces it as `summary.fixed`.\n */\n fixed?: number;\n /**\n * Per-file unified diffs of a dry run (`--fix-dry-run`). Present only then.\n * json embeds them as a top-level `diffs` array so machine consumers get the\n * dry run's output in-band — the raw diff must never pollute a structured\n * format's stdout. Other reporters ignore this (the CLI prints/routes the\n * raw diff itself for text / sarif / github).\n */\n fixDiffs?: readonly { path: string; diff: string }[];\n}\n\nexport interface Reporter {\n format(\n diagnostics: readonly Diagnostic[],\n rulesByName: Map<string, AnyRule>,\n meta?: ReporterMeta,\n ): string;\n}\n\nexport type ReporterFormat = 'text' | 'json' | 'sarif' | 'github';\n\n/**\n * Human-readable reporter for terminal output. Groups diagnostics by file,\n * interpolates {{placeholders}} from `data` against the rule's message\n * template, and ends with a count summary.\n *\n * Coloring uses picocolors which honors NO_COLOR / FORCE_COLOR env vars.\n */\nexport const textReporter: Reporter = {\n // Deliberately 2-param (meta unused): the CLI prints baselineHiddenSummary()\n // itself for text format, so the footer can outlive a suppressed banner.\n format(diagnostics, rulesByName) {\n if (diagnostics.length === 0) {\n return pc.green('✓ All clear.');\n }\n\n const byFile = groupByFile(diagnostics);\n const lines: string[] = [];\n\n // Aligns a suggestion / docs line under the message column:\n // severity gutter (9) + padded ruleId (28) + the 2-space separator.\n const gutter = ' '.repeat(9 + 28 + 2);\n\n for (const [file, fileDiagnostics] of byFile) {\n lines.push(pc.underline(file));\n // ruleName -> docs URL, first-seen order, one entry per rule in the group.\n const docsByRule = new Map<string, string>();\n for (const d of fileDiagnostics) {\n const sev =\n d.severity === 'error'\n ? pc.red(' error ')\n : pc.yellow(' warn ');\n const ruleId = pc.dim(d.ruleName.padEnd(28));\n const message = renderMessage(d, rulesByName);\n // Autofix (#28): a dim marker tells the reader `--fix` can repair this.\n const fixTag = d.fixable ? pc.dim(' 🔧 fixable') : '';\n lines.push(`${sev}${ruleId} ${message}${fixTag}`);\n if (d.suggestion) {\n lines.push(`${gutter}${pc.cyan(`→ ${d.suggestion}`)}`);\n }\n if (d.docsUrl && !docsByRule.has(d.ruleName)) {\n docsByRule.set(d.ruleName, d.docsUrl);\n }\n }\n // Compact footer: the docs URL once per rule per file group, never per\n // diagnostic — self-contained without repeating the link. See issue #67.\n for (const [ruleName, url] of docsByRule) {\n lines.push(pc.dim(` ${ruleName} ${url}`));\n }\n lines.push('');\n }\n\n const errors = diagnostics.filter((d) => d.severity === 'error').length;\n const warnings = diagnostics.filter((d) => d.severity === 'warn').length;\n lines.push(pc.bold(formatSummary(errors, warnings)));\n\n return lines.join('\\n');\n },\n};\n\nfunction groupByFile(diagnostics: readonly Diagnostic[]): Map<string, Diagnostic[]> {\n const map = new Map<string, Diagnostic[]>();\n for (const d of diagnostics) {\n if (!map.has(d.path)) map.set(d.path, []);\n map.get(d.path)?.push(d);\n }\n return map;\n}\n\nfunction renderMessage(\n d: Diagnostic,\n rulesByName: Map<string, AnyRule>,\n): string {\n const rule = rulesByName.get(d.ruleName);\n const template = rule?.meta.messages[d.messageId] ?? d.messageId;\n return interpolate(template, d.data ?? {});\n}\n\nfunction formatSummary(errors: number, warnings: number): string {\n const parts: string[] = [];\n if (errors > 0) parts.push(`${errors} ${errors === 1 ? 'error' : 'errors'}`);\n if (warnings > 0) parts.push(`${warnings} ${warnings === 1 ? 'warning' : 'warnings'}`);\n return parts.join(', ');\n}\n\n/**\n * Machine-readable JSON reporter. Stable shape — additive changes only,\n * tagged via `version`. Tools can `JSON.parse` stdout and consume `results`\n * directly without scraping the human-readable formatter.\n */\nexport const jsonReporter: Reporter = {\n format(diagnostics, rulesByName, meta) {\n const errors = diagnostics.filter((d) => d.severity === 'error').length;\n const warnings = diagnostics.filter((d) => d.severity === 'warn').length;\n const payload = {\n version: 1,\n summary: {\n total: diagnostics.length,\n errors,\n warnings,\n // Always present (0 when no baseline) so CI consumers can detect an\n // active baseline without probing for the key. SARIF stays untouched.\n baselineHidden: meta?.baselineHidden ?? 0,\n // `fixed` appears ONLY when a fix pass ran (#28) — a plain lint keeps\n // the summary shape it always had.\n ...(meta?.fixed !== undefined ? { fixed: meta.fixed } : {}),\n },\n // Dry-run diffs (#28): embedded so `--fix-dry-run --format json` keeps\n // stdout pure JSON while still delivering the diff. Absent otherwise.\n ...(meta?.fixDiffs !== undefined ? { diffs: meta.fixDiffs } : {}),\n results: diagnostics.map((d) => ({\n path: d.path,\n ruleName: d.ruleName,\n messageId: d.messageId,\n severity: d.severity,\n message: renderMessage(d, rulesByName),\n // Self-contained diagnostics (#67): always present so consumers can\n // rely on the keys — `suggestion` is null when the rule declares none.\n suggestion: d.suggestion,\n docsUrl: d.docsUrl,\n // Autofix (#28): whether `--fix` can repair this diagnostic.\n fixable: d.fixable,\n data: d.data ?? {},\n })),\n };\n return JSON.stringify(payload, null, 2);\n },\n};\n\n/**\n * SARIF 2.1.0 reporter. Emits a minimal-but-valid Static Analysis Results\n * Interchange Format document so GitHub Code Scanning, VS Code SARIF\n * Viewer, and other consumers can ingest madr-lint results.\n *\n * Spec: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html\n */\nexport const sarifReporter: Reporter = {\n format(diagnostics, rulesByName) {\n const ruleNames = [...new Set(diagnostics.map((d) => d.ruleName))];\n const ruleIndex = new Map(ruleNames.map((name, i) => [name, i]));\n\n const sarifRules = ruleNames.map((name) => {\n const rule = rulesByName.get(name);\n return {\n id: name,\n name: name.replace(/[^A-Za-z0-9]/g, '_'),\n shortDescription: { text: rule?.meta.docs.description ?? name },\n helpUri: rule?.meta.docs.url,\n };\n });\n\n const sarifResults = diagnostics.map((d) => ({\n ruleId: d.ruleName,\n ruleIndex: ruleIndex.get(d.ruleName) ?? 0,\n level: d.severity === 'error' ? 'error' : 'warning',\n message: { text: renderMessage(d, rulesByName) },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: d.path, uriBaseId: '%SRCROOT%' },\n region: { startLine: 1 },\n },\n },\n ],\n }));\n\n const sarif = {\n $schema:\n 'https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n tool: {\n driver: {\n name: 'madr-lint',\n informationUri: 'https://github.com/knktkc/madr-lint',\n rules: sarifRules,\n },\n },\n results: sarifResults,\n },\n ],\n };\n return JSON.stringify(sarif, null, 2);\n },\n};\n\n/** Escape a workflow-command message value (data after `::`). */\nfunction escapeGhMessage(s: string): string {\n return s.replace(/%/g, '%25').replace(/\\r/g, '%0D').replace(/\\n/g, '%0A');\n}\n\n/** Escape a workflow-command property value (e.g. file=, title=). */\nfunction escapeGhProperty(s: string): string {\n return escapeGhMessage(s)\n .replace(/,/g, '%2C')\n .replace(/:/g, '%3A');\n}\n\n/**\n * GitHub Actions annotation reporter. Emits one `::error` / `::warning`\n * workflow command per diagnostic so GitHub renders them as PR diff annotations,\n * then a one-line human summary on stdout.\n *\n * Escape rules from https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions\n */\nexport const githubReporter: Reporter = {\n format(diagnostics, rulesByName, meta) {\n const lines: string[] = [];\n\n for (const d of diagnostics) {\n const level = d.severity === 'error' ? 'error' : 'warning';\n const file = escapeGhProperty(d.path);\n const title = escapeGhProperty(d.ruleName);\n // Self-contained diagnostics (#67): fold the suggestion into the single\n // annotation message (annotations carry no separate remediation field),\n // escaping the combined string once. Properties are unchanged.\n const rendered = renderMessage(d, rulesByName);\n const message = escapeGhMessage(\n d.suggestion ? `${rendered} — ${d.suggestion}` : rendered,\n );\n\n const linePart = d.loc ? `,line=${d.loc.line}` : '';\n lines.push(`::${level} file=${file}${linePart},title=${title}::${message}`);\n }\n\n const errors = diagnostics.filter((d) => d.severity === 'error').length;\n const warnings = diagnostics.filter((d) => d.severity === 'warn').length;\n const summaryParts: string[] = [];\n if (errors > 0) summaryParts.push(`${errors} ${errors === 1 ? 'error' : 'errors'}`);\n if (warnings > 0) summaryParts.push(`${warnings} ${warnings === 1 ? 'warning' : 'warnings'}`);\n // A clean run still prints counts — a silent log line reads as a broken CI step.\n if (summaryParts.length === 0) summaryParts.push('0 errors, 0 warnings');\n const baselineHidden = meta?.baselineHidden ?? 0;\n if (baselineHidden > 0) summaryParts.push(`${baselineHidden} hidden by baseline`);\n lines.push(summaryParts.join(', '));\n\n return lines.join('\\n');\n },\n};\n\nexport const reporters: Record<ReporterFormat, Reporter> = {\n text: textReporter,\n json: jsonReporter,\n sarif: sarifReporter,\n github: githubReporter,\n};\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","relative","resolve","sep","defineCommand","writeFileSync","resolve","existsSync","existsSync","readFileSync","readdirSync","join","resolve","sep","readdirSync","resolve","readFileSync","join","existsSync","pkg","readFileSync","statSync","resolve","existsSync","readFileSync","join","rule","join","rule","existsSync","readFileSync","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","join","join","existsSync","readFileSync","mkdirSync","dirname","writeFileSync","i","j","rule","rule","readFileSync","rule","resolve","statSync","schema_default","rule","schema_default","dirname","schema_default","rule","schema_default","dirname","basename","schema_default","rule","schema_default","basename","basename","schema_default","NUMBER_REGEX","rule","schema_default","basename","toString","schema_default","rule","schema_default","toString","schema_default","rule","schema_default","basename","schema_default","basename","rule","schema_default","writeFileSync","resolve","basename","rule","toPosix","sep","readFileSync","defineCommand","resolve","relative","existsSync","writeFileSync"]}