@ai-matrx/content-ir 0.12.0 → 0.13.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/index.js CHANGED
@@ -5169,6 +5169,29 @@ function normalizeFenceLanguage(language) {
5169
5169
  return CODE_LANGUAGE_ALIASES[lower] ?? lower;
5170
5170
  }
5171
5171
 
5172
+ // source/fence-nesting.ts
5173
+ var NESTING_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
5174
+ "markdown",
5175
+ "md",
5176
+ "mdx"
5177
+ ]);
5178
+ function fenceNestsInnerFences(language) {
5179
+ return !!language && NESTING_FENCE_LANGUAGES.has(language.toLowerCase());
5180
+ }
5181
+ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
5182
+ let ticks = 0;
5183
+ while (ticks < trimmed.length && trimmed[ticks] === "`") ticks++;
5184
+ if (ticks < 3) return "content";
5185
+ const info = trimmed.slice(ticks).trim();
5186
+ if (info === "") {
5187
+ if (ticks < openTicks) return "content";
5188
+ if (nests && nestedDepth > 0) return "close-nested";
5189
+ return "close-outer";
5190
+ }
5191
+ if (nests && ticks >= openTicks && !info.includes("`")) return "open-nested";
5192
+ return "content";
5193
+ }
5194
+
5172
5195
  // source/math.ts
5173
5196
  var TEX_SIGNAL = /\\[A-Za-z]+|[\\^_{}=<>+]/;
