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,619 @@
1
+ /**
2
+ * Service Summary Generator Tests
3
+ * Generate "What does this repo do" one-pager
4
+ */
5
+
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os';
10
+
11
+ const { ServiceSummaryGenerator, generateServiceSummary, createServiceSummaryGenerator } = await import('./service-summary.js');
12
+
13
+ describe('ServiceSummaryGenerator', () => {
14
+ let tempDir;
15
+ let generator;
16
+
17
+ beforeEach(() => {
18
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'service-summary-test-'));
19
+ generator = new ServiceSummaryGenerator(tempDir);
20
+ });
21
+
22
+ afterEach(() => {
23
+ fs.rmSync(tempDir, { recursive: true, force: true });
24
+ });
25
+
26
+ describe('extractPurpose', () => {
27
+ it('extracts purpose from package.json description', () => {
28
+ fs.writeFileSync(
29
+ path.join(tempDir, 'package.json'),
30
+ JSON.stringify({
31
+ name: 'user-service',
32
+ description: 'Handles user authentication and profile management',
33
+ })
34
+ );
35
+
36
+ const purpose = generator.extractPurpose();
37
+
38
+ expect(purpose).toBe('Handles user authentication and profile management');
39
+ });
40
+
41
+ it('falls back to README first paragraph when no package.json description', () => {
42
+ fs.writeFileSync(
43
+ path.join(tempDir, 'package.json'),
44
+ JSON.stringify({
45
+ name: 'my-service',
46
+ })
47
+ );
48
+ fs.writeFileSync(
49
+ path.join(tempDir, 'README.md'),
50
+ `# My Service
51
+
52
+ This service provides API endpoints for managing orders and inventory.
53
+
54
+ ## Installation
55
+
56
+ Run npm install.
57
+ `
58
+ );
59
+
60
+ const purpose = generator.extractPurpose();
61
+
62
+ expect(purpose).toContain('API endpoints');
63
+ expect(purpose).toContain('orders');
64
+ });
65
+
66
+ it('returns empty string when no description available', () => {
67
+ // No package.json, no README
68
+ const purpose = generator.extractPurpose();
69
+
70
+ expect(purpose).toBe('');
71
+ });
72
+
73
+ it('handles package.json with empty description', () => {
74
+ fs.writeFileSync(
75
+ path.join(tempDir, 'package.json'),
76
+ JSON.stringify({
77
+ name: 'empty-desc',
78
+ description: '',
79
+ })
80
+ );
81
+
82
+ const purpose = generator.extractPurpose();
83
+
84
+ expect(purpose).toBe('');
85
+ });
86
+ });
87
+
88
+ describe('identifyMainEntryPoints', () => {
89
+ it('identifies index.js as main entry point', () => {
90
+ fs.writeFileSync(
91
+ path.join(tempDir, 'package.json'),
92
+ JSON.stringify({ name: 'test-pkg' })
93
+ );
94
+ fs.writeFileSync(path.join(tempDir, 'index.js'), 'module.exports = {};');
95
+
96
+ const entryPoints = generator.identifyMainEntryPoints();
97
+
98
+ expect(entryPoints).toContainEqual(
99
+ expect.objectContaining({
100
+ file: 'index.js',
101
+ type: 'main',
102
+ })
103
+ );
104
+ });
105
+
106
+ it('identifies main.ts as entry point', () => {
107
+ fs.writeFileSync(
108
+ path.join(tempDir, 'package.json'),
109
+ JSON.stringify({ name: 'ts-pkg' })
110
+ );
111
+ fs.writeFileSync(path.join(tempDir, 'main.ts'), 'export default {};');
112
+
113
+ const entryPoints = generator.identifyMainEntryPoints();
114
+
115
+ expect(entryPoints).toContainEqual(
116
+ expect.objectContaining({
117
+ file: 'main.ts',
118
+ type: 'main',
119
+ })
120
+ );
121
+ });
122
+
123
+ it('uses package.json main field when specified', () => {
124
+ fs.mkdirSync(path.join(tempDir, 'dist'), { recursive: true });
125
+ fs.writeFileSync(
126
+ path.join(tempDir, 'package.json'),
127
+ JSON.stringify({
128
+ name: 'with-main',
129
+ main: 'dist/index.js',
130
+ })
131
+ );
132
+ fs.writeFileSync(path.join(tempDir, 'dist', 'index.js'), '');
133
+
134
+ const entryPoints = generator.identifyMainEntryPoints();
135
+
136
+ expect(entryPoints).toContainEqual(
137
+ expect.objectContaining({
138
+ file: 'dist/index.js',
139
+ type: 'main',
140
+ })
141
+ );
142
+ });
143
+
144
+ it('identifies bin entry points for CLI tools', () => {
145
+ fs.mkdirSync(path.join(tempDir, 'bin'), { recursive: true });
146
+ fs.writeFileSync(
147
+ path.join(tempDir, 'package.json'),
148
+ JSON.stringify({
149
+ name: 'cli-tool',
150
+ bin: {
151
+ 'my-cli': './bin/cli.js',
152
+ },
153
+ })
154
+ );
155
+ fs.writeFileSync(path.join(tempDir, 'bin', 'cli.js'), '#!/usr/bin/env node');
156
+
157
+ const entryPoints = generator.identifyMainEntryPoints();
158
+
159
+ expect(entryPoints).toContainEqual(
160
+ expect.objectContaining({
161
+ file: 'bin/cli.js',
162
+ type: 'bin',
163
+ name: 'my-cli',
164
+ })
165
+ );
166
+ });
167
+
168
+ it('returns empty array when no entry points found', () => {
169
+ // Empty directory
170
+ const entryPoints = generator.identifyMainEntryPoints();
171
+
172
+ expect(entryPoints).toEqual([]);
173
+ });
174
+ });
175
+
176
+ describe('listExports', () => {
177
+ it('lists exported functions from index.js', () => {
178
+ fs.writeFileSync(
179
+ path.join(tempDir, 'package.json'),
180
+ JSON.stringify({ name: 'exports-test' })
181
+ );
182
+ fs.writeFileSync(
183
+ path.join(tempDir, 'index.js'),
184
+ `
185
+ module.exports = {
186
+ createUser,
187
+ deleteUser,
188
+ updateUser,
189
+ };
190
+
191
+ function createUser() {}
192
+ function deleteUser() {}
193
+ function updateUser() {}
194
+ `
195
+ );
196
+
197
+ const exports = generator.listExports();
198
+
199
+ expect(exports).toContain('createUser');
200
+ expect(exports).toContain('deleteUser');
201
+ expect(exports).toContain('updateUser');
202
+ });
203
+
204
+ it('lists exported classes from ES modules', () => {
205
+ fs.writeFileSync(
206
+ path.join(tempDir, 'package.json'),
207
+ JSON.stringify({ name: 'class-exports', type: 'module' })
208
+ );
209
+ fs.writeFileSync(
210
+ path.join(tempDir, 'index.js'),
211
+ `
212
+ export class UserService {}
213
+ export class AuthService {}
214
+ export default class MainApp {}
215
+ `
216
+ );
217
+
218
+ const exports = generator.listExports();
219
+
220
+ expect(exports).toContain('UserService');
221
+ expect(exports).toContain('AuthService');
222
+ expect(exports).toContain('MainApp');
223
+ });
224
+
225
+ it('lists named exports from TypeScript', () => {
226
+ fs.writeFileSync(
227
+ path.join(tempDir, 'package.json'),
228
+ JSON.stringify({ name: 'ts-exports', main: 'index.ts' })
229
+ );
230
+ fs.writeFileSync(
231
+ path.join(tempDir, 'index.ts'),
232
+ `
233
+ export function processOrder(order: Order): void {}
234
+ export const ORDER_STATUS = { PENDING: 'pending' };
235
+ export interface Order { id: string; }
236
+ export type OrderId = string;
237
+ `
238
+ );
239
+
240
+ const exports = generator.listExports();
241
+
242
+ expect(exports).toContain('processOrder');
243
+ expect(exports).toContain('ORDER_STATUS');
244
+ // Interfaces and types may or may not be listed depending on implementation
245
+ });
246
+
247
+ it('returns empty array when no exports found', () => {
248
+ // No files
249
+ const exports = generator.listExports();
250
+
251
+ expect(exports).toEqual([]);
252
+ });
253
+ });
254
+
255
+ describe('getConsumerRepos', () => {
256
+ it('shows consumer repos from dependency tracker', () => {
257
+ fs.writeFileSync(
258
+ path.join(tempDir, 'package.json'),
259
+ JSON.stringify({ name: '@myorg/core', version: '1.0.0' })
260
+ );
261
+
262
+ // Mock workspace context with dependents
263
+ const mockWorkspace = {
264
+ getDependents: vi.fn().mockReturnValue(['api-service', 'web-app']),
265
+ };
266
+
267
+ generator.setWorkspaceContext(mockWorkspace);
268
+ const consumers = generator.getConsumerRepos();
269
+
270
+ expect(consumers).toContain('api-service');
271
+ expect(consumers).toContain('web-app');
272
+ });
273
+
274
+ it('returns empty array when no workspace context', () => {
275
+ fs.writeFileSync(
276
+ path.join(tempDir, 'package.json'),
277
+ JSON.stringify({ name: 'standalone' })
278
+ );
279
+
280
+ const consumers = generator.getConsumerRepos();
281
+
282
+ expect(consumers).toEqual([]);
283
+ });
284
+
285
+ it('returns empty array when no consumers exist', () => {
286
+ fs.writeFileSync(
287
+ path.join(tempDir, 'package.json'),
288
+ JSON.stringify({ name: '@myorg/leaf-service' })
289
+ );
290
+
291
+ const mockWorkspace = {
292
+ getDependents: vi.fn().mockReturnValue([]),
293
+ };
294
+
295
+ generator.setWorkspaceContext(mockWorkspace);
296
+ const consumers = generator.getConsumerRepos();
297
+
298
+ expect(consumers).toEqual([]);
299
+ });
300
+ });
301
+
302
+ describe('getDependencyRepos', () => {
303
+ it('shows dependency repos from dependency tracker', () => {
304
+ fs.writeFileSync(
305
+ path.join(tempDir, 'package.json'),
306
+ JSON.stringify({
307
+ name: '@myorg/api',
308
+ dependencies: {
309
+ '@myorg/core': 'workspace:*',
310
+ '@myorg/utils': 'workspace:*',
311
+ },
312
+ })
313
+ );
314
+
315
+ const mockWorkspace = {
316
+ getDependencies: vi.fn().mockReturnValue(['core', 'utils']),
317
+ };
318
+
319
+ generator.setWorkspaceContext(mockWorkspace);
320
+ const deps = generator.getDependencyRepos();
321
+
322
+ expect(deps).toContain('core');
323
+ expect(deps).toContain('utils');
324
+ });
325
+
326
+ it('returns empty array when no workspace context', () => {
327
+ fs.writeFileSync(
328
+ path.join(tempDir, 'package.json'),
329
+ JSON.stringify({ name: 'standalone' })
330
+ );
331
+
332
+ const deps = generator.getDependencyRepos();
333
+
334
+ expect(deps).toEqual([]);
335
+ });
336
+
337
+ it('returns empty array when no workspace dependencies', () => {
338
+ fs.writeFileSync(
339
+ path.join(tempDir, 'package.json'),
340
+ JSON.stringify({
341
+ name: '@myorg/core',
342
+ dependencies: {
343
+ express: '^4.18.0', // External, not workspace
344
+ },
345
+ })
346
+ );
347
+
348
+ const mockWorkspace = {
349
+ getDependencies: vi.fn().mockReturnValue([]),
350
+ };
351
+
352
+ generator.setWorkspaceContext(mockWorkspace);
353
+ const deps = generator.getDependencyRepos();
354
+
355
+ expect(deps).toEqual([]);
356
+ });
357
+ });
358
+
359
+ describe('analyzeFileStructure', () => {
360
+ it('infers API service from routes directory', () => {
361
+ fs.writeFileSync(
362
+ path.join(tempDir, 'package.json'),
363
+ JSON.stringify({ name: 'api-service' })
364
+ );
365
+ fs.mkdirSync(path.join(tempDir, 'src', 'routes'), { recursive: true });
366
+ fs.writeFileSync(path.join(tempDir, 'src', 'routes', 'users.js'), '');
367
+
368
+ const analysis = generator.analyzeFileStructure();
369
+
370
+ expect(analysis.type).toBe('api');
371
+ expect(analysis.indicators).toContain('routes directory');
372
+ });
373
+
374
+ it('infers CLI tool from bin directory', () => {
375
+ fs.writeFileSync(
376
+ path.join(tempDir, 'package.json'),
377
+ JSON.stringify({
378
+ name: 'my-cli',
379
+ bin: { 'my-cli': './bin/cli.js' },
380
+ })
381
+ );
382
+ fs.mkdirSync(path.join(tempDir, 'bin'), { recursive: true });
383
+ fs.writeFileSync(path.join(tempDir, 'bin', 'cli.js'), '');
384
+
385
+ const analysis = generator.analyzeFileStructure();
386
+
387
+ expect(analysis.type).toBe('cli');
388
+ expect(analysis.indicators).toContain('bin entry');
389
+ });
390
+
391
+ it('infers library from src with index exports', () => {
392
+ fs.writeFileSync(
393
+ path.join(tempDir, 'package.json'),
394
+ JSON.stringify({ name: 'my-lib' })
395
+ );
396
+ fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
397
+ fs.writeFileSync(path.join(tempDir, 'src', 'index.js'), 'export const lib = {};');
398
+
399
+ const analysis = generator.analyzeFileStructure();
400
+
401
+ expect(analysis.type).toBe('library');
402
+ });
403
+
404
+ it('infers web app from components directory', () => {
405
+ fs.writeFileSync(
406
+ path.join(tempDir, 'package.json'),
407
+ JSON.stringify({ name: 'web-app' })
408
+ );
409
+ fs.mkdirSync(path.join(tempDir, 'src', 'components'), { recursive: true });
410
+ fs.writeFileSync(path.join(tempDir, 'src', 'components', 'App.jsx'), '');
411
+
412
+ const analysis = generator.analyzeFileStructure();
413
+
414
+ expect(analysis.type).toBe('web-app');
415
+ expect(analysis.indicators).toContain('components directory');
416
+ });
417
+
418
+ it('returns unknown type for unrecognized structure', () => {
419
+ fs.writeFileSync(
420
+ path.join(tempDir, 'package.json'),
421
+ JSON.stringify({ name: 'mystery' })
422
+ );
423
+
424
+ const analysis = generator.analyzeFileStructure();
425
+
426
+ expect(analysis.type).toBe('unknown');
427
+ });
428
+ });
429
+
430
+ describe('generate', () => {
431
+ it('generates markdown summary', () => {
432
+ fs.writeFileSync(
433
+ path.join(tempDir, 'package.json'),
434
+ JSON.stringify({
435
+ name: 'user-service',
436
+ description: 'Manages user accounts',
437
+ version: '2.1.0',
438
+ })
439
+ );
440
+ fs.writeFileSync(
441
+ path.join(tempDir, 'index.js'),
442
+ `
443
+ module.exports = { createUser, getUser };
444
+ function createUser() {}
445
+ function getUser() {}
446
+ `
447
+ );
448
+ fs.mkdirSync(path.join(tempDir, 'src', 'routes'), { recursive: true });
449
+
450
+ const summary = generator.generate();
451
+
452
+ expect(summary).toContain('# user-service');
453
+ expect(summary).toContain('Manages user accounts');
454
+ expect(summary).toContain('## Overview');
455
+ expect(summary).toContain('## Entry Points');
456
+ expect(summary).toContain('## Exports');
457
+ });
458
+
459
+ it('includes consumers section when consumers exist', () => {
460
+ fs.writeFileSync(
461
+ path.join(tempDir, 'package.json'),
462
+ JSON.stringify({ name: '@myorg/shared' })
463
+ );
464
+
465
+ const mockWorkspace = {
466
+ getDependents: vi.fn().mockReturnValue(['api', 'web']),
467
+ getDependencies: vi.fn().mockReturnValue([]),
468
+ };
469
+
470
+ generator.setWorkspaceContext(mockWorkspace);
471
+ const summary = generator.generate();
472
+
473
+ expect(summary).toContain('## Consumers');
474
+ expect(summary).toContain('api');
475
+ expect(summary).toContain('web');
476
+ });
477
+
478
+ it('includes dependencies section when dependencies exist', () => {
479
+ fs.writeFileSync(
480
+ path.join(tempDir, 'package.json'),
481
+ JSON.stringify({ name: '@myorg/api' })
482
+ );
483
+
484
+ const mockWorkspace = {
485
+ getDependents: vi.fn().mockReturnValue([]),
486
+ getDependencies: vi.fn().mockReturnValue(['core', 'utils']),
487
+ };
488
+
489
+ generator.setWorkspaceContext(mockWorkspace);
490
+ const summary = generator.generate();
491
+
492
+ expect(summary).toContain('## Dependencies');
493
+ expect(summary).toContain('core');
494
+ expect(summary).toContain('utils');
495
+ });
496
+
497
+ it('handles repo with minimal info', () => {
498
+ fs.writeFileSync(
499
+ path.join(tempDir, 'package.json'),
500
+ JSON.stringify({ name: 'minimal' })
501
+ );
502
+
503
+ const summary = generator.generate();
504
+
505
+ expect(summary).toContain('# minimal');
506
+ // Should not throw and should produce valid markdown
507
+ expect(summary).toMatch(/^# /);
508
+ });
509
+
510
+ it('skips empty sections', () => {
511
+ fs.writeFileSync(
512
+ path.join(tempDir, 'package.json'),
513
+ JSON.stringify({ name: 'empty-sections' })
514
+ );
515
+
516
+ const summary = generator.generate();
517
+
518
+ // Should not have Consumers section if no consumers
519
+ expect(summary).not.toContain('## Consumers');
520
+ // Should not have Dependencies section if no workspace deps
521
+ expect(summary).not.toContain('## Dependencies');
522
+ });
523
+ });
524
+
525
+ describe('write', () => {
526
+ it('writes summary to disk', () => {
527
+ fs.writeFileSync(
528
+ path.join(tempDir, 'package.json'),
529
+ JSON.stringify({ name: 'writeable' })
530
+ );
531
+
532
+ generator.write();
533
+
534
+ const outputPath = path.join(tempDir, 'SERVICE-SUMMARY.md');
535
+ expect(fs.existsSync(outputPath)).toBe(true);
536
+
537
+ const content = fs.readFileSync(outputPath, 'utf-8');
538
+ expect(content).toContain('# writeable');
539
+ });
540
+
541
+ it('accepts custom output path', () => {
542
+ fs.writeFileSync(
543
+ path.join(tempDir, 'package.json'),
544
+ JSON.stringify({ name: 'custom-path' })
545
+ );
546
+ fs.mkdirSync(path.join(tempDir, 'docs'), { recursive: true });
547
+
548
+ const customPath = path.join(tempDir, 'docs', 'SUMMARY.md');
549
+ generator.write(customPath);
550
+
551
+ expect(fs.existsSync(customPath)).toBe(true);
552
+ });
553
+ });
554
+ });
555
+
556
+ describe('generateServiceSummary', () => {
557
+ let tempDir;
558
+
559
+ beforeEach(() => {
560
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'service-summary-fn-test-'));
561
+ });
562
+
563
+ afterEach(() => {
564
+ fs.rmSync(tempDir, { recursive: true, force: true });
565
+ });
566
+
567
+ it('is a convenience function that generates summary for a path', () => {
568
+ fs.writeFileSync(
569
+ path.join(tempDir, 'package.json'),
570
+ JSON.stringify({
571
+ name: 'quick-service',
572
+ description: 'Quick generation test',
573
+ })
574
+ );
575
+
576
+ const summary = generateServiceSummary(tempDir);
577
+
578
+ expect(summary).toContain('# quick-service');
579
+ expect(summary).toContain('Quick generation test');
580
+ });
581
+ });
582
+
583
+ describe('createServiceSummaryGenerator', () => {
584
+ let tempDir;
585
+
586
+ beforeEach(() => {
587
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'service-summary-factory-test-'));
588
+ });
589
+
590
+ afterEach(() => {
591
+ fs.rmSync(tempDir, { recursive: true, force: true });
592
+ });
593
+
594
+ it('creates a generator with methods', () => {
595
+ const gen = createServiceSummaryGenerator(tempDir);
596
+
597
+ expect(gen.generate).toBeDefined();
598
+ expect(gen.write).toBeDefined();
599
+ expect(gen.extractPurpose).toBeDefined();
600
+ expect(gen.identifyMainEntryPoints).toBeDefined();
601
+ expect(gen.listExports).toBeDefined();
602
+ expect(gen.getConsumerRepos).toBeDefined();
603
+ expect(gen.getDependencyRepos).toBeDefined();
604
+ expect(gen.analyzeFileStructure).toBeDefined();
605
+ expect(gen.setWorkspaceContext).toBeDefined();
606
+ });
607
+
608
+ it('generates summary through factory instance', () => {
609
+ fs.writeFileSync(
610
+ path.join(tempDir, 'package.json'),
611
+ JSON.stringify({ name: 'factory-service' })
612
+ );
613
+
614
+ const gen = createServiceSummaryGenerator(tempDir);
615
+ const summary = gen.generate();
616
+
617
+ expect(summary).toContain('# factory-service');
618
+ });
619
+ });