@hatem427/code-guard-ci 1.0.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 (107) hide show
  1. package/.husky/pre-commit +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +646 -0
  4. package/config/angular.config.ts +223 -0
  5. package/config/guidelines.config.ts +229 -0
  6. package/config/nextjs.config.ts +160 -0
  7. package/config/react.config.ts +330 -0
  8. package/dist/config/angular.config.d.ts +15 -0
  9. package/dist/config/angular.config.d.ts.map +1 -0
  10. package/dist/config/angular.config.js +187 -0
  11. package/dist/config/angular.config.js.map +1 -0
  12. package/dist/config/guidelines.config.d.ts +63 -0
  13. package/dist/config/guidelines.config.d.ts.map +1 -0
  14. package/dist/config/guidelines.config.js +167 -0
  15. package/dist/config/guidelines.config.js.map +1 -0
  16. package/dist/config/nextjs.config.d.ts +18 -0
  17. package/dist/config/nextjs.config.d.ts.map +1 -0
  18. package/dist/config/nextjs.config.js +133 -0
  19. package/dist/config/nextjs.config.js.map +1 -0
  20. package/dist/config/react.config.d.ts +15 -0
  21. package/dist/config/react.config.d.ts.map +1 -0
  22. package/dist/config/react.config.js +287 -0
  23. package/dist/config/react.config.js.map +1 -0
  24. package/dist/scripts/auto-fix.d.ts +16 -0
  25. package/dist/scripts/auto-fix.d.ts.map +1 -0
  26. package/dist/scripts/auto-fix.js +130 -0
  27. package/dist/scripts/auto-fix.js.map +1 -0
  28. package/dist/scripts/cli.d.ts +17 -0
  29. package/dist/scripts/cli.d.ts.map +1 -0
  30. package/dist/scripts/cli.js +255 -0
  31. package/dist/scripts/cli.js.map +1 -0
  32. package/dist/scripts/delete-bypass-logs.d.ts +17 -0
  33. package/dist/scripts/delete-bypass-logs.d.ts.map +1 -0
  34. package/dist/scripts/delete-bypass-logs.js +242 -0
  35. package/dist/scripts/delete-bypass-logs.js.map +1 -0
  36. package/dist/scripts/generate-doc.d.ts +18 -0
  37. package/dist/scripts/generate-doc.d.ts.map +1 -0
  38. package/dist/scripts/generate-doc.js +300 -0
  39. package/dist/scripts/generate-doc.js.map +1 -0
  40. package/dist/scripts/generate-pr-checklist.d.ts +20 -0
  41. package/dist/scripts/generate-pr-checklist.d.ts.map +1 -0
  42. package/dist/scripts/generate-pr-checklist.js +276 -0
  43. package/dist/scripts/generate-pr-checklist.js.map +1 -0
  44. package/dist/scripts/precommit-check.d.ts +23 -0
  45. package/dist/scripts/precommit-check.d.ts.map +1 -0
  46. package/dist/scripts/precommit-check.js +331 -0
  47. package/dist/scripts/precommit-check.js.map +1 -0
  48. package/dist/scripts/set-admin-password.d.ts +14 -0
  49. package/dist/scripts/set-admin-password.d.ts.map +1 -0
  50. package/dist/scripts/set-admin-password.js +116 -0
  51. package/dist/scripts/set-admin-password.js.map +1 -0
  52. package/dist/scripts/set-bypass-password.d.ts +11 -0
  53. package/dist/scripts/set-bypass-password.d.ts.map +1 -0
  54. package/dist/scripts/set-bypass-password.js +106 -0
  55. package/dist/scripts/set-bypass-password.js.map +1 -0
  56. package/dist/scripts/utils/auto-fixer.d.ts +28 -0
  57. package/dist/scripts/utils/auto-fixer.d.ts.map +1 -0
  58. package/dist/scripts/utils/auto-fixer.js +177 -0
  59. package/dist/scripts/utils/auto-fixer.js.map +1 -0
  60. package/dist/scripts/utils/bypass-manager.d.ts +101 -0
  61. package/dist/scripts/utils/bypass-manager.d.ts.map +1 -0
  62. package/dist/scripts/utils/bypass-manager.js +496 -0
  63. package/dist/scripts/utils/bypass-manager.js.map +1 -0
  64. package/dist/scripts/utils/code-analyzer.d.ts +34 -0
  65. package/dist/scripts/utils/code-analyzer.d.ts.map +1 -0
  66. package/dist/scripts/utils/code-analyzer.js +323 -0
  67. package/dist/scripts/utils/code-analyzer.js.map +1 -0
  68. package/dist/scripts/utils/file-checker.d.ts +93 -0
  69. package/dist/scripts/utils/file-checker.d.ts.map +1 -0
  70. package/dist/scripts/utils/file-checker.js +248 -0
  71. package/dist/scripts/utils/file-checker.js.map +1 -0
  72. package/dist/scripts/utils/logger.d.ts +26 -0
  73. package/dist/scripts/utils/logger.d.ts.map +1 -0
  74. package/dist/scripts/utils/logger.js +86 -0
  75. package/dist/scripts/utils/logger.js.map +1 -0
  76. package/dist/scripts/utils/project-detector.d.ts +34 -0
  77. package/dist/scripts/utils/project-detector.d.ts.map +1 -0
  78. package/dist/scripts/utils/project-detector.js +124 -0
  79. package/dist/scripts/utils/project-detector.js.map +1 -0
  80. package/dist/scripts/utils/rule-engine.d.ts +57 -0
  81. package/dist/scripts/utils/rule-engine.d.ts.map +1 -0
  82. package/dist/scripts/utils/rule-engine.js +158 -0
  83. package/dist/scripts/utils/rule-engine.js.map +1 -0
  84. package/dist/scripts/view-bypass-log.d.ts +13 -0
  85. package/dist/scripts/view-bypass-log.d.ts.map +1 -0
  86. package/dist/scripts/view-bypass-log.js +117 -0
  87. package/dist/scripts/view-bypass-log.js.map +1 -0
  88. package/package.json +74 -0
  89. package/scripts/auto-fix.ts +115 -0
  90. package/scripts/cli.ts +246 -0
  91. package/scripts/delete-bypass-logs.ts +253 -0
  92. package/scripts/generate-doc.ts +317 -0
  93. package/scripts/generate-pr-checklist.ts +285 -0
  94. package/scripts/precommit-check.ts +349 -0
  95. package/scripts/set-admin-password.ts +90 -0
  96. package/scripts/set-bypass-password.ts +80 -0
  97. package/scripts/utils/auto-fixer.ts +181 -0
  98. package/scripts/utils/bypass-manager.ts +566 -0
  99. package/scripts/utils/code-analyzer.ts +341 -0
  100. package/scripts/utils/file-checker.ts +253 -0
  101. package/scripts/utils/logger.ts +88 -0
  102. package/scripts/utils/project-detector.ts +115 -0
  103. package/scripts/utils/rule-engine.ts +186 -0
  104. package/scripts/view-bypass-log.ts +92 -0
  105. package/templates/feature-doc-api.md +101 -0
  106. package/templates/feature-doc-service.md +113 -0
  107. package/templates/feature-doc-ui.md +91 -0
