tlc-claude-code 1.3.0 → 1.4.1

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 (105) 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/WorkspaceDocsPane.js +0 -16
  14. package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
  15. package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
  16. package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
  17. package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
  18. package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
  19. package/package.json +1 -1
  20. package/server/lib/access-control-doc.js +541 -0
  21. package/server/lib/access-control-doc.test.js +672 -0
  22. package/server/lib/adr-generator.js +423 -0
  23. package/server/lib/adr-generator.test.js +586 -0
  24. package/server/lib/agent-progress-monitor.js +223 -0
  25. package/server/lib/agent-progress-monitor.test.js +202 -0
  26. package/server/lib/audit-attribution.js +191 -0
  27. package/server/lib/audit-attribution.test.js +359 -0
  28. package/server/lib/audit-classifier.js +202 -0
  29. package/server/lib/audit-classifier.test.js +209 -0
  30. package/server/lib/audit-command.js +275 -0
  31. package/server/lib/audit-command.test.js +325 -0
  32. package/server/lib/audit-exporter.js +380 -0
  33. package/server/lib/audit-exporter.test.js +464 -0
  34. package/server/lib/audit-logger.js +236 -0
  35. package/server/lib/audit-logger.test.js +364 -0
  36. package/server/lib/audit-query.js +257 -0
  37. package/server/lib/audit-query.test.js +352 -0
  38. package/server/lib/audit-storage.js +269 -0
  39. package/server/lib/audit-storage.test.js +272 -0
  40. package/server/lib/bulk-repo-init.js +342 -0
  41. package/server/lib/bulk-repo-init.test.js +388 -0
  42. package/server/lib/compliance-checklist.js +866 -0
  43. package/server/lib/compliance-checklist.test.js +476 -0
  44. package/server/lib/compliance-command.js +616 -0
  45. package/server/lib/compliance-command.test.js +551 -0
  46. package/server/lib/compliance-reporter.js +692 -0
  47. package/server/lib/compliance-reporter.test.js +707 -0
  48. package/server/lib/data-flow-doc.js +665 -0
  49. package/server/lib/data-flow-doc.test.js +659 -0
  50. package/server/lib/ephemeral-storage.js +249 -0
  51. package/server/lib/ephemeral-storage.test.js +254 -0
  52. package/server/lib/evidence-collector.js +627 -0
  53. package/server/lib/evidence-collector.test.js +901 -0
  54. package/server/lib/flow-diagram-generator.js +474 -0
  55. package/server/lib/flow-diagram-generator.test.js +446 -0
  56. package/server/lib/idp-manager.js +626 -0
  57. package/server/lib/idp-manager.test.js +587 -0
  58. package/server/lib/memory-exclusion.js +326 -0
  59. package/server/lib/memory-exclusion.test.js +241 -0
  60. package/server/lib/mfa-handler.js +452 -0
  61. package/server/lib/mfa-handler.test.js +490 -0
  62. package/server/lib/oauth-flow.js +375 -0
  63. package/server/lib/oauth-flow.test.js +487 -0
  64. package/server/lib/oauth-registry.js +190 -0
  65. package/server/lib/oauth-registry.test.js +306 -0
  66. package/server/lib/readme-generator.js +490 -0
  67. package/server/lib/readme-generator.test.js +493 -0
  68. package/server/lib/repo-dependency-tracker.js +261 -0
  69. package/server/lib/repo-dependency-tracker.test.js +350 -0
  70. package/server/lib/retention-policy.js +281 -0
  71. package/server/lib/retention-policy.test.js +486 -0
  72. package/server/lib/role-mapper.js +236 -0
  73. package/server/lib/role-mapper.test.js +395 -0
  74. package/server/lib/saml-provider.js +765 -0
  75. package/server/lib/saml-provider.test.js +643 -0
  76. package/server/lib/security-policy-generator.js +682 -0
  77. package/server/lib/security-policy-generator.test.js +544 -0
  78. package/server/lib/sensitive-detector.js +112 -0
  79. package/server/lib/sensitive-detector.test.js +209 -0
  80. package/server/lib/service-interaction-diagram.js +700 -0
  81. package/server/lib/service-interaction-diagram.test.js +638 -0
  82. package/server/lib/service-summary.js +553 -0
  83. package/server/lib/service-summary.test.js +619 -0
  84. package/server/lib/session-purge.js +460 -0
  85. package/server/lib/session-purge.test.js +312 -0
  86. package/server/lib/sso-command.js +544 -0
  87. package/server/lib/sso-command.test.js +552 -0
  88. package/server/lib/sso-session.js +492 -0
  89. package/server/lib/sso-session.test.js +670 -0
  90. package/server/lib/workspace-command.js +249 -0
  91. package/server/lib/workspace-command.test.js +264 -0
  92. package/server/lib/workspace-config.js +270 -0
  93. package/server/lib/workspace-config.test.js +312 -0
  94. package/server/lib/workspace-docs-command.js +547 -0
  95. package/server/lib/workspace-docs-command.test.js +692 -0
  96. package/server/lib/workspace-memory.js +451 -0
  97. package/server/lib/workspace-memory.test.js +403 -0
  98. package/server/lib/workspace-scanner.js +452 -0
  99. package/server/lib/workspace-scanner.test.js +677 -0
  100. package/server/lib/workspace-test-runner.js +315 -0
  101. package/server/lib/workspace-test-runner.test.js +294 -0
  102. package/server/lib/zero-retention-command.js +439 -0
  103. package/server/lib/zero-retention-command.test.js +448 -0
  104. package/server/lib/zero-retention.js +322 -0
  105. package/server/lib/zero-retention.test.js +258 -0
