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.
- package/dist/commands/setup/index.d.ts +17 -0
- package/dist/commands/setup/index.d.ts.map +1 -0
- package/dist/commands/setup/index.js +592 -0
- package/dist/commands/setup/index.js.map +1 -0
- package/dist/commands/setup/runners/base.d.ts +34 -0
- package/dist/commands/setup/runners/base.d.ts.map +1 -0
- package/dist/commands/setup/runners/base.js +20 -0
- package/dist/commands/setup/runners/base.js.map +1 -0
- package/dist/commands/setup/runners/callgraph.d.ts +17 -0
- package/dist/commands/setup/runners/callgraph.d.ts.map +1 -0
- package/dist/commands/setup/runners/callgraph.js +91 -0
- package/dist/commands/setup/runners/callgraph.js.map +1 -0
- package/dist/commands/setup/runners/coupling.d.ts +20 -0
- package/dist/commands/setup/runners/coupling.d.ts.map +1 -0
- package/dist/commands/setup/runners/coupling.js +121 -0
- package/dist/commands/setup/runners/coupling.js.map +1 -0
- package/dist/commands/setup/runners/dna.d.ts +17 -0
- package/dist/commands/setup/runners/dna.d.ts.map +1 -0
- package/dist/commands/setup/runners/dna.js +72 -0
- package/dist/commands/setup/runners/dna.js.map +1 -0
- package/dist/commands/setup/runners/index.d.ts +12 -0
- package/dist/commands/setup/runners/index.d.ts.map +1 -0
- package/dist/commands/setup/runners/index.js +12 -0
- package/dist/commands/setup/runners/index.js.map +1 -0
- package/dist/commands/setup/runners/memory.d.ts +17 -0
- package/dist/commands/setup/runners/memory.d.ts.map +1 -0
- package/dist/commands/setup/runners/memory.js +71 -0
- package/dist/commands/setup/runners/memory.js.map +1 -0
- package/dist/commands/setup/runners/test-topology.d.ts +20 -0
- package/dist/commands/setup/runners/test-topology.d.ts.map +1 -0
- package/dist/commands/setup/runners/test-topology.js +137 -0
- package/dist/commands/setup/runners/test-topology.js.map +1 -0
- package/dist/commands/setup/types.d.ts +99 -0
- package/dist/commands/setup/types.d.ts.map +1 -0
- package/dist/commands/setup/types.js +42 -0
- package/dist/commands/setup/types.js.map +1 -0
- package/dist/commands/setup/ui.d.ts +16 -0
- package/dist/commands/setup/ui.d.ts.map +1 -0
- package/dist/commands/setup/ui.js +108 -0
- package/dist/commands/setup/ui.js.map +1 -0
- package/dist/commands/setup/utils.d.ts +20 -0
- package/dist/commands/setup/utils.d.ts.map +1 -0
- package/dist/commands/setup/utils.js +178 -0
- package/dist/commands/setup/utils.js.map +1 -0
- package/dist/commands/setup.d.ts +2 -18
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +2 -883
- package/dist/commands/setup.js.map +1 -1
- package/package.json +5 -5
package/dist/commands/setup.js
CHANGED
|
@@ -1,888 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Setup Command -
|
|
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
|
-
|
|
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
|