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,30 @@
1
+ export interface AuditEntry {
2
+ id: string;
3
+ timestamp: string;
4
+ action: string;
5
+ user: string;
6
+ severity: 'error' | 'warn' | 'info' | 'debug';
7
+ details: string;
8
+ }
9
+ export interface AuditPaneProps {
10
+ entries?: AuditEntry[];
11
+ users?: string[];
12
+ actions?: string[];
13
+ userFilter?: string;
14
+ actionFilter?: string;
15
+ dateFrom?: string;
16
+ dateTo?: string;
17
+ expandedId?: string;
18
+ integrityStatus?: 'valid' | 'invalid' | 'unknown';
19
+ isActive?: boolean;
20
+ maxLines?: number;
21
+ onUserFilterChange?: (user: string | undefined) => void;
22
+ onActionFilterChange?: (action: string | undefined) => void;
23
+ onExpandEntry?: (id: string | undefined) => void;
24
+ }
25
+ export declare function formatTimestamp(ts: string | undefined): string;
26
+ export declare function filterByUser(entries: AuditEntry[], user: string | undefined): AuditEntry[];
27
+ export declare function filterByAction(entries: AuditEntry[], action: string | undefined): AuditEntry[];
28
+ export declare function filterByDateRange(entries: AuditEntry[], from: string | undefined, to: string | undefined): AuditEntry[];
29
+ export declare function AuditPane({ entries, users, actions, userFilter, actionFilter, dateFrom, dateTo, expandedId, integrityStatus, isActive, maxLines, onUserFilterChange, onActionFilterChange, onExpandEntry, }: AuditPaneProps): import("react/jsx-runtime").JSX.Element;
30
+ export default AuditPane;
@@ -0,0 +1,127 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { useState } from 'react';
4
+ const severityColors = {
5
+ error: 'red',
6
+ warn: 'yellow',
7
+ info: 'cyan',
8
+ debug: 'gray',
9
+ };
10
+ const severityIcons = {
11
+ error: 'x',
12
+ warn: '!',
13
+ info: 'i',
14
+ debug: '.',
15
+ };
16
+ export function formatTimestamp(ts) {
17
+ if (!ts)
18
+ return '';
19
+ try {
20
+ const date = new Date(ts);
21
+ if (isNaN(date.getTime()))
22
+ return '';
23
+ const hours = String(date.getUTCHours()).padStart(2, '0');
24
+ const minutes = String(date.getUTCMinutes()).padStart(2, '0');
25
+ const seconds = String(date.getUTCSeconds()).padStart(2, '0');
26
+ return `${hours}:${minutes}:${seconds}`;
27
+ }
28
+ catch {
29
+ return '';
30
+ }
31
+ }
32
+ export function filterByUser(entries, user) {
33
+ if (!user)
34
+ return entries;
35
+ return entries.filter((entry) => entry.user === user);
36
+ }
37
+ export function filterByAction(entries, action) {
38
+ if (!action)
39
+ return entries;
40
+ return entries.filter((entry) => entry.action === action);
41
+ }
42
+ export function filterByDateRange(entries, from, to) {
43
+ if (!from && !to)
44
+ return entries;
45
+ return entries.filter((entry) => {
46
+ const entryDate = new Date(entry.timestamp);
47
+ if (from && entryDate < new Date(from))
48
+ return false;
49
+ if (to && entryDate > new Date(to))
50
+ return false;
51
+ return true;
52
+ });
53
+ }
54
+ function IntegrityIndicator({ status }) {
55
+ if (!status)
56
+ return null;
57
+ let color;
58
+ let label;
59
+ switch (status) {
60
+ case 'valid':
61
+ color = 'green';
62
+ label = 'OK';
63
+ break;
64
+ case 'invalid':
65
+ color = 'red';
66
+ label = 'FAILED';
67
+ break;
68
+ default:
69
+ color = 'gray';
70
+ label = '?';
71
+ }
72
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Integrity: " }), _jsx(Text, { color: color, children: label })] }));
73
+ }
74
+ function AuditEntryLine({ entry, expanded, }) {
75
+ const timestamp = formatTimestamp(entry.timestamp);
76
+ const color = severityColors[entry.severity] || 'white';
77
+ const icon = severityIcons[entry.severity] || ' ';
78
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [timestamp && _jsxs(Text, { dimColor: true, children: ["[", timestamp, "] "] }), _jsxs(Text, { color: color, children: ["[", entry.severity, "] "] }), _jsxs(Text, { color: color, children: [icon, " "] }), _jsx(Text, { color: "blue", children: entry.user }), _jsx(Text, { children: " " }), _jsx(Text, { children: entry.action })] }), expanded && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { dimColor: true, children: entry.details }) }))] }));
79
+ }
80
+ function FilterDisplay({ users, actions, userFilter, actionFilter, }) {
81
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [users.length > 0 && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Users: " }), _jsx(Text, { bold: !userFilter, color: !userFilter ? 'blue' : undefined, children: "[All]" }), users.map((u) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { bold: userFilter === u, color: userFilter === u ? 'blue' : undefined, children: ["[", u, "]"] }) }, u)))] })), actions.length > 0 && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Actions: " }), _jsx(Text, { bold: !actionFilter, color: !actionFilter ? 'blue' : undefined, children: "[All]" }), actions.map((a) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { bold: actionFilter === a, color: actionFilter === a ? 'blue' : undefined, children: ["[", a, "]"] }) }, a)))] }))] }));
82
+ }
83
+ export function AuditPane({ entries = [], users = [], actions = [], userFilter, actionFilter, dateFrom, dateTo, expandedId, integrityStatus, isActive = false, maxLines = 20, onUserFilterChange, onActionFilterChange, onExpandEntry, }) {
84
+ const [localExpandedId, setLocalExpandedId] = useState(expandedId);
85
+ const effectiveExpandedId = expandedId !== undefined ? expandedId : localExpandedId;
86
+ // Apply filters
87
+ let filteredEntries = filterByUser(entries, userFilter);
88
+ filteredEntries = filterByAction(filteredEntries, actionFilter);
89
+ filteredEntries = filterByDateRange(filteredEntries, dateFrom, dateTo);
90
+ // Take last N lines for display
91
+ const displayEntries = filteredEntries.slice(-maxLines);
92
+ useInput((input, key) => {
93
+ if (!isActive)
94
+ return;
95
+ // User filter shortcuts (1-9 for users)
96
+ if (input === '0' || input === 'u') {
97
+ onUserFilterChange?.(undefined);
98
+ }
99
+ else if (input >= '1' && input <= '9') {
100
+ const idx = parseInt(input, 10) - 1;
101
+ if (idx < users.length) {
102
+ onUserFilterChange?.(users[idx]);
103
+ }
104
+ }
105
+ // Action filter
106
+ if (input === 'a') {
107
+ onActionFilterChange?.(undefined);
108
+ }
109
+ // Toggle expand
110
+ if (key.return && displayEntries.length > 0) {
111
+ const firstEntry = displayEntries[0];
112
+ if (effectiveExpandedId === firstEntry.id) {
113
+ setLocalExpandedId(undefined);
114
+ onExpandEntry?.(undefined);
115
+ }
116
+ else {
117
+ setLocalExpandedId(firstEntry.id);
118
+ onExpandEntry?.(firstEntry.id);
119
+ }
120
+ }
121
+ }, { isActive });
122
+ if (entries.length === 0) {
123
+ return (_jsxs(Box, { padding: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Audit Log" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "No audit entries" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Audit events will appear here" }) }), integrityStatus && (_jsx(Box, { marginTop: 1, children: _jsx(IntegrityIndicator, { status: integrityStatus }) }))] }));
124
+ }
125
+ return (_jsxs(Box, { padding: 1, flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Audit Log " }), _jsxs(Text, { dimColor: true, children: ["(", filteredEntries.length, " of ", entries.length, " entries)"] })] }), integrityStatus && (_jsx(Box, { marginBottom: 1, children: _jsx(IntegrityIndicator, { status: integrityStatus }) })), (users.length > 0 || actions.length > 0) && (_jsx(FilterDisplay, { users: users, actions: actions, userFilter: userFilter, actionFilter: actionFilter })), _jsx(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: displayEntries.length === 0 ? (_jsx(Text, { color: "gray", children: "No matching entries" })) : (displayEntries.map((entry) => (_jsx(AuditEntryLine, { entry: entry, expanded: effectiveExpandedId === entry.id }, entry.id)))) }), isActive && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[0] All users [1-9] Select user [a] All actions [Enter] Expand" }) }))] }));
126
+ }
127
+ export default AuditPane;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,339 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { AuditPane } from './AuditPane.js';
5
+ describe('AuditPane', () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers();
8
+ });
9
+ afterEach(() => {
10
+ vi.useRealTimers();
11
+ });
12
+ describe('audit entries list', () => {
13
+ it('renders audit entries list', () => {
14
+ const entries = [
15
+ {
16
+ id: '1',
17
+ timestamp: '2024-01-15T10:30:00.000Z',
18
+ action: 'claim',
19
+ user: 'alice',
20
+ severity: 'info',
21
+ details: 'Claimed task 1',
22
+ },
23
+ {
24
+ id: '2',
25
+ timestamp: '2024-01-15T10:31:00.000Z',
26
+ action: 'verify',
27
+ user: 'bob',
28
+ severity: 'info',
29
+ details: 'Verified task 1',
30
+ },
31
+ ];
32
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries }));
33
+ const output = lastFrame();
34
+ expect(output).toContain('Audit Log');
35
+ expect(output).toContain('claim');
36
+ expect(output).toContain('verify');
37
+ });
38
+ });
39
+ describe('entry with timestamp and action', () => {
40
+ it('renders entry with timestamp and action', () => {
41
+ const entries = [
42
+ {
43
+ id: '1',
44
+ timestamp: '2024-01-15T10:30:45.000Z',
45
+ action: 'release',
46
+ user: 'alice',
47
+ severity: 'info',
48
+ details: 'Released task 2',
49
+ },
50
+ ];
51
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries }));
52
+ const output = lastFrame();
53
+ expect(output).toContain('10:30:45');
54
+ expect(output).toContain('release');
55
+ expect(output).toContain('alice');
56
+ });
57
+ });
58
+ describe('severity badge', () => {
59
+ it('renders severity badge with correct color for error', () => {
60
+ const entries = [
61
+ {
62
+ id: '1',
63
+ timestamp: '2024-01-15T10:30:00.000Z',
64
+ action: 'integrity-fail',
65
+ user: 'system',
66
+ severity: 'error',
67
+ details: 'Integrity check failed',
68
+ },
69
+ ];
70
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries }));
71
+ const output = lastFrame();
72
+ expect(output).toContain('error');
73
+ expect(output).toContain('integrity-fail');
74
+ });
75
+ it('renders severity badge with correct color for warn', () => {
76
+ const entries = [
77
+ {
78
+ id: '1',
79
+ timestamp: '2024-01-15T10:30:00.000Z',
80
+ action: 'conflict',
81
+ user: 'alice',
82
+ severity: 'warn',
83
+ details: 'Task conflict detected',
84
+ },
85
+ ];
86
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries }));
87
+ const output = lastFrame();
88
+ expect(output).toContain('warn');
89
+ });
90
+ it('renders severity badge with correct color for info', () => {
91
+ const entries = [
92
+ {
93
+ id: '1',
94
+ timestamp: '2024-01-15T10:30:00.000Z',
95
+ action: 'claim',
96
+ user: 'alice',
97
+ severity: 'info',
98
+ details: 'Claimed task',
99
+ },
100
+ ];
101
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries }));
102
+ const output = lastFrame();
103
+ expect(output).toContain('info');
104
+ });
105
+ });
106
+ describe('expandable entry details', () => {
107
+ it('expands entry to show details', () => {
108
+ const entries = [
109
+ {
110
+ id: '1',
111
+ timestamp: '2024-01-15T10:30:00.000Z',
112
+ action: 'claim',
113
+ user: 'alice',
114
+ severity: 'info',
115
+ details: 'Claimed task 1 in phase 5',
116
+ },
117
+ ];
118
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, expandedId: "1" }));
119
+ const output = lastFrame();
120
+ expect(output).toContain('Claimed task 1 in phase 5');
121
+ });
122
+ it('does not show details when not expanded', () => {
123
+ const entries = [
124
+ {
125
+ id: '1',
126
+ timestamp: '2024-01-15T10:30:00.000Z',
127
+ action: 'claim',
128
+ user: 'alice',
129
+ severity: 'info',
130
+ details: 'This is a long detail that should be hidden',
131
+ },
132
+ ];
133
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries }));
134
+ const output = lastFrame();
135
+ // Details should be truncated or hidden
136
+ expect(output).not.toContain('This is a long detail that should be hidden');
137
+ });
138
+ });
139
+ describe('user filter', () => {
140
+ it('filters by user selection', () => {
141
+ const entries = [
142
+ {
143
+ id: '1',
144
+ timestamp: '2024-01-15T10:30:00.000Z',
145
+ action: 'claim',
146
+ user: 'alice',
147
+ severity: 'info',
148
+ details: 'Alice claim',
149
+ },
150
+ {
151
+ id: '2',
152
+ timestamp: '2024-01-15T10:31:00.000Z',
153
+ action: 'verify',
154
+ user: 'bob',
155
+ severity: 'info',
156
+ details: 'Bob verify',
157
+ },
158
+ {
159
+ id: '3',
160
+ timestamp: '2024-01-15T10:32:00.000Z',
161
+ action: 'release',
162
+ user: 'alice',
163
+ severity: 'info',
164
+ details: 'Alice release',
165
+ },
166
+ ];
167
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, userFilter: "alice" }));
168
+ const output = lastFrame();
169
+ expect(output).toContain('alice');
170
+ expect(output).toContain('claim');
171
+ expect(output).toContain('release');
172
+ expect(output).not.toContain('bob');
173
+ });
174
+ });
175
+ describe('action type filter', () => {
176
+ it('filters by action type', () => {
177
+ const entries = [
178
+ {
179
+ id: '1',
180
+ timestamp: '2024-01-15T10:30:00.000Z',
181
+ action: 'claim',
182
+ user: 'alice',
183
+ severity: 'info',
184
+ details: 'Claim 1',
185
+ },
186
+ {
187
+ id: '2',
188
+ timestamp: '2024-01-15T10:31:00.000Z',
189
+ action: 'verify',
190
+ user: 'bob',
191
+ severity: 'info',
192
+ details: 'Verify 1',
193
+ },
194
+ {
195
+ id: '3',
196
+ timestamp: '2024-01-15T10:32:00.000Z',
197
+ action: 'claim',
198
+ user: 'bob',
199
+ severity: 'info',
200
+ details: 'Claim 2',
201
+ },
202
+ ];
203
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, actionFilter: "claim" }));
204
+ const output = lastFrame();
205
+ expect(output).toContain('claim');
206
+ expect(output).not.toContain('verify');
207
+ });
208
+ });
209
+ describe('date range filter', () => {
210
+ it('filters by date range', () => {
211
+ const entries = [
212
+ {
213
+ id: '1',
214
+ timestamp: '2024-01-14T10:30:00.000Z',
215
+ action: 'claim',
216
+ user: 'alice',
217
+ severity: 'info',
218
+ details: 'Yesterday claim',
219
+ },
220
+ {
221
+ id: '2',
222
+ timestamp: '2024-01-15T10:30:00.000Z',
223
+ action: 'verify',
224
+ user: 'bob',
225
+ severity: 'info',
226
+ details: 'Today verify',
227
+ },
228
+ {
229
+ id: '3',
230
+ timestamp: '2024-01-16T10:30:00.000Z',
231
+ action: 'release',
232
+ user: 'alice',
233
+ severity: 'info',
234
+ details: 'Tomorrow release',
235
+ },
236
+ ];
237
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, dateFrom: "2024-01-15T00:00:00.000Z", dateTo: "2024-01-15T23:59:59.000Z" }));
238
+ const output = lastFrame();
239
+ expect(output).toContain('verify');
240
+ expect(output).not.toContain('Yesterday');
241
+ expect(output).not.toContain('Tomorrow');
242
+ });
243
+ });
244
+ describe('integrity status', () => {
245
+ it('shows integrity status indicator when valid', () => {
246
+ const entries = [
247
+ {
248
+ id: '1',
249
+ timestamp: '2024-01-15T10:30:00.000Z',
250
+ action: 'claim',
251
+ user: 'alice',
252
+ severity: 'info',
253
+ details: 'Claimed task',
254
+ },
255
+ ];
256
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, integrityStatus: "valid" }));
257
+ const output = lastFrame();
258
+ expect(output).toContain('Integrity');
259
+ expect(output).toMatch(/valid|OK/i);
260
+ });
261
+ it('shows integrity status indicator when invalid', () => {
262
+ const entries = [
263
+ {
264
+ id: '1',
265
+ timestamp: '2024-01-15T10:30:00.000Z',
266
+ action: 'claim',
267
+ user: 'alice',
268
+ severity: 'info',
269
+ details: 'Claimed task',
270
+ },
271
+ ];
272
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, integrityStatus: "invalid" }));
273
+ const output = lastFrame();
274
+ expect(output).toContain('Integrity');
275
+ expect(output).toMatch(/invalid|FAILED/i);
276
+ });
277
+ it('shows integrity status indicator when unknown', () => {
278
+ const entries = [
279
+ {
280
+ id: '1',
281
+ timestamp: '2024-01-15T10:30:00.000Z',
282
+ action: 'claim',
283
+ user: 'alice',
284
+ severity: 'info',
285
+ details: 'Claimed task',
286
+ },
287
+ ];
288
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, integrityStatus: "unknown" }));
289
+ const output = lastFrame();
290
+ expect(output).toContain('Integrity');
291
+ });
292
+ });
293
+ describe('empty audit log', () => {
294
+ it('handles empty audit log', () => {
295
+ const { lastFrame } = render(_jsx(AuditPane, { entries: [] }));
296
+ const output = lastFrame();
297
+ expect(output).toContain('Audit');
298
+ expect(output).toMatch(/No.*entries|empty/i);
299
+ });
300
+ });
301
+ describe('scrollable list', () => {
302
+ it('shows scrollable list of audit entries', () => {
303
+ const entries = Array.from({ length: 50 }, (_, i) => ({
304
+ id: String(i + 1),
305
+ timestamp: `2024-01-15T10:${String(i).padStart(2, '0')}:00.000Z`,
306
+ action: 'claim',
307
+ user: 'user' + (i % 3),
308
+ severity: 'info',
309
+ details: `Entry ${i + 1}`,
310
+ }));
311
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, maxLines: 10 }));
312
+ const output = lastFrame();
313
+ // Should show entry count
314
+ expect(output).toContain('50');
315
+ // Should not show all 50 entries visually
316
+ expect(output).toBeDefined();
317
+ });
318
+ });
319
+ describe('filter controls display', () => {
320
+ it('shows filter controls when active', () => {
321
+ const entries = [
322
+ {
323
+ id: '1',
324
+ timestamp: '2024-01-15T10:30:00.000Z',
325
+ action: 'claim',
326
+ user: 'alice',
327
+ severity: 'info',
328
+ details: 'Claimed task',
329
+ },
330
+ ];
331
+ const users = ['alice', 'bob'];
332
+ const actions = ['claim', 'verify', 'release'];
333
+ const { lastFrame } = render(_jsx(AuditPane, { entries: entries, users: users, actions: actions, isActive: true }));
334
+ const output = lastFrame();
335
+ // Should show some filter UI
336
+ expect(output).toBeDefined();
337
+ });
338
+ });
339
+ });
@@ -0,0 +1,39 @@
1
+ export interface CategoryScore {
2
+ name: string;
3
+ score: number;
4
+ gapCount: number;
5
+ }
6
+ export interface EvidenceItem {
7
+ id: string;
8
+ name: string;
9
+ collectedAt: string;
10
+ status: 'valid' | 'expired' | 'pending';
11
+ }
12
+ export interface Gap {
13
+ id: string;
14
+ name: string;
15
+ severity: 'high' | 'medium' | 'low';
16
+ status: string;
17
+ }
18
+ export interface TimelineEvent {
19
+ id: string;
20
+ date: string;
21
+ event: string;
22
+ status: 'upcoming' | 'completed' | 'in-progress';
23
+ }
24
+ export interface CompliancePaneProps {
25
+ score: number;
26
+ riskLevel: 'low' | 'medium' | 'high' | 'critical';
27
+ categories: CategoryScore[];
28
+ evidence: EvidenceItem[];
29
+ gaps: Gap[];
30
+ lastReportDate?: string;
31
+ auditTimeline?: TimelineEvent[];
32
+ onDownloadReport?: () => void;
33
+ onRefresh?: () => void;
34
+ loading?: boolean;
35
+ error?: string | null;
36
+ isActive?: boolean;
37
+ }
38
+ export declare function CompliancePane({ score, riskLevel, categories, evidence, gaps, lastReportDate, auditTimeline, onDownloadReport, onRefresh, loading, error, isActive, }: CompliancePaneProps): import("react/jsx-runtime").JSX.Element;
39
+ export default CompliancePane;
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ function getScoreColor(score) {
4
+ if (score >= 80)
5
+ return 'green';
6
+ if (score >= 60)
7
+ return 'yellow';
8
+ return 'red';
9
+ }
10
+ function getRiskColor(riskLevel) {
11
+ switch (riskLevel) {
12
+ case 'low':
13
+ return 'green';
14
+ case 'medium':
15
+ return 'yellow';
16
+ case 'high':
17
+ return 'red';
18
+ case 'critical':
19
+ return 'magenta';
20
+ default:
21
+ return 'gray';
22
+ }
23
+ }
24
+ function getSeverityColor(severity) {
25
+ switch (severity) {
26
+ case 'high':
27
+ return 'red';
28
+ case 'medium':
29
+ return 'yellow';
30
+ case 'low':
31
+ return 'green';
32
+ default:
33
+ return 'gray';
34
+ }
35
+ }
36
+ function ScoreDisplay({ score, riskLevel }) {
37
+ const barLength = 20;
38
+ const filledLength = Math.round((score / 100) * barLength);
39
+ const bar = '\u2588'.repeat(filledLength) + '\u2591'.repeat(barLength - filledLength);
40
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Score: " }), _jsxs(Text, { color: getScoreColor(score), children: [score, "%"] }), _jsx(Text, { dimColor: true, children: " | Risk: " }), _jsx(Text, { color: getRiskColor(riskLevel), children: riskLevel.toUpperCase() })] }), _jsx(Box, { children: _jsxs(Text, { color: getScoreColor(score), children: ["[", bar, "]"] }) })] }));
41
+ }
42
+ function CategoryBreakdown({ categories }) {
43
+ if (categories.length === 0) {
44
+ return null;
45
+ }
46
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "Categories:" }), categories.map((category) => (_jsxs(Box, { children: [_jsxs(Text, { children: [" ", category.name, ": "] }), _jsxs(Text, { color: getScoreColor(category.score), children: [category.score, "%"] }), category.gapCount > 0 && (_jsxs(Text, { dimColor: true, children: [" (", category.gapCount, " gaps)"] }))] }, category.name)))] }));
47
+ }
48
+ function EvidenceList({ evidence }) {
49
+ if (evidence.length === 0) {
50
+ return null;
51
+ }
52
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "Recent Evidence:" }), evidence.slice(0, 5).map((item) => {
53
+ const statusColor = item.status === 'valid' ? 'green' : item.status === 'expired' ? 'red' : 'yellow';
54
+ return (_jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { children: item.name }), _jsx(Text, { dimColor: true, children: " - " }), _jsx(Text, { color: statusColor, children: item.status })] }, item.id));
55
+ })] }));
56
+ }
57
+ function GapsList({ gaps }) {
58
+ if (gaps.length === 0) {
59
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "Gaps:" }), _jsx(Text, { color: "green", children: " No compliance gaps" })] }));
60
+ }
61
+ const highCount = gaps.filter((g) => g.severity === 'high').length;
62
+ const mediumCount = gaps.filter((g) => g.severity === 'medium').length;
63
+ const lowCount = gaps.filter((g) => g.severity === 'low').length;
64
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { bold: true, dimColor: true, children: ["Gaps (", gaps.length, " total):"] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " Summary: " }), highCount > 0 && _jsxs(Text, { color: "red", children: [highCount, " high "] }), mediumCount > 0 && _jsxs(Text, { color: "yellow", children: [mediumCount, " medium "] }), lowCount > 0 && _jsxs(Text, { color: "green", children: [lowCount, " low"] })] }), gaps.slice(0, 5).map((gap) => (_jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: getSeverityColor(gap.severity), children: ["[", gap.severity, "]"] }), _jsxs(Text, { children: [" ", gap.name] }), _jsxs(Text, { dimColor: true, children: [" (", gap.status, ")"] })] }, gap.id)))] }));
65
+ }
66
+ function AuditTimeline({ timeline }) {
67
+ if (!timeline || timeline.length === 0) {
68
+ return null;
69
+ }
70
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "Audit Timeline:" }), timeline.map((event) => {
71
+ const statusColor = event.status === 'completed' ? 'green' : event.status === 'upcoming' ? 'cyan' : 'yellow';
72
+ return (_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [" ", event.date, " "] }), _jsx(Text, { children: event.event }), _jsxs(Text, { color: statusColor, children: [" [", event.status, "]"] })] }, event.id));
73
+ })] }));
74
+ }
75
+ function LoadingIndicator() {
76
+ return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", children: "Loading compliance data..." }) }));
77
+ }
78
+ function ErrorDisplay({ error }) {
79
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "red", paddingX: 1, children: [_jsx(Text, { color: "red", bold: true, children: "Error" }), _jsx(Text, { color: "red", children: error })] }));
80
+ }
81
+ export function CompliancePane({ score, riskLevel, categories, evidence, gaps, lastReportDate, auditTimeline, onDownloadReport, onRefresh, loading = false, error = null, isActive = false, }) {
82
+ useInput((input, _key) => {
83
+ if (!isActive)
84
+ return;
85
+ // Download report with 'd'
86
+ if (input === 'd' && onDownloadReport) {
87
+ onDownloadReport();
88
+ }
89
+ // Refresh with 'r'
90
+ if (input === 'r' && onRefresh) {
91
+ onRefresh();
92
+ }
93
+ }, { isActive });
94
+ return (_jsxs(Box, { padding: 1, flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Compliance Overview" }), lastReportDate && (_jsxs(Text, { dimColor: true, children: [" (Last report: ", lastReportDate, ")"] }))] }), loading && _jsx(LoadingIndicator, {}), error && _jsx(ErrorDisplay, { error: error }), !loading && (_jsxs(_Fragment, { children: [_jsx(ScoreDisplay, { score: score, riskLevel: riskLevel }), _jsx(CategoryBreakdown, { categories: categories }), _jsx(EvidenceList, { evidence: evidence }), _jsx(GapsList, { gaps: gaps }), _jsx(AuditTimeline, { timeline: auditTimeline || [] })] })), isActive && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [onDownloadReport && '[d] Download report ', onRefresh && '[r] Refresh'] }) }))] }));
95
+ }
96
+ export default CompliancePane;
@@ -0,0 +1 @@
1
+ export {};