driftdetect 0.9.45 → 0.9.47

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 (49) hide show
  1. package/dist/commands/setup/index.d.ts +17 -0
  2. package/dist/commands/setup/index.d.ts.map +1 -0
  3. package/dist/commands/setup/index.js +592 -0
  4. package/dist/commands/setup/index.js.map +1 -0
  5. package/dist/commands/setup/runners/base.d.ts +34 -0
  6. package/dist/commands/setup/runners/base.d.ts.map +1 -0
  7. package/dist/commands/setup/runners/base.js +20 -0
  8. package/dist/commands/setup/runners/base.js.map +1 -0
  9. package/dist/commands/setup/runners/callgraph.d.ts +17 -0
  10. package/dist/commands/setup/runners/callgraph.d.ts.map +1 -0
  11. package/dist/commands/setup/runners/callgraph.js +91 -0
  12. package/dist/commands/setup/runners/callgraph.js.map +1 -0
  13. package/dist/commands/setup/runners/coupling.d.ts +20 -0
  14. package/dist/commands/setup/runners/coupling.d.ts.map +1 -0
  15. package/dist/commands/setup/runners/coupling.js +121 -0
  16. package/dist/commands/setup/runners/coupling.js.map +1 -0
  17. package/dist/commands/setup/runners/dna.d.ts +17 -0
  18. package/dist/commands/setup/runners/dna.d.ts.map +1 -0
  19. package/dist/commands/setup/runners/dna.js +72 -0
  20. package/dist/commands/setup/runners/dna.js.map +1 -0
  21. package/dist/commands/setup/runners/index.d.ts +12 -0
  22. package/dist/commands/setup/runners/index.d.ts.map +1 -0
  23. package/dist/commands/setup/runners/index.js +12 -0
  24. package/dist/commands/setup/runners/index.js.map +1 -0
  25. package/dist/commands/setup/runners/memory.d.ts +17 -0
  26. package/dist/commands/setup/runners/memory.d.ts.map +1 -0
  27. package/dist/commands/setup/runners/memory.js +71 -0
  28. package/dist/commands/setup/runners/memory.js.map +1 -0
  29. package/dist/commands/setup/runners/test-topology.d.ts +20 -0
  30. package/dist/commands/setup/runners/test-topology.d.ts.map +1 -0
  31. package/dist/commands/setup/runners/test-topology.js +137 -0
  32. package/dist/commands/setup/runners/test-topology.js.map +1 -0
  33. package/dist/commands/setup/types.d.ts +99 -0
  34. package/dist/commands/setup/types.d.ts.map +1 -0
  35. package/dist/commands/setup/types.js +42 -0
  36. package/dist/commands/setup/types.js.map +1 -0
  37. package/dist/commands/setup/ui.d.ts +16 -0
  38. package/dist/commands/setup/ui.d.ts.map +1 -0
  39. package/dist/commands/setup/ui.js +108 -0
  40. package/dist/commands/setup/ui.js.map +1 -0
  41. package/dist/commands/setup/utils.d.ts +20 -0
  42. package/dist/commands/setup/utils.d.ts.map +1 -0
  43. package/dist/commands/setup/utils.js +178 -0
  44. package/dist/commands/setup/utils.js.map +1 -0
  45. package/dist/commands/setup.d.ts +2 -18
  46. package/dist/commands/setup.d.ts.map +1 -1
  47. package/dist/commands/setup.js +2 -883
  48. package/dist/commands/setup.js.map +1 -1
  49. package/package.json +5 -5
@@ -1,888 +1,7 @@
1
1
  /**
2
- * Setup Command - drift setup
3
- *
4
- * Enterprise-grade guided onboarding that creates a Source of Truth
5
- * for your codebase. Every feature is explained, every choice persisted.
6
- *
7
- * Philosophy:
8
- * - Users understand what each feature does before enabling
9
- * - All choices are persisted and recallable
10
- * - Source of Truth is created and versioned
11
- * - Subsequent scans are tracked against the baseline
12
- * - Changes require explicit approval
2
+ * Setup Command - Re-export from modular implementation
13
3
  *
14
4
  * @module commands/setup
15
5
  */
