@brainwav/diagram 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.diagram/contracts/machine-command-coverage.json +73 -0
  2. package/.diagram/migration/finalization-policy.json +20 -0
  3. package/LICENSE +202 -21
  4. package/README.md +132 -339
  5. package/package.json +46 -13
  6. package/scripts/refresh-diagram-context.sh +274 -182
  7. package/src/analyzers/default-analyzer.js +11 -0
  8. package/src/analyzers/index.js +34 -0
  9. package/src/artifacts/agent-context.js +105 -0
  10. package/src/artifacts/artifact-budget.js +224 -0
  11. package/src/artifacts/brief.js +153 -0
  12. package/src/artifacts/evidence-manifest.js +206 -0
  13. package/src/artifacts/evidence-summary.js +29 -0
  14. package/src/commands/analyze.js +125 -0
  15. package/src/commands/changed.js +185 -0
  16. package/src/commands/context.js +110 -0
  17. package/src/commands/diff.js +142 -0
  18. package/src/commands/doctor.js +335 -0
  19. package/src/commands/explain.js +273 -0
  20. package/src/commands/generate-all.js +170 -0
  21. package/src/commands/generate-animated.js +50 -0
  22. package/src/commands/generate-video.js +65 -0
  23. package/src/commands/generate.js +522 -0
  24. package/src/commands/init.js +123 -0
  25. package/src/commands/output.js +76 -0
  26. package/src/commands/scan.js +624 -0
  27. package/src/commands/shared.js +396 -0
  28. package/src/commands/validate.js +328 -0
  29. package/src/commands/video-shared.js +105 -0
  30. package/src/commands/workflow-pr.js +26 -0
  31. package/src/confidence/pipeline.js +186 -0
  32. package/src/config/diagramrc.js +79 -0
  33. package/src/context/build-context-pack.js +291 -0
  34. package/src/context/normalize-diagram-manifest.js +282 -0
  35. package/src/core/analysis-generation-analyze-components.js +102 -0
  36. package/src/core/analysis-generation-analyze-dependencies.js +33 -0
  37. package/src/core/analysis-generation-analyze-files.js +48 -0
  38. package/src/core/analysis-generation-analyze-options.js +73 -0
  39. package/src/core/analysis-generation-analyze.js +63 -0
  40. package/src/core/analysis-generation-constants.js +53 -0
  41. package/src/core/analysis-generation-diagrams-core-architecture.js +105 -0
  42. package/src/core/analysis-generation-diagrams-core-dependency.js +68 -0
  43. package/src/core/analysis-generation-diagrams-core-sequence.js +142 -0
  44. package/src/core/analysis-generation-diagrams-core-shapes.js +104 -0
  45. package/src/core/analysis-generation-diagrams-core.js +12 -0
  46. package/src/core/analysis-generation-diagrams-empty.js +68 -0
  47. package/src/core/analysis-generation-diagrams-erd.js +59 -0
  48. package/src/core/analysis-generation-diagrams-limit.js +27 -0
  49. package/src/core/analysis-generation-diagrams-role-ai-agent.js +103 -0
  50. package/src/core/analysis-generation-diagrams-role-ai-context.js +186 -0
  51. package/src/core/analysis-generation-diagrams-role-ai.js +11 -0
  52. package/src/core/analysis-generation-diagrams-role-data.js +182 -0
  53. package/src/core/analysis-generation-diagrams-role-helpers.js +129 -0
  54. package/src/core/analysis-generation-diagrams-role-security.js +129 -0
  55. package/src/core/analysis-generation-diagrams-role.js +25 -0
  56. package/src/core/analysis-generation-diagrams.js +182 -0
  57. package/src/core/analysis-generation-role-tags-constants.js +55 -0
  58. package/src/core/analysis-generation-role-tags-imports.js +32 -0
  59. package/src/core/analysis-generation-role-tags-infer.js +49 -0
  60. package/src/core/analysis-generation-role-tags-match.js +19 -0
  61. package/src/core/analysis-generation-role-tags.js +7 -0
  62. package/src/core/analysis-generation-utils-core.js +308 -0
  63. package/src/core/analysis-generation-utils-graph.js +321 -0
  64. package/src/core/analysis-generation-utils-resolution.js +76 -0
  65. package/src/core/analysis-generation-utils.js +9 -0
  66. package/src/core/analysis-generation.js +44 -0
  67. package/src/diagram.js +180 -1760
  68. package/src/formatters/console.js +198 -0
  69. package/src/formatters/index.js +41 -0
  70. package/src/formatters/json.js +113 -0
  71. package/src/formatters/junit.js +123 -0
  72. package/src/graph.js +159 -0
  73. package/src/incremental/cache.js +210 -0
  74. package/src/ir/architecture-ir.js +48 -0
  75. package/src/migration/evidence.js +262 -0
  76. package/src/migration/finalization-policy.js +35 -0
  77. package/src/renderers/report-html.js +265 -0
  78. package/src/rules/factory.js +108 -0
  79. package/src/rules/types/base.js +54 -0
  80. package/src/rules/types/import-rule.js +286 -0
  81. package/src/rules.js +380 -0
  82. package/src/schema/erd-confidence.js +56 -0
  83. package/src/schema/erd-extractor.js +504 -0
  84. package/src/schema/erd-model.js +176 -0
  85. package/src/schema/rules-schema.js +170 -0
  86. package/src/utils/suggestions.js +67 -0
  87. package/src/video.js +4 -5
  88. package/src/workflow/git-helpers.js +576 -0
  89. package/src/workflow/pr-command.js +694 -0
  90. package/src/workflow/pr-impact.js +848 -0
  91. package/src/workflow/sort-utils.js +16 -0
