tlc-claude-code 1.3.0 → 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 (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,452 @@
1
+ /**
2
+ * Workspace Scanner - Discover and index repos in workspace
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const SOURCE_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
9
+
10
+ class WorkspaceScanner {
11
+ constructor(workspaceConfig) {
12
+ this.workspaceConfig = workspaceConfig;
13
+ this.rootDir = workspaceConfig.rootDir;
14
+ this.cachedResult = null;
15
+ }
16
+
17
+ /**
18
+ * Scan all repos in the workspace
19
+ * @param {Object} options - Scan options
20
+ * @param {boolean} options.force - Force rescan (ignore cache)
21
+ * @param {boolean} options.scanImports - Scan source files for import references
22
+ * @returns {Object} Scan result with repos, graphs, and stats
23
+ */
24
+ scan(options = {}) {
25
+ const { force = false, scanImports = false } = options;
26
+
27
+ // Return cached result if available and not forcing
28
+ if (this.cachedResult && !force) {
29
+ return this.cachedResult;
30
+ }
31
+
32
+ const config = this.workspaceConfig.getConfig();
33
+ const repos = [];
34
+ const byPath = {};
35
+ const byName = {};
36
+ const dependencyGraph = {};
37
+
38
+ // Scan each repo
39
+ for (const repoPath of config.repos) {
40
+ const repoInfo = this.scanRepo(repoPath, scanImports);
41
+ repos.push(repoInfo);
42
+ byPath[repoPath] = repoInfo;
43
+ if (repoInfo.name) {
44
+ byName[repoInfo.name] = repoInfo;
45
+ }
46
+ }
47
+
48
+ // Build dependency graph after all repos are scanned
49
+ for (const repo of repos) {
50
+ dependencyGraph[repo.path] = this.findDependencyPaths(repo, byName);
51
+ }
52
+
53
+ // Detect circular dependencies
54
+ const { hasCircular, cycles } = this.detectCircularDeps(dependencyGraph);
55
+
56
+ // Calculate dependency order (topological sort)
57
+ const dependencyOrder = this.calculateDependencyOrder(dependencyGraph);
58
+
59
+ // Build stats
60
+ const stats = {
61
+ totalRepos: repos.length,
62
+ reposWithPackageJson: repos.filter(r => r.hasPackageJson).length,
63
+ reposWithCircularDeps: hasCircular ? cycles.length : 0,
64
+ };
65
+
66
+ const result = {
67
+ repos,
68
+ byPath,
69
+ byName,
70
+ dependencyGraph,
71
+ dependencyOrder,
72
+ hasCircularDeps: hasCircular,
73
+ circularDeps: cycles,
74
+ stats,
75
+ };
76
+
77
+ this.cachedResult = result;
78
+ return result;
79
+ }
80
+
81
+ /**
82
+ * Scan a single repo for project info
83
+ * @param {string} repoPath - Relative path to repo
84
+ * @param {boolean} scanImports - Whether to scan source files for imports
85
+ * @returns {Object} Repo info
86
+ */
87
+ scanRepo(repoPath, scanImports = false) {
88
+ const absolutePath = path.join(this.rootDir, repoPath);
89
+ const packageJsonPath = path.join(absolutePath, 'package.json');
90
+
91
+ const info = {
92
+ path: repoPath,
93
+ name: repoPath, // Default to path if no package.json
94
+ version: null,
95
+ description: null,
96
+ main: null,
97
+ module: null,
98
+ scripts: {},
99
+ dependencies: [],
100
+ devDependencies: [],
101
+ workspaceDeps: [],
102
+ importedRepos: [],
103
+ hasPackageJson: false,
104
+ };
105
+
106
+ // Try to read package.json
107
+ if (fs.existsSync(packageJsonPath)) {
108
+ try {
109
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
110
+ info.hasPackageJson = true;
111
+ info.name = pkg.name || repoPath;
112
+ info.version = pkg.version || null;
113
+ info.description = pkg.description || null;
114
+ info.main = pkg.main || null;
115
+ info.module = pkg.module || null;
116
+ info.scripts = pkg.scripts || {};
117
+
118
+ // Extract dependencies
119
+ info.dependencies = Object.keys(pkg.dependencies || {});
120
+ info.devDependencies = Object.keys(pkg.devDependencies || {});
121
+
122
+ // Detect workspace dependencies
123
+ info.workspaceDeps = this.extractWorkspaceDeps(pkg);
124
+ } catch (err) {
125
+ // Ignore parse errors, use defaults
126
+ }
127
+ }
128
+
129
+ // Scan source files for import references if requested
130
+ if (scanImports) {
131
+ info.importedRepos = this.scanImportReferences(absolutePath);
132
+ }
133
+
134
+ return info;
135
+ }
136
+
137
+ /**
138
+ * Extract workspace dependencies from package.json
139
+ * @param {Object} pkg - Parsed package.json
140
+ * @returns {string[]} Array of workspace dependency names
141
+ */
142
+ extractWorkspaceDeps(pkg) {
143
+ const workspaceDeps = [];
144
+ const allDeps = {
145
+ ...pkg.dependencies,
146
+ ...pkg.devDependencies,
147
+ };
148
+
149
+ for (const [name, version] of Object.entries(allDeps)) {
150
+ // Detect workspace: protocol
151
+ if (typeof version === 'string' && version.startsWith('workspace:')) {
152
+ workspaceDeps.push(name);
153
+ }
154
+ // Detect file: protocol (relative path)
155
+ else if (typeof version === 'string' && version.startsWith('file:')) {
156
+ workspaceDeps.push(name);
157
+ }
158
+ }
159
+
160
+ return workspaceDeps;
161
+ }
162
+
163
+ /**
164
+ * Scan source files for import references to workspace packages
165
+ * @param {string} repoDir - Absolute path to repo
166
+ * @returns {string[]} Array of imported workspace package names
167
+ */
168
+ scanImportReferences(repoDir) {
169
+ const importedRepos = new Set();
170
+ const workspacePackages = this.getWorkspacePackageNames();
171
+
172
+ const scanDir = (dir) => {
173
+ if (!fs.existsSync(dir)) return;
174
+
175
+ try {
176
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
177
+
178
+ for (const entry of entries) {
179
+ const fullPath = path.join(dir, entry.name);
180
+
181
+ // Skip ignored directories
182
+ if (entry.isDirectory()) {
183
+ if (['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
184
+ continue;
185
+ }
186
+ scanDir(fullPath);
187
+ } else if (SOURCE_EXTENSIONS.some(ext => entry.name.endsWith(ext))) {
188
+ // Scan source file for imports
189
+ const imports = this.extractImportsFromFile(fullPath);
190
+ for (const imp of imports) {
191
+ if (workspacePackages.has(imp)) {
192
+ importedRepos.add(imp);
193
+ }
194
+ }
195
+ }
196
+ }
197
+ } catch (err) {
198
+ // Ignore read errors
199
+ }
200
+ };
201
+
202
+ scanDir(repoDir);
203
+ return Array.from(importedRepos);
204
+ }
205
+
206
+ /**
207
+ * Get all workspace package names
208
+ * @returns {Set<string>} Set of package names
209
+ */
210
+ getWorkspacePackageNames() {
211
+ const names = new Set();
212
+ const config = this.workspaceConfig.getConfig();
213
+
214
+ for (const repoPath of config.repos) {
215
+ const packageJsonPath = path.join(this.rootDir, repoPath, 'package.json');
216
+ if (fs.existsSync(packageJsonPath)) {
217
+ try {
218
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
219
+ if (pkg.name) {
220
+ names.add(pkg.name);
221
+ }
222
+ } catch (err) {
223
+ // Ignore parse errors
224
+ }
225
+ }
226
+ }
227
+
228
+ return names;
229
+ }
230
+
231
+ /**
232
+ * Extract import/require statements from a file
233
+ * @param {string} filePath - Absolute path to file
234
+ * @returns {string[]} Array of imported module names
235
+ */
236
+ extractImportsFromFile(filePath) {
237
+ const imports = [];
238
+
239
+ try {
240
+ const content = fs.readFileSync(filePath, 'utf-8');
241
+
242
+ // ES6 imports: import x from 'y', import { x } from 'y'
243
+ const es6Regex = /import\s+(?:(?:[\w*{}\s,]+)\s+from\s+)?['"]([^'"]+)['"]/g;
244
+ let match;
245
+ while ((match = es6Regex.exec(content)) !== null) {
246
+ imports.push(this.getPackageName(match[1]));
247
+ }
248
+
249
+ // CommonJS: require('x')
250
+ const cjsRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
251
+ while ((match = cjsRegex.exec(content)) !== null) {
252
+ imports.push(this.getPackageName(match[1]));
253
+ }
254
+
255
+ // Dynamic imports: import('x')
256
+ const dynamicRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
257
+ while ((match = dynamicRegex.exec(content)) !== null) {
258
+ imports.push(this.getPackageName(match[1]));
259
+ }
260
+ } catch (err) {
261
+ // Ignore read errors
262
+ }
263
+
264
+ return imports;
265
+ }
266
+
267
+ /**
268
+ * Get package name from import path
269
+ * @param {string} importPath - Import path
270
+ * @returns {string} Package name (handles scoped packages)
271
+ */
272
+ getPackageName(importPath) {
273
+ // Handle relative paths
274
+ if (importPath.startsWith('.') || importPath.startsWith('/')) {
275
+ return importPath;
276
+ }
277
+
278
+ // Handle scoped packages (@scope/package)
279
+ if (importPath.startsWith('@')) {
280
+ const parts = importPath.split('/');
281
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importPath;
282
+ }
283
+
284
+ // Regular package
285
+ return importPath.split('/')[0];
286
+ }
287
+
288
+ /**
289
+ * Find repo paths that a repo depends on
290
+ * @param {Object} repoInfo - Repo info object
291
+ * @param {Object} byName - Repos indexed by name
292
+ * @returns {string[]} Array of dependency repo paths
293
+ */
294
+ findDependencyPaths(repoInfo, byName) {
295
+ const depPaths = [];
296
+
297
+ // Check workspace deps (include self-references for circular dep detection)
298
+ for (const depName of repoInfo.workspaceDeps) {
299
+ const depRepo = byName[depName];
300
+ if (depRepo && !depPaths.includes(depRepo.path)) {
301
+ depPaths.push(depRepo.path);
302
+ }
303
+ }
304
+
305
+ // Check imported repos
306
+ for (const importedName of repoInfo.importedRepos) {
307
+ const depRepo = byName[importedName];
308
+ if (depRepo && !depPaths.includes(depRepo.path)) {
309
+ depPaths.push(depRepo.path);
310
+ }
311
+ }
312
+
313
+ return depPaths;
314
+ }
315
+
316
+ /**
317
+ * Detect circular dependencies in the graph
318
+ * @param {Object} graph - Dependency graph { repoPath: [depPaths] }
319
+ * @returns {Object} { hasCircular: boolean, cycles: string[][] }
320
+ */
321
+ detectCircularDeps(graph) {
322
+ const cycles = [];
323
+ const visited = new Set();
324
+ const stack = new Set();
325
+ const path = [];
326
+
327
+ const dfs = (node) => {
328
+ if (stack.has(node)) {
329
+ // Found a cycle
330
+ const cycleStart = path.indexOf(node);
331
+ const cycle = path.slice(cycleStart);
332
+ cycle.push(node);
333
+ cycles.push(cycle);
334
+ return true;
335
+ }
336
+
337
+ if (visited.has(node)) {
338
+ return false;
339
+ }
340
+
341
+ visited.add(node);
342
+ stack.add(node);
343
+ path.push(node);
344
+
345
+ const deps = graph[node] || [];
346
+ for (const dep of deps) {
347
+ dfs(dep);
348
+ }
349
+
350
+ path.pop();
351
+ stack.delete(node);
352
+ return false;
353
+ };
354
+
355
+ for (const node of Object.keys(graph)) {
356
+ if (!visited.has(node)) {
357
+ dfs(node);
358
+ }
359
+ }
360
+
361
+ return {
362
+ hasCircular: cycles.length > 0,
363
+ cycles,
364
+ };
365
+ }
366
+
367
+ /**
368
+ * Calculate topological order for dependencies
369
+ * @param {Object} graph - Dependency graph { repoPath: [depPaths] }
370
+ * @returns {string[]} Ordered array of repo paths
371
+ */
372
+ calculateDependencyOrder(graph) {
373
+ const nodes = Object.keys(graph);
374
+ const visited = new Set();
375
+ const order = [];
376
+
377
+ const visit = (node) => {
378
+ if (visited.has(node)) {
379
+ return;
380
+ }
381
+ visited.add(node);
382
+
383
+ // Visit dependencies first
384
+ const deps = graph[node] || [];
385
+ for (const dep of deps) {
386
+ if (graph.hasOwnProperty(dep)) {
387
+ visit(dep);
388
+ }
389
+ }
390
+
391
+ order.push(node);
392
+ };
393
+
394
+ // Visit all nodes
395
+ for (const node of nodes) {
396
+ visit(node);
397
+ }
398
+
399
+ return order;
400
+ }
401
+
402
+ /**
403
+ * Get repos affected by changes to a given repo
404
+ * @param {string} repoPath - Path of changed repo
405
+ * @returns {string[]} Array of affected repo paths
406
+ */
407
+ getAffectedRepos(repoPath) {
408
+ // Make sure we have scanned
409
+ if (!this.cachedResult) {
410
+ this.scan();
411
+ }
412
+
413
+ const affected = new Set();
414
+ const graph = this.cachedResult.dependencyGraph;
415
+
416
+ // Build reverse graph (dependents -> dependencies)
417
+ const reverseGraph = {};
418
+ for (const [repo, deps] of Object.entries(graph)) {
419
+ for (const dep of deps) {
420
+ if (!reverseGraph[dep]) {
421
+ reverseGraph[dep] = [];
422
+ }
423
+ reverseGraph[dep].push(repo);
424
+ }
425
+ }
426
+
427
+ // BFS to find all affected repos
428
+ const queue = [repoPath];
429
+ while (queue.length > 0) {
430
+ const current = queue.shift();
431
+ const dependents = reverseGraph[current] || [];
432
+
433
+ for (const dependent of dependents) {
434
+ if (!affected.has(dependent)) {
435
+ affected.add(dependent);
436
+ queue.push(dependent);
437
+ }
438
+ }
439
+ }
440
+
441
+ return Array.from(affected);
442
+ }
443
+
444
+ /**
445
+ * Clear cached results
446
+ */
447
+ clearCache() {
448
+ this.cachedResult = null;
449
+ }
450
+ }
451
+
452
+ module.exports = { WorkspaceScanner };