tlc-claude-code 1.2.29 → 1.4.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 (182) hide show
  1. package/dashboard/dist/components/AuditPane.d.ts +30 -0
  2. package/dashboard/dist/components/AuditPane.js +127 -0
  3. package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
  4. package/dashboard/dist/components/AuditPane.test.js +339 -0
  5. package/dashboard/dist/components/CompliancePane.d.ts +39 -0
  6. package/dashboard/dist/components/CompliancePane.js +96 -0
  7. package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
  8. package/dashboard/dist/components/CompliancePane.test.js +183 -0
  9. package/dashboard/dist/components/SSOPane.d.ts +36 -0
  10. package/dashboard/dist/components/SSOPane.js +71 -0
  11. package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
  12. package/dashboard/dist/components/SSOPane.test.js +155 -0
  13. package/dashboard/dist/components/UsagePane.d.ts +13 -0
  14. package/dashboard/dist/components/UsagePane.js +51 -0
  15. package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
  16. package/dashboard/dist/components/UsagePane.test.js +142 -0
  17. package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
  18. package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
  19. package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
  20. package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
  21. package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
  22. package/dashboard/dist/components/WorkspacePane.js +17 -0
  23. package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
  24. package/dashboard/dist/components/WorkspacePane.test.js +84 -0
  25. package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
  26. package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
  27. package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
  28. package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
  29. package/package.json +1 -1
  30. package/server/lib/access-control-doc.js +541 -0
  31. package/server/lib/access-control-doc.test.js +672 -0
  32. package/server/lib/adr-generator.js +423 -0
  33. package/server/lib/adr-generator.test.js +586 -0
  34. package/server/lib/agent-progress-monitor.js +223 -0
  35. package/server/lib/agent-progress-monitor.test.js +202 -0
  36. package/server/lib/architecture-command.js +450 -0
  37. package/server/lib/architecture-command.test.js +754 -0
  38. package/server/lib/ast-analyzer.js +324 -0
  39. package/server/lib/ast-analyzer.test.js +437 -0
  40. package/server/lib/audit-attribution.js +191 -0
  41. package/server/lib/audit-attribution.test.js +359 -0
  42. package/server/lib/audit-classifier.js +202 -0
  43. package/server/lib/audit-classifier.test.js +209 -0
  44. package/server/lib/audit-command.js +275 -0
  45. package/server/lib/audit-command.test.js +325 -0
  46. package/server/lib/audit-exporter.js +380 -0
  47. package/server/lib/audit-exporter.test.js +464 -0
  48. package/server/lib/audit-logger.js +236 -0
  49. package/server/lib/audit-logger.test.js +364 -0
  50. package/server/lib/audit-query.js +257 -0
  51. package/server/lib/audit-query.test.js +352 -0
  52. package/server/lib/audit-storage.js +269 -0
  53. package/server/lib/audit-storage.test.js +272 -0
  54. package/server/lib/auth-system.test.js +4 -1
  55. package/server/lib/boundary-detector.js +427 -0
  56. package/server/lib/boundary-detector.test.js +320 -0
  57. package/server/lib/budget-alerts.js +138 -0
  58. package/server/lib/budget-alerts.test.js +235 -0
  59. package/server/lib/bulk-repo-init.js +342 -0
  60. package/server/lib/bulk-repo-init.test.js +388 -0
  61. package/server/lib/candidates-tracker.js +210 -0
  62. package/server/lib/candidates-tracker.test.js +300 -0
  63. package/server/lib/checkpoint-manager.js +251 -0
  64. package/server/lib/checkpoint-manager.test.js +474 -0
  65. package/server/lib/circular-detector.js +337 -0
  66. package/server/lib/circular-detector.test.js +353 -0
  67. package/server/lib/cohesion-analyzer.js +310 -0
  68. package/server/lib/cohesion-analyzer.test.js +447 -0
  69. package/server/lib/compliance-checklist.js +866 -0
  70. package/server/lib/compliance-checklist.test.js +476 -0
  71. package/server/lib/compliance-command.js +616 -0
  72. package/server/lib/compliance-command.test.js +551 -0
  73. package/server/lib/compliance-reporter.js +692 -0
  74. package/server/lib/compliance-reporter.test.js +707 -0
  75. package/server/lib/contract-testing.js +625 -0
  76. package/server/lib/contract-testing.test.js +342 -0
  77. package/server/lib/conversion-planner.js +469 -0
  78. package/server/lib/conversion-planner.test.js +361 -0
  79. package/server/lib/convert-command.js +351 -0
  80. package/server/lib/convert-command.test.js +608 -0
  81. package/server/lib/coupling-calculator.js +189 -0
  82. package/server/lib/coupling-calculator.test.js +509 -0
  83. package/server/lib/data-flow-doc.js +665 -0
  84. package/server/lib/data-flow-doc.test.js +659 -0
  85. package/server/lib/dependency-graph.js +367 -0
  86. package/server/lib/dependency-graph.test.js +516 -0
  87. package/server/lib/duplication-detector.js +349 -0
  88. package/server/lib/duplication-detector.test.js +401 -0
  89. package/server/lib/ephemeral-storage.js +249 -0
  90. package/server/lib/ephemeral-storage.test.js +254 -0
  91. package/server/lib/evidence-collector.js +627 -0
  92. package/server/lib/evidence-collector.test.js +901 -0
  93. package/server/lib/example-service.js +616 -0
  94. package/server/lib/example-service.test.js +397 -0
  95. package/server/lib/flow-diagram-generator.js +474 -0
  96. package/server/lib/flow-diagram-generator.test.js +446 -0
  97. package/server/lib/idp-manager.js +626 -0
  98. package/server/lib/idp-manager.test.js +587 -0
  99. package/server/lib/impact-scorer.js +184 -0
  100. package/server/lib/impact-scorer.test.js +211 -0
  101. package/server/lib/memory-exclusion.js +326 -0
  102. package/server/lib/memory-exclusion.test.js +241 -0
  103. package/server/lib/mermaid-generator.js +358 -0
  104. package/server/lib/mermaid-generator.test.js +301 -0
  105. package/server/lib/messaging-patterns.js +750 -0
  106. package/server/lib/messaging-patterns.test.js +213 -0
  107. package/server/lib/mfa-handler.js +452 -0
  108. package/server/lib/mfa-handler.test.js +490 -0
  109. package/server/lib/microservice-template.js +386 -0
  110. package/server/lib/microservice-template.test.js +325 -0
  111. package/server/lib/new-project-microservice.js +450 -0
  112. package/server/lib/new-project-microservice.test.js +600 -0
  113. package/server/lib/oauth-flow.js +375 -0
  114. package/server/lib/oauth-flow.test.js +487 -0
  115. package/server/lib/oauth-registry.js +190 -0
  116. package/server/lib/oauth-registry.test.js +306 -0
  117. package/server/lib/readme-generator.js +490 -0
  118. package/server/lib/readme-generator.test.js +493 -0
  119. package/server/lib/refactor-command.js +326 -0
  120. package/server/lib/refactor-command.test.js +528 -0
  121. package/server/lib/refactor-executor.js +254 -0
  122. package/server/lib/refactor-executor.test.js +305 -0
  123. package/server/lib/refactor-observer.js +292 -0
  124. package/server/lib/refactor-observer.test.js +422 -0
  125. package/server/lib/refactor-progress.js +193 -0
  126. package/server/lib/refactor-progress.test.js +251 -0
  127. package/server/lib/refactor-reporter.js +237 -0
  128. package/server/lib/refactor-reporter.test.js +247 -0
  129. package/server/lib/repo-dependency-tracker.js +261 -0
  130. package/server/lib/repo-dependency-tracker.test.js +350 -0
  131. package/server/lib/retention-policy.js +281 -0
  132. package/server/lib/retention-policy.test.js +486 -0
  133. package/server/lib/role-mapper.js +236 -0
  134. package/server/lib/role-mapper.test.js +395 -0
  135. package/server/lib/saml-provider.js +765 -0
  136. package/server/lib/saml-provider.test.js +643 -0
  137. package/server/lib/security-policy-generator.js +682 -0
  138. package/server/lib/security-policy-generator.test.js +544 -0
  139. package/server/lib/semantic-analyzer.js +198 -0
  140. package/server/lib/semantic-analyzer.test.js +474 -0
  141. package/server/lib/sensitive-detector.js +112 -0
  142. package/server/lib/sensitive-detector.test.js +209 -0
  143. package/server/lib/service-interaction-diagram.js +700 -0
  144. package/server/lib/service-interaction-diagram.test.js +638 -0
  145. package/server/lib/service-scaffold.js +486 -0
  146. package/server/lib/service-scaffold.test.js +373 -0
  147. package/server/lib/service-summary.js +553 -0
  148. package/server/lib/service-summary.test.js +619 -0
  149. package/server/lib/session-purge.js +460 -0
  150. package/server/lib/session-purge.test.js +312 -0
  151. package/server/lib/shared-kernel.js +578 -0
  152. package/server/lib/shared-kernel.test.js +255 -0
  153. package/server/lib/sso-command.js +544 -0
  154. package/server/lib/sso-command.test.js +552 -0
  155. package/server/lib/sso-session.js +492 -0
  156. package/server/lib/sso-session.test.js +670 -0
  157. package/server/lib/traefik-config.js +282 -0
  158. package/server/lib/traefik-config.test.js +312 -0
  159. package/server/lib/usage-command.js +218 -0
  160. package/server/lib/usage-command.test.js +391 -0
  161. package/server/lib/usage-formatter.js +192 -0
  162. package/server/lib/usage-formatter.test.js +267 -0
  163. package/server/lib/usage-history.js +122 -0
  164. package/server/lib/usage-history.test.js +206 -0
  165. package/server/lib/workspace-command.js +249 -0
  166. package/server/lib/workspace-command.test.js +264 -0
  167. package/server/lib/workspace-config.js +270 -0
  168. package/server/lib/workspace-config.test.js +312 -0
  169. package/server/lib/workspace-docs-command.js +547 -0
  170. package/server/lib/workspace-docs-command.test.js +692 -0
  171. package/server/lib/workspace-memory.js +451 -0
  172. package/server/lib/workspace-memory.test.js +403 -0
  173. package/server/lib/workspace-scanner.js +452 -0
  174. package/server/lib/workspace-scanner.test.js +677 -0
  175. package/server/lib/workspace-test-runner.js +315 -0
  176. package/server/lib/workspace-test-runner.test.js +294 -0
  177. package/server/lib/zero-retention-command.js +439 -0
  178. package/server/lib/zero-retention-command.test.js +448 -0
  179. package/server/lib/zero-retention.js +322 -0
  180. package/server/lib/zero-retention.test.js +258 -0
  181. package/server/package-lock.json +14 -0
  182. package/server/package.json +1 -0
