@probelabs/probe 0.6.0-rc109 → 0.6.0-rc113

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.
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { createMessagePreview } from '../tools/common.js';
7
+ import { validate, fixText, extractMermaidBlocks } from '@probelabs/maid';
7
8
 
8
9
  /**
9
10
  * HTML entity decoder map for common entities that might appear in mermaid diagrams
@@ -482,8 +483,9 @@ export function extractMermaidFromMarkdown(response) {
482
483
 
483
484
  while ((match = mermaidBlockRegex.exec(response)) !== null) {
484
485
  const attributes = match[1] ? match[1].trim() : '';
485
- const fullContent = match[2].trim();
486
-
486
+ // Don't trim the content - maid 0.0.6 requires trailing newlines for sequence diagrams
487
+ const fullContent = match[2];
488
+
487
489
  // If attributes exist, they were captured separately, so fullContent is just the diagram
488
490
  // If no attributes, the first line of fullContent might be diagram type or actual content
489
491
  diagrams.push({
@@ -544,135 +546,47 @@ export async function validateMermaidDiagram(diagram) {
544
546
  }
545
547
 
546
548
  try {
547
- const trimmedDiagram = diagram.trim();
548
-
549
+ // Don't trim the diagram - maid 0.0.6 requires trailing newlines for sequence diagrams
550
+ // and handles leading/trailing whitespace correctly
551
+
549
552
  // Check for markdown code block markers
550
- if (trimmedDiagram.includes('```')) {
551
- return {
552
- isValid: false,
553
+ if (diagram.includes('```')) {
554
+ return {
555
+ isValid: false,
553
556
  error: 'Diagram contains markdown code block markers',
554
557
  detailedError: 'Mermaid diagram should not contain ``` markers when extracted from markdown'
555
558
  };
556
559
  }
557
560
 
558
- // Check for common mermaid diagram types (more flexible patterns)
559
- const diagramPatterns = [
560
- { pattern: /^(graph|flowchart)/i, type: 'flowchart' },
561
- { pattern: /^sequenceDiagram/i, type: 'sequence' },
562
- { pattern: /^gantt/i, type: 'gantt' },
563
- { pattern: /^pie/i, type: 'pie' },
564
- { pattern: /^stateDiagram/i, type: 'state' },
565
- { pattern: /^classDiagram/i, type: 'class' },
566
- { pattern: /^erDiagram/i, type: 'er' },
567
- { pattern: /^journey/i, type: 'journey' },
568
- { pattern: /^gitgraph/i, type: 'gitgraph' },
569
- { pattern: /^requirementDiagram/i, type: 'requirement' },
570
- { pattern: /^C4Context/i, type: 'c4' },
571
- ];
561
+ // Use maid to validate the diagram
562
+ const result = validate(diagram);
572
563
 
573
- // Find matching diagram type
574
- let diagramType = null;
575
- for (const { pattern, type } of diagramPatterns) {
576
- if (pattern.test(trimmedDiagram)) {
577
- diagramType = type;
578
- break;
579
- }
580
- }
581
-
582
- if (!diagramType) {
583
- return {
584
- isValid: false,
585
- error: 'Diagram does not match any known Mermaid diagram pattern',
586
- detailedError: 'The diagram must start with a valid Mermaid diagram type (graph, sequenceDiagram, gantt, pie, etc.)'
564
+ // Maid returns { type: string, errors: array }
565
+ // Valid if errors array is empty
566
+ if (result.errors && result.errors.length === 0) {
567
+ return {
568
+ isValid: true,
569
+ diagramType: result.type || 'unknown'
587
570
  };
588
- }
571
+ } else {
572
+ // Format maid errors into a readable error message
573
+ const errorMessages = (result.errors || []).map(err => {
574
+ const location = err.line ? `line ${err.line}${err.column ? `:${err.column}` : ''}` : '';
575
+ return location ? `${location} - ${err.message}` : err.message;
576
+ });
589
577
 
590
- // GitHub-compatible strict syntax validation
591
- const lines = trimmedDiagram.split('\n');
592
-
593
- for (let i = 0; i < lines.length; i++) {
594
- const line = lines[i].trim();
595
- if (!line) continue;
596
-
597
- // Check for GitHub-incompatible patterns that cause "got 'PS'" errors
598
- if (diagramType === 'flowchart') {
599
- // Check for unbalanced brackets in node labels
600
- const brackets = line.match(/\[[^\]]*$/); // Unclosed bracket
601
- if (brackets) {
602
- return {
603
- isValid: false,
604
- error: `Unclosed bracket on line ${i + 1}`,
605
- detailedError: `Line "${line}" contains an unclosed bracket`
606
- };
607
- }
608
-
609
- // GitHub-strict: Check for parentheses inside node labels (causes PS token error)
610
- // But allow parentheses inside double-quoted strings
611
- const nodeWithParens = line.match(/\[[^"\[\]]*\([^"\[\]]*\]/);
612
- if (nodeWithParens) {
613
- return {
614
- isValid: false,
615
- error: `Parentheses in node label on line ${i + 1} (GitHub incompatible)`,
616
- detailedError: `Line "${line}" contains parentheses inside node label brackets. GitHub mermaid renderer fails with 'got PS' error. Use quotes or escape characters instead.`
617
- };
618
- }
619
-
620
- // GitHub-strict: Check for single quotes and backticks inside node labels (causes PS token error)
621
- const nodeWithQuotes = line.match(/\{[^{}]*['`][^{}]*\}|\[[^[\]]*['`][^[\]]*\]/);
622
- if (nodeWithQuotes) {
623
- const hasBacktick = line.includes('`');
624
- const quoteType = hasBacktick ? 'backticks' : 'single quotes';
625
- return {
626
- isValid: false,
627
- error: `${hasBacktick ? 'Backticks' : 'Single quotes'} in node label on line ${i + 1} (GitHub incompatible)`,
628
- detailedError: `Line "${line}" contains ${quoteType} inside node label. GitHub mermaid renderer fails with 'got PS' error. Use double quotes or escape characters instead.`
629
- };
630
- }
631
-
632
- // GitHub-strict: Check for complex expressions inside diamond nodes
633
- // Allow double-quoted strings in diamond nodes, but catch problematic single quotes and complex expressions
634
- // Allow HTML breaks (<br/>, <br>, etc.) but catch other problematic patterns
635
- const diamondWithComplexContent = line.match(/\{[^"{}]*[()'"<>&][^"{}]*\}/);
636
- const hasHtmlBreak = line.match(/\{[^{}]*<br\s*\/?>.*\}/);
637
- if (diamondWithComplexContent && !line.match(/\{\"[^\"]*\"\}/) && !hasHtmlBreak) {
638
- return {
639
- isValid: false,
640
- error: `Complex expression in diamond node on line ${i + 1} (GitHub incompatible)`,
641
- detailedError: `Line "${line}" contains special characters in diamond node that may cause GitHub parsing errors. Use simpler text or escape characters.`
642
- };
643
- }
644
-
645
- // GitHub-strict: Check for parentheses in subgraph labels (causes PS token error)
646
- if (line.startsWith('subgraph ') && line.match(/subgraph\s+[^"]*\([^"]*\)/)) {
647
- return {
648
- isValid: false,
649
- error: `Parentheses in subgraph label on line ${i + 1} (GitHub incompatible)`,
650
- detailedError: `Line "${line}" contains parentheses in subgraph label. GitHub mermaid renderer fails with 'got PS' error. Use quotes around the label or avoid parentheses.`
651
- };
652
- }
653
- }
654
-
655
- if (diagramType === 'sequence') {
656
- // Check for missing colon in sequence messages
657
- if (line.includes('->>') && !line.includes(':')) {
658
- return {
659
- isValid: false,
660
- error: `Missing colon in sequence message on line ${i + 1}`,
661
- detailedError: `Line "${line}" appears to be a sequence message but is missing a colon`
662
- };
663
- }
664
- }
578
+ return {
579
+ isValid: false,
580
+ diagramType: result.type || 'unknown',
581
+ error: errorMessages[0] || 'Validation failed',
582
+ detailedError: errorMessages.join('\n'),
583
+ errors: result.errors || [] // Include raw maid errors for AI fixing
584
+ };
665
585
  }
666
586
 
667
- // If we get here, basic validation passed
668
- return {
669
- isValid: true,
670
- diagramType
671
- };
672
-
673
587
  } catch (error) {
674
- return {
675
- isValid: false,
588
+ return {
589
+ isValid: false,
676
590
  error: error.message || 'Unknown mermaid parsing error',
677
591
  detailedError: error.stack || error.toString()
678
592
  };
@@ -758,6 +672,61 @@ Ensure all Mermaid diagrams are properly formatted within \`\`\`mermaid code blo
758
672
  return prompt;
759
673
  }
760
674
 
675
+ /**
676
+ * Use maid to attempt auto-fixing of mermaid diagrams
677
+ * @param {string} diagramContent - The diagram content to fix
678
+ * @param {Object} options - Fix options
679
+ * @returns {Object} - {fixed: string, wasFixed: boolean, errors: Array}
680
+ */
681
+ export async function tryMaidAutoFix(diagramContent, options = {}) {
682
+ const { debug = false } = options;
683
+
684
+ try {
685
+ // Always use 'all' level fixes (most aggressive)
686
+ if (debug) {
687
+ console.log(`[DEBUG] Mermaid maid: Trying 'all' level auto-fixes...`);
688
+ }
689
+ const result = fixText(diagramContent, { level: 'all' });
690
+ const validation = validate(result.fixed);
691
+
692
+ // Maid validation returns { type, errors }
693
+ // Valid if errors array is empty
694
+ if (validation.errors && validation.errors.length === 0) {
695
+ if (debug) {
696
+ console.log(`[DEBUG] Mermaid maid: 'All' level fixes succeeded`);
697
+ }
698
+ return {
699
+ fixed: result.fixed,
700
+ wasFixed: result.fixed !== diagramContent,
701
+ errors: [],
702
+ fixLevel: 'all'
703
+ };
704
+ }
705
+
706
+ // Maid couldn't fix it completely, return the best attempt with remaining errors
707
+ if (debug) {
708
+ console.log(`[DEBUG] Mermaid maid: Auto-fixes couldn't resolve all issues, ${validation.errors?.length || 0} errors remain`);
709
+ }
710
+ return {
711
+ fixed: result.fixed,
712
+ wasFixed: result.fixed !== diagramContent,
713
+ errors: validation.errors || [], // Pass maid's structured errors for AI fixing
714
+ fixLevel: 'all'
715
+ };
716
+
717
+ } catch (error) {
718
+ if (debug) {
719
+ console.error(`[DEBUG] Mermaid maid: Auto-fix error: ${error.message}`);
720
+ }
721
+ return {
722
+ fixed: diagramContent,
723
+ wasFixed: false,
724
+ errors: [{ message: error.message }],
725
+ fixLevel: null
726
+ };
727
+ }
728
+ }
729
+
761
730
  /**
762
731
  * Specialized Mermaid diagram fixing agent
763
732
  * Uses a separate ProbeAgent instance optimized for Mermaid syntax correction
@@ -842,7 +811,8 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
842
811
  model: this.options.model,
843
812
  debug: this.options.debug,
844
813
  tracer: this.options.tracer,
845
- allowEdit: this.options.allowEdit
814
+ allowEdit: this.options.allowEdit,
815
+ maxIterations: 2 // Limit mermaid fixing to 2 iterations to prevent long loops
846
816
  });
847
817
  }
848
818
 
@@ -881,17 +851,30 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
881
851
 
882
852
  await this.initializeAgent();
883
853
 
884
- const errorContext = originalErrors.length > 0
885
- ? `\n\nDetected errors: ${originalErrors.join(', ')}`
886
- : '';
854
+ // Format error context - handle both simple strings and maid's structured errors
855
+ let errorContext = '';
856
+ if (originalErrors.length > 0) {
857
+ const formattedErrors = originalErrors.map(err => {
858
+ // Check if this is a maid structured error object
859
+ if (typeof err === 'object' && err.message) {
860
+ const location = err.line ? `line ${err.line}${err.column ? `:${err.column}` : ''}` : '';
861
+ const hint = err.hint ? `\n Hint: ${err.hint}` : '';
862
+ return location ? `- ${location}: ${err.message}${hint}` : `- ${err.message}${hint}`;
863
+ }
864
+ // Handle simple string errors
865
+ return `- ${err}`;
866
+ }).join('\n');
887
867
 
888
- const diagramTypeHint = diagramInfo.diagramType
889
- ? `\n\nExpected diagram type: ${diagramInfo.diagramType}`
868
+ errorContext = `\n\nDetected errors:\n${formattedErrors}`;
869
+ }
870
+
871
+ const diagramTypeHint = diagramInfo.diagramType
872
+ ? `\n\nExpected diagram type: ${diagramInfo.diagramType}`
890
873
  : '';
891
874
 
892
875
  // Use decoded content for AI fixing to ensure HTML entities are handled
893
876
  const contentToFix = decodedContent !== diagramContent ? decodedContent : diagramContent;
894
-
877
+
895
878
  const prompt = `Analyze and fix the following Mermaid diagram.${errorContext}${diagramTypeHint}
896
879
 
897
880
  Broken Mermaid diagram:
@@ -902,10 +885,11 @@ ${contentToFix}
902
885
  Provide only the corrected Mermaid diagram within a mermaid code block. Do not add any explanations or additional text.`;
903
886
 
904
887
  try {
905
- const result = await this.agent.answer(prompt, [], {
906
- schema: 'Return only valid Mermaid diagram code within ```mermaid code block'
907
- });
908
-
888
+ // Don't pass schema to avoid infinite loop where AI returns raw mermaid code
889
+ // instead of using attempt_completion tool. The custom prompt already instructs
890
+ // to return only mermaid code blocks.
891
+ const result = await this.agent.answer(prompt, []);
892
+
909
893
  // Extract the mermaid code from the response
910
894
  const extractedDiagram = this.extractCorrectedDiagram(result);
911
895
  return extractedDiagram || result;
@@ -958,54 +942,35 @@ Provide only the corrected Mermaid diagram within a mermaid code block. Do not a
958
942
  }
959
943
 
960
944
  /**
961
- * Enhanced Mermaid validation with specialized agent fixing
962
- * @param {string} response - Response that may contain mermaid diagrams
963
- * @param {Object} options - Options for validation and fixing
964
- * @returns {Promise<Object>} - Enhanced validation result with fixing capability
945
+ * Validate and fix Mermaid diagrams using maid
946
+ * Replaces old manual fix logic with maid's auto-fix capabilities
947
+ * @param {string} response - Response containing mermaid diagrams
948
+ * @param {Object} options - Validation options
949
+ * @returns {Promise<Object>} - Validation and fixing results
965
950
  */
