driftdetect 0.4.7 → 0.6.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 (92) hide show
  1. package/dist/bin/drift.js +37 -1
  2. package/dist/bin/drift.js.map +1 -1
  3. package/dist/commands/approve.d.ts +20 -0
  4. package/dist/commands/approve.d.ts.map +1 -1
  5. package/dist/commands/approve.js +38 -72
  6. package/dist/commands/approve.js.map +1 -1
  7. package/dist/commands/check.d.ts +41 -0
  8. package/dist/commands/check.d.ts.map +1 -1
  9. package/dist/commands/check.js +21 -11
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/constraints.d.ts +17 -0
  12. package/dist/commands/constraints.d.ts.map +1 -0
  13. package/dist/commands/constraints.js +686 -0
  14. package/dist/commands/constraints.js.map +1 -0
  15. package/dist/commands/coupling.d.ts +17 -0
  16. package/dist/commands/coupling.d.ts.map +1 -0
  17. package/dist/commands/coupling.js +726 -0
  18. package/dist/commands/coupling.js.map +1 -0
  19. package/dist/commands/decisions.d.ts +19 -0
  20. package/dist/commands/decisions.d.ts.map +1 -0
  21. package/dist/commands/decisions.js +771 -0
  22. package/dist/commands/decisions.js.map +1 -0
  23. package/dist/commands/error-handling.d.ts +15 -0
  24. package/dist/commands/error-handling.d.ts.map +1 -0
  25. package/dist/commands/error-handling.js +608 -0
  26. package/dist/commands/error-handling.js.map +1 -0
  27. package/dist/commands/export.d.ts +16 -0
  28. package/dist/commands/export.d.ts.map +1 -1
  29. package/dist/commands/export.js +46 -50
  30. package/dist/commands/export.js.map +1 -1
  31. package/dist/commands/files.d.ts +15 -0
  32. package/dist/commands/files.d.ts.map +1 -1
  33. package/dist/commands/files.js +27 -48
  34. package/dist/commands/files.js.map +1 -1
  35. package/dist/commands/go.d.ts +21 -0
  36. package/dist/commands/go.d.ts.map +1 -0
  37. package/dist/commands/go.js +530 -0
  38. package/dist/commands/go.js.map +1 -0
  39. package/dist/commands/ignore.d.ts +20 -0
  40. package/dist/commands/ignore.d.ts.map +1 -1
  41. package/dist/commands/ignore.js +25 -48
  42. package/dist/commands/ignore.js.map +1 -1
  43. package/dist/commands/index.d.ts +11 -0
  44. package/dist/commands/index.d.ts.map +1 -1
  45. package/dist/commands/index.js +15 -0
  46. package/dist/commands/index.js.map +1 -1
  47. package/dist/commands/migrate-storage.d.ts +23 -0
  48. package/dist/commands/migrate-storage.d.ts.map +1 -0
  49. package/dist/commands/migrate-storage.js +337 -0
  50. package/dist/commands/migrate-storage.js.map +1 -0
  51. package/dist/commands/report.d.ts +22 -0
  52. package/dist/commands/report.d.ts.map +1 -1
  53. package/dist/commands/report.js +19 -10
  54. package/dist/commands/report.js.map +1 -1
  55. package/dist/commands/scan.d.ts +2 -0
  56. package/dist/commands/scan.d.ts.map +1 -1
  57. package/dist/commands/scan.js +134 -3
  58. package/dist/commands/scan.js.map +1 -1
  59. package/dist/commands/simulate.d.ts +17 -0
  60. package/dist/commands/simulate.d.ts.map +1 -0
  61. package/dist/commands/simulate.js +253 -0
  62. package/dist/commands/simulate.js.map +1 -0
  63. package/dist/commands/skills.d.ts +16 -0
  64. package/dist/commands/skills.d.ts.map +1 -0
  65. package/dist/commands/skills.js +409 -0
  66. package/dist/commands/skills.js.map +1 -0
  67. package/dist/commands/status.d.ts +20 -0
  68. package/dist/commands/status.d.ts.map +1 -1
  69. package/dist/commands/status.js +74 -72
  70. package/dist/commands/status.js.map +1 -1
  71. package/dist/commands/test-topology.d.ts +15 -0
  72. package/dist/commands/test-topology.d.ts.map +1 -0
  73. package/dist/commands/test-topology.js +589 -0
  74. package/dist/commands/test-topology.js.map +1 -0
  75. package/dist/commands/where.d.ts +15 -0
  76. package/dist/commands/where.d.ts.map +1 -1
  77. package/dist/commands/where.js +41 -88
  78. package/dist/commands/where.js.map +1 -1
  79. package/dist/commands/wpf.d.ts +21 -0
  80. package/dist/commands/wpf.d.ts.map +1 -0
  81. package/dist/commands/wpf.js +632 -0
  82. package/dist/commands/wpf.js.map +1 -0
  83. package/dist/commands/wrappers.d.ts +16 -0
  84. package/dist/commands/wrappers.d.ts.map +1 -0
  85. package/dist/commands/wrappers.js +181 -0
  86. package/dist/commands/wrappers.js.map +1 -0
  87. package/dist/services/pattern-service-factory.d.ts +37 -0
  88. package/dist/services/pattern-service-factory.d.ts.map +1 -0
  89. package/dist/services/pattern-service-factory.js +41 -0
  90. package/dist/services/pattern-service-factory.js.map +1 -0
  91. package/package.json +35 -23
  92. package/LICENSE +0 -21