@@ -0,0 +1,700 @@
1
+ /**
2
+ * Service Interaction Diagram Generator
3
+ * Generate detailed service interaction diagrams (sequence, component, deployment, ER)
4
+ */
5
+
6
+ /**
7
+ * Simple YAML parser for docker-compose files
8
+ * Handles the subset of YAML needed for docker-compose parsing
9
+ * @param {string} yamlContent - YAML content to parse
10
+ * @returns {Object} Parsed object
11
+ */
12
+ function parseSimpleYaml(yamlContent) {
13
+ if (!yamlContent || yamlContent.trim() === '') {
14
+ return null;
15
+ }
16
+
17
+ const result = {};
18
+ const lines = yamlContent.split('\n');
19
+
20
+ // Stack stores the context at each indentation level
21
+ // { container, key, indent } where container[key] is the current object/array
22
+ const stack = [{ container: { root: result }, key: 'root', indent: -2 }];
23
+
24
+ for (let i = 0; i < lines.length; i++) {
25
+ const line = lines[i];
26
+ const trimmed = line.trim();
27
+
28
+ // Skip empty lines and comments
29
+ if (!trimmed || trimmed.startsWith('#')) {
30
+ continue;
31
+ }
32
+
33
+ // Calculate indentation
34
+ const indent = line.search(/\S/);
35
+ if (indent === -1) continue;
36
+
37
+ // Pop stack until we find the parent at the right level
38
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
39
+ stack.pop();
40
+ }
41
+
42
+ const current = stack[stack.length - 1];
43
+ const parent = current.container[current.key];
44
+
45
+ // Handle array items (- value)
46
+ if (trimmed.startsWith('- ')) {
47
+ const value = trimmed.slice(2).trim();
48
+
49
+ // Convert parent to array if it's currently an empty object
50
+ if (typeof parent === 'object' && !Array.isArray(parent) && Object.keys(parent).length === 0) {
51
+ current.container[current.key] = [];
52
+ }
53
+
54
+ const arr = current.container[current.key];
55
+ if (Array.isArray(arr)) {
56
+ // Handle "key: value" inside array item (but not port mappings like "80:80")
57
+ if (value.includes(':') && !value.match(/^["']?[\d.]+:[\d.]+["']?$/)) {
58
+ const colonIdx = value.indexOf(':');
59
+ const objKey = value.slice(0, colonIdx).trim();
60
+ const objVal = value.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
61
+ const obj = {};
62
+ obj[objKey] = objVal;
63
+ arr.push(obj);
64
+ } else {
65
+ arr.push(value.replace(/^["']|["']$/g, ''));
66
+ }
67
+ }
68
+ continue;
69
+ }
70
+
71
+ // Handle key-value pairs
72
+ const colonIndex = trimmed.indexOf(':');
73
+ if (colonIndex === -1) continue;
74
+
75
+ const key = trimmed.slice(0, colonIndex).trim();
76
+ let value = trimmed.slice(colonIndex + 1).trim();
77
+
78
+ if (value === '' || value === '|' || value === '>') {
79
+ // Nested object (or array, we'll convert if we see '-')
80
+ parent[key] = {};
81
+ stack.push({ container: parent, key: key, indent: indent });
82
+ } else if (value.startsWith('[') && value.endsWith(']')) {
83
+ // Inline array
84
+ const arrayContent = value.slice(1, -1);
85
+ if (arrayContent.trim() === '') {
86
+ parent[key] = [];
87
+ } else {
88
+ parent[key] = arrayContent.split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
89
+ }
90
+ } else {
91
+ // Simple value
92
+ value = value.replace(/^["']|["']$/g, '');
93
+ if (value === 'true') value = true;
94
+ else if (value === 'false') value = false;
95
+ else if (/^\d+$/.test(value)) value = parseInt(value, 10);
96
+ parent[key] = value;
97
+ }
98
+ }
99
+
100
+ return result;
101
+ }
102
+
103
+ /**
104
+ * ServiceInteractionDiagram class for generating various Mermaid diagrams
105
+ */
106
+ class ServiceInteractionDiagram {
107
+ /**
108
+ * Create a ServiceInteractionDiagram instance
109
+ * @param {Object} options - Configuration options
110
+ */
111
+ constructor(options = {}) {
112
+ this.options = {
113
+ direction: options.direction || 'TB',
114
+ groupByLayer: options.groupByLayer || false,
115
+ showPorts: options.showPorts !== false,
116
+ ...options,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Generate a sequence diagram from API flow
122
+ * @param {Object} apiFlow - API flow definition
123
+ * @returns {string} Mermaid sequence diagram
124
+ */
125
+ generateSequenceDiagram(apiFlow) {
126
+ if (!apiFlow.steps || apiFlow.steps.length === 0) {
127
+ return 'sequenceDiagram\n Note over Client: No interactions defined';
128
+ }
129
+
130
+ let mermaid = 'sequenceDiagram\n';
131
+
132
+ // Add title if provided
133
+ if (apiFlow.name) {
134
+ mermaid += ` title ${this.escapeLabel(apiFlow.name)}\n`;
135
+ }
136
+
137
+ // Collect all participants
138
+ const participants = new Set();
139
+ for (const step of apiFlow.steps) {
140
+ participants.add(step.from);
141
+ participants.add(step.to);
142
+ }
143
+
144
+ // Declare participants
145
+ for (const participant of participants) {
146
+ const sanitized = this.escapeLabel(participant);
147
+ mermaid += ` participant ${this.sanitizeId(participant)} as ${sanitized}\n`;
148
+ }
149
+
150
+ mermaid += '\n';
151
+
152
+ // Generate steps
153
+ for (const step of apiFlow.steps) {
154
+ const fromId = this.sanitizeId(step.from);
155
+ const toId = this.sanitizeId(step.to);
156
+ const action = this.escapeLabel(step.action);
157
+
158
+ let arrow;
159
+ switch (step.type) {
160
+ case 'response':
161
+ arrow = '-->>';
162
+ break;
163
+ case 'async':
164
+ case 'event':
165
+ arrow = '-)'
166
+ break;
167
+ case 'query':
168
+ case 'call':
169
+ case 'request':
170
+ default:
171
+ arrow = '->>';
172
+ break;
173
+ }
174
+
175
+ mermaid += ` ${fromId}${arrow}${toId}: ${action}\n`;
176
+
177
+ // Add note if provided
178
+ if (step.note) {
179
+ mermaid += ` Note right of ${toId}: ${this.escapeLabel(step.note)}\n`;
180
+ }
181
+ }
182
+
183
+ return mermaid;
184
+ }
185
+
186
+ /**
187
+ * Generate a component diagram showing service boundaries
188
+ * @param {Object} services - Services configuration
189
+ * @returns {string} Mermaid flowchart diagram
190
+ */
191
+ generateComponentDiagram(services) {
192
+ let mermaid = `flowchart ${this.options.direction}\n`;
193
+
194
+ if (!services.components || services.components.length === 0) {
195
+ mermaid += ' empty[No components defined]\n';
196
+ return mermaid;
197
+ }
198
+
199
+ // Add styles
200
+ mermaid += this.generateComponentStyles();
201
+
202
+ // Group by layer if enabled
203
+ if (this.options.groupByLayer) {
204
+ const layers = this.groupByLayer(services.components);
205
+ for (const [layerName, components] of Object.entries(layers)) {
206
+ mermaid += `\n subgraph ${layerName}["${this.capitalizeFirst(layerName)} Layer"]\n`;
207
+ for (const component of components) {
208
+ mermaid += this.generateComponentNode(component, ' ');
209
+ }
210
+ mermaid += ' end\n';
211
+ }
212
+ } else {
213
+ // Flat list of components
214
+ mermaid += '\n %% Components\n';
215
+ for (const component of services.components) {
216
+ mermaid += this.generateComponentNode(component, ' ');
217
+ }
218
+ }
219
+
220
+ // Generate connections
221
+ if (services.connections && services.connections.length > 0) {
222
+ mermaid += '\n %% Connections\n';
223
+ for (const conn of services.connections) {
224
+ const fromId = this.sanitizeId(conn.from);
225
+ const toId = this.sanitizeId(conn.to);
226
+ const label = conn.protocol || conn.type || '';
227
+ mermaid += ` ${fromId} -->|${label}| ${toId}\n`;
228
+ }
229
+ }
230
+
231
+ return mermaid;
232
+ }
233
+
234
+ /**
235
+ * Generate a component node with appropriate shape
236
+ * @param {Object} component - Component definition
237
+ * @param {string} indent - Indentation
238
+ * @returns {string} Mermaid node definition
239
+ */
240
+ generateComponentNode(component, indent = ' ') {
241
+ const id = this.sanitizeId(component.name);
242
+ const label = this.sanitizeLabel(component.name);
243
+ let shape;
244
+
245
+ switch (component.type) {
246
+ case 'gateway':
247
+ shape = `{{${label}}}`;
248
+ break;
249
+ case 'database':
250
+ shape = `[(${label})]`;
251
+ break;
252
+ case 'cache':
253
+ shape = `([${label}])`;
254
+ break;
255
+ case 'queue':
256
+ shape = `>/${label}/]`;
257
+ break;
258
+ case 'service':
259
+ default:
260
+ shape = `[${label}]`;
261
+ break;
262
+ }
263
+
264
+ let node = `${indent}${id}${shape}`;
265
+
266
+ // Add class for styling
267
+ if (component.type) {
268
+ node += `:::${component.type}`;
269
+ }
270
+ node += '\n';
271
+
272
+ return node;
273
+ }
274
+
275
+ /**
276
+ * Group components by layer
277
+ * @param {Array} components - Components array
278
+ * @returns {Object} Grouped components
279
+ */
280
+ groupByLayer(components) {
281
+ const layers = {};
282
+ for (const component of components) {
283
+ const layer = component.layer || 'default';
284
+ if (!layers[layer]) {
285
+ layers[layer] = [];
286
+ }
287
+ layers[layer].push(component);
288
+ }
289
+ return layers;
290
+ }
291
+
292
+ /**
293
+ * Generate deployment diagram from docker-compose YAML
294
+ * @param {string} dockerCompose - Docker-compose YAML content
295
+ * @returns {string} Mermaid flowchart diagram
296
+ */
297
+ generateDeploymentDiagram(dockerCompose) {
298
+ let mermaid = `flowchart ${this.options.direction}\n`;
299
+
300
+ if (!dockerCompose || dockerCompose.trim() === '') {
301
+ mermaid += ' empty[No infrastructure configuration found]\n';
302
+ return mermaid;
303
+ }
304
+
305
+ let config;
306
+ try {
307
+ config = parseSimpleYaml(dockerCompose);
308
+ } catch (e) {
309
+ // Invalid YAML, return minimal diagram
310
+ mermaid += ' error[Invalid infrastructure configuration]\n';
311
+ return mermaid;
312
+ }
313
+
314
+ if (!config || !config.services) {
315
+ mermaid += ' empty[No infrastructure configuration found]\n';
316
+ return mermaid;
317
+ }
318
+
319
+ // Add styles
320
+ mermaid += this.generateDeploymentStyles();
321
+
322
+ // Parse services
323
+ const services = config.services || {};
324
+ const volumes = config.volumes || {};
325
+ const networks = config.networks || {};
326
+
327
+ // Generate network subgraphs
328
+ if (Object.keys(networks).length > 0) {
329
+ for (const [networkName, _networkConfig] of Object.entries(networks)) {
330
+ const servicesInNetwork = [];
331
+ for (const [serviceName, serviceConfig] of Object.entries(services)) {
332
+ if (serviceConfig.networks && serviceConfig.networks.includes(networkName)) {
333
+ servicesInNetwork.push(serviceName);
334
+ }
335
+ }
336
+ if (servicesInNetwork.length > 0) {
337
+ mermaid += `\n subgraph ${this.sanitizeId(networkName)}["Network: ${networkName}"]\n`;
338
+ for (const svc of servicesInNetwork) {
339
+ mermaid += ` ${this.sanitizeId(svc)}_net[${svc}]\n`;
340
+ }
341
+ mermaid += ' end\n';
342
+ }
343
+ }
344
+ }
345
+
346
+ // Generate service nodes
347
+ mermaid += '\n %% Services\n';
348
+ for (const [name, serviceConfig] of Object.entries(services)) {
349
+ const id = this.sanitizeId(name);
350
+ const image = serviceConfig.image || 'custom';
351
+ let label = `${name}`;
352
+
353
+ // Add port info
354
+ if (serviceConfig.ports && serviceConfig.ports.length > 0) {
355
+ const ports = serviceConfig.ports.map(p => {
356
+ if (typeof p === 'string') {
357
+ return p.split(':')[0];
358
+ }
359
+ return p.published || p;
360
+ });
361
+ label += `<br/>Ports: ${ports.join(', ')}`;
362
+ }
363
+
364
+ mermaid += ` ${id}["${label}"]:::service\n`;
365
+ }
366
+
367
+ // Generate volume nodes
368
+ if (Object.keys(volumes).length > 0) {
369
+ mermaid += '\n %% Volumes\n';
370
+ for (const volumeName of Object.keys(volumes)) {
371
+ const id = this.sanitizeId(`vol_${volumeName}`);
372
+ mermaid += ` ${id}[("${volumeName}")]:::volume\n`;
373
+ }
374
+ }
375
+
376
+ // Generate dependencies (depends_on)
377
+ mermaid += '\n %% Dependencies\n';
378
+ for (const [name, serviceConfig] of Object.entries(services)) {
379
+ const id = this.sanitizeId(name);
380
+ if (serviceConfig.depends_on) {
381
+ const deps = Array.isArray(serviceConfig.depends_on)
382
+ ? serviceConfig.depends_on
383
+ : Object.keys(serviceConfig.depends_on);
384
+ for (const dep of deps) {
385
+ const depId = this.sanitizeId(dep);
386
+ mermaid += ` ${id} -->|depends_on| ${depId}\n`;
387
+ }
388
+ }
389
+ }
390
+
391
+ // Generate volume mounts
392
+ for (const [name, serviceConfig] of Object.entries(services)) {
393
+ const id = this.sanitizeId(name);
394
+ if (serviceConfig.volumes) {
395
+ for (const vol of serviceConfig.volumes) {
396
+ const volName = typeof vol === 'string' ? vol.split(':')[0] : vol.source;
397
+ if (volumes[volName]) {
398
+ const volId = this.sanitizeId(`vol_${volName}`);
399
+ mermaid += ` ${id} -.->|mounts| ${volId}\n`;
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ return mermaid;
406
+ }
407
+
408
+ /**
409
+ * Generate ER diagram from schema definitions
410
+ * @param {Array} schemas - Array of parsed schema models
411
+ * @returns {string} Mermaid ER diagram
412
+ */
413
+ generateERDiagram(schemas) {
414
+ if (!schemas || schemas.length === 0) {
415
+ return 'erDiagram\n EMPTY["No entities defined"]';
416
+ }
417
+
418
+ let mermaid = 'erDiagram\n';
419
+
420
+ // Build table name lookup for relationship inference
421
+ const tableNames = new Set(schemas.map(s => s.tableName));
422
+
423
+ // Track relationships to avoid duplicates
424
+ const relationships = new Set();
425
+
426
+ // Generate entities
427
+ for (const schema of schemas) {
428
+ const tableName = this.sanitizeId(schema.tableName);
429
+ mermaid += ` ${tableName} {\n`;
430
+
431
+ for (const column of schema.columns) {
432
+ const type = column.type || 'unknown';
433
+ let markers = '';
434
+
435
+ if (column.primary) {
436
+ markers = 'PK';
437
+ } else if (column.foreignKey) {
438
+ markers = 'FK';
439
+ } else if (column.unique) {
440
+ markers = 'UK';
441
+ }
442
+
443
+ const markerStr = markers ? ` "${markers}"` : '';
444
+ mermaid += ` ${type} ${column.name}${markerStr}\n`;
445
+
446
+ // Track explicit foreign keys
447
+ if (column.foreignKey) {
448
+ const refTable = this.sanitizeId(column.foreignKey.table);
449
+ const rel = `${refTable} ||--o{ ${tableName}`;
450
+ if (!relationships.has(rel)) {
451
+ relationships.add(rel);
452
+ }
453
+ }
454
+
455
+ // Infer relationships from naming conventions (e.g., user_id -> users)
456
+ if (!column.foreignKey && column.name.endsWith('_id')) {
457
+ const inferredTable = column.name.slice(0, -3) + 's'; // user_id -> users
458
+ if (tableNames.has(inferredTable)) {
459
+ const refTable = this.sanitizeId(inferredTable);
460
+ const rel = `${refTable} ||--o{ ${tableName}`;
461
+ if (!relationships.has(rel)) {
462
+ relationships.add(rel);
463
+ }
464
+ }
465
+ }
466
+ }
467
+
468
+ mermaid += ' }\n';
469
+ }
470
+
471
+ // Add relationships
472
+ if (relationships.size > 0) {
473
+ mermaid += '\n';
474
+ for (const rel of relationships) {
475
+ mermaid += ` ${rel} : has\n`;
476
+ }
477
+ }
478
+
479
+ return mermaid;
480
+ }
481
+
482
+ /**
483
+ * Extract API flow from route handler code
484
+ * @param {string} handlerName - Name of the handler
485
+ * @param {string} code - Handler code
486
+ * @returns {Object} Extracted API flow
487
+ */
488
+ extractAPIFlow(handlerName, code) {
489
+ const flow = {
490
+ name: handlerName,
491
+ steps: [],
492
+ };
493
+
494
+ // Find await calls to extract service interactions
495
+ const awaitPattern = /await\s+(\w+)\.(\w+)\s*\([^)]*\)/g;
496
+ let match;
497
+ let stepOrder = 0;
498
+
499
+ while ((match = awaitPattern.exec(code)) !== null) {
500
+ const [fullMatch, service, method] = match;
501
+ stepOrder++;
502
+
503
+ flow.steps.push({
504
+ from: handlerName,
505
+ to: service,
506
+ action: `${service}.${method}()`,
507
+ type: this.inferStepType(service, method),
508
+ order: stepOrder,
509
+ });
510
+ }
511
+
512
+ // Detect response patterns
513
+ const responsePattern = /return\s+res\.status\((\d+)\)\.json/;
514
+ const responseMatch = code.match(responsePattern);
515
+ if (responseMatch) {
516
+ flow.steps.push({
517
+ from: handlerName,
518
+ to: 'Client',
519
+ action: `${responseMatch[1]} Response`,
520
+ type: 'response',
521
+ order: stepOrder + 1,
522
+ });
523
+ }
524
+
525
+ return flow;
526
+ }
527
+
528
+ /**
529
+ * Infer step type from service and method names
530
+ * @param {string} service - Service name
531
+ * @param {string} method - Method name
532
+ * @returns {string} Step type
533
+ */
534
+ inferStepType(service, method) {
535
+ const serviceLower = service.toLowerCase();
536
+ const methodLower = method.toLowerCase();
537
+
538
+ if (serviceLower.includes('repository') || serviceLower.includes('db') ||
539
+ methodLower.includes('find') || methodLower.includes('create') ||
540
+ methodLower.includes('update') || methodLower.includes('delete')) {
541
+ return 'query';
542
+ }
543
+
544
+ if (serviceLower.includes('event') || serviceLower.includes('bus') ||
545
+ methodLower.includes('publish') || methodLower.includes('emit')) {
546
+ return 'event';
547
+ }
548
+
549
+ if (serviceLower.includes('cache') || serviceLower.includes('redis')) {
550
+ return 'cache';
551
+ }
552
+
553
+ return 'call';
554
+ }
555
+
556
+ /**
557
+ * Generate a combined workspace diagram
558
+ * @param {Object} workspace - Workspace configuration
559
+ * @returns {string} Mermaid flowchart diagram
560
+ */
561
+ generateWorkspaceDiagram(workspace) {
562
+ let mermaid = `flowchart ${this.options.direction}\n`;
563
+
564
+ // Add styles
565
+ mermaid += this.generateWorkspaceStyles();
566
+
567
+ // Generate service nodes
568
+ if (workspace.services && workspace.services.length > 0) {
569
+ mermaid += '\n %% Services\n';
570
+ for (const service of workspace.services) {
571
+ const id = this.sanitizeId(service.name);
572
+ const shape = service.type === 'gateway' ? `{{${service.name}}}` : `[${service.name}]`;
573
+ mermaid += ` ${id}${shape}:::service\n`;
574
+ }
575
+ }
576
+
577
+ // Generate database nodes
578
+ if (workspace.databases && workspace.databases.length > 0) {
579
+ mermaid += '\n %% Databases\n';
580
+ for (const db of workspace.databases) {
581
+ const id = this.sanitizeId(db.name);
582
+ mermaid += ` ${id}[(${db.name})]:::database\n`;
583
+ }
584
+ }
585
+
586
+ // Generate queue nodes
587
+ if (workspace.queues && workspace.queues.length > 0) {
588
+ mermaid += '\n %% Message Queues\n';
589
+ for (const queue of workspace.queues) {
590
+ const id = this.sanitizeId(queue.name);
591
+ mermaid += ` ${id}([${queue.name}]):::queue\n`;
592
+ }
593
+ }
594
+
595
+ // Generate connections
596
+ if (workspace.connections && workspace.connections.length > 0) {
597
+ mermaid += '\n %% Connections\n';
598
+ for (const conn of workspace.connections) {
599
+ const fromId = this.sanitizeId(conn.from);
600
+ const toId = this.sanitizeId(conn.to);
601
+ const label = conn.type || '';
602
+ mermaid += ` ${fromId} -->|${label}| ${toId}\n`;
603
+ }
604
+ }
605
+
606
+ return mermaid;
607
+ }
608
+
609
+ /**
610
+ * Generate component diagram styles
611
+ * @returns {string} Style definitions
612
+ */
613
+ generateComponentStyles() {
614
+ return ` %% Styles
615
+ classDef gateway fill:#fff3e0,stroke:#e65100,stroke-width:2px
616
+ classDef service fill:#e1f5fe,stroke:#01579b,stroke-width:2px
617
+ classDef database fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px
618
+ classDef cache fill:#fce4ec,stroke:#880e4f,stroke-width:2px
619
+ classDef queue fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
620
+ \n`;
621
+ }
622
+
623
+ /**
624
+ * Generate deployment diagram styles
625
+ * @returns {string} Style definitions
626
+ */
627
+ generateDeploymentStyles() {
628
+ return ` %% Styles
629
+ classDef service fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
630
+ classDef volume fill:#f5f5f5,stroke:#616161,stroke-width:2px,stroke-dasharray: 5 5
631
+ \n`;
632
+ }
633
+
634
+ /**
635
+ * Generate workspace diagram styles
636
+ * @returns {string} Style definitions
637
+ */
638
+ generateWorkspaceStyles() {
639
+ return ` %% Styles
640
+ classDef service fill:#e1f5fe,stroke:#01579b,stroke-width:2px
641
+ classDef database fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px
642
+ classDef queue fill:#fff3e0,stroke:#e65100,stroke-width:2px
643
+ \n`;
644
+ }
645
+
646
+ /**
647
+ * Sanitize string for use as Mermaid ID
648
+ * @param {string} str - String to sanitize
649
+ * @returns {string} Sanitized ID
650
+ */
651
+ sanitizeId(str) {
652
+ return str
653
+ .replace(/@/g, '')
654
+ .replace(/\//g, '_')
655
+ .replace(/[^a-zA-Z0-9]/g, '_')
656
+ .replace(/^_+|_+$/g, '')
657
+ .replace(/_+/g, '_')
658
+ || 'node';
659
+ }
660
+
661
+ /**
662
+ * Escape label for Mermaid
663
+ * @param {string} str - String to escape
664
+ * @returns {string} Escaped label
665
+ */
666
+ escapeLabel(str) {
667
+ return str
668
+ .replace(/"/g, "'")
669
+ .replace(/\[/g, '(')
670
+ .replace(/\]/g, ')')
671
+ .replace(/>/g, ')')
672
+ .replace(/</g, '(')
673
+ .replace(/\{/g, '(')
674
+ .replace(/\}/g, ')');
675
+ }
676
+
677
+ /**
678
+ * Escape and sanitize label for component names (more aggressive than escapeLabel)
679
+ * @param {string} str - String to escape
680
+ * @returns {string} Escaped label
681
+ */
682
+ sanitizeLabel(str) {
683
+ return this.escapeLabel(str)
684
+ .replace(/@/g, '')
685
+ .replace(/\//g, '_')
686
+ .replace(/-/g, '_')
687
+ .replace(/\./g, '_');
688
+ }
689
+
690
+ /**
691
+ * Capitalize first letter
692
+ * @param {string} str - String to capitalize
693
+ * @returns {string} Capitalized string
694
+ */
695
+ capitalizeFirst(str) {
696
+ return str.charAt(0).toUpperCase() + str.slice(1);
697
+ }
698
+ }
699
+
700
+ module.exports = { ServiceInteractionDiagram };