966
951
  export async function validateAndFixMermaidResponse(response, options = {}) {
967
952
  const { schema, debug, path, provider, model, tracer } = options;
968
953
  const startTime = Date.now();
969
-
954
+
970
955
  if (debug) {
971
- console.log(`[DEBUG] Mermaid validation: Starting enhanced validation for response (${response.length} chars)`);
972
- console.log(`[DEBUG] Mermaid validation: Options - path: ${path}, provider: ${provider}, model: ${model}`);
956
+ console.log(`[DEBUG] Mermaid validation: Starting maid-based validation for response (${response.length} chars)`);
973
957
  }
974
958
 
975
- /**
976
- * Helper function to determine if node content needs quoting due to problematic characters
977
- * @param {string} content - The node content to check
978
- * @returns {boolean} - True if content needs to be quoted
979
- */
980
- const needsQuoting = (content) => {
981
- return /[()'"<>&`]/.test(content) || // Core problematic characters
982
- content.includes('e.g.') ||
983
- content.includes('i.e.') ||
984
- content.includes('src/') ||
985
- content.includes('defaults/') ||
986
- content.includes('.ts') ||
987
- content.includes('.js') ||
988
- content.includes('.yaml') ||
989
- content.includes('.json') ||
990
- content.includes('.md') ||
991
- content.includes('.html') ||
992
- content.includes('.css');
993
- };
994
-
995
959
  // Record mermaid validation start in telemetry
996
960
  if (tracer) {
997
961
  tracer.recordMermaidValidationEvent('started', {
998
962
  'mermaid_validation.response_length': response.length,
999
963
  'mermaid_validation.provider': provider,
1000
- 'mermaid_validation.model': model
964
+ 'mermaid_validation.model': model,
965
+ 'mermaid_validation.method': 'maid'
1001
966
  });
1002
967
  }
1003
-
968
+
1004
969
  // First, run standard validation
1005
970
  const validationStart = Date.now();
1006
971
  const validation = await validateMermaidResponse(response);
1007
972
  const validationTime = Date.now() - validationStart;
1008
-
973
+
1009
974
  if (debug) {
1010
975
  console.log(`[DEBUG] Mermaid validation: Initial validation completed in ${validationTime}ms`);
1011
976
  console.log(`[DEBUG] Mermaid validation: Found ${validation.diagrams?.length || 0} diagrams, valid: ${validation.isValid}`);
@@ -1018,24 +983,13 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1018
983
  });
1019
984
  }
