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.
- package/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +0 -16
- package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compliance Command - CLI command for compliance operations
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - tlc compliance status - show compliance status
|
|
6
|
+
* - tlc compliance checklist - show SOC 2 checklist
|
|
7
|
+
* - tlc compliance evidence - collect evidence
|
|
8
|
+
* - tlc compliance report - generate report
|
|
9
|
+
* - tlc compliance policies - generate policies
|
|
10
|
+
* - tlc compliance gaps - show gaps
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
createComplianceChecklist,
|
|
15
|
+
getSOC2Checklist,
|
|
16
|
+
getCompliancePercentage,
|
|
17
|
+
getComplianceGaps,
|
|
18
|
+
getControlsByCategory,
|
|
19
|
+
TSC_CATEGORIES,
|
|
20
|
+
} from './compliance-checklist.js';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
createReporter,
|
|
24
|
+
generateReadinessReport,
|
|
25
|
+
formatReportMarkdown,
|
|
26
|
+
formatReportHTML,
|
|
27
|
+
calculateCategoryScores,
|
|
28
|
+
} from './compliance-reporter.js';
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
createEvidenceCollector,
|
|
32
|
+
collectPolicyDocuments,
|
|
33
|
+
collectConfigSnapshot,
|
|
34
|
+
collectAuditLogs,
|
|
35
|
+
} from './evidence-collector.js';
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
generateAccessControlPolicy,
|
|
39
|
+
generateDataProtectionPolicy,
|
|
40
|
+
generateIncidentResponsePolicy,
|
|
41
|
+
generateAuthPolicy,
|
|
42
|
+
generateAcceptableUsePolicy,
|
|
43
|
+
exportAsMarkdown,
|
|
44
|
+
} from './security-policy-generator.js';
|
|
45
|
+
|
|
46
|
+
const VALID_SUBCOMMANDS = ['status', 'checklist', 'evidence', 'report', 'policies', 'gaps'];
|
|
47
|
+
const VALID_FORMATS = ['text', 'markdown', 'html', 'json'];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse command line arguments
|
|
51
|
+
* @param {string[]} args - Command line arguments
|
|
52
|
+
* @returns {Object} Parsed options
|
|
53
|
+
*/
|
|
54
|
+
export function parseArgs(args) {
|
|
55
|
+
const result = {
|
|
56
|
+
subcommand: null,
|
|
57
|
+
category: null,
|
|
58
|
+
format: 'text',
|
|
59
|
+
output: null,
|
|
60
|
+
unknownSubcommand: null,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < args.length; i++) {
|
|
64
|
+
const arg = args[i];
|
|
65
|
+
|
|
66
|
+
// Check for subcommands first (non-flag arguments)
|
|
67
|
+
if (!arg.startsWith('--') && !arg.startsWith('-') && result.subcommand === null && result.unknownSubcommand === null) {
|
|
68
|
+
if (VALID_SUBCOMMANDS.includes(arg)) {
|
|
69
|
+
result.subcommand = arg;
|
|
70
|
+
} else {
|
|
71
|
+
result.unknownSubcommand = arg;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Parse flags
|
|
77
|
+
if (arg === '--category') {
|
|
78
|
+
result.category = args[i + 1];
|
|
79
|
+
i++;
|
|
80
|
+
} else if (arg.startsWith('--category=')) {
|
|
81
|
+
result.category = arg.split('=')[1];
|
|
82
|
+
} else if (arg === '--format') {
|
|
83
|
+
result.format = args[i + 1];
|
|
84
|
+
i++;
|
|
85
|
+
} else if (arg.startsWith('--format=')) {
|
|
86
|
+
result.format = arg.split('=')[1];
|
|
87
|
+
} else if (arg === '--output') {
|
|
88
|
+
result.output = args[i + 1];
|
|
89
|
+
i++;
|
|
90
|
+
} else if (arg.startsWith('--output=')) {
|
|
91
|
+
result.output = arg.split('=')[1];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* ComplianceCommand class - handles tlc compliance command
|
|
100
|
+
*/
|
|
101
|
+
export class ComplianceCommand {
|
|
102
|
+
/**
|
|
103
|
+
* Create a ComplianceCommand instance
|
|
104
|
+
* @param {Object} options - Configuration options
|
|
105
|
+
* @param {Object} options.checklist - Compliance checklist instance
|
|
106
|
+
* @param {Object} options.reporter - Compliance reporter instance
|
|
107
|
+
* @param {Object} options.evidenceCollector - Evidence collector instance
|
|
108
|
+
* @param {Object} options.policyGenerator - Policy generator (optional)
|
|
109
|
+
* @param {string} options.projectDir - Project directory
|
|
110
|
+
*/
|
|
111
|
+
constructor(options = {}) {
|
|
112
|
+
this.checklist = options.checklist || createComplianceChecklist();
|
|
113
|
+
this.reporter = options.reporter || createReporter({ checklist: this.checklist });
|
|
114
|
+
this.evidenceCollector = options.evidenceCollector || createEvidenceCollector();
|
|
115
|
+
this.policyGenerator = options.policyGenerator || {
|
|
116
|
+
generateAccessControlPolicy,
|
|
117
|
+
generateDataProtectionPolicy,
|
|
118
|
+
generateIncidentResponsePolicy,
|
|
119
|
+
generateAuthPolicy,
|
|
120
|
+
generateAcceptableUsePolicy,
|
|
121
|
+
};
|
|
122
|
+
this.projectDir = options.projectDir || process.cwd();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Execute the compliance command
|
|
127
|
+
* @param {string[]} args - Command arguments
|
|
128
|
+
* @returns {Promise<Object>} Result { success, output, error? }
|
|
129
|
+
*/
|
|
130
|
+
async execute(args) {
|
|
131
|
+
const options = parseArgs(args);
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
// Check for unknown subcommand first
|
|
135
|
+
if (options.unknownSubcommand) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
output: '',
|
|
139
|
+
error: `Unknown subcommand: ${options.unknownSubcommand}. Valid commands: ${VALID_SUBCOMMANDS.join(', ')}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Default to status if no subcommand
|
|
144
|
+
const subcommand = options.subcommand || 'status';
|
|
145
|
+
|
|
146
|
+
switch (subcommand) {
|
|
147
|
+
case 'status':
|
|
148
|
+
return await this.handleStatus(options);
|
|
149
|
+
case 'checklist':
|
|
150
|
+
return await this.handleChecklist(options);
|
|
151
|
+
case 'evidence':
|
|
152
|
+
return await this.handleEvidence(options);
|
|
153
|
+
case 'report':
|
|
154
|
+
return await this.handleReport(options);
|
|
155
|
+
case 'policies':
|
|
156
|
+
return await this.handlePolicies(options);
|
|
157
|
+
case 'gaps':
|
|
158
|
+
return await this.handleGaps(options);
|
|
159
|
+
default:
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
output: '',
|
|
163
|
+
error: `Unknown subcommand: ${subcommand}. Valid commands: ${VALID_SUBCOMMANDS.join(', ')}`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
output: '',
|
|
170
|
+
error: error.message,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Handle status subcommand
|
|
177
|
+
* @param {Object} options - Parsed options
|
|
178
|
+
* @returns {Promise<Object>} Result
|
|
179
|
+
*/
|
|
180
|
+
async handleStatus(options) {
|
|
181
|
+
const compliance = getCompliancePercentage(this.checklist);
|
|
182
|
+
const gaps = getComplianceGaps(this.checklist);
|
|
183
|
+
const output = this.formatStatus(compliance, gaps);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
output,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Handle checklist subcommand
|
|
193
|
+
* @param {Object} options - Parsed options
|
|
194
|
+
* @returns {Promise<Object>} Result
|
|
195
|
+
*/
|
|
196
|
+
async handleChecklist(options) {
|
|
197
|
+
let controls;
|
|
198
|
+
|
|
199
|
+
if (options.category) {
|
|
200
|
+
const byCategory = getControlsByCategory(this.checklist, options.category);
|
|
201
|
+
controls = byCategory[options.category] || [];
|
|
202
|
+
} else {
|
|
203
|
+
controls = getSOC2Checklist(this.checklist);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const output = this.formatChecklist(controls, options);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
success: true,
|
|
210
|
+
output,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Handle evidence subcommand
|
|
216
|
+
* @param {Object} options - Parsed options
|
|
217
|
+
* @returns {Promise<Object>} Result
|
|
218
|
+
*/
|
|
219
|
+
async handleEvidence(options) {
|
|
220
|
+
// Collect various evidence types
|
|
221
|
+
const evidenceItems = this.evidenceCollector.getAll ? this.evidenceCollector.getAll() : [];
|
|
222
|
+
|
|
223
|
+
const output = this.formatEvidence(evidenceItems);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
output,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Handle report subcommand
|
|
233
|
+
* @param {Object} options - Parsed options
|
|
234
|
+
* @returns {Promise<Object>} Result
|
|
235
|
+
*/
|
|
236
|
+
async handleReport(options) {
|
|
237
|
+
const report = this.reporter.generateReadinessReport
|
|
238
|
+
? this.reporter.generateReadinessReport()
|
|
239
|
+
: generateReadinessReport(this.reporter);
|
|
240
|
+
|
|
241
|
+
let output;
|
|
242
|
+
switch (options.format) {
|
|
243
|
+
case 'markdown':
|
|
244
|
+
output = this.reporter.formatReportMarkdown
|
|
245
|
+
? this.reporter.formatReportMarkdown(report)
|
|
246
|
+
: formatReportMarkdown(report);
|
|
247
|
+
break;
|
|
248
|
+
case 'html':
|
|
249
|
+
output = this.reporter.formatReportHTML
|
|
250
|
+
? this.reporter.formatReportHTML(report)
|
|
251
|
+
: formatReportHTML(report);
|
|
252
|
+
break;
|
|
253
|
+
case 'json':
|
|
254
|
+
output = JSON.stringify(report, null, 2);
|
|
255
|
+
break;
|
|
256
|
+
default:
|
|
257
|
+
output = this.formatReportText(report);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
output,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Handle policies subcommand
|
|
268
|
+
* @param {Object} options - Parsed options
|
|
269
|
+
* @returns {Promise<Object>} Result
|
|
270
|
+
*/
|
|
271
|
+
async handlePolicies(options) {
|
|
272
|
+
const policies = [
|
|
273
|
+
{ name: 'Access Control', policy: this.policyGenerator.generateAccessControlPolicy() },
|
|
274
|
+
{ name: 'Data Protection', policy: this.policyGenerator.generateDataProtectionPolicy() },
|
|
275
|
+
{ name: 'Incident Response', policy: this.policyGenerator.generateIncidentResponsePolicy() },
|
|
276
|
+
{ name: 'Authentication', policy: this.policyGenerator.generateAuthPolicy() },
|
|
277
|
+
{ name: 'Acceptable Use', policy: this.policyGenerator.generateAcceptableUsePolicy() },
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const output = this.formatPolicies(policies);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
success: true,
|
|
284
|
+
output,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Handle gaps subcommand
|
|
290
|
+
* @param {Object} options - Parsed options
|
|
291
|
+
* @returns {Promise<Object>} Result
|
|
292
|
+
*/
|
|
293
|
+
async handleGaps(options) {
|
|
294
|
+
const gaps = getComplianceGaps(this.checklist);
|
|
295
|
+
const output = this.formatGaps(gaps);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
success: true,
|
|
299
|
+
output,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Format compliance status for display
|
|
305
|
+
* @param {Object} compliance - Compliance metrics
|
|
306
|
+
* @param {Array} gaps - List of gaps
|
|
307
|
+
* @returns {string} Formatted output
|
|
308
|
+
*/
|
|
309
|
+
formatStatus(compliance, gaps = []) {
|
|
310
|
+
const lines = [];
|
|
311
|
+
|
|
312
|
+
lines.push('Compliance Status');
|
|
313
|
+
lines.push('=================');
|
|
314
|
+
lines.push('');
|
|
315
|
+
|
|
316
|
+
// Overall score with progress bar
|
|
317
|
+
const percentage = Math.round(compliance.percentage);
|
|
318
|
+
const progressBar = this.createProgressBar(percentage);
|
|
319
|
+
lines.push(` Overall Score: ${percentage}% ${progressBar}`);
|
|
320
|
+
|
|
321
|
+
// Risk level based on compliance
|
|
322
|
+
const riskLevel = this.calculateRiskLevel(percentage);
|
|
323
|
+
lines.push(` Risk Level: ${riskLevel}`);
|
|
324
|
+
lines.push('');
|
|
325
|
+
|
|
326
|
+
// Category breakdown
|
|
327
|
+
lines.push(' Category Breakdown:');
|
|
328
|
+
|
|
329
|
+
if (compliance.byCategory) {
|
|
330
|
+
for (const [category, data] of Object.entries(compliance.byCategory)) {
|
|
331
|
+
const catPercentage = Math.round(data.percentage);
|
|
332
|
+
const catBar = this.createProgressBar(catPercentage, 20);
|
|
333
|
+
lines.push(` ${category.padEnd(22)} ${String(catPercentage).padStart(3)}% ${catBar}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
lines.push('');
|
|
338
|
+
|
|
339
|
+
// Gap summary
|
|
340
|
+
const gapCount = gaps.length || compliance.notImplemented + (compliance.partial || 0);
|
|
341
|
+
lines.push(` Gaps: ${gapCount} controls need attention`);
|
|
342
|
+
lines.push(" Run 'tlc compliance gaps' for details");
|
|
343
|
+
|
|
344
|
+
return lines.join('\n');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Format checklist for display
|
|
349
|
+
* @param {Array} controls - List of controls
|
|
350
|
+
* @param {Object} options - Display options
|
|
351
|
+
* @returns {string} Formatted output
|
|
352
|
+
*/
|
|
353
|
+
formatChecklist(controls, options = {}) {
|
|
354
|
+
const lines = [];
|
|
355
|
+
|
|
356
|
+
lines.push('SOC 2 Compliance Checklist');
|
|
357
|
+
lines.push('==========================');
|
|
358
|
+
lines.push('');
|
|
359
|
+
|
|
360
|
+
// Group by category if not filtered
|
|
361
|
+
const byCategory = {};
|
|
362
|
+
for (const control of controls) {
|
|
363
|
+
const cat = control.category || 'Uncategorized';
|
|
364
|
+
if (!byCategory[cat]) {
|
|
365
|
+
byCategory[cat] = [];
|
|
366
|
+
}
|
|
367
|
+
byCategory[cat].push(control);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
for (const [category, categoryControls] of Object.entries(byCategory)) {
|
|
371
|
+
lines.push(`${category}`);
|
|
372
|
+
lines.push('-'.repeat(category.length));
|
|
373
|
+
|
|
374
|
+
for (const control of categoryControls) {
|
|
375
|
+
const statusIcon = this.getStatusIcon(control.status);
|
|
376
|
+
lines.push(` ${statusIcon} [${control.id}] ${control.name}`);
|
|
377
|
+
if (control.status !== 'implemented') {
|
|
378
|
+
lines.push(` Status: ${control.status}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
lines.push('');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return lines.join('\n');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Format evidence for display
|
|
389
|
+
* @param {Array} evidenceItems - List of evidence items
|
|
390
|
+
* @returns {string} Formatted output
|
|
391
|
+
*/
|
|
392
|
+
formatEvidence(evidenceItems) {
|
|
393
|
+
const lines = [];
|
|
394
|
+
|
|
395
|
+
lines.push('Evidence Collection');
|
|
396
|
+
lines.push('===================');
|
|
397
|
+
lines.push('');
|
|
398
|
+
|
|
399
|
+
if (!evidenceItems || evidenceItems.length === 0) {
|
|
400
|
+
lines.push(' No evidence items collected yet.');
|
|
401
|
+
lines.push('');
|
|
402
|
+
lines.push(' To collect evidence, run:');
|
|
403
|
+
lines.push(' tlc compliance evidence --collect');
|
|
404
|
+
return lines.join('\n');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Group by type
|
|
408
|
+
const byType = {};
|
|
409
|
+
for (const item of evidenceItems) {
|
|
410
|
+
const type = item.type || 'other';
|
|
411
|
+
if (!byType[type]) {
|
|
412
|
+
byType[type] = [];
|
|
413
|
+
}
|
|
414
|
+
byType[type].push(item);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
lines.push(` Total Items: ${evidenceItems.length}`);
|
|
418
|
+
lines.push('');
|
|
419
|
+
|
|
420
|
+
for (const [type, items] of Object.entries(byType)) {
|
|
421
|
+
lines.push(` ${type.charAt(0).toUpperCase() + type.slice(1)} (${items.length}):`);
|
|
422
|
+
for (const item of items.slice(0, 5)) {
|
|
423
|
+
lines.push(` - ${item.id}: ${item.title || item.type}`);
|
|
424
|
+
}
|
|
425
|
+
if (items.length > 5) {
|
|
426
|
+
lines.push(` ... and ${items.length - 5} more`);
|
|
427
|
+
}
|
|
428
|
+
lines.push('');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return lines.join('\n');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Format report as text
|
|
436
|
+
* @param {Object} report - Generated report
|
|
437
|
+
* @returns {string} Formatted output
|
|
438
|
+
*/
|
|
439
|
+
formatReportText(report) {
|
|
440
|
+
const lines = [];
|
|
441
|
+
|
|
442
|
+
lines.push(report.title || 'SOC 2 Type II Readiness Report');
|
|
443
|
+
lines.push('='.repeat(40));
|
|
444
|
+
lines.push('');
|
|
445
|
+
|
|
446
|
+
if (report.summary) {
|
|
447
|
+
lines.push('Summary');
|
|
448
|
+
lines.push('-------');
|
|
449
|
+
lines.push(` Overall Score: ${report.summary.overallScore}%`);
|
|
450
|
+
lines.push(` Risk Level: ${report.summary.riskLevel}`);
|
|
451
|
+
lines.push(` Total Controls: ${report.summary.totalControls}`);
|
|
452
|
+
lines.push(` Implemented: ${report.summary.implemented}`);
|
|
453
|
+
lines.push(` Partial: ${report.summary.partial}`);
|
|
454
|
+
lines.push(` Gaps: ${report.summary.gaps}`);
|
|
455
|
+
lines.push('');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (report.categories) {
|
|
459
|
+
lines.push('Categories');
|
|
460
|
+
lines.push('----------');
|
|
461
|
+
for (const [category, data] of Object.entries(report.categories)) {
|
|
462
|
+
lines.push(` ${category}: ${data.score}%`);
|
|
463
|
+
}
|
|
464
|
+
lines.push('');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (report.recommendations && report.recommendations.length > 0) {
|
|
468
|
+
lines.push('Top Recommendations');
|
|
469
|
+
lines.push('-------------------');
|
|
470
|
+
for (const rec of report.recommendations.slice(0, 5)) {
|
|
471
|
+
lines.push(` - [${rec.controlId}] ${rec.action}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return lines.join('\n');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Format policies for display
|
|
480
|
+
* @param {Array} policies - List of policy objects
|
|
481
|
+
* @returns {string} Formatted output
|
|
482
|
+
*/
|
|
483
|
+
formatPolicies(policies) {
|
|
484
|
+
const lines = [];
|
|
485
|
+
|
|
486
|
+
lines.push('Security Policies');
|
|
487
|
+
lines.push('=================');
|
|
488
|
+
lines.push('');
|
|
489
|
+
|
|
490
|
+
lines.push(' Available Policies:');
|
|
491
|
+
lines.push('');
|
|
492
|
+
|
|
493
|
+
for (const { name, policy } of policies) {
|
|
494
|
+
lines.push(` [*] ${name}`);
|
|
495
|
+
if (policy && policy.title) {
|
|
496
|
+
lines.push(` Title: ${policy.title}`);
|
|
497
|
+
}
|
|
498
|
+
if (policy && policy.sections) {
|
|
499
|
+
lines.push(` Sections: ${policy.sections.length}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
lines.push('');
|
|
504
|
+
lines.push(' To export a policy, run:');
|
|
505
|
+
lines.push(' tlc compliance policies --export <type> --format markdown');
|
|
506
|
+
|
|
507
|
+
return lines.join('\n');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Format gaps for display
|
|
512
|
+
* @param {Array} gaps - List of gap controls
|
|
513
|
+
* @returns {string} Formatted output
|
|
514
|
+
*/
|
|
515
|
+
formatGaps(gaps) {
|
|
516
|
+
const lines = [];
|
|
517
|
+
|
|
518
|
+
lines.push('Compliance Gaps');
|
|
519
|
+
lines.push('===============');
|
|
520
|
+
lines.push('');
|
|
521
|
+
|
|
522
|
+
if (!gaps || gaps.length === 0) {
|
|
523
|
+
lines.push(' No gaps found - all controls are implemented!');
|
|
524
|
+
return lines.join('\n');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Group by severity
|
|
528
|
+
const bySeverity = {
|
|
529
|
+
high: [],
|
|
530
|
+
medium: [],
|
|
531
|
+
low: [],
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
for (const gap of gaps) {
|
|
535
|
+
const severity = gap.gapSeverity || 'medium';
|
|
536
|
+
if (bySeverity[severity]) {
|
|
537
|
+
bySeverity[severity].push(gap);
|
|
538
|
+
} else {
|
|
539
|
+
bySeverity.medium.push(gap);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Display high priority first
|
|
544
|
+
if (bySeverity.high.length > 0) {
|
|
545
|
+
lines.push(' High Priority:');
|
|
546
|
+
for (const gap of bySeverity.high) {
|
|
547
|
+
lines.push(` [${gap.id}] ${gap.name} - ${gap.status}`);
|
|
548
|
+
}
|
|
549
|
+
lines.push('');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (bySeverity.medium.length > 0) {
|
|
553
|
+
lines.push(' Medium Priority:');
|
|
554
|
+
for (const gap of bySeverity.medium) {
|
|
555
|
+
lines.push(` [${gap.id}] ${gap.name} - ${gap.status}`);
|
|
556
|
+
}
|
|
557
|
+
lines.push('');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (bySeverity.low.length > 0) {
|
|
561
|
+
lines.push(' Low Priority:');
|
|
562
|
+
for (const gap of bySeverity.low) {
|
|
563
|
+
lines.push(` [${gap.id}] ${gap.name} - ${gap.status}`);
|
|
564
|
+
}
|
|
565
|
+
lines.push('');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
lines.push(` Total gaps: ${gaps.length}`);
|
|
569
|
+
|
|
570
|
+
return lines.join('\n');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Create a progress bar string
|
|
575
|
+
* @param {number} percentage - Percentage (0-100)
|
|
576
|
+
* @param {number} width - Bar width in characters
|
|
577
|
+
* @returns {string} Progress bar
|
|
578
|
+
*/
|
|
579
|
+
createProgressBar(percentage, width = 22) {
|
|
580
|
+
const filled = Math.round((percentage / 100) * width);
|
|
581
|
+
const empty = width - filled;
|
|
582
|
+
return '\u2588'.repeat(filled) + '\u2591'.repeat(empty);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Calculate risk level from compliance percentage
|
|
587
|
+
* @param {number} percentage - Compliance percentage
|
|
588
|
+
* @returns {string} Risk level
|
|
589
|
+
*/
|
|
590
|
+
calculateRiskLevel(percentage) {
|
|
591
|
+
if (percentage >= 90) return 'Low';
|
|
592
|
+
if (percentage >= 70) return 'Medium';
|
|
593
|
+
if (percentage >= 50) return 'High';
|
|
594
|
+
return 'Critical';
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Get status icon for a control
|
|
599
|
+
* @param {string} status - Control status
|
|
600
|
+
* @returns {string} Status icon
|
|
601
|
+
*/
|
|
602
|
+
getStatusIcon(status) {
|
|
603
|
+
switch (status) {
|
|
604
|
+
case 'implemented':
|
|
605
|
+
return '[x]';
|
|
606
|
+
case 'partial':
|
|
607
|
+
return '[~]';
|
|
608
|
+
case 'not_implemented':
|
|
609
|
+
return '[ ]';
|
|
610
|
+
case 'not_applicable':
|
|
611
|
+
return '[N/A]';
|
|
612
|
+
default:
|
|
613
|
+
return '[ ]';
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|