@@ -0,0 +1,493 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+
6
+ const { ReadmeGenerator, generateReadme, createReadmeGenerator } = await import('./readme-generator.js');
7
+
8
+ describe('ReadmeGenerator', () => {
9
+ let tempDir;
10
+ let generator;
11
+
12
+ beforeEach(() => {
13
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'readme-gen-test-'));
14
+ generator = new ReadmeGenerator(tempDir);
15
+ });
16
+
17
+ afterEach(() => {
18
+ fs.rmSync(tempDir, { recursive: true, force: true });
19
+ });
20
+
21
+ describe('extractProjectInfo', () => {
22
+ it('extracts project name and description from package.json', () => {
23
+ fs.writeFileSync(
24
+ path.join(tempDir, 'package.json'),
25
+ JSON.stringify({
26
+ name: 'my-awesome-project',
27
+ description: 'A project that does awesome things',
28
+ })
29
+ );
30
+
31
+ const info = generator.extractProjectInfo();
32
+
33
+ expect(info.name).toBe('my-awesome-project');
34
+ expect(info.description).toBe('A project that does awesome things');
35
+ });
36
+
37
+ it('handles scoped package names', () => {
38
+ fs.writeFileSync(
39
+ path.join(tempDir, 'package.json'),
40
+ JSON.stringify({
41
+ name: '@myorg/my-package',
42
+ description: 'Scoped package',
43
+ })
44
+ );
45
+
46
+ const info = generator.extractProjectInfo();
47
+
48
+ expect(info.name).toBe('@myorg/my-package');
49
+ });
50
+
51
+ it('returns defaults when package.json is missing', () => {
52
+ const info = generator.extractProjectInfo();
53
+
54
+ expect(info.name).toBe(path.basename(tempDir));
55
+ expect(info.description).toBe('');
56
+ });
57
+
58
+ it('handles package.json with missing fields', () => {
59
+ fs.writeFileSync(
60
+ path.join(tempDir, 'package.json'),
61
+ JSON.stringify({ version: '1.0.0' })
62
+ );
63
+
64
+ const info = generator.extractProjectInfo();
65
+
66
+ expect(info.name).toBe(path.basename(tempDir));
67
+ expect(info.description).toBe('');
68
+ });
69
+ });
70
+
71
+ describe('extractScripts', () => {
72
+ it('detects and documents scripts (build, test, start)', () => {
73
+ fs.writeFileSync(
74
+ path.join(tempDir, 'package.json'),
75
+ JSON.stringify({
76
+ name: 'test-project',
77
+ scripts: {
78
+ build: 'tsc',
79
+ test: 'vitest',
80
+ start: 'node dist/index.js',
81
+ lint: 'eslint .',
82
+ },
83
+ })
84
+ );
85
+
86
+ const scripts = generator.extractScripts();
87
+
88
+ expect(scripts).toContainEqual({ name: 'build', command: 'tsc' });
89
+ expect(scripts).toContainEqual({ name: 'test', command: 'vitest' });
90
+ expect(scripts).toContainEqual({ name: 'start', command: 'node dist/index.js' });
91
+ expect(scripts).toContainEqual({ name: 'lint', command: 'eslint .' });
92
+ });
93
+
94
+ it('returns empty array when no scripts', () => {
95
+ fs.writeFileSync(
96
+ path.join(tempDir, 'package.json'),
97
+ JSON.stringify({ name: 'no-scripts' })
98
+ );
99
+
100
+ const scripts = generator.extractScripts();
101
+
102
+ expect(scripts).toEqual([]);
103
+ });
104
+
105
+ it('returns empty array when package.json is missing', () => {
106
+ const scripts = generator.extractScripts();
107
+
108
+ expect(scripts).toEqual([]);
109
+ });
110
+ });
111
+
112
+ describe('extractDependencies', () => {
113
+ it('lists runtime dependencies', () => {
114
+ fs.writeFileSync(
115
+ path.join(tempDir, 'package.json'),
116
+ JSON.stringify({
117
+ name: 'dep-test',
118
+ dependencies: {
119
+ express: '^4.18.0',
120
+ lodash: '^4.17.21',
121
+ },
122
+ })
123
+ );
124
+
125
+ const deps = generator.extractDependencies();
126
+
127
+ expect(deps.runtime).toContainEqual({ name: 'express', version: '^4.18.0' });
128
+ expect(deps.runtime).toContainEqual({ name: 'lodash', version: '^4.17.21' });
129
+ });
130
+
131
+ it('lists dev dependencies', () => {
132
+ fs.writeFileSync(
133
+ path.join(tempDir, 'package.json'),
134
+ JSON.stringify({
135
+ name: 'dev-dep-test',
136
+ devDependencies: {
137
+ vitest: '^1.0.0',
138
+ typescript: '^5.0.0',
139
+ },
140
+ })
141
+ );
142
+
143
+ const deps = generator.extractDependencies();
144
+
145
+ expect(deps.dev).toContainEqual({ name: 'vitest', version: '^1.0.0' });
146
+ expect(deps.dev).toContainEqual({ name: 'typescript', version: '^5.0.0' });
147
+ });
148
+
149
+ it('returns empty arrays when no dependencies', () => {
150
+ fs.writeFileSync(
151
+ path.join(tempDir, 'package.json'),
152
+ JSON.stringify({ name: 'no-deps' })
153
+ );
154
+
155
+ const deps = generator.extractDependencies();
156
+
157
+ expect(deps.runtime).toEqual([]);
158
+ expect(deps.dev).toEqual([]);
159
+ });
160
+ });
161
+
162
+ describe('extractEnvVars', () => {
163
+ it('documents environment variables from .env.example', () => {
164
+ fs.writeFileSync(
165
+ path.join(tempDir, '.env.example'),
166
+ `# Database configuration
167
+ DATABASE_URL=postgres://localhost:5432/mydb
168
+ # API keys
169
+ API_KEY=your-api-key-here
170
+ SECRET_TOKEN=
171
+ PORT=3000
172
+ `
173
+ );
174
+
175
+ const envVars = generator.extractEnvVars();
176
+
177
+ expect(envVars).toContainEqual({
178
+ name: 'DATABASE_URL',
179
+ example: 'postgres://localhost:5432/mydb',
180
+ comment: 'Database configuration',
181
+ });
182
+ expect(envVars).toContainEqual({
183
+ name: 'API_KEY',
184
+ example: 'your-api-key-here',
185
+ comment: 'API keys',
186
+ });
187
+ expect(envVars).toContainEqual({
188
+ name: 'SECRET_TOKEN',
189
+ example: '',
190
+ comment: '',
191
+ });
192
+ expect(envVars).toContainEqual({
193
+ name: 'PORT',
194
+ example: '3000',
195
+ comment: '',
196
+ });
197
+ });
198
+
199
+ it('returns empty array when .env.example is missing', () => {
200
+ const envVars = generator.extractEnvVars();
201
+
202
+ expect(envVars).toEqual([]);
203
+ });
204
+
205
+ it('handles .env.example with no variables', () => {
206
+ fs.writeFileSync(
207
+ path.join(tempDir, '.env.example'),
208
+ '# Just comments\n# No actual variables\n'
209
+ );
210
+
211
+ const envVars = generator.extractEnvVars();
212
+
213
+ expect(envVars).toEqual([]);
214
+ });
215
+ });
216
+
217
+ describe('detectApiEndpoints', () => {
218
+ it('documents API endpoints if detected', () => {
219
+ // Create a simple route file
220
+ fs.mkdirSync(path.join(tempDir, 'src', 'routes'), { recursive: true });
221
+ fs.writeFileSync(
222
+ path.join(tempDir, 'src', 'routes', 'users.js'),
223
+ `
224
+ const express = require('express');
225
+ const router = express.Router();
226
+
227
+ router.get('/users', (req, res) => {
228
+ res.json([]);
229
+ });
230
+
231
+ router.post('/users', (req, res) => {
232
+ res.status(201).json({ id: 1 });
233
+ });
234
+
235
+ router.get('/users/:id', (req, res) => {
236
+ res.json({ id: req.params.id });
237
+ });
238
+
239
+ module.exports = router;
240
+ `
241
+ );
242
+
243
+ const endpoints = generator.detectApiEndpoints();
244
+
245
+ expect(endpoints.length).toBeGreaterThan(0);
246
+ expect(endpoints.some(e => e.method === 'GET' && e.path.includes('users'))).toBe(true);
247
+ expect(endpoints.some(e => e.method === 'POST' && e.path.includes('users'))).toBe(true);
248
+ });
249
+
250
+ it('returns empty array when no routes found', () => {
251
+ const endpoints = generator.detectApiEndpoints();
252
+
253
+ expect(endpoints).toEqual([]);
254
+ });
255
+ });
256
+
257
+ describe('generateInstallationSection', () => {
258
+ it('includes installation instructions', () => {
259
+ fs.writeFileSync(
260
+ path.join(tempDir, 'package.json'),
261
+ JSON.stringify({ name: 'test-project' })
262
+ );
263
+
264
+ const section = generator.generateInstallationSection();
265
+
266
+ expect(section).toContain('npm install');
267
+ expect(section).toContain('Installation');
268
+ });
269
+
270
+ it('includes git clone if repo is specified', () => {
271
+ fs.writeFileSync(
272
+ path.join(tempDir, 'package.json'),
273
+ JSON.stringify({
274
+ name: 'test-project',
275
+ repository: {
276
+ url: 'https://github.com/user/repo.git',
277
+ },
278
+ })
279
+ );
280
+
281
+ const section = generator.generateInstallationSection();
282
+
283
+ expect(section).toContain('git clone');
284
+ });
285
+ });
286
+
287
+ describe('generate', () => {
288
+ it('generates README with project name and description', () => {
289
+ fs.writeFileSync(
290
+ path.join(tempDir, 'package.json'),
291
+ JSON.stringify({
292
+ name: 'my-project',
293
+ description: 'Does wonderful things',
294
+ })
295
+ );
296
+
297
+ const readme = generator.generate();
298
+
299
+ expect(readme).toContain('# my-project');
300
+ expect(readme).toContain('Does wonderful things');
301
+ });
302
+
303
+ it('includes npm scripts documentation', () => {
304
+ fs.writeFileSync(
305
+ path.join(tempDir, 'package.json'),
306
+ JSON.stringify({
307
+ name: 'scripted-project',
308
+ scripts: {
309
+ test: 'vitest',
310
+ build: 'tsc',
311
+ },
312
+ })
313
+ );
314
+
315
+ const readme = generator.generate();
316
+
317
+ expect(readme).toContain('npm run test');
318
+ expect(readme).toContain('npm run build');
319
+ });
320
+
321
+ it('lists key dependencies', () => {
322
+ fs.writeFileSync(
323
+ path.join(tempDir, 'package.json'),
324
+ JSON.stringify({
325
+ name: 'with-deps',
326
+ dependencies: {
327
+ express: '^4.18.0',
328
+ pg: '^8.11.0',
329
+ },
330
+ })
331
+ );
332
+
333
+ const readme = generator.generate();
334
+
335
+ expect(readme).toContain('express');
336
+ expect(readme).toContain('Dependencies');
337
+ });
338
+
339
+ it('documents env vars from .env.example', () => {
340
+ fs.writeFileSync(
341
+ path.join(tempDir, 'package.json'),
342
+ JSON.stringify({ name: 'env-project' })
343
+ );
344
+ fs.writeFileSync(
345
+ path.join(tempDir, '.env.example'),
346
+ 'DATABASE_URL=postgres://localhost/db\nAPI_KEY=your-key\n'
347
+ );
348
+
349
+ const readme = generator.generate();
350
+
351
+ expect(readme).toContain('DATABASE_URL');
352
+ expect(readme).toContain('Environment Variables');
353
+ });
354
+
355
+ it('includes installation section', () => {
356
+ fs.writeFileSync(
357
+ path.join(tempDir, 'package.json'),
358
+ JSON.stringify({ name: 'installable' })
359
+ );
360
+
361
+ const readme = generator.generate();
362
+
363
+ expect(readme).toContain('## Installation');
364
+ expect(readme).toContain('npm install');
365
+ });
366
+
367
+ it('skips sections with no content', () => {
368
+ fs.writeFileSync(
369
+ path.join(tempDir, 'package.json'),
370
+ JSON.stringify({ name: 'minimal-project' })
371
+ );
372
+
373
+ const readme = generator.generate();
374
+
375
+ // Should have title but skip empty sections
376
+ expect(readme).toContain('# minimal-project');
377
+ // Should not have Environment Variables section if no .env.example
378
+ expect(readme).not.toContain('## Environment Variables');
379
+ });
380
+
381
+ it('formats markdown correctly', () => {
382
+ fs.writeFileSync(
383
+ path.join(tempDir, 'package.json'),
384
+ JSON.stringify({
385
+ name: 'formatted-project',
386
+ description: 'Test project',
387
+ scripts: { test: 'vitest' },
388
+ })
389
+ );
390
+
391
+ const readme = generator.generate();
392
+
393
+ // Should have proper markdown structure
394
+ expect(readme).toMatch(/^# formatted-project/);
395
+ expect(readme).toMatch(/## \w+/); // At least one h2 section
396
+ expect(readme).toContain('```'); // Code blocks for commands
397
+ });
398
+ });
399
+
400
+ describe('write', () => {
401
+ it('writes README.md to disk', () => {
402
+ fs.writeFileSync(
403
+ path.join(tempDir, 'package.json'),
404
+ JSON.stringify({ name: 'writeable-project' })
405
+ );
406
+
407
+ generator.write();
408
+
409
+ const readmePath = path.join(tempDir, 'README.md');
410
+ expect(fs.existsSync(readmePath)).toBe(true);
411
+
412
+ const content = fs.readFileSync(readmePath, 'utf-8');
413
+ expect(content).toContain('# writeable-project');
414
+ });
415
+
416
+ it('accepts custom output path', () => {
417
+ fs.writeFileSync(
418
+ path.join(tempDir, 'package.json'),
419
+ JSON.stringify({ name: 'custom-path-project' })
420
+ );
421
+
422
+ const customPath = path.join(tempDir, 'docs', 'README.md');
423
+ fs.mkdirSync(path.join(tempDir, 'docs'), { recursive: true });
424
+
425
+ generator.write(customPath);
426
+
427
+ expect(fs.existsSync(customPath)).toBe(true);
428
+ });
429
+ });
430
+ });
431
+
432
+ describe('generateReadme', () => {
433
+ let tempDir;
434
+
435
+ beforeEach(() => {
436
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'readme-fn-test-'));
437
+ });
438
+
439
+ afterEach(() => {
440
+ fs.rmSync(tempDir, { recursive: true, force: true });
441
+ });
442
+
443
+ it('is a convenience function that generates readme for a path', () => {
444
+ fs.writeFileSync(
445
+ path.join(tempDir, 'package.json'),
446
+ JSON.stringify({
447
+ name: 'convenience-project',
448
+ description: 'Quick generation',
449
+ })
450
+ );
451
+
452
+ const readme = generateReadme(tempDir);
453
+
454
+ expect(readme).toContain('# convenience-project');
455
+ expect(readme).toContain('Quick generation');
456
+ });
457
+ });
458
+
459
+ describe('createReadmeGenerator', () => {
460
+ let tempDir;
461
+
462
+ beforeEach(() => {
463
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'readme-factory-test-'));
464
+ });
465
+
466
+ afterEach(() => {
467
+ fs.rmSync(tempDir, { recursive: true, force: true });
468
+ });
469
+
470
+ it('creates a generator with methods', () => {
471
+ const gen = createReadmeGenerator(tempDir);
472
+
473
+ expect(gen.generate).toBeDefined();
474
+ expect(gen.write).toBeDefined();
475
+ expect(gen.extractProjectInfo).toBeDefined();
476
+ expect(gen.extractScripts).toBeDefined();
477
+ expect(gen.extractDependencies).toBeDefined();
478
+ expect(gen.extractEnvVars).toBeDefined();
479
+ expect(gen.detectApiEndpoints).toBeDefined();
480
+ });
481
+
482
+ it('generates readme through factory instance', () => {
483
+ fs.writeFileSync(
484
+ path.join(tempDir, 'package.json'),
485
+ JSON.stringify({ name: 'factory-project' })
486
+ );
487
+
488
+ const gen = createReadmeGenerator(tempDir);
489
+ const readme = gen.generate();
490
+
491
+ expect(readme).toContain('# factory-project');
492
+ });
493
+ });