5174
5197
  var LONE_VARIABLE = /^[A-Za-z](?:'|[0-9])?$/;
@@ -5181,8 +5204,9 @@ function isSingleDollarMath(content, after) {
5181
5204
  return TEX_SIGNAL.test(content);
5182
5205
  }
5183
5206
  function singleDollarMathEnd(text, open, limit = text.length) {
5207
+ const bound = Math.min(limit, open + 403);
5184
5208
  let close = -1;
5185
- for (let k = open + 1; k < limit; k += 1) {
5209
+ for (let k = open + 1; k < bound; k += 1) {
5186
5210
  const c = text[k];
5187
5211
  if (c === "\n") break;
5188
5212
  if (c === "\\") {
@@ -5199,11 +5223,69 @@ function singleDollarMathEnd(text, open, limit = text.length) {
5199
5223
  return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
5200
5224
  }
5201
5225
 
5226
+ // source/math-pairs.ts
5227
+ var MAX_MATH_SPAN = 600;
5228
+ var STRUCTURAL_MARKDOWN = /\]\(|https?:\/\/|\*\*|(?:^|\n)[ \t]{0,3}#{1,6}[ \t]|(?:^|\n)[ \t]*[-*+][ \t]+|(?:^|\n)[ \t]*\d+[.)][ \t]/;
5229
+ var LATEX_COMMAND = /\\[a-zA-Z]/;
5230
+ var PROSE_WORD = /[A-Za-z]{3,}/g;
5231
+ var PROSE_WORD_LIMIT = 6;
5232
+ function looksLikeDisplayMath(inner) {
5233
+ const s = inner.trim();
5234
+ if (!s) return false;
5235
+ if (STRUCTURAL_MARKDOWN.test(s)) return false;
5236
+ if (s.length > MAX_MATH_SPAN) return false;
5237
+ if (LATEX_COMMAND.test(s)) {
5238
+ return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT * 3;
5239
+ }
5240
+ if (/\n[ \t]*\n/.test(s)) return false;
5241
+ return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT;
5242
+ }
5243
+ function codeRanges(text) {
5244
+ const ranges = [];
5245
+ for (const re of [/```[\s\S]*?(?:```|$)/g, /~~~[\s\S]*?(?:~~~|$)/g, /`[^`\n]*`/g]) {
5246
+ let m;
5247
+ while ((m = re.exec(text)) !== null) {
5248
+ ranges.push([m.index, m.index + m[0].length]);
5249
+ if (m[0].length === 0) re.lastIndex++;
5250
+ }
5251
+ }
5252
+ return ranges.sort((a, b) => a[0] - b[0]);
5253
+ }
5254
+ function pairDisplayMath(text) {
5255
+ const pairs = /* @__PURE__ */ new Map();
5256
+ if (!text.includes("$$")) return pairs;
5257
+ const ranges = codeRanges(text);
5258
+ const tokens = [];
5259
+ let r = 0;
5260
+ let maxEnd = -1;
5261
+ for (let i = text.indexOf("$$"); i !== -1 && i < text.length - 1; i = text.indexOf("$$", i)) {
5262
+ while (r < ranges.length && ranges[r][0] <= i) {
5263
+ maxEnd = Math.max(maxEnd, ranges[r][1]);
5264
+ r++;
5265
+ }
5266
+ if (maxEnd <= i) tokens.push(i);
5267
+ i += 2;
5268
+ }
5269
+ let j = 0;
5270
+ while (j < tokens.length) {
5271
+ const open = tokens[j];
5272
+ const close = tokens[j + 1];
5273
+ if (close === void 0) break;
5274
+ if (looksLikeDisplayMath(text.slice(open + 2, close))) {
5275
+ pairs.set(open, close);
5276
+ j += 2;
5277
+ continue;
5278
+ }
5279
+ j += 1;
5280
+ }
5281
+ return pairs;
5282
+ }
5283
+
5202
5284
  // source/xml-tag.ts
5203
5285
  var XML_NAME_START = /[A-Za-z_]/;
5204
5286
  var XML_NAME_CHARACTER = /[\w.:-]/;
5205
5287
  var WHITESPACE = /\s/;
5206
- function readXmlTag(content, start) {
5288
+ function readXmlTag(content, start, find = (needle, from) => content.indexOf(needle, from)) {
5207
5289
  let cursor = start + 1;
5208
5290
  const isClosing = content[cursor] === "/";
5209
5291
  if (isClosing) cursor++;
@@ -5256,8 +5338,9 @@ function readXmlTag(content, start) {
5256
5338
  const quote = content[cursor];
5257
5339
  if (quote !== '"' && quote !== "'") return null;
5258
5340
  const valueStart = ++cursor;
5259
- while (cursor < content.length && content[cursor] !== quote) cursor++;
5260
- if (cursor === content.length) return null;
5341
+ const valueEnd = find(quote, valueStart);
5342
+ if (valueEnd === -1) return null;
5343
+ cursor = valueEnd;
5261
5344
  attributes.push({ name, value: content.slice(valueStart, cursor) });
5262
5345
  cursor++;
5263
5346
  }
@@ -5476,11 +5559,15 @@ function backtickRunLength(str2, pos) {
5476
5559
  while (pos + n < str2.length && str2[pos + n] === "`") n++;
5477
5560
  return n;
5478
5561
  }
5562
+ function isSplitterTreeLine(line) {
5563
+ return TREE_GLYPHS.test(line) || /^[\s│|]*[├└+|][\s─\-]+/.test(line);
5564
+ }
5479
5565
  var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
5480
5566
  var Tokenizer = class {
5481
5567
  constructor(text) {
5482
5568
  this.text = text;
5483
5569
  this.n = text.length;
5570
+ this.mathPairs = pairDisplayMath(text);
5484
5571
  let start = 0;
5485
5572
  for (let i = 0; i < text.length; i++) {
5486
5573
  if (text.charCodeAt(i) === 10) {
@@ -5506,6 +5593,34 @@ var Tokenizer = class {
5506
5593
  /** Lazily built: for line i, first line j >= i where brace net from i drops to <= 0. */
5507
5594
  braceStop = null;
5508
5595
  braceNet = null;
5596
+ /** Every occurrence offset of each needle searched so far (overlapping, like indexOf). */
5597
+ occurrences = /* @__PURE__ */ new Map();
5598
+ /** `$$` opener → closer for the pairs the core renders as math. */
5599
+ mathPairs;
5600
+ finder = (needle, from) => this.next(needle, from);
5601
+ /**
5602
+ * `text.indexOf(needle, from)` in O(log k): the first query for a needle
5603
+ * indexes all of its occurrences once, so repeated searches for a closer
5604
+ * that never comes stay linear over the whole text.
5605
+ */
5606
+ next(needle, from) {
5607
+ let list = this.occurrences.get(needle);
5608
+ if (!list) {
5609
+ list = [];
5610
+ for (let at = this.text.indexOf(needle); at !== -1; at = this.text.indexOf(needle, at + 1)) {
5611
+ list.push(at);
5612
+ }
5613
+ this.occurrences.set(needle, list);
5614
+ }
5615
+ let lo = 0;
5616
+ let hi = list.length;
5617
+ while (lo < hi) {
5618
+ const mid = lo + hi >>> 1;
5619
+ if (list[mid] < from) lo = mid + 1;
5620
+ else hi = mid;
5621
+ }
5622
+ return lo < list.length ? list[lo] : -1;
5623
+ }
5509
5624
  get lineCount() {
5510
5625
  return this.lineStarts.length;
5511
5626
  }
@@ -5637,7 +5752,12 @@ var Tokenizer = class {
5637
5752
  }
5638
5753
  return null;
5639
5754
  }
5640
- /** Backtick fence — the splitter's close rules, JSON-string aware for ```json. */
5755
+ /**
5756
+ * Backtick fence — the renderer splitter's close rules: JSON-string aware for
5757
+ * ```json, and THE nested-fence rule (source/fence-nesting.ts) for markdown
5758
+ * fences, with the splitter's strict-CommonMark retry when a nested fence is
5759
+ * still open at the end of the text.
5760
+ */
5641
5761
  backtickFence(p, li, t) {
5642
5762
  const openTicks = backtickRunLength(t, 0);
5643
5763
  const info = t.slice(openTicks).trim();
@@ -5646,7 +5766,19 @@ var Tokenizer = class {
5646
5766
  const normalized = normalizeFenceLanguage(lang);
5647
5767
  const meta = { fence: "`", ticks: openTicks, lang };
5648
5768
  if (normalized && SPECIAL_CODE_LANGUAGES.has(normalized)) meta.special = normalized;
5769
+ const nesting = fenceNestsInnerFences(lang);
5770
+ let close = this.backtickFenceClose(li, openTicks, isJson, nesting);
5771
+ if (close === "retry-strict") close = this.backtickFenceClose(li, openTicks, isJson, false);
5772
+ if (typeof close === "number") {
5773
+ return this.fenceIsland(p, this.contentEnds[close], true, meta, li, close, isJson);
5774
+ }
5775
+ return this.fenceIsland(p, this.n, false, meta, li, this.lineCount, isJson);
5776
+ }
5777
+ /** The closing line of a backtick fence opened on line `li`, or null when it never closes. */
5778
+ backtickFenceClose(li, openTicks, isJson, nesting) {
5649
5779
  let state = { inString: false, escaped: false };
5780
+ let nestedDepth = 0;
5781
+ let sawNested = false;
5650
5782
  for (let k = li + 1; k < this.lineCount; k++) {
5651
5783
  const line = this.lineText(k);
5652
5784
  const trimmedLine = line.trim();
@@ -5659,11 +5791,13 @@ var Tokenizer = class {
5659
5791
  continue;
5660
5792
  }
5661
5793
  }
5662
- const closeTicks = backtickRunLength(trimmedLine, 0);
5663
- const isBare = trimmedLine.slice(closeTicks).trim() === "";
5664
- if (isBare && closeTicks >= openTicks) {
5665
- return this.fenceIsland(p, this.contentEnds[k], true, meta, li, k, isJson);
5794
+ const kind = classifyInnerFenceLine(trimmedLine, openTicks, nesting, nestedDepth);
5795
+ if (kind === "close-outer") return k;
5796
+ if (kind === "open-nested") {
5797
+ nestedDepth++;
5798
+ sawNested = true;
5666
5799
  }
5800
+ if (kind === "close-nested") nestedDepth--;
5667
5801
  if (isJson) state = jsonStringState(line, 0, line.length, state);
5668
5802
  continue;
5669
5803
  }
@@ -5678,15 +5812,13 @@ var Tokenizer = class {
5678
5812
  }
5679
5813
  const closeTicks = backtickRunLength(line, at);
5680
5814
  const after = line.slice(at + closeTicks).trim();
5681
- if (closeTicks >= openTicks && after === "") {
5682
- return this.fenceIsland(p, this.contentEnds[k], true, meta, li, k, isJson);
5683
- }
5815
+ if (closeTicks >= openTicks && after === "" && nestedDepth === 0) return k;
5684
5816
  if (isJson) state = jsonStringState(line, 0, line.length, state);
5685
5817
  continue;
5686
5818
  }
5687
5819
  if (isJson) state = jsonStringState(line, 0, line.length, state);
5688
5820
  }
5689
- return this.fenceIsland(p, this.n, false, meta, li, this.lineCount, isJson);
5821
+ return sawNested && nestedDepth > 0 ? "retry-strict" : null;
5690
5822
  }
5691
5823
  fenceIsland(p, end, complete, meta, openLine, closeLine, isJson) {
5692
5824
  if (isJson && openLine + 1 < this.lineCount) {
@@ -5712,18 +5844,22 @@ var Tokenizer = class {
5712
5844
  }
5713
5845
  return this.island("fence", p, this.n, false, meta);
5714
5846
  }
5847
+ /**
5848
+ * A line-leading `$$` that OPENS a math pair (source/math-pairs.ts — the
5849
+ * core's rule). A closer, an unpaired `$$`, or a prose pair is never a block:
5850
+ * a lone `$$` renders as literal text and locks nothing.
5851
+ */
5715
5852
  mathBlock(p, tp) {
5853
+ const close = this.mathPairs.get(tp);
5854
+ if (close === void 0) return null;
5855
+ const end = close + 2;
5716
5856
  const li = this.lineOf(tp);
5717
5857
  const ce = this.contentEnds[li];
5718
- const sameLine = this.text.indexOf("$$", tp + 2);
5719
- if (sameLine !== -1 && sameLine + 2 <= ce) {
5720
- if (!this.isBlank(sameLine + 2, ce)) return null;
5721
- if (sameLine === tp + 2) return null;
5722
- return this.island("math_block", p, sameLine + 2, true, { delimiter: "$$" });
5723
- }
5724
- const close = this.text.indexOf("$$", tp + 2);
5725
- if (close === -1) return this.island("math_block", p, this.n, false, { delimiter: "$$" });
5726
- return this.island("math_block", p, close + 2, true, { delimiter: "$$" });
5858
+ if (end <= ce && !this.isBlank(end, ce)) return null;
5859
+ const endLine = this.lineOf(close);
5860
+ const endCe = this.contentEnds[endLine];
5861
+ if (endLine !== li && !this.isBlank(end, endCe)) return null;
5862
+ return this.island("math_block", p, end, true, { delimiter: "$$" });
5727
5863
  }
5728
5864
  /** `<artifact …>`, `<decision …>`, … at the start of a line. */
5729
5865
  attributeXml(p, tp, t) {
@@ -5740,7 +5876,7 @@ var Tokenizer = class {
5740
5876
  attributeElement(name, p, tp, type) {
5741
5877
  const li = this.lineOf(tp);
5742
5878
  const ce = this.contentEnds[li];
5743
- const tag = readXmlTag(this.text, tp);
5879
+ const tag = readXmlTag(this.text, tp, this.finder);
5744
5880
  let openEnd;
5745
5881
  if (tag && !tag.isClosing) {
5746
5882
  if (tag.isSelfClosing) {
@@ -5748,13 +5884,13 @@ var Tokenizer = class {
5748
5884
  }
5749
5885
  openEnd = tp + tag.raw.length;
5750
5886
  } else {
5751
- const gt = this.text.indexOf(">", tp);
5887
+ const gt = this.next(">", tp);
5752
5888
  if (gt === -1) return this.island(type, p, this.n, false, { tag: name });
5753
5889
  if (gt >= ce) return null;
5754
5890
  openEnd = gt + 1;
5755
5891
  }
5756
5892
  const closer = `</${name}>`;
5757
- const close = this.text.indexOf(closer, openEnd);
5893
+ const close = this.next(closer, openEnd);
5758
5894
  if (close === -1) return this.island(type, p, this.n, false, { tag: name });
5759
5895
  return this.island(type, p, close + closer.length, true, { tag: name });
5760
5896
  }
@@ -5764,7 +5900,7 @@ var Tokenizer = class {
5764
5900
  const opener = `<${name}>`;
5765
5901
  if (!t.startsWith(opener)) continue;
5766
5902
  const closer = `</${name}>`;
5767
- const close = this.text.indexOf(closer, tp + opener.length);
5903
+ const close = this.next(closer, tp + opener.length);
5768
5904
  if (close === -1) return this.island("xml_region", p, this.n, false, { tag: name });
5769
5905
  return this.island("xml_region", p, close + closer.length, true, { tag: name });
5770
5906
  }
@@ -5800,7 +5936,7 @@ var Tokenizer = class {
5800
5936
  return null;
5801
5937
  }
5802
5938
  blockComment(p, tp, li) {
5803
- const close = this.text.indexOf("-->", tp + 4);
5939
+ const close = this.next("-->", tp + 4);
5804
5940
  if (close === -1) return this.island("html_comment", p, this.n, false);
5805
5941
  const end = close + 3;
5806
5942
  const raw = this.text.slice(tp, end);
@@ -5878,9 +6014,8 @@ var Tokenizer = class {
5878
6014
  }
5879
6015
  }
5880
6016
  if (lastLine === null) {
5881
- const partial = this.text.slice(tp);
5882
- if (isRecognizedJsonHead(partial)) {
5883
- return this.island("json", p, this.n, false, jsonMeta(partial));
6017
+ if (isRecognizedJsonHead(this.text.slice(tp, tp + 512))) {
6018
+ return this.island("json", p, this.n, false, jsonMeta(this.text.slice(tp)));
5884
6019
  }
5885
6020
  return null;
5886
6021
  }
@@ -5915,11 +6050,43 @@ var Tokenizer = class {
5915
6050
  // ── prose ──────────────────────────────────────────────────────────────
5916
6051
  paragraph(pos, li) {
5917
6052
  let end = this.contentEnds[li];
6053
+ let breakLine = -1;
5918
6054
  for (let k = li + 1; k < this.lineCount; k++) {
5919
6055
  if (this.lineIsBlank(k)) break;
5920
- if (this.detectBlock(this.lineStarts[k], k, false)) break;
6056
+ if (this.detectBlock(this.lineStarts[k], k, false)) {
6057
+ breakLine = k;
6058
+ break;
6059
+ }
5921
6060
  end = this.contentEnds[k];
5922
6061
  }
6062
+ if (breakLine !== -1) {
6063
+ const breakIsland = this.detectBlock(this.lineStarts[breakLine], breakLine, false);
6064
+ if (breakIsland?.type === "tree") {
6065
+ let root = breakLine;
6066
+ for (let j = breakLine - 1; j >= li; j--) {
6067
+ const trimmed = this.lineText(j).trim();
6068
+ if (!trimmed || isSplitterTreeLine(trimmed) || /^#{1,6}\s/.test(trimmed)) break;
6069
+ root = j;
6070
+ }
6071
+ if (root < breakLine) {
6072
+ const treeStart = root === li ? pos : this.lineStarts[root];
6073
+ const tree = { ...breakIsland, start: treeStart };
6074
+ if (treeStart > pos) {
6075
+ const proseEnd = this.contentEnds[root - 1];
6076
+ const scan2 = this.scanInline(pos, proseEnd);
6077
+ if (!scan2.breakIsland) {
6078
+ this.push({ kind: "prose", start: pos, end: proseEnd, island: null, inlines: scan2.inlines });
6079
+ this.push({ kind: "gap", start: proseEnd, end: treeStart, island: null, inlines: [] });
6080
+ this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
6081
+ return tree.end;
6082
+ }
6083
+ } else {
6084
+ this.push({ kind: "island", start: treeStart, end: tree.end, island: tree, inlines: [] });
6085
+ return tree.end;
6086
+ }
6087
+ }
6088
+ }
6089
+ }
5923
6090
  const scan = this.scanInline(pos, end);
5924
6091
  if (!scan.breakIsland) {
5925
6092
  this.push({ kind: "prose", start: pos, end, island: null, inlines: scan.inlines });
@@ -5952,17 +6119,49 @@ var Tokenizer = class {
5952
6119
  const text = this.text;
5953
6120
  const inlines = [];
5954
6121
  const hasKind = text.slice(ps, pe).includes("__kind");
6122
+ let kindBudget = 4 * (pe - ps) + 65536;
5955
6123
  const stop = (island, orphan = false) => ({ inlines, breakIsland: island, orphan });
6124
+ const within = (needle, from) => {
6125
+ const at = this.next(needle, from);
6126
+ return at !== -1 && at + needle.length <= pe ? at : -1;
6127
+ };
6128
+ let runs = null;
6129
+ const codeSpanClose = (n, from) => {
6130
+ if (!runs) {
6131
+ runs = /* @__PURE__ */ new Map();
6132
+ for (let k = ps; k < pe; ) {
6133
+ if (text.charCodeAt(k) !== 96) {
6134
+ k++;
6135
+ continue;
6136
+ }
6137
+ const len = backtickRunLength(text, k);
6138
+ const list2 = runs.get(len);
6139
+ if (list2) list2.push(k);
6140
+ else runs.set(len, [k]);
6141
+ k += len;
6142
+ }
6143
+ }
6144
+ const list = runs.get(n);
6145
+ if (!list) return -1;
6146
+ let lo = 0;
6147
+ let hi = list.length;
6148
+ while (lo < hi) {
6149
+ const mid = lo + hi >>> 1;
6150
+ if (list[mid] < from) lo = mid + 1;
6151
+ else hi = mid;
6152
+ }
6153
+ const at = list[lo];
6154
+ return at !== void 0 && at + n <= pe ? at : -1;
6155
+ };
5956
6156
  let i = ps;
5957
6157
  while (i < pe) {
5958
6158
  const ch = text[i];
5959
6159
  if (ch === "\\") {
5960
- const next = text[i + 1];
5961
- if (next === "(" || next === "[") {
5962
- const closer = next === "(" ? "\\)" : "\\]";
5963
- const close = text.indexOf(closer, i + 2);
5964
- if (close !== -1 && close + 2 <= pe) {
5965
- inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: next === "(" ? "\\(" : "\\[" }));
6160
+ const nextCh = text[i + 1];
6161
+ if (nextCh === "(" || nextCh === "[") {
6162
+ const close = within(nextCh === "(" ? "\\)" : "\\]", i + 2);
6163
+ if (close !== -1) {
6164
+ inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: nextCh === "(" ? "\\(" : "\\[" }));
5966
6165
  i = close + 2;
5967
6166
  continue;
5968
6167
  }
@@ -5972,33 +6171,20 @@ var Tokenizer = class {
5972
6171
  }
5973
6172
  if (ch === "`") {
5974
6173
  const n = backtickRunLength(text, i);
5975
- const run = "`".repeat(n);
5976
- let k = i + n;
5977
- let close = -1;
5978
- while (k < pe) {
5979
- const idx = text.indexOf(run, k);
5980
- if (idx === -1 || idx >= pe) break;
5981
- if (text[idx + n] === "`") {
5982
- let m = idx;
5983
- while (text[m] === "`") m++;
5984
- k = m;
5985
- continue;
5986
- }
5987
- close = idx;
5988
- break;
5989
- }
6174
+ const close = codeSpanClose(n, i + n);
5990
6175
  i = close === -1 ? i + n : close + n;
5991
6176
  continue;
5992
6177
  }
5993
6178
  if (ch === "$") {
5994
6179
  if (text[i + 1] === "$") {
5995
- const close = text.indexOf("$$", i + 2);
5996
- if (close !== -1 && close + 2 <= pe && close > i + 2) {
5997
- inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: "$$" }));
5998
- i = close + 2;
5999
- } else {
6180
+ const close = this.mathPairs.get(i);
6181
+ if (close === void 0) {
6000
6182
  i += 2;
6183
+ continue;
6001
6184
  }
6185
+ if (close + 2 > pe) return stop(this.island("math_block", i, close + 2, true, { delimiter: "$$" }));
6186
+ inlines.push(this.island("math_inline", i, close + 2, true, { delimiter: "$$" }));
6187
+ i = close + 2;
6002
6188
  continue;
6003
6189
  }
6004
6190
  const end = singleDollarMathEnd(text, i, pe);
@@ -6012,7 +6198,7 @@ var Tokenizer = class {
6012
6198
  }
6013
6199
  if (ch === "{") {
6014
6200
  if (text[i + 1] === "{") {
6015
- const close = text.indexOf("}}", i + 2);
6201
+ const close = this.next("}}", i + 2);
6016
6202
  if (close !== -1 && close + 2 <= pe && close - i <= 200 && !text.slice(i, close).includes("\n")) {
6017
6203
  const raw = text.slice(i, close + 2);
6018
6204
  const strict = STRICT_VARIABLE_RE.exec(raw);
@@ -6025,8 +6211,10 @@ var Tokenizer = class {
6025
6211
  i += 2;
6026
6212
  continue;
6027
6213
  }
6028
- if (hasKind) {
6029
- const end = matchingJsonObjectEnd(text, i, pe);
6214
+ if (hasKind && kindBudget > 0 && /^\{\s*"/.test(text.slice(i, i + 64))) {
6215
+ const limit = Math.min(pe, i + 65536);
6216
+ const end = matchingJsonObjectEnd(text, i, limit);
6217
+ kindBudget -= (end ?? limit) - i;
6030
6218
  if (end !== null) {
6031
6219
  const kind = declaredKind(text.slice(i, end));
6032
6220
  if (kind) {
@@ -6042,8 +6230,9 @@ var Tokenizer = class {
6042
6230
  if (ch === "[") {
6043
6231
  const media = MEDIA_REF_PREFIXES.find((prefix) => text.startsWith(prefix, i));
6044
6232
  if (media) {
6045
- const close = text.indexOf("]", i + media.length);
6046
- if (close !== -1 && close < pe && !text.slice(i, close).includes("\n")) {
6233
+ const close = within("]", i + media.length);
6234
+ const newline = this.next("\n", i);
6235
+ if (close !== -1 && (newline === -1 || newline > close)) {
6047
6236
  inlines.push(this.island("media_ref", i, close + 1, true, { media: media.slice(1, 6).toLowerCase() }));
6048
6237
  i = close + 1;
6049
6238
  continue;
@@ -6054,10 +6243,10 @@ var Tokenizer = class {
6054
6243
  }
6055
6244
  if (ch === "<") {
6056
6245
  if (text.startsWith("<!--", i)) {
6057
- const close = text.indexOf("-->", i + 4);
6246
+ const close = this.next("-->", i + 4);
6058
6247
  if (close === -1) return stop(this.island("html_comment", i, this.n, false));
6059
6248
  const end = close + 3;
6060
- const anchor = PINNED_ANCHOR_RE.exec(text.slice(i, end));
6249
+ const anchor = end - i <= 64 ? PINNED_ANCHOR_RE.exec(text.slice(i, end)) : null;
6061
6250
  const type = anchor ? "anchor" : "html_comment";
6062
6251
  const meta = anchor ? { id: anchor[1] } : {};
6063
6252
  if (end > pe) return stop(this.island(type, i, end, true, meta));
@@ -6071,7 +6260,7 @@ var Tokenizer = class {
6071
6260
  i += cite[0].length;
6072
6261
  continue;
6073
6262
  }
6074
- const tag = readXmlTag(text, i);
6263
+ const tag = readXmlTag(text, i, this.finder);
6075
6264
  if (tag && i + tag.raw.length <= pe) {
6076
6265
  const name = tag.tagName;
6077
6266
  const tagEnd = i + tag.raw.length;
@@ -6084,7 +6273,7 @@ var Tokenizer = class {
6084
6273
  continue;
6085
6274
  }
6086
6275
  const closer = `</${name}>`;
6087
- const close = text.indexOf(closer, tagEnd);
6276
+ const close = this.next(closer, tagEnd);
6088
6277
  const blockType = isAttr ? "xml_attr" : "xml_region";
6089
6278
  if (close === -1) return stop(this.island(blockType, i, this.n, false, { tag: name }));
6090
6279
  const end = close + closer.length;
@@ -6108,7 +6297,7 @@ var Tokenizer = class {
6108
6297
  const pending = ATTRIBUTE_XML_NAMES.find(
6109
6298
  (name) => text.startsWith(`<${name}`, i) && /\s/.test(text[i + name.length + 1] ?? "")
6110
6299
  );
6111
- if (pending && text.indexOf(">", i) === -1) {
6300
+ if (pending && this.next(">", i) === -1) {
6112
6301
  return stop(this.island("xml_attr", i, this.n, false, { tag: pending }));
6113
6302
  }
6114
6303
  i++;
@@ -6199,9 +6388,22 @@ var SourceSpliceError = class extends Error {
6199
6388
  function blockEdit(block, text) {
6200
6389
  return { start: block.start, end: block.end, text };
6201
6390
  }
6391
+ function islandEdit(island, text) {
6392
+ return { start: island.start, end: island.end, text, island: true };
6393
+ }
6202
6394
  function blockRangeEdit(first, last, text) {
6203
6395
  return { start: first.start, end: last.end, text };
6204
6396
  }
6397
+ function locateIsland(text, raw, from, expected, expectedFromEnd) {
6398
+ if (expected >= from && text.startsWith(raw, expected)) return expected;
6399
+ if (expectedFromEnd >= from && text.startsWith(raw, expectedFromEnd)) return expectedFromEnd;
6400
+ const after = text.indexOf(raw, Math.max(from, expected));
6401
+ const before = expected > from ? text.lastIndexOf(raw, expected) : -1;
6402
+ const beforeOk = before >= from ? before : -1;
6403
+ if (after === -1) return beforeOk;
6404
+ if (beforeOk === -1) return after;
6405
+ return expected - beforeOk <= after - expected ? beforeOk : after;
6406
+ }
6205
6407
  function commonPrefix(a, b) {
6206
6408
  const max = Math.min(a.length, b.length);
6207
6409
  let i = 0;
@@ -6226,13 +6428,23 @@ function spliceSave(original, edits, options = {}) {
6226
6428
  boundaries.add(block.start);
6227
6429
  boundaries.add(block.end);
6228
6430
  }
6431
+ const islands = listIslands(blocks);
6432
+ const islandAt = /* @__PURE__ */ new Map();
6433
+ for (const island of islands) islandAt.set(`${island.start}:${island.end}`, island);
6229
6434
  const sorted = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
6230
6435
  let previousEnd = -1;
6231
6436
  for (const edit of sorted) {
6232
6437
  if (edit.start < 0 || edit.end > original.length || edit.start > edit.end) {
6233
6438
  throw new SourceSpliceError("out_of_range", `edit [${edit.start}, ${edit.end}) is outside the text`);
6234
6439
  }
6235
- if (!boundaries.has(edit.start) || !boundaries.has(edit.end)) {
6440
+ if (edit.island) {
6441
+ if (!islandAt.has(`${edit.start}:${edit.end}`)) {
6442
+ throw new SourceSpliceError(
6443
+ "misaligned",
6444
+ `islandEdit [${edit.start}, ${edit.end}) does not name an island's exact span`
6445
+ );
6446
+ }
6447
+ } else if (!boundaries.has(edit.start) || !boundaries.has(edit.end)) {
6236
6448
  throw new SourceSpliceError(
6237
6449
  "misaligned",
6238
6450
  `edit [${edit.start}, ${edit.end}) does not start and end on block boundaries`
@@ -6244,18 +6456,42 @@ function spliceSave(original, edits, options = {}) {
6244
6456
  previousEnd = edit.end;
6245
6457
  }
6246
6458
  const effective = [];
6247
- for (const edit of sorted) {
6248
- const old = original.slice(edit.start, edit.end);
6249
- if (old === edit.text) continue;
6250
- const prefix = commonPrefix(old, edit.text);
6251
- const suffix = commonSuffix(old, edit.text, prefix);
6459
+ const replacedIslands = /* @__PURE__ */ new Set();
6460
+ const pushSegment = (oldStart, oldEnd, replacement) => {
6461
+ const old = original.slice(oldStart, oldEnd);
6462
+ if (old === replacement) return;
6463
+ const prefix = commonPrefix(old, replacement);
6464
+ const suffix = commonSuffix(old, replacement, prefix);
6252
6465
  effective.push({
6253
- blockStart: edit.start,
6254
- blockEnd: edit.end,
6255
- oldStart: edit.start + prefix,
6256
- oldEnd: edit.end - suffix,
6257
- replacement: edit.text.slice(prefix, edit.text.length - suffix)
6466
+ oldStart: oldStart + prefix,
6467
+ oldEnd: oldEnd - suffix,
6468
+ replacement: replacement.slice(prefix, replacement.length - suffix)
6258
6469
  });
6470
+ };
6471
+ for (const edit of sorted) {
6472
+ if (edit.island) {
6473
+ replacedIslands.add(`${edit.start}:${edit.end}`);
6474
+ pushSegment(edit.start, edit.end, edit.text);
6475
+ continue;
6476
+ }
6477
+ if (edit.text === original.slice(edit.start, edit.end)) continue;
6478
+ const inside = islands.filter((island) => island.start >= edit.start && island.end <= edit.end);
6479
+ let oldCursor2 = edit.start;
6480
+ let newCursor2 = 0;
6481
+ for (const island of inside) {
6482
+ const at = locateIsland(edit.text, island.raw, newCursor2, newCursor2 + (island.start - oldCursor2), edit.text.length - (edit.end - island.start));
6483
+ if (at === -1) {
6484
+ const name = island.meta.tag ?? island.meta.name ?? island.meta.lang ?? "";
6485
+ throw new SourceSpliceError(
6486
+ "island_edit",
6487
+ `edit [${edit.start}, ${edit.end}) changes or removes the protected ${island.islandType}${name ? ` (${String(name)})` : ""} island at [${island.start}, ${island.end}); islands change only through islandEdit()`
6488
+ );
6489
+ }
6490
+ pushSegment(oldCursor2, island.start, edit.text.slice(newCursor2, at));
6491
+ oldCursor2 = island.end;
6492
+ newCursor2 = at + island.raw.length;
6493
+ }
6494
+ pushSegment(oldCursor2, edit.end, edit.text.slice(newCursor2));
6259
6495
  }
6260
6496
  if (effective.length === 0) {
6261
6497
  return {
@@ -6275,9 +6511,10 @@ function spliceSave(original, edits, options = {}) {
6275
6511
  const newStart = edit.oldStart + delta;
6276
6512
  text += edit.replacement;
6277
6513
  const newEnd = newStart + edit.replacement.length;
6514
+ const owner = sorted.find((e) => e.start <= edit.oldStart && e.end >= edit.oldEnd);
6278
6515
  pending.push({
6279
- blockStart: edit.blockStart,
6280
- blockEnd: edit.blockEnd,
6516
+ blockStart: owner?.start ?? edit.oldStart,
6517
+ blockEnd: owner?.end ?? edit.oldEnd,
6281
6518
  oldStart: edit.oldStart,
6282
6519
  oldEnd: edit.oldEnd,
6283
6520
  newStart,
@@ -6315,11 +6552,8 @@ function spliceSave(original, edits, options = {}) {
6315
6552
  else newIslandsByStart.set(island.start, [island.raw]);
6316
6553
  }
6317
6554
  const disturbed = [];
6318
- for (const island of listIslands(blocks)) {
6319
- const touched = effective.some(
6320
- (edit) => island.start < edit.blockEnd && island.end > edit.blockStart
6321
- );
6322
- if (touched) continue;
6555
+ for (const island of islands) {
6556
+ if (replacedIslands.has(`${island.start}:${island.end}`)) continue;
6323
6557
  const mappedStart = mapPosition(changes, island.start, { assoc: 1 }).pos;
6324
6558
  const candidates = newIslandsByStart.get(mappedStart);
6325
6559
  if (!candidates || !candidates.includes(island.raw)) {
@@ -6337,10 +6571,11 @@ function spliceSave(original, edits, options = {}) {
6337
6571
  bytesOutsideEditsIdentical,
6338
6572
  disturbed
6339
6573
  };
6340
- if (options.requireIntegrity && !integrity.ok) {
6574
+ if ((options.requireIntegrity ?? true) && !integrity.ok) {
6575
+ const named = disturbed.slice(0, 5).map((island) => `${island.islandType}@${island.start}`).join(", ");
6341
6576
  throw new SourceSpliceError(
6342
6577
  "integrity",
6343
- `the edit disturbed ${disturbed.length} protected island(s) outside the edited blocks`
6578
+ `integrity: the save disturbed ${disturbed.length} protected island(s) it did not replace${named ? ` (${named}${disturbed.length > 5 ? ", \u2026" : ""})` : ""}${bytesOutsideEditsIdentical ? "" : "; bytes outside the edits changed"}`
6344
6579
  );
6345
6580
  }
6346
6581
  return { text, changed: true, changes, integrity, blocks: newBlocks };
@@ -6382,6 +6617,6 @@ function mapRange(changes, start, end, unit = "utf16") {
6382
6617
  return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
6383
6618
  }
6384
6619
 
6385
- export { ACTION_BY_CLASS, CAPABILITY_BY_CLASS, CLASSES, DIRECTIVE_VERSION, DirectiveDecodeError, IN_CONTENT_CLASSES, IR_ENVELOPE_CACHE_VERSION, IR_ENVELOPE_KEY, IR_PARTIAL_KEY, IR_VERSION, IrTree, JSON_DISCRIMINATOR, JsonStreamTokenizer, KIND_KEY, KindStorageError, KindStreamParser, NODE_OUTCOME_KIND, ParseSession, RESERVED_PREFIX, ROOT_STORAGE_NAME, RUN_RESULT_KIND, SIDE_EFFECT_CLASSES, SLUG_PREFIX, SourceSpliceError, XmlContainerTracker, advancePartialKind, asKindInstance, blockAt, blockEdit, blockRangeEdit, buildAgentSchemaWithRenderBlockSupport, buildCodePointIndex, buildCompliantKindSnapshot, buildDirectiveSlug, buildKindDirective, capabilityOf, classifyInboundEnvelopeMetadata, collectReferencedKinds, collectSchemaReferencedKinds, compareWithExistingKindSchema, convertAiSchemaToBlockFields, createFingerprinter, createKindStreamParser, decodeDirective, describeDualGateFailure, directiveDisplay, directiveItemKindFromEdges, directiveSlugOf, disposeParseSession, emitPayloadFence, emitPayloadJson, emptyValueForFieldSchema, envelopeCacheFromEnvelopes, envelopeFromCompleteValue, executesAtOutputRoot, fenceDiscriminator, fieldsToDbPayload, fingerprintText, formatBlockLabel, getParseSession, injectKindIntoObjectSchema, irPathIsUnderOrEqual, irPathKey, irPathLabel, irPathsEqual, isCanonicalBlockIR, isDirectiveClass, isDuplicateBlockSlug, isEmptyResidue, isIrEnvelopeCache, isJsonAnyField, isKindDirective, isLegacyShell, isProvisionalKind, isReservedDirectiveSlug, isScalarArrayType, isSingleDollarMath, isTerminalKindEvent, itemFacts, itemSubtitle, itemTitle, joinSource, kindSchemaFromJsonSchema, kindSchemaToJsonSchema, kindSchemaToStorage, kindVerdictOf, legacyShellUses, listIslands, looksLikeDirectiveHead, makePartialKindStalenessGate, mapPosition, mapRange, mergeResidueIntoValue, normalizeAiSchemaInput, normalizeJsonRegion, nounFamily, nounLabel, nounTitleColumn, openParseSession, parseDirectiveSlug, readEnvelope, readNodeOutcomeValue, readObjectKind, readOutputRef, readPartialKindEvent, readRunResultValue, readXmlTag, reconstructRegionValue, rehydrateNodeOutcome, rehydrateRunResult, resetLegacyShellUses, resolvesInContent, reuseEnvelopeIfCurrent, runKindDualGate, runSchemaConversion, sanitizeInboundEnvelopeMetadata, sanitizeInboundPartialKindMetadata, scalarArrayItemType, schemaLayoutMode, schemaStructureDepth, setJsonRootKeyLookup, singleDollarMathEnd, spliceSave, splitsSurrogatePair, storageToKindSchema, stripKindDeep, titleCaseToken, toCodePointOffset, toUtf16Offset, tokenizeSource, tryDecodeDirective, tryDecodeDirectiveContent, validateBlockSchemaSavePlan, validateStructuralLeg, withRootKind, xmlDiscriminator };
6620
+ export { ACTION_BY_CLASS, CAPABILITY_BY_CLASS, CLASSES, DIRECTIVE_VERSION, DirectiveDecodeError, IN_CONTENT_CLASSES, IR_ENVELOPE_CACHE_VERSION, IR_ENVELOPE_KEY, IR_PARTIAL_KEY, IR_VERSION, IrTree, JSON_DISCRIMINATOR, JsonStreamTokenizer, KIND_KEY, KindStorageError, KindStreamParser, NESTING_FENCE_LANGUAGES, NODE_OUTCOME_KIND, ParseSession, RESERVED_PREFIX, ROOT_STORAGE_NAME, RUN_RESULT_KIND, SIDE_EFFECT_CLASSES, SLUG_PREFIX, SourceSpliceError, XmlContainerTracker, advancePartialKind, asKindInstance, blockAt, blockEdit, blockRangeEdit, buildAgentSchemaWithRenderBlockSupport, buildCodePointIndex, buildCompliantKindSnapshot, buildDirectiveSlug, buildKindDirective, capabilityOf, classifyInboundEnvelopeMetadata, classifyInnerFenceLine, collectReferencedKinds, collectSchemaReferencedKinds, compareWithExistingKindSchema, convertAiSchemaToBlockFields, createFingerprinter, createKindStreamParser, decodeDirective, describeDualGateFailure, directiveDisplay, directiveItemKindFromEdges, directiveSlugOf, disposeParseSession, emitPayloadFence, emitPayloadJson, emptyValueForFieldSchema, envelopeCacheFromEnvelopes, envelopeFromCompleteValue, executesAtOutputRoot, fenceDiscriminator, fenceNestsInnerFences, fieldsToDbPayload, fingerprintText, formatBlockLabel, getParseSession, injectKindIntoObjectSchema, irPathIsUnderOrEqual, irPathKey, irPathLabel, irPathsEqual, isCanonicalBlockIR, isDirectiveClass, isDuplicateBlockSlug, isEmptyResidue, isIrEnvelopeCache, isJsonAnyField, isKindDirective, isLegacyShell, isProvisionalKind, isReservedDirectiveSlug, isScalarArrayType, isSingleDollarMath, isTerminalKindEvent, islandEdit, itemFacts, itemSubtitle, itemTitle, joinSource, kindSchemaFromJsonSchema, kindSchemaToJsonSchema, kindSchemaToStorage, kindVerdictOf, legacyShellUses, listIslands, looksLikeDirectiveHead, looksLikeDisplayMath, makePartialKindStalenessGate, mapPosition, mapRange, mergeResidueIntoValue, normalizeAiSchemaInput, normalizeJsonRegion, nounFamily, nounLabel, nounTitleColumn, openParseSession, pairDisplayMath, parseDirectiveSlug, readEnvelope, readNodeOutcomeValue, readObjectKind, readOutputRef, readPartialKindEvent, readRunResultValue, readXmlTag, reconstructRegionValue, rehydrateNodeOutcome, rehydrateRunResult, resetLegacyShellUses, resolvesInContent, reuseEnvelopeIfCurrent, runKindDualGate, runSchemaConversion, sanitizeInboundEnvelopeMetadata, sanitizeInboundPartialKindMetadata, scalarArrayItemType, schemaLayoutMode, schemaStructureDepth, setJsonRootKeyLookup, singleDollarMathEnd, spliceSave, splitsSurrogatePair, storageToKindSchema, stripKindDeep, titleCaseToken, toCodePointOffset, toUtf16Offset, tokenizeSource, tryDecodeDirective, tryDecodeDirectiveContent, validateBlockSchemaSavePlan, validateStructuralLeg, withRootKind, xmlDiscriminator };
6386
6621
  //# sourceMappingURL=index.js.map
6387
6622
  //# sourceMappingURL=index.js.map