@descent-vtt/spec-graph 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +477 -0
  2. package/README.md +109 -17
  3. package/dist/cli.d.ts +2 -2
  4. package/dist/cli.js +38 -19
  5. package/dist/cli.js.map +1 -1
  6. package/dist/directives.d.ts +9 -2
  7. package/dist/directives.js +15 -4
  8. package/dist/directives.js.map +1 -1
  9. package/dist/extract.d.ts +1 -1
  10. package/dist/extract.js +50 -16
  11. package/dist/extract.js.map +1 -1
  12. package/dist/glob.d.ts +44 -44
  13. package/dist/glob.js +152 -116
  14. package/dist/glob.js.map +1 -1
  15. package/dist/identity.d.ts +12 -4
  16. package/dist/identity.js +32 -8
  17. package/dist/identity.js.map +1 -1
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +4 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/lifecycle.js +10 -2
  22. package/dist/lifecycle.js.map +1 -1
  23. package/dist/markdown.d.ts +53 -161
  24. package/dist/markdown.js +42 -864
  25. package/dist/markdown.js.map +1 -1
  26. package/dist/report.d.ts +23 -0
  27. package/dist/report.js +80 -0
  28. package/dist/report.js.map +1 -1
  29. package/dist/rules.js +48 -14
  30. package/dist/rules.js.map +1 -1
  31. package/dist/sections.d.ts +1 -1
  32. package/dist/sections.js +18 -4
  33. package/dist/sections.js.map +1 -1
  34. package/dist/select.d.ts +2 -2
  35. package/dist/select.js +3 -3
  36. package/dist/select.js.map +1 -1
  37. package/dist/source.d.ts +6 -19
  38. package/dist/source.js +4 -70
  39. package/dist/source.js.map +1 -1
  40. package/dist/types.d.ts +23 -3
  41. package/dist/types.js +1 -1
  42. package/dist/types.js.map +1 -1
  43. package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
  44. package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
  45. package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
  46. package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
  47. package/dist/vendor/spec-core/markdown/headings.js +199 -0
  48. package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
  49. package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
  50. package/dist/vendor/spec-core/markdown/index.js +4 -0
  51. package/dist/vendor/spec-core/markdown/index.js.map +1 -0
  52. package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
  53. package/dist/vendor/spec-core/markdown/layout.js +28 -0
  54. package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
  55. package/dist/vendor/spec-core/markdown/links.d.ts +22 -0
  56. package/dist/vendor/spec-core/markdown/links.js +364 -0
  57. package/dist/vendor/spec-core/markdown/links.js.map +1 -0
  58. package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
  59. package/dist/vendor/spec-core/markdown/lists.js +127 -0
  60. package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
  61. package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
  62. package/dist/vendor/spec-core/markdown/scan.js +439 -0
  63. package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
  64. package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
  65. package/dist/vendor/spec-core/markdown/syntax.js +181 -0
  66. package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
  67. package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
  68. package/dist/vendor/spec-core/markdown/tables.js +86 -0
  69. package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
  70. package/dist/vendor/spec-core/markdown/types.d.ts +287 -0
  71. package/dist/vendor/spec-core/markdown/types.js +9 -0
  72. package/dist/vendor/spec-core/markdown/types.js.map +1 -0
  73. package/dist/vendor/spec-core/path/index.d.ts +1 -0
  74. package/dist/vendor/spec-core/path/index.js +2 -0
  75. package/dist/vendor/spec-core/path/index.js.map +1 -0
  76. package/dist/vendor/spec-core/path/posix.d.ts +83 -0
  77. package/dist/vendor/spec-core/path/posix.js +150 -0
  78. package/dist/vendor/spec-core/path/posix.js.map +1 -0
  79. package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
  80. package/dist/vendor/spec-core/pattern/automaton.js +340 -0
  81. package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
  82. package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
  83. package/dist/vendor/spec-core/pattern/charset.js +116 -0
  84. package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
  85. package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
  86. package/dist/vendor/spec-core/pattern/glob.js +481 -0
  87. package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
  88. package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
  89. package/dist/vendor/spec-core/pattern/index.js +4 -0
  90. package/dist/vendor/spec-core/pattern/index.js.map +1 -0
  91. package/dist/{regex.d.ts → vendor/spec-core/pattern/regex.d.ts} +20 -29
  92. package/dist/{regex.js → vendor/spec-core/pattern/regex.js} +40 -49
  93. package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
  94. package/dist/vendor/spec-core/text/index.d.ts +2 -0
  95. package/dist/vendor/spec-core/text/index.js +2 -0
  96. package/dist/vendor/spec-core/text/index.js.map +1 -0
  97. package/dist/vendor/spec-core/text/lines.d.ts +72 -0
  98. package/dist/vendor/spec-core/text/lines.js +156 -0
  99. package/dist/vendor/spec-core/text/lines.js.map +1 -0
  100. package/dist/yaml.d.ts +44 -12
  101. package/dist/yaml.js +115 -206
  102. package/dist/yaml.js.map +1 -1
  103. package/package.json +3 -2
  104. package/src/vendor/spec-core/LICENSE +21 -0
  105. package/src/vendor/spec-core/README.md +8 -0
  106. package/dist/regex.js.map +0 -1
