@vibedrift/cli 0.5.4 → 0.5.5
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/README.md +6 -3
- package/dist/index.js +361 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -41,12 +41,15 @@ vibedrift . --deep # uses one of your 3 monthly deep scans
|
|
|
41
41
|
- **Dependency health** — phantom deps, missing deps, undocumented env vars
|
|
42
42
|
- **Code quality** — cyclomatic complexity, dead code, TODO density, long functions, unclear naming
|
|
43
43
|
|
|
44
|
-
### Layer 1.5 — Cross-File Drift Detection (
|
|
45
|
-
- **Architectural consistency** —
|
|
44
|
+
### Layer 1.5 — Cross-File Drift Detection (8 detectors)
|
|
45
|
+
- **Architectural consistency** — error handling, data access, DI, and config patterns across all source files
|
|
46
46
|
- **Convention oscillation** — naming conventions, file naming across the whole project
|
|
47
47
|
- **Security consistency** — auth middleware, input validation, rate limiting across routes
|
|
48
48
|
- **Semantic duplication** — functions that do the same thing under different names in different files
|
|
49
|
-
- **Phantom scaffolding** — CRUD endpoints
|
|
49
|
+
- **Phantom scaffolding** — exported types and CRUD endpoints that are never used
|
|
50
|
+
- **Import style** — relative paths vs path aliases, default vs named imports
|
|
51
|
+
- **Export style** — default exports vs named-only exports per file
|
|
52
|
+
- **Async patterns** — async/await vs .then() chains consistency
|
|
50
53
|
|
|
51
54
|
### Layer 1.7 — Code DNA Engine (5 modules, runs locally)
|
|
52
55
|
- **Semantic fingerprinting** — normalizes function bodies and hashes them. Identical hashes = exact semantic duplicates at confidence 1.0
|
package/dist/index.js
CHANGED
|
@@ -5178,11 +5178,14 @@ init_esm_shims();
|
|
|
5178
5178
|
// src/drift/types.ts
|
|
5179
5179
|
init_esm_shims();
|
|
5180
5180
|
var DRIFT_WEIGHTS = {
|
|
5181
|
-
architectural_consistency:
|
|
5182
|
-
security_posture:
|
|
5183
|
-
semantic_duplication:
|
|
5184
|
-
naming_conventions:
|
|
5185
|
-
phantom_scaffolding:
|
|
5181
|
+
architectural_consistency: 16,
|
|
5182
|
+
security_posture: 14,
|
|
5183
|
+
semantic_duplication: 14,
|
|
5184
|
+
naming_conventions: 12,
|
|
5185
|
+
phantom_scaffolding: 12,
|
|
5186
|
+
import_style: 12,
|
|
5187
|
+
export_style: 10,
|
|
5188
|
+
async_patterns: 10
|
|
5186
5189
|
};
|
|
5187
5190
|
|
|
5188
5191
|
// src/drift/architectural-contradiction.ts
|
|
@@ -5200,9 +5203,6 @@ function extractEvidence(content, pattern, maxResults = 3) {
|
|
|
5200
5203
|
}
|
|
5201
5204
|
return evidence;
|
|
5202
5205
|
}
|
|
5203
|
-
function isHandlerOrController(path2) {
|
|
5204
|
-
return /(?:handler|controller|route|endpoint|view|api|resource|page|action)/i.test(path2) && !/(?:repository|repo|store|dal|model|query|migration|test|spec|mock)/i.test(path2);
|
|
5205
|
-
}
|
|
5206
5206
|
function detectDataAccess(file) {
|
|
5207
5207
|
const results = [];
|
|
5208
5208
|
const c = file.content;
|
|
@@ -5220,7 +5220,7 @@ function detectDataAccess(file) {
|
|
|
5220
5220
|
results.push({ pattern: "direct_db", evidence: dbEvidence });
|
|
5221
5221
|
}
|
|
5222
5222
|
const httpEvidence = extractEvidence(c, /\b(?:fetch|axios|http\.(?:Get|Post|Put)|requests\.(?:get|post))\s*\(/g);
|
|
5223
|
-
if (httpEvidence.length > 0
|
|
5223
|
+
if (httpEvidence.length > 0) {
|
|
5224
5224
|
results.push({ pattern: "http_client", evidence: httpEvidence });
|
|
5225
5225
|
}
|
|
5226
5226
|
return results;
|
|
@@ -5280,13 +5280,19 @@ function detectDIPattern(file) {
|
|
|
5280
5280
|
}
|
|
5281
5281
|
return results;
|
|
5282
5282
|
}
|
|
5283
|
+
function isAnalyzableSource(path2) {
|
|
5284
|
+
if (/(?:test|spec|mock|fixture|__test__|__mocks__|\.test\.|\.spec\.)/i.test(path2)) return false;
|
|
5285
|
+
if (/(?:\.config\.|\.d\.ts$|node_modules|dist\/|build\/)/i.test(path2)) return false;
|
|
5286
|
+
return true;
|
|
5287
|
+
}
|
|
5283
5288
|
function buildProfile(file) {
|
|
5284
|
-
if (!file.language
|
|
5289
|
+
if (!file.language) return null;
|
|
5290
|
+
if (!isAnalyzableSource(file.path)) return null;
|
|
5285
5291
|
const dataAccess = detectDataAccess(file);
|
|
5286
5292
|
const errorHandling = detectErrorHandling(file);
|
|
5287
5293
|
const config = detectConfigPattern(file);
|
|
5288
5294
|
const di = detectDIPattern(file);
|
|
5289
|
-
if (dataAccess.length === 0 && errorHandling.length === 0) return null;
|
|
5295
|
+
if (dataAccess.length === 0 && errorHandling.length === 0 && config.length === 0) return null;
|
|
5290
5296
|
return { file: file.path, language: file.language, dataAccess, errorHandling, config, di };
|
|
5291
5297
|
}
|
|
5292
5298
|
function detectFilePattern(patterns) {
|
|
@@ -6285,6 +6291,341 @@ var phantomScaffolding = {
|
|
|
6285
6291
|
}
|
|
6286
6292
|
};
|
|
6287
6293
|
|
|
6294
|
+
// src/drift/import-consistency.ts
|
|
6295
|
+
init_esm_shims();
|
|
6296
|
+
function isSourceFile(path2) {
|
|
6297
|
+
if (/(?:test|spec|mock|fixture|__test__|__mocks__|\.test\.|\.spec\.)/i.test(path2)) return false;
|
|
6298
|
+
if (/(?:\.config\.|\.d\.ts$|node_modules|dist\/|build\/)/i.test(path2)) return false;
|
|
6299
|
+
return true;
|
|
6300
|
+
}
|
|
6301
|
+
function analyzeImports(file) {
|
|
6302
|
+
if (!file.language || !["javascript", "typescript"].includes(file.language)) return null;
|
|
6303
|
+
if (!isSourceFile(file.path)) return null;
|
|
6304
|
+
const lines = file.content.split("\n");
|
|
6305
|
+
let relativeCount = 0;
|
|
6306
|
+
let aliasCount = 0;
|
|
6307
|
+
let defaultCount = 0;
|
|
6308
|
+
let namedCount = 0;
|
|
6309
|
+
const evidence = [];
|
|
6310
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6311
|
+
const line = lines[i].trim();
|
|
6312
|
+
const importMatch = line.match(/^import\s+(?!type\s)/);
|
|
6313
|
+
if (!importMatch) continue;
|
|
6314
|
+
const fromMatch = line.match(/from\s+["']([^"']+)["']/);
|
|
6315
|
+
if (!fromMatch) continue;
|
|
6316
|
+
const importPath = fromMatch[1];
|
|
6317
|
+
if (!importPath.startsWith(".") && !importPath.startsWith("@/") && !importPath.startsWith("~/")) continue;
|
|
6318
|
+
if (importPath.startsWith("./") || importPath.startsWith("../")) {
|
|
6319
|
+
relativeCount++;
|
|
6320
|
+
} else if (importPath.startsWith("@/") || importPath.startsWith("~/")) {
|
|
6321
|
+
aliasCount++;
|
|
6322
|
+
}
|
|
6323
|
+
if (/^import\s+\{/.test(line)) {
|
|
6324
|
+
namedCount++;
|
|
6325
|
+
} else if (/^import\s+\w+\s+from/.test(line) || /^import\s+\w+,\s*\{/.test(line)) {
|
|
6326
|
+
defaultCount++;
|
|
6327
|
+
}
|
|
6328
|
+
if (evidence.length < 3) {
|
|
6329
|
+
evidence.push({ line: i + 1, code: line });
|
|
6330
|
+
}
|
|
6331
|
+
}
|
|
6332
|
+
const totalLocalImports = relativeCount + aliasCount;
|
|
6333
|
+
if (totalLocalImports < 3) return null;
|
|
6334
|
+
const pathStyle = aliasCount === 0 ? "relative" : relativeCount === 0 ? "alias" : relativeCount >= aliasCount ? "relative" : "alias";
|
|
6335
|
+
const totalBindings = defaultCount + namedCount;
|
|
6336
|
+
let bindingStyle = null;
|
|
6337
|
+
if (totalBindings >= 3) {
|
|
6338
|
+
const defaultRatio = defaultCount / totalBindings;
|
|
6339
|
+
bindingStyle = defaultRatio > 0.6 ? "default_heavy" : defaultRatio < 0.4 ? "named_heavy" : "mixed";
|
|
6340
|
+
}
|
|
6341
|
+
return {
|
|
6342
|
+
file: file.path,
|
|
6343
|
+
pathStyle,
|
|
6344
|
+
bindingStyle,
|
|
6345
|
+
relativeCount,
|
|
6346
|
+
aliasCount,
|
|
6347
|
+
defaultCount,
|
|
6348
|
+
namedCount,
|
|
6349
|
+
evidence
|
|
6350
|
+
};
|
|
6351
|
+
}
|
|
6352
|
+
var PATH_STYLE_NAMES = {
|
|
6353
|
+
relative: "relative paths (./)",
|
|
6354
|
+
alias: "path aliases (@/)"
|
|
6355
|
+
};
|
|
6356
|
+
function buildFinding(profiles, getStyle, styleNames, subCategory, label) {
|
|
6357
|
+
const withStyle = profiles.filter((p) => getStyle(p) !== null);
|
|
6358
|
+
if (withStyle.length < 5) return null;
|
|
6359
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6360
|
+
for (const p of withStyle) {
|
|
6361
|
+
const style = getStyle(p);
|
|
6362
|
+
if (!counts.has(style)) counts.set(style, { count: 0, files: [] });
|
|
6363
|
+
const entry = counts.get(style);
|
|
6364
|
+
entry.count++;
|
|
6365
|
+
entry.files.push(p.file);
|
|
6366
|
+
}
|
|
6367
|
+
if (counts.size < 2) return null;
|
|
6368
|
+
let dominant = null;
|
|
6369
|
+
let dominantCount = 0;
|
|
6370
|
+
for (const [style, data] of counts) {
|
|
6371
|
+
if (data.count > dominantCount) {
|
|
6372
|
+
dominantCount = data.count;
|
|
6373
|
+
dominant = style;
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
if (!dominant) return null;
|
|
6377
|
+
const totalFiles = withStyle.length;
|
|
6378
|
+
const consistencyScore = Math.round(dominantCount / totalFiles * 100);
|
|
6379
|
+
if (consistencyScore === 100) return null;
|
|
6380
|
+
const deviating = [];
|
|
6381
|
+
for (const [style, data] of counts) {
|
|
6382
|
+
if (style === dominant) continue;
|
|
6383
|
+
for (const filePath of data.files) {
|
|
6384
|
+
const profile = withStyle.find((p) => p.file === filePath);
|
|
6385
|
+
deviating.push({
|
|
6386
|
+
path: filePath,
|
|
6387
|
+
detectedPattern: styleNames[style] ?? style,
|
|
6388
|
+
evidence: profile?.evidence.slice(0, 2) ?? []
|
|
6389
|
+
});
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
return {
|
|
6393
|
+
detector: "import_style",
|
|
6394
|
+
subCategory,
|
|
6395
|
+
driftCategory: "import_style",
|
|
6396
|
+
severity: deviating.length >= 5 ? "warning" : "info",
|
|
6397
|
+
confidence: 0.8,
|
|
6398
|
+
finding: `${label}: ${dominantCount} files use ${styleNames[dominant] ?? dominant}, ${deviating.length} deviate`,
|
|
6399
|
+
dominantPattern: styleNames[dominant] ?? dominant,
|
|
6400
|
+
dominantCount,
|
|
6401
|
+
totalRelevantFiles: totalFiles,
|
|
6402
|
+
consistencyScore,
|
|
6403
|
+
deviatingFiles: deviating,
|
|
6404
|
+
recommendation: `Standardize on ${styleNames[dominant] ?? dominant} across all files for consistency.`
|
|
6405
|
+
};
|
|
6406
|
+
}
|
|
6407
|
+
var importConsistency = {
|
|
6408
|
+
id: "import-consistency",
|
|
6409
|
+
name: "Import Style Consistency",
|
|
6410
|
+
category: "import_style",
|
|
6411
|
+
detect(ctx) {
|
|
6412
|
+
const findings = [];
|
|
6413
|
+
const profiles = [];
|
|
6414
|
+
for (const file of ctx.files) {
|
|
6415
|
+
const p = analyzeImports(file);
|
|
6416
|
+
if (p) profiles.push(p);
|
|
6417
|
+
}
|
|
6418
|
+
if (profiles.length < 5) return findings;
|
|
6419
|
+
const pathFinding = buildFinding(
|
|
6420
|
+
profiles,
|
|
6421
|
+
(p) => p.pathStyle,
|
|
6422
|
+
PATH_STYLE_NAMES,
|
|
6423
|
+
"path_style",
|
|
6424
|
+
"Import path style"
|
|
6425
|
+
);
|
|
6426
|
+
if (pathFinding) findings.push(pathFinding);
|
|
6427
|
+
return findings;
|
|
6428
|
+
}
|
|
6429
|
+
};
|
|
6430
|
+
|
|
6431
|
+
// src/drift/export-consistency.ts
|
|
6432
|
+
init_esm_shims();
|
|
6433
|
+
function isSourceFile2(path2) {
|
|
6434
|
+
if (/(?:test|spec|mock|fixture|__test__|__mocks__|\.test\.|\.spec\.)/i.test(path2)) return false;
|
|
6435
|
+
if (/(?:\.config\.|\.d\.ts$|node_modules|dist\/|build\/|index\.)/i.test(path2)) return false;
|
|
6436
|
+
return true;
|
|
6437
|
+
}
|
|
6438
|
+
function analyzeExports(file) {
|
|
6439
|
+
if (!file.language || !["javascript", "typescript"].includes(file.language)) return null;
|
|
6440
|
+
if (!isSourceFile2(file.path)) return null;
|
|
6441
|
+
const lines = file.content.split("\n");
|
|
6442
|
+
let hasDefaultExport = false;
|
|
6443
|
+
let hasNamedExport = false;
|
|
6444
|
+
const evidence = [];
|
|
6445
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6446
|
+
const line = lines[i].trim();
|
|
6447
|
+
if (/^export\s+default\b/.test(line) || /\bmodule\.exports\s*=/.test(line)) {
|
|
6448
|
+
hasDefaultExport = true;
|
|
6449
|
+
if (evidence.length < 2) evidence.push({ line: i + 1, code: line.slice(0, 100) });
|
|
6450
|
+
} else if (/^export\s+(?:function|class|const|let|var|interface|type|enum|abstract)\b/.test(line)) {
|
|
6451
|
+
hasNamedExport = true;
|
|
6452
|
+
if (evidence.length < 2) evidence.push({ line: i + 1, code: line.slice(0, 100) });
|
|
6453
|
+
} else if (/^export\s*\{/.test(line)) {
|
|
6454
|
+
hasNamedExport = true;
|
|
6455
|
+
if (evidence.length < 2) evidence.push({ line: i + 1, code: line.slice(0, 100) });
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
if (!hasDefaultExport && !hasNamedExport) return null;
|
|
6459
|
+
const style = hasDefaultExport ? "default_export" : "named_only";
|
|
6460
|
+
return { file: file.path, style, evidence };
|
|
6461
|
+
}
|
|
6462
|
+
var STYLE_NAMES = {
|
|
6463
|
+
default_export: "default exports",
|
|
6464
|
+
named_only: "named exports only"
|
|
6465
|
+
};
|
|
6466
|
+
var exportConsistency = {
|
|
6467
|
+
id: "export-consistency",
|
|
6468
|
+
name: "Export Style Consistency",
|
|
6469
|
+
category: "export_style",
|
|
6470
|
+
detect(ctx) {
|
|
6471
|
+
const profiles = [];
|
|
6472
|
+
for (const file of ctx.files) {
|
|
6473
|
+
const p = analyzeExports(file);
|
|
6474
|
+
if (p) profiles.push(p);
|
|
6475
|
+
}
|
|
6476
|
+
if (profiles.length < 5) return [];
|
|
6477
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6478
|
+
for (const p of profiles) {
|
|
6479
|
+
if (!counts.has(p.style)) counts.set(p.style, { count: 0, files: [] });
|
|
6480
|
+
const entry = counts.get(p.style);
|
|
6481
|
+
entry.count++;
|
|
6482
|
+
entry.files.push(p.file);
|
|
6483
|
+
}
|
|
6484
|
+
if (counts.size < 2) return [];
|
|
6485
|
+
let dominant = null;
|
|
6486
|
+
let dominantCount = 0;
|
|
6487
|
+
for (const [style, data] of counts) {
|
|
6488
|
+
if (data.count > dominantCount) {
|
|
6489
|
+
dominantCount = data.count;
|
|
6490
|
+
dominant = style;
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
if (!dominant) return [];
|
|
6494
|
+
const totalFiles = profiles.length;
|
|
6495
|
+
const consistencyScore = Math.round(dominantCount / totalFiles * 100);
|
|
6496
|
+
if (consistencyScore === 100) return [];
|
|
6497
|
+
const deviating = [];
|
|
6498
|
+
for (const [style, data] of counts) {
|
|
6499
|
+
if (style === dominant) continue;
|
|
6500
|
+
for (const filePath of data.files) {
|
|
6501
|
+
const profile = profiles.find((p) => p.file === filePath);
|
|
6502
|
+
deviating.push({
|
|
6503
|
+
path: filePath,
|
|
6504
|
+
detectedPattern: STYLE_NAMES[style],
|
|
6505
|
+
evidence: profile?.evidence.slice(0, 2) ?? []
|
|
6506
|
+
});
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6509
|
+
return [{
|
|
6510
|
+
detector: "export_style",
|
|
6511
|
+
subCategory: "export_style",
|
|
6512
|
+
driftCategory: "export_style",
|
|
6513
|
+
severity: deviating.length >= 5 ? "warning" : "info",
|
|
6514
|
+
confidence: 0.75,
|
|
6515
|
+
finding: `Export style: ${dominantCount} files use ${STYLE_NAMES[dominant]}, ${deviating.length} use ${STYLE_NAMES[dominant === "default_export" ? "named_only" : "default_export"]}`,
|
|
6516
|
+
dominantPattern: STYLE_NAMES[dominant],
|
|
6517
|
+
dominantCount,
|
|
6518
|
+
totalRelevantFiles: totalFiles,
|
|
6519
|
+
consistencyScore,
|
|
6520
|
+
deviatingFiles: deviating,
|
|
6521
|
+
recommendation: `Standardize on ${STYLE_NAMES[dominant]}. ${dominant === "named_only" ? "Named exports enable tree-shaking and explicit imports." : "Default exports simplify imports but disable tree-shaking."}`
|
|
6522
|
+
}];
|
|
6523
|
+
}
|
|
6524
|
+
};
|
|
6525
|
+
|
|
6526
|
+
// src/drift/async-consistency.ts
|
|
6527
|
+
init_esm_shims();
|
|
6528
|
+
function isSourceFile3(path2) {
|
|
6529
|
+
if (/(?:test|spec|mock|fixture|__test__|__mocks__|\.test\.|\.spec\.)/i.test(path2)) return false;
|
|
6530
|
+
if (/(?:\.config\.|\.d\.ts$|node_modules|dist\/|build\/)/i.test(path2)) return false;
|
|
6531
|
+
return true;
|
|
6532
|
+
}
|
|
6533
|
+
function analyzeAsync(file) {
|
|
6534
|
+
if (!file.language || !["javascript", "typescript"].includes(file.language)) return null;
|
|
6535
|
+
if (!isSourceFile3(file.path)) return null;
|
|
6536
|
+
const lines = file.content.split("\n");
|
|
6537
|
+
let awaitCount = 0;
|
|
6538
|
+
let thenCount = 0;
|
|
6539
|
+
const evidence = [];
|
|
6540
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6541
|
+
const line = lines[i];
|
|
6542
|
+
if (/\bawait\s+/.test(line) && !line.trim().startsWith("//") && !line.trim().startsWith("*")) {
|
|
6543
|
+
awaitCount++;
|
|
6544
|
+
if (evidence.length < 3) {
|
|
6545
|
+
evidence.push({ line: i + 1, code: line.trim().slice(0, 100) });
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
if (/\.\s*then\s*\(/.test(line) && !line.trim().startsWith("//") && !line.trim().startsWith("*") && !/type\s|interface\s/.test(line)) {
|
|
6549
|
+
thenCount++;
|
|
6550
|
+
if (evidence.length < 3) {
|
|
6551
|
+
evidence.push({ line: i + 1, code: line.trim().slice(0, 100) });
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
const total = awaitCount + thenCount;
|
|
6556
|
+
if (total < 2) return null;
|
|
6557
|
+
let style;
|
|
6558
|
+
const awaitRatio = awaitCount / total;
|
|
6559
|
+
if (awaitRatio > 0.7) style = "async_await";
|
|
6560
|
+
else if (awaitRatio < 0.3) style = "then_chains";
|
|
6561
|
+
else style = "mixed";
|
|
6562
|
+
return { file: file.path, style, awaitCount, thenCount, evidence };
|
|
6563
|
+
}
|
|
6564
|
+
var STYLE_NAMES2 = {
|
|
6565
|
+
async_await: "async/await",
|
|
6566
|
+
then_chains: ".then() chains",
|
|
6567
|
+
mixed: "mixed async patterns"
|
|
6568
|
+
};
|
|
6569
|
+
var asyncConsistency = {
|
|
6570
|
+
id: "async-consistency",
|
|
6571
|
+
name: "Async Pattern Consistency",
|
|
6572
|
+
category: "async_patterns",
|
|
6573
|
+
detect(ctx) {
|
|
6574
|
+
const profiles = [];
|
|
6575
|
+
for (const file of ctx.files) {
|
|
6576
|
+
const p = analyzeAsync(file);
|
|
6577
|
+
if (p) profiles.push(p);
|
|
6578
|
+
}
|
|
6579
|
+
if (profiles.length < 5) return [];
|
|
6580
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6581
|
+
for (const p of profiles) {
|
|
6582
|
+
if (!counts.has(p.style)) counts.set(p.style, { count: 0, files: [] });
|
|
6583
|
+
const entry = counts.get(p.style);
|
|
6584
|
+
entry.count++;
|
|
6585
|
+
entry.files.push(p.file);
|
|
6586
|
+
}
|
|
6587
|
+
if (counts.size < 2) return [];
|
|
6588
|
+
let dominant = null;
|
|
6589
|
+
let dominantCount = 0;
|
|
6590
|
+
for (const [style, data] of counts) {
|
|
6591
|
+
if (data.count > dominantCount) {
|
|
6592
|
+
dominantCount = data.count;
|
|
6593
|
+
dominant = style;
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
if (!dominant) return [];
|
|
6597
|
+
const totalFiles = profiles.length;
|
|
6598
|
+
const consistencyScore = Math.round(dominantCount / totalFiles * 100);
|
|
6599
|
+
if (consistencyScore === 100) return [];
|
|
6600
|
+
const deviating = [];
|
|
6601
|
+
for (const [style, data] of counts) {
|
|
6602
|
+
if (style === dominant) continue;
|
|
6603
|
+
for (const filePath of data.files) {
|
|
6604
|
+
const profile = profiles.find((p) => p.file === filePath);
|
|
6605
|
+
deviating.push({
|
|
6606
|
+
path: filePath,
|
|
6607
|
+
detectedPattern: STYLE_NAMES2[style],
|
|
6608
|
+
evidence: profile?.evidence.slice(0, 2) ?? []
|
|
6609
|
+
});
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
return [{
|
|
6613
|
+
detector: "async_patterns",
|
|
6614
|
+
subCategory: "async_style",
|
|
6615
|
+
driftCategory: "async_patterns",
|
|
6616
|
+
severity: deviating.length >= 5 ? "warning" : "info",
|
|
6617
|
+
confidence: 0.8,
|
|
6618
|
+
finding: `Async style: ${dominantCount} files use ${STYLE_NAMES2[dominant]}, ${deviating.length} use different patterns`,
|
|
6619
|
+
dominantPattern: STYLE_NAMES2[dominant],
|
|
6620
|
+
dominantCount,
|
|
6621
|
+
totalRelevantFiles: totalFiles,
|
|
6622
|
+
consistencyScore,
|
|
6623
|
+
deviatingFiles: deviating,
|
|
6624
|
+
recommendation: `Standardize on ${STYLE_NAMES2[dominant]}. ${dominant === "async_await" ? "async/await is more readable and has clearer error handling with try/catch." : "Consider migrating to async/await for consistency and readability."}`
|
|
6625
|
+
}];
|
|
6626
|
+
}
|
|
6627
|
+
};
|
|
6628
|
+
|
|
6288
6629
|
// src/drift/index.ts
|
|
6289
6630
|
function createDriftDetectors() {
|
|
6290
6631
|
return [
|
|
@@ -6292,7 +6633,10 @@ function createDriftDetectors() {
|
|
|
6292
6633
|
conventionOscillation,
|
|
6293
6634
|
securityConsistency,
|
|
6294
6635
|
semanticDuplication,
|
|
6295
|
-
phantomScaffolding
|
|
6636
|
+
phantomScaffolding,
|
|
6637
|
+
importConsistency,
|
|
6638
|
+
exportConsistency,
|
|
6639
|
+
asyncConsistency
|
|
6296
6640
|
];
|
|
6297
6641
|
}
|
|
6298
6642
|
function buildDriftContext(ctx) {
|
|
@@ -6325,7 +6669,10 @@ function computeDriftScores(findings) {
|
|
|
6325
6669
|
"security_posture",
|
|
6326
6670
|
"semantic_duplication",
|
|
6327
6671
|
"naming_conventions",
|
|
6328
|
-
"phantom_scaffolding"
|
|
6672
|
+
"phantom_scaffolding",
|
|
6673
|
+
"import_style",
|
|
6674
|
+
"export_style",
|
|
6675
|
+
"async_patterns"
|
|
6329
6676
|
];
|
|
6330
6677
|
const scores = {};
|
|
6331
6678
|
for (const cat of categories) {
|
|
@@ -7246,7 +7593,7 @@ function buildRadarSection(result) {
|
|
|
7246
7593
|
return `<section class="section">
|
|
7247
7594
|
<div class="label">DRIFT FINGERPRINT</div>
|
|
7248
7595
|
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:20px;line-height:1.6">
|
|
7249
|
-
This radar shows how well your codebase maintains consistency across
|
|
7596
|
+
This radar shows how well your codebase maintains consistency across 8 drift categories.
|
|
7250
7597
|
The outer edge represents a perfect score in each category. Points closer to the center indicate more drift.
|
|
7251
7598
|
A balanced shape means consistent quality; an uneven shape reveals where drift concentrates.
|
|
7252
7599
|
</p>
|