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