@descent-vtt/spec-graph 0.9.1 → 0.9.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +162 -15
  3. package/dist/baseline.js +6 -0
  4. package/dist/baseline.js.map +1 -1
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +50 -17
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts +3 -3
  9. package/dist/config.js +16 -8
  10. package/dist/config.js.map +1 -1
  11. package/dist/diff.js +14 -0
  12. package/dist/diff.js.map +1 -1
  13. package/dist/directives.js +9 -6
  14. package/dist/directives.js.map +1 -1
  15. package/dist/extract.js +120 -57
  16. package/dist/extract.js.map +1 -1
  17. package/dist/glob.js +28 -16
  18. package/dist/glob.js.map +1 -1
  19. package/dist/graph.d.ts +2 -1
  20. package/dist/graph.js +10 -1
  21. package/dist/graph.js.map +1 -1
  22. package/dist/identity.d.ts +6 -0
  23. package/dist/identity.js +68 -68
  24. package/dist/identity.js.map +1 -1
  25. package/dist/lifecycle.js +17 -11
  26. package/dist/lifecycle.js.map +1 -1
  27. package/dist/report.js +34 -5
  28. package/dist/report.js.map +1 -1
  29. package/dist/resolve.js +40 -14
  30. package/dist/resolve.js.map +1 -1
  31. package/dist/rules.d.ts +8 -0
  32. package/dist/rules.js +83 -16
  33. package/dist/rules.js.map +1 -1
  34. package/dist/runner.js +22 -4
  35. package/dist/runner.js.map +1 -1
  36. package/dist/sections.d.ts +9 -0
  37. package/dist/sections.js +60 -9
  38. package/dist/sections.js.map +1 -1
  39. package/dist/select.d.ts +18 -2
  40. package/dist/select.js +46 -16
  41. package/dist/select.js.map +1 -1
  42. package/dist/source.js +2 -0
  43. package/dist/source.js.map +1 -1
  44. package/dist/state.js +46 -12
  45. package/dist/state.js.map +1 -1
  46. package/dist/yaml.js +11 -3
  47. package/dist/yaml.js.map +1 -1
  48. package/package.json +6 -3
  49. package/src/vendor/spec-core/README.md +0 -8
package/dist/config.js CHANGED
@@ -12,9 +12,9 @@
12
12
  * and a YAML or TOML reader would be the largest thing in the package, to read
13
13
  * a file with nine keys in it.
14
14
  *
15
- * Every key mirrors a flag, and a flag always wins. Configuration is where a
16
- * team writes down what is true of the repository; a flag is where somebody
17
- * overrides it for one run.
15
+ * Every key but `rules` and `maxRelated` mirrors a flag, and a flag always
16
+ * wins. Configuration is where a team writes down what is true of the
17
+ * repository; a flag is where somebody overrides it for one run.
18
18
  */
19
19
  import { existsSync, readFileSync } from 'node:fs';
20
20
  import { dirnamePosix, toPosix } from './paths.js';
@@ -43,6 +43,9 @@ export function loadConfig(root, read = defaultRead) {
43
43
  continue;
44
44
  return parseConfig(raw, name);
45
45
  }
46
+ // The first two returns are the third one's answer given early: no file is
47
+ // `JSON.parse(null)`, which is `null`, and a parse that failed leaves
48
+ // `parsed` undefined - neither is a record. Their mutants are equivalent.
46
49
  const pkg = tryRead(read, `${root}/package.json`);
47
50
  if (pkg === null)
48
51
  return EMPTY;
