@descent-vtt/spec-graph 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/extract.js CHANGED
@@ -351,13 +351,39 @@ const NEGATION = /(?:^|[^\p{L}\p{N}-])(?:no|not|no-one|nobody|none|never|neither
351
351
  * by" are read, and "not only blocked by" and "nothing but blocked by",
352
352
  * which state the relation, are not. Only a space stands between, so a
353
353
  * mark keeps the word to itself: the option `"never" blocked by` states the
354
- * relation, as "Not in this release: deferred to" does. A comma is blanked
355
- * before this is read, as it was, so "If not, blocked by" is cancelled: a
356
- * miss, kept so that a comma moves no phrase's reach. A contraction is a
357
- * negation, `isn't` and `doesn’t`, and so is `cannot`. Tested against the
358
- * text just before the phrase, which ends with the space before it.
354
+ * relation, as "Not in this release: deferred to" does. A comma does too,
355
+ * and ends the clause the word belongs to: "If not, blocked by" and "both
356
+ * or neither, because" state the relation, where with the comma blanked
357
+ * they were cancelled (ADR-0006, 2026-10-19). A contraction is a negation,
358
+ * `isn't` and `doesn’t`, and so is `cannot`. Tested against the text just
359
+ * before the phrase, which ends with the space or the comma before it.
360
+ *
361
+ * An aside between the two, closed by a comma of its own, leaves the clause
362
+ * open: "Not, as it happens, blocked by", "never, ever, blocked by" and "it
363
+ * is not, as far as anyone knows, blocked by" deny the relation as the words
364
+ * without the aside do. The aside is anything up to a comma, and may hold
365
+ * several, so the negation may also end a clause the phrase is not in -
366
+ * "if not, then, blocked by" - and the relation such a sentence states is
367
+ * missed, which ADR-0006 prefers to one it denies (2026-10-24). The same few
368
+ * words may stand after the last comma, "not, in the end, been superseded
369
+ * by". A run of spaces and commas is one comma in the text this is tested
370
+ * on, so no aside is empty and `.*` would read as `.+` does; and only
371
+ * whether the pattern matches is asked, so a lazy `.+?` reads alike too.
372
+ */
373
+ const NEGATED_BEFORE = /(?:^|[^\p{L}\p{N}-])(?:no|not|no one|no-one|nobody|none|never|neither|nor|nothing|without|cannot|\p{L}+n['’]t)(?: (?:be|been|being|is|are|was|were|yet|longer|ever|even|currently|actually|really|necessarily|always))*(?: |,.+,(?:(?:be|been|being|is|are|was|were|yet|longer|ever|even|currently|actually|really|necessarily|always) )*)$/u;
374
+ /**
375
+ * A word that points the reader at the reference, and governs it as a
376
+ * citation from nearer than any phrase before it: "Per tick, see [ADR-7]"
377
+ * read ADR-7 as a premise, and "blocked by the cache, see [ADR-7]" read it
378
+ * as what blocks. `see` is no phrase for the vagueness that makes it a
379
+ * citation, and between a phrase and the reference it says the reference is
380
+ * where to read more, not what the phrase names (ADR-0006, 2026-10-24). It
381
+ * is a word as a negation is, between anything but a letter, a digit or a
382
+ * hyphen, so "the must-see list", "the licensee" and "seen in" point at
383
+ * nothing. The text it is tested on starts just after a phrase, where no
384
+ * letter stands, so the word is never its first and needs no start of text.
359
385
  */
360
- const NEGATED_BEFORE = /(?:^|[^\p{L}\p{N}-])(?:no|not|no one|no-one|nobody|none|never|neither|nor|nothing|without|cannot|\p{L}+n['’]t)(?: (?:be|been|being|is|are|was|were|yet|longer|ever|even|currently|actually|really|necessarily|always))* $/u;
386
+ const POINTER = /[^\p{L}\p{N}-]see(?![\p{L}\p{N}-])/u;
361
387
  /**
362
388
  * A code span that holds nothing but a negation word: `` `none` ``.
363
389
  *
@@ -1276,7 +1302,7 @@ function ownerOf(items, offset) {
1276
1302
  * neutral citation.
1277
1303
  */
1278
1304
  export function classifyReference(text, start, end, section) {
1279
- const before = sentenceBefore(text, start);
1305
+ const { words: before, commas } = sentenceBefore(text, start);
1280
1306
  // Left to right, so the last match within the window is the nearest one - and
1281
1307
  // the nearest governing phrase is the one that governs. `matchAll`, not a
1282
1308
  // loop over `exec`: an empty match leaves `lastIndex` where it began, and
@@ -1292,11 +1318,20 @@ export function classifyReference(text, start, end, section) {
1292
1318
  continue;
1293
1319
  // What sits between the phrase and the reference has to be connective. A
1294
1320
  // negation in there reverses the claim the phrase would otherwise make,
1295
- // and so does one just before the phrase.
1321
+ // and so does one just before the phrase, where no comma ends its clause
1322
+ // first. A pointer in there governs the reference itself, as a citation.
1296
1323
  if (NEGATION.test(before.slice(end)))
1297
1324
  continue;
1298
- if (NEGATED_BEFORE.test(before.slice(0, m.index)))
1325
+ if (POINTER.test(before.slice(end)))
1326
+ continue;
1327
+ // A negation before a phrase stands between any phrase before it and the
1328
+ // reference, and denies what follows it: a phrase it leaves before an
1329
+ // aside governs no more than the one it cancels, so "Not, as decided in
1330
+ // [ADR-3], blocked by [ADR-7]" makes ADR-7 a premise of nothing.
1331
+ if (NEGATED_BEFORE.test(commas.slice(0, m.index))) {
1332
+ best = null;
1299
1333
  continue;
1334
+ }
1300
1335
  // Always found: the pattern was compiled from this table's own keys, and a
1301
1336
  // lower-cased search matches them only as written. The check is for types.
1302
1337
  const rule = VERB_LOOKUP.get(phrase);
@@ -1358,17 +1393,26 @@ const STATEMENT_BREAK = /[.?!;]\s|[。?!;]|\n\s*\n|\n\s*[-*+>#]|\|/g;
1358
1393
  * names a word, and `isn't` is one word. Kept, each counts toward the
1359
1394
  * distance a phrase reaches where it stands beside a space, as a curly one
1360
1395
  * did (ADR-0006, 2026-10-18).
1396
+ *
1397
+ * The same text is given a second time with each run of spaces that held a
1398
+ * comma written as the comma: one character either way, so the two line up
1399
+ * and a comma counts toward no phrase's reach, and a negation before a
1400
+ * phrase is read from the second, where a comma keeps the two apart
1401
+ * (ADR-0006, 2026-10-19).
1361
1402
  */
1362
1403
  function sentenceBefore(text, start) {
1363
1404
  const window = text.slice(Math.max(0, start - 160), start);
1364
1405
  let cut = 0;
1365
1406
  for (const m of window.matchAll(STATEMENT_BREAK))
1366
1407
  cut = m.index + m[0].length;
1367
- return window
1408
+ const statement = window
1368
1409
  .slice(cut)
1369
1410
  .toLowerCase()
1370
- .replace(/[`*_~()\[\],]/g, ' ')
1371
- .replace(/\s+/g, ' ');
1411
+ .replace(/[`*_~()\[\]]/g, ' ');
1412
+ return {
1413
+ words: statement.replace(/[\s,]+/g, ' '),
1414
+ commas: statement.replace(/[\s,]+/g, (run) => (run.includes(',') ? ',' : ' ')),
1415
+ };
1372
1416
  }
1373
1417
  /**
1374
1418
  * The statement after a reference, to the first stop, the stops of a sentence