@brainwav/diagram 1.0.8 → 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 +178 -1761
  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,142 @@
1
+ const path = require('path');
2
+ const {
3
+ escapeMermaid,
4
+ mapSafeNames,
5
+ byNameIndex,
6
+ componentsByRole,
7
+ } = require('./analysis-generation-utils');
8
+ const { limitItems } = require('./analysis-generation-diagrams-limit');
9
+ const { sequenceNote } = require('./analysis-generation-diagrams-empty');
10
+
11
+ const ROLE_VERB_PRIORITY = [
12
+ ['auth', 'authenticates via'],
13
+ ['events', 'emits to'],
14
+ ['llm', 'calls LLM'],
15
+ ['tool', 'invokes tool'],
16
+ ];
17
+
18
+ /**
19
+ * Selects the preferred sequence-message verb for a dependency based on its role tags.
20
+ * @param {Array<string>} roleTags - Array of role tag identifiers; if not an array it is treated as empty.
21
+ * @returns {string} The chosen verb (for example `reads from`, `authenticates via`, `emits to`); returns `"calls"` if no role matches.
22
+ */
23
+ function resolveSequenceVerb(roleTags) {
24
+ const tags = Array.isArray(roleTags) ? roleTags : [];
25
+ if (tags.includes('database')) {
26
+ if (tags.includes('write') || tags.includes('writes')) return 'writes to';
27
+ return 'queries';
28
+ }
29
+ for (const [role, verb] of ROLE_VERB_PRIORITY) {
30
+ if (tags.includes(role)) return verb;
31
+ }
32
+ return 'calls';
33
+ }
34
+
35
+ /**
36
+ * Generate a Mermaid sequence diagram for the provided components and their dependencies.
37
+ *
38
+ * @param {Object} data - Input model containing components and optional entry points.
39
+ * @param {Array<Object>} data.components - Array of component objects. Each component is expected to include at least:
40
+ * - {string} name - Internal identifier.
41
+ * - {string} originalName - Human-visible name.
42
+ * - {string} [type] - Component type (e.g. 'service').
43
+ * - {Array<string>} [roleTags] - Role tags (e.g. 'user', 'database').
44
+ * - {Array<string>} [dependencies] - Names of dependent components.
45
+ * @param {Array<string>} [data.entryPoints] - Optional list of entry point file paths; basenames are matched against component.originalName to seed the diagram.
46
+ * @returns {string} The Mermaid sequence diagram text starting with `sequenceDiagram`, or a short note string such as "No data available" or "No services detected".
47
+ */
48
+ function generateSequence(data) {
49
+ if (!data || !Array.isArray(data.components)) {
50
+ return sequenceNote('No data available');
51
+ }
52
+
53
+ const MAX_PARTICIPANTS = 8;
54
+ const byName = byNameIndex(data.components);
55
+ const participantLookup = new Map();
56
+
57
+ const entryNames = new Set(
58
+ (data.entryPoints || []).map((ep) => path.basename(ep, path.extname(ep)))
59
+ );
60
+ let roots = data.components.filter((component) => entryNames.has(component.originalName));
61
+
62
+ if (roots.length === 0) {
63
+ roots = limitItems([
64
+ ...componentsByRole(data.components, 'user'),
65
+ ...data.components.filter((component) => component.type === 'service'),
66
+ ], 2);
67
+ }
68
+
69
+ const visited = new Map();
70
+ const queue = [];
71
+ for (const root of roots) {
72
+ if (root && !visited.has(root.name)) {
73
+ visited.set(root.name, 0);
74
+ queue.push({ comp: root, depth: 0 });
75
+ }
76
+ }
77
+ let queueIndex = 0;
78
+ while (queueIndex < queue.length && visited.size < MAX_PARTICIPANTS) {
79
+ const { comp, depth } = queue[queueIndex++];
80
+ for (const depName of comp.dependencies || []) {
81
+ if (visited.has(depName)) continue;
82
+ if (visited.size >= MAX_PARTICIPANTS) break;
83
+ const dep = byName.get(depName);
84
+ if (!dep) continue;
85
+ visited.set(depName, depth + 1);
86
+ queue.push({ comp: dep, depth: depth + 1 });
87
+ }
88
+ }
89
+
90
+ const participants = [...visited.entries()]
91
+ .sort((a, b) => a[1] - b[1])
92
+ .map(([name]) => byName.get(name))
93
+ .filter(Boolean);
94
+
95
+ for (const participant of participants) {
96
+ participantLookup.set(participant.name, participant);
97
+ }
98
+
99
+ if (participants.length === 0) {
100
+ return sequenceNote('No services detected');
101
+ }
102
+
103
+ const safeMap = mapSafeNames(participants);
104
+ const lines = ['sequenceDiagram'];
105
+
106
+ const participantType = (component) => {
107
+ const tags = component.roleTags || [];
108
+ if (tags.includes('user')) return 'actor';
109
+ if (tags.includes('database') || tags.includes('memory')) return 'database';
110
+ return 'participant';
111
+ };
112
+
113
+ for (const participant of participants) {
114
+ const safe = safeMap.get(participant);
115
+ lines.push(` ${participantType(participant)} ${safe} as ${escapeMermaid(participant.originalName)}`);
116
+ }
117
+
118
+ lines.push('');
119
+
120
+ const emittedEdges = new Set();
121
+ for (const caller of participants) {
122
+ const callerSafe = safeMap.get(caller);
123
+ for (const depName of caller.dependencies || []) {
124
+ const callee = participantLookup.get(depName);
125
+ if (!callee) continue;
126
+ const calleeSafe = safeMap.get(callee);
127
+ const key = `${callerSafe}->${calleeSafe}`;
128
+ if (emittedEdges.has(key)) continue;
129
+ emittedEdges.add(key);
130
+ const verb = resolveSequenceVerb(callee.roleTags);
131
+ lines.push(` ${callerSafe}->>${calleeSafe}: ${verb}`);
132
+ lines.push(` ${calleeSafe}-->>${callerSafe}: response`);
133
+ }
134
+ }
135
+
136
+ return lines.join('\n');
137
+ }
138
+
139
+ module.exports = {
140
+ generateSequence,
141
+ resolveSequenceVerb,
142
+ };
@@ -0,0 +1,104 @@
1
+ const { escapeMermaid, mapSafeNames } = require('./analysis-generation-utils');
2
+ const { limitItems } = require('./analysis-generation-diagrams-limit');
3
+ const { classNote } = require('./analysis-generation-diagrams-empty');
4
+
5
+ /**
6
+ * Build a Mermaid `classDiagram` string from the provided data.
7
+ *
8
+ * Filters `data.components` to entries whose `type` is `'class'` or `'component'`, limits the list to 20 items,
9
+ * emits a class block for each selected component containing its `filePath`, and adds dependency edges for up to
10
+ * the first 3 dependencies per component only when the dependency name is present among the selected classes.
11
+ *
12
+ * @param {Object} data - Input container expected to have a `components` array of component objects.
13
+ * Each component should include at least `name`, `type` and `filePath`, and may include `dependencies` (array of names).
14
+ * @returns {string} A Mermaid `classDiagram` text. If `data` is missing or invalid returns the result of `classNote('No data available')`.
15
+ * If no classes are selected the diagram contains a `note "No classes found"`.
16
+ */
17
+ function generateClass(data) {
18
+ if (!data || !Array.isArray(data.components)) {
19
+ return classNote('No data available');
20
+ }
21
+
22
+ const lines = ['classDiagram'];
23
+ const MAX_CLASSES = 20;
24
+ const classes = limitItems(
25
+ data.components.filter((component) => component.type === 'class' || component.type === 'component'),
26
+ MAX_CLASSES,
27
+ 'Class diagram limited to {limit} classes'
28
+ );
29
+ const classNames = new Set(classes.map((component) => component.name));
30
+ const classByName = new Map(classes.map((component) => [component.name, component]));
31
+ const classIds = mapSafeNames(classes);
32
+
33
+ if (classes.length === 0) {
34
+ lines.push(' note "No classes found"');
35
+ return lines.join('\n');
36
+ }
37
+
38
+ for (const component of classes) {
39
+ const safeId = classIds.get(component);
40
+ if (!safeId) continue;
41
+ lines.push(` class ${safeId} {`);
42
+ lines.push(` +${escapeMermaid(component.filePath)}`);
43
+ lines.push(' }');
44
+ }
45
+
46
+ for (const component of classes) {
47
+ const deps = (component.dependencies || []).slice(0, 3);
48
+ for (const depName of deps) {
49
+ if (classNames.has(depName)) {
50
+ const from = classIds.get(component);
51
+ const to = classIds.get(classByName.get(depName));
52
+ if (!from || !to) continue;
53
+ lines.push(` ${from} --> ${to}`);
54
+ }
55
+ }
56
+ }
57
+
58
+ return lines.join('\n');
59
+ }
60
+
61
+ /**
62
+ * Build a Mermaid flowchart that chains components sequentially from Start to End.
63
+ *
64
+ * If `data` is missing or `data.components` is not an array, a minimal Start → End flow is returned.
65
+ * The component list is limited to 8 items; if the resulting list is empty the diagram connects Start directly to End.
66
+ * Each included component becomes a node labelled with its `originalName` and is connected in order, ending with an edge to End.
67
+ *
68
+ * @param {Object} data - Object containing a `components` array of component descriptors.
69
+ * @returns {string} A Mermaid `flowchart TD` diagram as a string.
70
+ */
71
+ function generateFlow(data) {
72
+ if (!data || !Array.isArray(data.components)) {
73
+ return 'flowchart TD\n Start(["Start"])\n End(["End"])\n Start --> End';
74
+ }
75
+
76
+ const lines = ['flowchart TD'];
77
+ lines.push(' Start(["Start"])');
78
+ const MAX_COMPONENTS = 8;
79
+ const comps = limitItems(data.components, MAX_COMPONENTS, 'Flow diagram limited to {limit} components');
80
+
81
+ if (comps.length === 0) {
82
+ lines.push(' End(["End"])');
83
+ lines.push(' Start --> End');
84
+ return lines.join('\n');
85
+ }
86
+
87
+ let prev = 'Start';
88
+ const nodeIds = mapSafeNames(comps);
89
+ for (const component of comps) {
90
+ const safeName = nodeIds.get(component);
91
+ if (!safeName) continue;
92
+ lines.push(` ${safeName}["${escapeMermaid(component.originalName)}"]`);
93
+ lines.push(` ${prev} --> ${safeName}`);
94
+ prev = safeName;
95
+ }
96
+ lines.push(' End(["End"])');
97
+ lines.push(` ${prev} --> End`);
98
+ return lines.join('\n');
99
+ }
100
+
101
+ module.exports = {
102
+ generateClass,
103
+ generateFlow,
104
+ };
@@ -0,0 +1,12 @@
1
+ const { generateArchitecture } = require('./analysis-generation-diagrams-core-architecture');
2
+ const { generateSequence } = require('./analysis-generation-diagrams-core-sequence');
3
+ const { generateDependency } = require('./analysis-generation-diagrams-core-dependency');
4
+ const { generateClass, generateFlow } = require('./analysis-generation-diagrams-core-shapes');
5
+
6
+ module.exports = {
7
+ generateArchitecture,
8
+ generateSequence,
9
+ generateDependency,
10
+ generateClass,
11
+ generateFlow,
12
+ };
@@ -0,0 +1,68 @@
1
+ const { escapeMermaid } = require('./analysis-generation-utils-core');
2
+
3
+ /**
4
+ * Create a Mermaid Note node string containing the given message.
5
+ * @param {string} message - The text to include inside the note node.
6
+ * @returns {string} The Mermaid fragment in the form ` Note["<message>"]`.
7
+ */
8
+ function noteNode(message) {
9
+ return ` Note["${escapeMermaid(message)}"]`;
10
+ }
11
+
12
+ /**
13
+ * Create a Mermaid `graph` fragment containing a single note node.
14
+ * @param {string} message - Text to place inside the note node.
15
+ * @param {string} [direction='TD'] - Graph layout direction (for example `'TD'` for top-down or `'LR'` for left-right).
16
+ * @returns {string} A Mermaid `graph` block containing the note node with the provided message.
17
+ */
18
+ function graphNote(message, direction = 'TD') {
19
+ return `graph ${direction}\n${noteNode(message)}`;
20
+ }
21
+
22
+ /**
23
+ * Construct a Mermaid flowchart snippet that contains a single note.
24
+ *
25
+ * @param {string} message - Text to place inside the note.
26
+ * @param {string} [direction='TD'] - Flow direction for the chart (commonly 'TD' for top-down, 'LR' for left-right, etc.).
27
+ * @returns {string} A Mermaid `flowchart` fragment starting with `flowchart <direction>` followed by a note node containing the provided message.
28
+ */
29
+ function flowNote(message, direction = 'TD') {
30
+ return `flowchart ${direction}\n${noteNode(message)}`;
31
+ }
32
+
33
+ /**
34
+ * Build a Mermaid sequenceDiagram fragment containing a note positioned over User and App.
35
+ * @param {string} message - Text to place inside the note.
36
+ * @returns {string} A `sequenceDiagram` snippet with a `Note over User,App` containing the provided message.
37
+ */
38
+ function sequenceNote(message) {
39
+ return `sequenceDiagram\n Note over User,App: ${escapeMermaid(message)}`;
40
+ }
41
+
42
+ /**
43
+ * Create a Mermaid classDiagram fragment containing a note with the provided message.
44
+ * @param {string} message - The note text to include inside the class diagram.
45
+ * @returns {string} The Mermaid `classDiagram` snippet that contains the note with the provided message.
46
+ */
47
+ function classNote(message) {
48
+ return `classDiagram\n note "${escapeMermaid(message)}"`;
49
+ }
50
+
51
+ /**
52
+ * Create a Mermaid `architecture-beta` snippet that attaches a service note to the `server`.
53
+ *
54
+ * @param {string} message - The note text to place inside the service note for the `server`.
55
+ * @returns {string} The Mermaid diagram fragment `architecture-beta\n service note(server)[<message>]`.
56
+ */
57
+ function architectureNote(message) {
58
+ return `architecture-beta\n service note(server)[${escapeMermaid(message)}]`;
59
+ }
60
+
61
+ module.exports = {
62
+ noteNode,
63
+ graphNote,
64
+ flowNote,
65
+ sequenceNote,
66
+ classNote,
67
+ architectureNote,
68
+ };
@@ -0,0 +1,59 @@
1
+ const { extractErdModel } = require('../schema/erd-extractor');
2
+ const { evaluateErdConfidence } = require('../schema/erd-confidence');
3
+ const { renderErdMermaid } = require('../schema/erd-model');
4
+
5
+ function commentLine(text) {
6
+ return ` %% ${String(text || '').replace(/\r?\n/g, ' ').trim()}`;
7
+ }
8
+
9
+ function insertErdComments(mermaid, comments) {
10
+ const lines = String(mermaid || 'erDiagram').split(/\r?\n/);
11
+ const [header, ...rest] = lines;
12
+ return [
13
+ header || 'erDiagram',
14
+ ...comments.filter(Boolean).map(commentLine),
15
+ ...rest,
16
+ ].join('\n');
17
+ }
18
+
19
+ function buildErdMetadata(extraction, confidence) {
20
+ return {
21
+ purpose: 'schema_entity_relationships',
22
+ consumers: ['human', 'agent', 'ci'],
23
+ source: 'schema_extraction',
24
+ extractionInvoked: Boolean(extraction?.extractionInvoked),
25
+ terminalClass: extraction?.terminalClass || 'unknown',
26
+ schemaSources: Array.isArray(extraction?.sourceFiles) ? extraction.sourceFiles : [],
27
+ sourcePrecedence: Array.isArray(extraction?.sourcePrecedence) ? extraction.sourcePrecedence : [],
28
+ compactEligible: !confidence.shouldFail,
29
+ confidence,
30
+ };
31
+ }
32
+
33
+ function generateErdArtifact(data) {
34
+ const extraction = extractErdModel({
35
+ rootPath: data?.rootPath,
36
+ ignore: Array.isArray(data?.exclude) ? data.exclude : [],
37
+ });
38
+ const confidence = evaluateErdConfidence(extraction.model);
39
+ const mermaid = renderErdMermaid(extraction.model, {
40
+ lowConfidenceMarker: confidence.markerRequired,
41
+ inferenceShare: confidence.counts?.inferenceShare,
42
+ });
43
+ const comments = [
44
+ `erd extraction: ${extraction.terminalClass}`,
45
+ extraction.sourceFiles.length > 0
46
+ ? `schema sources: ${extraction.sourceFiles.join(', ')}`
47
+ : 'schema sources: none',
48
+ ...((extraction.diagnostics || []).slice(0, 3)),
49
+ ];
50
+
51
+ return {
52
+ mermaid: insertErdComments(mermaid, comments),
53
+ metadata: buildErdMetadata(extraction, confidence),
54
+ };
55
+ }
56
+
57
+ module.exports = {
58
+ generateErdArtifact,
59
+ };
@@ -0,0 +1,27 @@
1
+ const chalk = require('chalk');
2
+
3
+ /**
4
+ * Limit an input list to the first `limit` elements, optionally emitting a formatted warning when items were truncated.
5
+ *
6
+ * @param {*} items - The value to be treated as an array; if not an array it is treated as an empty list.
7
+ * @param {number} limit - Maximum number of items to keep.
8
+ * @param {string|null} [warningTemplate=null] - Optional template for a warning message; if a string and the original array length exceeds `limit` the function logs this template with `{limit}` replaced by the limit.
9
+ * @returns {Array} The original items truncated to at most `limit` elements (or an empty array if `items` was not an array).
10
+ */
11
+ function limitItems(items, limit, warningTemplate = null) {
12
+ const list = Array.isArray(items) ? items : [];
13
+ const parsedLimit = Number(limit);
14
+ const safeLimit = Number.isFinite(parsedLimit) ? Math.max(0, Math.trunc(parsedLimit)) : list.length;
15
+ const capped = list.slice(0, safeLimit);
16
+
17
+ if (typeof warningTemplate === 'string' && list.length > safeLimit) {
18
+ const warning = warningTemplate.replace('{limit}', String(safeLimit));
19
+ console.warn(chalk.yellow(`⚠️ ${warning}`));
20
+ }
21
+
22
+ return capped;
23
+ }
24
+
25
+ module.exports = {
26
+ limitItems,
27
+ };
@@ -0,0 +1,103 @@
1
+ const {
2
+ escapeMermaid,
3
+ componentsByRole,
4
+ mergeRoleComponents,
5
+ mapSafeNames,
6
+ byNameIndex,
7
+ appendDependencyEdges,
8
+ } = require('./analysis-generation-utils');
9
+ const { flowNote, noteNode } = require('./analysis-generation-diagrams-empty');
10
+ const {
11
+ safeNodeIds,
12
+ emitRoleClassStyle,
13
+ emitSubgraphSpecs,
14
+ } = require('./analysis-generation-diagrams-role-helpers');
15
+
16
+ /**
17
+ * Build a Mermaid flowchart string visualising agents, tools, memories and LLMs as layered subgraphs with dependency edges and per-role styling.
18
+ *
19
+ * @param {Object} data - Input container.
20
+ * @param {Array} data.components - Array of component descriptors (roles like `agent`, `tool`, `memory`, `llm`, etc.) used to populate layers and edges.
21
+ * @returns {string} A Mermaid `flowchart TD` diagram string. If `data` is missing or contains no relevant components, the diagram contains a note indicating no data is available.
22
+ */
23
+ function generateAgent(data) {
24
+ if (!data || !Array.isArray(data.components)) {
25
+ return flowNote('No data available');
26
+ }
27
+
28
+ const lines = ['flowchart TD'];
29
+
30
+ const agents = componentsByRole(data.components, 'agent');
31
+ const tools = componentsByRole(data.components, 'tool');
32
+ const memories = componentsByRole(data.components, 'memory');
33
+ const llms = componentsByRole(data.components, 'llm');
34
+
35
+ const all = mergeRoleComponents(data.components, [
36
+ 'agent',
37
+ 'tool',
38
+ 'memory',
39
+ 'llm',
40
+ { role: 'user', limit: 2 },
41
+ ]);
42
+ if (all.length === 0) {
43
+ lines.push(noteNode('No agent/LLM components found — add agent, tool, memory, or llm patterns'));
44
+ return lines.join('\n');
45
+ }
46
+
47
+ const safeNames = mapSafeNames(all);
48
+ const byName = byNameIndex(all);
49
+
50
+ const layerSpecs = [
51
+ {
52
+ id: 'Orchestration',
53
+ title: '🎯 Orchestration Layer',
54
+ components: agents,
55
+ renderNode: (component, safe) => `${safe}["🤖 ${escapeMermaid(component.originalName)}"]`,
56
+ },
57
+ {
58
+ id: 'LLMLayer',
59
+ title: '🧠 LLM / Model Layer',
60
+ components: llms,
61
+ renderNode: (component, safe) => `${safe}["💡 ${escapeMermaid(component.originalName)}"]`,
62
+ },
63
+ {
64
+ id: 'ToolLayer',
65
+ title: '🔧 Tool Layer',
66
+ components: tools,
67
+ renderNode: (component, safe) => `${safe}["🔧 ${escapeMermaid(component.originalName)}"]`,
68
+ },
69
+ {
70
+ id: 'MemoryLayer',
71
+ title: '📚 Memory / Vector Layer',
72
+ components: memories,
73
+ renderNode: (component, safe) => `${safe}[("📚 ${escapeMermaid(component.originalName)}")]`,
74
+ },
75
+ ];
76
+ emitSubgraphSpecs(lines, layerSpecs, safeNames);
77
+
78
+ const edges = new Set();
79
+ appendDependencyEdges(lines, all, byName, safeNames, edges, (_caller, callee) => {
80
+ const tags = callee.roleTags || [];
81
+ if (tags.includes('tool')) return 'invokes';
82
+ if (tags.includes('memory')) return 'retrieves from';
83
+ if (tags.includes('llm')) return 'calls LLM';
84
+ if (tags.includes('agent')) return 'delegates to';
85
+ return 'uses';
86
+ });
87
+
88
+ const classSpecs = [
89
+ { className: 'agentNode', roleKey: 'agent', components: agents },
90
+ { className: 'llmNode', roleKey: 'llm', components: llms },
91
+ { className: 'toolNode', roleKey: 'tool', components: tools },
92
+ { className: 'memNode', roleKey: 'memory', components: memories },
93
+ ];
94
+ for (const spec of classSpecs) {
95
+ emitRoleClassStyle(lines, spec.className, spec.roleKey, safeNodeIds(spec.components, safeNames));
96
+ }
97
+
98
+ return lines.join('\n');
99
+ }
100
+
101
+ module.exports = {
102
+ generateAgent,
103
+ };