gitnexus 1.6.5-rc.21 → 1.6.5-rc.22

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.
@@ -14,6 +14,7 @@ import path from 'path';
14
14
  import { execSync, execFileSync } from 'child_process';
15
15
  import { initWikiDb, closeWikiDb, touchWikiDb, getFilesWithExports, getAllFiles, getIntraModuleCallEdges, getInterModuleCallEdges, getProcessesForFiles, getAllProcesses, getInterModuleEdgesForOverview, } from './graph-queries.js';
16
16
  import { generateHTMLViewer } from './html-viewer.js';
17
+ import { sanitizeMermaidMarkdown } from './mermaid-sanitizer.js';
17
18
  import { callLLM, estimateTokens, } from './llm-client.js';
18
19
  import { callCursorLLM, resolveCursorConfig } from './cursor-client.js';
19
20
  import { GROUPING_SYSTEM_PROMPT, GROUPING_USER_PROMPT, MODULE_SYSTEM_PROMPT, MODULE_USER_PROMPT, PARENT_SYSTEM_PROMPT, PARENT_USER_PROMPT, OVERVIEW_SYSTEM_PROMPT, OVERVIEW_USER_PROMPT, fillTemplate, formatFileListForGrouping, formatDirectoryTree, formatCallEdges, formatProcesses, } from './prompts.js';
@@ -445,7 +446,7 @@ export class WikiGenerator {
445
446
  });
446
447
  const response = await this.invokeLLM(prompt, MODULE_SYSTEM_PROMPT, this.streamOpts(node.name));
447
448
  // Write page with front matter
448
- const pageContent = `# ${node.name}\n\n${response.content}`;
449
+ const pageContent = sanitizeMermaidMarkdown(`# ${node.name}\n\n${response.content}`);
449
450
  await fs.writeFile(path.join(this.wikiDir, `${node.slug}.md`), pageContent, 'utf-8');
450
451
  }
451
452
  /**
@@ -480,7 +481,7 @@ export class WikiGenerator {
480
481
  CROSS_PROCESSES: formatProcesses(processes),
481
482
  });
482
483
  const response = await this.invokeLLM(prompt, PARENT_SYSTEM_PROMPT, this.streamOpts(node.name));
483
- const pageContent = `# ${node.name}\n\n${response.content}`;
484
+ const pageContent = sanitizeMermaidMarkdown(`# ${node.name}\n\n${response.content}`);
484
485
  await fs.writeFile(path.join(this.wikiDir, `${node.slug}.md`), pageContent, 'utf-8');
485
486
  }
486
487
  // ─── Phase 3: Generate Overview ─────────────────────────────────────
@@ -516,7 +517,7 @@ export class WikiGenerator {
516
517
  TOP_PROCESSES: formatProcesses(topProcesses),
517
518
  });
518
519
  const response = await this.invokeLLM(prompt, OVERVIEW_SYSTEM_PROMPT, this.streamOpts('Generating overview', 88));
519
- const pageContent = `# ${path.basename(this.repoPath)} — Wiki\n\n${response.content}`;
520
+ const pageContent = sanitizeMermaidMarkdown(`# ${path.basename(this.repoPath)} — Wiki\n\n${response.content}`);
520
521
  await fs.writeFile(path.join(this.wikiDir, 'overview.md'), pageContent, 'utf-8');
521
522
  }
522
523
  // ─── Incremental Updates ────────────────────────────────────────────
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import fs from 'fs/promises';
8
8
  import path from 'path';
9
+ import { sanitizeMermaidMarkdown } from './mermaid-sanitizer.js';
9
10
  /**
10
11
  * Generate the wiki HTML viewer (index.html) from existing markdown pages.
11
12
  */
