@probelabs/probe 0.6.0-rc286 → 0.6.0-rc288

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.
@@ -8933,7 +8933,14 @@ function parseTargets(targets) {
8933
8933
  }
8934
8934
  function parseAndResolvePaths(pathStr, cwd) {
8935
8935
  if (!pathStr) return [];
8936
- const paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
8936
+ let paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
8937
+ paths = paths.flatMap((p) => {
8938
+ if (!/\s/.test(p)) return [p];
8939
+ const parts = p.split(/\s+/).filter(Boolean);
8940
+ if (parts.length <= 1) return [p];
8941
+ const allLookLikePaths = parts.every((part) => /[/\\]/.test(part) || /\.\w+/.test(part));
8942
+ return allLookLikePaths ? parts : [p];
8943
+ });
8937
8944
  return paths.map((p) => {
8938
8945
  if (isAbsolute(p)) {
8939
8946
  return p;
@@ -9167,11 +9174,24 @@ function normalizeTargets(targets) {
9167
9174
  if (typeof target !== "string") continue;
9168
9175
  const trimmed = target.trim();
9169
9176
  if (!trimmed || seen.has(trimmed)) continue;
9170
- seen.add(trimmed);
9171
- normalized.push(trimmed);
9177
+ const subTargets = splitSpaceSeparatedPaths(trimmed);
9178
+ for (const sub of subTargets) {
9179
+ if (!seen.has(sub)) {
9180
+ seen.add(sub);
9181
+ normalized.push(sub);
9182
+ }
9183
+ }
9172
9184
  }
9173
9185
  return normalized;
9174
9186
  }
9187
+ function splitSpaceSeparatedPaths(target) {
9188
+ if (!/\s/.test(target)) return [target];
9189
+ const parts = target.split(/\s+/).filter(Boolean);
9190
+ if (parts.length <= 1) return [target];
9191
+ const allLookLikePaths = parts.every((p) => /[/\\]/.test(p) || /\.\w+/.test(p));
9192
+ if (allLookLikePaths) return parts;
9193
+ return [target];
9194
+ }
9175
9195
  function extractJsonSnippet(text) {
9176
9196
  const jsonBlockMatch = text.match(/```json\s*([\s\S]*?)```/i);
9177
9197
  if (jsonBlockMatch) {
@@ -44307,6 +44327,7 @@ var init_parser2 = __esm({
44307
44327
  { ALT: () => this.CONSUME(Identifier) },
44308
44328
  { ALT: () => this.CONSUME(Text) },
44309
44329
  { ALT: () => this.CONSUME(NumberLiteral) },
44330
+ { ALT: () => this.CONSUME(ColorValue) },
44310
44331
  // Note: RoundOpen and RoundClose (parentheses) are NOT allowed in unquoted labels
44311
44332
  // to match Mermaid's behavior - use quoted labels like ["text (with parens)"] instead
44312
44333
  // Allow HTML-like tags (e.g., <br/>) inside labels
@@ -44398,6 +44419,7 @@ var init_parser2 = __esm({
44398
44419
  { ALT: () => this.CONSUME(Identifier) },
44399
44420
  { ALT: () => this.CONSUME(Text) },
44400
44421
  { ALT: () => this.CONSUME(NumberLiteral) },
44422
+ { ALT: () => this.CONSUME(ColorValue) },
44401
44423
  // Allow HTML-like angle brackets and slashes for <br/>, <i>, etc.
44402
44424
  { ALT: () => this.CONSUME(AngleLess) },
44403
44425
  { ALT: () => this.CONSUME(AngleOpen) },
@@ -45573,13 +45595,24 @@ function mapFlowchartParserError(err, text) {
45573
45595
  length: len
45574
45596
  };
45575
45597
  }
45576
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
45598
+ if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose" || tokType === "DiamondOpen" || tokType === "DiamondClose") {
45577
45599
  const context = err?.context;
45578
45600
  const inLinkRule = context?.ruleStack?.includes("linkTextInline") || context?.ruleStack?.includes("link") || false;
45579
45601
  const lineContent = allLines[Math.max(0, line - 1)] || "";
45580
45602
  const beforeQuote = lineContent.slice(0, Math.max(0, column - 1));
45581
45603
  const hasLinkBefore = beforeQuote.match(/--\s*$|==\s*$|-\.\s*$|-\.-\s*$|\[\s*$/);
45582
45604
  if (inLinkRule || hasLinkBefore) {
45605
+ if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
45606
+ return {
45607
+ line,
45608
+ column,
45609
+ severity: "error",
45610
+ code: "FL-EDGE-LABEL-CURLY-IN-PIPES",
45611
+ message: "Curly braces { } are not supported inside pipe-delimited edge labels.",
45612
+ hint: "Use HTML entities &#123; and &#125; inside |...|, e.g., --|Resolve $&#123;VAR&#125;|-->",
45613
+ length: len
45614
+ };
45615
+ }
45583
45616
  if (tokType === "SquareOpen" || tokType === "SquareClose") {
45584
45617
  return {
45585
45618
  line,
@@ -45641,6 +45674,17 @@ function mapFlowchartParserError(err, text) {
45641
45674
  length: len
45642
45675
  };
45643
45676
  }
45677
+ if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
45678
+ return {
45679
+ line,
45680
+ column,
45681
+ severity: "error",
45682
+ code: "FL-LABEL-CURLY-IN-UNQUOTED",
45683
+ message: "Curly braces are not supported inside unquoted node labels.",
45684
+ hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;].",
45685
+ length: len
45686
+ };
45687
+ }
45644
45688
  {
45645
45689
  const caret0 = Math.max(0, column - 1);
45646
45690
  const openIdx = lineStr.lastIndexOf("[", caret0);
@@ -45682,9 +45726,20 @@ function mapFlowchartParserError(err, text) {
45682
45726
  length: len
45683
45727
  };
45684
45728
  }
45729
+ if (seg.includes("{") || seg.includes("}")) {
45730
+ return {
45731
+ line,
45732
+ column,
45733
+ severity: "error",
45734
+ code: "FL-LABEL-CURLY-IN-UNQUOTED",
45735
+ message: "Curly braces are not supported inside unquoted node labels.",
45736
+ hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;].",
45737
+ length: len
45738
+ };
45739
+ }
45685
45740
  }
45686
45741
  }
45687
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
45742
+ if (tokType === "QuotedString") {
45688
45743
  return {
45689
45744
  line,
45690
45745
  column,
@@ -45695,6 +45750,17 @@ function mapFlowchartParserError(err, text) {
45695
45750
  length: len
45696
45751
  };
45697
45752
  }
45753
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
45754
+ return {
45755
+ line,
45756
+ column,
45757
+ severity: "error",
45758
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
45759
+ message: "Square brackets are not supported inside unquoted node labels.",
45760
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
45761
+ length: len
45762
+ };
45763
+ }
45698
45764
  const q = findInnerQuoteIssue("[");
45699
45765
  if (q?.kind === "escaped") {
45700
45766
  return { line, column: q.column, severity: "error", code: "FL-LABEL-ESCAPED-QUOTE", message: 'Escaped quotes (\\") in node labels are not supported by Mermaid. Use &quot; instead.', hint: 'Prefer "He said &quot;Hi&quot;".', length: 2 };
@@ -45705,7 +45771,7 @@ function mapFlowchartParserError(err, text) {
45705
45771
  return { line, column, severity: "error", code: "FL-NODE-UNCLOSED-BRACKET", message: "Unclosed '['. Add a matching ']' before the arrow or newline.", hint: "Example: A[Label] --> B", length: 1 };
45706
45772
  }
45707
45773
  if (expecting(err, "RoundClose")) {
45708
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
45774
+ if (tokType === "QuotedString") {
45709
45775
  return {
45710
45776
  line,
45711
45777
  column,
@@ -45716,6 +45782,17 @@ function mapFlowchartParserError(err, text) {
45716
45782
  length: len
45717
45783
  };
45718
45784
  }
45785
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
45786
+ return {
45787
+ line,
45788
+ column,
45789
+ severity: "error",
45790
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
45791
+ message: "Square brackets are not supported inside unquoted node labels.",
45792
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
45793
+ length: len
45794
+ };
45795
+ }
45719
45796
  {
45720
45797
  const caret0 = Math.max(0, column - 1);
45721
45798
  const openIdx = lineStr.lastIndexOf("(", caret0);
@@ -45744,7 +45821,7 @@ function mapFlowchartParserError(err, text) {
45744
45821
  return { line, column, severity: "error", code: "FL-NODE-UNCLOSED-BRACKET", message: "Unclosed '('. Add a matching ')'.", hint: "Example: B(Label)", length: 1 };
45745
45822
  }
45746
45823
  if (expecting(err, "DiamondClose")) {
45747
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
45824
+ if (tokType === "QuotedString") {
45748
45825
  return {
45749
45826
  line,
45750
45827
  column,
@@ -45755,6 +45832,17 @@ function mapFlowchartParserError(err, text) {
45755
45832
  length: len
45756
45833
  };
45757
45834
  }
45835
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
45836
+ return {
45837
+ line,
45838
+ column,
45839
+ severity: "error",
45840
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
45841
+ message: "Square brackets are not supported inside unquoted node labels.",
45842
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
45843
+ length: len
45844
+ };
45845
+ }
45758
45846
  {
45759
45847
  const caret0 = Math.max(0, column - 1);
45760
45848
  const openIdx = lineStr.lastIndexOf("{", caret0);
@@ -46597,7 +46685,7 @@ function validateFlowchart(text, options = {}) {
46597
46685
  const byLine = /* @__PURE__ */ new Map();
46598
46686
  const collect = (arr) => {
46599
46687
  for (const e of arr || []) {
46600
- if (e && (e.code === "FL-LABEL-PARENS-UNQUOTED" || e.code === "FL-LABEL-AT-IN-UNQUOTED" || e.code === "FL-LABEL-QUOTE-IN-UNQUOTED" || e.code === "FL-LABEL-SLASH-UNQUOTED")) {
46688
+ if (e && (e.code === "FL-LABEL-PARENS-UNQUOTED" || e.code === "FL-LABEL-AT-IN-UNQUOTED" || e.code === "FL-LABEL-QUOTE-IN-UNQUOTED" || e.code === "FL-LABEL-SLASH-UNQUOTED" || e.code === "FL-LABEL-CURLY-IN-UNQUOTED" || e.code === "FL-LABEL-BRACKET-IN-UNQUOTED")) {
46601
46689
  const ln = e.line ?? 0;
46602
46690
  const col = e.column ?? 1;
46603
46691
  const list = byLine.get(ln) || [];
@@ -46679,6 +46767,9 @@ function validateFlowchart(text, options = {}) {
46679
46767
  const hasParens = seg.includes("(") || seg.includes(")");
46680
46768
  const hasAt = seg.includes("@");
46681
46769
  const hasQuote = seg.includes('"');
46770
+ const hasCurly = seg.includes("{") || seg.includes("}");
46771
+ const hasBracket = seg.includes("[") || seg.includes("]");
46772
+ const isDoubleSquare = raw.slice(i, i + 2) === "[[" && raw.slice(j - 2, j) === "]]";
46682
46773
  const isSingleQuoted = /^'[^]*'$/.test(trimmed);
46683
46774
  const hasLeadingSlash = lsp === "/" || lsp === "\\";
46684
46775
  if (!covered && !isQuoted && !isParenWrapped && hasParens) {
@@ -46696,6 +46787,16 @@ function validateFlowchart(text, options = {}) {
46696
46787
  existing.push({ start: startCol, end: endCol });
46697
46788
  byLine.set(ln, existing);
46698
46789
  }
46790
+ if (!covered && !isQuoted && !isSlashPair && hasCurly) {
46791
+ errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-CURLY-IN-UNQUOTED", message: "Curly braces are not supported inside unquoted node labels.", hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;]." });
46792
+ existing.push({ start: startCol, end: endCol });
46793
+ byLine.set(ln, existing);
46794
+ }
46795
+ if (!covered && !isQuoted && !isSlashPair && !isDoubleSquare && hasBracket) {
46796
+ errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-BRACKET-IN-UNQUOTED", message: "Square brackets are not supported inside unquoted node labels.", hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]' });
46797
+ existing.push({ start: startCol, end: endCol });
46798
+ byLine.set(ln, existing);
46799
+ }
46699
46800
  if (!covered && !isQuoted && !isSlashPair && hasQuote && !isSingleQuoted) {
46700
46801
  errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-QUOTE-IN-UNQUOTED", message: "Quotes are not allowed inside unquoted node labels. Use &quot; for quotes or wrap the entire label in quotes.", hint: 'Example: C["HTML Output: data-trigger-visibility=&quot;true&quot;"]' });
46701
46802
  existing.push({ start: startCol, end: endCol });
@@ -49056,7 +49157,7 @@ function computeFixes(text, errors, level = "safe") {
49056
49157
  }
49057
49158
  continue;
49058
49159
  }
49059
- if (is("FL-EDGE-LABEL-BRACKET", e)) {
49160
+ if (is("FL-EDGE-LABEL-BRACKET", e) || is("FL-EDGE-LABEL-CURLY-IN-PIPES", e)) {
49060
49161
  const lineText = lineTextAt(text, e.line);
49061
49162
  const firstBar = lineText.indexOf("|");
49062
49163
  const secondBar = firstBar >= 0 ? lineText.indexOf("|", firstBar + 1) : -1;
@@ -49064,7 +49165,8 @@ function computeFixes(text, errors, level = "safe") {
49064
49165
  const before = lineText.slice(0, firstBar + 1);
49065
49166
  const label = lineText.slice(firstBar + 1, secondBar);
49066
49167
  const after = lineText.slice(secondBar);
49067
- const fixedLabel = label.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
49168
+ let fixedLabel = label.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
49169
+ fixedLabel = fixedLabel.replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
49068
49170
  const fixedLine = before + fixedLabel + after;
49069
49171
  const finalLine = fixedLine.replace(/\[([^\]]*)\]/g, (m, seg) => "[" + String(seg).replace(/`/g, "") + "]");
49070
49172
  edits.push({ start: { line: e.line, column: 1 }, end: { line: e.line, column: lineText.length + 1 }, newText: finalLine });
@@ -49704,7 +49806,7 @@ function computeFixes(text, errors, level = "safe") {
49704
49806
  }
49705
49807
  continue;
49706
49808
  }
49707
- if (is("FL-LABEL-PARENS-UNQUOTED", e) || is("FL-LABEL-AT-IN-UNQUOTED", e) || is("FL-LABEL-SLASH-UNQUOTED", e)) {
49809
+ if (is("FL-LABEL-PARENS-UNQUOTED", e) || is("FL-LABEL-AT-IN-UNQUOTED", e) || is("FL-LABEL-SLASH-UNQUOTED", e) || is("FL-LABEL-CURLY-IN-UNQUOTED", e) || is("FL-LABEL-BRACKET-IN-UNQUOTED", e)) {
49708
49810
  if (level === "safe" || level === "all") {
49709
49811
  if (patchedLines.has(e.line))
49710
49812
  continue;
@@ -49745,7 +49847,7 @@ function computeFixes(text, errors, level = "safe") {
49745
49847
  if (openIdx === -1)
49746
49848
  break;
49747
49849
  const contentStart = openIdx + shape.open.length;
49748
- const closeIdx = shape.open === "(" && shape.close === ")" ? findMatchingCloser(lineText, openIdx, shape.open, shape.close) : lineText.indexOf(shape.close, contentStart);
49850
+ const closeIdx = shape.open === "(" && shape.close === ")" || shape.open === "[" && shape.close === "]" ? findMatchingCloser(lineText, openIdx, shape.open, shape.close) : lineText.indexOf(shape.close, contentStart);
49749
49851
  if (closeIdx === -1)
49750
49852
  break;
49751
49853
  if (openIdx <= caret0 && caret0 < closeIdx) {
@@ -49765,11 +49867,21 @@ function computeFixes(text, errors, level = "safe") {
49765
49867
  const isSlashPair = (l, r) => l === "/" && r === "/" || l === "\\" && r === "\\" || l === "/" && r === "\\" || l === "\\" && r === "/";
49766
49868
  const isParallelogramShape = core.length >= 2 && isSlashPair(left, right);
49767
49869
  let replaced;
49768
- if (!isParallelogramShape) {
49769
- const escaped = inner.replace(/`/g, "").replace(/\"/g, "&quot;").replace(/"/g, "&quot;");
49870
+ if (is("FL-LABEL-BRACKET-IN-UNQUOTED", e) && !isParallelogramShape) {
49871
+ const hasOtherHazards = /[(){}@]/.test(inner) || inner.includes('"') || inner.includes('\\"');
49872
+ if (hasOtherHazards) {
49873
+ const escaped = inner.replace(/`/g, "").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
49874
+ replaced = '"' + escaped + '"';
49875
+ } else {
49876
+ replaced = inner.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
49877
+ }
49878
+ } else if (is("FL-LABEL-CURLY-IN-UNQUOTED", e)) {
49879
+ replaced = inner.replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
49880
+ } else if (!isParallelogramShape) {
49881
+ const escaped = inner.replace(/`/g, "").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
49770
49882
  replaced = '"' + escaped + '"';
49771
49883
  } else {
49772
- replaced = inner.replace(/`/g, "").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/\"/g, "&quot;").replace(/"/g, "&quot;");
49884
+ replaced = inner.replace(/`/g, "").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/\[/g, "&#91;").replace(/\]/g, "&#93;").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
49773
49885
  }
49774
49886
  if (replaced !== inner) {
49775
49887
  edits.push({ start: { line: e.line, column: contentStart + 1 }, end: { line: e.line, column: closeIdx + 1 }, newText: replaced });
@@ -228,7 +228,17 @@ export function parseAndResolvePaths(pathStr, cwd) {
228
228
  if (!pathStr) return [];
229
229
 
230
230
  // Split on comma and trim whitespace
231
- const paths = pathStr.split(',').map(p => p.trim()).filter(p => p.length > 0);
231
+ let paths = pathStr.split(',').map(p => p.trim()).filter(p => p.length > 0);
232
+
233
+ // Auto-fix: model sometimes passes space-separated file paths as one string
234
+ // e.g. "src/ranking.rs src/simd_ranking.rs" — split if each part looks like a path
235
+ paths = paths.flatMap(p => {
236
+ if (!/\s/.test(p)) return [p];
237
+ const parts = p.split(/\s+/).filter(Boolean);
238
+ if (parts.length <= 1) return [p];
239
+ const allLookLikePaths = parts.every(part => /[/\\]/.test(part) || /\.\w+/.test(part));
240
+ return allLookLikePaths ? parts : [p];
241
+ });
232
242
 
233
243
  // Resolve relative paths against cwd
234
244
  return paths.map(p => {
@@ -105,13 +105,42 @@ function normalizeTargets(targets) {
105
105
  if (typeof target !== 'string') continue;
106
106
  const trimmed = target.trim();
107
107
  if (!trimmed || seen.has(trimmed)) continue;
108
- seen.add(trimmed);
109
- normalized.push(trimmed);
108
+
109
+ // Auto-fix: model sometimes puts multiple space-separated file paths in one string
110
+ // e.g. "src/ranking.rs src/simd_ranking.rs" — split them apart
111
+ const subTargets = splitSpaceSeparatedPaths(trimmed);
112
+ for (const sub of subTargets) {
113
+ if (!seen.has(sub)) {
114
+ seen.add(sub);
115
+ normalized.push(sub);
116
+ }
117
+ }
110
118
  }
111
119
 
112
120
  return normalized;
113
121
  }
114
122
 
123
+ /**
124
+ * Split a string that may contain multiple space-separated file paths.
125
+ * Detects patterns like "path/file.ext path2/file2.ext" and splits them.
126
+ * Preserves single paths and paths with suffixes like ":10-20" or "#Symbol".
127
+ */
128
+ function splitSpaceSeparatedPaths(target) {
129
+ // If no spaces, it's a single target
130
+ if (!/\s/.test(target)) return [target];
131
+
132
+ // Split on whitespace and check if parts look like file paths
133
+ const parts = target.split(/\s+/).filter(Boolean);
134
+ if (parts.length <= 1) return [target];
135
+
136
+ // Check if each part looks like a file path (has a dot extension or path separator)
137
+ const allLookLikePaths = parts.every(p => /[/\\]/.test(p) || /\.\w+/.test(p));
138
+ if (allLookLikePaths) return parts;
139
+
140
+ // Not confident these are separate paths — return as-is
141
+ return [target];
142
+ }
143
+
115
144
  function extractJsonSnippet(text) {
116
145
  const jsonBlockMatch = text.match(/```json\s*([\s\S]*?)```/i);
117
146
  if (jsonBlockMatch) {
@@ -27070,7 +27070,14 @@ function parseTargets(targets) {
27070
27070
  }
27071
27071
  function parseAndResolvePaths(pathStr, cwd) {
27072
27072
  if (!pathStr) return [];
27073
- const paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
27073
+ let paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
27074
+ paths = paths.flatMap((p) => {
27075
+ if (!/\s/.test(p)) return [p];
27076
+ const parts = p.split(/\s+/).filter(Boolean);
27077
+ if (parts.length <= 1) return [p];
27078
+ const allLookLikePaths = parts.every((part) => /[/\\]/.test(part) || /\.\w+/.test(part));
27079
+ return allLookLikePaths ? parts : [p];
27080
+ });
27074
27081
  return paths.map((p) => {
27075
27082
  if ((0, import_path5.isAbsolute)(p)) {
27076
27083
  return p;
@@ -27303,11 +27310,24 @@ function normalizeTargets(targets) {
27303
27310
  if (typeof target !== "string") continue;
27304
27311
  const trimmed = target.trim();
27305
27312
  if (!trimmed || seen.has(trimmed)) continue;
27306
- seen.add(trimmed);
27307
- normalized.push(trimmed);
27313
+ const subTargets = splitSpaceSeparatedPaths(trimmed);
27314
+ for (const sub of subTargets) {
27315
+ if (!seen.has(sub)) {
27316
+ seen.add(sub);
27317
+ normalized.push(sub);
27318
+ }
27319
+ }
27308
27320
  }
27309
27321
  return normalized;
27310
27322
  }
27323
+ function splitSpaceSeparatedPaths(target) {
27324
+ if (!/\s/.test(target)) return [target];
27325
+ const parts = target.split(/\s+/).filter(Boolean);
27326
+ if (parts.length <= 1) return [target];
27327
+ const allLookLikePaths = parts.every((p) => /[/\\]/.test(p) || /\.\w+/.test(p));
27328
+ if (allLookLikePaths) return parts;
27329
+ return [target];
27330
+ }
27311
27331
  function extractJsonSnippet(text) {
27312
27332
  const jsonBlockMatch = text.match(/```json\s*([\s\S]*?)```/i);
27313
27333
  if (jsonBlockMatch) {
@@ -62012,6 +62032,7 @@ var init_parser2 = __esm({
62012
62032
  { ALT: () => this.CONSUME(Identifier) },
62013
62033
  { ALT: () => this.CONSUME(Text) },
62014
62034
  { ALT: () => this.CONSUME(NumberLiteral) },
62035
+ { ALT: () => this.CONSUME(ColorValue) },
62015
62036
  // Note: RoundOpen and RoundClose (parentheses) are NOT allowed in unquoted labels
62016
62037
  // to match Mermaid's behavior - use quoted labels like ["text (with parens)"] instead
62017
62038
  // Allow HTML-like tags (e.g., <br/>) inside labels
@@ -62103,6 +62124,7 @@ var init_parser2 = __esm({
62103
62124
  { ALT: () => this.CONSUME(Identifier) },
62104
62125
  { ALT: () => this.CONSUME(Text) },
62105
62126
  { ALT: () => this.CONSUME(NumberLiteral) },
62127
+ { ALT: () => this.CONSUME(ColorValue) },
62106
62128
  // Allow HTML-like angle brackets and slashes for <br/>, <i>, etc.
62107
62129
  { ALT: () => this.CONSUME(AngleLess) },
62108
62130
  { ALT: () => this.CONSUME(AngleOpen) },
@@ -63278,13 +63300,24 @@ function mapFlowchartParserError(err, text) {
63278
63300
  length: len
63279
63301
  };
63280
63302
  }
63281
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
63303
+ if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose" || tokType === "DiamondOpen" || tokType === "DiamondClose") {
63282
63304
  const context = err?.context;
63283
63305
  const inLinkRule = context?.ruleStack?.includes("linkTextInline") || context?.ruleStack?.includes("link") || false;
63284
63306
  const lineContent = allLines[Math.max(0, line - 1)] || "";
63285
63307
  const beforeQuote = lineContent.slice(0, Math.max(0, column - 1));
63286
63308
  const hasLinkBefore = beforeQuote.match(/--\s*$|==\s*$|-\.\s*$|-\.-\s*$|\[\s*$/);
63287
63309
  if (inLinkRule || hasLinkBefore) {
63310
+ if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
63311
+ return {
63312
+ line,
63313
+ column,
63314
+ severity: "error",
63315
+ code: "FL-EDGE-LABEL-CURLY-IN-PIPES",
63316
+ message: "Curly braces { } are not supported inside pipe-delimited edge labels.",
63317
+ hint: "Use HTML entities &#123; and &#125; inside |...|, e.g., --|Resolve $&#123;VAR&#125;|-->",
63318
+ length: len
63319
+ };
63320
+ }
63288
63321
  if (tokType === "SquareOpen" || tokType === "SquareClose") {
63289
63322
  return {
63290
63323
  line,
@@ -63346,6 +63379,17 @@ function mapFlowchartParserError(err, text) {
63346
63379
  length: len
63347
63380
  };
63348
63381
  }
63382
+ if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
63383
+ return {
63384
+ line,
63385
+ column,
63386
+ severity: "error",
63387
+ code: "FL-LABEL-CURLY-IN-UNQUOTED",
63388
+ message: "Curly braces are not supported inside unquoted node labels.",
63389
+ hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;].",
63390
+ length: len
63391
+ };
63392
+ }
63349
63393
  {
63350
63394
  const caret0 = Math.max(0, column - 1);
63351
63395
  const openIdx = lineStr.lastIndexOf("[", caret0);
@@ -63387,9 +63431,20 @@ function mapFlowchartParserError(err, text) {
63387
63431
  length: len
63388
63432
  };
63389
63433
  }
63434
+ if (seg.includes("{") || seg.includes("}")) {
63435
+ return {
63436
+ line,
63437
+ column,
63438
+ severity: "error",
63439
+ code: "FL-LABEL-CURLY-IN-UNQUOTED",
63440
+ message: "Curly braces are not supported inside unquoted node labels.",
63441
+ hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;].",
63442
+ length: len
63443
+ };
63444
+ }
63390
63445
  }
63391
63446
  }
63392
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
63447
+ if (tokType === "QuotedString") {
63393
63448
  return {
63394
63449
  line,
63395
63450
  column,
@@ -63400,6 +63455,17 @@ function mapFlowchartParserError(err, text) {
63400
63455
  length: len
63401
63456
  };
63402
63457
  }
63458
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
63459
+ return {
63460
+ line,
63461
+ column,
63462
+ severity: "error",
63463
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
63464
+ message: "Square brackets are not supported inside unquoted node labels.",
63465
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
63466
+ length: len
63467
+ };
63468
+ }
63403
63469
  const q = findInnerQuoteIssue("[");
63404
63470
  if (q?.kind === "escaped") {
63405
63471
  return { line, column: q.column, severity: "error", code: "FL-LABEL-ESCAPED-QUOTE", message: 'Escaped quotes (\\") in node labels are not supported by Mermaid. Use &quot; instead.', hint: 'Prefer "He said &quot;Hi&quot;".', length: 2 };
@@ -63410,7 +63476,7 @@ function mapFlowchartParserError(err, text) {
63410
63476
  return { line, column, severity: "error", code: "FL-NODE-UNCLOSED-BRACKET", message: "Unclosed '['. Add a matching ']' before the arrow or newline.", hint: "Example: A[Label] --> B", length: 1 };
63411
63477
  }
63412
63478
  if (expecting(err, "RoundClose")) {
63413
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
63479
+ if (tokType === "QuotedString") {
63414
63480
  return {
63415
63481
  line,
63416
63482
  column,
@@ -63421,6 +63487,17 @@ function mapFlowchartParserError(err, text) {
63421
63487
  length: len
63422
63488
  };
63423
63489
  }
63490
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
63491
+ return {
63492
+ line,
63493
+ column,
63494
+ severity: "error",
63495
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
63496
+ message: "Square brackets are not supported inside unquoted node labels.",
63497
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
63498
+ length: len
63499
+ };
63500
+ }
63424
63501
  {
63425
63502
  const caret0 = Math.max(0, column - 1);
63426
63503
  const openIdx = lineStr.lastIndexOf("(", caret0);
@@ -63449,7 +63526,7 @@ function mapFlowchartParserError(err, text) {
63449
63526
  return { line, column, severity: "error", code: "FL-NODE-UNCLOSED-BRACKET", message: "Unclosed '('. Add a matching ')'.", hint: "Example: B(Label)", length: 1 };
63450
63527
  }
63451
63528
  if (expecting(err, "DiamondClose")) {
63452
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
63529
+ if (tokType === "QuotedString") {
63453
63530
  return {
63454
63531
  line,
63455
63532
  column,
@@ -63460,6 +63537,17 @@ function mapFlowchartParserError(err, text) {
63460
63537
  length: len
63461
63538
  };
63462
63539
  }
63540
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
63541
+ return {
63542
+ line,
63543
+ column,
63544
+ severity: "error",
63545
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
63546
+ message: "Square brackets are not supported inside unquoted node labels.",
63547
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
63548
+ length: len
63549
+ };
63550
+ }
63463
63551
  {
63464
63552
  const caret0 = Math.max(0, column - 1);
63465
63553
  const openIdx = lineStr.lastIndexOf("{", caret0);
@@ -64302,7 +64390,7 @@ function validateFlowchart(text, options = {}) {
64302
64390
  const byLine = /* @__PURE__ */ new Map();
64303
64391
  const collect = (arr) => {
64304
64392
  for (const e of arr || []) {
64305
- if (e && (e.code === "FL-LABEL-PARENS-UNQUOTED" || e.code === "FL-LABEL-AT-IN-UNQUOTED" || e.code === "FL-LABEL-QUOTE-IN-UNQUOTED" || e.code === "FL-LABEL-SLASH-UNQUOTED")) {
64393
+ if (e && (e.code === "FL-LABEL-PARENS-UNQUOTED" || e.code === "FL-LABEL-AT-IN-UNQUOTED" || e.code === "FL-LABEL-QUOTE-IN-UNQUOTED" || e.code === "FL-LABEL-SLASH-UNQUOTED" || e.code === "FL-LABEL-CURLY-IN-UNQUOTED" || e.code === "FL-LABEL-BRACKET-IN-UNQUOTED")) {
64306
64394
  const ln = e.line ?? 0;
64307
64395
  const col = e.column ?? 1;
64308
64396
  const list = byLine.get(ln) || [];
@@ -64384,6 +64472,9 @@ function validateFlowchart(text, options = {}) {
64384
64472
  const hasParens = seg.includes("(") || seg.includes(")");
64385
64473
  const hasAt = seg.includes("@");
64386
64474
  const hasQuote = seg.includes('"');
64475
+ const hasCurly = seg.includes("{") || seg.includes("}");
64476
+ const hasBracket = seg.includes("[") || seg.includes("]");
64477
+ const isDoubleSquare = raw.slice(i, i + 2) === "[[" && raw.slice(j - 2, j) === "]]";
64387
64478
  const isSingleQuoted = /^'[^]*'$/.test(trimmed);
64388
64479
  const hasLeadingSlash = lsp === "/" || lsp === "\\";
64389
64480
  if (!covered && !isQuoted && !isParenWrapped && hasParens) {
@@ -64401,6 +64492,16 @@ function validateFlowchart(text, options = {}) {
64401
64492
  existing.push({ start: startCol, end: endCol });
64402
64493
  byLine.set(ln, existing);
64403
64494
  }
64495
+ if (!covered && !isQuoted && !isSlashPair && hasCurly) {
64496
+ errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-CURLY-IN-UNQUOTED", message: "Curly braces are not supported inside unquoted node labels.", hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;]." });
64497
+ existing.push({ start: startCol, end: endCol });
64498
+ byLine.set(ln, existing);
64499
+ }
64500
+ if (!covered && !isQuoted && !isSlashPair && !isDoubleSquare && hasBracket) {
64501
+ errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-BRACKET-IN-UNQUOTED", message: "Square brackets are not supported inside unquoted node labels.", hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]' });
64502
+ existing.push({ start: startCol, end: endCol });
64503
+ byLine.set(ln, existing);
64504
+ }
64404
64505
  if (!covered && !isQuoted && !isSlashPair && hasQuote && !isSingleQuoted) {
64405
64506
  errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-QUOTE-IN-UNQUOTED", message: "Quotes are not allowed inside unquoted node labels. Use &quot; for quotes or wrap the entire label in quotes.", hint: 'Example: C["HTML Output: data-trigger-visibility=&quot;true&quot;"]' });
64406
64507
  existing.push({ start: startCol, end: endCol });
@@ -66761,7 +66862,7 @@ function computeFixes(text, errors, level = "safe") {
66761
66862
  }
66762
66863
  continue;
66763
66864
  }
66764
- if (is("FL-EDGE-LABEL-BRACKET", e)) {
66865
+ if (is("FL-EDGE-LABEL-BRACKET", e) || is("FL-EDGE-LABEL-CURLY-IN-PIPES", e)) {
66765
66866
  const lineText = lineTextAt(text, e.line);
66766
66867
  const firstBar = lineText.indexOf("|");
66767
66868
  const secondBar = firstBar >= 0 ? lineText.indexOf("|", firstBar + 1) : -1;
@@ -66769,7 +66870,8 @@ function computeFixes(text, errors, level = "safe") {
66769
66870
  const before = lineText.slice(0, firstBar + 1);
66770
66871
  const label = lineText.slice(firstBar + 1, secondBar);
66771
66872
  const after = lineText.slice(secondBar);
66772
- const fixedLabel = label.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
66873
+ let fixedLabel = label.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
66874
+ fixedLabel = fixedLabel.replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
66773
66875
  const fixedLine = before + fixedLabel + after;
66774
66876
  const finalLine = fixedLine.replace(/\[([^\]]*)\]/g, (m, seg) => "[" + String(seg).replace(/`/g, "") + "]");
66775
66877
  edits.push({ start: { line: e.line, column: 1 }, end: { line: e.line, column: lineText.length + 1 }, newText: finalLine });
@@ -67409,7 +67511,7 @@ function computeFixes(text, errors, level = "safe") {
67409
67511
  }
67410
67512
  continue;
67411
67513
  }
67412
- if (is("FL-LABEL-PARENS-UNQUOTED", e) || is("FL-LABEL-AT-IN-UNQUOTED", e) || is("FL-LABEL-SLASH-UNQUOTED", e)) {
67514
+ if (is("FL-LABEL-PARENS-UNQUOTED", e) || is("FL-LABEL-AT-IN-UNQUOTED", e) || is("FL-LABEL-SLASH-UNQUOTED", e) || is("FL-LABEL-CURLY-IN-UNQUOTED", e) || is("FL-LABEL-BRACKET-IN-UNQUOTED", e)) {
67413
67515
  if (level === "safe" || level === "all") {
67414
67516
  if (patchedLines.has(e.line))
67415
67517
  continue;
@@ -67450,7 +67552,7 @@ function computeFixes(text, errors, level = "safe") {
67450
67552
  if (openIdx === -1)
67451
67553
  break;
67452
67554
  const contentStart = openIdx + shape.open.length;
67453
- const closeIdx = shape.open === "(" && shape.close === ")" ? findMatchingCloser(lineText, openIdx, shape.open, shape.close) : lineText.indexOf(shape.close, contentStart);
67555
+ const closeIdx = shape.open === "(" && shape.close === ")" || shape.open === "[" && shape.close === "]" ? findMatchingCloser(lineText, openIdx, shape.open, shape.close) : lineText.indexOf(shape.close, contentStart);
67454
67556
  if (closeIdx === -1)
67455
67557
  break;
67456
67558
  if (openIdx <= caret0 && caret0 < closeIdx) {
@@ -67470,11 +67572,21 @@ function computeFixes(text, errors, level = "safe") {
67470
67572
  const isSlashPair = (l, r) => l === "/" && r === "/" || l === "\\" && r === "\\" || l === "/" && r === "\\" || l === "\\" && r === "/";
67471
67573
  const isParallelogramShape = core.length >= 2 && isSlashPair(left, right);
67472
67574
  let replaced;
67473
- if (!isParallelogramShape) {
67474
- const escaped = inner.replace(/`/g, "").replace(/\"/g, "&quot;").replace(/"/g, "&quot;");
67575
+ if (is("FL-LABEL-BRACKET-IN-UNQUOTED", e) && !isParallelogramShape) {
67576
+ const hasOtherHazards = /[(){}@]/.test(inner) || inner.includes('"') || inner.includes('\\"');
67577
+ if (hasOtherHazards) {
67578
+ const escaped = inner.replace(/`/g, "").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
67579
+ replaced = '"' + escaped + '"';
67580
+ } else {
67581
+ replaced = inner.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
67582
+ }
67583
+ } else if (is("FL-LABEL-CURLY-IN-UNQUOTED", e)) {
67584
+ replaced = inner.replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
67585
+ } else if (!isParallelogramShape) {
67586
+ const escaped = inner.replace(/`/g, "").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
67475
67587
  replaced = '"' + escaped + '"';
67476
67588
  } else {
67477
- replaced = inner.replace(/`/g, "").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/\"/g, "&quot;").replace(/"/g, "&quot;");
67589
+ replaced = inner.replace(/`/g, "").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/\[/g, "&#91;").replace(/\]/g, "&#93;").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
67478
67590
  }
67479
67591
  if (replaced !== inner) {
67480
67592
  edits.push({ start: { line: e.line, column: contentStart + 1 }, end: { line: e.line, column: closeIdx + 1 }, newText: replaced });
package/cjs/index.cjs CHANGED
@@ -27057,7 +27057,14 @@ function parseTargets(targets) {
27057
27057
  }
27058
27058
  function parseAndResolvePaths(pathStr, cwd) {
27059
27059
  if (!pathStr) return [];
27060
- const paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
27060
+ let paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
27061
+ paths = paths.flatMap((p) => {
27062
+ if (!/\s/.test(p)) return [p];
27063
+ const parts = p.split(/\s+/).filter(Boolean);
27064
+ if (parts.length <= 1) return [p];
27065
+ const allLookLikePaths = parts.every((part) => /[/\\]/.test(part) || /\.\w+/.test(part));
27066
+ return allLookLikePaths ? parts : [p];
27067
+ });
27061
27068
  return paths.map((p) => {
27062
27069
  if ((0, import_path5.isAbsolute)(p)) {
27063
27070
  return p;
@@ -46720,6 +46727,7 @@ var init_parser2 = __esm({
46720
46727
  { ALT: () => this.CONSUME(Identifier) },
46721
46728
  { ALT: () => this.CONSUME(Text) },
46722
46729
  { ALT: () => this.CONSUME(NumberLiteral) },
46730
+ { ALT: () => this.CONSUME(ColorValue) },
46723
46731
  // Note: RoundOpen and RoundClose (parentheses) are NOT allowed in unquoted labels
46724
46732
  // to match Mermaid's behavior - use quoted labels like ["text (with parens)"] instead
46725
46733
  // Allow HTML-like tags (e.g., <br/>) inside labels
@@ -46811,6 +46819,7 @@ var init_parser2 = __esm({
46811
46819
  { ALT: () => this.CONSUME(Identifier) },
46812
46820
  { ALT: () => this.CONSUME(Text) },
46813
46821
  { ALT: () => this.CONSUME(NumberLiteral) },
46822
+ { ALT: () => this.CONSUME(ColorValue) },
46814
46823
  // Allow HTML-like angle brackets and slashes for <br/>, <i>, etc.
46815
46824
  { ALT: () => this.CONSUME(AngleLess) },
46816
46825
  { ALT: () => this.CONSUME(AngleOpen) },
@@ -47986,13 +47995,24 @@ function mapFlowchartParserError(err, text) {
47986
47995
  length: len
47987
47996
  };
47988
47997
  }
47989
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
47998
+ if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose" || tokType === "DiamondOpen" || tokType === "DiamondClose") {
47990
47999
  const context = err?.context;
47991
48000
  const inLinkRule = context?.ruleStack?.includes("linkTextInline") || context?.ruleStack?.includes("link") || false;
47992
48001
  const lineContent = allLines[Math.max(0, line - 1)] || "";
47993
48002
  const beforeQuote = lineContent.slice(0, Math.max(0, column - 1));
47994
48003
  const hasLinkBefore = beforeQuote.match(/--\s*$|==\s*$|-\.\s*$|-\.-\s*$|\[\s*$/);
47995
48004
  if (inLinkRule || hasLinkBefore) {
48005
+ if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
48006
+ return {
48007
+ line,
48008
+ column,
48009
+ severity: "error",
48010
+ code: "FL-EDGE-LABEL-CURLY-IN-PIPES",
48011
+ message: "Curly braces { } are not supported inside pipe-delimited edge labels.",
48012
+ hint: "Use HTML entities &#123; and &#125; inside |...|, e.g., --|Resolve $&#123;VAR&#125;|-->",
48013
+ length: len
48014
+ };
48015
+ }
47996
48016
  if (tokType === "SquareOpen" || tokType === "SquareClose") {
47997
48017
  return {
47998
48018
  line,
@@ -48054,6 +48074,17 @@ function mapFlowchartParserError(err, text) {
48054
48074
  length: len
48055
48075
  };
48056
48076
  }
48077
+ if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
48078
+ return {
48079
+ line,
48080
+ column,
48081
+ severity: "error",
48082
+ code: "FL-LABEL-CURLY-IN-UNQUOTED",
48083
+ message: "Curly braces are not supported inside unquoted node labels.",
48084
+ hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;].",
48085
+ length: len
48086
+ };
48087
+ }
48057
48088
  {
48058
48089
  const caret0 = Math.max(0, column - 1);
48059
48090
  const openIdx = lineStr.lastIndexOf("[", caret0);
@@ -48095,9 +48126,20 @@ function mapFlowchartParserError(err, text) {
48095
48126
  length: len
48096
48127
  };
48097
48128
  }
48129
+ if (seg.includes("{") || seg.includes("}")) {
48130
+ return {
48131
+ line,
48132
+ column,
48133
+ severity: "error",
48134
+ code: "FL-LABEL-CURLY-IN-UNQUOTED",
48135
+ message: "Curly braces are not supported inside unquoted node labels.",
48136
+ hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;].",
48137
+ length: len
48138
+ };
48139
+ }
48098
48140
  }
48099
48141
  }
48100
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
48142
+ if (tokType === "QuotedString") {
48101
48143
  return {
48102
48144
  line,
48103
48145
  column,
@@ -48108,6 +48150,17 @@ function mapFlowchartParserError(err, text) {
48108
48150
  length: len
48109
48151
  };
48110
48152
  }
48153
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
48154
+ return {
48155
+ line,
48156
+ column,
48157
+ severity: "error",
48158
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
48159
+ message: "Square brackets are not supported inside unquoted node labels.",
48160
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
48161
+ length: len
48162
+ };
48163
+ }
48111
48164
  const q = findInnerQuoteIssue("[");
48112
48165
  if (q?.kind === "escaped") {
48113
48166
  return { line, column: q.column, severity: "error", code: "FL-LABEL-ESCAPED-QUOTE", message: 'Escaped quotes (\\") in node labels are not supported by Mermaid. Use &quot; instead.', hint: 'Prefer "He said &quot;Hi&quot;".', length: 2 };
@@ -48118,7 +48171,7 @@ function mapFlowchartParserError(err, text) {
48118
48171
  return { line, column, severity: "error", code: "FL-NODE-UNCLOSED-BRACKET", message: "Unclosed '['. Add a matching ']' before the arrow or newline.", hint: "Example: A[Label] --> B", length: 1 };
48119
48172
  }
48120
48173
  if (expecting(err, "RoundClose")) {
48121
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
48174
+ if (tokType === "QuotedString") {
48122
48175
  return {
48123
48176
  line,
48124
48177
  column,
@@ -48129,6 +48182,17 @@ function mapFlowchartParserError(err, text) {
48129
48182
  length: len
48130
48183
  };
48131
48184
  }
48185
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
48186
+ return {
48187
+ line,
48188
+ column,
48189
+ severity: "error",
48190
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
48191
+ message: "Square brackets are not supported inside unquoted node labels.",
48192
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
48193
+ length: len
48194
+ };
48195
+ }
48132
48196
  {
48133
48197
  const caret0 = Math.max(0, column - 1);
48134
48198
  const openIdx = lineStr.lastIndexOf("(", caret0);
@@ -48157,7 +48221,7 @@ function mapFlowchartParserError(err, text) {
48157
48221
  return { line, column, severity: "error", code: "FL-NODE-UNCLOSED-BRACKET", message: "Unclosed '('. Add a matching ')'.", hint: "Example: B(Label)", length: 1 };
48158
48222
  }
48159
48223
  if (expecting(err, "DiamondClose")) {
48160
- if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
48224
+ if (tokType === "QuotedString") {
48161
48225
  return {
48162
48226
  line,
48163
48227
  column,
@@ -48168,6 +48232,17 @@ function mapFlowchartParserError(err, text) {
48168
48232
  length: len
48169
48233
  };
48170
48234
  }
48235
+ if (tokType === "SquareOpen" || tokType === "SquareClose") {
48236
+ return {
48237
+ line,
48238
+ column,
48239
+ severity: "error",
48240
+ code: "FL-LABEL-BRACKET-IN-UNQUOTED",
48241
+ message: "Square brackets are not supported inside unquoted node labels.",
48242
+ hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
48243
+ length: len
48244
+ };
48245
+ }
48171
48246
  {
48172
48247
  const caret0 = Math.max(0, column - 1);
48173
48248
  const openIdx = lineStr.lastIndexOf("{", caret0);
@@ -49010,7 +49085,7 @@ function validateFlowchart(text, options = {}) {
49010
49085
  const byLine = /* @__PURE__ */ new Map();
49011
49086
  const collect = (arr) => {
49012
49087
  for (const e of arr || []) {
49013
- if (e && (e.code === "FL-LABEL-PARENS-UNQUOTED" || e.code === "FL-LABEL-AT-IN-UNQUOTED" || e.code === "FL-LABEL-QUOTE-IN-UNQUOTED" || e.code === "FL-LABEL-SLASH-UNQUOTED")) {
49088
+ if (e && (e.code === "FL-LABEL-PARENS-UNQUOTED" || e.code === "FL-LABEL-AT-IN-UNQUOTED" || e.code === "FL-LABEL-QUOTE-IN-UNQUOTED" || e.code === "FL-LABEL-SLASH-UNQUOTED" || e.code === "FL-LABEL-CURLY-IN-UNQUOTED" || e.code === "FL-LABEL-BRACKET-IN-UNQUOTED")) {
49014
49089
  const ln = e.line ?? 0;
49015
49090
  const col = e.column ?? 1;
49016
49091
  const list = byLine.get(ln) || [];
@@ -49092,6 +49167,9 @@ function validateFlowchart(text, options = {}) {
49092
49167
  const hasParens = seg.includes("(") || seg.includes(")");
49093
49168
  const hasAt = seg.includes("@");
49094
49169
  const hasQuote = seg.includes('"');
49170
+ const hasCurly = seg.includes("{") || seg.includes("}");
49171
+ const hasBracket = seg.includes("[") || seg.includes("]");
49172
+ const isDoubleSquare = raw.slice(i, i + 2) === "[[" && raw.slice(j - 2, j) === "]]";
49095
49173
  const isSingleQuoted = /^'[^]*'$/.test(trimmed);
49096
49174
  const hasLeadingSlash = lsp === "/" || lsp === "\\";
49097
49175
  if (!covered && !isQuoted && !isParenWrapped && hasParens) {
@@ -49109,6 +49187,16 @@ function validateFlowchart(text, options = {}) {
49109
49187
  existing.push({ start: startCol, end: endCol });
49110
49188
  byLine.set(ln, existing);
49111
49189
  }
49190
+ if (!covered && !isQuoted && !isSlashPair && hasCurly) {
49191
+ errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-CURLY-IN-UNQUOTED", message: "Curly braces are not supported inside unquoted node labels.", hint: "Use &#123; and &#125; for literal braces, e.g., C[Substitute &#123;params&#125;]." });
49192
+ existing.push({ start: startCol, end: endCol });
49193
+ byLine.set(ln, existing);
49194
+ }
49195
+ if (!covered && !isQuoted && !isSlashPair && !isDoubleSquare && hasBracket) {
49196
+ errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-BRACKET-IN-UNQUOTED", message: "Square brackets are not supported inside unquoted node labels.", hint: 'Use &#91; and &#93; for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]' });
49197
+ existing.push({ start: startCol, end: endCol });
49198
+ byLine.set(ln, existing);
49199
+ }
49112
49200
  if (!covered && !isQuoted && !isSlashPair && hasQuote && !isSingleQuoted) {
49113
49201
  errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-QUOTE-IN-UNQUOTED", message: "Quotes are not allowed inside unquoted node labels. Use &quot; for quotes or wrap the entire label in quotes.", hint: 'Example: C["HTML Output: data-trigger-visibility=&quot;true&quot;"]' });
49114
49202
  existing.push({ start: startCol, end: endCol });
@@ -51469,7 +51557,7 @@ function computeFixes(text, errors, level = "safe") {
51469
51557
  }
51470
51558
  continue;
51471
51559
  }
51472
- if (is("FL-EDGE-LABEL-BRACKET", e)) {
51560
+ if (is("FL-EDGE-LABEL-BRACKET", e) || is("FL-EDGE-LABEL-CURLY-IN-PIPES", e)) {
51473
51561
  const lineText = lineTextAt(text, e.line);
51474
51562
  const firstBar = lineText.indexOf("|");
51475
51563
  const secondBar = firstBar >= 0 ? lineText.indexOf("|", firstBar + 1) : -1;
@@ -51477,7 +51565,8 @@ function computeFixes(text, errors, level = "safe") {
51477
51565
  const before = lineText.slice(0, firstBar + 1);
51478
51566
  const label = lineText.slice(firstBar + 1, secondBar);
51479
51567
  const after = lineText.slice(secondBar);
51480
- const fixedLabel = label.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
51568
+ let fixedLabel = label.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
51569
+ fixedLabel = fixedLabel.replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
51481
51570
  const fixedLine = before + fixedLabel + after;
51482
51571
  const finalLine = fixedLine.replace(/\[([^\]]*)\]/g, (m, seg) => "[" + String(seg).replace(/`/g, "") + "]");
51483
51572
  edits.push({ start: { line: e.line, column: 1 }, end: { line: e.line, column: lineText.length + 1 }, newText: finalLine });
@@ -52117,7 +52206,7 @@ function computeFixes(text, errors, level = "safe") {
52117
52206
  }
52118
52207
  continue;
52119
52208
  }
52120
- if (is("FL-LABEL-PARENS-UNQUOTED", e) || is("FL-LABEL-AT-IN-UNQUOTED", e) || is("FL-LABEL-SLASH-UNQUOTED", e)) {
52209
+ if (is("FL-LABEL-PARENS-UNQUOTED", e) || is("FL-LABEL-AT-IN-UNQUOTED", e) || is("FL-LABEL-SLASH-UNQUOTED", e) || is("FL-LABEL-CURLY-IN-UNQUOTED", e) || is("FL-LABEL-BRACKET-IN-UNQUOTED", e)) {
52121
52210
  if (level === "safe" || level === "all") {
52122
52211
  if (patchedLines.has(e.line))
52123
52212
  continue;
@@ -52158,7 +52247,7 @@ function computeFixes(text, errors, level = "safe") {
52158
52247
  if (openIdx === -1)
52159
52248
  break;
52160
52249
  const contentStart = openIdx + shape.open.length;
52161
- const closeIdx = shape.open === "(" && shape.close === ")" ? findMatchingCloser(lineText, openIdx, shape.open, shape.close) : lineText.indexOf(shape.close, contentStart);
52250
+ const closeIdx = shape.open === "(" && shape.close === ")" || shape.open === "[" && shape.close === "]" ? findMatchingCloser(lineText, openIdx, shape.open, shape.close) : lineText.indexOf(shape.close, contentStart);
52162
52251
  if (closeIdx === -1)
52163
52252
  break;
52164
52253
  if (openIdx <= caret0 && caret0 < closeIdx) {
@@ -52178,11 +52267,21 @@ function computeFixes(text, errors, level = "safe") {
52178
52267
  const isSlashPair = (l, r) => l === "/" && r === "/" || l === "\\" && r === "\\" || l === "/" && r === "\\" || l === "\\" && r === "/";
52179
52268
  const isParallelogramShape = core.length >= 2 && isSlashPair(left, right);
52180
52269
  let replaced;
52181
- if (!isParallelogramShape) {
52182
- const escaped = inner.replace(/`/g, "").replace(/\"/g, "&quot;").replace(/"/g, "&quot;");
52270
+ if (is("FL-LABEL-BRACKET-IN-UNQUOTED", e) && !isParallelogramShape) {
52271
+ const hasOtherHazards = /[(){}@]/.test(inner) || inner.includes('"') || inner.includes('\\"');
52272
+ if (hasOtherHazards) {
52273
+ const escaped = inner.replace(/`/g, "").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
52274
+ replaced = '"' + escaped + '"';
52275
+ } else {
52276
+ replaced = inner.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
52277
+ }
52278
+ } else if (is("FL-LABEL-CURLY-IN-UNQUOTED", e)) {
52279
+ replaced = inner.replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
52280
+ } else if (!isParallelogramShape) {
52281
+ const escaped = inner.replace(/`/g, "").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
52183
52282
  replaced = '"' + escaped + '"';
52184
52283
  } else {
52185
- replaced = inner.replace(/`/g, "").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/\"/g, "&quot;").replace(/"/g, "&quot;");
52284
+ replaced = inner.replace(/`/g, "").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/\[/g, "&#91;").replace(/\]/g, "&#93;").replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
52186
52285
  }
52187
52286
  if (replaced !== inner) {
52188
52287
  edits.push({ start: { line: e.line, column: contentStart + 1 }, end: { line: e.line, column: closeIdx + 1 }, newText: replaced });
@@ -100920,11 +101019,24 @@ function normalizeTargets(targets) {
100920
101019
  if (typeof target !== "string") continue;
100921
101020
  const trimmed = target.trim();
100922
101021
  if (!trimmed || seen.has(trimmed)) continue;
100923
- seen.add(trimmed);
100924
- normalized.push(trimmed);
101022
+ const subTargets = splitSpaceSeparatedPaths(trimmed);
101023
+ for (const sub of subTargets) {
101024
+ if (!seen.has(sub)) {
101025
+ seen.add(sub);
101026
+ normalized.push(sub);
101027
+ }
101028
+ }
100925
101029
  }
100926
101030
  return normalized;
100927
101031
  }
101032
+ function splitSpaceSeparatedPaths(target) {
101033
+ if (!/\s/.test(target)) return [target];
101034
+ const parts = target.split(/\s+/).filter(Boolean);
101035
+ if (parts.length <= 1) return [target];
101036
+ const allLookLikePaths = parts.every((p) => /[/\\]/.test(p) || /\.\w+/.test(p));
101037
+ if (allLookLikePaths) return parts;
101038
+ return [target];
101039
+ }
100928
101040
  function extractJsonSnippet(text) {
100929
101041
  const jsonBlockMatch = text.match(/```json\s*([\s\S]*?)```/i);
100930
101042
  if (jsonBlockMatch) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc286",
3
+ "version": "0.6.0-rc288",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -80,11 +80,11 @@
80
80
  "@anthropic-ai/claude-agent-sdk": "^0.1.46",
81
81
  "@modelcontextprotocol/sdk": "^1.0.0",
82
82
  "@nyariv/sandboxjs": "github:probelabs/SandboxJS",
83
- "@probelabs/maid": "^0.0.26",
83
+ "@probelabs/maid": "^0.0.27",
84
84
  "acorn": "^8.15.0",
85
85
  "acorn-walk": "^8.3.4",
86
86
  "adm-zip": "^0.5.16",
87
- "ai": "^6.0.121",
87
+ "ai": "^6.0.0",
88
88
  "ajv": "^8.17.1",
89
89
  "astring": "^1.9.0",
90
90
  "axios": "^1.8.3",
@@ -228,7 +228,17 @@ export function parseAndResolvePaths(pathStr, cwd) {
228
228
  if (!pathStr) return [];
229
229
 
230
230
  // Split on comma and trim whitespace
231
- const paths = pathStr.split(',').map(p => p.trim()).filter(p => p.length > 0);
231
+ let paths = pathStr.split(',').map(p => p.trim()).filter(p => p.length > 0);
232
+
233
+ // Auto-fix: model sometimes passes space-separated file paths as one string
234
+ // e.g. "src/ranking.rs src/simd_ranking.rs" — split if each part looks like a path
235
+ paths = paths.flatMap(p => {
236
+ if (!/\s/.test(p)) return [p];
237
+ const parts = p.split(/\s+/).filter(Boolean);
238
+ if (parts.length <= 1) return [p];
239
+ const allLookLikePaths = parts.every(part => /[/\\]/.test(part) || /\.\w+/.test(part));
240
+ return allLookLikePaths ? parts : [p];
241
+ });
232
242
 
233
243
  // Resolve relative paths against cwd
234
244
  return paths.map(p => {
@@ -105,13 +105,42 @@ function normalizeTargets(targets) {
105
105
  if (typeof target !== 'string') continue;
106
106
  const trimmed = target.trim();
107
107
  if (!trimmed || seen.has(trimmed)) continue;
108
- seen.add(trimmed);
109
- normalized.push(trimmed);
108
+
109
+ // Auto-fix: model sometimes puts multiple space-separated file paths in one string
110
+ // e.g. "src/ranking.rs src/simd_ranking.rs" — split them apart
111
+ const subTargets = splitSpaceSeparatedPaths(trimmed);
112
+ for (const sub of subTargets) {
113
+ if (!seen.has(sub)) {
114
+ seen.add(sub);
115
+ normalized.push(sub);
116
+ }
117
+ }
110
118
  }
111
119
 
112
120
  return normalized;
113
121
  }
114
122
 
123
+ /**
124
+ * Split a string that may contain multiple space-separated file paths.
125
+ * Detects patterns like "path/file.ext path2/file2.ext" and splits them.
126
+ * Preserves single paths and paths with suffixes like ":10-20" or "#Symbol".
127
+ */
128
+ function splitSpaceSeparatedPaths(target) {
129
+ // If no spaces, it's a single target
130
+ if (!/\s/.test(target)) return [target];
131
+
132
+ // Split on whitespace and check if parts look like file paths
133
+ const parts = target.split(/\s+/).filter(Boolean);
134
+ if (parts.length <= 1) return [target];
135
+
136
+ // Check if each part looks like a file path (has a dot extension or path separator)
137
+ const allLookLikePaths = parts.every(p => /[/\\]/.test(p) || /\.\w+/.test(p));
138
+ if (allLookLikePaths) return parts;
139
+
140
+ // Not confident these are separate paths — return as-is
141
+ return [target];
142
+ }
143
+
115
144
  function extractJsonSnippet(text) {
116
145
  const jsonBlockMatch = text.match(/```json\s*([\s\S]*?)```/i);
117
146
  if (jsonBlockMatch) {