driftdetect 0.1.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 (117) hide show
  1. package/dist/bin/drift.d.ts +11 -0
  2. package/dist/bin/drift.d.ts.map +1 -0
  3. package/dist/bin/drift.js +83 -0
  4. package/dist/bin/drift.js.map +1 -0
  5. package/dist/commands/approve.d.ts +18 -0
  6. package/dist/commands/approve.d.ts.map +1 -0
  7. package/dist/commands/approve.js +271 -0
  8. package/dist/commands/approve.js.map +1 -0
  9. package/dist/commands/check.d.ts +39 -0
  10. package/dist/commands/check.d.ts.map +1 -0
  11. package/dist/commands/check.js +268 -0
  12. package/dist/commands/check.js.map +1 -0
  13. package/dist/commands/export.d.ts +14 -0
  14. package/dist/commands/export.d.ts.map +1 -0
  15. package/dist/commands/export.js +109 -0
  16. package/dist/commands/export.js.map +1 -0
  17. package/dist/commands/files.d.ts +13 -0
  18. package/dist/commands/files.d.ts.map +1 -0
  19. package/dist/commands/files.js +88 -0
  20. package/dist/commands/files.js.map +1 -0
  21. package/dist/commands/ignore.d.ts +18 -0
  22. package/dist/commands/ignore.d.ts.map +1 -0
  23. package/dist/commands/ignore.js +200 -0
  24. package/dist/commands/ignore.js.map +1 -0
  25. package/dist/commands/index.d.ts +16 -0
  26. package/dist/commands/index.d.ts.map +1 -0
  27. package/dist/commands/index.js +16 -0
  28. package/dist/commands/index.js.map +1 -0
  29. package/dist/commands/init.d.ts +19 -0
  30. package/dist/commands/init.d.ts.map +1 -0
  31. package/dist/commands/init.js +320 -0
  32. package/dist/commands/init.js.map +1 -0
  33. package/dist/commands/report.d.ts +20 -0
  34. package/dist/commands/report.d.ts.map +1 -0
  35. package/dist/commands/report.js +202 -0
  36. package/dist/commands/report.js.map +1 -0
  37. package/dist/commands/scan.d.ts +27 -0
  38. package/dist/commands/scan.d.ts.map +1 -0
  39. package/dist/commands/scan.js +444 -0
  40. package/dist/commands/scan.js.map +1 -0
  41. package/dist/commands/status.d.ts +18 -0
  42. package/dist/commands/status.d.ts.map +1 -0
  43. package/dist/commands/status.js +199 -0
  44. package/dist/commands/status.js.map +1 -0
  45. package/dist/commands/where.d.ts +13 -0
  46. package/dist/commands/where.d.ts.map +1 -0
  47. package/dist/commands/where.js +80 -0
  48. package/dist/commands/where.js.map +1 -0
  49. package/dist/git/hooks.d.ts +108 -0
  50. package/dist/git/hooks.d.ts.map +1 -0
  51. package/dist/git/hooks.js +389 -0
  52. package/dist/git/hooks.js.map +1 -0
  53. package/dist/git/index.d.ts +6 -0
  54. package/dist/git/index.d.ts.map +1 -0
  55. package/dist/git/index.js +6 -0
  56. package/dist/git/index.js.map +1 -0
  57. package/dist/git/staged-files.d.ts +41 -0
  58. package/dist/git/staged-files.d.ts.map +1 -0
  59. package/dist/git/staged-files.js +118 -0
  60. package/dist/git/staged-files.js.map +1 -0
  61. package/dist/index.d.ts +17 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +21 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/reporters/github-reporter.d.ts +13 -0
  66. package/dist/reporters/github-reporter.d.ts.map +1 -0
  67. package/dist/reporters/github-reporter.js +59 -0
  68. package/dist/reporters/github-reporter.js.map +1 -0
  69. package/dist/reporters/gitlab-reporter.d.ts +16 -0
  70. package/dist/reporters/gitlab-reporter.d.ts.map +1 -0
  71. package/dist/reporters/gitlab-reporter.js +62 -0
  72. package/dist/reporters/gitlab-reporter.js.map +1 -0
  73. package/dist/reporters/index.d.ts +9 -0
  74. package/dist/reporters/index.d.ts.map +1 -0
  75. package/dist/reporters/index.js +9 -0
  76. package/dist/reporters/index.js.map +1 -0
  77. package/dist/reporters/json-reporter.d.ts +13 -0
  78. package/dist/reporters/json-reporter.d.ts.map +1 -0
  79. package/dist/reporters/json-reporter.js +44 -0
  80. package/dist/reporters/json-reporter.js.map +1 -0
  81. package/dist/reporters/text-reporter.d.ts +13 -0
  82. package/dist/reporters/text-reporter.d.ts.map +1 -0
  83. package/dist/reporters/text-reporter.js +96 -0
  84. package/dist/reporters/text-reporter.js.map +1 -0
  85. package/dist/reporters/types.d.ts +42 -0
  86. package/dist/reporters/types.d.ts.map +1 -0
  87. package/dist/reporters/types.js +5 -0
  88. package/dist/reporters/types.js.map +1 -0
  89. package/dist/services/scanner-service.d.ts +166 -0
  90. package/dist/services/scanner-service.d.ts.map +1 -0
  91. package/dist/services/scanner-service.js +453 -0
  92. package/dist/services/scanner-service.js.map +1 -0
  93. package/dist/types/index.d.ts +24 -0
  94. package/dist/types/index.d.ts.map +1 -0
  95. package/dist/types/index.js +5 -0
  96. package/dist/types/index.js.map +1 -0
  97. package/dist/ui/index.d.ts +11 -0
  98. package/dist/ui/index.d.ts.map +1 -0
  99. package/dist/ui/index.js +15 -0
  100. package/dist/ui/index.js.map +1 -0
  101. package/dist/ui/progress.d.ts +115 -0
  102. package/dist/ui/progress.d.ts.map +1 -0
  103. package/dist/ui/progress.js +232 -0
  104. package/dist/ui/progress.js.map +1 -0
  105. package/dist/ui/prompts.d.ts +91 -0
  106. package/dist/ui/prompts.d.ts.map +1 -0
  107. package/dist/ui/prompts.js +160 -0
  108. package/dist/ui/prompts.js.map +1 -0
  109. package/dist/ui/spinner.d.ts +109 -0
  110. package/dist/ui/spinner.d.ts.map +1 -0
  111. package/dist/ui/spinner.js +179 -0
  112. package/dist/ui/spinner.js.map +1 -0
  113. package/dist/ui/table.d.ts +118 -0
  114. package/dist/ui/table.d.ts.map +1 -0
  115. package/dist/ui/table.js +235 -0
  116. package/dist/ui/table.js.map +1 -0
  117. package/package.json +57 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Report Command - drift report