1020
985
  }
1021
-
1022
- // Always check for HTML entities, even if diagrams are technically valid
1023
- let needsHtmlEntityCheck = false;
1024
- if (validation.diagrams && validation.diagrams.length > 0) {
1025
- for (const diagram of validation.diagrams) {
1026
- if (diagram.content && (diagram.content.includes('&lt;') || diagram.content.includes('&gt;') || diagram.content.includes('&amp;') || diagram.content.includes('&quot;') || diagram.content.includes('&#39;'))) {
1027
- needsHtmlEntityCheck = true;
1028
- break;
1029
- }
1030
- }
1031
- }
1032
986
 
1033
- if (validation.isValid && !needsHtmlEntityCheck) {
987
+ // If all valid, return early
988
+ if (validation.isValid) {
1034
989
  if (debug) {
1035
- console.log(`[DEBUG] Mermaid validation: All diagrams valid and no HTML entities found, no fixing needed`);
990
+ console.log(`[DEBUG] Mermaid validation: All diagrams valid, no fixing needed`);
1036
991
  }
1037
-
1038
- // Record successful validation in telemetry
992
+
1039
993
  if (tracer) {
1040
994
  tracer.recordMermaidValidationEvent('completed', {
1041
995
  'mermaid_validation.success': true,
@@ -1044,8 +998,7 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1044
998
  'mermaid_validation.duration_ms': Date.now() - startTime
1045
999
  });
1046
1000
  }
1047
-
1048
- // All diagrams are valid and no HTML entities found, no fixing needed
1001
+
1049
1002
  return {
1050
1003
  ...validation,
1051
1004
  wasFixed: false,
@@ -1054,10 +1007,10 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1054
1007
  };
1055
1008
  }
1056
1009
 
1057
- // If no diagrams found at all, return without attempting to fix
1010
+ // If no diagrams found, return without fixing
1058
1011
  if (!validation.diagrams || validation.diagrams.length === 0) {
1059
1012
  if (debug) {
1060
- console.log(`[DEBUG] Mermaid validation: No mermaid diagrams found in response, skipping fixes`);
1013
+ console.log(`[DEBUG] Mermaid validation: No mermaid diagrams found in response`);
1061
1014
  }
1062
1015
  return {
1063
1016
  ...validation,
@@ -1067,596 +1020,147 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1067
1020
  };
1068
1021
  }
1069
1022
 
1070
- // Try HTML entity decoding auto-fix (for both invalid diagrams and valid diagrams with HTML entities)
1023
+ // Try maid auto-fix for invalid diagrams
1071
1024
  const invalidCount = validation.diagrams.filter(d => !d.isValid).length;
1072
1025
  if (debug) {
1073
- if (invalidCount > 0) {
1074
- console.log(`[DEBUG] Mermaid validation: ${invalidCount} invalid diagrams detected, trying HTML entity auto-fix first...`);
1075
- } else {
1076
- console.log(`[DEBUG] Mermaid validation: Diagrams are valid but HTML entities detected, applying HTML entity auto-fix...`);
1077
- }
1026
+ console.log(`[DEBUG] Mermaid validation: ${invalidCount} invalid diagrams, trying maid auto-fix...`);
1078
1027
  }
1079
1028
 
1080
1029
  try {
1081
1030
  let fixedResponse = response;
1082
1031
  const fixingResults = [];
1083
- let htmlEntityFixesApplied = false;
1084
-
1085
- // Extract diagrams with position information for replacement
1032
+ let maidFixesApplied = false;
1033
+
1034
+ // Extract diagrams with position information
1086
1035
  const { diagrams } = extractMermaidFromMarkdown(response);
1087
-
1088
- // First pass: Try HTML entity decoding on ALL diagrams (not just invalid ones)
1089
- // HTML entities in mermaid diagrams are almost always unintended, even if the diagram is technically valid
1090
- const allDiagrams = validation.diagrams
1036
+
1037
+ // Process invalid diagrams in reverse to maintain indices
1038
+ const invalidDiagrams = validation.diagrams
1091
1039
  .map((result, index) => ({ ...result, originalIndex: index }))
1040
+ .filter(result => !result.isValid)
1092
1041
  .reverse();
1093
1042
 
1094
- for (const diagram of allDiagrams) {
1095
- const originalContent = diagram.content;
1096
- const decodedContent = decodeHtmlEntities(originalContent);
1097
-
1098
- if (decodedContent !== originalContent) {
1099
- // HTML entities were found and decoded, validate the result
1100
- try {
1101
- const quickValidation = await validateMermaidDiagram(decodedContent);
1102
- if (quickValidation.isValid) {
1103
- // HTML entity decoding improved this diagram!
1104
- const originalDiagram = diagrams[diagram.originalIndex];
1105
- const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1106
- const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${decodedContent}\n\`\`\``;
1107
-
1108
- fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1109
- newCodeBlock +
1110
- fixedResponse.slice(originalDiagram.endIndex);
1111
-
1112
- fixingResults.push({
1113
- diagramIndex: diagram.originalIndex,
1114
- wasFixed: true,
1115
- originalContent: originalContent,
1116
- fixedContent: decodedContent,
1117
- originalError: diagram.error || 'HTML entity cleanup',
1118
- fixedWithHtmlDecoding: true
1119
- });
1120
-
1121
- htmlEntityFixesApplied = true;
1122
-
1123
- if (debug) {
1124
- console.log(`[DEBUG] Mermaid validation: Fixed diagram ${diagram.originalIndex + 1} with HTML entity decoding`);
1125
- console.log(`[DEBUG] Mermaid validation: Original status: ${diagram.isValid ? 'valid' : 'invalid'} - ${diagram.error || 'no error'}`);
1126
- console.log(`[DEBUG] Mermaid validation: Decoded HTML entities`);
1127
- }
1128
- }
1129
- } catch (error) {
1130
- if (debug) {
1131
- console.log(`[DEBUG] Mermaid validation: HTML entity decoding didn't improve diagram ${diagram.originalIndex + 1}: ${error.message}`);
1132
- }
1133
- }
1134
- }
1135
- }
1136
-
1137
- // If HTML entity fixes were applied, re-validate the entire response
1138
- if (htmlEntityFixesApplied) {
1139
- const revalidation = await validateMermaidResponse(fixedResponse);
1140
- if (revalidation.isValid) {
1141
- // All diagrams are now valid, return without AI fixing
1142
- const totalTime = Date.now() - startTime;
1143
- if (debug) {
1144
- console.log(`[DEBUG] Mermaid validation: All diagrams fixed with HTML entity decoding in ${totalTime}ms, no AI needed`);
1145
- console.log(`[DEBUG] Mermaid validation: Applied ${fixingResults.length} HTML entity fixes`);
1146
- }
1147
-
1148
- // Record HTML entity fix success in telemetry
1149
- if (tracer) {
1150
- tracer.recordMermaidValidationEvent('html_fix_completed', {
1151
- 'mermaid_validation.success': true,
1152
- 'mermaid_validation.fix_method': 'html_entity_decoding',
1153
- 'mermaid_validation.diagrams_fixed': fixingResults.length,
1154
- 'mermaid_validation.duration_ms': totalTime
1155
- });
1156
- }
1157
- return {
1158
- ...revalidation,
1159
- wasFixed: true,
1160
- originalResponse: response,
1161
- fixedResponse: fixedResponse,
1162
- fixingResults: fixingResults
1163
- };
1164
- }
1165
- }
1166
-
1167
- // Proactive pass: Fix common node label issues in ALL diagrams (not just invalid ones)
1168
- let proactiveFixesApplied = false;
1169
-
1170
- // Re-extract diagrams after HTML entity fixes
1171
- const { diagrams: currentDiagrams } = extractMermaidFromMarkdown(fixedResponse);
1172
-
1173
- for (let diagramIndex = currentDiagrams.length - 1; diagramIndex >= 0; diagramIndex--) {
1174
- const diagram = currentDiagrams[diagramIndex];
1175
- const originalContent = diagram.content;
1176
- const lines = originalContent.split('\n');
1177
- let wasFixed = false;
1178
-
1179
- // Proactively fix node labels that contain special characters
1180
- const fixedLines = lines.map(line => {
1181
- const trimmedLine = line.trim();
1182
- let modifiedLine = line;
1183
-
1184
- // Enhanced auto-fixing for square bracket nodes [...]
1185
- if (trimmedLine.match(/\[[^\]]*\]/)) {
1186
- modifiedLine = modifiedLine.replace(/\[([^\]]*)\]/g, (match, content) => {
1187
- // Check if already properly quoted with outer quotes
1188
- if (content.trim().startsWith('"') && content.trim().endsWith('"')) {
1189
- // Extract the inner content (between the outer quotes)
1190
- const innerContent = content.trim().slice(1, -1);
1191
- // Check if inner content has unescaped quotes that need escaping
1192
- if (innerContent.includes('"') || innerContent.includes("'")) {
1193
- wasFixed = true;
1194
- // Decode any existing HTML entities first, then re-encode ALL quotes
1195
- const decodedContent = decodeHtmlEntities(innerContent);
1196
- const safeContent = decodedContent
1197
- .replace(/"/g, '&quot;') // Replace ALL double quotes with HTML entity
1198
- .replace(/'/g, '&#39;'); // Replace ALL single quotes with HTML entity
1199
- return `["${safeContent}"]`;
1200
- }
1201
- return match;
1202
- }
1203
-
1204
- // Check if content needs quoting (contains problematic patterns)
1205
- if (needsQuoting(content)) {
1206
- wasFixed = true;
1207
- // Use HTML entities for quotes as per Mermaid best practices:
1208
- // - GitHub doesn't support single quotes in node labels (causes 'got PS' error)
1209
- // - HTML entities are the official way to escape quotes in Mermaid
1210
- // - Always use double quotes with square brackets ["..."] for node labels
1211
- // IMPORTANT: Decode any existing HTML entities first to avoid double-encoding
1212
- const decodedContent = decodeHtmlEntities(content);
1213
- const safeContent = decodedContent
1214
- .replace(/"/g, '&quot;') // Replace double quotes with HTML entity
1215
- .replace(/'/g, '&#39;'); // Replace single quotes with HTML entity
1216
- return `["${safeContent}"]`;
1217
- }
1218
-
1219
- return match;
1220
- });
1221
- }
1222
-
1223
- // Enhanced auto-fixing for diamond nodes {...}
1224
- if (trimmedLine.match(/\{[^{}]*\}/)) {
1225
- modifiedLine = modifiedLine.replace(/\{([^{}]*)\}/g, (match, content) => {
1226
- // Check if already properly quoted with outer quotes
1227
- if (content.trim().startsWith('"') && content.trim().endsWith('"')) {
1228
- // Extract the inner content (between the outer quotes)
1229
- const innerContent = content.trim().slice(1, -1);
1230
- // Check if inner content has unescaped quotes that need escaping
1231
- if (innerContent.includes('"') || innerContent.includes("'")) {
1232
- wasFixed = true;
1233
- // Decode any existing HTML entities first, then re-encode ALL quotes
1234
- const decodedContent = decodeHtmlEntities(innerContent);
1235
- const safeContent = decodedContent
1236
- .replace(/"/g, '&quot;') // Replace ALL double quotes with HTML entity
1237
- .replace(/'/g, '&#39;'); // Replace ALL single quotes with HTML entity
1238
- return `{"${safeContent}"}`;
1239
- }
1240
- return match;
1241
- }
1242
-
1243
- // Check if content needs quoting (contains problematic patterns)
1244
- if (needsQuoting(content)) {
1245
- wasFixed = true;
1246
- // Use HTML entities for quotes as per Mermaid best practices:
1247
- // - GitHub doesn't support single quotes in node labels (causes 'got PS' error)
1248
- // - HTML entities are the official way to escape quotes in Mermaid
1249
- // - Always use double quotes with curly brackets {"..."} for diamond nodes
1250
- // IMPORTANT: Decode any existing HTML entities first to avoid double-encoding
1251
- const decodedContent = decodeHtmlEntities(content);
1252
- const safeContent = decodedContent
1253
- .replace(/"/g, '&quot;') // Replace double quotes with HTML entity
1254
- .replace(/'/g, '&#39;'); // Replace single quotes with HTML entity
1255
- return `{"${safeContent}"}`;
1256
- }
1257
-
1258
- return match;
1259
- });
1260
- }
1261
-
1262
- return modifiedLine;
1263
- });
1264
-
1265
- if (wasFixed) {
1266
- const fixedContent = fixedLines.join('\n');
1267
-
1268
- // Replace the diagram in the response
1269
- const attributesStr = diagram.attributes ? ` ${diagram.attributes}` : '';
1270
- const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${fixedContent}\n\`\`\``;
1271
-
1272
- fixedResponse = fixedResponse.slice(0, diagram.startIndex) +
1273
- newCodeBlock +
1274
- fixedResponse.slice(diagram.endIndex);
1275
-
1043
+ for (const invalidDiagram of invalidDiagrams) {
1044
+ const originalContent = invalidDiagram.content;
1045
+
1046
+ // Try maid auto-fix
1047
+ const maidResult = await tryMaidAutoFix(originalContent, { debug });
1048
+
1049
+ if (maidResult.errors.length === 0) {
1050
+ // Maid fixed it completely
1051
+ const originalDiagram = diagrams[invalidDiagram.originalIndex];
1052
+ const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1053
+ const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${maidResult.fixed}\n\`\`\``;
1054
+
1055
+ fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1056
+ newCodeBlock +
1057
+ fixedResponse.slice(originalDiagram.endIndex);
1058
+
1276
1059
  fixingResults.push({
1277
- diagramIndex: diagramIndex,
1060
+ diagramIndex: invalidDiagram.originalIndex,
1278
1061
  wasFixed: true,
1279
1062
  originalContent: originalContent,
1280
- fixedContent: fixedContent,
1281
- originalError: 'Proactive node label quoting',
1282
- fixMethod: 'node_label_quote_wrapping',
1283
- fixedWithProactiveQuoting: true
1063
+ fixedContent: maidResult.fixed,
1064
+ originalError: invalidDiagram.error,
1065
+ fixedWithMaid: true,
1066
+ fixLevel: maidResult.fixLevel
1284
1067
  });
1285
-
1286
- proactiveFixesApplied = true;
1287
-
1288
- if (debug) {
1289
- console.log(`[DEBUG] Mermaid validation: Proactively fixed diagram ${diagramIndex + 1} with node label quoting`);
1290
- console.log(`[DEBUG] Mermaid validation: Applied automatic quoting to special characters`);
1291
- }
1292
- }
1293
- }
1294
-
1295
- // If proactive fixes were applied, re-validate the entire response
1296
- if (proactiveFixesApplied) {
1297
- const revalidation = await validateMermaidResponse(fixedResponse);
1298
- if (revalidation.isValid) {
1299
- // All diagrams are now valid, return without AI fixing
1300
- const totalTime = Date.now() - startTime;
1068
+
1069
+ maidFixesApplied = true;
1070
+
1301
1071
  if (debug) {
1302
- console.log(`[DEBUG] Mermaid validation: All diagrams fixed with proactive quoting in ${totalTime}ms, no AI needed`);
1303
- console.log(`[DEBUG] Mermaid validation: Applied ${fixingResults.length} proactive fixes`);
1072
+ console.log(`[DEBUG] Mermaid validation: Maid fixed diagram ${invalidDiagram.originalIndex + 1}`);
1304
1073
  }
1305
-
1306
- // Record proactive fix success in telemetry
1307
- if (tracer) {
1308
- tracer.recordMermaidValidationEvent('proactive_fix_completed', {
1309
- 'mermaid_validation.success': true,
1310
- 'mermaid_validation.fix_method': 'node_label_quote_wrapping',
1311
- 'mermaid_validation.diagrams_fixed': fixingResults.length,
1312
- 'mermaid_validation.duration_ms': totalTime
1313
- });
1314
- }
1315
- return {
1316
- ...revalidation,
1317
- wasFixed: true,
1318
- originalResponse: response,
1319
- fixedResponse: fixedResponse,
1320
- fixingResults: fixingResults,
1321
- performanceMetrics: {
1322
- totalTimeMs: totalTime,
1323
- aiFixingTimeMs: 0,
1324
- finalValidationTimeMs: 0,
1325
- diagramsProcessed: fixingResults.length,
1326
- diagramsFixed: fixingResults.length
1327
- }
1328
- };
1329
- }
1330
- }
1331
-
1332
- // Second pass: Try auto-fixing unquoted subgraph names with parentheses
1333
- let subgraphFixesApplied = false;
1334
-
1335
- // Re-extract diagrams and re-validate after HTML entity fixes
1336
- const { diagrams: postHtmlDiagrams } = extractMermaidFromMarkdown(fixedResponse);
1337
- const postHtmlValidation = await validateMermaidResponse(fixedResponse);
1338
-
1339
- const stillInvalidAfterHtml = postHtmlValidation.diagrams
1340
- .map((result, index) => ({ ...result, originalIndex: index }))
1341
- .filter(result => !result.isValid)
1342
- .reverse();
1074
+ } else if (maidResult.wasFixed) {
1075
+ // Maid improved it but didn't fix everything - update content for AI fixing
1076
+ const originalDiagram = diagrams[invalidDiagram.originalIndex];
1077
+ const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1078
+ const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${maidResult.fixed}\n\`\`\``;
1343
1079
 
1344
- for (const invalidDiagram of stillInvalidAfterHtml) {
1345
- // Check if this is a subgraph parentheses error that we can auto-fix
1346
- if (invalidDiagram.error && invalidDiagram.error.includes('Parentheses in subgraph label')) {
1347
- const originalContent = invalidDiagram.content;
1348
- const lines = originalContent.split('\n');
1349
- let wasFixed = false;
1350
-
1351
- // Find and fix unquoted subgraph lines with parentheses
1352
- const fixedLines = lines.map(line => {
1353
- const trimmedLine = line.trim();
1354
- if (trimmedLine.startsWith('subgraph ') &&
1355
- !trimmedLine.match(/subgraph\s+"[^"]*"/) && // not already quoted
1356
- trimmedLine.match(/subgraph\s+[^"]*\([^"]*\)/)) { // has unquoted parentheses
1357
-
1358
- // Extract the subgraph name part
1359
- const match = trimmedLine.match(/^(\s*subgraph\s+)(.+)$/);
1360
- if (match) {
1361
- const prefix = match[1];
1362
- const name = match[2];
1363
- const fixedLine = line.replace(trimmedLine, `${prefix.trim()} "${name}"`);
1364
- wasFixed = true;
1365
- return fixedLine;
1366
- }
1367
- }
1368
- return line;
1080
+ fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1081
+ newCodeBlock +
1082
+ fixedResponse.slice(originalDiagram.endIndex);
1083
+
1084
+ fixingResults.push({
1085
+ diagramIndex: invalidDiagram.originalIndex,
1086
+ wasFixed: false,
1087
+ originalContent: originalContent,
1088
+ partiallyFixedContent: maidResult.fixed,
1089
+ originalError: invalidDiagram.error,
1090
+ remainingErrors: maidResult.errors,
1091
+ fixedWithMaid: 'partial',
1092
+ fixLevel: maidResult.fixLevel
1369
1093
  });
1370
-
1371
- if (wasFixed) {
1372
- const fixedContent = fixedLines.join('\n');
1373
-
1374
- // Validate the fixed content
1375
- try {
1376
- const quickValidation = await validateMermaidDiagram(fixedContent);
1377
- if (quickValidation.isValid) {
1378
- // Subgraph auto-fix worked!
1379
- const originalDiagram = postHtmlDiagrams[invalidDiagram.originalIndex];
1380
- const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1381
- const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${fixedContent}\n\`\`\``;
1382
-
1383
- fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1384
- newCodeBlock +
1385
- fixedResponse.slice(originalDiagram.endIndex);
1386
-
1387
- fixingResults.push({
1388
- originalIndex: invalidDiagram.originalIndex,
1389
- wasFixed: true,
1390
- originalError: invalidDiagram.error,
1391
- fixMethod: 'subgraph_quote_wrapping',
1392
- fixedWithSubgraphQuoting: true
1393
- });
1394
-
1395
- subgraphFixesApplied = true;
1396
-
1397
- if (debug) {
1398
- console.log(`[DEBUG] Mermaid validation: Fixed diagram ${invalidDiagram.originalIndex + 1} with subgraph quote wrapping`);
1399
- console.log(`[DEBUG] Mermaid validation: Original error: ${invalidDiagram.error}`);
1400
- }
1401
- }
1402
- } catch (error) {
1403
- if (debug) {
1404
- console.log(`[DEBUG] Mermaid validation: Subgraph auto-fix didn't work for diagram ${invalidDiagram.originalIndex + 1}: ${error.message}`);
1405
- }
1406
- }
1407
- }
1408
- }
1409
- }
1410
-
1411
- // If subgraph fixes were applied, re-validate the entire response
1412
- if (subgraphFixesApplied) {
1413
- const revalidation = await validateMermaidResponse(fixedResponse);
1414
- if (revalidation.isValid) {
1415
- // All diagrams are now valid, return without AI fixing
1416
- const totalTime = Date.now() - startTime;
1094
+
1095
+ maidFixesApplied = true;
1096
+
1417
1097
  if (debug) {
1418
- console.log(`[DEBUG] Mermaid validation: All diagrams fixed with auto-fixes in ${totalTime}ms, no AI needed`);
1419
- console.log(`[DEBUG] Mermaid validation: Applied ${fixingResults.length} auto-fixes (HTML entities + subgraph quotes)`);
1420
- }
1421
-
1422
- // Record auto-fix success in telemetry
1423
- if (tracer) {
1424
- tracer.recordMermaidValidationEvent('auto_fix_completed', {
1425
- 'mermaid_validation.success': true,
1426
- 'mermaid_validation.fix_method': 'auto_fixes',
1427
- 'mermaid_validation.diagrams_fixed': fixingResults.length,
1428
- 'mermaid_validation.duration_ms': totalTime
1429
- });
1098
+ console.log(`[DEBUG] Mermaid validation: Maid partially fixed diagram ${invalidDiagram.originalIndex + 1}, ${maidResult.errors.length} errors remain`);
1430
1099
  }
1431
- return {
1432
- ...revalidation,
1433
- wasFixed: true,
1434
- originalResponse: response,
1435
- fixedResponse: fixedResponse,
1436
- fixingResults: fixingResults,
1437
- performanceMetrics: {
1438
- totalTimeMs: totalTime,
1439
- aiFixingTimeMs: 0,
1440
- finalValidationTimeMs: 0,
1441
- diagramsProcessed: fixingResults.length,
1442
- diagramsFixed: fixingResults.length
1443
- }
1444
- };
1100
+ } else {
1101
+ // Maid couldn't fix it, keep track for AI fixing
1102
+ fixingResults.push({
1103
+ diagramIndex: invalidDiagram.originalIndex,
1104
+ wasFixed: false,
1105
+ originalContent: originalContent,
1106
+ originalError: invalidDiagram.error,
1107
+ maidErrors: maidResult.errors,
1108
+ fixedWithMaid: false
1109
+ });
1445
1110
  }
1446
1111
  }
1447
-
1448
- // Third pass: Try auto-fixing node labels with parentheses or single quotes
1449
- let nodeLabelFixesApplied = false;
1450
-
1451
- // Re-extract diagrams and re-validate after previous fixes
1452
- const { diagrams: postSubgraphDiagrams } = extractMermaidFromMarkdown(fixedResponse);
1453
- const postSubgraphValidation = await validateMermaidResponse(fixedResponse);
1454
-
1455
- const stillInvalidAfterSubgraph = postSubgraphValidation.diagrams
1456
- .map((result, index) => ({ ...result, originalIndex: index }))
1457
- .filter(result => !result.isValid)
1458
- .reverse();
1459
1112
 
1460
- for (const invalidDiagram of stillInvalidAfterSubgraph) {
1461
- // Check if this is a node label error that we can auto-fix
1462
- if (invalidDiagram.error &&
1463
- (invalidDiagram.error.includes('Parentheses in node label') ||
1464
- invalidDiagram.error.includes('Complex expression in diamond node') ||
1465
- invalidDiagram.error.includes('Single quotes in node label') ||
1466
- invalidDiagram.error.includes('Backticks in node label'))) {
1467
- const originalContent = invalidDiagram.content;
1468
- const lines = originalContent.split('\n');
1469
- let wasFixed = false;
1470
-
1471
- // Find and fix node labels with special characters that need quoting
1472
- const fixedLines = lines.map(line => {
1473
- const trimmedLine = line.trim();
1474
- let modifiedLine = line;
1475
-
1476
- // Enhanced auto-fixing for square bracket nodes [...]
1477
- // Look for any node labels that contain special characters and aren't already quoted
1478
- if (trimmedLine.match(/\[[^\]]*\]/)) {
1479
- modifiedLine = modifiedLine.replace(/\[([^\]]*)\]/g, (match, content) => {
1480
- // Check if already properly quoted with outer quotes
1481
- if (content.trim().startsWith('"') && content.trim().endsWith('"')) {
1482
- // Extract the inner content (between the outer quotes)
1483
- const innerContent = content.trim().slice(1, -1);
1484
- // Check if inner content has unescaped quotes that need escaping
1485
- if (innerContent.includes('"') || innerContent.includes("'")) {
1486
- wasFixed = true;
1487
- // Decode any existing HTML entities first, then re-encode ALL quotes
1488
- const decodedContent = decodeHtmlEntities(innerContent);
1489
- const safeContent = decodedContent
1490
- .replace(/"/g, '&quot;') // Replace ALL double quotes with HTML entity
1491
- .replace(/'/g, '&#39;'); // Replace ALL single quotes with HTML entity
1492
- return `["${safeContent}"]`;
1493
- }
1494
- return match;
1495
- }
1496
-
1497
- // Check if content needs quoting (contains problematic patterns)
1498
- if (needsQuoting(content)) {
1499
- wasFixed = true;
1500
- // Use HTML entities for quotes as per Mermaid best practices:
1501
- // - GitHub doesn't support single quotes in node labels (causes 'got PS' error)
1502
- // - HTML entities are the official way to escape quotes in Mermaid
1503
- // - Always use double quotes with square brackets ["..."] for node labels
1504
- // IMPORTANT: Decode any existing HTML entities first to avoid double-encoding
1505
- const decodedContent = decodeHtmlEntities(content);
1506
- const safeContent = decodedContent
1507
- .replace(/"/g, '&quot;') // Replace double quotes with HTML entity
1508
- .replace(/'/g, '&#39;'); // Replace single quotes with HTML entity
1509
- return `["${safeContent}"]`;
1510
- }
1511
-
1512
- return match;
1513
- });
1514
- }
1515
-
1516
- // Enhanced auto-fixing for diamond nodes {...}
1517
- if (trimmedLine.match(/\{[^{}]*\}/)) {
1518
- modifiedLine = modifiedLine.replace(/\{([^{}]*)\}/g, (match, content) => {
1519
- // Check if already properly quoted with outer quotes
1520
- if (content.trim().startsWith('"') && content.trim().endsWith('"')) {
1521
- // Extract the inner content (between the outer quotes)
1522
- const innerContent = content.trim().slice(1, -1);
1523
- // Check if inner content has unescaped quotes that need escaping
1524
- if (innerContent.includes('"') || innerContent.includes("'")) {
1525
- wasFixed = true;
1526
- // Decode any existing HTML entities first, then re-encode ALL quotes
1527
- const decodedContent = decodeHtmlEntities(innerContent);
1528
- const safeContent = decodedContent
1529
- .replace(/"/g, '&quot;') // Replace ALL double quotes with HTML entity
1530
- .replace(/'/g, '&#39;'); // Replace ALL single quotes with HTML entity
1531
- return `{"${safeContent}"}`;
1532
- }
1533
- return match;
1534
- }
1535
-
1536
- // Check if content needs quoting (contains problematic patterns)
1537
- if (needsQuoting(content)) {
1538
- wasFixed = true;
1539
- // Use HTML entities for quotes as per Mermaid best practices:
1540
- // - GitHub doesn't support single quotes in node labels (causes 'got PS' error)
1541
- // - HTML entities are the official way to escape quotes in Mermaid
1542
- // - Always use double quotes with curly brackets {"..."} for diamond nodes
1543
- // IMPORTANT: Decode any existing HTML entities first to avoid double-encoding
1544
- const decodedContent = decodeHtmlEntities(content);
1545
- const safeContent = decodedContent
1546
- .replace(/"/g, '&quot;') // Replace double quotes with HTML entity
1547
- .replace(/'/g, '&#39;'); // Replace single quotes with HTML entity
1548
- return `{"${safeContent}"}`;
1549
- }
1550
-
1551
- return match;
1552
- });
1553
- }
1554
-
1555
- return modifiedLine;
1113
+ // Re-validate after maid fixes
1114
+ const revalidation = await validateMermaidResponse(fixedResponse);
1115
+ if (revalidation.isValid) {
1116
+ // All diagrams fixed with maid
1117
+ const totalTime = Date.now() - startTime;
1118
+ if (debug) {
1119
+ console.log(`[DEBUG] Mermaid validation: All diagrams fixed with maid in ${totalTime}ms, no AI needed`);
1120
+ }
1121
+
1122
+ if (tracer) {
1123
+ tracer.recordMermaidValidationEvent('maid_fix_completed', {
1124
+ 'mermaid_validation.success': true,
1125
+ 'mermaid_validation.fix_method': 'maid',
1126
+ 'mermaid_validation.diagrams_fixed': fixingResults.filter(r => r.wasFixed).length,
1127
+ 'mermaid_validation.duration_ms': totalTime
1556
1128
  });
1557
-
1558
- if (wasFixed) {
1559
- const fixedContent = fixedLines.join('\n');
1560
-
1561
- // Validate the fixed content
1562
- try {
1563
- const quickValidation = await validateMermaidDiagram(fixedContent);
1564
- if (quickValidation.isValid) {
1565
- // Node label auto-fix worked!
1566
- const originalDiagram = postSubgraphDiagrams[invalidDiagram.originalIndex];
1567
- const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1568
- const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${fixedContent}\n\`\`\``;
1569
-
1570
- fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1571
- newCodeBlock +
1572
- fixedResponse.slice(originalDiagram.endIndex);
1573
-
1574
- fixingResults.push({
1575
- originalIndex: invalidDiagram.originalIndex,
1576
- wasFixed: true,
1577
- originalError: invalidDiagram.error,
1578
- fixMethod: 'node_label_quote_wrapping',
1579
- fixedWithNodeLabelQuoting: true
1580
- });
1581
-
1582
- nodeLabelFixesApplied = true;
1583
-
1584
- if (debug) {
1585
- console.log(`[DEBUG] Mermaid validation: Fixed diagram ${invalidDiagram.originalIndex + 1} with node label quote wrapping`);
1586
- console.log(`[DEBUG] Mermaid validation: Original error: ${invalidDiagram.error}`);
1587
- }
1588
- }
1589
- } catch (error) {
1590
- if (debug) {
1591
- console.log(`[DEBUG] Mermaid validation: Node label auto-fix didn't work for diagram ${invalidDiagram.originalIndex + 1}: ${error.message}`);
1592
- }
1593
- }
1594
- }
1595
1129
  }
1596
- }
1597
-
1598
- // If node label fixes were applied, re-validate the entire response
1599
- if (nodeLabelFixesApplied) {
1600
- const revalidation = await validateMermaidResponse(fixedResponse);
1601
- if (revalidation.isValid) {
1602
- // All diagrams are now valid, return without AI fixing
1603
- const totalTime = Date.now() - startTime;
1604
- if (debug) {
1605
- console.log(`[DEBUG] Mermaid validation: All diagrams fixed with auto-fixes in ${totalTime}ms, no AI needed`);
1606
- console.log(`[DEBUG] Mermaid validation: Applied ${fixingResults.length} auto-fixes (HTML entities + subgraph quotes + node label quotes)`);
1607
- }
1608
-
1609
- // Record auto-fix success in telemetry
1610
- if (tracer) {
1611
- tracer.recordMermaidValidationEvent('auto_fix_completed', {
1612
- 'mermaid_validation.success': true,
1613
- 'mermaid_validation.fix_method': 'auto_fixes',
1614
- 'mermaid_validation.diagrams_fixed': fixingResults.length,
1615
- 'mermaid_validation.duration_ms': totalTime
1616
- });
1130
+
1131
+ return {
1132
+ ...revalidation,
1133
+ wasFixed: true,
1134
+ originalResponse: response,
1135
+ fixedResponse: fixedResponse,
1136
+ fixingResults: fixingResults,
1137
+ performanceMetrics: {
1138
+ totalTimeMs: totalTime,
1139
+ aiFixingTimeMs: 0,
1140
+ diagramsProcessed: fixingResults.length,
1141
+ diagramsFixed: fixingResults.filter(r => r.wasFixed).length
1617
1142
  }
1618
- return {
1619
- ...revalidation,
1620
- wasFixed: true,
1621
- originalResponse: response,
1622
- fixedResponse: fixedResponse,
1623
- fixingResults: fixingResults,
1624
- performanceMetrics: {
1625
- totalTimeMs: totalTime,
1626
- aiFixingTimeMs: 0,
1627
- finalValidationTimeMs: 0,
1628
- diagramsProcessed: fixingResults.length,
1629
- diagramsFixed: fixingResults.length
1630
- }
1631
- };
1632
- }
1633
- }
1634
-
1635
- // Re-extract diagrams and re-validate after HTML entity fixes
1636
- const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
1637
- const updatedValidation = await validateMermaidResponse(fixedResponse);
1638
-
1639
- // Still have invalid diagrams after all auto-fixes, proceed with AI fixing
1640
- if (debug) {
1641
- const stillInvalidAfterHtml = updatedValidation?.diagrams?.filter(d => !d.isValid)?.length || invalidCount;
1642
- console.log(`[DEBUG] Mermaid validation: ${stillInvalidAfterHtml} diagrams still invalid after HTML entity decoding, starting AI fixing...`);
1643
- console.log(`[DEBUG] Mermaid validation: HTML entity fixes applied: ${fixingResults.length}`);
1143
+ };
1644
1144
  }
1645
-
1646
- // Create specialized fixing agent for remaining invalid diagrams
1145
+
1146
+ // Still have invalid diagrams, proceed with AI fixing
1647
1147
  if (debug) {
1648
- console.log(`[DEBUG] Mermaid validation: Creating specialized AI fixing agent...`);
1148
+ const stillInvalid = revalidation.diagrams.filter(d => !d.isValid).length;
1149
+ console.log(`[DEBUG] Mermaid validation: ${stillInvalid} diagrams still invalid after maid, starting AI fixing...`);
1649
1150
  }
1151
+
1650
1152
  const aiFixingStart = Date.now();
1651
1153
  const mermaidFixer = new MermaidFixingAgent({
1652
1154
  path, provider, model, debug, tracer
1653
1155
  });
1654
-
1655
- const stillInvalidDiagrams = updatedValidation.diagrams
1156
+
1157
+ // Extract updated diagrams and validation
1158
+ const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
1159
+ const stillInvalidDiagrams = revalidation.diagrams
1656
1160
  .map((result, index) => ({ ...result, originalIndex: index }))
1657
1161
  .filter(result => !result.isValid)
1658
1162
  .reverse();
1659
-
1163
+
1660
1164
  if (debug) {
1661
1165
  console.log(`[DEBUG] Mermaid validation: Found ${stillInvalidDiagrams.length} diagrams requiring AI fixing`);
1662
1166
  }
@@ -1664,15 +1168,18 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1664
1168
  for (const invalidDiagram of stillInvalidDiagrams) {
1665
1169
  if (debug) {
1666
1170
  console.log(`[DEBUG] Mermaid validation: Attempting AI fix for diagram ${invalidDiagram.originalIndex + 1}`);
1667
- console.log(`[DEBUG] Mermaid validation: Diagram type: ${invalidDiagram.diagramType || 'unknown'}`);
1668
- console.log(`[DEBUG] Mermaid validation: Error to fix: ${invalidDiagram.error}`);
1669
1171
  }
1670
-
1172
+
1671
1173
  const diagramFixStart = Date.now();
1672
1174
  try {
1175
+ // Pass maid's structured errors if available
1176
+ const errorsToPass = invalidDiagram.errors && invalidDiagram.errors.length > 0
1177
+ ? invalidDiagram.errors
1178
+ : [invalidDiagram.error];
1179
+
1673
1180
  const fixedContent = await mermaidFixer.fixMermaidDiagram(
1674
1181
  invalidDiagram.content,
1675
- [invalidDiagram.error],
1182
+ errorsToPass,
1676
1183
  { diagramType: invalidDiagram.diagramType }
1677
1184
  );
1678
1185
  const diagramFixTime = Date.now() - diagramFixStart;
@@ -1682,91 +1189,68 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1682
1189
  const originalDiagram = updatedDiagrams[invalidDiagram.originalIndex];
1683
1190
  const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
1684
1191
  const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${fixedContent}\n\`\`\``;
1685
-
1686
- fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1687
- newCodeBlock +
1192
+
1193
+ fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
1194
+ newCodeBlock +
1688
1195
  fixedResponse.slice(originalDiagram.endIndex);
1689
-
1690
- fixingResults.push({
1691
- diagramIndex: invalidDiagram.originalIndex,
1692
- wasFixed: true,
1693
- originalContent: invalidDiagram.content,
1694
- fixedContent: fixedContent,
1695
- originalError: invalidDiagram.error,
1696
- aiFixingTimeMs: diagramFixTime
1697
- });
1196
+
1197
+ // Find existing result or create new one
1198
+ const existingResultIndex = fixingResults.findIndex(r => r.diagramIndex === invalidDiagram.originalIndex);
1199
+ if (existingResultIndex >= 0) {
1200
+ fixingResults[existingResultIndex] = {
1201
+ ...fixingResults[existingResultIndex],
1202
+ wasFixed: true,
1203
+ fixedContent: fixedContent,
1204
+ aiFixingTimeMs: diagramFixTime
1205
+ };
1206
+ } else {
1207
+ fixingResults.push({
1208
+ diagramIndex: invalidDiagram.originalIndex,
1209
+ wasFixed: true,
1210
+ originalContent: invalidDiagram.content,
1211
+ fixedContent: fixedContent,
1212
+ originalError: invalidDiagram.error,
1213
+ aiFixingTimeMs: diagramFixTime
1214
+ });
1215
+ }
1698
1216
 
1699
1217
  if (debug) {
1700
- console.log(`[DEBUG] Mermaid validation: Successfully fixed diagram ${invalidDiagram.originalIndex + 1} in ${diagramFixTime}ms`);
1701
- console.log(`[DEBUG] Mermaid validation: Original error: ${invalidDiagram.error}`);
1702
- console.log(`[DEBUG] Mermaid validation: Content changes: ${invalidDiagram.content.length} -> ${fixedContent.length} chars`);
1218
+ console.log(`[DEBUG] Mermaid validation: Successfully fixed diagram ${invalidDiagram.originalIndex + 1} with AI in ${diagramFixTime}ms`);
1703
1219
  }
1704
1220
  } else {
1705
- fixingResults.push({
1706
- diagramIndex: invalidDiagram.originalIndex,
1707
- wasFixed: false,
1708
- originalContent: invalidDiagram.content,
1709
- originalError: invalidDiagram.error,
1710
- fixingError: 'No valid fix generated',
1711
- aiFixingTimeMs: diagramFixTime
1712
- });
1713
-
1714
1221
  if (debug) {
1715
1222
  console.log(`[DEBUG] Mermaid validation: AI fix failed for diagram ${invalidDiagram.originalIndex + 1} - no valid fix generated`);
1716
1223
  }
1717
1224
  }
1718
1225
  } catch (error) {
1719
- const diagramFixTime = Date.now() - diagramFixStart;
1720
- fixingResults.push({
1721
- diagramIndex: invalidDiagram.originalIndex,
1722
- wasFixed: false,
1723
- originalContent: invalidDiagram.content,
1724
- originalError: invalidDiagram.error,
1725
- fixingError: error.message,
1726
- aiFixingTimeMs: diagramFixTime
1727
- });
1728
-
1729
1226
  if (debug) {
1730
- console.log(`[DEBUG] Mermaid validation: AI fix failed for diagram ${invalidDiagram.originalIndex + 1} after ${diagramFixTime}ms: ${error.message}`);
1227
+ console.log(`[DEBUG] Mermaid validation: AI fix failed for diagram ${invalidDiagram.originalIndex + 1}: ${error.message}`);
1731
1228
  }
1732
1229
  }
1733
1230
  }
1734
1231
 
1735
- // Re-validate the fixed response
1736
- const finalValidationStart = Date.now();
1232
+ // Final validation
1737
1233
  const finalValidation = await validateMermaidResponse(fixedResponse);
1738
- const finalValidationTime = Date.now() - finalValidationStart;
1739
1234
  const totalTime = Date.now() - startTime;
1740
1235
  const aiFixingTime = Date.now() - aiFixingStart;
1741
1236
 
1742
- // Check if any diagrams were actually fixed
1743
1237
  const wasActuallyFixed = fixingResults.some(result => result.wasFixed);
1744
1238
  const fixedCount = fixingResults.filter(result => result.wasFixed).length;
1745
- const totalAttempts = fixingResults.length;
1746
1239
 
1747
1240
  if (debug) {
1748
- console.log(`[DEBUG] Mermaid validation: Final validation completed in ${finalValidationTime}ms`);
1749
1241
  console.log(`[DEBUG] Mermaid validation: Total process time: ${totalTime}ms (AI fixing: ${aiFixingTime}ms)`);
1750
- console.log(`[DEBUG] Mermaid validation: Fixed ${fixedCount}/${totalAttempts} diagrams with AI`);
1242
+ console.log(`[DEBUG] Mermaid validation: Fixed ${fixedCount}/${fixingResults.length} diagrams`);
1751
1243
  console.log(`[DEBUG] Mermaid validation: Final result - all valid: ${finalValidation.isValid}`);
1752
-
1753
- if (mermaidFixer.getTokenUsage) {
1754
- const tokenUsage = mermaidFixer.getTokenUsage();
1755
- console.log(`[DEBUG] Mermaid validation: AI token usage - prompt: ${tokenUsage?.promptTokens || 0}, completion: ${tokenUsage?.completionTokens || 0}`);
1756
- }
1757
1244
  }
1758
-
1759
- // Record final completion in telemetry
1245
+
1760
1246
  if (tracer) {
1761
1247
  tracer.recordMermaidValidationEvent('completed', {
1762
1248
  'mermaid_validation.success': finalValidation.isValid,
1763
1249
  'mermaid_validation.was_fixed': wasActuallyFixed,
1764
- 'mermaid_validation.diagrams_processed': totalAttempts,
1250
+ 'mermaid_validation.diagrams_processed': fixingResults.length,
1765
1251
  'mermaid_validation.diagrams_fixed': fixedCount,
1766
1252
  'mermaid_validation.total_duration_ms': totalTime,
1767
- 'mermaid_validation.ai_fixing_duration_ms': aiFixingTime,
1768
- 'mermaid_validation.final_validation_duration_ms': finalValidationTime,
1769
- 'mermaid_validation.token_usage': mermaidFixer.getTokenUsage ? mermaidFixer.getTokenUsage() : null
1253
+ 'mermaid_validation.ai_fixing_duration_ms': aiFixingTime
1770
1254
  });
1771
1255
  }
1772
1256
 
@@ -1779,8 +1263,7 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1779
1263
  performanceMetrics: {
1780
1264
  totalTimeMs: totalTime,
1781
1265
  aiFixingTimeMs: aiFixingTime,
1782
- finalValidationTimeMs: finalValidationTime,
1783
- diagramsProcessed: totalAttempts,
1266
+ diagramsProcessed: fixingResults.length,
1784
1267
  diagramsFixed: fixedCount
1785
1268
  },
1786
1269
  tokenUsage: mermaidFixer.getTokenUsage()
@@ -1788,10 +1271,9 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1788
1271
 
1789
1272
  } catch (error) {
1790
1273
  if (debug) {
1791
- console.error(`[DEBUG] Mermaid fixing agent failed: ${error.message}`);
1274
+ console.error(`[DEBUG] Mermaid fixing failed: ${error.message}`);
1792
1275
  }
1793
1276
 
1794
- // Return original validation with fixing error
1795
1277
  return {
1796
1278
  ...validation,
1797
1279
  wasFixed: false,
@@ -1800,4 +1282,4 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
1800
1282
  fixingError: error.message
1801
1283
  };
1802
1284
  }
1803
- }
1285
+ }