@@ -33,7 +34,7 @@ export async function generateHTMLViewer(wikiDir, projectName) {
33
34
  const dirEntries = await fs.readdir(wikiDir);
34
35
  for (const f of dirEntries.filter((f) => f.endsWith('.md'))) {
35
36
  const content = await fs.readFile(path.join(wikiDir, f), 'utf-8');
36
- pages[f.replace(/\.md$/, '')] = content;
37
+ pages[f.replace(/\.md$/, '')] = sanitizeMermaidMarkdown(content);
37
38
  }
38
39
  const html = buildHTML(projectName, moduleTree, pages, meta);
39
40
  const outputPath = path.join(wikiDir, 'index.html');
@@ -0,0 +1,2 @@
1
+ export declare function sanitizeMermaidMarkdown(markdown: string): string;
2
+ export declare function sanitizeMermaidDiagram(diagram: string): string;
@@ -0,0 +1,100 @@
1
+ const MERMAID_FENCE_RE = /```mermaid\s*\n([\s\S]*?)```/g;
2
+ const NODE_LABEL_RE = /(\[[^\]\n]*(?:\\n)[^\]\n]*\]|\{[^}\n]*(?:\\n)[^}\n]*\}|\([^)\n]*(?:\\n)[^)\n]*\))/g;
3
+ const EDGE_LABEL_RE = /\|([^|\n]+)\|/g;
4
+ const UNSAFE_EDGE_LABEL_RE = /[()[\]{}<>]/;
5
+ const UNSAFE_NODE_ID_RE = /[^A-Za-z0-9_-]/;
6
+ const NODE_ID_RE = /^[A-Za-z0-9_.:/()-]+$/;
7
+ const LINE_PREFIX_RE = /^(\s*(?:(?:[-A-Za-z0-9_]+)\s*:\s*)?)(.*)$/;
8
+ const EDGE_RE = /(\s*(?:[ox])?(?:--+|==+|\.\.+)(?:[>|ox])?\|[^|\n]*\|(?:[>|ox])?|\s*(?:[ox])?(?:--+|==+|\.\.+)(?:[>|ox])?|\s*<--+>?\s*)/g;
9
+ export function sanitizeMermaidMarkdown(markdown) {
10
+ return markdown.replace(MERMAID_FENCE_RE, (_match, diagram) => {
11
+ return '```mermaid\n' + sanitizeMermaidDiagram(diagram) + '```';
12
+ });
13
+ }
14
+ export function sanitizeMermaidDiagram(diagram) {
15
+ const aliases = new Map();
16
+ let nextAlias = 1;
17
+ const aliasFor = (id) => {
18
+ const existing = aliases.get(id);
19
+ if (existing)
20
+ return existing;
21
+ const base = id.replace(/[^A-Za-z0-9_-]/g, '_').replace(/^_+|_+$/g, '') || 'node';
22
+ let alias = base;
23
+ while ([...aliases.values()].includes(alias)) {
24
+ nextAlias += 1;
25
+ alias = `${base}_${nextAlias}`;
26
+ }
27
+ aliases.set(id, alias);
28
+ return alias;
29
+ };
30
+ return diagram
31
+ .split('\n')
32
+ .map((line) => sanitizeMermaidLine(line, aliasFor))
33
+ .join('\n');
34
+ }
35
+ function sanitizeMermaidLine(line, aliasFor) {
36
+ let sanitized = replaceLiteralLineBreaksInLabels(line);
37
+ sanitized = quoteUnsafeEdgeLabels(sanitized);
38
+ const prefixMatch = sanitized.match(LINE_PREFIX_RE);
39
+ if (!prefixMatch)
40
+ return sanitized;
41
+ const prefix = prefixMatch[1];
42
+ const body = prefixMatch[2];
43
+ if (isDirectiveLine(body))
44
+ return sanitized;
45
+ const parts = body.split(EDGE_RE);
46
+ if (parts.length === 1)
47
+ return sanitized;
48
+ for (let i = 0; i < parts.length; i += 2) {
49
+ parts[i] = sanitizeNodeReference(parts[i], aliasFor);
50
+ }
51
+ return prefix + parts.join('');
52
+ }
53
+ function replaceLiteralLineBreaksInLabels(line) {
54
+ return line.replace(NODE_LABEL_RE, (label) => label.replace(/\\n/g, '<br/>'));
55
+ }
56
+ function quoteUnsafeEdgeLabels(line) {
57
+ return line.replace(EDGE_LABEL_RE, (match, label) => {
58
+ const trimmed = label.trim();
59
+ if (!UNSAFE_EDGE_LABEL_RE.test(trimmed))
60
+ return match;
61
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
62
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
63
+ return match;
64
+ }
65
+ return `|"${escapeMermaidLabel(trimmed)}"|`;
66
+ });
67
+ }
68
+ function sanitizeNodeReference(segment, aliasFor) {
69
+ const match = segment.match(/^(\s*)([A-Za-z0-9_.:/()-]+)(.*?)(\s*)$/);
70
+ if (!match)
71
+ return segment;
72
+ const [, leading, id, suffix, trailing] = match;
73
+ if (!NODE_ID_RE.test(id) || !UNSAFE_NODE_ID_RE.test(id))
74
+ return segment;
75
+ const hasInlineLabel = suffix.trim().startsWith('[') || suffix.trim().startsWith('(') || suffix.trim().startsWith('{');
76
+ if (hasInlineLabel)
77
+ return `${leading}${aliasFor(id)}${suffix}${trailing}`;
78
+ return `${leading}${aliasFor(id)}["${escapeMermaidLabel(id)}"]${suffix}${trailing}`;
79
+ }
80
+ function escapeMermaidLabel(label) {
81
+ return label.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
82
+ }
83
+ function isDirectiveLine(line) {
84
+ const trimmed = line.trim();
85
+ return (trimmed === '' ||
86
+ trimmed.startsWith('%%') ||
87
+ trimmed.startsWith('graph ') ||
88
+ trimmed.startsWith('flowchart ') ||
89
+ trimmed.startsWith('sequenceDiagram') ||
90
+ trimmed.startsWith('classDiagram') ||
91
+ trimmed.startsWith('stateDiagram') ||
92
+ trimmed.startsWith('erDiagram') ||
93
+ trimmed.startsWith('journey') ||
94
+ trimmed.startsWith('gantt') ||
95
+ trimmed.startsWith('pie ') ||
96
+ trimmed.startsWith('mindmap') ||
97
+ trimmed.startsWith('timeline') ||
98
+ trimmed.startsWith('subgraph ') ||
99
+ trimmed === 'end');
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.21",
3
+ "version": "1.6.5-rc.22",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",