@vibgrate/cli 2026.708.3 → 2026.709.2
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/DOCS.md +451 -295
- package/README.md +26 -19
- package/dist/baseline-VGDQ33ZE.js +6 -0
- package/dist/{baseline-AUC3HAXW.js.map → baseline-VGDQ33ZE.js.map} +1 -1
- package/dist/{chunk-2DJ3UXG7.js → chunk-3TCE6IGZ.js} +721 -437
- package/dist/chunk-3TCE6IGZ.js.map +1 -0
- package/dist/chunk-FU4MOQTC.js +3735 -0
- package/dist/chunk-FU4MOQTC.js.map +1 -0
- package/dist/{chunk-QUELGOCR.js → chunk-PIWHQSAR.js} +4 -4
- package/dist/{chunk-QUELGOCR.js.map → chunk-PIWHQSAR.js.map} +1 -1
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +845 -532
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +2 -2
- package/package.json +4 -3
- package/skills/vg/SKILL.md +26 -15
- package/dist/baseline-AUC3HAXW.js +0 -6
- package/dist/chunk-2DJ3UXG7.js.map +0 -1
- package/dist/chunk-JXGVMXYE.js +0 -31
- package/dist/chunk-JXGVMXYE.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { resolveDsn, resolveIngestHost, dashHostForIngestHost, resolveCliInvocation, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, ingestHostForRegionId, dsnCommand, pushCommand, CliError, ExitCode, NPX_INVOCATION, refreshIfStale, driftCount, embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, queryGraphSemantic, queryGraph, unavailableMessage, countPending, resolveOne,
|
|
2
|
+
import { resolveDsn, resolveIngestHost, dashHostForIngestHost, resolveCliInvocation, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, ingestHostForRegionId, dsnCommand, pushCommand, CliError, ExitCode, NPX_INVOCATION, refreshIfStale, driftCount, embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, queryGraphSemantic, queryGraph, unavailableMessage, recordCliCall, CLI_TOOL_ALIASES, countPending, resolveOne, indexFor, countTokens, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, serveStdio, ASSISTANTS, usageError, loadGraph, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, coveringTests, FREE_PACK, readScanArtifact, enrichOnline, ECOSYSTEMS, discoverModels, readSavings, readUsage, exportGraph, catalogPath, libDir, stableStringify, buildGraph, writeArtifacts, writeSnapshot, UsageError, ResourceLimitError, epistemicBreakdown, cacheDir, probeFreshness, discover, vibgrateDir, modelCacheInfo, clearModelCache, GraphSource, createServer, assistantById, serializeGraph, resolveLimits, loadCatalog, driftFor, addLibrary, parseDsn as parseDsn$1, publishPrivateLibrary, resolveLib, libId, resolveVersion, readDoc, localPackageDocs, localApiSurface, assessDocQuality, symbolsFromApi, fetchHostedDocs, selectForBudget, formatForExt, verifyDeterminism, renderHtml, isModelReady } from './chunk-3TCE6IGZ.js';
|
|
3
3
|
import { resolvedGrammarFiles, grammarSetVersion, canonicalize } from './chunk-X5YT263H.js';
|
|
4
|
-
import { loadAdvancedScanHook, baselineCommand } from './chunk-
|
|
5
|
-
import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-
|
|
4
|
+
import { loadAdvancedScanHook, baselineCommand } from './chunk-FU4MOQTC.js';
|
|
5
|
+
import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-PIWHQSAR.js';
|
|
6
6
|
import './chunk-RXP66R2E.js';
|
|
7
7
|
import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-GI6W53LM.js';
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
8
|
+
import * as fs6 from 'fs';
|
|
9
|
+
import fs6__default, { realpathSync } from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
|
-
import path__default
|
|
11
|
+
import path__default from 'path';
|
|
12
12
|
import { fileURLToPath } from 'url';
|
|
13
13
|
import { Command, CommanderError, Option } from 'commander';
|
|
14
14
|
import { execSync, spawn, spawnSync, execFileSync, execFile } from 'child_process';
|
|
15
15
|
import * as crypto from 'crypto';
|
|
16
16
|
import { randomUUID } from 'crypto';
|
|
17
|
-
import
|
|
17
|
+
import chalk8 from 'chalk';
|
|
18
18
|
import * as os from 'os';
|
|
19
19
|
import * as readline from 'readline';
|
|
20
|
-
import * as
|
|
20
|
+
import * as fs15 from 'fs/promises';
|
|
21
21
|
import semver from 'semver';
|
|
22
22
|
|
|
23
23
|
var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
|
|
@@ -178,14 +178,14 @@ async function signGraphAttestation(root, graph, opts) {
|
|
|
178
178
|
const explicitKey = opts.key ?? process.env.VG_ATTEST_KEY;
|
|
179
179
|
const keyPath = explicitKey ? path.resolve(explicitKey) : path.join(root, ".vibgrate", DEFAULT_KEY);
|
|
180
180
|
let keyGeneratedAt;
|
|
181
|
-
if (!
|
|
181
|
+
if (!fs6.existsSync(keyPath)) {
|
|
182
182
|
if (explicitKey) {
|
|
183
183
|
throw new CliError(`signing key not found: ${explicitKey}`, ExitCode.USAGE_ERROR);
|
|
184
184
|
}
|
|
185
185
|
const kp = generateKeypair();
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
fs6.mkdirSync(path.dirname(keyPath), { recursive: true });
|
|
187
|
+
fs6.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
|
|
188
|
+
fs6.writeFileSync(`${keyPath}.pub`, kp.publicPem);
|
|
189
189
|
keyGeneratedAt = keyPath;
|
|
190
190
|
notices.push(
|
|
191
191
|
`minted a new Ed25519 signing key at ${rel(root, keyPath)} (keyid ${kp.keyid}) \u2014 keep it, add it to .gitignore, and reuse it to re-sign reproducibly`
|
|
@@ -193,7 +193,7 @@ async function signGraphAttestation(root, graph, opts) {
|
|
|
193
193
|
}
|
|
194
194
|
let privateKey;
|
|
195
195
|
try {
|
|
196
|
-
privateKey = crypto.createPrivateKey(
|
|
196
|
+
privateKey = crypto.createPrivateKey(fs6.readFileSync(keyPath, "utf8"));
|
|
197
197
|
} catch {
|
|
198
198
|
throw new CliError(`could not read an Ed25519 private key from ${rel(root, keyPath)}`, ExitCode.USAGE_ERROR);
|
|
199
199
|
}
|
|
@@ -207,8 +207,8 @@ async function signGraphAttestation(root, graph, opts) {
|
|
|
207
207
|
const statement = buildStatement(graph, { commit });
|
|
208
208
|
const envelope = signStatement(statement, privateKey);
|
|
209
209
|
const outPath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
fs6.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
211
|
+
fs6.writeFileSync(outPath, serializeEnvelope(envelope));
|
|
212
212
|
return {
|
|
213
213
|
summary: {
|
|
214
214
|
keyid: envelope.signatures[0]?.keyid ?? "",
|
|
@@ -223,7 +223,7 @@ async function signGraphAttestation(root, graph, opts) {
|
|
|
223
223
|
}
|
|
224
224
|
function verifyGraphAttestation(root, opts) {
|
|
225
225
|
const bundlePath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
|
|
226
|
-
if (!
|
|
226
|
+
if (!fs6.existsSync(bundlePath)) {
|
|
227
227
|
if (opts.attestation || opts.pub) {
|
|
228
228
|
throw new CliError(
|
|
229
229
|
`no attestation at ${rel(root, bundlePath)} \u2014 sign one with \`vg build --attest\``,
|
|
@@ -232,8 +232,8 @@ function verifyGraphAttestation(root, opts) {
|
|
|
232
232
|
}
|
|
233
233
|
return { bundlePath, missing: true };
|
|
234
234
|
}
|
|
235
|
-
const envelope = parseEnvelope(
|
|
236
|
-
const publicKeyPem = opts.pub ?
|
|
235
|
+
const envelope = parseEnvelope(fs6.readFileSync(bundlePath, "utf8"));
|
|
236
|
+
const publicKeyPem = opts.pub ? fs6.readFileSync(path.resolve(opts.pub), "utf8") : void 0;
|
|
237
237
|
const graph = loadGraph(root) ?? void 0;
|
|
238
238
|
return { bundlePath, missing: false, result: verifyEnvelope(envelope, { publicKeyPem, graph }) };
|
|
239
239
|
}
|
|
@@ -274,9 +274,9 @@ function json(value) {
|
|
|
274
274
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
275
275
|
`);
|
|
276
276
|
}
|
|
277
|
-
var c =
|
|
277
|
+
var c = chalk8;
|
|
278
278
|
function disableColor() {
|
|
279
|
-
|
|
279
|
+
chalk8.level = 0;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
// src/util/logo.ts
|
|
@@ -346,7 +346,7 @@ var ProgressBar = class {
|
|
|
346
346
|
|
|
347
347
|
// src/cli-options.ts
|
|
348
348
|
function applyGlobalOptions(cmd) {
|
|
349
|
-
return cmd.option("-C, --cwd <dir>", "run as if started in <dir>", ".").option("--deep", "do more: precise resolution, all edges, semantic links").option("--json", "machine-readable JSON on stdout").option("--generated-at <iso>", "pin the artifact timestamp for byte-deterministic output").option("--graph <file>", "override the map path").option("--no-cache", "full rebuild (ignore the incremental cache)").option("--local", "never touch the network (lexical-only semantic)").option("--quiet", "suppress progress output").option("--no-color", "disable colored output");
|
|
349
|
+
return cmd.option("-C, --cwd <dir>", "run as if started in <dir>", ".").option("--deep", "do more: precise resolution, all edges, semantic links").option("--json", "machine-readable JSON on stdout").option("--generated-at <iso>", "pin the artifact timestamp for byte-deterministic output").option("--graph <file>", "override the map path").option("--no-cache", "full rebuild (ignore the incremental cache)").option("--local", "never touch the network (lexical-only semantic)").option("--quiet", "suppress progress output").option("--client <name>", "identify the AI client (e.g. claude) so navigation calls are counted in vg savings").option("--no-color", "disable colored output");
|
|
350
350
|
}
|
|
351
351
|
function readGlobal(cmd) {
|
|
352
352
|
const o = cmd.opts();
|
|
@@ -358,7 +358,8 @@ function readGlobal(cmd) {
|
|
|
358
358
|
graph: o.graph,
|
|
359
359
|
noCache: o.cache === false,
|
|
360
360
|
local: o.local,
|
|
361
|
-
quiet: o.quiet
|
|
361
|
+
quiet: o.quiet,
|
|
362
|
+
client: o.client
|
|
362
363
|
};
|
|
363
364
|
}
|
|
364
365
|
|
|
@@ -583,8 +584,8 @@ function writeExport(graph, target) {
|
|
|
583
584
|
ExitCode.USAGE_ERROR
|
|
584
585
|
);
|
|
585
586
|
}
|
|
586
|
-
|
|
587
|
-
|
|
587
|
+
fs6.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
|
|
588
|
+
fs6.writeFileSync(target, content);
|
|
588
589
|
}
|
|
589
590
|
function collect(value, prev) {
|
|
590
591
|
return [...prev, value];
|
|
@@ -603,7 +604,7 @@ async function runStatus(global) {
|
|
|
603
604
|
const root = path.resolve(global.cwd ?? ".");
|
|
604
605
|
const graphPath = global.graph ?? defaultGraphPath(root);
|
|
605
606
|
const graph = loadGraph(root, graphPath);
|
|
606
|
-
const hasCache =
|
|
607
|
+
const hasCache = fs6.existsSync(path.join(cacheDir(root), "parse-cache.json"));
|
|
607
608
|
let stale = null;
|
|
608
609
|
let staleExact = false;
|
|
609
610
|
if (graph) {
|
|
@@ -716,6 +717,20 @@ function registerAsk(program) {
|
|
|
716
717
|
result = queryGraph(graph, q, { budget });
|
|
717
718
|
if (global.local) note = "semantic skipped under --local; used lexical";
|
|
718
719
|
}
|
|
720
|
+
if (global.client) {
|
|
721
|
+
const files = new Set(result.matches.map((m) => m.node.file).filter(Boolean));
|
|
722
|
+
recordCliCall(
|
|
723
|
+
root,
|
|
724
|
+
{
|
|
725
|
+
tool: CLI_TOOL_ALIASES.ask,
|
|
726
|
+
client: global.client,
|
|
727
|
+
outcome: result.matches.length === 0 ? "miss" : "complete",
|
|
728
|
+
vgTokens: result.tokensEstimate,
|
|
729
|
+
baselineFiles: files.size
|
|
730
|
+
},
|
|
731
|
+
Date.now()
|
|
732
|
+
);
|
|
733
|
+
}
|
|
719
734
|
if (global.json) {
|
|
720
735
|
json({
|
|
721
736
|
question: result.question,
|
|
@@ -845,12 +860,28 @@ function registerShow(program) {
|
|
|
845
860
|
}
|
|
846
861
|
throw ambiguityError(`"${name}" is ambiguous`, candidates);
|
|
847
862
|
}
|
|
848
|
-
const index =
|
|
863
|
+
const index = indexFor(graph);
|
|
849
864
|
const callees = dedupe(index.callees(node.id).map((x) => x.node));
|
|
850
865
|
const callers = dedupe(index.callers(node.id).map((x) => x.node));
|
|
851
866
|
const extendsEdges = index.out(node.id, "extends").concat(index.out(node.id, "implements"));
|
|
852
867
|
const supertypes = extendsEdges.map((e) => index.node(e.dst)?.qualifiedName).filter(Boolean);
|
|
853
868
|
const area = graph.areas.find((a) => a.id === node.area);
|
|
869
|
+
if (global.client) {
|
|
870
|
+
const files = /* @__PURE__ */ new Set([node.file]);
|
|
871
|
+
for (const n of [...callees, ...callers]) if (n.file) files.add(n.file);
|
|
872
|
+
const shown = node.qualifiedName + (node.signature ?? "") + callees.map((n) => n.qualifiedName).join("") + callers.map((n) => n.qualifiedName).join("");
|
|
873
|
+
recordCliCall(
|
|
874
|
+
rootOf(global),
|
|
875
|
+
{
|
|
876
|
+
tool: CLI_TOOL_ALIASES.show,
|
|
877
|
+
client: global.client,
|
|
878
|
+
outcome: "complete",
|
|
879
|
+
vgTokens: countTokens(shown),
|
|
880
|
+
baselineFiles: files.size
|
|
881
|
+
},
|
|
882
|
+
Date.now()
|
|
883
|
+
);
|
|
884
|
+
}
|
|
854
885
|
if (global.json) {
|
|
855
886
|
json({
|
|
856
887
|
id: node.id,
|
|
@@ -904,6 +935,13 @@ function registerPath(program) {
|
|
|
904
935
|
const rb = resolveOne(graph, b, opts.pickB ? Number(opts.pickB) : void 0);
|
|
905
936
|
if (!rb.node) throw ambiguityError(`"${b}" ${rb.candidates.length ? "is ambiguous" : "not found"}`, rb.candidates, "--pick-b");
|
|
906
937
|
const result = shortestPath(graph, ra.node.id, rb.node.id);
|
|
938
|
+
if (global.client) {
|
|
939
|
+
recordCliCall(
|
|
940
|
+
rootOf(global),
|
|
941
|
+
{ tool: CLI_TOOL_ALIASES.path, client: global.client, outcome: result ? "complete" : "miss" },
|
|
942
|
+
Date.now()
|
|
943
|
+
);
|
|
944
|
+
}
|
|
907
945
|
if (!result) {
|
|
908
946
|
throw new CliError(
|
|
909
947
|
`no path between ${ra.node.qualifiedName} and ${rb.node.qualifiedName}`,
|
|
@@ -929,9 +967,16 @@ function registerTree(program) {
|
|
|
929
967
|
const { graph } = requireGraph(global);
|
|
930
968
|
const { node, candidates } = resolveOne(graph, name, opts.pick ? Number(opts.pick) : void 0);
|
|
931
969
|
if (!node) throw ambiguityError(`"${name}" ${candidates.length ? "is ambiguous" : "not found"}`, candidates);
|
|
932
|
-
const index =
|
|
970
|
+
const index = indexFor(graph);
|
|
933
971
|
const maxDepth = Math.max(1, Number(opts.depth) || 3);
|
|
934
972
|
const direction = opts.callers ? "callers" : "callees";
|
|
973
|
+
if (global.client) {
|
|
974
|
+
recordCliCall(
|
|
975
|
+
rootOf(global),
|
|
976
|
+
{ tool: CLI_TOOL_ALIASES.tree, client: global.client, outcome: "complete" },
|
|
977
|
+
Date.now()
|
|
978
|
+
);
|
|
979
|
+
}
|
|
935
980
|
if (global.json) {
|
|
936
981
|
json(toJsonTree(index, node, direction, maxDepth, /* @__PURE__ */ new Set()));
|
|
937
982
|
return;
|
|
@@ -1091,6 +1136,13 @@ function registerImpact(program) {
|
|
|
1091
1136
|
if (!node) throw ambiguityError(`"${name}" ${candidates.length ? "is ambiguous" : "not found"}`, candidates);
|
|
1092
1137
|
const depth = Number(opts.depth) || 4;
|
|
1093
1138
|
const result = impactOf(graph, node.id, { depth });
|
|
1139
|
+
if (global.client) {
|
|
1140
|
+
recordCliCall(
|
|
1141
|
+
rootOf(global),
|
|
1142
|
+
{ tool: CLI_TOOL_ALIASES.impact, client: global.client, outcome: "complete" },
|
|
1143
|
+
Date.now()
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1094
1146
|
const withTests = opts.tests || opts.failOnUntested;
|
|
1095
1147
|
const ti = withTests ? testsToRun(graph, node.id, depth) : void 0;
|
|
1096
1148
|
const runner = ti ? detectRunner(rootOf(global), node.lang) : void 0;
|
|
@@ -1230,24 +1282,209 @@ function fmtRef(r) {
|
|
|
1230
1282
|
function pad2(s, n) {
|
|
1231
1283
|
return s.padStart(n, " ");
|
|
1232
1284
|
}
|
|
1285
|
+
var SCHEMA_VERSION = "1";
|
|
1286
|
+
var OFFSET_FILE = "stats-share.json";
|
|
1287
|
+
var INSTALL_ID_DIR = path.join(os.homedir(), ".vibgrate");
|
|
1288
|
+
var INSTALL_ID_FILE = path.join(INSTALL_ID_DIR, "install-id");
|
|
1289
|
+
var SEND_TIMEOUT_MS = 4e3;
|
|
1290
|
+
function installId() {
|
|
1291
|
+
try {
|
|
1292
|
+
const existing = fs6.readFileSync(INSTALL_ID_FILE, "utf8").trim();
|
|
1293
|
+
if (isUuid(existing)) return existing;
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
const id = randomUuid();
|
|
1297
|
+
try {
|
|
1298
|
+
fs6.mkdirSync(INSTALL_ID_DIR, { recursive: true });
|
|
1299
|
+
fs6.writeFileSync(INSTALL_ID_FILE, id + "\n", { mode: 384 });
|
|
1300
|
+
} catch {
|
|
1301
|
+
}
|
|
1302
|
+
return id;
|
|
1303
|
+
}
|
|
1304
|
+
function isUuid(s) {
|
|
1305
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
|
|
1306
|
+
}
|
|
1307
|
+
function randomUuid() {
|
|
1308
|
+
return randomUUID();
|
|
1309
|
+
}
|
|
1310
|
+
function statsEndpoint() {
|
|
1311
|
+
const override = process.env.VIBGRATE_STATS_ENDPOINT;
|
|
1312
|
+
if (override && /^https?:\/\//.test(override)) return override;
|
|
1313
|
+
return `https://${resolveIngestHost()}/v1/ingest/cli-mcp-usage`;
|
|
1314
|
+
}
|
|
1315
|
+
function offsetPath(root) {
|
|
1316
|
+
return path.join(cacheDir(root), OFFSET_FILE);
|
|
1317
|
+
}
|
|
1318
|
+
function ledgerFilePath(root) {
|
|
1319
|
+
return path.join(cacheDir(root), "savings.jsonl");
|
|
1320
|
+
}
|
|
1321
|
+
function readOffset(root) {
|
|
1322
|
+
try {
|
|
1323
|
+
const raw = JSON.parse(fs6.readFileSync(offsetPath(root), "utf8"));
|
|
1324
|
+
return typeof raw.offset === "number" && raw.offset >= 0 ? raw.offset : 0;
|
|
1325
|
+
} catch {
|
|
1326
|
+
return 0;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
function writeOffset(root, offset) {
|
|
1330
|
+
try {
|
|
1331
|
+
fs6.mkdirSync(cacheDir(root), { recursive: true });
|
|
1332
|
+
fs6.writeFileSync(offsetPath(root), JSON.stringify({ offset }) + "\n");
|
|
1333
|
+
} catch {
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function buildBatch(root, fromOffset, id = installId()) {
|
|
1337
|
+
const file = ledgerFilePath(root);
|
|
1338
|
+
let size = 0;
|
|
1339
|
+
try {
|
|
1340
|
+
size = fs6.statSync(file).size;
|
|
1341
|
+
} catch {
|
|
1342
|
+
return { batch: null, newOffset: 0 };
|
|
1343
|
+
}
|
|
1344
|
+
const start = fromOffset > size ? 0 : fromOffset;
|
|
1345
|
+
if (start >= size) return { batch: null, newOffset: size };
|
|
1346
|
+
let text = "";
|
|
1347
|
+
try {
|
|
1348
|
+
const fd = fs6.openSync(file, "r");
|
|
1349
|
+
try {
|
|
1350
|
+
const buf = Buffer.alloc(size - start);
|
|
1351
|
+
fs6.readSync(fd, buf, 0, buf.length, start);
|
|
1352
|
+
text = buf.toString("utf8");
|
|
1353
|
+
} finally {
|
|
1354
|
+
fs6.closeSync(fd);
|
|
1355
|
+
}
|
|
1356
|
+
} catch {
|
|
1357
|
+
return { batch: null, newOffset: fromOffset };
|
|
1358
|
+
}
|
|
1359
|
+
const lastNl = text.lastIndexOf("\n");
|
|
1360
|
+
if (lastNl < 0) return { batch: null, newOffset: start };
|
|
1361
|
+
const consumed = start + lastNl + 1;
|
|
1362
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1363
|
+
let total = 0;
|
|
1364
|
+
let minTs = Infinity;
|
|
1365
|
+
let maxTs = -Infinity;
|
|
1366
|
+
for (const line of text.slice(0, lastNl).split("\n")) {
|
|
1367
|
+
if (!line.trim()) continue;
|
|
1368
|
+
let e;
|
|
1369
|
+
try {
|
|
1370
|
+
e = JSON.parse(line);
|
|
1371
|
+
} catch {
|
|
1372
|
+
continue;
|
|
1373
|
+
}
|
|
1374
|
+
const source = e.source ?? "mcp";
|
|
1375
|
+
const client = e.client ?? "unknown";
|
|
1376
|
+
const outcome = e.outcome ?? "complete";
|
|
1377
|
+
const key = `${source}|${client}|${e.tool}|${outcome}`;
|
|
1378
|
+
const row = rows.get(key) ?? {
|
|
1379
|
+
source,
|
|
1380
|
+
client,
|
|
1381
|
+
tool: e.tool,
|
|
1382
|
+
outcome,
|
|
1383
|
+
calls: 0,
|
|
1384
|
+
vgTokens: 0,
|
|
1385
|
+
baselineTokens: 0
|
|
1386
|
+
};
|
|
1387
|
+
row.calls++;
|
|
1388
|
+
row.vgTokens += e.vgTokens ?? 0;
|
|
1389
|
+
row.baselineTokens += e.baselineTokens ?? 0;
|
|
1390
|
+
rows.set(key, row);
|
|
1391
|
+
total++;
|
|
1392
|
+
if (typeof e.ts === "number") {
|
|
1393
|
+
if (e.ts < minTs) minTs = e.ts;
|
|
1394
|
+
if (e.ts > maxTs) maxTs = e.ts;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
if (total === 0 || !Number.isFinite(minTs)) return { batch: null, newOffset: consumed };
|
|
1398
|
+
const batch = {
|
|
1399
|
+
schemaVersion: SCHEMA_VERSION,
|
|
1400
|
+
installId: id,
|
|
1401
|
+
vgVersion: VERSION,
|
|
1402
|
+
os: process.platform,
|
|
1403
|
+
arch: process.arch,
|
|
1404
|
+
windowStart: new Date(minTs).toISOString(),
|
|
1405
|
+
windowEnd: new Date(maxTs).toISOString(),
|
|
1406
|
+
totalCalls: total,
|
|
1407
|
+
rows: [...rows.values()]
|
|
1408
|
+
};
|
|
1409
|
+
return { batch, newOffset: consumed };
|
|
1410
|
+
}
|
|
1411
|
+
async function postBatch(batch) {
|
|
1412
|
+
const controller = new AbortController();
|
|
1413
|
+
const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS);
|
|
1414
|
+
timer.unref?.();
|
|
1415
|
+
try {
|
|
1416
|
+
const res = await fetch(statsEndpoint(), {
|
|
1417
|
+
method: "POST",
|
|
1418
|
+
headers: { "Content-Type": "application/json", Connection: "close" },
|
|
1419
|
+
body: JSON.stringify(batch),
|
|
1420
|
+
signal: controller.signal
|
|
1421
|
+
});
|
|
1422
|
+
return res.ok;
|
|
1423
|
+
} catch {
|
|
1424
|
+
return false;
|
|
1425
|
+
} finally {
|
|
1426
|
+
clearTimeout(timer);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
var StatsSharer = class {
|
|
1430
|
+
constructor(root) {
|
|
1431
|
+
this.root = root;
|
|
1432
|
+
this.offset = readOffset(root);
|
|
1433
|
+
}
|
|
1434
|
+
root;
|
|
1435
|
+
id = installId();
|
|
1436
|
+
offset;
|
|
1437
|
+
flushing = false;
|
|
1438
|
+
/**
|
|
1439
|
+
* Flush any new ledger entries. Single-flight (a slow POST won't overlap the
|
|
1440
|
+
* next tick). Best-effort: any failure is swallowed and retried next time.
|
|
1441
|
+
*/
|
|
1442
|
+
async flush() {
|
|
1443
|
+
if (this.flushing) return;
|
|
1444
|
+
this.flushing = true;
|
|
1445
|
+
try {
|
|
1446
|
+
const { batch, newOffset } = buildBatch(this.root, this.offset, this.id);
|
|
1447
|
+
if (!batch) {
|
|
1448
|
+
if (newOffset > this.offset) {
|
|
1449
|
+
this.offset = newOffset;
|
|
1450
|
+
writeOffset(this.root, newOffset);
|
|
1451
|
+
}
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const ok = await postBatch(batch);
|
|
1455
|
+
if (ok) {
|
|
1456
|
+
this.offset = newOffset;
|
|
1457
|
+
writeOffset(this.root, newOffset);
|
|
1458
|
+
}
|
|
1459
|
+
} catch {
|
|
1460
|
+
} finally {
|
|
1461
|
+
this.flushing = false;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
// src/commands/serve.ts
|
|
1467
|
+
var SHARE_FLUSH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1233
1468
|
function registerServe(program) {
|
|
1234
|
-
const cmd = program.command("serve").description("start Vibgrate AI Context \u2014 local-first MCP serving your code map, drift & version-correct docs to your AI").option("--http", "serve over streamable HTTP instead of stdio").option("--port <n>", "port for --http", "7437").option("--host <h>", "host for --http", "127.0.0.1").option("--savings", "record local, counts-only usage savings (opt-in; off by default)").option("--dedup", "collapse a node's heavy relation lists on repeat reads within a session (opt-in; saves tokens)").option("--no-refresh", "serve the map as built \u2014 skip the auto-rebuild when files change").action(async function(opts) {
|
|
1469
|
+
const cmd = program.command("serve").description("start Vibgrate AI Context \u2014 local-first MCP serving your code map, drift & version-correct docs to your AI").option("--http", "serve over streamable HTTP instead of stdio").option("--port <n>", "port for --http", "7437").option("--host <h>", "host for --http", "127.0.0.1").option("--savings", "record local, counts-only usage savings (opt-in; off by default)").option("--share-stats", "ALSO upload the counts-only usage ledger to Vibgrate to improve the local MCP (opt-in; off by default; implies --savings; disabled under --local)").option("--dedup", "collapse a node's heavy relation lists on repeat reads within a session (opt-in; saves tokens)").option("--no-refresh", "serve the map as built \u2014 skip the auto-rebuild when files change").action(async function(opts) {
|
|
1235
1470
|
const global = readGlobal(this);
|
|
1236
1471
|
const root = rootOf(global);
|
|
1237
1472
|
const graphPath = global.graph ?? defaultGraphPath(root);
|
|
1238
1473
|
const refresh = opts.refresh !== false && !global.graph;
|
|
1474
|
+
const local = global.local === true;
|
|
1475
|
+
const shareStats = opts.shareStats === true && !local;
|
|
1239
1476
|
const serveOpts = {
|
|
1240
|
-
savings: opts.savings === true,
|
|
1241
|
-
|
|
1477
|
+
savings: opts.savings === true || shareStats,
|
|
1478
|
+
shareStats,
|
|
1479
|
+
local,
|
|
1242
1480
|
dedup: opts.dedup === true,
|
|
1243
1481
|
refresh
|
|
1244
1482
|
};
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
ExitCode.NOT_FOUND
|
|
1249
|
-
);
|
|
1483
|
+
await ensureServableGraph(root, graphPath, global, refresh);
|
|
1484
|
+
if (opts.shareStats === true && local) {
|
|
1485
|
+
info(c.dim("vg \xB7 --share-stats ignored under --local (air-gapped): recording locally, not uploading."));
|
|
1250
1486
|
}
|
|
1487
|
+
if (shareStats) startSharing(root);
|
|
1251
1488
|
const freshness = refresh ? "auto-refresh" : "as built";
|
|
1252
1489
|
if (opts.http) {
|
|
1253
1490
|
await serveHttp(graphPath, opts.host ?? "127.0.0.1", Number(opts.port) || 7437, serveOpts, freshness);
|
|
@@ -1258,6 +1495,57 @@ function registerServe(program) {
|
|
|
1258
1495
|
});
|
|
1259
1496
|
applyGlobalOptions(cmd);
|
|
1260
1497
|
}
|
|
1498
|
+
async function ensureServableGraph(root, graphPath, global, refresh, opts = {}) {
|
|
1499
|
+
if (refresh) {
|
|
1500
|
+
if (!fs6.existsSync(graphPath)) {
|
|
1501
|
+
info(c.dim("vg \xB7 no map found \u2014 building it before serving\u2026"));
|
|
1502
|
+
await runBuild(
|
|
1503
|
+
[],
|
|
1504
|
+
{ html: false, report: false, jobs: opts.inline ? "1" : void 0 },
|
|
1505
|
+
{ ...global, json: false }
|
|
1506
|
+
);
|
|
1507
|
+
} else {
|
|
1508
|
+
const refreshed = await refreshIfStale(root, { inline: opts.inline });
|
|
1509
|
+
if (refreshed.status === "refreshed") {
|
|
1510
|
+
const n = driftCount(refreshed.drift);
|
|
1511
|
+
info(c.dim(`vg \xB7 map refreshed before serving \u2014 ${n} file(s) drifted (${(refreshed.ms / 1e3).toFixed(2)}s)`));
|
|
1512
|
+
} else if (refreshed.status === "error") {
|
|
1513
|
+
info(c.yellow(`vg \xB7 map refresh failed (${refreshed.message}) \u2014 serving the last built map`));
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (!fs6.existsSync(graphPath)) {
|
|
1518
|
+
throw new CliError(
|
|
1519
|
+
`no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
|
|
1520
|
+
ExitCode.NOT_FOUND
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function startSharing(root) {
|
|
1525
|
+
const sharer = new StatsSharer(root);
|
|
1526
|
+
info(
|
|
1527
|
+
c.dim(
|
|
1528
|
+
`vg \xB7 sharing counts-only usage stats with Vibgrate (${statsEndpoint()}) to improve the local MCP. No code, paths, or questions are sent. Stop by omitting --share-stats.`
|
|
1529
|
+
)
|
|
1530
|
+
);
|
|
1531
|
+
const timer = setInterval(() => void sharer.flush(), SHARE_FLUSH_INTERVAL_MS);
|
|
1532
|
+
timer.unref?.();
|
|
1533
|
+
let flushed = false;
|
|
1534
|
+
const finalFlush = () => {
|
|
1535
|
+
if (flushed) return;
|
|
1536
|
+
flushed = true;
|
|
1537
|
+
void sharer.flush();
|
|
1538
|
+
};
|
|
1539
|
+
process.once("SIGINT", () => {
|
|
1540
|
+
finalFlush();
|
|
1541
|
+
process.exit(0);
|
|
1542
|
+
});
|
|
1543
|
+
process.once("SIGTERM", () => {
|
|
1544
|
+
finalFlush();
|
|
1545
|
+
process.exit(0);
|
|
1546
|
+
});
|
|
1547
|
+
process.once("beforeExit", finalFlush);
|
|
1548
|
+
}
|
|
1261
1549
|
async function serveHttp(graphPath, host, port, opts, freshness) {
|
|
1262
1550
|
const { createServer: createHttp } = await import('http');
|
|
1263
1551
|
const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
@@ -1361,7 +1649,7 @@ function registerShare(program) {
|
|
|
1361
1649
|
const global = readGlobal(this);
|
|
1362
1650
|
const root = rootOf(global);
|
|
1363
1651
|
const gitDir = path.join(root, ".git");
|
|
1364
|
-
if (!
|
|
1652
|
+
if (!fs6.existsSync(gitDir)) {
|
|
1365
1653
|
throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
|
|
1366
1654
|
}
|
|
1367
1655
|
if (opts.undo) {
|
|
@@ -1383,22 +1671,22 @@ function registerShare(program) {
|
|
|
1383
1671
|
}
|
|
1384
1672
|
function writeVibgrateGitignore(root, reports) {
|
|
1385
1673
|
const dir = path.join(root, ".vibgrate");
|
|
1386
|
-
|
|
1674
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
1387
1675
|
const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
|
|
1388
1676
|
if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
|
|
1389
|
-
|
|
1677
|
+
fs6.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
|
|
1390
1678
|
`);
|
|
1391
1679
|
}
|
|
1392
1680
|
function writeGitAttributes(root) {
|
|
1393
1681
|
const file = path.join(root, ".gitattributes");
|
|
1394
1682
|
const line = ".vibgrate/graph.json merge=vg";
|
|
1395
|
-
let content =
|
|
1683
|
+
let content = fs6.existsSync(file) ? fs6.readFileSync(file, "utf8") : "";
|
|
1396
1684
|
if (!content.split("\n").some((l) => l.trim() === line)) {
|
|
1397
1685
|
content = content.length ? `${content.replace(/\s*$/, "")}
|
|
1398
1686
|
${line}
|
|
1399
1687
|
` : `${line}
|
|
1400
1688
|
`;
|
|
1401
|
-
|
|
1689
|
+
fs6.writeFileSync(file, content);
|
|
1402
1690
|
}
|
|
1403
1691
|
}
|
|
1404
1692
|
function configureMergeDriver(root) {
|
|
@@ -1407,7 +1695,7 @@ function configureMergeDriver(root) {
|
|
|
1407
1695
|
}
|
|
1408
1696
|
function installPreCommitHook(root) {
|
|
1409
1697
|
const hooksDir = hookDir(root);
|
|
1410
|
-
|
|
1698
|
+
fs6.mkdirSync(hooksDir, { recursive: true });
|
|
1411
1699
|
const file = path.join(hooksDir, "pre-commit");
|
|
1412
1700
|
const block = [
|
|
1413
1701
|
HOOK_BEGIN,
|
|
@@ -1417,7 +1705,7 @@ function installPreCommitHook(root) {
|
|
|
1417
1705
|
"fi",
|
|
1418
1706
|
HOOK_END
|
|
1419
1707
|
].join("\n");
|
|
1420
|
-
let content =
|
|
1708
|
+
let content = fs6.existsSync(file) ? fs6.readFileSync(file, "utf8") : "#!/bin/sh\n";
|
|
1421
1709
|
const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
|
|
1422
1710
|
if (re.test(content)) content = content.replace(re, block);
|
|
1423
1711
|
else content = `${content.replace(/\s*$/, "")}
|
|
@@ -1426,21 +1714,21 @@ ${block}
|
|
|
1426
1714
|
`;
|
|
1427
1715
|
if (!content.startsWith("#!")) content = `#!/bin/sh
|
|
1428
1716
|
${content}`;
|
|
1429
|
-
|
|
1430
|
-
|
|
1717
|
+
fs6.writeFileSync(file, content);
|
|
1718
|
+
fs6.chmodSync(file, 493);
|
|
1431
1719
|
}
|
|
1432
1720
|
function undo(root) {
|
|
1433
1721
|
const hookFile = path.join(hookDir(root), "pre-commit");
|
|
1434
|
-
if (
|
|
1722
|
+
if (fs6.existsSync(hookFile)) {
|
|
1435
1723
|
const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
|
|
1436
|
-
const next2 =
|
|
1437
|
-
if (next2.trim() === "#!/bin/sh")
|
|
1438
|
-
else
|
|
1724
|
+
const next2 = fs6.readFileSync(hookFile, "utf8").replace(re, "\n");
|
|
1725
|
+
if (next2.trim() === "#!/bin/sh") fs6.rmSync(hookFile);
|
|
1726
|
+
else fs6.writeFileSync(hookFile, next2);
|
|
1439
1727
|
}
|
|
1440
1728
|
const attr = path.join(root, ".gitattributes");
|
|
1441
|
-
if (
|
|
1442
|
-
const next2 =
|
|
1443
|
-
|
|
1729
|
+
if (fs6.existsSync(attr)) {
|
|
1730
|
+
const next2 = fs6.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
|
|
1731
|
+
fs6.writeFileSync(attr, next2);
|
|
1444
1732
|
}
|
|
1445
1733
|
try {
|
|
1446
1734
|
git(root, ["config", "--remove-section", "merge.vg"]);
|
|
@@ -1573,7 +1861,7 @@ function grepBaselineTokens(term, files) {
|
|
|
1573
1861
|
if (f.size > 512 * 1024) continue;
|
|
1574
1862
|
let content = "";
|
|
1575
1863
|
try {
|
|
1576
|
-
content =
|
|
1864
|
+
content = fs6.readFileSync(f.abs, "utf8");
|
|
1577
1865
|
} catch {
|
|
1578
1866
|
continue;
|
|
1579
1867
|
}
|
|
@@ -1583,7 +1871,7 @@ function grepBaselineTokens(term, files) {
|
|
|
1583
1871
|
}
|
|
1584
1872
|
function safeSize(abs) {
|
|
1585
1873
|
try {
|
|
1586
|
-
return
|
|
1874
|
+
return fs6.statSync(abs).size;
|
|
1587
1875
|
} catch {
|
|
1588
1876
|
return 0;
|
|
1589
1877
|
}
|
|
@@ -1917,7 +2205,7 @@ function loadStandards(root) {
|
|
|
1917
2205
|
const p = path__default.join(root, rel3);
|
|
1918
2206
|
let text;
|
|
1919
2207
|
try {
|
|
1920
|
-
text =
|
|
2208
|
+
text = fs6__default.readFileSync(p, "utf8");
|
|
1921
2209
|
} catch {
|
|
1922
2210
|
continue;
|
|
1923
2211
|
}
|
|
@@ -2046,7 +2334,7 @@ function registerSavings(program) {
|
|
|
2046
2334
|
if (usage.totals.calls === 0) {
|
|
2047
2335
|
info(
|
|
2048
2336
|
c.dim(
|
|
2049
|
-
report.enabled ? " no calls recorded yet in this window" : " recording is off. Enable with `vg serve --savings`,
|
|
2337
|
+
report.enabled ? " no calls recorded yet in this window" : " recording is off. Enable it for MCP with `vg serve --savings`, and for CLI calls by passing `--client=<ai>` to vg."
|
|
2050
2338
|
)
|
|
2051
2339
|
);
|
|
2052
2340
|
return;
|
|
@@ -2057,6 +2345,7 @@ function registerSavings(program) {
|
|
|
2057
2345
|
info(c.dim(" estimates with stated assumptions (~4 chars/token; ~400 tokens/file baseline); scales with repo size."));
|
|
2058
2346
|
}
|
|
2059
2347
|
printBreakdown(usage);
|
|
2348
|
+
printSplit(usage);
|
|
2060
2349
|
});
|
|
2061
2350
|
applyGlobalOptions(cmd);
|
|
2062
2351
|
}
|
|
@@ -2083,6 +2372,24 @@ function printBreakdown(usage) {
|
|
|
2083
2372
|
);
|
|
2084
2373
|
info(c.dim(` avg success across commands: ${usage.avgSuccessPct}%`));
|
|
2085
2374
|
}
|
|
2375
|
+
function printSplit(usage) {
|
|
2376
|
+
const label = (key) => key === "mcp" ? "MCP tools" : key === "cli" ? "vg CLI" : key;
|
|
2377
|
+
if (usage.sources.length) {
|
|
2378
|
+
info("");
|
|
2379
|
+
info(c.bold(" by source") + c.dim(" (how the call arrived)"));
|
|
2380
|
+
for (const s of usage.sources) {
|
|
2381
|
+
const pct = usage.totals.calls ? Math.round(s.calls / usage.totals.calls * 100) : 0;
|
|
2382
|
+
info(` ${label(s.key).padEnd(12)} ${String(s.calls).padStart(6)} calls ${c.dim(`(${pct}%)`)}`);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
if (usage.clients.length) {
|
|
2386
|
+
info("");
|
|
2387
|
+
info(c.bold(" by client") + c.dim(" (which AI is calling; pass --client to vg to attribute CLI calls)"));
|
|
2388
|
+
for (const cl of usage.clients) {
|
|
2389
|
+
info(` ${cl.key.padEnd(12)} ${String(cl.calls).padStart(6)} calls`);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2086
2393
|
function registerLib(program) {
|
|
2087
2394
|
const cmd = program.command("lib").description("library currency: version-correct, drift-annotated usage docs").argument("[args...]", "a library name, or: add <source> | publish <name> | resolve <name> | refresh").option("--name <name>", "library name for `add`").option("--version <v>", "pin the doc version for `add`/`publish`").option("--online", "(deprecated; network is on by default) allow network for add/refresh URL sources").option("-b, --budget <n>", "trim docs to ~N tokens").option("--readme <path>", "README path for `publish` (default ./README.md)").option("--dts <path>", "TypeScript declaration (.d.ts) path for `publish`").option("--language <lang>", "primary language for `publish`").option("--region <region>", "data-residency region for the hosted catalog (same as scans; default us)").option("--ingest <url>", "hosted catalog/ingest URL override (host extracted; wins over --region)").action(async function(args, opts) {
|
|
2088
2395
|
const global = readGlobal(this);
|
|
@@ -2136,7 +2443,7 @@ async function addCmd(root, rest, opts, asJson) {
|
|
|
2136
2443
|
}
|
|
2137
2444
|
function readPackageVersion(root) {
|
|
2138
2445
|
try {
|
|
2139
|
-
const pkg = JSON.parse(
|
|
2446
|
+
const pkg = JSON.parse(fs6__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
|
|
2140
2447
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
2141
2448
|
} catch {
|
|
2142
2449
|
return void 0;
|
|
@@ -2144,7 +2451,7 @@ function readPackageVersion(root) {
|
|
|
2144
2451
|
}
|
|
2145
2452
|
function readOptionalFile(p) {
|
|
2146
2453
|
try {
|
|
2147
|
-
const t =
|
|
2454
|
+
const t = fs6__default.readFileSync(p, "utf8");
|
|
2148
2455
|
return t.trim() ? t : void 0;
|
|
2149
2456
|
} catch {
|
|
2150
2457
|
return void 0;
|
|
@@ -2289,8 +2596,8 @@ function registerExport(program) {
|
|
|
2289
2596
|
out(content.trimEnd());
|
|
2290
2597
|
return;
|
|
2291
2598
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2599
|
+
fs6.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
|
|
2600
|
+
fs6.writeFileSync(file, content);
|
|
2294
2601
|
info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
|
|
2295
2602
|
});
|
|
2296
2603
|
applyGlobalOptions(cmd);
|
|
@@ -2313,24 +2620,24 @@ function registerBundle(program) {
|
|
|
2313
2620
|
} catch {
|
|
2314
2621
|
throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
|
|
2315
2622
|
}
|
|
2316
|
-
|
|
2623
|
+
fs6.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
|
|
2317
2624
|
let grammarCount = 0;
|
|
2318
2625
|
for (const { fileName, absPath } of grammarFiles) {
|
|
2319
|
-
|
|
2626
|
+
fs6.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
|
|
2320
2627
|
grammarCount++;
|
|
2321
2628
|
}
|
|
2322
2629
|
const included = [`grammars/ (${grammarCount} .wasm)`];
|
|
2323
2630
|
const graphSrc = global.graph ?? defaultGraphPath(root);
|
|
2324
|
-
if (
|
|
2325
|
-
|
|
2631
|
+
if (fs6.existsSync(graphSrc)) {
|
|
2632
|
+
fs6.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
|
|
2326
2633
|
included.push("graph.json");
|
|
2327
2634
|
}
|
|
2328
2635
|
const cat = catalogPath(root);
|
|
2329
|
-
if (
|
|
2330
|
-
|
|
2636
|
+
if (fs6.existsSync(cat)) {
|
|
2637
|
+
fs6.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
|
|
2331
2638
|
included.push("vibgrate.lib.json");
|
|
2332
|
-
if (
|
|
2333
|
-
|
|
2639
|
+
if (fs6.existsSync(libDir(root))) {
|
|
2640
|
+
fs6.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
|
|
2334
2641
|
included.push("lib/");
|
|
2335
2642
|
}
|
|
2336
2643
|
}
|
|
@@ -2342,7 +2649,7 @@ function registerBundle(program) {
|
|
|
2342
2649
|
included,
|
|
2343
2650
|
usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
|
|
2344
2651
|
};
|
|
2345
|
-
|
|
2652
|
+
fs6.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
|
|
2346
2653
|
`);
|
|
2347
2654
|
if (global.json) {
|
|
2348
2655
|
json({ out: path.relative(root, outDir), ...manifest });
|
|
@@ -2358,22 +2665,22 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
2358
2665
|
const rootDir = path.resolve(targetPath);
|
|
2359
2666
|
const vibgrateDir2 = path.join(rootDir, ".vibgrate");
|
|
2360
2667
|
await ensureDir(vibgrateDir2);
|
|
2361
|
-
console.log(
|
|
2668
|
+
console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold(".vibgrate/")} directory`);
|
|
2362
2669
|
const configPath = path.join(rootDir, "vibgrate.config.ts");
|
|
2363
2670
|
if (await pathExists(configPath)) {
|
|
2364
|
-
console.log(
|
|
2671
|
+
console.log(chalk8.dim(" vibgrate.config.ts already exists, skipping"));
|
|
2365
2672
|
} else {
|
|
2366
2673
|
await writeDefaultConfig(rootDir);
|
|
2367
|
-
console.log(
|
|
2674
|
+
console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold("vibgrate.config.ts")}`);
|
|
2368
2675
|
}
|
|
2369
2676
|
if (opts.baseline) {
|
|
2370
|
-
const { runBaseline } = await import('./baseline-
|
|
2677
|
+
const { runBaseline } = await import('./baseline-VGDQ33ZE.js');
|
|
2371
2678
|
await runBaseline(rootDir);
|
|
2372
2679
|
}
|
|
2373
2680
|
console.log("");
|
|
2374
|
-
console.log(
|
|
2375
|
-
console.log(` ${
|
|
2376
|
-
console.log(` ${
|
|
2681
|
+
console.log(chalk8.bold("Next steps:"));
|
|
2682
|
+
console.log(` ${chalk8.cyan("vibgrate scan")} Scan for upgrade drift`);
|
|
2683
|
+
console.log(` ${chalk8.cyan("vibgrate baseline")} Create a drift baseline`);
|
|
2377
2684
|
console.log("");
|
|
2378
2685
|
});
|
|
2379
2686
|
|
|
@@ -2408,22 +2715,22 @@ async function detectAiAssistant(rootDir) {
|
|
|
2408
2715
|
return null;
|
|
2409
2716
|
}
|
|
2410
2717
|
function printAiContextPrompt(detectedAssistant) {
|
|
2411
|
-
const teal2 =
|
|
2412
|
-
const mint2 =
|
|
2718
|
+
const teal2 = chalk8.hex("#3FB0A4");
|
|
2719
|
+
const mint2 = chalk8.hex("#4FE3C1");
|
|
2413
2720
|
const cli = resolveCliInvocation();
|
|
2414
2721
|
const installArgs = detectedAssistant ? `install ${detectedAssistant}` : "install --all";
|
|
2415
2722
|
const installCmd = `${cli} ${installArgs}`;
|
|
2416
|
-
const detectedNote = detectedAssistant ?
|
|
2723
|
+
const detectedNote = detectedAssistant ? chalk8.dim(` (${detectedAssistant} config detected)`) : "";
|
|
2417
2724
|
console.log("");
|
|
2418
2725
|
console.log(teal2(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
2419
|
-
console.log(teal2(" \u2502") + " " + mint2("\u25C6") + " " +
|
|
2726
|
+
console.log(teal2(" \u2502") + " " + mint2("\u25C6") + " " + chalk8.bold.white("Get AI-aware answers in your editor") + " " + teal2("\u2502"));
|
|
2420
2727
|
console.log(teal2(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
2421
2728
|
console.log("");
|
|
2422
|
-
console.log(" " +
|
|
2729
|
+
console.log(" " + chalk8.bold.white(installCmd) + detectedNote);
|
|
2423
2730
|
console.log("");
|
|
2424
|
-
console.log(" " + teal2("\xB7") + " " +
|
|
2425
|
-
console.log(" " + teal2("\xB7") + " " +
|
|
2426
|
-
console.log(" " + teal2("\xB7") + " " +
|
|
2731
|
+
console.log(" " + teal2("\xB7") + " " + chalk8.white("Code map in your assistant") + chalk8.dim(" \u2014 call trees, impact surfaces, import paths"));
|
|
2732
|
+
console.log(" " + teal2("\xB7") + " " + chalk8.white("Offline drift") + chalk8.dim(" \u2014 DriftScore and upgrade priorities, right inside your editor"));
|
|
2733
|
+
console.log(" " + teal2("\xB7") + " " + chalk8.white("Version-correct library docs") + chalk8.dim(" \u2014 pinned to your lockfile, no hallucinated APIs"));
|
|
2427
2734
|
console.log("");
|
|
2428
2735
|
}
|
|
2429
2736
|
|
|
@@ -2434,14 +2741,14 @@ function shouldBuildCodeMap(opts) {
|
|
|
2434
2741
|
async function autoPush(artifact, rootDir, opts) {
|
|
2435
2742
|
const dsn = resolveDsn(opts.dsn);
|
|
2436
2743
|
if (!dsn) {
|
|
2437
|
-
console.error(
|
|
2438
|
-
console.error(
|
|
2744
|
+
console.error(chalk8.red("No DSN provided for push."));
|
|
2745
|
+
console.error(chalk8.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
|
|
2439
2746
|
if (opts.strict) process.exit(1);
|
|
2440
2747
|
return;
|
|
2441
2748
|
}
|
|
2442
2749
|
const parsed = parseDsn(dsn);
|
|
2443
2750
|
if (!parsed) {
|
|
2444
|
-
console.error(
|
|
2751
|
+
console.error(chalk8.red("Invalid DSN format."));
|
|
2445
2752
|
if (opts.strict) process.exit(1);
|
|
2446
2753
|
return;
|
|
2447
2754
|
}
|
|
@@ -2452,7 +2759,7 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
2452
2759
|
try {
|
|
2453
2760
|
host = resolveIngestHost(opts.region);
|
|
2454
2761
|
} catch (e) {
|
|
2455
|
-
console.error(
|
|
2762
|
+
console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
|
|
2456
2763
|
if (opts.strict) process.exit(1);
|
|
2457
2764
|
return;
|
|
2458
2765
|
}
|
|
@@ -2460,7 +2767,7 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
2460
2767
|
const originalSize = JSON.stringify(artifact).length;
|
|
2461
2768
|
const compressedSize = body.length;
|
|
2462
2769
|
const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
|
|
2463
|
-
console.log(
|
|
2770
|
+
console.log(chalk8.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
|
|
2464
2771
|
try {
|
|
2465
2772
|
const { response, host: uploadedHost } = await uploadScanArtifact({
|
|
2466
2773
|
scheme: parsed.scheme,
|
|
@@ -2480,36 +2787,36 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
2480
2787
|
const result = await response.json();
|
|
2481
2788
|
if (result.unchanged) {
|
|
2482
2789
|
console.log(
|
|
2483
|
-
|
|
2790
|
+
chalk8.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
|
|
2484
2791
|
);
|
|
2485
2792
|
if (result.previousIngestId) {
|
|
2486
2793
|
emitIngestIdLine(result.previousIngestId, { unchanged: true });
|
|
2487
2794
|
const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.previousIngestId}`;
|
|
2488
|
-
console.log(
|
|
2795
|
+
console.log(chalk8.dim(` Previous report: ${dashUrl}`));
|
|
2489
2796
|
} else if (opts.strict) {
|
|
2490
|
-
console.error(
|
|
2797
|
+
console.error(chalk8.red("Repository unchanged but no previous ingest id returned."));
|
|
2491
2798
|
process.exit(1);
|
|
2492
2799
|
}
|
|
2493
2800
|
return;
|
|
2494
2801
|
}
|
|
2495
|
-
console.log(
|
|
2802
|
+
console.log(chalk8.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
|
|
2496
2803
|
if (result.ingestId) {
|
|
2497
2804
|
emitIngestIdLine(result.ingestId);
|
|
2498
2805
|
const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.ingestId}`;
|
|
2499
2806
|
const CLEAR_LINE = process.platform === "win32" ? "\x1B[0G\x1B[2K" : "";
|
|
2500
2807
|
console.log("");
|
|
2501
|
-
console.log(CLEAR_LINE +
|
|
2808
|
+
console.log(CLEAR_LINE + chalk8.dim(" Processing continues in the background. Results available shortly."));
|
|
2502
2809
|
console.log("");
|
|
2503
|
-
console.log(CLEAR_LINE +
|
|
2504
|
-
console.log(CLEAR_LINE +
|
|
2505
|
-
console.log(CLEAR_LINE + " " +
|
|
2506
|
-
console.log(CLEAR_LINE +
|
|
2810
|
+
console.log(CLEAR_LINE + chalk8.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2811
|
+
console.log(CLEAR_LINE + chalk8.bold(" \u{1F4CA} View Scan Report"));
|
|
2812
|
+
console.log(CLEAR_LINE + " " + chalk8.underline.cyan(dashUrl));
|
|
2813
|
+
console.log(CLEAR_LINE + chalk8.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2507
2814
|
console.log("");
|
|
2508
2815
|
console.log("");
|
|
2509
2816
|
}
|
|
2510
2817
|
} catch (e) {
|
|
2511
2818
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2512
|
-
console.error(
|
|
2819
|
+
console.error(chalk8.red(`Upload failed: ${msg}`));
|
|
2513
2820
|
if (opts.strict) process.exit(1);
|
|
2514
2821
|
}
|
|
2515
2822
|
}
|
|
@@ -2521,15 +2828,15 @@ function reportStandards(rootDir) {
|
|
|
2521
2828
|
if (!loaded.policy) return;
|
|
2522
2829
|
const violations = checkStandards(loaded.policy, inventory(rootDir).records);
|
|
2523
2830
|
if (violations.length === 0) {
|
|
2524
|
-
console.log(
|
|
2831
|
+
console.log(chalk8.green("\u2714") + " Standards: no banned dependencies in use");
|
|
2525
2832
|
return;
|
|
2526
2833
|
}
|
|
2527
|
-
console.log(
|
|
2834
|
+
console.log(chalk8.red(`
|
|
2528
2835
|
\u26A0 Standards: ${violations.length} banned dependency(ies) in use`));
|
|
2529
2836
|
for (const v of violations) {
|
|
2530
|
-
const fix = v.use ?
|
|
2531
|
-
const why = v.reason ?
|
|
2532
|
-
console.log(` ${
|
|
2837
|
+
const fix = v.use ? chalk8.dim(` \u2192 use ${v.use}`) : "";
|
|
2838
|
+
const why = v.reason ? chalk8.dim(` (${v.reason})`) : "";
|
|
2839
|
+
console.log(` ${chalk8.red("banned")} ${v.ecosystem}:${v.name}${v.installed ? chalk8.dim(` ${v.installed}`) : ""}${fix}${why}`);
|
|
2533
2840
|
}
|
|
2534
2841
|
}
|
|
2535
2842
|
function parseNonNegativeNumber(value, label) {
|
|
@@ -2548,7 +2855,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2548
2855
|
).option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--full", "Comprehensive scan: turns on known-vulnerability detection (= --vulns) and, when a standards policy exists, a banned-dependency report \u2014 on top of drift scoring and the code map").option("--vulns", "Also scan installed dependencies for known vulnerabilities (OSV online, or advisories from --package-manifest when offline)").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--project-scan-timeout <seconds>", "Per-project scan timeout in seconds (default: 180)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").option("--repository-name <name>", "Override the repository name recorded for this scan (defaults to the directory or package.json name)").option("--force", "Always create a fresh scan ingest, even if the repository is unchanged since the last scan (skips the unchanged/reuse optimization). Used by scheduled and dashboard-triggered scans.").option("--no-graph", "Skip building the local code map (the AI/docs index) that scan produces after scoring drift").option("--quiet", "Suppress promotional output (the free-plan tracking panel and the AI Context install prompt); scan results are unaffected").action(async (targetPath, opts) => {
|
|
2549
2856
|
const rootDir = path.resolve(targetPath);
|
|
2550
2857
|
if (!await pathExists$1(rootDir)) {
|
|
2551
|
-
console.error(
|
|
2858
|
+
console.error(chalk8.red(`Path does not exist: ${rootDir}`));
|
|
2552
2859
|
process.exit(1);
|
|
2553
2860
|
}
|
|
2554
2861
|
const hasDsn = !!resolveDsn(opts.dsn);
|
|
@@ -2575,9 +2882,9 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2575
2882
|
planTier = preflight.plan?.tier;
|
|
2576
2883
|
upgradeUrl = preflight.upgradeUrl ?? `https://${dashHostForIngestHost(preflight.ingestHost ?? ingestHost)}/${parsed.workspaceId}`;
|
|
2577
2884
|
if (preflight.vm && !preflight.vm.allowed) {
|
|
2578
|
-
console.error(
|
|
2885
|
+
console.error(chalk8.red(preflight.error ?? "VM meter usage exhausted"));
|
|
2579
2886
|
console.error(
|
|
2580
|
-
|
|
2887
|
+
chalk8.dim(
|
|
2581
2888
|
`VM minutes: ${preflight.vm.used}/${preflight.vm.limit} (${preflight.plan.label} plan) \u2014 enable overages or upgrade your plan.`
|
|
2582
2889
|
)
|
|
2583
2890
|
);
|
|
@@ -2585,25 +2892,25 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2585
2892
|
}
|
|
2586
2893
|
if (preflight.repositories && !preflight.repositories.allowed) {
|
|
2587
2894
|
console.error(
|
|
2588
|
-
|
|
2895
|
+
chalk8.red(
|
|
2589
2896
|
preflight.error ?? `Repository limit reached for the ${preflight.plan.label} plan \u2014 cannot scan a new repository.`
|
|
2590
2897
|
)
|
|
2591
2898
|
);
|
|
2592
2899
|
const max = preflight.repositories.max < 0 ? "unlimited" : String(preflight.repositories.max);
|
|
2593
2900
|
console.error(
|
|
2594
|
-
|
|
2901
|
+
chalk8.dim(
|
|
2595
2902
|
`Repositories: ${preflight.repositories.total}/${max} (${preflight.plan.label} plan) \u2014 archive a repository or upgrade your plan.`
|
|
2596
2903
|
)
|
|
2597
2904
|
);
|
|
2598
2905
|
if (preflight.upgradeUrl) {
|
|
2599
|
-
console.error(
|
|
2906
|
+
console.error(chalk8.dim(` Upgrade: ${preflight.upgradeUrl}`));
|
|
2600
2907
|
}
|
|
2601
2908
|
process.exit(1);
|
|
2602
2909
|
}
|
|
2603
2910
|
if (preflight.status === "error" || !preflight.scans.allowed) {
|
|
2604
|
-
console.error(
|
|
2911
|
+
console.error(chalk8.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
|
|
2605
2912
|
console.error(
|
|
2606
|
-
|
|
2913
|
+
chalk8.dim(
|
|
2607
2914
|
`Credits: ${preflight.scans.used}/${preflight.scans.limit} (${preflight.plan.label} plan)`
|
|
2608
2915
|
)
|
|
2609
2916
|
);
|
|
@@ -2611,27 +2918,27 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2611
2918
|
process.exit(1);
|
|
2612
2919
|
}
|
|
2613
2920
|
console.log(
|
|
2614
|
-
|
|
2921
|
+
chalk8.dim(
|
|
2615
2922
|
`Plan: ${preflight.plan.label} \u2014 scan credits ${preflight.scans.used}/${preflight.scans.limit} this month`
|
|
2616
2923
|
)
|
|
2617
2924
|
);
|
|
2618
2925
|
if (preflight.repository?.unchanged && !opts.force) {
|
|
2619
2926
|
console.log(
|
|
2620
|
-
|
|
2927
|
+
chalk8.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
|
|
2621
2928
|
);
|
|
2622
2929
|
if (preflight.repository.lastIngestId) {
|
|
2623
2930
|
emitIngestIdLine(preflight.repository.lastIngestId, { unchanged: true });
|
|
2624
2931
|
const dashUrl = `https://${dashHostForIngestHost(ingestHost)}/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
|
|
2625
|
-
console.log(
|
|
2932
|
+
console.log(chalk8.dim(` Latest report: ${dashUrl}`));
|
|
2626
2933
|
} else if (opts.strict) {
|
|
2627
|
-
console.error(
|
|
2934
|
+
console.error(chalk8.red("Repository unchanged but no previous ingest id available."));
|
|
2628
2935
|
process.exit(1);
|
|
2629
2936
|
}
|
|
2630
2937
|
return;
|
|
2631
2938
|
}
|
|
2632
2939
|
} catch (e) {
|
|
2633
2940
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2634
|
-
console.error(
|
|
2941
|
+
console.error(chalk8.yellow(`Preflight check failed: ${msg}`));
|
|
2635
2942
|
if (opts.strict) process.exit(1);
|
|
2636
2943
|
}
|
|
2637
2944
|
}
|
|
@@ -2705,24 +3012,24 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2705
3012
|
const hasErrors = artifact.findings.some((f) => f.level === "error");
|
|
2706
3013
|
const hasWarnings = artifact.findings.some((f) => f.level === "warning");
|
|
2707
3014
|
if (opts.failOn === "error" && hasErrors) {
|
|
2708
|
-
console.error(
|
|
3015
|
+
console.error(chalk8.red(`
|
|
2709
3016
|
Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
|
|
2710
3017
|
process.exit(2);
|
|
2711
3018
|
}
|
|
2712
3019
|
if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
|
|
2713
|
-
console.error(
|
|
3020
|
+
console.error(chalk8.red(`
|
|
2714
3021
|
Failing: findings detected at warn level or above.`));
|
|
2715
3022
|
process.exit(2);
|
|
2716
3023
|
}
|
|
2717
3024
|
}
|
|
2718
3025
|
if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
|
|
2719
|
-
console.error(
|
|
3026
|
+
console.error(chalk8.red(`
|
|
2720
3027
|
Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
|
|
2721
3028
|
process.exit(2);
|
|
2722
3029
|
}
|
|
2723
3030
|
if (scanOpts.driftWorseningPercent !== void 0) {
|
|
2724
3031
|
if (artifact.delta === void 0) {
|
|
2725
|
-
console.error(
|
|
3032
|
+
console.error(chalk8.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
|
|
2726
3033
|
process.exit(2);
|
|
2727
3034
|
}
|
|
2728
3035
|
if (artifact.delta > 0) {
|
|
@@ -2730,7 +3037,7 @@ Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget
|
|
|
2730
3037
|
const denominator = Math.max(Math.abs(baselineScore), 1e-4);
|
|
2731
3038
|
const worseningPercent = artifact.delta / denominator * 100;
|
|
2732
3039
|
if (worseningPercent > scanOpts.driftWorseningPercent) {
|
|
2733
|
-
console.error(
|
|
3040
|
+
console.error(chalk8.red(`
|
|
2734
3041
|
Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (threshold ${scanOpts.driftWorseningPercent}%).`));
|
|
2735
3042
|
process.exit(2);
|
|
2736
3043
|
}
|
|
@@ -2808,7 +3115,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
2808
3115
|
function scanStaleness(rootDir, artifactPath) {
|
|
2809
3116
|
let ref;
|
|
2810
3117
|
try {
|
|
2811
|
-
ref =
|
|
3118
|
+
ref = fs6.statSync(artifactPath).mtimeMs;
|
|
2812
3119
|
} catch {
|
|
2813
3120
|
return { stale: false };
|
|
2814
3121
|
}
|
|
@@ -2820,7 +3127,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
2820
3127
|
if (depth > 8 || scanned > MAX_ENTRIES) return;
|
|
2821
3128
|
let entries;
|
|
2822
3129
|
try {
|
|
2823
|
-
entries =
|
|
3130
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2824
3131
|
} catch {
|
|
2825
3132
|
return;
|
|
2826
3133
|
}
|
|
@@ -2835,7 +3142,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
2835
3142
|
const abs = path.join(dir, e.name);
|
|
2836
3143
|
let mtimeMs;
|
|
2837
3144
|
try {
|
|
2838
|
-
mtimeMs =
|
|
3145
|
+
mtimeMs = fs6.statSync(abs).mtimeMs;
|
|
2839
3146
|
} catch {
|
|
2840
3147
|
continue;
|
|
2841
3148
|
}
|
|
@@ -2969,7 +3276,7 @@ function analyzeTree(root, packages) {
|
|
|
2969
3276
|
if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
|
|
2970
3277
|
let entries;
|
|
2971
3278
|
try {
|
|
2972
|
-
entries =
|
|
3279
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2973
3280
|
} catch {
|
|
2974
3281
|
return;
|
|
2975
3282
|
}
|
|
@@ -2987,8 +3294,8 @@ function analyzeTree(root, packages) {
|
|
|
2987
3294
|
const full = path.join(dir, entry.name);
|
|
2988
3295
|
let content;
|
|
2989
3296
|
try {
|
|
2990
|
-
if (
|
|
2991
|
-
content =
|
|
3297
|
+
if (fs6.statSync(full).size > MAX_FILE_BYTES) continue;
|
|
3298
|
+
content = fs6.readFileSync(full, "utf8");
|
|
2992
3299
|
} catch {
|
|
2993
3300
|
continue;
|
|
2994
3301
|
}
|
|
@@ -3061,48 +3368,48 @@ function deltaSummary(delta) {
|
|
|
3061
3368
|
return parts.length ? parts.join(", ") : `${delta.total}`;
|
|
3062
3369
|
}
|
|
3063
3370
|
function riskLabel(score) {
|
|
3064
|
-
if (score <= 15) return
|
|
3065
|
-
if (score <= 40) return
|
|
3066
|
-
return
|
|
3371
|
+
if (score <= 15) return chalk8.green(`${score}/100 low`);
|
|
3372
|
+
if (score <= 40) return chalk8.yellow(`${score}/100 moderate`);
|
|
3373
|
+
return chalk8.red(`${score}/100 high`);
|
|
3067
3374
|
}
|
|
3068
3375
|
function planBlock(plan, recommended) {
|
|
3069
3376
|
const lines = [];
|
|
3070
|
-
const marker = recommended ?
|
|
3071
|
-
lines.push(`${marker} ${
|
|
3377
|
+
const marker = recommended ? chalk8.green("\u25CF recommended") : chalk8.dim("\u25CB");
|
|
3378
|
+
lines.push(`${marker} ${chalk8.bold(plan.label)} \u2014 ${chalk8.dim(plan.description)}`);
|
|
3072
3379
|
const drift = typeof plan.expectedDriftScore === "number" && typeof plan.driftDelta === "number" ? ` \xB7 DriftScore \u2192${plan.expectedDriftScore} (${plan.driftDelta <= 0 ? "" : "+"}${plan.driftDelta})` : "";
|
|
3073
3380
|
lines.push(
|
|
3074
3381
|
` risk ${riskLabel(plan.riskScore)} \xB7 ${plan.upgrades.length} upgrade(s) \xB7 fixes ${deltaSummary(plan.fixes)}${drift}`
|
|
3075
3382
|
);
|
|
3076
3383
|
if (plan.introduces.total) {
|
|
3077
|
-
lines.push(
|
|
3384
|
+
lines.push(chalk8.red(` \u26A0 introduces ${deltaSummary(plan.introduces)} advisory(ies) in target versions`));
|
|
3078
3385
|
}
|
|
3079
3386
|
const shown = plan.upgrades.slice(0, 12);
|
|
3080
3387
|
for (const u of shown) {
|
|
3081
|
-
const codemod = u.playbook?.codemod ?
|
|
3082
|
-
lines.push(` ${
|
|
3388
|
+
const codemod = u.playbook?.codemod ? chalk8.magenta(` [codemod: ${u.playbook.codemod}]`) : "";
|
|
3389
|
+
lines.push(` ${chalk8.cyan(u.package)} ${chalk8.dim(`${u.from ?? "?"} \u2192 ${u.to ?? "?"}`)} ${chalk8.dim(u.reason)}${codemod}`);
|
|
3083
3390
|
}
|
|
3084
3391
|
if (plan.upgrades.length > shown.length) {
|
|
3085
|
-
lines.push(
|
|
3392
|
+
lines.push(chalk8.dim(` \u2026 and ${plan.upgrades.length - shown.length} more`));
|
|
3086
3393
|
}
|
|
3087
3394
|
if (plan.upgrades.length === 0) {
|
|
3088
|
-
lines.push(
|
|
3395
|
+
lines.push(chalk8.dim(" (no upgrades in this plan)"));
|
|
3089
3396
|
}
|
|
3090
3397
|
return lines.join("\n");
|
|
3091
3398
|
}
|
|
3092
3399
|
function dataNote(report) {
|
|
3093
3400
|
if (report.vulnerabilityData === "unavailable") {
|
|
3094
|
-
return
|
|
3401
|
+
return chalk8.dim("Advisory data was unavailable \u2014 vulnerability impact is not shown.");
|
|
3095
3402
|
}
|
|
3096
3403
|
if (report.vulnerabilityData === "partial") {
|
|
3097
|
-
return
|
|
3404
|
+
return chalk8.dim("Advisory data was partial \u2014 some ecosystems were not checked.");
|
|
3098
3405
|
}
|
|
3099
3406
|
return null;
|
|
3100
3407
|
}
|
|
3101
3408
|
function renderText(report) {
|
|
3102
3409
|
const out2 = [];
|
|
3103
|
-
out2.push(
|
|
3410
|
+
out2.push(chalk8.bold("Vibgrate fix \u2014 upgrade plan"));
|
|
3104
3411
|
out2.push(
|
|
3105
|
-
|
|
3412
|
+
chalk8.dim(
|
|
3106
3413
|
`${report.totalCandidates} drifted dependency(ies) analysed \xB7 ${report.deepAnalysis ? "deep (major) analysis on" : "preflight only"}`
|
|
3107
3414
|
)
|
|
3108
3415
|
);
|
|
@@ -3110,18 +3417,18 @@ function renderText(report) {
|
|
|
3110
3417
|
if (note) out2.push(note);
|
|
3111
3418
|
if (report.exploitability && report.exploitability.kevPackages > 0) {
|
|
3112
3419
|
const epss = report.exploitability.maxEpss != null ? `, peak EPSS ${(report.exploitability.maxEpss * 100).toFixed(0)}%` : "";
|
|
3113
|
-
out2.push(
|
|
3420
|
+
out2.push(chalk8.red(`\u26A0 ${report.exploitability.kevPackages} package(s) carry a KNOWN-EXPLOITED (KEV) advisory${epss} \u2014 prioritise these.`));
|
|
3114
3421
|
}
|
|
3115
3422
|
out2.push("");
|
|
3116
3423
|
for (const plan of report.plans) {
|
|
3117
3424
|
out2.push(planBlock(plan, plan.tier === report.recommended));
|
|
3118
3425
|
out2.push("");
|
|
3119
3426
|
}
|
|
3120
|
-
out2.push(
|
|
3121
|
-
out2.push(` ${
|
|
3427
|
+
out2.push(chalk8.bold("Recommendation"));
|
|
3428
|
+
out2.push(` ${chalk8.green(report.plans.find((p) => p.tier === report.recommended)?.label ?? report.recommended)} \u2014 ${report.rationale}`);
|
|
3122
3429
|
if (report.unresolved.total) {
|
|
3123
3430
|
out2.push("");
|
|
3124
|
-
out2.push(
|
|
3431
|
+
out2.push(chalk8.red(`\u26A0 ${deltaSummary(report.unresolved)} advisory(ies) have no upgrade path in any plan.`));
|
|
3125
3432
|
}
|
|
3126
3433
|
return out2.join("\n");
|
|
3127
3434
|
}
|
|
@@ -3193,11 +3500,15 @@ function estimateDriftScore(artifact, upgraded) {
|
|
|
3193
3500
|
}
|
|
3194
3501
|
return computeDriftScore(projects).score;
|
|
3195
3502
|
}
|
|
3503
|
+
function isRootDir(dir) {
|
|
3504
|
+
return dir === "" || dir === ".";
|
|
3505
|
+
}
|
|
3196
3506
|
function pmCommandFor(ecosystem, pkg, version, pm = "npm", opts = {}) {
|
|
3507
|
+
const ws = opts.workspaceRoot && pm === "pnpm" ? ["-w"] : [];
|
|
3197
3508
|
switch (ecosystem) {
|
|
3198
3509
|
case "npm":
|
|
3199
|
-
if (pm === "pnpm") return { cmd: "pnpm", args:
|
|
3200
|
-
if (pm === "yarn") return { cmd: "yarn", args:
|
|
3510
|
+
if (pm === "pnpm") return { cmd: "pnpm", args: ["add", ...ws, `${pkg}@${version}`] };
|
|
3511
|
+
if (pm === "yarn") return { cmd: "yarn", args: ["add", `${pkg}@${version}`] };
|
|
3201
3512
|
if (pm === "bun") return { cmd: "bun", args: ["add", `${pkg}@${version}`] };
|
|
3202
3513
|
return { cmd: "npm", args: ["install", `${pkg}@${version}`] };
|
|
3203
3514
|
case "pypi":
|
|
@@ -3233,46 +3544,232 @@ function defaultRun(cmd, cwd) {
|
|
|
3233
3544
|
if (res.status !== 0) return { ok: false, detail: `exit ${res.status ?? "signal"}` };
|
|
3234
3545
|
return { ok: true };
|
|
3235
3546
|
}
|
|
3236
|
-
|
|
3237
|
-
try {
|
|
3238
|
-
if (existsSync(join(dir, "pnpm-workspace.yaml"))) return true;
|
|
3239
|
-
const pkgPath = join(dir, "package.json");
|
|
3240
|
-
if (existsSync(pkgPath)) {
|
|
3241
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
3242
|
-
if (pkg?.workspaces) return true;
|
|
3243
|
-
}
|
|
3244
|
-
} catch {
|
|
3245
|
-
}
|
|
3246
|
-
return false;
|
|
3247
|
-
}
|
|
3547
|
+
var ROOT_TARGET = [{ dir: ".", isWorkspaceRoot: false }];
|
|
3248
3548
|
function applyPlan(rootDir, upgrades, opts = {}) {
|
|
3249
3549
|
const run = opts.run ?? defaultRun;
|
|
3250
|
-
const
|
|
3550
|
+
const resolveTargets = opts.resolveTargets ?? (() => ROOT_TARGET);
|
|
3251
3551
|
const results = [];
|
|
3252
3552
|
for (const u of upgrades) {
|
|
3253
3553
|
if (!u.to) {
|
|
3254
3554
|
results.push({ package: u.package, to: u.to, status: "skipped", detail: "no target version" });
|
|
3255
3555
|
continue;
|
|
3256
3556
|
}
|
|
3257
|
-
|
|
3258
|
-
if (!command) {
|
|
3557
|
+
if (!pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager)) {
|
|
3259
3558
|
results.push({ package: u.package, to: u.to, status: "manual", detail: `${u.ecosystem}: update the manifest to ${u.to} manually` });
|
|
3260
3559
|
continue;
|
|
3261
3560
|
}
|
|
3561
|
+
const targets = resolveTargets(u);
|
|
3562
|
+
const effective = targets.length > 0 ? targets : ROOT_TARGET;
|
|
3563
|
+
const labelled = effective.length > 1;
|
|
3564
|
+
const where = (t) => labelled || !isRootDir(t.dir) ? `${t.dir || "."}: ` : "";
|
|
3262
3565
|
if (opts.dryRun) {
|
|
3263
|
-
|
|
3566
|
+
const detail = effective.map((t) => {
|
|
3567
|
+
const c2 = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager, { workspaceRoot: t.isWorkspaceRoot });
|
|
3568
|
+
return `would run: ${c2.cmd} ${c2.args.join(" ")}${labelled || !isRootDir(t.dir) ? ` (in ${t.dir || "."})` : ""}`;
|
|
3569
|
+
}).join("; ");
|
|
3570
|
+
results.push({ package: u.package, to: u.to, status: "skipped", detail });
|
|
3264
3571
|
continue;
|
|
3265
3572
|
}
|
|
3266
|
-
const
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
to: u.to,
|
|
3270
|
-
status: outcome.ok ? "applied" : outcome.toolMissing ? "manual" : "failed",
|
|
3271
|
-
detail: outcome.toolMissing && outcome.detail ? `${outcome.detail} \u2014 upgrade manually` : outcome.detail
|
|
3573
|
+
const outcomes = effective.map((t) => {
|
|
3574
|
+
const c2 = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager, { workspaceRoot: t.isWorkspaceRoot });
|
|
3575
|
+
return { t, res: run(c2, path.join(rootDir, t.dir)) };
|
|
3272
3576
|
});
|
|
3577
|
+
const failures = outcomes.filter((o) => !o.res.ok);
|
|
3578
|
+
if (failures.length === 0) {
|
|
3579
|
+
results.push({
|
|
3580
|
+
package: u.package,
|
|
3581
|
+
to: u.to,
|
|
3582
|
+
status: "applied",
|
|
3583
|
+
detail: labelled ? `applied in ${effective.map((t) => t.dir || ".").join(", ")}` : void 0
|
|
3584
|
+
});
|
|
3585
|
+
} else if (failures.every((o) => o.res.toolMissing)) {
|
|
3586
|
+
results.push({
|
|
3587
|
+
package: u.package,
|
|
3588
|
+
to: u.to,
|
|
3589
|
+
status: "manual",
|
|
3590
|
+
detail: failures.map((o) => `${where(o.t)}${o.res.detail ?? "tool missing"} \u2014 upgrade manually`).join("; ")
|
|
3591
|
+
});
|
|
3592
|
+
} else {
|
|
3593
|
+
results.push({
|
|
3594
|
+
package: u.package,
|
|
3595
|
+
to: u.to,
|
|
3596
|
+
status: "failed",
|
|
3597
|
+
detail: failures.map((o) => `${where(o.t)}${o.res.detail ?? "failed"}`).join("; ")
|
|
3598
|
+
});
|
|
3599
|
+
}
|
|
3273
3600
|
}
|
|
3274
3601
|
return results;
|
|
3275
3602
|
}
|
|
3603
|
+
var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
|
|
3604
|
+
var CACHE_DIR = path.join(os.homedir(), ".vibgrate");
|
|
3605
|
+
var CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
|
|
3606
|
+
async function fetchLatestVersion() {
|
|
3607
|
+
try {
|
|
3608
|
+
const controller = new AbortController();
|
|
3609
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
3610
|
+
timeout.unref?.();
|
|
3611
|
+
let response;
|
|
3612
|
+
try {
|
|
3613
|
+
response = await fetch(REGISTRY_URL, {
|
|
3614
|
+
headers: { Accept: "application/json" },
|
|
3615
|
+
signal: controller.signal
|
|
3616
|
+
});
|
|
3617
|
+
} finally {
|
|
3618
|
+
clearTimeout(timeout);
|
|
3619
|
+
}
|
|
3620
|
+
if (!response.ok) return null;
|
|
3621
|
+
const data = await response.json();
|
|
3622
|
+
const latest = data.version;
|
|
3623
|
+
if (!latest || !semver.valid(latest)) return null;
|
|
3624
|
+
await writeCache({ latest, checkedAt: Date.now() });
|
|
3625
|
+
return latest;
|
|
3626
|
+
} catch {
|
|
3627
|
+
return null;
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
async function writeCache(data) {
|
|
3631
|
+
try {
|
|
3632
|
+
await fs15.mkdir(CACHE_DIR, { recursive: true });
|
|
3633
|
+
await fs15.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
3634
|
+
} catch {
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
// src/reporting/commands/update.ts
|
|
3639
|
+
function detectGlobalInstall() {
|
|
3640
|
+
const execPath = process.argv[1] || "";
|
|
3641
|
+
if (execPath.includes("/lib/node_modules/") || execPath.includes("\\node_modules\\")) {
|
|
3642
|
+
if (!execPath.includes(process.cwd())) {
|
|
3643
|
+
if (execPath.includes("pnpm")) return "pnpm";
|
|
3644
|
+
if (execPath.includes("yarn")) return "yarn";
|
|
3645
|
+
if (execPath.includes("bun")) return "bun";
|
|
3646
|
+
return "npm";
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
return null;
|
|
3650
|
+
}
|
|
3651
|
+
function getGlobalUpdateCommand(pm, pkg, version) {
|
|
3652
|
+
const spec = `${pkg}@${version}`;
|
|
3653
|
+
switch (pm) {
|
|
3654
|
+
case "pnpm":
|
|
3655
|
+
return `pnpm add -g ${spec}`;
|
|
3656
|
+
case "yarn":
|
|
3657
|
+
return `yarn global add ${spec}`;
|
|
3658
|
+
case "bun":
|
|
3659
|
+
return `bun add -g ${spec}`;
|
|
3660
|
+
case "npm":
|
|
3661
|
+
default:
|
|
3662
|
+
return `npm install -g ${spec}`;
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
async function detectPackageManager(cwd) {
|
|
3666
|
+
if (await pathExists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
3667
|
+
if (await pathExists(path.join(cwd, "bun.lockb"))) return "bun";
|
|
3668
|
+
if (await pathExists(path.join(cwd, "yarn.lock"))) return "yarn";
|
|
3669
|
+
return "npm";
|
|
3670
|
+
}
|
|
3671
|
+
async function detectWorkspaceRoot(cwd) {
|
|
3672
|
+
if (await pathExists(path.join(cwd, "pnpm-workspace.yaml"))) return true;
|
|
3673
|
+
try {
|
|
3674
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
3675
|
+
const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
|
|
3676
|
+
const pkg = JSON.parse(raw);
|
|
3677
|
+
return pkg.workspaces != null;
|
|
3678
|
+
} catch {
|
|
3679
|
+
return false;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
function getInstallCommand(pm, pkg, version, isDev, opts = {}) {
|
|
3683
|
+
const spec = `${pkg}@${version}`;
|
|
3684
|
+
const ws = opts.workspaceRoot && pm === "pnpm" ? " -w" : "";
|
|
3685
|
+
switch (pm) {
|
|
3686
|
+
case "pnpm":
|
|
3687
|
+
return isDev ? `pnpm add${ws} -D ${spec}` : `pnpm add${ws} ${spec}`;
|
|
3688
|
+
case "yarn":
|
|
3689
|
+
return isDev ? `yarn add --dev ${spec}` : `yarn add ${spec}`;
|
|
3690
|
+
case "bun":
|
|
3691
|
+
return isDev ? `bun add -d ${spec}` : `bun add ${spec}`;
|
|
3692
|
+
case "npm":
|
|
3693
|
+
default:
|
|
3694
|
+
return isDev ? `npm install --save-dev ${spec}` : `npm install ${spec}`;
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
async function isDevDependency(cwd) {
|
|
3698
|
+
try {
|
|
3699
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
3700
|
+
const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
|
|
3701
|
+
const pkg = JSON.parse(raw);
|
|
3702
|
+
return Boolean(pkg.devDependencies?.["@vibgrate/cli"]);
|
|
3703
|
+
} catch {
|
|
3704
|
+
return true;
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
async function confirmWorkspaceRoot(pm) {
|
|
3708
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
|
|
3709
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3710
|
+
return new Promise((resolve16) => {
|
|
3711
|
+
rl.question(chalk8.yellow(`Update the ${pm} workspace root with -w? [y/N]: `), (ans) => {
|
|
3712
|
+
rl.close();
|
|
3713
|
+
const t = ans.trim().toLowerCase();
|
|
3714
|
+
resolve16(t === "y" || t === "yes");
|
|
3715
|
+
});
|
|
3716
|
+
});
|
|
3717
|
+
}
|
|
3718
|
+
var updateCommand = new Command("update").description("Update vibgrate to the latest version").option("--check", "Only check for updates, do not install").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--global", "Update global installation").option("-y, --yes", "Skip confirmation prompts (e.g. installing at a workspace root)").option("-w, --workspace-root", "Allow updating the pnpm workspace root (implies --yes for that prompt)").action(
|
|
3719
|
+
async (opts) => {
|
|
3720
|
+
console.log(chalk8.dim(`Current version: ${VERSION}`));
|
|
3721
|
+
console.log(chalk8.dim("Checking npm registry..."));
|
|
3722
|
+
const latest = await fetchLatestVersion();
|
|
3723
|
+
if (!latest) {
|
|
3724
|
+
console.error(chalk8.red("Could not reach the npm registry. Check your network connection."));
|
|
3725
|
+
process.exit(1);
|
|
3726
|
+
}
|
|
3727
|
+
const semver2 = await import('semver');
|
|
3728
|
+
if (!semver2.gt(latest, VERSION)) {
|
|
3729
|
+
console.log(chalk8.green("\u2714") + ` You are on the latest version (${VERSION}).`);
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
console.log(chalk8.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
|
|
3733
|
+
if (opts.check) {
|
|
3734
|
+
console.log(chalk8.dim('Run "vg update" to install.'));
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
const cwd = process.cwd();
|
|
3738
|
+
const globalPm = detectGlobalInstall();
|
|
3739
|
+
const isGlobal = opts.global || globalPm !== null;
|
|
3740
|
+
const pm = opts.pm || (globalPm ?? await detectPackageManager(cwd));
|
|
3741
|
+
let cmd;
|
|
3742
|
+
if (isGlobal) {
|
|
3743
|
+
cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
|
|
3744
|
+
console.log(chalk8.dim(`Updating global installation with ${pm}: ${cmd}`));
|
|
3745
|
+
} else {
|
|
3746
|
+
const isDev = await isDevDependency(cwd);
|
|
3747
|
+
let workspaceRoot = false;
|
|
3748
|
+
if (pm === "pnpm" && await detectWorkspaceRoot(cwd)) {
|
|
3749
|
+
console.log(
|
|
3750
|
+
chalk8.yellow("Detected a pnpm workspace root.") + chalk8.dim(" Installing here needs the -w flag and updates the root package.json.")
|
|
3751
|
+
);
|
|
3752
|
+
const proceed = opts.yes || opts.workspaceRoot || await confirmWorkspaceRoot(pm);
|
|
3753
|
+
if (!proceed) {
|
|
3754
|
+
const rootCmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot: true });
|
|
3755
|
+
console.log(chalk8.dim(`Skipped. To update the workspace root, run: ${rootCmd}`));
|
|
3756
|
+
console.log(chalk8.dim('or re-run "vg update --yes" (or --workspace-root) to let vg do it.'));
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
workspaceRoot = true;
|
|
3760
|
+
}
|
|
3761
|
+
cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot });
|
|
3762
|
+
console.log(chalk8.dim(`Using ${pm}: ${cmd}`));
|
|
3763
|
+
}
|
|
3764
|
+
try {
|
|
3765
|
+
execSync(cmd, { cwd, stdio: "inherit" });
|
|
3766
|
+
console.log(chalk8.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
|
|
3767
|
+
} catch {
|
|
3768
|
+
console.error(chalk8.red(`Update failed. Run manually: ${cmd}`));
|
|
3769
|
+
process.exit(1);
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
);
|
|
3276
3773
|
|
|
3277
3774
|
// src/reporting/commands/fix.ts
|
|
3278
3775
|
var SEVERITY_RANK = { unknown: 0, low: 1, moderate: 2, high: 3, critical: 4 };
|
|
@@ -3323,12 +3820,12 @@ async function loadArtifact(rootDir, inFile, scan = defaultFixScanner) {
|
|
|
3323
3820
|
return readJsonFile(artifactPath);
|
|
3324
3821
|
}
|
|
3325
3822
|
console.error(
|
|
3326
|
-
|
|
3823
|
+
chalk8.dim(
|
|
3327
3824
|
`Scan is out of date (${staleness.newestChanged ?? "a dependency manifest"} changed since the last scan) \u2014 re-running a drift scan first (code map skipped)\u2026`
|
|
3328
3825
|
)
|
|
3329
3826
|
);
|
|
3330
3827
|
} else {
|
|
3331
|
-
console.error(
|
|
3828
|
+
console.error(chalk8.dim("No scan found \u2014 running a drift scan first (code map skipped)\u2026"));
|
|
3332
3829
|
}
|
|
3333
3830
|
return scan(rootDir);
|
|
3334
3831
|
}
|
|
@@ -3345,20 +3842,20 @@ async function repositoryIdentity(rootDir, override) {
|
|
|
3345
3842
|
var fixCommand = new Command("fix").description("Analyse drift and get ranked, risk-tiered upgrade plans from the hosted planner (requires login/DSN; read-only \u2014 never edits your project)").argument("[path]", "Path to analyse", ".").option("--in <file>", "Scan artifact to read", ".vibgrate/scan_result.json").option("--format <format>", "Output format (text|json|md)", "text").option("--dsn <dsn>", 'DSN token (or use VIBGRATE_DSN env / "vg login")').option("--region <region>", "Override data residency region (us, eu)").option("--repository-name <name>", "Override the repository name recorded for this plan").option("--plan <tier>", "Apply a specific plan non-interactively (safe|balanced|aggressive)").option("--yes", "Apply the recommended plan without prompting").option("--dry-run", "Show what would change without applying").option("--no-apply", "Only print the plans; never modify the project").option("--fail-on-vulns <severity>", "Exit non-zero if the recommended plan leaves an advisory at/above this severity unresolved (low|moderate|high|critical)").action(async (targetPath, opts) => {
|
|
3346
3843
|
const rootDir = path.resolve(targetPath);
|
|
3347
3844
|
if (!await pathExists(rootDir)) {
|
|
3348
|
-
console.error(
|
|
3845
|
+
console.error(chalk8.red(`Path does not exist: ${rootDir}`));
|
|
3349
3846
|
process.exit(1);
|
|
3350
3847
|
}
|
|
3351
3848
|
const failOn = opts.failOnVulns;
|
|
3352
3849
|
if (failOn && !(failOn in SEVERITY_RANK)) {
|
|
3353
|
-
console.error(
|
|
3850
|
+
console.error(chalk8.red(`Invalid --fail-on-vulns value '${opts.failOnVulns}'. Use one of: low, moderate, high, critical.`));
|
|
3354
3851
|
process.exit(1);
|
|
3355
3852
|
}
|
|
3356
3853
|
const dsn = resolveDsn(opts.dsn);
|
|
3357
3854
|
if (!dsn) {
|
|
3358
3855
|
const cli = resolveCliInvocation();
|
|
3359
|
-
console.error(
|
|
3856
|
+
console.error(chalk8.red("vg fix needs a Vibgrate login."));
|
|
3360
3857
|
console.error(
|
|
3361
|
-
|
|
3858
|
+
chalk8.dim(
|
|
3362
3859
|
`Run "${cli} login" (or set VIBGRATE_DSN / pass --dsn) to analyse upgrades with the hosted planner. See https://vibgrate.com/cli for details.`
|
|
3363
3860
|
)
|
|
3364
3861
|
);
|
|
@@ -3366,21 +3863,21 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
|
|
|
3366
3863
|
}
|
|
3367
3864
|
const parsed = parseDsn(dsn);
|
|
3368
3865
|
if (!parsed) {
|
|
3369
|
-
console.error(
|
|
3866
|
+
console.error(chalk8.red('Invalid DSN format. Re-run "vg login" or check VIBGRATE_DSN.'));
|
|
3370
3867
|
process.exit(1);
|
|
3371
3868
|
}
|
|
3372
3869
|
let host;
|
|
3373
3870
|
try {
|
|
3374
3871
|
host = opts.region ? resolveIngestHost(opts.region) : parsed.host;
|
|
3375
3872
|
} catch (e) {
|
|
3376
|
-
console.error(
|
|
3873
|
+
console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
|
|
3377
3874
|
process.exit(1);
|
|
3378
3875
|
}
|
|
3379
3876
|
let artifact;
|
|
3380
3877
|
try {
|
|
3381
3878
|
artifact = await loadArtifact(rootDir, opts.in);
|
|
3382
3879
|
} catch (e) {
|
|
3383
|
-
console.error(
|
|
3880
|
+
console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
|
|
3384
3881
|
process.exit(1);
|
|
3385
3882
|
}
|
|
3386
3883
|
const raw = collectCandidates(artifact);
|
|
@@ -3423,13 +3920,13 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
|
|
|
3423
3920
|
response = await parseFixPlanResponse(httpResponse);
|
|
3424
3921
|
} catch (e) {
|
|
3425
3922
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3426
|
-
console.error(
|
|
3427
|
-
console.error(
|
|
3923
|
+
console.error(chalk8.red(`Could not reach the upgrade planner: ${msg}`));
|
|
3924
|
+
console.error(chalk8.dim("Check your connection and try again, or see https://vibgrate.com/help."));
|
|
3428
3925
|
process.exit(1);
|
|
3429
3926
|
}
|
|
3430
3927
|
if (response.status === "error") {
|
|
3431
|
-
console.error(
|
|
3432
|
-
if (response.requestId) console.error(
|
|
3928
|
+
console.error(chalk8.red(response.error ?? "The planner returned an error."));
|
|
3929
|
+
if (response.requestId) console.error(chalk8.dim(` (ref ${response.requestId})`));
|
|
3433
3930
|
process.exit(1);
|
|
3434
3931
|
}
|
|
3435
3932
|
const currentDrift = artifact.drift?.score;
|
|
@@ -3458,7 +3955,7 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
|
|
|
3458
3955
|
}
|
|
3459
3956
|
if (stillOpen > 0) {
|
|
3460
3957
|
console.error(
|
|
3461
|
-
|
|
3958
|
+
chalk8.red(`
|
|
3462
3959
|
Failing: the recommended plan leaves ${stillOpen} advisory(ies) at/above ${failOn} unresolved.`)
|
|
3463
3960
|
);
|
|
3464
3961
|
process.exit(2);
|
|
@@ -3468,21 +3965,21 @@ Failing: the recommended plan leaves ${stillOpen} advisory(ies) at/above ${failO
|
|
|
3468
3965
|
function handleHttpError(response, workspaceId, host) {
|
|
3469
3966
|
const upgradeUrl = `https://${dashHostForIngestHost(host)}/${workspaceId}`;
|
|
3470
3967
|
if (response.status === 401 || response.status === 403) {
|
|
3471
|
-
console.error(
|
|
3472
|
-
console.error(
|
|
3968
|
+
console.error(chalk8.red("Not authorised. Your DSN may be invalid or lack access to this workspace."));
|
|
3969
|
+
console.error(chalk8.dim('Re-run "vg login" or check VIBGRATE_DSN.'));
|
|
3473
3970
|
process.exit(1);
|
|
3474
3971
|
}
|
|
3475
3972
|
if (response.status === 402) {
|
|
3476
|
-
console.error(
|
|
3477
|
-
console.error(
|
|
3973
|
+
console.error(chalk8.red("vg fix is a paid capability that is not enabled on your current plan."));
|
|
3974
|
+
console.error(chalk8.dim(`Upgrade to enable the hosted upgrade planner: ${upgradeUrl}`));
|
|
3478
3975
|
process.exit(1);
|
|
3479
3976
|
}
|
|
3480
3977
|
if (response.status === 429) {
|
|
3481
|
-
console.error(
|
|
3978
|
+
console.error(chalk8.red("Rate limited by the planner. Wait a moment and try again."));
|
|
3482
3979
|
process.exit(1);
|
|
3483
3980
|
}
|
|
3484
|
-
console.error(
|
|
3485
|
-
console.error(
|
|
3981
|
+
console.error(chalk8.red(`The planner returned HTTP ${response.status}.`));
|
|
3982
|
+
console.error(chalk8.dim("Try again shortly, or see https://vibgrate.com/help."));
|
|
3486
3983
|
process.exit(1);
|
|
3487
3984
|
}
|
|
3488
3985
|
function emit(report, format) {
|
|
@@ -3499,6 +3996,28 @@ function emit(report, format) {
|
|
|
3499
3996
|
break;
|
|
3500
3997
|
}
|
|
3501
3998
|
}
|
|
3999
|
+
function relProjectDir(rootDir, projectPath) {
|
|
4000
|
+
const rel3 = path.isAbsolute(projectPath) ? path.relative(rootDir, projectPath) : projectPath;
|
|
4001
|
+
return rel3 === "" ? "." : rel3;
|
|
4002
|
+
}
|
|
4003
|
+
async function buildTargetResolver(rootDir, artifact) {
|
|
4004
|
+
const rootIsWorkspace = await detectWorkspaceRoot(rootDir);
|
|
4005
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4006
|
+
for (const project of artifact.projects ?? []) {
|
|
4007
|
+
const ecosystem = ecosystemId(project.type);
|
|
4008
|
+
const dir = relProjectDir(rootDir, project.path);
|
|
4009
|
+
for (const dep of project.dependencies ?? []) {
|
|
4010
|
+
const key = `${ecosystem}\0${dep.package}`;
|
|
4011
|
+
(owners.get(key) ?? owners.set(key, /* @__PURE__ */ new Set()).get(key)).add(dir);
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
const rootFallback = [{ dir: ".", isWorkspaceRoot: rootIsWorkspace }];
|
|
4015
|
+
return (upgrade) => {
|
|
4016
|
+
const dirs = owners.get(`${upgrade.ecosystem}\0${upgrade.package}`);
|
|
4017
|
+
if (!dirs || dirs.size === 0) return rootFallback;
|
|
4018
|
+
return [...dirs].sort((a, b) => a.localeCompare(b)).map((dir) => ({ dir, isWorkspaceRoot: dir === "." && rootIsWorkspace }));
|
|
4019
|
+
};
|
|
4020
|
+
}
|
|
3502
4021
|
function npmPackageManager(artifact) {
|
|
3503
4022
|
for (const p of artifact.projects ?? []) {
|
|
3504
4023
|
if ((p.type === "node" || p.type === "typescript") && p.packageManager) {
|
|
@@ -3508,51 +4027,13 @@ function npmPackageManager(artifact) {
|
|
|
3508
4027
|
}
|
|
3509
4028
|
return "npm";
|
|
3510
4029
|
}
|
|
3511
|
-
function toNpmPackageManager(pm) {
|
|
3512
|
-
return pm === "pnpm" || pm === "yarn" || pm === "bun" ? pm : "npm";
|
|
3513
|
-
}
|
|
3514
|
-
function projectsDeclaring(rootDir, artifact, pkg, ecosystem) {
|
|
3515
|
-
const out2 = [];
|
|
3516
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3517
|
-
for (const project of artifact.projects ?? []) {
|
|
3518
|
-
if (ecosystemId(project.type) !== ecosystem) continue;
|
|
3519
|
-
if (!(project.dependencies ?? []).some((d) => d.package === pkg)) continue;
|
|
3520
|
-
const dir = path.resolve(rootDir, project.path && project.path !== "." ? project.path : ".");
|
|
3521
|
-
if (seen.has(dir)) continue;
|
|
3522
|
-
seen.add(dir);
|
|
3523
|
-
out2.push({ dir, pm: toNpmPackageManager(project.packageManager) });
|
|
3524
|
-
}
|
|
3525
|
-
return out2;
|
|
3526
|
-
}
|
|
3527
|
-
function applyChosenPlan(rootDir, artifact, upgrades, opts) {
|
|
3528
|
-
const fallbackPm = npmPackageManager(artifact);
|
|
3529
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3530
|
-
for (const u of upgrades) {
|
|
3531
|
-
let targets = projectsDeclaring(rootDir, artifact, u.package, u.ecosystem);
|
|
3532
|
-
if (targets.length === 0) targets = [{ dir: rootDir, pm: fallbackPm }];
|
|
3533
|
-
for (const t of targets) {
|
|
3534
|
-
const group = groups.get(t.dir) ?? { dir: t.dir, pm: t.pm, upgrades: [] };
|
|
3535
|
-
group.upgrades.push(u);
|
|
3536
|
-
groups.set(t.dir, group);
|
|
3537
|
-
}
|
|
3538
|
-
}
|
|
3539
|
-
const results = [];
|
|
3540
|
-
for (const group of groups.values()) {
|
|
3541
|
-
const applied = applyPlan(group.dir, group.upgrades, { dryRun: opts.dryRun, packageManager: group.pm });
|
|
3542
|
-
const rel3 = path.relative(rootDir, group.dir) || ".";
|
|
3543
|
-
for (const r of applied) {
|
|
3544
|
-
results.push(rel3 === "." ? r : { ...r, detail: [r.detail, `in ${rel3}`].filter(Boolean).join(" \xB7 ") });
|
|
3545
|
-
}
|
|
3546
|
-
}
|
|
3547
|
-
return results;
|
|
3548
|
-
}
|
|
3549
4030
|
function promptPlanSelection(response) {
|
|
3550
4031
|
const plans = response.plans.filter((p) => p.upgrades.length > 0);
|
|
3551
|
-
console.log(
|
|
4032
|
+
console.log(chalk8.bold("\nSelect a plan to apply:"));
|
|
3552
4033
|
plans.forEach((p, i) => {
|
|
3553
|
-
const rec = p.tier === response.recommended ?
|
|
4034
|
+
const rec = p.tier === response.recommended ? chalk8.green(" (recommended)") : "";
|
|
3554
4035
|
const drift = typeof p.expectedDriftScore === "number" && typeof response.currentDriftScore === "number" ? ` \xB7 DriftScore ${response.currentDriftScore}\u2192${p.expectedDriftScore}` : "";
|
|
3555
|
-
console.log(` ${i + 1}) ${
|
|
4036
|
+
console.log(` ${i + 1}) ${chalk8.bold(p.label)}${rec} risk ${p.riskScore}/100 \xB7 ${p.upgrades.length} upgrade(s)${drift}`);
|
|
3556
4037
|
});
|
|
3557
4038
|
const defaultIdx = plans.findIndex((p) => p.tier === response.recommended);
|
|
3558
4039
|
const def = defaultIdx >= 0 ? defaultIdx + 1 : 1;
|
|
@@ -3571,14 +4052,14 @@ function promptPlanSelection(response) {
|
|
|
3571
4052
|
async function runApplyFlow(rootDir, artifact, response, opts) {
|
|
3572
4053
|
const nonEmpty = response.plans.filter((p) => p.upgrades.length > 0);
|
|
3573
4054
|
if (nonEmpty.length === 0) {
|
|
3574
|
-
console.log(
|
|
4055
|
+
console.log(chalk8.green("\n\u2714 Nothing to upgrade \u2014 every tracked dependency is current."));
|
|
3575
4056
|
return;
|
|
3576
4057
|
}
|
|
3577
4058
|
let chosen;
|
|
3578
4059
|
if (opts.plan) {
|
|
3579
4060
|
chosen = response.plans.find((p) => p.tier === opts.plan);
|
|
3580
4061
|
if (!chosen) {
|
|
3581
|
-
console.error(
|
|
4062
|
+
console.error(chalk8.red(`Unknown plan '${opts.plan}'. Use safe, balanced, or aggressive.`));
|
|
3582
4063
|
process.exit(1);
|
|
3583
4064
|
}
|
|
3584
4065
|
} else if (nonEmpty.length === 1) {
|
|
@@ -3588,41 +4069,43 @@ async function runApplyFlow(rootDir, artifact, response, opts) {
|
|
|
3588
4069
|
} else if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
3589
4070
|
const tier = await promptPlanSelection(response);
|
|
3590
4071
|
if (!tier) {
|
|
3591
|
-
console.log(
|
|
4072
|
+
console.log(chalk8.dim("No plan applied."));
|
|
3592
4073
|
return;
|
|
3593
4074
|
}
|
|
3594
4075
|
chosen = response.plans.find((p) => p.tier === tier);
|
|
3595
4076
|
} else {
|
|
3596
|
-
console.log(
|
|
4077
|
+
console.log(chalk8.dim("\nMultiple plans available \u2014 re-run with --plan <tier> or --yes to apply, or --dry-run to preview."));
|
|
3597
4078
|
return;
|
|
3598
4079
|
}
|
|
3599
4080
|
if (!chosen || chosen.upgrades.length === 0) {
|
|
3600
|
-
console.log(
|
|
4081
|
+
console.log(chalk8.dim("Selected plan has no upgrades."));
|
|
3601
4082
|
return;
|
|
3602
4083
|
}
|
|
3603
|
-
const
|
|
4084
|
+
const pm = npmPackageManager(artifact);
|
|
4085
|
+
const resolveTargets = await buildTargetResolver(rootDir, artifact);
|
|
4086
|
+
const results = applyPlan(rootDir, chosen.upgrades, { dryRun: opts.dryRun, packageManager: pm, resolveTargets });
|
|
3604
4087
|
if (opts.dryRun) {
|
|
3605
|
-
console.log(
|
|
4088
|
+
console.log(chalk8.bold(`
|
|
3606
4089
|
Dry run \u2014 ${chosen.label} plan (${chosen.upgrades.length} upgrade(s)):`));
|
|
3607
4090
|
for (const r of results) {
|
|
3608
|
-
if (r.status === "manual") console.log(
|
|
3609
|
-
else console.log(
|
|
4091
|
+
if (r.status === "manual") console.log(chalk8.yellow(` \u26A0 ${r.package}: ${r.detail}`));
|
|
4092
|
+
else console.log(chalk8.dim(` ${r.package}: ${r.detail}`));
|
|
3610
4093
|
}
|
|
3611
4094
|
if (typeof chosen.expectedDriftScore === "number") {
|
|
3612
|
-
console.log(
|
|
4095
|
+
console.log(chalk8.dim(`Expected DriftScore after apply: ~${chosen.expectedDriftScore} (was ${response.currentDriftScore ?? "?"}).`));
|
|
3613
4096
|
}
|
|
3614
4097
|
return;
|
|
3615
4098
|
}
|
|
3616
4099
|
const applied = results.filter((r) => r.status === "applied");
|
|
3617
4100
|
const failed = results.filter((r) => r.status === "failed");
|
|
3618
4101
|
const manual = results.filter((r) => r.status === "manual");
|
|
3619
|
-
console.log(
|
|
4102
|
+
console.log(chalk8.bold(`
|
|
3620
4103
|
Applied the ${chosen.label} plan:`));
|
|
3621
4104
|
console.log(
|
|
3622
|
-
|
|
4105
|
+
chalk8.green(` \u2714 ${applied.length} upgraded`) + (failed.length ? chalk8.red(` \u2716 ${failed.length} failed`) : "") + (manual.length ? chalk8.yellow(` \u26A0 ${manual.length} need manual work`) : "")
|
|
3623
4106
|
);
|
|
3624
|
-
for (const r of failed) console.log(
|
|
3625
|
-
for (const r of manual) console.log(
|
|
4107
|
+
for (const r of failed) console.log(chalk8.red(` \u2716 ${r.package} \u2192 ${r.to}: ${r.detail ?? "failed"}`));
|
|
4108
|
+
for (const r of manual) console.log(chalk8.yellow(` \u26A0 ${r.package} \u2192 ${r.to}: ${r.detail}`));
|
|
3626
4109
|
if (typeof chosen.expectedDriftScore === "number" && typeof response.currentDriftScore === "number") {
|
|
3627
4110
|
const delta = chosen.expectedDriftScore - response.currentDriftScore;
|
|
3628
4111
|
console.log(
|
|
@@ -3633,20 +4116,20 @@ Applied the ${chosen.label} plan:`));
|
|
|
3633
4116
|
}
|
|
3634
4117
|
function formatText(artifact) {
|
|
3635
4118
|
const lines = [];
|
|
3636
|
-
const teal2 =
|
|
3637
|
-
const mint2 =
|
|
4119
|
+
const teal2 = chalk8.hex("#3FB0A4");
|
|
4120
|
+
const mint2 = chalk8.hex("#4FE3C1");
|
|
3638
4121
|
lines.push("");
|
|
3639
4122
|
lines.push(" " + teal2("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E") + mint2("\u279C"));
|
|
3640
|
-
lines.push(" " +
|
|
3641
|
-
lines.push(" " +
|
|
4123
|
+
lines.push(" " + chalk8.dim("\u2524") + teal2("\u2502") + " " + mint2("\u25FC") + " " + mint2("\u25FC") + " " + teal2("\u2502") + chalk8.dim("\u251C") + " " + chalk8.bold.white("vibgrate"));
|
|
4124
|
+
lines.push(" " + chalk8.dim("\u2524") + teal2("\u2502") + " " + chalk8.dim("\u2581\u2581") + " " + teal2("\u2502") + chalk8.dim("\u251C") + " " + chalk8.dim(`Code Intelligence Engine v${VERSION}`));
|
|
3642
4125
|
lines.push(" " + teal2("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
3643
4126
|
lines.push("");
|
|
3644
4127
|
lines.push(...titleBox("Vibgrate Drift Report", teal2));
|
|
3645
4128
|
lines.push("");
|
|
3646
4129
|
for (const project of artifact.projects) {
|
|
3647
|
-
lines.push(
|
|
4130
|
+
lines.push(chalk8.bold(` \u2500\u2500 ${project.name} `) + chalk8.dim(`(${project.type}) ${project.path}`));
|
|
3648
4131
|
if (project.runtime) {
|
|
3649
|
-
const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ?
|
|
4132
|
+
const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk8.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk8.green(" (current)");
|
|
3650
4133
|
lines.push(` Runtime: ${project.runtime}${behindStr}`);
|
|
3651
4134
|
}
|
|
3652
4135
|
if (project.targetFramework) {
|
|
@@ -3655,7 +4138,7 @@ function formatText(artifact) {
|
|
|
3655
4138
|
if (project.frameworks.length > 0) {
|
|
3656
4139
|
lines.push(" Frameworks:");
|
|
3657
4140
|
for (const fw of project.frameworks) {
|
|
3658
|
-
const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ?
|
|
4141
|
+
const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk8.green("current") : chalk8.yellow(`${fw.majorsBehind} behind`) : chalk8.dim("unknown");
|
|
3659
4142
|
lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
|
|
3660
4143
|
}
|
|
3661
4144
|
}
|
|
@@ -3663,13 +4146,13 @@ function formatText(artifact) {
|
|
|
3663
4146
|
const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
|
|
3664
4147
|
if (total > 0) {
|
|
3665
4148
|
lines.push(" Dependencies:");
|
|
3666
|
-
lines.push(` ${
|
|
4149
|
+
lines.push(` ${chalk8.green(`${b.current} current`)} ${chalk8.yellow(`${b.oneBehind} 1-behind`)} ${chalk8.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk8.dim(`${b.unknown} unknown`)}`);
|
|
3667
4150
|
}
|
|
3668
4151
|
lines.push("");
|
|
3669
4152
|
}
|
|
3670
4153
|
if (artifact.delta !== void 0) {
|
|
3671
|
-
const deltaStr = artifact.delta > 0 ?
|
|
3672
|
-
lines.push(
|
|
4154
|
+
const deltaStr = artifact.delta > 0 ? chalk8.red(`+${artifact.delta}`) : artifact.delta < 0 ? chalk8.green(`${artifact.delta}`) : chalk8.dim("0");
|
|
4155
|
+
lines.push(chalk8.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
|
|
3673
4156
|
lines.push("");
|
|
3674
4157
|
}
|
|
3675
4158
|
if (artifact.extended) {
|
|
@@ -3680,15 +4163,15 @@ function formatText(artifact) {
|
|
|
3680
4163
|
const warnings = artifact.findings.filter((f) => f.level === "warning");
|
|
3681
4164
|
const notes = artifact.findings.filter((f) => f.level === "note");
|
|
3682
4165
|
const summary = [
|
|
3683
|
-
errors.length > 0 ?
|
|
3684
|
-
warnings.length > 0 ?
|
|
3685
|
-
notes.length > 0 ?
|
|
3686
|
-
].filter(Boolean).join(
|
|
3687
|
-
lines.push(
|
|
4166
|
+
errors.length > 0 ? chalk8.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
|
|
4167
|
+
warnings.length > 0 ? chalk8.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
|
|
4168
|
+
notes.length > 0 ? chalk8.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
|
|
4169
|
+
].filter(Boolean).join(chalk8.dim(", "));
|
|
4170
|
+
lines.push(chalk8.bold.underline(` Findings`) + chalk8.dim(` (${summary})`));
|
|
3688
4171
|
for (const f of artifact.findings) {
|
|
3689
|
-
const icon = f.level === "error" ?
|
|
4172
|
+
const icon = f.level === "error" ? chalk8.red("\u2716") : f.level === "warning" ? chalk8.yellow("\u26A0") : chalk8.blue("\u2139");
|
|
3690
4173
|
lines.push(` ${icon} ${f.message}`);
|
|
3691
|
-
lines.push(
|
|
4174
|
+
lines.push(chalk8.dim(` ${f.ruleId} in ${f.location}`));
|
|
3692
4175
|
}
|
|
3693
4176
|
lines.push("");
|
|
3694
4177
|
}
|
|
@@ -3698,10 +4181,10 @@ function formatText(artifact) {
|
|
|
3698
4181
|
lines.push("");
|
|
3699
4182
|
for (let i = 0; i < actions.length; i++) {
|
|
3700
4183
|
const a = actions[i];
|
|
3701
|
-
const num =
|
|
3702
|
-
lines.push(`${num} ${
|
|
3703
|
-
lines.push(
|
|
3704
|
-
if (a.impact) lines.push(` Impact: ${
|
|
4184
|
+
const num = chalk8.bold.cyan(` ${i + 1}.`);
|
|
4185
|
+
lines.push(`${num} ${chalk8.bold(a.title)}`);
|
|
4186
|
+
lines.push(chalk8.dim(` ${a.explanation}`));
|
|
4187
|
+
if (a.impact) lines.push(` Impact: ${chalk8.green(a.impact)}`);
|
|
3705
4188
|
lines.push("");
|
|
3706
4189
|
}
|
|
3707
4190
|
}
|
|
@@ -3713,30 +4196,30 @@ function formatText(artifact) {
|
|
|
3713
4196
|
lines.push("");
|
|
3714
4197
|
for (const solution of artifact.solutions) {
|
|
3715
4198
|
const solScore = solution.drift?.score;
|
|
3716
|
-
const color = typeof solScore === "number" ? solScore <= 30 ?
|
|
3717
|
-
lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) :
|
|
4199
|
+
const color = typeof solScore === "number" ? solScore <= 30 ? chalk8.green : solScore <= 60 ? chalk8.yellow : chalk8.red : chalk8.dim;
|
|
4200
|
+
lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk8.dim("n/a")}`);
|
|
3718
4201
|
}
|
|
3719
4202
|
lines.push("");
|
|
3720
4203
|
}
|
|
3721
|
-
const scoreColor = artifact.drift.score <= 30 ?
|
|
4204
|
+
const scoreColor = artifact.drift.score <= 30 ? chalk8.green : artifact.drift.score <= 60 ? chalk8.yellow : chalk8.red;
|
|
3722
4205
|
lines.push(...titleBox("DriftScore Summary"));
|
|
3723
4206
|
lines.push("");
|
|
3724
|
-
lines.push(
|
|
3725
|
-
lines.push(
|
|
3726
|
-
lines.push(
|
|
4207
|
+
lines.push(chalk8.bold(" DriftScore: ") + scoreColor.bold(`${artifact.drift.score}/100`));
|
|
4208
|
+
lines.push(chalk8.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
|
|
4209
|
+
lines.push(chalk8.bold(" Projects: ") + `${artifact.projects.length}`);
|
|
3727
4210
|
if (artifact.vcs) {
|
|
3728
4211
|
const vcsParts = [artifact.vcs.type];
|
|
3729
4212
|
if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
|
|
3730
|
-
if (artifact.vcs.shortSha) vcsParts.push(
|
|
3731
|
-
lines.push(
|
|
4213
|
+
if (artifact.vcs.shortSha) vcsParts.push(chalk8.dim(artifact.vcs.shortSha));
|
|
4214
|
+
lines.push(chalk8.bold(" VCS: ") + vcsParts.join(" "));
|
|
3732
4215
|
}
|
|
3733
4216
|
lines.push("");
|
|
3734
4217
|
const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
|
|
3735
|
-
lines.push(" " +
|
|
3736
|
-
lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) :
|
|
3737
|
-
lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) :
|
|
3738
|
-
lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) :
|
|
3739
|
-
lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) :
|
|
4218
|
+
lines.push(" " + chalk8.bold.underline("Score Breakdown"));
|
|
4219
|
+
lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk8.dim("n/a")}`);
|
|
4220
|
+
lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk8.dim("n/a")}`);
|
|
4221
|
+
lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk8.dim("n/a")}`);
|
|
4222
|
+
lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk8.dim("n/a")}`);
|
|
3740
4223
|
lines.push("");
|
|
3741
4224
|
const scannedParts = [`Scanned at ${artifact.timestamp}`];
|
|
3742
4225
|
if (artifact.durationMs !== void 0) {
|
|
@@ -3750,18 +4233,18 @@ function formatText(artifact) {
|
|
|
3750
4233
|
scannedParts.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files`);
|
|
3751
4234
|
scannedParts.push(`${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
|
|
3752
4235
|
}
|
|
3753
|
-
lines.push(
|
|
4236
|
+
lines.push(chalk8.dim(` ${scannedParts.join(" \xB7 ")}`));
|
|
3754
4237
|
lines.push("");
|
|
3755
4238
|
return lines.join("\n");
|
|
3756
4239
|
}
|
|
3757
4240
|
function riskBadge(level) {
|
|
3758
4241
|
switch (level) {
|
|
3759
4242
|
case "low":
|
|
3760
|
-
return
|
|
4243
|
+
return chalk8.bgGreen.black(" LOW ");
|
|
3761
4244
|
case "moderate":
|
|
3762
|
-
return
|
|
4245
|
+
return chalk8.bgYellow.black(" MODERATE ");
|
|
3763
4246
|
case "high":
|
|
3764
|
-
return
|
|
4247
|
+
return chalk8.bgRed.white(" HIGH ");
|
|
3765
4248
|
default:
|
|
3766
4249
|
return level;
|
|
3767
4250
|
}
|
|
@@ -3796,11 +4279,11 @@ function formatExtended(ext) {
|
|
|
3796
4279
|
const inv = ext.toolingInventory;
|
|
3797
4280
|
const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
|
|
3798
4281
|
if (categories.length > 0) {
|
|
3799
|
-
lines.push(
|
|
4282
|
+
lines.push(chalk8.bold.underline(" Tech Stack"));
|
|
3800
4283
|
for (const [cat, items] of categories) {
|
|
3801
4284
|
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
3802
|
-
const names = items.map((i) =>
|
|
3803
|
-
lines.push(` ${
|
|
4285
|
+
const names = items.map((i) => chalk8.white(i.name)).join(chalk8.dim(", "));
|
|
4286
|
+
lines.push(` ${chalk8.cyan(label)}: ${names}`);
|
|
3804
4287
|
}
|
|
3805
4288
|
lines.push("");
|
|
3806
4289
|
}
|
|
@@ -3809,14 +4292,14 @@ function formatExtended(ext) {
|
|
|
3809
4292
|
const svc = ext.serviceDependencies;
|
|
3810
4293
|
const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
|
|
3811
4294
|
if (categories.length > 0) {
|
|
3812
|
-
lines.push(
|
|
4295
|
+
lines.push(chalk8.bold.underline(" Services & Integrations"));
|
|
3813
4296
|
for (const [cat, items] of categories) {
|
|
3814
4297
|
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
3815
4298
|
const names = items.map((i) => {
|
|
3816
|
-
const ver = i.version ?
|
|
3817
|
-
return
|
|
3818
|
-
}).join(
|
|
3819
|
-
lines.push(` ${
|
|
4299
|
+
const ver = i.version ? chalk8.dim(` ${i.version}`) : "";
|
|
4300
|
+
return chalk8.white(i.name) + ver;
|
|
4301
|
+
}).join(chalk8.dim(", "));
|
|
4302
|
+
lines.push(` ${chalk8.cyan(label)}: ${names}`);
|
|
3820
4303
|
}
|
|
3821
4304
|
lines.push("");
|
|
3822
4305
|
}
|
|
@@ -3824,19 +4307,19 @@ function formatExtended(ext) {
|
|
|
3824
4307
|
if (ext.breakingChangeExposure) {
|
|
3825
4308
|
const bc = ext.breakingChangeExposure;
|
|
3826
4309
|
if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
|
|
3827
|
-
lines.push(
|
|
3828
|
-
const exposureColor = bc.exposureScore >= 40 ?
|
|
4310
|
+
lines.push(chalk8.bold.underline(" Breaking Change Exposure"));
|
|
4311
|
+
const exposureColor = bc.exposureScore >= 40 ? chalk8.red : bc.exposureScore >= 20 ? chalk8.yellow : chalk8.green;
|
|
3829
4312
|
lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
|
|
3830
4313
|
if (bc.deprecatedPackages.length > 0) {
|
|
3831
|
-
lines.push(` ${
|
|
4314
|
+
lines.push(` ${chalk8.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk8.dim(p)).join(", ")}`);
|
|
3832
4315
|
}
|
|
3833
4316
|
if (bc.legacyPolyfills.length > 0) {
|
|
3834
|
-
lines.push(` ${
|
|
4317
|
+
lines.push(` ${chalk8.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk8.dim(p)).join(", ")}`);
|
|
3835
4318
|
}
|
|
3836
4319
|
if (bc.peerConflictsDetected) {
|
|
3837
|
-
lines.push(` ${
|
|
4320
|
+
lines.push(` ${chalk8.red("\u26A0")} Peer dependency conflicts detected`);
|
|
3838
4321
|
}
|
|
3839
|
-
lines.push(` Recommendation: ${
|
|
4322
|
+
lines.push(` Recommendation: ${chalk8.bold(bc.overallRecommendation)}`);
|
|
3840
4323
|
const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
|
|
3841
4324
|
if (projectsWithPlans.length > 0) {
|
|
3842
4325
|
lines.push(" Major Upgrade Intelligence:");
|
|
@@ -3852,21 +4335,21 @@ function formatExtended(ext) {
|
|
|
3852
4335
|
}
|
|
3853
4336
|
if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
|
|
3854
4337
|
const ts = ext.tsModernity;
|
|
3855
|
-
lines.push(
|
|
4338
|
+
lines.push(chalk8.bold.underline(" TypeScript"));
|
|
3856
4339
|
const parts = [];
|
|
3857
4340
|
parts.push(`v${ts.typescriptVersion}`);
|
|
3858
|
-
if (ts.strict === true) parts.push(
|
|
3859
|
-
else if (ts.strict === false) parts.push(
|
|
4341
|
+
if (ts.strict === true) parts.push(chalk8.green("strict \u2714"));
|
|
4342
|
+
else if (ts.strict === false) parts.push(chalk8.yellow("strict \u2716"));
|
|
3860
4343
|
if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
|
|
3861
4344
|
if (ts.target) parts.push(`target: ${ts.target}`);
|
|
3862
|
-
lines.push(` ${parts.join(
|
|
4345
|
+
lines.push(` ${parts.join(chalk8.dim(" \xB7 "))}`);
|
|
3863
4346
|
lines.push("");
|
|
3864
4347
|
}
|
|
3865
4348
|
if (ext.buildDeploy) {
|
|
3866
4349
|
const bd = ext.buildDeploy;
|
|
3867
4350
|
const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
|
|
3868
4351
|
if (hasSomething) {
|
|
3869
|
-
lines.push(
|
|
4352
|
+
lines.push(chalk8.bold.underline(" Build & Deploy"));
|
|
3870
4353
|
if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
|
|
3871
4354
|
if (bd.docker.dockerfileCount > 0) {
|
|
3872
4355
|
lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
|
|
@@ -3879,42 +4362,42 @@ function formatExtended(ext) {
|
|
|
3879
4362
|
}
|
|
3880
4363
|
if (ext.uiPurpose) {
|
|
3881
4364
|
const up = ext.uiPurpose;
|
|
3882
|
-
lines.push(
|
|
3883
|
-
lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") :
|
|
3884
|
-
lines.push(` Evidence: ${up.topEvidence.length}${up.capped ?
|
|
4365
|
+
lines.push(chalk8.bold.underline(" Product Purpose Signals"));
|
|
4366
|
+
lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk8.dim("unknown")}`);
|
|
4367
|
+
lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk8.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
|
|
3885
4368
|
const top = up.topEvidence.slice(0, 8);
|
|
3886
4369
|
if (top.length > 0) {
|
|
3887
4370
|
lines.push(" Top Signals:");
|
|
3888
4371
|
for (const item of top) {
|
|
3889
|
-
lines.push(` - [${item.kind}] ${item.value} ${
|
|
4372
|
+
lines.push(` - [${item.kind}] ${item.value} ${chalk8.dim(`(${item.file})`)}`);
|
|
3890
4373
|
}
|
|
3891
4374
|
}
|
|
3892
4375
|
if (up.unknownSignals.length > 0) {
|
|
3893
4376
|
lines.push(" Unknowns:");
|
|
3894
4377
|
for (const u of up.unknownSignals.slice(0, 4)) {
|
|
3895
|
-
lines.push(` - ${
|
|
4378
|
+
lines.push(` - ${chalk8.yellow(u)}`);
|
|
3896
4379
|
}
|
|
3897
4380
|
}
|
|
3898
4381
|
lines.push("");
|
|
3899
4382
|
}
|
|
3900
4383
|
if (ext.securityPosture) {
|
|
3901
4384
|
const sec = ext.securityPosture;
|
|
3902
|
-
lines.push(
|
|
4385
|
+
lines.push(chalk8.bold.underline(" Security Posture"));
|
|
3903
4386
|
const checks = [];
|
|
3904
|
-
checks.push(sec.lockfilePresent ?
|
|
3905
|
-
checks.push(sec.gitignoreCoversEnv ?
|
|
3906
|
-
checks.push(sec.gitignoreCoversNodeModules ?
|
|
3907
|
-
if (sec.multipleLockfileTypes) checks.push(
|
|
3908
|
-
if (sec.envFilesTracked) checks.push(
|
|
3909
|
-
lines.push(` ${checks.join(
|
|
4387
|
+
checks.push(sec.lockfilePresent ? chalk8.green("Lockfile \u2714") : chalk8.red("Lockfile \u2716"));
|
|
4388
|
+
checks.push(sec.gitignoreCoversEnv ? chalk8.green(".env \u2714") : chalk8.red(".env \u2716"));
|
|
4389
|
+
checks.push(sec.gitignoreCoversNodeModules ? chalk8.green("node_modules \u2714") : chalk8.yellow("node_modules \u2716"));
|
|
4390
|
+
if (sec.multipleLockfileTypes) checks.push(chalk8.yellow("Multiple lockfiles \u26A0"));
|
|
4391
|
+
if (sec.envFilesTracked) checks.push(chalk8.red("Env files tracked \u2716"));
|
|
4392
|
+
lines.push(` ${checks.join(chalk8.dim(" \xB7 "))}`);
|
|
3910
4393
|
lines.push("");
|
|
3911
4394
|
}
|
|
3912
4395
|
if (ext.platformMatrix) {
|
|
3913
4396
|
const pm = ext.platformMatrix;
|
|
3914
4397
|
if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
|
|
3915
|
-
lines.push(
|
|
4398
|
+
lines.push(chalk8.bold.underline(" Platform"));
|
|
3916
4399
|
if (pm.nativeModules.length > 0) {
|
|
3917
|
-
lines.push(` Native modules: ${pm.nativeModules.map((m) =>
|
|
4400
|
+
lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk8.dim(m)).join(", ")}`);
|
|
3918
4401
|
}
|
|
3919
4402
|
if (pm.osAssumptions.length > 0) {
|
|
3920
4403
|
lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
|
|
@@ -3924,25 +4407,25 @@ function formatExtended(ext) {
|
|
|
3924
4407
|
}
|
|
3925
4408
|
if (ext.codeQuality) {
|
|
3926
4409
|
const cq = ext.codeQuality;
|
|
3927
|
-
lines.push(
|
|
3928
|
-
lines.push(` Files: ${
|
|
4410
|
+
lines.push(chalk8.bold.underline(" Code Quality"));
|
|
4411
|
+
lines.push(` Files: ${chalk8.white(`${cq.filesAnalyzed}`)} \xB7 Functions: ${chalk8.white(`${cq.functionsAnalyzed}`)} \xB7 Avg complexity: ${chalk8.white(`${cq.avgCyclomaticComplexity}`)} \xB7 Avg length: ${chalk8.white(`${cq.avgFunctionLength}`)} lines`);
|
|
3929
4412
|
lines.push(` Max nesting: ${cq.maxNestingDepth} \xB7 Circular deps: ${cq.circularDependencies} \xB7 Dead code: ${cq.deadCodePercent}%`);
|
|
3930
4413
|
if (cq.godFiles.length > 0) {
|
|
3931
4414
|
const preview = cq.godFiles.slice(0, 3).map((f) => `${f.path} (${f.lines} lines)`).join(", ");
|
|
3932
|
-
lines.push(` ${
|
|
4415
|
+
lines.push(` ${chalk8.yellow("God files")}: ${preview}`);
|
|
3933
4416
|
}
|
|
3934
4417
|
lines.push("");
|
|
3935
4418
|
}
|
|
3936
4419
|
if (ext.dependencyGraph) {
|
|
3937
4420
|
const dg = ext.dependencyGraph;
|
|
3938
4421
|
if (dg.lockfileType) {
|
|
3939
|
-
lines.push(
|
|
3940
|
-
lines.push(` ${dg.lockfileType}: ${
|
|
4422
|
+
lines.push(chalk8.bold.underline(" Dependency Graph"));
|
|
4423
|
+
lines.push(` ${dg.lockfileType}: ${chalk8.white(`${dg.totalUnique}`)} unique, ${chalk8.white(`${dg.totalInstalled}`)} installed`);
|
|
3941
4424
|
if (dg.duplicatedPackages.length > 0) {
|
|
3942
|
-
lines.push(` ${
|
|
4425
|
+
lines.push(` ${chalk8.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
|
|
3943
4426
|
}
|
|
3944
4427
|
if (dg.phantomDependencies.length > 0) {
|
|
3945
|
-
lines.push(` ${
|
|
4428
|
+
lines.push(` ${chalk8.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
|
|
3946
4429
|
}
|
|
3947
4430
|
lines.push("");
|
|
3948
4431
|
}
|
|
@@ -3953,13 +4436,13 @@ function formatArchitectureDiagram(arch) {
|
|
|
3953
4436
|
const lines = [];
|
|
3954
4437
|
lines.push(...titleBox("Architecture Layers"));
|
|
3955
4438
|
lines.push("");
|
|
3956
|
-
lines.push(
|
|
3957
|
-
lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ?
|
|
4439
|
+
lines.push(chalk8.bold(" Archetype: ") + `${arch.archetype}` + chalk8.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
|
|
4440
|
+
lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk8.dim(` (${arch.unclassified} unclassified)`) : ""));
|
|
3958
4441
|
lines.push("");
|
|
3959
4442
|
if (arch.layers.length > 0) {
|
|
3960
4443
|
for (const layer of arch.layers) {
|
|
3961
|
-
const risk = layer.riskLevel === "none" ?
|
|
3962
|
-
lines.push(` ${
|
|
4444
|
+
const risk = layer.riskLevel === "none" ? chalk8.dim("none") : layer.riskLevel === "low" ? chalk8.green("low") : layer.riskLevel === "moderate" ? chalk8.yellow("moderate") : chalk8.red("high");
|
|
4445
|
+
lines.push(` ${chalk8.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
|
|
3963
4446
|
}
|
|
3964
4447
|
lines.push("");
|
|
3965
4448
|
}
|
|
@@ -4330,8 +4813,8 @@ function formatMarkdown(artifact) {
|
|
|
4330
4813
|
var reportCommand = new Command("report").description("Generate a drift report from a scan artifact").option("--in <file>", "Input artifact file", ".vibgrate/scan_result.json").option("--format <format>", "Output format (md|text|json)", "text").action(async (opts) => {
|
|
4331
4814
|
const artifactPath = path.resolve(opts.in);
|
|
4332
4815
|
if (!await pathExists(artifactPath)) {
|
|
4333
|
-
console.error(
|
|
4334
|
-
console.error(
|
|
4816
|
+
console.error(chalk8.red(`Artifact not found: ${artifactPath}`));
|
|
4817
|
+
console.error(chalk8.dim('Run "vibgrate scan" first to generate a scan artifact.'));
|
|
4335
4818
|
process.exit(1);
|
|
4336
4819
|
}
|
|
4337
4820
|
const artifact = await readJsonFile(artifactPath);
|
|
@@ -4387,7 +4870,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4387
4870
|
try {
|
|
4388
4871
|
ingestHost = resolveIngestHost(opts.region, opts.ingest);
|
|
4389
4872
|
} catch (e) {
|
|
4390
|
-
console.error(
|
|
4873
|
+
console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
|
|
4391
4874
|
process.exit(1);
|
|
4392
4875
|
}
|
|
4393
4876
|
const base = `https://${ingestHost}/v1/auth/device`;
|
|
@@ -4399,28 +4882,28 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4399
4882
|
body: "{}"
|
|
4400
4883
|
});
|
|
4401
4884
|
if (!res.ok) {
|
|
4402
|
-
console.error(
|
|
4885
|
+
console.error(chalk8.red(`Failed to start login (HTTP ${res.status}).`));
|
|
4403
4886
|
process.exit(1);
|
|
4404
4887
|
}
|
|
4405
4888
|
start = await res.json();
|
|
4406
4889
|
} catch (e) {
|
|
4407
|
-
console.error(
|
|
4890
|
+
console.error(chalk8.red(`Could not reach ${ingestHost}: ${e instanceof Error ? e.message : String(e)}`));
|
|
4408
4891
|
process.exit(1);
|
|
4409
4892
|
}
|
|
4410
4893
|
console.log("");
|
|
4411
4894
|
console.log("To finish signing in, open this URL and approve the request:");
|
|
4412
4895
|
console.log("");
|
|
4413
|
-
console.log(" " +
|
|
4896
|
+
console.log(" " + chalk8.cyan(start.verificationUri));
|
|
4414
4897
|
console.log("");
|
|
4415
|
-
console.log(" Your code: " +
|
|
4898
|
+
console.log(" Your code: " + chalk8.bold(start.userCode));
|
|
4416
4899
|
console.log("");
|
|
4417
4900
|
if (opts.browser) {
|
|
4418
4901
|
const opened = openUrl(start.verificationUriComplete);
|
|
4419
4902
|
if (opened) {
|
|
4420
|
-
console.log(
|
|
4903
|
+
console.log(chalk8.dim("Opening your browser\u2026 (if it does not open, use the URL above)"));
|
|
4421
4904
|
}
|
|
4422
4905
|
}
|
|
4423
|
-
console.log(
|
|
4906
|
+
console.log(chalk8.dim("Waiting for approval\u2026"));
|
|
4424
4907
|
const intervalMs = Math.max(2, start.interval || 5) * 1e3;
|
|
4425
4908
|
const deadline = Date.now() + (start.expiresIn || 900) * 1e3;
|
|
4426
4909
|
while (Date.now() < deadline) {
|
|
@@ -4448,7 +4931,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4448
4931
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4449
4932
|
};
|
|
4450
4933
|
} else {
|
|
4451
|
-
console.log(
|
|
4934
|
+
console.log(chalk8.dim("Setting up your workspace\u2026"));
|
|
4452
4935
|
try {
|
|
4453
4936
|
const provisioned = await createWorkspaceDsn({
|
|
4454
4937
|
region: opts.region,
|
|
@@ -4463,10 +4946,10 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4463
4946
|
};
|
|
4464
4947
|
} catch (e) {
|
|
4465
4948
|
console.error(
|
|
4466
|
-
|
|
4949
|
+
chalk8.red("\u2716 Signed in, but workspace setup failed: ") + (e instanceof Error ? e.message : String(e))
|
|
4467
4950
|
);
|
|
4468
4951
|
console.error(
|
|
4469
|
-
|
|
4952
|
+
chalk8.dim(' Finish setup with "vibgrate dsn create --workspace new".')
|
|
4470
4953
|
);
|
|
4471
4954
|
process.exit(1);
|
|
4472
4955
|
}
|
|
@@ -4475,40 +4958,40 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4475
4958
|
writeStoredCredentials(creds, storeOpts);
|
|
4476
4959
|
const credsFile = credentialsPath(storeOpts);
|
|
4477
4960
|
console.log("");
|
|
4478
|
-
console.log(
|
|
4961
|
+
console.log(chalk8.green("\u2714") + " Logged in.");
|
|
4479
4962
|
if (creds.workspaceId) {
|
|
4480
|
-
console.log(" Workspace: " +
|
|
4963
|
+
console.log(" Workspace: " + chalk8.bold(creds.workspaceId));
|
|
4481
4964
|
}
|
|
4482
|
-
console.log(
|
|
4965
|
+
console.log(chalk8.dim(` Credentials saved to ${credsFile}`));
|
|
4483
4966
|
try {
|
|
4484
4967
|
const root = findGitRoot();
|
|
4485
4968
|
if (root && isInside(root, credsFile)) {
|
|
4486
4969
|
const res = ensureGitignored(gitignoreEntryForCredentials(root, credsFile), root);
|
|
4487
4970
|
if (res.status === "created") {
|
|
4488
|
-
console.log(
|
|
4971
|
+
console.log(chalk8.dim(` Created .gitignore and ignored ${res.entry}`));
|
|
4489
4972
|
} else if (res.status === "added") {
|
|
4490
|
-
console.log(
|
|
4973
|
+
console.log(chalk8.dim(` Added ${res.entry} to .gitignore`));
|
|
4491
4974
|
}
|
|
4492
4975
|
}
|
|
4493
4976
|
} catch {
|
|
4494
4977
|
}
|
|
4495
|
-
console.log(
|
|
4978
|
+
console.log(chalk8.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
|
|
4496
4979
|
return;
|
|
4497
4980
|
}
|
|
4498
4981
|
if (token.status === "access_denied") {
|
|
4499
|
-
console.error(
|
|
4982
|
+
console.error(chalk8.red("\u2716 Login was denied in the browser."));
|
|
4500
4983
|
process.exit(1);
|
|
4501
4984
|
}
|
|
4502
4985
|
if (token.status === "expired" || token.status === "invalid") {
|
|
4503
|
-
console.error(
|
|
4986
|
+
console.error(chalk8.red('\u2716 Login request expired. Run "vibgrate login" again.'));
|
|
4504
4987
|
process.exit(1);
|
|
4505
4988
|
}
|
|
4506
4989
|
if (token.status === "error") {
|
|
4507
|
-
console.error(
|
|
4990
|
+
console.error(chalk8.red(`\u2716 ${token.error ?? "Login failed."}`));
|
|
4508
4991
|
process.exit(1);
|
|
4509
4992
|
}
|
|
4510
4993
|
}
|
|
4511
|
-
console.error(
|
|
4994
|
+
console.error(chalk8.red('\u2716 Timed out waiting for approval. Run "vibgrate login" again.'));
|
|
4512
4995
|
process.exit(1);
|
|
4513
4996
|
});
|
|
4514
4997
|
|
|
@@ -4529,190 +5012,20 @@ function unsetEnvCommand(varName, env = {}) {
|
|
|
4529
5012
|
var logoutCommand = new Command("logout").description("Clear stored Vibgrate login credentials").action(() => {
|
|
4530
5013
|
const cleared = clearStoredCredentials();
|
|
4531
5014
|
if (cleared) {
|
|
4532
|
-
console.log(
|
|
5015
|
+
console.log(chalk8.green("\u2714") + " Logged out. Stored credentials removed.");
|
|
4533
5016
|
} else {
|
|
4534
|
-
console.log(
|
|
5017
|
+
console.log(chalk8.dim(`No stored credentials found at ${credentialsPath()}.`));
|
|
4535
5018
|
}
|
|
4536
5019
|
if (process.env.VIBGRATE_DSN) {
|
|
4537
5020
|
console.log("");
|
|
4538
5021
|
console.log(
|
|
4539
|
-
|
|
5022
|
+
chalk8.yellow("\u26A0") + " VIBGRATE_DSN is still set in your environment, so commands will keep using it."
|
|
4540
5023
|
);
|
|
4541
5024
|
console.log(
|
|
4542
|
-
|
|
5025
|
+
chalk8.dim(` Unset it to fully sign out: ${unsetEnvCommand("VIBGRATE_DSN")}`)
|
|
4543
5026
|
);
|
|
4544
5027
|
}
|
|
4545
5028
|
});
|
|
4546
|
-
var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
|
|
4547
|
-
var CACHE_DIR = path.join(os.homedir(), ".vibgrate");
|
|
4548
|
-
var CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
|
|
4549
|
-
async function fetchLatestVersion() {
|
|
4550
|
-
try {
|
|
4551
|
-
const controller = new AbortController();
|
|
4552
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
4553
|
-
timeout.unref?.();
|
|
4554
|
-
let response;
|
|
4555
|
-
try {
|
|
4556
|
-
response = await fetch(REGISTRY_URL, {
|
|
4557
|
-
headers: { Accept: "application/json" },
|
|
4558
|
-
signal: controller.signal
|
|
4559
|
-
});
|
|
4560
|
-
} finally {
|
|
4561
|
-
clearTimeout(timeout);
|
|
4562
|
-
}
|
|
4563
|
-
if (!response.ok) return null;
|
|
4564
|
-
const data = await response.json();
|
|
4565
|
-
const latest = data.version;
|
|
4566
|
-
if (!latest || !semver.valid(latest)) return null;
|
|
4567
|
-
await writeCache({ latest, checkedAt: Date.now() });
|
|
4568
|
-
return latest;
|
|
4569
|
-
} catch {
|
|
4570
|
-
return null;
|
|
4571
|
-
}
|
|
4572
|
-
}
|
|
4573
|
-
async function writeCache(data) {
|
|
4574
|
-
try {
|
|
4575
|
-
await fs14.mkdir(CACHE_DIR, { recursive: true });
|
|
4576
|
-
await fs14.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
4577
|
-
} catch {
|
|
4578
|
-
}
|
|
4579
|
-
}
|
|
4580
|
-
|
|
4581
|
-
// src/reporting/commands/update.ts
|
|
4582
|
-
function detectGlobalInstall() {
|
|
4583
|
-
const execPath = process.argv[1] || "";
|
|
4584
|
-
if (execPath.includes("/lib/node_modules/") || execPath.includes("\\node_modules\\")) {
|
|
4585
|
-
if (!execPath.includes(process.cwd())) {
|
|
4586
|
-
if (execPath.includes("pnpm")) return "pnpm";
|
|
4587
|
-
if (execPath.includes("yarn")) return "yarn";
|
|
4588
|
-
if (execPath.includes("bun")) return "bun";
|
|
4589
|
-
return "npm";
|
|
4590
|
-
}
|
|
4591
|
-
}
|
|
4592
|
-
return null;
|
|
4593
|
-
}
|
|
4594
|
-
function getGlobalUpdateCommand(pm, pkg, version) {
|
|
4595
|
-
const spec = `${pkg}@${version}`;
|
|
4596
|
-
switch (pm) {
|
|
4597
|
-
case "pnpm":
|
|
4598
|
-
return `pnpm add -g ${spec}`;
|
|
4599
|
-
case "yarn":
|
|
4600
|
-
return `yarn global add ${spec}`;
|
|
4601
|
-
case "bun":
|
|
4602
|
-
return `bun add -g ${spec}`;
|
|
4603
|
-
case "npm":
|
|
4604
|
-
default:
|
|
4605
|
-
return `npm install -g ${spec}`;
|
|
4606
|
-
}
|
|
4607
|
-
}
|
|
4608
|
-
async function detectPackageManager(cwd) {
|
|
4609
|
-
if (await pathExists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
4610
|
-
if (await pathExists(path.join(cwd, "bun.lockb"))) return "bun";
|
|
4611
|
-
if (await pathExists(path.join(cwd, "yarn.lock"))) return "yarn";
|
|
4612
|
-
return "npm";
|
|
4613
|
-
}
|
|
4614
|
-
async function detectWorkspaceRoot(cwd) {
|
|
4615
|
-
if (await pathExists(path.join(cwd, "pnpm-workspace.yaml"))) return true;
|
|
4616
|
-
try {
|
|
4617
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
4618
|
-
const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
|
|
4619
|
-
const pkg = JSON.parse(raw);
|
|
4620
|
-
return pkg.workspaces != null;
|
|
4621
|
-
} catch {
|
|
4622
|
-
return false;
|
|
4623
|
-
}
|
|
4624
|
-
}
|
|
4625
|
-
function getInstallCommand(pm, pkg, version, isDev, opts = {}) {
|
|
4626
|
-
const spec = `${pkg}@${version}`;
|
|
4627
|
-
const ws = opts.workspaceRoot && pm === "pnpm" ? " -w" : "";
|
|
4628
|
-
switch (pm) {
|
|
4629
|
-
case "pnpm":
|
|
4630
|
-
return isDev ? `pnpm add${ws} -D ${spec}` : `pnpm add${ws} ${spec}`;
|
|
4631
|
-
case "yarn":
|
|
4632
|
-
return isDev ? `yarn add --dev ${spec}` : `yarn add ${spec}`;
|
|
4633
|
-
case "bun":
|
|
4634
|
-
return isDev ? `bun add -d ${spec}` : `bun add ${spec}`;
|
|
4635
|
-
case "npm":
|
|
4636
|
-
default:
|
|
4637
|
-
return isDev ? `npm install --save-dev ${spec}` : `npm install ${spec}`;
|
|
4638
|
-
}
|
|
4639
|
-
}
|
|
4640
|
-
async function isDevDependency(cwd) {
|
|
4641
|
-
try {
|
|
4642
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
4643
|
-
const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
|
|
4644
|
-
const pkg = JSON.parse(raw);
|
|
4645
|
-
return Boolean(pkg.devDependencies?.["@vibgrate/cli"]);
|
|
4646
|
-
} catch {
|
|
4647
|
-
return true;
|
|
4648
|
-
}
|
|
4649
|
-
}
|
|
4650
|
-
async function confirmWorkspaceRoot(pm) {
|
|
4651
|
-
if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
|
|
4652
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4653
|
-
return new Promise((resolve16) => {
|
|
4654
|
-
rl.question(chalk7.yellow(`Update the ${pm} workspace root with -w? [y/N]: `), (ans) => {
|
|
4655
|
-
rl.close();
|
|
4656
|
-
const t = ans.trim().toLowerCase();
|
|
4657
|
-
resolve16(t === "y" || t === "yes");
|
|
4658
|
-
});
|
|
4659
|
-
});
|
|
4660
|
-
}
|
|
4661
|
-
var updateCommand = new Command("update").description("Update vibgrate to the latest version").option("--check", "Only check for updates, do not install").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--global", "Update global installation").option("-y, --yes", "Skip confirmation prompts (e.g. installing at a workspace root)").option("-w, --workspace-root", "Allow updating the pnpm workspace root (implies --yes for that prompt)").action(
|
|
4662
|
-
async (opts) => {
|
|
4663
|
-
console.log(chalk7.dim(`Current version: ${VERSION}`));
|
|
4664
|
-
console.log(chalk7.dim("Checking npm registry..."));
|
|
4665
|
-
const latest = await fetchLatestVersion();
|
|
4666
|
-
if (!latest) {
|
|
4667
|
-
console.error(chalk7.red("Could not reach the npm registry. Check your network connection."));
|
|
4668
|
-
process.exit(1);
|
|
4669
|
-
}
|
|
4670
|
-
const semver2 = await import('semver');
|
|
4671
|
-
if (!semver2.gt(latest, VERSION)) {
|
|
4672
|
-
console.log(chalk7.green("\u2714") + ` You are on the latest version (${VERSION}).`);
|
|
4673
|
-
return;
|
|
4674
|
-
}
|
|
4675
|
-
console.log(chalk7.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
|
|
4676
|
-
if (opts.check) {
|
|
4677
|
-
console.log(chalk7.dim('Run "vg update" to install.'));
|
|
4678
|
-
return;
|
|
4679
|
-
}
|
|
4680
|
-
const cwd = process.cwd();
|
|
4681
|
-
const globalPm = detectGlobalInstall();
|
|
4682
|
-
const isGlobal = opts.global || globalPm !== null;
|
|
4683
|
-
const pm = opts.pm || (globalPm ?? await detectPackageManager(cwd));
|
|
4684
|
-
let cmd;
|
|
4685
|
-
if (isGlobal) {
|
|
4686
|
-
cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
|
|
4687
|
-
console.log(chalk7.dim(`Updating global installation with ${pm}: ${cmd}`));
|
|
4688
|
-
} else {
|
|
4689
|
-
const isDev = await isDevDependency(cwd);
|
|
4690
|
-
let workspaceRoot = false;
|
|
4691
|
-
if (pm === "pnpm" && await detectWorkspaceRoot(cwd)) {
|
|
4692
|
-
console.log(
|
|
4693
|
-
chalk7.yellow("Detected a pnpm workspace root.") + chalk7.dim(" Installing here needs the -w flag and updates the root package.json.")
|
|
4694
|
-
);
|
|
4695
|
-
const proceed = opts.yes || opts.workspaceRoot || await confirmWorkspaceRoot(pm);
|
|
4696
|
-
if (!proceed) {
|
|
4697
|
-
const rootCmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot: true });
|
|
4698
|
-
console.log(chalk7.dim(`Skipped. To update the workspace root, run: ${rootCmd}`));
|
|
4699
|
-
console.log(chalk7.dim('or re-run "vg update --yes" (or --workspace-root) to let vg do it.'));
|
|
4700
|
-
return;
|
|
4701
|
-
}
|
|
4702
|
-
workspaceRoot = true;
|
|
4703
|
-
}
|
|
4704
|
-
cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot });
|
|
4705
|
-
console.log(chalk7.dim(`Using ${pm}: ${cmd}`));
|
|
4706
|
-
}
|
|
4707
|
-
try {
|
|
4708
|
-
execSync(cmd, { cwd, stdio: "inherit" });
|
|
4709
|
-
console.log(chalk7.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
|
|
4710
|
-
} catch {
|
|
4711
|
-
console.error(chalk7.red(`Update failed. Run manually: ${cmd}`));
|
|
4712
|
-
process.exit(1);
|
|
4713
|
-
}
|
|
4714
|
-
}
|
|
4715
|
-
);
|
|
4716
5029
|
var OPENVEX_CONTEXT = "https://openvex.dev/ns/v0.2.0";
|
|
4717
5030
|
var VEX_STATUSES = ["not_affected", "affected", "fixed", "under_investigation"];
|
|
4718
5031
|
var VEX_JUSTIFICATIONS = [
|
|
@@ -4804,7 +5117,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
|
|
|
4804
5117
|
if (opts.from) {
|
|
4805
5118
|
const fromPath = path.resolve(opts.from);
|
|
4806
5119
|
if (!await pathExists(fromPath)) {
|
|
4807
|
-
process.stderr.write(
|
|
5120
|
+
process.stderr.write(chalk8.red(`Statements file not found: ${fromPath}
|
|
4808
5121
|
`));
|
|
4809
5122
|
process.exit(1);
|
|
4810
5123
|
}
|
|
@@ -4822,7 +5135,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
|
|
|
4822
5135
|
if (opts.out) {
|
|
4823
5136
|
await writeTextFile(path.resolve(opts.out), json2 + "\n");
|
|
4824
5137
|
process.stderr.write(
|
|
4825
|
-
|
|
5138
|
+
chalk8.green("\u2714") + ` Wrote OpenVEX document (${doc.statements.length} statement(s)) to ${path.resolve(opts.out)}
|
|
4826
5139
|
`
|
|
4827
5140
|
);
|
|
4828
5141
|
} else {
|
|
@@ -4830,7 +5143,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
|
|
|
4830
5143
|
}
|
|
4831
5144
|
} catch (err) {
|
|
4832
5145
|
if (err instanceof VexValidationError) {
|
|
4833
|
-
process.stderr.write(
|
|
5146
|
+
process.stderr.write(chalk8.red(`VEX validation error: ${err.message}
|
|
4834
5147
|
`));
|
|
4835
5148
|
process.exit(1);
|
|
4836
5149
|
}
|
|
@@ -5007,7 +5320,7 @@ function formatDeltaText(base, current) {
|
|
|
5007
5320
|
async function readArtifactOrExit(filePath) {
|
|
5008
5321
|
const absolutePath = path.resolve(filePath);
|
|
5009
5322
|
if (!await pathExists(absolutePath)) {
|
|
5010
|
-
console.error(
|
|
5323
|
+
console.error(chalk8.red(`Artifact not found: ${absolutePath}`));
|
|
5011
5324
|
process.exit(1);
|
|
5012
5325
|
}
|
|
5013
5326
|
return readJsonFile(absolutePath);
|
|
@@ -5016,14 +5329,14 @@ var exportCommand = new Command("export").description("Export scan artifact as S
|
|
|
5016
5329
|
const artifact = await readArtifactOrExit(opts.in);
|
|
5017
5330
|
const format = opts.format.toLowerCase();
|
|
5018
5331
|
if (format !== "cyclonedx" && format !== "spdx") {
|
|
5019
|
-
console.error(
|
|
5332
|
+
console.error(chalk8.red("Invalid SBOM format. Use cyclonedx or spdx."));
|
|
5020
5333
|
process.exit(1);
|
|
5021
5334
|
}
|
|
5022
5335
|
const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
|
|
5023
5336
|
const body = JSON.stringify(sbom, null, 2);
|
|
5024
5337
|
if (opts.out) {
|
|
5025
5338
|
await writeTextFile(path.resolve(opts.out), body);
|
|
5026
|
-
console.log(
|
|
5339
|
+
console.log(chalk8.green("\u2714") + ` SBOM written to ${opts.out}`);
|
|
5027
5340
|
} else {
|
|
5028
5341
|
console.log(body);
|
|
5029
5342
|
}
|
|
@@ -5034,7 +5347,7 @@ var deltaCommand = new Command("delta").description("Show SBOM delta between two
|
|
|
5034
5347
|
const report = formatDeltaText(base, current);
|
|
5035
5348
|
if (opts.out) {
|
|
5036
5349
|
await writeTextFile(path.resolve(opts.out), report);
|
|
5037
|
-
console.log(
|
|
5350
|
+
console.log(chalk8.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
|
|
5038
5351
|
} else {
|
|
5039
5352
|
console.log(report);
|
|
5040
5353
|
}
|
|
@@ -5094,7 +5407,7 @@ function buildProgram() {
|
|
|
5094
5407
|
const cli = resolveCliInvocation();
|
|
5095
5408
|
const aliasNote = cli === "vg" ? "The `vibgrate` command is an alias for `vg`.\n" : cli === NPX_INVOCATION ? "Install globally (`npm i -g @vibgrate/cli`) to use the shorter `vg` command.\n" : "";
|
|
5096
5409
|
return `
|
|
5097
|
-
Run \`${cli}\` to scan + map the current folder, \`${cli} "<question>"\` to ask, \`${cli} status
|
|
5410
|
+
Run \`${cli}\` to scan + map the current folder, \`${cli} "<question>"\` to ask, \`${cli} status\` for state, \`${cli} build --verify\` to re-verify the map.
|
|
5098
5411
|
Drift: \`${cli} scan\` / \`${cli} report\`. Wire into your AI agent: \`${cli} install\`.
|
|
5099
5412
|
${aliasNote}Docs: https://vibgrate.com/help`;
|
|
5100
5413
|
});
|
|
@@ -5190,7 +5503,7 @@ function dispatch(argv, cwd) {
|
|
|
5190
5503
|
}
|
|
5191
5504
|
const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
|
|
5192
5505
|
if (looksLikeQuestion) return ["ask", ...args];
|
|
5193
|
-
if (
|
|
5506
|
+
if (fs6.existsSync(path.resolve(cwd, first))) {
|
|
5194
5507
|
return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
|
|
5195
5508
|
}
|
|
5196
5509
|
const moved = MOVED_COMMANDS[first];
|