@@ -0,0 +1,186 @@
1
+ const path = require('path');
2
+ const {
3
+ escapeMermaid,
4
+ componentsByRole,
5
+ mapSafeNames,
6
+ } = require('./analysis-generation-utils');
7
+ const { collectExternalImports } = require('./analysis-generation-role-tags');
8
+ const { graphNote, flowNote } = require('./analysis-generation-diagrams-empty');
9
+ const {
10
+ emitRoleClassStyle,
11
+ emitSubgraph,
12
+ } = require('./analysis-generation-diagrams-role-helpers');
13
+
14
+ const EXTERNAL_CATEGORY_RULES = Object.freeze([
15
+ { category: 'payment', pattern: /stripe|pay|billing|invoice/ },
16
+ { category: 'email', pattern: /sendgrid|mail|email|smtp|postmark/ },
17
+ { category: 'database', pattern: /postgres|pg|mysql|sqlite|mongo|redis|dynamo|prisma|typeorm|sequelize/ },
18
+ { category: 'ai', pattern: /openai|anthropic|gemini|ollama|hugging/ },
19
+ { category: 'vcs', pattern: /github|gitlab|bitbucket|octokit/ },
20
+ { category: 'messaging', pattern: /slack|discord|teams|twilio/ },
21
+ { category: 'cloud', pattern: /s3|gcs|azure|cloudflare|vercel|supabase/ },
22
+ ]);
23
+
24
+ const CATEGORY_LABELS = Object.freeze({
25
+ payment: 'Payment Provider',
26
+ email: 'Email Service',
27
+ database: 'Database',
28
+ ai: 'AI / LLM Provider',
29
+ vcs: 'Version Control',
30
+ messaging: 'Messaging Service',
31
+ cloud: 'Cloud Provider',
32
+ external: 'External Service',
33
+ });
34
+
35
+ /**
36
+ * Determine the classification category for an external package identifier.
37
+ *
38
+ * @param {string} pkg - Package or import identifier to classify (may be falsy).
39
+ * @returns {string} The matched category key from EXTERNAL_CATEGORY_RULES, or `'external'` when no rule matches.
40
+ */
41
+ function classifyExternalPackage(pkg) {
42
+ const candidate = String(pkg || '').toLowerCase();
43
+ for (const rule of EXTERNAL_CATEGORY_RULES) {
44
+ if (rule.pattern.test(candidate)) return rule.category;
45
+ }
46
+ return 'external';
47
+ }
48
+
49
+ /**
50
+ * Builds a C4 Context Mermaid diagram for the project and its detected external systems.
51
+ *
52
+ * Iterates components' imports to classify and group external packages, emits a main
53
+ * System node with a Developer person and "Uses" relationship, and adds one external
54
+ * System_Ext node per detected category (listing up to three package names). If no
55
+ * externals are found a placeholder External Systems node is emitted.
56
+ *
57
+ * @param {Object} data - Analysis payload.
58
+ * @param {string} [data.rootPath] - Project root path used to derive the diagram title.
59
+ * @param {Array<Object>} data.components - Component descriptors; each may include an `imports` array of package strings.
60
+ * @returns {string} The Mermaid C4Context diagram markup. If `data` is missing or `components` is not an array, returns a short note indicating no data is available.
61
+ */
62
+ function generateC4Context(data) {
63
+ if (!data || !Array.isArray(data.components)) {
64
+ return graphNote('No data available');
65
+ }
66
+
67
+ const projectName = path.basename(data.rootPath || 'System').replace(/[-_]/g, ' ');
68
+ const lines = ['C4Context', ` title "System Context — ${escapeMermaid(projectName)}"`];
69
+
70
+ lines.push(` System(mainSystem, "${escapeMermaid(projectName)}", "The system being documented")`);
71
+ lines.push(' Person(developer, "Developer / User", "Uses the system")');
72
+ lines.push(' Rel(developer, mainSystem, "Uses")');
73
+
74
+ const externalByRole = new Map();
75
+ for (const component of data.components) {
76
+ for (const pkg of collectExternalImports(component.imports || [])) {
77
+ const category = classifyExternalPackage(pkg);
78
+ if (!externalByRole.has(category)) externalByRole.set(category, new Set());
79
+ externalByRole.get(category).add(pkg);
80
+ }
81
+ }
82
+
83
+ let extIdx = 0;
84
+ for (const [category, packages] of externalByRole) {
85
+ const label = CATEGORY_LABELS[category] || 'External System';
86
+ const extId = `ext_${extIdx++}`;
87
+ const pkgList = [...packages].slice(0, 3).join(', ');
88
+ lines.push(` System_Ext(${extId}, "${label}", "${escapeMermaid(pkgList)}")`);
89
+ lines.push(` Rel(mainSystem, ${extId}, "uses")`);
90
+ }
91
+
92
+ if (externalByRole.size === 0) {
93
+ lines.push(' System_Ext(noExt, "External Systems", "None detected")');
94
+ }
95
+
96
+ return lines.join('\n');
97
+ }
98
+
99
+ /**
100
+ * Build a Mermaid `flowchart LR` diagram describing a RAG (Retrieval-Augmented Generation) pipeline and any detected memory stores, LLM clients or agentic tools.
101
+ *
102
+ * The diagram always includes the canonical RAG nodes (User Query → Embedding → Vector Store → Retriever → LLM/Generator → Response). When components with roles `memory`, `llm` or `tool` are present they are rendered as subgraphs and connected with role-specific edges. If `data` is missing or `data.components` is not an array, a short "No data available" flowchart note is returned.
103
+ *
104
+ * @param {object} data - Analysis data containing detected components.
105
+ * @param {Array<object>} data.components - Array of component descriptors used to detect memories, LLMs and tools; each component may include an `originalName` used for node labels.
106
+ * @returns {string} Mermaid `flowchart LR` markup representing the RAG pipeline and any detected subgraphs, or a short note diagram when input data is absent or invalid.
107
+ */
108
+ function generateRag(data) {
109
+ if (!data || !Array.isArray(data.components)) {
110
+ return flowNote('No data available', 'LR');
111
+ }
112
+
113
+ const memories = componentsByRole(data.components, 'memory');
114
+ const llms = componentsByRole(data.components, 'llm');
115
+ const tools = componentsByRole(data.components, 'tool');
116
+ const toolNodeIds = [];
117
+
118
+ const lines = ['flowchart LR'];
119
+ lines.push(' UserQ(["👤 User Query"])');
120
+ lines.push(' Embed["📐 Embedding Model"]');
121
+ lines.push(' VecDB[("📚 Vector Store")]');
122
+ lines.push(' Retriever["🔍 Retriever"]');
123
+ lines.push(' LLMNode["🧠 LLM / Generator"]');
124
+ lines.push(' Output(["✅ Response"])');
125
+
126
+ lines.push(' UserQ -->|query| Embed');
127
+ lines.push(' Embed -->|vector| VecDB');
128
+ lines.push(' VecDB -->|top-k chunks| Retriever');
129
+ lines.push(' Retriever -->|context + query| LLMNode');
130
+ lines.push(' LLMNode -->|generated answer| Output');
131
+
132
+ const detectedSpecs = [
133
+ {
134
+ id: 'DetectedMemory',
135
+ title: 'Detected memory stores',
136
+ components: memories,
137
+ renderNode: (memory, safe) => `${safe}[("${escapeMermaid(memory.originalName)}")]`,
138
+ extraEdges: [' VecDB -. "implemented by" .-> DetectedMemory'],
139
+ },
140
+ {
141
+ id: 'DetectedLLM',
142
+ title: 'Detected LLM clients',
143
+ components: llms,
144
+ renderNode: (llm, safe) => `${safe}["${escapeMermaid(llm.originalName)}"]`,
145
+ extraEdges: [' LLMNode -. "implemented by" .-> DetectedLLM'],
146
+ },
147
+ {
148
+ id: 'DetectedTools',
149
+ title: 'Agentic tool calls',
150
+ components: tools,
151
+ renderNode: (tool, safe) => `${safe}["🔧 ${escapeMermaid(tool.originalName)}"]`,
152
+ extraEdges: [
153
+ ' LLMNode -->|tool use| DetectedTools',
154
+ ' DetectedTools -->|result| LLMNode',
155
+ ],
156
+ },
157
+ ];
158
+
159
+ for (const spec of detectedSpecs) {
160
+ const scopedSafeNames = new Map(
161
+ [...mapSafeNames(spec.components)].map(([component, safe]) => [component, `det_${spec.id.toLowerCase()}_${safe}`])
162
+ );
163
+ if (!emitSubgraph(lines, spec.id, spec.title, spec.components, spec.renderNode, scopedSafeNames)) continue;
164
+ if (spec.id === 'DetectedTools') {
165
+ for (const component of spec.components) {
166
+ const safe = scopedSafeNames.get(component);
167
+ if (safe) toolNodeIds.push(safe);
168
+ }
169
+ }
170
+ for (const edge of spec.extraEdges) {
171
+ lines.push(edge);
172
+ }
173
+ }
174
+
175
+ emitRoleClassStyle(lines, 'memNode', 'memory', ['VecDB', 'Retriever']);
176
+ emitRoleClassStyle(lines, 'llmNode', 'llm', ['LLMNode', 'Embed']);
177
+ emitRoleClassStyle(lines, 'toolNode', 'tool', toolNodeIds);
178
+
179
+ return lines.join('\n');
180
+ }
181
+
182
+ module.exports = {
183
+ generateC4Context,
184
+ generateRag,
185
+ classifyExternalPackage,
186
+ };
@@ -0,0 +1,11 @@
1
+ const { generateAgent } = require('./analysis-generation-diagrams-role-ai-agent');
2
+ const {
3
+ generateC4Context,
4
+ generateRag,
5
+ } = require('./analysis-generation-diagrams-role-ai-context');
6
+
7
+ module.exports = {
8
+ generateAgent,
9
+ generateC4Context,
10
+ generateRag,
11
+ };
@@ -0,0 +1,182 @@
1
+ const {
2
+ escapeMermaid,
3
+ componentsByRole,
4
+ buildRoleDiagramContext,
5
+ appendDependencyEdges,
6
+ inferDbIntent,
7
+ } = require('./analysis-generation-utils');
8
+ const { flowNote, noteNode } = require('./analysis-generation-diagrams-empty');
9
+ const {
10
+ emitClassStyle,
11
+ emitSeedNodesWithIngress,
12
+ } = require('./analysis-generation-diagrams-role-helpers');
13
+
14
+ const DATABASE_DEPTH = 2;
15
+ const DATABASE_NODE_LIMIT = 30;
16
+ const USER_DEPTH = 2;
17
+ const USER_NODE_LIMIT = 30;
18
+ const EVENTS_DEPTH = 2;
19
+ const EVENTS_NODE_LIMIT = 30;
20
+
21
+ /**
22
+ * Builds a Mermaid flowchart (top-down) representing database-focused components and their implied control flow.
23
+ *
24
+ * @param {Object} data - Source object containing a `components` array of component records; components with role `"database"` will be rendered.
25
+ * @returns {string} The Mermaid `flowchart TD` diagram as a single string. If `data` is missing or `data.components` is not an array, returns a "No data available" note string; if no database components are present, returns a role-specific note string.
26
+ */
27
+ function generateDatabase(data) {
28
+ if (!data || !Array.isArray(data.components)) {
29
+ return flowNote('No data available');
30
+ }
31
+
32
+ const lines = ['flowchart TD'];
33
+ const seeds = componentsByRole(data.components, 'database');
34
+ if (seeds.length === 0) {
35
+ lines.push(noteNode('No database-focused components found'));
36
+ return lines.join('\n');
37
+ }
38
+
39
+ const { byName, safeNames } = buildRoleDiagramContext(data, seeds, DATABASE_DEPTH, DATABASE_NODE_LIMIT);
40
+
41
+ lines.push(' UserRequest["User request"]');
42
+ lines.push(' Decision{Record exists?}');
43
+
44
+ const addedEdges = new Set();
45
+ const dbNodeIds = [];
46
+
47
+ for (const seed of seeds) {
48
+ const safe = safeNames.get(seed);
49
+ if (!safe) continue;
50
+
51
+ dbNodeIds.push(safe);
52
+ lines.push(` ${safe}["${escapeMermaid(seed.originalName)}"]`);
53
+ lines.push(` UserRequest --> ${safe}`);
54
+
55
+ const intent = inferDbIntent(seed);
56
+ if (intent.hasLookup) {
57
+ const lookup = `${safe}_lookup`;
58
+ const create = `${safe}_create`;
59
+ const update = `${safe}_update`;
60
+ lines.push(` ${safe} --> ${lookup}["lookup query"]`);
61
+ lines.push(` ${lookup} --> Decision`);
62
+ lines.push(` Decision -->|found| ${update}["update or modify"]`);
63
+ lines.push(` Decision -->|not found| ${create}["insert/create"]`);
64
+ lines.push(` ${update} --> ${safe}_result["result"]`);
65
+ lines.push(` ${create} --> ${safe}_result["result"]`);
66
+ } else if (intent.hasWrite) {
67
+ const write = `${safe}_write`;
68
+ lines.push(` ${safe} --> ${write}["write/update"]`);
69
+ lines.push(` ${write} --> ${safe}_result["result"]`);
70
+ } else {
71
+ const result = `${safe}_result`;
72
+ lines.push(` ${safe} --> ${result}["result"]`);
73
+ }
74
+ }
75
+
76
+ appendDependencyEdges(lines, seeds, byName, safeNames, addedEdges);
77
+
78
+ emitClassStyle(lines, 'dbNode', '#0ea5e9', '#fff', dbNodeIds);
79
+ emitClassStyle(lines, 'decisionNode', '#0284c7', '#fff', []);
80
+ lines.push(' class Decision decisionNode');
81
+ return lines.join('\n');
82
+ }
83
+
84
+ /**
85
+ * Builds a left-to-right Mermaid flowchart representing user-facing components and their dependency connections.
86
+ *
87
+ * @param {Object} data - Analysis input containing a `components` array; components are filtered by role `'user'`.
88
+ * @returns {string} A Mermaid `flowchart LR` diagram as a string (may contain a note node when no data or no user-facing components are present).
89
+ */
90
+ function generateUserInteractions(data) {
91
+ if (!data || !Array.isArray(data.components)) {
92
+ return flowNote('No data available', 'LR');
93
+ }
94
+
95
+ const lines = ['flowchart LR'];
96
+ const seeds = componentsByRole(data.components, 'user');
97
+ if (seeds.length === 0) {
98
+ lines.push(noteNode('No user-facing components found'));
99
+ return lines.join('\n');
100
+ }
101
+
102
+ const { connected, byName, safeNames } = buildRoleDiagramContext(data, seeds, USER_DEPTH, USER_NODE_LIMIT);
103
+ const edges = new Set();
104
+ const userNodeIds = [];
105
+
106
+ lines.push(' User(("User"))');
107
+ emitSeedNodesWithIngress(lines, seeds, safeNames, {
108
+ nodeIds: userNodeIds,
109
+ renderNode: (seed, safe) => `${safe}["${escapeMermaid(seed.originalName)}"]`,
110
+ ingressFrom: 'User',
111
+ edges,
112
+ });
113
+
114
+ appendDependencyEdges(lines, connected, byName, safeNames, edges);
115
+
116
+ emitClassStyle(lines, 'userNode', '#16a34a', '#fff', userNodeIds);
117
+ return lines.join('\n');
118
+ }
119
+
120
+ /**
121
+ * Generate a Mermaid TD flowchart representing event channels/queues and which components emit or consume them.
122
+ *
123
+ * Builds a diagram that places event channel components inside a "Channels" subgraph and marks seed components as event sources (emitters) while others are rendered as consumers; also appends dependency edges between components. If `data` is missing or `data.components` is not an array, returns a compact "No data available" flow note; if no event-role components are found, returns a diagram containing a "No event/channels components found" note.
124
+ *
125
+ * @param {Object} data - Input model containing a `components` array describing system components.
126
+ * @returns {string} The complete Mermaid flowchart (TD) as a string.
127
+ */
128
+ function generateEvents(data) {
129
+ if (!data || !Array.isArray(data.components)) {
130
+ return flowNote('No data available');
131
+ }
132
+
133
+ const lines = ['flowchart TD'];
134
+ const seeds = componentsByRole(data.components, 'events');
135
+ if (seeds.length === 0) {
136
+ lines.push(noteNode('No event/channels components found'));
137
+ return lines.join('\n');
138
+ }
139
+
140
+ const { connected, byName, safeNames } = buildRoleDiagramContext(data, seeds, EVENTS_DEPTH, EVENTS_NODE_LIMIT);
141
+ const edges = new Set();
142
+ const eventNodeIds = [];
143
+ const eventSeedNames = new Set(seeds.map((seed) => seed.name));
144
+
145
+ lines.push(' subgraph Channels["Event channels / queues"]');
146
+ for (const component of connected) {
147
+ const safe = safeNames.get(component);
148
+ if (!safe) continue;
149
+ const isEventSource = eventSeedNames.has(component.name);
150
+ if (isEventSource) {
151
+ eventNodeIds.push(safe);
152
+ lines.push(` ${safe}{{"${escapeMermaid(component.originalName)}"}}`);
153
+ } else {
154
+ lines.push(` ${safe}["${escapeMermaid(component.originalName)}"]`);
155
+ }
156
+ }
157
+ lines.push(' end');
158
+
159
+ appendDependencyEdges(
160
+ lines,
161
+ connected,
162
+ byName,
163
+ safeNames,
164
+ edges,
165
+ (component, dep) => {
166
+ const from = safeNames.get(component);
167
+ const to = safeNames.get(dep);
168
+ if (!from || !to) return '';
169
+ const label = eventSeedNames.has(component.name) ? 'emit' : 'consume';
170
+ return `${from} -->|${label}| ${to}`;
171
+ }
172
+ );
173
+
174
+ emitClassStyle(lines, 'eventNode', '#db2777', '#fff', eventNodeIds);
175
+ return lines.join('\n');
176
+ }
177
+
178
+ module.exports = {
179
+ generateDatabase,
180
+ generateUserInteractions,
181
+ generateEvents,
182
+ };
@@ -0,0 +1,129 @@
1
+ const {
2
+ mapSafeNames,
3
+ appendClassAssignment,
4
+ } = require('./analysis-generation-utils');
5
+
6
+ /**
7
+ * Convert a list of components into their corresponding safe node identifiers.
8
+ *
9
+ * @param {Array} components - Array of component keys to map; non-array inputs are treated as empty.
10
+ * @param {Map} safeNames - Map from component key to safe node identifier.
11
+ * @returns {Array<string>} An array of safe node IDs corresponding to entries in `components`; entries without a mapping are omitted.
12
+ */
13
+ function safeNodeIds(components, safeNames) {
14
+ if (!Array.isArray(components)) return [];
15
+ return components.map((component) => safeNames.get(component)).filter(Boolean);
16
+ }
17
+
18
+ /**
19
+ * Emit a Mermaid class definition and assign that class to the given nodes.
20
+ * @param {string[]} lines - Mutable array of diagram lines to append to.
21
+ * @param {string} className - Identifier for the class to define and assign.
22
+ * @param {string} fill - Fill colour for the class (e.g. `#ffffff` or `transparent`).
23
+ * @param {string} color - Text/stroke colour for the class.
24
+ * @param {string[]|undefined} nodeIds - Optional list of safe node identifiers to assign to the class.
25
+ */
26
+ function emitClassStyle(lines, className, fill, color, nodeIds) {
27
+ lines.push(` classDef ${className} fill:${fill},color:${color}`);
28
+ appendClassAssignment(lines, nodeIds, className);
29
+ }
30
+
31
+ /**
32
+ * Emit a Mermaid class definition for role-specific styling and assign that class to the given nodes.
33
+ * @param {string[]} lines - Mutable array of diagram lines to append to.
34
+ * @param {string} className - Identifier for the class to define and assign.
35
+ * @param {string} roleKey - Role key to look up in the palette.
36
+ * @param {string[]|undefined} nodeIds - Optional list of safe node identifiers to assign to the class.
37
+ * @param {Object} palette - Object mapping role keys to {fill, color} objects; falls back to 'general' if roleKey is not found.
38
+ */
39
+ function emitRoleClassStyle(lines, className, roleKey, nodeIds, palette) {
40
+ const style = (palette && palette[roleKey]) || (palette && palette.general) || { fill: '#555', color: '#fff' };
41
+ lines.push(` classDef ${className} fill:${style.fill},color:${style.color}`);
42
+ appendClassAssignment(lines, nodeIds, className);
43
+ }
44
+
45
+ /**
46
+ * Appends rendered seed nodes to the output lines and, optionally, records their safe IDs and emits unique ingress edges from a specified source.
47
+ * @param {string[]} lines - Array of output lines to append to.
48
+ * @param {Array} seeds - Seed identifiers to process.
49
+ * @param {Map} safeNames - Map from seed identifier to its safe node id.
50
+ * @param {Object} [options] - Optional behaviours.
51
+ * @param {string[]} [options.nodeIds] - Array that will receive safe node ids for processed seeds.
52
+ * @param {Function} [options.renderNode] - Function(seed, safeId) that returns a node markup string to append to lines.
53
+ * @param {string} [options.ingressFrom] - Source node id for ingress edges; when falsy no ingress edges are emitted.
54
+ * @param {Set} [options.edges] - Set used to deduplicate emitted ingress edges; a key `${ingressFrom}->${safe}` is added for each emitted edge.
55
+ */
56
+ function emitSeedNodesWithIngress(lines, seeds, safeNames, options = {}) {
57
+ const {
58
+ nodeIds,
59
+ renderNode,
60
+ ingressFrom,
61
+ edges,
62
+ } = options;
63
+
64
+ if (!Array.isArray(seeds)) return;
65
+ for (const seed of seeds) {
66
+ const safe = safeNames.get(seed);
67
+ if (!safe) continue;
68
+
69
+ if (Array.isArray(nodeIds)) nodeIds.push(safe);
70
+ if (typeof renderNode === 'function') {
71
+ lines.push(` ${renderNode(seed, safe)}`);
72
+ }
73
+
74
+ if (!ingressFrom || !(edges instanceof Set)) continue;
75
+ const key = `${ingressFrom}->${safe}`;
76
+ if (edges.has(key)) continue;
77
+ edges.add(key);
78
+ lines.push(` ${ingressFrom} --> ${safe}`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Emit a Mermaid `subgraph` block into `lines` for the provided components.
84
+ *
85
+ * Adds a subgraph header with the given `id` and `title`, emits a line for each
86
+ * component that has a corresponding safe name, and closes the subgraph.
87
+ *
88
+ * @param {string[]} lines - Array to which Mermaid lines are appended.
89
+ * @param {string|number} id - Identifier for the subgraph.
90
+ * @param {string} title - Display title for the subgraph.
91
+ * @param {Array} components - List of components to include in the subgraph.
92
+ * @param {function(any, string): string} renderNode - Function that returns the line for a component given the original component and its safe name.
93
+ * @param {Map<any,string>} [safeNames] - Optional map of component -> safe node id; if omitted, safe names are derived from `components`.
94
+ * @returns {boolean} `true` if a subgraph was emitted, `false` otherwise.
95
+ */
96
+ function emitSubgraph(lines, id, title, components, renderNode, safeNames) {
97
+ if (!Array.isArray(components) || components.length === 0) return false;
98
+
99
+ const names = safeNames || mapSafeNames(components);
100
+ lines.push(` subgraph ${id}["${title}"]`);
101
+ for (const component of components) {
102
+ const safe = names.get(component);
103
+ if (!safe) continue;
104
+ lines.push(` ${renderNode(component, safe)}`);
105
+ }
106
+ lines.push(' end');
107
+ return true;
108
+ }
109
+
110
+ function emitSubgraphSpecs(lines, specs, safeNames) {
111
+ if (!Array.isArray(specs)) return [];
112
+
113
+ const emitted = [];
114
+ for (const spec of specs) {
115
+ if (!spec) continue;
116
+ const didEmit = emitSubgraph(lines, spec.id, spec.title, spec.components, spec.renderNode, safeNames);
117
+ if (didEmit) emitted.push(spec);
118
+ }
119
+ return emitted;
120
+ }
121
+
122
+ module.exports = {
123
+ safeNodeIds,
124
+ emitClassStyle,
125
+ emitRoleClassStyle,
126
+ emitSeedNodesWithIngress,
127
+ emitSubgraph,
128
+ emitSubgraphSpecs,
129
+ };
@@ -0,0 +1,129 @@
1
+ const {
2
+ escapeMermaid,
3
+ sanitize,
4
+ componentsByRole,
5
+ mergeRoleComponents,
6
+ buildRoleDiagramContext,
7
+ appendDependencyEdges,
8
+ } = require('./analysis-generation-utils');
9
+ const { collectExternalImports } = require('./analysis-generation-role-tags');
10
+ const { flowNote, noteNode } = require('./analysis-generation-diagrams-empty');
11
+ const {
12
+ emitClassStyle,
13
+ emitSeedNodesWithIngress,
14
+ } = require('./analysis-generation-diagrams-role-helpers');
15
+
16
+ /**
17
+ * Build a Mermaid flowchart showing authentication-related components and their dependencies.
18
+ *
19
+ * @param {Object} data - Analysis data containing a `components` array; each component may include role, imports and identifying fields used to render nodes.
20
+ * @returns {string} The Mermaid `flowchart TD` source representing authentication components, their ingress boundary, dependency edges and external provider nodes; when input is missing or contains no components this returns a small note diagram indicating no data or no authentication components.
21
+ */
22
+ function generateAuth(data) {
23
+ if (!data || !Array.isArray(data.components)) {
24
+ return flowNote('No data available');
25
+ }
26
+
27
+ const lines = ['flowchart TD'];
28
+ const seeds = componentsByRole(data.components, 'auth');
29
+ if (seeds.length === 0) {
30
+ lines.push(noteNode('No authentication components found'));
31
+ return lines.join('\n');
32
+ }
33
+
34
+ const { connected, byName, safeNames } = buildRoleDiagramContext(data, seeds, 2, 24);
35
+ const edges = new Set();
36
+ const authNodeIds = [];
37
+
38
+ lines.push(' Request["Authentication request"]');
39
+ lines.push(' Boundary{"Auth Boundary"}');
40
+ lines.push(' Request --> Boundary');
41
+
42
+ emitSeedNodesWithIngress(lines, seeds, safeNames, {
43
+ nodeIds: authNodeIds,
44
+ renderNode: (seed, safe) => `${safe}["${escapeMermaid(seed.originalName)}"]`,
45
+ ingressFrom: 'Boundary',
46
+ edges,
47
+ });
48
+
49
+ appendDependencyEdges(lines, connected, byName, safeNames, edges);
50
+
51
+ const providerNodeByPackage = new Map();
52
+ const providerEdges = new Set();
53
+ const externalImportsBySeed = new Map();
54
+ for (const seed of seeds) {
55
+ const externalImports = collectExternalImports(seed.imports || []);
56
+ externalImportsBySeed.set(seed, externalImports);
57
+ for (const pkg of externalImports) {
58
+ if (!providerNodeByPackage.has(pkg)) {
59
+ providerNodeByPackage.set(pkg, `ext_${sanitize(pkg)}`);
60
+ }
61
+ }
62
+ }
63
+ for (const [provider, providerNode] of providerNodeByPackage) {
64
+ lines.push(` ${providerNode}[("${escapeMermaid(provider)}")]`);
65
+ }
66
+ for (const seed of seeds) {
67
+ const seedNode = safeNames.get(seed);
68
+ if (!seedNode) continue;
69
+ for (const pkg of externalImportsBySeed.get(seed) || []) {
70
+ const providerNode = providerNodeByPackage.get(pkg);
71
+ if (!providerNode) continue;
72
+ const edgeKey = `${seedNode}->${providerNode}`;
73
+ if (providerEdges.has(edgeKey)) continue;
74
+ providerEdges.add(edgeKey);
75
+ lines.push(` ${seedNode} --> ${providerNode}`);
76
+ }
77
+ }
78
+
79
+ emitClassStyle(lines, 'authNode', '#7c3aed', '#fff', authNodeIds);
80
+ return lines.join('\n');
81
+ }
82
+
83
+ /**
84
+ * Generate a Mermaid flowchart showing security-related components and their dependencies.
85
+ *
86
+ * Builds a `flowchart TD` diagram with an "Untrusted input" ingress node, nodes for components
87
+ * with roles `security`, `auth` and `integrations`, dependency edges between them, and class
88
+ * styling for security nodes.
89
+ *
90
+ * @param {Object} data - Analysis input containing a `components` array of component objects.
91
+ * @returns {string} The Mermaid `flowchart TD` source. If `data` is missing or malformed, the
92
+ * returned diagram contains a "No data available" note; if no matching components are found,
93
+ * the diagram contains a "No security-focused components found" note.
94
+ */
95
+ function generateSecurity(data) {
96
+ if (!data || !Array.isArray(data.components)) {
97
+ return flowNote('No data available');
98
+ }
99
+
100
+ const lines = ['flowchart TD'];
101
+ const seeds = mergeRoleComponents(data.components, ['security', 'auth', 'integrations']);
102
+
103
+ if (seeds.length === 0) {
104
+ lines.push(noteNode('No security-focused components found'));
105
+ return lines.join('\n');
106
+ }
107
+
108
+ const { connected, byName, safeNames } = buildRoleDiagramContext(data, seeds, 2, 40);
109
+ const edges = new Set();
110
+ const securityNodeIds = [];
111
+
112
+ lines.push(' Untrusted["Untrusted input"]');
113
+ emitSeedNodesWithIngress(lines, seeds, safeNames, {
114
+ nodeIds: securityNodeIds,
115
+ renderNode: (seed, safe) => `${safe}["${escapeMermaid(seed.originalName)}"]`,
116
+ ingressFrom: 'Untrusted',
117
+ edges,
118
+ });
119
+
120
+ appendDependencyEdges(lines, connected, byName, safeNames, edges);
121
+
122
+ emitClassStyle(lines, 'securityNode', '#dc2626', '#fff', securityNodeIds);
123
+ return lines.join('\n');
124
+ }
125
+
126
+ module.exports = {
127
+ generateAuth,
128
+ generateSecurity,
129
+ };
@@ -0,0 +1,25 @@
1
+ const {
2
+ generateDatabase,
3
+ generateUserInteractions,
4
+ generateEvents,
5
+ } = require('./analysis-generation-diagrams-role-data');
6
+ const {
7
+ generateAuth,
8
+ generateSecurity,
9
+ } = require('./analysis-generation-diagrams-role-security');
10
+ const {
11
+ generateAgent,
12
+ generateC4Context,
13
+ generateRag,
14
+ } = require('./analysis-generation-diagrams-role-ai');
15
+
16
+ module.exports = {
17
+ generateDatabase,
18
+ generateUserInteractions,
19
+ generateEvents,
20
+ generateAuth,
21
+ generateSecurity,
22
+ generateAgent,
23
+ generateC4Context,
24
+ generateRag,
25
+ };