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,665 @@
1
+ /**
2
+ * Data Flow Documenter
3
+ *
4
+ * Documents data flows through the system including:
5
+ * - Data source identification (user input, APIs, databases)
6
+ * - Data transformation tracking
7
+ * - Data destination documentation
8
+ * - Sensitivity classification
9
+ * - Mermaid flow diagram generation
10
+ * - Retention policy documentation
11
+ */
12
+
13
+ /**
14
+ * Patterns for identifying data sources in code
15
+ */
16
+ const SOURCE_PATTERNS = {
17
+ user_input: [
18
+ /new FormData\s*\(/gi,
19
+ /document\.getElementById\s*\(['"]\w+['"]\)\.value/gi,
20
+ /document\.querySelector\s*\([^)]+\)\.value/gi,
21
+ /event\.target\.elements\.\w+\.value/gi,
22
+ /\.(value|files|checked)\s*[;,)]/gi,
23
+ /req\.body/gi,
24
+ /req\.query/gi,
25
+ /req\.params/gi,
26
+ ],
27
+ api: [
28
+ /app\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
29
+ /router\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
30
+ /fetch\s*\(\s*['"`]([^'"`]+)['"`]/gi,
31
+ /axios\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
32
+ /\/api\/\w+/gi,
33
+ ],
34
+ database: [
35
+ /new Database\s*\(/gi,
36
+ /mysql\.createPool/gi,
37
+ /mongoose\.connect/gi,
38
+ /new Pool\s*\(/gi,
39
+ /createConnection/gi,
40
+ /SELECT\s+.+\s+FROM\s+/gi,
41
+ /INSERT\s+INTO\s+/gi,
42
+ /UPDATE\s+\w+\s+SET/gi,
43
+ /DELETE\s+FROM\s+/gi,
44
+ /pg\.connect/gi,
45
+ /knex\s*\(/gi,
46
+ /prisma\./gi,
47
+ ],
48
+ };
49
+
50
+ /**
51
+ * Sensitivity classification rules
52
+ */
53
+ const SENSITIVITY_RULES = {
54
+ critical: [
55
+ 'password',
56
+ 'secret',
57
+ 'ssn',
58
+ 'social_security',
59
+ 'credit_card',
60
+ 'card_number',
61
+ 'cvv',
62
+ 'pin',
63
+ 'private_key',
64
+ 'api_key',
65
+ 'apikey',
66
+ 'access_token',
67
+ 'refresh_token',
68
+ 'auth_token',
69
+ ],
70
+ high: [
71
+ 'email',
72
+ 'phone',
73
+ 'address',
74
+ 'date_of_birth',
75
+ 'dob',
76
+ 'ip_address',
77
+ 'location',
78
+ 'gps',
79
+ 'biometric',
80
+ ],
81
+ medium: [
82
+ 'name',
83
+ 'first_name',
84
+ 'last_name',
85
+ 'username',
86
+ 'user_id',
87
+ 'account_id',
88
+ 'order_id',
89
+ 'transaction_id',
90
+ ],
91
+ low: [
92
+ 'timestamp',
93
+ 'page_number',
94
+ 'sort_order',
95
+ 'page_views',
96
+ 'count',
97
+ 'total',
98
+ 'quantity',
99
+ 'status',
100
+ 'type',
101
+ 'category',
102
+ ],
103
+ };
104
+
105
+ /**
106
+ * Default retention policies by sensitivity level
107
+ */
108
+ const DEFAULT_RETENTION = {
109
+ critical: '1 year (or until account deletion)',
110
+ high: '3 years',
111
+ medium: '5 years',
112
+ low: '7 years',
113
+ public: 'Indefinite',
114
+ };
115
+
116
+ /**
117
+ * Identify data sources in code
118
+ * @param {string} code - Source code to analyze
119
+ * @returns {Array} Array of identified data sources
120
+ */
121
+ export function identifyDataSources(code) {
122
+ const sources = [];
123
+ const seen = new Set();
124
+
125
+ for (const [type, patterns] of Object.entries(SOURCE_PATTERNS)) {
126
+ for (const pattern of patterns) {
127
+ const regex = new RegExp(pattern.source, pattern.flags);
128
+ let match;
129
+
130
+ while ((match = regex.exec(code)) !== null) {
131
+ const name = extractSourceName(match, type);
132
+ const key = `${type}:${name}`;
133
+
134
+ if (!seen.has(key)) {
135
+ seen.add(key);
136
+ sources.push({
137
+ type,
138
+ name,
139
+ match: match[0],
140
+ location: match.index,
141
+ });
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ return sources;
148
+ }
149
+
150
+ /**
151
+ * Extract a meaningful name from a regex match
152
+ * @param {Array} match - Regex match result
153
+ * @param {string} type - Source type
154
+ * @returns {string} Extracted name
155
+ */
156
+ function extractSourceName(match, type) {
157
+ const fullMatch = match[0];
158
+
159
+ if (type === 'api') {
160
+ // Extract URL path from API patterns
161
+ const urlMatch = fullMatch.match(/['"`]([^'"`]+)['"`]/);
162
+ if (urlMatch) {
163
+ return urlMatch[1];
164
+ }
165
+ return fullMatch;
166
+ }
167
+
168
+ if (type === 'user_input') {
169
+ if (fullMatch.includes('FormData')) {
170
+ return 'FormData';
171
+ }
172
+ if (fullMatch.includes('getElementById')) {
173
+ const idMatch = fullMatch.match(/getElementById\s*\(['"](\w+)['"]\)/);
174
+ return idMatch ? `form_${idMatch[1]}` : 'form_element';
175
+ }
176
+ if (fullMatch.includes('querySelector')) {
177
+ return 'form_selector';
178
+ }
179
+ if (fullMatch.includes('req.body')) {
180
+ return 'request_body';
181
+ }
182
+ if (fullMatch.includes('req.query')) {
183
+ return 'query_params';
184
+ }
185
+ if (fullMatch.includes('req.params')) {
186
+ return 'url_params';
187
+ }
188
+ return 'user_input';
189
+ }
190
+
191
+ if (type === 'database') {
192
+ if (fullMatch.includes('SELECT')) {
193
+ const tableMatch = fullMatch.match(/FROM\s+(\w+)/i);
194
+ return tableMatch ? `query:${tableMatch[1]}` : 'sql_query';
195
+ }
196
+ if (fullMatch.includes('INSERT')) {
197
+ const tableMatch = fullMatch.match(/INTO\s+(\w+)/i);
198
+ return tableMatch ? `insert:${tableMatch[1]}` : 'sql_insert';
199
+ }
200
+ if (fullMatch.includes('mongoose')) {
201
+ return 'mongodb';
202
+ }
203
+ if (fullMatch.includes('mysql')) {
204
+ return 'mysql';
205
+ }
206
+ if (fullMatch.includes('Pool') || fullMatch.includes('pg')) {
207
+ return 'postgresql';
208
+ }
209
+ return 'database';
210
+ }
211
+
212
+ return fullMatch.substring(0, 50);
213
+ }
214
+
215
+ /**
216
+ * Track a data flow through the system
217
+ * @param {Object} flowDefinition - Flow definition
218
+ * @returns {Object} Tracked flow with metadata
219
+ */
220
+ export function trackDataFlow(flowDefinition) {
221
+ const {
222
+ id = generateFlowId(),
223
+ name,
224
+ source,
225
+ steps = [],
226
+ destination,
227
+ dataTypes = [],
228
+ sensitivity,
229
+ retention,
230
+ } = flowDefinition;
231
+
232
+ return {
233
+ id,
234
+ name,
235
+ source,
236
+ transformations: steps.map((step, index) => ({
237
+ ...step,
238
+ order: index + 1,
239
+ })),
240
+ destination,
241
+ dataTypes,
242
+ sensitivity: sensitivity || 'unclassified',
243
+ retention: retention || null,
244
+ createdAt: new Date().toISOString(),
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Generate a unique flow ID
250
+ * @returns {string} Generated ID
251
+ */
252
+ function generateFlowId() {
253
+ return `flow-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
254
+ }
255
+
256
+ /**
257
+ * Classify data types by sensitivity level
258
+ * @param {Array} dataTypes - Array of data type objects with name and optional value
259
+ * @returns {Array} Array of classified data types
260
+ */
261
+ export function classifyData(dataTypes) {
262
+ return dataTypes.map((dataType) => {
263
+ const name = typeof dataType === 'string' ? dataType : dataType.name;
264
+ const lowerName = name.toLowerCase();
265
+
266
+ let sensitivity = 'low';
267
+ let reason = 'No sensitive patterns detected';
268
+
269
+ // Check against sensitivity rules
270
+ for (const [level, keywords] of Object.entries(SENSITIVITY_RULES)) {
271
+ if (keywords.some((keyword) => lowerName.includes(keyword))) {
272
+ sensitivity = level;
273
+ reason = `Matches ${level} sensitivity pattern: ${keywords.find((k) => lowerName.includes(k))}`;
274
+ break;
275
+ }
276
+ }
277
+
278
+ // Special handling for PII that might have different labels
279
+ if (sensitivity === 'high' && isPII(lowerName)) {
280
+ reason = `Personal Identifiable Information (PII): ${name}`;
281
+ }
282
+
283
+ return {
284
+ name,
285
+ value: dataType.value,
286
+ sensitivity,
287
+ reason,
288
+ classification: getSensitivityLabel(sensitivity),
289
+ };
290
+ });
291
+ }
292
+
293
+ /**
294
+ * Check if a data type is PII
295
+ * @param {string} name - Data type name (lowercase)
296
+ * @returns {boolean} True if PII
297
+ */
298
+ function isPII(name) {
299
+ const piiPatterns = ['email', 'phone', 'address', 'name', 'dob', 'birth'];
300
+ return piiPatterns.some((pattern) => name.includes(pattern));
301
+ }
302
+
303
+ /**
304
+ * Get a human-readable sensitivity label
305
+ * @param {string} level - Sensitivity level
306
+ * @returns {string} Human-readable label
307
+ */
308
+ function getSensitivityLabel(level) {
309
+ const labels = {
310
+ critical: 'Critical - Requires encryption and strict access control',
311
+ high: 'High - Contains PII, requires protection',
312
+ medium: 'Medium - Internal use, standard protection',
313
+ low: 'Low - Non-sensitive data',
314
+ public: 'Public - Can be freely shared',
315
+ };
316
+ return labels[level] || 'Unclassified';
317
+ }
318
+
319
+ /**
320
+ * Generate a Mermaid flow diagram for a data flow
321
+ * @param {Object} flow - Data flow object
322
+ * @returns {string} Mermaid diagram syntax
323
+ */
324
+ export function generateFlowDiagram(flow) {
325
+ const lines = ['flowchart LR'];
326
+ const nodeIds = [];
327
+
328
+ // Add source node
329
+ const sourceId = 'source';
330
+ const sourceShape = getNodeShape(flow.source.type, flow.source.name);
331
+ lines.push(` ${sourceId}${sourceShape}`);
332
+ nodeIds.push(sourceId);
333
+
334
+ // Add transformation nodes
335
+ if (flow.transformations && flow.transformations.length > 0) {
336
+ flow.transformations.forEach((transform, index) => {
337
+ const nodeId = `step${index + 1}`;
338
+ const label = transform.step || transform.description;
339
+ lines.push(` ${nodeId}[${label}]`);
340
+ nodeIds.push(nodeId);
341
+ });
342
+ }
343
+
344
+ // Add destination node
345
+ const destId = 'dest';
346
+ const destShape = getNodeShape(flow.destination.type, flow.destination.name);
347
+ lines.push(` ${destId}${destShape}`);
348
+ nodeIds.push(destId);
349
+
350
+ // Add connections
351
+ for (let i = 0; i < nodeIds.length - 1; i++) {
352
+ lines.push(` ${nodeIds[i]} --> ${nodeIds[i + 1]}`);
353
+ }
354
+
355
+ return lines.join('\n');
356
+ }
357
+
358
+ /**
359
+ * Get the Mermaid node shape for a given type
360
+ * @param {string} type - Node type (user_input, api, database)
361
+ * @param {string} name - Node name/label
362
+ * @returns {string} Mermaid node syntax
363
+ */
364
+ function getNodeShape(type, name) {
365
+ switch (type) {
366
+ case 'database':
367
+ return `[(${name})]`;
368
+ case 'api':
369
+ return `{{${name}}}`;
370
+ case 'user_input':
371
+ default:
372
+ return `[${name}]`;
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Document retention policy for a data flow
378
+ * @param {Object} dataFlow - Data flow object
379
+ * @param {Object} retentionPolicy - Optional custom retention policy
380
+ * @returns {Object} Data flow with retention documentation
381
+ */
382
+ export function documentRetention(dataFlow, retentionPolicy = {}) {
383
+ const {
384
+ default: defaultRetention = DEFAULT_RETENTION[dataFlow.sensitivity] || '5 years',
385
+ byType = {},
386
+ legal = null,
387
+ deletionProcedure = 'Standard secure deletion',
388
+ } = retentionPolicy;
389
+
390
+ return {
391
+ ...dataFlow,
392
+ retention: {
393
+ default: defaultRetention,
394
+ byType,
395
+ legal,
396
+ deletionProcedure,
397
+ lastReviewed: new Date().toISOString(),
398
+ },
399
+ };
400
+ }
401
+
402
+ /**
403
+ * Get inventory of all data types across flows
404
+ * @param {DataFlowDocumenter} documenter - Documenter instance
405
+ * @returns {Array} Array of unique data types with metadata
406
+ */
407
+ export function getDataInventory(documenter) {
408
+ const inventory = new Map();
409
+
410
+ for (const flow of documenter.getFlows()) {
411
+ for (const dataType of flow.dataTypes || []) {
412
+ const name = typeof dataType === 'string' ? dataType : dataType.name;
413
+
414
+ if (!inventory.has(name)) {
415
+ const classification = classifyData([{ name }])[0];
416
+ inventory.set(name, {
417
+ name,
418
+ sensitivity: classification.sensitivity,
419
+ reason: classification.reason,
420
+ usedInFlows: [],
421
+ });
422
+ }
423
+
424
+ inventory.get(name).usedInFlows.push(flow.id);
425
+ }
426
+ }
427
+
428
+ return Array.from(inventory.values());
429
+ }
430
+
431
+ /**
432
+ * Get lineage information for a specific data type
433
+ * @param {DataFlowDocumenter} documenter - Documenter instance
434
+ * @param {string} dataTypeName - Name of the data type to trace
435
+ * @returns {Object} Lineage information
436
+ */
437
+ export function getDataLineage(documenter, dataTypeName) {
438
+ const flows = documenter.getFlows().filter((flow) => {
439
+ const dataTypes = flow.dataTypes || [];
440
+ return dataTypes.some((dt) => {
441
+ const name = typeof dt === 'string' ? dt : dt.name;
442
+ return name === dataTypeName;
443
+ });
444
+ });
445
+
446
+ const path = [];
447
+ if (flows.length > 0) {
448
+ // Build path from flows
449
+ for (const flow of flows) {
450
+ if (!path.includes(flow.source.name)) {
451
+ path.push(flow.source.name);
452
+ }
453
+ for (const transform of flow.transformations || []) {
454
+ const stepName = transform.step || transform.description;
455
+ if (!path.includes(stepName)) {
456
+ path.push(stepName);
457
+ }
458
+ }
459
+ if (!path.includes(flow.destination.name)) {
460
+ path.push(flow.destination.name);
461
+ }
462
+ }
463
+ }
464
+
465
+ return {
466
+ dataType: dataTypeName,
467
+ flows,
468
+ path: path.join(' -> '),
469
+ totalFlows: flows.length,
470
+ };
471
+ }
472
+
473
+ /**
474
+ * Export data flow report in various formats
475
+ * @param {DataFlowDocumenter} documenter - Documenter instance
476
+ * @param {Object} options - Export options
477
+ * @returns {Object|string} Report in specified format
478
+ */
479
+ export function exportDataFlowReport(documenter, options = {}) {
480
+ const { format = 'json', includeDiagrams = false } = options;
481
+
482
+ const flows = documenter.getFlows();
483
+ const inventory = getDataInventory(documenter);
484
+
485
+ // Build sensitivity summary
486
+ const sensitivitySummary = {
487
+ critical: inventory.filter((d) => d.sensitivity === 'critical').length,
488
+ high: inventory.filter((d) => d.sensitivity === 'high').length,
489
+ medium: inventory.filter((d) => d.sensitivity === 'medium').length,
490
+ low: inventory.filter((d) => d.sensitivity === 'low').length,
491
+ };
492
+
493
+ // Build diagrams if requested
494
+ const diagrams = {};
495
+ if (includeDiagrams) {
496
+ for (const flow of flows) {
497
+ diagrams[flow.id] = generateFlowDiagram(flow);
498
+ }
499
+ }
500
+
501
+ const report = {
502
+ title: 'Data Flow Report',
503
+ generatedAt: new Date().toISOString(),
504
+ flows,
505
+ dataInventory: inventory,
506
+ sensitivitySummary,
507
+ diagrams: includeDiagrams ? diagrams : undefined,
508
+ totalFlows: flows.length,
509
+ totalDataTypes: inventory.length,
510
+ };
511
+
512
+ if (format === 'markdown') {
513
+ return generateMarkdownReport(report);
514
+ }
515
+
516
+ if (format === 'compliance') {
517
+ return {
518
+ ...report,
519
+ complianceInfo: {
520
+ gdprRelevant: inventory.some((d) => d.sensitivity === 'critical' || d.sensitivity === 'high'),
521
+ piiCount: inventory.filter((d) => d.sensitivity === 'high').length,
522
+ sensitiveCount: inventory.filter((d) => d.sensitivity === 'critical').length,
523
+ },
524
+ };
525
+ }
526
+
527
+ return report;
528
+ }
529
+
530
+ /**
531
+ * Generate markdown report
532
+ * @param {Object} report - Report object
533
+ * @returns {string} Markdown formatted report
534
+ */
535
+ function generateMarkdownReport(report) {
536
+ const lines = [
537
+ '# Data Flow Report',
538
+ '',
539
+ `Generated: ${report.generatedAt}`,
540
+ '',
541
+ '## Summary',
542
+ '',
543
+ `- Total Flows: ${report.totalFlows}`,
544
+ `- Total Data Types: ${report.totalDataTypes}`,
545
+ '',
546
+ '### Sensitivity Distribution',
547
+ '',
548
+ `- Critical: ${report.sensitivitySummary.critical}`,
549
+ `- High: ${report.sensitivitySummary.high}`,
550
+ `- Medium: ${report.sensitivitySummary.medium}`,
551
+ `- Low: ${report.sensitivitySummary.low}`,
552
+ '',
553
+ '## Flows',
554
+ '',
555
+ ];
556
+
557
+ for (const flow of report.flows) {
558
+ lines.push(`### ${flow.name}`);
559
+ lines.push('');
560
+ lines.push(`- **ID:** ${flow.id}`);
561
+ lines.push(`- **Source:** ${flow.source.type} - ${flow.source.name}`);
562
+ lines.push(`- **Destination:** ${flow.destination.type} - ${flow.destination.name}`);
563
+ lines.push(`- **Sensitivity:** ${flow.sensitivity}`);
564
+ if (flow.retention) {
565
+ lines.push(`- **Retention:** ${flow.retention}`);
566
+ }
567
+ lines.push('');
568
+
569
+ if (flow.transformations && flow.transformations.length > 0) {
570
+ lines.push('**Transformations:**');
571
+ for (const t of flow.transformations) {
572
+ lines.push(`- ${t.step}: ${t.description}`);
573
+ }
574
+ lines.push('');
575
+ }
576
+
577
+ if (report.diagrams && report.diagrams[flow.id]) {
578
+ lines.push('```mermaid');
579
+ lines.push(report.diagrams[flow.id]);
580
+ lines.push('```');
581
+ lines.push('');
582
+ }
583
+ }
584
+
585
+ lines.push('## Data Inventory');
586
+ lines.push('');
587
+ lines.push('| Data Type | Sensitivity | Used In Flows |');
588
+ lines.push('|-----------|-------------|---------------|');
589
+
590
+ for (const item of report.dataInventory) {
591
+ lines.push(`| ${item.name} | ${item.sensitivity} | ${item.usedInFlows.join(', ')} |`);
592
+ }
593
+
594
+ return lines.join('\n');
595
+ }
596
+
597
+ /**
598
+ * DataFlowDocumenter class for managing data flows
599
+ */
600
+ export class DataFlowDocumenter {
601
+ constructor() {
602
+ this.flows = new Map();
603
+ }
604
+
605
+ /**
606
+ * Add a data flow
607
+ * @param {Object} flow - Flow definition
608
+ */
609
+ addDataFlow(flow) {
610
+ const trackedFlow = trackDataFlow(flow);
611
+ this.flows.set(trackedFlow.id, trackedFlow);
612
+ }
613
+
614
+ /**
615
+ * Get all flows
616
+ * @returns {Array} Array of all flows
617
+ */
618
+ getFlows() {
619
+ return Array.from(this.flows.values());
620
+ }
621
+
622
+ /**
623
+ * Get a specific flow by ID
624
+ * @param {string} id - Flow ID
625
+ * @returns {Object|undefined} Flow or undefined
626
+ */
627
+ getFlowById(id) {
628
+ return this.flows.get(id);
629
+ }
630
+
631
+ /**
632
+ * Remove a data flow
633
+ * @param {string} id - Flow ID to remove
634
+ */
635
+ removeDataFlow(id) {
636
+ this.flows.delete(id);
637
+ }
638
+
639
+ /**
640
+ * Update an existing data flow
641
+ * @param {string} id - Flow ID to update
642
+ * @param {Object} updates - Updates to apply
643
+ */
644
+ updateDataFlow(id, updates) {
645
+ const existing = this.flows.get(id);
646
+ if (existing) {
647
+ this.flows.set(id, { ...existing, ...updates });
648
+ }
649
+ }
650
+
651
+ /**
652
+ * Get flow count
653
+ * @returns {number} Number of flows
654
+ */
655
+ getFlowCount() {
656
+ return this.flows.size;
657
+ }
658
+
659
+ /**
660
+ * Clear all flows
661
+ */
662
+ clear() {
663
+ this.flows.clear();
664
+ }
665
+ }