@ai-matrx/content-ir 0.16.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +24 -0
- package/dist/index.cjs +113 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +113 -32
- package/dist/index.js.map +1 -1
- package/dist/source.cjs +113 -31
- package/dist/source.cjs.map +1 -1
- package/dist/source.d.cts +27 -2
- package/dist/source.d.ts +27 -2
- package/dist/source.js +113 -32
- package/dist/source.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0 — 2026-09-26
|
|
4
|
+
|
|
5
|
+
**`/source` streaming: what "settled" promises, pinned — and the one
|
|
6
|
+
documented exception** (verify-RC-B3 § Re-verification (0.16.0): G3, G6, H1,
|
|
7
|
+
H2). The verifier's mutations M1–M13 each turn
|
|
8
|
+
`__tests__/source-attack.test.ts` red ON ITS OWN — including the three
|
|
9
|
+
holdbacks the 0.16.0 suite could not see (M9 unclosed backtick, M10 open `{`
|
|
10
|
+
line, M12 pending `$$`), the `$$` open-tail rule (M11) and the held footnote
|
|
11
|
+
(M13). Unmutated: green in CI and in 10 consecutive long runs under 24 CPU
|
|
12
|
+
spinners.
|
|
13
|
+
|
|
14
|
+
- **G3** — the suite's bounds are counts, never the clock, so no test may be
|
|
15
|
+
failed by the clock either: an explicit suite-wide timeout (15 min CI, 1 h
|
|
16
|
+
long). 0.16.0's long mode died on vitest's default 5 s timeout while every
|
|
17
|
+
work assertion held.
|
|
18
|
+
- **G6** — each streaming holdback is pinned directly and by a flicker
|
|
19
|
+
message, and a new property checks, over random documents with and without
|
|
20
|
+
a final newline, that every island a stream shows as settled is the one-shot
|
|
21
|
+
island.
|
|
22
|
+
- **H1** — a footnote whose continuation is undecided (it ends the text, or
|
|
23
|
+
the next line is still arriving — even one that currently looks like a
|
|
24
|
+
block) is held open (`complete: false`), never shown settled.
|
|
25
|
+
- **H2 (chair ruling, 2026-09-26)** — a late orphan `</thinking>` /
|
|
26
|
+
`</think>` / `</reasoning>` closer is the ONE documented exception: it folds
|
|
27
|
+
the text before it into a thinking region retroactively. Consumers re-read
|
|
28
|
+
the finished text one-shot when a stream completes (README § streaming
|
|
29
|
+
mode); matrx-frontend's chat renderer does the same for its accumulator.
|
|
30
|
+
- The property found and fixed further settle-then-change classes, each now
|
|
31
|
+
held back in streaming mode: a lone allowed HTML tag at any block start on
|
|
32
|
+
the open line; an undecided single-`$` span (everything from its opener);
|
|
33
|
+
a code span the final paragraph may still close (`pendingCodeSpanStart`,
|
|
34
|
+
counting a single trailing newline as still-open) — held only for the `$$`
|
|
35
|
+
readings it can change; `$$` pairs that a completing backtick / tilde run
|
|
36
|
+
would re-pair. The paragraph's end is always computed exactly as one-shot
|
|
37
|
+
computes it; only what is reported stops at the provisional point.
|
|
38
|
+
|
|
39
|
+
**New (`/source` `findCodeRanges`, `CodeRange`): THE ONE code-range rule**
|
|
40
|
+
(RC-B10 one-canonical). Fenced blocks and inline code spans by CommonMark —
|
|
41
|
+
a fence opens only at line start (≤3 spaces) and closes by the one
|
|
42
|
+
fence-nesting rule; a code span pairs equal backtick runs within one
|
|
43
|
+
paragraph. Moved out of `math-pairs` (with `pendingCodeSpanStart`) so every
|
|
44
|
+
package asks one function: `@ai-matrx/kit` delimiter-guard and
|
|
45
|
+
`@ai-matrx/chat` speakable text now import it. Shared vectors:
|
|
46
|
+
`__tests__/fixtures/code-range-vectors.json` (read by content-ir and kit).
|
|
47
|
+
Guard: `__tests__/one-code-range-rule.test.ts` fails on any NEW fence/code-span
|
|
48
|
+
detection in `apps/shared` (shrink-only census of the existing walkers).
|
|
49
|
+
|
|
50
|
+
**Consumer action:** streaming consumers re-tokenize one-shot on completion
|
|
51
|
+
(the H2 contract). Stored text is unaffected.
|
|
52
|
+
|
|
3
53
|
## 0.16.2
|
|
4
54
|
|
|
5
55
|
- **Fixed (`/source` `pairDisplayMath`, `pendingDisplayMathOpener`):** a
|
|
6
56
|
`$$...$$` / `$$…$$` placeholder in prose was paired as display math. New
|
|
7
57
|
`isMathPlaceholder(inner)`; such a pair is consumed whole as literal text.
|
|
8
58
|
Same rule as `@ai-matrx/kit` `delimiter-guard`.
|
|
59
|
+
- **Fixed (`/source` `pairDisplayMath`): an inline triple backtick in prose
|
|
60
|
+
hid every later `$$` formula.** Code is now found the CommonMark way — a fence
|
|
61
|
+
opens only at line start (≤3 spaces), closes by the one fence-nesting rule
|
|
62
|
+
(`classifyInnerFenceLine`), and a code span pairs equal backtick runs inside
|
|
63
|
+
one paragraph. `tokenizeSource` math islands inherit it.
|
|
9
64
|
- Consumer action: none — refresh to latest.
|
|
10
65
|
|
|
11
66
|
## 0.16.1
|
package/README.md
CHANGED
|
@@ -43,6 +43,30 @@ import {
|
|
|
43
43
|
|
|
44
44
|
The package has no browser, React runtime, application store, database client, routing, or renderer dependency. `ajv` is its only runtime dependency. Host-specific registry fetching, component compilation, rendering, and error capture remain adapters around this kernel.
|
|
45
45
|
|
|
46
|
+
## `/source` streaming mode: what "settled" promises
|
|
47
|
+
|
|
48
|
+
`tokenizeSource(text)` reads stored text; `tokenizeSource(prefix, { streaming: true })`
|
|
49
|
+
reads a message still arriving. In streaming mode anything the next bytes could
|
|
50
|
+
still change is held back as plain prose (an open last line, an unclosed code
|
|
51
|
+
span, a `$$` that can still pair, a footnote whose continuation is undecided, …),
|
|
52
|
+
so **an island reported `complete` is the island the finished text has** — with
|
|
53
|
+
ONE documented exception:
|
|
54
|
+
|
|
55
|
+
- **A late orphan reasoning closer.** Models do emit closer-only reasoning (the
|
|
56
|
+
`<thinking>` opener lost upstream). When a `</thinking>` / `</think>` /
|
|
57
|
+
`</reasoning>` arrives with no opener, the one-shot reading folds everything
|
|
58
|
+
before it into a thinking region — retroactively. No streaming reader can
|
|
59
|
+
know that in advance short of holding ALL prose provisional until the stream
|
|
60
|
+
ends, which costs more than it saves (chair ruling, 2026-09-26; common-docs
|
|
61
|
+
`projects/rich-content-unification/REGISTER.md`, RC-B3 rulings).
|
|
62
|
+
|
|
63
|
+
**Consumer contract:** when a stream completes, re-read the finished text in
|
|
64
|
+
one-shot mode (`tokenizeSource(text)`, no `streaming`) and show THAT — the
|
|
65
|
+
final screen always equals the one-shot result, which is also what a reload
|
|
66
|
+
shows. Stored text never goes through streaming mode, so none of this can
|
|
67
|
+
change a stored byte. (matrx-frontend's chat renderer does the same for its
|
|
68
|
+
own live accumulator: `components/mardown-display/chat-markdown/settle-stream-blocks.ts`.)
|
|
69
|
+
|
|
46
70
|
## Release gate
|
|
47
71
|
|
|
48
72
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -5288,32 +5288,8 @@ function singleDollarMathEnd(text, open, limit = text.length) {
|
|
|
5288
5288
|
return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
|
|
5289
5289
|
}
|
|
5290
5290
|
|
|
5291
|
-
// source/
|
|
5292
|
-
|
|
5293
|
-
var STRUCTURAL_MARKDOWN = /\]\(|https?:\/\/|\*\*|(?:^|\n)[ \t]{0,3}#{1,6}[ \t]|(?:^|\n)[ \t]*[-*+][ \t]+|(?:^|\n)[ \t]*\d+[.)][ \t]/;
|
|
5294
|
-
var LATEX_COMMAND = /\\[a-zA-Z]/;
|
|
5295
|
-
var PROSE_WORD = /[A-Za-z]{3,}/g;
|
|
5296
|
-
var PROSE_WORD_LIMIT = 6;
|
|
5297
|
-
function isMathPlaceholder(inner) {
|
|
5298
|
-
return /^[\s.…·⋯_-]*$/.test(inner) && /[.…·⋯]/.test(inner);
|
|
5299
|
-
}
|
|
5300
|
-
function opensFlowBlock(text, open) {
|
|
5301
|
-
for (let k = open - 1; k >= 0 && text[k] !== "\n"; k--) if (text[k] !== " " && text[k] !== " ") return false;
|
|
5302
|
-
for (let k = open + 2; k < text.length && text[k] !== "\n"; k++) if (text[k] !== " " && text[k] !== " " && text[k] !== "\r") return false;
|
|
5303
|
-
return true;
|
|
5304
|
-
}
|
|
5305
|
-
function looksLikeDisplayMath(inner, flow = false) {
|
|
5306
|
-
const s = inner.trim();
|
|
5307
|
-
if (!s) return false;
|
|
5308
|
-
if (STRUCTURAL_MARKDOWN.test(s)) return false;
|
|
5309
|
-
if (s.length > MAX_MATH_SPAN) return false;
|
|
5310
|
-
if (!flow && /\n[ \t]*\n/.test(inner)) return false;
|
|
5311
|
-
if (LATEX_COMMAND.test(s)) {
|
|
5312
|
-
return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT * 3;
|
|
5313
|
-
}
|
|
5314
|
-
return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT;
|
|
5315
|
-
}
|
|
5316
|
-
function codeRanges(text) {
|
|
5291
|
+
// source/code-ranges.ts
|
|
5292
|
+
function findCodeRanges(text) {
|
|
5317
5293
|
const ranges = [];
|
|
5318
5294
|
const n = text.length;
|
|
5319
5295
|
let fence = null;
|
|
@@ -5345,7 +5321,7 @@ function codeRanges(text) {
|
|
|
5345
5321
|
}
|
|
5346
5322
|
const [s0] = runs[r];
|
|
5347
5323
|
const [s1, l1] = runs[j];
|
|
5348
|
-
ranges.push(
|
|
5324
|
+
ranges.push({ start: s0, end: s1 + l1, kind: "span" });
|
|
5349
5325
|
r = j + 1;
|
|
5350
5326
|
}
|
|
5351
5327
|
};
|
|
@@ -5371,7 +5347,7 @@ function codeRanges(text) {
|
|
|
5371
5347
|
closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
|
|
5372
5348
|
}
|
|
5373
5349
|
if (closes) {
|
|
5374
|
-
ranges.push(
|
|
5350
|
+
ranges.push({ start: fence.start, end: lineEnd, kind: "fence" });
|
|
5375
5351
|
fence = null;
|
|
5376
5352
|
}
|
|
5377
5353
|
} else {
|
|
@@ -5391,9 +5367,106 @@ function codeRanges(text) {
|
|
|
5391
5367
|
if (nl === -1) break;
|
|
5392
5368
|
pos = nl + 1;
|
|
5393
5369
|
}
|
|
5394
|
-
if (fence) ranges.push(
|
|
5370
|
+
if (fence) ranges.push({ start: fence.start, end: n, kind: "fence" });
|
|
5395
5371
|
else if (para !== -1) spans(para, n);
|
|
5396
|
-
return ranges.sort((a, b) => a
|
|
5372
|
+
return ranges.sort((a, b) => a.start - b.start);
|
|
5373
|
+
}
|
|
5374
|
+
function pendingCodeSpanStart(text) {
|
|
5375
|
+
const n = text.length;
|
|
5376
|
+
if (n === 0 || /\n[ \t]*\n[ \t]*$/.test(text)) return -1;
|
|
5377
|
+
let fence = null;
|
|
5378
|
+
let para = -1;
|
|
5379
|
+
let pos = 0;
|
|
5380
|
+
while (pos <= n) {
|
|
5381
|
+
const nl = text.indexOf("\n", pos);
|
|
5382
|
+
const lineEnd = nl === -1 ? n : nl;
|
|
5383
|
+
const line = text.slice(pos, lineEnd);
|
|
5384
|
+
let indent = 0;
|
|
5385
|
+
while (indent < line.length && line[indent] === " ") indent++;
|
|
5386
|
+
const body = line.slice(indent);
|
|
5387
|
+
if (fence) {
|
|
5388
|
+
const trimmed = trimFenceLine(line);
|
|
5389
|
+
let closes = false;
|
|
5390
|
+
if (fence.char === "`") {
|
|
5391
|
+
const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
|
|
5392
|
+
if (kind === "open-nested") fence.depth++;
|
|
5393
|
+
else if (kind === "close-nested") fence.depth--;
|
|
5394
|
+
else if (kind === "close-outer") closes = indent <= 3;
|
|
5395
|
+
} else {
|
|
5396
|
+
let t = 0;
|
|
5397
|
+
while (t < trimmed.length && trimmed[t] === "~") t++;
|
|
5398
|
+
closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
|
|
5399
|
+
}
|
|
5400
|
+
if (closes) fence = null;
|
|
5401
|
+
} else {
|
|
5402
|
+
const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
|
|
5403
|
+
const info = opener ? opener[2] : "";
|
|
5404
|
+
if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
|
|
5405
|
+
para = -1;
|
|
5406
|
+
const char = opener[1][0];
|
|
5407
|
+
const parsed = char === "`" ? parseFenceOpener(line) : null;
|
|
5408
|
+
fence = { char, len: opener[1].length, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
|
|
5409
|
+
} else if (body.trim() === "") {
|
|
5410
|
+
if (!(nl === -1 && line === "")) para = -1;
|
|
5411
|
+
} else if (para === -1) para = pos;
|
|
5412
|
+
}
|
|
5413
|
+
if (nl === -1) break;
|
|
5414
|
+
pos = nl + 1;
|
|
5415
|
+
}
|
|
5416
|
+
if (fence || para === -1) return -1;
|
|
5417
|
+
const runs = [];
|
|
5418
|
+
for (let k = para; k < n; ) {
|
|
5419
|
+
if (text.charCodeAt(k) !== 96) {
|
|
5420
|
+
k++;
|
|
5421
|
+
continue;
|
|
5422
|
+
}
|
|
5423
|
+
let len = 1;
|
|
5424
|
+
while (k + len < n && text.charCodeAt(k + len) === 96) len++;
|
|
5425
|
+
runs.push([k, len]);
|
|
5426
|
+
k += len;
|
|
5427
|
+
}
|
|
5428
|
+
const growing = (r) => {
|
|
5429
|
+
const [start, len] = runs[r];
|
|
5430
|
+
return start + len === n;
|
|
5431
|
+
};
|
|
5432
|
+
for (let r = 0; r < runs.length; ) {
|
|
5433
|
+
if (growing(r)) return runs[r][0];
|
|
5434
|
+
const len = runs[r][1];
|
|
5435
|
+
let j = r + 1;
|
|
5436
|
+
while (j < runs.length && (runs[j][1] !== len || growing(j))) j++;
|
|
5437
|
+
if (j >= runs.length) return runs[r][0];
|
|
5438
|
+
r = j + 1;
|
|
5439
|
+
}
|
|
5440
|
+
return -1;
|
|
5441
|
+
}
|
|
5442
|
+
|
|
5443
|
+
// source/math-pairs.ts
|
|
5444
|
+
var MAX_MATH_SPAN = 600;
|
|
5445
|
+
var STRUCTURAL_MARKDOWN = /\]\(|https?:\/\/|\*\*|(?:^|\n)[ \t]{0,3}#{1,6}[ \t]|(?:^|\n)[ \t]*[-*+][ \t]+|(?:^|\n)[ \t]*\d+[.)][ \t]/;
|
|
5446
|
+
var LATEX_COMMAND = /\\[a-zA-Z]/;
|
|
5447
|
+
var PROSE_WORD = /[A-Za-z]{3,}/g;
|
|
5448
|
+
var PROSE_WORD_LIMIT = 6;
|
|
5449
|
+
function isMathPlaceholder(inner) {
|
|
5450
|
+
return /^[\s.…·⋯_-]*$/.test(inner) && /[.…·⋯]/.test(inner);
|
|
5451
|
+
}
|
|
5452
|
+
function opensFlowBlock(text, open) {
|
|
5453
|
+
for (let k = open - 1; k >= 0 && text[k] !== "\n"; k--) if (text[k] !== " " && text[k] !== " ") return false;
|
|
5454
|
+
for (let k = open + 2; k < text.length && text[k] !== "\n"; k++) if (text[k] !== " " && text[k] !== " " && text[k] !== "\r") return false;
|
|
5455
|
+
return true;
|
|
5456
|
+
}
|
|
5457
|
+
function looksLikeDisplayMath(inner, flow = false) {
|
|
5458
|
+
const s = inner.trim();
|
|
5459
|
+
if (!s) return false;
|
|
5460
|
+
if (STRUCTURAL_MARKDOWN.test(s)) return false;
|
|
5461
|
+
if (s.length > MAX_MATH_SPAN) return false;
|
|
5462
|
+
if (!flow && /\n[ \t]*\n/.test(inner)) return false;
|
|
5463
|
+
if (LATEX_COMMAND.test(s)) {
|
|
5464
|
+
return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT * 3;
|
|
5465
|
+
}
|
|
5466
|
+
return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT;
|
|
5467
|
+
}
|
|
5468
|
+
function codeRanges(text) {
|
|
5469
|
+
return findCodeRanges(text).map((r) => [r.start, r.end]);
|
|
5397
5470
|
}
|
|
5398
5471
|
function pendingDisplayMathOpener(text) {
|
|
5399
5472
|
if (!text.includes("$$")) return -1;
|
|
@@ -5805,6 +5878,14 @@ var Tokenizer = class {
|
|
|
5805
5878
|
this.provisionalFrom = streaming ? pendingDisplayMathOpener(text) : -1;
|
|
5806
5879
|
if (this.provisionalFrom === -1) this.provisionalFrom = Number.POSITIVE_INFINITY;
|
|
5807
5880
|
if (streaming && text.includes("$$")) {
|
|
5881
|
+
const span = pendingCodeSpanStart(text);
|
|
5882
|
+
if (span !== -1) {
|
|
5883
|
+
const firstToken = text.indexOf("$$", span);
|
|
5884
|
+
if (firstToken !== -1) this.provisionalFrom = Math.min(this.provisionalFrom, firstToken);
|
|
5885
|
+
for (const [open, close] of this.mathPairs) {
|
|
5886
|
+
if (close >= span) this.provisionalFrom = Math.min(this.provisionalFrom, open);
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5808
5889
|
for (const pad of ["`", "``", "```", "~~~"]) {
|
|
5809
5890
|
const alt = pairDisplayMath(text + pad);
|
|
5810
5891
|
for (const [open, close] of this.mathPairs) {
|
|
@@ -6160,8 +6241,8 @@ var Tokenizer = class {
|
|
|
6160
6241
|
last = k;
|
|
6161
6242
|
continue;
|
|
6162
6243
|
}
|
|
6163
|
-
if (FOOTNOTE_DEF.test(line.trimStart()) || this.detectBlock(this.lineStarts[k], k, false)) break;
|
|
6164
6244
|
if (this.isOpenTail(k)) return this.heldFootnote(p, open[1]);
|
|
6245
|
+
if (FOOTNOTE_DEF.test(line.trimStart()) || this.detectBlock(this.lineStarts[k], k, false)) break;
|
|
6165
6246
|
last = k;
|
|
6166
6247
|
}
|
|
6167
6248
|
const end = this.contentEnds[last];
|
|
@@ -7211,6 +7292,7 @@ exports.executesAtOutputRoot = executesAtOutputRoot;
|
|
|
7211
7292
|
exports.fenceDiscriminator = fenceDiscriminator;
|
|
7212
7293
|
exports.fenceNestsInnerFences = fenceNestsInnerFences;
|
|
7213
7294
|
exports.fieldsToDbPayload = fieldsToDbPayload;
|
|
7295
|
+
exports.findCodeRanges = findCodeRanges;
|
|
7214
7296
|
exports.fingerprintText = fingerprintText;
|
|
7215
7297
|
exports.formatBlockLabel = formatBlockLabel;
|
|
7216
7298
|
exports.getParseSession = getParseSession;
|