@descent-vtt/spec-graph 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/README.md +87 -2
- package/dist/cli.d.ts +11 -2
- package/dist/cli.js +34 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/extract.d.ts +49 -1
- package/dist/extract.js +156 -8
- package/dist/extract.js.map +1 -1
- package/dist/identity.js +14 -5
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +16 -2
- package/dist/markdown.js +89 -22
- package/dist/markdown.js.map +1 -1
- package/dist/report.d.ts +21 -4
- package/dist/report.js +123 -7
- package/dist/report.js.map +1 -1
- package/dist/resolve.d.ts +2 -1
- package/dist/resolve.js +56 -6
- package/dist/resolve.js.map +1 -1
- package/dist/rules.js +30 -0
- package/dist/rules.js.map +1 -1
- package/dist/types.d.ts +23 -3
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The command line.\n *\n * Four verbs, because there are four questions worth asking of a specification\n * graph: is it consistent (`check`), what does it contain (`query`), what does\n * it look like (`graph`), and what will you check for me (`rules`).\n *\n * Exit codes are the contract with CI: `0` clean, `1` findings, `2` the tool\n * itself could not run. A usage mistake never masquerades as a passing build.\n */\n\nimport { applyBaseline, EMPTY_BASELINE, formatBaseline, parseBaseline, type Baseline } from './baseline.js';\nimport { loadConfig, type SpecGraphConfig } from './config.js';\nimport { underRoot } from './glob.js';\nimport { analyse, DEFAULT_PATTERNS, withDiagnostics, type AnalyseOptions } from './runner.js';\nimport { formatGraph, formatJson, formatReport, shouldUseAscii, shouldUseColor, type GraphFormat } from './report.js';\nimport { DEFAULT_SEVERITIES, resolveStrict, RULE_DESCRIPTIONS, RULE_IDS, RULE_QUERIES } from './rules.js';\nimport { formatRef } from './source.js';\nimport { execute, parseQuery, QueryError, renderMatch, type Match } from './select.js';\nimport type { RuleId, Severity, SpecNode } from './types.js';\n\nexport const EXIT_OK = 0;\nexport const EXIT_FAILED = 1;\nexport const EXIT_ERROR = 2;\n\nexport class UsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UsageError';\n }\n}\n\nexport type Command = 'check' | 'query' | 'graph' | 'rules';\n\nexport interface CliOptions {\n readonly command: Command;\n readonly patterns: readonly string[];\n readonly root: string;\n readonly ignore: readonly string[];\n /** Reference targets to leave unreported when they do not resolve. */\n readonly ignoreReferences: readonly string[];\n /** Families a bare identifier may name. Empty means any family in the corpus. */\n readonly families: readonly string[];\n /** Families that are never citations. */\n readonly ignoreFamilies: readonly string[];\n /** Files that log what was decided rather than deciding it. */\n readonly historyPatterns: readonly string[];\n /** Accepted-debt file to read. `null` means none was asked for. */\n readonly baseline: string | null;\n /** Where to write the current findings as accepted debt. */\n readonly recordBaseline: string | null;\n /** Skip the repository configuration file entirely. */\n readonly noConfig: boolean;\n readonly format: 'human' | 'json';\n readonly graphFormat: GraphFormat;\n readonly severities: Partial<Record<RuleId, Severity>>;\n readonly color: boolean | null;\n readonly ascii: boolean | null;\n readonly verbose: boolean;\n readonly documentsOnly: boolean;\n readonly max: number;\n readonly maxWarnings: number;\n /** Raise every warning to an error. Explicit `--rule` overrides still win. */\n readonly strict: boolean;\n readonly selector: string | null;\n readonly help: boolean;\n readonly version: boolean;\n}\n\nexport interface CliIO {\n readonly argv?: readonly string[] | undefined;\n readonly cwd?: string | undefined;\n readonly stdout?: ((text: string) => void) | undefined;\n readonly stderr?: ((text: string) => void) | undefined;\n readonly env?: Readonly<Record<string, string | undefined>> | undefined;\n readonly isTTY?: boolean | undefined;\n}\n\nexport const HELP = `spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [--explain]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector and print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the built-in diagnostics.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against (default: cwd)\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. Missing file = accept nothing.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format human|json Report format (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems and per-file detail\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n`;\n\nconst SEVERITIES: readonly Severity[] = ['error', 'warn', 'info', 'off'];\n\n/** Parses argv into options. Throws {@link UsageError} on anything malformed. */\nexport function parseArgs(argv: readonly string[], cwd: string): CliOptions {\n const args = [...argv];\n let command: Command = 'check';\n\n if (args.length > 0 && !((args[0] as string).startsWith('-'))) {\n const first = args[0] as string;\n if (first === 'check' || first === 'query' || first === 'graph' || first === 'rules') {\n command = first;\n args.shift();\n }\n }\n\n const patterns: string[] = [];\n const ignore: string[] = [];\n const ignoreReferences: string[] = [];\n const families: string[] = [];\n const ignoreFamilies: string[] = [];\n const historyPatterns: string[] = [];\n let noConfig = false;\n const severities: Partial<Record<RuleId, Severity>> = {};\n let root = cwd;\n let format: 'human' | 'json' = 'human';\n let graphFormat: GraphFormat = 'dot';\n let color: boolean | null = null;\n let ascii: boolean | null = null;\n let verbose = false;\n let documentsOnly = false;\n let max = 0;\n let maxWarnings = -1;\n let strict = false;\n let baseline: string | null = null;\n let recordBaseline: string | null = null;\n let selector: string | null = null;\n let help = false;\n let version = false;\n\n const next = (flag: string, index: number): string => {\n const value = args[index + 1];\n if (value === undefined) throw new UsageError(`${flag} needs a value`);\n return value;\n };\n\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i] as string;\n\n if (arg === '--') {\n patterns.push(...args.slice(i + 1));\n break;\n }\n\n if (!arg.startsWith('-')) {\n if (command === 'query' && selector === null) selector = arg;\n else patterns.push(arg);\n continue;\n }\n\n switch (arg) {\n case '-h':\n case '--help':\n help = true;\n break;\n case '-v':\n case '--version':\n version = true;\n break;\n case '--verbose':\n verbose = true;\n break;\n case '--color':\n color = true;\n break;\n case '--no-color':\n color = false;\n break;\n case '--ascii':\n ascii = true;\n break;\n case '--documents-only':\n documentsOnly = true;\n break;\n case '--strict':\n strict = true;\n break;\n case '--explain':\n verbose = true;\n break;\n case '--root':\n root = next(arg, i);\n i += 1;\n break;\n case '--ignore':\n ignore.push(next(arg, i));\n i += 1;\n break;\n case '--ignore-ref':\n ignoreReferences.push(next(arg, i));\n i += 1;\n break;\n case '--family':\n families.push(next(arg, i));\n i += 1;\n break;\n case '--ignore-family':\n ignoreFamilies.push(next(arg, i));\n i += 1;\n break;\n case '--history':\n historyPatterns.push(next(arg, i));\n i += 1;\n break;\n case '--baseline':\n baseline = next(arg, i);\n i += 1;\n break;\n case '--record-baseline':\n recordBaseline = next(arg, i);\n i += 1;\n break;\n case '--no-config':\n noConfig = true;\n break;\n case '--format': {\n const value = next(arg, i);\n if (value !== 'human' && value !== 'json') {\n throw new UsageError(`--format must be human or json, got \"${value}\"`);\n }\n format = value;\n i += 1;\n break;\n }\n case '--graph-format': {\n const value = next(arg, i);\n if (value !== 'dot' && value !== 'mermaid' && value !== 'json') {\n throw new UsageError(`--graph-format must be dot, mermaid or json, got \"${value}\"`);\n }\n graphFormat = value;\n i += 1;\n break;\n }\n case '--rule': {\n const value = next(arg, i);\n const equals = value.indexOf('=');\n if (equals === -1) throw new UsageError(`--rule expects <id>=<severity>, got \"${value}\"`);\n const id = value.slice(0, equals) as RuleId;\n const level = value.slice(equals + 1) as Severity;\n if (!RULE_IDS.includes(id)) {\n throw new UsageError(`unknown rule \"${id}\"\\n known rules: ${RULE_IDS.join(', ')}`);\n }\n if (!SEVERITIES.includes(level)) {\n throw new UsageError(`unknown severity \"${level}\", expected one of: ${SEVERITIES.join(', ')}`);\n }\n severities[id] = level;\n i += 1;\n break;\n }\n case '--max': {\n max = parseCount(arg, next(arg, i));\n i += 1;\n break;\n }\n case '--max-warnings': {\n maxWarnings = parseCount(arg, next(arg, i));\n i += 1;\n break;\n }\n default:\n throw new UsageError(`unknown option \"${arg}\"\\n run \"spec-graph --help\" to see the available options`);\n }\n }\n\n if (command === 'query' && selector === null && !help && !version) {\n throw new UsageError('query needs a selector, for example:\\n spec-graph query \\'item[openness=open]\\'');\n }\n\n return {\n command,\n patterns,\n root,\n ignore,\n ignoreReferences,\n families,\n ignoreFamilies,\n historyPatterns,\n baseline,\n recordBaseline,\n noConfig,\n format,\n graphFormat,\n severities,\n color,\n ascii,\n verbose,\n documentsOnly,\n max,\n maxWarnings,\n strict,\n selector,\n help,\n version,\n };\n}\n\nfunction count(n: number, word: string, many?: string): string {\n return `${n} ${n === 1 ? word : (many ?? `${word}s`)}`;\n}\n\n/**\n * Reads a baseline file.\n *\n * A missing file is not an error: `--baseline` against a repository that has\n * not recorded one yet should report everything, which is exactly what an empty\n * baseline does.\n */\nasync function readBaseline(path: string, source: string): Promise<ReturnType<typeof parseBaseline>> {\n let raw: string;\n try {\n const { readFile } = await import('node:fs/promises');\n raw = await readFile(path, 'utf8');\n } catch {\n return { baseline: EMPTY_BASELINE, problems: [] };\n }\n return parseBaseline(raw, source);\n}\n\nfunction parseCount(flag: string, value: string): number {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 0 || String(parsed) !== value.trim()) {\n throw new UsageError(`${flag} expects a non-negative whole number, got \"${value}\"`);\n }\n return parsed;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Entry point */\n/* -------------------------------------------------------------------------- */\n\n/** Runs the CLI and returns the process exit code. Never throws. */\nexport async function main(io: CliIO = {}): Promise<number> {\n const argv = io.argv ?? process.argv.slice(2);\n const cwd = io.cwd ?? process.cwd();\n const out = io.stdout ?? ((text) => process.stdout.write(text));\n const err = io.stderr ?? ((text) => process.stderr.write(text));\n const env = io.env ?? process.env;\n\n let options: CliOptions;\n try {\n options = parseArgs(argv, cwd);\n } catch (error) {\n if (error instanceof UsageError) {\n err(`spec-graph: ${error.message}\\n`);\n return EXIT_ERROR;\n }\n throw error;\n }\n\n if (options.help) {\n out(HELP);\n return EXIT_OK;\n }\n if (options.version) {\n out(`${await readVersion()}\\n`);\n return EXIT_OK;\n }\n\n const color = options.color ?? shouldUseColor({ isTTY: io.isTTY, env });\n const ascii = options.ascii ?? shouldUseAscii({ env });\n\n if (options.command === 'rules') {\n out(renderRules(options.verbose));\n return EXIT_OK;\n }\n\n // Configuration is what is true of the repository; a flag is somebody\n // overriding it for one run. So a flag always wins, and lists add rather than\n // replace - a `--ignore-ref` on the command line is one more exclusion, not a\n // decision to throw away the ones the repository already declared.\n const loaded = options.noConfig ? { config: {} as SpecGraphConfig, source: null, problems: [] } : loadConfig(options.root);\n for (const problem of loaded.problems) err(`spec-graph: ${problem}\\n`);\n const file = loaded.config;\n\n const patterns =\n options.patterns.length > 0 ? options.patterns : (file.patterns ?? DEFAULT_PATTERNS);\n const severityOverrides = { ...(file.severities ?? {}), ...options.severities };\n const { severities, escalated } = resolveStrict(severityOverrides, options.strict || (file.strict ?? false));\n\n const analyseOptions: AnalyseOptions = {\n root: options.root,\n patterns,\n ignore: [...(file.ignore ?? []), ...options.ignore],\n ignoreReferences: [...(file.ignoreReferences ?? []), ...options.ignoreReferences],\n families: [...(file.families ?? []), ...options.families],\n ignoreFamilies: [...(file.ignoreFamilies ?? []), ...options.ignoreFamilies],\n historyPatterns: [...(file.historyPatterns ?? []), ...options.historyPatterns],\n severities,\n ...(file.maxRelated !== undefined ? { maxRelated: file.maxRelated } : {}),\n };\n\n if (options.verbose && loaded.source !== null) out(`configuration: ${loaded.source}\\n`);\n\n let result;\n try {\n result = await analyse(analyseOptions);\n } catch (error) {\n err(`spec-graph: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n\n if (result.files.length === 0) {\n err(\n `spec-graph: no specifications matched ${\n options.patterns.length > 0 ? options.patterns.map((p) => `\"${p}\"`).join(', ') : 'the default patterns'\n }\\n looked under ${options.root}\\n`,\n );\n return EXIT_ERROR;\n }\n\n switch (options.command) {\n case 'graph':\n out(formatGraph(result.graph, options.graphFormat, { documentsOnly: options.documentsOnly }));\n return EXIT_OK;\n\n case 'query': {\n try {\n const matches = execute(result.graph, parseQuery(options.selector as string));\n out(renderMatches(matches, options.format, result.graph.nodes.size));\n return matches.length > 0 ? EXIT_OK : EXIT_FAILED;\n } catch (error) {\n if (error instanceof QueryError) {\n err(renderQueryError(options.selector as string, error));\n return EXIT_ERROR;\n }\n throw error;\n }\n }\n\n default: {\n // Recording is not checking. It writes down what is wrong today so that\n // tomorrow can be compared against it, and says nothing about whether\n // today is acceptable - so it reports what it wrote and exits clean.\n if (options.recordBaseline !== null) {\n const text = formatBaseline(result.graph, result.diagnostics);\n try {\n const { writeFile } = await import('node:fs/promises');\n await writeFile(underRoot(options.root, options.recordBaseline), text, 'utf8');\n } catch (error) {\n err(`spec-graph: cannot write ${options.recordBaseline}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n const entries = (JSON.parse(text) as Baseline).findings.length;\n out(\n `recorded ${count(result.diagnostics.length, 'finding')} as ${count(entries, 'entry', 'entries')} in ${options.recordBaseline}\\n`,\n );\n return EXIT_OK;\n }\n\n const source = options.baseline ?? file.baseline ?? null;\n let reported = result;\n let note: { source: string; suppressed: number; stale: number } | undefined;\n if (source !== null) {\n const held = await readBaseline(underRoot(options.root, source), source);\n for (const problem of held.problems) err(`spec-graph: ${problem}\\n`);\n const outcome = applyBaseline(result.graph, result.diagnostics, held.baseline);\n reported = withDiagnostics(result, outcome.kept);\n note = { source, suppressed: outcome.suppressed, stale: outcome.stale.length };\n if (options.verbose) {\n for (const entry of outcome.stale) {\n out(` paid: ${entry.rule} ${entry.document}${entry.subject === '' ? '' : ` \"${entry.subject}\"`}\\n`);\n }\n }\n }\n\n const baselineNote = note === undefined ? {} : { baseline: note };\n out(\n options.format === 'json'\n ? formatJson(reported, { escalated, ...baselineNote })\n : `${formatReport(reported, { color, ascii, verbose: options.verbose, max: options.max, escalated, ...baselineNote })}\\n`,\n );\n if (!reported.ok) return EXIT_FAILED;\n if (options.maxWarnings >= 0 && reported.summary.warnings > options.maxWarnings) return EXIT_FAILED;\n return EXIT_OK;\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Rendering */\n/* -------------------------------------------------------------------------- */\n\nfunction renderRules(explain: boolean): string {\n const width = Math.max(...RULE_IDS.map((id) => id.length));\n const lines: string[] = [];\n for (const id of RULE_IDS) {\n lines.push(`${id.padEnd(width)} ${DEFAULT_SEVERITIES[id].padEnd(5)} ${RULE_DESCRIPTIONS[id]}`);\n const query = RULE_QUERIES[id];\n if (explain && query) lines.push(`${' '.repeat(width)} ${query}`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction renderMatches(matches: readonly Match[], format: 'human' | 'json', total: number): string {\n if (format === 'json') {\n return `${JSON.stringify(\n {\n version: 1,\n count: matches.length,\n matches: matches.map((match) => ({\n nodes: match.nodes.map((node) => nodeSummary(node)),\n edges: match.edges.map((edge) => ({\n kind: edge.kind,\n from: edge.from,\n to: edge.to,\n file: edge.declaredAt.file,\n line: edge.declaredAt.span.start.line,\n })),\n })),\n },\n null,\n 2,\n )}\\n`;\n }\n\n if (matches.length === 0) return `no matches (searched ${total} nodes)\\n`;\n\n const lines: string[] = [];\n for (const match of matches) {\n const head = match.nodes[0] as SpecNode;\n const tail = match.nodes[match.nodes.length - 1] as SpecNode;\n lines.push(renderMatch(match));\n lines.push(` ${formatRef(head.at)}${match.nodes.length > 1 ? ` ${formatRef(tail.at)}` : ''}`);\n }\n lines.push(`${matches.length} ${matches.length === 1 ? 'match' : 'matches'}`);\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction nodeSummary(node: SpecNode): Record<string, unknown> {\n return {\n id: node.id,\n kind: node.kind,\n title: node.title,\n file: node.at.file,\n line: node.at.span.start.line,\n ...(node.kind === 'document' ? { phase: node.phase } : { state: node.disposition, openness: node.openness }),\n };\n}\n\n/** Points at the exact character of a selector that failed to parse. */\nfunction renderQueryError(selector: string, error: QueryError): string {\n const caret = `${' '.repeat(Math.max(0, error.offset))}^`;\n return `spec-graph: ${error.message}\\n ${selector}\\n ${caret}\\n`;\n}\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFile } = await import('node:fs/promises');\n const url = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(await readFile(url, 'utf8'));\n if (typeof parsed === 'object' && parsed !== null && 'version' in parsed) {\n return String((parsed as { version: unknown }).version);\n }\n } catch {\n // Fall through to the placeholder rather than failing `--version`.\n }\n return '0.0.0';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The command line.\n *\n * Four verbs, because there are four questions worth asking of a specification\n * graph: is it consistent (`check`), what does it contain (`query`), what does\n * it look like (`graph`), and what will you check for me (`rules`).\n *\n * Exit codes are the contract with CI: `0` clean, `1` findings, `2` the tool\n * itself could not run. A usage mistake never masquerades as a passing build.\n */\n\nimport { applyBaseline, EMPTY_BASELINE, formatBaseline, parseBaseline, type Baseline } from './baseline.js';\nimport { loadConfig, type SpecGraphConfig } from './config.js';\nimport { underRoot } from './glob.js';\nimport { analyse, DEFAULT_PATTERNS, withDiagnostics, type AnalyseOptions } from './runner.js';\nimport {\n formatGraph,\n formatJson,\n formatReport,\n formatSarif,\n shouldUseAscii,\n shouldUseColor,\n type GraphFormat,\n} from './report.js';\nimport { DEFAULT_SEVERITIES, resolveStrict, RULE_DESCRIPTIONS, RULE_IDS, RULE_QUERIES } from './rules.js';\nimport { formatRef } from './source.js';\nimport { execute, parseQuery, QueryError, renderMatch, type Match } from './select.js';\nimport type { RuleId, Severity, SpecNode } from './types.js';\n\nexport const EXIT_OK = 0;\nexport const EXIT_FAILED = 1;\nexport const EXIT_ERROR = 2;\n\nexport class UsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UsageError';\n }\n}\n\nexport type Command = 'check' | 'query' | 'graph' | 'rules';\n\nexport interface CliOptions {\n readonly command: Command;\n readonly patterns: readonly string[];\n readonly root: string;\n readonly ignore: readonly string[];\n /** Reference targets to leave unreported when they do not resolve. */\n readonly ignoreReferences: readonly string[];\n /** Families a bare identifier may name. Empty means any family in the corpus. */\n readonly families: readonly string[];\n /** Families that are never citations. */\n readonly ignoreFamilies: readonly string[];\n /** Files that log what was decided rather than deciding it. */\n readonly historyPatterns: readonly string[];\n /** Accepted-debt file to read. `null` means none was asked for. */\n readonly baseline: string | null;\n /** Where to write the current findings as accepted debt. */\n readonly recordBaseline: string | null;\n /**\n * Fail when the baseline allows something that no longer happens.\n *\n * The other side of the ratchet, and off by default for the reason ADR-0012\n * gives: failing a build because somebody fixed something is a strange way to\n * encourage them. A team that has decided its debt only goes one way asks for\n * it explicitly, and then a dead exemption cannot outlive the defect.\n */\n readonly ratchet: boolean;\n /** Skip the repository configuration file entirely. */\n readonly noConfig: boolean;\n readonly format: 'human' | 'json' | 'sarif';\n readonly graphFormat: GraphFormat;\n readonly severities: Partial<Record<RuleId, Severity>>;\n readonly color: boolean | null;\n readonly ascii: boolean | null;\n readonly verbose: boolean;\n readonly documentsOnly: boolean;\n readonly max: number;\n readonly maxWarnings: number;\n /** Raise every warning to an error. Explicit `--rule` overrides still win. */\n readonly strict: boolean;\n readonly selector: string | null;\n readonly help: boolean;\n readonly version: boolean;\n}\n\nexport interface CliIO {\n readonly argv?: readonly string[] | undefined;\n readonly cwd?: string | undefined;\n readonly stdout?: ((text: string) => void) | undefined;\n readonly stderr?: ((text: string) => void) | undefined;\n readonly env?: Readonly<Record<string, string | undefined>> | undefined;\n readonly isTTY?: boolean | undefined;\n}\n\nexport const HELP = `spec-graph - turn Markdown specifications into a verifiable graph\n\nUSAGE\n spec-graph [check] [patterns...] [options]\n spec-graph query <selector> [patterns...] [options]\n spec-graph graph [patterns...] [--graph-format dot|mermaid|json]\n spec-graph rules [--explain]\n\nCOMMANDS\n check Validate the specification graph. The default.\n query Run a selector and print the matching paths.\n graph Export the graph for Graphviz, Mermaid, or another tool.\n rules List the built-in diagnostics.\n\nOPTIONS\n --root <dir> Directory the patterns resolve against (default: cwd)\n --ignore <glob> Skip paths. Repeatable.\n --ignore-ref <glob> Do not report these reference targets when they fail\n to resolve, for repositories where [[...]] tags a\n concept rather than naming a file. Repeatable.\n Suppresses findings only, never edges.\n --family <name> Families a bare identifier in prose may name. When\n given, everything else stays prose. Repeatable.\n --ignore-family <name> Families that are never citations - RFC when the repo\n cites RFC 2119 and keeps its own RFCs. Repeatable.\n --history <glob> Files that log what was decided rather than deciding\n it - journals, changelogs, minutes. Their links are\n still checked; their obligations are not. Repeatable.\n --baseline <file> Accept the findings recorded in this file and report\n only what is new since. Missing file = accept nothing.\n --record-baseline <f> Write today's findings to this file as accepted debt,\n and exit 0 without judging them.\n --ratchet Also fail when a baseline entry no longer occurs, so\n a paid-off exemption cannot outlive the defect.\n --no-config Ignore .spec-graph.json and the package.json key.\n --format <fmt> human, json, or sarif - the interchange format\n GitHub code scanning and editors already read\n (default: human)\n --graph-format <fmt> dot, mermaid or json (default: dot)\n --documents-only Leave items out of the exported graph\n --rule <id>=<severity> Override one rule: error, warn, info or off. Repeatable.\n --max <n> Show at most n findings (0 = no limit)\n --max-warnings <n> Fail when warnings exceed n (default: no limit)\n --strict Raise every warning to an error. An explicit --rule\n still wins, so --strict --rule x=warn exempts x.\n --color / --no-color Force colour on or off\n --ascii Use ASCII glyphs only\n --verbose Include parse problems and per-file detail\n -h, --help Show this help\n -v, --version Show the version\n\nSELECTORS\n A selector is a path through the graph.\n\n item[openness=open] -delegates-to-> document[phase=retired]\n document[phase=active] -assumes-> document[phase=retired]\n document =supersedes=> document\n *[path^=docs/adr] -contains-> item[state=accepted-debt]\n\n Node types: document, item, * (any)\n Attributes: id, kind, title, path, file, line, phase, status, receptivity,\n alias, document, state (or disposition), openness, section,\n text, body, evidence, conflicted, fm.<front-matter-key>\n Operators: = != ^= $= *= ~= and [attr] for \"is present\"\n Relations: -kind-> <-kind- =kind=> <=kind= (= forms are transitive)\n\nEXIT CODES\n 0 clean\n 1 findings at error severity (or over --max-warnings)\n 2 the tool could not run\n\nCONFIGURATION\n Anything repeated on every run belongs in the repository rather than in the\n command. spec-graph reads the first of these that exists:\n\n .spec-graph.json, spec-graph.config.json, or a \"spec-graph\" key in\n package.json\n\n { \"patterns\": [\"docs/**/*.md\"],\n \"ignoreReferences\": [\"trap *\"],\n \"ignoreFamilies\": [\"RFC\"],\n \"historyPatterns\": [\"**/JOURNAL_*.md\", \"archive/**\"],\n \"baseline\": \".spec-graph-baseline.json\",\n \"severities\": { \"self-reference\": \"off\" },\n \"strict\": true }\n\n A flag always wins over the file, and list flags add to it rather than\n replacing it.\n\nADOPTING THIS ON AN OLD REPOSITORY\n Record what is already wrong, then report only what happens next:\n\n spec-graph check --record-baseline .spec-graph-baseline.json\n git add .spec-graph-baseline.json\n\n The file is keyed by specification and citation, not by line number, so it\n survives edits, moves and renames. Findings that stop occurring are reported\n so the file can be tightened; nothing new gets in.\n\nEXAMPLES\n spec-graph \"docs/**/*.md\"\n spec-graph check --rule self-reference=off --format json\n spec-graph query 'item[openness=open] -delegates-to-> document[phase=retired]'\n spec-graph check --ignore-ref \"trap *\" # [[trap 55]] tags a concept, not a file\n spec-graph graph --documents-only --graph-format mermaid > graph.mmd\n spec-graph check --history \"**/JOURNAL_*.md\" # a log is not a specification\n spec-graph check --baseline .spec-graph-baseline.json\n`;\n\nconst SEVERITIES: readonly Severity[] = ['error', 'warn', 'info', 'off'];\n\n/** Parses argv into options. Throws {@link UsageError} on anything malformed. */\nexport function parseArgs(argv: readonly string[], cwd: string): CliOptions {\n const args = [...argv];\n let command: Command = 'check';\n\n if (args.length > 0 && !((args[0] as string).startsWith('-'))) {\n const first = args[0] as string;\n if (first === 'check' || first === 'query' || first === 'graph' || first === 'rules') {\n command = first;\n args.shift();\n }\n }\n\n const patterns: string[] = [];\n const ignore: string[] = [];\n const ignoreReferences: string[] = [];\n const families: string[] = [];\n const ignoreFamilies: string[] = [];\n const historyPatterns: string[] = [];\n let noConfig = false;\n const severities: Partial<Record<RuleId, Severity>> = {};\n let root = cwd;\n let format: 'human' | 'json' | 'sarif' = 'human';\n let graphFormat: GraphFormat = 'dot';\n let color: boolean | null = null;\n let ascii: boolean | null = null;\n let verbose = false;\n let documentsOnly = false;\n let max = 0;\n let maxWarnings = -1;\n let strict = false;\n let baseline: string | null = null;\n let ratchet = false;\n let recordBaseline: string | null = null;\n let selector: string | null = null;\n let help = false;\n let version = false;\n\n const next = (flag: string, index: number): string => {\n const value = args[index + 1];\n if (value === undefined) throw new UsageError(`${flag} needs a value`);\n return value;\n };\n\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i] as string;\n\n if (arg === '--') {\n patterns.push(...args.slice(i + 1));\n break;\n }\n\n if (!arg.startsWith('-')) {\n if (command === 'query' && selector === null) selector = arg;\n else patterns.push(arg);\n continue;\n }\n\n switch (arg) {\n case '-h':\n case '--help':\n help = true;\n break;\n case '-v':\n case '--version':\n version = true;\n break;\n case '--verbose':\n verbose = true;\n break;\n case '--color':\n color = true;\n break;\n case '--no-color':\n color = false;\n break;\n case '--ascii':\n ascii = true;\n break;\n case '--documents-only':\n documentsOnly = true;\n break;\n case '--strict':\n strict = true;\n break;\n case '--ratchet':\n ratchet = true;\n break;\n case '--explain':\n verbose = true;\n break;\n case '--root':\n root = next(arg, i);\n i += 1;\n break;\n case '--ignore':\n ignore.push(next(arg, i));\n i += 1;\n break;\n case '--ignore-ref':\n ignoreReferences.push(next(arg, i));\n i += 1;\n break;\n case '--family':\n families.push(next(arg, i));\n i += 1;\n break;\n case '--ignore-family':\n ignoreFamilies.push(next(arg, i));\n i += 1;\n break;\n case '--history':\n historyPatterns.push(next(arg, i));\n i += 1;\n break;\n case '--baseline':\n baseline = next(arg, i);\n i += 1;\n break;\n case '--record-baseline':\n recordBaseline = next(arg, i);\n i += 1;\n break;\n case '--no-config':\n noConfig = true;\n break;\n case '--format': {\n const value = next(arg, i);\n if (value !== 'human' && value !== 'json' && value !== 'sarif') {\n throw new UsageError(`--format must be human, json or sarif, got \"${value}\"`);\n }\n format = value;\n i += 1;\n break;\n }\n case '--graph-format': {\n const value = next(arg, i);\n if (value !== 'dot' && value !== 'mermaid' && value !== 'json') {\n throw new UsageError(`--graph-format must be dot, mermaid or json, got \"${value}\"`);\n }\n graphFormat = value;\n i += 1;\n break;\n }\n case '--rule': {\n const value = next(arg, i);\n const equals = value.indexOf('=');\n if (equals === -1) throw new UsageError(`--rule expects <id>=<severity>, got \"${value}\"`);\n const id = value.slice(0, equals) as RuleId;\n const level = value.slice(equals + 1) as Severity;\n if (!RULE_IDS.includes(id)) {\n throw new UsageError(`unknown rule \"${id}\"\\n known rules: ${RULE_IDS.join(', ')}`);\n }\n if (!SEVERITIES.includes(level)) {\n throw new UsageError(`unknown severity \"${level}\", expected one of: ${SEVERITIES.join(', ')}`);\n }\n severities[id] = level;\n i += 1;\n break;\n }\n case '--max': {\n max = parseCount(arg, next(arg, i));\n i += 1;\n break;\n }\n case '--max-warnings': {\n maxWarnings = parseCount(arg, next(arg, i));\n i += 1;\n break;\n }\n default:\n throw new UsageError(`unknown option \"${arg}\"\\n run \"spec-graph --help\" to see the available options`);\n }\n }\n\n // SARIF is a report about findings, and only `check` produces those. Falling\n // back to JSON would hand a pipeline something its uploader rejects with a\n // message about a schema rather than about the command that was run.\n if (format === 'sarif' && command !== 'check' && !help && !version) {\n throw new UsageError(`--format sarif reports findings, so it belongs to check, not to ${command}`);\n }\n\n if (command === 'query' && selector === null && !help && !version) {\n throw new UsageError('query needs a selector, for example:\\n spec-graph query \\'item[openness=open]\\'');\n }\n\n return {\n command,\n patterns,\n root,\n ignore,\n ignoreReferences,\n families,\n ignoreFamilies,\n historyPatterns,\n baseline,\n ratchet,\n recordBaseline,\n noConfig,\n format,\n graphFormat,\n severities,\n color,\n ascii,\n verbose,\n documentsOnly,\n max,\n maxWarnings,\n strict,\n selector,\n help,\n version,\n };\n}\n\nfunction count(n: number, word: string, many?: string): string {\n return `${n} ${n === 1 ? word : (many ?? `${word}s`)}`;\n}\n\n/**\n * Reads a baseline file.\n *\n * A missing file is not an error: `--baseline` against a repository that has\n * not recorded one yet should report everything, which is exactly what an empty\n * baseline does.\n */\nasync function readBaseline(path: string, source: string): Promise<ReturnType<typeof parseBaseline>> {\n let raw: string;\n try {\n const { readFile } = await import('node:fs/promises');\n raw = await readFile(path, 'utf8');\n } catch {\n return { baseline: EMPTY_BASELINE, problems: [] };\n }\n return parseBaseline(raw, source);\n}\n\nfunction parseCount(flag: string, value: string): number {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 0 || String(parsed) !== value.trim()) {\n throw new UsageError(`${flag} expects a non-negative whole number, got \"${value}\"`);\n }\n return parsed;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Entry point */\n/* -------------------------------------------------------------------------- */\n\n/** Runs the CLI and returns the process exit code. Never throws. */\nexport async function main(io: CliIO = {}): Promise<number> {\n const argv = io.argv ?? process.argv.slice(2);\n const cwd = io.cwd ?? process.cwd();\n const out = io.stdout ?? ((text) => process.stdout.write(text));\n const err = io.stderr ?? ((text) => process.stderr.write(text));\n const env = io.env ?? process.env;\n\n let options: CliOptions;\n try {\n options = parseArgs(argv, cwd);\n } catch (error) {\n if (error instanceof UsageError) {\n err(`spec-graph: ${error.message}\\n`);\n return EXIT_ERROR;\n }\n throw error;\n }\n\n if (options.help) {\n out(HELP);\n return EXIT_OK;\n }\n if (options.version) {\n out(`${await readVersion()}\\n`);\n return EXIT_OK;\n }\n\n const color = options.color ?? shouldUseColor({ isTTY: io.isTTY, env });\n const ascii = options.ascii ?? shouldUseAscii({ env });\n\n if (options.command === 'rules') {\n out(renderRules(options.verbose));\n return EXIT_OK;\n }\n\n // Configuration is what is true of the repository; a flag is somebody\n // overriding it for one run. So a flag always wins, and lists add rather than\n // replace - a `--ignore-ref` on the command line is one more exclusion, not a\n // decision to throw away the ones the repository already declared.\n const loaded = options.noConfig ? { config: {} as SpecGraphConfig, source: null, problems: [] } : loadConfig(options.root);\n for (const problem of loaded.problems) err(`spec-graph: ${problem}\\n`);\n const file = loaded.config;\n\n const patterns =\n options.patterns.length > 0 ? options.patterns : (file.patterns ?? DEFAULT_PATTERNS);\n const severityOverrides = { ...(file.severities ?? {}), ...options.severities };\n const { severities, escalated } = resolveStrict(severityOverrides, options.strict || (file.strict ?? false));\n\n const analyseOptions: AnalyseOptions = {\n root: options.root,\n patterns,\n ignore: [...(file.ignore ?? []), ...options.ignore],\n ignoreReferences: [...(file.ignoreReferences ?? []), ...options.ignoreReferences],\n families: [...(file.families ?? []), ...options.families],\n ignoreFamilies: [...(file.ignoreFamilies ?? []), ...options.ignoreFamilies],\n historyPatterns: [...(file.historyPatterns ?? []), ...options.historyPatterns],\n severities,\n ...(file.maxRelated !== undefined ? { maxRelated: file.maxRelated } : {}),\n };\n\n if (options.verbose && loaded.source !== null) out(`configuration: ${loaded.source}\\n`);\n\n let result;\n try {\n result = await analyse(analyseOptions);\n } catch (error) {\n err(`spec-graph: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n\n if (result.files.length === 0) {\n err(\n `spec-graph: no specifications matched ${\n options.patterns.length > 0 ? options.patterns.map((p) => `\"${p}\"`).join(', ') : 'the default patterns'\n }\\n looked under ${options.root}\\n`,\n );\n return EXIT_ERROR;\n }\n\n switch (options.command) {\n case 'graph':\n out(formatGraph(result.graph, options.graphFormat, { documentsOnly: options.documentsOnly }));\n return EXIT_OK;\n\n case 'query': {\n try {\n const matches = execute(result.graph, parseQuery(options.selector as string));\n out(renderMatches(matches, options.format === 'json' ? 'json' : 'human', result.graph.nodes.size));\n return matches.length > 0 ? EXIT_OK : EXIT_FAILED;\n } catch (error) {\n if (error instanceof QueryError) {\n err(renderQueryError(options.selector as string, error));\n return EXIT_ERROR;\n }\n throw error;\n }\n }\n\n default: {\n // Recording is not checking. It writes down what is wrong today so that\n // tomorrow can be compared against it, and says nothing about whether\n // today is acceptable - so it reports what it wrote and exits clean.\n if (options.recordBaseline !== null) {\n const text = formatBaseline(result.graph, result.diagnostics);\n try {\n const { writeFile } = await import('node:fs/promises');\n await writeFile(underRoot(options.root, options.recordBaseline), text, 'utf8');\n } catch (error) {\n err(`spec-graph: cannot write ${options.recordBaseline}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n const entries = (JSON.parse(text) as Baseline).findings.length;\n out(\n `recorded ${count(result.diagnostics.length, 'finding')} as ${count(entries, 'entry', 'entries')} in ${options.recordBaseline}\\n`,\n );\n return EXIT_OK;\n }\n\n const source = options.baseline ?? file.baseline ?? null;\n const ratchet = options.ratchet || file.ratchet === true;\n let reported = result;\n let note: { source: string; suppressed: number; stale: number; ratchet: boolean } | undefined;\n if (source !== null) {\n const held = await readBaseline(underRoot(options.root, source), source);\n for (const problem of held.problems) err(`spec-graph: ${problem}\\n`);\n const outcome = applyBaseline(result.graph, result.diagnostics, held.baseline);\n reported = withDiagnostics(result, outcome.kept);\n note = { source, suppressed: outcome.suppressed, stale: outcome.stale.length, ratchet };\n // Listed rather than counted when the run turns on them: a number is\n // enough to know the file has slack, and not enough to strike it. Never\n // in JSON, where the entries are already in the report and a stray line\n // on stdout is the difference between parsing and not.\n if (options.format === 'human' && (options.verbose || (ratchet && outcome.stale.length > 0))) {\n for (const entry of outcome.stale) {\n out(` paid: ${entry.rule} ${entry.document}${entry.subject === '' ? '' : ` \"${entry.subject}\"`}\\n`);\n }\n }\n }\n const looseBaseline = note !== undefined && note.ratchet && note.stale > 0;\n\n const baselineNote = note === undefined ? {} : { baseline: note };\n out(\n options.format === 'sarif'\n ? formatSarif(reported, reported.graph, { version: await readVersion(), escalated })\n : options.format === 'json'\n ? formatJson(reported, { escalated, ...baselineNote })\n : `${formatReport(reported, { color, ascii, verbose: options.verbose, max: options.max, escalated, ...baselineNote })}\\n`,\n );\n if (!reported.ok || looseBaseline) return EXIT_FAILED;\n if (options.maxWarnings >= 0 && reported.summary.warnings > options.maxWarnings) return EXIT_FAILED;\n return EXIT_OK;\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Rendering */\n/* -------------------------------------------------------------------------- */\n\nfunction renderRules(explain: boolean): string {\n const width = Math.max(...RULE_IDS.map((id) => id.length));\n const lines: string[] = [];\n for (const id of RULE_IDS) {\n lines.push(`${id.padEnd(width)} ${DEFAULT_SEVERITIES[id].padEnd(5)} ${RULE_DESCRIPTIONS[id]}`);\n const query = RULE_QUERIES[id];\n if (explain && query) lines.push(`${' '.repeat(width)} ${query}`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction renderMatches(matches: readonly Match[], format: 'human' | 'json', total: number): string {\n if (format === 'json') {\n return `${JSON.stringify(\n {\n version: 1,\n count: matches.length,\n matches: matches.map((match) => ({\n nodes: match.nodes.map((node) => nodeSummary(node)),\n edges: match.edges.map((edge) => ({\n kind: edge.kind,\n from: edge.from,\n to: edge.to,\n file: edge.declaredAt.file,\n line: edge.declaredAt.span.start.line,\n })),\n })),\n },\n null,\n 2,\n )}\\n`;\n }\n\n if (matches.length === 0) return `no matches (searched ${total} nodes)\\n`;\n\n const lines: string[] = [];\n for (const match of matches) {\n const head = match.nodes[0] as SpecNode;\n const tail = match.nodes[match.nodes.length - 1] as SpecNode;\n lines.push(renderMatch(match));\n lines.push(` ${formatRef(head.at)}${match.nodes.length > 1 ? ` ${formatRef(tail.at)}` : ''}`);\n }\n lines.push(`${matches.length} ${matches.length === 1 ? 'match' : 'matches'}`);\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction nodeSummary(node: SpecNode): Record<string, unknown> {\n return {\n id: node.id,\n kind: node.kind,\n title: node.title,\n file: node.at.file,\n line: node.at.span.start.line,\n ...(node.kind === 'document' ? { phase: node.phase } : { state: node.disposition, openness: node.openness }),\n };\n}\n\n/** Points at the exact character of a selector that failed to parse. */\nfunction renderQueryError(selector: string, error: QueryError): string {\n const caret = `${' '.repeat(Math.max(0, error.offset))}^`;\n return `spec-graph: ${error.message}\\n ${selector}\\n ${caret}\\n`;\n}\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFile } = await import('node:fs/promises');\n const url = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(await readFile(url, 'utf8'));\n if (typeof parsed === 'object' && parsed !== null && 'version' in parsed) {\n return String((parsed as { version: unknown }).version);\n }\n } catch {\n // Fall through to the placeholder rather than failing `--version`.\n }\n return '0.0.0';\n}\n"]}
|
package/dist/config.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface SpecGraphConfig {
|
|
|
43
43
|
readonly historyPatterns?: readonly string[] | undefined;
|
|
44
44
|
/** Path to the accepted-debt baseline, relative to the root. See ADR-0012. */
|
|
45
45
|
readonly baseline?: string | undefined;
|
|
46
|
+
/** Fail when a baseline entry no longer occurs. See ADR-0012. */
|
|
47
|
+
readonly ratchet?: boolean | undefined;
|
|
46
48
|
readonly severities?: Partial<Record<RuleId, Severity>> | undefined;
|
|
47
49
|
readonly strict?: boolean | undefined;
|
|
48
50
|
readonly maxRelated?: number | undefined;
|
package/dist/config.js
CHANGED
|
@@ -106,6 +106,12 @@ function readFields(raw, source) {
|
|
|
106
106
|
else
|
|
107
107
|
config.strict = raw['strict'];
|
|
108
108
|
}
|
|
109
|
+
if (raw['ratchet'] !== undefined) {
|
|
110
|
+
if (typeof raw['ratchet'] !== 'boolean')
|
|
111
|
+
problems.push(`${source}: "ratchet" must be true or false`);
|
|
112
|
+
else
|
|
113
|
+
config.ratchet = raw['ratchet'];
|
|
114
|
+
}
|
|
109
115
|
if (raw['maxRelated'] !== undefined) {
|
|
110
116
|
const value = raw['maxRelated'];
|
|
111
117
|
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
|
|
@@ -150,6 +156,7 @@ const KNOWN_KEYS = new Set([
|
|
|
150
156
|
'ignoreFamilies',
|
|
151
157
|
'historyPatterns',
|
|
152
158
|
'baseline',
|
|
159
|
+
'ratchet',
|
|
153
160
|
'severities',
|
|
154
161
|
'strict',
|
|
155
162
|
'maxRelated',
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Repository configuration.\n *\n * Three ADRs left the same open question: `--ignore-ref` patterns, include\n * globs and rule severities all belong to a repository rather than to whoever\n * typed the command, and an npm script was doing that job by accumulating\n * flags. A CI invocation carrying eight `--ignore-ref` arguments is a\n * configuration file that has not admitted what it is.\n *\n * The format is JSON, parsed with `JSON.parse`. That is not a shortcut - it is\n * the reason this file exists at all. spec-graph has no runtime dependencies,\n * and a YAML or TOML reader would be the largest thing in the package, to read\n * a file with nine keys in it.\n *\n * Every key mirrors a flag, and a flag always wins. Configuration is where a\n * team writes down what is true of the repository; a flag is where somebody\n * overrides it for one run.\n */\n\nimport { readFileSync } from 'node:fs';\n\nimport { RULE_IDS } from './rules.js';\nimport type { RuleId, Severity } from './types.js';\n\n/** File names looked for, in order. The first that exists is the one used. */\nexport const CONFIG_FILES: readonly string[] = Object.freeze([\n '.spec-graph.json',\n 'spec-graph.config.json',\n]);\n\n/** The `package.json` key read when no config file is present. */\nexport const CONFIG_PACKAGE_KEY = 'spec-graph';\n\nexport interface SpecGraphConfig {\n readonly patterns?: readonly string[] | undefined;\n readonly ignore?: readonly string[] | undefined;\n readonly ignoreReferences?: readonly string[] | undefined;\n /**\n * Families a bare identifier in prose may name.\n *\n * When set, `ADR-0099` is only ever read as a citation if `ADR` is listed.\n * Everything else in prose stays prose.\n */\n readonly families?: readonly string[] | undefined;\n /** Families that are never citations, whatever else the corpus contains. */\n readonly ignoreFamilies?: readonly string[] | undefined;\n /**\n * Files that are logs of what was decided rather than decisions themselves.\n *\n * Journals, changelogs, minutes. Their links are still checked; their\n * obligations and lifecycle are not. See ADR-0011.\n */\n readonly historyPatterns?: readonly string[] | undefined;\n /** Path to the accepted-debt baseline, relative to the root. See ADR-0012. */\n readonly baseline?: string | undefined;\n readonly severities?: Partial<Record<RuleId, Severity>> | undefined;\n readonly strict?: boolean | undefined;\n readonly maxRelated?: number | undefined;\n}\n\nexport interface LoadedConfig {\n readonly config: SpecGraphConfig;\n /** Where it came from, for `--verbose` and for error messages. */\n readonly source: string | null;\n /** Keys that were present but unusable. Reported, never silently dropped. */\n readonly problems: readonly string[];\n}\n\nconst EMPTY: LoadedConfig = { config: {}, source: null, problems: [] };\n\n/**\n * Reads configuration from a directory.\n *\n * Never throws. A malformed config is reported as a problem and the run\n * continues with defaults: a broken config file should not stop a team seeing\n * the findings it was going to show them anyway.\n */\nexport function loadConfig(root: string, read: (path: string) => string = defaultRead): LoadedConfig {\n for (const name of CONFIG_FILES) {\n const raw = tryRead(read, `${root}/${name}`);\n if (raw === null) continue;\n return parseConfig(raw, name);\n }\n\n const pkg = tryRead(read, `${root}/package.json`);\n if (pkg === null) return EMPTY;\n let parsed: unknown;\n try {\n parsed = JSON.parse(pkg);\n } catch {\n return EMPTY;\n }\n if (!isRecord(parsed)) return EMPTY;\n const section = parsed[CONFIG_PACKAGE_KEY];\n if (section === undefined) return EMPTY;\n if (!isRecord(section)) {\n return { config: {}, source: `package.json`, problems: [`\"${CONFIG_PACKAGE_KEY}\" must be an object`] };\n }\n return readFields(section, `package.json#${CONFIG_PACKAGE_KEY}`);\n}\n\n/** Parses config text. Exported so the shape can be tested without a disk. */\nexport function parseConfig(raw: string, source: string): LoadedConfig {\n let parsed: unknown;\n try {\n parsed = JSON.parse(withoutBom(raw));\n } catch (error) {\n return { config: {}, source, problems: [`${source} is not valid JSON: ${(error as Error).message}`] };\n }\n if (!isRecord(parsed)) return { config: {}, source, problems: [`${source} must contain a JSON object`] };\n return readFields(parsed, source);\n}\n\nfunction readFields(raw: Record<string, unknown>, source: string): LoadedConfig {\n const problems: string[] = [];\n const config: {\n -readonly [K in keyof SpecGraphConfig]: SpecGraphConfig[K];\n } = {};\n\n const strings = (key: keyof SpecGraphConfig): readonly string[] | undefined => {\n const value = raw[key];\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {\n problems.push(`${source}: \"${key}\" must be an array of strings`);\n return undefined;\n }\n return value as string[];\n };\n\n config.patterns = strings('patterns');\n config.ignore = strings('ignore');\n config.ignoreReferences = strings('ignoreReferences');\n config.families = strings('families');\n config.ignoreFamilies = strings('ignoreFamilies');\n config.historyPatterns = strings('historyPatterns');\n\n if (raw['baseline'] !== undefined) {\n if (typeof raw['baseline'] !== 'string' || raw['baseline'].trim().length === 0) {\n problems.push(`${source}: \"baseline\" must be a path`);\n } else {\n config.baseline = raw['baseline'];\n }\n }\n\n if (raw['strict'] !== undefined) {\n if (typeof raw['strict'] !== 'boolean') problems.push(`${source}: \"strict\" must be true or false`);\n else config.strict = raw['strict'];\n }\n\n if (raw['maxRelated'] !== undefined) {\n const value = raw['maxRelated'];\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n problems.push(`${source}: \"maxRelated\" must be a non-negative whole number`);\n } else {\n config.maxRelated = value;\n }\n }\n\n if (raw['severities'] !== undefined) {\n const value = raw['severities'];\n if (!isRecord(value)) {\n problems.push(`${source}: \"severities\" must be an object of rule to severity`);\n } else {\n const severities: Partial<Record<RuleId, Severity>> = {};\n for (const [rule, level] of Object.entries(value)) {\n if (!RULE_IDS.includes(rule as RuleId)) {\n problems.push(`${source}: unknown rule \"${rule}\" in \"severities\"`);\n continue;\n }\n if (level !== 'error' && level !== 'warn' && level !== 'info' && level !== 'off') {\n problems.push(`${source}: \"severities.${rule}\" must be error, warn, info or off`);\n continue;\n }\n severities[rule as RuleId] = level;\n }\n config.severities = severities;\n }\n }\n\n for (const key of Object.keys(raw)) {\n if (!KNOWN_KEYS.has(key)) problems.push(`${source}: unknown key \"${key}\"`);\n }\n\n return { config, source, problems };\n}\n\nconst KNOWN_KEYS: ReadonlySet<string> = new Set([\n 'patterns',\n 'ignore',\n 'ignoreReferences',\n 'families',\n 'ignoreFamilies',\n 'historyPatterns',\n 'baseline',\n 'severities',\n 'strict',\n 'maxRelated',\n // Tolerated so an editor can be pointed at a schema without being told off.\n '$schema',\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction tryRead(read: (path: string) => string, path: string): string | null {\n try {\n return withoutBom(read(path));\n } catch {\n return null;\n }\n}\n\n/**\n * Drops a leading byte-order mark.\n *\n * `JSON.parse` rejects one, and several Windows editors write one by default -\n * a config that silently stops applying because of an invisible first character\n * is the worst kind of configuration bug.\n */\nfunction withoutBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\nfunction defaultRead(path: string): string {\n return readFileSync(path, 'utf8');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Repository configuration.\n *\n * Three ADRs left the same open question: `--ignore-ref` patterns, include\n * globs and rule severities all belong to a repository rather than to whoever\n * typed the command, and an npm script was doing that job by accumulating\n * flags. A CI invocation carrying eight `--ignore-ref` arguments is a\n * configuration file that has not admitted what it is.\n *\n * The format is JSON, parsed with `JSON.parse`. That is not a shortcut - it is\n * the reason this file exists at all. spec-graph has no runtime dependencies,\n * and a YAML or TOML reader would be the largest thing in the package, to read\n * a file with nine keys in it.\n *\n * Every key mirrors a flag, and a flag always wins. Configuration is where a\n * team writes down what is true of the repository; a flag is where somebody\n * overrides it for one run.\n */\n\nimport { readFileSync } from 'node:fs';\n\nimport { RULE_IDS } from './rules.js';\nimport type { RuleId, Severity } from './types.js';\n\n/** File names looked for, in order. The first that exists is the one used. */\nexport const CONFIG_FILES: readonly string[] = Object.freeze([\n '.spec-graph.json',\n 'spec-graph.config.json',\n]);\n\n/** The `package.json` key read when no config file is present. */\nexport const CONFIG_PACKAGE_KEY = 'spec-graph';\n\nexport interface SpecGraphConfig {\n readonly patterns?: readonly string[] | undefined;\n readonly ignore?: readonly string[] | undefined;\n readonly ignoreReferences?: readonly string[] | undefined;\n /**\n * Families a bare identifier in prose may name.\n *\n * When set, `ADR-0099` is only ever read as a citation if `ADR` is listed.\n * Everything else in prose stays prose.\n */\n readonly families?: readonly string[] | undefined;\n /** Families that are never citations, whatever else the corpus contains. */\n readonly ignoreFamilies?: readonly string[] | undefined;\n /**\n * Files that are logs of what was decided rather than decisions themselves.\n *\n * Journals, changelogs, minutes. Their links are still checked; their\n * obligations and lifecycle are not. See ADR-0011.\n */\n readonly historyPatterns?: readonly string[] | undefined;\n /** Path to the accepted-debt baseline, relative to the root. See ADR-0012. */\n readonly baseline?: string | undefined;\n /** Fail when a baseline entry no longer occurs. See ADR-0012. */\n readonly ratchet?: boolean | undefined;\n readonly severities?: Partial<Record<RuleId, Severity>> | undefined;\n readonly strict?: boolean | undefined;\n readonly maxRelated?: number | undefined;\n}\n\nexport interface LoadedConfig {\n readonly config: SpecGraphConfig;\n /** Where it came from, for `--verbose` and for error messages. */\n readonly source: string | null;\n /** Keys that were present but unusable. Reported, never silently dropped. */\n readonly problems: readonly string[];\n}\n\nconst EMPTY: LoadedConfig = { config: {}, source: null, problems: [] };\n\n/**\n * Reads configuration from a directory.\n *\n * Never throws. A malformed config is reported as a problem and the run\n * continues with defaults: a broken config file should not stop a team seeing\n * the findings it was going to show them anyway.\n */\nexport function loadConfig(root: string, read: (path: string) => string = defaultRead): LoadedConfig {\n for (const name of CONFIG_FILES) {\n const raw = tryRead(read, `${root}/${name}`);\n if (raw === null) continue;\n return parseConfig(raw, name);\n }\n\n const pkg = tryRead(read, `${root}/package.json`);\n if (pkg === null) return EMPTY;\n let parsed: unknown;\n try {\n parsed = JSON.parse(pkg);\n } catch {\n return EMPTY;\n }\n if (!isRecord(parsed)) return EMPTY;\n const section = parsed[CONFIG_PACKAGE_KEY];\n if (section === undefined) return EMPTY;\n if (!isRecord(section)) {\n return { config: {}, source: `package.json`, problems: [`\"${CONFIG_PACKAGE_KEY}\" must be an object`] };\n }\n return readFields(section, `package.json#${CONFIG_PACKAGE_KEY}`);\n}\n\n/** Parses config text. Exported so the shape can be tested without a disk. */\nexport function parseConfig(raw: string, source: string): LoadedConfig {\n let parsed: unknown;\n try {\n parsed = JSON.parse(withoutBom(raw));\n } catch (error) {\n return { config: {}, source, problems: [`${source} is not valid JSON: ${(error as Error).message}`] };\n }\n if (!isRecord(parsed)) return { config: {}, source, problems: [`${source} must contain a JSON object`] };\n return readFields(parsed, source);\n}\n\nfunction readFields(raw: Record<string, unknown>, source: string): LoadedConfig {\n const problems: string[] = [];\n const config: {\n -readonly [K in keyof SpecGraphConfig]: SpecGraphConfig[K];\n } = {};\n\n const strings = (key: keyof SpecGraphConfig): readonly string[] | undefined => {\n const value = raw[key];\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {\n problems.push(`${source}: \"${key}\" must be an array of strings`);\n return undefined;\n }\n return value as string[];\n };\n\n config.patterns = strings('patterns');\n config.ignore = strings('ignore');\n config.ignoreReferences = strings('ignoreReferences');\n config.families = strings('families');\n config.ignoreFamilies = strings('ignoreFamilies');\n config.historyPatterns = strings('historyPatterns');\n\n if (raw['baseline'] !== undefined) {\n if (typeof raw['baseline'] !== 'string' || raw['baseline'].trim().length === 0) {\n problems.push(`${source}: \"baseline\" must be a path`);\n } else {\n config.baseline = raw['baseline'];\n }\n }\n\n if (raw['strict'] !== undefined) {\n if (typeof raw['strict'] !== 'boolean') problems.push(`${source}: \"strict\" must be true or false`);\n else config.strict = raw['strict'];\n }\n\n if (raw['ratchet'] !== undefined) {\n if (typeof raw['ratchet'] !== 'boolean') problems.push(`${source}: \"ratchet\" must be true or false`);\n else config.ratchet = raw['ratchet'];\n }\n\n if (raw['maxRelated'] !== undefined) {\n const value = raw['maxRelated'];\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n problems.push(`${source}: \"maxRelated\" must be a non-negative whole number`);\n } else {\n config.maxRelated = value;\n }\n }\n\n if (raw['severities'] !== undefined) {\n const value = raw['severities'];\n if (!isRecord(value)) {\n problems.push(`${source}: \"severities\" must be an object of rule to severity`);\n } else {\n const severities: Partial<Record<RuleId, Severity>> = {};\n for (const [rule, level] of Object.entries(value)) {\n if (!RULE_IDS.includes(rule as RuleId)) {\n problems.push(`${source}: unknown rule \"${rule}\" in \"severities\"`);\n continue;\n }\n if (level !== 'error' && level !== 'warn' && level !== 'info' && level !== 'off') {\n problems.push(`${source}: \"severities.${rule}\" must be error, warn, info or off`);\n continue;\n }\n severities[rule as RuleId] = level;\n }\n config.severities = severities;\n }\n }\n\n for (const key of Object.keys(raw)) {\n if (!KNOWN_KEYS.has(key)) problems.push(`${source}: unknown key \"${key}\"`);\n }\n\n return { config, source, problems };\n}\n\nconst KNOWN_KEYS: ReadonlySet<string> = new Set([\n 'patterns',\n 'ignore',\n 'ignoreReferences',\n 'families',\n 'ignoreFamilies',\n 'historyPatterns',\n 'baseline',\n 'ratchet',\n 'severities',\n 'strict',\n 'maxRelated',\n // Tolerated so an editor can be pointed at a schema without being told off.\n '$schema',\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction tryRead(read: (path: string) => string, path: string): string | null {\n try {\n return withoutBom(read(path));\n } catch {\n return null;\n }\n}\n\n/**\n * Drops a leading byte-order mark.\n *\n * `JSON.parse` rejects one, and several Windows editors write one by default -\n * a config that silently stops applying because of an invisible first character\n * is the worst kind of configuration bug.\n */\nfunction withoutBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\nfunction defaultRead(path: string): string {\n return readFileSync(path, 'utf8');\n}\n"]}
|
package/dist/extract.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { type DocumentIdentity } from './identity.js';
|
|
18
18
|
import { type ScannedDocument } from './markdown.js';
|
|
19
|
-
import type { DocumentNode, EdgeKind, EdgeOrigin, ItemNode, ParseProblem, SourceRef } from './types.js';
|
|
19
|
+
import type { DocumentNode, EdgeKind, EdgeOrigin, ItemNode, MisreadKey, ParseProblem, SourceRef } from './types.js';
|
|
20
20
|
/**
|
|
21
21
|
* A relation that has been read but not yet resolved to a target node.
|
|
22
22
|
*
|
|
@@ -50,6 +50,13 @@ export interface ExtractedDocument {
|
|
|
50
50
|
readonly items: readonly ItemNode[];
|
|
51
51
|
readonly references: readonly ReferenceCandidate[];
|
|
52
52
|
readonly problems: readonly ParseProblem[];
|
|
53
|
+
/**
|
|
54
|
+
* Front-matter keys that read as relations and declared none.
|
|
55
|
+
*
|
|
56
|
+
* Kept apart from `problems` because a problem is about the text and this is
|
|
57
|
+
* about the graph: the edge the author wrote down is not in it.
|
|
58
|
+
*/
|
|
59
|
+
readonly misreadKeys: readonly MisreadKey[];
|
|
53
60
|
/** Heading slugs, for resolving `#anchor` references into this document. */
|
|
54
61
|
readonly anchors: ReadonlySet<string>;
|
|
55
62
|
readonly identity: DocumentIdentity;
|
|
@@ -70,6 +77,47 @@ export interface ExtractedDocument {
|
|
|
70
77
|
*/
|
|
71
78
|
readonly containerId: string | null;
|
|
72
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Front-matter keys that declare a relation, and what they mean.
|
|
82
|
+
*
|
|
83
|
+
* Every directional kind is spelled in **both** directions. A register that
|
|
84
|
+
* records `depends-on` but not `depended-on-by` is not a smaller vocabulary, it
|
|
85
|
+
* is a trap: the author writes the inverse, the key means nothing, and the edge
|
|
86
|
+
* they declared is missing from a graph that reports itself as consistent. Which
|
|
87
|
+
* half of a pair a repository writes is a filing convention, and a filing
|
|
88
|
+
* convention is not something a linter gets to have an opinion about.
|
|
89
|
+
*
|
|
90
|
+
* `relates-to` is the exception, and it is not one: the relation is symmetric,
|
|
91
|
+
* so it has no other direction to spell. `contains` is structural and never
|
|
92
|
+
* written by hand. `tests/parsing.test.ts` holds both facts to the fire.
|
|
93
|
+
*/
|
|
94
|
+
export declare const RELATION_KEYS: Readonly<Record<string, {
|
|
95
|
+
kind: EdgeKind;
|
|
96
|
+
inverted: boolean;
|
|
97
|
+
}>>;
|
|
98
|
+
/**
|
|
99
|
+
* True when two folded keys are one edit apart.
|
|
100
|
+
*
|
|
101
|
+
* One edit, not two, and a transposition counts as one: `supercedesby` for
|
|
102
|
+
* `supercededby` is a slip of the finger, `categories` for `dependencies` is a
|
|
103
|
+
* different word. Written out rather than as a distance matrix because the
|
|
104
|
+
* answer is a yes or a no and the bound is one - the general algorithm would be
|
|
105
|
+
* more code, not less.
|
|
106
|
+
*/
|
|
107
|
+
export declare function withinOneEdit(a: string, b: string): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Folds a front-matter key to its lookup form.
|
|
110
|
+
*
|
|
111
|
+
* `depends-on`, `depends_on`, `dependsOn` and `Depends On` are one key written
|
|
112
|
+
* four ways, and which one a repository uses is decided by whatever wrote the
|
|
113
|
+
* front matter first. Dropping every separator collapses them - the YAML reader
|
|
114
|
+
* has already lower-cased the key, which is what makes camel case fold too.
|
|
115
|
+
*
|
|
116
|
+
* Which case this folds to is arbitrary and no test pins it: both sides of every
|
|
117
|
+
* comparison come through here, so upper would work exactly as well. Asserting
|
|
118
|
+
* on it would be asserting on the implementation.
|
|
119
|
+
*/
|
|
120
|
+
export declare function foldRelationKey(key: string): string;
|
|
73
121
|
/** Headings under which a link is bookkeeping rather than a commitment. */
|
|
74
122
|
declare const WEAK_SECTIONS: ReadonlySet<string>;
|
|
75
123
|
/** Headings whose bullets are obligations even without a checkbox. */
|
package/dist/extract.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* broken reference to specification 256.
|
|
16
16
|
*/
|
|
17
17
|
import { attr, attrList, directiveFor, parseDirectives } from './directives.js';
|
|
18
|
-
import { identify, isExternal, normaliseRef, ID_KEYS } from './identity.js';
|
|
18
|
+
import { identify, isDocumentTarget, isExternal, looksLikePath, normaliseRef, parsePrefixedRef, ID_KEYS, } from './identity.js';
|
|
19
19
|
import { isStatusHeading, phaseFromPath, phaseOf, STATUS_KEYS, supersessionTargetsIn } from './lifecycle.js';
|
|
20
20
|
import { scanMarkdown, slugify } from './markdown.js';
|
|
21
21
|
import { findSpecificationRegions, regionAt } from './sections.js';
|
|
@@ -25,34 +25,176 @@ import { parseFrontMatter, toRecord, valuesOf } from './yaml.js';
|
|
|
25
25
|
/* -------------------------------------------------------------------------- */
|
|
26
26
|
/* Vocabulary */
|
|
27
27
|
/* -------------------------------------------------------------------------- */
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Front-matter keys that declare a relation, and what they mean.
|
|
30
|
+
*
|
|
31
|
+
* Every directional kind is spelled in **both** directions. A register that
|
|
32
|
+
* records `depends-on` but not `depended-on-by` is not a smaller vocabulary, it
|
|
33
|
+
* is a trap: the author writes the inverse, the key means nothing, and the edge
|
|
34
|
+
* they declared is missing from a graph that reports itself as consistent. Which
|
|
35
|
+
* half of a pair a repository writes is a filing convention, and a filing
|
|
36
|
+
* convention is not something a linter gets to have an opinion about.
|
|
37
|
+
*
|
|
38
|
+
* `relates-to` is the exception, and it is not one: the relation is symmetric,
|
|
39
|
+
* so it has no other direction to spell. `contains` is structural and never
|
|
40
|
+
* written by hand. `tests/parsing.test.ts` holds both facts to the fire.
|
|
41
|
+
*/
|
|
42
|
+
export const RELATION_KEYS = {
|
|
30
43
|
supersedes: { kind: 'supersedes', inverted: false },
|
|
31
44
|
supercedes: { kind: 'supersedes', inverted: false },
|
|
32
45
|
replaces: { kind: 'supersedes', inverted: false },
|
|
33
46
|
obsoletes: { kind: 'supersedes', inverted: false },
|
|
47
|
+
deprecates: { kind: 'supersedes', inverted: false },
|
|
34
48
|
'superseded-by': { kind: 'supersedes', inverted: true },
|
|
35
49
|
'superceded-by': { kind: 'supersedes', inverted: true },
|
|
36
50
|
'replaced-by': { kind: 'supersedes', inverted: true },
|
|
37
51
|
'obsoleted-by': { kind: 'supersedes', inverted: true },
|
|
52
|
+
'deprecated-by': { kind: 'supersedes', inverted: true },
|
|
53
|
+
'rolled-into': { kind: 'supersedes', inverted: true },
|
|
38
54
|
amends: { kind: 'amends', inverted: false },
|
|
39
55
|
extends: { kind: 'amends', inverted: false },
|
|
56
|
+
refines: { kind: 'amends', inverted: false },
|
|
57
|
+
clarifies: { kind: 'amends', inverted: false },
|
|
58
|
+
revises: { kind: 'amends', inverted: false },
|
|
40
59
|
'amended-by': { kind: 'amends', inverted: true },
|
|
60
|
+
'extended-by': { kind: 'amends', inverted: true },
|
|
61
|
+
'refined-by': { kind: 'amends', inverted: true },
|
|
62
|
+
'clarified-by': { kind: 'amends', inverted: true },
|
|
63
|
+
'revised-by': { kind: 'amends', inverted: true },
|
|
41
64
|
'depends-on': { kind: 'depends-on', inverted: false },
|
|
65
|
+
'dependent-on': { kind: 'depends-on', inverted: false },
|
|
42
66
|
dependencies: { kind: 'depends-on', inverted: false },
|
|
43
67
|
requires: { kind: 'depends-on', inverted: false },
|
|
68
|
+
'builds-on': { kind: 'depends-on', inverted: false },
|
|
69
|
+
'relies-on': { kind: 'depends-on', inverted: false },
|
|
70
|
+
'depended-on-by': { kind: 'depends-on', inverted: true },
|
|
71
|
+
'required-by': { kind: 'depends-on', inverted: true },
|
|
72
|
+
dependents: { kind: 'depends-on', inverted: true },
|
|
44
73
|
assumes: { kind: 'assumes', inverted: false },
|
|
74
|
+
'assumed-by': { kind: 'assumes', inverted: true },
|
|
45
75
|
'blocked-by': { kind: 'blocked-by', inverted: false },
|
|
76
|
+
'blocked-on': { kind: 'blocked-by', inverted: false },
|
|
77
|
+
'waiting-on': { kind: 'blocked-by', inverted: false },
|
|
78
|
+
'waiting-for': { kind: 'blocked-by', inverted: false },
|
|
79
|
+
'gated-on': { kind: 'blocked-by', inverted: false },
|
|
80
|
+
'gated-by': { kind: 'blocked-by', inverted: false },
|
|
46
81
|
blocks: { kind: 'blocked-by', inverted: true },
|
|
47
82
|
'delegates-to': { kind: 'delegates-to', inverted: false },
|
|
48
83
|
'delegated-to': { kind: 'delegates-to', inverted: false },
|
|
84
|
+
'deferred-to': { kind: 'delegates-to', inverted: false },
|
|
49
85
|
'tracked-in': { kind: 'delegates-to', inverted: false },
|
|
86
|
+
'tracked-by': { kind: 'delegates-to', inverted: false },
|
|
87
|
+
'continued-in': { kind: 'delegates-to', inverted: false },
|
|
88
|
+
'delegated-from': { kind: 'delegates-to', inverted: true },
|
|
50
89
|
related: { kind: 'relates-to', inverted: false },
|
|
51
90
|
'relates-to': { kind: 'relates-to', inverted: false },
|
|
91
|
+
'related-to': { kind: 'relates-to', inverted: false },
|
|
52
92
|
'see-also': { kind: 'relates-to', inverted: false },
|
|
53
93
|
references: { kind: 'references', inverted: false },
|
|
94
|
+
reference: { kind: 'references', inverted: false },
|
|
54
95
|
refs: { kind: 'references', inverted: false },
|
|
96
|
+
'referenced-by': { kind: 'references', inverted: true },
|
|
55
97
|
};
|
|
98
|
+
/**
|
|
99
|
+
* True when two folded keys are one edit apart.
|
|
100
|
+
*
|
|
101
|
+
* One edit, not two, and a transposition counts as one: `supercedesby` for
|
|
102
|
+
* `supercededby` is a slip of the finger, `categories` for `dependencies` is a
|
|
103
|
+
* different word. Written out rather than as a distance matrix because the
|
|
104
|
+
* answer is a yes or a no and the bound is one - the general algorithm would be
|
|
105
|
+
* more code, not less.
|
|
106
|
+
*/
|
|
107
|
+
export function withinOneEdit(a, b) {
|
|
108
|
+
if (a === b)
|
|
109
|
+
return false;
|
|
110
|
+
if (a.length === b.length) {
|
|
111
|
+
let first = -1;
|
|
112
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
113
|
+
if (a[i] === b[i])
|
|
114
|
+
continue;
|
|
115
|
+
if (first === -1) {
|
|
116
|
+
first = i;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// A second difference is allowed only if the two are a swapped pair, and
|
|
120
|
+
// only if there is no third.
|
|
121
|
+
return first === i - 1 && a[first] === b[i] && a[i] === b[first] && a.slice(i + 1) === b.slice(i + 1);
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const [short, long] = a.length < b.length ? [a, b] : [b, a];
|
|
126
|
+
if (long.length - short.length !== 1)
|
|
127
|
+
return false;
|
|
128
|
+
for (let i = 0; i < short.length; i += 1) {
|
|
129
|
+
if (short[i] === long[i])
|
|
130
|
+
continue;
|
|
131
|
+
return short.slice(i) === long.slice(i + 1);
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* True when a front-matter value is shaped like a citation.
|
|
137
|
+
*
|
|
138
|
+
* The second half of the gate on {@link misreadRelationKeys}, and the half that
|
|
139
|
+
* does the work. `sidebar_position: 4` and `ref: main` are a hair away from a
|
|
140
|
+
* relation key by spelling alone; neither carries anything that could name a
|
|
141
|
+
* document, and reporting them would make the rule an irritation rather than a
|
|
142
|
+
* catch (ADR-0006).
|
|
143
|
+
*/
|
|
144
|
+
function looksLikeCitation(value) {
|
|
145
|
+
const trimmed = value.trim();
|
|
146
|
+
if (trimmed.length === 0)
|
|
147
|
+
return false;
|
|
148
|
+
if (parsePrefixedRef(trimmed) !== null)
|
|
149
|
+
return true;
|
|
150
|
+
return looksLikePath(trimmed) && isDocumentTarget(trimmed) && !isExternal(trimmed);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Front-matter keys that read as relations and declare none.
|
|
154
|
+
*
|
|
155
|
+
* Front matter is an open vocabulary and most of what lives there is nobody
|
|
156
|
+
* else's business, so the bar is deliberately high: the key has to be one edit
|
|
157
|
+
* from a relation key *and* carry something that could name a document. Both
|
|
158
|
+
* conditions together are what separates `dependson: ADR-0001`, which is an edge
|
|
159
|
+
* the author believes exists, from `description: ...`, which is a title.
|
|
160
|
+
*/
|
|
161
|
+
function misreadRelationKeys(byKey, from, at) {
|
|
162
|
+
const out = [];
|
|
163
|
+
for (const [key, entry] of byKey) {
|
|
164
|
+
const folded = foldRelationKey(key);
|
|
165
|
+
if (folded.length === 0 || RELATION_INDEX.has(folded))
|
|
166
|
+
continue;
|
|
167
|
+
if (!valuesOf(entry).some(looksLikeCitation))
|
|
168
|
+
continue;
|
|
169
|
+
const near = [...RELATION_INDEX.values()]
|
|
170
|
+
.filter((relation) => withinOneEdit(folded, foldRelationKey(relation.canonical)))
|
|
171
|
+
.map((relation) => relation.canonical);
|
|
172
|
+
if (near.length === 0)
|
|
173
|
+
continue;
|
|
174
|
+
out.push({ key, suggestion: near.join(', '), from, at: at(entry.start, entry.end) });
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Folds a front-matter key to its lookup form.
|
|
180
|
+
*
|
|
181
|
+
* `depends-on`, `depends_on`, `dependsOn` and `Depends On` are one key written
|
|
182
|
+
* four ways, and which one a repository uses is decided by whatever wrote the
|
|
183
|
+
* front matter first. Dropping every separator collapses them - the YAML reader
|
|
184
|
+
* has already lower-cased the key, which is what makes camel case fold too.
|
|
185
|
+
*
|
|
186
|
+
* Which case this folds to is arbitrary and no test pins it: both sides of every
|
|
187
|
+
* comparison come through here, so upper would work exactly as well. Asserting
|
|
188
|
+
* on it would be asserting on the implementation.
|
|
189
|
+
*/
|
|
190
|
+
export function foldRelationKey(key) {
|
|
191
|
+
return key.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
192
|
+
}
|
|
193
|
+
/** The vocabulary above, indexed by folded key. */
|
|
194
|
+
const RELATION_INDEX = new Map(Object.entries(RELATION_KEYS).map(([canonical, relation]) => [
|
|
195
|
+
foldRelationKey(canonical),
|
|
196
|
+
{ ...relation, canonical },
|
|
197
|
+
]));
|
|
56
198
|
/**
|
|
57
199
|
* Phrases that give a link its meaning, longest-matching-nearest wins.
|
|
58
200
|
*
|
|
@@ -432,6 +574,7 @@ export function extractDocument(input) {
|
|
|
432
574
|
// Hand each region the items and references that fall inside it.
|
|
433
575
|
const ownItems = allItems.filter((item) => item.document === identity.id);
|
|
434
576
|
const ownReferences = allReferences.filter((reference) => belongsTo(reference, identity.id, allItems));
|
|
577
|
+
const misreadKeys = misreadRelationKeys(byKey, identity.id, at);
|
|
435
578
|
const anchors = new Set(scanned.headings.map((h) => h.slug));
|
|
436
579
|
const filled = subSpecifications.map((spec) => ({
|
|
437
580
|
...spec.extracted,
|
|
@@ -448,6 +591,7 @@ export function extractDocument(input) {
|
|
|
448
591
|
items: ownItems,
|
|
449
592
|
references: ownReferences,
|
|
450
593
|
problems,
|
|
594
|
+
misreadKeys,
|
|
451
595
|
anchors,
|
|
452
596
|
identity,
|
|
453
597
|
scanned,
|
|
@@ -526,6 +670,8 @@ function buildRegion(context) {
|
|
|
526
670
|
items: [],
|
|
527
671
|
references,
|
|
528
672
|
problems: [],
|
|
673
|
+
// A region has no front matter of its own to misread.
|
|
674
|
+
misreadKeys: [],
|
|
529
675
|
anchors,
|
|
530
676
|
identity,
|
|
531
677
|
scanned,
|
|
@@ -717,10 +863,12 @@ function extractReferences(context) {
|
|
|
717
863
|
const { scanned, directives, document, documentAt, items, byKey, status, claimed, file, index } = context;
|
|
718
864
|
const out = [];
|
|
719
865
|
const at = (start, end) => refOf(file, index, start, end);
|
|
720
|
-
// Front matter
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
866
|
+
// Front matter, walked in the order it was written rather than in the order
|
|
867
|
+
// the vocabulary happens to be listed: a key the table does not know still has
|
|
868
|
+
// to be looked at, and only the document knows which those are.
|
|
869
|
+
for (const [key, entry] of byKey) {
|
|
870
|
+
const relation = RELATION_INDEX.get(foldRelationKey(key));
|
|
871
|
+
if (!relation)
|
|
724
872
|
continue;
|
|
725
873
|
for (const value of valuesOf(entry)) {
|
|
726
874
|
const target = cleanTarget(value);
|
|
@@ -1081,7 +1229,7 @@ function renderLink(link) {
|
|
|
1081
1229
|
return `<${link.target}>`;
|
|
1082
1230
|
case 'reference':
|
|
1083
1231
|
case 'shortcut':
|
|
1084
|
-
return `[${link.text}][${link.
|
|
1232
|
+
return `[${link.text}][${link.label ?? ''}]`;
|
|
1085
1233
|
default:
|
|
1086
1234
|
return `[${link.text}](${link.target})`;
|
|
1087
1235
|
}
|