11#!/usr/bin/env python3
22"""
33Enhanced Docs Autopilot for TriBridRAG
4- Generates comprehensive documentation using OpenAI GPT-4 with full context awareness
4+ Generates comprehensive documentation using OpenAI GPT-5 (Responses API) with full context awareness
55
66TriBridRAG is a tri-brid RAG engine combining:
77- Vector search (pgvector in PostgreSQL)
2323from dataclasses import dataclass , field
2424
2525
26+ _MERMAID_FENCE_RE = re .compile (r"```mermaid\s*\n(?P<code>[\s\S]*?)\n```" , re .MULTILINE )
27+
28+
29+ def _normalize_mermaid_v11_code (code : str ) -> str :
30+ """
31+ Normalize Mermaid flowchart syntax to reduce Mermaid v11 parse errors.
32+
33+ This is intentionally conservative and only fixes common, mechanical issues:
34+ - `\\ n` line breaks must be inside quoted labels: `A[foo\\ nbar]` -> `A[\" foo\\ nbar\" ]`
35+ - `A[foo]\\ nbar` -> `A[\" foo\\ nbar\" ]`
36+ - Endpoint tokens like `/metrics` must NOT be node IDs: `--> /metrics` -> `--> METRICS[\" /metrics\" ]`
37+ - `subgraph` titles with spaces should be quoted: `subgraph Foo Bar` -> `subgraph \" Foo Bar\" `
38+ """
39+
40+ fixed = code
41+
42+ # 1) Quote subgraph titles that contain spaces and are not already quoted / bracketed.
43+ lines : list [str ] = []
44+ for line in fixed .splitlines ():
45+ m = re .match (r"^(\s*)subgraph\s+([^\[\"\n]+)$" , line )
46+ if m :
47+ indent , title = m .group (1 ), m .group (2 ).strip ()
48+ if " " in title and not title .startswith ('"' ) and "[" not in title :
49+ line = f'{ indent } subgraph "{ title } "'
50+ lines .append (line )
51+ fixed = "\n " .join (lines )
52+
53+ # 2) Replace bare endpoint tokens used as node IDs.
54+ endpoint_nodes = {
55+ "/metrics" : "METRICS" ,
56+ "/ready" : "READY" ,
57+ "/health" : "HEALTH" ,
58+ }
59+ for endpoint , node_id in endpoint_nodes .items ():
60+ # ... --> /metrics
61+ fixed = re .sub (
62+ rf"(-->)\s*{ re .escape (endpoint )} \s*$" ,
63+ rf'\1 { node_id } ["{ endpoint } "]' ,
64+ fixed ,
65+ flags = re .MULTILINE ,
66+ )
67+ # /metrics --> ...
68+ fixed = re .sub (
69+ rf"^(\s*){ re .escape (endpoint )} (\s*-->)" ,
70+ rf'\1{ node_id } ["{ endpoint } "]\2' ,
71+ fixed ,
72+ flags = re .MULTILINE ,
73+ )
74+
75+ # 3) Quote labels that contain a literal "\\n" inside brackets.
76+ # A[foo\nbar] -> A["foo\nbar"]
77+ fixed = re .sub (
78+ r'(\b[A-Za-z][A-Za-z0-9_]*)\[(?!")(\s*[^\]]*\\n[^\]]*)\]' ,
79+ r'\1["\2"]' ,
80+ fixed ,
81+ )
82+
83+ # 4) Merge the invalid pattern: A[foo]\\nbar -> A["foo\\nbar"]
84+ fixed = re .sub (
85+ r'(\b[A-Za-z][A-Za-z0-9_]*)\[([^\]]+)\]\\n([^\n]+)$' ,
86+ r'\1["\2\\n\3"]' ,
87+ fixed ,
88+ flags = re .MULTILINE ,
89+ )
90+
91+ return fixed
92+
93+
94+ def normalize_mermaid_v11_markdown (markdown : str ) -> Tuple [str , int ]:
95+ """Normalize Mermaid blocks in markdown. Returns (updated_markdown, blocks_changed)."""
96+
97+ blocks_changed = 0
98+
99+ def _replace (match : re .Match [str ]) -> str :
100+ nonlocal blocks_changed
101+ code = match .group ("code" )
102+ normalized = _normalize_mermaid_v11_code (code )
103+ if normalized != code :
104+ blocks_changed += 1
105+ return f"```mermaid\n { normalized } \n ```"
106+
107+ updated = _MERMAID_FENCE_RE .sub (_replace , markdown or "" )
108+ return updated , blocks_changed
109+
110+
26111@dataclass
27112class DocumentationContext :
28113 """Comprehensive context for documentation generation
@@ -504,6 +589,22 @@ def search(query: str, repo_id: str): # (1)
504589 Rerank --> Results[Final Results]
505590```
506591
592+ ### MERMAID v11 (CRITICAL: AVOID SYNTAX ERRORS)
593+ - ONLY generate `flowchart` diagrams (`flowchart LR` / `flowchart TB`). Avoid other diagram types.
594+ - NO HTML anywhere in Mermaid (no `<br>`, no tags, no raw HTML labels).
595+ - Node IDs MUST be simple: start with a letter, then letters/numbers/underscore only (`^[A-Za-z][A-Za-z0-9_]*$`).
596+ - NEVER use URL-ish or path-ish tokens as node IDs (e.g., do NOT write `--> /metrics`). Use an ID + quoted label:
597+ - `METRICS["/metrics"]`, `READY["/ready"]`, `HEALTH["/health"]`
598+ - If you want multi-line labels, you MUST quote the label and put `\\ n` *inside* the quotes:
599+ - GOOD: `UI["Frontend\\ n(generated.ts)"]`
600+ - BAD: `UI[Frontend]\\ n(generated.ts)`
601+ - If you use `subgraph` and the title contains spaces, quote it:
602+ - GOOD: `subgraph "Tuning Inputs"`
603+ - GOOD: `subgraph tuning_inputs["Tuning Inputs"]`
604+ - BAD: `subgraph Tuning Inputs`
605+ - Prefer quoting any label containing punctuation like `/`, `(`, `)`, `:`, `+`, `-`.
606+ - Keep diagrams small and shallow. Prefer 6–14 nodes per diagram; use multiple diagrams instead of one huge diagram.
607+
507608### 7. CONTENT ORGANIZATION
508609- Use hierarchical headers (##, ###, ####)
509610- Add table of contents markers
@@ -1126,10 +1227,38 @@ def write_documentation_files(self, docs_updates: Dict[str, str]) -> None:
11261227 # Filter content one more time before writing
11271228 filtered_content = self ._filter_sensitive_content (content )
11281229 filtered_content = self ._filter_banned_terms (filtered_content )
1230+ filtered_content , blocks_changed = normalize_mermaid_v11_markdown (filtered_content )
11291231
11301232 full_path .write_text (filtered_content , encoding = "utf-8" )
1233+ if blocks_changed :
1234+ print (f" ↳ Mermaid normalized: { blocks_changed } block(s)" )
11311235 print (f" ✅ Wrote: { file_path } " )
11321236
1237+ def normalize_existing_mermaid (self ) -> Tuple [int , int ]:
1238+ """Normalize Mermaid blocks across existing mkdocs/docs markdown files."""
1239+
1240+ if not self .docs_dir .exists ():
1241+ return 0 , 0
1242+
1243+ files_changed = 0
1244+ blocks_changed_total = 0
1245+
1246+ for md_file in sorted (self .docs_dir .rglob ("*.md" )):
1247+ try :
1248+ original = md_file .read_text (encoding = "utf-8" )
1249+ except Exception :
1250+ continue
1251+
1252+ updated , blocks_changed = normalize_mermaid_v11_markdown (original )
1253+ if blocks_changed and updated != original :
1254+ md_file .write_text (updated , encoding = "utf-8" )
1255+ files_changed += 1
1256+ blocks_changed_total += blocks_changed
1257+ rel = md_file .relative_to (self .docs_dir )
1258+ print (f" ✅ Mermaid normalized: { rel } ({ blocks_changed } block(s))" )
1259+
1260+ return files_changed , blocks_changed_total
1261+
11331262 def write_mkdocs_config (self , config : dict ) -> None :
11341263 """Write mkdocs.yml configuration"""
11351264
@@ -1316,6 +1445,11 @@ def main():
13161445 parser .add_argument ("--dry-run" , action = "store_true" , help = "Don't write files, just show what would be done" )
13171446 parser .add_argument ("--regenerate-all" , action = "store_true" , help = "Regenerate all documentation from entire codebase" )
13181447 parser .add_argument ("--full-scan" , action = "store_true" , help = "Scan entire repository, not just changes" )
1448+ parser .add_argument (
1449+ "--normalize-mermaid" ,
1450+ action = "store_true" ,
1451+ help = "Normalize Mermaid v11 blocks in existing docs (no LLM call)" ,
1452+ )
13191453 args = parser .parse_args ()
13201454
13211455 print ("=" * 60 )
@@ -1324,6 +1458,12 @@ def main():
13241458
13251459 autopilot = EnhancedDocsAutopilot ()
13261460
1461+ if args .normalize_mermaid :
1462+ print ("\n 🧹 Normalizing Mermaid blocks across mkdocs/docs ..." )
1463+ files_changed , blocks_changed = autopilot .normalize_existing_mermaid ()
1464+ print (f"✅ Mermaid normalization complete: { files_changed } file(s), { blocks_changed } block(s) updated" )
1465+ return
1466+
13271467 # Force full repository scan if regenerate-all or full-scan
13281468 if args .regenerate_all or args .full_scan :
13291469 print ("\n 🔄 Full repository scan mode - generating docs from entire codebase..." )
0 commit comments