3
+ *
4
+ * Generate reports in various formats.
5
+ *
6
+ * @requirements 29.7
7
+ */
8
+ import { Command } from 'commander';
9
+ import * as fs from 'node:fs/promises';
10
+ import * as path from 'node:path';
11
+ import chalk from 'chalk';
12
+ import { PatternStore, } from 'driftdetect-core';
13
+ import { createSpinner, status } from '../ui/spinner.js';
14
+ import { promptReportFormat, promptCategorySelection } from '../ui/prompts.js';
15
+ import { TextReporter, JsonReporter, GitHubReporter, GitLabReporter, } from '../reporters/index.js';
16
+ /** Directory name for drift configuration */
17
+ const DRIFT_DIR = '.drift';
18
+ /**
19
+ * Check if drift is initialized
20
+ */
21
+ async function isDriftInitialized(rootDir) {
22
+ try {
23
+ await fs.access(path.join(rootDir, DRIFT_DIR));
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ /**
31
+ * Get reporter based on format
32
+ */
33
+ function getReporter(format) {
34
+ switch (format) {
35
+ case 'json':
36
+ return new JsonReporter();
37
+ case 'github':
38
+ return new GitHubReporter();
39
+ case 'gitlab':
40
+ return new GitLabReporter();
41
+ case 'text':
42
+ default:
43
+ return new TextReporter();
44
+ }
45
+ }
46
+ /**
47
+ * Get file extension for format
48
+ */
49
+ function getExtension(format) {
50
+ switch (format) {
51
+ case 'json':
52
+ case 'gitlab':
53
+ return 'json';
54
+ case 'github':
55
+ case 'text':
56
+ default:
57
+ return 'txt';
58
+ }
59
+ }
60
+ /**
61
+ * Report command implementation
62
+ */
63
+ async function reportAction(options) {
64
+ const rootDir = process.cwd();
65
+ const verbose = options.verbose ?? false;
66
+ console.log();
67
+ console.log(chalk.bold('🔍 Drift - Generate Report'));
68
+ console.log();
69
+ // Check if initialized
70
+ if (!(await isDriftInitialized(rootDir))) {
71
+ status.error('Drift is not initialized. Run `drift init` first.');
72
+ process.exit(1);
73
+ }
74
+ // Initialize pattern store
75
+ const spinner = createSpinner('Loading patterns...');
76
+ spinner.start();
77
+ const store = new PatternStore({ rootDir });
78
+ await store.initialize();
79
+ spinner.succeed('Patterns loaded');
80
+ // Get format
81
+ let format = options.format;
82
+ if (!format) {
83
+ format = await promptReportFormat();
84
+ }
85
+ // Get categories to include
86
+ let categories = options.categories;
87
+ if (!categories || categories.length === 0) {
88
+ const stats = store.getStats();
89
+ const availableCategories = Object.entries(stats.byCategory)
90
+ .filter(([_, count]) => count > 0)
91
+ .map(([category]) => category);
92
+ if (availableCategories.length > 1 && !options.output) {
93
+ // Interactive category selection
94
+ categories = await promptCategorySelection(availableCategories);
95
+ }
96
+ else {
97
+ categories = availableCategories;
98
+ }
99
+ }
100
+ // Filter patterns by category
101
+ let patterns = store.getApproved();
102
+ if (categories && categories.length > 0) {
103
+ patterns = patterns.filter((p) => categories.includes(p.category));
104
+ }
105
+ if (patterns.length === 0) {
106
+ status.info('No approved patterns to report on');
107
+ console.log(chalk.gray('Run `drift scan` and `drift approve` to add patterns'));
108
+ return;
109
+ }
110
+ // Collect violations from outliers
111
+ const violations = [];
112
+ for (const pattern of patterns) {
113
+ for (const outlier of pattern.outliers) {
114
+ const violation = {
115
+ id: `${pattern.id}-${outlier.file}-${outlier.line}`,
116
+ patternId: pattern.id,
117
+ severity: pattern.severity,
118
+ file: outlier.file,
119
+ range: {
120
+ start: { line: outlier.line, character: outlier.column },
121
+ end: { line: outlier.endLine ?? outlier.line, character: outlier.endColumn ?? outlier.column },
122
+ },
123
+ message: `Deviation from pattern: ${pattern.name}`,
124
+ explanation: outlier.reason,
125
+ expected: pattern.description,
126
+ actual: `Code at line ${outlier.line} deviates from the established pattern`,
127
+ aiExplainAvailable: true,
128
+ aiFixAvailable: pattern.autoFixable,
129
+ firstSeen: new Date(),
130
+ occurrences: 1,
131
+ };
132
+ violations.push(violation);
133
+ }
134
+ }
135
+ // Calculate summary
136
+ const errorCount = violations.filter((v) => v.severity === 'error').length;
137
+ const warningCount = violations.filter((v) => v.severity === 'warning').length;
138
+ const infoCount = violations.filter((v) => v.severity === 'info').length;
139
+ const hintCount = violations.filter((v) => v.severity === 'hint').length;
140
+ // Prepare report data
141
+ const reportData = {
142
+ violations,
143
+ summary: {
144
+ total: violations.length,
145
+ errors: errorCount,
146
+ warnings: warningCount,
147
+ infos: infoCount,
148
+ hints: hintCount,
149
+ },
150
+ patterns,
151
+ timestamp: new Date().toISOString(),
152
+ rootDir,
153
+ };
154
+ // Generate report
155
+ const generateSpinner = createSpinner('Generating report...');
156
+ generateSpinner.start();
157
+ const reporter = getReporter(format);
158
+ const report = reporter.generate(reportData);
159
+ generateSpinner.succeed('Report generated');
160
+ // Output report
161
+ if (options.output) {
162
+ // Write to file
163
+ const outputPath = path.resolve(rootDir, options.output);
164
+ const outputDir = path.dirname(outputPath);
165
+ await fs.mkdir(outputDir, { recursive: true });
166
+ await fs.writeFile(outputPath, report);
167
+ status.success(`Report saved to ${path.relative(rootDir, outputPath)}`);
168
+ }
169
+ else {
170
+ // Print to console
171
+ console.log();
172
+ console.log(report);
173
+ }
174
+ // Save to reports directory
175
+ const reportsDir = path.join(rootDir, DRIFT_DIR, 'reports');
176
+ await fs.mkdir(reportsDir, { recursive: true });
177
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
178
+ const reportPath = path.join(reportsDir, `report-${timestamp}.${getExtension(format)}`);
179
+ await fs.writeFile(reportPath, report);
180
+ if (verbose) {
181
+ status.info(`Report also saved to ${path.relative(rootDir, reportPath)}`);
182
+ }
183
+ // Summary
184
+ console.log();
185
+ console.log(chalk.bold('Report Summary'));
186
+ console.log(chalk.gray('─'.repeat(40)));
187
+ console.log(` Format: ${format}`);
188
+ console.log(` Patterns: ${patterns.length}`);
189
+ console.log(` Violations: ${violations.length}`);
190
+ if (categories && categories.length > 0) {
191
+ console.log(` Categories: ${categories.join(', ')}`);
192
+ }
193
+ console.log();
194
+ }
195
+ export const reportCommand = new Command('report')
196
+ .description('Generate a report')
197
+ .option('-f, --format <format>', 'Output format (text, json, github, gitlab)')
198
+ .option('-o, --output <path>', 'Output file path')
199
+ .option('-c, --categories <categories...>', 'Include only specific categories')
200
+ .option('--verbose', 'Enable verbose output')
201
+ .action(reportAction);
202
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Scan Command - drift scan
3
+ *
4
+ * Perform a full codebase scan to discover patterns using
5
+ * enterprise-grade detectors from driftdetect-detectors.
6
+ *
7
+ * @requirements 29.2
8
+ */
9
+ import { Command } from 'commander';
10
+ export interface ScanCommandOptions {
11
+ /** Specific paths to scan */
12
+ paths?: string[];
13
+ /** Enable verbose output */
14
+ verbose?: boolean;
15
+ /** Force rescan even if cache is valid */
16
+ force?: boolean;
17
+ /** Only run critical detectors */
18
+ critical?: boolean;
19
+ /** Filter by categories */
20
+ categories?: string[];
21
+ /** Generate manifest with semantic locations */
22
+ manifest?: boolean;
23
+ /** Incremental scan (only changed files) */
24
+ incremental?: boolean;
25
+ }
26
+ export declare const scanCommand: Command;
27
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,MAAM,WAAW,kBAAkB;IACjC,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA4dD,eAAO,MAAM,WAAW,SASH,CAAC"}
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Scan Command - drift scan
3
+ *
4
+ * Perform a full codebase scan to discover patterns using
5
+ * enterprise-grade detectors from driftdetect-detectors.
6
+ *
7
+ * @requirements 29.2
8
+ */
9
+ import { Command } from 'commander';
10
+ import * as fs from 'node:fs/promises';
11
+ import * as path from 'node:path';
12
+ import * as crypto from 'node:crypto';
13
+ import chalk from 'chalk';
14
+ import { PatternStore, FileWalker, } from 'driftdetect-core';
15
+ import { createSpinner, status } from '../ui/spinner.js';
16
+ import { createPatternsTable } from '../ui/table.js';
17
+ import { createScannerService } from '../services/scanner-service.js';
18
+ /** Directory name for drift configuration */
19
+ const DRIFT_DIR = '.drift';
20
+ /**
21
+ * Check if drift is initialized
22
+ */
23
+ async function isDriftInitialized(rootDir) {
24
+ try {
25
+ await fs.access(path.join(rootDir, DRIFT_DIR));
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Load ignore patterns from .driftignore
34
+ */
35
+ async function loadIgnorePatterns(rootDir) {
36
+ const defaultIgnores = [
37
+ 'node_modules/**',
38
+ '.git/**',
39
+ 'dist/**',
40
+ 'build/**',
41
+ 'coverage/**',
42
+ '.drift/**',
43
+ '__pycache__/**',
44
+ '.venv/**',
45
+ 'venv/**',
46
+ ];
47
+ try {
48
+ const driftignorePath = path.join(rootDir, '.driftignore');
49
+ const content = await fs.readFile(driftignorePath, 'utf-8');
50
+ const patterns = content
51
+ .split('\n')
52
+ .map((line) => line.trim())
53
+ .filter((line) => line && !line.startsWith('#'));
54
+ return [...defaultIgnores, ...patterns];
55
+ }
56
+ catch {
57
+ return defaultIgnores;
58
+ }
59
+ }
60
+ /**
61
+ * Get file extension
62
+ */
63
+ function getExtension(filePath) {
64
+ const ext = path.extname(filePath).toLowerCase();
65
+ return ext.startsWith('.') ? ext.slice(1) : ext;
66
+ }
67
+ /**
68
+ * Check if file is scannable
69
+ */
70
+ function isScannableFile(filePath) {
71
+ const scannableExtensions = [
72
+ 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',
73
+ 'py', 'pyw',
74
+ 'css', 'scss', 'sass', 'less',
75
+ 'json', 'yaml', 'yml',
76
+ 'md', 'mdx',
77
+ 'html', 'htm',
78
+ 'vue', 'svelte',
79
+ ];
80
+ const ext = getExtension(filePath);
81
+ return scannableExtensions.includes(ext);
82
+ }
83
+ /**
84
+ * Group files by type for reporting
85
+ */
86
+ function groupFilesByType(files) {
87
+ const groups = new Map();
88
+ for (const file of files) {
89
+ const ext = getExtension(file) || 'other';
90
+ groups.set(ext, (groups.get(ext) ?? 0) + 1);
91
+ }
92
+ return groups;
93
+ }
94
+ /**
95
+ * Map detector category to PatternCategory
96
+ */
97
+ function mapToPatternCategory(category) {
98
+ const mapping = {
99
+ 'api': 'api',
100
+ 'auth': 'auth',
101
+ 'security': 'security',
102
+ 'errors': 'errors',
103
+ 'structural': 'structural',
104
+ 'components': 'components',
105
+ 'styling': 'styling',
106
+ 'logging': 'logging',
107
+ 'testing': 'testing',
108
+ 'data-access': 'data-access',
109
+ 'config': 'config',
110
+ 'types': 'types',
111
+ 'performance': 'performance',
112
+ 'accessibility': 'accessibility',
113
+ 'documentation': 'documentation',
114
+ };
115
+ return mapping[category] || 'structural';
116
+ }
117
+ /**
118
+ * Convert aggregated pattern to Pattern for storage
119
+ */
120
+ function convertToPattern(aggPattern, violations, rootDir) {
121
+ const now = new Date().toISOString();
122
+ // Generate unique ID
123
+ const id = crypto.createHash('sha256')
124
+ .update(`${aggPattern.patternId}-${rootDir}`)
125
+ .digest('hex')
126
+ .slice(0, 16);
127
+ // Calculate confidence
128
+ const spread = new Set(aggPattern.locations.map(l => l.file)).size;
129
+ const confidenceScore = Math.min(0.95, aggPattern.confidence);
130
+ const confidenceInfo = {
131
+ frequency: Math.min(1, aggPattern.occurrences / 100),
132
+ consistency: 0.9,
133
+ age: 0,
134
+ spread,
135
+ score: confidenceScore,
136
+ level: confidenceScore >= 0.85 ? 'high' : confidenceScore >= 0.65 ? 'medium' : confidenceScore >= 0.45 ? 'low' : 'uncertain',
137
+ };
138
+ // Create detector config
139
+ const detectorConfig = {
140
+ type: 'regex', // Most detectors are regex-based
141
+ config: {
142
+ detectorId: aggPattern.detectorId,
143
+ patternId: aggPattern.patternId,
144
+ },
145
+ };
146
+ // Get violations for this pattern as outliers
147
+ const patternViolations = violations.filter(v => v.detectorId === aggPattern.detectorId);
148
+ const outliers = patternViolations.map(v => ({
149
+ file: v.file,
150
+ line: v.line,
151
+ column: v.column,
152
+ reason: v.message,
153
+ deviationScore: v.severity === 'error' ? 1.0 : v.severity === 'warning' ? 0.7 : 0.4,
154
+ }));
155
+ // Limit locations to prevent huge files
156
+ const locations = aggPattern.locations.slice(0, 100);
157
+ return {
158
+ id,
159
+ category: mapToPatternCategory(aggPattern.category),
160
+ subcategory: aggPattern.subcategory,
161
+ name: aggPattern.name,
162
+ description: aggPattern.description,
163
+ detector: detectorConfig,
164
+ confidence: confidenceInfo,
165
+ locations,
166
+ outliers,
167
+ metadata: {
168
+ firstSeen: now,
169
+ lastSeen: now,
170
+ source: 'auto-detected',
171
+ tags: [aggPattern.category, aggPattern.subcategory],
172
+ },
173
+ severity: patternViolations.length > 0 ?
174
+ (patternViolations.some(v => v.severity === 'error') ? 'error' : 'warning') :
175
+ 'info',
176
+ autoFixable: false,
177
+ status: 'discovered',
178
+ };
179
+ }
180
+ /**
181
+ * Scan command implementation
182
+ */
183
+ async function scanAction(options) {
184
+ const rootDir = process.cwd();
185
+ const verbose = options.verbose ?? false;
186
+ console.log();
187
+ console.log(chalk.bold('🔍 Drift - Enterprise Pattern Scanner'));
188
+ console.log();
189
+ // Check if initialized
190
+ if (!(await isDriftInitialized(rootDir))) {
191
+ status.error('Drift is not initialized. Run `drift init` first.');
192
+ process.exit(1);
193
+ }
194
+ // Initialize pattern store
195
+ const store = new PatternStore({ rootDir });
196
+ await store.initialize();
197
+ // Load ignore patterns
198
+ const ignorePatterns = await loadIgnorePatterns(rootDir);
199
+ if (verbose) {
200
+ status.info(`Loaded ${ignorePatterns.length} ignore patterns`);
201
+ }
202
+ // Initialize file walker
203
+ const walker = new FileWalker();
204
+ // Discover files
205
+ const discoverSpinner = createSpinner('Discovering files...');
206
+ discoverSpinner.start();
207
+ let files;
208
+ try {
209
+ const scanOptions = {
210
+ rootDir,
211
+ ignorePatterns,
212
+ respectGitignore: true,
213
+ respectDriftignore: true,
214
+ followSymlinks: false,
215
+ maxDepth: 50,
216
+ };
217
+ // If specific paths provided, use those
218
+ if (options.paths && options.paths.length > 0) {
219
+ files = [];
220
+ for (const p of options.paths) {
221
+ const fullPath = path.resolve(rootDir, p);
222
+ const stat = await fs.stat(fullPath);
223
+ if (stat.isDirectory()) {
224
+ const subResult = await walker.walk({
225
+ ...scanOptions,
226
+ rootDir: fullPath,
227
+ });
228
+ files.push(...subResult.files.map((f) => path.relative(rootDir, f.path)));
229
+ }
230
+ else {
231
+ files.push(path.relative(rootDir, fullPath));
232
+ }
233
+ }
234
+ }
235
+ else {
236
+ const result = await walker.walk(scanOptions);
237
+ files = result.files.map((f) => f.relativePath);
238
+ }
239
+ // Filter to scannable files
240
+ files = files.filter(isScannableFile);
241
+ discoverSpinner.succeed(`Discovered ${files.length} files`);
242
+ }
243
+ catch (error) {
244
+ discoverSpinner.fail('Failed to discover files');
245
+ console.error(chalk.red(error.message));
246
+ process.exit(1);
247
+ }
248
+ // Show file type breakdown
249
+ if (verbose) {
250
+ const fileGroups = groupFilesByType(files);
251
+ console.log();
252
+ console.log(chalk.gray('File types:'));
253
+ for (const [ext, count] of Array.from(fileGroups.entries()).sort((a, b) => b[1] - a[1])) {
254
+ console.log(chalk.gray(` .${ext}: ${count}`));
255
+ }
256
+ console.log();
257
+ }
258
+ // Initialize scanner service with real detectors
259
+ const scannerService = createScannerService({
260
+ rootDir,
261
+ verbose,
262
+ criticalOnly: options.critical ?? false,
263
+ categories: options.categories ?? [],
264
+ generateManifest: options.manifest ?? false,
265
+ incremental: options.incremental ?? false,
266
+ });
267
+ const initSpinner = createSpinner('Loading detectors...');
268
+ initSpinner.start();
269
+ try {
270
+ await scannerService.initialize();
271
+ const counts = scannerService.getDetectorCounts();
272
+ initSpinner.succeed(`Loaded ${scannerService.getDetectorCount()} detectors (${counts.total} available)`);
273
+ }
274
+ catch (error) {
275
+ initSpinner.fail('Failed to load detectors');
276
+ console.error(chalk.red(error.message));
277
+ process.exit(1);
278
+ }
279
+ // Create project context
280
+ const projectContext = {
281
+ rootDir,
282
+ files,
283
+ config: {},
284
+ };
285
+ // Scan files with progress
286
+ const scanSpinner = createSpinner('Analyzing patterns with enterprise detectors...');
287
+ scanSpinner.start();
288
+ const startTime = Date.now();
289
+ try {
290
+ const scanResults = await scannerService.scanFiles(files, projectContext);
291
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
292
+ scanSpinner.succeed(`Analyzed ${scanResults.totalFiles} files in ${duration}s ` +
293
+ `(${scanResults.patterns.length} pattern types, ${scanResults.totalViolations} violations)`);
294
+ if (verbose) {
295
+ console.log(chalk.gray(` Detectors ran: ${scanResults.detectorStats.ran}`));
296
+ console.log(chalk.gray(` Detectors skipped: ${scanResults.detectorStats.skipped}`));
297
+ if (scanResults.errors.length > 0) {
298
+ console.log(chalk.yellow(` Warnings: ${scanResults.errors.length}`));
299
+ }
300
+ }
301
+ // Show detected patterns by category
302
+ if (scanResults.patterns.length > 0) {
303
+ console.log();
304
+ console.log(chalk.bold('Patterns detected by category:'));
305
+ const byCategory = new Map();
306
+ for (const pattern of scanResults.patterns) {
307
+ byCategory.set(pattern.category, (byCategory.get(pattern.category) ?? 0) + pattern.occurrences);
308
+ }
309
+ for (const [category, count] of Array.from(byCategory.entries()).sort((a, b) => b[1] - a[1])) {
310
+ console.log(` ${chalk.cyan(category)}: ${count} occurrences`);
311
+ }
312
+ }
313
+ // Show violations (HIGH VALUE)
314
+ if (scanResults.violations.length > 0) {
315
+ console.log();
316
+ console.log(chalk.bold.red(`⚠️ ${scanResults.violations.length} Violations Found:`));
317
+ // Group by severity
318
+ const errors = scanResults.violations.filter(v => v.severity === 'error');
319
+ const warnings = scanResults.violations.filter(v => v.severity === 'warning');
320
+ const infos = scanResults.violations.filter(v => v.severity === 'info');
321
+ if (errors.length > 0) {
322
+ console.log();
323
+ console.log(chalk.red(` Errors (${errors.length}):`));
324
+ for (const v of errors.slice(0, 5)) {
325
+ console.log(chalk.red(` ${v.file}:${v.line} - ${v.message}`));
326
+ }
327
+ if (errors.length > 5) {
328
+ console.log(chalk.gray(` ... and ${errors.length - 5} more errors`));
329
+ }
330
+ }
331
+ if (warnings.length > 0) {
332
+ console.log();
333
+ console.log(chalk.yellow(` Warnings (${warnings.length}):`));
334
+ for (const v of warnings.slice(0, 5)) {
335
+ console.log(chalk.yellow(` ${v.file}:${v.line} - ${v.message}`));
336
+ }
337
+ if (warnings.length > 5) {
338
+ console.log(chalk.gray(` ... and ${warnings.length - 5} more warnings`));
339
+ }
340
+ }
341
+ if (verbose && infos.length > 0) {
342
+ console.log();
343
+ console.log(chalk.blue(` Info (${infos.length}):`));
344
+ for (const v of infos.slice(0, 3)) {
345
+ console.log(chalk.blue(` ${v.file}:${v.line} - ${v.message}`));
346
+ }
347
+ if (infos.length > 3) {
348
+ console.log(chalk.gray(` ... and ${infos.length - 3} more`));
349
+ }
350
+ }
351
+ }
352
+ // Convert and store patterns
353
+ const saveSpinner = createSpinner('Saving patterns...');
354
+ saveSpinner.start();
355
+ let addedCount = 0;
356
+ let skippedCount = 0;
357
+ for (const aggPattern of scanResults.patterns) {
358
+ const pattern = convertToPattern(aggPattern, scanResults.violations, rootDir);
359
+ // Skip if pattern already exists
360
+ if (store.has(pattern.id)) {
361
+ skippedCount++;
362
+ continue;
363
+ }
364
+ try {
365
+ store.add(pattern);
366
+ addedCount++;
367
+ }
368
+ catch (e) {
369
+ if (verbose) {
370
+ console.log(chalk.yellow(` Warning: Could not add pattern ${aggPattern.patternId}: ${e.message}`));
371
+ }
372
+ }
373
+ }
374
+ await store.saveAll();
375
+ saveSpinner.succeed(`Saved ${addedCount} new patterns (${skippedCount} already existed)`);
376
+ // Show manifest info if generated
377
+ if (options.manifest && scanResults.manifest) {
378
+ console.log();
379
+ console.log(chalk.bold('📋 Manifest Generated:'));
380
+ console.log(chalk.gray(` Location: .drift/index/manifest.json`));
381
+ console.log(chalk.gray(` Patterns: ${scanResults.manifest.summary.totalPatterns}`));
382
+ console.log(chalk.gray(` Files: ${scanResults.manifest.summary.totalFiles}`));
383
+ console.log(chalk.gray(` Locations: ${scanResults.manifest.summary.totalLocations}`));
384
+ console.log();
385
+ console.log(chalk.gray('Use these commands to explore:'));
386
+ console.log(chalk.cyan(' drift export --format ai-context'));
387
+ console.log(chalk.cyan(' drift where <pattern>'));
388
+ console.log(chalk.cyan(' drift files <path>'));
389
+ }
390
+ }
391
+ catch (error) {
392
+ scanSpinner.fail('Scan failed');
393
+ console.error(chalk.red(error.message));
394
+ process.exit(1);
395
+ }
396
+ // Summary
397
+ console.log();
398
+ const stats = store.getStats();
399
+ console.log(chalk.bold('Scan Summary'));
400
+ console.log(chalk.gray('─'.repeat(40)));
401
+ console.log(` Files scanned: ${chalk.cyan(files.length)}`);
402
+ console.log(` Total patterns: ${chalk.cyan(stats.totalPatterns)}`);
403
+ console.log(` Discovered: ${chalk.yellow(stats.byStatus.discovered)}`);
404
+ console.log(` Approved: ${chalk.green(stats.byStatus.approved)}`);
405
+ console.log(` Ignored: ${chalk.gray(stats.byStatus.ignored)}`);
406
+ console.log();
407
+ // Show discovered patterns if any
408
+ if (stats.byStatus.discovered > 0) {
409
+ const discovered = store.getDiscovered();
410
+ const highConfidence = discovered.filter((p) => p.confidence.level === 'high');
411
+ if (highConfidence.length > 0) {
412
+ console.log(chalk.bold('High Confidence Patterns (ready for approval):'));
413
+ console.log();
414
+ const rows = highConfidence.slice(0, 10).map((p) => ({
415
+ id: p.id.slice(0, 13),
416
+ name: p.name.slice(0, 28),
417
+ category: p.category,
418
+ confidence: p.confidence.score,
419
+ locations: p.locations.length,
420
+ outliers: p.outliers.length,
421
+ }));
422
+ console.log(createPatternsTable(rows));
423
+ if (highConfidence.length > 10) {
424
+ console.log(chalk.gray(` ... and ${highConfidence.length - 10} more`));
425
+ }
426
+ console.log();
427
+ }
428
+ console.log(chalk.gray('To review and approve patterns:'));
429
+ console.log(chalk.cyan(' drift status'));
430
+ console.log(chalk.cyan(' drift approve <pattern-id>'));
431
+ }
432
+ console.log();
433
+ }
434
+ export const scanCommand = new Command('scan')
435
+ .description('Scan codebase for patterns using enterprise detectors')
436
+ .option('-p, --paths <paths...>', 'Specific paths to scan')
437
+ .option('--force', 'Force rescan even if cache is valid')
438
+ .option('--verbose', 'Enable verbose output')
439
+ .option('--critical', 'Only run critical/high-value detectors')
440
+ .option('-c, --categories <categories...>', 'Filter by categories (api, auth, security, etc.)')
441
+ .option('--manifest', 'Generate manifest with semantic locations')
442
+ .option('--incremental', 'Only scan changed files')
443
+ .action(scanAction);
444
+ //# sourceMappingURL=scan.js.map