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,403 @@
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 { WorkspaceMemory } = await import('./workspace-memory.js');
7
+
8
+ describe('WorkspaceMemory', () => {
9
+ let tempDir;
10
+ let workspaceRoot;
11
+ let repoA;
12
+ let repoB;
13
+
14
+ beforeEach(() => {
15
+ // Create temp workspace with two repos
16
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-memory-test-'));
17
+ workspaceRoot = tempDir;
18
+
19
+ // Create repo-a
20
+ repoA = path.join(tempDir, 'repo-a');
21
+ fs.mkdirSync(repoA);
22
+ fs.writeFileSync(path.join(repoA, 'package.json'), JSON.stringify({ name: 'repo-a' }));
23
+
24
+ // Create repo-b
25
+ repoB = path.join(tempDir, 'repo-b');
26
+ fs.mkdirSync(repoB);
27
+ fs.writeFileSync(path.join(repoB, 'package.json'), JSON.stringify({ name: 'repo-b' }));
28
+
29
+ // Create workspace config
30
+ fs.writeFileSync(
31
+ path.join(tempDir, '.tlc-workspace.json'),
32
+ JSON.stringify({
33
+ root: tempDir,
34
+ repos: ['repo-a', 'repo-b'],
35
+ })
36
+ );
37
+ });
38
+
39
+ afterEach(() => {
40
+ fs.rmSync(tempDir, { recursive: true, force: true });
41
+ });
42
+
43
+ describe('initialization', () => {
44
+ it('creates workspace memory directory', () => {
45
+ const memory = new WorkspaceMemory(workspaceRoot);
46
+ memory.init();
47
+
48
+ const memoryDir = path.join(workspaceRoot, '.tlc-workspace', 'memory');
49
+ expect(fs.existsSync(memoryDir)).toBe(true);
50
+ });
51
+
52
+ it('creates decisions subdirectory in workspace memory', () => {
53
+ const memory = new WorkspaceMemory(workspaceRoot);
54
+ memory.init();
55
+
56
+ const decisionsDir = path.join(workspaceRoot, '.tlc-workspace', 'memory', 'decisions');
57
+ expect(fs.existsSync(decisionsDir)).toBe(true);
58
+ });
59
+
60
+ it('creates gotchas subdirectory in workspace memory', () => {
61
+ const memory = new WorkspaceMemory(workspaceRoot);
62
+ memory.init();
63
+
64
+ const gotchasDir = path.join(workspaceRoot, '.tlc-workspace', 'memory', 'gotchas');
65
+ expect(fs.existsSync(gotchasDir)).toBe(true);
66
+ });
67
+ });
68
+
69
+ describe('workspace-level memory', () => {
70
+ it('writes to workspace-level memory', async () => {
71
+ const memory = new WorkspaceMemory(workspaceRoot);
72
+ memory.init();
73
+
74
+ await memory.writeDecision({
75
+ title: 'Use ESM modules across all repos',
76
+ reasoning: 'Consistent module system',
77
+ level: 'workspace',
78
+ });
79
+
80
+ const decisionsDir = path.join(workspaceRoot, '.tlc-workspace', 'memory', 'decisions');
81
+ const files = fs.readdirSync(decisionsDir);
82
+ expect(files.length).toBe(1);
83
+ expect(files[0]).toMatch(/use-esm-modules-across-all-repos\.md$/);
84
+ });
85
+
86
+ it('reads from workspace-level memory', async () => {
87
+ const memory = new WorkspaceMemory(workspaceRoot);
88
+ memory.init();
89
+
90
+ await memory.writeDecision({
91
+ title: 'Use TypeScript everywhere',
92
+ reasoning: 'Type safety',
93
+ level: 'workspace',
94
+ });
95
+
96
+ const decisions = await memory.getDecisions('workspace');
97
+ expect(decisions.length).toBe(1);
98
+ expect(decisions[0].title).toBe('Use TypeScript everywhere');
99
+ });
100
+
101
+ it('workspace memory visible to all repos', async () => {
102
+ const memory = new WorkspaceMemory(workspaceRoot);
103
+ memory.init();
104
+
105
+ // Write workspace-level decision
106
+ await memory.writeDecision({
107
+ title: 'Shared API conventions',
108
+ reasoning: 'Consistency across services',
109
+ level: 'workspace',
110
+ });
111
+
112
+ // Read from repo-a context
113
+ const decisionsA = await memory.getDecisions('all', repoA);
114
+ expect(decisionsA.some(d => d.title === 'Shared API conventions')).toBe(true);
115
+
116
+ // Read from repo-b context
117
+ const decisionsB = await memory.getDecisions('all', repoB);
118
+ expect(decisionsB.some(d => d.title === 'Shared API conventions')).toBe(true);
119
+ });
120
+ });
121
+
122
+ describe('repo-level memory', () => {
123
+ it('writes to repo-level memory', async () => {
124
+ const memory = new WorkspaceMemory(workspaceRoot);
125
+ memory.init();
126
+
127
+ await memory.writeDecision({
128
+ title: 'Use Jest for testing',
129
+ reasoning: 'Already configured',
130
+ level: 'repo',
131
+ repoPath: repoA,
132
+ });
133
+
134
+ const decisionsDir = path.join(repoA, '.tlc', 'memory', 'team', 'decisions');
135
+ expect(fs.existsSync(decisionsDir)).toBe(true);
136
+ const files = fs.readdirSync(decisionsDir);
137
+ expect(files.length).toBe(1);
138
+ });
139
+
140
+ it('reads from repo-level memory', async () => {
141
+ const memory = new WorkspaceMemory(workspaceRoot);
142
+ memory.init();
143
+
144
+ await memory.writeDecision({
145
+ title: 'Custom logging format',
146
+ reasoning: 'Repo-specific needs',
147
+ level: 'repo',
148
+ repoPath: repoA,
149
+ });
150
+
151
+ const decisions = await memory.getDecisions('repo', repoA);
152
+ expect(decisions.length).toBe(1);
153
+ expect(decisions[0].title).toBe('Custom logging format');
154
+ });
155
+
156
+ it('repo memory only visible to that repo', async () => {
157
+ const memory = new WorkspaceMemory(workspaceRoot);
158
+ memory.init();
159
+
160
+ // Write repo-a specific decision
161
+ await memory.writeDecision({
162
+ title: 'RepoA-only config',
163
+ reasoning: 'Only for repo-a',
164
+ level: 'repo',
165
+ repoPath: repoA,
166
+ });
167
+
168
+ // Read from repo-a - should see it
169
+ const decisionsA = await memory.getDecisions('repo', repoA);
170
+ expect(decisionsA.some(d => d.title === 'RepoA-only config')).toBe(true);
171
+
172
+ // Read from repo-b - should NOT see it
173
+ const decisionsB = await memory.getDecisions('repo', repoB);
174
+ expect(decisionsB.some(d => d.title === 'RepoA-only config')).toBe(false);
175
+ });
176
+ });
177
+
178
+ describe('reads from both levels', () => {
179
+ it('returns both workspace and repo decisions with getDecisions("all")', async () => {
180
+ const memory = new WorkspaceMemory(workspaceRoot);
181
+ memory.init();
182
+
183
+ // Write workspace decision
184
+ await memory.writeDecision({
185
+ title: 'Workspace-wide rule',
186
+ reasoning: 'Applies everywhere',
187
+ level: 'workspace',
188
+ });
189
+
190
+ // Write repo decision
191
+ await memory.writeDecision({
192
+ title: 'Repo-specific rule',
193
+ reasoning: 'Only repo-a',
194
+ level: 'repo',
195
+ repoPath: repoA,
196
+ });
197
+
198
+ // Read all from repo-a context
199
+ const decisions = await memory.getDecisions('all', repoA);
200
+ expect(decisions.length).toBe(2);
201
+ expect(decisions.some(d => d.title === 'Workspace-wide rule')).toBe(true);
202
+ expect(decisions.some(d => d.title === 'Repo-specific rule')).toBe(true);
203
+ });
204
+ });
205
+
206
+ describe('conflict resolution', () => {
207
+ it('repo overrides workspace for same key', async () => {
208
+ const memory = new WorkspaceMemory(workspaceRoot);
209
+ memory.init();
210
+
211
+ // Write workspace decision
212
+ await memory.writeDecision({
213
+ title: 'Testing framework',
214
+ reasoning: 'Use Vitest',
215
+ level: 'workspace',
216
+ });
217
+
218
+ // Write repo decision with same title (override)
219
+ await memory.writeDecision({
220
+ title: 'Testing framework',
221
+ reasoning: 'Use Jest instead',
222
+ level: 'repo',
223
+ repoPath: repoA,
224
+ });
225
+
226
+ // Get resolved decisions - repo should win
227
+ const resolved = await memory.getResolvedDecisions(repoA);
228
+ const testingDecision = resolved.find(d => d.title === 'Testing framework');
229
+ expect(testingDecision).toBeDefined();
230
+ expect(testingDecision.reasoning).toContain('Jest');
231
+ expect(testingDecision.level).toBe('repo');
232
+ });
233
+
234
+ it('workspace applies when no repo override', async () => {
235
+ const memory = new WorkspaceMemory(workspaceRoot);
236
+ memory.init();
237
+
238
+ // Write workspace decision
239
+ await memory.writeDecision({
240
+ title: 'Code style',
241
+ reasoning: 'Use Prettier',
242
+ level: 'workspace',
243
+ });
244
+
245
+ // Get resolved decisions from repo-a (no override)
246
+ const resolved = await memory.getResolvedDecisions(repoA);
247
+ const styleDecision = resolved.find(d => d.title === 'Code style');
248
+ expect(styleDecision).toBeDefined();
249
+ expect(styleDecision.reasoning).toContain('Prettier');
250
+ expect(styleDecision.level).toBe('workspace');
251
+ });
252
+ });
253
+
254
+ describe('memory search', () => {
255
+ it('memory search spans workspace', async () => {
256
+ const memory = new WorkspaceMemory(workspaceRoot);
257
+ memory.init();
258
+
259
+ // Write workspace decision
260
+ await memory.writeDecision({
261
+ title: 'API versioning strategy',
262
+ reasoning: 'Use URL path versioning /v1/, /v2/',
263
+ level: 'workspace',
264
+ });
265
+
266
+ // Write repo-a decision
267
+ await memory.writeDecision({
268
+ title: 'Database connection pooling',
269
+ reasoning: 'Pool size of 10',
270
+ level: 'repo',
271
+ repoPath: repoA,
272
+ });
273
+
274
+ // Write repo-b decision
275
+ await memory.writeDecision({
276
+ title: 'Cache expiration',
277
+ reasoning: 'TTL of 5 minutes',
278
+ level: 'repo',
279
+ repoPath: repoB,
280
+ });
281
+
282
+ // Search across all
283
+ const results = await memory.search('versioning');
284
+ expect(results.length).toBe(1);
285
+ expect(results[0].title).toBe('API versioning strategy');
286
+
287
+ // Search finds repo-specific content
288
+ const poolResults = await memory.search('pooling');
289
+ expect(poolResults.length).toBe(1);
290
+ expect(poolResults[0].title).toBe('Database connection pooling');
291
+ });
292
+
293
+ it('search returns results from all repos', async () => {
294
+ const memory = new WorkspaceMemory(workspaceRoot);
295
+ memory.init();
296
+
297
+ // Write decisions with common term
298
+ await memory.writeDecision({
299
+ title: 'Config for repo-a',
300
+ reasoning: 'Configuration pattern',
301
+ level: 'repo',
302
+ repoPath: repoA,
303
+ });
304
+
305
+ await memory.writeDecision({
306
+ title: 'Config for repo-b',
307
+ reasoning: 'Configuration pattern',
308
+ level: 'repo',
309
+ repoPath: repoB,
310
+ });
311
+
312
+ const results = await memory.search('config');
313
+ expect(results.length).toBe(2);
314
+ });
315
+ });
316
+
317
+ describe('gotchas', () => {
318
+ it('writes workspace-level gotcha', async () => {
319
+ const memory = new WorkspaceMemory(workspaceRoot);
320
+ memory.init();
321
+
322
+ await memory.writeGotcha({
323
+ title: 'Cross-repo import issue',
324
+ issue: 'Circular dependencies between repos',
325
+ level: 'workspace',
326
+ });
327
+
328
+ const gotchasDir = path.join(workspaceRoot, '.tlc-workspace', 'memory', 'gotchas');
329
+ const files = fs.readdirSync(gotchasDir);
330
+ expect(files.length).toBe(1);
331
+ });
332
+
333
+ it('writes repo-level gotcha', async () => {
334
+ const memory = new WorkspaceMemory(workspaceRoot);
335
+ memory.init();
336
+
337
+ await memory.writeGotcha({
338
+ title: 'Node version mismatch',
339
+ issue: 'Must use Node 18+',
340
+ level: 'repo',
341
+ repoPath: repoA,
342
+ });
343
+
344
+ const gotchasDir = path.join(repoA, '.tlc', 'memory', 'team', 'gotchas');
345
+ expect(fs.existsSync(gotchasDir)).toBe(true);
346
+ const files = fs.readdirSync(gotchasDir);
347
+ expect(files.length).toBe(1);
348
+ });
349
+
350
+ it('reads gotchas from both levels', async () => {
351
+ const memory = new WorkspaceMemory(workspaceRoot);
352
+ memory.init();
353
+
354
+ await memory.writeGotcha({
355
+ title: 'Workspace gotcha',
356
+ issue: 'Shared issue',
357
+ level: 'workspace',
358
+ });
359
+
360
+ await memory.writeGotcha({
361
+ title: 'Repo gotcha',
362
+ issue: 'Repo-specific issue',
363
+ level: 'repo',
364
+ repoPath: repoA,
365
+ });
366
+
367
+ const gotchas = await memory.getGotchas('all', repoA);
368
+ expect(gotchas.length).toBe(2);
369
+ });
370
+ });
371
+
372
+ describe('sync decisions', () => {
373
+ it('syncs decisions from workspace to repo', async () => {
374
+ const memory = new WorkspaceMemory(workspaceRoot);
375
+ memory.init();
376
+
377
+ // Write workspace decision
378
+ await memory.writeDecision({
379
+ title: 'Sync test decision',
380
+ reasoning: 'Testing sync',
381
+ level: 'workspace',
382
+ });
383
+
384
+ // Sync to repo-a
385
+ await memory.syncToRepo(repoA);
386
+
387
+ // Repo-a should be able to read workspace decisions in its context
388
+ const decisions = await memory.getDecisions('all', repoA);
389
+ expect(decisions.some(d => d.title === 'Sync test decision')).toBe(true);
390
+ });
391
+ });
392
+
393
+ describe('getWorkspaceConfig', () => {
394
+ it('loads workspace config', () => {
395
+ const memory = new WorkspaceMemory(workspaceRoot);
396
+
397
+ const config = memory.getWorkspaceConfig();
398
+ expect(config).toBeDefined();
399
+ expect(config.repos).toContain('repo-a');
400
+ expect(config.repos).toContain('repo-b');
401
+ });
402
+ });
403
+ });