@@ -0,0 +1,566 @@
1
+ /**
2
+ * ============================================================================
3
+ * bypass-manager.ts — Bypass audit logging with authentication
4
+ * ============================================================================
5
+ *
6
+ * Tracks all bypass attempts with:
7
+ * - Author name (from git config)
8
+ * - Timestamp
9
+ * - Reason
10
+ * - Password verification
11
+ * - Commit hash
12
+ *
13
+ * Bypass log saved to: .code-guardian/bypass-log.json
14
+ */
15
+
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ import { execSync } from 'child_process';
19
+ import * as crypto from 'crypto';
20
+
21
+ // ── Types ───────────────────────────────────────────────────────────────────
22
+
23
+ export interface BypassEntry {
24
+ /** Unique ID for this bypass */
25
+ id: string;
26
+ /** Git author name */
27
+ author: string;
28
+ /** Git author email */
29
+ email: string;
30
+ /** When the bypass occurred */
31
+ timestamp: string;
32
+ /** Reason for bypass */
33
+ reason: string;
34
+ /** Commit message */
35
+ commitMessage: string;
36
+ /** Commit hash (after commit) */
37
+ commitHash?: string;
38
+ /** Branch name */
39
+ branch: string;
40
+ /** Files that were bypassed */
41
+ files: string[];
42
+ /** How bypass was triggered (message flag or env var) */
43
+ method: 'commit-message' | 'env-variable' | 'password';
44
+ }
45
+
46
+ export interface BypassLog {
47
+ /** All bypass entries */
48
+ entries: BypassEntry[];
49
+ /** Last updated timestamp */
50
+ lastUpdated: string;
51
+ /** Integrity checksum to detect tampering */
52
+ checksum?: string;
53
+ /** Flag indicating if log is immutable (cannot be deleted) */
54
+ immutable: boolean;
55
+ }
56
+
57
+ // ── Configuration ───────────────────────────────────────────────────────────
58
+
59
+ const BYPASS_DIR = '.code-guardian';
60
+ const BYPASS_LOG_FILE = 'bypass-log.json';
61
+ const PASSWORD_FILE = 'bypass-password.hash';
62
+ const ADMIN_FILE = 'admin-credentials.hash';
63
+ const LOG_ARCHIVE_DIR = 'bypass-archive';
64
+
65
+ // Default password hash (password: "bypass123" - CHANGE THIS!)
66
+ const DEFAULT_PASSWORD_HASH = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'; // SHA-256 of "bypass123"
67
+ // Default admin password hash (password: "admin123" - CHANGE THIS IMMEDIATELY!)
68
+ const DEFAULT_ADMIN_PASSWORD_HASH = '240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9'; // SHA-256 of "admin123"
69
+
70
+ // ── Password Management ─────────────────────────────────────────────────────
71
+
72
+ /**
73
+ * Hash a password using SHA-256
74
+ */
75
+ function hashPassword(password: string): string {
76
+ return crypto.createHash('sha256').update(password).digest('hex');
77
+ }
78
+
79
+ /**
80
+ * Get the stored password hash, or create default if not exists
81
+ */
82
+ function getPasswordHash(): string {
83
+ const passwordPath = path.join(process.cwd(), BYPASS_DIR, PASSWORD_FILE);
84
+
85
+ if (!fs.existsSync(passwordPath)) {
86
+ // Create default password file
87
+ const dir = path.join(process.cwd(), BYPASS_DIR);
88
+ if (!fs.existsSync(dir)) {
89
+ fs.mkdirSync(dir, { recursive: true });
90
+ }
91
+
92
+ fs.writeFileSync(passwordPath, DEFAULT_PASSWORD_HASH, 'utf-8');
93
+ console.warn('⚠️ Default bypass password created. Change it by running:');
94
+ console.warn(' npm run set-bypass-password');
95
+ return DEFAULT_PASSWORD_HASH;
96
+ }
97
+
98
+ return fs.readFileSync(passwordPath, 'utf-8').trim();
99
+ }
100
+
101
+ /**
102
+ * Verify a password against the stored hash
103
+ */
104
+ export function verifyBypassPassword(password: string): boolean {
105
+ const storedHash = getPasswordHash();
106
+ const inputHash = hashPassword(password);
107
+ return inputHash === storedHash;
108
+ }
109
+
110
+ /**
111
+ * Set a new bypass password
112
+ */
113
+ export function setBypassPassword(newPassword: string): void {
114
+ const dir = path.join(process.cwd(), BYPASS_DIR);
115
+ if (!fs.existsSync(dir)) {
116
+ fs.mkdirSync(dir, { recursive: true });
117
+ }
118
+
119
+ const passwordPath = path.join(dir, PASSWORD_FILE);
120
+ const hash = hashPassword(newPassword);
121
+ fs.writeFileSync(passwordPath, hash, 'utf-8');
122
+
123
+ console.log('✅ Bypass password updated successfully.');
124
+ console.log('⚠️ Keep this password secure and share only with authorized team members.');
125
+ }
126
+
127
+ // ── Admin Management ────────────────────────────────────────────────────────
128
+
129
+ /**
130
+ * Get the stored admin password hash, or create default if not exists
131
+ */
132
+ function getAdminPasswordHash(): string {
133
+ const adminPath = path.join(process.cwd(), BYPASS_DIR, ADMIN_FILE);
134
+
135
+ if (!fs.existsSync(adminPath)) {
136
+ // Create default admin password file
137
+ const dir = path.join(process.cwd(), BYPASS_DIR);
138
+ if (!fs.existsSync(dir)) {
139
+ fs.mkdirSync(dir, { recursive: true });
140
+ }
141
+
142
+ fs.writeFileSync(adminPath, DEFAULT_ADMIN_PASSWORD_HASH, 'utf-8');
143
+ console.warn('⚠️ Default admin password created. CHANGE IT IMMEDIATELY by running:');
144
+ console.warn(' npm run set-admin-password');
145
+ return DEFAULT_ADMIN_PASSWORD_HASH;
146
+ }
147
+
148
+ return fs.readFileSync(adminPath, 'utf-8').trim();
149
+ }
150
+
151
+ /**
152
+ * Verify admin password
153
+ */
154
+ export function verifyAdminPassword(password: string): boolean {
155
+ const storedHash = getAdminPasswordHash();
156
+ const inputHash = hashPassword(password);
157
+ return inputHash === storedHash;
158
+ }
159
+
160
+ /**
161
+ * Set a new admin password
162
+ */
163
+ export function setAdminPassword(newPassword: string): void {
164
+ const dir = path.join(process.cwd(), BYPASS_DIR);
165
+ if (!fs.existsSync(dir)) {
166
+ fs.mkdirSync(dir, { recursive: true });
167
+ }
168
+
169
+ const adminPath = path.join(dir, ADMIN_FILE);
170
+ const hash = hashPassword(newPassword);
171
+ fs.writeFileSync(adminPath, hash, 'utf-8');
172
+
173
+ console.log('✅ Admin password updated successfully.');
174
+ console.log('🔐 Admin privileges: View and delete bypass logs');
175
+ console.log('⚠️ Keep this password highly secure!');
176
+ }
177
+
178
+ // ── Log Integrity ───────────────────────────────────────────────────────────
179
+
180
+ /**
181
+ * Calculate checksum for bypass log to detect tampering
182
+ */
183
+ function calculateLogChecksum(log: BypassLog): string {
184
+ // Create a stable string representation (excluding checksum itself)
185
+ const data = JSON.stringify({
186
+ entries: log.entries,
187
+ lastUpdated: log.lastUpdated,
188
+ });
189
+ return crypto.createHash('sha256').update(data).digest('hex');
190
+ }
191
+
192
+ /**
193
+ * Verify log integrity
194
+ */
195
+ function verifyLogIntegrity(log: BypassLog): boolean {
196
+ if (!log.checksum) {
197
+ // Legacy log without checksum - consider valid but warn
198
+ return true;
199
+ }
200
+ const expectedChecksum = calculateLogChecksum(log);
201
+ return expectedChecksum === log.checksum;
202
+ }
203
+
204
+ // ── Git Helpers ─────────────────────────────────────────────────────────────
205
+
206
+ function getGitAuthor(): { name: string; email: string } {
207
+ try {
208
+ const name = execSync('git config user.name', { encoding: 'utf-8' }).trim();
209
+ const email = execSync('git config user.email', { encoding: 'utf-8' }).trim();
210
+ return { name, email };
211
+ } catch {
212
+ return { name: 'Unknown', email: 'unknown@example.com' };
213
+ }
214
+ }
215
+
216
+ function getCurrentBranch(): string {
217
+ try {
218
+ return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
219
+ } catch {
220
+ return 'unknown';
221
+ }
222
+ }
223
+
224
+ function getStagedFiles(): string[] {
225
+ try {
226
+ const output = execSync('git diff --cached --name-only', { encoding: 'utf-8' }).trim();
227
+ return output ? output.split('\n') : [];
228
+ } catch {
229
+ return [];
230
+ }
231
+ }
232
+
233
+ // ── Bypass Log Management ───────────────────────────────────────────────────
234
+
235
+ /**
236
+ * Load the bypass log from disk
237
+ */
238
+ function loadBypassLog(): BypassLog {
239
+ const logPath = path.join(process.cwd(), BYPASS_DIR, BYPASS_LOG_FILE);
240
+
241
+ if (!fs.existsSync(logPath)) {
242
+ return {
243
+ entries: [],
244
+ lastUpdated: new Date().toISOString(),
245
+ immutable: true,
246
+ };
247
+ }
248
+
249
+ try {
250
+ const content = fs.readFileSync(logPath, 'utf-8');
251
+ const log = JSON.parse(content);
252
+
253
+ // Ensure immutable flag exists (for backward compatibility)
254
+ if (log.immutable === undefined) {
255
+ log.immutable = true;
256
+ }
257
+
258
+ // Verify integrity
259
+ if (!verifyLogIntegrity(log)) {
260
+ console.warn('⚠️ WARNING: Bypass log integrity check failed!');
261
+ console.warn(' The log may have been tampered with.');
262
+ console.warn(' Location: ' + logPath);
263
+ // Archive the potentially tampered log
264
+ archiveTamperedLog(log);
265
+ }
266
+
267
+ return log;
268
+ } catch (err) {
269
+ console.error('❌ Error loading bypass log:', err);
270
+ return {
271
+ entries: [],
272
+ lastUpdated: new Date().toISOString(),
273
+ immutable: true,
274
+ };
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Save the bypass log to disk
280
+ */
281
+ function saveBypassLog(log: BypassLog): void {
282
+ const dir = path.join(process.cwd(), BYPASS_DIR);
283
+ if (!fs.existsSync(dir)) {
284
+ fs.mkdirSync(dir, { recursive: true });
285
+ }
286
+
287
+ const logPath = path.join(dir, BYPASS_LOG_FILE);
288
+ log.lastUpdated = new Date().toISOString();
289
+ log.immutable = true; // Always mark as immutable
290
+
291
+ // Calculate and store checksum for integrity verification
292
+ log.checksum = calculateLogChecksum(log);
293
+
294
+ try {
295
+ // Temporarily make file writable if it exists and is read-only.
296
+ // This allows the log to remain effectively "read-only" to users while
297
+ // still letting Code Guardian append new entries.
298
+ if (fs.existsSync(logPath)) {
299
+ try {
300
+ fs.chmodSync(logPath, 0o644); // rw-r--r--
301
+ } catch {
302
+ // If chmod fails (e.g., filesystem does not support it), continue and
303
+ // rely on the write below to surface any real permission problems.
304
+ }
305
+ }
306
+
307
+ // Write the log file
308
+ fs.writeFileSync(logPath, JSON.stringify(log, null, 2), 'utf-8');
309
+
310
+ // After a successful write, set file permissions back to read-only
311
+ // (444 = r--r--r--) to discourage manual edits.
312
+ try {
313
+ fs.chmodSync(logPath, 0o444);
314
+ } catch {
315
+ // Non-fatal: if we cannot change permissions, keep going.
316
+ }
317
+ } catch (err: any) {
318
+ // If we cannot write the log (for example, the file was made truly
319
+ // immutable with OS-level flags), do NOT block the bypass or commit.
320
+ // Instead, warn and continue so the hook stays usable.
321
+ console.warn('⚠️ Code Guardian: Failed to update bypass log file.');
322
+ console.warn(
323
+ ' Reason: ' + (err && err.message ? err.message : String(err))
324
+ );
325
+ console.warn(
326
+ ' Tip: Ensure `.code-guardian/bypass-log.json` is writable by git user, '
327
+ + 'or remove OS-level immutability flags if you want new bypasses to be recorded.'
328
+ );
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Archive a potentially tampered log file
334
+ */
335
+ function archiveTamperedLog(log: BypassLog): void {
336
+ try {
337
+ const archiveDir = path.join(process.cwd(), BYPASS_DIR, LOG_ARCHIVE_DIR);
338
+ if (!fs.existsSync(archiveDir)) {
339
+ fs.mkdirSync(archiveDir, { recursive: true });
340
+ }
341
+
342
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
343
+ const archivePath = path.join(archiveDir, `tampered-log-${timestamp}.json`);
344
+
345
+ fs.writeFileSync(archivePath, JSON.stringify(log, null, 2), 'utf-8');
346
+ console.log(` Archived to: ${archivePath}`);
347
+ } catch (err) {
348
+ console.error(' Failed to archive tampered log:', err);
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Record a bypass attempt in the audit log
354
+ */
355
+ export function recordBypass(
356
+ reason: string,
357
+ commitMessage: string,
358
+ method: 'commit-message' | 'env-variable' | 'password'
359
+ ): void {
360
+ const author = getGitAuthor();
361
+ const branch = getCurrentBranch();
362
+ const files = getStagedFiles();
363
+
364
+ const entry: BypassEntry = {
365
+ id: crypto.randomUUID(),
366
+ author: author.name,
367
+ email: author.email,
368
+ timestamp: new Date().toISOString(),
369
+ reason,
370
+ commitMessage,
371
+ branch,
372
+ files,
373
+ method,
374
+ };
375
+
376
+ const log = loadBypassLog();
377
+ log.entries.push(entry);
378
+ saveBypassLog(log);
379
+
380
+ console.log('');
381
+ console.log('📝 Bypass recorded in audit log:');
382
+ console.log(` Author: ${author.name} <${author.email}>`);
383
+ console.log(` Reason: ${reason}`);
384
+ console.log(` Method: ${method}`);
385
+ console.log(` Files: ${files.length} file(s)`);
386
+ console.log('');
387
+ }
388
+
389
+ /**
390
+ * Update a bypass entry with the commit hash after commit succeeds
391
+ */
392
+ export function updateBypassWithCommitHash(entryId: string, commitHash: string): void {
393
+ const log = loadBypassLog();
394
+ const entry = log.entries.find((e) => e.id === entryId);
395
+
396
+ if (entry) {
397
+ entry.commitHash = commitHash;
398
+ saveBypassLog(log);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Get all bypass entries for a specific author
404
+ */
405
+ export function getBypassesByAuthor(authorEmail: string): BypassEntry[] {
406
+ const log = loadBypassLog();
407
+ return log.entries.filter((e) => e.email === authorEmail);
408
+ }
409
+
410
+ /**
411
+ * Get recent bypass entries (last N days)
412
+ */
413
+ export function getRecentBypasses(days: number = 30): BypassEntry[] {
414
+ const log = loadBypassLog();
415
+ const cutoff = new Date();
416
+ cutoff.setDate(cutoff.getDate() - days);
417
+
418
+ return log.entries.filter((e) => new Date(e.timestamp) >= cutoff);
419
+ }
420
+
421
+ /**
422
+ * Delete bypass entries (ADMIN ONLY)
423
+ * @param entryIds - Array of entry IDs to delete
424
+ * @param adminPassword - Admin password for verification
425
+ * @returns Success status and message
426
+ */
427
+ export function deleteBypassEntries(
428
+ entryIds: string[],
429
+ adminPassword: string
430
+ ): { success: boolean; message: string; deletedCount: number } {
431
+ // Verify admin password
432
+ if (!verifyAdminPassword(adminPassword)) {
433
+ return {
434
+ success: false,
435
+ message: '❌ Invalid admin password. Access denied.',
436
+ deletedCount: 0,
437
+ };
438
+ }
439
+
440
+ const logPath = path.join(process.cwd(), BYPASS_DIR, BYPASS_LOG_FILE);
441
+
442
+ // Remove read-only protection temporarily
443
+ try {
444
+ fs.chmodSync(logPath, 0o644);
445
+ } catch (err) {
446
+ // Ignore if chmod fails
447
+ }
448
+
449
+ const log = loadBypassLog();
450
+ const initialCount = log.entries.length;
451
+
452
+ // Archive deleted entries before removal
453
+ const deletedEntries = log.entries.filter((e) => entryIds.includes(e.id));
454
+ if (deletedEntries.length > 0) {
455
+ archiveDeletedEntries(deletedEntries, adminPassword);
456
+ }
457
+
458
+ // Remove entries
459
+ log.entries = log.entries.filter((e) => !entryIds.includes(e.id));
460
+ const deletedCount = initialCount - log.entries.length;
461
+
462
+ if (deletedCount === 0) {
463
+ return {
464
+ success: false,
465
+ message: '⚠️ No matching entries found to delete.',
466
+ deletedCount: 0,
467
+ };
468
+ }
469
+
470
+ // Save updated log
471
+ saveBypassLog(log);
472
+
473
+ return {
474
+ success: true,
475
+ message: `✅ Deleted ${deletedCount} bypass entry(ies) successfully.`,
476
+ deletedCount,
477
+ };
478
+ }
479
+
480
+ /**
481
+ * Archive deleted entries for audit trail
482
+ */
483
+ function archiveDeletedEntries(entries: BypassEntry[], adminPassword: string): void {
484
+ try {
485
+ const archiveDir = path.join(process.cwd(), BYPASS_DIR, LOG_ARCHIVE_DIR);
486
+ if (!fs.existsSync(archiveDir)) {
487
+ fs.mkdirSync(archiveDir, { recursive: true });
488
+ }
489
+
490
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
491
+ const archivePath = path.join(archiveDir, `deleted-entries-${timestamp}.json`);
492
+
493
+ const archiveData = {
494
+ deletedAt: new Date().toISOString(),
495
+ deletedBy: 'admin',
496
+ adminPasswordHash: hashPassword(adminPassword), // For audit purposes
497
+ entries,
498
+ };
499
+
500
+ fs.writeFileSync(archivePath, JSON.stringify(archiveData, null, 2), 'utf-8');
501
+ console.log(`📦 Deleted entries archived to: ${archivePath}`);
502
+ } catch (err) {
503
+ console.error('⚠️ Warning: Failed to archive deleted entries:', err);
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Get all archived logs (deleted and tampered)
509
+ */
510
+ export function getArchivedLogs(): { deleted: string[]; tampered: string[] } {
511
+ const archiveDir = path.join(process.cwd(), BYPASS_DIR, LOG_ARCHIVE_DIR);
512
+
513
+ if (!fs.existsSync(archiveDir)) {
514
+ return { deleted: [], tampered: [] };
515
+ }
516
+
517
+ const files = fs.readdirSync(archiveDir);
518
+
519
+ return {
520
+ deleted: files.filter((f) => f.startsWith('deleted-entries-')),
521
+ tampered: files.filter((f) => f.startsWith('tampered-log-')),
522
+ };
523
+ }
524
+
525
+ /**
526
+ * Generate a bypass audit report
527
+ */
528
+ export function generateBypassReport(): string {
529
+ const log = loadBypassLog();
530
+
531
+ if (log.entries.length === 0) {
532
+ return '📊 Bypass Audit Report\n\nNo bypass entries found.';
533
+ }
534
+
535
+ let report = '📊 Bypass Audit Report\n';
536
+ report += '═'.repeat(60) + '\n\n';
537
+ report += `Total bypasses: ${log.entries.length}\n`;
538
+ report += `Last updated: ${log.lastUpdated}\n\n`;
539
+
540
+ // Group by author
541
+ const byAuthor = new Map<string, BypassEntry[]>();
542
+ for (const entry of log.entries) {
543
+ const key = `${entry.author} <${entry.email}>`;
544
+ const existing = byAuthor.get(key) || [];
545
+ existing.push(entry);
546
+ byAuthor.set(key, existing);
547
+ }
548
+
549
+ report += '📈 Bypasses by Author:\n';
550
+ report += '─'.repeat(60) + '\n';
551
+ for (const [author, entries] of byAuthor) {
552
+ report += `\n${author}: ${entries.length} bypass(es)\n`;
553
+
554
+ for (const entry of entries.slice(-5)) {
555
+ // Show last 5
556
+ const date = new Date(entry.timestamp).toLocaleString();
557
+ report += ` • ${date} - ${entry.reason}\n`;
558
+ report += ` Branch: ${entry.branch}, Files: ${entry.files.length}\n`;
559
+ }
560
+ }
561
+
562
+ report += '\n' + '═'.repeat(60) + '\n';
563
+ report += `\nFull log: ${path.join(BYPASS_DIR, BYPASS_LOG_FILE)}\n`;
564
+
565
+ return report;
566
+ }