@@ -0,0 +1,686 @@
1
+ /**
2
+ * Constraints Command - drift constraints
3
+ *
4
+ * Manage architectural constraints learned from the codebase.
5
+ * Constraints are invariants that MUST be satisfied by code.
6
+ */
7
+ import { Command } from 'commander';
8
+ import * as fs from 'node:fs/promises';
9
+ import * as path from 'node:path';
10
+ import chalk from 'chalk';
11
+ import { createConstraintStore, createInvariantDetector, createConstraintSynthesizer, createConstraintVerifier, } from 'driftdetect-core';
12
+ import { createSpinner } from '../ui/spinner.js';
13
+ const DRIFT_DIR = '.drift';
14
+ const CONSTRAINTS_DIR = 'constraints';
15
+ /**
16
+ * Check if constraints exist
17
+ */
18
+ async function constraintsExist(rootDir) {
19
+ try {
20
+ await fs.access(path.join(rootDir, DRIFT_DIR, CONSTRAINTS_DIR, 'index.json'));
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ /**
28
+ * Show helpful message when no constraints
29
+ */
30
+ function showNoConstraintsMessage() {
31
+ console.log();
32
+ console.log(chalk.yellow('⚠️ No constraints found.'));
33
+ console.log();
34
+ console.log(chalk.gray('Extract constraints from your codebase:'));
35
+ console.log();
36
+ console.log(chalk.cyan(' drift constraints extract'));
37
+ console.log();
38
+ }
39
+ /**
40
+ * Extract subcommand - discover constraints from codebase
41
+ */
42
+ async function extractAction(options) {
43
+ const rootDir = process.cwd();
44
+ const format = options.format ?? 'text';
45
+ const isTextFormat = format === 'text';
46
+ try {
47
+ if (isTextFormat) {
48
+ console.log();
49
+ console.log(chalk.bold('🔍 Extracting Constraints'));
50
+ console.log(chalk.gray('═'.repeat(50)));
51
+ }
52
+ const spinner = isTextFormat ? createSpinner('Initializing...') : null;
53
+ spinner?.start();
54
+ // Initialize store
55
+ spinner?.text('Loading constraint store...');
56
+ const store = createConstraintStore({ rootDir });
57
+ await store.initialize();
58
+ // Initialize detector
59
+ spinner?.text('Initializing invariant detector...');
60
+ const detector = createInvariantDetector({ rootDir });
61
+ // Initialize synthesizer
62
+ const synthesizer = createConstraintSynthesizer({ store, detector });
63
+ // Extract constraints
64
+ spinner?.text('Analyzing codebase for invariants...');
65
+ const synthesisOptions = {
66
+ minConfidence: options.minConfidence ?? 0.85,
67
+ };
68
+ if (options.verbose) {
69
+ synthesisOptions.includeViolationDetails = true;
70
+ }
71
+ if (options.category) {
72
+ synthesisOptions.categories = [options.category];
73
+ }
74
+ const result = await synthesizer.synthesize(synthesisOptions);
75
+ spinner?.stop();
76
+ if (format === 'json') {
77
+ console.log(JSON.stringify(result, null, 2));
78
+ return;
79
+ }
80
+ // Text output
81
+ console.log();
82
+ console.log(chalk.green.bold('✓ Constraint extraction complete'));
83
+ console.log();
84
+ console.log(chalk.bold('📊 Results'));
85
+ console.log(chalk.gray('─'.repeat(50)));
86
+ console.log(` Discovered: ${chalk.cyan.bold(result.discovered.length)}`);
87
+ console.log(` Updated: ${chalk.cyan(result.updated.length)}`);
88
+ console.log(` Invalidated: ${chalk.yellow(result.invalidated.length)}`);
89
+ console.log(` Time: ${chalk.gray(result.stats.executionTimeMs + 'ms')}`);
90
+ console.log();
91
+ if (result.discovered.length > 0) {
92
+ console.log(chalk.bold('🆕 New Constraints'));
93
+ console.log(chalk.gray('─'.repeat(50)));
94
+ for (const c of result.discovered.slice(0, 10)) {
95
+ formatConstraintBrief(c);
96
+ }
97
+ if (result.discovered.length > 10) {
98
+ console.log(chalk.gray(` ... and ${result.discovered.length - 10} more`));
99
+ }
100
+ console.log();
101
+ }
102
+ console.log(chalk.gray('─'.repeat(50)));
103
+ console.log(chalk.bold('📌 Next Steps:'));
104
+ console.log(chalk.gray(` • drift constraints list ${chalk.white('View all constraints')}`));
105
+ console.log(chalk.gray(` • drift constraints approve ${chalk.white('Approve a constraint')}`));
106
+ console.log(chalk.gray(` • drift constraints verify ${chalk.white('Verify code against constraints')}`));
107
+ console.log();
108
+ }
109
+ catch (error) {
110
+ if (format === 'json') {
111
+ console.log(JSON.stringify({ error: String(error) }));
112
+ }
113
+ else {
114
+ console.log(chalk.red(`\n❌ Error: ${error}`));
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * List subcommand - show all constraints
120
+ */
121
+ async function listAction(options) {
122
+ const rootDir = process.cwd();
123
+ const format = options.format ?? 'text';
124
+ if (!(await constraintsExist(rootDir))) {
125
+ if (format === 'json') {
126
+ console.log(JSON.stringify({ error: 'No constraints found' }));
127
+ }
128
+ else {
129
+ showNoConstraintsMessage();
130
+ }
131
+ return;
132
+ }
133
+ try {
134
+ const store = createConstraintStore({ rootDir });
135
+ await store.initialize();
136
+ const queryOptions = {
137
+ limit: options.limit ?? 50,
138
+ };
139
+ if (options.category) {
140
+ queryOptions.category = options.category;
141
+ }
142
+ if (options.status) {
143
+ queryOptions.status = options.status;
144
+ }
145
+ if (options.minConfidence !== undefined) {
146
+ queryOptions.minConfidence = options.minConfidence;
147
+ }
148
+ const result = store.query(queryOptions);
149
+ if (format === 'json') {
150
+ console.log(JSON.stringify(result, null, 2));
151
+ return;
152
+ }
153
+ console.log();
154
+ console.log(chalk.bold('📋 Constraints'));
155
+ console.log(chalk.gray('─'.repeat(60)));
156
+ console.log();
157
+ if (result.constraints.length === 0) {
158
+ console.log(chalk.yellow('No constraints match the filters.'));
159
+ console.log();
160
+ return;
161
+ }
162
+ // Group by category
163
+ const byCategory = new Map();
164
+ for (const c of result.constraints) {
165
+ const list = byCategory.get(c.category) ?? [];
166
+ list.push(c);
167
+ byCategory.set(c.category, list);
168
+ }
169
+ for (const [category, constraints] of byCategory) {
170
+ console.log(chalk.bold(`${getCategoryIcon(category)} ${category.toUpperCase()}`));
171
+ console.log(chalk.gray('─'.repeat(40)));
172
+ for (const c of constraints) {
173
+ formatConstraintBrief(c);
174
+ }
175
+ console.log();
176
+ }
177
+ console.log(chalk.gray(`Showing ${result.constraints.length} of ${result.total} constraints`));
178
+ console.log();
179
+ }
180
+ catch (error) {
181
+ if (format === 'json') {
182
+ console.log(JSON.stringify({ error: String(error) }));
183
+ }
184
+ else {
185
+ console.log(chalk.red(`Error: ${error}`));
186
+ }
187
+ }
188
+ }
189
+ /**
190
+ * Show subcommand - show constraint details
191
+ */
192
+ async function showAction(id, options) {
193
+ const rootDir = process.cwd();
194
+ const format = options.format ?? 'text';
195
+ try {
196
+ const store = createConstraintStore({ rootDir });
197
+ await store.initialize();
198
+ const constraint = store.get(id);
199
+ if (!constraint) {
200
+ if (format === 'json') {
201
+ console.log(JSON.stringify({ error: 'Constraint not found' }));
202
+ }
203
+ else {
204
+ console.log(chalk.red(`Constraint not found: ${id}`));
205
+ }
206
+ return;
207
+ }
208
+ if (format === 'json') {
209
+ console.log(JSON.stringify(constraint, null, 2));
210
+ return;
211
+ }
212
+ formatConstraintDetailed(constraint);
213
+ }
214
+ catch (error) {
215
+ if (format === 'json') {
216
+ console.log(JSON.stringify({ error: String(error) }));
217
+ }
218
+ else {
219
+ console.log(chalk.red(`Error: ${error}`));
220
+ }
221
+ }
222
+ }
223
+ /**
224
+ * Approve subcommand - approve a constraint
225
+ */
226
+ async function approveAction(id, options) {
227
+ const rootDir = process.cwd();
228
+ const format = options.format ?? 'text';
229
+ try {
230
+ const store = createConstraintStore({ rootDir });
231
+ await store.initialize();
232
+ const result = await store.approve(id);
233
+ if (!result) {
234
+ if (format === 'json') {
235
+ console.log(JSON.stringify({ error: 'Constraint not found' }));
236
+ }
237
+ else {
238
+ console.log(chalk.red(`Constraint not found: ${id}`));
239
+ }
240
+ return;
241
+ }
242
+ if (format === 'json') {
243
+ console.log(JSON.stringify({ success: true, constraint: result }));
244
+ return;
245
+ }
246
+ console.log();
247
+ console.log(chalk.green(`✓ Approved: ${result.name}`));
248
+ console.log(chalk.gray(` ID: ${result.id}`));
249
+ console.log(chalk.gray(` Status: ${chalk.green('approved')}`));
250
+ console.log();
251
+ }
252
+ catch (error) {
253
+ if (format === 'json') {
254
+ console.log(JSON.stringify({ error: String(error) }));
255
+ }
256
+ else {
257
+ console.log(chalk.red(`Error: ${error}`));
258
+ }
259
+ }
260
+ }
261
+ /**
262
+ * Ignore subcommand - ignore a constraint
263
+ */
264
+ async function ignoreAction(id, reason, options) {
265
+ const rootDir = process.cwd();
266
+ const format = options.format ?? 'text';
267
+ try {
268
+ const store = createConstraintStore({ rootDir });
269
+ await store.initialize();
270
+ const result = await store.ignore(id, reason);
271
+ if (!result) {
272
+ if (format === 'json') {
273
+ console.log(JSON.stringify({ error: 'Constraint not found' }));
274
+ }
275
+ else {
276
+ console.log(chalk.red(`Constraint not found: ${id}`));
277
+ }
278
+ return;
279
+ }
280
+ if (format === 'json') {
281
+ console.log(JSON.stringify({ success: true, constraint: result }));
282
+ return;
283
+ }
284
+ console.log();
285
+ console.log(chalk.yellow(`✓ Ignored: ${result.name}`));
286
+ console.log(chalk.gray(` ID: ${result.id}`));
287
+ if (reason) {
288
+ console.log(chalk.gray(` Reason: ${reason}`));
289
+ }
290
+ console.log();
291
+ }
292
+ catch (error) {
293
+ if (format === 'json') {
294
+ console.log(JSON.stringify({ error: String(error) }));
295
+ }
296
+ else {
297
+ console.log(chalk.red(`Error: ${error}`));
298
+ }
299
+ }
300
+ }
301
+ /**
302
+ * Verify subcommand - verify a file against constraints
303
+ */
304
+ async function verifyAction(file, options) {
305
+ const rootDir = process.cwd();
306
+ const format = options.format ?? 'text';
307
+ const spinner = format === 'text' ? createSpinner('Verifying...') : null;
308
+ spinner?.start();
309
+ try {
310
+ const store = createConstraintStore({ rootDir });
311
+ await store.initialize();
312
+ const verifier = createConstraintVerifier({ rootDir, store });
313
+ // Read file content
314
+ const filePath = path.resolve(rootDir, file);
315
+ const content = await fs.readFile(filePath, 'utf-8');
316
+ const verifyOptions = {
317
+ includeFixes: true,
318
+ };
319
+ if (options.category) {
320
+ verifyOptions.categories = [options.category];
321
+ }
322
+ if (options.minConfidence !== undefined) {
323
+ verifyOptions.minConfidence = options.minConfidence;
324
+ }
325
+ if (options.verbose) {
326
+ verifyOptions.includeExamples = true;
327
+ }
328
+ const result = await verifier.verifyFile(file, content, verifyOptions);
329
+ spinner?.stop();
330
+ if (format === 'json') {
331
+ console.log(JSON.stringify(result, null, 2));
332
+ return;
333
+ }
334
+ formatVerificationResult(result);
335
+ }
336
+ catch (error) {
337
+ spinner?.stop();
338
+ if (format === 'json') {
339
+ console.log(JSON.stringify({ error: String(error) }));
340
+ }
341
+ else {
342
+ console.log(chalk.red(`Error: ${error}`));
343
+ }
344
+ }
345
+ }
346
+ /**
347
+ * Check subcommand - verify all files against constraints
348
+ */
349
+ async function checkAction(options) {
350
+ const rootDir = process.cwd();
351
+ const format = options.format ?? 'text';
352
+ const spinner = format === 'text' ? createSpinner('Checking codebase...') : null;
353
+ spinner?.start();
354
+ try {
355
+ const store = createConstraintStore({ rootDir });
356
+ await store.initialize();
357
+ const verifier = createConstraintVerifier({ rootDir, store });
358
+ // Find source files
359
+ const files = await findSourceFiles(rootDir);
360
+ let totalViolations = 0;
361
+ let totalErrors = 0;
362
+ let totalWarnings = 0;
363
+ const fileResults = [];
364
+ for (const file of files) {
365
+ try {
366
+ const content = await fs.readFile(path.join(rootDir, file), 'utf-8');
367
+ const verifyOptions = {};
368
+ if (options.category) {
369
+ verifyOptions.categories = [options.category];
370
+ }
371
+ if (options.minConfidence !== undefined) {
372
+ verifyOptions.minConfidence = options.minConfidence;
373
+ }
374
+ const result = await verifier.verifyFile(file, content, verifyOptions);
375
+ if (result.violations.length > 0) {
376
+ fileResults.push({ file, result });
377
+ totalViolations += result.violations.length;
378
+ totalErrors += result.violations.filter(v => v.severity === 'error').length;
379
+ totalWarnings += result.violations.filter(v => v.severity === 'warning').length;
380
+ }
381
+ }
382
+ catch {
383
+ // Skip files that can't be read
384
+ }
385
+ }
386
+ spinner?.stop();
387
+ if (format === 'json') {
388
+ console.log(JSON.stringify({
389
+ passed: totalViolations === 0,
390
+ filesChecked: files.length,
391
+ totalViolations,
392
+ errors: totalErrors,
393
+ warnings: totalWarnings,
394
+ files: fileResults,
395
+ }, null, 2));
396
+ return;
397
+ }
398
+ console.log();
399
+ console.log(chalk.bold('🔍 Constraint Check'));
400
+ console.log(chalk.gray('─'.repeat(60)));
401
+ console.log();
402
+ console.log(`Files checked: ${chalk.cyan(files.length)}`);
403
+ console.log();
404
+ if (totalViolations === 0) {
405
+ console.log(chalk.green.bold('✓ All constraints satisfied!'));
406
+ console.log();
407
+ return;
408
+ }
409
+ console.log(chalk.bold('Violations:'));
410
+ console.log(` ${chalk.red('Errors:')} ${totalErrors}`);
411
+ console.log(` ${chalk.yellow('Warnings:')} ${totalWarnings}`);
412
+ console.log();
413
+ // Show violations by file
414
+ for (const { file, result } of fileResults.slice(0, 10)) {
415
+ console.log(chalk.bold(file));
416
+ for (const v of result.violations.slice(0, 5)) {
417
+ const icon = v.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
418
+ console.log(` ${icon} Line ${v.location.line}: ${v.message}`);
419
+ console.log(chalk.gray(` ${v.constraintName}`));
420
+ }
421
+ if (result.violations.length > 5) {
422
+ console.log(chalk.gray(` ... and ${result.violations.length - 5} more`));
423
+ }
424
+ console.log();
425
+ }
426
+ if (fileResults.length > 10) {
427
+ console.log(chalk.gray(`... and ${fileResults.length - 10} more files with violations`));
428
+ }
429
+ // Exit with error if there are errors
430
+ if (totalErrors > 0) {
431
+ process.exit(1);
432
+ }
433
+ }
434
+ catch (error) {
435
+ spinner?.stop();
436
+ if (format === 'json') {
437
+ console.log(JSON.stringify({ error: String(error) }));
438
+ }
439
+ else {
440
+ console.log(chalk.red(`Error: ${error}`));
441
+ }
442
+ }
443
+ }
444
+ /**
445
+ * Export subcommand - export constraints
446
+ */
447
+ async function exportAction(output, options) {
448
+ const rootDir = process.cwd();
449
+ const format = options.format ?? 'text';
450
+ try {
451
+ const store = createConstraintStore({ rootDir });
452
+ await store.initialize();
453
+ const constraints = store.getAll();
454
+ // Filter if needed
455
+ let filtered = constraints;
456
+ if (options.category) {
457
+ filtered = filtered.filter(c => c.category === options.category);
458
+ }
459
+ if (options.status) {
460
+ filtered = filtered.filter(c => c.status === options.status);
461
+ }
462
+ const exportData = {
463
+ version: '1.0.0',
464
+ exportedAt: new Date().toISOString(),
465
+ count: filtered.length,
466
+ constraints: filtered,
467
+ };
468
+ await fs.writeFile(output, JSON.stringify(exportData, null, 2));
469
+ if (format === 'json') {
470
+ console.log(JSON.stringify({ success: true, count: filtered.length, output }));
471
+ }
472
+ else {
473
+ console.log(chalk.green(`✓ Exported ${filtered.length} constraints to ${output}`));
474
+ }
475
+ }
476
+ catch (error) {
477
+ if (format === 'json') {
478
+ console.log(JSON.stringify({ error: String(error) }));
479
+ }
480
+ else {
481
+ console.log(chalk.red(`Error: ${error}`));
482
+ }
483
+ }
484
+ }
485
+ // ============================================================================
486
+ // Formatters
487
+ // ============================================================================
488
+ function formatConstraintBrief(c) {
489
+ const statusIcon = getStatusIcon(c.status);
490
+ const severityColor = c.enforcement.level === 'error' ? chalk.red :
491
+ c.enforcement.level === 'warning' ? chalk.yellow : chalk.gray;
492
+ console.log(` ${statusIcon} ${chalk.white(c.name)}`);
493
+ console.log(chalk.gray(` ${c.id}`));
494
+ console.log(` ${severityColor(c.enforcement.level)} | ${getConfidenceColor(c.confidence.score)} | ${c.confidence.evidence} evidence`);
495
+ }
496
+ function formatConstraintDetailed(c) {
497
+ console.log();
498
+ console.log(chalk.bold(`${getCategoryIcon(c.category)} ${c.name}`));
499
+ console.log(chalk.gray('═'.repeat(60)));
500
+ console.log();
501
+ console.log(chalk.bold('Details'));
502
+ console.log(chalk.gray('─'.repeat(40)));
503
+ console.log(` ID: ${chalk.cyan(c.id)}`);
504
+ console.log(` Category: ${c.category}`);
505
+ console.log(` Status: ${getStatusIcon(c.status)} ${c.status}`);
506
+ console.log(` Language: ${c.language}`);
507
+ console.log(` Enforcement: ${c.enforcement.level}`);
508
+ console.log();
509
+ console.log(chalk.bold('Description'));
510
+ console.log(chalk.gray('─'.repeat(40)));
511
+ console.log(` ${c.description}`);
512
+ console.log();
513
+ console.log(chalk.bold('Invariant'));
514
+ console.log(chalk.gray('─'.repeat(40)));
515
+ console.log(` Type: ${c.invariant.type}`);
516
+ console.log(` Condition: ${c.invariant.condition}`);
517
+ console.log();
518
+ console.log(chalk.bold('Confidence'));
519
+ console.log(chalk.gray('─'.repeat(40)));
520
+ console.log(` Score: ${getConfidenceColor(c.confidence.score)}`);
521
+ console.log(` Evidence: ${c.confidence.evidence} conforming instances`);
522
+ console.log(` Violations: ${c.confidence.violations} violations`);
523
+ console.log();
524
+ if (c.enforcement.guidance) {
525
+ console.log(chalk.bold('Guidance'));
526
+ console.log(chalk.gray('─'.repeat(40)));
527
+ console.log(` ${c.enforcement.guidance}`);
528
+ console.log();
529
+ }
530
+ }
531
+ function formatVerificationResult(result) {
532
+ console.log();
533
+ console.log(chalk.bold('🔍 Verification Result'));
534
+ console.log(chalk.gray('─'.repeat(60)));
535
+ console.log();
536
+ if (result.passed) {
537
+ console.log(chalk.green.bold('✓ All constraints satisfied'));
538
+ }
539
+ else {
540
+ console.log(chalk.red.bold('✗ Constraint violations found'));
541
+ }
542
+ console.log();
543
+ console.log(` Checked: ${result.metadata.constraintsChecked} constraints`);
544
+ console.log(` Satisfied: ${chalk.green(result.satisfied.length)}`);
545
+ console.log(` Violations: ${chalk.red(result.violations.length)}`);
546
+ console.log(` Skipped: ${chalk.gray(result.skipped.length)}`);
547
+ console.log(` Time: ${result.metadata.executionTimeMs}ms`);
548
+ console.log();
549
+ if (result.violations.length > 0) {
550
+ console.log(chalk.bold('Violations'));
551
+ console.log(chalk.gray('─'.repeat(40)));
552
+ for (const v of result.violations) {
553
+ const icon = v.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
554
+ console.log(`${icon} ${chalk.white(v.constraintName)}`);
555
+ console.log(chalk.gray(` Line ${v.location.line}: ${v.message}`));
556
+ if (v.guidance) {
557
+ console.log(chalk.cyan(` → ${v.guidance}`));
558
+ }
559
+ console.log();
560
+ }
561
+ }
562
+ }
563
+ // ============================================================================
564
+ // Helpers
565
+ // ============================================================================
566
+ function getStatusIcon(status) {
567
+ switch (status) {
568
+ case 'approved': return chalk.green('✓');
569
+ case 'discovered': return chalk.blue('○');
570
+ case 'ignored': return chalk.gray('⊘');
571
+ case 'custom': return chalk.magenta('★');
572
+ default: return '?';
573
+ }
574
+ }
575
+ function getCategoryIcon(category) {
576
+ const icons = {
577
+ api: '🌐',
578
+ auth: '🔐',
579
+ data: '💾',
580
+ error: '⚠️',
581
+ test: '🧪',
582
+ security: '🛡️',
583
+ structural: '🏗️',
584
+ performance: '⚡',
585
+ logging: '📝',
586
+ validation: '✅',
587
+ };
588
+ return icons[category] ?? '📋';
589
+ }
590
+ function getConfidenceColor(score) {
591
+ const percent = Math.round(score * 100);
592
+ if (score >= 0.9)
593
+ return chalk.green(`${percent}%`);
594
+ if (score >= 0.7)
595
+ return chalk.yellow(`${percent}%`);
596
+ return chalk.red(`${percent}%`);
597
+ }
598
+ const SKIP_DIRS = new Set([
599
+ 'node_modules', 'vendor', 'dist', 'build', '.git', '.drift',
600
+ '__pycache__', '.venv', 'venv', 'target', 'bin', 'obj',
601
+ ]);
602
+ const SOURCE_EXTENSIONS = new Set([
603
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
604
+ '.py', '.java', '.cs', '.php',
605
+ ]);
606
+ async function findSourceFiles(rootDir, subDir = '') {
607
+ const files = [];
608
+ const currentDir = path.join(rootDir, subDir);
609
+ try {
610
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
611
+ for (const entry of entries) {
612
+ if (SKIP_DIRS.has(entry.name))
613
+ continue;
614
+ const relativePath = path.join(subDir, entry.name);
615
+ if (entry.isDirectory()) {
616
+ const subFiles = await findSourceFiles(rootDir, relativePath);
617
+ files.push(...subFiles);
618
+ }
619
+ else if (entry.isFile()) {
620
+ const ext = path.extname(entry.name).toLowerCase();
621
+ if (SOURCE_EXTENSIONS.has(ext)) {
622
+ files.push(relativePath);
623
+ }
624
+ }
625
+ }
626
+ }
627
+ catch {
628
+ // Directory not readable
629
+ }
630
+ return files;
631
+ }
632
+ // ============================================================================
633
+ // Command Registration
634
+ // ============================================================================
635
+ export function createConstraintsCommand() {
636
+ const cmd = new Command('constraints')
637
+ .description('Manage architectural constraints')
638
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
639
+ .option('-v, --verbose', 'Enable verbose output');
640
+ cmd
641
+ .command('extract')
642
+ .description('Extract constraints from codebase')
643
+ .option('-c, --category <category>', 'Filter by category')
644
+ .option('--min-confidence <number>', 'Minimum confidence threshold', '0.85')
645
+ .action((opts) => extractAction({ ...cmd.opts(), ...opts }));
646
+ cmd
647
+ .command('list')
648
+ .description('List all constraints')
649
+ .option('-c, --category <category>', 'Filter by category')
650
+ .option('-s, --status <status>', 'Filter by status (discovered, approved, ignored)')
651
+ .option('-l, --limit <number>', 'Maximum results', '50')
652
+ .option('--min-confidence <number>', 'Minimum confidence')
653
+ .action((opts) => listAction({ ...cmd.opts(), ...opts }));
654
+ cmd
655
+ .command('show <id>')
656
+ .description('Show constraint details')
657
+ .action((id) => showAction(id, cmd.opts()));
658
+ cmd
659
+ .command('approve <id>')
660
+ .description('Approve a discovered constraint')
661
+ .action((id) => approveAction(id, cmd.opts()));
662
+ cmd
663
+ .command('ignore <id> [reason]')
664
+ .description('Ignore a constraint')
665
+ .action((id, reason) => ignoreAction(id, reason, cmd.opts()));
666
+ cmd
667
+ .command('verify <file>')
668
+ .description('Verify a file against constraints')
669
+ .option('-c, --category <category>', 'Filter by category')
670
+ .option('--min-confidence <number>', 'Minimum confidence')
671
+ .action((file, opts) => verifyAction(file, { ...cmd.opts(), ...opts }));
672
+ cmd
673
+ .command('check')
674
+ .description('Check all files against constraints')
675
+ .option('-c, --category <category>', 'Filter by category')
676
+ .option('--min-confidence <number>', 'Minimum confidence')
677
+ .action((opts) => checkAction({ ...cmd.opts(), ...opts }));
678
+ cmd
679
+ .command('export <output>')
680
+ .description('Export constraints to JSON file')
681
+ .option('-c, --category <category>', 'Filter by category')
682
+ .option('-s, --status <status>', 'Filter by status')
683
+ .action((output, opts) => exportAction(output, { ...cmd.opts(), ...opts }));
684
+ return cmd;
685
+ }
686
+ //# sourceMappingURL=constraints.js.map