@vibedrift/cli 0.2.0 → 0.3.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/index.js +337 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1199,6 +1199,95 @@ var init_confidence = __esm({
|
|
|
1199
1199
|
}
|
|
1200
1200
|
});
|
|
1201
1201
|
|
|
1202
|
+
// src/ml-client/project-name.ts
|
|
1203
|
+
var project_name_exports = {};
|
|
1204
|
+
__export(project_name_exports, {
|
|
1205
|
+
detectProjectIdentity: () => detectProjectIdentity
|
|
1206
|
+
});
|
|
1207
|
+
import { basename, join as join6 } from "path";
|
|
1208
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1209
|
+
import { createHash as createHash2 } from "crypto";
|
|
1210
|
+
async function detectProjectIdentity(rootDir, override) {
|
|
1211
|
+
const hash = createHash2("sha256").update(rootDir).digest("hex");
|
|
1212
|
+
if (override && override.trim()) {
|
|
1213
|
+
return { name: override.trim(), hash };
|
|
1214
|
+
}
|
|
1215
|
+
const fromPackageJson = await readJsonField(
|
|
1216
|
+
join6(rootDir, "package.json"),
|
|
1217
|
+
"name"
|
|
1218
|
+
);
|
|
1219
|
+
if (fromPackageJson) return { name: fromPackageJson, hash };
|
|
1220
|
+
const fromCargo = await readTomlFieldInSection(
|
|
1221
|
+
join6(rootDir, "Cargo.toml"),
|
|
1222
|
+
"package",
|
|
1223
|
+
"name"
|
|
1224
|
+
);
|
|
1225
|
+
if (fromCargo) return { name: fromCargo, hash };
|
|
1226
|
+
const fromGoMod = await readGoModule(join6(rootDir, "go.mod"));
|
|
1227
|
+
if (fromGoMod) return { name: fromGoMod, hash };
|
|
1228
|
+
const fromPyProject = await readTomlFieldInSection(
|
|
1229
|
+
join6(rootDir, "pyproject.toml"),
|
|
1230
|
+
"project",
|
|
1231
|
+
"name"
|
|
1232
|
+
) ?? await readTomlFieldInSection(
|
|
1233
|
+
join6(rootDir, "pyproject.toml"),
|
|
1234
|
+
"tool.poetry",
|
|
1235
|
+
"name"
|
|
1236
|
+
);
|
|
1237
|
+
if (fromPyProject) return { name: fromPyProject, hash };
|
|
1238
|
+
return { name: basename(rootDir) || "untitled", hash };
|
|
1239
|
+
}
|
|
1240
|
+
async function readJsonField(path2, field) {
|
|
1241
|
+
try {
|
|
1242
|
+
const raw = await readFile5(path2, "utf-8");
|
|
1243
|
+
const parsed = JSON.parse(raw);
|
|
1244
|
+
const value = parsed[field];
|
|
1245
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
1246
|
+
} catch {
|
|
1247
|
+
}
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
async function readTomlFieldInSection(path2, section, key) {
|
|
1251
|
+
try {
|
|
1252
|
+
const raw = await readFile5(path2, "utf-8");
|
|
1253
|
+
const lines = raw.split("\n");
|
|
1254
|
+
let inSection = false;
|
|
1255
|
+
for (const line of lines) {
|
|
1256
|
+
const trimmed = line.trim();
|
|
1257
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
1258
|
+
inSection = trimmed === `[${section}]`;
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
if (!inSection) continue;
|
|
1262
|
+
const match = trimmed.match(/^([\w-]+)\s*=\s*"([^"]+)"/);
|
|
1263
|
+
if (match && match[1] === key && match[2]) {
|
|
1264
|
+
return match[2];
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
async function readGoModule(path2) {
|
|
1272
|
+
try {
|
|
1273
|
+
const raw = await readFile5(path2, "utf-8");
|
|
1274
|
+
const match = raw.match(/^\s*module\s+(\S+)/m);
|
|
1275
|
+
if (match && match[1]) {
|
|
1276
|
+
const segs = match[1].split("/");
|
|
1277
|
+
const last = segs[segs.length - 1];
|
|
1278
|
+
if (last) return last;
|
|
1279
|
+
}
|
|
1280
|
+
} catch {
|
|
1281
|
+
}
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
var init_project_name = __esm({
|
|
1285
|
+
"src/ml-client/project-name.ts"() {
|
|
1286
|
+
"use strict";
|
|
1287
|
+
init_esm_shims();
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1202
1291
|
// src/ml-client/index.ts
|
|
1203
1292
|
var ml_client_exports = {};
|
|
1204
1293
|
__export(ml_client_exports, {
|
|
@@ -1269,9 +1358,22 @@ async function runMlAnalysis(ctx, codeDnaResult, findings, options) {
|
|
|
1269
1358
|
console.error(`[deep] Sending ${sampled.length} functions + ${deviations.length} deviations (${Math.round(payloadSize / 1024)}KB) to VibeDrift API...`);
|
|
1270
1359
|
console.error(`[deep] No full files transmitted \u2014 only function snippets and structural metadata.`);
|
|
1271
1360
|
}
|
|
1361
|
+
const { detectProjectIdentity: detectProjectIdentity2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
1362
|
+
const projectIdentity = await detectProjectIdentity2(
|
|
1363
|
+
ctx.rootDir,
|
|
1364
|
+
options.projectName
|
|
1365
|
+
);
|
|
1272
1366
|
const request = {
|
|
1273
1367
|
language: ctx.dominantLanguage ?? "unknown",
|
|
1274
1368
|
file_count: ctx.files.length,
|
|
1369
|
+
project_hash: projectIdentity.hash,
|
|
1370
|
+
project_name: projectIdentity.name,
|
|
1371
|
+
score_hint: options.scoreHint,
|
|
1372
|
+
grade_hint: options.gradeHint,
|
|
1373
|
+
// Tell the server NOT to persist a row from the analyze call —
|
|
1374
|
+
// the CLI logs the full scan summary via /v1/scans/log AFTER the
|
|
1375
|
+
// pipeline finishes, which has accurate metadata.
|
|
1376
|
+
defer_persist: true,
|
|
1275
1377
|
functions: sampled,
|
|
1276
1378
|
deviations,
|
|
1277
1379
|
llm_validations: []
|
|
@@ -1283,6 +1385,9 @@ async function runMlAnalysis(ctx, codeDnaResult, findings, options) {
|
|
|
1283
1385
|
);
|
|
1284
1386
|
}
|
|
1285
1387
|
const filtered = filterByConfidence(response);
|
|
1388
|
+
if (response.scan_id) {
|
|
1389
|
+
filtered.scanId = response.scan_id;
|
|
1390
|
+
}
|
|
1286
1391
|
if (response.deviations?.length > 0 && codeDnaResult?.deviationJustifications) {
|
|
1287
1392
|
for (const mlDev of response.deviations) {
|
|
1288
1393
|
const local = codeDnaResult.deviationJustifications.find(
|
|
@@ -1445,6 +1550,160 @@ var init_summarize = __esm({
|
|
|
1445
1550
|
}
|
|
1446
1551
|
});
|
|
1447
1552
|
|
|
1553
|
+
// src/ml-client/log-scan.ts
|
|
1554
|
+
var log_scan_exports = {};
|
|
1555
|
+
__export(log_scan_exports, {
|
|
1556
|
+
logScan: () => logScan
|
|
1557
|
+
});
|
|
1558
|
+
async function logScan(opts) {
|
|
1559
|
+
const { payload, token, apiUrl, verbose } = opts;
|
|
1560
|
+
const base = apiUrl ?? DEFAULT_API_URL2;
|
|
1561
|
+
const trimmed = { ...payload };
|
|
1562
|
+
if (trimmed.report_html) {
|
|
1563
|
+
const size = Buffer.byteLength(trimmed.report_html, "utf-8");
|
|
1564
|
+
if (size > MAX_HTML_BYTES) {
|
|
1565
|
+
if (verbose) {
|
|
1566
|
+
console.error(
|
|
1567
|
+
`[scan-log] HTML too large (${Math.round(size / 1024)}KB), dropping the blob`
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
delete trimmed.report_html;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
const controller = new AbortController();
|
|
1574
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS3);
|
|
1575
|
+
try {
|
|
1576
|
+
const res = await fetch(`${base}/v1/scans/log`, {
|
|
1577
|
+
method: "POST",
|
|
1578
|
+
headers: {
|
|
1579
|
+
Authorization: `Bearer ${token}`,
|
|
1580
|
+
"Content-Type": "application/json"
|
|
1581
|
+
},
|
|
1582
|
+
body: JSON.stringify(trimmed),
|
|
1583
|
+
signal: controller.signal
|
|
1584
|
+
});
|
|
1585
|
+
if (!res.ok) {
|
|
1586
|
+
const text = await res.text().catch(() => "");
|
|
1587
|
+
if (verbose) {
|
|
1588
|
+
console.error(`[scan-log] HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
1589
|
+
}
|
|
1590
|
+
return { ok: false, error: `HTTP ${res.status}` };
|
|
1591
|
+
}
|
|
1592
|
+
const data = await res.json().catch(() => ({}));
|
|
1593
|
+
if (verbose) {
|
|
1594
|
+
console.error(
|
|
1595
|
+
`[scan-log] Logged scan ${data.scan_id?.slice(0, 8) ?? "?"} (project ${data.project_id?.slice(0, 8) ?? "?"}, ${data.bytes_stored ?? 0} HTML bytes)`
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
return {
|
|
1599
|
+
ok: true,
|
|
1600
|
+
scanId: data.scan_id,
|
|
1601
|
+
projectId: data.project_id,
|
|
1602
|
+
bytesStored: data.bytes_stored
|
|
1603
|
+
};
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1606
|
+
if (verbose) console.error(`[scan-log] Failed: ${msg}`);
|
|
1607
|
+
return { ok: false, error: msg };
|
|
1608
|
+
} finally {
|
|
1609
|
+
clearTimeout(timer);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
var DEFAULT_API_URL2, TIMEOUT_MS3, MAX_HTML_BYTES;
|
|
1613
|
+
var init_log_scan = __esm({
|
|
1614
|
+
"src/ml-client/log-scan.ts"() {
|
|
1615
|
+
"use strict";
|
|
1616
|
+
init_esm_shims();
|
|
1617
|
+
DEFAULT_API_URL2 = "https://vibedrift-api.fly.dev";
|
|
1618
|
+
TIMEOUT_MS3 = 2e4;
|
|
1619
|
+
MAX_HTML_BYTES = 15e5;
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
// src/ml-client/sanitize-result.ts
|
|
1624
|
+
var sanitize_result_exports = {};
|
|
1625
|
+
__export(sanitize_result_exports, {
|
|
1626
|
+
sanitizeResultForUpload: () => sanitizeResultForUpload
|
|
1627
|
+
});
|
|
1628
|
+
function sanitizeResultForUpload(result) {
|
|
1629
|
+
const ctx = result.context;
|
|
1630
|
+
const rootDir = ctx?.rootDir ?? "";
|
|
1631
|
+
const stripPath = (p) => {
|
|
1632
|
+
if (!p) return null;
|
|
1633
|
+
if (rootDir && p.startsWith(rootDir)) {
|
|
1634
|
+
const rel = p.slice(rootDir.length).replace(/^\/+/, "");
|
|
1635
|
+
return rel || ".";
|
|
1636
|
+
}
|
|
1637
|
+
return p;
|
|
1638
|
+
};
|
|
1639
|
+
const sanitizeNode = (node) => {
|
|
1640
|
+
if (typeof node === "string") return stripPath(node) ?? node;
|
|
1641
|
+
if (Array.isArray(node)) return node.map(sanitizeNode);
|
|
1642
|
+
if (node && typeof node === "object") {
|
|
1643
|
+
const out = {};
|
|
1644
|
+
for (const [k, v] of Object.entries(node)) {
|
|
1645
|
+
if (k === "rootDir") continue;
|
|
1646
|
+
if (k === "files" && Array.isArray(v)) {
|
|
1647
|
+
out[k] = v.map((f) => ({
|
|
1648
|
+
relativePath: stripPath(f.relativePath) ?? f.relativePath,
|
|
1649
|
+
lineCount: f.lineCount,
|
|
1650
|
+
language: f.language
|
|
1651
|
+
}));
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
if (k === "ast" || k === "treeSitterNode") continue;
|
|
1655
|
+
out[k] = sanitizeNode(v);
|
|
1656
|
+
}
|
|
1657
|
+
return out;
|
|
1658
|
+
}
|
|
1659
|
+
if (node instanceof Map) {
|
|
1660
|
+
const obj = {};
|
|
1661
|
+
for (const [k, v] of node.entries()) {
|
|
1662
|
+
const safeKey = typeof k === "string" ? stripPath(k) ?? k : String(k);
|
|
1663
|
+
obj[safeKey] = sanitizeNode(v);
|
|
1664
|
+
}
|
|
1665
|
+
return obj;
|
|
1666
|
+
}
|
|
1667
|
+
if (node instanceof Set) {
|
|
1668
|
+
return [...node].map(sanitizeNode);
|
|
1669
|
+
}
|
|
1670
|
+
return node;
|
|
1671
|
+
};
|
|
1672
|
+
const envelope = {
|
|
1673
|
+
schema: "vibedrift-scan-result/v1",
|
|
1674
|
+
project: {
|
|
1675
|
+
// populated by the caller, since the CLI knows project_name + hash
|
|
1676
|
+
},
|
|
1677
|
+
language: {
|
|
1678
|
+
dominant: ctx?.dominantLanguage ?? null,
|
|
1679
|
+
breakdown: sanitizeNode(ctx?.languageBreakdown),
|
|
1680
|
+
totalLines: ctx?.totalLines ?? 0
|
|
1681
|
+
},
|
|
1682
|
+
fileCount: (ctx?.files ?? []).length,
|
|
1683
|
+
files: sanitizeNode(ctx?.files),
|
|
1684
|
+
score: {
|
|
1685
|
+
composite: result.compositeScore,
|
|
1686
|
+
max: result.maxCompositeScore,
|
|
1687
|
+
categories: sanitizeNode(result.scores)
|
|
1688
|
+
},
|
|
1689
|
+
findings: sanitizeNode(result.findings),
|
|
1690
|
+
driftFindings: sanitizeNode(result.driftFindings),
|
|
1691
|
+
driftScores: sanitizeNode(result.driftScores),
|
|
1692
|
+
codeDnaResult: sanitizeNode(result.codeDnaResult),
|
|
1693
|
+
perFileScores: sanitizeNode(result.perFileScores),
|
|
1694
|
+
teaseMessages: result.teaseMessages,
|
|
1695
|
+
aiSummary: result.aiSummary ?? null,
|
|
1696
|
+
scanTimeMs: result.scanTimeMs
|
|
1697
|
+
};
|
|
1698
|
+
return envelope;
|
|
1699
|
+
}
|
|
1700
|
+
var init_sanitize_result = __esm({
|
|
1701
|
+
"src/ml-client/sanitize-result.ts"() {
|
|
1702
|
+
"use strict";
|
|
1703
|
+
init_esm_shims();
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1448
1707
|
// src/output/csv.ts
|
|
1449
1708
|
var csv_exports = {};
|
|
1450
1709
|
__export(csv_exports, {
|
|
@@ -4917,10 +5176,10 @@ function extractSymbols(file) {
|
|
|
4917
5176
|
function analyzeFileNaming(files) {
|
|
4918
5177
|
const fileNameConventions = /* @__PURE__ */ new Map();
|
|
4919
5178
|
for (const file of files) {
|
|
4920
|
-
const
|
|
4921
|
-
if (
|
|
4922
|
-
if (/(?:test|spec|config|setup|__)/i.test(
|
|
4923
|
-
const conv = classifyName(
|
|
5179
|
+
const basename2 = file.path.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "";
|
|
5180
|
+
if (basename2.length <= 1) continue;
|
|
5181
|
+
if (/(?:test|spec|config|setup|__)/i.test(basename2)) continue;
|
|
5182
|
+
const conv = classifyName(basename2);
|
|
4924
5183
|
if (!conv) continue;
|
|
4925
5184
|
if (!fileNameConventions.has(conv)) fileNameConventions.set(conv, []);
|
|
4926
5185
|
fileNameConventions.get(conv).push(file.path);
|
|
@@ -7658,6 +7917,15 @@ async function runScan(targetPath, options) {
|
|
|
7658
7917
|
}
|
|
7659
7918
|
bearerToken = resolved.token;
|
|
7660
7919
|
apiUrl = await resolveApiUrl(options.apiUrl);
|
|
7920
|
+
} else {
|
|
7921
|
+
try {
|
|
7922
|
+
const resolved = await resolveToken();
|
|
7923
|
+
if (resolved) {
|
|
7924
|
+
bearerToken = resolved.token;
|
|
7925
|
+
apiUrl = await resolveApiUrl(options.apiUrl);
|
|
7926
|
+
}
|
|
7927
|
+
} catch {
|
|
7928
|
+
}
|
|
7661
7929
|
}
|
|
7662
7930
|
const startTime = Date.now();
|
|
7663
7931
|
const timings = {};
|
|
@@ -7735,7 +8003,8 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7735
8003
|
token: bearerToken,
|
|
7736
8004
|
apiUrl,
|
|
7737
8005
|
verbose: options.verbose,
|
|
7738
|
-
driftFindings: driftResult.driftFindings
|
|
8006
|
+
driftFindings: driftResult.driftFindings,
|
|
8007
|
+
projectName: options.projectName
|
|
7739
8008
|
});
|
|
7740
8009
|
allFindings.push(...mlResult.highConfidence);
|
|
7741
8010
|
mlMediumConfidence = mlResult.mediumConfidence;
|
|
@@ -7806,6 +8075,62 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7806
8075
|
}
|
|
7807
8076
|
}
|
|
7808
8077
|
await saveScanResult(rootDir, scores, compositeScore);
|
|
8078
|
+
if (bearerToken) {
|
|
8079
|
+
try {
|
|
8080
|
+
const { logScan: logScan2 } = await Promise.resolve().then(() => (init_log_scan(), log_scan_exports));
|
|
8081
|
+
const { detectProjectIdentity: detectProjectIdentity2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
8082
|
+
const { sanitizeResultForUpload: sanitizeResultForUpload2 } = await Promise.resolve().then(() => (init_sanitize_result(), sanitize_result_exports));
|
|
8083
|
+
const projectIdentity = await detectProjectIdentity2(
|
|
8084
|
+
rootDir,
|
|
8085
|
+
options.projectName
|
|
8086
|
+
);
|
|
8087
|
+
const mlDuplicates = allFindings.filter((f) => f.analyzerId === "ml-duplicate").length;
|
|
8088
|
+
const mlIntent = allFindings.filter((f) => f.analyzerId === "ml-intent").length;
|
|
8089
|
+
const mlAnomaly = allFindings.filter((f) => f.analyzerId === "ml-anomaly").length;
|
|
8090
|
+
let html;
|
|
8091
|
+
try {
|
|
8092
|
+
html = renderHtmlReport(result);
|
|
8093
|
+
} catch {
|
|
8094
|
+
html = void 0;
|
|
8095
|
+
}
|
|
8096
|
+
const pct = maxCompositeScore > 0 ? compositeScore / maxCompositeScore * 100 : 0;
|
|
8097
|
+
const grade = pct >= 90 ? "A" : pct >= 75 ? "B" : pct >= 50 ? "C" : pct >= 25 ? "D" : "F";
|
|
8098
|
+
const sanitizedResult = sanitizeResultForUpload2(result);
|
|
8099
|
+
sanitizedResult.project = {
|
|
8100
|
+
name: projectIdentity.name,
|
|
8101
|
+
hash: projectIdentity.hash
|
|
8102
|
+
};
|
|
8103
|
+
sanitizedResult.grade = grade;
|
|
8104
|
+
sanitizedResult.scanType = options.deep ? "deep" : "free";
|
|
8105
|
+
sanitizedResult.scannedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8106
|
+
await logScan2({
|
|
8107
|
+
token: bearerToken,
|
|
8108
|
+
apiUrl,
|
|
8109
|
+
verbose: options.verbose,
|
|
8110
|
+
payload: {
|
|
8111
|
+
project_hash: projectIdentity.hash,
|
|
8112
|
+
project_name: projectIdentity.name,
|
|
8113
|
+
language: ctx.dominantLanguage ?? "unknown",
|
|
8114
|
+
file_count: ctx.files.length,
|
|
8115
|
+
function_count: codeDnaResult?.functions?.length ?? 0,
|
|
8116
|
+
finding_count: allFindings.length,
|
|
8117
|
+
score: compositeScore,
|
|
8118
|
+
grade,
|
|
8119
|
+
duplicates_found: mlDuplicates,
|
|
8120
|
+
intent_mismatches: mlIntent,
|
|
8121
|
+
anomalies_found: mlAnomaly,
|
|
8122
|
+
is_deep: !!options.deep,
|
|
8123
|
+
processing_time_ms: scanTimeMs,
|
|
8124
|
+
report_html: html,
|
|
8125
|
+
result_json: sanitizedResult
|
|
8126
|
+
}
|
|
8127
|
+
});
|
|
8128
|
+
} catch (err) {
|
|
8129
|
+
if (options.verbose) {
|
|
8130
|
+
console.error(chalk2.dim(`[scan-log] Failed: ${err.message}`));
|
|
8131
|
+
}
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
7809
8134
|
const format = options.format ?? (options.json ? "json" : "html");
|
|
7810
8135
|
if (format === "html") {
|
|
7811
8136
|
const html = renderHtmlReport(result);
|
|
@@ -8416,7 +8741,7 @@ async function runBilling() {
|
|
|
8416
8741
|
init_esm_shims();
|
|
8417
8742
|
import chalk10 from "chalk";
|
|
8418
8743
|
import { homedir as homedir3, platform, arch } from "os";
|
|
8419
|
-
import { join as
|
|
8744
|
+
import { join as join7 } from "path";
|
|
8420
8745
|
import { stat as stat4, access, constants } from "fs/promises";
|
|
8421
8746
|
init_version();
|
|
8422
8747
|
async function runDoctor() {
|
|
@@ -8462,7 +8787,7 @@ async function runDoctor() {
|
|
|
8462
8787
|
info("Config file", "absent (not logged in)");
|
|
8463
8788
|
}
|
|
8464
8789
|
}
|
|
8465
|
-
const historyDir =
|
|
8790
|
+
const historyDir = join7(homedir3(), ".vibedrift", "scans");
|
|
8466
8791
|
try {
|
|
8467
8792
|
const info2 = await stat4(historyDir);
|
|
8468
8793
|
if (info2.isDirectory()) ok("Scan history", historyDir);
|
|
@@ -8591,6 +8916,9 @@ program.command("scan", { isDefault: true }).description("Scan a project for vib
|
|
|
8591
8916
|
).option("--no-codedna", "skip Code DNA semantic analysis").option(
|
|
8592
8917
|
"--deep",
|
|
8593
8918
|
"enable AI-powered deep analysis (requires `vibedrift login`)"
|
|
8919
|
+
).option(
|
|
8920
|
+
"--project-name <name>",
|
|
8921
|
+
"override the auto-detected project name shown in the dashboard"
|
|
8594
8922
|
).option(
|
|
8595
8923
|
"--include <pattern>",
|
|
8596
8924
|
"only scan files matching this glob (repeatable)",
|
|
@@ -8621,7 +8949,8 @@ program.command("scan", { isDefault: true }).description("Scan a project for vib
|
|
|
8621
8949
|
apiUrl: options.apiUrl,
|
|
8622
8950
|
include: options.include,
|
|
8623
8951
|
exclude: options.exclude,
|
|
8624
|
-
verbose: options.verbose
|
|
8952
|
+
verbose: options.verbose,
|
|
8953
|
+
projectName: options.projectName
|
|
8625
8954
|
});
|
|
8626
8955
|
});
|
|
8627
8956
|
program.command("login").description("Log in to your VibeDrift account").option("--no-browser", "don't open the browser automatically").addOption(
|