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,249 @@
1
+ /**
2
+ * Ephemeral Storage - In-memory storage that never touches disk
3
+ *
4
+ * Provides a key-value store that:
5
+ * - Stores data in memory only (no disk persistence)
6
+ * - Auto-expires based on TTL
7
+ * - Provides same API as persistent storage (get, set, delete, clear)
8
+ * - Clears all data on process exit
9
+ * - Supports optional encryption in memory
10
+ */
11
+
12
+ const crypto = require('crypto');
13
+
14
+ /**
15
+ * Simple encryption utilities for in-memory data
16
+ */
17
+ class MemoryEncryption {
18
+ constructor() {
19
+ // Generate a random key for this session (not persisted)
20
+ this.key = crypto.randomBytes(32);
21
+ this.algorithm = 'aes-256-gcm';
22
+ }
23
+
24
+ encrypt(data) {
25
+ const iv = crypto.randomBytes(16);
26
+ const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
27
+
28
+ const serialized = typeof data === 'string' ? data : JSON.stringify(data);
29
+ let encrypted = cipher.update(serialized, 'utf8', 'hex');
30
+ encrypted += cipher.final('hex');
31
+
32
+ const authTag = cipher.getAuthTag();
33
+
34
+ return {
35
+ iv: iv.toString('hex'),
36
+ data: encrypted,
37
+ tag: authTag.toString('hex'),
38
+ isObject: typeof data !== 'string'
39
+ };
40
+ }
41
+
42
+ decrypt(encrypted) {
43
+ const iv = Buffer.from(encrypted.iv, 'hex');
44
+ const authTag = Buffer.from(encrypted.tag, 'hex');
45
+
46
+ const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
47
+ decipher.setAuthTag(authTag);
48
+
49
+ let decrypted = decipher.update(encrypted.data, 'hex', 'utf8');
50
+ decrypted += decipher.final('utf8');
51
+
52
+ return encrypted.isObject ? JSON.parse(decrypted) : decrypted;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Storage entry with optional TTL
58
+ */
59
+ class StorageEntry {
60
+ constructor(value, options = {}) {
61
+ this.value = value;
62
+ this.createdAt = Date.now();
63
+ this.expiresAt = options.ttl ? this.createdAt + options.ttl : null;
64
+ }
65
+
66
+ isExpired() {
67
+ if (!this.expiresAt) return false;
68
+ return Date.now() > this.expiresAt;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * EphemeralStorage - In-memory key-value store
74
+ */
75
+ class EphemeralStorage {
76
+ /**
77
+ * Create an ephemeral storage instance
78
+ * @param {Object} options - Configuration options
79
+ * @param {boolean} options.encrypt - Enable encryption for stored values
80
+ * @param {boolean} options.registerExitHandler - Register cleanup on process exit
81
+ * @param {string} options.basePath - Ignored, for API compatibility only
82
+ */
83
+ constructor(options = {}) {
84
+ this.store = new Map();
85
+ this.encryption = options.encrypt ? new MemoryEncryption() : null;
86
+ this.options = options;
87
+
88
+ if (options.registerExitHandler) {
89
+ this._registerExitHandlers();
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Store a value in memory
95
+ * @param {string} key - The key to store under
96
+ * @param {*} value - The value to store
97
+ * @param {Object} options - Storage options
98
+ * @param {number} options.ttl - Time to live in milliseconds
99
+ */
100
+ set(key, value, options = {}) {
101
+ const storedValue = this.encryption
102
+ ? this.encryption.encrypt(value)
103
+ : value;
104
+
105
+ const entry = new StorageEntry(storedValue, options);
106
+ this.store.set(key, entry);
107
+ }
108
+
109
+ /**
110
+ * Retrieve a value from memory
111
+ * @param {string} key - The key to retrieve
112
+ * @returns {*} The stored value, or null if not found/expired
113
+ */
114
+ get(key) {
115
+ const entry = this.store.get(key);
116
+
117
+ if (!entry) return null;
118
+
119
+ if (entry.isExpired()) {
120
+ this.store.delete(key);
121
+ return null;
122
+ }
123
+
124
+ if (this.encryption) {
125
+ return this.encryption.decrypt(entry.value);
126
+ }
127
+
128
+ return entry.value;
129
+ }
130
+
131
+ /**
132
+ * Get raw stored value (for testing encryption)
133
+ * @param {string} key - The key to retrieve
134
+ * @returns {*} The raw stored value
135
+ */
136
+ getRaw(key) {
137
+ const entry = this.store.get(key);
138
+ if (!entry || entry.isExpired()) return null;
139
+ return entry.value;
140
+ }
141
+
142
+ /**
143
+ * Check if a key exists and is not expired
144
+ * @param {string} key - The key to check
145
+ * @returns {boolean} True if key exists and is valid
146
+ */
147
+ has(key) {
148
+ const entry = this.store.get(key);
149
+ if (!entry) return false;
150
+ if (entry.isExpired()) {
151
+ this.store.delete(key);
152
+ return false;
153
+ }
154
+ return true;
155
+ }
156
+
157
+ /**
158
+ * Delete a specific key
159
+ * @param {string} key - The key to delete
160
+ */
161
+ delete(key) {
162
+ this.store.delete(key);
163
+ }
164
+
165
+ /**
166
+ * Clear all stored data
167
+ */
168
+ clear() {
169
+ this.store.clear();
170
+ }
171
+
172
+ /**
173
+ * Get all keys
174
+ * @returns {string[]} Array of all keys (excluding expired)
175
+ */
176
+ keys() {
177
+ const validKeys = [];
178
+ for (const [key, entry] of this.store) {
179
+ if (!entry.isExpired()) {
180
+ validKeys.push(key);
181
+ } else {
182
+ this.store.delete(key);
183
+ }
184
+ }
185
+ return validKeys;
186
+ }
187
+
188
+ /**
189
+ * Get memory usage statistics
190
+ * @returns {Object} Stats including keyCount, approximateBytes, expiringKeys
191
+ */
192
+ getStats() {
193
+ let approximateBytes = 0;
194
+ let expiringKeys = 0;
195
+ let validKeys = 0;
196
+
197
+ for (const [key, entry] of this.store) {
198
+ if (entry.isExpired()) {
199
+ this.store.delete(key);
200
+ continue;
201
+ }
202
+
203
+ validKeys++;
204
+
205
+ // Approximate size calculation
206
+ approximateBytes += key.length * 2; // UTF-16 string
207
+ const value = entry.value;
208
+ if (typeof value === 'string') {
209
+ approximateBytes += value.length * 2;
210
+ } else if (typeof value === 'object') {
211
+ approximateBytes += JSON.stringify(value).length * 2;
212
+ }
213
+
214
+ if (entry.expiresAt) {
215
+ expiringKeys++;
216
+ }
217
+ }
218
+
219
+ return {
220
+ keyCount: validKeys,
221
+ approximateBytes,
222
+ expiringKeys
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Handle process exit - clear all data
228
+ */
229
+ handleExit() {
230
+ this.clear();
231
+ }
232
+
233
+ /**
234
+ * Register exit handlers for process cleanup
235
+ * @private
236
+ */
237
+ _registerExitHandlers() {
238
+ const exitHandler = () => this.handleExit();
239
+
240
+ process.on('exit', exitHandler);
241
+ process.on('SIGINT', exitHandler);
242
+ process.on('SIGTERM', exitHandler);
243
+ process.on('uncaughtException', exitHandler);
244
+ }
245
+ }
246
+
247
+ module.exports = {
248
+ EphemeralStorage
249
+ };
@@ -0,0 +1,254 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { EphemeralStorage } from './ephemeral-storage.js';
6
+
7
+ describe('EphemeralStorage', () => {
8
+ let storage;
9
+ let testDir;
10
+
11
+ beforeEach(() => {
12
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tlc-ephemeral-test-'));
13
+ storage = new EphemeralStorage();
14
+ vi.useFakeTimers();
15
+ });
16
+
17
+ afterEach(() => {
18
+ storage.clear();
19
+ vi.useRealTimers();
20
+ fs.rmSync(testDir, { recursive: true, force: true });
21
+ });
22
+
23
+ describe('set', () => {
24
+ it('stores value in memory', () => {
25
+ storage.set('key1', 'value1');
26
+
27
+ expect(storage.get('key1')).toBe('value1');
28
+ });
29
+
30
+ it('stores objects in memory', () => {
31
+ const data = { name: 'test', count: 42 };
32
+ storage.set('obj', data);
33
+
34
+ expect(storage.get('obj')).toEqual(data);
35
+ });
36
+ });
37
+
38
+ describe('get', () => {
39
+ it('retrieves stored value', () => {
40
+ storage.set('myKey', 'myValue');
41
+
42
+ const result = storage.get('myKey');
43
+
44
+ expect(result).toBe('myValue');
45
+ });
46
+
47
+ it('returns null for missing key', () => {
48
+ const result = storage.get('nonexistent');
49
+
50
+ expect(result).toBeNull();
51
+ });
52
+ });
53
+
54
+ describe('TTL expiration', () => {
55
+ it('set with TTL expires after time', () => {
56
+ storage.set('expiring', 'value', { ttl: 1000 }); // 1 second TTL
57
+
58
+ // Before expiry
59
+ expect(storage.get('expiring')).toBe('value');
60
+
61
+ // After expiry
62
+ vi.advanceTimersByTime(1001);
63
+ expect(storage.get('expiring')).toBeNull();
64
+ });
65
+
66
+ it('TTL does not affect other keys', () => {
67
+ storage.set('short', 'expires', { ttl: 500 });
68
+ storage.set('long', 'stays', { ttl: 5000 });
69
+
70
+ vi.advanceTimersByTime(600);
71
+
72
+ expect(storage.get('short')).toBeNull();
73
+ expect(storage.get('long')).toBe('stays');
74
+ });
75
+ });
76
+
77
+ describe('clear', () => {
78
+ it('removes all data', () => {
79
+ storage.set('a', 1);
80
+ storage.set('b', 2);
81
+ storage.set('c', 3);
82
+
83
+ storage.clear();
84
+
85
+ expect(storage.get('a')).toBeNull();
86
+ expect(storage.get('b')).toBeNull();
87
+ expect(storage.get('c')).toBeNull();
88
+ });
89
+ });
90
+
91
+ describe('delete', () => {
92
+ it('removes specific key', () => {
93
+ storage.set('keep', 'value1');
94
+ storage.set('remove', 'value2');
95
+
96
+ storage.delete('remove');
97
+
98
+ expect(storage.get('keep')).toBe('value1');
99
+ expect(storage.get('remove')).toBeNull();
100
+ });
101
+
102
+ it('does nothing for non-existent key', () => {
103
+ storage.set('exists', 'value');
104
+
105
+ // Should not throw
106
+ storage.delete('nonexistent');
107
+
108
+ expect(storage.get('exists')).toBe('value');
109
+ });
110
+ });
111
+
112
+ describe('data not persisted to disk', () => {
113
+ it('does not write to disk', () => {
114
+ const filePath = path.join(testDir, 'should-not-exist.json');
115
+ const diskStorage = new EphemeralStorage({ basePath: testDir });
116
+
117
+ diskStorage.set('key', 'value');
118
+ diskStorage.set('data', { complex: true, nested: { value: 123 } });
119
+
120
+ // Verify no files were created
121
+ const files = fs.readdirSync(testDir);
122
+ expect(files.length).toBe(0);
123
+ });
124
+ });
125
+
126
+ describe('onExit handler', () => {
127
+ it('clears all data on exit', () => {
128
+ storage.set('session', 'data');
129
+ storage.set('user', { id: 1 });
130
+
131
+ // Simulate process exit
132
+ storage.handleExit();
133
+
134
+ expect(storage.get('session')).toBeNull();
135
+ expect(storage.get('user')).toBeNull();
136
+ });
137
+
138
+ it('registerExitHandler registers cleanup on process exit', () => {
139
+ const processSpy = vi.spyOn(process, 'on');
140
+
141
+ const newStorage = new EphemeralStorage({ registerExitHandler: true });
142
+
143
+ // Should have registered at least one exit handler
144
+ expect(processSpy).toHaveBeenCalledWith('exit', expect.any(Function));
145
+
146
+ processSpy.mockRestore();
147
+ newStorage.clear();
148
+ });
149
+ });
150
+
151
+ describe('encrypt option', () => {
152
+ it('encrypts values in memory', () => {
153
+ const encryptedStorage = new EphemeralStorage({ encrypt: true });
154
+ const sensitiveData = 'my-secret-token';
155
+
156
+ encryptedStorage.set('secret', sensitiveData);
157
+
158
+ // Get should return decrypted value
159
+ expect(encryptedStorage.get('secret')).toBe(sensitiveData);
160
+
161
+ // Internal storage should be encrypted (not plain text)
162
+ const rawValue = encryptedStorage.getRaw('secret');
163
+ expect(rawValue).not.toBe(sensitiveData);
164
+ // Encrypted value is stored as an object with iv, data, tag properties
165
+ expect(typeof rawValue).toBe('object');
166
+ expect(rawValue).toHaveProperty('iv');
167
+ expect(rawValue).toHaveProperty('data');
168
+ expect(rawValue).toHaveProperty('tag');
169
+
170
+ encryptedStorage.clear();
171
+ });
172
+
173
+ it('encrypts object values', () => {
174
+ const encryptedStorage = new EphemeralStorage({ encrypt: true });
175
+ const data = { apiKey: 'secret123', user: 'admin' };
176
+
177
+ encryptedStorage.set('config', data);
178
+
179
+ expect(encryptedStorage.get('config')).toEqual(data);
180
+
181
+ const rawValue = encryptedStorage.getRaw('config');
182
+ expect(rawValue).not.toEqual(data);
183
+
184
+ encryptedStorage.clear();
185
+ });
186
+ });
187
+
188
+ describe('getStats', () => {
189
+ it('returns memory usage info', () => {
190
+ storage.set('a', 'short');
191
+ storage.set('b', 'a'.repeat(1000));
192
+ storage.set('c', { nested: { deep: 'value' } });
193
+
194
+ const stats = storage.getStats();
195
+
196
+ expect(stats).toHaveProperty('keyCount', 3);
197
+ expect(stats).toHaveProperty('approximateBytes');
198
+ expect(typeof stats.approximateBytes).toBe('number');
199
+ expect(stats.approximateBytes).toBeGreaterThan(0);
200
+ });
201
+
202
+ it('returns zero stats for empty storage', () => {
203
+ const stats = storage.getStats();
204
+
205
+ expect(stats.keyCount).toBe(0);
206
+ expect(stats.approximateBytes).toBe(0);
207
+ });
208
+
209
+ it('includes expired key tracking', () => {
210
+ storage.set('temp', 'value', { ttl: 100 });
211
+
212
+ const statsBefore = storage.getStats();
213
+ expect(statsBefore.expiringKeys).toBe(1);
214
+
215
+ vi.advanceTimersByTime(101);
216
+
217
+ // Trigger cleanup by accessing
218
+ storage.get('temp');
219
+
220
+ const statsAfter = storage.getStats();
221
+ expect(statsAfter.expiringKeys).toBe(0);
222
+ });
223
+ });
224
+
225
+ describe('API compatibility', () => {
226
+ it('provides same API as persistent storage (get, set, delete, clear)', () => {
227
+ // All methods should exist
228
+ expect(typeof storage.get).toBe('function');
229
+ expect(typeof storage.set).toBe('function');
230
+ expect(typeof storage.delete).toBe('function');
231
+ expect(typeof storage.clear).toBe('function');
232
+ });
233
+
234
+ it('has method returns boolean for key existence', () => {
235
+ storage.set('exists', 'value');
236
+
237
+ expect(storage.has('exists')).toBe(true);
238
+ expect(storage.has('missing')).toBe(false);
239
+ });
240
+
241
+ it('keys method returns all keys', () => {
242
+ storage.set('a', 1);
243
+ storage.set('b', 2);
244
+ storage.set('c', 3);
245
+
246
+ const keys = storage.keys();
247
+
248
+ expect(keys).toContain('a');
249
+ expect(keys).toContain('b');
250
+ expect(keys).toContain('c');
251
+ expect(keys.length).toBe(3);
252
+ });
253
+ });
254
+ });