16
- import * as crypto from 'node:crypto';
17
- import * as fs from 'node:fs/promises';
18
- import * as path from 'node:path';
19
- import chalk from 'chalk';
20
- import { Command } from 'commander';
21
- import { confirm, select } from '@inquirer/prompts';
22
- import { createSpinner } from '../ui/spinner.js';
23
- import { createCLIPatternService } from '../services/pattern-service-factory.js';
24
- import { createScannerService } from '../services/scanner-service.js';
25
- import { VERSION } from '../index.js';
26
- import { PatternStore, getProjectRegistry, FileWalker, getDefaultIgnorePatterns, mergeIgnorePatterns, isNativeAvailable, buildCallGraph, shouldIgnoreDirectory, createWorkspaceManager, } from 'driftdetect-core';
27
- // ═══════════════════════════════════════════════════════════════════════════
28
- // CONSTANTS
29
- // ═══════════════════════════════════════════════════════════════════════════
30
- const DRIFT_DIR = '.drift';
31
- const SOURCE_OF_TRUTH_FILE = 'source-of-truth.json';
32
- const SETUP_STATE_FILE = '.setup-state.json';
33
- const SCHEMA_VERSION = '2.0.0';
34
- const DRIFT_SUBDIRS = [
35
- 'patterns/discovered',
36
- 'patterns/approved',
37
- 'patterns/ignored',
38
- 'patterns/variants',
39
- 'history/snapshots',
40
- 'cache',
41
- 'reports',
42
- 'lake/callgraph',
43
- 'lake/patterns',
44
- 'lake/security',
45
- 'lake/examples',
46
- 'boundaries',
47
- 'test-topology',
48
- 'module-coupling',
49
- 'error-handling',
50
- 'constraints/discovered',
51
- 'constraints/approved',
52
- 'constraints/ignored',
53
- 'constraints/custom',
54
- 'constraints/history',
55
- 'contracts/discovered',
56
- 'contracts/verified',
57
- 'contracts/mismatch',
58
- 'contracts/ignored',
59
- 'indexes',
60
- 'views',
61
- 'dna',
62
- 'environment',
63
- 'memory',
64
- 'audit/snapshots',
65
- ];
66
- // ═══════════════════════════════════════════════════════════════════════════
67
- // HELPER FUNCTIONS
68
- // ═══════════════════════════════════════════════════════════════════════════
69
- async function isDriftInitialized(rootDir) {
70
- try {
71
- await fs.access(path.join(rootDir, DRIFT_DIR));
72
- return true;
73
- }
74
- catch {
75
- return false;
76
- }
77
- }
78
- async function loadSourceOfTruth(rootDir) {
79
- try {
80
- const sotPath = path.join(rootDir, DRIFT_DIR, SOURCE_OF_TRUTH_FILE);
81
- const content = await fs.readFile(sotPath, 'utf-8');
82
- return JSON.parse(content);
83
- }
84
- catch {
85
- return null;
86
- }
87
- }
88
- async function saveSourceOfTruth(rootDir, sot) {
89
- const sotPath = path.join(rootDir, DRIFT_DIR, SOURCE_OF_TRUTH_FILE);
90
- sot.updatedAt = new Date().toISOString();
91
- await fs.writeFile(sotPath, JSON.stringify(sot, null, 2));
92
- }
93
- async function loadSetupState(rootDir) {
94
- try {
95
- const statePath = path.join(rootDir, DRIFT_DIR, SETUP_STATE_FILE);
96
- const content = await fs.readFile(statePath, 'utf-8');
97
- return JSON.parse(content);
98
- }
99
- catch {
100
- return null;
101
- }
102
- }
103
- async function saveSetupState(rootDir, state) {
104
- const statePath = path.join(rootDir, DRIFT_DIR, SETUP_STATE_FILE);
105
- await fs.writeFile(statePath, JSON.stringify(state, null, 2));
106
- }
107
- async function clearSetupState(rootDir) {
108
- try {
109
- const statePath = path.join(rootDir, DRIFT_DIR, SETUP_STATE_FILE);
110
- await fs.unlink(statePath);
111
- }
112
- catch { /* ignore */ }
113
- }
114
- async function createDriftDirectory(rootDir) {
115
- const driftDir = path.join(rootDir, DRIFT_DIR);
116
- await fs.mkdir(driftDir, { recursive: true });
117
- for (const subdir of DRIFT_SUBDIRS) {
118
- await fs.mkdir(path.join(driftDir, subdir), { recursive: true });
119
- }
120
- }
121
- async function createDefaultConfig(rootDir, projectId) {
122
- const configPath = path.join(rootDir, DRIFT_DIR, 'config.json');
123
- const config = {
124
- version: SCHEMA_VERSION,
125
- project: {
126
- id: projectId,
127
- name: path.basename(rootDir),
128
- initializedAt: new Date().toISOString(),
129
- },
130
- severity: {},
131
- ignore: [
132
- 'node_modules/**', 'dist/**', 'build/**', '.git/**', 'coverage/**',
133
- '*.min.js', '*.bundle.js', 'vendor/**', '__pycache__/**', '.venv/**',
134
- 'target/**', 'bin/**', 'obj/**',
135
- ],
136
- ci: { failOn: 'error', reportFormat: 'text' },
137
- learning: { autoApproveThreshold: 0.85, minOccurrences: 3, semanticLearning: true },
138
- performance: { maxWorkers: 4, cacheEnabled: true, incrementalAnalysis: true, cacheTTL: 3600 },
139
- features: { callGraph: true, boundaries: true, dna: true, contracts: true },
140
- };
141
- await fs.writeFile(configPath, JSON.stringify(config, null, 2));
142
- }
143
- async function createDriftignore(rootDir) {
144
- const driftignorePath = path.join(rootDir, '.driftignore');
145
- try {
146
- await fs.access(driftignorePath);
147
- }
148
- catch {
149
- await fs.writeFile(driftignorePath, `# Drift ignore patterns
150
- node_modules/
151
- dist/
152
- build/
153
- .git/
154
- coverage/
155
- vendor/
156
- __pycache__/
157
- .venv/
158
- target/
159
- bin/
160
- obj/
161
- `);
162
- }
163
- }
164
- async function countSourceFiles(rootDir) {
165
- const exts = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.cs', '.java', '.php', '.go', '.rs', '.cpp', '.c', '.h']);
166
- let count = 0;
167
- async function walk(dir) {
168
- try {
169
- const entries = await fs.readdir(dir, { withFileTypes: true });
170
- for (const entry of entries) {
171
- if (entry.isDirectory() && !shouldIgnoreDirectory(entry.name)) {
172
- await walk(path.join(dir, entry.name));
173
- }
174
- else if (entry.isFile() && exts.has(path.extname(entry.name).toLowerCase())) {
175
- count++;
176
- }
177
- }
178
- }
179
- catch { /* skip */ }
180
- }
181
- await walk(rootDir);
182
- return count;
183
- }
184
- async function loadIgnorePatterns(rootDir) {
185
- try {
186
- const content = await fs.readFile(path.join(rootDir, '.driftignore'), 'utf-8');
187
- const userPatterns = content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
188
- return mergeIgnorePatterns(userPatterns);
189
- }
190
- catch {
191
- return getDefaultIgnorePatterns();
192
- }
193
- }
194
- function computeChecksum(data) {
195
- return crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex').slice(0, 16);
196
- }
197
- function isScannableFile(filePath) {
198
- const exts = ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'py', 'cs', 'java', 'php', 'go', 'rs', 'c', 'cpp', 'cc', 'h', 'hpp', 'vue', 'svelte'];
199
- const ext = path.extname(filePath).toLowerCase().slice(1);
200
- return exts.includes(ext);
201
- }
202
- function mapToPatternCategory(category) {
203
- const mapping = {
204
- 'api': 'api', 'auth': 'auth', 'security': 'security', 'errors': 'errors',
205
- 'structural': 'structural', 'components': 'components', 'styling': 'styling',
206
- 'logging': 'logging', 'testing': 'testing', 'data-access': 'data-access',
207
- 'config': 'config', 'types': 'types', 'performance': 'performance',
208
- 'accessibility': 'accessibility', 'documentation': 'documentation',
209
- };
210
- return mapping[category] || 'structural';
211
- }
212
- // ═══════════════════════════════════════════════════════════════════════════
213
- // UI HELPERS
214
- // ═══════════════════════════════════════════════════════════════════════════
215
- function printWelcome() {
216
- console.log();
217
- console.log(chalk.bold.magenta('╔════════════════════════════════════════════════════════════╗'));
218
- console.log(chalk.bold.magenta('║') + chalk.bold(' 🔍 Drift Setup Wizard ') + chalk.bold.magenta('║'));
219
- console.log(chalk.bold.magenta('║') + chalk.gray(' Create your codebase Source of Truth ') + chalk.bold.magenta('║'));
220
- console.log(chalk.bold.magenta('╚════════════════════════════════════════════════════════════╝'));
221
- console.log();
222
- }
223
- function printPhase(num, title, description) {
224
- console.log();
225
- console.log(chalk.bold.cyan(`━━━ Phase ${num}: ${title} ━━━`));
226
- console.log(chalk.gray(` ${description}`));
227
- console.log();
228
- }
229
- function printFeature(icon, name, oneLiner, benefit) {
230
- console.log(` ${icon} ${chalk.bold(name)}`);
231
- console.log(chalk.gray(` ${oneLiner}`));
232
- console.log(chalk.green(` → ${benefit}`));
233
- console.log();
234
- }
235
- function printSuccess(message) {
236
- console.log(chalk.green(` ✓ ${message}`));
237
- }
238
- function printSkip(message) {
239
- console.log(chalk.gray(` ○ ${message}`));
240
- }
241
- function printInfo(message) {
242
- console.log(chalk.gray(` ${message}`));
243
- }
244
- // ═══════════════════════════════════════════════════════════════════════════
245
- // PHASE IMPLEMENTATIONS
246
- // ═══════════════════════════════════════════════════════════════════════════
247
- async function phaseDetectExisting(rootDir, autoYes, _verbose) {
248
- printPhase(0, 'Detection', 'Checking for existing Drift installation');
249
- const initialized = await isDriftInitialized(rootDir);
250
- const sot = initialized ? await loadSourceOfTruth(rootDir) : null;
251
- if (!initialized) {
252
- printInfo('No existing installation found. Starting fresh setup.');
253
- return { isNew: true, sot: null, shouldContinue: true };
254
- }
255
- if (sot) {
256
- console.log(chalk.yellow(' ⚡ Existing Source of Truth detected!'));
257
- console.log();
258
- console.log(chalk.gray(` Project: ${sot.project.name}`));
259
- console.log(chalk.gray(` Created: ${new Date(sot.createdAt).toLocaleDateString()}`));
260
- console.log(chalk.gray(` Patterns: ${sot.baseline.patternCount} (${sot.baseline.approvedCount} approved)`));
261
- console.log();
262
- if (autoYes) {
263
- printInfo('Using existing Source of Truth (--yes flag)');
264
- return { isNew: false, sot, shouldContinue: true };
265
- }
266
- const choice = await select({
267
- message: 'What would you like to do?',
268
- choices: [
269
- { value: 'use', name: 'Use existing Source of Truth (recommended)' },
270
- { value: 'rescan', name: 'Rescan and update baseline (keeps approved patterns)' },
271
- { value: 'fresh', name: 'Start fresh (creates backup first)' },
272
- { value: 'cancel', name: 'Cancel setup' },
273
- ],
274
- });
275
- if (choice === 'cancel') {
276
- return { isNew: false, sot, shouldContinue: false };
277
- }
278
- if (choice === 'use') {
279
- printSuccess('Using existing Source of Truth');
280
- return { isNew: false, sot, shouldContinue: true };
281
- }
282
- if (choice === 'fresh') {
283
- // Create backup before fresh start
284
- const spinner = createSpinner('Creating backup...');
285
- spinner.start();
286
- try {
287
- const manager = createWorkspaceManager(rootDir);
288
- await manager.initialize({ driftVersion: VERSION });
289
- await manager.createBackup('pre_destructive_operation');
290
- spinner.succeed('Backup created');
291
- }
292
- catch (error) {
293
- spinner.fail(`Backup failed: ${error.message}`);
294
- if (!autoYes) {
295
- const proceed = await confirm({
296
- message: 'Continue without backup?',
297
- default: false,
298
- });
299
- if (!proceed) {
300
- return { isNew: false, sot, shouldContinue: false };
301
- }
302
- }
303
- }
304
- return { isNew: true, sot: null, shouldContinue: true };
305
- }
306
- // rescan - keep existing but update
307
- return { isNew: false, sot, shouldContinue: true };
308
- }
309
- // Initialized but no SOT - legacy installation
310
- console.log(chalk.yellow(' ⚠ Legacy installation detected (no Source of Truth)'));
311
- printInfo('Will create Source of Truth from existing data.');
312
- return { isNew: false, sot: null, shouldContinue: true };
313
- }
314
- async function phaseInitialize(rootDir, isNew, _state) {
315
- printPhase(1, 'Initialize', 'Setting up project structure');
316
- const projectId = crypto.randomUUID();
317
- if (isNew) {
318
- const spinner = createSpinner('Creating .drift directory...');
319
- spinner.start();
320
- await createDriftDirectory(rootDir);
321
- await createDefaultConfig(rootDir, projectId);
322
- await createDriftignore(rootDir);
323
- spinner.succeed('Project structure created');
324
- }
325
- else {
326
- printInfo('Using existing project structure');
327
- }
328
- // Register with global registry
329
- try {
330
- const registry = await getProjectRegistry();
331
- const existing = registry.findByPath(rootDir);
332
- if (existing) {
333
- await registry.setActive(existing.id);
334
- printSuccess(`Project registered: ${chalk.cyan(existing.name)}`);
335
- return existing.id;
336
- }
337
- else {
338
- const project = await registry.register(rootDir);
339
- await registry.setActive(project.id);
340
- printSuccess(`Project registered: ${chalk.cyan(project.name)}`);
341
- return project.id;
342
- }
343
- }
344
- catch {
345
- printInfo('Global registry unavailable (single-project mode)');
346
- return projectId;
347
- }
348
- }
349
- async function phaseScan(rootDir, autoYes, verbose, state) {
350
- printPhase(2, 'Pattern Discovery', 'Scanning your codebase for patterns');
351
- console.log(chalk.gray(' Drift analyzes your code to discover:'));
352
- console.log(chalk.gray(' • API patterns (routes, endpoints, middleware)'));
353
- console.log(chalk.gray(' • Auth patterns (authentication, authorization)'));
354
- console.log(chalk.gray(' • Error handling patterns'));
355
- console.log(chalk.gray(' • Data access patterns (queries, ORM usage)'));
356
- console.log(chalk.gray(' • Structural patterns (naming, organization)'));
357
- console.log(chalk.gray(' • And 10+ more categories...'));
358
- console.log();
359
- const fileCount = await countSourceFiles(rootDir);
360
- console.log(` Found ${chalk.cyan(fileCount.toLocaleString())} source files.`);
361
- console.log();
362
- const shouldScan = autoYes || await confirm({
363
- message: 'Run pattern scan?',
364
- default: true,
365
- });
366
- if (!shouldScan) {
367
- printSkip('Skipping scan. Run `drift scan` later.');
368
- return { success: false, patternCount: 0, categories: {} };
369
- }
370
- const spinner = createSpinner(`Scanning ${fileCount.toLocaleString()} files...`);
371
- spinner.start();
372
- try {
373
- const store = new PatternStore({ rootDir });
374
- await store.initialize();
375
- const ignorePatterns = await loadIgnorePatterns(rootDir);
376
- const walker = new FileWalker();
377
- const scanOptions = {
378
- rootDir,
379
- ignorePatterns,
380
- respectGitignore: true,
381
- respectDriftignore: true,
382
- followSymlinks: false,
383
- maxDepth: 50,
384
- maxFileSize: 1048576,
385
- };
386
- const result = await walker.walk(scanOptions);
387
- const files = result.files.map(f => f.relativePath).filter(isScannableFile);
388
- const scannerService = createScannerService({
389
- rootDir,
390
- verbose,
391
- criticalOnly: false,
392
- categories: [],
393
- generateManifest: false,
394
- incremental: false,
395
- });
396
- await scannerService.initialize();
397
- const projectContext = { rootDir, files, config: {} };
398
- const scanResults = await scannerService.scanFiles(files, projectContext);
399
- const now = new Date().toISOString();
400
- const categories = {};
401
- for (const aggPattern of scanResults.patterns) {
402
- const cat = aggPattern.category;
403
- categories[cat] = (categories[cat] ?? 0) + aggPattern.occurrences;
404
- const id = crypto.createHash('sha256')
405
- .update(`${aggPattern.patternId}-${rootDir}`)
406
- .digest('hex')
407
- .slice(0, 16);
408
- const spread = new Set(aggPattern.locations.map((l) => l.file)).size;
409
- const confidenceScore = Math.min(0.95, aggPattern.confidence);
410
- const confidenceInfo = {
411
- frequency: Math.min(1, aggPattern.occurrences / 100),
412
- consistency: 0.9,
413
- age: 0,
414
- spread,
415
- score: confidenceScore,
416
- level: confidenceScore >= 0.85 ? 'high' : confidenceScore >= 0.65 ? 'medium' : confidenceScore >= 0.45 ? 'low' : 'uncertain',
417
- };
418
- const locations = aggPattern.locations.slice(0, 100).map((l) => ({
419
- file: l.file,
420
- line: l.line,
421
- column: l.column ?? 0,
422
- snippet: l.snippet,
423
- }));
424
- const pattern = {
425
- id,
426
- category: mapToPatternCategory(aggPattern.category),
427
- subcategory: aggPattern.subcategory,
428
- name: aggPattern.name,
429
- description: aggPattern.description,
430
- detector: { type: 'regex', config: { detectorId: aggPattern.detectorId, patternId: aggPattern.patternId } },
431
- confidence: confidenceInfo,
432
- locations,
433
- outliers: [],
434
- metadata: { firstSeen: now, lastSeen: now },
435
- severity: 'warning',
436
- autoFixable: false,
437
- status: 'discovered',
438
- };
439
- if (!store.has(pattern.id)) {
440
- store.add(pattern);
441
- }
442
- }
443
- await store.saveAll();
444
- const patternCount = scanResults.patterns.length;
445
- spinner.succeed(`Discovered ${chalk.cyan(patternCount)} patterns across ${chalk.cyan(Object.keys(categories).length)} categories`);
446
- // Show breakdown
447
- if (Object.keys(categories).length > 0) {
448
- console.log();
449
- const sorted = Object.entries(categories).sort((a, b) => b[1] - a[1]).slice(0, 6);
450
- for (const [cat, count] of sorted) {
451
- console.log(chalk.gray(` ${cat}: ${count} occurrences`));
452
- }
453
- if (Object.keys(categories).length > 6) {
454
- console.log(chalk.gray(` ... and ${Object.keys(categories).length - 6} more categories`));
455
- }
456
- }
457
- state.completed.push('scan');
458
- return { success: true, patternCount, categories };
459
- }
460
- catch (error) {
461
- spinner.fail('Scan failed');
462
- if (verbose)
463
- console.error(chalk.red(` ${error.message}`));
464
- return { success: false, patternCount: 0, categories: {} };
465
- }
466
- }
467
- async function phaseApproval(rootDir, autoYes, patternCount, state) {
468
- if (patternCount === 0) {
469
- return { approved: 0, threshold: 0.85 };
470
- }
471
- printPhase(3, 'Pattern Approval', 'Establish your coding standards');
472
- console.log(chalk.gray(' Patterns define your coding conventions.'));
473
- console.log(chalk.gray(' Approved patterns become your "golden standard".'));
474
- console.log();
475
- console.log(chalk.bold(' Why approve patterns?'));
476
- console.log(chalk.green(' → AI follows approved patterns when generating code'));
477
- console.log(chalk.green(' → Violations are flagged in CI/CD pipelines'));
478
- console.log(chalk.green(' → New code is checked against your standards'));
479
- console.log();
480
- const choice = autoYes ? 'auto-85' : await select({
481
- message: 'How would you like to handle pattern approval?',
482
- choices: [
483
- { value: 'auto-85', name: '✓ Auto-approve high confidence (≥85%) - Recommended' },
484
- { value: 'auto-90', name: '✓ Auto-approve very high confidence (≥90%) - Conservative' },
485
- { value: 'all', name: '✓ Approve all discovered patterns - Trust the scan' },
486
- { value: 'skip', name: '○ Skip - Review manually with `drift approve all`' },
487
- ],
488
- });
489
- if (choice === 'skip') {
490
- printSkip('Skipping approval. Review with `drift approve all` or `drift dashboard`.');
491
- state.choices.autoApprove = false;
492
- return { approved: 0, threshold: 0 };
493
- }
494
- const threshold = choice === 'auto-90' ? 0.90 : choice === 'auto-85' ? 0.85 : 0;
495
- state.choices.autoApprove = true;
496
- state.choices.approveThreshold = threshold;
497
- const spinner = createSpinner('Approving patterns...');
498
- spinner.start();
499
- try {
500
- const service = createCLIPatternService(rootDir);
501
- const discovered = await service.listByStatus('discovered', { limit: 5000 });
502
- const eligible = choice === 'all'
503
- ? discovered.items
504
- : discovered.items.filter(p => p.confidence >= threshold);
505
- let approved = 0;
506
- for (const pattern of eligible) {
507
- try {
508
- await service.approvePattern(pattern.id);
509
- approved++;
510
- }
511
- catch { /* skip */ }
512
- }
513
- spinner.succeed(`Approved ${chalk.cyan(approved)} patterns`);
514
- const remaining = discovered.items.length - approved;
515
- if (remaining > 0 && choice !== 'all') {
516
- printInfo(`${remaining} patterns below threshold - review with \`drift approve all\``);
517
- }
518
- state.completed.push('approval');
519
- return { approved, threshold };
520
- }
521
- catch (error) {
522
- spinner.fail('Approval failed');
523
- return { approved: 0, threshold };
524
- }
525
- }
526
- async function phaseDeepAnalysis(rootDir, autoYes, _verbose, state) {
527
- printPhase(4, 'Deep Analysis', 'Optional advanced features');
528
- console.log(chalk.gray(' These features provide deeper insights but take longer to build.'));
529
- console.log(chalk.gray(' Each can be run later with individual commands.'));
530
- console.log();
531
- // CALL GRAPH
532
- printFeature('📊', 'Call Graph Analysis', 'Maps function calls to understand code flow and data access.', 'Answer: "What data can this code access?" and "Who calls this?"');
533
- state.choices.buildCallGraph = autoYes || await confirm({
534
- message: 'Build call graph?',
535
- default: true,
536
- });
537
- if (state.choices.buildCallGraph) {
538
- const spinner = createSpinner('Building call graph...');
539
- spinner.start();
540
- try {
541
- if (isNativeAvailable()) {
542
- const ignorePatterns = await loadIgnorePatterns(rootDir);
543
- await buildCallGraph({ root: rootDir, patterns: ignorePatterns });
544
- }
545
- spinner.succeed('Call graph built');
546
- state.completed.push('callgraph');
547
- }
548
- catch (error) {
549
- spinner.fail(`Call graph failed: ${error.message}`);
550
- }
551
- }
552
- else {
553
- printSkip('Run `drift callgraph build` later');
554
- }
555
- // TEST TOPOLOGY
556
- printFeature('🧪', 'Test Topology', 'Maps tests to code, finds untested functions.', 'Answer: "Which tests cover this?" and "What\'s untested?"');
557
- state.choices.buildTestTopology = autoYes || await confirm({
558
- message: 'Build test topology?',
559
- default: true,
560
- });
561
- if (state.choices.buildTestTopology) {
562
- const spinner = createSpinner('Building test topology...');
563
- spinner.start();
564
- try {
565
- const dir = path.join(rootDir, DRIFT_DIR, 'test-topology');
566
- await fs.mkdir(dir, { recursive: true });
567
- await fs.writeFile(path.join(dir, 'summary.json'), JSON.stringify({ builtAt: new Date().toISOString(), status: 'initialized' }, null, 2));
568
- spinner.succeed('Test topology initialized');
569
- printInfo('Run `drift test-topology build` for full analysis');
570
- state.completed.push('test-topology');
571
- }
572
- catch (error) {
573
- spinner.fail(`Test topology failed: ${error.message}`);
574
- }
575
- }
576
- else {
577
- printSkip('Run `drift test-topology build` later');
578
- }
579
- // DATA BOUNDARIES
580
- printFeature('🔒', 'Data Boundaries', 'Tracks which code accesses which database tables.', 'Security analysis: "Who can access user.password?"');
581
- state.choices.buildBoundaries = autoYes ? false : await confirm({
582
- message: 'Build data boundaries?',
583
- default: false,
584
- });
585
- if (state.choices.buildBoundaries) {
586
- const spinner = createSpinner('Building data boundaries...');
587
- spinner.start();
588
- try {
589
- const dir = path.join(rootDir, DRIFT_DIR, 'boundaries');
590
- await fs.mkdir(dir, { recursive: true });
591
- await fs.writeFile(path.join(dir, 'access-map.json'), JSON.stringify({ builtAt: new Date().toISOString(), tables: {} }, null, 2));
592
- spinner.succeed('Data boundaries initialized');
593
- state.completed.push('boundaries');
594
- }
595
- catch (error) {
596
- spinner.fail(`Boundaries failed: ${error.message}`);
597
- }
598
- }
599
- else {
600
- printSkip('Run `drift boundaries build` later');
601
- }
602
- // MODULE COUPLING
603
- printFeature('🔗', 'Module Coupling', 'Analyzes dependencies, detects circular imports.', 'Find tightly coupled modules and dependency cycles');
604
- state.choices.buildCoupling = autoYes ? false : await confirm({
605
- message: 'Build coupling analysis?',
606
- default: false,
607
- });
608
- if (state.choices.buildCoupling) {
609
- const spinner = createSpinner('Analyzing coupling...');
610
- spinner.start();
611
- try {
612
- const dir = path.join(rootDir, DRIFT_DIR, 'module-coupling');
613
- await fs.mkdir(dir, { recursive: true });
614
- await fs.writeFile(path.join(dir, 'graph.json'), JSON.stringify({ builtAt: new Date().toISOString(), modules: [] }, null, 2));
615
- spinner.succeed('Coupling analysis initialized');
616
- printInfo('Run `drift coupling build` for full analysis');
617
- state.completed.push('coupling');
618
- }
619
- catch (error) {
620
- spinner.fail(`Coupling failed: ${error.message}`);
621
- }
622
- }
623
- else {
624
- printSkip('Run `drift coupling build` later');
625
- }
626
- // DNA PROFILE
627
- printFeature('🧬', 'Styling DNA', 'Analyzes frontend styling patterns (variants, spacing, theming).', 'AI generates components matching your exact style');
628
- state.choices.scanDna = autoYes ? false : await confirm({
629
- message: 'Scan styling DNA? (Best for frontend projects)',
630
- default: false,
631
- });
632
- if (state.choices.scanDna) {
633
- const spinner = createSpinner('Scanning DNA...');
634
- spinner.start();
635
- try {
636
- const dir = path.join(rootDir, DRIFT_DIR, 'dna');
637
- await fs.mkdir(dir, { recursive: true });
638
- await fs.writeFile(path.join(dir, 'profile.json'), JSON.stringify({ scannedAt: new Date().toISOString(), genes: {} }, null, 2));
639
- spinner.succeed('DNA profile created');
640
- printInfo('Run `drift dna scan` for full analysis');
641
- state.completed.push('dna');
642
- }
643
- catch (error) {
644
- spinner.fail(`DNA scan failed: ${error.message}`);
645
- }
646
- }
647
- else {
648
- printSkip('Run `drift dna scan` later');
649
- }
650
- }
651
- async function phaseMemory(rootDir, autoYes, state) {
652
- printPhase(5, 'Cortex Memory', 'Living knowledge system');
653
- console.log(chalk.gray(' Cortex Memory replaces static AGENTS.md/CLAUDE.md files.'));
654
- console.log(chalk.gray(' It\'s a living system that learns and adapts.'));
655
- console.log();
656
- printFeature('🧠', 'Memory Types', 'Tribal knowledge, procedures, corrections, and learned patterns.', 'AI retrieves relevant context based on what you\'re doing');
657
- console.log(chalk.gray(' Examples:'));
658
- console.log(chalk.gray(' • "Always use bcrypt for password hashing"'));
659
- console.log(chalk.gray(' • "Deploy process: 1. Run tests 2. Build 3. Push"'));
660
- console.log(chalk.gray(' • Corrections AI learns from your feedback'));
661
- console.log();
662
- state.choices.initMemory = autoYes ? false : await confirm({
663
- message: 'Initialize Cortex memory system?',
664
- default: false,
665
- });
666
- if (state.choices.initMemory) {
667
- const spinner = createSpinner('Initializing memory...');
668
- spinner.start();
669
- try {
670
- const dir = path.join(rootDir, DRIFT_DIR, 'memory');
671
- await fs.mkdir(dir, { recursive: true });
672
- await fs.writeFile(path.join(dir, 'memories.json'), JSON.stringify({
673
- version: '2.0.0',
674
- memories: [],
675
- metadata: { createdAt: new Date().toISOString() },
676
- }, null, 2));
677
- await fs.writeFile(path.join(dir, 'graph.json'), JSON.stringify({ nodes: [], edges: [] }, null, 2));
678
- spinner.succeed('Memory system initialized');
679
- printInfo('Add memories: `drift memory add tribal "your knowledge"`');
680
- state.completed.push('memory');
681
- }
682
- catch (error) {
683
- spinner.fail(`Memory init failed: ${error.message}`);
684
- }
685
- }
686
- else {
687
- printSkip('Run `drift memory init` later');
688
- }
689
- }
690
- async function phaseFinalize(rootDir, projectId, scanResult, approvalResult, state) {
691
- printPhase(6, 'Finalize', 'Creating Source of Truth');
692
- const spinner = createSpinner('Creating Source of Truth...');
693
- spinner.start();
694
- const now = new Date().toISOString();
695
- const scanId = crypto.randomUUID().slice(0, 8);
696
- const sot = {
697
- version: VERSION,
698
- schemaVersion: SCHEMA_VERSION,
699
- createdAt: now,
700
- updatedAt: now,
701
- project: {
702
- id: projectId,
703
- name: path.basename(rootDir),
704
- rootPath: rootDir,
705
- },
706
- baseline: {
707
- scanId,
708
- scannedAt: now,
709
- fileCount: await countSourceFiles(rootDir),
710
- patternCount: scanResult.patternCount,
711
- approvedCount: approvalResult.approved,
712
- categories: scanResult.categories,
713
- checksum: computeChecksum({
714
- patterns: scanResult.patternCount,
715
- categories: scanResult.categories,
716
- approved: approvalResult.approved,
717
- }),
718
- },
719
- features: {
720
- callGraph: { enabled: state.choices.buildCallGraph, builtAt: state.choices.buildCallGraph ? now : undefined },
721
- testTopology: { enabled: state.choices.buildTestTopology, builtAt: state.choices.buildTestTopology ? now : undefined },
722
- coupling: { enabled: state.choices.buildCoupling, builtAt: state.choices.buildCoupling ? now : undefined },
723
- dna: { enabled: state.choices.scanDna, scannedAt: state.choices.scanDna ? now : undefined },
724
- memory: { enabled: state.choices.initMemory, initializedAt: state.choices.initMemory ? now : undefined },
725
- boundaries: { enabled: state.choices.buildBoundaries, builtAt: state.choices.buildBoundaries ? now : undefined },
726
- },
727
- settings: {
728
- autoApproveThreshold: state.choices.approveThreshold,
729
- autoApproveEnabled: state.choices.autoApprove,
730
- },
731
- history: [
732
- {
733
- action: 'setup_complete',
734
- timestamp: now,
735
- details: `Initial setup: ${scanResult.patternCount} patterns, ${approvalResult.approved} approved`,
736
- },
737
- ],
738
- };
739
- await saveSourceOfTruth(rootDir, sot);
740
- await clearSetupState(rootDir);
741
- // Update manifest
742
- const manifestPath = path.join(rootDir, DRIFT_DIR, 'manifest.json');
743
- const manifest = {
744
- version: SCHEMA_VERSION,
745
- driftVersion: VERSION,
746
- lastUpdatedAt: now,
747
- sourceOfTruthId: scanId,
748
- };
749
- await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
750
- // Pre-compute views for fast access
751
- const viewsDir = path.join(rootDir, DRIFT_DIR, 'views');
752
- await fs.mkdir(viewsDir, { recursive: true });
753
- await fs.writeFile(path.join(viewsDir, 'status.json'), JSON.stringify({
754
- lastUpdated: now,
755
- patterns: {
756
- total: scanResult.patternCount,
757
- byStatus: { discovered: scanResult.patternCount - approvalResult.approved, approved: approvalResult.approved, ignored: 0 },
758
- byCategory: scanResult.categories,
759
- },
760
- }, null, 2));
761
- spinner.succeed('Source of Truth created');
762
- return sot;
763
- }
764
- function printSummary(sot, _state) {
765
- console.log();
766
- console.log(chalk.bold.green('╔════════════════════════════════════════════════════════════╗'));
767
- console.log(chalk.bold.green('║') + chalk.bold(' Setup Complete! 🎉 ') + chalk.bold.green('║'));
768
- console.log(chalk.bold.green('╚════════════════════════════════════════════════════════════╝'));
769
- console.log();
770
- console.log(chalk.bold(' Source of Truth Created'));
771
- console.log(chalk.gray(` ID: ${sot.baseline.scanId}`));
772
- console.log(chalk.gray(` Checksum: ${sot.baseline.checksum}`));
773
- console.log();
774
- console.log(chalk.bold(' What was configured:'));
775
- printSuccess(`Project: ${sot.project.name}`);
776
- if (sot.baseline.patternCount > 0) {
777
- printSuccess(`${sot.baseline.patternCount} patterns discovered`);
778
- }
779
- if (sot.baseline.approvedCount > 0) {
780
- printSuccess(`${sot.baseline.approvedCount} patterns approved`);
781
- }
782
- if (sot.features.callGraph.enabled)
783
- printSuccess('Call graph built');
784
- if (sot.features.testTopology.enabled)
785
- printSuccess('Test topology initialized');
786
- if (sot.features.boundaries.enabled)
787
- printSuccess('Data boundaries initialized');
788
- if (sot.features.coupling.enabled)
789
- printSuccess('Coupling analysis initialized');
790
- if (sot.features.dna.enabled)
791
- printSuccess('DNA profile created');
792
- if (sot.features.memory.enabled)
793
- printSuccess('Memory system initialized');
794
- console.log();
795
- console.log(chalk.bold(' What happens next:'));
796
- console.log(chalk.gray(' • All data is pre-computed for fast CLI/MCP access'));
797
- console.log(chalk.gray(' • Future scans are tracked against this baseline'));
798
- console.log(chalk.gray(' • Changes require explicit approval'));
799
- console.log(chalk.gray(' • Backups are created before destructive operations'));
800
- console.log();
801
- console.log(chalk.bold(' Quick commands:'));
802
- console.log(chalk.cyan(' drift status') + chalk.gray(' - See current state'));
803
- console.log(chalk.cyan(' drift dashboard') + chalk.gray(' - Visual pattern browser'));
804
- console.log(chalk.cyan(' drift check') + chalk.gray(' - Check for violations'));
805
- if (sot.baseline.approvedCount === 0 && sot.baseline.patternCount > 0) {
806
- console.log(chalk.cyan(' drift approve all') + chalk.gray(' - Review patterns'));
807
- }
808
- console.log();
809
- console.log(chalk.bold(' For AI integration:'));
810
- console.log(chalk.gray(' Install: ') + chalk.cyan('npm install -g driftdetect-mcp'));
811
- console.log(chalk.gray(' Then configure your AI tool (Claude, Cursor, Kiro)'));
812
- console.log();
813
- }
814
- // ═══════════════════════════════════════════════════════════════════════════
815
- // MAIN SETUP ACTION
816
- // ═══════════════════════════════════════════════════════════════════════════
817
- async function setupAction(options) {
818
- const rootDir = process.cwd();
819
- const verbose = options.verbose ?? false;
820
- const autoYes = options.yes ?? false;
821
- printWelcome();
822
- // Initialize state
823
- let state = {
824
- phase: 0,
825
- completed: [],
826
- choices: {
827
- autoApprove: false,
828
- approveThreshold: 0.85,
829
- buildCallGraph: false,
830
- buildTestTopology: false,
831
- buildCoupling: false,
832
- scanDna: false,
833
- initMemory: false,
834
- buildBoundaries: false,
835
- },
836
- startedAt: new Date().toISOString(),
837
- };
838
- // Check for resume
839
- if (options.resume) {
840
- const savedState = await loadSetupState(rootDir);
841
- if (savedState) {
842
- console.log(chalk.yellow(' Resuming previous setup...'));
843
- state = savedState;
844
- }
845
- }
846
- // Phase 0: Detect existing
847
- const { isNew, sot: existingSot, shouldContinue } = await phaseDetectExisting(rootDir, autoYes, verbose);
848
- if (!shouldContinue) {
849
- console.log(chalk.gray(' Setup cancelled.'));
850
- return;
851
- }
852
- // If using existing SOT, just show summary
853
- if (existingSot && !isNew) {
854
- printSummary(existingSot, state);
855
- return;
856
- }
857
- // Phase 1: Initialize
858
- const projectId = await phaseInitialize(rootDir, isNew, state);
859
- state.phase = 1;
860
- await saveSetupState(rootDir, state);
861
- // Phase 2: Scan
862
- const scanResult = await phaseScan(rootDir, autoYes, verbose, state);
863
- state.phase = 2;
864
- await saveSetupState(rootDir, state);
865
- // Phase 3: Approval
866
- const approvalResult = await phaseApproval(rootDir, autoYes, scanResult.patternCount, state);
867
- state.phase = 3;
868
- await saveSetupState(rootDir, state);
869
- // Phase 4: Deep Analysis
870
- await phaseDeepAnalysis(rootDir, autoYes, verbose, state);
871
- state.phase = 4;
872
- await saveSetupState(rootDir, state);
873
- // Phase 5: Memory
874
- await phaseMemory(rootDir, autoYes, state);
875
- state.phase = 5;
876
- await saveSetupState(rootDir, state);
877
- // Phase 6: Finalize
878
- const sot = await phaseFinalize(rootDir, projectId, scanResult, approvalResult, state);
879
- // Summary
880
- printSummary(sot, state);
881
- }
882
- export const setupCommand = new Command('setup')
883
- .description('Guided setup wizard - create your codebase Source of Truth')
884
- .option('-y, --yes', 'Skip prompts and use recommended defaults')
885
- .option('--verbose', 'Enable verbose output')
886
- .option('--resume', 'Resume interrupted setup')
887
- .action(setupAction);
6
+ export { setupCommand } from './setup/index.js';
888
7
  //# sourceMappingURL=setup.js.map