@@ -91,10 +94,12 @@ export function discoverConfig(from, io = {}) {
91
94
  if (exists(`${directory}/.git`))
92
95
  break;
93
96
  const parent = dirnamePosix(directory);
94
- // Two guards for one job, and each makes the other unreachable: a path with
95
- // no separator left in it gives back `''`, and `''` gives back itself. Only
96
- // one of them can ever fire, which is why neither has a test - removing
97
- // either leaves the loop terminating on the other.
97
+ // A path with no separator left in it gives back `''`, and stopping there is
98
+ // what keeps a relative start from going on to read `/.spec-graph.json`,
99
+ // which is what a file name joined to `''` spells. The second guard is true
100
+ // only when the directory is `''` already, and then so is the first: its
101
+ // mutant is equivalent. It stays so the loop still ends if the parent of a
102
+ // path is ever spelled as Node's `dirname` spells it, `.` for `.`.
98
103
  if (parent === '' || parent === directory)
99
104
  break;
100
105
  directory = parent;
@@ -155,6 +160,8 @@ function readFields(raw, source) {
155
160
  }
156
161
  if (raw['maxRelated'] !== undefined) {
157
162
  const value = raw['maxRelated'];
163
+ // `Number.isInteger` is false for anything but a number, so the `typeof`
164
+ // only narrows the type for `value < 0`, and its mutant is equivalent.
158
165
  if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
159
166
  problems.push(`${source}: "maxRelated" must be a non-negative whole number`);
160
167
  }
@@ -193,7 +200,8 @@ function readFields(raw, source) {
193
200
  }
194
201
  // A severity naming a project rule that nothing defines is a line that will
195
202
  // never do anything, and reading like it does is the one thing a
196
- // configuration file must not be allowed to do.
203
+ // configuration file must not be allowed to do. (A fallback seeded with
204
+ // something that is not a rule defines no id, so that mutant is equivalent.)
197
205
  const defined = new Set((compiled?.rules ?? []).map((rule) => rule.id));
198
206
  for (const rule of Object.keys(config.severities ?? {})) {
199
207
  if (!isProjectRule(rule) || defined.has(rule))
@@ -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 { existsSync, readFileSync } from 'node:fs';\n\nimport { dirnamePosix, toPosix } from './paths.js';\nimport { compileProjectRules, type ProjectRule } from './project-rules.js';\nimport { RULE_IDS } from './rules.js';\nimport { isProjectRule, type AnyRuleId, type RuleId, type 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<AnyRuleId, Severity>> | undefined;\n readonly strict?: boolean | undefined;\n readonly maxRelated?: number | undefined;\n /**\n * Conventions this repository checks that spec-graph does not.\n *\n * A selector, a sentence and a severity. Compiled here rather than carried as\n * raw JSON, so a rule that cannot run is reported at load time next to every\n * other configuration problem - and not as a rule that quietly finds nothing.\n * See ADR-0016.\n */\n readonly rules?: readonly ProjectRule[] | 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\nexport interface DiscoveredConfig extends LoadedConfig {\n /**\n * The directory the configuration was found in.\n *\n * This is the root of the run. Every path in spec-graph is relative to it -\n * node identities, baseline keys, SARIF locations - so the configuration file\n * naming the repository is the same statement as the repository having a\n * root. See ADR-0018.\n */\n readonly root: string;\n}\n\n/** Injected so discovery can be tested without a directory tree. */\nexport interface DiscoveryIO {\n readonly read?: ((path: string) => string) | undefined;\n readonly exists?: ((path: string) => boolean) | undefined;\n}\n\nconst EMPTY: LoadedConfig = { config: {}, source: null, problems: [] };\n\n/**\n * Reads configuration from a directory.\n *\n * Never throws, and never decides. Every problem is collected and handed back\n * whole, so the reader sees all of them at once rather than the first; what a\n * problem costs is the caller's to say, and the CLI stops on one.\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: [`package.json: \"${CONFIG_PACKAGE_KEY}\" must be an object`] };\n }\n return readFields(section, `package.json#${CONFIG_PACKAGE_KEY}`);\n}\n\n/**\n * Finds the configuration by walking up from a directory.\n *\n * A monorepo is invoked from inside a package - `packages/auth`, or\n * `docs/architecture` - and reading configuration from the working directory\n * meant that the same repository checked differently depending on where\n * somebody stood in it. ADR-0010 worried that discovery would make a run\n * depend on where it started. It does the opposite: because the directory\n * holding the configuration becomes the root, a run from anywhere inside the\n * repository produces byte-identical output to a run from the top.\n *\n * The walk stops at the repository, which is a directory holding `.git`.\n * Above that is somebody else's checkout or a home directory, and a run that\n * silently picked up a configuration file nobody in the repository can see\n * would be worse than no discovery at all. See ADR-0018.\n */\nexport function discoverConfig(from: string, io: DiscoveryIO = {}): DiscoveredConfig {\n const read = io.read ?? defaultRead;\n const exists = io.exists ?? existsSync;\n const start = toPosix(from).replace(/\\/+$/, '');\n\n let directory = start;\n for (;;) {\n const loaded = loadConfig(directory, read);\n if (loaded.source !== null) return { ...loaded, root: directory };\n if (exists(`${directory}/.git`)) break;\n const parent = dirnamePosix(directory);\n // Two guards for one job, and each makes the other unreachable: a path with\n // no separator left in it gives back `''`, and `''` gives back itself. Only\n // one of them can ever fire, which is why neither has a test - removing\n // either leaves the loop terminating on the other.\n if (parent === '' || parent === directory) break;\n directory = parent;\n }\n return { ...EMPTY, root: start };\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<AnyRuleId, Severity>> = {};\n for (const [rule, level] of Object.entries(value)) {\n // A project rule is named here by its full id, `project:no-drafts`, the\n // same spelling `--rule` takes and a report prints. Whether one exists\n // is checked below, once both halves of the file have been read.\n if (!RULE_IDS.includes(rule as RuleId) && !isProjectRule(rule as AnyRuleId)) {\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 AnyRuleId] = level;\n }\n config.severities = severities;\n }\n }\n\n const compiled = raw['rules'] === undefined ? null : compileProjectRules(raw['rules'], source);\n if (compiled !== null) {\n problems.push(...compiled.problems);\n config.rules = compiled.rules;\n }\n\n // A severity naming a project rule that nothing defines is a line that will\n // never do anything, and reading like it does is the one thing a\n // configuration file must not be allowed to do.\n const defined = new Set<string>((compiled?.rules ?? []).map((rule) => rule.id));\n for (const rule of Object.keys(config.severities ?? {})) {\n if (!isProjectRule(rule as AnyRuleId) || defined.has(rule)) continue;\n problems.push(`${source}: \"severities.${rule}\" names no rule in \"rules\"`);\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 'rules',\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 but `rules` and `maxRelated` mirrors a flag, and a flag always\n * wins. Configuration is where a team writes down what is true of the\n * repository; a flag is where somebody overrides it for one run.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\nimport { dirnamePosix, toPosix } from './paths.js';\nimport { compileProjectRules, type ProjectRule } from './project-rules.js';\nimport { RULE_IDS } from './rules.js';\nimport { isProjectRule, type AnyRuleId, type RuleId, type 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<AnyRuleId, Severity>> | undefined;\n readonly strict?: boolean | undefined;\n readonly maxRelated?: number | undefined;\n /**\n * Conventions this repository checks that spec-graph does not.\n *\n * A selector, a sentence and a severity. Compiled here rather than carried as\n * raw JSON, so a rule that cannot run is reported at load time next to every\n * other configuration problem - and not as a rule that quietly finds nothing.\n * See ADR-0016.\n */\n readonly rules?: readonly ProjectRule[] | 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\nexport interface DiscoveredConfig extends LoadedConfig {\n /**\n * The directory the configuration was found in.\n *\n * This is the root of the run. Every path in spec-graph is relative to it -\n * node identities, baseline keys, SARIF locations - so the configuration file\n * naming the repository is the same statement as the repository having a\n * root. See ADR-0018.\n */\n readonly root: string;\n}\n\n/** Injected so discovery can be tested without a directory tree. */\nexport interface DiscoveryIO {\n readonly read?: ((path: string) => string) | undefined;\n readonly exists?: ((path: string) => boolean) | undefined;\n}\n\nconst EMPTY: LoadedConfig = { config: {}, source: null, problems: [] };\n\n/**\n * Reads configuration from a directory.\n *\n * Never throws, and never decides. Every problem is collected and handed back\n * whole, so the reader sees all of them at once rather than the first; what a\n * problem costs is the caller's to say, and the CLI stops on one.\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 // The first two returns are the third one's answer given early: no file is\n // `JSON.parse(null)`, which is `null`, and a parse that failed leaves\n // `parsed` undefined - neither is a record. Their mutants are equivalent.\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: [`package.json: \"${CONFIG_PACKAGE_KEY}\" must be an object`] };\n }\n return readFields(section, `package.json#${CONFIG_PACKAGE_KEY}`);\n}\n\n/**\n * Finds the configuration by walking up from a directory.\n *\n * A monorepo is invoked from inside a package - `packages/auth`, or\n * `docs/architecture` - and reading configuration from the working directory\n * meant that the same repository checked differently depending on where\n * somebody stood in it. ADR-0010 worried that discovery would make a run\n * depend on where it started. It does the opposite: because the directory\n * holding the configuration becomes the root, a run from anywhere inside the\n * repository produces byte-identical output to a run from the top.\n *\n * The walk stops at the repository, which is a directory holding `.git`.\n * Above that is somebody else's checkout or a home directory, and a run that\n * silently picked up a configuration file nobody in the repository can see\n * would be worse than no discovery at all. See ADR-0018.\n */\nexport function discoverConfig(from: string, io: DiscoveryIO = {}): DiscoveredConfig {\n const read = io.read ?? defaultRead;\n const exists = io.exists ?? existsSync;\n const start = toPosix(from).replace(/\\/+$/, '');\n\n let directory = start;\n for (;;) {\n const loaded = loadConfig(directory, read);\n if (loaded.source !== null) return { ...loaded, root: directory };\n if (exists(`${directory}/.git`)) break;\n const parent = dirnamePosix(directory);\n // A path with no separator left in it gives back `''`, and stopping there is\n // what keeps a relative start from going on to read `/.spec-graph.json`,\n // which is what a file name joined to `''` spells. The second guard is true\n // only when the directory is `''` already, and then so is the first: its\n // mutant is equivalent. It stays so the loop still ends if the parent of a\n // path is ever spelled as Node's `dirname` spells it, `.` for `.`.\n if (parent === '' || parent === directory) break;\n directory = parent;\n }\n return { ...EMPTY, root: start };\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 // `Number.isInteger` is false for anything but a number, so the `typeof`\n // only narrows the type for `value < 0`, and its mutant is equivalent.\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<AnyRuleId, Severity>> = {};\n for (const [rule, level] of Object.entries(value)) {\n // A project rule is named here by its full id, `project:no-drafts`, the\n // same spelling `--rule` takes and a report prints. Whether one exists\n // is checked below, once both halves of the file have been read.\n if (!RULE_IDS.includes(rule as RuleId) && !isProjectRule(rule as AnyRuleId)) {\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 AnyRuleId] = level;\n }\n config.severities = severities;\n }\n }\n\n const compiled = raw['rules'] === undefined ? null : compileProjectRules(raw['rules'], source);\n if (compiled !== null) {\n problems.push(...compiled.problems);\n config.rules = compiled.rules;\n }\n\n // A severity naming a project rule that nothing defines is a line that will\n // never do anything, and reading like it does is the one thing a\n // configuration file must not be allowed to do. (A fallback seeded with\n // something that is not a rule defines no id, so that mutant is equivalent.)\n const defined = new Set<string>((compiled?.rules ?? []).map((rule) => rule.id));\n for (const rule of Object.keys(config.severities ?? {})) {\n if (!isProjectRule(rule as AnyRuleId) || defined.has(rule)) continue;\n problems.push(`${source}: \"severities.${rule}\" names no rule in \"rules\"`);\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 'rules',\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/diff.js CHANGED
@@ -17,12 +17,19 @@
17
17
  export class DiffInputError extends Error {
18
18
  constructor(message) {
19
19
  super(message);
20
+ // For a stack trace, which nothing prints: the command catches every one
21
+ // of these and prints its message, and the library does not export the
22
+ // class. A mutant that blanks the name is equivalent.
20
23
  this.name = 'DiffInputError';
21
24
  }
22
25
  }
23
26
  /* -------------------------------------------------------------------------- */
24
27
  /* Reading an export */
25
28
  /* -------------------------------------------------------------------------- */
29
+ // Every caller goes on to read a field and check it with `text` or
30
+ // `Array.isArray`, and a number, string or boolean yields `undefined` for every
31
+ // field read here, which both refuse. So the `typeof` decides nothing a caller
32
+ // can see, and its mutant is equivalent.
26
33
  function isRecord(value) {
27
34
  return typeof value === 'object' && value !== null && !Array.isArray(value);
28
35
  }
@@ -175,6 +182,10 @@ function compareRelations(before, after) {
175
182
  function byPlace(a, b) {
176
183
  const left = [a.document, ...a.section];
177
184
  const right = [b.document, ...b.section];
185
+ // Past the end of the shorter path `byText` meets `undefined`, which is
186
+ // neither less nor greater than anything, and answers 0: the mutants that
187
+ // run the loop on decide nothing the lengths below do not, and are
188
+ // equivalent.
178
189
  for (let at = 0; at < left.length && at < right.length; at++) {
179
190
  const order = byText(left[at], right[at]);
180
191
  if (order !== 0)
@@ -194,6 +205,9 @@ function headingsIn(graph) {
194
205
  function compareObligations(before, after) {
195
206
  const headingsBefore = headingsIn(before);
196
207
  const headingsAfter = headingsIn(after);
208
+ // Seeded with a string, as a mutant seeds it, this holds a "pair" of two
209
+ // characters with no openness on either side, which the transition filter
210
+ // drops: that mutant is equivalent.
197
211
  const pairs = [];
198
212
  const paired = new Set();
199
213
  // A declared id names the same item in both states however it was reworded or
package/dist/diff.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What changed between two states of the graph, read from two JSON exports.\n *\n * Two files in, one report out, and no git underneath: CI already has both\n * checkouts, and the pipeline stays a pure function of text. See ADR-0020.\n *\n * The rule the whole module follows is that a change is reported only against a\n * name that means the same thing in both exports. A document's id does. An\n * item's numbered id does not - it is an ordinal within a section, so one\n * inserted question renumbers every question after it - and keying on it\n * reports a question as reopened when nobody touched it. So an obligation is\n * paired only by a declared id, or by its document, section and title when\n * nothing else shares them; anything unpaired is said to have appeared or\n * disappeared, and never to have changed.\n */\n\n/** Refused input: not JSON, not an export, or a version this cannot read. */\nexport class DiffInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DiffInputError';\n }\n}\n\nexport interface Generator {\n readonly name: string;\n readonly version: string;\n}\n\nexport interface ExportedDocument {\n readonly id: string;\n readonly title: string;\n readonly path: string;\n readonly phase: string;\n readonly status: string | null;\n}\n\nexport interface ExportedItem {\n readonly id: string;\n readonly title: string;\n readonly document: string;\n readonly section: readonly string[];\n readonly openness: string;\n /** `null` when the export predates the flag, so its ids cannot be trusted. */\n readonly declared: boolean | null;\n}\n\nexport interface ExportedRelation {\n readonly kind: string;\n readonly from: string;\n readonly to: string;\n}\n\n/** One side of a diff: a `--graph-format json` export, read and checked. */\nexport interface GraphExport {\n readonly generator: Generator | null;\n readonly documents: readonly ExportedDocument[];\n readonly items: readonly ExportedItem[];\n readonly edges: readonly ExportedRelation[];\n}\n\nexport interface DocumentChange {\n readonly id: string;\n readonly path?: readonly [string, string] | undefined;\n readonly title?: readonly [string, string] | undefined;\n readonly phase?: readonly [string, string] | undefined;\n readonly status?: readonly [string | null, string | null] | undefined;\n}\n\nexport interface ObligationRef {\n readonly document: string;\n readonly section: readonly string[];\n /**\n * The section path as a reader wants it: without the document's own title\n * heading, which every item under it shares and the id already names. Items\n * are matched on the same headings, kept as a list so that no heading holding\n * \" > \" can pass for two.\n */\n readonly place: string;\n readonly title: string;\n}\n\n/** An item with no pair: its state on the one side it exists on, and no more. */\nexport interface UnpairedObligation extends ObligationRef {\n readonly openness: string;\n}\n\nexport interface Transition extends ObligationRef {\n readonly openness: readonly [string, string];\n}\n\nexport interface GraphDiff {\n readonly generators: { readonly before: Generator | null; readonly after: Generator | null };\n readonly documents: {\n readonly added: readonly ExportedDocument[];\n readonly removed: readonly ExportedDocument[];\n readonly changed: readonly DocumentChange[];\n };\n readonly relations: { readonly added: readonly ExportedRelation[]; readonly removed: readonly ExportedRelation[] };\n readonly obligations: {\n readonly transitions: readonly Transition[];\n readonly appeared: readonly UnpairedObligation[];\n readonly disappeared: readonly UnpairedObligation[];\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reading an export */\n/* -------------------------------------------------------------------------- */\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown): string | null {\n return typeof value === 'string' ? value : null;\n}\n\n/**\n * Reads one export.\n *\n * Checked as far as the comparison reads it and no further: a field this does\n * not use is not a reason to refuse a file, and a later export that adds fields\n * is still version 1.\n */\nexport function parseGraphExport(raw: string, source: string): GraphExport {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new DiffInputError(`${source} is not JSON; make it with \\`spec-graph graph --graph-format json\\``);\n }\n if (!isRecord(parsed) || !Array.isArray(parsed['nodes']) || !Array.isArray(parsed['edges'])) {\n throw new DiffInputError(`${source} is not a graph export; make it with \\`spec-graph graph --graph-format json\\``);\n }\n if (parsed['version'] !== 1) {\n throw new DiffInputError(`${source} is export version ${JSON.stringify(parsed['version'])}, and this reads version 1`);\n }\n\n const generator = isRecord(parsed['generator']) ? parsed['generator'] : null;\n const name = generator ? text(generator['name']) : null;\n const version = generator ? text(generator['version']) : null;\n\n const documents: ExportedDocument[] = [];\n const items: ExportedItem[] = [];\n parsed['nodes'].forEach((node: unknown, index) => {\n const id = isRecord(node) ? text(node['id']) : null;\n if (!isRecord(node) || id === null) throw new DiffInputError(`${source}: node ${index} has no id`);\n if (node['kind'] === 'document') {\n documents.push({\n id,\n title: text(node['title']) ?? '',\n path: text(node['path']) ?? '',\n phase: text(node['phase']) ?? 'unknown',\n status: text(node['status']),\n });\n } else if (node['kind'] === 'item') {\n const section = Array.isArray(node['section']) ? node['section'].filter((part): part is string => typeof part === 'string') : [];\n items.push({\n id,\n title: text(node['title']) ?? '',\n document: text(node['document']) ?? '',\n section,\n openness: text(node['openness']) ?? 'unknown',\n declared: typeof node['declared'] === 'boolean' ? node['declared'] : null,\n });\n } else {\n throw new DiffInputError(`${source}: node ${id} is neither a document nor an item`);\n }\n });\n\n const edges: ExportedRelation[] = parsed['edges'].map((edge: unknown, index) => {\n const kind = isRecord(edge) ? text(edge['kind']) : null;\n const from = isRecord(edge) ? text(edge['from']) : null;\n const to = isRecord(edge) ? text(edge['to']) : null;\n if (kind === null || from === null || to === null) throw new DiffInputError(`${source}: edge ${index} is missing its kind or ends`);\n return { kind, from, to };\n });\n\n return { generator: name !== null && version !== null ? { name, version } : null, documents, items, edges };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Comparing */\n/* -------------------------------------------------------------------------- */\n\n// Code-unit order, not locale order: the same input sorts the same way on every\n// machine, which is what keeps a report byte-identical.\nfunction byText(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Compares two exports. Pure, and deterministic in everything it returns. */\nexport function diffExports(before: GraphExport, after: GraphExport): GraphDiff {\n return {\n generators: { before: before.generator, after: after.generator },\n documents: compareDocuments(before, after),\n relations: compareRelations(before, after),\n obligations: compareObligations(before, after),\n };\n}\n\nfunction compareDocuments(before: GraphExport, after: GraphExport): GraphDiff['documents'] {\n const was = new Map(before.documents.map((document) => [document.id, document]));\n const now = new Map(after.documents.map((document) => [document.id, document]));\n const changed: DocumentChange[] = [];\n for (const [id, next] of now) {\n const previous = was.get(id);\n if (previous === undefined) continue;\n // The id is the decision's name, so the same id at a new path is the same\n // decision moved, never one removed and another added (ADR-0009).\n const change: DocumentChange = {\n id,\n ...(previous.path !== next.path && { path: [previous.path, next.path] as const }),\n ...(previous.title !== next.title && { title: [previous.title, next.title] as const }),\n ...(previous.phase !== next.phase && { phase: [previous.phase, next.phase] as const }),\n ...(previous.status !== next.status && { status: [previous.status, next.status] as const }),\n };\n if (Object.keys(change).length > 1) changed.push(change);\n }\n const ids = (documents: Iterable<ExportedDocument>) => [...documents].sort((a, b) => byText(a.id, b.id));\n return {\n added: ids([...now.values()].filter((document) => !was.has(document.id))),\n removed: ids([...was.values()].filter((document) => !now.has(document.id))),\n changed: changed.sort((a, b) => byText(a.id, b.id)),\n };\n}\n\n/**\n * The relations between documents, projected the way `--documents-only`\n * projects a graph: an item's edges lifted onto its document, and a relation\n * that ends where it began dropped - which is also what becomes of a document\n * containing its own items, so containment survives only between documents.\n * Where a relation was declared is not part of it - moving a supersession from\n * a sentence into front matter changes nothing a reader relies on.\n */\nfunction relationsOf(graph: GraphExport): Map<string, ExportedRelation> {\n // A document, or a target nothing in the export defines, stands for itself.\n const owners = new Map(graph.items.map((item) => [item.id, item.document]));\n const out = new Map<string, ExportedRelation>();\n for (const edge of graph.edges) {\n const from = owners.get(edge.from) ?? edge.from;\n const to = owners.get(edge.to) ?? edge.to;\n if (from === to) continue;\n // Tab-joined: kinds and ids cannot contain one.\n out.set(`${edge.kind}\\t${from}\\t${to}`, { kind: edge.kind, from, to });\n }\n return out;\n}\n\nfunction compareRelations(before: GraphExport, after: GraphExport): GraphDiff['relations'] {\n const was = relationsOf(before);\n const now = relationsOf(after);\n const sorted = (entries: [string, ExportedRelation][]) => entries.sort(([a], [b]) => byText(a, b)).map(([, relation]) => relation);\n return {\n added: sorted([...now].filter(([key]) => !was.has(key))),\n removed: sorted([...was].filter(([key]) => !now.has(key))),\n };\n}\n\n/**\n * Obligations by where they sit and then by title, heading by heading, so a\n * section's own questions come before those of the sections under it.\n */\nfunction byPlace(a: ObligationRef, b: ObligationRef): number {\n const left = [a.document, ...a.section];\n const right = [b.document, ...b.section];\n for (let at = 0; at < left.length && at < right.length; at++) {\n const order = byText(left[at] as string, right[at] as string);\n if (order !== 0) return order;\n }\n return left.length - right.length || byText(a.title, b.title);\n}\n\n/**\n * The headings an item sits under, below its document's own. A section path\n * starts at the document's heading, which is its title, so without this a\n * retitled document would lose track of every question in it.\n */\nfunction headingsIn(graph: GraphExport): (item: ExportedItem) => readonly string[] {\n const titles = new Map(graph.documents.map((document) => [document.id, document.title]));\n return (item) => (item.section[0] === titles.get(item.document) ? item.section.slice(1) : item.section);\n}\n\nfunction compareObligations(before: GraphExport, after: GraphExport): GraphDiff['obligations'] {\n const headingsBefore = headingsIn(before);\n const headingsAfter = headingsIn(after);\n const pairs: [ExportedItem, ExportedItem][] = [];\n const paired = new Set<ExportedItem>();\n\n // A declared id names the same item in both states however it was reworded or\n // moved - provided it names one item on each side. An export without the flag\n // cannot say which ids were declared, so none of its ids are trusted.\n const declaredIn = (items: readonly ExportedItem[]) => {\n const out = new Map<string, ExportedItem | null>();\n for (const item of items) {\n if (item.declared !== true) continue;\n out.set(item.id, out.has(item.id) ? null : item);\n }\n return out;\n };\n const declaredBefore = declaredIn(before.items);\n const declaredAfter = declaredIn(after.items);\n for (const [id, item] of declaredBefore) {\n const match = declaredAfter.get(id);\n if (item === null || match === null || match === undefined) continue;\n pairs.push([item, match]);\n paired.add(item);\n paired.add(match);\n }\n\n // Otherwise the same document, headings and title, and only when nothing else\n // on either side shares them: two identical titles in one section cannot be\n // told apart, so neither is paired. Joined as JSON so that no two different\n // triples can produce the same key.\n const group = (items: readonly ExportedItem[], headings: (item: ExportedItem) => readonly string[]) => {\n const out = new Map<string, ExportedItem[]>();\n for (const item of items) {\n if (paired.has(item)) continue;\n const key = JSON.stringify([item.document, headings(item), item.title]);\n out.set(key, [...(out.get(key) ?? []), item]);\n }\n return out;\n };\n const was = group(before.items, headingsBefore);\n const now = group(after.items, headingsAfter);\n for (const [key, previous] of was) {\n const next = now.get(key);\n if (previous.length !== 1 || next?.length !== 1) continue;\n const [a, b] = [previous[0] as ExportedItem, next[0] as ExportedItem];\n pairs.push([a, b]);\n paired.add(a);\n paired.add(b);\n }\n\n const refIn = (headings: (item: ExportedItem) => readonly string[]) => (item: ExportedItem): ObligationRef => ({\n document: item.document,\n section: item.section,\n place: headings(item).join(' > '),\n title: item.title,\n });\n const wasRef = refIn(headingsBefore);\n const nowRef = refIn(headingsAfter);\n return {\n transitions: pairs\n .filter(([a, b]) => a.openness !== b.openness)\n .map(([a, b]) => ({ ...nowRef(b), openness: [a.openness, b.openness] as const }))\n .sort(byPlace),\n appeared: after.items.filter((item) => !paired.has(item)).map((item) => ({ ...nowRef(item), openness: item.openness })).sort(byPlace),\n disappeared: before.items.filter((item) => !paired.has(item)).map((item) => ({ ...wasRef(item), openness: item.openness })).sort(byPlace),\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reporting */\n/* -------------------------------------------------------------------------- */\n\nfunction generatorName(generator: Generator | null): string {\n return generator === null ? 'an unknown version' : `${generator.name} ${generator.version}`;\n}\n\n/**\n * The warning a diff carries when its two exports may disagree for a reason\n * that is not the repository. `null` when both name the same generator.\n */\nexport function generatorWarning(diff: GraphDiff): string | null {\n const { before, after } = diff.generators;\n if (before !== null && after !== null && before.name === after.name && before.version === after.version) return null;\n return `the exports were made by ${generatorName(before)} and ${generatorName(after)}; a change in extraction between versions reads as a change in the repository`;\n}\n\nfunction changeCount(diff: GraphDiff): { documents: number; relations: number; obligations: number } {\n return {\n documents: diff.documents.added.length + diff.documents.removed.length + diff.documents.changed.length,\n relations: diff.relations.added.length + diff.relations.removed.length,\n obligations: diff.obligations.transitions.length + diff.obligations.appeared.length + diff.obligations.disappeared.length,\n };\n}\n\nfunction plural(n: number, word: string): string {\n return `${n} ${n === 1 ? word : `${word}s`}`;\n}\n\n/** One sentence for the whole diff, which is what a pull-request reader sees first. */\nexport function diffVerdict(diff: GraphDiff): string {\n const count = changeCount(diff);\n const parts = [\n count.documents > 0 ? plural(count.documents, 'document') : null,\n count.relations > 0 ? plural(count.relations, 'relation') : null,\n count.obligations > 0 ? plural(count.obligations, 'obligation') : null,\n ].filter((part): part is string => part !== null);\n if (parts.length === 0) return 'No relational change.';\n const listed = parts.length === 1 ? (parts[0] as string) : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1] as string}`;\n return `${listed} changed.`;\n}\n\nfunction where(ref: ObligationRef): string {\n return ref.place === '' ? ref.document : `${ref.document} > ${ref.place}`;\n}\n\nfunction transitionWord([from, to]: readonly [string, string]): string {\n return to === 'closed' ? 'resolved' : from === 'closed' ? 'reopened' : to === 'partial' ? 'narrowed' : 'widened';\n}\n\nfunction describeChange(change: DocumentChange): string {\n const parts: string[] = [];\n if (change.path) parts.push(`moved from ${change.path[0]} to ${change.path[1]}`);\n if (change.phase) parts.push(`${change.phase[0]} -> ${change.phase[1]}`);\n if (change.status) parts.push(`status ${change.status[0] ?? 'none'} -> ${change.status[1] ?? 'none'}`);\n if (change.title) parts.push(`retitled \"${change.title[1]}\"`);\n return parts.join('; ');\n}\n\n/** The report for a terminal. */\nexport function formatDiffText(diff: GraphDiff): string {\n const lines: string[] = [];\n const warning = generatorWarning(diff);\n if (warning !== null) lines.push(`warning: ${warning}`, '');\n lines.push(diffVerdict(diff));\n\n const { documents, relations, obligations } = diff;\n if (documents.added.length + documents.removed.length + documents.changed.length > 0) {\n lines.push('', 'Documents');\n for (const document of documents.added) lines.push(` + ${document.id} ${document.title} (${document.phase})`);\n for (const document of documents.removed) lines.push(` - ${document.id} ${document.title}`);\n for (const change of documents.changed) lines.push(` ~ ${change.id} ${describeChange(change)}`);\n }\n if (relations.added.length + relations.removed.length > 0) {\n lines.push('', 'Relations');\n for (const relation of relations.added) lines.push(` + ${relation.from} -${relation.kind}-> ${relation.to}`);\n for (const relation of relations.removed) lines.push(` - ${relation.from} -${relation.kind}-> ${relation.to}`);\n }\n if (obligations.transitions.length + obligations.appeared.length + obligations.disappeared.length > 0) {\n lines.push('', 'Obligations');\n // Padded to the longest word, so the places line up.\n const word = (text: string) => text.padEnd('disappeared'.length);\n for (const transition of obligations.transitions) {\n lines.push(` ${word(transitionWord(transition.openness))} ${where(transition)}: \"${transition.title}\" (${transition.openness[0]} -> ${transition.openness[1]})`);\n }\n for (const item of obligations.appeared) lines.push(` ${word('appeared')} ${where(item)}: \"${item.title}\" (${item.openness})`);\n for (const item of obligations.disappeared) lines.push(` disappeared ${where(item)}: \"${item.title}\" (was ${item.openness})`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** The report for a bot: the diff itself, with the verdict and warning beside it. */\nexport function formatDiffJson(diff: GraphDiff): string {\n return `${JSON.stringify({ version: 1, verdict: diffVerdict(diff), warning: generatorWarning(diff), ...diff }, null, 2)}\\n`;\n}\n\n// A table cell: a pipe would end the cell, and a newline would end the row.\nfunction cell(value: string): string {\n return value.replace(/[|]/g, '&#124;').replace(/\\s+/g, ' ');\n}\n\n/** The report for a job summary or a pull-request comment. */\nexport function formatDiffMarkdown(diff: GraphDiff): string {\n const lines: string[] = ['### spec-graph diff', '', diffVerdict(diff), ''];\n const warning = generatorWarning(diff);\n if (warning !== null) lines.push(`> **Warning:** ${cell(warning)}.`, '');\n\n const { documents, relations, obligations } = diff;\n if (documents.added.length + documents.removed.length + documents.changed.length > 0) {\n lines.push('| | Document | Change |', '| --- | --- | --- |');\n for (const document of documents.added) lines.push(`| added | \\`${cell(document.id)}\\` | ${cell(document.title)} - ${cell(document.phase)} |`);\n for (const document of documents.removed) lines.push(`| removed | \\`${cell(document.id)}\\` | ${cell(document.title)} |`);\n for (const change of documents.changed) lines.push(`| changed | \\`${cell(change.id)}\\` | ${cell(describeChange(change))} |`);\n lines.push('');\n }\n if (relations.added.length + relations.removed.length > 0) {\n lines.push('| | From | Relation | To |', '| --- | --- | --- | --- |');\n for (const relation of relations.added) lines.push(`| added | \\`${cell(relation.from)}\\` | ${cell(relation.kind)} | \\`${cell(relation.to)}\\` |`);\n for (const relation of relations.removed) lines.push(`| removed | \\`${cell(relation.from)}\\` | ${cell(relation.kind)} | \\`${cell(relation.to)}\\` |`);\n lines.push('');\n }\n if (obligations.transitions.length + obligations.appeared.length + obligations.disappeared.length > 0) {\n lines.push('| | Where | Obligation |', '| --- | --- | --- |');\n for (const transition of obligations.transitions) {\n lines.push(`| ${transitionWord(transition.openness)} | ${cell(where(transition))} | ${cell(transition.title)} (${transition.openness[0]} -> ${transition.openness[1]}) |`);\n }\n for (const item of obligations.appeared) lines.push(`| appeared | ${cell(where(item))} | ${cell(item.title)} (${item.openness}) |`);\n for (const item of obligations.disappeared) lines.push(`| disappeared | ${cell(where(item))} | ${cell(item.title)} (was ${item.openness}) |`);\n lines.push('');\n }\n return `${lines.join('\\n').trimEnd()}\\n`;\n}\n"]}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What changed between two states of the graph, read from two JSON exports.\n *\n * Two files in, one report out, and no git underneath: CI already has both\n * checkouts, and the pipeline stays a pure function of text. See ADR-0020.\n *\n * The rule the whole module follows is that a change is reported only against a\n * name that means the same thing in both exports. A document's id does. An\n * item's numbered id does not - it is an ordinal within a section, so one\n * inserted question renumbers every question after it - and keying on it\n * reports a question as reopened when nobody touched it. So an obligation is\n * paired only by a declared id, or by its document, section and title when\n * nothing else shares them; anything unpaired is said to have appeared or\n * disappeared, and never to have changed.\n */\n\n/** Refused input: not JSON, not an export, or a version this cannot read. */\nexport class DiffInputError extends Error {\n constructor(message: string) {\n super(message);\n // For a stack trace, which nothing prints: the command catches every one\n // of these and prints its message, and the library does not export the\n // class. A mutant that blanks the name is equivalent.\n this.name = 'DiffInputError';\n }\n}\n\nexport interface Generator {\n readonly name: string;\n readonly version: string;\n}\n\nexport interface ExportedDocument {\n readonly id: string;\n readonly title: string;\n readonly path: string;\n readonly phase: string;\n readonly status: string | null;\n}\n\nexport interface ExportedItem {\n readonly id: string;\n readonly title: string;\n readonly document: string;\n readonly section: readonly string[];\n readonly openness: string;\n /** `null` when the export predates the flag, so its ids cannot be trusted. */\n readonly declared: boolean | null;\n}\n\nexport interface ExportedRelation {\n readonly kind: string;\n readonly from: string;\n readonly to: string;\n}\n\n/** One side of a diff: a `--graph-format json` export, read and checked. */\nexport interface GraphExport {\n readonly generator: Generator | null;\n readonly documents: readonly ExportedDocument[];\n readonly items: readonly ExportedItem[];\n readonly edges: readonly ExportedRelation[];\n}\n\nexport interface DocumentChange {\n readonly id: string;\n readonly path?: readonly [string, string] | undefined;\n readonly title?: readonly [string, string] | undefined;\n readonly phase?: readonly [string, string] | undefined;\n readonly status?: readonly [string | null, string | null] | undefined;\n}\n\nexport interface ObligationRef {\n readonly document: string;\n readonly section: readonly string[];\n /**\n * The section path as a reader wants it: without the document's own title\n * heading, which every item under it shares and the id already names. Items\n * are matched on the same headings, kept as a list so that no heading holding\n * \" > \" can pass for two.\n */\n readonly place: string;\n readonly title: string;\n}\n\n/** An item with no pair: its state on the one side it exists on, and no more. */\nexport interface UnpairedObligation extends ObligationRef {\n readonly openness: string;\n}\n\nexport interface Transition extends ObligationRef {\n readonly openness: readonly [string, string];\n}\n\nexport interface GraphDiff {\n readonly generators: { readonly before: Generator | null; readonly after: Generator | null };\n readonly documents: {\n readonly added: readonly ExportedDocument[];\n readonly removed: readonly ExportedDocument[];\n readonly changed: readonly DocumentChange[];\n };\n readonly relations: { readonly added: readonly ExportedRelation[]; readonly removed: readonly ExportedRelation[] };\n readonly obligations: {\n readonly transitions: readonly Transition[];\n readonly appeared: readonly UnpairedObligation[];\n readonly disappeared: readonly UnpairedObligation[];\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reading an export */\n/* -------------------------------------------------------------------------- */\n\n// Every caller goes on to read a field and check it with `text` or\n// `Array.isArray`, and a number, string or boolean yields `undefined` for every\n// field read here, which both refuse. So the `typeof` decides nothing a caller\n// can see, and its mutant is equivalent.\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown): string | null {\n return typeof value === 'string' ? value : null;\n}\n\n/**\n * Reads one export.\n *\n * Checked as far as the comparison reads it and no further: a field this does\n * not use is not a reason to refuse a file, and a later export that adds fields\n * is still version 1.\n */\nexport function parseGraphExport(raw: string, source: string): GraphExport {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new DiffInputError(`${source} is not JSON; make it with \\`spec-graph graph --graph-format json\\``);\n }\n if (!isRecord(parsed) || !Array.isArray(parsed['nodes']) || !Array.isArray(parsed['edges'])) {\n throw new DiffInputError(`${source} is not a graph export; make it with \\`spec-graph graph --graph-format json\\``);\n }\n if (parsed['version'] !== 1) {\n throw new DiffInputError(`${source} is export version ${JSON.stringify(parsed['version'])}, and this reads version 1`);\n }\n\n const generator = isRecord(parsed['generator']) ? parsed['generator'] : null;\n const name = generator ? text(generator['name']) : null;\n const version = generator ? text(generator['version']) : null;\n\n const documents: ExportedDocument[] = [];\n const items: ExportedItem[] = [];\n parsed['nodes'].forEach((node: unknown, index) => {\n const id = isRecord(node) ? text(node['id']) : null;\n if (!isRecord(node) || id === null) throw new DiffInputError(`${source}: node ${index} has no id`);\n if (node['kind'] === 'document') {\n documents.push({\n id,\n title: text(node['title']) ?? '',\n path: text(node['path']) ?? '',\n phase: text(node['phase']) ?? 'unknown',\n status: text(node['status']),\n });\n } else if (node['kind'] === 'item') {\n const section = Array.isArray(node['section']) ? node['section'].filter((part): part is string => typeof part === 'string') : [];\n items.push({\n id,\n title: text(node['title']) ?? '',\n document: text(node['document']) ?? '',\n section,\n openness: text(node['openness']) ?? 'unknown',\n declared: typeof node['declared'] === 'boolean' ? node['declared'] : null,\n });\n } else {\n throw new DiffInputError(`${source}: node ${id} is neither a document nor an item`);\n }\n });\n\n const edges: ExportedRelation[] = parsed['edges'].map((edge: unknown, index) => {\n const kind = isRecord(edge) ? text(edge['kind']) : null;\n const from = isRecord(edge) ? text(edge['from']) : null;\n const to = isRecord(edge) ? text(edge['to']) : null;\n if (kind === null || from === null || to === null) throw new DiffInputError(`${source}: edge ${index} is missing its kind or ends`);\n return { kind, from, to };\n });\n\n return { generator: name !== null && version !== null ? { name, version } : null, documents, items, edges };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Comparing */\n/* -------------------------------------------------------------------------- */\n\n// Code-unit order, not locale order: the same input sorts the same way on every\n// machine, which is what keeps a report byte-identical.\nfunction byText(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Compares two exports. Pure, and deterministic in everything it returns. */\nexport function diffExports(before: GraphExport, after: GraphExport): GraphDiff {\n return {\n generators: { before: before.generator, after: after.generator },\n documents: compareDocuments(before, after),\n relations: compareRelations(before, after),\n obligations: compareObligations(before, after),\n };\n}\n\nfunction compareDocuments(before: GraphExport, after: GraphExport): GraphDiff['documents'] {\n const was = new Map(before.documents.map((document) => [document.id, document]));\n const now = new Map(after.documents.map((document) => [document.id, document]));\n const changed: DocumentChange[] = [];\n for (const [id, next] of now) {\n const previous = was.get(id);\n if (previous === undefined) continue;\n // The id is the decision's name, so the same id at a new path is the same\n // decision moved, never one removed and another added (ADR-0009).\n const change: DocumentChange = {\n id,\n ...(previous.path !== next.path && { path: [previous.path, next.path] as const }),\n ...(previous.title !== next.title && { title: [previous.title, next.title] as const }),\n ...(previous.phase !== next.phase && { phase: [previous.phase, next.phase] as const }),\n ...(previous.status !== next.status && { status: [previous.status, next.status] as const }),\n };\n if (Object.keys(change).length > 1) changed.push(change);\n }\n const ids = (documents: Iterable<ExportedDocument>) => [...documents].sort((a, b) => byText(a.id, b.id));\n return {\n added: ids([...now.values()].filter((document) => !was.has(document.id))),\n removed: ids([...was.values()].filter((document) => !now.has(document.id))),\n changed: changed.sort((a, b) => byText(a.id, b.id)),\n };\n}\n\n/**\n * The relations between documents, projected the way `--documents-only`\n * projects a graph: an item's edges lifted onto its document, and a relation\n * that ends where it began dropped - which is also what becomes of a document\n * containing its own items, so containment survives only between documents.\n * Where a relation was declared is not part of it - moving a supersession from\n * a sentence into front matter changes nothing a reader relies on.\n */\nfunction relationsOf(graph: GraphExport): Map<string, ExportedRelation> {\n // A document, or a target nothing in the export defines, stands for itself.\n const owners = new Map(graph.items.map((item) => [item.id, item.document]));\n const out = new Map<string, ExportedRelation>();\n for (const edge of graph.edges) {\n const from = owners.get(edge.from) ?? edge.from;\n const to = owners.get(edge.to) ?? edge.to;\n if (from === to) continue;\n // Tab-joined: kinds and ids cannot contain one.\n out.set(`${edge.kind}\\t${from}\\t${to}`, { kind: edge.kind, from, to });\n }\n return out;\n}\n\nfunction compareRelations(before: GraphExport, after: GraphExport): GraphDiff['relations'] {\n const was = relationsOf(before);\n const now = relationsOf(after);\n const sorted = (entries: [string, ExportedRelation][]) => entries.sort(([a], [b]) => byText(a, b)).map(([, relation]) => relation);\n return {\n added: sorted([...now].filter(([key]) => !was.has(key))),\n removed: sorted([...was].filter(([key]) => !now.has(key))),\n };\n}\n\n/**\n * Obligations by where they sit and then by title, heading by heading, so a\n * section's own questions come before those of the sections under it.\n */\nfunction byPlace(a: ObligationRef, b: ObligationRef): number {\n const left = [a.document, ...a.section];\n const right = [b.document, ...b.section];\n // Past the end of the shorter path `byText` meets `undefined`, which is\n // neither less nor greater than anything, and answers 0: the mutants that\n // run the loop on decide nothing the lengths below do not, and are\n // equivalent.\n for (let at = 0; at < left.length && at < right.length; at++) {\n const order = byText(left[at] as string, right[at] as string);\n if (order !== 0) return order;\n }\n return left.length - right.length || byText(a.title, b.title);\n}\n\n/**\n * The headings an item sits under, below its document's own. A section path\n * starts at the document's heading, which is its title, so without this a\n * retitled document would lose track of every question in it.\n */\nfunction headingsIn(graph: GraphExport): (item: ExportedItem) => readonly string[] {\n const titles = new Map(graph.documents.map((document) => [document.id, document.title]));\n return (item) => (item.section[0] === titles.get(item.document) ? item.section.slice(1) : item.section);\n}\n\nfunction compareObligations(before: GraphExport, after: GraphExport): GraphDiff['obligations'] {\n const headingsBefore = headingsIn(before);\n const headingsAfter = headingsIn(after);\n // Seeded with a string, as a mutant seeds it, this holds a \"pair\" of two\n // characters with no openness on either side, which the transition filter\n // drops: that mutant is equivalent.\n const pairs: [ExportedItem, ExportedItem][] = [];\n const paired = new Set<ExportedItem>();\n\n // A declared id names the same item in both states however it was reworded or\n // moved - provided it names one item on each side. An export without the flag\n // cannot say which ids were declared, so none of its ids are trusted.\n const declaredIn = (items: readonly ExportedItem[]) => {\n const out = new Map<string, ExportedItem | null>();\n for (const item of items) {\n if (item.declared !== true) continue;\n out.set(item.id, out.has(item.id) ? null : item);\n }\n return out;\n };\n const declaredBefore = declaredIn(before.items);\n const declaredAfter = declaredIn(after.items);\n for (const [id, item] of declaredBefore) {\n const match = declaredAfter.get(id);\n if (item === null || match === null || match === undefined) continue;\n pairs.push([item, match]);\n paired.add(item);\n paired.add(match);\n }\n\n // Otherwise the same document, headings and title, and only when nothing else\n // on either side shares them: two identical titles in one section cannot be\n // told apart, so neither is paired. Joined as JSON so that no two different\n // triples can produce the same key.\n const group = (items: readonly ExportedItem[], headings: (item: ExportedItem) => readonly string[]) => {\n const out = new Map<string, ExportedItem[]>();\n for (const item of items) {\n if (paired.has(item)) continue;\n const key = JSON.stringify([item.document, headings(item), item.title]);\n out.set(key, [...(out.get(key) ?? []), item]);\n }\n return out;\n };\n const was = group(before.items, headingsBefore);\n const now = group(after.items, headingsAfter);\n for (const [key, previous] of was) {\n const next = now.get(key);\n if (previous.length !== 1 || next?.length !== 1) continue;\n const [a, b] = [previous[0] as ExportedItem, next[0] as ExportedItem];\n pairs.push([a, b]);\n paired.add(a);\n paired.add(b);\n }\n\n const refIn = (headings: (item: ExportedItem) => readonly string[]) => (item: ExportedItem): ObligationRef => ({\n document: item.document,\n section: item.section,\n place: headings(item).join(' > '),\n title: item.title,\n });\n const wasRef = refIn(headingsBefore);\n const nowRef = refIn(headingsAfter);\n return {\n transitions: pairs\n .filter(([a, b]) => a.openness !== b.openness)\n .map(([a, b]) => ({ ...nowRef(b), openness: [a.openness, b.openness] as const }))\n .sort(byPlace),\n appeared: after.items.filter((item) => !paired.has(item)).map((item) => ({ ...nowRef(item), openness: item.openness })).sort(byPlace),\n disappeared: before.items.filter((item) => !paired.has(item)).map((item) => ({ ...wasRef(item), openness: item.openness })).sort(byPlace),\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reporting */\n/* -------------------------------------------------------------------------- */\n\nfunction generatorName(generator: Generator | null): string {\n return generator === null ? 'an unknown version' : `${generator.name} ${generator.version}`;\n}\n\n/**\n * The warning a diff carries when its two exports may disagree for a reason\n * that is not the repository. `null` when both name the same generator.\n */\nexport function generatorWarning(diff: GraphDiff): string | null {\n const { before, after } = diff.generators;\n if (before !== null && after !== null && before.name === after.name && before.version === after.version) return null;\n return `the exports were made by ${generatorName(before)} and ${generatorName(after)}; a change in extraction between versions reads as a change in the repository`;\n}\n\nfunction changeCount(diff: GraphDiff): { documents: number; relations: number; obligations: number } {\n return {\n documents: diff.documents.added.length + diff.documents.removed.length + diff.documents.changed.length,\n relations: diff.relations.added.length + diff.relations.removed.length,\n obligations: diff.obligations.transitions.length + diff.obligations.appeared.length + diff.obligations.disappeared.length,\n };\n}\n\nfunction plural(n: number, word: string): string {\n return `${n} ${n === 1 ? word : `${word}s`}`;\n}\n\n/** One sentence for the whole diff, which is what a pull-request reader sees first. */\nexport function diffVerdict(diff: GraphDiff): string {\n const count = changeCount(diff);\n const parts = [\n count.documents > 0 ? plural(count.documents, 'document') : null,\n count.relations > 0 ? plural(count.relations, 'relation') : null,\n count.obligations > 0 ? plural(count.obligations, 'obligation') : null,\n ].filter((part): part is string => part !== null);\n if (parts.length === 0) return 'No relational change.';\n const listed = parts.length === 1 ? (parts[0] as string) : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1] as string}`;\n return `${listed} changed.`;\n}\n\nfunction where(ref: ObligationRef): string {\n return ref.place === '' ? ref.document : `${ref.document} > ${ref.place}`;\n}\n\nfunction transitionWord([from, to]: readonly [string, string]): string {\n return to === 'closed' ? 'resolved' : from === 'closed' ? 'reopened' : to === 'partial' ? 'narrowed' : 'widened';\n}\n\nfunction describeChange(change: DocumentChange): string {\n const parts: string[] = [];\n if (change.path) parts.push(`moved from ${change.path[0]} to ${change.path[1]}`);\n if (change.phase) parts.push(`${change.phase[0]} -> ${change.phase[1]}`);\n if (change.status) parts.push(`status ${change.status[0] ?? 'none'} -> ${change.status[1] ?? 'none'}`);\n if (change.title) parts.push(`retitled \"${change.title[1]}\"`);\n return parts.join('; ');\n}\n\n/** The report for a terminal. */\nexport function formatDiffText(diff: GraphDiff): string {\n const lines: string[] = [];\n const warning = generatorWarning(diff);\n if (warning !== null) lines.push(`warning: ${warning}`, '');\n lines.push(diffVerdict(diff));\n\n const { documents, relations, obligations } = diff;\n if (documents.added.length + documents.removed.length + documents.changed.length > 0) {\n lines.push('', 'Documents');\n for (const document of documents.added) lines.push(` + ${document.id} ${document.title} (${document.phase})`);\n for (const document of documents.removed) lines.push(` - ${document.id} ${document.title}`);\n for (const change of documents.changed) lines.push(` ~ ${change.id} ${describeChange(change)}`);\n }\n if (relations.added.length + relations.removed.length > 0) {\n lines.push('', 'Relations');\n for (const relation of relations.added) lines.push(` + ${relation.from} -${relation.kind}-> ${relation.to}`);\n for (const relation of relations.removed) lines.push(` - ${relation.from} -${relation.kind}-> ${relation.to}`);\n }\n if (obligations.transitions.length + obligations.appeared.length + obligations.disappeared.length > 0) {\n lines.push('', 'Obligations');\n // Padded to the longest word, so the places line up.\n const word = (text: string) => text.padEnd('disappeared'.length);\n for (const transition of obligations.transitions) {\n lines.push(` ${word(transitionWord(transition.openness))} ${where(transition)}: \"${transition.title}\" (${transition.openness[0]} -> ${transition.openness[1]})`);\n }\n for (const item of obligations.appeared) lines.push(` ${word('appeared')} ${where(item)}: \"${item.title}\" (${item.openness})`);\n for (const item of obligations.disappeared) lines.push(` disappeared ${where(item)}: \"${item.title}\" (was ${item.openness})`);\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** The report for a bot: the diff itself, with the verdict and warning beside it. */\nexport function formatDiffJson(diff: GraphDiff): string {\n return `${JSON.stringify({ version: 1, verdict: diffVerdict(diff), warning: generatorWarning(diff), ...diff }, null, 2)}\\n`;\n}\n\n// A table cell: a pipe would end the cell, and a newline would end the row.\nfunction cell(value: string): string {\n return value.replace(/[|]/g, '&#124;').replace(/\\s+/g, ' ');\n}\n\n/** The report for a job summary or a pull-request comment. */\nexport function formatDiffMarkdown(diff: GraphDiff): string {\n const lines: string[] = ['### spec-graph diff', '', diffVerdict(diff), ''];\n const warning = generatorWarning(diff);\n if (warning !== null) lines.push(`> **Warning:** ${cell(warning)}.`, '');\n\n const { documents, relations, obligations } = diff;\n if (documents.added.length + documents.removed.length + documents.changed.length > 0) {\n lines.push('| | Document | Change |', '| --- | --- | --- |');\n for (const document of documents.added) lines.push(`| added | \\`${cell(document.id)}\\` | ${cell(document.title)} - ${cell(document.phase)} |`);\n for (const document of documents.removed) lines.push(`| removed | \\`${cell(document.id)}\\` | ${cell(document.title)} |`);\n for (const change of documents.changed) lines.push(`| changed | \\`${cell(change.id)}\\` | ${cell(describeChange(change))} |`);\n lines.push('');\n }\n if (relations.added.length + relations.removed.length > 0) {\n lines.push('| | From | Relation | To |', '| --- | --- | --- | --- |');\n for (const relation of relations.added) lines.push(`| added | \\`${cell(relation.from)}\\` | ${cell(relation.kind)} | \\`${cell(relation.to)}\\` |`);\n for (const relation of relations.removed) lines.push(`| removed | \\`${cell(relation.from)}\\` | ${cell(relation.kind)} | \\`${cell(relation.to)}\\` |`);\n lines.push('');\n }\n if (obligations.transitions.length + obligations.appeared.length + obligations.disappeared.length > 0) {\n lines.push('| | Where | Obligation |', '| --- | --- | --- |');\n for (const transition of obligations.transitions) {\n lines.push(`| ${transitionWord(transition.openness)} | ${cell(where(transition))} | ${cell(transition.title)} (${transition.openness[0]} -> ${transition.openness[1]}) |`);\n }\n for (const item of obligations.appeared) lines.push(`| appeared | ${cell(where(item))} | ${cell(item.title)} (${item.openness}) |`);\n for (const item of obligations.disappeared) lines.push(`| disappeared | ${cell(where(item))} | ${cell(item.title)} (was ${item.openness}) |`);\n lines.push('');\n }\n return `${lines.join('\\n').trimEnd()}\\n`;\n}\n"]}
@@ -30,7 +30,8 @@ const ATTRIBUTE = /([A-Za-z_][A-Za-z0-9_-]*)\s*(?:=\s*("([^"]*)"|'([^']*)'|([^\s
30
30
  /** Attributes each directive understands. Anything else is reported, not ignored. */
31
31
  const SCHEMA = {
32
32
  'spec-node': ['id', 'status', 'title', 'aliases', 'kind'],
33
- // Takes no attributes: the file either is a record or it is not.
33
+ // Takes no attributes: the file either is a record or it is not. A mutant
34
+ // that lists `Stryker was here` lists no name: one never holds a space.
34
35
  'spec-history': [],
35
36
  'spec-item': ['id', 'state', 'title', 'owner', 'note'],
36
37
  'spec-edge': ['kind', 'to', 'from', 'reason'],
@@ -56,7 +57,8 @@ export function parseDirectives(comments) {
56
57
  if (!KNOWN.has(name))
57
58
  continue;
58
59
  const directiveName = name;
59
- const bodyStart = (head.index ?? 0) + head[0].length;
60
+ // The head is anchored, so it starts where the comment's text does.
61
+ const bodyStart = head[0].length;
60
62
  const body = comment.inner.slice(bodyStart);
61
63
  const attributes = new Map();
62
64
  const unknown = [];
@@ -104,10 +106,10 @@ export function attrList(directive, name) {
104
106
  const found = attr(directive, name);
105
107
  if (!found)
106
108
  return [];
107
- return found.value
108
- .split(/[,;\s]+/)
109
- .map((part) => part.trim())
110
- .filter((part) => part.length > 0);
109
+ // Whitespace is a separator, so no part holds any to trim. The filter drops
110
+ // what a separator at either end leaves, and would drop what each separator
111
+ // in a run leaves too, so splitting at one at a time is equivalent.
112
+ return found.value.split(/[,;\s]+/).filter((part) => part.length > 0);
111
113
  }
112
114
  /**
113
115
  * Finds the directive that annotates a given region.
@@ -130,6 +132,7 @@ lookBehind) {
130
132
  const above = directive.end <= region.start && region.start - directive.end <= lookBehind;
131
133
  if (!inside && !above)
132
134
  continue;
135
+ // Two directives never start at one offset, so `>=` reads the same.
133
136
  if (best === null || directive.start > best.start)
134
137
  best = directive;
135
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"directives.js","sourceRoot":"","sources":["../src/directives.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Explicit annotations, written as HTML comments.\n *\n * Inference covers the overwhelming majority of real documents, but inference\n * that cannot be overridden is a trap: the first time it guesses wrong about a\n * document somebody cannot restructure, the whole tool gets switched off. These\n * directives are the escape hatch, and they are HTML comments so they stay\n * invisible in GitHub, MkDocs, Docusaurus and every other renderer.\n *\n * ```md\n * <!-- @spec-node id=\"ADR-0007\" status=\"accepted\" aliases=\"adr-7, sharding\" -->\n * <!-- @spec-item id=\"shard-key\" state=\"narrowed\" -->\n * <!-- @spec-edge kind=\"delegates-to\" to=\"ADR-0011#scope\" -->\n * <!-- @spec-ignore -->\n * ```\n *\n * A directive always wins over anything inferred, and says so in the report, so\n * an override is visible rather than mysterious.\n */\n\nimport { isMarkdownLine, type HtmlComment, type ListItem, type ScannedDocument } from './markdown.js';\n\nexport type DirectiveName = 'spec-node' | 'spec-item' | 'spec-edge' | 'spec-ignore' | 'spec-history';\n\nconst KNOWN: ReadonlySet<string> = new Set<DirectiveName>([\n 'spec-node',\n 'spec-item',\n 'spec-edge',\n 'spec-ignore',\n 'spec-history',\n]);\n\nexport interface Attribute {\n readonly value: string;\n /** Absolute offset of the value text, quotes excluded. */\n readonly start: number;\n readonly end: number;\n}\n\nexport interface Directive {\n readonly name: DirectiveName;\n readonly attributes: ReadonlyMap<string, Attribute>;\n /** Offset of the enclosing comment. */\n readonly start: number;\n readonly end: number;\n readonly line: number;\n /** Attribute names that were written but are not part of this directive. */\n readonly unknownAttributes: readonly string[];\n}\n\nconst DIRECTIVE_HEAD = /^\\s*@([a-z][a-z0-9-]*)/;\nconst ATTRIBUTE = /([A-Za-z_][A-Za-z0-9_-]*)\\s*(?:=\\s*(\"([^\"]*)\"|'([^']*)'|([^\\s\"'<>]+)))?/g;\n\n/** Attributes each directive understands. Anything else is reported, not ignored. */\nconst SCHEMA: Readonly<Record<DirectiveName, readonly string[]>> = {\n 'spec-node': ['id', 'status', 'title', 'aliases', 'kind'],\n // Takes no attributes: the file either is a record or it is not.\n 'spec-history': [],\n 'spec-item': ['id', 'state', 'title', 'owner', 'note'],\n 'spec-edge': ['kind', 'to', 'from', 'reason'],\n 'spec-ignore': ['reason'],\n};\n\n/**\n * Reads every spec-graph directive out of a document's HTML comments.\n *\n * Only out of a comment that closes. One that opens a line and never does runs\n * to the end of the document, and read as a directive, a stray\n * `<!-- @spec-ignore` would drop the file, and a stray `<!-- @spec-node` would\n * take every `name=value` in the rest of it as an attribute.\n */\nexport function parseDirectives(comments: readonly HtmlComment[]): Directive[] {\n const out: Directive[] = [];\n for (const comment of comments) {\n if (comment.closed === false) continue;\n const head = DIRECTIVE_HEAD.exec(comment.inner);\n if (!head) continue;\n const name = head[1] as string;\n if (!KNOWN.has(name)) continue;\n\n const directiveName = name as DirectiveName;\n const bodyStart = (head.index ?? 0) + (head[0] as string).length;\n const body = comment.inner.slice(bodyStart);\n const attributes = new Map<string, Attribute>();\n const unknown: string[] = [];\n const allowed = SCHEMA[directiveName];\n\n ATTRIBUTE.lastIndex = 0;\n for (let m = ATTRIBUTE.exec(body); m !== null; m = ATTRIBUTE.exec(body)) {\n const key = (m[1] as string).toLowerCase();\n const quoted = m[3] ?? m[4];\n const bare = m[5];\n // A bare attribute is a flag: `<!-- @spec-ignore reason -->`.\n const value = quoted ?? bare ?? 'true';\n const raw = m[2];\n const valueOffset =\n raw === undefined\n ? (m.index ?? 0) + (m[0] as string).length\n : (m.index ?? 0) + (m[0] as string).indexOf(raw) + (quoted === undefined ? 0 : 1);\n const start = comment.innerStart + bodyStart + valueOffset;\n\n if (!allowed.includes(key)) unknown.push(key);\n attributes.set(key, { value, start, end: start + value.length });\n }\n\n out.push({\n name: directiveName,\n attributes,\n start: comment.start,\n end: comment.end,\n line: comment.line,\n unknownAttributes: unknown,\n });\n }\n return out;\n}\n\n/** Reads an attribute, trimmed. Returns `null` when absent or blank. */\nexport function attr(directive: Directive, name: string): Attribute | null {\n const found = directive.attributes.get(name);\n if (!found) return null;\n const trimmed = found.value.trim();\n if (trimmed.length === 0) return null;\n const offset = found.value.indexOf(trimmed);\n return { value: trimmed, start: found.start + offset, end: found.start + offset + trimmed.length };\n}\n\n/** Splits a comma- or whitespace-separated attribute into its parts. */\nexport function attrList(directive: Directive, name: string): string[] {\n const found = attr(directive, name);\n if (!found) return [];\n return found.value\n .split(/[,;\\s]+/)\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n}\n\n/**\n * Finds the directive that annotates a given region.\n *\n * A `@spec-item` directive may sit on the line above the item, on the same\n * line, or inside the item body. All three read naturally in the source, so all\n * three are accepted, with the closest preceding directive winning.\n *\n * It answers for one region with no view of the others, so a directive in reach\n * of two items is found for both. Extraction uses `bindItemDirectives`.\n */\nexport function directiveFor(\n directives: readonly Directive[],\n name: DirectiveName,\n region: { start: number; end: number },\n /** How far above the region a directive may sit and still apply. */\n lookBehind: number,\n): Directive | null {\n let best: Directive | null = null;\n for (const directive of directives) {\n if (directive.name !== name) continue;\n const inside = directive.start >= region.start && directive.end <= region.end;\n const above = directive.end <= region.start && region.start - directive.end <= lookBehind;\n if (!inside && !above) continue;\n if (best === null || directive.start > best.start) best = directive;\n }\n return best;\n}\n\n/**\n * Pairs each `@spec-item` directive with the one list item it annotates.\n *\n * A directive is written for one item, so it binds to one: the item directly\n * below it, with nothing but blank lines and other comments between, or else\n * the item it is written in, the innermost where items nest. An item that\n * several bind to takes the last one written.\n *\n * A directive alone on its line is in an item only when indented further than\n * that item's marker, which is how CommonMark reads it. Indented under an item,\n * it annotates that item and not the sibling below; flush between two items, it\n * annotates the second.\n */\nexport function bindItemDirectives(\n scanned: ScannedDocument,\n directives: readonly Directive[],\n): Map<ListItem, Directive> {\n const bound = new Map<ListItem, Directive>();\n const wanted = directives.filter((directive) => directive.name === 'spec-item');\n // Most documents carry none. Sparing them the pass over their lines changes\n // how long this takes and nothing else.\n if (wanted.length === 0) return bound;\n\n const { lines, listItems } = scanned;\n const masked = scanned.masks.structure;\n // The indentation of each line holding nothing but whitespace and comments.\n // Code and raw-text HTML are masked too, so they are told apart by their flags.\n const quiet = new Map<number, number>();\n // For every other line, where the run of quiet lines directly above it began,\n // when there was one.\n const quietFrom = new Map<number, number | undefined>();\n let run: number | undefined;\n for (const line of lines) {\n if (isMarkdownLine(line) && masked.slice(line.contentStart, line.end).trim() === '') {\n quiet.set(line.line, line.indent);\n run ??= line.start;\n continue;\n }\n quietFrom.set(line.line, run);\n run = undefined;\n }\n\n // The items whose text the cursor is in, outermost first. Items nest, so one\n // that has ended is always on top. An item ends at the end of a line, the next\n // thing starts on a later one, and no two things start together, so `<` and\n // `<=` read alike in every comparison of offsets below.\n const open: ListItem[] = [];\n const close = (offset: number): void => {\n while (open.length > 0 && (open[open.length - 1] as ListItem).end <= offset) open.pop();\n };\n let next = 0;\n for (const directive of wanted) {\n while (next < listItems.length && (listItems[next] as ListItem).start < directive.start) {\n const item = listItems[next] as ListItem;\n close(item.start);\n open.push(item);\n next += 1;\n }\n close(directive.start);\n\n // Sharing its line with text, a directive is in whatever that text is in.\n const indent = quiet.get(directive.line) ?? Infinity;\n // No deeper than the scanner already went to find where these items end.\n let depth = open.length - 1;\n while (depth >= 0 && (open[depth] as ListItem).indent >= indent) depth -= 1;\n const holder = open[depth];\n\n const below = listItems[next];\n const directlyAbove = below !== undefined && (quietFrom.get(below.line) ?? Infinity) <= directive.start;\n if (directlyAbove && (holder === undefined || below.start < holder.end)) {\n bound.set(below, directive);\n continue;\n }\n // Flush under an item and above no other, it continues the item as any\n // unindented line does.\n const written = holder ?? open[open.length - 1];\n if (written !== undefined) bound.set(written, directive);\n }\n return bound;\n}\n"]}
1
+ {"version":3,"file":"directives.js","sourceRoot":"","sources":["../src/directives.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Explicit annotations, written as HTML comments.\n *\n * Inference covers the overwhelming majority of real documents, but inference\n * that cannot be overridden is a trap: the first time it guesses wrong about a\n * document somebody cannot restructure, the whole tool gets switched off. These\n * directives are the escape hatch, and they are HTML comments so they stay\n * invisible in GitHub, MkDocs, Docusaurus and every other renderer.\n *\n * ```md\n * <!-- @spec-node id=\"ADR-0007\" status=\"accepted\" aliases=\"adr-7, sharding\" -->\n * <!-- @spec-item id=\"shard-key\" state=\"narrowed\" -->\n * <!-- @spec-edge kind=\"delegates-to\" to=\"ADR-0011#scope\" -->\n * <!-- @spec-ignore -->\n * ```\n *\n * A directive always wins over anything inferred, and says so in the report, so\n * an override is visible rather than mysterious.\n */\n\nimport { isMarkdownLine, type HtmlComment, type ListItem, type ScannedDocument } from './markdown.js';\n\nexport type DirectiveName = 'spec-node' | 'spec-item' | 'spec-edge' | 'spec-ignore' | 'spec-history';\n\nconst KNOWN: ReadonlySet<string> = new Set<DirectiveName>([\n 'spec-node',\n 'spec-item',\n 'spec-edge',\n 'spec-ignore',\n 'spec-history',\n]);\n\nexport interface Attribute {\n readonly value: string;\n /** Absolute offset of the value text, quotes excluded. */\n readonly start: number;\n readonly end: number;\n}\n\nexport interface Directive {\n readonly name: DirectiveName;\n readonly attributes: ReadonlyMap<string, Attribute>;\n /** Offset of the enclosing comment. */\n readonly start: number;\n readonly end: number;\n readonly line: number;\n /** Attribute names that were written but are not part of this directive. */\n readonly unknownAttributes: readonly string[];\n}\n\nconst DIRECTIVE_HEAD = /^\\s*@([a-z][a-z0-9-]*)/;\nconst ATTRIBUTE = /([A-Za-z_][A-Za-z0-9_-]*)\\s*(?:=\\s*(\"([^\"]*)\"|'([^']*)'|([^\\s\"'<>]+)))?/g;\n\n/** Attributes each directive understands. Anything else is reported, not ignored. */\nconst SCHEMA: Readonly<Record<DirectiveName, readonly string[]>> = {\n 'spec-node': ['id', 'status', 'title', 'aliases', 'kind'],\n // Takes no attributes: the file either is a record or it is not. A mutant\n // that lists `Stryker was here` lists no name: one never holds a space.\n 'spec-history': [],\n 'spec-item': ['id', 'state', 'title', 'owner', 'note'],\n 'spec-edge': ['kind', 'to', 'from', 'reason'],\n 'spec-ignore': ['reason'],\n};\n\n/**\n * Reads every spec-graph directive out of a document's HTML comments.\n *\n * Only out of a comment that closes. One that opens a line and never does runs\n * to the end of the document, and read as a directive, a stray\n * `<!-- @spec-ignore` would drop the file, and a stray `<!-- @spec-node` would\n * take every `name=value` in the rest of it as an attribute.\n */\nexport function parseDirectives(comments: readonly HtmlComment[]): Directive[] {\n const out: Directive[] = [];\n for (const comment of comments) {\n if (comment.closed === false) continue;\n const head = DIRECTIVE_HEAD.exec(comment.inner);\n if (!head) continue;\n const name = head[1] as string;\n if (!KNOWN.has(name)) continue;\n\n const directiveName = name as DirectiveName;\n // The head is anchored, so it starts where the comment's text does.\n const bodyStart = (head[0] as string).length;\n const body = comment.inner.slice(bodyStart);\n const attributes = new Map<string, Attribute>();\n const unknown: string[] = [];\n const allowed = SCHEMA[directiveName];\n\n ATTRIBUTE.lastIndex = 0;\n for (let m = ATTRIBUTE.exec(body); m !== null; m = ATTRIBUTE.exec(body)) {\n const key = (m[1] as string).toLowerCase();\n const quoted = m[3] ?? m[4];\n const bare = m[5];\n // A bare attribute is a flag: `<!-- @spec-ignore reason -->`.\n const value = quoted ?? bare ?? 'true';\n const raw = m[2];\n const valueOffset =\n raw === undefined\n ? (m.index ?? 0) + (m[0] as string).length\n : (m.index ?? 0) + (m[0] as string).indexOf(raw) + (quoted === undefined ? 0 : 1);\n const start = comment.innerStart + bodyStart + valueOffset;\n\n if (!allowed.includes(key)) unknown.push(key);\n attributes.set(key, { value, start, end: start + value.length });\n }\n\n out.push({\n name: directiveName,\n attributes,\n start: comment.start,\n end: comment.end,\n line: comment.line,\n unknownAttributes: unknown,\n });\n }\n return out;\n}\n\n/** Reads an attribute, trimmed. Returns `null` when absent or blank. */\nexport function attr(directive: Directive, name: string): Attribute | null {\n const found = directive.attributes.get(name);\n if (!found) return null;\n const trimmed = found.value.trim();\n if (trimmed.length === 0) return null;\n const offset = found.value.indexOf(trimmed);\n return { value: trimmed, start: found.start + offset, end: found.start + offset + trimmed.length };\n}\n\n/** Splits a comma- or whitespace-separated attribute into its parts. */\nexport function attrList(directive: Directive, name: string): string[] {\n const found = attr(directive, name);\n if (!found) return [];\n // Whitespace is a separator, so no part holds any to trim. The filter drops\n // what a separator at either end leaves, and would drop what each separator\n // in a run leaves too, so splitting at one at a time is equivalent.\n return found.value.split(/[,;\\s]+/).filter((part) => part.length > 0);\n}\n\n/**\n * Finds the directive that annotates a given region.\n *\n * A `@spec-item` directive may sit on the line above the item, on the same\n * line, or inside the item body. All three read naturally in the source, so all\n * three are accepted, with the closest preceding directive winning.\n *\n * It answers for one region with no view of the others, so a directive in reach\n * of two items is found for both. Extraction uses `bindItemDirectives`.\n */\nexport function directiveFor(\n directives: readonly Directive[],\n name: DirectiveName,\n region: { start: number; end: number },\n /** How far above the region a directive may sit and still apply. */\n lookBehind: number,\n): Directive | null {\n let best: Directive | null = null;\n for (const directive of directives) {\n if (directive.name !== name) continue;\n const inside = directive.start >= region.start && directive.end <= region.end;\n const above = directive.end <= region.start && region.start - directive.end <= lookBehind;\n if (!inside && !above) continue;\n // Two directives never start at one offset, so `>=` reads the same.\n if (best === null || directive.start > best.start) best = directive;\n }\n return best;\n}\n\n/**\n * Pairs each `@spec-item` directive with the one list item it annotates.\n *\n * A directive is written for one item, so it binds to one: the item directly\n * below it, with nothing but blank lines and other comments between, or else\n * the item it is written in, the innermost where items nest. An item that\n * several bind to takes the last one written.\n *\n * A directive alone on its line is in an item only when indented further than\n * that item's marker, which is how CommonMark reads it. Indented under an item,\n * it annotates that item and not the sibling below; flush between two items, it\n * annotates the second.\n */\nexport function bindItemDirectives(\n scanned: ScannedDocument,\n directives: readonly Directive[],\n): Map<ListItem, Directive> {\n const bound = new Map<ListItem, Directive>();\n const wanted = directives.filter((directive) => directive.name === 'spec-item');\n // Most documents carry none. Sparing them the pass over their lines changes\n // how long this takes and nothing else.\n if (wanted.length === 0) return bound;\n\n const { lines, listItems } = scanned;\n const masked = scanned.masks.structure;\n // The indentation of each line holding nothing but whitespace and comments.\n // Code and raw-text HTML are masked too, so they are told apart by their flags.\n const quiet = new Map<number, number>();\n // For every other line, where the run of quiet lines directly above it began,\n // when there was one.\n const quietFrom = new Map<number, number | undefined>();\n let run: number | undefined;\n for (const line of lines) {\n if (isMarkdownLine(line) && masked.slice(line.contentStart, line.end).trim() === '') {\n quiet.set(line.line, line.indent);\n run ??= line.start;\n continue;\n }\n quietFrom.set(line.line, run);\n run = undefined;\n }\n\n // The items whose text the cursor is in, outermost first. Items nest, so one\n // that has ended is always on top. An item ends at the end of a line, the next\n // thing starts on a later one, and no two things start together, so `<` and\n // `<=` read alike in every comparison of offsets below.\n const open: ListItem[] = [];\n const close = (offset: number): void => {\n while (open.length > 0 && (open[open.length - 1] as ListItem).end <= offset) open.pop();\n };\n let next = 0;\n for (const directive of wanted) {\n while (next < listItems.length && (listItems[next] as ListItem).start < directive.start) {\n const item = listItems[next] as ListItem;\n close(item.start);\n open.push(item);\n next += 1;\n }\n close(directive.start);\n\n // Sharing its line with text, a directive is in whatever that text is in.\n const indent = quiet.get(directive.line) ?? Infinity;\n // No deeper than the scanner already went to find where these items end.\n let depth = open.length - 1;\n while (depth >= 0 && (open[depth] as ListItem).indent >= indent) depth -= 1;\n const holder = open[depth];\n\n const below = listItems[next];\n const directlyAbove = below !== undefined && (quietFrom.get(below.line) ?? Infinity) <= directive.start;\n if (directlyAbove && (holder === undefined || below.start < holder.end)) {\n bound.set(below, directive);\n continue;\n }\n // Flush under an item and above no other, it continues the item as any\n // unindented line does.\n const written = holder ?? open[open.length - 1];\n if (written !== undefined) bound.set(written, directive);\n }\n return bound;\n}\n"]}