package/dist/extract.js CHANGED
@@ -17,11 +17,11 @@
17
17
  import { attr, attrList, bindItemDirectives, parseDirectives } from './directives.js';
18
18
  import { identify, isDocumentTarget, isExternal, looksLikePath, normaliseRef, parsePrefixedRef, withinOneEdit, ID_KEYS, } from './identity.js';
19
19
  import { isStatusHeading, phaseFromPath, phaseOf, STATUS_KEYS, supersessionTargetsIn } from './lifecycle.js';
20
- import { scanMarkdown, slugify } from './markdown.js';
20
+ import { anchorsOf, isMarkdownLine, isOnlyComment, referenceLinks, scanMarkdown, slugify, } from './markdown.js';
21
21
  import { findSpecificationRegions, regionAt } from './sections.js';
22
22
  import { resolveItemState } from './state.js';
23
23
  import { refOf } from './source.js';
24
- import { parseFrontMatter, toRecord, valuesOf } from './yaml.js';
24
+ import { readEntries, toRecord, valuesOf } from './yaml.js';
25
25
  /* -------------------------------------------------------------------------- */
26
26
  /* Vocabulary */
27
27
  /* -------------------------------------------------------------------------- */
@@ -158,6 +158,19 @@ const RELATION_INDEX = new Map(Object.entries(RELATION_KEYS).map(([canonical, re
158
158
  foldRelationKey(canonical),
159
159
  { ...relation, canonical },
160
160
  ]));
161
+ /**
162
+ * Whether a front-matter key is one the graph is built from: a status, an id
163
+ * or an alias, or a relation. Left unread, each changes a verdict - a status
164
+ * is unknown, a node is named by its path, an edge is missing. A title or a
165
+ * description left unread changes a label.
166
+ */
167
+ function buildsGraph(key) {
168
+ return (STATUS_KEYS.includes(key) ||
169
+ ID_KEYS.includes(key) ||
170
+ key === 'alias' ||
171
+ key === 'aliases' ||
172
+ RELATION_INDEX.has(foldRelationKey(key)));
173
+ }
161
174
  /**
162
175
  * Keys a region must answer for itself, so the file's answer cannot shadow one.
163
176
  *
@@ -476,6 +489,17 @@ export function extractDocument(input) {
476
489
  at: at(nul, nul + 1),
477
490
  });
478
491
  }
492
+ // CommonMark reads a comment that opens a line and never closes as running
493
+ // to the end of the document, and so does the scan: nothing after it is a
494
+ // heading, an item or a link. A renderer hides it all, so the author may see
495
+ // it, but the graph would lose it without a word.
496
+ const unclosed = scanned.comments.find((comment) => !comment.closed);
497
+ if (unclosed !== undefined) {
498
+ problems.push({
499
+ message: 'a comment opened here is never closed, so nothing after it is read - close it with -->',
500
+ at: at(unclosed.start, unclosed.start + 4),
501
+ });
502
+ }
479
503
  for (const directive of directives) {
480
504
  for (const unknown of directive.unknownAttributes) {
481
505
  problems.push({
@@ -484,7 +508,13 @@ export function extractDocument(input) {
484
508
  });
485
509
  }
486
510
  }
487
- const entries = scanned.frontMatter ? parseFrontMatter(scanned.frontMatter.raw, scanned.frontMatter.start) : [];
511
+ const { entries, problems: unread } = readEntries(scanned);
512
+ for (const problem of unread) {
513
+ // A line that names no key could have held any of them, and so could a
514
+ // block that was not read at all.
515
+ const lost = problem.unread && (problem.key === null || buildsGraph(problem.key));
516
+ problems.push({ message: problem.message, at: at(problem.start, problem.end), ...(lost ? { unread: true } : {}) });
517
+ }
488
518
  const byKey = new Map();
489
519
  for (const entry of entries)
490
520
  byKey.set(entry.key, entry);
@@ -534,6 +564,7 @@ export function extractDocument(input) {
534
564
  path: input.path,
535
565
  aliases: identity.aliases,
536
566
  phase,
567
+ history: record,
537
568
  rawStatus: status.raw,
538
569
  statusAt: status.at,
539
570
  frontMatter: toRecord(entries),
@@ -587,7 +618,7 @@ export function extractDocument(input) {
587
618
  const ownItems = allItems.filter((item) => item.document === identity.id);
588
619
  const ownReferences = allReferences.filter((reference) => belongsTo(reference, identity.id, allItems));
589
620
  const misreadKeys = misreadRelationKeys(byKey, identity.id, at);
590
- const anchors = new Set(scanned.headings.map((h) => h.slug));
621
+ const anchors = anchorsOf(scanned.headings);
591
622
  const filled = subSpecifications.map((spec) => ({
592
623
  ...spec.extracted,
593
624
  items: allItems.filter((item) => item.document === spec.document.id),
@@ -659,11 +690,12 @@ function buildRegion(context) {
659
690
  // wrote for itself. Being a record is a statement about the file made from
660
691
  // outside it, and it does not stop at a heading.
661
692
  phase: record ? 'record' : declaredPhase !== 'unknown' ? declaredPhase : pathPhase,
693
+ history: record,
662
694
  rawStatus,
663
695
  statusAt,
664
696
  frontMatter: inherited,
665
697
  };
666
- const anchors = new Set(scanned.headings.filter((h) => h.start >= region.start && h.start < region.end).map((h) => h.slug));
698
+ const anchors = anchorsOf(scanned.headings.filter((h) => h.start >= region.start && h.start < region.end));
667
699
  // Relations a table declares by column, typed by the header the author wrote.
668
700
  const references = region.relations.map((relation) => ({
669
701
  kind: relation.kind,
@@ -744,9 +776,9 @@ function statusSectionBody(scanned, headingLine) {
744
776
  for (const line of scanned.lines) {
745
777
  if (line.line <= headingLine)
746
778
  continue;
747
- if (line.blank)
779
+ if (line.blank || isOnlyComment(scanned, line))
748
780
  continue;
749
- if (line.code)
781
+ if (!isMarkdownLine(line))
750
782
  return null;
751
783
  const trimmed = line.content.trim();
752
784
  // A heading immediately after means the section is empty.
@@ -960,7 +992,8 @@ function extractReferences(context) {
960
992
  // so `| ADR-0002 | ... | [ADR-0001](0001.md) |` yields one typed edge rather
961
993
  // than a typed edge and a neutral citation beside it.
962
994
  const linked = new Set();
963
- for (const link of scanned.links) {
995
+ const links = referenceLinks(scanned);
996
+ for (const link of links) {
964
997
  if (link.form === 'definition')
965
998
  continue;
966
999
  if (isClaimed(claimed, link.start))
@@ -972,14 +1005,14 @@ function extractReferences(context) {
972
1005
  continue;
973
1006
  const owner = ownerOf(items, link.start) ?? documentAt(link.start).id;
974
1007
  const section = sectionPathAt(scanned, link.start);
975
- const classified = classifyReference(scanned.masked, link.start, link.end, section);
1008
+ const classified = classifyReference(scanned.masks.structure, link.start, link.end, section);
976
1009
  linked.add(normaliseRef(stripAnchor(target)));
977
1010
  out.push({
978
1011
  kind: classified.kind,
979
1012
  from: owner,
980
1013
  target,
981
1014
  origin: 'link',
982
- declaredAt: at(link.targetStart, link.targetStart + link.target.length),
1015
+ declaredAt: at(link.targetStart, link.targetEnd),
983
1016
  raw: renderLink(link),
984
1017
  inverted: classified.inverted,
985
1018
  opportunistic: false,
@@ -987,7 +1020,7 @@ function extractReferences(context) {
987
1020
  }
988
1021
  // Bare identifiers in prose. Everything already covered by a link is skipped
989
1022
  // so a citation written as `[ADR-7](0007.md)` is not counted twice.
990
- for (const bare of findBareReferences(scanned)) {
1023
+ for (const bare of findBareReferences(scanned, links)) {
991
1024
  const key = normaliseRef(bare.text);
992
1025
  if (linked.has(key))
993
1026
  continue;
@@ -995,7 +1028,7 @@ function extractReferences(context) {
995
1028
  continue;
996
1029
  const owner = ownerOf(items, bare.start) ?? documentAt(bare.start).id;
997
1030
  const section = sectionPathAt(scanned, bare.start);
998
- const classified = classifyReference(scanned.masked, bare.start, bare.end, section);
1031
+ const classified = classifyReference(scanned.masks.structure, bare.start, bare.end, section);
999
1032
  out.push({
1000
1033
  kind: classified.kind,
1001
1034
  from: owner,
@@ -1167,15 +1200,16 @@ function sentenceAfter(text, end) {
1167
1200
  * Link constructs are blanked first. Without that, `[ADR-7](https://x/adr-7)`
1168
1201
  * would yield three references to the same thing - one real and two harvested
1169
1202
  * out of a URL - which is precisely the mis-match that makes hand-rolled regex
1170
- * checks untrustworthy.
1203
+ * checks untrustworthy. Images are not blanked, since they are not references:
1204
+ * an identifier in one's text is prose, as it always was here.
1171
1205
  */
1172
- function findBareReferences(scanned) {
1206
+ function findBareReferences(scanned, links) {
1173
1207
  // Slice-based for the same reason as the scanner's masking: this runs over
1174
1208
  // every document, and a per-character array is the wrong shape for the job.
1175
- const source = scanned.masked;
1209
+ const source = scanned.masks.structure;
1176
1210
  let text = '';
1177
1211
  let cursor = 0;
1178
- for (const link of scanned.links) {
1212
+ for (const link of links) {
1179
1213
  const start = Math.max(link.start, cursor);
1180
1214
  const end = Math.min(link.end, source.length);
1181
1215
  if (end <= start)