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,247 @@
1
+ /**
2
+ * Refactor Reporter Tests
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+
7
+ describe('RefactorReporter', () => {
8
+ describe('plain English summaries', () => {
9
+ it('generates plain English: Extracted X from Y', async () => {
10
+ const { RefactorReporter } = await import('./refactor-reporter.js');
11
+ const reporter = new RefactorReporter();
12
+
13
+ const summary = reporter.describeChange({
14
+ type: 'extract',
15
+ name: 'validateEmail',
16
+ source: 'createUser',
17
+ lines: 15,
18
+ });
19
+
20
+ expect(summary).toContain('Extracted');
21
+ expect(summary).toContain('validateEmail');
22
+ expect(summary).toContain('createUser');
23
+ });
24
+
25
+ it('generates summary for rename', async () => {
26
+ const { RefactorReporter } = await import('./refactor-reporter.js');
27
+ const reporter = new RefactorReporter();
28
+
29
+ const summary = reporter.describeChange({
30
+ type: 'rename',
31
+ oldName: 'x',
32
+ newName: 'userCount',
33
+ filesAffected: 3,
34
+ });
35
+
36
+ expect(summary).toContain('Renamed');
37
+ expect(summary).toContain('x');
38
+ expect(summary).toContain('userCount');
39
+ expect(summary).toContain('3');
40
+ });
41
+
42
+ it('generates summary for split', async () => {
43
+ const { RefactorReporter } = await import('./refactor-reporter.js');
44
+ const reporter = new RefactorReporter();
45
+
46
+ const summary = reporter.describeChange({
47
+ type: 'split',
48
+ source: 'handlers.js',
49
+ targets: ['userHandlers.js', 'authHandlers.js'],
50
+ });
51
+
52
+ expect(summary).toContain('Split');
53
+ expect(summary).toContain('handlers.js');
54
+ expect(summary).toContain('2');
55
+ });
56
+ });
57
+
58
+ describe('diff generation', () => {
59
+ it('generates unified diff format', async () => {
60
+ const { RefactorReporter } = await import('./refactor-reporter.js');
61
+ const reporter = new RefactorReporter();
62
+
63
+ const diff = reporter.generateDiff({
64
+ file: 'test.js',
65
+ before: 'const x = 1;',
66
+ after: 'const count = 1;',
67
+ });
68
+
69
+ expect(diff).toContain('--- a/test.js');
70
+ expect(diff).toContain('+++ b/test.js');
71
+ expect(diff).toContain('-const x = 1;');
72
+ expect(diff).toContain('+const count = 1;');
73
+ });
74
+
75
+ it('handles empty before/after', async () => {
76
+ const { RefactorReporter } = await import('./refactor-reporter.js');
77
+ const reporter = new RefactorReporter();
78
+
79
+ const diff = reporter.generateDiff({
80
+ file: 'test.js',
81
+ });
82
+
83
+ expect(diff).toBe('');
84
+ });
85
+ });
86
+
87
+ describe('Mermaid diagrams', () => {
88
+ it('generates Mermaid diagram for function relationships', async () => {
89
+ const { RefactorReporter } = await import('./refactor-reporter.js');
90
+ const reporter = new RefactorReporter();
91
+
92
+ const diagram = reporter.generateMermaidDiagram([
93
+ { type: 'extract', source: 'main', name: 'helper' },
94
+ { type: 'extract', source: 'main', name: 'utils' },
95
+ ]);
96
+
97
+ expect(diagram).toContain('graph TD');
98
+ expect(diagram).toContain('main --> helper');
99
+ expect(diagram).toContain('main --> utils');
100
+ });
101
+
102
+ it('handles split operations', async () => {
103
+ const { RefactorReporter } = await import('./refactor-reporter.js');
104
+ const reporter = new RefactorReporter();
105
+
106
+ const diagram = reporter.generateMermaidDiagram([
107
+ { type: 'split', source: 'handlers', targets: [{ name: 'userHandlers' }, { name: 'authHandlers' }] },
108
+ ]);
109
+
110
+ expect(diagram).toContain('handlers --> userHandlers');
111
+ expect(diagram).toContain('handlers --> authHandlers');
112
+ });
113
+
114
+ it('returns empty for non-structural changes', async () => {
115
+ const { RefactorReporter } = await import('./refactor-reporter.js');
116
+ const reporter = new RefactorReporter();
117
+
118
+ const diagram = reporter.generateMermaidDiagram([
119
+ { type: 'rename', oldName: 'x', newName: 'y' },
120
+ ]);
121
+
122
+ expect(diagram).toBe('');
123
+ });
124
+ });
125
+
126
+ describe('Markdown output', () => {
127
+ it('outputs valid Markdown', async () => {
128
+ const { RefactorReporter } = await import('./refactor-reporter.js');
129
+ const reporter = new RefactorReporter();
130
+
131
+ const md = reporter.toMarkdown([
132
+ { type: 'extract', source: 'main.js', name: 'helper' },
133
+ ]);
134
+
135
+ expect(md).toContain('# Refactoring Report');
136
+ expect(md).toContain('## Summary');
137
+ expect(md).toContain('## Changes');
138
+ });
139
+
140
+ it('includes collapsible diff sections', async () => {
141
+ const { RefactorReporter } = await import('./refactor-reporter.js');
142
+ const reporter = new RefactorReporter();
143
+
144
+ const md = reporter.toMarkdown([
145
+ { type: 'rename', oldName: 'x', newName: 'y', file: 'test.js', before: 'x', after: 'y' },
146
+ ]);
147
+
148
+ expect(md).toContain('<details>');
149
+ expect(md).toContain('View Diff');
150
+ expect(md).toContain('</details>');
151
+ });
152
+ });
153
+
154
+ describe('JSON output', () => {
155
+ it('outputs valid JSON', async () => {
156
+ const { RefactorReporter } = await import('./refactor-reporter.js');
157
+ const reporter = new RefactorReporter();
158
+
159
+ const json = reporter.toJson([
160
+ { type: 'extract', source: 'main.js', name: 'helper' },
161
+ ]);
162
+
163
+ const parsed = JSON.parse(json);
164
+ expect(parsed.summary).toBeDefined();
165
+ expect(parsed.changes).toBeDefined();
166
+ expect(parsed.generatedAt).toBeDefined();
167
+ });
168
+
169
+ it('includes descriptions in JSON', async () => {
170
+ const { RefactorReporter } = await import('./refactor-reporter.js');
171
+ const reporter = new RefactorReporter();
172
+
173
+ const json = reporter.toJson([
174
+ { type: 'rename', oldName: 'x', newName: 'count' },
175
+ ]);
176
+
177
+ const parsed = JSON.parse(json);
178
+ expect(parsed.changes[0].description).toContain('Renamed');
179
+ });
180
+ });
181
+
182
+ describe('HTML output', () => {
183
+ it('outputs valid HTML', async () => {
184
+ const { RefactorReporter } = await import('./refactor-reporter.js');
185
+ const reporter = new RefactorReporter();
186
+
187
+ const html = reporter.toHtml([
188
+ { type: 'extract', source: 'main.js', name: 'helper' },
189
+ ]);
190
+
191
+ expect(html).toContain('<!DOCTYPE html>');
192
+ expect(html).toContain('<html>');
193
+ expect(html).toContain('</html>');
194
+ expect(html).toContain('<title>Refactoring Report</title>');
195
+ });
196
+
197
+ it('escapes HTML entities', async () => {
198
+ const { RefactorReporter } = await import('./refactor-reporter.js');
199
+ const reporter = new RefactorReporter();
200
+
201
+ const escaped = reporter.escapeHtml('<script>alert("xss")</script>');
202
+
203
+ expect(escaped).not.toContain('<script>');
204
+ expect(escaped).toContain('&lt;script&gt;');
205
+ });
206
+ });
207
+
208
+ describe('before/after comparisons', () => {
209
+ it('includes before/after code blocks', async () => {
210
+ const { RefactorReporter } = await import('./refactor-reporter.js');
211
+ const reporter = new RefactorReporter();
212
+
213
+ const comparison = reporter.generateComparison({
214
+ file: 'test.js',
215
+ type: 'rename',
216
+ before: 'const x = 1;',
217
+ after: 'const count = 1;',
218
+ });
219
+
220
+ expect(comparison.before).toBe('const x = 1;');
221
+ expect(comparison.after).toBe('const count = 1;');
222
+ expect(comparison.file).toBe('test.js');
223
+ });
224
+ });
225
+
226
+ describe('generate method', () => {
227
+ it('defaults to markdown format', async () => {
228
+ const { RefactorReporter } = await import('./refactor-reporter.js');
229
+ const reporter = new RefactorReporter();
230
+
231
+ const output = reporter.generate([{ type: 'extract', name: 'test', source: 'main' }]);
232
+
233
+ expect(output).toContain('# Refactoring Report');
234
+ });
235
+
236
+ it('respects format parameter', async () => {
237
+ const { RefactorReporter } = await import('./refactor-reporter.js');
238
+ const reporter = new RefactorReporter();
239
+
240
+ const json = reporter.generate([{ type: 'extract', name: 'test', source: 'main' }], 'json');
241
+ expect(() => JSON.parse(json)).not.toThrow();
242
+
243
+ const html = reporter.generate([{ type: 'extract', name: 'test', source: 'main' }], 'html');
244
+ expect(html).toContain('<!DOCTYPE html>');
245
+ });
246
+ });
247
+ });
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Repo Dependency Tracker - Track dependencies between repos in a workspace
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ class RepoDependencyTracker {
9
+ constructor(workspaceRoot, repos) {
10
+ this.workspaceRoot = workspaceRoot;
11
+ this.repos = repos;
12
+ this.repoPackages = new Map(); // repo -> package.json data
13
+ this.repoNames = new Map(); // package name -> repo directory
14
+ this.dependencyGraph = {}; // repo -> [dependencies]
15
+
16
+ this.loadRepoData();
17
+ this.buildDependencyGraph();
18
+ }
19
+
20
+ /**
21
+ * Load package.json for all repos
22
+ */
23
+ loadRepoData() {
24
+ for (const repo of this.repos) {
25
+ const pkgPath = path.join(this.workspaceRoot, repo, 'package.json');
26
+
27
+ if (fs.existsSync(pkgPath)) {
28
+ try {
29
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
30
+ this.repoPackages.set(repo, pkg);
31
+ if (pkg.name) {
32
+ this.repoNames.set(pkg.name, repo);
33
+ }
34
+ } catch (err) {
35
+ // Invalid JSON, skip
36
+ this.repoPackages.set(repo, {});
37
+ }
38
+ } else {
39
+ this.repoPackages.set(repo, {});
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Build dependency graph from repo data
46
+ */
47
+ buildDependencyGraph() {
48
+ for (const repo of this.repos) {
49
+ this.dependencyGraph[repo] = [];
50
+ const pkg = this.repoPackages.get(repo) || {};
51
+
52
+ const allDeps = {
53
+ ...pkg.dependencies,
54
+ ...pkg.devDependencies,
55
+ ...pkg.peerDependencies,
56
+ };
57
+
58
+ for (const [depName, depVersion] of Object.entries(allDeps || {})) {
59
+ // Check for workspace: protocol
60
+ if (typeof depVersion === 'string' && depVersion.startsWith('workspace:')) {
61
+ const depRepo = this.repoNames.get(depName);
62
+ if (depRepo && depRepo !== repo) {
63
+ this.dependencyGraph[repo].push(depRepo);
64
+ }
65
+ }
66
+
67
+ // Check for file: protocol pointing to another repo
68
+ if (typeof depVersion === 'string' && depVersion.startsWith('file:')) {
69
+ const filePath = depVersion.slice(5); // Remove 'file:'
70
+ const resolvedPath = path.resolve(path.join(this.workspaceRoot, repo), filePath);
71
+ const relativePath = path.relative(this.workspaceRoot, resolvedPath);
72
+
73
+ // Check if it points to one of our repos
74
+ for (const otherRepo of this.repos) {
75
+ if (relativePath === otherRepo || relativePath.startsWith(otherRepo + path.sep)) {
76
+ if (otherRepo !== repo) {
77
+ this.dependencyGraph[repo].push(otherRepo);
78
+ }
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get dependencies of a repo
89
+ * @param {string} repo - Repo directory name
90
+ * @returns {string[]} Array of repo names this repo depends on
91
+ */
92
+ getDependencies(repo) {
93
+ return this.dependencyGraph[repo] || [];
94
+ }
95
+
96
+ /**
97
+ * Check if repoA depends on repoB
98
+ * @param {string} repoA
99
+ * @param {string} repoB
100
+ * @returns {boolean}
101
+ */
102
+ dependsOn(repoA, repoB) {
103
+ const deps = this.getDependencies(repoA);
104
+ return deps.includes(repoB);
105
+ }
106
+
107
+ /**
108
+ * Get repos that depend on the given repo
109
+ * @param {string} repo
110
+ * @returns {string[]}
111
+ */
112
+ getDependents(repo) {
113
+ const dependents = [];
114
+
115
+ for (const [r, deps] of Object.entries(this.dependencyGraph)) {
116
+ if (deps.includes(repo)) {
117
+ dependents.push(r);
118
+ }
119
+ }
120
+
121
+ return dependents;
122
+ }
123
+
124
+ /**
125
+ * Get all repos affected when the given repo changes (transitive dependents)
126
+ * @param {string} repo
127
+ * @returns {string[]}
128
+ */
129
+ getAffectedRepos(repo) {
130
+ const affected = new Set();
131
+ const queue = [repo];
132
+ const visited = new Set([repo]);
133
+
134
+ while (queue.length > 0) {
135
+ const current = queue.shift();
136
+ const dependents = this.getDependents(current);
137
+
138
+ for (const dep of dependents) {
139
+ if (!visited.has(dep)) {
140
+ visited.add(dep);
141
+ affected.add(dep);
142
+ queue.push(dep);
143
+ }
144
+ }
145
+ }
146
+
147
+ return Array.from(affected);
148
+ }
149
+
150
+ /**
151
+ * Get topological order for build/test runs
152
+ * @returns {string[]}
153
+ */
154
+ getTopologicalOrder() {
155
+ const visited = new Set();
156
+ const result = [];
157
+
158
+ const visit = (repo) => {
159
+ if (visited.has(repo)) return;
160
+ visited.add(repo);
161
+
162
+ const deps = this.getDependencies(repo);
163
+ for (const dep of deps) {
164
+ visit(dep);
165
+ }
166
+
167
+ result.push(repo);
168
+ };
169
+
170
+ for (const repo of this.repos) {
171
+ visit(repo);
172
+ }
173
+
174
+ return result;
175
+ }
176
+
177
+ /**
178
+ * Detect circular dependencies
179
+ * @returns {string[][]} Array of cycles found
180
+ */
181
+ detectCircularDependencies() {
182
+ const cycles = [];
183
+ const visited = new Set();
184
+ const recursionStack = new Set();
185
+ const path = [];
186
+
187
+ const dfs = (repo) => {
188
+ visited.add(repo);
189
+ recursionStack.add(repo);
190
+ path.push(repo);
191
+
192
+ const deps = this.getDependencies(repo);
193
+ for (const dep of deps) {
194
+ if (!visited.has(dep)) {
195
+ const cycle = dfs(dep);
196
+ if (cycle) return cycle;
197
+ } else if (recursionStack.has(dep)) {
198
+ // Found cycle
199
+ const cycleStart = path.indexOf(dep);
200
+ const cycle = path.slice(cycleStart);
201
+ cycle.push(dep); // Complete the cycle
202
+ cycles.push(cycle);
203
+ return cycle;
204
+ }
205
+ }
206
+
207
+ path.pop();
208
+ recursionStack.delete(repo);
209
+ return null;
210
+ };
211
+
212
+ for (const repo of this.repos) {
213
+ if (!visited.has(repo)) {
214
+ dfs(repo);
215
+ }
216
+ }
217
+
218
+ return cycles;
219
+ }
220
+
221
+ /**
222
+ * Generate Mermaid diagram of dependencies
223
+ * @returns {string}
224
+ */
225
+ generateMermaidDiagram() {
226
+ const lines = ['graph TD'];
227
+
228
+ // Add all nodes
229
+ for (const repo of this.repos) {
230
+ lines.push(` ${this.sanitizeId(repo)}[${repo}]`);
231
+ }
232
+
233
+ // Add edges
234
+ for (const [repo, deps] of Object.entries(this.dependencyGraph)) {
235
+ for (const dep of deps) {
236
+ lines.push(` ${this.sanitizeId(repo)} --> ${this.sanitizeId(dep)}`);
237
+ }
238
+ }
239
+
240
+ return lines.join('\n');
241
+ }
242
+
243
+ /**
244
+ * Sanitize repo name for Mermaid ID
245
+ */
246
+ sanitizeId(name) {
247
+ return name.replace(/[^a-zA-Z0-9]/g, '_');
248
+ }
249
+
250
+ /**
251
+ * Get the full dependency graph
252
+ * @returns {Object}
253
+ */
254
+ getDependencyGraph() {
255
+ return { ...this.dependencyGraph };
256
+ }
257
+ }
258
+
259
+ module.exports = {
260
+ RepoDependencyTracker,
261
+ };