@vibgrate/cli 2026.708.3 → 2026.709.1
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 +9 -1
- package/dist/baseline-7AFTGG3I.js +6 -0
- package/dist/{baseline-AUC3HAXW.js.map → baseline-7AFTGG3I.js.map} +1 -1
- package/dist/chunk-IRZCYPFC.js +3735 -0
- package/dist/chunk-IRZCYPFC.js.map +1 -0
- package/dist/{chunk-2DJ3UXG7.js → chunk-MPKMAERO.js} +496 -405
- package/dist/chunk-MPKMAERO.js.map +1 -0
- package/dist/{chunk-QUELGOCR.js → chunk-PXZ5KPVN.js} +4 -4
- package/dist/{chunk-QUELGOCR.js.map → chunk-PXZ5KPVN.js.map} +1 -1
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +819 -527
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +2 -2
- package/package.json +2 -2
- 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-MPKMAERO.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-IRZCYPFC.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-PXZ5KPVN.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,214 @@ 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
|
-
if (!
|
|
1483
|
+
if (!fs6.existsSync(graphPath)) {
|
|
1246
1484
|
throw new CliError(
|
|
1247
1485
|
`no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
|
|
1248
1486
|
ExitCode.NOT_FOUND
|
|
1249
1487
|
);
|
|
1250
1488
|
}
|
|
1489
|
+
if (opts.shareStats === true && local) {
|
|
1490
|
+
info(c.dim("vg \xB7 --share-stats ignored under --local (air-gapped): recording locally, not uploading."));
|
|
1491
|
+
}
|
|
1492
|
+
if (shareStats) startSharing(root);
|
|
1251
1493
|
const freshness = refresh ? "auto-refresh" : "as built";
|
|
1252
1494
|
if (opts.http) {
|
|
1253
1495
|
await serveHttp(graphPath, opts.host ?? "127.0.0.1", Number(opts.port) || 7437, serveOpts, freshness);
|
|
@@ -1258,6 +1500,31 @@ function registerServe(program) {
|
|
|
1258
1500
|
});
|
|
1259
1501
|
applyGlobalOptions(cmd);
|
|
1260
1502
|
}
|
|
1503
|
+
function startSharing(root) {
|
|
1504
|
+
const sharer = new StatsSharer(root);
|
|
1505
|
+
info(
|
|
1506
|
+
c.dim(
|
|
1507
|
+
`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.`
|
|
1508
|
+
)
|
|
1509
|
+
);
|
|
1510
|
+
const timer = setInterval(() => void sharer.flush(), SHARE_FLUSH_INTERVAL_MS);
|
|
1511
|
+
timer.unref?.();
|
|
1512
|
+
let flushed = false;
|
|
1513
|
+
const finalFlush = () => {
|
|
1514
|
+
if (flushed) return;
|
|
1515
|
+
flushed = true;
|
|
1516
|
+
void sharer.flush();
|
|
1517
|
+
};
|
|
1518
|
+
process.once("SIGINT", () => {
|
|
1519
|
+
finalFlush();
|
|
1520
|
+
process.exit(0);
|
|
1521
|
+
});
|
|
1522
|
+
process.once("SIGTERM", () => {
|
|
1523
|
+
finalFlush();
|
|
1524
|
+
process.exit(0);
|
|
1525
|
+
});
|
|
1526
|
+
process.once("beforeExit", finalFlush);
|
|
1527
|
+
}
|
|
1261
1528
|
async function serveHttp(graphPath, host, port, opts, freshness) {
|
|
1262
1529
|
const { createServer: createHttp } = await import('http');
|
|
1263
1530
|
const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
@@ -1361,7 +1628,7 @@ function registerShare(program) {
|
|
|
1361
1628
|
const global = readGlobal(this);
|
|
1362
1629
|
const root = rootOf(global);
|
|
1363
1630
|
const gitDir = path.join(root, ".git");
|
|
1364
|
-
if (!
|
|
1631
|
+
if (!fs6.existsSync(gitDir)) {
|
|
1365
1632
|
throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
|
|
1366
1633
|
}
|
|
1367
1634
|
if (opts.undo) {
|
|
@@ -1383,22 +1650,22 @@ function registerShare(program) {
|
|
|
1383
1650
|
}
|
|
1384
1651
|
function writeVibgrateGitignore(root, reports) {
|
|
1385
1652
|
const dir = path.join(root, ".vibgrate");
|
|
1386
|
-
|
|
1653
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
1387
1654
|
const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
|
|
1388
1655
|
if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
|
|
1389
|
-
|
|
1656
|
+
fs6.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
|
|
1390
1657
|
`);
|
|
1391
1658
|
}
|
|
1392
1659
|
function writeGitAttributes(root) {
|
|
1393
1660
|
const file = path.join(root, ".gitattributes");
|
|
1394
1661
|
const line = ".vibgrate/graph.json merge=vg";
|
|
1395
|
-
let content =
|
|
1662
|
+
let content = fs6.existsSync(file) ? fs6.readFileSync(file, "utf8") : "";
|
|
1396
1663
|
if (!content.split("\n").some((l) => l.trim() === line)) {
|
|
1397
1664
|
content = content.length ? `${content.replace(/\s*$/, "")}
|
|
1398
1665
|
${line}
|
|
1399
1666
|
` : `${line}
|
|
1400
1667
|
`;
|
|
1401
|
-
|
|
1668
|
+
fs6.writeFileSync(file, content);
|
|
1402
1669
|
}
|
|
1403
1670
|
}
|
|
1404
1671
|
function configureMergeDriver(root) {
|
|
@@ -1407,7 +1674,7 @@ function configureMergeDriver(root) {
|
|
|
1407
1674
|
}
|
|
1408
1675
|
function installPreCommitHook(root) {
|
|
1409
1676
|
const hooksDir = hookDir(root);
|
|
1410
|
-
|
|
1677
|
+
fs6.mkdirSync(hooksDir, { recursive: true });
|
|
1411
1678
|
const file = path.join(hooksDir, "pre-commit");
|
|
1412
1679
|
const block = [
|
|
1413
1680
|
HOOK_BEGIN,
|
|
@@ -1417,7 +1684,7 @@ function installPreCommitHook(root) {
|
|
|
1417
1684
|
"fi",
|
|
1418
1685
|
HOOK_END
|
|
1419
1686
|
].join("\n");
|
|
1420
|
-
let content =
|
|
1687
|
+
let content = fs6.existsSync(file) ? fs6.readFileSync(file, "utf8") : "#!/bin/sh\n";
|
|
1421
1688
|
const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
|
|
1422
1689
|
if (re.test(content)) content = content.replace(re, block);
|
|
1423
1690
|
else content = `${content.replace(/\s*$/, "")}
|
|
@@ -1426,21 +1693,21 @@ ${block}
|
|
|
1426
1693
|
`;
|
|
1427
1694
|
if (!content.startsWith("#!")) content = `#!/bin/sh
|
|
1428
1695
|
${content}`;
|
|
1429
|
-
|
|
1430
|
-
|
|
1696
|
+
fs6.writeFileSync(file, content);
|
|
1697
|
+
fs6.chmodSync(file, 493);
|
|
1431
1698
|
}
|
|
1432
1699
|
function undo(root) {
|
|
1433
1700
|
const hookFile = path.join(hookDir(root), "pre-commit");
|
|
1434
|
-
if (
|
|
1701
|
+
if (fs6.existsSync(hookFile)) {
|
|
1435
1702
|
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
|
|
1703
|
+
const next2 = fs6.readFileSync(hookFile, "utf8").replace(re, "\n");
|
|
1704
|
+
if (next2.trim() === "#!/bin/sh") fs6.rmSync(hookFile);
|
|
1705
|
+
else fs6.writeFileSync(hookFile, next2);
|
|
1439
1706
|
}
|
|
1440
1707
|
const attr = path.join(root, ".gitattributes");
|
|
1441
|
-
if (
|
|
1442
|
-
const next2 =
|
|
1443
|
-
|
|
1708
|
+
if (fs6.existsSync(attr)) {
|
|
1709
|
+
const next2 = fs6.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
|
|
1710
|
+
fs6.writeFileSync(attr, next2);
|
|
1444
1711
|
}
|
|
1445
1712
|
try {
|
|
1446
1713
|
git(root, ["config", "--remove-section", "merge.vg"]);
|
|
@@ -1573,7 +1840,7 @@ function grepBaselineTokens(term, files) {
|
|
|
1573
1840
|
if (f.size > 512 * 1024) continue;
|
|
1574
1841
|
let content = "";
|
|
1575
1842
|
try {
|
|
1576
|
-
content =
|
|
1843
|
+
content = fs6.readFileSync(f.abs, "utf8");
|
|
1577
1844
|
} catch {
|
|
1578
1845
|
continue;
|
|
1579
1846
|
}
|
|
@@ -1583,7 +1850,7 @@ function grepBaselineTokens(term, files) {
|
|
|
1583
1850
|
}
|
|
1584
1851
|
function safeSize(abs) {
|
|
1585
1852
|
try {
|
|
1586
|
-
return
|
|
1853
|
+
return fs6.statSync(abs).size;
|
|
1587
1854
|
} catch {
|
|
1588
1855
|
return 0;
|
|
1589
1856
|
}
|
|
@@ -1917,7 +2184,7 @@ function loadStandards(root) {
|
|
|
1917
2184
|
const p = path__default.join(root, rel3);
|
|
1918
2185
|
let text;
|
|
1919
2186
|
try {
|
|
1920
|
-
text =
|
|
2187
|
+
text = fs6__default.readFileSync(p, "utf8");
|
|
1921
2188
|
} catch {
|
|
1922
2189
|
continue;
|
|
1923
2190
|
}
|
|
@@ -2046,7 +2313,7 @@ function registerSavings(program) {
|
|
|
2046
2313
|
if (usage.totals.calls === 0) {
|
|
2047
2314
|
info(
|
|
2048
2315
|
c.dim(
|
|
2049
|
-
report.enabled ? " no calls recorded yet in this window" : " recording is off. Enable with `vg serve --savings`,
|
|
2316
|
+
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
2317
|
)
|
|
2051
2318
|
);
|
|
2052
2319
|
return;
|
|
@@ -2057,6 +2324,7 @@ function registerSavings(program) {
|
|
|
2057
2324
|
info(c.dim(" estimates with stated assumptions (~4 chars/token; ~400 tokens/file baseline); scales with repo size."));
|
|
2058
2325
|
}
|
|
2059
2326
|
printBreakdown(usage);
|
|
2327
|
+
printSplit(usage);
|
|
2060
2328
|
});
|
|
2061
2329
|
applyGlobalOptions(cmd);
|
|
2062
2330
|
}
|
|
@@ -2083,6 +2351,24 @@ function printBreakdown(usage) {
|
|
|
2083
2351
|
);
|
|
2084
2352
|
info(c.dim(` avg success across commands: ${usage.avgSuccessPct}%`));
|
|
2085
2353
|
}
|
|
2354
|
+
function printSplit(usage) {
|
|
2355
|
+
const label = (key) => key === "mcp" ? "MCP tools" : key === "cli" ? "vg CLI" : key;
|
|
2356
|
+
if (usage.sources.length) {
|
|
2357
|
+
info("");
|
|
2358
|
+
info(c.bold(" by source") + c.dim(" (how the call arrived)"));
|
|
2359
|
+
for (const s of usage.sources) {
|
|
2360
|
+
const pct = usage.totals.calls ? Math.round(s.calls / usage.totals.calls * 100) : 0;
|
|
2361
|
+
info(` ${label(s.key).padEnd(12)} ${String(s.calls).padStart(6)} calls ${c.dim(`(${pct}%)`)}`);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
if (usage.clients.length) {
|
|
2365
|
+
info("");
|
|
2366
|
+
info(c.bold(" by client") + c.dim(" (which AI is calling; pass --client to vg to attribute CLI calls)"));
|
|
2367
|
+
for (const cl of usage.clients) {
|
|
2368
|
+
info(` ${cl.key.padEnd(12)} ${String(cl.calls).padStart(6)} calls`);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2086
2372
|
function registerLib(program) {
|
|
2087
2373
|
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
2374
|
const global = readGlobal(this);
|
|
@@ -2136,7 +2422,7 @@ async function addCmd(root, rest, opts, asJson) {
|
|
|
2136
2422
|
}
|
|
2137
2423
|
function readPackageVersion(root) {
|
|
2138
2424
|
try {
|
|
2139
|
-
const pkg = JSON.parse(
|
|
2425
|
+
const pkg = JSON.parse(fs6__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
|
|
2140
2426
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
2141
2427
|
} catch {
|
|
2142
2428
|
return void 0;
|
|
@@ -2144,7 +2430,7 @@ function readPackageVersion(root) {
|
|
|
2144
2430
|
}
|
|
2145
2431
|
function readOptionalFile(p) {
|
|
2146
2432
|
try {
|
|
2147
|
-
const t =
|
|
2433
|
+
const t = fs6__default.readFileSync(p, "utf8");
|
|
2148
2434
|
return t.trim() ? t : void 0;
|
|
2149
2435
|
} catch {
|
|
2150
2436
|
return void 0;
|
|
@@ -2289,8 +2575,8 @@ function registerExport(program) {
|
|
|
2289
2575
|
out(content.trimEnd());
|
|
2290
2576
|
return;
|
|
2291
2577
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2578
|
+
fs6.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
|
|
2579
|
+
fs6.writeFileSync(file, content);
|
|
2294
2580
|
info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
|
|
2295
2581
|
});
|
|
2296
2582
|
applyGlobalOptions(cmd);
|
|
@@ -2313,24 +2599,24 @@ function registerBundle(program) {
|
|
|
2313
2599
|
} catch {
|
|
2314
2600
|
throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
|
|
2315
2601
|
}
|
|
2316
|
-
|
|
2602
|
+
fs6.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
|
|
2317
2603
|
let grammarCount = 0;
|
|
2318
2604
|
for (const { fileName, absPath } of grammarFiles) {
|
|
2319
|
-
|
|
2605
|
+
fs6.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
|
|
2320
2606
|
grammarCount++;
|
|
2321
2607
|
}
|
|
2322
2608
|
const included = [`grammars/ (${grammarCount} .wasm)`];
|
|
2323
2609
|
const graphSrc = global.graph ?? defaultGraphPath(root);
|
|
2324
|
-
if (
|
|
2325
|
-
|
|
2610
|
+
if (fs6.existsSync(graphSrc)) {
|
|
2611
|
+
fs6.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
|
|
2326
2612
|
included.push("graph.json");
|
|
2327
2613
|
}
|
|
2328
2614
|
const cat = catalogPath(root);
|
|
2329
|
-
if (
|
|
2330
|
-
|
|
2615
|
+
if (fs6.existsSync(cat)) {
|
|
2616
|
+
fs6.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
|
|
2331
2617
|
included.push("vibgrate.lib.json");
|
|
2332
|
-
if (
|
|
2333
|
-
|
|
2618
|
+
if (fs6.existsSync(libDir(root))) {
|
|
2619
|
+
fs6.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
|
|
2334
2620
|
included.push("lib/");
|
|
2335
2621
|
}
|
|
2336
2622
|
}
|
|
@@ -2342,7 +2628,7 @@ function registerBundle(program) {
|
|
|
2342
2628
|
included,
|
|
2343
2629
|
usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
|
|
2344
2630
|
};
|
|
2345
|
-
|
|
2631
|
+
fs6.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
|
|
2346
2632
|
`);
|
|
2347
2633
|
if (global.json) {
|
|
2348
2634
|
json({ out: path.relative(root, outDir), ...manifest });
|
|
@@ -2358,22 +2644,22 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
2358
2644
|
const rootDir = path.resolve(targetPath);
|
|
2359
2645
|
const vibgrateDir2 = path.join(rootDir, ".vibgrate");
|
|
2360
2646
|
await ensureDir(vibgrateDir2);
|
|
2361
|
-
console.log(
|
|
2647
|
+
console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold(".vibgrate/")} directory`);
|
|
2362
2648
|
const configPath = path.join(rootDir, "vibgrate.config.ts");
|
|
2363
2649
|
if (await pathExists(configPath)) {
|
|
2364
|
-
console.log(
|
|
2650
|
+
console.log(chalk8.dim(" vibgrate.config.ts already exists, skipping"));
|
|
2365
2651
|
} else {
|
|
2366
2652
|
await writeDefaultConfig(rootDir);
|
|
2367
|
-
console.log(
|
|
2653
|
+
console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold("vibgrate.config.ts")}`);
|
|
2368
2654
|
}
|
|
2369
2655
|
if (opts.baseline) {
|
|
2370
|
-
const { runBaseline } = await import('./baseline-
|
|
2656
|
+
const { runBaseline } = await import('./baseline-7AFTGG3I.js');
|
|
2371
2657
|
await runBaseline(rootDir);
|
|
2372
2658
|
}
|
|
2373
2659
|
console.log("");
|
|
2374
|
-
console.log(
|
|
2375
|
-
console.log(` ${
|
|
2376
|
-
console.log(` ${
|
|
2660
|
+
console.log(chalk8.bold("Next steps:"));
|
|
2661
|
+
console.log(` ${chalk8.cyan("vibgrate scan")} Scan for upgrade drift`);
|
|
2662
|
+
console.log(` ${chalk8.cyan("vibgrate baseline")} Create a drift baseline`);
|
|
2377
2663
|
console.log("");
|
|
2378
2664
|
});
|
|
2379
2665
|
|
|
@@ -2408,22 +2694,22 @@ async function detectAiAssistant(rootDir) {
|
|
|
2408
2694
|
return null;
|
|
2409
2695
|
}
|
|
2410
2696
|
function printAiContextPrompt(detectedAssistant) {
|
|
2411
|
-
const teal2 =
|
|
2412
|
-
const mint2 =
|
|
2697
|
+
const teal2 = chalk8.hex("#3FB0A4");
|
|
2698
|
+
const mint2 = chalk8.hex("#4FE3C1");
|
|
2413
2699
|
const cli = resolveCliInvocation();
|
|
2414
2700
|
const installArgs = detectedAssistant ? `install ${detectedAssistant}` : "install --all";
|
|
2415
2701
|
const installCmd = `${cli} ${installArgs}`;
|
|
2416
|
-
const detectedNote = detectedAssistant ?
|
|
2702
|
+
const detectedNote = detectedAssistant ? chalk8.dim(` (${detectedAssistant} config detected)`) : "";
|
|
2417
2703
|
console.log("");
|
|
2418
2704
|
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") + " " +
|
|
2705
|
+
console.log(teal2(" \u2502") + " " + mint2("\u25C6") + " " + chalk8.bold.white("Get AI-aware answers in your editor") + " " + teal2("\u2502"));
|
|
2420
2706
|
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
2707
|
console.log("");
|
|
2422
|
-
console.log(" " +
|
|
2708
|
+
console.log(" " + chalk8.bold.white(installCmd) + detectedNote);
|
|
2423
2709
|
console.log("");
|
|
2424
|
-
console.log(" " + teal2("\xB7") + " " +
|
|
2425
|
-
console.log(" " + teal2("\xB7") + " " +
|
|
2426
|
-
console.log(" " + teal2("\xB7") + " " +
|
|
2710
|
+
console.log(" " + teal2("\xB7") + " " + chalk8.white("Code map in your assistant") + chalk8.dim(" \u2014 call trees, impact surfaces, import paths"));
|
|
2711
|
+
console.log(" " + teal2("\xB7") + " " + chalk8.white("Offline drift") + chalk8.dim(" \u2014 DriftScore and upgrade priorities, right inside your editor"));
|
|
2712
|
+
console.log(" " + teal2("\xB7") + " " + chalk8.white("Version-correct library docs") + chalk8.dim(" \u2014 pinned to your lockfile, no hallucinated APIs"));
|
|
2427
2713
|
console.log("");
|
|
2428
2714
|
}
|
|
2429
2715
|
|
|
@@ -2434,14 +2720,14 @@ function shouldBuildCodeMap(opts) {
|
|
|
2434
2720
|
async function autoPush(artifact, rootDir, opts) {
|
|
2435
2721
|
const dsn = resolveDsn(opts.dsn);
|
|
2436
2722
|
if (!dsn) {
|
|
2437
|
-
console.error(
|
|
2438
|
-
console.error(
|
|
2723
|
+
console.error(chalk8.red("No DSN provided for push."));
|
|
2724
|
+
console.error(chalk8.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
|
|
2439
2725
|
if (opts.strict) process.exit(1);
|
|
2440
2726
|
return;
|
|
2441
2727
|
}
|
|
2442
2728
|
const parsed = parseDsn(dsn);
|
|
2443
2729
|
if (!parsed) {
|
|
2444
|
-
console.error(
|
|
2730
|
+
console.error(chalk8.red("Invalid DSN format."));
|
|
2445
2731
|
if (opts.strict) process.exit(1);
|
|
2446
2732
|
return;
|
|
2447
2733
|
}
|
|
@@ -2452,7 +2738,7 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
2452
2738
|
try {
|
|
2453
2739
|
host = resolveIngestHost(opts.region);
|
|
2454
2740
|
} catch (e) {
|
|
2455
|
-
console.error(
|
|
2741
|
+
console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
|
|
2456
2742
|
if (opts.strict) process.exit(1);
|
|
2457
2743
|
return;
|
|
2458
2744
|
}
|
|
@@ -2460,7 +2746,7 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
2460
2746
|
const originalSize = JSON.stringify(artifact).length;
|
|
2461
2747
|
const compressedSize = body.length;
|
|
2462
2748
|
const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
|
|
2463
|
-
console.log(
|
|
2749
|
+
console.log(chalk8.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
|
|
2464
2750
|
try {
|
|
2465
2751
|
const { response, host: uploadedHost } = await uploadScanArtifact({
|
|
2466
2752
|
scheme: parsed.scheme,
|
|
@@ -2480,36 +2766,36 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
2480
2766
|
const result = await response.json();
|
|
2481
2767
|
if (result.unchanged) {
|
|
2482
2768
|
console.log(
|
|
2483
|
-
|
|
2769
|
+
chalk8.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
|
|
2484
2770
|
);
|
|
2485
2771
|
if (result.previousIngestId) {
|
|
2486
2772
|
emitIngestIdLine(result.previousIngestId, { unchanged: true });
|
|
2487
2773
|
const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.previousIngestId}`;
|
|
2488
|
-
console.log(
|
|
2774
|
+
console.log(chalk8.dim(` Previous report: ${dashUrl}`));
|
|
2489
2775
|
} else if (opts.strict) {
|
|
2490
|
-
console.error(
|
|
2776
|
+
console.error(chalk8.red("Repository unchanged but no previous ingest id returned."));
|
|
2491
2777
|
process.exit(1);
|
|
2492
2778
|
}
|
|
2493
2779
|
return;
|
|
2494
2780
|
}
|
|
2495
|
-
console.log(
|
|
2781
|
+
console.log(chalk8.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
|
|
2496
2782
|
if (result.ingestId) {
|
|
2497
2783
|
emitIngestIdLine(result.ingestId);
|
|
2498
2784
|
const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.ingestId}`;
|
|
2499
2785
|
const CLEAR_LINE = process.platform === "win32" ? "\x1B[0G\x1B[2K" : "";
|
|
2500
2786
|
console.log("");
|
|
2501
|
-
console.log(CLEAR_LINE +
|
|
2787
|
+
console.log(CLEAR_LINE + chalk8.dim(" Processing continues in the background. Results available shortly."));
|
|
2502
2788
|
console.log("");
|
|
2503
|
-
console.log(CLEAR_LINE +
|
|
2504
|
-
console.log(CLEAR_LINE +
|
|
2505
|
-
console.log(CLEAR_LINE + " " +
|
|
2506
|
-
console.log(CLEAR_LINE +
|
|
2789
|
+
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"));
|
|
2790
|
+
console.log(CLEAR_LINE + chalk8.bold(" \u{1F4CA} View Scan Report"));
|
|
2791
|
+
console.log(CLEAR_LINE + " " + chalk8.underline.cyan(dashUrl));
|
|
2792
|
+
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
2793
|
console.log("");
|
|
2508
2794
|
console.log("");
|
|
2509
2795
|
}
|
|
2510
2796
|
} catch (e) {
|
|
2511
2797
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2512
|
-
console.error(
|
|
2798
|
+
console.error(chalk8.red(`Upload failed: ${msg}`));
|
|
2513
2799
|
if (opts.strict) process.exit(1);
|
|
2514
2800
|
}
|
|
2515
2801
|
}
|
|
@@ -2521,15 +2807,15 @@ function reportStandards(rootDir) {
|
|
|
2521
2807
|
if (!loaded.policy) return;
|
|
2522
2808
|
const violations = checkStandards(loaded.policy, inventory(rootDir).records);
|
|
2523
2809
|
if (violations.length === 0) {
|
|
2524
|
-
console.log(
|
|
2810
|
+
console.log(chalk8.green("\u2714") + " Standards: no banned dependencies in use");
|
|
2525
2811
|
return;
|
|
2526
2812
|
}
|
|
2527
|
-
console.log(
|
|
2813
|
+
console.log(chalk8.red(`
|
|
2528
2814
|
\u26A0 Standards: ${violations.length} banned dependency(ies) in use`));
|
|
2529
2815
|
for (const v of violations) {
|
|
2530
|
-
const fix = v.use ?
|
|
2531
|
-
const why = v.reason ?
|
|
2532
|
-
console.log(` ${
|
|
2816
|
+
const fix = v.use ? chalk8.dim(` \u2192 use ${v.use}`) : "";
|
|
2817
|
+
const why = v.reason ? chalk8.dim(` (${v.reason})`) : "";
|
|
2818
|
+
console.log(` ${chalk8.red("banned")} ${v.ecosystem}:${v.name}${v.installed ? chalk8.dim(` ${v.installed}`) : ""}${fix}${why}`);
|
|
2533
2819
|
}
|
|
2534
2820
|
}
|
|
2535
2821
|
function parseNonNegativeNumber(value, label) {
|
|
@@ -2548,7 +2834,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2548
2834
|
).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
2835
|
const rootDir = path.resolve(targetPath);
|
|
2550
2836
|
if (!await pathExists$1(rootDir)) {
|
|
2551
|
-
console.error(
|
|
2837
|
+
console.error(chalk8.red(`Path does not exist: ${rootDir}`));
|
|
2552
2838
|
process.exit(1);
|
|
2553
2839
|
}
|
|
2554
2840
|
const hasDsn = !!resolveDsn(opts.dsn);
|
|
@@ -2575,9 +2861,9 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2575
2861
|
planTier = preflight.plan?.tier;
|
|
2576
2862
|
upgradeUrl = preflight.upgradeUrl ?? `https://${dashHostForIngestHost(preflight.ingestHost ?? ingestHost)}/${parsed.workspaceId}`;
|
|
2577
2863
|
if (preflight.vm && !preflight.vm.allowed) {
|
|
2578
|
-
console.error(
|
|
2864
|
+
console.error(chalk8.red(preflight.error ?? "VM meter usage exhausted"));
|
|
2579
2865
|
console.error(
|
|
2580
|
-
|
|
2866
|
+
chalk8.dim(
|
|
2581
2867
|
`VM minutes: ${preflight.vm.used}/${preflight.vm.limit} (${preflight.plan.label} plan) \u2014 enable overages or upgrade your plan.`
|
|
2582
2868
|
)
|
|
2583
2869
|
);
|
|
@@ -2585,25 +2871,25 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2585
2871
|
}
|
|
2586
2872
|
if (preflight.repositories && !preflight.repositories.allowed) {
|
|
2587
2873
|
console.error(
|
|
2588
|
-
|
|
2874
|
+
chalk8.red(
|
|
2589
2875
|
preflight.error ?? `Repository limit reached for the ${preflight.plan.label} plan \u2014 cannot scan a new repository.`
|
|
2590
2876
|
)
|
|
2591
2877
|
);
|
|
2592
2878
|
const max = preflight.repositories.max < 0 ? "unlimited" : String(preflight.repositories.max);
|
|
2593
2879
|
console.error(
|
|
2594
|
-
|
|
2880
|
+
chalk8.dim(
|
|
2595
2881
|
`Repositories: ${preflight.repositories.total}/${max} (${preflight.plan.label} plan) \u2014 archive a repository or upgrade your plan.`
|
|
2596
2882
|
)
|
|
2597
2883
|
);
|
|
2598
2884
|
if (preflight.upgradeUrl) {
|
|
2599
|
-
console.error(
|
|
2885
|
+
console.error(chalk8.dim(` Upgrade: ${preflight.upgradeUrl}`));
|
|
2600
2886
|
}
|
|
2601
2887
|
process.exit(1);
|
|
2602
2888
|
}
|
|
2603
2889
|
if (preflight.status === "error" || !preflight.scans.allowed) {
|
|
2604
|
-
console.error(
|
|
2890
|
+
console.error(chalk8.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
|
|
2605
2891
|
console.error(
|
|
2606
|
-
|
|
2892
|
+
chalk8.dim(
|
|
2607
2893
|
`Credits: ${preflight.scans.used}/${preflight.scans.limit} (${preflight.plan.label} plan)`
|
|
2608
2894
|
)
|
|
2609
2895
|
);
|
|
@@ -2611,27 +2897,27 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2611
2897
|
process.exit(1);
|
|
2612
2898
|
}
|
|
2613
2899
|
console.log(
|
|
2614
|
-
|
|
2900
|
+
chalk8.dim(
|
|
2615
2901
|
`Plan: ${preflight.plan.label} \u2014 scan credits ${preflight.scans.used}/${preflight.scans.limit} this month`
|
|
2616
2902
|
)
|
|
2617
2903
|
);
|
|
2618
2904
|
if (preflight.repository?.unchanged && !opts.force) {
|
|
2619
2905
|
console.log(
|
|
2620
|
-
|
|
2906
|
+
chalk8.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
|
|
2621
2907
|
);
|
|
2622
2908
|
if (preflight.repository.lastIngestId) {
|
|
2623
2909
|
emitIngestIdLine(preflight.repository.lastIngestId, { unchanged: true });
|
|
2624
2910
|
const dashUrl = `https://${dashHostForIngestHost(ingestHost)}/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
|
|
2625
|
-
console.log(
|
|
2911
|
+
console.log(chalk8.dim(` Latest report: ${dashUrl}`));
|
|
2626
2912
|
} else if (opts.strict) {
|
|
2627
|
-
console.error(
|
|
2913
|
+
console.error(chalk8.red("Repository unchanged but no previous ingest id available."));
|
|
2628
2914
|
process.exit(1);
|
|
2629
2915
|
}
|
|
2630
2916
|
return;
|
|
2631
2917
|
}
|
|
2632
2918
|
} catch (e) {
|
|
2633
2919
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2634
|
-
console.error(
|
|
2920
|
+
console.error(chalk8.yellow(`Preflight check failed: ${msg}`));
|
|
2635
2921
|
if (opts.strict) process.exit(1);
|
|
2636
2922
|
}
|
|
2637
2923
|
}
|
|
@@ -2705,24 +2991,24 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
2705
2991
|
const hasErrors = artifact.findings.some((f) => f.level === "error");
|
|
2706
2992
|
const hasWarnings = artifact.findings.some((f) => f.level === "warning");
|
|
2707
2993
|
if (opts.failOn === "error" && hasErrors) {
|
|
2708
|
-
console.error(
|
|
2994
|
+
console.error(chalk8.red(`
|
|
2709
2995
|
Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
|
|
2710
2996
|
process.exit(2);
|
|
2711
2997
|
}
|
|
2712
2998
|
if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
|
|
2713
|
-
console.error(
|
|
2999
|
+
console.error(chalk8.red(`
|
|
2714
3000
|
Failing: findings detected at warn level or above.`));
|
|
2715
3001
|
process.exit(2);
|
|
2716
3002
|
}
|
|
2717
3003
|
}
|
|
2718
3004
|
if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
|
|
2719
|
-
console.error(
|
|
3005
|
+
console.error(chalk8.red(`
|
|
2720
3006
|
Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
|
|
2721
3007
|
process.exit(2);
|
|
2722
3008
|
}
|
|
2723
3009
|
if (scanOpts.driftWorseningPercent !== void 0) {
|
|
2724
3010
|
if (artifact.delta === void 0) {
|
|
2725
|
-
console.error(
|
|
3011
|
+
console.error(chalk8.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
|
|
2726
3012
|
process.exit(2);
|
|
2727
3013
|
}
|
|
2728
3014
|
if (artifact.delta > 0) {
|
|
@@ -2730,7 +3016,7 @@ Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget
|
|
|
2730
3016
|
const denominator = Math.max(Math.abs(baselineScore), 1e-4);
|
|
2731
3017
|
const worseningPercent = artifact.delta / denominator * 100;
|
|
2732
3018
|
if (worseningPercent > scanOpts.driftWorseningPercent) {
|
|
2733
|
-
console.error(
|
|
3019
|
+
console.error(chalk8.red(`
|
|
2734
3020
|
Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (threshold ${scanOpts.driftWorseningPercent}%).`));
|
|
2735
3021
|
process.exit(2);
|
|
2736
3022
|
}
|
|
@@ -2808,7 +3094,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
2808
3094
|
function scanStaleness(rootDir, artifactPath) {
|
|
2809
3095
|
let ref;
|
|
2810
3096
|
try {
|
|
2811
|
-
ref =
|
|
3097
|
+
ref = fs6.statSync(artifactPath).mtimeMs;
|
|
2812
3098
|
} catch {
|
|
2813
3099
|
return { stale: false };
|
|
2814
3100
|
}
|
|
@@ -2820,7 +3106,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
2820
3106
|
if (depth > 8 || scanned > MAX_ENTRIES) return;
|
|
2821
3107
|
let entries;
|
|
2822
3108
|
try {
|
|
2823
|
-
entries =
|
|
3109
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2824
3110
|
} catch {
|
|
2825
3111
|
return;
|
|
2826
3112
|
}
|
|
@@ -2835,7 +3121,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
2835
3121
|
const abs = path.join(dir, e.name);
|
|
2836
3122
|
let mtimeMs;
|
|
2837
3123
|
try {
|
|
2838
|
-
mtimeMs =
|
|
3124
|
+
mtimeMs = fs6.statSync(abs).mtimeMs;
|
|
2839
3125
|
} catch {
|
|
2840
3126
|
continue;
|
|
2841
3127
|
}
|
|
@@ -2969,7 +3255,7 @@ function analyzeTree(root, packages) {
|
|
|
2969
3255
|
if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
|
|
2970
3256
|
let entries;
|
|
2971
3257
|
try {
|
|
2972
|
-
entries =
|
|
3258
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2973
3259
|
} catch {
|
|
2974
3260
|
return;
|
|
2975
3261
|
}
|
|
@@ -2987,8 +3273,8 @@ function analyzeTree(root, packages) {
|
|
|
2987
3273
|
const full = path.join(dir, entry.name);
|
|
2988
3274
|
let content;
|
|
2989
3275
|
try {
|
|
2990
|
-
if (
|
|
2991
|
-
content =
|
|
3276
|
+
if (fs6.statSync(full).size > MAX_FILE_BYTES) continue;
|
|
3277
|
+
content = fs6.readFileSync(full, "utf8");
|
|
2992
3278
|
} catch {
|
|
2993
3279
|
continue;
|
|
2994
3280
|
}
|
|
@@ -3061,48 +3347,48 @@ function deltaSummary(delta) {
|
|
|
3061
3347
|
return parts.length ? parts.join(", ") : `${delta.total}`;
|
|
3062
3348
|
}
|
|
3063
3349
|
function riskLabel(score) {
|
|
3064
|
-
if (score <= 15) return
|
|
3065
|
-
if (score <= 40) return
|
|
3066
|
-
return
|
|
3350
|
+
if (score <= 15) return chalk8.green(`${score}/100 low`);
|
|
3351
|
+
if (score <= 40) return chalk8.yellow(`${score}/100 moderate`);
|
|
3352
|
+
return chalk8.red(`${score}/100 high`);
|
|
3067
3353
|
}
|
|
3068
3354
|
function planBlock(plan, recommended) {
|
|
3069
3355
|
const lines = [];
|
|
3070
|
-
const marker = recommended ?
|
|
3071
|
-
lines.push(`${marker} ${
|
|
3356
|
+
const marker = recommended ? chalk8.green("\u25CF recommended") : chalk8.dim("\u25CB");
|
|
3357
|
+
lines.push(`${marker} ${chalk8.bold(plan.label)} \u2014 ${chalk8.dim(plan.description)}`);
|
|
3072
3358
|
const drift = typeof plan.expectedDriftScore === "number" && typeof plan.driftDelta === "number" ? ` \xB7 DriftScore \u2192${plan.expectedDriftScore} (${plan.driftDelta <= 0 ? "" : "+"}${plan.driftDelta})` : "";
|
|
3073
3359
|
lines.push(
|
|
3074
3360
|
` risk ${riskLabel(plan.riskScore)} \xB7 ${plan.upgrades.length} upgrade(s) \xB7 fixes ${deltaSummary(plan.fixes)}${drift}`
|
|
3075
3361
|
);
|
|
3076
3362
|
if (plan.introduces.total) {
|
|
3077
|
-
lines.push(
|
|
3363
|
+
lines.push(chalk8.red(` \u26A0 introduces ${deltaSummary(plan.introduces)} advisory(ies) in target versions`));
|
|
3078
3364
|
}
|
|
3079
3365
|
const shown = plan.upgrades.slice(0, 12);
|
|
3080
3366
|
for (const u of shown) {
|
|
3081
|
-
const codemod = u.playbook?.codemod ?
|
|
3082
|
-
lines.push(` ${
|
|
3367
|
+
const codemod = u.playbook?.codemod ? chalk8.magenta(` [codemod: ${u.playbook.codemod}]`) : "";
|
|
3368
|
+
lines.push(` ${chalk8.cyan(u.package)} ${chalk8.dim(`${u.from ?? "?"} \u2192 ${u.to ?? "?"}`)} ${chalk8.dim(u.reason)}${codemod}`);
|
|
3083
3369
|
}
|
|
3084
3370
|
if (plan.upgrades.length > shown.length) {
|
|
3085
|
-
lines.push(
|
|
3371
|
+
lines.push(chalk8.dim(` \u2026 and ${plan.upgrades.length - shown.length} more`));
|
|
3086
3372
|
}
|
|
3087
3373
|
if (plan.upgrades.length === 0) {
|
|
3088
|
-
lines.push(
|
|
3374
|
+
lines.push(chalk8.dim(" (no upgrades in this plan)"));
|
|
3089
3375
|
}
|
|
3090
3376
|
return lines.join("\n");
|
|
3091
3377
|
}
|
|
3092
3378
|
function dataNote(report) {
|
|
3093
3379
|
if (report.vulnerabilityData === "unavailable") {
|
|
3094
|
-
return
|
|
3380
|
+
return chalk8.dim("Advisory data was unavailable \u2014 vulnerability impact is not shown.");
|
|
3095
3381
|
}
|
|
3096
3382
|
if (report.vulnerabilityData === "partial") {
|
|
3097
|
-
return
|
|
3383
|
+
return chalk8.dim("Advisory data was partial \u2014 some ecosystems were not checked.");
|
|
3098
3384
|
}
|
|
3099
3385
|
return null;
|
|
3100
3386
|
}
|
|
3101
3387
|
function renderText(report) {
|
|
3102
3388
|
const out2 = [];
|
|
3103
|
-
out2.push(
|
|
3389
|
+
out2.push(chalk8.bold("Vibgrate fix \u2014 upgrade plan"));
|
|
3104
3390
|
out2.push(
|
|
3105
|
-
|
|
3391
|
+
chalk8.dim(
|
|
3106
3392
|
`${report.totalCandidates} drifted dependency(ies) analysed \xB7 ${report.deepAnalysis ? "deep (major) analysis on" : "preflight only"}`
|
|
3107
3393
|
)
|
|
3108
3394
|
);
|
|
@@ -3110,18 +3396,18 @@ function renderText(report) {
|
|
|
3110
3396
|
if (note) out2.push(note);
|
|
3111
3397
|
if (report.exploitability && report.exploitability.kevPackages > 0) {
|
|
3112
3398
|
const epss = report.exploitability.maxEpss != null ? `, peak EPSS ${(report.exploitability.maxEpss * 100).toFixed(0)}%` : "";
|
|
3113
|
-
out2.push(
|
|
3399
|
+
out2.push(chalk8.red(`\u26A0 ${report.exploitability.kevPackages} package(s) carry a KNOWN-EXPLOITED (KEV) advisory${epss} \u2014 prioritise these.`));
|
|
3114
3400
|
}
|
|
3115
3401
|
out2.push("");
|
|
3116
3402
|
for (const plan of report.plans) {
|
|
3117
3403
|
out2.push(planBlock(plan, plan.tier === report.recommended));
|
|
3118
3404
|
out2.push("");
|
|
3119
3405
|
}
|
|
3120
|
-
out2.push(
|
|
3121
|
-
out2.push(` ${
|
|
3406
|
+
out2.push(chalk8.bold("Recommendation"));
|
|
3407
|
+
out2.push(` ${chalk8.green(report.plans.find((p) => p.tier === report.recommended)?.label ?? report.recommended)} \u2014 ${report.rationale}`);
|
|
3122
3408
|
if (report.unresolved.total) {
|
|
3123
3409
|
out2.push("");
|
|
3124
|
-
out2.push(
|
|
3410
|
+
out2.push(chalk8.red(`\u26A0 ${deltaSummary(report.unresolved)} advisory(ies) have no upgrade path in any plan.`));
|
|
3125
3411
|
}
|
|
3126
3412
|
return out2.join("\n");
|
|
3127
3413
|
}
|
|
@@ -3193,11 +3479,15 @@ function estimateDriftScore(artifact, upgraded) {
|
|
|
3193
3479
|
}
|
|
3194
3480
|
return computeDriftScore(projects).score;
|
|
3195
3481
|
}
|
|
3482
|
+
function isRootDir(dir) {
|
|
3483
|
+
return dir === "" || dir === ".";
|
|
3484
|
+
}
|
|
3196
3485
|
function pmCommandFor(ecosystem, pkg, version, pm = "npm", opts = {}) {
|
|
3486
|
+
const ws = opts.workspaceRoot && pm === "pnpm" ? ["-w"] : [];
|
|
3197
3487
|
switch (ecosystem) {
|
|
3198
3488
|
case "npm":
|
|
3199
|
-
if (pm === "pnpm") return { cmd: "pnpm", args:
|
|
3200
|
-
if (pm === "yarn") return { cmd: "yarn", args:
|
|
3489
|
+
if (pm === "pnpm") return { cmd: "pnpm", args: ["add", ...ws, `${pkg}@${version}`] };
|
|
3490
|
+
if (pm === "yarn") return { cmd: "yarn", args: ["add", `${pkg}@${version}`] };
|
|
3201
3491
|
if (pm === "bun") return { cmd: "bun", args: ["add", `${pkg}@${version}`] };
|
|
3202
3492
|
return { cmd: "npm", args: ["install", `${pkg}@${version}`] };
|
|
3203
3493
|
case "pypi":
|
|
@@ -3233,46 +3523,232 @@ function defaultRun(cmd, cwd) {
|
|
|
3233
3523
|
if (res.status !== 0) return { ok: false, detail: `exit ${res.status ?? "signal"}` };
|
|
3234
3524
|
return { ok: true };
|
|
3235
3525
|
}
|
|
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
|
-
}
|
|
3526
|
+
var ROOT_TARGET = [{ dir: ".", isWorkspaceRoot: false }];
|
|
3248
3527
|
function applyPlan(rootDir, upgrades, opts = {}) {
|
|
3249
3528
|
const run = opts.run ?? defaultRun;
|
|
3250
|
-
const
|
|
3529
|
+
const resolveTargets = opts.resolveTargets ?? (() => ROOT_TARGET);
|
|
3251
3530
|
const results = [];
|
|
3252
3531
|
for (const u of upgrades) {
|
|
3253
3532
|
if (!u.to) {
|
|
3254
3533
|
results.push({ package: u.package, to: u.to, status: "skipped", detail: "no target version" });
|
|
3255
3534
|
continue;
|
|
3256
3535
|
}
|
|
3257
|
-
|
|
3258
|
-
if (!command) {
|
|
3536
|
+
if (!pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager)) {
|
|
3259
3537
|
results.push({ package: u.package, to: u.to, status: "manual", detail: `${u.ecosystem}: update the manifest to ${u.to} manually` });
|
|
3260
3538
|
continue;
|
|
3261
3539
|
}
|
|
3540
|
+
const targets = resolveTargets(u);
|
|
3541
|
+
const effective = targets.length > 0 ? targets : ROOT_TARGET;
|
|
3542
|
+
const labelled = effective.length > 1;
|
|
3543
|
+
const where = (t) => labelled || !isRootDir(t.dir) ? `${t.dir || "."}: ` : "";
|
|
3262
3544
|
if (opts.dryRun) {
|
|
3263
|
-
|
|
3545
|
+
const detail = effective.map((t) => {
|
|
3546
|
+
const c2 = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager, { workspaceRoot: t.isWorkspaceRoot });
|
|
3547
|
+
return `would run: ${c2.cmd} ${c2.args.join(" ")}${labelled || !isRootDir(t.dir) ? ` (in ${t.dir || "."})` : ""}`;
|
|
3548
|
+
}).join("; ");
|
|
3549
|
+
results.push({ package: u.package, to: u.to, status: "skipped", detail });
|
|
3264
3550
|
continue;
|
|
3265
3551
|
}
|
|
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
|
|
3552
|
+
const outcomes = effective.map((t) => {
|
|
3553
|
+
const c2 = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager, { workspaceRoot: t.isWorkspaceRoot });
|
|
3554
|
+
return { t, res: run(c2, path.join(rootDir, t.dir)) };
|
|
3272
3555
|
});
|
|
3556
|
+
const failures = outcomes.filter((o) => !o.res.ok);
|
|
3557
|
+
if (failures.length === 0) {
|
|
3558
|
+
results.push({
|
|
3559
|
+
package: u.package,
|
|
3560
|
+
to: u.to,
|
|
3561
|
+
status: "applied",
|
|
3562
|
+
detail: labelled ? `applied in ${effective.map((t) => t.dir || ".").join(", ")}` : void 0
|
|
3563
|
+
});
|
|
3564
|
+
} else if (failures.every((o) => o.res.toolMissing)) {
|
|
3565
|
+
results.push({
|
|
3566
|
+
package: u.package,
|
|
3567
|
+
to: u.to,
|
|
3568
|
+
status: "manual",
|
|
3569
|
+
detail: failures.map((o) => `${where(o.t)}${o.res.detail ?? "tool missing"} \u2014 upgrade manually`).join("; ")
|
|
3570
|
+
});
|
|
3571
|
+
} else {
|
|
3572
|
+
results.push({
|
|
3573
|
+
package: u.package,
|
|
3574
|
+
to: u.to,
|
|
3575
|
+
status: "failed",
|
|
3576
|
+
detail: failures.map((o) => `${where(o.t)}${o.res.detail ?? "failed"}`).join("; ")
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3273
3579
|
}
|
|
3274
3580
|
return results;
|
|
3275
3581
|
}
|
|
3582
|
+
var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
|
|
3583
|
+
var CACHE_DIR = path.join(os.homedir(), ".vibgrate");
|
|
3584
|
+
var CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
|
|
3585
|
+
async function fetchLatestVersion() {
|
|
3586
|
+
try {
|
|
3587
|
+
const controller = new AbortController();
|
|
3588
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
3589
|
+
timeout.unref?.();
|
|
3590
|
+
let response;
|
|
3591
|
+
try {
|
|
3592
|
+
response = await fetch(REGISTRY_URL, {
|
|
3593
|
+
headers: { Accept: "application/json" },
|
|
3594
|
+
signal: controller.signal
|
|
3595
|
+
});
|
|
3596
|
+
} finally {
|
|
3597
|
+
clearTimeout(timeout);
|
|
3598
|
+
}
|
|
3599
|
+
if (!response.ok) return null;
|
|
3600
|
+
const data = await response.json();
|
|
3601
|
+
const latest = data.version;
|
|
3602
|
+
if (!latest || !semver.valid(latest)) return null;
|
|
3603
|
+
await writeCache({ latest, checkedAt: Date.now() });
|
|
3604
|
+
return latest;
|
|
3605
|
+
} catch {
|
|
3606
|
+
return null;
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
async function writeCache(data) {
|
|
3610
|
+
try {
|
|
3611
|
+
await fs15.mkdir(CACHE_DIR, { recursive: true });
|
|
3612
|
+
await fs15.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
3613
|
+
} catch {
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
// src/reporting/commands/update.ts
|
|
3618
|
+
function detectGlobalInstall() {
|
|
3619
|
+
const execPath = process.argv[1] || "";
|
|
3620
|
+
if (execPath.includes("/lib/node_modules/") || execPath.includes("\\node_modules\\")) {
|
|
3621
|
+
if (!execPath.includes(process.cwd())) {
|
|
3622
|
+
if (execPath.includes("pnpm")) return "pnpm";
|
|
3623
|
+
if (execPath.includes("yarn")) return "yarn";
|
|
3624
|
+
if (execPath.includes("bun")) return "bun";
|
|
3625
|
+
return "npm";
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
return null;
|
|
3629
|
+
}
|
|
3630
|
+
function getGlobalUpdateCommand(pm, pkg, version) {
|
|
3631
|
+
const spec = `${pkg}@${version}`;
|
|
3632
|
+
switch (pm) {
|
|
3633
|
+
case "pnpm":
|
|
3634
|
+
return `pnpm add -g ${spec}`;
|
|
3635
|
+
case "yarn":
|
|
3636
|
+
return `yarn global add ${spec}`;
|
|
3637
|
+
case "bun":
|
|
3638
|
+
return `bun add -g ${spec}`;
|
|
3639
|
+
case "npm":
|
|
3640
|
+
default:
|
|
3641
|
+
return `npm install -g ${spec}`;
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
async function detectPackageManager(cwd) {
|
|
3645
|
+
if (await pathExists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
3646
|
+
if (await pathExists(path.join(cwd, "bun.lockb"))) return "bun";
|
|
3647
|
+
if (await pathExists(path.join(cwd, "yarn.lock"))) return "yarn";
|
|
3648
|
+
return "npm";
|
|
3649
|
+
}
|
|
3650
|
+
async function detectWorkspaceRoot(cwd) {
|
|
3651
|
+
if (await pathExists(path.join(cwd, "pnpm-workspace.yaml"))) return true;
|
|
3652
|
+
try {
|
|
3653
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
3654
|
+
const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
|
|
3655
|
+
const pkg = JSON.parse(raw);
|
|
3656
|
+
return pkg.workspaces != null;
|
|
3657
|
+
} catch {
|
|
3658
|
+
return false;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
function getInstallCommand(pm, pkg, version, isDev, opts = {}) {
|
|
3662
|
+
const spec = `${pkg}@${version}`;
|
|
3663
|
+
const ws = opts.workspaceRoot && pm === "pnpm" ? " -w" : "";
|
|
3664
|
+
switch (pm) {
|
|
3665
|
+
case "pnpm":
|
|
3666
|
+
return isDev ? `pnpm add${ws} -D ${spec}` : `pnpm add${ws} ${spec}`;
|
|
3667
|
+
case "yarn":
|
|
3668
|
+
return isDev ? `yarn add --dev ${spec}` : `yarn add ${spec}`;
|
|
3669
|
+
case "bun":
|
|
3670
|
+
return isDev ? `bun add -d ${spec}` : `bun add ${spec}`;
|
|
3671
|
+
case "npm":
|
|
3672
|
+
default:
|
|
3673
|
+
return isDev ? `npm install --save-dev ${spec}` : `npm install ${spec}`;
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
async function isDevDependency(cwd) {
|
|
3677
|
+
try {
|
|
3678
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
3679
|
+
const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
|
|
3680
|
+
const pkg = JSON.parse(raw);
|
|
3681
|
+
return Boolean(pkg.devDependencies?.["@vibgrate/cli"]);
|
|
3682
|
+
} catch {
|
|
3683
|
+
return true;
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
async function confirmWorkspaceRoot(pm) {
|
|
3687
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
|
|
3688
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3689
|
+
return new Promise((resolve16) => {
|
|
3690
|
+
rl.question(chalk8.yellow(`Update the ${pm} workspace root with -w? [y/N]: `), (ans) => {
|
|
3691
|
+
rl.close();
|
|
3692
|
+
const t = ans.trim().toLowerCase();
|
|
3693
|
+
resolve16(t === "y" || t === "yes");
|
|
3694
|
+
});
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
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(
|
|
3698
|
+
async (opts) => {
|
|
3699
|
+
console.log(chalk8.dim(`Current version: ${VERSION}`));
|
|
3700
|
+
console.log(chalk8.dim("Checking npm registry..."));
|
|
3701
|
+
const latest = await fetchLatestVersion();
|
|
3702
|
+
if (!latest) {
|
|
3703
|
+
console.error(chalk8.red("Could not reach the npm registry. Check your network connection."));
|
|
3704
|
+
process.exit(1);
|
|
3705
|
+
}
|
|
3706
|
+
const semver2 = await import('semver');
|
|
3707
|
+
if (!semver2.gt(latest, VERSION)) {
|
|
3708
|
+
console.log(chalk8.green("\u2714") + ` You are on the latest version (${VERSION}).`);
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
console.log(chalk8.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
|
|
3712
|
+
if (opts.check) {
|
|
3713
|
+
console.log(chalk8.dim('Run "vg update" to install.'));
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
const cwd = process.cwd();
|
|
3717
|
+
const globalPm = detectGlobalInstall();
|
|
3718
|
+
const isGlobal = opts.global || globalPm !== null;
|
|
3719
|
+
const pm = opts.pm || (globalPm ?? await detectPackageManager(cwd));
|
|
3720
|
+
let cmd;
|
|
3721
|
+
if (isGlobal) {
|
|
3722
|
+
cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
|
|
3723
|
+
console.log(chalk8.dim(`Updating global installation with ${pm}: ${cmd}`));
|
|
3724
|
+
} else {
|
|
3725
|
+
const isDev = await isDevDependency(cwd);
|
|
3726
|
+
let workspaceRoot = false;
|
|
3727
|
+
if (pm === "pnpm" && await detectWorkspaceRoot(cwd)) {
|
|
3728
|
+
console.log(
|
|
3729
|
+
chalk8.yellow("Detected a pnpm workspace root.") + chalk8.dim(" Installing here needs the -w flag and updates the root package.json.")
|
|
3730
|
+
);
|
|
3731
|
+
const proceed = opts.yes || opts.workspaceRoot || await confirmWorkspaceRoot(pm);
|
|
3732
|
+
if (!proceed) {
|
|
3733
|
+
const rootCmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot: true });
|
|
3734
|
+
console.log(chalk8.dim(`Skipped. To update the workspace root, run: ${rootCmd}`));
|
|
3735
|
+
console.log(chalk8.dim('or re-run "vg update --yes" (or --workspace-root) to let vg do it.'));
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
workspaceRoot = true;
|
|
3739
|
+
}
|
|
3740
|
+
cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot });
|
|
3741
|
+
console.log(chalk8.dim(`Using ${pm}: ${cmd}`));
|
|
3742
|
+
}
|
|
3743
|
+
try {
|
|
3744
|
+
execSync(cmd, { cwd, stdio: "inherit" });
|
|
3745
|
+
console.log(chalk8.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
|
|
3746
|
+
} catch {
|
|
3747
|
+
console.error(chalk8.red(`Update failed. Run manually: ${cmd}`));
|
|
3748
|
+
process.exit(1);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
);
|
|
3276
3752
|
|
|
3277
3753
|
// src/reporting/commands/fix.ts
|
|
3278
3754
|
var SEVERITY_RANK = { unknown: 0, low: 1, moderate: 2, high: 3, critical: 4 };
|
|
@@ -3323,12 +3799,12 @@ async function loadArtifact(rootDir, inFile, scan = defaultFixScanner) {
|
|
|
3323
3799
|
return readJsonFile(artifactPath);
|
|
3324
3800
|
}
|
|
3325
3801
|
console.error(
|
|
3326
|
-
|
|
3802
|
+
chalk8.dim(
|
|
3327
3803
|
`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
3804
|
)
|
|
3329
3805
|
);
|
|
3330
3806
|
} else {
|
|
3331
|
-
console.error(
|
|
3807
|
+
console.error(chalk8.dim("No scan found \u2014 running a drift scan first (code map skipped)\u2026"));
|
|
3332
3808
|
}
|
|
3333
3809
|
return scan(rootDir);
|
|
3334
3810
|
}
|
|
@@ -3345,20 +3821,20 @@ async function repositoryIdentity(rootDir, override) {
|
|
|
3345
3821
|
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
3822
|
const rootDir = path.resolve(targetPath);
|
|
3347
3823
|
if (!await pathExists(rootDir)) {
|
|
3348
|
-
console.error(
|
|
3824
|
+
console.error(chalk8.red(`Path does not exist: ${rootDir}`));
|
|
3349
3825
|
process.exit(1);
|
|
3350
3826
|
}
|
|
3351
3827
|
const failOn = opts.failOnVulns;
|
|
3352
3828
|
if (failOn && !(failOn in SEVERITY_RANK)) {
|
|
3353
|
-
console.error(
|
|
3829
|
+
console.error(chalk8.red(`Invalid --fail-on-vulns value '${opts.failOnVulns}'. Use one of: low, moderate, high, critical.`));
|
|
3354
3830
|
process.exit(1);
|
|
3355
3831
|
}
|
|
3356
3832
|
const dsn = resolveDsn(opts.dsn);
|
|
3357
3833
|
if (!dsn) {
|
|
3358
3834
|
const cli = resolveCliInvocation();
|
|
3359
|
-
console.error(
|
|
3835
|
+
console.error(chalk8.red("vg fix needs a Vibgrate login."));
|
|
3360
3836
|
console.error(
|
|
3361
|
-
|
|
3837
|
+
chalk8.dim(
|
|
3362
3838
|
`Run "${cli} login" (or set VIBGRATE_DSN / pass --dsn) to analyse upgrades with the hosted planner. See https://vibgrate.com/cli for details.`
|
|
3363
3839
|
)
|
|
3364
3840
|
);
|
|
@@ -3366,21 +3842,21 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
|
|
|
3366
3842
|
}
|
|
3367
3843
|
const parsed = parseDsn(dsn);
|
|
3368
3844
|
if (!parsed) {
|
|
3369
|
-
console.error(
|
|
3845
|
+
console.error(chalk8.red('Invalid DSN format. Re-run "vg login" or check VIBGRATE_DSN.'));
|
|
3370
3846
|
process.exit(1);
|
|
3371
3847
|
}
|
|
3372
3848
|
let host;
|
|
3373
3849
|
try {
|
|
3374
3850
|
host = opts.region ? resolveIngestHost(opts.region) : parsed.host;
|
|
3375
3851
|
} catch (e) {
|
|
3376
|
-
console.error(
|
|
3852
|
+
console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
|
|
3377
3853
|
process.exit(1);
|
|
3378
3854
|
}
|
|
3379
3855
|
let artifact;
|
|
3380
3856
|
try {
|
|
3381
3857
|
artifact = await loadArtifact(rootDir, opts.in);
|
|
3382
3858
|
} catch (e) {
|
|
3383
|
-
console.error(
|
|
3859
|
+
console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
|
|
3384
3860
|
process.exit(1);
|
|
3385
3861
|
}
|
|
3386
3862
|
const raw = collectCandidates(artifact);
|
|
@@ -3423,13 +3899,13 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
|
|
|
3423
3899
|
response = await parseFixPlanResponse(httpResponse);
|
|
3424
3900
|
} catch (e) {
|
|
3425
3901
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3426
|
-
console.error(
|
|
3427
|
-
console.error(
|
|
3902
|
+
console.error(chalk8.red(`Could not reach the upgrade planner: ${msg}`));
|
|
3903
|
+
console.error(chalk8.dim("Check your connection and try again, or see https://vibgrate.com/help."));
|
|
3428
3904
|
process.exit(1);
|
|
3429
3905
|
}
|
|
3430
3906
|
if (response.status === "error") {
|
|
3431
|
-
console.error(
|
|
3432
|
-
if (response.requestId) console.error(
|
|
3907
|
+
console.error(chalk8.red(response.error ?? "The planner returned an error."));
|
|
3908
|
+
if (response.requestId) console.error(chalk8.dim(` (ref ${response.requestId})`));
|
|
3433
3909
|
process.exit(1);
|
|
3434
3910
|
}
|
|
3435
3911
|
const currentDrift = artifact.drift?.score;
|
|
@@ -3458,7 +3934,7 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
|
|
|
3458
3934
|
}
|
|
3459
3935
|
if (stillOpen > 0) {
|
|
3460
3936
|
console.error(
|
|
3461
|
-
|
|
3937
|
+
chalk8.red(`
|
|
3462
3938
|
Failing: the recommended plan leaves ${stillOpen} advisory(ies) at/above ${failOn} unresolved.`)
|
|
3463
3939
|
);
|
|
3464
3940
|
process.exit(2);
|
|
@@ -3468,21 +3944,21 @@ Failing: the recommended plan leaves ${stillOpen} advisory(ies) at/above ${failO
|
|
|
3468
3944
|
function handleHttpError(response, workspaceId, host) {
|
|
3469
3945
|
const upgradeUrl = `https://${dashHostForIngestHost(host)}/${workspaceId}`;
|
|
3470
3946
|
if (response.status === 401 || response.status === 403) {
|
|
3471
|
-
console.error(
|
|
3472
|
-
console.error(
|
|
3947
|
+
console.error(chalk8.red("Not authorised. Your DSN may be invalid or lack access to this workspace."));
|
|
3948
|
+
console.error(chalk8.dim('Re-run "vg login" or check VIBGRATE_DSN.'));
|
|
3473
3949
|
process.exit(1);
|
|
3474
3950
|
}
|
|
3475
3951
|
if (response.status === 402) {
|
|
3476
|
-
console.error(
|
|
3477
|
-
console.error(
|
|
3952
|
+
console.error(chalk8.red("vg fix is a paid capability that is not enabled on your current plan."));
|
|
3953
|
+
console.error(chalk8.dim(`Upgrade to enable the hosted upgrade planner: ${upgradeUrl}`));
|
|
3478
3954
|
process.exit(1);
|
|
3479
3955
|
}
|
|
3480
3956
|
if (response.status === 429) {
|
|
3481
|
-
console.error(
|
|
3957
|
+
console.error(chalk8.red("Rate limited by the planner. Wait a moment and try again."));
|
|
3482
3958
|
process.exit(1);
|
|
3483
3959
|
}
|
|
3484
|
-
console.error(
|
|
3485
|
-
console.error(
|
|
3960
|
+
console.error(chalk8.red(`The planner returned HTTP ${response.status}.`));
|
|
3961
|
+
console.error(chalk8.dim("Try again shortly, or see https://vibgrate.com/help."));
|
|
3486
3962
|
process.exit(1);
|
|
3487
3963
|
}
|
|
3488
3964
|
function emit(report, format) {
|
|
@@ -3499,6 +3975,28 @@ function emit(report, format) {
|
|
|
3499
3975
|
break;
|
|
3500
3976
|
}
|
|
3501
3977
|
}
|
|
3978
|
+
function relProjectDir(rootDir, projectPath) {
|
|
3979
|
+
const rel3 = path.isAbsolute(projectPath) ? path.relative(rootDir, projectPath) : projectPath;
|
|
3980
|
+
return rel3 === "" ? "." : rel3;
|
|
3981
|
+
}
|
|
3982
|
+
async function buildTargetResolver(rootDir, artifact) {
|
|
3983
|
+
const rootIsWorkspace = await detectWorkspaceRoot(rootDir);
|
|
3984
|
+
const owners = /* @__PURE__ */ new Map();
|
|
3985
|
+
for (const project of artifact.projects ?? []) {
|
|
3986
|
+
const ecosystem = ecosystemId(project.type);
|
|
3987
|
+
const dir = relProjectDir(rootDir, project.path);
|
|
3988
|
+
for (const dep of project.dependencies ?? []) {
|
|
3989
|
+
const key = `${ecosystem}\0${dep.package}`;
|
|
3990
|
+
(owners.get(key) ?? owners.set(key, /* @__PURE__ */ new Set()).get(key)).add(dir);
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
const rootFallback = [{ dir: ".", isWorkspaceRoot: rootIsWorkspace }];
|
|
3994
|
+
return (upgrade) => {
|
|
3995
|
+
const dirs = owners.get(`${upgrade.ecosystem}\0${upgrade.package}`);
|
|
3996
|
+
if (!dirs || dirs.size === 0) return rootFallback;
|
|
3997
|
+
return [...dirs].sort((a, b) => a.localeCompare(b)).map((dir) => ({ dir, isWorkspaceRoot: dir === "." && rootIsWorkspace }));
|
|
3998
|
+
};
|
|
3999
|
+
}
|
|
3502
4000
|
function npmPackageManager(artifact) {
|
|
3503
4001
|
for (const p of artifact.projects ?? []) {
|
|
3504
4002
|
if ((p.type === "node" || p.type === "typescript") && p.packageManager) {
|
|
@@ -3508,51 +4006,13 @@ function npmPackageManager(artifact) {
|
|
|
3508
4006
|
}
|
|
3509
4007
|
return "npm";
|
|
3510
4008
|
}
|
|
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
4009
|
function promptPlanSelection(response) {
|
|
3550
4010
|
const plans = response.plans.filter((p) => p.upgrades.length > 0);
|
|
3551
|
-
console.log(
|
|
4011
|
+
console.log(chalk8.bold("\nSelect a plan to apply:"));
|
|
3552
4012
|
plans.forEach((p, i) => {
|
|
3553
|
-
const rec = p.tier === response.recommended ?
|
|
4013
|
+
const rec = p.tier === response.recommended ? chalk8.green(" (recommended)") : "";
|
|
3554
4014
|
const drift = typeof p.expectedDriftScore === "number" && typeof response.currentDriftScore === "number" ? ` \xB7 DriftScore ${response.currentDriftScore}\u2192${p.expectedDriftScore}` : "";
|
|
3555
|
-
console.log(` ${i + 1}) ${
|
|
4015
|
+
console.log(` ${i + 1}) ${chalk8.bold(p.label)}${rec} risk ${p.riskScore}/100 \xB7 ${p.upgrades.length} upgrade(s)${drift}`);
|
|
3556
4016
|
});
|
|
3557
4017
|
const defaultIdx = plans.findIndex((p) => p.tier === response.recommended);
|
|
3558
4018
|
const def = defaultIdx >= 0 ? defaultIdx + 1 : 1;
|
|
@@ -3571,14 +4031,14 @@ function promptPlanSelection(response) {
|
|
|
3571
4031
|
async function runApplyFlow(rootDir, artifact, response, opts) {
|
|
3572
4032
|
const nonEmpty = response.plans.filter((p) => p.upgrades.length > 0);
|
|
3573
4033
|
if (nonEmpty.length === 0) {
|
|
3574
|
-
console.log(
|
|
4034
|
+
console.log(chalk8.green("\n\u2714 Nothing to upgrade \u2014 every tracked dependency is current."));
|
|
3575
4035
|
return;
|
|
3576
4036
|
}
|
|
3577
4037
|
let chosen;
|
|
3578
4038
|
if (opts.plan) {
|
|
3579
4039
|
chosen = response.plans.find((p) => p.tier === opts.plan);
|
|
3580
4040
|
if (!chosen) {
|
|
3581
|
-
console.error(
|
|
4041
|
+
console.error(chalk8.red(`Unknown plan '${opts.plan}'. Use safe, balanced, or aggressive.`));
|
|
3582
4042
|
process.exit(1);
|
|
3583
4043
|
}
|
|
3584
4044
|
} else if (nonEmpty.length === 1) {
|
|
@@ -3588,41 +4048,43 @@ async function runApplyFlow(rootDir, artifact, response, opts) {
|
|
|
3588
4048
|
} else if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
3589
4049
|
const tier = await promptPlanSelection(response);
|
|
3590
4050
|
if (!tier) {
|
|
3591
|
-
console.log(
|
|
4051
|
+
console.log(chalk8.dim("No plan applied."));
|
|
3592
4052
|
return;
|
|
3593
4053
|
}
|
|
3594
4054
|
chosen = response.plans.find((p) => p.tier === tier);
|
|
3595
4055
|
} else {
|
|
3596
|
-
console.log(
|
|
4056
|
+
console.log(chalk8.dim("\nMultiple plans available \u2014 re-run with --plan <tier> or --yes to apply, or --dry-run to preview."));
|
|
3597
4057
|
return;
|
|
3598
4058
|
}
|
|
3599
4059
|
if (!chosen || chosen.upgrades.length === 0) {
|
|
3600
|
-
console.log(
|
|
4060
|
+
console.log(chalk8.dim("Selected plan has no upgrades."));
|
|
3601
4061
|
return;
|
|
3602
4062
|
}
|
|
3603
|
-
const
|
|
4063
|
+
const pm = npmPackageManager(artifact);
|
|
4064
|
+
const resolveTargets = await buildTargetResolver(rootDir, artifact);
|
|
4065
|
+
const results = applyPlan(rootDir, chosen.upgrades, { dryRun: opts.dryRun, packageManager: pm, resolveTargets });
|
|
3604
4066
|
if (opts.dryRun) {
|
|
3605
|
-
console.log(
|
|
4067
|
+
console.log(chalk8.bold(`
|
|
3606
4068
|
Dry run \u2014 ${chosen.label} plan (${chosen.upgrades.length} upgrade(s)):`));
|
|
3607
4069
|
for (const r of results) {
|
|
3608
|
-
if (r.status === "manual") console.log(
|
|
3609
|
-
else console.log(
|
|
4070
|
+
if (r.status === "manual") console.log(chalk8.yellow(` \u26A0 ${r.package}: ${r.detail}`));
|
|
4071
|
+
else console.log(chalk8.dim(` ${r.package}: ${r.detail}`));
|
|
3610
4072
|
}
|
|
3611
4073
|
if (typeof chosen.expectedDriftScore === "number") {
|
|
3612
|
-
console.log(
|
|
4074
|
+
console.log(chalk8.dim(`Expected DriftScore after apply: ~${chosen.expectedDriftScore} (was ${response.currentDriftScore ?? "?"}).`));
|
|
3613
4075
|
}
|
|
3614
4076
|
return;
|
|
3615
4077
|
}
|
|
3616
4078
|
const applied = results.filter((r) => r.status === "applied");
|
|
3617
4079
|
const failed = results.filter((r) => r.status === "failed");
|
|
3618
4080
|
const manual = results.filter((r) => r.status === "manual");
|
|
3619
|
-
console.log(
|
|
4081
|
+
console.log(chalk8.bold(`
|
|
3620
4082
|
Applied the ${chosen.label} plan:`));
|
|
3621
4083
|
console.log(
|
|
3622
|
-
|
|
4084
|
+
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
4085
|
);
|
|
3624
|
-
for (const r of failed) console.log(
|
|
3625
|
-
for (const r of manual) console.log(
|
|
4086
|
+
for (const r of failed) console.log(chalk8.red(` \u2716 ${r.package} \u2192 ${r.to}: ${r.detail ?? "failed"}`));
|
|
4087
|
+
for (const r of manual) console.log(chalk8.yellow(` \u26A0 ${r.package} \u2192 ${r.to}: ${r.detail}`));
|
|
3626
4088
|
if (typeof chosen.expectedDriftScore === "number" && typeof response.currentDriftScore === "number") {
|
|
3627
4089
|
const delta = chosen.expectedDriftScore - response.currentDriftScore;
|
|
3628
4090
|
console.log(
|
|
@@ -3633,20 +4095,20 @@ Applied the ${chosen.label} plan:`));
|
|
|
3633
4095
|
}
|
|
3634
4096
|
function formatText(artifact) {
|
|
3635
4097
|
const lines = [];
|
|
3636
|
-
const teal2 =
|
|
3637
|
-
const mint2 =
|
|
4098
|
+
const teal2 = chalk8.hex("#3FB0A4");
|
|
4099
|
+
const mint2 = chalk8.hex("#4FE3C1");
|
|
3638
4100
|
lines.push("");
|
|
3639
4101
|
lines.push(" " + teal2("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E") + mint2("\u279C"));
|
|
3640
|
-
lines.push(" " +
|
|
3641
|
-
lines.push(" " +
|
|
4102
|
+
lines.push(" " + chalk8.dim("\u2524") + teal2("\u2502") + " " + mint2("\u25FC") + " " + mint2("\u25FC") + " " + teal2("\u2502") + chalk8.dim("\u251C") + " " + chalk8.bold.white("vibgrate"));
|
|
4103
|
+
lines.push(" " + chalk8.dim("\u2524") + teal2("\u2502") + " " + chalk8.dim("\u2581\u2581") + " " + teal2("\u2502") + chalk8.dim("\u251C") + " " + chalk8.dim(`Code Intelligence Engine v${VERSION}`));
|
|
3642
4104
|
lines.push(" " + teal2("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
3643
4105
|
lines.push("");
|
|
3644
4106
|
lines.push(...titleBox("Vibgrate Drift Report", teal2));
|
|
3645
4107
|
lines.push("");
|
|
3646
4108
|
for (const project of artifact.projects) {
|
|
3647
|
-
lines.push(
|
|
4109
|
+
lines.push(chalk8.bold(` \u2500\u2500 ${project.name} `) + chalk8.dim(`(${project.type}) ${project.path}`));
|
|
3648
4110
|
if (project.runtime) {
|
|
3649
|
-
const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ?
|
|
4111
|
+
const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk8.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk8.green(" (current)");
|
|
3650
4112
|
lines.push(` Runtime: ${project.runtime}${behindStr}`);
|
|
3651
4113
|
}
|
|
3652
4114
|
if (project.targetFramework) {
|
|
@@ -3655,7 +4117,7 @@ function formatText(artifact) {
|
|
|
3655
4117
|
if (project.frameworks.length > 0) {
|
|
3656
4118
|
lines.push(" Frameworks:");
|
|
3657
4119
|
for (const fw of project.frameworks) {
|
|
3658
|
-
const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ?
|
|
4120
|
+
const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk8.green("current") : chalk8.yellow(`${fw.majorsBehind} behind`) : chalk8.dim("unknown");
|
|
3659
4121
|
lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
|
|
3660
4122
|
}
|
|
3661
4123
|
}
|
|
@@ -3663,13 +4125,13 @@ function formatText(artifact) {
|
|
|
3663
4125
|
const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
|
|
3664
4126
|
if (total > 0) {
|
|
3665
4127
|
lines.push(" Dependencies:");
|
|
3666
|
-
lines.push(` ${
|
|
4128
|
+
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
4129
|
}
|
|
3668
4130
|
lines.push("");
|
|
3669
4131
|
}
|
|
3670
4132
|
if (artifact.delta !== void 0) {
|
|
3671
|
-
const deltaStr = artifact.delta > 0 ?
|
|
3672
|
-
lines.push(
|
|
4133
|
+
const deltaStr = artifact.delta > 0 ? chalk8.red(`+${artifact.delta}`) : artifact.delta < 0 ? chalk8.green(`${artifact.delta}`) : chalk8.dim("0");
|
|
4134
|
+
lines.push(chalk8.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
|
|
3673
4135
|
lines.push("");
|
|
3674
4136
|
}
|
|
3675
4137
|
if (artifact.extended) {
|
|
@@ -3680,15 +4142,15 @@ function formatText(artifact) {
|
|
|
3680
4142
|
const warnings = artifact.findings.filter((f) => f.level === "warning");
|
|
3681
4143
|
const notes = artifact.findings.filter((f) => f.level === "note");
|
|
3682
4144
|
const summary = [
|
|
3683
|
-
errors.length > 0 ?
|
|
3684
|
-
warnings.length > 0 ?
|
|
3685
|
-
notes.length > 0 ?
|
|
3686
|
-
].filter(Boolean).join(
|
|
3687
|
-
lines.push(
|
|
4145
|
+
errors.length > 0 ? chalk8.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
|
|
4146
|
+
warnings.length > 0 ? chalk8.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
|
|
4147
|
+
notes.length > 0 ? chalk8.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
|
|
4148
|
+
].filter(Boolean).join(chalk8.dim(", "));
|
|
4149
|
+
lines.push(chalk8.bold.underline(` Findings`) + chalk8.dim(` (${summary})`));
|
|
3688
4150
|
for (const f of artifact.findings) {
|
|
3689
|
-
const icon = f.level === "error" ?
|
|
4151
|
+
const icon = f.level === "error" ? chalk8.red("\u2716") : f.level === "warning" ? chalk8.yellow("\u26A0") : chalk8.blue("\u2139");
|
|
3690
4152
|
lines.push(` ${icon} ${f.message}`);
|
|
3691
|
-
lines.push(
|
|
4153
|
+
lines.push(chalk8.dim(` ${f.ruleId} in ${f.location}`));
|
|
3692
4154
|
}
|
|
3693
4155
|
lines.push("");
|
|
3694
4156
|
}
|
|
@@ -3698,10 +4160,10 @@ function formatText(artifact) {
|
|
|
3698
4160
|
lines.push("");
|
|
3699
4161
|
for (let i = 0; i < actions.length; i++) {
|
|
3700
4162
|
const a = actions[i];
|
|
3701
|
-
const num =
|
|
3702
|
-
lines.push(`${num} ${
|
|
3703
|
-
lines.push(
|
|
3704
|
-
if (a.impact) lines.push(` Impact: ${
|
|
4163
|
+
const num = chalk8.bold.cyan(` ${i + 1}.`);
|
|
4164
|
+
lines.push(`${num} ${chalk8.bold(a.title)}`);
|
|
4165
|
+
lines.push(chalk8.dim(` ${a.explanation}`));
|
|
4166
|
+
if (a.impact) lines.push(` Impact: ${chalk8.green(a.impact)}`);
|
|
3705
4167
|
lines.push("");
|
|
3706
4168
|
}
|
|
3707
4169
|
}
|
|
@@ -3713,30 +4175,30 @@ function formatText(artifact) {
|
|
|
3713
4175
|
lines.push("");
|
|
3714
4176
|
for (const solution of artifact.solutions) {
|
|
3715
4177
|
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`) :
|
|
4178
|
+
const color = typeof solScore === "number" ? solScore <= 30 ? chalk8.green : solScore <= 60 ? chalk8.yellow : chalk8.red : chalk8.dim;
|
|
4179
|
+
lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk8.dim("n/a")}`);
|
|
3718
4180
|
}
|
|
3719
4181
|
lines.push("");
|
|
3720
4182
|
}
|
|
3721
|
-
const scoreColor = artifact.drift.score <= 30 ?
|
|
4183
|
+
const scoreColor = artifact.drift.score <= 30 ? chalk8.green : artifact.drift.score <= 60 ? chalk8.yellow : chalk8.red;
|
|
3722
4184
|
lines.push(...titleBox("DriftScore Summary"));
|
|
3723
4185
|
lines.push("");
|
|
3724
|
-
lines.push(
|
|
3725
|
-
lines.push(
|
|
3726
|
-
lines.push(
|
|
4186
|
+
lines.push(chalk8.bold(" DriftScore: ") + scoreColor.bold(`${artifact.drift.score}/100`));
|
|
4187
|
+
lines.push(chalk8.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
|
|
4188
|
+
lines.push(chalk8.bold(" Projects: ") + `${artifact.projects.length}`);
|
|
3727
4189
|
if (artifact.vcs) {
|
|
3728
4190
|
const vcsParts = [artifact.vcs.type];
|
|
3729
4191
|
if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
|
|
3730
|
-
if (artifact.vcs.shortSha) vcsParts.push(
|
|
3731
|
-
lines.push(
|
|
4192
|
+
if (artifact.vcs.shortSha) vcsParts.push(chalk8.dim(artifact.vcs.shortSha));
|
|
4193
|
+
lines.push(chalk8.bold(" VCS: ") + vcsParts.join(" "));
|
|
3732
4194
|
}
|
|
3733
4195
|
lines.push("");
|
|
3734
4196
|
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) :
|
|
4197
|
+
lines.push(" " + chalk8.bold.underline("Score Breakdown"));
|
|
4198
|
+
lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk8.dim("n/a")}`);
|
|
4199
|
+
lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk8.dim("n/a")}`);
|
|
4200
|
+
lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk8.dim("n/a")}`);
|
|
4201
|
+
lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk8.dim("n/a")}`);
|
|
3740
4202
|
lines.push("");
|
|
3741
4203
|
const scannedParts = [`Scanned at ${artifact.timestamp}`];
|
|
3742
4204
|
if (artifact.durationMs !== void 0) {
|
|
@@ -3750,18 +4212,18 @@ function formatText(artifact) {
|
|
|
3750
4212
|
scannedParts.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files`);
|
|
3751
4213
|
scannedParts.push(`${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
|
|
3752
4214
|
}
|
|
3753
|
-
lines.push(
|
|
4215
|
+
lines.push(chalk8.dim(` ${scannedParts.join(" \xB7 ")}`));
|
|
3754
4216
|
lines.push("");
|
|
3755
4217
|
return lines.join("\n");
|
|
3756
4218
|
}
|
|
3757
4219
|
function riskBadge(level) {
|
|
3758
4220
|
switch (level) {
|
|
3759
4221
|
case "low":
|
|
3760
|
-
return
|
|
4222
|
+
return chalk8.bgGreen.black(" LOW ");
|
|
3761
4223
|
case "moderate":
|
|
3762
|
-
return
|
|
4224
|
+
return chalk8.bgYellow.black(" MODERATE ");
|
|
3763
4225
|
case "high":
|
|
3764
|
-
return
|
|
4226
|
+
return chalk8.bgRed.white(" HIGH ");
|
|
3765
4227
|
default:
|
|
3766
4228
|
return level;
|
|
3767
4229
|
}
|
|
@@ -3796,11 +4258,11 @@ function formatExtended(ext) {
|
|
|
3796
4258
|
const inv = ext.toolingInventory;
|
|
3797
4259
|
const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
|
|
3798
4260
|
if (categories.length > 0) {
|
|
3799
|
-
lines.push(
|
|
4261
|
+
lines.push(chalk8.bold.underline(" Tech Stack"));
|
|
3800
4262
|
for (const [cat, items] of categories) {
|
|
3801
4263
|
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
3802
|
-
const names = items.map((i) =>
|
|
3803
|
-
lines.push(` ${
|
|
4264
|
+
const names = items.map((i) => chalk8.white(i.name)).join(chalk8.dim(", "));
|
|
4265
|
+
lines.push(` ${chalk8.cyan(label)}: ${names}`);
|
|
3804
4266
|
}
|
|
3805
4267
|
lines.push("");
|
|
3806
4268
|
}
|
|
@@ -3809,14 +4271,14 @@ function formatExtended(ext) {
|
|
|
3809
4271
|
const svc = ext.serviceDependencies;
|
|
3810
4272
|
const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
|
|
3811
4273
|
if (categories.length > 0) {
|
|
3812
|
-
lines.push(
|
|
4274
|
+
lines.push(chalk8.bold.underline(" Services & Integrations"));
|
|
3813
4275
|
for (const [cat, items] of categories) {
|
|
3814
4276
|
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
3815
4277
|
const names = items.map((i) => {
|
|
3816
|
-
const ver = i.version ?
|
|
3817
|
-
return
|
|
3818
|
-
}).join(
|
|
3819
|
-
lines.push(` ${
|
|
4278
|
+
const ver = i.version ? chalk8.dim(` ${i.version}`) : "";
|
|
4279
|
+
return chalk8.white(i.name) + ver;
|
|
4280
|
+
}).join(chalk8.dim(", "));
|
|
4281
|
+
lines.push(` ${chalk8.cyan(label)}: ${names}`);
|
|
3820
4282
|
}
|
|
3821
4283
|
lines.push("");
|
|
3822
4284
|
}
|
|
@@ -3824,19 +4286,19 @@ function formatExtended(ext) {
|
|
|
3824
4286
|
if (ext.breakingChangeExposure) {
|
|
3825
4287
|
const bc = ext.breakingChangeExposure;
|
|
3826
4288
|
if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
|
|
3827
|
-
lines.push(
|
|
3828
|
-
const exposureColor = bc.exposureScore >= 40 ?
|
|
4289
|
+
lines.push(chalk8.bold.underline(" Breaking Change Exposure"));
|
|
4290
|
+
const exposureColor = bc.exposureScore >= 40 ? chalk8.red : bc.exposureScore >= 20 ? chalk8.yellow : chalk8.green;
|
|
3829
4291
|
lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
|
|
3830
4292
|
if (bc.deprecatedPackages.length > 0) {
|
|
3831
|
-
lines.push(` ${
|
|
4293
|
+
lines.push(` ${chalk8.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk8.dim(p)).join(", ")}`);
|
|
3832
4294
|
}
|
|
3833
4295
|
if (bc.legacyPolyfills.length > 0) {
|
|
3834
|
-
lines.push(` ${
|
|
4296
|
+
lines.push(` ${chalk8.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk8.dim(p)).join(", ")}`);
|
|
3835
4297
|
}
|
|
3836
4298
|
if (bc.peerConflictsDetected) {
|
|
3837
|
-
lines.push(` ${
|
|
4299
|
+
lines.push(` ${chalk8.red("\u26A0")} Peer dependency conflicts detected`);
|
|
3838
4300
|
}
|
|
3839
|
-
lines.push(` Recommendation: ${
|
|
4301
|
+
lines.push(` Recommendation: ${chalk8.bold(bc.overallRecommendation)}`);
|
|
3840
4302
|
const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
|
|
3841
4303
|
if (projectsWithPlans.length > 0) {
|
|
3842
4304
|
lines.push(" Major Upgrade Intelligence:");
|
|
@@ -3852,21 +4314,21 @@ function formatExtended(ext) {
|
|
|
3852
4314
|
}
|
|
3853
4315
|
if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
|
|
3854
4316
|
const ts = ext.tsModernity;
|
|
3855
|
-
lines.push(
|
|
4317
|
+
lines.push(chalk8.bold.underline(" TypeScript"));
|
|
3856
4318
|
const parts = [];
|
|
3857
4319
|
parts.push(`v${ts.typescriptVersion}`);
|
|
3858
|
-
if (ts.strict === true) parts.push(
|
|
3859
|
-
else if (ts.strict === false) parts.push(
|
|
4320
|
+
if (ts.strict === true) parts.push(chalk8.green("strict \u2714"));
|
|
4321
|
+
else if (ts.strict === false) parts.push(chalk8.yellow("strict \u2716"));
|
|
3860
4322
|
if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
|
|
3861
4323
|
if (ts.target) parts.push(`target: ${ts.target}`);
|
|
3862
|
-
lines.push(` ${parts.join(
|
|
4324
|
+
lines.push(` ${parts.join(chalk8.dim(" \xB7 "))}`);
|
|
3863
4325
|
lines.push("");
|
|
3864
4326
|
}
|
|
3865
4327
|
if (ext.buildDeploy) {
|
|
3866
4328
|
const bd = ext.buildDeploy;
|
|
3867
4329
|
const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
|
|
3868
4330
|
if (hasSomething) {
|
|
3869
|
-
lines.push(
|
|
4331
|
+
lines.push(chalk8.bold.underline(" Build & Deploy"));
|
|
3870
4332
|
if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
|
|
3871
4333
|
if (bd.docker.dockerfileCount > 0) {
|
|
3872
4334
|
lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
|
|
@@ -3879,42 +4341,42 @@ function formatExtended(ext) {
|
|
|
3879
4341
|
}
|
|
3880
4342
|
if (ext.uiPurpose) {
|
|
3881
4343
|
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 ?
|
|
4344
|
+
lines.push(chalk8.bold.underline(" Product Purpose Signals"));
|
|
4345
|
+
lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk8.dim("unknown")}`);
|
|
4346
|
+
lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk8.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
|
|
3885
4347
|
const top = up.topEvidence.slice(0, 8);
|
|
3886
4348
|
if (top.length > 0) {
|
|
3887
4349
|
lines.push(" Top Signals:");
|
|
3888
4350
|
for (const item of top) {
|
|
3889
|
-
lines.push(` - [${item.kind}] ${item.value} ${
|
|
4351
|
+
lines.push(` - [${item.kind}] ${item.value} ${chalk8.dim(`(${item.file})`)}`);
|
|
3890
4352
|
}
|
|
3891
4353
|
}
|
|
3892
4354
|
if (up.unknownSignals.length > 0) {
|
|
3893
4355
|
lines.push(" Unknowns:");
|
|
3894
4356
|
for (const u of up.unknownSignals.slice(0, 4)) {
|
|
3895
|
-
lines.push(` - ${
|
|
4357
|
+
lines.push(` - ${chalk8.yellow(u)}`);
|
|
3896
4358
|
}
|
|
3897
4359
|
}
|
|
3898
4360
|
lines.push("");
|
|
3899
4361
|
}
|
|
3900
4362
|
if (ext.securityPosture) {
|
|
3901
4363
|
const sec = ext.securityPosture;
|
|
3902
|
-
lines.push(
|
|
4364
|
+
lines.push(chalk8.bold.underline(" Security Posture"));
|
|
3903
4365
|
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(
|
|
4366
|
+
checks.push(sec.lockfilePresent ? chalk8.green("Lockfile \u2714") : chalk8.red("Lockfile \u2716"));
|
|
4367
|
+
checks.push(sec.gitignoreCoversEnv ? chalk8.green(".env \u2714") : chalk8.red(".env \u2716"));
|
|
4368
|
+
checks.push(sec.gitignoreCoversNodeModules ? chalk8.green("node_modules \u2714") : chalk8.yellow("node_modules \u2716"));
|
|
4369
|
+
if (sec.multipleLockfileTypes) checks.push(chalk8.yellow("Multiple lockfiles \u26A0"));
|
|
4370
|
+
if (sec.envFilesTracked) checks.push(chalk8.red("Env files tracked \u2716"));
|
|
4371
|
+
lines.push(` ${checks.join(chalk8.dim(" \xB7 "))}`);
|
|
3910
4372
|
lines.push("");
|
|
3911
4373
|
}
|
|
3912
4374
|
if (ext.platformMatrix) {
|
|
3913
4375
|
const pm = ext.platformMatrix;
|
|
3914
4376
|
if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
|
|
3915
|
-
lines.push(
|
|
4377
|
+
lines.push(chalk8.bold.underline(" Platform"));
|
|
3916
4378
|
if (pm.nativeModules.length > 0) {
|
|
3917
|
-
lines.push(` Native modules: ${pm.nativeModules.map((m) =>
|
|
4379
|
+
lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk8.dim(m)).join(", ")}`);
|
|
3918
4380
|
}
|
|
3919
4381
|
if (pm.osAssumptions.length > 0) {
|
|
3920
4382
|
lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
|
|
@@ -3924,25 +4386,25 @@ function formatExtended(ext) {
|
|
|
3924
4386
|
}
|
|
3925
4387
|
if (ext.codeQuality) {
|
|
3926
4388
|
const cq = ext.codeQuality;
|
|
3927
|
-
lines.push(
|
|
3928
|
-
lines.push(` Files: ${
|
|
4389
|
+
lines.push(chalk8.bold.underline(" Code Quality"));
|
|
4390
|
+
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
4391
|
lines.push(` Max nesting: ${cq.maxNestingDepth} \xB7 Circular deps: ${cq.circularDependencies} \xB7 Dead code: ${cq.deadCodePercent}%`);
|
|
3930
4392
|
if (cq.godFiles.length > 0) {
|
|
3931
4393
|
const preview = cq.godFiles.slice(0, 3).map((f) => `${f.path} (${f.lines} lines)`).join(", ");
|
|
3932
|
-
lines.push(` ${
|
|
4394
|
+
lines.push(` ${chalk8.yellow("God files")}: ${preview}`);
|
|
3933
4395
|
}
|
|
3934
4396
|
lines.push("");
|
|
3935
4397
|
}
|
|
3936
4398
|
if (ext.dependencyGraph) {
|
|
3937
4399
|
const dg = ext.dependencyGraph;
|
|
3938
4400
|
if (dg.lockfileType) {
|
|
3939
|
-
lines.push(
|
|
3940
|
-
lines.push(` ${dg.lockfileType}: ${
|
|
4401
|
+
lines.push(chalk8.bold.underline(" Dependency Graph"));
|
|
4402
|
+
lines.push(` ${dg.lockfileType}: ${chalk8.white(`${dg.totalUnique}`)} unique, ${chalk8.white(`${dg.totalInstalled}`)} installed`);
|
|
3941
4403
|
if (dg.duplicatedPackages.length > 0) {
|
|
3942
|
-
lines.push(` ${
|
|
4404
|
+
lines.push(` ${chalk8.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
|
|
3943
4405
|
}
|
|
3944
4406
|
if (dg.phantomDependencies.length > 0) {
|
|
3945
|
-
lines.push(` ${
|
|
4407
|
+
lines.push(` ${chalk8.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
|
|
3946
4408
|
}
|
|
3947
4409
|
lines.push("");
|
|
3948
4410
|
}
|
|
@@ -3953,13 +4415,13 @@ function formatArchitectureDiagram(arch) {
|
|
|
3953
4415
|
const lines = [];
|
|
3954
4416
|
lines.push(...titleBox("Architecture Layers"));
|
|
3955
4417
|
lines.push("");
|
|
3956
|
-
lines.push(
|
|
3957
|
-
lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ?
|
|
4418
|
+
lines.push(chalk8.bold(" Archetype: ") + `${arch.archetype}` + chalk8.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
|
|
4419
|
+
lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk8.dim(` (${arch.unclassified} unclassified)`) : ""));
|
|
3958
4420
|
lines.push("");
|
|
3959
4421
|
if (arch.layers.length > 0) {
|
|
3960
4422
|
for (const layer of arch.layers) {
|
|
3961
|
-
const risk = layer.riskLevel === "none" ?
|
|
3962
|
-
lines.push(` ${
|
|
4423
|
+
const risk = layer.riskLevel === "none" ? chalk8.dim("none") : layer.riskLevel === "low" ? chalk8.green("low") : layer.riskLevel === "moderate" ? chalk8.yellow("moderate") : chalk8.red("high");
|
|
4424
|
+
lines.push(` ${chalk8.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
|
|
3963
4425
|
}
|
|
3964
4426
|
lines.push("");
|
|
3965
4427
|
}
|
|
@@ -4330,8 +4792,8 @@ function formatMarkdown(artifact) {
|
|
|
4330
4792
|
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
4793
|
const artifactPath = path.resolve(opts.in);
|
|
4332
4794
|
if (!await pathExists(artifactPath)) {
|
|
4333
|
-
console.error(
|
|
4334
|
-
console.error(
|
|
4795
|
+
console.error(chalk8.red(`Artifact not found: ${artifactPath}`));
|
|
4796
|
+
console.error(chalk8.dim('Run "vibgrate scan" first to generate a scan artifact.'));
|
|
4335
4797
|
process.exit(1);
|
|
4336
4798
|
}
|
|
4337
4799
|
const artifact = await readJsonFile(artifactPath);
|
|
@@ -4387,7 +4849,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4387
4849
|
try {
|
|
4388
4850
|
ingestHost = resolveIngestHost(opts.region, opts.ingest);
|
|
4389
4851
|
} catch (e) {
|
|
4390
|
-
console.error(
|
|
4852
|
+
console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
|
|
4391
4853
|
process.exit(1);
|
|
4392
4854
|
}
|
|
4393
4855
|
const base = `https://${ingestHost}/v1/auth/device`;
|
|
@@ -4399,28 +4861,28 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4399
4861
|
body: "{}"
|
|
4400
4862
|
});
|
|
4401
4863
|
if (!res.ok) {
|
|
4402
|
-
console.error(
|
|
4864
|
+
console.error(chalk8.red(`Failed to start login (HTTP ${res.status}).`));
|
|
4403
4865
|
process.exit(1);
|
|
4404
4866
|
}
|
|
4405
4867
|
start = await res.json();
|
|
4406
4868
|
} catch (e) {
|
|
4407
|
-
console.error(
|
|
4869
|
+
console.error(chalk8.red(`Could not reach ${ingestHost}: ${e instanceof Error ? e.message : String(e)}`));
|
|
4408
4870
|
process.exit(1);
|
|
4409
4871
|
}
|
|
4410
4872
|
console.log("");
|
|
4411
4873
|
console.log("To finish signing in, open this URL and approve the request:");
|
|
4412
4874
|
console.log("");
|
|
4413
|
-
console.log(" " +
|
|
4875
|
+
console.log(" " + chalk8.cyan(start.verificationUri));
|
|
4414
4876
|
console.log("");
|
|
4415
|
-
console.log(" Your code: " +
|
|
4877
|
+
console.log(" Your code: " + chalk8.bold(start.userCode));
|
|
4416
4878
|
console.log("");
|
|
4417
4879
|
if (opts.browser) {
|
|
4418
4880
|
const opened = openUrl(start.verificationUriComplete);
|
|
4419
4881
|
if (opened) {
|
|
4420
|
-
console.log(
|
|
4882
|
+
console.log(chalk8.dim("Opening your browser\u2026 (if it does not open, use the URL above)"));
|
|
4421
4883
|
}
|
|
4422
4884
|
}
|
|
4423
|
-
console.log(
|
|
4885
|
+
console.log(chalk8.dim("Waiting for approval\u2026"));
|
|
4424
4886
|
const intervalMs = Math.max(2, start.interval || 5) * 1e3;
|
|
4425
4887
|
const deadline = Date.now() + (start.expiresIn || 900) * 1e3;
|
|
4426
4888
|
while (Date.now() < deadline) {
|
|
@@ -4448,7 +4910,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4448
4910
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4449
4911
|
};
|
|
4450
4912
|
} else {
|
|
4451
|
-
console.log(
|
|
4913
|
+
console.log(chalk8.dim("Setting up your workspace\u2026"));
|
|
4452
4914
|
try {
|
|
4453
4915
|
const provisioned = await createWorkspaceDsn({
|
|
4454
4916
|
region: opts.region,
|
|
@@ -4463,10 +4925,10 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4463
4925
|
};
|
|
4464
4926
|
} catch (e) {
|
|
4465
4927
|
console.error(
|
|
4466
|
-
|
|
4928
|
+
chalk8.red("\u2716 Signed in, but workspace setup failed: ") + (e instanceof Error ? e.message : String(e))
|
|
4467
4929
|
);
|
|
4468
4930
|
console.error(
|
|
4469
|
-
|
|
4931
|
+
chalk8.dim(' Finish setup with "vibgrate dsn create --workspace new".')
|
|
4470
4932
|
);
|
|
4471
4933
|
process.exit(1);
|
|
4472
4934
|
}
|
|
@@ -4475,40 +4937,40 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
4475
4937
|
writeStoredCredentials(creds, storeOpts);
|
|
4476
4938
|
const credsFile = credentialsPath(storeOpts);
|
|
4477
4939
|
console.log("");
|
|
4478
|
-
console.log(
|
|
4940
|
+
console.log(chalk8.green("\u2714") + " Logged in.");
|
|
4479
4941
|
if (creds.workspaceId) {
|
|
4480
|
-
console.log(" Workspace: " +
|
|
4942
|
+
console.log(" Workspace: " + chalk8.bold(creds.workspaceId));
|
|
4481
4943
|
}
|
|
4482
|
-
console.log(
|
|
4944
|
+
console.log(chalk8.dim(` Credentials saved to ${credsFile}`));
|
|
4483
4945
|
try {
|
|
4484
4946
|
const root = findGitRoot();
|
|
4485
4947
|
if (root && isInside(root, credsFile)) {
|
|
4486
4948
|
const res = ensureGitignored(gitignoreEntryForCredentials(root, credsFile), root);
|
|
4487
4949
|
if (res.status === "created") {
|
|
4488
|
-
console.log(
|
|
4950
|
+
console.log(chalk8.dim(` Created .gitignore and ignored ${res.entry}`));
|
|
4489
4951
|
} else if (res.status === "added") {
|
|
4490
|
-
console.log(
|
|
4952
|
+
console.log(chalk8.dim(` Added ${res.entry} to .gitignore`));
|
|
4491
4953
|
}
|
|
4492
4954
|
}
|
|
4493
4955
|
} catch {
|
|
4494
4956
|
}
|
|
4495
|
-
console.log(
|
|
4957
|
+
console.log(chalk8.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
|
|
4496
4958
|
return;
|
|
4497
4959
|
}
|
|
4498
4960
|
if (token.status === "access_denied") {
|
|
4499
|
-
console.error(
|
|
4961
|
+
console.error(chalk8.red("\u2716 Login was denied in the browser."));
|
|
4500
4962
|
process.exit(1);
|
|
4501
4963
|
}
|
|
4502
4964
|
if (token.status === "expired" || token.status === "invalid") {
|
|
4503
|
-
console.error(
|
|
4965
|
+
console.error(chalk8.red('\u2716 Login request expired. Run "vibgrate login" again.'));
|
|
4504
4966
|
process.exit(1);
|
|
4505
4967
|
}
|
|
4506
4968
|
if (token.status === "error") {
|
|
4507
|
-
console.error(
|
|
4969
|
+
console.error(chalk8.red(`\u2716 ${token.error ?? "Login failed."}`));
|
|
4508
4970
|
process.exit(1);
|
|
4509
4971
|
}
|
|
4510
4972
|
}
|
|
4511
|
-
console.error(
|
|
4973
|
+
console.error(chalk8.red('\u2716 Timed out waiting for approval. Run "vibgrate login" again.'));
|
|
4512
4974
|
process.exit(1);
|
|
4513
4975
|
});
|
|
4514
4976
|
|
|
@@ -4529,190 +4991,20 @@ function unsetEnvCommand(varName, env = {}) {
|
|
|
4529
4991
|
var logoutCommand = new Command("logout").description("Clear stored Vibgrate login credentials").action(() => {
|
|
4530
4992
|
const cleared = clearStoredCredentials();
|
|
4531
4993
|
if (cleared) {
|
|
4532
|
-
console.log(
|
|
4994
|
+
console.log(chalk8.green("\u2714") + " Logged out. Stored credentials removed.");
|
|
4533
4995
|
} else {
|
|
4534
|
-
console.log(
|
|
4996
|
+
console.log(chalk8.dim(`No stored credentials found at ${credentialsPath()}.`));
|
|
4535
4997
|
}
|
|
4536
4998
|
if (process.env.VIBGRATE_DSN) {
|
|
4537
4999
|
console.log("");
|
|
4538
5000
|
console.log(
|
|
4539
|
-
|
|
5001
|
+
chalk8.yellow("\u26A0") + " VIBGRATE_DSN is still set in your environment, so commands will keep using it."
|
|
4540
5002
|
);
|
|
4541
5003
|
console.log(
|
|
4542
|
-
|
|
5004
|
+
chalk8.dim(` Unset it to fully sign out: ${unsetEnvCommand("VIBGRATE_DSN")}`)
|
|
4543
5005
|
);
|
|
4544
5006
|
}
|
|
4545
5007
|
});
|
|
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
5008
|
var OPENVEX_CONTEXT = "https://openvex.dev/ns/v0.2.0";
|
|
4717
5009
|
var VEX_STATUSES = ["not_affected", "affected", "fixed", "under_investigation"];
|
|
4718
5010
|
var VEX_JUSTIFICATIONS = [
|
|
@@ -4804,7 +5096,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
|
|
|
4804
5096
|
if (opts.from) {
|
|
4805
5097
|
const fromPath = path.resolve(opts.from);
|
|
4806
5098
|
if (!await pathExists(fromPath)) {
|
|
4807
|
-
process.stderr.write(
|
|
5099
|
+
process.stderr.write(chalk8.red(`Statements file not found: ${fromPath}
|
|
4808
5100
|
`));
|
|
4809
5101
|
process.exit(1);
|
|
4810
5102
|
}
|
|
@@ -4822,7 +5114,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
|
|
|
4822
5114
|
if (opts.out) {
|
|
4823
5115
|
await writeTextFile(path.resolve(opts.out), json2 + "\n");
|
|
4824
5116
|
process.stderr.write(
|
|
4825
|
-
|
|
5117
|
+
chalk8.green("\u2714") + ` Wrote OpenVEX document (${doc.statements.length} statement(s)) to ${path.resolve(opts.out)}
|
|
4826
5118
|
`
|
|
4827
5119
|
);
|
|
4828
5120
|
} else {
|
|
@@ -4830,7 +5122,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
|
|
|
4830
5122
|
}
|
|
4831
5123
|
} catch (err) {
|
|
4832
5124
|
if (err instanceof VexValidationError) {
|
|
4833
|
-
process.stderr.write(
|
|
5125
|
+
process.stderr.write(chalk8.red(`VEX validation error: ${err.message}
|
|
4834
5126
|
`));
|
|
4835
5127
|
process.exit(1);
|
|
4836
5128
|
}
|
|
@@ -5007,7 +5299,7 @@ function formatDeltaText(base, current) {
|
|
|
5007
5299
|
async function readArtifactOrExit(filePath) {
|
|
5008
5300
|
const absolutePath = path.resolve(filePath);
|
|
5009
5301
|
if (!await pathExists(absolutePath)) {
|
|
5010
|
-
console.error(
|
|
5302
|
+
console.error(chalk8.red(`Artifact not found: ${absolutePath}`));
|
|
5011
5303
|
process.exit(1);
|
|
5012
5304
|
}
|
|
5013
5305
|
return readJsonFile(absolutePath);
|
|
@@ -5016,14 +5308,14 @@ var exportCommand = new Command("export").description("Export scan artifact as S
|
|
|
5016
5308
|
const artifact = await readArtifactOrExit(opts.in);
|
|
5017
5309
|
const format = opts.format.toLowerCase();
|
|
5018
5310
|
if (format !== "cyclonedx" && format !== "spdx") {
|
|
5019
|
-
console.error(
|
|
5311
|
+
console.error(chalk8.red("Invalid SBOM format. Use cyclonedx or spdx."));
|
|
5020
5312
|
process.exit(1);
|
|
5021
5313
|
}
|
|
5022
5314
|
const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
|
|
5023
5315
|
const body = JSON.stringify(sbom, null, 2);
|
|
5024
5316
|
if (opts.out) {
|
|
5025
5317
|
await writeTextFile(path.resolve(opts.out), body);
|
|
5026
|
-
console.log(
|
|
5318
|
+
console.log(chalk8.green("\u2714") + ` SBOM written to ${opts.out}`);
|
|
5027
5319
|
} else {
|
|
5028
5320
|
console.log(body);
|
|
5029
5321
|
}
|
|
@@ -5034,7 +5326,7 @@ var deltaCommand = new Command("delta").description("Show SBOM delta between two
|
|
|
5034
5326
|
const report = formatDeltaText(base, current);
|
|
5035
5327
|
if (opts.out) {
|
|
5036
5328
|
await writeTextFile(path.resolve(opts.out), report);
|
|
5037
|
-
console.log(
|
|
5329
|
+
console.log(chalk8.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
|
|
5038
5330
|
} else {
|
|
5039
5331
|
console.log(report);
|
|
5040
5332
|
}
|
|
@@ -5190,7 +5482,7 @@ function dispatch(argv, cwd) {
|
|
|
5190
5482
|
}
|
|
5191
5483
|
const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
|
|
5192
5484
|
if (looksLikeQuestion) return ["ask", ...args];
|
|
5193
|
-
if (
|
|
5485
|
+
if (fs6.existsSync(path.resolve(cwd, first))) {
|
|
5194
5486
|
return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
|
|
5195
5487
|
}
|
|
5196
5488
|
const moved = MOVED_COMMANDS[first];
|