@vibgrate/cli 2026.826.1 → 2026.829.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 +204 -9
- package/README.md +2 -0
- package/dist/{agent-4RHH2FR2.js → agent-5GW7C7EB.js} +14 -14
- package/dist/{agent-4RHH2FR2.js.map → agent-5GW7C7EB.js.map} +1 -1
- package/dist/{approvals-XDFM346D.js → approvals-ISJFILVK.js} +5 -5
- package/dist/{approvals-XDFM346D.js.map → approvals-ISJFILVK.js.map} +1 -1
- package/dist/{attach-LGTCYNKC.js → attach-JUSALQ75.js} +3 -3
- package/dist/{attach-LGTCYNKC.js.map → attach-JUSALQ75.js.map} +1 -1
- package/dist/baseline-YL2IZZ4Y.js +9 -0
- package/dist/{baseline-PRY5HKL5.js.map → baseline-YL2IZZ4Y.js.map} +1 -1
- package/dist/{chunk-ZRDTI67N.js → chunk-5RW5PIBH.js} +3 -3
- package/dist/{chunk-ZRDTI67N.js.map → chunk-5RW5PIBH.js.map} +1 -1
- package/dist/{chunk-TUVB6Q6F.js → chunk-65YN3BG3.js} +1207 -40
- package/dist/chunk-65YN3BG3.js.map +1 -0
- package/dist/{chunk-TKUWEJZJ.js → chunk-B5VCPIKT.js} +7 -6
- package/dist/chunk-B5VCPIKT.js.map +1 -0
- package/dist/{chunk-WU2DVESP.js → chunk-BA6YBVNT.js} +3 -3
- package/dist/{chunk-WU2DVESP.js.map → chunk-BA6YBVNT.js.map} +1 -1
- package/dist/{chunk-IM7HME2T.js → chunk-BODXEZEY.js} +9 -9
- package/dist/{chunk-IM7HME2T.js.map → chunk-BODXEZEY.js.map} +1 -1
- package/dist/{chunk-PIB3YNJU.js → chunk-D3SV3JWB.js} +3 -3
- package/dist/{chunk-PIB3YNJU.js.map → chunk-D3SV3JWB.js.map} +1 -1
- package/dist/chunk-EGMBHPXV.js +127 -0
- package/dist/chunk-EGMBHPXV.js.map +1 -0
- package/dist/{chunk-7KOEEEIZ.js → chunk-FHSEK64W.js} +7 -7
- package/dist/{chunk-7KOEEEIZ.js.map → chunk-FHSEK64W.js.map} +1 -1
- package/dist/{chunk-PEHMRPYM.js → chunk-FKCN653F.js} +3 -3
- package/dist/{chunk-PEHMRPYM.js.map → chunk-FKCN653F.js.map} +1 -1
- package/dist/{chunk-K2IWOZEQ.js → chunk-FPIMNVLI.js} +12 -4
- package/dist/chunk-FPIMNVLI.js.map +1 -0
- package/dist/{chunk-BHDP7EVJ.js → chunk-HHMWE52J.js} +10 -135
- package/dist/chunk-HHMWE52J.js.map +1 -0
- package/dist/{chunk-5UG4UHKU.js → chunk-HUJZO4K5.js} +281 -106
- package/dist/chunk-HUJZO4K5.js.map +1 -0
- package/dist/{chunk-TQ75FB52.js → chunk-JWQ2JCQS.js} +4 -4
- package/dist/{chunk-TQ75FB52.js.map → chunk-JWQ2JCQS.js.map} +1 -1
- package/dist/{chunk-WGA4KUGS.js → chunk-K7QR7U3V.js} +12 -11
- package/dist/chunk-K7QR7U3V.js.map +1 -0
- package/dist/{chunk-E44I7635.js → chunk-KCKG3EEJ.js} +3 -3
- package/dist/{chunk-E44I7635.js.map → chunk-KCKG3EEJ.js.map} +1 -1
- package/dist/{chunk-WGSLN73L.js → chunk-MMIIVRQC.js} +7 -7
- package/dist/chunk-MMIIVRQC.js.map +1 -0
- package/dist/chunk-N6R4XPLX.js +27 -0
- package/dist/chunk-N6R4XPLX.js.map +1 -0
- package/dist/{chunk-WPQCJADP.js → chunk-NQQT75AN.js} +62 -22
- package/dist/chunk-NQQT75AN.js.map +1 -0
- package/dist/{chunk-TOHNVUDD.js → chunk-NSC7ZK62.js} +3 -3
- package/dist/{chunk-TOHNVUDD.js.map → chunk-NSC7ZK62.js.map} +1 -1
- package/dist/{chunk-LAYDXJEQ.js → chunk-OAYSRM7J.js} +12 -14
- package/dist/chunk-OAYSRM7J.js.map +1 -0
- package/dist/{chunk-R5BGRJ6X.js → chunk-QMROD7MI.js} +15 -8
- package/dist/chunk-QMROD7MI.js.map +1 -0
- package/dist/{chunk-SHD5TCZ5.js → chunk-R2FX4USU.js} +7 -7
- package/dist/chunk-R2FX4USU.js.map +1 -0
- package/dist/{chunk-7S4ELKK6.js → chunk-RL7JYG3D.js} +3 -3
- package/dist/{chunk-7S4ELKK6.js.map → chunk-RL7JYG3D.js.map} +1 -1
- package/dist/{chunk-QLNJ3VWO.js → chunk-RRSUNTZI.js} +3 -3
- package/dist/{chunk-QLNJ3VWO.js.map → chunk-RRSUNTZI.js.map} +1 -1
- package/dist/{chunk-QONHVC4H.js → chunk-TWFPHXPN.js} +3 -3
- package/dist/{chunk-QONHVC4H.js.map → chunk-TWFPHXPN.js.map} +1 -1
- package/dist/chunk-UGRUG2FA.js +6 -0
- package/dist/{chunk-JQ7OL4XG.js.map → chunk-UGRUG2FA.js.map} +1 -1
- package/dist/{chunk-XW4FCBL4.js → chunk-VOEEYCR3.js} +6 -6
- package/dist/{chunk-XW4FCBL4.js.map → chunk-VOEEYCR3.js.map} +1 -1
- package/dist/{chunk-WHNB3443.js → chunk-WBDRBP22.js} +4 -4
- package/dist/{chunk-WHNB3443.js.map → chunk-WBDRBP22.js.map} +1 -1
- package/dist/{chunk-NQU6E6OG.js → chunk-YL332BU2.js} +20 -15
- package/dist/chunk-YL332BU2.js.map +1 -0
- package/dist/{chunk-IEZCFMNL.js → chunk-ZQY5TWCB.js} +3 -3
- package/dist/{chunk-IEZCFMNL.js.map → chunk-ZQY5TWCB.js.map} +1 -1
- package/dist/cli.d.ts +16 -1
- package/dist/cli.js +2359 -340
- package/dist/cli.js.map +1 -1
- package/dist/{daemon-2YMCH7R2.js → daemon-NRIIDMX2.js} +10 -10
- package/dist/{daemon-2YMCH7R2.js.map → daemon-NRIIDMX2.js.map} +1 -1
- package/dist/embed-worker-main.js +3 -3
- package/dist/{ensure-map-S2GBW2UC.js → ensure-map-VAIYCCEJ.js} +17 -16
- package/dist/{ensure-map-S2GBW2UC.js.map → ensure-map-VAIYCCEJ.js.map} +1 -1
- package/dist/explain-EXPJTCB5.js +126 -0
- package/dist/explain-EXPJTCB5.js.map +1 -0
- package/dist/{graph-backend-QDEU5RIR.js → graph-backend-V5A2H5ZB.js} +7 -7
- package/dist/{graph-backend-QDEU5RIR.js.map → graph-backend-V5A2H5ZB.js.map} +1 -1
- package/dist/index.d.ts +217 -13
- package/dist/index.js +17 -16
- package/dist/index.js.map +1 -1
- package/dist/{interactive-GMGWUEQO.js → interactive-VQM2CTE2.js} +32 -31
- package/dist/interactive-VQM2CTE2.js.map +1 -0
- package/dist/load-UAVA732M.js +11 -0
- package/dist/{load-JXCTAAEI.js.map → load-UAVA732M.js.map} +1 -1
- package/dist/parse-worker.js +2 -2
- package/dist/{rules-ASYEUNB5.js → rules-TLRGB5L4.js} +12 -4
- package/dist/rules-TLRGB5L4.js.map +1 -0
- package/dist/{run-outcome-5NA7PI52.js → run-outcome-EATWIKLR.js} +5 -5
- package/dist/{run-outcome-5NA7PI52.js.map → run-outcome-EATWIKLR.js.map} +1 -1
- package/dist/{runtime-session-IEM6DZDP.js → runtime-session-Y2BRBXUW.js} +9 -9
- package/dist/{runtime-session-IEM6DZDP.js.map → runtime-session-Y2BRBXUW.js.map} +1 -1
- package/dist/{session-VYFLJ7KL.js → session-N2FTYIHQ.js} +9 -9
- package/dist/{session-VYFLJ7KL.js.map → session-N2FTYIHQ.js.map} +1 -1
- package/dist/{stream-json-2ISQLKPD.js → stream-json-GCZAHORX.js} +14 -14
- package/dist/{stream-json-2ISQLKPD.js.map → stream-json-GCZAHORX.js.map} +1 -1
- package/dist/version-XNZ4GLMQ.js +4 -0
- package/dist/{version-B62ELJRY.js.map → version-XNZ4GLMQ.js.map} +1 -1
- package/dist/{vg-mcp-bridge-CJIK5U56.js → vg-mcp-bridge-VDZWFEO2.js} +22 -11
- package/dist/vg-mcp-bridge-VDZWFEO2.js.map +1 -0
- package/dist/{vgd-4EB6SKFB.js → vgd-3A2TRNP5.js} +8 -8
- package/dist/vgd-3A2TRNP5.js.map +1 -0
- package/package.json +2 -2
- package/dist/baseline-PRY5HKL5.js +0 -9
- package/dist/chunk-5UG4UHKU.js.map +0 -1
- package/dist/chunk-BHDP7EVJ.js.map +0 -1
- package/dist/chunk-JQ7OL4XG.js +0 -6
- package/dist/chunk-K2IWOZEQ.js.map +0 -1
- package/dist/chunk-LAYDXJEQ.js.map +0 -1
- package/dist/chunk-NQU6E6OG.js.map +0 -1
- package/dist/chunk-R5BGRJ6X.js.map +0 -1
- package/dist/chunk-SHD5TCZ5.js.map +0 -1
- package/dist/chunk-TKUWEJZJ.js.map +0 -1
- package/dist/chunk-TUVB6Q6F.js.map +0 -1
- package/dist/chunk-WGA4KUGS.js.map +0 -1
- package/dist/chunk-WGSLN73L.js.map +0 -1
- package/dist/chunk-WPQCJADP.js.map +0 -1
- package/dist/chunk-YENM3HT7.js +0 -23
- package/dist/chunk-YENM3HT7.js.map +0 -1
- package/dist/interactive-GMGWUEQO.js.map +0 -1
- package/dist/load-JXCTAAEI.js +0 -11
- package/dist/rules-ASYEUNB5.js.map +0 -1
- package/dist/version-B62ELJRY.js +0 -4
- package/dist/vg-mcp-bridge-CJIK5U56.js.map +0 -1
- package/dist/vgd-4EB6SKFB.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { fetchCatalog, resolveProviders } from './chunk-
|
|
3
|
-
import { telemetryOptOut, StatsSharer, statsEndpoint, isCI } from './chunk-
|
|
4
|
-
import { loadAdvancedScanHook, baselineCommand } from './chunk-
|
|
5
|
-
import { sha256Hex, generateKeypair, dssePae, DSSE_PAYLOAD_TYPE, keyId, registerBuild, LiveStatus, ProgressBar, printLogo, runBuild } from './chunk-
|
|
6
|
-
export { runBuild } from './chunk-
|
|
2
|
+
import { fetchCatalog, resolveProviders } from './chunk-WBDRBP22.js';
|
|
3
|
+
import { telemetryOptOut, StatsSharer, statsEndpoint, isCI } from './chunk-D3SV3JWB.js';
|
|
4
|
+
import { loadAdvancedScanHook, baselineCommand } from './chunk-RRSUNTZI.js';
|
|
5
|
+
import { sha256Hex, generateKeypair, dssePae, DSSE_PAYLOAD_TYPE, keyId, registerBuild, LiveStatus, ProgressBar, printLogo, runBuild } from './chunk-K7QR7U3V.js';
|
|
6
|
+
export { runBuild } from './chunk-K7QR7U3V.js';
|
|
7
7
|
import { readOrchestratorState, writeOrchestratorState, parseMode, setDefaultMode, recommendMode, activePin, resolveMode, providerForBackend, QUALIFIED_PACKS, pinPack, unpinPack, buildOrchestratorStatus } from './chunk-OLIMW4PQ.js';
|
|
8
8
|
import { defaultProcessEndpoint, startProcessHostServer, resolveInferenceIsolation } from './chunk-IASO2HCI.js';
|
|
9
|
-
import { runCodeSession } from './chunk-
|
|
10
|
-
import { CONTEXT_POLICY_VERSION, computeFcsReport, renderFcsReportMarkdown, createTrajectoryCollector, runAgent } from './chunk-
|
|
9
|
+
import { runCodeSession } from './chunk-MMIIVRQC.js';
|
|
10
|
+
import { CONTEXT_POLICY_VERSION, computeFcsReport, renderFcsReportMarkdown, createTrajectoryCollector, runAgent } from './chunk-NQQT75AN.js';
|
|
11
11
|
import './chunk-543E6WDJ.js';
|
|
12
|
-
import './chunk-
|
|
13
|
-
import './chunk-
|
|
14
|
-
import { parseReasoningEffort, summarizeDiffs, ScriptedProvider } from './chunk-
|
|
12
|
+
import './chunk-FKCN653F.js';
|
|
13
|
+
import './chunk-5RW5PIBH.js';
|
|
14
|
+
import { parseReasoningEffort, summarizeDiffs, ScriptedProvider } from './chunk-R2FX4USU.js';
|
|
15
15
|
import './chunk-NV7XNBX4.js';
|
|
16
16
|
import { buildModelInstallPlan } from './chunk-W44THEXF.js';
|
|
17
17
|
import { ensurePackage, ensureUnavailableMessage } from './chunk-3HNPVXUK.js';
|
|
18
|
-
import { vgdVersionSkew, restartVgdIfRunning, stopVgd, registerDaemon, rootOf, requireGraph } from './chunk-
|
|
19
|
-
import { readGlobal, applyGlobalOptions } from './chunk-
|
|
18
|
+
import { vgdVersionSkew, restartVgdIfRunning, stopVgd, registerDaemon, rootOf, requireGraph } from './chunk-FHSEK64W.js';
|
|
19
|
+
import { readGlobal, applyGlobalOptions } from './chunk-N6R4XPLX.js';
|
|
20
20
|
import { ActivityLog } from './chunk-H5Q4JUY4.js';
|
|
21
21
|
import { disableColor, info, c, json, out } from './chunk-CT5BS56U.js';
|
|
22
22
|
import { init_resolve_ollama, gatherSystemMemory, resolveOllamaBinary, hasOllamaBinary, resolve_ollama_exports } from './chunk-WUFIXAEJ.js';
|
|
23
23
|
import { fmt, estimateModelBytes } from './chunk-YJYEPYB2.js';
|
|
24
|
-
import { isManifest, isDependencyFile, manifestHash, loadScanCache, writeScanCache, manifestKind, findPackageLine, endOfLine, findRuntimeLine } from './chunk-
|
|
24
|
+
import { isManifest, isDependencyFile, manifestHash, loadScanCache, writeScanCache, manifestKind, findPackageLine, endOfLine, findRuntimeLine } from './chunk-VOEEYCR3.js';
|
|
25
25
|
import { clearHostSessionPool, acquireHostSession, generateOnSession, listHostSessions, hostSessionPoolSize } from './chunk-3LOWJSAF.js';
|
|
26
26
|
import { buildIdentifierTrieFromGraph } from './chunk-5HYCB55P.js';
|
|
27
27
|
import './chunk-IS7VTH2Q.js';
|
|
28
28
|
import './chunk-IZDH67DX.js';
|
|
29
|
-
import { moduleInstalled, removeRelevanceModule, installRelevanceModule, DISCLOSURE, RELEVANCE_MODULE_NAME, moduleInstalledAt, removeModule, installModule, kickRelevanceReadiness, modulesBaseDir, ensureRelevanceModule, DaemonSemanticSession, serveStdio, kernelDisabled, writeConsent, readConsent, exportGraph, GraphSource, createServer, formatForExt, RefreshScheduler } from './chunk-
|
|
30
|
-
import { refreshIfStale } from './chunk-
|
|
31
|
-
import { driftCount, ASSISTANTS, detectAssistants, isAssistantInstalled, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, buildGraph, writeSnapshot, probeFreshness, assistantById, SMALL_REPO_FILES } from './chunk-
|
|
32
|
-
import
|
|
29
|
+
import { moduleInstalled, removeRelevanceModule, installRelevanceModule, DISCLOSURE, RELEVANCE_MODULE_NAME, moduleInstalledAt, removeModule, installModule, kickRelevanceReadiness, modulesBaseDir, ensureRelevanceModule, DaemonSemanticSession, serveStdio, kernelDisabled, writeConsent, readConsent, exportGraph, GraphSource, createServer, formatForExt, RefreshScheduler } from './chunk-BODXEZEY.js';
|
|
30
|
+
import { refreshIfStale } from './chunk-B5VCPIKT.js';
|
|
31
|
+
import { driftCount, ASSISTANTS, detectAssistants, isAssistantInstalled, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, buildGraph, writeSnapshot, probeFreshness, assistantById, SMALL_REPO_FILES } from './chunk-HHMWE52J.js';
|
|
32
|
+
import './chunk-EGMBHPXV.js';
|
|
33
|
+
import { resolveDsn, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, parseDsn as parseDsn$1, uploadScanArtifact, dsnCommand, pushCommand, countTokens, shortestPath, pathDisconnect, testsToRun, detectRunner, coveringTests, FREE_PACK, readScanArtifact, rejectPushWhenOffline, publishPrivateLibrary, assessDocQuality, symbolsFromApi, fetchHostedDocsCached, selectForBudget, hostedBase, isGitRepo, collectChangeSet, fileRecencyDays, readDeclaredIntent, readBaseFileTexts, SimilarityIndex, isComparable, FINDINGS_SCHEMA, digest, repoKey, receiptId, receiptDigest, CAPSULE_SCHEMA, REVIEW_POLICY_VERSION, RECEIPT_SCHEMA, buildEnvelope, collectSpans, pushReceipt, readStoredCredentials, projectCredentialsPath, MAX_ROLE_PEERS, classifyDataAccess, MIN_GROUP_SIZE, voteAll, CAPSULE_BUDGETS, digestString, routesForFile, classifyRoute, voteAllAuth, isRegression, describePattern, homeCredentialsPath, TARGET_ALIGNMENTS, defaultRun } from './chunk-65YN3BG3.js';
|
|
33
34
|
import { resolveIngestHost, dashHostForIngestHost, availableRegionIds, ingestHostForRegionId } from './chunk-HTLDXEHW.js';
|
|
34
|
-
import { writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, loadConfig, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, refineArchitectureWithAstRoles, refineArchitectureWithGraph, PROJECT_TYPE_TO_OSV_ECOSYSTEM, fetchRiskySymbols, perDependencyDrift,
|
|
35
|
-
import './chunk-
|
|
35
|
+
import { writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, loadConfig, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, refineArchitectureWithAstRoles, refineArchitectureWithGraph, classifyFile, PROJECT_TYPE_TO_OSV_ECOSYSTEM, fetchRiskySymbols, boundaryProfileRules, evaluateLayerBoundary, perDependencyDrift, parseToml } from './chunk-HUJZO4K5.js';
|
|
36
|
+
import './chunk-QMROD7MI.js';
|
|
36
37
|
import './chunk-VIWZ4QT7.js';
|
|
37
|
-
import { catalogPath, libDir, loadCatalog, driftFor, addLibrary, resolveLib, libId, resolveVersion, readDoc, localPackageDocs, localApiSurface } from './chunk-
|
|
38
|
-
import { recordCliCall, CLI_TOOL_ALIASES, savingsLedgerPath, clearSavings, readSavings, readUsage, readModelSavings, CAPSULE_RANKING_VERSION, SAVINGS_TOOLS, sanitizeClient } from './chunk-
|
|
38
|
+
import { catalogPath, libDir, loadCatalog, driftFor, addLibrary, resolveLib, libId, resolveVersion, readDoc, localPackageDocs, localApiSurface } from './chunk-TWFPHXPN.js';
|
|
39
|
+
import { recordCliCall, CLI_TOOL_ALIASES, savingsLedgerPath, clearSavings, readSavings, readUsage, readModelSavings, CAPSULE_RANKING_VERSION, SAVINGS_TOOLS, sanitizeClient } from './chunk-OAYSRM7J.js';
|
|
39
40
|
import { discoverModels } from './chunk-WNKGRXUA.js';
|
|
40
41
|
import { freeBytesOnVolume, weightStoreDir, listCachedWeights, ensureWeightCached, catalogIntegrityReport } from './chunk-57ZRSCHY.js';
|
|
41
42
|
import { CliError, ExitCode, usageError, engineUnavailable } from './chunk-MS6V5XKB.js';
|
|
42
|
-
import { attachVgd } from './chunk-
|
|
43
|
+
import { attachVgd } from './chunk-KCKG3EEJ.js';
|
|
43
44
|
import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-PVNALGTG.js';
|
|
44
45
|
import { ensureDir, pathExists, readJsonFile, writeTextFile, writeJsonFile } from './chunk-CRAMOEBE.js';
|
|
45
46
|
import './chunk-5IQBIXHS.js';
|
|
@@ -48,27 +49,27 @@ import { publishGraphToVgd } from './chunk-A3WWRW7U.js';
|
|
|
48
49
|
import { isRelevantChange } from './chunk-DBX3DL27.js';
|
|
49
50
|
import { inventory, enrichOnline, ECOSYSTEMS } from './chunk-6BEBJCK4.js';
|
|
50
51
|
import { resolveOne } from './chunk-J7LKEB2R.js';
|
|
51
|
-
import { queryGraphSemantic, queryGraph, indexFor, impactOf } from './chunk-
|
|
52
|
-
import { loadGraph, loadGraphPreferIndex, SCHEMA_VERSION } from './chunk-
|
|
52
|
+
import { rankingAskFrom, queryGraphSemantic, queryGraph, indexFor, impactOf } from './chunk-YL332BU2.js';
|
|
53
|
+
import { loadGraph, loadGraphPreferIndex, SCHEMA_VERSION } from './chunk-RL7JYG3D.js';
|
|
53
54
|
import './chunk-VHRSG6CF.js';
|
|
54
55
|
import './chunk-6H2QV5DR.js';
|
|
55
|
-
import { discover, isSkippedDirName } from './chunk-RTURQXQL.js';
|
|
56
|
-
import { resolvedGrammarFiles, TOOLCHAIN_GRAMMARS, resolveToolchainGrammar, grammarSetVersion, fileRolesFromParseCache } from './chunk-
|
|
56
|
+
import { discover, mergeExcludes, isSkippedDirName } from './chunk-RTURQXQL.js';
|
|
57
|
+
import { resolvedGrammarFiles, TOOLCHAIN_GRAMMARS, resolveToolchainGrammar, grammarSetVersion, fileRolesFromParseCache } from './chunk-NSC7ZK62.js';
|
|
57
58
|
import './chunk-T7RSQ5WD.js';
|
|
58
59
|
import './chunk-6WRG7VDO.js';
|
|
59
|
-
import { embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, unavailableMessage, countPending, modelCacheInfo, embeddingsPath, clearModelCache, withTimeout } from './chunk-
|
|
60
|
-
import { resetRelevanceProviderCache, loadRelevanceProvider, resolveGraphPath, loadTopicTags, rankQuestion, renderReport, loadGraphFileWithSnapshot, writeArtifacts, vibgrateDir, mapFileExists, ensureVibgrateGitignore } from './chunk-
|
|
60
|
+
import { embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, unavailableMessage, countPending, modelCacheInfo, embeddingsPath, clearModelCache, acquireLock, releaseLock, withTimeout } from './chunk-BA6YBVNT.js';
|
|
61
|
+
import { resetRelevanceProviderCache, loadRelevanceProvider, resolveGraphPath, loadTopicTags, rankQuestion, renderReport, loadGraphFileWithSnapshot, writeArtifacts, displayGraphPath, vibgrateDir, mapFileExists, ensureVibgrateGitignore } from './chunk-FPIMNVLI.js';
|
|
61
62
|
import { cacheDir } from './chunk-MVPRPSXU.js';
|
|
62
63
|
import { serializeGraph, stableStringify, parseGraph } from './chunk-O3LABX6I.js';
|
|
63
|
-
import { VERSION } from './chunk-
|
|
64
|
+
import { VERSION } from './chunk-UGRUG2FA.js';
|
|
64
65
|
import './chunk-XP6UUCZZ.js';
|
|
65
66
|
import { canonicalize, hashString } from './chunk-JFGYT6BI.js';
|
|
66
67
|
import './chunk-XQNKCA2J.js';
|
|
67
68
|
import { __toCommonJS } from './chunk-Z6ZWNWWR.js';
|
|
68
69
|
import * as fs12 from 'fs';
|
|
69
70
|
import fs12__default, { realpathSync } from 'fs';
|
|
70
|
-
import * as
|
|
71
|
-
import
|
|
71
|
+
import * as path51 from 'path';
|
|
72
|
+
import path51__default from 'path';
|
|
72
73
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
73
74
|
import { Command, CommanderError, Option } from 'commander';
|
|
74
75
|
import { execSync, spawn, spawnSync, execFileSync } from 'child_process';
|
|
@@ -79,7 +80,7 @@ import * as readline from 'readline';
|
|
|
79
80
|
import * as crypto3 from 'crypto';
|
|
80
81
|
import { randomUUID } from 'crypto';
|
|
81
82
|
import chalk7 from 'chalk';
|
|
82
|
-
import * as
|
|
83
|
+
import * as fs32 from 'fs/promises';
|
|
83
84
|
import { parse, stringify } from 'yaml';
|
|
84
85
|
|
|
85
86
|
init_cli_invocation();
|
|
@@ -90,10 +91,10 @@ function registerStatus(program) {
|
|
|
90
91
|
applyGlobalOptions(cmd);
|
|
91
92
|
}
|
|
92
93
|
async function runStatus(global) {
|
|
93
|
-
const root3 =
|
|
94
|
+
const root3 = path51.resolve(global.cwd ?? ".");
|
|
94
95
|
const graphPath = resolveGraphPath(root3, global.graph);
|
|
95
96
|
const graph = loadGraph(root3, graphPath);
|
|
96
|
-
const hasCache = fs12.existsSync(
|
|
97
|
+
const hasCache = fs12.existsSync(path51.join(cacheDir(root3), "parse-cache.json"));
|
|
97
98
|
let stale = null;
|
|
98
99
|
let staleExact = false;
|
|
99
100
|
if (graph) {
|
|
@@ -113,8 +114,8 @@ async function runStatus(global) {
|
|
|
113
114
|
}
|
|
114
115
|
if (global.json) {
|
|
115
116
|
json({
|
|
116
|
-
root:
|
|
117
|
-
graphPath:
|
|
117
|
+
root: path51.relative(process.cwd(), root3) || ".",
|
|
118
|
+
graphPath: displayGraphPath(root3, graphPath),
|
|
118
119
|
built: graph !== null,
|
|
119
120
|
generatedAt: graph?.generatedAt ?? null,
|
|
120
121
|
counts: graph?.meta.counts ?? null,
|
|
@@ -129,11 +130,11 @@ async function runStatus(global) {
|
|
|
129
130
|
return;
|
|
130
131
|
}
|
|
131
132
|
if (!graph) {
|
|
132
|
-
info(`${c.cyan("vg")} \xB7 no map yet at ${c.dim(
|
|
133
|
+
info(`${c.cyan("vg")} \xB7 no map yet at ${c.dim(displayGraphPath(root3, graphPath))}`);
|
|
133
134
|
info(` run ${c.bold("vg")} to build one`);
|
|
134
135
|
return;
|
|
135
136
|
}
|
|
136
|
-
info(`${c.cyan("vg")} \xB7 ${
|
|
137
|
+
info(`${c.cyan("vg")} \xB7 ${displayGraphPath(root3, graphPath)}`);
|
|
137
138
|
info(` generated ${graph.generatedAt}`);
|
|
138
139
|
info(
|
|
139
140
|
` nodes ${c.bold(String(graph.meta.counts.nodes))} edges ${c.bold(
|
|
@@ -183,13 +184,13 @@ function registerAsk(program) {
|
|
|
183
184
|
);
|
|
184
185
|
const budget = Number(opts.budget) || 2e3;
|
|
185
186
|
const q = question.join(" ");
|
|
186
|
-
const wantSemantic = opts.semantic !== false && !global.
|
|
187
|
+
const wantSemantic = opts.semantic !== false && !global.offline;
|
|
187
188
|
let result;
|
|
188
189
|
let mode = "lexical";
|
|
189
190
|
let note = "";
|
|
190
|
-
if (!global.
|
|
191
|
+
if (!global.offline) await ensureRelevanceModule();
|
|
191
192
|
const topicTags = await loadTopicTags(graph, root3, resolveGraphPath(root3, global.graph));
|
|
192
|
-
const modRank = await rankQuestion(graph, q, { limit: 48, topicTags });
|
|
193
|
+
const modRank = await rankQuestion(graph, rankingAskFrom(q), { limit: 48, topicTags });
|
|
193
194
|
try {
|
|
194
195
|
if (wantSemantic) {
|
|
195
196
|
live?.set("asking the daemon\u2026");
|
|
@@ -219,7 +220,7 @@ function registerAsk(program) {
|
|
|
219
220
|
let reason;
|
|
220
221
|
let detail;
|
|
221
222
|
const embedder = await loadEmbedder({
|
|
222
|
-
local: global.
|
|
223
|
+
local: global.offline,
|
|
223
224
|
onUnavailable: (r, d) => {
|
|
224
225
|
reason = r;
|
|
225
226
|
detail = d;
|
|
@@ -242,8 +243,8 @@ function registerAsk(program) {
|
|
|
242
243
|
}
|
|
243
244
|
} else {
|
|
244
245
|
result = queryGraph(graph, q, { budget, ranked: modRank });
|
|
245
|
-
if (global.
|
|
246
|
-
activity.add("answer", "skip", global.
|
|
246
|
+
if (global.offline) note = "semantic skipped under --offline; used lexical";
|
|
247
|
+
activity.add("answer", "skip", global.offline ? "lexical only (--offline)" : "lexical only (--no-semantic)");
|
|
247
248
|
}
|
|
248
249
|
} finally {
|
|
249
250
|
live?.stop();
|
|
@@ -309,11 +310,11 @@ function crashedMessage(signal) {
|
|
|
309
310
|
return `semantic search is disabled: its native backend (onnxruntime-node, via the vendored fastembed path) crashed with ${signal} \u2014 using fast lexical search instead. This usually means the installed native binaries are broken (an interrupted install, or a platform without prebuilt CPU binaries). Reinstall to repair \u2014 ${envPrefix}npm install -g --allow-scripts=@vibgrate/cli,onnxruntime-node,msgpackr-extract @vibgrate/cli \u2014 or run with --local to stay lexical quietly.`;
|
|
310
311
|
}
|
|
311
312
|
function defaultCacheDir() {
|
|
312
|
-
const base = process.env.XDG_CACHE_HOME ||
|
|
313
|
-
return
|
|
313
|
+
const base = process.env.XDG_CACHE_HOME || path51.join(os.homedir(), ".cache");
|
|
314
|
+
return path51.join(base, "vibgrate", "models");
|
|
314
315
|
}
|
|
315
316
|
function verdictPath(dir) {
|
|
316
|
-
return
|
|
317
|
+
return path51.join(dir, `embed-health-${process.platform}-${process.arch}.json`);
|
|
317
318
|
}
|
|
318
319
|
function readVerdict(dir) {
|
|
319
320
|
try {
|
|
@@ -333,12 +334,12 @@ function writeVerdict(dir, verdict) {
|
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
336
|
function probeModuleUrl(from = import.meta.url) {
|
|
336
|
-
const dir =
|
|
337
|
+
const dir = path51.dirname(fileURLToPath(from));
|
|
337
338
|
for (const name of ["embeddings.js", "index.js"]) {
|
|
338
|
-
const candidate =
|
|
339
|
+
const candidate = path51.join(dir, name);
|
|
339
340
|
if (fs12.existsSync(candidate)) return pathToFileURL(candidate).href;
|
|
340
341
|
}
|
|
341
|
-
return pathToFileURL(
|
|
342
|
+
return pathToFileURL(path51.join(dir, "index.js")).href;
|
|
342
343
|
}
|
|
343
344
|
function defaultRunProbe(timeoutMs) {
|
|
344
345
|
const moduleUrl = probeModuleUrl();
|
|
@@ -357,7 +358,7 @@ function defaultRunProbe(timeoutMs) {
|
|
|
357
358
|
})
|
|
358
359
|
.catch(() => process.stdout.write('{"ok":false}'));
|
|
359
360
|
`;
|
|
360
|
-
return new Promise((
|
|
361
|
+
return new Promise((resolve25) => {
|
|
361
362
|
const child = spawn(process.execPath, ["--input-type=module", "-e", script], {
|
|
362
363
|
stdio: ["ignore", "pipe", "ignore"],
|
|
363
364
|
// The probe must never inherit a debugger port or loader flags that would
|
|
@@ -373,7 +374,7 @@ function defaultRunProbe(timeoutMs) {
|
|
|
373
374
|
if (settled) return;
|
|
374
375
|
settled = true;
|
|
375
376
|
clearTimeout(timer);
|
|
376
|
-
|
|
377
|
+
resolve25({ signal, status, stdout });
|
|
377
378
|
};
|
|
378
379
|
const timer = setTimeout(() => {
|
|
379
380
|
child.kill("SIGTERM");
|
|
@@ -437,7 +438,7 @@ function registerEmbed(program) {
|
|
|
437
438
|
const root3 = rootOf(global);
|
|
438
439
|
const bg = opts.bg === true;
|
|
439
440
|
const speak = !bg && !global.json;
|
|
440
|
-
if (global.
|
|
441
|
+
if (global.offline) {
|
|
441
442
|
if (global.json) json({ embedded: 0, model: modelId, semantic: false, reason: "local" });
|
|
442
443
|
else if (speak) info(c.dim("vg embed \xB7 semantic is off (--local) \u2014 lexical search only"));
|
|
443
444
|
return;
|
|
@@ -457,7 +458,7 @@ function registerEmbed(program) {
|
|
|
457
458
|
let reason;
|
|
458
459
|
let detail;
|
|
459
460
|
const embedder = await loadEmbedder({
|
|
460
|
-
local: global.
|
|
461
|
+
local: global.offline,
|
|
461
462
|
// Background warm-ups skip the download unless explicitly asked to fetch
|
|
462
463
|
// (`--bg --download`, spawned by `vg build` so the model is ready by the
|
|
463
464
|
// time `vg serve`/`vg ask` needs it).
|
|
@@ -1091,7 +1092,7 @@ var PUBLISH_INTERVAL_MS = 2e3;
|
|
|
1091
1092
|
var KEEPALIVE_MS = 1e4;
|
|
1092
1093
|
var STALE_MS = 6e4;
|
|
1093
1094
|
function liveStatsDir(root3) {
|
|
1094
|
-
return
|
|
1095
|
+
return path51.join(cacheDir(root3), "serve-live");
|
|
1095
1096
|
}
|
|
1096
1097
|
function reapOtherVersionServers(dir, keepVersion, now = Date.now(), kill = (pid, signal) => process.kill(pid, signal), selfPid = process.pid) {
|
|
1097
1098
|
let entries;
|
|
@@ -1105,7 +1106,7 @@ function reapOtherVersionServers(dir, keepVersion, now = Date.now(), kill = (pid
|
|
|
1105
1106
|
if (!/^serve-\d+\.json$/.test(name)) continue;
|
|
1106
1107
|
let parsed;
|
|
1107
1108
|
try {
|
|
1108
|
-
parsed = JSON.parse(fs12.readFileSync(
|
|
1109
|
+
parsed = JSON.parse(fs12.readFileSync(path51.join(dir, name), "utf8"));
|
|
1109
1110
|
} catch {
|
|
1110
1111
|
continue;
|
|
1111
1112
|
}
|
|
@@ -1127,7 +1128,7 @@ var LiveStatsBus = class {
|
|
|
1127
1128
|
this.dir = dir;
|
|
1128
1129
|
this.stats = stats;
|
|
1129
1130
|
this.pid = pid;
|
|
1130
|
-
this.file =
|
|
1131
|
+
this.file = path51.join(dir, `serve-${this.pid}.json`);
|
|
1131
1132
|
}
|
|
1132
1133
|
dir;
|
|
1133
1134
|
stats;
|
|
@@ -1180,7 +1181,7 @@ var LiveStatsBus = class {
|
|
|
1180
1181
|
const out2 = [];
|
|
1181
1182
|
for (const name of entries.sort()) {
|
|
1182
1183
|
if (!/^serve-\d+\.json$/.test(name)) continue;
|
|
1183
|
-
const file =
|
|
1184
|
+
const file = path51.join(this.dir, name);
|
|
1184
1185
|
let parsed;
|
|
1185
1186
|
try {
|
|
1186
1187
|
parsed = JSON.parse(fs12.readFileSync(file, "utf8"));
|
|
@@ -1570,7 +1571,7 @@ function registerServe(program) {
|
|
|
1570
1571
|
const root3 = rootOf(global);
|
|
1571
1572
|
const graphPath = resolveGraphPath(root3, global.graph);
|
|
1572
1573
|
const refresh = opts.refresh !== false && !global.graph;
|
|
1573
|
-
const local = global.
|
|
1574
|
+
const local = global.offline === true;
|
|
1574
1575
|
const optOut = telemetryOptOut();
|
|
1575
1576
|
const shareStats = opts.shareStats === true && !local && optOut === null;
|
|
1576
1577
|
const quiet = global.quiet === true;
|
|
@@ -1713,7 +1714,7 @@ async function serveHttp(graphPath, host, port, opts, freshness, onReady) {
|
|
|
1713
1714
|
info(c.red(`vg serve: request error: ${err.message}`));
|
|
1714
1715
|
}
|
|
1715
1716
|
});
|
|
1716
|
-
await new Promise((
|
|
1717
|
+
await new Promise((resolve25) => httpServer.listen(port, host, resolve25));
|
|
1717
1718
|
info(c.dim(`vg \xB7 MCP server on http://${host}:${port}/mcp (read-only, local, ${freshness})`));
|
|
1718
1719
|
onReady?.();
|
|
1719
1720
|
await new Promise(() => {
|
|
@@ -1826,10 +1827,10 @@ var Connection = class {
|
|
|
1826
1827
|
};
|
|
1827
1828
|
function projectDir(root3, projectPath) {
|
|
1828
1829
|
const rel = projectPath === "" || projectPath === "." ? "" : projectPath;
|
|
1829
|
-
return rel ?
|
|
1830
|
+
return rel ? path51.resolve(root3, rel) : path51.resolve(root3);
|
|
1830
1831
|
}
|
|
1831
1832
|
function projectForFile(root3, projects, filePath) {
|
|
1832
|
-
const dir =
|
|
1833
|
+
const dir = path51.dirname(path51.resolve(filePath));
|
|
1833
1834
|
for (const proj of projects) {
|
|
1834
1835
|
if (projectDir(root3, proj.path) === dir) return proj;
|
|
1835
1836
|
}
|
|
@@ -1838,7 +1839,7 @@ function projectForFile(root3, projects, filePath) {
|
|
|
1838
1839
|
let bestLen = -1;
|
|
1839
1840
|
for (const proj of projects) {
|
|
1840
1841
|
const projDir = projectDir(root3, proj.path);
|
|
1841
|
-
if ((dir === projDir || dir.startsWith(projDir +
|
|
1842
|
+
if ((dir === projDir || dir.startsWith(projDir + path51.sep)) && projDir.length > bestLen) {
|
|
1842
1843
|
best = proj;
|
|
1843
1844
|
bestLen = projDir.length;
|
|
1844
1845
|
}
|
|
@@ -1848,7 +1849,7 @@ function projectForFile(root3, projects, filePath) {
|
|
|
1848
1849
|
var MAX_ENTRIES = 1e3;
|
|
1849
1850
|
var KEEP_ENTRIES = 500;
|
|
1850
1851
|
function historyPath(root3) {
|
|
1851
|
-
return
|
|
1852
|
+
return path51.join(root3, ".vibgrate", "score-history.jsonl");
|
|
1852
1853
|
}
|
|
1853
1854
|
function parseLine(line) {
|
|
1854
1855
|
try {
|
|
@@ -1889,7 +1890,7 @@ function recordScore(root3, entry) {
|
|
|
1889
1890
|
}
|
|
1890
1891
|
}
|
|
1891
1892
|
const file = historyPath(root3);
|
|
1892
|
-
fs12.mkdirSync(
|
|
1893
|
+
fs12.mkdirSync(path51.dirname(file), { recursive: true });
|
|
1893
1894
|
ensureVibgrateGitignore(root3);
|
|
1894
1895
|
fs12.appendFileSync(file, `${JSON.stringify(entry)}
|
|
1895
1896
|
`);
|
|
@@ -1907,7 +1908,7 @@ function deltaFrom(prev, current) {
|
|
|
1907
1908
|
return current.score - prev.score;
|
|
1908
1909
|
}
|
|
1909
1910
|
function ignoreFilePath(root3) {
|
|
1910
|
-
return
|
|
1911
|
+
return path51.join(root3, ".vibgrate", "ignore.json");
|
|
1911
1912
|
}
|
|
1912
1913
|
function isActive(entry, now = Date.now()) {
|
|
1913
1914
|
if (!entry.until) return true;
|
|
@@ -1939,7 +1940,7 @@ function readIgnores(root3, now = Date.now()) {
|
|
|
1939
1940
|
return out2;
|
|
1940
1941
|
}
|
|
1941
1942
|
function inventoryPath(root3) {
|
|
1942
|
-
return
|
|
1943
|
+
return path51.join(root3, ".vibgrate", "inline-inventory.json");
|
|
1943
1944
|
}
|
|
1944
1945
|
function inventoryKey(projectPath, pkg) {
|
|
1945
1946
|
return `${projectPath}::${pkg}`;
|
|
@@ -2492,6 +2493,9 @@ function architectureWire(arch) {
|
|
|
2492
2493
|
};
|
|
2493
2494
|
if (arch.folders) out2.folders = arch.folders;
|
|
2494
2495
|
if (arch.unclassifiedFiles) out2.unclassifiedFiles = arch.unclassifiedFiles;
|
|
2496
|
+
if (arch.coverage) out2.coverage = arch.coverage;
|
|
2497
|
+
if (arch.unclassifiedFolders) out2.unclassifiedFolders = arch.unclassifiedFolders;
|
|
2498
|
+
if (arch.unclassifiedFoldersCapped) out2.unclassifiedFoldersCapped = true;
|
|
2495
2499
|
if (arch.classified) out2.classified = arch.classified;
|
|
2496
2500
|
if (arch.workspaceShape) out2.workspaceShape = arch.workspaceShape;
|
|
2497
2501
|
if (arch.supportLevel !== void 0) out2.supportLevel = arch.supportLevel;
|
|
@@ -2889,7 +2893,7 @@ var VibgrateLanguageServer = class {
|
|
|
2889
2893
|
* the workspace scan cache (billing / repo score stay on the official scan).
|
|
2890
2894
|
*/
|
|
2891
2895
|
async scanOpenManifest(uri, filePath) {
|
|
2892
|
-
const relDir =
|
|
2896
|
+
const relDir = path51.relative(this.opts.root, path51.dirname(path51.resolve(filePath)));
|
|
2893
2897
|
const key2 = relDir === "" ? "." : relDir;
|
|
2894
2898
|
if (this.openManifestScans.has(key2) || this.openManifestFailed.has(key2)) return;
|
|
2895
2899
|
if (key2 === "." || key2.startsWith("..")) return;
|
|
@@ -2909,7 +2913,7 @@ var VibgrateLanguageServer = class {
|
|
|
2909
2913
|
quiet: true,
|
|
2910
2914
|
vibgrateVersion: VERSION
|
|
2911
2915
|
};
|
|
2912
|
-
const sub = await runCoreScanSilently(
|
|
2916
|
+
const sub = await runCoreScanSilently(path51.dirname(filePath), scanOpts, advanced);
|
|
2913
2917
|
const proj = (sub.projects ?? []).find((p) => p.path === "." || p.path === "") ?? sub.projects?.[0];
|
|
2914
2918
|
if (!proj || !this.artifact) {
|
|
2915
2919
|
this.openManifestFailed.add(key2);
|
|
@@ -3217,7 +3221,7 @@ var VibgrateLanguageServer = class {
|
|
|
3217
3221
|
const ecosystem = osvEcosystem(proj.type);
|
|
3218
3222
|
if (!ecosystem) continue;
|
|
3219
3223
|
const relManifest = manifestRelativePath(proj);
|
|
3220
|
-
const absManifest =
|
|
3224
|
+
const absManifest = path51.resolve(this.opts.root, relManifest);
|
|
3221
3225
|
let text = null;
|
|
3222
3226
|
try {
|
|
3223
3227
|
if (fs12.existsSync(absManifest)) text = fs12.readFileSync(absManifest, "utf8");
|
|
@@ -3410,7 +3414,7 @@ function pruneArtifactProjects(a) {
|
|
|
3410
3414
|
return { ...a, projects, findings };
|
|
3411
3415
|
}
|
|
3412
3416
|
function projectByPath(a, projectPath) {
|
|
3413
|
-
const norm = (p) =>
|
|
3417
|
+
const norm = (p) => path51.normalize((p === "" ? "." : p).replace(/\\/g, "/"));
|
|
3414
3418
|
const want = norm(projectPath);
|
|
3415
3419
|
for (const proj of a.projects ?? []) {
|
|
3416
3420
|
if (norm(proj.path) === want) return proj;
|
|
@@ -3452,7 +3456,7 @@ function projectForFile2(root3, a, filePath) {
|
|
|
3452
3456
|
return projectForFile(root3, a.projects ?? [], filePath);
|
|
3453
3457
|
}
|
|
3454
3458
|
function sameProject(location, projectPath) {
|
|
3455
|
-
return
|
|
3459
|
+
return path51.normalize(location) === path51.normalize(projectPath);
|
|
3456
3460
|
}
|
|
3457
3461
|
function hasReleaseDates(a) {
|
|
3458
3462
|
for (const proj of a.projects ?? []) {
|
|
@@ -3522,7 +3526,7 @@ function buildBreakdown(rootDir, projects) {
|
|
|
3522
3526
|
const relManifest = manifestRelativePath(p);
|
|
3523
3527
|
if (isPrunedWorkspacePath(p.path) || isPrunedWorkspacePath(relManifest)) continue;
|
|
3524
3528
|
const ecosystem = osvEcosystem(p.type);
|
|
3525
|
-
const absManifest =
|
|
3529
|
+
const absManifest = path51.resolve(rootDir, relManifest);
|
|
3526
3530
|
let text = null;
|
|
3527
3531
|
try {
|
|
3528
3532
|
if (fs12.existsSync(absManifest)) text = fs12.readFileSync(absManifest, "utf8");
|
|
@@ -3590,34 +3594,34 @@ function buildBreakdown(rootDir, projects) {
|
|
|
3590
3594
|
}
|
|
3591
3595
|
function manifestRelativePath(proj) {
|
|
3592
3596
|
const base = proj.path === "." || proj.path === "" ? "" : proj.path;
|
|
3593
|
-
const
|
|
3597
|
+
const join39 = (name) => base ? path51.join(base, name) : name;
|
|
3594
3598
|
switch (proj.type) {
|
|
3595
3599
|
case "node":
|
|
3596
3600
|
case "typescript":
|
|
3597
|
-
return
|
|
3601
|
+
return join39("package.json");
|
|
3598
3602
|
case "python":
|
|
3599
|
-
return
|
|
3603
|
+
return join39("pyproject.toml");
|
|
3600
3604
|
case "go":
|
|
3601
|
-
return
|
|
3605
|
+
return join39("go.mod");
|
|
3602
3606
|
case "rust":
|
|
3603
|
-
return
|
|
3607
|
+
return join39("Cargo.toml");
|
|
3604
3608
|
case "ruby":
|
|
3605
|
-
return
|
|
3609
|
+
return join39("Gemfile");
|
|
3606
3610
|
case "php":
|
|
3607
|
-
return
|
|
3611
|
+
return join39("composer.json");
|
|
3608
3612
|
case "java":
|
|
3609
3613
|
case "kotlin":
|
|
3610
3614
|
case "scala":
|
|
3611
3615
|
case "groovy":
|
|
3612
|
-
return
|
|
3616
|
+
return join39("pom.xml");
|
|
3613
3617
|
case "dotnet":
|
|
3614
|
-
return
|
|
3618
|
+
return join39(proj.name ? `${proj.name}.csproj` : "Directory.Build.props");
|
|
3615
3619
|
case "dart":
|
|
3616
|
-
return
|
|
3620
|
+
return join39("pubspec.yaml");
|
|
3617
3621
|
case "elixir":
|
|
3618
|
-
return
|
|
3622
|
+
return join39("mix.exs");
|
|
3619
3623
|
default:
|
|
3620
|
-
return
|
|
3624
|
+
return join39("package.json");
|
|
3621
3625
|
}
|
|
3622
3626
|
}
|
|
3623
3627
|
function lockfileRelativePath(rootDir, proj) {
|
|
@@ -3648,9 +3652,9 @@ function lockfileRelativePath(rootDir, proj) {
|
|
|
3648
3652
|
const dirsToTry = base ? [base, ""] : [""];
|
|
3649
3653
|
for (const dir of dirsToTry) {
|
|
3650
3654
|
for (const name of candidateNames) {
|
|
3651
|
-
const rel = dir ?
|
|
3655
|
+
const rel = dir ? path51.join(dir, name) : name;
|
|
3652
3656
|
try {
|
|
3653
|
-
if (fs12.existsSync(
|
|
3657
|
+
if (fs12.existsSync(path51.join(rootDir, rel))) return rel;
|
|
3654
3658
|
} catch {
|
|
3655
3659
|
}
|
|
3656
3660
|
}
|
|
@@ -3721,7 +3725,7 @@ function lookupImportFiles(map, pkg) {
|
|
|
3721
3725
|
}
|
|
3722
3726
|
function firstPackageMentionLine(root3, relFile, pkg) {
|
|
3723
3727
|
try {
|
|
3724
|
-
const abs =
|
|
3728
|
+
const abs = path51.resolve(root3, relFile);
|
|
3725
3729
|
const stat2 = fs12.statSync(abs);
|
|
3726
3730
|
if (!stat2.isFile() || stat2.size > 256 * 1024) return null;
|
|
3727
3731
|
const text = fs12.readFileSync(abs, "utf8");
|
|
@@ -3952,7 +3956,7 @@ function registerLsp(program) {
|
|
|
3952
3956
|
root: root3,
|
|
3953
3957
|
// `--local` is the established air-gap flag across the CLI; honour it
|
|
3954
3958
|
// here too, so an offline editor session stays offline.
|
|
3955
|
-
offline: global.
|
|
3959
|
+
offline: global.offline === true,
|
|
3956
3960
|
diagnostics: opts.diagnostics === true,
|
|
3957
3961
|
graph: opts.graph !== false,
|
|
3958
3962
|
semantic: opts.semantic !== false,
|
|
@@ -3970,8 +3974,8 @@ function hookCommand(vgBin) {
|
|
|
3970
3974
|
return `${vgBin} hook pre-tool-use`;
|
|
3971
3975
|
}
|
|
3972
3976
|
function installClaudeHooks(root3, vgBin = "vg") {
|
|
3973
|
-
const file =
|
|
3974
|
-
const rel =
|
|
3977
|
+
const file = path51.join(root3, ".claude", "settings.json");
|
|
3978
|
+
const rel = path51.join(".claude", "settings.json");
|
|
3975
3979
|
let settings = {};
|
|
3976
3980
|
if (fs12.existsSync(file)) {
|
|
3977
3981
|
try {
|
|
@@ -4012,8 +4016,8 @@ function installClaudeHooks(root3, vgBin = "vg") {
|
|
|
4012
4016
|
return { file: rel, status: "written" };
|
|
4013
4017
|
}
|
|
4014
4018
|
function uninstallClaudeHooks(root3) {
|
|
4015
|
-
const file =
|
|
4016
|
-
const rel =
|
|
4019
|
+
const file = path51.join(root3, ".claude", "settings.json");
|
|
4020
|
+
const rel = path51.join(".claude", "settings.json");
|
|
4017
4021
|
if (!fs12.existsSync(file)) return { file: rel, status: "unchanged" };
|
|
4018
4022
|
let settings;
|
|
4019
4023
|
try {
|
|
@@ -4039,7 +4043,7 @@ function uninstallClaudeHooks(root3) {
|
|
|
4039
4043
|
return { file: rel, status: "written" };
|
|
4040
4044
|
}
|
|
4041
4045
|
function writeSettings(file, settings) {
|
|
4042
|
-
fs12.mkdirSync(
|
|
4046
|
+
fs12.mkdirSync(path51.dirname(file), { recursive: true });
|
|
4043
4047
|
fs12.writeFileSync(file, `${JSON.stringify(settings, null, 2)}
|
|
4044
4048
|
`);
|
|
4045
4049
|
}
|
|
@@ -4159,19 +4163,19 @@ function disabled() {
|
|
|
4159
4163
|
return v === "1" || v === "true";
|
|
4160
4164
|
}
|
|
4161
4165
|
function hcsModuleDir() {
|
|
4162
|
-
return
|
|
4166
|
+
return path51.join(modulesBaseDir(), "hcs");
|
|
4163
4167
|
}
|
|
4164
4168
|
function candidatePaths() {
|
|
4165
4169
|
const custom = process.env.VIBGRATE_HCS_PATH;
|
|
4166
4170
|
if (custom) {
|
|
4167
|
-
const p =
|
|
4171
|
+
const p = path51.resolve(custom);
|
|
4168
4172
|
try {
|
|
4169
|
-
if (fs12.statSync(p).isDirectory()) return [
|
|
4173
|
+
if (fs12.statSync(p).isDirectory()) return [path51.join(p, "index.js")];
|
|
4170
4174
|
} catch {
|
|
4171
4175
|
}
|
|
4172
4176
|
return [p];
|
|
4173
4177
|
}
|
|
4174
|
-
return [
|
|
4178
|
+
return [path51.join(hcsModuleDir(), "index.js")];
|
|
4175
4179
|
}
|
|
4176
4180
|
var cached;
|
|
4177
4181
|
function resetHcsEngineCache() {
|
|
@@ -4321,11 +4325,11 @@ function requireModule(name) {
|
|
|
4321
4325
|
return mod;
|
|
4322
4326
|
}
|
|
4323
4327
|
function confirm(question) {
|
|
4324
|
-
return new Promise((
|
|
4328
|
+
return new Promise((resolve25) => {
|
|
4325
4329
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
4326
4330
|
rl.question(question, (answer) => {
|
|
4327
4331
|
rl.close();
|
|
4328
|
-
|
|
4332
|
+
resolve25(answer.trim() === "" || /^y(es)?$/i.test(answer.trim()));
|
|
4329
4333
|
});
|
|
4330
4334
|
});
|
|
4331
4335
|
}
|
|
@@ -4391,7 +4395,7 @@ function registerShare(program) {
|
|
|
4391
4395
|
const cmd = program.command("share").description("make the map committable + auto-updating for your team (git)").option("--undo", "reverse what `vg share` installed").option("--reports", "also commit graph.html / GRAPH_REPORT.md (default: gitignored)").action(function(opts) {
|
|
4392
4396
|
const global = readGlobal(this);
|
|
4393
4397
|
const root3 = rootOf(global);
|
|
4394
|
-
const gitDir =
|
|
4398
|
+
const gitDir = path51.join(root3, ".git");
|
|
4395
4399
|
if (!fs12.existsSync(gitDir)) {
|
|
4396
4400
|
throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
|
|
4397
4401
|
}
|
|
@@ -4413,15 +4417,15 @@ function registerShare(program) {
|
|
|
4413
4417
|
applyGlobalOptions(cmd);
|
|
4414
4418
|
}
|
|
4415
4419
|
function writeVibgrateGitignore(root3, reports) {
|
|
4416
|
-
const dir =
|
|
4420
|
+
const dir = path51.join(root3, ".vibgrate");
|
|
4417
4421
|
fs12.mkdirSync(dir, { recursive: true });
|
|
4418
4422
|
const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl", "graph.snap"];
|
|
4419
4423
|
if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
|
|
4420
|
-
fs12.writeFileSync(
|
|
4424
|
+
fs12.writeFileSync(path51.join(dir, ".gitignore"), `${lines.join("\n")}
|
|
4421
4425
|
`);
|
|
4422
4426
|
}
|
|
4423
4427
|
function writeGitAttributes(root3) {
|
|
4424
|
-
const file =
|
|
4428
|
+
const file = path51.join(root3, ".gitattributes");
|
|
4425
4429
|
const line = ".vibgrate/graph.json merge=vg";
|
|
4426
4430
|
let content = fs12.existsSync(file) ? fs12.readFileSync(file, "utf8") : "";
|
|
4427
4431
|
if (!content.split("\n").some((l) => l.trim() === line)) {
|
|
@@ -4439,7 +4443,7 @@ function configureMergeDriver(root3) {
|
|
|
4439
4443
|
function installPreCommitHook(root3) {
|
|
4440
4444
|
const hooksDir = hookDir(root3);
|
|
4441
4445
|
fs12.mkdirSync(hooksDir, { recursive: true });
|
|
4442
|
-
const file =
|
|
4446
|
+
const file = path51.join(hooksDir, "pre-commit");
|
|
4443
4447
|
const block = [
|
|
4444
4448
|
HOOK_BEGIN,
|
|
4445
4449
|
"if command -v vg >/dev/null 2>&1; then",
|
|
@@ -4461,14 +4465,14 @@ ${content}`;
|
|
|
4461
4465
|
fs12.chmodSync(file, 493);
|
|
4462
4466
|
}
|
|
4463
4467
|
function undo(root3) {
|
|
4464
|
-
const hookFile =
|
|
4468
|
+
const hookFile = path51.join(hookDir(root3), "pre-commit");
|
|
4465
4469
|
if (fs12.existsSync(hookFile)) {
|
|
4466
4470
|
const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
|
|
4467
4471
|
const next2 = fs12.readFileSync(hookFile, "utf8").replace(re, "\n");
|
|
4468
4472
|
if (next2.trim() === "#!/bin/sh") fs12.rmSync(hookFile);
|
|
4469
4473
|
else fs12.writeFileSync(hookFile, next2);
|
|
4470
4474
|
}
|
|
4471
|
-
const attr =
|
|
4475
|
+
const attr = path51.join(root3, ".gitattributes");
|
|
4472
4476
|
if (fs12.existsSync(attr)) {
|
|
4473
4477
|
const next2 = fs12.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
|
|
4474
4478
|
fs12.writeFileSync(attr, next2);
|
|
@@ -4481,10 +4485,10 @@ function undo(root3) {
|
|
|
4481
4485
|
function hookDir(root3) {
|
|
4482
4486
|
try {
|
|
4483
4487
|
const configured = git(root3, ["config", "--get", "core.hooksPath"]).trim();
|
|
4484
|
-
if (configured) return
|
|
4488
|
+
if (configured) return path51.resolve(root3, configured);
|
|
4485
4489
|
} catch {
|
|
4486
4490
|
}
|
|
4487
|
-
return
|
|
4491
|
+
return path51.join(root3, ".git", "hooks");
|
|
4488
4492
|
}
|
|
4489
4493
|
function git(root3, args) {
|
|
4490
4494
|
return execFileSync("git", args, { cwd: root3, encoding: "utf8" });
|
|
@@ -4498,7 +4502,7 @@ function registerWatch(program) {
|
|
|
4498
4502
|
const global = readGlobal(this);
|
|
4499
4503
|
const root3 = rootOf(global);
|
|
4500
4504
|
const debounceMs = Math.max(50, Number(opts.debounce) || DEFAULT_DEBOUNCE_MS);
|
|
4501
|
-
const watchRoots = (paths.length ? paths : ["."]).map((p) =>
|
|
4505
|
+
const watchRoots = (paths.length ? paths : ["."]).map((p) => path51.resolve(root3, p));
|
|
4502
4506
|
for (const w of watchRoots) {
|
|
4503
4507
|
if (!fs12.existsSync(w)) {
|
|
4504
4508
|
throw new CliError(`watch path not found: ${w}`, ExitCode.USAGE_ERROR);
|
|
@@ -4566,12 +4570,12 @@ function registerWatch(program) {
|
|
|
4566
4570
|
throw new CliError(`cannot watch ${w}: ${err.message}`, ExitCode.ERROR);
|
|
4567
4571
|
}
|
|
4568
4572
|
}
|
|
4569
|
-
info(c.dim(`vg watch \xB7 watching ${watchRoots.map((p) =>
|
|
4570
|
-
await new Promise((
|
|
4573
|
+
info(c.dim(`vg watch \xB7 watching ${watchRoots.map((p) => path51.relative(root3, p) || ".").join(", ")} (debounce ${debounceMs}ms) \xB7 Ctrl+C to stop`));
|
|
4574
|
+
await new Promise((resolve25) => {
|
|
4571
4575
|
const stop = () => {
|
|
4572
4576
|
for (const w of watchers) w.close();
|
|
4573
4577
|
if (timer) clearTimeout(timer);
|
|
4574
|
-
|
|
4578
|
+
resolve25();
|
|
4575
4579
|
};
|
|
4576
4580
|
process.once("SIGINT", stop);
|
|
4577
4581
|
process.once("SIGTERM", stop);
|
|
@@ -4899,7 +4903,7 @@ function normalizeStandards(raw) {
|
|
|
4899
4903
|
}
|
|
4900
4904
|
function loadStandards(root3) {
|
|
4901
4905
|
for (const rel of STANDARDS_FILES) {
|
|
4902
|
-
const p =
|
|
4906
|
+
const p = path51__default.join(root3, rel);
|
|
4903
4907
|
let text;
|
|
4904
4908
|
try {
|
|
4905
4909
|
text = fs12__default.readFileSync(p, "utf8");
|
|
@@ -4928,14 +4932,14 @@ function registerDrift(program) {
|
|
|
4928
4932
|
const cmd = program.command("drift").description("what is outdated across dependencies (offline; --online for currency)").option("--online", "query the npm registry for latest versions (opt-in network)").option("--fail-on <level>", "exit 2 on violations \u2014 major|minor (version drift) or standards (enterprise policy)").action(async function(opts) {
|
|
4929
4933
|
const global = readGlobal(this);
|
|
4930
4934
|
const root3 = rootOf(global);
|
|
4931
|
-
if (opts.online && global.
|
|
4935
|
+
if (opts.online && global.offline) {
|
|
4932
4936
|
throw new CliError("--online conflicts with --local (no network in local mode)", ExitCode.USAGE_ERROR);
|
|
4933
4937
|
}
|
|
4934
4938
|
if (opts.failOn && !["major", "minor", "standards"].includes(opts.failOn)) {
|
|
4935
4939
|
throw new CliError(`--fail-on must be one of: major, minor, standards (got "${opts.failOn}")`, ExitCode.USAGE_ERROR);
|
|
4936
4940
|
}
|
|
4937
4941
|
const inv = inventory(root3);
|
|
4938
|
-
if (opts.online && !global.
|
|
4942
|
+
if (opts.online && !global.offline) await enrichOnline(inv.records);
|
|
4939
4943
|
let standards = null;
|
|
4940
4944
|
if (opts.failOn === "standards") {
|
|
4941
4945
|
const loaded = loadStandards(root3);
|
|
@@ -5620,8 +5624,8 @@ async function runHostBench(options = {}) {
|
|
|
5620
5624
|
}
|
|
5621
5625
|
async function runArmSimulate(input) {
|
|
5622
5626
|
const { arm, lib, modelPath, turns, trie, env, live } = input;
|
|
5623
|
-
const
|
|
5624
|
-
const session = await acquireHostSession(lib,
|
|
5627
|
+
const path53 = `${modelPath}#${arm.id}`;
|
|
5628
|
+
const session = await acquireHostSession(lib, path53);
|
|
5625
5629
|
const system = "You are a coding agent with a Task Capsule.";
|
|
5626
5630
|
const capsule = "function readConfig(): Config { return load(); }";
|
|
5627
5631
|
let last = null;
|
|
@@ -6567,7 +6571,7 @@ function registerModels(program) {
|
|
|
6567
6571
|
);
|
|
6568
6572
|
});
|
|
6569
6573
|
applyGlobalOptions(modeCmd);
|
|
6570
|
-
const
|
|
6574
|
+
const resolve25 = cmd.command("resolve").description("resolve a Code Mode to pack + underlying model + fit (no download)").argument("[mode]", "spark | flow | forge (default: auto-fit or configured default)").action(async function(modeArg) {
|
|
6571
6575
|
const global = readGlobal(this);
|
|
6572
6576
|
const root3 = rootOf(global);
|
|
6573
6577
|
const sys = await gatherSystemMemory();
|
|
@@ -6599,7 +6603,7 @@ function registerModels(program) {
|
|
|
6599
6603
|
info(c.yellow(" will not fit \u2014 choose a lower mode or free memory before loading"));
|
|
6600
6604
|
}
|
|
6601
6605
|
});
|
|
6602
|
-
applyGlobalOptions(
|
|
6606
|
+
applyGlobalOptions(resolve25);
|
|
6603
6607
|
const installCmd = cmd.command("install").description("resolve the best Code Mode for this machine and install the pack").argument("[mode]", "spark | flow | forge (default: auto-fit)").option("--dry-run", "print the install plan only \u2014 do not download").option("--yes", "skip prompts (install already runs by default)").action(async function(modeArg, opts) {
|
|
6604
6608
|
const global = readGlobal(this);
|
|
6605
6609
|
const root3 = rootOf(global);
|
|
@@ -6636,7 +6640,7 @@ function registerModels(program) {
|
|
|
6636
6640
|
promise: resolved.pack.promise,
|
|
6637
6641
|
fit: resolved.fit.label,
|
|
6638
6642
|
installedNames: models.map((m) => m.name),
|
|
6639
|
-
offline: !!global.
|
|
6643
|
+
offline: !!global.offline,
|
|
6640
6644
|
hasBinary,
|
|
6641
6645
|
downloadUrl: resolved.underlying.downloadUrl,
|
|
6642
6646
|
sha256: resolved.underlying.sha256
|
|
@@ -6664,7 +6668,7 @@ function registerModels(program) {
|
|
|
6664
6668
|
if (installPlan.license) info(c.dim(` license: ${installPlan.license}`));
|
|
6665
6669
|
}
|
|
6666
6670
|
const result = await executeModelInstallPlan(installPlan, {
|
|
6667
|
-
offline: !!global.
|
|
6671
|
+
offline: !!global.offline,
|
|
6668
6672
|
quiet: !!global.json,
|
|
6669
6673
|
onProgress: (line) => {
|
|
6670
6674
|
if (!global.json) info(c.dim(` ${line}`));
|
|
@@ -6733,7 +6737,7 @@ function registerModels(program) {
|
|
|
6733
6737
|
backend: "ollama",
|
|
6734
6738
|
weightsRef: name,
|
|
6735
6739
|
installedNames: models.map((m) => m.name),
|
|
6736
|
-
offline: !!global.
|
|
6740
|
+
offline: !!global.offline,
|
|
6737
6741
|
hasBinary
|
|
6738
6742
|
});
|
|
6739
6743
|
if (installPlan.ready) {
|
|
@@ -6751,7 +6755,7 @@ function registerModels(program) {
|
|
|
6751
6755
|
}
|
|
6752
6756
|
if (!global.json) printInstallPlanHuman("vg models pull", installPlan);
|
|
6753
6757
|
const result = await executeModelInstallPlan(installPlan, {
|
|
6754
|
-
offline: !!global.
|
|
6758
|
+
offline: !!global.offline,
|
|
6755
6759
|
quiet: !!global.json,
|
|
6756
6760
|
onProgress: (line) => {
|
|
6757
6761
|
if (!global.json) info(c.dim(` ${line}`));
|
|
@@ -6988,8 +6992,8 @@ function registerModels(program) {
|
|
|
6988
6992
|
}
|
|
6989
6993
|
});
|
|
6990
6994
|
if (opts.out) {
|
|
6991
|
-
const outPath =
|
|
6992
|
-
fs12.mkdirSync(
|
|
6995
|
+
const outPath = path51.resolve(opts.out);
|
|
6996
|
+
fs12.mkdirSync(path51.dirname(outPath), { recursive: true });
|
|
6993
6997
|
fs12.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}
|
|
6994
6998
|
`);
|
|
6995
6999
|
if (!global.json) info(c.dim(` wrote ${outPath}`));
|
|
@@ -7011,9 +7015,9 @@ function registerModels(program) {
|
|
|
7011
7015
|
}
|
|
7012
7016
|
});
|
|
7013
7017
|
applyGlobalOptions(codingMetrics);
|
|
7014
|
-
const catalog = cmd.command("catalog").description("the live hosted model catalog (OpenRouter) \u2014 key-free, cached, refreshed from the network").option("--
|
|
7018
|
+
const catalog = cmd.command("catalog").description("the live hosted model catalog (OpenRouter) \u2014 key-free, cached, refreshed from the network").option("--refresh", "bypass the cache and refresh from the network").action(async function(opts) {
|
|
7015
7019
|
const global = readGlobal(this);
|
|
7016
|
-
const cat = await fetchCatalog({ offline:
|
|
7020
|
+
const cat = await fetchCatalog({ offline: global.offline, noCache: opts.refresh });
|
|
7017
7021
|
if (global.json) {
|
|
7018
7022
|
json(cat);
|
|
7019
7023
|
return;
|
|
@@ -7104,11 +7108,11 @@ function fitColor(label) {
|
|
|
7104
7108
|
return label;
|
|
7105
7109
|
}
|
|
7106
7110
|
function confirmYesNo(question) {
|
|
7107
|
-
return new Promise((
|
|
7111
|
+
return new Promise((resolve25) => {
|
|
7108
7112
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
7109
7113
|
rl.question(`${question} [y/N] `, (answer) => {
|
|
7110
7114
|
rl.close();
|
|
7111
|
-
|
|
7115
|
+
resolve25(/^y(es)?$/i.test(answer.trim()));
|
|
7112
7116
|
});
|
|
7113
7117
|
});
|
|
7114
7118
|
}
|
|
@@ -7139,14 +7143,14 @@ function resolveUninstallTarget(name, runtimeOpt) {
|
|
|
7139
7143
|
}
|
|
7140
7144
|
return exact[0];
|
|
7141
7145
|
}
|
|
7142
|
-
const base =
|
|
7146
|
+
const base = path51.basename(wantLc);
|
|
7143
7147
|
const soft = fleet.filter((m) => {
|
|
7144
7148
|
const n = m.name.toLowerCase();
|
|
7145
|
-
return n === base || n.endsWith("/" + base) || n.endsWith(wantLc) ||
|
|
7149
|
+
return n === base || n.endsWith("/" + base) || n.endsWith(wantLc) || path51.basename(n) === base;
|
|
7146
7150
|
});
|
|
7147
7151
|
if (soft.length === 1) return soft[0];
|
|
7148
7152
|
if (soft.length > 1) {
|
|
7149
|
-
const baseHits = soft.filter((m) =>
|
|
7153
|
+
const baseHits = soft.filter((m) => path51.basename(m.name).toLowerCase() === base);
|
|
7150
7154
|
const pool = baseHits.length ? baseHits : soft;
|
|
7151
7155
|
const order = ["ollama", "gguf", "lm-studio"];
|
|
7152
7156
|
for (const rt of order) {
|
|
@@ -7184,7 +7188,7 @@ function printBackends(backends) {
|
|
|
7184
7188
|
}
|
|
7185
7189
|
}
|
|
7186
7190
|
function codeConfigPath(root3) {
|
|
7187
|
-
return
|
|
7191
|
+
return path51.join(root3, ".vibgrate", "code.json");
|
|
7188
7192
|
}
|
|
7189
7193
|
function loadCodeConfig(root3) {
|
|
7190
7194
|
try {
|
|
@@ -7266,7 +7270,7 @@ function discoverMcpServers(root3, ownServers) {
|
|
|
7266
7270
|
const servers = {};
|
|
7267
7271
|
const sources = [];
|
|
7268
7272
|
for (const rel of MCP_CONFIG_FILES) {
|
|
7269
|
-
const found = readMcpServersFile(
|
|
7273
|
+
const found = readMcpServersFile(path51.join(root3, rel));
|
|
7270
7274
|
const names = Object.keys(found);
|
|
7271
7275
|
if (names.length) {
|
|
7272
7276
|
sources.push(rel);
|
|
@@ -7436,7 +7440,7 @@ function registerCode(program) {
|
|
|
7436
7440
|
}
|
|
7437
7441
|
global.cwd = activeWorktree.path;
|
|
7438
7442
|
}
|
|
7439
|
-
if (!global.
|
|
7443
|
+
if (!global.offline) {
|
|
7440
7444
|
await ensureRelevanceModule();
|
|
7441
7445
|
}
|
|
7442
7446
|
const config = loadCodeConfig(rootOf(global));
|
|
@@ -7449,7 +7453,7 @@ function registerCode(program) {
|
|
|
7449
7453
|
try {
|
|
7450
7454
|
const { parseMode: parseMode2, resolveMode: resolveMode2, readOrchestratorState: readOrchestratorState2, activePin: activePin2 } = await import('./model-orchestrator-X3NE3Y2G.js');
|
|
7451
7455
|
const { gatherSystemMemory: gatherSystemMemory2 } = await import('./local-runtime-GA43KYS5.js');
|
|
7452
|
-
const { loadGraph: loadGraph2 } = await import('./load-
|
|
7456
|
+
const { loadGraph: loadGraph2 } = await import('./load-UAVA732M.js');
|
|
7453
7457
|
const state = readOrchestratorState2();
|
|
7454
7458
|
const mode = parseMode2(modeFlag) ?? state.defaultMode ?? void 0;
|
|
7455
7459
|
const pin = mode ? activePin2(mode, state) : null;
|
|
@@ -7535,7 +7539,7 @@ function registerCode(program) {
|
|
|
7535
7539
|
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
7536
7540
|
throw usageError('say what to change, e.g. `vg code "add a --timeout flag to the scan command"` (guided mode needs an interactive terminal)');
|
|
7537
7541
|
}
|
|
7538
|
-
const { runInteractive } = await import('./interactive-
|
|
7542
|
+
const { runInteractive } = await import('./interactive-VQM2CTE2.js');
|
|
7539
7543
|
await runInteractive(
|
|
7540
7544
|
rootOf(global),
|
|
7541
7545
|
global,
|
|
@@ -7565,10 +7569,10 @@ function registerCode(program) {
|
|
|
7565
7569
|
}
|
|
7566
7570
|
if (!instruction && !stdinFirstTurn) throw usageError('say what to change, e.g. `vg code "add a --timeout flag to the scan command"`');
|
|
7567
7571
|
{
|
|
7568
|
-
const { ensureCodeMap } = await import('./ensure-map-
|
|
7572
|
+
const { ensureCodeMap: ensureCodeMap2 } = await import('./ensure-map-VAIYCCEJ.js');
|
|
7569
7573
|
const mapRoot = rootOf(global);
|
|
7570
7574
|
try {
|
|
7571
|
-
await
|
|
7575
|
+
await ensureCodeMap2(mapRoot, global, {
|
|
7572
7576
|
onProgress: (p) => {
|
|
7573
7577
|
if (opts.streamJson) {
|
|
7574
7578
|
process.stdout.write(
|
|
@@ -7625,7 +7629,7 @@ function registerCode(program) {
|
|
|
7625
7629
|
const ensure = await ensureManagerRuntime({
|
|
7626
7630
|
providerId: route.providers[0].id,
|
|
7627
7631
|
model: route.providers[0].model,
|
|
7628
|
-
offline: !!global.
|
|
7632
|
+
offline: !!global.offline,
|
|
7629
7633
|
// Code Mode / explicit custom selection / --yes count as consent for
|
|
7630
7634
|
// npm runtime deps (and ollama pull when the binary is already present).
|
|
7631
7635
|
consent: !!(opts.yes || codeModeActive || customSelection || process.stdin.isTTY),
|
|
@@ -7650,20 +7654,20 @@ function registerCode(program) {
|
|
|
7650
7654
|
process.stdout.write(JSON.stringify(line) + "\n");
|
|
7651
7655
|
};
|
|
7652
7656
|
try {
|
|
7653
|
-
const { RunOutcomeRecorder, newOneShotRunId, turnRunId } = await import('./run-outcome-
|
|
7657
|
+
const { RunOutcomeRecorder, newOneShotRunId, turnRunId } = await import('./run-outcome-EATWIKLR.js');
|
|
7654
7658
|
const primarySlug = route.providers[0]?.model.includes("/") ? route.providers[0].model.split("/")[0] : route.providers[0]?.id;
|
|
7655
|
-
const { runCodeStreamJson, parseHostMessage } = await import('./stream-json-
|
|
7656
|
-
const { nodeCodeFs } = await import('./session-
|
|
7659
|
+
const { runCodeStreamJson, parseHostMessage } = await import('./stream-json-GCZAHORX.js');
|
|
7660
|
+
const { nodeCodeFs } = await import('./session-N2FTYIHQ.js');
|
|
7657
7661
|
const readline6 = await import('readline');
|
|
7658
7662
|
const child = await import('child_process');
|
|
7659
7663
|
const { loadOrDiscoverFederation } = await import('./federation-M4ZNXOPX.js');
|
|
7660
7664
|
const { resolveExecutionEnv } = await import('./execution-env-4MS5AYAN.js');
|
|
7661
7665
|
const { resolveModelExecutionProfile, mergeModelExecutionProfile } = await import('./model-execution-profile-ELWF3Z6H.js');
|
|
7662
|
-
const { startCodeRuntimeSession } = await import('./runtime-session-
|
|
7663
|
-
const { resolveGraphBackend } = await import('./graph-backend-
|
|
7666
|
+
const { startCodeRuntimeSession } = await import('./runtime-session-Y2BRBXUW.js');
|
|
7667
|
+
const { resolveGraphBackend } = await import('./graph-backend-V5A2H5ZB.js');
|
|
7664
7668
|
const { detectGitRef } = await import('./git-ref-LMT3EXYY.js');
|
|
7665
7669
|
const { repositoryIdFromRoot } = await import('./paths-RVK6FKRN.js');
|
|
7666
|
-
const { vgdRequest } = await import('./vgd-
|
|
7670
|
+
const { vgdRequest } = await import('./vgd-3A2TRNP5.js');
|
|
7667
7671
|
const federation = loadOrDiscoverFederation(root3);
|
|
7668
7672
|
const modelProfile = mergeModelExecutionProfile(
|
|
7669
7673
|
resolveModelExecutionProfile({
|
|
@@ -7721,7 +7725,7 @@ ${res.stderr}` : ""),
|
|
|
7721
7725
|
exitCode: res.status ?? 1
|
|
7722
7726
|
};
|
|
7723
7727
|
};
|
|
7724
|
-
const { createVgBuiltinMcpTools, mergeExternalToolsets } = await import('./vg-mcp-bridge-
|
|
7728
|
+
const { createVgBuiltinMcpTools, mergeExternalToolsets } = await import('./vg-mcp-bridge-VDZWFEO2.js');
|
|
7725
7729
|
const vgBuiltin = createVgBuiltinMcpTools({
|
|
7726
7730
|
getGraph: () => graph,
|
|
7727
7731
|
root: root3,
|
|
@@ -7766,8 +7770,8 @@ ${res.stderr}` : ""),
|
|
|
7766
7770
|
files,
|
|
7767
7771
|
indexFile: checkpointIndex
|
|
7768
7772
|
});
|
|
7769
|
-
const { loadApprovalRules, addApprovalRule, ruleForAction, ruleLabel } = await import('./approvals-
|
|
7770
|
-
const { loadProjectRules, nodeRulesReader } = await import('./rules-
|
|
7773
|
+
const { loadApprovalRules, addApprovalRule, ruleForAction, ruleLabel } = await import('./approvals-ISJFILVK.js');
|
|
7774
|
+
const { loadProjectRules, nodeRulesReader } = await import('./rules-TLRGB5L4.js');
|
|
7771
7775
|
const projectRules = loadProjectRules(nodeRulesReader(root3));
|
|
7772
7776
|
if (projectRules.sources.length && !global.quiet) {
|
|
7773
7777
|
emitStream({
|
|
@@ -7824,8 +7828,8 @@ ${res.stderr}` : ""),
|
|
|
7824
7828
|
};
|
|
7825
7829
|
try {
|
|
7826
7830
|
if (opts.session) {
|
|
7827
|
-
const { runCodeStreamJsonSession, STREAM_JSON_PROTOCOL_VERSION } = await import('./stream-json-
|
|
7828
|
-
const { runAgent: runAgent2 } = await import('./agent-
|
|
7831
|
+
const { runCodeStreamJsonSession, STREAM_JSON_PROTOCOL_VERSION } = await import('./stream-json-GCZAHORX.js');
|
|
7832
|
+
const { runAgent: runAgent2 } = await import('./agent-5GW7C7EB.js');
|
|
7829
7833
|
const {
|
|
7830
7834
|
condenseSession,
|
|
7831
7835
|
loadLatestSession,
|
|
@@ -7836,7 +7840,7 @@ ${res.stderr}` : ""),
|
|
|
7836
7840
|
sessionsDir,
|
|
7837
7841
|
summarizeSession
|
|
7838
7842
|
} = await import('./session-store-AQUORKWB.js');
|
|
7839
|
-
const { VERSION: VERSION2 } = await import('./version-
|
|
7843
|
+
const { VERSION: VERSION2 } = await import('./version-XNZ4GLMQ.js');
|
|
7840
7844
|
const resumedRecord = typeof opts.continue === "string" ? loadSession(mainRoot, opts.continue) : opts.continue ? loadLatestSession(mainRoot) : void 0;
|
|
7841
7845
|
if (typeof opts.continue === "string" && !resumedRecord) {
|
|
7842
7846
|
emitStream({
|
|
@@ -7868,8 +7872,8 @@ ${res.stderr}` : ""),
|
|
|
7868
7872
|
if (ended) return Promise.resolve(null);
|
|
7869
7873
|
const queued = pendingTurns.shift();
|
|
7870
7874
|
if (queued !== void 0) return Promise.resolve(queued);
|
|
7871
|
-
return new Promise((
|
|
7872
|
-
wakeTurn =
|
|
7875
|
+
return new Promise((resolve25) => {
|
|
7876
|
+
wakeTurn = resolve25;
|
|
7873
7877
|
});
|
|
7874
7878
|
};
|
|
7875
7879
|
let turnIndex = 0;
|
|
@@ -8048,8 +8052,8 @@ ${res.stderr}` : ""),
|
|
|
8048
8052
|
if (!autonomous && !tty) {
|
|
8049
8053
|
throw usageError("the agent approves each edit/command \u2014 run in a terminal to approve interactively, or pass --auto to run autonomously (or --single for a one-shot diff).");
|
|
8050
8054
|
}
|
|
8051
|
-
const { agentTask } = await import('./interactive-
|
|
8052
|
-
const { nodeCodeFs } = await import('./session-
|
|
8055
|
+
const { agentTask } = await import('./interactive-VQM2CTE2.js');
|
|
8056
|
+
const { nodeCodeFs } = await import('./session-N2FTYIHQ.js');
|
|
8053
8057
|
const { TtyPrompter } = await import('./ui-K7D3BCHD.js');
|
|
8054
8058
|
let priorSummary;
|
|
8055
8059
|
let priorInstruction;
|
|
@@ -8064,7 +8068,7 @@ ${res.stderr}` : ""),
|
|
|
8064
8068
|
}
|
|
8065
8069
|
}
|
|
8066
8070
|
const prompter = tty ? new TtyPrompter() : void 0;
|
|
8067
|
-
const { createVgBuiltinMcpTools, mergeExternalToolsets } = await import('./vg-mcp-bridge-
|
|
8071
|
+
const { createVgBuiltinMcpTools, mergeExternalToolsets } = await import('./vg-mcp-bridge-VDZWFEO2.js');
|
|
8068
8072
|
const vgBuiltin = createVgBuiltinMcpTools({
|
|
8069
8073
|
getGraph: () => graph,
|
|
8070
8074
|
root: root3,
|
|
@@ -8088,11 +8092,11 @@ ${res.stderr}` : ""),
|
|
|
8088
8092
|
const { loadFederation } = await import('./federation-M4ZNXOPX.js');
|
|
8089
8093
|
const { resolveExecutionEnv } = await import('./execution-env-4MS5AYAN.js');
|
|
8090
8094
|
const { resolveModelExecutionProfile, mergeModelExecutionProfile } = await import('./model-execution-profile-ELWF3Z6H.js');
|
|
8091
|
-
const { startCodeRuntimeSession } = await import('./runtime-session-
|
|
8092
|
-
const { resolveGraphBackend } = await import('./graph-backend-
|
|
8095
|
+
const { startCodeRuntimeSession } = await import('./runtime-session-Y2BRBXUW.js');
|
|
8096
|
+
const { resolveGraphBackend } = await import('./graph-backend-V5A2H5ZB.js');
|
|
8093
8097
|
const { detectGitRef } = await import('./git-ref-LMT3EXYY.js');
|
|
8094
8098
|
const { repositoryIdFromRoot } = await import('./paths-RVK6FKRN.js');
|
|
8095
|
-
const { vgdRequest } = await import('./vgd-
|
|
8099
|
+
const { vgdRequest } = await import('./vgd-3A2TRNP5.js');
|
|
8096
8100
|
const federation = loadFederation(root3);
|
|
8097
8101
|
const modelProfile = mergeModelExecutionProfile(
|
|
8098
8102
|
resolveModelExecutionProfile({
|
|
@@ -8238,11 +8242,11 @@ function resolveSecurityTierFlag(flag, fromConfig, auto) {
|
|
|
8238
8242
|
return auto ? "L1" : "L0";
|
|
8239
8243
|
}
|
|
8240
8244
|
function confirm2(question) {
|
|
8241
|
-
return new Promise((
|
|
8245
|
+
return new Promise((resolve25) => {
|
|
8242
8246
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
8243
8247
|
rl.question(`${question} [y/N] `, (answer) => {
|
|
8244
8248
|
rl.close();
|
|
8245
|
-
|
|
8249
|
+
resolve25(/^y(es)?$/i.test(answer.trim()));
|
|
8246
8250
|
});
|
|
8247
8251
|
});
|
|
8248
8252
|
}
|
|
@@ -8351,9 +8355,9 @@ function registerLib(program) {
|
|
|
8351
8355
|
const global = readGlobal(this);
|
|
8352
8356
|
const root3 = rootOf(global);
|
|
8353
8357
|
const [verb, ...rest] = args;
|
|
8354
|
-
const network = !global.
|
|
8358
|
+
const network = !global.offline;
|
|
8355
8359
|
if (!verb) return listCatalog(root3, global.json);
|
|
8356
|
-
const online = (Boolean(opts.online) || network) && !global.
|
|
8360
|
+
const online = (Boolean(opts.online) || network) && !global.offline;
|
|
8357
8361
|
if (verb === "add") return addCmd(root3, rest, { ...opts, online }, global.json);
|
|
8358
8362
|
if (verb === "publish") return publishCmd(root3, rest, opts, global, global.json);
|
|
8359
8363
|
if (verb === "resolve") return resolveCmd(root3, rest, global.json);
|
|
@@ -8400,7 +8404,7 @@ async function addCmd(root3, rest, opts, asJson) {
|
|
|
8400
8404
|
}
|
|
8401
8405
|
function readPackageVersion(root3) {
|
|
8402
8406
|
try {
|
|
8403
|
-
const pkg = JSON.parse(fs12__default.readFileSync(
|
|
8407
|
+
const pkg = JSON.parse(fs12__default.readFileSync(path51__default.join(root3, "package.json"), "utf8"));
|
|
8404
8408
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
8405
8409
|
} catch {
|
|
8406
8410
|
return void 0;
|
|
@@ -8415,7 +8419,7 @@ function readOptionalFile(p) {
|
|
|
8415
8419
|
}
|
|
8416
8420
|
}
|
|
8417
8421
|
async function publishCmd(root3, rest, opts, global, asJson) {
|
|
8418
|
-
if (global.
|
|
8422
|
+
if (global.offline) throw usageError("vg lib publish uploads to the hosted catalog \u2014 it needs network (remove --offline/--local)");
|
|
8419
8423
|
const name = (rest.join(" ") || opts.name || "").trim();
|
|
8420
8424
|
if (!name) throw usageError("usage: vg lib publish <name> [--version <v>] [--readme <path>] [--dts <path>] [--language <lang>]");
|
|
8421
8425
|
const dsn = resolveDsn();
|
|
@@ -8423,8 +8427,8 @@ async function publishCmd(root3, rest, opts, global, asJson) {
|
|
|
8423
8427
|
if (!parsed) throw new CliError("publishing a private library requires a DSN \u2014 run `vibgrate login` or set VIBGRATE_DSN", ExitCode.USAGE_ERROR);
|
|
8424
8428
|
const version = (opts.version || readPackageVersion(root3) || "").trim();
|
|
8425
8429
|
if (!version) throw usageError("a --version is required (or run from a package whose package.json has a version)");
|
|
8426
|
-
const readme = readOptionalFile(opts.readme ?
|
|
8427
|
-
const dts = opts.dts ? readOptionalFile(
|
|
8430
|
+
const readme = readOptionalFile(opts.readme ? path51__default.resolve(opts.readme) : path51__default.join(root3, "README.md"));
|
|
8431
|
+
const dts = opts.dts ? readOptionalFile(path51__default.resolve(opts.dts)) : void 0;
|
|
8428
8432
|
if (!readme && !dts) throw usageError("nothing to publish \u2014 no README.md found and no --dts given");
|
|
8429
8433
|
const result = await publishPrivateLibrary(
|
|
8430
8434
|
{ name, version, readme, dts, language: opts.language },
|
|
@@ -8547,7 +8551,7 @@ function registerExport(program) {
|
|
|
8547
8551
|
const content = exportGraph(format, {
|
|
8548
8552
|
graph,
|
|
8549
8553
|
deps: needsDeps ? inventory(root3).records : void 0,
|
|
8550
|
-
models: needsDeps && !global.
|
|
8554
|
+
models: needsDeps && !global.offline ? discoverModels() : void 0,
|
|
8551
8555
|
generatedAt: graph.generatedAt,
|
|
8552
8556
|
compact: opts.compact === true ? true : opts.compact === false ? false : void 0,
|
|
8553
8557
|
slim: opts.slim === true
|
|
@@ -8556,7 +8560,7 @@ function registerExport(program) {
|
|
|
8556
8560
|
out(content.trimEnd());
|
|
8557
8561
|
return;
|
|
8558
8562
|
}
|
|
8559
|
-
fs12.mkdirSync(
|
|
8563
|
+
fs12.mkdirSync(path51.dirname(path51.resolve(file)), { recursive: true });
|
|
8560
8564
|
fs12.writeFileSync(file, content);
|
|
8561
8565
|
info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
|
|
8562
8566
|
});
|
|
@@ -8567,49 +8571,49 @@ function detectFormat(file) {
|
|
|
8567
8571
|
if (lower.endsWith(".cdx.json")) return "cyclonedx";
|
|
8568
8572
|
if (lower.endsWith(".spdx.json")) return "spdx";
|
|
8569
8573
|
if (file === "-") return "json";
|
|
8570
|
-
return formatForExt(
|
|
8574
|
+
return formatForExt(path51.extname(file));
|
|
8571
8575
|
}
|
|
8572
8576
|
function registerBundle(program) {
|
|
8573
|
-
const cmd = program.command("bundle").description("build an air-gapped bundle (grammars + graph + library catalog)").option("
|
|
8577
|
+
const cmd = program.command("bundle").description("build an air-gapped bundle (grammars + graph + library catalog)").option("-o, --out <dir>", "output directory", "vg-bundle").action(function(opts) {
|
|
8574
8578
|
const global = readGlobal(this);
|
|
8575
8579
|
const root3 = rootOf(global);
|
|
8576
|
-
const outDir =
|
|
8580
|
+
const outDir = path51.resolve(root3, opts.out ?? "vg-bundle");
|
|
8577
8581
|
let grammarFiles;
|
|
8578
8582
|
try {
|
|
8579
8583
|
grammarFiles = resolvedGrammarFiles();
|
|
8580
8584
|
} catch {
|
|
8581
8585
|
throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
|
|
8582
8586
|
}
|
|
8583
|
-
fs12.mkdirSync(
|
|
8587
|
+
fs12.mkdirSync(path51.join(outDir, "grammars"), { recursive: true });
|
|
8584
8588
|
let grammarCount = 0;
|
|
8585
8589
|
for (const { fileName, absPath } of grammarFiles) {
|
|
8586
|
-
fs12.copyFileSync(absPath,
|
|
8590
|
+
fs12.copyFileSync(absPath, path51.join(outDir, "grammars", fileName));
|
|
8587
8591
|
grammarCount++;
|
|
8588
8592
|
}
|
|
8589
8593
|
for (const id of Object.keys(TOOLCHAIN_GRAMMARS).sort()) {
|
|
8590
8594
|
const absPath = resolveToolchainGrammar(id);
|
|
8591
8595
|
if (!absPath) continue;
|
|
8592
|
-
fs12.copyFileSync(absPath,
|
|
8596
|
+
fs12.copyFileSync(absPath, path51.join(outDir, "grammars", path51.basename(absPath)));
|
|
8593
8597
|
grammarCount++;
|
|
8594
8598
|
}
|
|
8595
8599
|
const included = [`grammars/ (${grammarCount} .wasm)`];
|
|
8596
8600
|
const graphSrc = resolveGraphPath(root3, global.graph);
|
|
8597
8601
|
if (fs12.existsSync(graphSrc)) {
|
|
8598
|
-
fs12.copyFileSync(graphSrc,
|
|
8602
|
+
fs12.copyFileSync(graphSrc, path51.join(outDir, "graph.json"));
|
|
8599
8603
|
included.push("graph.json");
|
|
8600
8604
|
} else {
|
|
8601
8605
|
const graph = loadGraphFileWithSnapshot(graphSrc);
|
|
8602
8606
|
if (graph) {
|
|
8603
|
-
fs12.writeFileSync(
|
|
8607
|
+
fs12.writeFileSync(path51.join(outDir, "graph.json"), serializeGraph(graph));
|
|
8604
8608
|
included.push("graph.json");
|
|
8605
8609
|
}
|
|
8606
8610
|
}
|
|
8607
8611
|
const cat = catalogPath(root3);
|
|
8608
8612
|
if (fs12.existsSync(cat)) {
|
|
8609
|
-
fs12.copyFileSync(cat,
|
|
8613
|
+
fs12.copyFileSync(cat, path51.join(outDir, "vibgrate.lib.json"));
|
|
8610
8614
|
included.push("vibgrate.lib.json");
|
|
8611
8615
|
if (fs12.existsSync(libDir(root3))) {
|
|
8612
|
-
fs12.cpSync(libDir(root3),
|
|
8616
|
+
fs12.cpSync(libDir(root3), path51.join(outDir, "lib"), { recursive: true });
|
|
8613
8617
|
included.push("lib/");
|
|
8614
8618
|
}
|
|
8615
8619
|
}
|
|
@@ -8621,15 +8625,15 @@ function registerBundle(program) {
|
|
|
8621
8625
|
included,
|
|
8622
8626
|
usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
|
|
8623
8627
|
};
|
|
8624
|
-
fs12.writeFileSync(
|
|
8628
|
+
fs12.writeFileSync(path51.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
|
|
8625
8629
|
`);
|
|
8626
8630
|
if (global.json) {
|
|
8627
|
-
json({ out:
|
|
8631
|
+
json({ out: path51.relative(root3, outDir), ...manifest });
|
|
8628
8632
|
return;
|
|
8629
8633
|
}
|
|
8630
|
-
info(`${c.cyan("vg bundle")} \xB7 wrote ${
|
|
8634
|
+
info(`${c.cyan("vg bundle")} \xB7 wrote ${path51.relative(root3, outDir)}/`);
|
|
8631
8635
|
for (const i of included) info(` ${c.green("\u2714")} ${i}`);
|
|
8632
|
-
info(c.dim(` offline: vg build --grammars "${
|
|
8636
|
+
info(c.dim(` offline: vg build --grammars "${path51.relative(root3, outDir)}/grammars"`));
|
|
8633
8637
|
});
|
|
8634
8638
|
applyGlobalOptions(cmd);
|
|
8635
8639
|
}
|
|
@@ -8645,9 +8649,9 @@ var REACH_TIMEOUT_MS = 3e3;
|
|
|
8645
8649
|
var CONFIG_BASENAMES = ["vibgrate.config.ts", "vibgrate.config.js", "vibgrate.config.json"];
|
|
8646
8650
|
async function runDoctor(global) {
|
|
8647
8651
|
const root3 = rootOf(global);
|
|
8648
|
-
const local = global.
|
|
8652
|
+
const local = global.offline === true;
|
|
8649
8653
|
const graphPath = resolveGraphPath(root3, global.graph);
|
|
8650
|
-
const configFile = CONFIG_BASENAMES.find((f) => fs12.existsSync(
|
|
8654
|
+
const configFile = CONFIG_BASENAMES.find((f) => fs12.existsSync(path51.join(root3, f))) ?? null;
|
|
8651
8655
|
const graph = loadGraph(root3, graphPath);
|
|
8652
8656
|
let staleFiles = null;
|
|
8653
8657
|
if (graph) {
|
|
@@ -8673,13 +8677,13 @@ async function runDoctor(global) {
|
|
|
8673
8677
|
root: root3,
|
|
8674
8678
|
configFile,
|
|
8675
8679
|
map: {
|
|
8676
|
-
path:
|
|
8680
|
+
path: displayGraphPath(root3, graphPath),
|
|
8677
8681
|
built: graph !== null,
|
|
8678
8682
|
generatedAt: graph?.generatedAt ?? null,
|
|
8679
8683
|
staleFiles
|
|
8680
8684
|
},
|
|
8681
8685
|
libCatalog: {
|
|
8682
|
-
path:
|
|
8686
|
+
path: path51.relative(root3, catalogPath(root3)),
|
|
8683
8687
|
present: fs12.existsSync(catalogPath(root3)),
|
|
8684
8688
|
libraries: libCount
|
|
8685
8689
|
},
|
|
@@ -8737,7 +8741,7 @@ async function runDoctor(global) {
|
|
|
8737
8741
|
);
|
|
8738
8742
|
info(
|
|
8739
8743
|
` host pool ${li.hostPool.size} warm session(s)` + (li.hostPool.sessions[0] ? c.dim(
|
|
8740
|
-
` \xB7 last ${
|
|
8744
|
+
` \xB7 last ${path51.basename(li.hostPool.sessions[0].modelPath)} kv=${li.hostPool.sessions[0].kvBlocks}`
|
|
8741
8745
|
) : c.dim(" \xB7 none (load via vg code / vgd host-load)"))
|
|
8742
8746
|
);
|
|
8743
8747
|
if (li.dynamicOnToken || li.dynamicCustomSampler) {
|
|
@@ -8907,12 +8911,12 @@ function registerLlmHost(program) {
|
|
|
8907
8911
|
let binding = null;
|
|
8908
8912
|
const ensured = await ensurePackage("node-llama-cpp@^3", {
|
|
8909
8913
|
consent: !!opts.yes,
|
|
8910
|
-
local: !!global.
|
|
8914
|
+
local: !!global.offline,
|
|
8911
8915
|
interactive: false
|
|
8912
8916
|
});
|
|
8913
8917
|
if (ensured.module) {
|
|
8914
8918
|
binding = ensured.module;
|
|
8915
|
-
} else if (opts.yes || !global.
|
|
8919
|
+
} else if (opts.yes || !global.offline) {
|
|
8916
8920
|
if (!global.json) {
|
|
8917
8921
|
info(
|
|
8918
8922
|
c.dim(
|
|
@@ -9044,7 +9048,7 @@ async function readStdin() {
|
|
|
9044
9048
|
return Buffer.concat(chunks).toString("utf-8");
|
|
9045
9049
|
}
|
|
9046
9050
|
function readFileOrThrow(file, label) {
|
|
9047
|
-
const p =
|
|
9051
|
+
const p = path51.resolve(file);
|
|
9048
9052
|
if (!fs12.existsSync(p)) throw usageError(`${label} file not found: ${p}`);
|
|
9049
9053
|
return fs12.readFileSync(p, "utf-8");
|
|
9050
9054
|
}
|
|
@@ -9056,8 +9060,8 @@ function writeOutput(outFile, content) {
|
|
|
9056
9060
|
process.stdout.write(content.endsWith("\n") ? content : content + "\n");
|
|
9057
9061
|
return;
|
|
9058
9062
|
}
|
|
9059
|
-
const outPath =
|
|
9060
|
-
const dir =
|
|
9063
|
+
const outPath = path51.resolve(outFile);
|
|
9064
|
+
const dir = path51.dirname(outPath);
|
|
9061
9065
|
if (!fs12.existsSync(dir)) throw usageError(`output directory does not exist: ${dir}`);
|
|
9062
9066
|
fs12.writeFileSync(outPath, content.endsWith("\n") ? content : content + "\n", "utf-8");
|
|
9063
9067
|
info(c.dim(`wrote ${outPath}`));
|
|
@@ -9092,7 +9096,7 @@ function registerHcs(program) {
|
|
|
9092
9096
|
}
|
|
9093
9097
|
const statePath = opts.previous ?? opts.out;
|
|
9094
9098
|
const useIncremental = canIncremental && statePath !== void 0;
|
|
9095
|
-
const root3 =
|
|
9099
|
+
const root3 = path51.resolve(dir);
|
|
9096
9100
|
if (!fs12.existsSync(root3) || !fs12.statSync(root3).isDirectory()) {
|
|
9097
9101
|
throw usageError(`not a directory: ${root3}`);
|
|
9098
9102
|
}
|
|
@@ -9117,7 +9121,7 @@ function registerHcs(program) {
|
|
|
9117
9121
|
}
|
|
9118
9122
|
for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
9119
9123
|
if (entry.name.startsWith(".")) continue;
|
|
9120
|
-
const absChild =
|
|
9124
|
+
const absChild = path51.join(abs, entry.name);
|
|
9121
9125
|
const relChild = rel ? `${rel}/${entry.name}` : entry.name;
|
|
9122
9126
|
if (entry.isDirectory()) {
|
|
9123
9127
|
if (!SKIP_DIRS.has(entry.name)) walk2(absChild, relChild);
|
|
@@ -9125,7 +9129,7 @@ function registerHcs(program) {
|
|
|
9125
9129
|
}
|
|
9126
9130
|
if (!entry.isFile()) continue;
|
|
9127
9131
|
if (!opts.includeTests && TEST_PATTERNS.some((p) => p.test(relChild))) continue;
|
|
9128
|
-
const lang = extToLang.get(
|
|
9132
|
+
const lang = extToLang.get(path51.extname(entry.name).toLowerCase());
|
|
9129
9133
|
if (!lang) continue;
|
|
9130
9134
|
try {
|
|
9131
9135
|
if (fs12.statSync(absChild).size > MAX_EXTRACT_FILE_BYTES) continue;
|
|
@@ -9159,7 +9163,7 @@ function registerHcs(program) {
|
|
|
9159
9163
|
writeOutput(opts.out, parts2.join("\n"));
|
|
9160
9164
|
return;
|
|
9161
9165
|
}
|
|
9162
|
-
const previousPath =
|
|
9166
|
+
const previousPath = path51.resolve(statePath);
|
|
9163
9167
|
const previousNdjson = !opts.full && fs12.existsSync(previousPath) ? fs12.readFileSync(previousPath, "utf-8") : "";
|
|
9164
9168
|
if (opts.full) info(c.dim("running a full re-extraction (--full)"));
|
|
9165
9169
|
else if (!previousNdjson) info(c.dim(`previous stream not found at ${previousPath} \u2014 running a full extraction`));
|
|
@@ -9263,12 +9267,2005 @@ function registerHcs(program) {
|
|
|
9263
9267
|
});
|
|
9264
9268
|
applyGlobalOptions(cmd);
|
|
9265
9269
|
}
|
|
9270
|
+
|
|
9271
|
+
// src/review/format.ts
|
|
9272
|
+
var DECISION_LABEL = {
|
|
9273
|
+
pass: "pass",
|
|
9274
|
+
needs_review: "needs review",
|
|
9275
|
+
fail: "fail",
|
|
9276
|
+
undetermined: "undetermined"
|
|
9277
|
+
};
|
|
9278
|
+
function colorDecision(decision) {
|
|
9279
|
+
const label = DECISION_LABEL[decision];
|
|
9280
|
+
if (decision === "pass") return c.green(label);
|
|
9281
|
+
if (decision === "fail") return c.red(label);
|
|
9282
|
+
return c.yellow(label);
|
|
9283
|
+
}
|
|
9284
|
+
function formatText(result) {
|
|
9285
|
+
const { receipt, reasons, budget } = result;
|
|
9286
|
+
const lines = [];
|
|
9287
|
+
const all = [...receipt.findings.architecture_findings, ...receipt.findings.security_findings];
|
|
9288
|
+
lines.push(`${c.cyan("vg review")} \xB7 ${colorDecision(receipt.decision)} \xB7 ${receipt.enforcement}`);
|
|
9289
|
+
lines.push(
|
|
9290
|
+
c.dim(
|
|
9291
|
+
` ${receipt.git.dirty ? "working tree" : receipt.git.head_sha.slice(0, 8)} vs ${receipt.git.base_sha.slice(0, 8)}${receipt.git.merge_base ? " (merge-base)" : ""} \xB7 ${receipt.change_class.join(", ")}`
|
|
9292
|
+
)
|
|
9293
|
+
);
|
|
9294
|
+
lines.push("");
|
|
9295
|
+
if (all.length === 0) {
|
|
9296
|
+
lines.push(c.dim(" no architecture or security-control findings"));
|
|
9297
|
+
}
|
|
9298
|
+
for (const f of all) {
|
|
9299
|
+
const badge = f.protected_finding ? c.red("protected") : severityColor(f.severity);
|
|
9300
|
+
lines.push(` ${c.bold(f.id)} ${badge} ${c.dim(f.kind)}`);
|
|
9301
|
+
lines.push(` ${f.claim}`);
|
|
9302
|
+
lines.push(c.dim(` \u2192 ${f.remediation}`));
|
|
9303
|
+
lines.push(
|
|
9304
|
+
c.dim(` alignment ${f.target_alignment} \xB7 confidence ${f.confidence.toFixed(2)} \xB7 ${f.paths.slice(0, 3).join(", ")}`)
|
|
9305
|
+
);
|
|
9306
|
+
lines.push("");
|
|
9307
|
+
}
|
|
9308
|
+
if (receipt.findings.unknowns.length > 0) {
|
|
9309
|
+
lines.push(c.yellow(" unknowns"));
|
|
9310
|
+
for (const u of receipt.findings.unknowns) lines.push(c.dim(` \xB7 ${u}`));
|
|
9311
|
+
lines.push("");
|
|
9312
|
+
}
|
|
9313
|
+
if (receipt.findings.required_checks.length > 0) {
|
|
9314
|
+
lines.push(c.dim(` required checks: ${receipt.findings.required_checks.join(", ")}`));
|
|
9315
|
+
}
|
|
9316
|
+
lines.push(c.dim(` policy: ${reasons.join("; ")}`));
|
|
9317
|
+
lines.push(
|
|
9318
|
+
c.dim(
|
|
9319
|
+
` capsule ~${budget.estimatedTokens} tokens (median target ${budget.median}, cap ${budget.cap}${budget.trimmed ? ", trimmed" : ""})`
|
|
9320
|
+
)
|
|
9321
|
+
);
|
|
9322
|
+
lines.push(
|
|
9323
|
+
c.dim(
|
|
9324
|
+
` ${receipt.versions.policy} \xB7 model ${receipt.versions.model} \xB7 graph ${receipt.versions.graph_schema} \xB7 ${receipt.digests.receipt.slice(0, 19)}\u2026`
|
|
9325
|
+
)
|
|
9326
|
+
);
|
|
9327
|
+
lines.push(c.dim(" explain one: `vg review explain <finding-id>` \xB7 machine result: `vg review --format json`"));
|
|
9328
|
+
lines.push(c.dim(" Review reports change integrity. It is not a proof of security and absence of findings is not a certification."));
|
|
9329
|
+
return lines.join("\n");
|
|
9330
|
+
}
|
|
9331
|
+
function severityColor(severity) {
|
|
9332
|
+
if (severity === "critical" || severity === "high") return c.red(severity);
|
|
9333
|
+
if (severity === "medium") return c.yellow(severity);
|
|
9334
|
+
return c.dim(severity);
|
|
9335
|
+
}
|
|
9336
|
+
function formatMarkdown(result) {
|
|
9337
|
+
const { receipt, reasons } = result;
|
|
9338
|
+
const all = [...receipt.findings.architecture_findings, ...receipt.findings.security_findings];
|
|
9339
|
+
const lines = [];
|
|
9340
|
+
lines.push(`## Vibgrate Review \u2014 \`${DECISION_LABEL[receipt.decision]}\``);
|
|
9341
|
+
lines.push("");
|
|
9342
|
+
lines.push(
|
|
9343
|
+
`\`${receipt.git.base_sha.slice(0, 8)}\` \u2192 \`${receipt.git.head_sha.slice(0, 8)}\`${receipt.git.dirty ? " (working tree)" : ""} \xB7 enforcement \`${receipt.enforcement}\``
|
|
9344
|
+
);
|
|
9345
|
+
lines.push("");
|
|
9346
|
+
lines.push(
|
|
9347
|
+
`| architecture | security | protected | unknowns |
|
|
9348
|
+
|---:|---:|---:|---:|
|
|
9349
|
+
| ${receipt.counts.architecture} | ${receipt.counts.security} | ${receipt.counts.protected} | ${receipt.counts.unknowns} |`
|
|
9350
|
+
);
|
|
9351
|
+
lines.push("");
|
|
9352
|
+
if (all.length === 0) {
|
|
9353
|
+
lines.push("No architecture or security-control findings.");
|
|
9354
|
+
} else {
|
|
9355
|
+
lines.push("| id | kind | severity | alignment | claim |");
|
|
9356
|
+
lines.push("|---|---|---|---|---|");
|
|
9357
|
+
for (const f of all) {
|
|
9358
|
+
const kind = f.protected_finding ? `${f.kind} **(protected)**` : f.kind;
|
|
9359
|
+
lines.push(`| \`${f.id}\` | ${kind} | ${f.severity} | ${f.target_alignment} | ${escapeCell(f.claim)} |`);
|
|
9360
|
+
}
|
|
9361
|
+
}
|
|
9362
|
+
if (receipt.findings.unknowns.length > 0) {
|
|
9363
|
+
lines.push("");
|
|
9364
|
+
lines.push("**Unknowns**");
|
|
9365
|
+
lines.push("");
|
|
9366
|
+
for (const u of receipt.findings.unknowns) lines.push(`- ${u}`);
|
|
9367
|
+
}
|
|
9368
|
+
lines.push("");
|
|
9369
|
+
lines.push(`<sub>${escapeCell(reasons.join("; "))}</sub>`);
|
|
9370
|
+
lines.push("");
|
|
9371
|
+
lines.push(
|
|
9372
|
+
`<sub>policy \`${receipt.versions.policy}\` \xB7 model \`${receipt.versions.model}\` \xB7 receipt \`${receipt.digests.receipt.slice(0, 23)}\u2026\`</sub>`
|
|
9373
|
+
);
|
|
9374
|
+
lines.push("");
|
|
9375
|
+
lines.push(
|
|
9376
|
+
"<sub>Vibgrate Review reports change integrity against the declared architecture and security controls. It does not prove code is secure, and absence of findings is not a certification.</sub>"
|
|
9377
|
+
);
|
|
9378
|
+
return lines.join("\n");
|
|
9379
|
+
}
|
|
9380
|
+
function escapeCell(s) {
|
|
9381
|
+
return s.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
9382
|
+
}
|
|
9383
|
+
function formatSarif(receipt) {
|
|
9384
|
+
const findings = receipt.findings.security_findings;
|
|
9385
|
+
const rules = [...new Map(findings.map((f) => [f.kind, f])).values()].map((f) => ({
|
|
9386
|
+
id: f.kind,
|
|
9387
|
+
name: f.kind,
|
|
9388
|
+
shortDescription: { text: f.kind.replace(/_/g, " ") },
|
|
9389
|
+
fullDescription: { text: f.remediation },
|
|
9390
|
+
defaultConfiguration: { level: sarifLevel(f) },
|
|
9391
|
+
properties: { tags: ["security", "vibgrate-review"] }
|
|
9392
|
+
}));
|
|
9393
|
+
return JSON.stringify(
|
|
9394
|
+
{
|
|
9395
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
|
|
9396
|
+
version: "2.1.0",
|
|
9397
|
+
runs: [
|
|
9398
|
+
{
|
|
9399
|
+
tool: {
|
|
9400
|
+
driver: {
|
|
9401
|
+
name: "Vibgrate Review",
|
|
9402
|
+
version: receipt.versions.cli,
|
|
9403
|
+
informationUri: "https://vibgrate.com/review",
|
|
9404
|
+
rules
|
|
9405
|
+
}
|
|
9406
|
+
},
|
|
9407
|
+
automationDetails: { id: `vibgrate-review/${receipt.git.head_sha}` },
|
|
9408
|
+
results: findings.map((f) => ({
|
|
9409
|
+
ruleId: f.kind,
|
|
9410
|
+
level: sarifLevel(f),
|
|
9411
|
+
message: { text: f.claim },
|
|
9412
|
+
locations: f.paths.map((p) => ({
|
|
9413
|
+
physicalLocation: { artifactLocation: { uri: p } }
|
|
9414
|
+
})),
|
|
9415
|
+
properties: {
|
|
9416
|
+
findingId: f.id,
|
|
9417
|
+
confidence: f.confidence,
|
|
9418
|
+
targetAlignment: f.target_alignment,
|
|
9419
|
+
protectedFinding: f.protected_finding === true,
|
|
9420
|
+
remediation: f.remediation
|
|
9421
|
+
}
|
|
9422
|
+
}))
|
|
9423
|
+
}
|
|
9424
|
+
]
|
|
9425
|
+
},
|
|
9426
|
+
null,
|
|
9427
|
+
2
|
|
9428
|
+
);
|
|
9429
|
+
}
|
|
9430
|
+
function sarifLevel(f) {
|
|
9431
|
+
if (f.protected_finding || f.severity === "critical" || f.severity === "high") return "error";
|
|
9432
|
+
if (f.severity === "medium") return "warning";
|
|
9433
|
+
return "note";
|
|
9434
|
+
}
|
|
9435
|
+
function formatExplain(result, findingId) {
|
|
9436
|
+
const all = [...result.receipt.findings.architecture_findings, ...result.receipt.findings.security_findings];
|
|
9437
|
+
const finding = all.find((f) => f.id === findingId);
|
|
9438
|
+
if (!finding) return null;
|
|
9439
|
+
const lines = [];
|
|
9440
|
+
lines.push(`${c.bold(finding.id)} ${severityColor(finding.severity)} ${c.dim(finding.kind)}`);
|
|
9441
|
+
lines.push("");
|
|
9442
|
+
lines.push(` ${finding.claim}`);
|
|
9443
|
+
lines.push("");
|
|
9444
|
+
lines.push(c.dim(` remediation: ${finding.remediation}`));
|
|
9445
|
+
lines.push(c.dim(` alignment: ${finding.target_alignment}`));
|
|
9446
|
+
lines.push(c.dim(` confidence: ${finding.confidence.toFixed(2)}`));
|
|
9447
|
+
lines.push(c.dim(` protected: ${finding.protected_finding === true ? "yes \u2014 policy cannot pass while it is unresolved" : "no"}`));
|
|
9448
|
+
lines.push(c.dim(` produced by: ${finding.source ?? "scanner"}`));
|
|
9449
|
+
lines.push("");
|
|
9450
|
+
lines.push(c.dim(" evidence"));
|
|
9451
|
+
for (const id of finding.evidence_ids) {
|
|
9452
|
+
const e = result.capsule.evidence.find((x) => x.id === id);
|
|
9453
|
+
if (!e) {
|
|
9454
|
+
lines.push(c.red(` ${id} \u2014 not present in the capsule (this finding failed verification)`));
|
|
9455
|
+
continue;
|
|
9456
|
+
}
|
|
9457
|
+
const span = e.start_line ? `:${e.start_line}${e.end_line && e.end_line !== e.start_line ? `-${e.end_line}` : ""}` : "";
|
|
9458
|
+
lines.push(` ${c.bold(e.id)} ${c.dim(e.kind)} ${e.path ?? ""}${span}`);
|
|
9459
|
+
if (e.note) lines.push(c.dim(` ${e.note}`));
|
|
9460
|
+
}
|
|
9461
|
+
return lines.join("\n");
|
|
9462
|
+
}
|
|
9463
|
+
|
|
9464
|
+
// src/review/policy.ts
|
|
9465
|
+
var ALIGNED = /* @__PURE__ */ new Set(["target", "approved_exception"]);
|
|
9466
|
+
function isProtected(f) {
|
|
9467
|
+
return f.protected_finding === true;
|
|
9468
|
+
}
|
|
9469
|
+
function unresolvedProtected(findings, config) {
|
|
9470
|
+
const enabled = (kind) => {
|
|
9471
|
+
if (kind === "unguarded_entrypoint" || kind === "guard_removed") return config.protected.unguarded_entrypoint;
|
|
9472
|
+
if (kind === "known_vulnerable_dependency") return config.protected.known_vulnerable_dependency;
|
|
9473
|
+
if (kind === "validated_taint") return config.protected.validated_taint;
|
|
9474
|
+
return true;
|
|
9475
|
+
};
|
|
9476
|
+
return [...findings.architecture_findings, ...findings.security_findings].filter(isProtected).filter((f) => enabled(f.kind));
|
|
9477
|
+
}
|
|
9478
|
+
function applyReviewPolicy(capsule, findings, config, verification) {
|
|
9479
|
+
const reasons = [];
|
|
9480
|
+
const all = [...findings.architecture_findings, ...findings.security_findings];
|
|
9481
|
+
const protectedFindings = unresolvedProtected(findings, config);
|
|
9482
|
+
const quickPath = findings.change_class.length === 1 && findings.change_class[0] === "none";
|
|
9483
|
+
const decide = () => {
|
|
9484
|
+
if (!verification.schema_valid || !verification.evidence_ids_valid) {
|
|
9485
|
+
reasons.push("the findings document failed schema or evidence verification \u2014 nothing here can be trusted to decide on");
|
|
9486
|
+
return "undetermined";
|
|
9487
|
+
}
|
|
9488
|
+
if (protectedFindings.length > 0) {
|
|
9489
|
+
reasons.push(
|
|
9490
|
+
`${protectedFindings.length} protected finding(s) are unresolved: ${protectedFindings.map((f) => f.kind).join(", ")}`
|
|
9491
|
+
);
|
|
9492
|
+
return "fail";
|
|
9493
|
+
}
|
|
9494
|
+
if (quickPath) {
|
|
9495
|
+
reasons.push("no material architectural or security-control delta in this change set");
|
|
9496
|
+
return "pass";
|
|
9497
|
+
}
|
|
9498
|
+
const high = all.filter(
|
|
9499
|
+
(f) => (f.severity === "high" || f.severity === "critical") && !ALIGNED.has(f.target_alignment) && f.confidence >= config.high_confidence_threshold
|
|
9500
|
+
);
|
|
9501
|
+
if (high.length > 0) {
|
|
9502
|
+
reasons.push(
|
|
9503
|
+
`${high.length} high-severity finding(s) at or above the ${config.high_confidence_threshold} confidence threshold`
|
|
9504
|
+
);
|
|
9505
|
+
return config.high_severity_decision;
|
|
9506
|
+
}
|
|
9507
|
+
const blockingUnknowns = findings.unknowns.length > 0 || findings.required_checks.length > 0;
|
|
9508
|
+
const material = all.filter((f) => !ALIGNED.has(f.target_alignment));
|
|
9509
|
+
if (material.length > 0) {
|
|
9510
|
+
reasons.push(`${material.length} finding(s) need a human look`);
|
|
9511
|
+
return "needs_review";
|
|
9512
|
+
}
|
|
9513
|
+
if (blockingUnknowns) {
|
|
9514
|
+
reasons.push(
|
|
9515
|
+
`no findings, but ${findings.unknowns.length} unknown(s) and ${findings.required_checks.length} required check(s) remain`
|
|
9516
|
+
);
|
|
9517
|
+
return capsule.patterns.unknown ? "undetermined" : "needs_review";
|
|
9518
|
+
}
|
|
9519
|
+
if (all.length > 0) {
|
|
9520
|
+
reasons.push("every finding is target-aligned or an approved exception");
|
|
9521
|
+
} else {
|
|
9522
|
+
reasons.push("no architecture or security-control findings in this change set");
|
|
9523
|
+
}
|
|
9524
|
+
return "pass";
|
|
9525
|
+
};
|
|
9526
|
+
let decision = decide();
|
|
9527
|
+
let protectedFalseBless = false;
|
|
9528
|
+
if (decision === "pass" && protectedFindings.length > 0) {
|
|
9529
|
+
protectedFalseBless = true;
|
|
9530
|
+
decision = "fail";
|
|
9531
|
+
reasons.push("policy attempted `pass` with an unresolved protected finding \u2014 downgraded to `fail`");
|
|
9532
|
+
}
|
|
9533
|
+
return {
|
|
9534
|
+
decision,
|
|
9535
|
+
reasons,
|
|
9536
|
+
protectedFalseBless,
|
|
9537
|
+
quickPath: quickPath && decision === "pass",
|
|
9538
|
+
protectedCount: protectedFindings.length
|
|
9539
|
+
};
|
|
9540
|
+
}
|
|
9541
|
+
var FAIL_ON_LEVELS = ["none", "fail", "needs_review"];
|
|
9542
|
+
function exitCodeForDecision(decision, failOn) {
|
|
9543
|
+
if (failOn === "none") return 0;
|
|
9544
|
+
if (decision === "fail") return ExitCode.GATE_FAILED;
|
|
9545
|
+
if (failOn === "needs_review" && (decision === "needs_review" || decision === "undetermined")) {
|
|
9546
|
+
return ExitCode.GATE_FAILED;
|
|
9547
|
+
}
|
|
9548
|
+
return 0;
|
|
9549
|
+
}
|
|
9550
|
+
function resolveFailOn(flag, config) {
|
|
9551
|
+
if (flag) return flag;
|
|
9552
|
+
return config.enforcement === "enforced" ? config.fail_on : "none";
|
|
9553
|
+
}
|
|
9554
|
+
|
|
9555
|
+
// src/review/delta.ts
|
|
9556
|
+
var SPEC_PATTERNS = [
|
|
9557
|
+
/(?:import|export)\s+(?:type\s+)?(?:[^'"\n;]+?\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
9558
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
9559
|
+
/from\s+['"]([^'"]+)['"]/g,
|
|
9560
|
+
/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
9561
|
+
/(?:^|\n)\s*import\s+"([^"]+)"/g,
|
|
9562
|
+
/(?:^|\n)\s*using\s+([A-Za-z0-9_.]+)\s*;/g
|
|
9563
|
+
];
|
|
9564
|
+
var TRY_SUFFIXES = [
|
|
9565
|
+
"",
|
|
9566
|
+
".ts",
|
|
9567
|
+
".tsx",
|
|
9568
|
+
".js",
|
|
9569
|
+
".jsx",
|
|
9570
|
+
".mjs",
|
|
9571
|
+
".cjs",
|
|
9572
|
+
".mts",
|
|
9573
|
+
".cts",
|
|
9574
|
+
".cs",
|
|
9575
|
+
".py",
|
|
9576
|
+
".go",
|
|
9577
|
+
".java",
|
|
9578
|
+
".kt",
|
|
9579
|
+
"/index.ts",
|
|
9580
|
+
"/index.tsx",
|
|
9581
|
+
"/index.js"
|
|
9582
|
+
];
|
|
9583
|
+
function normalize2(p) {
|
|
9584
|
+
return p.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
9585
|
+
}
|
|
9586
|
+
function dirname9(p) {
|
|
9587
|
+
const n = normalize2(p);
|
|
9588
|
+
const i = n.lastIndexOf("/");
|
|
9589
|
+
return i <= 0 ? "" : n.slice(0, i);
|
|
9590
|
+
}
|
|
9591
|
+
function resolveRelative(fromFile, spec) {
|
|
9592
|
+
const dir = dirname9(fromFile);
|
|
9593
|
+
const parts = `${dir ? `${dir}/` : ""}${spec}`.split("/");
|
|
9594
|
+
const out2 = [];
|
|
9595
|
+
for (const part of parts) {
|
|
9596
|
+
if (!part || part === ".") continue;
|
|
9597
|
+
if (part === "..") {
|
|
9598
|
+
if (out2.length > 0) out2.pop();
|
|
9599
|
+
continue;
|
|
9600
|
+
}
|
|
9601
|
+
out2.push(part);
|
|
9602
|
+
}
|
|
9603
|
+
return out2.join("/");
|
|
9604
|
+
}
|
|
9605
|
+
function stripExt(p) {
|
|
9606
|
+
return p.replace(/\.[^.]+$/, "");
|
|
9607
|
+
}
|
|
9608
|
+
function basenameNoExt(p) {
|
|
9609
|
+
const base = normalize2(p).split("/").pop() ?? p;
|
|
9610
|
+
return stripExt(base).toLowerCase();
|
|
9611
|
+
}
|
|
9612
|
+
function dependencyHints(text, fromFile) {
|
|
9613
|
+
const hints = /* @__PURE__ */ new Set();
|
|
9614
|
+
for (const pattern of SPEC_PATTERNS) {
|
|
9615
|
+
pattern.lastIndex = 0;
|
|
9616
|
+
let m;
|
|
9617
|
+
while ((m = pattern.exec(text)) !== null) {
|
|
9618
|
+
const spec = m[1]?.trim();
|
|
9619
|
+
if (!spec) continue;
|
|
9620
|
+
const cleaned = spec.replace(/\\/g, "/").replace(/\?.*$/, "").replace(/#.*$/, "");
|
|
9621
|
+
hints.add(cleaned.toLowerCase());
|
|
9622
|
+
hints.add(basenameNoExt(cleaned));
|
|
9623
|
+
if (cleaned.startsWith(".")) {
|
|
9624
|
+
const resolved = resolveRelative(fromFile, cleaned);
|
|
9625
|
+
hints.add(resolved.toLowerCase());
|
|
9626
|
+
hints.add(stripExt(resolved).toLowerCase());
|
|
9627
|
+
}
|
|
9628
|
+
}
|
|
9629
|
+
}
|
|
9630
|
+
return hints;
|
|
9631
|
+
}
|
|
9632
|
+
function destKeys(toPath) {
|
|
9633
|
+
const n = normalize2(toPath);
|
|
9634
|
+
const noExt = stripExt(n);
|
|
9635
|
+
return [n, noExt, n.toLowerCase(), noExt.toLowerCase(), basenameNoExt(n)];
|
|
9636
|
+
}
|
|
9637
|
+
function hintsCoverDest(hints, toPath) {
|
|
9638
|
+
for (const key2 of destKeys(toPath)) {
|
|
9639
|
+
if (hints.has(key2)) return true;
|
|
9640
|
+
}
|
|
9641
|
+
const n = normalize2(toPath).toLowerCase();
|
|
9642
|
+
for (const hint of hints) {
|
|
9643
|
+
for (const suffix of TRY_SUFFIXES) {
|
|
9644
|
+
if (hint + suffix === n || hint === n + suffix) return true;
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
return false;
|
|
9648
|
+
}
|
|
9649
|
+
function fileReferencesDest(text, fromFile, toPath) {
|
|
9650
|
+
return hintsCoverDest(dependencyHints(text, fromFile), toPath);
|
|
9651
|
+
}
|
|
9652
|
+
function isIntroducedEdge(op, fromFile, toPath, baseText) {
|
|
9653
|
+
if (op === "added") return true;
|
|
9654
|
+
if (op === "removed") return false;
|
|
9655
|
+
if (baseText === void 0) return false;
|
|
9656
|
+
return !fileReferencesDest(baseText, fromFile, toPath);
|
|
9657
|
+
}
|
|
9658
|
+
function pathShapedCover(hints, toPath) {
|
|
9659
|
+
const n = normalize2(toPath).toLowerCase();
|
|
9660
|
+
const noExt = stripExt(n);
|
|
9661
|
+
if (hints.has(n) || hints.has(noExt)) return true;
|
|
9662
|
+
for (const hint of hints) {
|
|
9663
|
+
if (!hint.includes("/")) continue;
|
|
9664
|
+
for (const suffix of TRY_SUFFIXES) {
|
|
9665
|
+
if (hint + suffix === n || hint === n + suffix) return true;
|
|
9666
|
+
}
|
|
9667
|
+
}
|
|
9668
|
+
return false;
|
|
9669
|
+
}
|
|
9670
|
+
function removedDestinations(fromFile, baseText, headText, knownFiles) {
|
|
9671
|
+
const oldHints = dependencyHints(baseText, fromFile);
|
|
9672
|
+
const newHints = dependencyHints(headText, fromFile);
|
|
9673
|
+
const out2 = [];
|
|
9674
|
+
for (const dest of knownFiles) {
|
|
9675
|
+
const n = normalize2(dest);
|
|
9676
|
+
if (!pathShapedCover(oldHints, n)) continue;
|
|
9677
|
+
if (pathShapedCover(newHints, n)) continue;
|
|
9678
|
+
out2.push(n);
|
|
9679
|
+
}
|
|
9680
|
+
return out2.sort();
|
|
9681
|
+
}
|
|
9682
|
+
|
|
9683
|
+
// src/review/capsule.ts
|
|
9684
|
+
var BOUNDARY_EDGE_KINDS = /* @__PURE__ */ new Set(["import", "call", "references"]);
|
|
9685
|
+
var EXEMPT_LAYERS = /* @__PURE__ */ new Set(["config", "shared", "testing"]);
|
|
9686
|
+
function normalize3(p) {
|
|
9687
|
+
return p.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
9688
|
+
}
|
|
9689
|
+
function nodeTouchedByChange(node2, file) {
|
|
9690
|
+
if (file.hunks.length === 0) return true;
|
|
9691
|
+
return file.hunks.some((h) => node2.span.start <= h.end && node2.span.end >= h.start);
|
|
9692
|
+
}
|
|
9693
|
+
function estimateTokens(value) {
|
|
9694
|
+
return Math.ceil(JSON.stringify(value).length / 4);
|
|
9695
|
+
}
|
|
9696
|
+
function compileCapsule(input) {
|
|
9697
|
+
const { graph, change, config, profile } = input;
|
|
9698
|
+
const unknowns = [];
|
|
9699
|
+
const evidence = [];
|
|
9700
|
+
const seenEvidence = /* @__PURE__ */ new Set();
|
|
9701
|
+
const addEvidence = (e) => {
|
|
9702
|
+
if (!seenEvidence.has(e.id)) {
|
|
9703
|
+
seenEvidence.add(e.id);
|
|
9704
|
+
evidence.push(e);
|
|
9705
|
+
}
|
|
9706
|
+
return e.id;
|
|
9707
|
+
};
|
|
9708
|
+
const changedPaths = new Set(change.files.map((f) => normalize3(f.path)));
|
|
9709
|
+
const changedByPath = new Map(change.files.map((f) => [normalize3(f.path), f]));
|
|
9710
|
+
const layerOf = /* @__PURE__ */ new Map();
|
|
9711
|
+
const classify = (p) => {
|
|
9712
|
+
const cached2 = layerOf.get(p);
|
|
9713
|
+
if (cached2) return cached2;
|
|
9714
|
+
const cls = classifyFile(p, "unknown");
|
|
9715
|
+
if (!cls) return null;
|
|
9716
|
+
const value = { layer: cls.layer, confidence: cls.confidence, role: cls.role };
|
|
9717
|
+
layerOf.set(p, value);
|
|
9718
|
+
return value;
|
|
9719
|
+
};
|
|
9720
|
+
const symbols = [];
|
|
9721
|
+
const changedNodeIds = /* @__PURE__ */ new Set();
|
|
9722
|
+
const nodesByFile = /* @__PURE__ */ new Map();
|
|
9723
|
+
if (graph) {
|
|
9724
|
+
for (const node2 of graph.nodes) {
|
|
9725
|
+
const file = normalize3(node2.file);
|
|
9726
|
+
let bucket = nodesByFile.get(file);
|
|
9727
|
+
if (!bucket) nodesByFile.set(file, bucket = []);
|
|
9728
|
+
bucket.push(node2);
|
|
9729
|
+
}
|
|
9730
|
+
for (const path53 of [...changedPaths].sort()) {
|
|
9731
|
+
const file = changedByPath.get(path53);
|
|
9732
|
+
for (const node2 of nodesByFile.get(path53) ?? []) {
|
|
9733
|
+
if (node2.kind === "file" || node2.kind === "document") continue;
|
|
9734
|
+
if (!nodeTouchedByChange(node2, file)) continue;
|
|
9735
|
+
changedNodeIds.add(node2.id);
|
|
9736
|
+
symbols.push({
|
|
9737
|
+
node_id: node2.id,
|
|
9738
|
+
name: node2.name,
|
|
9739
|
+
kind: node2.kind,
|
|
9740
|
+
path: path53,
|
|
9741
|
+
start_line: node2.span.start,
|
|
9742
|
+
end_line: node2.span.end
|
|
9743
|
+
});
|
|
9744
|
+
}
|
|
9745
|
+
}
|
|
9746
|
+
}
|
|
9747
|
+
const addedEdges = [];
|
|
9748
|
+
const removedEdges = [];
|
|
9749
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
9750
|
+
if (graph) for (const n of graph.nodes) nodeById.set(n.id, n);
|
|
9751
|
+
const edgeEvidenceId = (e) => `edge:${e.id.slice(0, 12)}`;
|
|
9752
|
+
const seenAdded = /* @__PURE__ */ new Set();
|
|
9753
|
+
if (graph) {
|
|
9754
|
+
for (const edge of graph.edges) {
|
|
9755
|
+
if (!BOUNDARY_EDGE_KINDS.has(edge.kind)) continue;
|
|
9756
|
+
const src = nodeById.get(edge.src);
|
|
9757
|
+
const dst = nodeById.get(edge.dst);
|
|
9758
|
+
if (!src || !dst) continue;
|
|
9759
|
+
const fromPath = normalize3(src.file);
|
|
9760
|
+
const toPath = normalize3(dst.file);
|
|
9761
|
+
if (fromPath === toPath) continue;
|
|
9762
|
+
if (!changedNodeIds.has(edge.src)) continue;
|
|
9763
|
+
const file = changedByPath.get(fromPath);
|
|
9764
|
+
const op = file?.op ?? "modified";
|
|
9765
|
+
const baseText = input.baseFileText?.get(fromPath);
|
|
9766
|
+
if (!isIntroducedEdge(op, fromPath, toPath, baseText)) continue;
|
|
9767
|
+
const pair = `${fromPath}\0${toPath}\0${edge.kind}`;
|
|
9768
|
+
if (seenAdded.has(pair)) continue;
|
|
9769
|
+
seenAdded.add(pair);
|
|
9770
|
+
const from = classify(fromPath);
|
|
9771
|
+
const to = classify(toPath);
|
|
9772
|
+
const id = addEvidence({
|
|
9773
|
+
id: edgeEvidenceId(edge),
|
|
9774
|
+
kind: "graph_edge",
|
|
9775
|
+
path: fromPath,
|
|
9776
|
+
start_line: src.span.start,
|
|
9777
|
+
end_line: src.span.end,
|
|
9778
|
+
protected_finding: false,
|
|
9779
|
+
note: `${edge.kind} \u2192 ${toPath}`
|
|
9780
|
+
});
|
|
9781
|
+
addedEdges.push({
|
|
9782
|
+
evidence_id: id,
|
|
9783
|
+
kind: edge.kind,
|
|
9784
|
+
from_path: fromPath,
|
|
9785
|
+
to_path: toPath,
|
|
9786
|
+
from_layer: from?.layer,
|
|
9787
|
+
to_layer: to?.layer
|
|
9788
|
+
});
|
|
9789
|
+
}
|
|
9790
|
+
const knownFiles = [...nodesByFile.keys()];
|
|
9791
|
+
let removedN = 0;
|
|
9792
|
+
for (const file of change.files) {
|
|
9793
|
+
const fromPath = normalize3(file.path);
|
|
9794
|
+
const baseText = input.baseFileText?.get(fromPath);
|
|
9795
|
+
const headText = input.headFileText?.get(fromPath);
|
|
9796
|
+
if (baseText === void 0 || headText === void 0) continue;
|
|
9797
|
+
for (const toPath of removedDestinations(fromPath, baseText, headText, knownFiles)) {
|
|
9798
|
+
removedN++;
|
|
9799
|
+
const from = classify(fromPath);
|
|
9800
|
+
const to = classify(toPath);
|
|
9801
|
+
const id = addEvidence({
|
|
9802
|
+
id: `edge:removed:${removedN}`,
|
|
9803
|
+
kind: "graph_edge",
|
|
9804
|
+
path: fromPath,
|
|
9805
|
+
protected_finding: false,
|
|
9806
|
+
note: `removed \u2192 ${toPath}`
|
|
9807
|
+
});
|
|
9808
|
+
removedEdges.push({
|
|
9809
|
+
evidence_id: id,
|
|
9810
|
+
kind: "import",
|
|
9811
|
+
from_path: fromPath,
|
|
9812
|
+
to_path: toPath,
|
|
9813
|
+
from_layer: from?.layer,
|
|
9814
|
+
to_layer: to?.layer
|
|
9815
|
+
});
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
}
|
|
9819
|
+
const roles = [];
|
|
9820
|
+
const roleCounter = /* @__PURE__ */ new Map();
|
|
9821
|
+
const roleEvidenceId = (role) => {
|
|
9822
|
+
const n = (roleCounter.get(role) ?? 0) + 1;
|
|
9823
|
+
roleCounter.set(role, n);
|
|
9824
|
+
return `role:${role}:${n}`;
|
|
9825
|
+
};
|
|
9826
|
+
const emitRole = (path53, changed) => {
|
|
9827
|
+
const cls = classify(path53);
|
|
9828
|
+
if (!cls) return;
|
|
9829
|
+
const role = cls.role ?? cls.layer;
|
|
9830
|
+
const id = addEvidence({
|
|
9831
|
+
id: roleEvidenceId(role),
|
|
9832
|
+
kind: "role",
|
|
9833
|
+
path: path53,
|
|
9834
|
+
protected_finding: false,
|
|
9835
|
+
note: `${role} (${cls.layer}, confidence ${cls.confidence.toFixed(2)})`
|
|
9836
|
+
});
|
|
9837
|
+
roles.push({ evidence_id: id, path: path53, role, layer: cls.layer, confidence: cls.confidence, changed });
|
|
9838
|
+
};
|
|
9839
|
+
for (const path53 of [...changedPaths].sort()) emitRole(path53, true);
|
|
9840
|
+
const peerPool = graph ? [...nodesByFile.keys()].sort() : [];
|
|
9841
|
+
const changedRoles = new Set(roles.filter((r) => r.changed).map((r) => r.role));
|
|
9842
|
+
for (const role of [...changedRoles].sort()) {
|
|
9843
|
+
let taken = 0;
|
|
9844
|
+
for (const path53 of peerPool) {
|
|
9845
|
+
if (taken >= MAX_ROLE_PEERS) break;
|
|
9846
|
+
if (changedPaths.has(path53)) continue;
|
|
9847
|
+
const cls = classify(path53);
|
|
9848
|
+
if (!cls) continue;
|
|
9849
|
+
if ((cls.role ?? cls.layer) !== role) continue;
|
|
9850
|
+
emitRole(path53, false);
|
|
9851
|
+
taken++;
|
|
9852
|
+
}
|
|
9853
|
+
}
|
|
9854
|
+
const outgoingLayers = /* @__PURE__ */ new Map();
|
|
9855
|
+
if (graph) {
|
|
9856
|
+
for (const edge of graph.edges) {
|
|
9857
|
+
if (!BOUNDARY_EDGE_KINDS.has(edge.kind)) continue;
|
|
9858
|
+
const src = nodeById.get(edge.src);
|
|
9859
|
+
const dst = nodeById.get(edge.dst);
|
|
9860
|
+
if (!src || !dst) continue;
|
|
9861
|
+
const from = normalize3(src.file);
|
|
9862
|
+
const to = normalize3(dst.file);
|
|
9863
|
+
if (from === to) continue;
|
|
9864
|
+
const toLayer = classify(to)?.layer;
|
|
9865
|
+
if (!toLayer) continue;
|
|
9866
|
+
let set = outgoingLayers.get(from);
|
|
9867
|
+
if (!set) outgoingLayers.set(from, set = /* @__PURE__ */ new Set());
|
|
9868
|
+
set.add(toLayer);
|
|
9869
|
+
}
|
|
9870
|
+
}
|
|
9871
|
+
const areaOfFile = /* @__PURE__ */ new Map();
|
|
9872
|
+
if (graph) {
|
|
9873
|
+
for (const area of graph.areas) {
|
|
9874
|
+
for (const member of area.members) {
|
|
9875
|
+
const node2 = nodeById.get(member);
|
|
9876
|
+
if (node2) areaOfFile.set(normalize3(node2.file), area.id);
|
|
9877
|
+
}
|
|
9878
|
+
}
|
|
9879
|
+
}
|
|
9880
|
+
const dataAccess = /* @__PURE__ */ new Map();
|
|
9881
|
+
const candidates = [];
|
|
9882
|
+
for (const filePath of graph ? [...nodesByFile.keys()].sort() : []) {
|
|
9883
|
+
const cls = classify(filePath);
|
|
9884
|
+
if (!cls) continue;
|
|
9885
|
+
const pattern = classifyDataAccess(cls.layer, [...outgoingLayers.get(filePath) ?? []]);
|
|
9886
|
+
if (!pattern) continue;
|
|
9887
|
+
dataAccess.set(filePath, pattern);
|
|
9888
|
+
const areaId = areaOfFile.get(filePath);
|
|
9889
|
+
const role = cls.role ?? cls.layer;
|
|
9890
|
+
candidates.push({
|
|
9891
|
+
path: filePath,
|
|
9892
|
+
pattern,
|
|
9893
|
+
role,
|
|
9894
|
+
areaGroup: areaId !== void 0 ? `area:${areaId}:${role}` : null,
|
|
9895
|
+
days: input.recencyDays?.get(filePath) ?? null
|
|
9896
|
+
});
|
|
9897
|
+
}
|
|
9898
|
+
const areaCounts = /* @__PURE__ */ new Map();
|
|
9899
|
+
for (const c2 of candidates) {
|
|
9900
|
+
if (c2.areaGroup) areaCounts.set(c2.areaGroup, (areaCounts.get(c2.areaGroup) ?? 0) + 1);
|
|
9901
|
+
}
|
|
9902
|
+
const peerFiles = candidates.map((c2) => {
|
|
9903
|
+
const useArea = c2.areaGroup !== null && (areaCounts.get(c2.areaGroup) ?? 0) >= MIN_GROUP_SIZE;
|
|
9904
|
+
return {
|
|
9905
|
+
path: c2.path,
|
|
9906
|
+
pattern: c2.pattern,
|
|
9907
|
+
group: useArea ? c2.areaGroup : `role:${c2.role}`,
|
|
9908
|
+
groupKind: useArea ? "area" : "role",
|
|
9909
|
+
daysSinceCommit: c2.days
|
|
9910
|
+
};
|
|
9911
|
+
});
|
|
9912
|
+
const votes = voteAll(peerFiles, { declaredPatterns: input.intent?.patterns });
|
|
9913
|
+
const observed = observedDominantPattern(roles);
|
|
9914
|
+
const declared = config.target_pattern ?? input.intent?.patterns[0] ?? null;
|
|
9915
|
+
const patterns = {
|
|
9916
|
+
observed_dominant_pattern: observed,
|
|
9917
|
+
declared_target_pattern: declared,
|
|
9918
|
+
approved_exceptions: [...config.approved_exceptions].sort(),
|
|
9919
|
+
legacy_pattern: observed && declared && observed !== declared ? observed : null,
|
|
9920
|
+
unknown: observed === null && declared === null
|
|
9921
|
+
};
|
|
9922
|
+
const changedGroups = new Set(
|
|
9923
|
+
peerFiles.filter((f) => changedPaths.has(f.path)).map((f) => f.group)
|
|
9924
|
+
);
|
|
9925
|
+
for (const vote of votes) {
|
|
9926
|
+
if (!changedGroups.has(vote.group)) continue;
|
|
9927
|
+
addEvidence({
|
|
9928
|
+
id: `vote:${vote.group}`,
|
|
9929
|
+
kind: "policy",
|
|
9930
|
+
protected_finding: false,
|
|
9931
|
+
note: vote.reason === "dominant" ? `${vote.size} peers, ${(vote.share * 100).toFixed(0)}% ${vote.dominant} (entropy ${vote.entropy.toFixed(2)})` : `${vote.size} peers, no convention (${vote.reason}, entropy ${vote.entropy.toFixed(2)})`
|
|
9932
|
+
});
|
|
9933
|
+
}
|
|
9934
|
+
for (const citation of input.intent?.citations.slice(0, 4) ?? []) {
|
|
9935
|
+
addEvidence({
|
|
9936
|
+
id: `intent:${citation.file}:${citation.line}`,
|
|
9937
|
+
kind: "policy",
|
|
9938
|
+
path: citation.file,
|
|
9939
|
+
start_line: citation.line,
|
|
9940
|
+
end_line: citation.line,
|
|
9941
|
+
protected_finding: false,
|
|
9942
|
+
note: citation.text
|
|
9943
|
+
});
|
|
9944
|
+
}
|
|
9945
|
+
const profileForRules = declared ?? observed ?? void 0;
|
|
9946
|
+
const policies = boundaryProfileRules(profileForRules).map((rule, i) => ({
|
|
9947
|
+
evidence_id: addEvidence({
|
|
9948
|
+
id: `policy:layering:${i + 1}`,
|
|
9949
|
+
kind: "policy",
|
|
9950
|
+
protected_finding: false,
|
|
9951
|
+
note: rule
|
|
9952
|
+
}),
|
|
9953
|
+
id: `layering-${i + 1}`,
|
|
9954
|
+
rule,
|
|
9955
|
+
source: declared ? "review.toml" : "derived"
|
|
9956
|
+
}));
|
|
9957
|
+
const crossesLayers = addedEdges.some(
|
|
9958
|
+
(e) => e.from_layer && e.to_layer && e.from_layer !== e.to_layer && !EXEMPT_LAYERS.has(e.from_layer) && !EXEMPT_LAYERS.has(e.to_layer)
|
|
9959
|
+
);
|
|
9960
|
+
if (policies.length === 0 && crossesLayers) {
|
|
9961
|
+
unknowns.push("This change crosses a layer boundary and no layering rules are enforced for this repository \u2014 set `target_pattern` in .vibgrate/review.toml to declare one.");
|
|
9962
|
+
}
|
|
9963
|
+
if (patterns.unknown && crossesLayers) {
|
|
9964
|
+
unknowns.push("This change crosses a layer boundary and neither a declared target pattern nor an observed dominant pattern could be established \u2014 alignment is reported as unknown, not as a pass.");
|
|
9965
|
+
}
|
|
9966
|
+
const paths = addedEdges.filter((e) => e.from_layer && e.to_layer && e.from_layer !== e.to_layer).filter((e) => !EXEMPT_LAYERS.has(e.from_layer) && !EXEMPT_LAYERS.has(e.to_layer)).map((e) => ({
|
|
9967
|
+
evidence_id: e.evidence_id,
|
|
9968
|
+
from: e.from_path,
|
|
9969
|
+
to: e.to_path,
|
|
9970
|
+
hops: [e.from_layer, e.to_layer],
|
|
9971
|
+
// Guard observation needs taint/authorization analysis this slice does
|
|
9972
|
+
// not run. `null` says "not observed" — never `true`.
|
|
9973
|
+
guarded: null
|
|
9974
|
+
}));
|
|
9975
|
+
if (paths.length > 0) {
|
|
9976
|
+
unknowns.push("Guard presence along the changed call paths was not observed \u2014 no runtime authorization test was supplied.");
|
|
9977
|
+
}
|
|
9978
|
+
const verification = changedFileCoverage(graph, changedPaths).map((v, i) => ({
|
|
9979
|
+
evidence_id: addEvidence({
|
|
9980
|
+
id: `verify:${v.kind}:${i + 1}`,
|
|
9981
|
+
kind: "graph_node",
|
|
9982
|
+
path: v.path,
|
|
9983
|
+
protected_finding: false,
|
|
9984
|
+
note: v.detail
|
|
9985
|
+
}),
|
|
9986
|
+
kind: v.kind,
|
|
9987
|
+
path: v.path,
|
|
9988
|
+
detail: v.detail
|
|
9989
|
+
}));
|
|
9990
|
+
const areas = graph ? graph.areas.map((a) => ({
|
|
9991
|
+
id: a.id,
|
|
9992
|
+
label: a.label,
|
|
9993
|
+
size: a.size,
|
|
9994
|
+
changed_members: a.members.filter((m) => changedNodeIds.has(m)).length
|
|
9995
|
+
})).filter((a) => a.changed_members > 0).sort((a, b) => b.changed_members - a.changed_members).slice(0, 8) : [];
|
|
9996
|
+
const capsule = {
|
|
9997
|
+
schema_version: CAPSULE_SCHEMA,
|
|
9998
|
+
identity: {
|
|
9999
|
+
repo_pseudonym: input.repoPseudonym,
|
|
10000
|
+
language: dominantLanguage(graph, change),
|
|
10001
|
+
graph_schema: graph?.schemaVersion ?? "none",
|
|
10002
|
+
analyzer_versions: { graph: graph?.provenance.version ?? "none", scanners: VERSION },
|
|
10003
|
+
profile
|
|
10004
|
+
},
|
|
10005
|
+
change: {
|
|
10006
|
+
base_sha: change.baseSha,
|
|
10007
|
+
head_sha: change.headSha,
|
|
10008
|
+
dirty: change.dirty,
|
|
10009
|
+
dirty_tree_hash: change.dirtyTreeHash,
|
|
10010
|
+
symbols,
|
|
10011
|
+
ops: change.files.map((f) => ({
|
|
10012
|
+
path: normalize3(f.path),
|
|
10013
|
+
op: f.op,
|
|
10014
|
+
added_lines: f.addedLines,
|
|
10015
|
+
removed_lines: f.removedLines
|
|
10016
|
+
})),
|
|
10017
|
+
added_edges: addedEdges,
|
|
10018
|
+
removed_edges: removedEdges,
|
|
10019
|
+
contract_changes: []
|
|
10020
|
+
},
|
|
10021
|
+
roles,
|
|
10022
|
+
areas,
|
|
10023
|
+
patterns,
|
|
10024
|
+
paths,
|
|
10025
|
+
security: [],
|
|
10026
|
+
policies,
|
|
10027
|
+
verification,
|
|
10028
|
+
evidence
|
|
10029
|
+
};
|
|
10030
|
+
const budget = CAPSULE_BUDGETS[profile];
|
|
10031
|
+
const trimmed = trimToBudget(capsule, budget.cap);
|
|
10032
|
+
return {
|
|
10033
|
+
capsule,
|
|
10034
|
+
votes,
|
|
10035
|
+
dataAccess,
|
|
10036
|
+
unknowns,
|
|
10037
|
+
estimatedTokens: estimateTokens(capsule),
|
|
10038
|
+
budget,
|
|
10039
|
+
trimmed
|
|
10040
|
+
};
|
|
10041
|
+
}
|
|
10042
|
+
function trimToBudget(capsule, cap) {
|
|
10043
|
+
let trimmed = false;
|
|
10044
|
+
const drop = () => {
|
|
10045
|
+
if (capsule.roles.some((r) => !r.changed)) {
|
|
10046
|
+
const keep = new Set(capsule.roles.filter((r) => r.changed).map((r) => r.evidence_id));
|
|
10047
|
+
const dropped = capsule.roles.filter((r) => !r.changed).map((r) => r.evidence_id);
|
|
10048
|
+
capsule.roles = capsule.roles.filter((r) => r.changed || keep.has(r.evidence_id));
|
|
10049
|
+
capsule.evidence = capsule.evidence.filter((e) => !dropped.includes(e.id) || e.protected_finding);
|
|
10050
|
+
return true;
|
|
10051
|
+
}
|
|
10052
|
+
if (capsule.areas.length > 0) {
|
|
10053
|
+
capsule.areas = [];
|
|
10054
|
+
return true;
|
|
10055
|
+
}
|
|
10056
|
+
return false;
|
|
10057
|
+
};
|
|
10058
|
+
while (estimateTokens(capsule) > cap) {
|
|
10059
|
+
if (!drop()) break;
|
|
10060
|
+
trimmed = true;
|
|
10061
|
+
}
|
|
10062
|
+
return trimmed;
|
|
10063
|
+
}
|
|
10064
|
+
function observedDominantPattern(roles) {
|
|
10065
|
+
const layers = new Set(roles.map((r) => r.layer).filter((l) => !EXEMPT_LAYERS.has(l)));
|
|
10066
|
+
if (layers.has("domain") && (layers.has("data-access") || layers.has("infrastructure")) && layers.has("services")) {
|
|
10067
|
+
return "clean";
|
|
10068
|
+
}
|
|
10069
|
+
if (layers.size >= 3) return "layered";
|
|
10070
|
+
return null;
|
|
10071
|
+
}
|
|
10072
|
+
function dominantLanguage(graph, change) {
|
|
10073
|
+
if (graph && graph.meta.languages.length > 0) return graph.meta.languages[0];
|
|
10074
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10075
|
+
for (const f of change.files) {
|
|
10076
|
+
const ext = f.path.split(".").pop() ?? "";
|
|
10077
|
+
counts.set(ext, (counts.get(ext) ?? 0) + 1);
|
|
10078
|
+
}
|
|
10079
|
+
const top = [...counts.entries()].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1))[0];
|
|
10080
|
+
return top?.[0] ?? "unknown";
|
|
10081
|
+
}
|
|
10082
|
+
function changedFileCoverage(graph, changedPaths) {
|
|
10083
|
+
if (!graph) return [];
|
|
10084
|
+
const testedFiles = /* @__PURE__ */ new Set();
|
|
10085
|
+
const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
10086
|
+
for (const edge of graph.edges) {
|
|
10087
|
+
if (edge.kind !== "test" && edge.kind !== "coverage") continue;
|
|
10088
|
+
const dst = nodeById.get(edge.dst);
|
|
10089
|
+
if (dst) testedFiles.add(normalize3(dst.file));
|
|
10090
|
+
}
|
|
10091
|
+
const out2 = [];
|
|
10092
|
+
for (const path53 of [...changedPaths].sort()) {
|
|
10093
|
+
if (/(^|\/)(__tests__|tests?|spec)\//i.test(path53) || /\.(test|spec)\.[^.]+$/i.test(path53)) continue;
|
|
10094
|
+
out2.push(
|
|
10095
|
+
testedFiles.has(path53) ? { kind: "test_covering_change", path: path53, detail: "a test edge reaches this file" } : { kind: "no_test_covering_change", path: path53, detail: "no test edge reaches this file in the map" }
|
|
10096
|
+
);
|
|
10097
|
+
}
|
|
10098
|
+
return out2.slice(0, 25);
|
|
10099
|
+
}
|
|
10100
|
+
function repoPseudonym(remote, root3) {
|
|
10101
|
+
return digestString(`vg.review.pseudonym\0${remote ?? `local:${root3}`}`);
|
|
10102
|
+
}
|
|
10103
|
+
var REVIEW_CONFIG_PATH = ".vibgrate/review.toml";
|
|
10104
|
+
var DEFAULT_REVIEW_CONFIG = {
|
|
10105
|
+
enforcement: "advisory",
|
|
10106
|
+
fail_on: "fail",
|
|
10107
|
+
target_pattern: null,
|
|
10108
|
+
approved_exceptions: [],
|
|
10109
|
+
protected: {
|
|
10110
|
+
unguarded_entrypoint: true,
|
|
10111
|
+
known_vulnerable_dependency: true,
|
|
10112
|
+
validated_taint: true
|
|
10113
|
+
},
|
|
10114
|
+
high_confidence_threshold: 0.8,
|
|
10115
|
+
high_severity_decision: "fail",
|
|
10116
|
+
source: "defaults"
|
|
10117
|
+
};
|
|
10118
|
+
function bool(value, fallback) {
|
|
10119
|
+
return typeof value === "boolean" ? value : fallback;
|
|
10120
|
+
}
|
|
10121
|
+
function str(value, allowed, fallback) {
|
|
10122
|
+
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
10123
|
+
}
|
|
10124
|
+
function parseReviewConfig(text, source) {
|
|
10125
|
+
const doc = parseToml(text);
|
|
10126
|
+
if (!doc) return { ...DEFAULT_REVIEW_CONFIG, source };
|
|
10127
|
+
const review = doc.review ?? {};
|
|
10128
|
+
const prot = review.protected ?? {};
|
|
10129
|
+
const exceptions = Array.isArray(review.approved_exceptions) ? review.approved_exceptions.filter((e) => typeof e === "string") : [];
|
|
10130
|
+
const threshold = typeof review.high_confidence_threshold === "number" ? Math.min(1, Math.max(0, review.high_confidence_threshold)) : DEFAULT_REVIEW_CONFIG.high_confidence_threshold;
|
|
10131
|
+
return {
|
|
10132
|
+
enforcement: str(review.enforcement, ["advisory", "enforced"], DEFAULT_REVIEW_CONFIG.enforcement),
|
|
10133
|
+
fail_on: str(review.fail_on, ["none", "fail", "needs_review"], DEFAULT_REVIEW_CONFIG.fail_on),
|
|
10134
|
+
target_pattern: typeof review.target_pattern === "string" ? review.target_pattern : null,
|
|
10135
|
+
approved_exceptions: exceptions,
|
|
10136
|
+
protected: {
|
|
10137
|
+
unguarded_entrypoint: bool(prot.unguarded_entrypoint, DEFAULT_REVIEW_CONFIG.protected.unguarded_entrypoint),
|
|
10138
|
+
known_vulnerable_dependency: bool(
|
|
10139
|
+
prot.known_vulnerable_dependency,
|
|
10140
|
+
DEFAULT_REVIEW_CONFIG.protected.known_vulnerable_dependency
|
|
10141
|
+
),
|
|
10142
|
+
validated_taint: bool(prot.validated_taint, DEFAULT_REVIEW_CONFIG.protected.validated_taint)
|
|
10143
|
+
},
|
|
10144
|
+
high_confidence_threshold: threshold,
|
|
10145
|
+
high_severity_decision: str(
|
|
10146
|
+
review.high_severity_decision,
|
|
10147
|
+
["fail", "needs_review"],
|
|
10148
|
+
DEFAULT_REVIEW_CONFIG.high_severity_decision
|
|
10149
|
+
),
|
|
10150
|
+
source
|
|
10151
|
+
};
|
|
10152
|
+
}
|
|
10153
|
+
function loadReviewConfig(root3, base, run) {
|
|
10154
|
+
const fromRef = (ref) => {
|
|
10155
|
+
const res = run(["show", `${ref}:${REVIEW_CONFIG_PATH}`], root3);
|
|
10156
|
+
return res.status === 0 && res.stdout.trim() ? res.stdout : null;
|
|
10157
|
+
};
|
|
10158
|
+
if (base) {
|
|
10159
|
+
const text = fromRef(base);
|
|
10160
|
+
if (text) return parseReviewConfig(text, "base-branch");
|
|
10161
|
+
}
|
|
10162
|
+
const head = fromRef("HEAD");
|
|
10163
|
+
if (head) return parseReviewConfig(head, "head");
|
|
10164
|
+
const local = path51.join(root3, REVIEW_CONFIG_PATH);
|
|
10165
|
+
if (fs12.existsSync(local)) {
|
|
10166
|
+
try {
|
|
10167
|
+
return parseReviewConfig(fs12.readFileSync(local, "utf8"), "working-tree");
|
|
10168
|
+
} catch {
|
|
10169
|
+
}
|
|
10170
|
+
}
|
|
10171
|
+
return { ...DEFAULT_REVIEW_CONFIG };
|
|
10172
|
+
}
|
|
10173
|
+
|
|
10174
|
+
// src/review/layers.ts
|
|
10175
|
+
var LAYERED_STACK = [
|
|
10176
|
+
"presentation",
|
|
10177
|
+
"routing",
|
|
10178
|
+
"middleware",
|
|
10179
|
+
"services",
|
|
10180
|
+
"domain",
|
|
10181
|
+
"data-access",
|
|
10182
|
+
"infrastructure"
|
|
10183
|
+
];
|
|
10184
|
+
var RANK = new Map(LAYERED_STACK.map((l, i) => [l, i]));
|
|
10185
|
+
var SKIP_PROFILES = /* @__PURE__ */ new Set(["layered", "mvc", "mvvm"]);
|
|
10186
|
+
var CLEAN_PROFILES = /* @__PURE__ */ new Set(["clean", "hexagonal", "onion"]);
|
|
10187
|
+
var OUTER_LAYERS = /* @__PURE__ */ new Set(["presentation", "routing", "middleware"]);
|
|
10188
|
+
var PERSISTENCE_LAYERS = /* @__PURE__ */ new Set(["data-access", "infrastructure"]);
|
|
10189
|
+
var NO_SKIP = { skipped: false, bypassed: [], rule: "" };
|
|
10190
|
+
function evaluateLayerSkip(profile, from, to) {
|
|
10191
|
+
if (CLEAN_PROFILES.has(profile)) {
|
|
10192
|
+
if (OUTER_LAYERS.has(from) && PERSISTENCE_LAYERS.has(to)) {
|
|
10193
|
+
return {
|
|
10194
|
+
skipped: true,
|
|
10195
|
+
bypassed: ["services"],
|
|
10196
|
+
rule: `${profile}:outer-ring:${from}\u2192${to}`
|
|
10197
|
+
};
|
|
10198
|
+
}
|
|
10199
|
+
return NO_SKIP;
|
|
10200
|
+
}
|
|
10201
|
+
if (!SKIP_PROFILES.has(profile)) return NO_SKIP;
|
|
10202
|
+
const a = RANK.get(from);
|
|
10203
|
+
const b = RANK.get(to);
|
|
10204
|
+
if (a === void 0 || b === void 0) return NO_SKIP;
|
|
10205
|
+
if (b <= a + 1) return NO_SKIP;
|
|
10206
|
+
const bypassed = LAYERED_STACK.slice(a + 1, b).filter((l) => l !== "domain");
|
|
10207
|
+
if (bypassed.length === 0) return NO_SKIP;
|
|
10208
|
+
return { skipped: true, bypassed, rule: `${profile}:skip:${from}\u2192${to}` };
|
|
10209
|
+
}
|
|
10210
|
+
|
|
10211
|
+
// src/review/scanners.ts
|
|
10212
|
+
var EXEMPT_LAYERS2 = /* @__PURE__ */ new Set(["config", "shared", "testing"]);
|
|
10213
|
+
var DEPENDENCY_MANIFESTS = /(^|\/)(package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock|.*\.csproj|packages\.lock\.json|Directory\.Packages\.props|requirements.*\.txt|pyproject\.toml|poetry\.lock|go\.mod|go\.sum|Cargo\.toml|Cargo\.lock|pom\.xml|build\.gradle(\.kts)?|Gemfile(\.lock)?|composer\.(json|lock))$/i;
|
|
10214
|
+
var GUARD_PATTERNS = [
|
|
10215
|
+
/\b(authorize|authorise|authenticate|requireAuth|requireUser|ensureAuth|checkPermission|hasPermission|can|verifyToken|verifyJwt|assertScope|requireScope|guard)\s*\(/i,
|
|
10216
|
+
/\[\s*Authorize\b/,
|
|
10217
|
+
// C# attribute
|
|
10218
|
+
/@(PreAuthorize|Secured|RolesAllowed|UseGuards)\b/,
|
|
10219
|
+
// Spring / Nest
|
|
10220
|
+
/\buse(Auth|Guard|Session)\b/,
|
|
10221
|
+
/\b(csrf|rateLimit|validateRequest|zod|schema)\.(parse|safeParse|validate)\s*\(/i
|
|
10222
|
+
];
|
|
10223
|
+
var ENTRYPOINT_LAYERS = /* @__PURE__ */ new Set(["routing", "middleware"]);
|
|
10224
|
+
var ENTRYPOINT_ROLES = /* @__PURE__ */ new Set(["controller", "handler", "router"]);
|
|
10225
|
+
function alignment(capsule, fromLayer, toLayer) {
|
|
10226
|
+
const pair = `${fromLayer}->${toLayer}`;
|
|
10227
|
+
if (capsule.patterns.approved_exceptions.includes(pair)) return "approved_exception";
|
|
10228
|
+
if (capsule.patterns.unknown) return "unknown";
|
|
10229
|
+
if (capsule.patterns.declared_target_pattern) return "regression";
|
|
10230
|
+
return "legacy_consistent";
|
|
10231
|
+
}
|
|
10232
|
+
function runScanners(input) {
|
|
10233
|
+
const { capsule, config } = input;
|
|
10234
|
+
const architecture = [];
|
|
10235
|
+
const security = [];
|
|
10236
|
+
const unknowns = [];
|
|
10237
|
+
const requiredChecks = [];
|
|
10238
|
+
const profile = capsule.patterns.declared_target_pattern ?? capsule.patterns.observed_dominant_pattern;
|
|
10239
|
+
if (profile) {
|
|
10240
|
+
let n = 0;
|
|
10241
|
+
for (const edge of capsule.change.added_edges) {
|
|
10242
|
+
const from = edge.from_layer;
|
|
10243
|
+
const to = edge.to_layer;
|
|
10244
|
+
if (!from || !to) continue;
|
|
10245
|
+
if (EXEMPT_LAYERS2.has(from) || EXEMPT_LAYERS2.has(to)) continue;
|
|
10246
|
+
const illegal = evaluateLayerBoundary(profile, from, to, edge.from_path, edge.to_path);
|
|
10247
|
+
const skip = capsule.patterns.declared_target_pattern ? evaluateLayerSkip(profile, from, to) : { skipped: false, bypassed: []};
|
|
10248
|
+
if (!illegal.illegal && !skip.skipped) continue;
|
|
10249
|
+
const align = alignment(capsule, from, to);
|
|
10250
|
+
const declared = Boolean(capsule.patterns.declared_target_pattern);
|
|
10251
|
+
n++;
|
|
10252
|
+
architecture.push({
|
|
10253
|
+
id: `arch-${String(n).padStart(2, "0")}`,
|
|
10254
|
+
kind: "boundary_bypass",
|
|
10255
|
+
// A declared target makes this a regression against a stated intent;
|
|
10256
|
+
// without one it is an observation about the repo's own majority.
|
|
10257
|
+
severity: declared ? "high" : "medium",
|
|
10258
|
+
confidence: declared ? 0.93 : 0.6,
|
|
10259
|
+
claim: skip.skipped ? `A changed ${from} file depends directly on ${to}, skipping ${skip.bypassed.join(", ")} (${edge.from_path} \u2192 ${edge.to_path}).` : `A changed ${from} file depends on ${to}, against the ${illegal.rule} rule (${edge.from_path} \u2192 ${edge.to_path}).`,
|
|
10260
|
+
evidence_ids: [edge.evidence_id, ...policyEvidenceIds(capsule)],
|
|
10261
|
+
target_alignment: align,
|
|
10262
|
+
remediation: `Route the operation through the ${intermediateLayer(from, to)} layer instead of calling ${to} directly.`,
|
|
10263
|
+
paths: [edge.from_path, edge.to_path],
|
|
10264
|
+
protected_finding: false,
|
|
10265
|
+
source: "scanner"
|
|
10266
|
+
});
|
|
10267
|
+
}
|
|
10268
|
+
}
|
|
10269
|
+
let sec = 0;
|
|
10270
|
+
if (config.protected.unguarded_entrypoint) {
|
|
10271
|
+
for (const [filePath, lines] of [...input.removedLines.entries()].sort()) {
|
|
10272
|
+
const removedGuards = lines.filter((l) => GUARD_PATTERNS.some((p) => p.test(l)));
|
|
10273
|
+
if (removedGuards.length === 0) continue;
|
|
10274
|
+
const stillGuarded = GUARD_PATTERNS.some((p) => p.test(input.fileText.get(filePath) ?? ""));
|
|
10275
|
+
if (stillGuarded) continue;
|
|
10276
|
+
sec++;
|
|
10277
|
+
security.push({
|
|
10278
|
+
id: `sec-${String(sec).padStart(2, "0")}`,
|
|
10279
|
+
kind: "guard_removed",
|
|
10280
|
+
severity: "high",
|
|
10281
|
+
confidence: 0.9,
|
|
10282
|
+
claim: `An authorization or validation guard was removed from ${filePath} and no equivalent guard remains in the file.`,
|
|
10283
|
+
evidence_ids: [roleEvidenceFor(capsule, filePath) ?? sourceSpanEvidence(capsule, filePath)],
|
|
10284
|
+
target_alignment: "regression",
|
|
10285
|
+
remediation: "Restore the guard, or move it to a middleware the changed path provably passes through.",
|
|
10286
|
+
paths: [filePath],
|
|
10287
|
+
protected_finding: true,
|
|
10288
|
+
source: "scanner"
|
|
10289
|
+
});
|
|
10290
|
+
}
|
|
10291
|
+
}
|
|
10292
|
+
if (config.protected.unguarded_entrypoint) {
|
|
10293
|
+
const classified = [];
|
|
10294
|
+
for (const [filePath, text] of [...input.fileText.entries()].sort()) {
|
|
10295
|
+
for (const route of routesForFile(filePath, text)) classified.push(classifyRoute(route));
|
|
10296
|
+
}
|
|
10297
|
+
for (const [filePath, text] of [...input.peerFileText?.entries() ?? []].sort()) {
|
|
10298
|
+
if (input.fileText.has(filePath)) continue;
|
|
10299
|
+
for (const route of routesForFile(filePath, text)) classified.push(classifyRoute(route));
|
|
10300
|
+
}
|
|
10301
|
+
const changedFiles = input.changedPaths ?? /* @__PURE__ */ new Set();
|
|
10302
|
+
for (const vote of voteAllAuth(classified)) {
|
|
10303
|
+
for (const route of vote.deviators) {
|
|
10304
|
+
if (!changedFiles.has(route.file)) continue;
|
|
10305
|
+
sec++;
|
|
10306
|
+
security.push({
|
|
10307
|
+
id: `sec-${String(sec).padStart(2, "0")}`,
|
|
10308
|
+
kind: "unguarded_entrypoint",
|
|
10309
|
+
severity: vote.aboveFloor ? "high" : "medium",
|
|
10310
|
+
confidence: vote.aboveFloor ? Math.min(0.95, vote.share) : 0.5,
|
|
10311
|
+
claim: `${route.method} ${route.path || route.file} has no authorization guard in scope, while ${vote.guarded} of ${vote.classified} mutating routes in ${vote.group} do (${route.rule}).`,
|
|
10312
|
+
evidence_ids: [roleEvidenceFor(capsule, route.file) ?? sourceSpanEvidence(capsule, route.file)],
|
|
10313
|
+
target_alignment: "regression",
|
|
10314
|
+
remediation: "Add an authorization check, or register the route behind a middleware it provably passes through.",
|
|
10315
|
+
paths: [route.file],
|
|
10316
|
+
// Below the peer floor this is a real observation but not a claim
|
|
10317
|
+
// strong enough to block a merge, so it must not be protected.
|
|
10318
|
+
protected_finding: vote.aboveFloor,
|
|
10319
|
+
source: "scanner"
|
|
10320
|
+
});
|
|
10321
|
+
if (vote.aboveFloor) requiredChecks.push("authz-test");
|
|
10322
|
+
}
|
|
10323
|
+
const changedUnsure = vote.unsure.filter((r) => changedFiles.has(r.file));
|
|
10324
|
+
if (changedUnsure.length > 0) {
|
|
10325
|
+
unknowns.push(
|
|
10326
|
+
`${changedUnsure.length} changed route(s) in ${vote.group} could not be classified as guarded or open (${[...new Set(changedUnsure.map((r) => r.rule))].join(", ")}) \u2014 neither confirmed nor excluded.`
|
|
10327
|
+
);
|
|
10328
|
+
}
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
const dependencyChanges = capsule.change.ops.filter((o) => DEPENDENCY_MANIFESTS.test(o.path));
|
|
10332
|
+
if (config.protected.known_vulnerable_dependency && dependencyChanges.length > 0) {
|
|
10333
|
+
if (input.vulnerablePackages === null) {
|
|
10334
|
+
unknowns.push(
|
|
10335
|
+
`${dependencyChanges.length} dependency manifest(s) changed and no advisory data was available \u2014 run \`vg scan --vulns\` so \`vg review\` can check them.`
|
|
10336
|
+
);
|
|
10337
|
+
requiredChecks.push("dependency-advisory-check");
|
|
10338
|
+
} else {
|
|
10339
|
+
for (const vuln of input.vulnerablePackages) {
|
|
10340
|
+
const declaredIn = dependencyChanges.find(
|
|
10341
|
+
(o) => (input.fileText.get(o.path) ?? "").includes(vuln.package)
|
|
10342
|
+
);
|
|
10343
|
+
if (!declaredIn) continue;
|
|
10344
|
+
sec++;
|
|
10345
|
+
security.push({
|
|
10346
|
+
id: `sec-${String(sec).padStart(2, "0")}`,
|
|
10347
|
+
kind: "known_vulnerable_dependency",
|
|
10348
|
+
severity: "high",
|
|
10349
|
+
confidence: 1,
|
|
10350
|
+
claim: `A changed dependency manifest declares ${vuln.package}, which has a known advisory: ${vuln.detail}`,
|
|
10351
|
+
evidence_ids: [dependencyEvidence(capsule, declaredIn.path)],
|
|
10352
|
+
target_alignment: "regression",
|
|
10353
|
+
remediation: "Upgrade to a fixed version, or record a scoped, expiry-bound exception.",
|
|
10354
|
+
paths: [declaredIn.path],
|
|
10355
|
+
protected_finding: true,
|
|
10356
|
+
source: "scanner"
|
|
10357
|
+
});
|
|
10358
|
+
}
|
|
10359
|
+
}
|
|
10360
|
+
}
|
|
10361
|
+
const hasTaintSurface = capsule.paths.length > 0 || capsule.roles.some(
|
|
10362
|
+
(r) => r.changed && (ENTRYPOINT_ROLES.has(r.role) || ENTRYPOINT_LAYERS.has(r.layer))
|
|
10363
|
+
);
|
|
10364
|
+
if (config.protected.validated_taint && hasTaintSurface) {
|
|
10365
|
+
unknowns.push("This change touches an entrypoint or a cross-layer path and taint validation was not run \u2014 dataflow analysis is not part of this review slice, so tainted-input findings are neither confirmed nor excluded.");
|
|
10366
|
+
}
|
|
10367
|
+
for (const vote of input.votes ?? []) {
|
|
10368
|
+
if (vote.reason !== "dominant" || !vote.dominant) continue;
|
|
10369
|
+
const changedDeviators = vote.deviators.filter((p) => input.changedPaths?.has(p));
|
|
10370
|
+
if (changedDeviators.length === 0) continue;
|
|
10371
|
+
for (const filePath of changedDeviators) {
|
|
10372
|
+
const actual = input.dataAccess?.get(filePath);
|
|
10373
|
+
if (!actual) continue;
|
|
10374
|
+
if (!isRegression(actual, vote.dominant)) continue;
|
|
10375
|
+
const declared = Boolean(capsule.patterns.declared_target_pattern);
|
|
10376
|
+
architecture.push({
|
|
10377
|
+
id: `arch-${String(architecture.length + 1).padStart(2, "0")}`,
|
|
10378
|
+
kind: "peer_deviation",
|
|
10379
|
+
severity: declared ? "high" : "medium",
|
|
10380
|
+
// The vote's own share is the calibration: a 100%-consistent group of
|
|
10381
|
+
// 20 peers is far stronger evidence than a bare 70% of 3.
|
|
10382
|
+
confidence: Math.min(0.95, vote.share * Math.min(1, vote.size / 8)),
|
|
10383
|
+
claim: `${filePath} ${describePattern(actual)}, while ${(vote.share * 100).toFixed(0)}% of its ${vote.size} ${vote.groupKind} peers ${describePattern(vote.dominant)}.`,
|
|
10384
|
+
evidence_ids: [`vote:${vote.group}`, ...policyEvidenceIds(capsule)].filter(
|
|
10385
|
+
(id) => capsule.evidence.some((e) => e.id === id)
|
|
10386
|
+
),
|
|
10387
|
+
target_alignment: alignment(capsule, void 0, void 0),
|
|
10388
|
+
remediation: `Follow the pattern its peers use \u2014 see ${vote.exemplars.slice(0, 2).join(", ") || "the peer group"}.`,
|
|
10389
|
+
paths: [filePath],
|
|
10390
|
+
protected_finding: false,
|
|
10391
|
+
source: "scanner"
|
|
10392
|
+
});
|
|
10393
|
+
}
|
|
10394
|
+
}
|
|
10395
|
+
if (input.similarity && input.changedBodies) {
|
|
10396
|
+
const changedIds = new Set(input.changedBodies.map((b) => b.id));
|
|
10397
|
+
const reported = /* @__PURE__ */ new Set();
|
|
10398
|
+
for (const body of input.changedBodies) {
|
|
10399
|
+
if (!isComparable(body.file)) continue;
|
|
10400
|
+
const hits = input.similarity.find(body, changedIds).filter((h) => isComparable(h.file));
|
|
10401
|
+
if (hits.length === 0) continue;
|
|
10402
|
+
const key2 = `${body.file}:${body.name}`;
|
|
10403
|
+
if (reported.has(key2)) continue;
|
|
10404
|
+
reported.add(key2);
|
|
10405
|
+
const best = hits[0];
|
|
10406
|
+
architecture.push({
|
|
10407
|
+
id: `arch-${String(architecture.length + 1).padStart(2, "0")}`,
|
|
10408
|
+
kind: "duplicate_implementation",
|
|
10409
|
+
severity: "medium",
|
|
10410
|
+
confidence: Math.min(0.9, best.score),
|
|
10411
|
+
claim: `${body.name} in ${body.file} is structurally ${(best.score * 100).toFixed(0)}% the same as ${best.name} in ${best.file}:${best.startLine}` + (hits.length > 1 ? ` (and ${hits.length - 1} other near-match(es))` : "") + ".",
|
|
10412
|
+
evidence_ids: [duplicateEvidence(capsule, body, best)],
|
|
10413
|
+
// Reusing what exists is what the architecture wants; this is a
|
|
10414
|
+
// deviation from that, but never a *security* regression.
|
|
10415
|
+
target_alignment: capsule.patterns.declared_target_pattern ? "regression" : "unknown",
|
|
10416
|
+
remediation: `Call ${best.name} instead, or extract the shared behaviour if the two genuinely differ.`,
|
|
10417
|
+
paths: [body.file, best.file],
|
|
10418
|
+
protected_finding: false,
|
|
10419
|
+
source: "scanner"
|
|
10420
|
+
});
|
|
10421
|
+
}
|
|
10422
|
+
}
|
|
10423
|
+
const untested = capsule.verification.filter((v) => v.kind === "no_test_covering_change");
|
|
10424
|
+
if (untested.length > 0) {
|
|
10425
|
+
architecture.push({
|
|
10426
|
+
id: `arch-${String(architecture.length + 1).padStart(2, "0")}`,
|
|
10427
|
+
kind: "unverified_change",
|
|
10428
|
+
severity: "medium",
|
|
10429
|
+
confidence: 0.7,
|
|
10430
|
+
claim: `${untested.length} changed file(s) have no test edge reaching them in the code map.`,
|
|
10431
|
+
evidence_ids: untested.slice(0, 8).map((v) => v.evidence_id),
|
|
10432
|
+
target_alignment: "unknown",
|
|
10433
|
+
remediation: "Add a test that exercises the changed call path, or point `vg build` at the coverage report that already covers it.",
|
|
10434
|
+
paths: untested.map((v) => v.path).slice(0, 25),
|
|
10435
|
+
protected_finding: false,
|
|
10436
|
+
source: "scanner"
|
|
10437
|
+
});
|
|
10438
|
+
requiredChecks.push("changed-call-path-test");
|
|
10439
|
+
}
|
|
10440
|
+
return {
|
|
10441
|
+
architecture,
|
|
10442
|
+
security,
|
|
10443
|
+
unknowns,
|
|
10444
|
+
requiredChecks: [...new Set(requiredChecks)].sort()
|
|
10445
|
+
};
|
|
10446
|
+
}
|
|
10447
|
+
function policyEvidenceIds(capsule) {
|
|
10448
|
+
return capsule.policies.slice(0, 2).map((p) => p.evidence_id);
|
|
10449
|
+
}
|
|
10450
|
+
function roleEvidenceFor(capsule, filePath) {
|
|
10451
|
+
return capsule.roles.find((r) => r.path === filePath)?.evidence_id ?? null;
|
|
10452
|
+
}
|
|
10453
|
+
function sourceSpanEvidence(capsule, filePath) {
|
|
10454
|
+
const existing = capsule.evidence.find((e) => e.path === filePath);
|
|
10455
|
+
if (existing) return existing.id;
|
|
10456
|
+
const id = `source_span:${capsule.evidence.length + 1}`;
|
|
10457
|
+
capsule.evidence.push({ id, kind: "source_span", path: filePath, protected_finding: true });
|
|
10458
|
+
return id;
|
|
10459
|
+
}
|
|
10460
|
+
function duplicateEvidence(capsule, body, match) {
|
|
10461
|
+
const id = `duplicate:${body.file}:${body.name}`;
|
|
10462
|
+
if (!capsule.evidence.some((e) => e.id === id)) {
|
|
10463
|
+
capsule.evidence.push({
|
|
10464
|
+
id,
|
|
10465
|
+
kind: "graph_node",
|
|
10466
|
+
path: match.file,
|
|
10467
|
+
start_line: match.startLine,
|
|
10468
|
+
end_line: match.endLine,
|
|
10469
|
+
protected_finding: false,
|
|
10470
|
+
note: `${body.name} (${body.file}:${body.startLine}) ~= ${match.name} (${match.file}:${match.startLine})`
|
|
10471
|
+
});
|
|
10472
|
+
}
|
|
10473
|
+
return id;
|
|
10474
|
+
}
|
|
10475
|
+
function dependencyEvidence(capsule, filePath) {
|
|
10476
|
+
const existing = capsule.evidence.find((e) => e.kind === "dependency" && e.path === filePath);
|
|
10477
|
+
if (existing) return existing.id;
|
|
10478
|
+
const id = `dependency:${filePath}`;
|
|
10479
|
+
capsule.evidence.push({ id, kind: "dependency", path: filePath, protected_finding: true });
|
|
10480
|
+
return id;
|
|
10481
|
+
}
|
|
10482
|
+
function intermediateLayer(from, to) {
|
|
10483
|
+
if (to === "data-access" || to === "infrastructure") return "application service";
|
|
10484
|
+
if (from === "domain") return "port / interface";
|
|
10485
|
+
return "service";
|
|
10486
|
+
}
|
|
10487
|
+
function removedLinesFromDiff(diff2) {
|
|
10488
|
+
const map = /* @__PURE__ */ new Map();
|
|
10489
|
+
let current = null;
|
|
10490
|
+
for (const line of diff2.split("\n")) {
|
|
10491
|
+
if (line.startsWith("--- ")) continue;
|
|
10492
|
+
if (line.startsWith("+++ ")) {
|
|
10493
|
+
const p = line.slice(4).trim();
|
|
10494
|
+
current = p === "/dev/null" ? null : p.replace(/^b\//, "");
|
|
10495
|
+
if (current && !map.has(current)) map.set(current, []);
|
|
10496
|
+
continue;
|
|
10497
|
+
}
|
|
10498
|
+
if (current && line.startsWith("-") && !line.startsWith("---")) {
|
|
10499
|
+
map.get(current).push(line.slice(1));
|
|
10500
|
+
}
|
|
10501
|
+
}
|
|
10502
|
+
return map;
|
|
10503
|
+
}
|
|
10504
|
+
function vulnerablePackagesFromScan(root3) {
|
|
10505
|
+
const file = path51.join(root3, ".vibgrate", "scan_result.json");
|
|
10506
|
+
if (!fs12.existsSync(file)) return null;
|
|
10507
|
+
try {
|
|
10508
|
+
const artifact = JSON.parse(fs12.readFileSync(file, "utf8"));
|
|
10509
|
+
const findings = (artifact.findings ?? []).filter((f) => f.ruleId === "vibgrate/vulnerability");
|
|
10510
|
+
return findings.map((f) => ({
|
|
10511
|
+
package: String(f.details?.package ?? f.location),
|
|
10512
|
+
detail: f.message
|
|
10513
|
+
}));
|
|
10514
|
+
} catch {
|
|
10515
|
+
return null;
|
|
10516
|
+
}
|
|
10517
|
+
}
|
|
10518
|
+
|
|
10519
|
+
// src/review/verify.ts
|
|
10520
|
+
var FORBIDDEN_CLAIMS = [
|
|
10521
|
+
{ pattern: /\b(is|are|remains?|stays?)\s+secure\b/i, label: "secure" },
|
|
10522
|
+
{ pattern: /\bno\s+vulnerabilit(y|ies)\b/i, label: "no vulnerability" },
|
|
10523
|
+
{ pattern: /\b(is|are)\s+safe\b/i, label: "safe" },
|
|
10524
|
+
{ pattern: /\b(is|are)\s+approved\b/i, label: "approved" },
|
|
10525
|
+
{ pattern: /\bvulnerability[- ]free\b/i, label: "vulnerability-free" }
|
|
10526
|
+
];
|
|
10527
|
+
var RUNTIME_CLAIM = /\b(at runtime|when (?:it |this )?runs|in production|will (?:always |never )?(?:throw|fail|return|execute)|executes?)\b/i;
|
|
10528
|
+
var UNCERTAINTY = /\b(may|might|could|appears?|likely|unlikely|not observed|unverified|no runtime|static(?:ally)?)\b/i;
|
|
10529
|
+
var SEVERITIES = /* @__PURE__ */ new Set(["low", "medium", "high", "critical"]);
|
|
10530
|
+
function verifyFinding(finding, domain, evidenceIds, errors, invalidEvidence) {
|
|
10531
|
+
const where = `${domain} finding ${finding.id || "(no id)"}`;
|
|
10532
|
+
if (typeof finding.id !== "string" || !finding.id) errors.push(`${where}: missing id`);
|
|
10533
|
+
if (typeof finding.kind !== "string" || !finding.kind) errors.push(`${where}: missing kind`);
|
|
10534
|
+
if (!SEVERITIES.has(finding.severity)) errors.push(`${where}: invalid severity "${String(finding.severity)}"`);
|
|
10535
|
+
if (typeof finding.confidence !== "number" || finding.confidence < 0 || finding.confidence > 1) {
|
|
10536
|
+
errors.push(`${where}: confidence must be a number in 0..1`);
|
|
10537
|
+
}
|
|
10538
|
+
if (typeof finding.claim !== "string" || !finding.claim.trim()) errors.push(`${where}: missing claim`);
|
|
10539
|
+
if (!TARGET_ALIGNMENTS.includes(finding.target_alignment)) {
|
|
10540
|
+
errors.push(`${where}: invalid target_alignment "${String(finding.target_alignment)}"`);
|
|
10541
|
+
}
|
|
10542
|
+
if (typeof finding.remediation !== "string" || !finding.remediation.trim()) {
|
|
10543
|
+
errors.push(`${where}: missing remediation`);
|
|
10544
|
+
}
|
|
10545
|
+
if (!Array.isArray(finding.paths)) errors.push(`${where}: paths must be an array`);
|
|
10546
|
+
if (!Array.isArray(finding.evidence_ids) || finding.evidence_ids.length === 0) {
|
|
10547
|
+
errors.push(`${where}: claims a flow with no supporting graph or scanner fact`);
|
|
10548
|
+
invalidEvidence.value = true;
|
|
10549
|
+
} else {
|
|
10550
|
+
for (const id of finding.evidence_ids) {
|
|
10551
|
+
if (!evidenceIds.has(id)) {
|
|
10552
|
+
errors.push(`${where}: cites evidence id "${id}" that is not in the capsule`);
|
|
10553
|
+
invalidEvidence.value = true;
|
|
10554
|
+
}
|
|
10555
|
+
}
|
|
10556
|
+
}
|
|
10557
|
+
const prose = `${finding.claim ?? ""} ${finding.remediation ?? ""}`;
|
|
10558
|
+
for (const { pattern, label } of FORBIDDEN_CLAIMS) {
|
|
10559
|
+
if (pattern.test(prose)) errors.push(`${where}: makes the absolute claim "${label}"`);
|
|
10560
|
+
}
|
|
10561
|
+
if (RUNTIME_CLAIM.test(prose) && !UNCERTAINTY.test(prose)) {
|
|
10562
|
+
errors.push(`${where}: asserts runtime behaviour without stating the uncertainty (nothing was executed)`);
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10565
|
+
function verifyFindings(findings, capsule) {
|
|
10566
|
+
const errors = [];
|
|
10567
|
+
const invalidEvidence = { value: false };
|
|
10568
|
+
if (findings?.schema_version !== FINDINGS_SCHEMA) {
|
|
10569
|
+
errors.push(`expected schema_version ${FINDINGS_SCHEMA}, got ${String(findings?.schema_version)}`);
|
|
10570
|
+
return { schema_valid: false, evidence_ids_valid: false, errors };
|
|
10571
|
+
}
|
|
10572
|
+
if ("decision" in findings) {
|
|
10573
|
+
errors.push("findings carry a `decision` field \u2014 only policy may decide");
|
|
10574
|
+
}
|
|
10575
|
+
if (!Array.isArray(findings.architecture_findings)) errors.push("architecture_findings must be an array");
|
|
10576
|
+
if (!Array.isArray(findings.security_findings)) errors.push("security_findings must be an array");
|
|
10577
|
+
if (!Array.isArray(findings.unknowns)) errors.push("unknowns must be an array");
|
|
10578
|
+
if (!Array.isArray(findings.required_checks)) errors.push("required_checks must be an array");
|
|
10579
|
+
if (!Array.isArray(findings.change_class)) errors.push("change_class must be an array");
|
|
10580
|
+
const evidenceIds = new Set(capsule.evidence.map((e) => e.id));
|
|
10581
|
+
for (const f of findings.architecture_findings ?? []) {
|
|
10582
|
+
verifyFinding(f, "architecture", evidenceIds, errors, invalidEvidence);
|
|
10583
|
+
}
|
|
10584
|
+
for (const f of findings.security_findings ?? []) {
|
|
10585
|
+
verifyFinding(f, "security", evidenceIds, errors, invalidEvidence);
|
|
10586
|
+
}
|
|
10587
|
+
const ids = [...findings.architecture_findings ?? [], ...findings.security_findings ?? []].map((f) => f.id);
|
|
10588
|
+
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
10589
|
+
for (const id of new Set(dupes)) errors.push(`duplicate finding id "${id}"`);
|
|
10590
|
+
return {
|
|
10591
|
+
schema_valid: errors.length === 0,
|
|
10592
|
+
evidence_ids_valid: !invalidEvidence.value,
|
|
10593
|
+
errors
|
|
10594
|
+
};
|
|
10595
|
+
}
|
|
10596
|
+
|
|
10597
|
+
// src/review/run.ts
|
|
10598
|
+
function defaultRun2(args, cwd) {
|
|
10599
|
+
const res = spawnSync("git", args, {
|
|
10600
|
+
cwd,
|
|
10601
|
+
encoding: "utf8",
|
|
10602
|
+
timeout: 2e4,
|
|
10603
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
10604
|
+
windowsHide: true
|
|
10605
|
+
});
|
|
10606
|
+
return { stdout: res.stdout ?? "", status: res.status ?? 1 };
|
|
10607
|
+
}
|
|
10608
|
+
var NON_CODE = /\.(md|mdx|markdown|txt|rst|adoc|png|jpe?g|gif|svg|ico|webp|woff2?|ttf|eot|pdf|csv|snap|lock)$/i;
|
|
10609
|
+
var NON_CODE_DIRS = /(^|\/)(docs?|\.github\/ISSUE_TEMPLATE|changelog|marketing)\//i;
|
|
10610
|
+
var DEPENDENCY_MANIFEST = /(^|\/)(package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock|.*\.csproj|packages\.lock\.json|Directory\.Packages\.props|requirements.*\.txt|pyproject\.toml|poetry\.lock|go\.mod|go\.sum|Cargo\.toml|Cargo\.lock|pom\.xml|build\.gradle(\.kts)?|Gemfile(\.lock)?|composer\.(json|lock))$/i;
|
|
10611
|
+
var EXEMPT_LAYERS3 = /* @__PURE__ */ new Set(["config", "shared", "testing"]);
|
|
10612
|
+
function changeClassOf(capsule, findings) {
|
|
10613
|
+
const classes = [];
|
|
10614
|
+
const roleByPath = new Map(capsule.roles.filter((r) => r.changed).map((r) => [r.path, r]));
|
|
10615
|
+
const architecturalPaths = capsule.change.ops.filter((op) => {
|
|
10616
|
+
if (DEPENDENCY_MANIFEST.test(op.path)) return false;
|
|
10617
|
+
if (NON_CODE.test(op.path) || NON_CODE_DIRS.test(op.path)) return false;
|
|
10618
|
+
const role = roleByPath.get(op.path);
|
|
10619
|
+
return role === void 0 || !EXEMPT_LAYERS3.has(role.layer);
|
|
10620
|
+
});
|
|
10621
|
+
if (findings.architecture > 0 || capsule.change.added_edges.length > 0 || architecturalPaths.length > 0) {
|
|
10622
|
+
classes.push("architecture");
|
|
10623
|
+
}
|
|
10624
|
+
const dependencyChanges = capsule.change.ops.filter((op) => DEPENDENCY_MANIFEST.test(op.path));
|
|
10625
|
+
if (findings.security > 0 || capsule.security.length > 0 || dependencyChanges.length > 0) {
|
|
10626
|
+
classes.push("security");
|
|
10627
|
+
}
|
|
10628
|
+
return classes.length > 0 ? classes : ["none"];
|
|
10629
|
+
}
|
|
10630
|
+
async function runReview(opts) {
|
|
10631
|
+
const root3 = path51.resolve(opts.root);
|
|
10632
|
+
const run = opts.run ?? defaultRun2;
|
|
10633
|
+
if (!isGitRepo(root3, run)) {
|
|
10634
|
+
throw new CliError(
|
|
10635
|
+
`\`vg review\` needs a git repository \u2014 ${root3} is not one (or git is not on PATH)`,
|
|
10636
|
+
ExitCode.USAGE_ERROR
|
|
10637
|
+
);
|
|
10638
|
+
}
|
|
10639
|
+
const change = collectChangeSet(root3, opts.base, run);
|
|
10640
|
+
const repoRoot = change.topLevel;
|
|
10641
|
+
const config = loadReviewConfig(repoRoot, opts.base, run);
|
|
10642
|
+
const graph = loadGraph(root3, opts.graphPath);
|
|
10643
|
+
if (!graph) {
|
|
10644
|
+
throw new CliError(
|
|
10645
|
+
"no code map found \u2014 run `vg` in this repository first, then `vg review`" + (opts.graphPath ? ` (looked at ${opts.graphPath})` : ""),
|
|
10646
|
+
ExitCode.ENGINE_UNAVAILABLE
|
|
10647
|
+
);
|
|
10648
|
+
}
|
|
10649
|
+
const recencyDays = fileRecencyDays(repoRoot, {}, run);
|
|
10650
|
+
const intent = readDeclaredIntent(repoRoot);
|
|
10651
|
+
const changedPathSet = new Set(change.files.map((f) => f.path.replace(/\\/g, "/")));
|
|
10652
|
+
const fileText = /* @__PURE__ */ new Map();
|
|
10653
|
+
for (const file of change.files) {
|
|
10654
|
+
if (file.op === "removed") continue;
|
|
10655
|
+
const abs = path51.join(repoRoot, file.path);
|
|
10656
|
+
try {
|
|
10657
|
+
const stat2 = fs12.statSync(abs);
|
|
10658
|
+
if (stat2.isFile() && stat2.size <= 2 * 1024 * 1024) {
|
|
10659
|
+
fileText.set(file.path.replace(/\\/g, "/"), fs12.readFileSync(abs, "utf8"));
|
|
10660
|
+
}
|
|
10661
|
+
} catch {
|
|
10662
|
+
}
|
|
10663
|
+
}
|
|
10664
|
+
const baseFileText = readBaseFileTexts(change, run);
|
|
10665
|
+
const profile = opts.base ? "ci-wide" : "interactive-narrow";
|
|
10666
|
+
const compiled = compileCapsule({
|
|
10667
|
+
graph,
|
|
10668
|
+
change,
|
|
10669
|
+
config,
|
|
10670
|
+
profile,
|
|
10671
|
+
repoPseudonym: repoPseudonym(change.remote, repoRoot),
|
|
10672
|
+
recencyDays,
|
|
10673
|
+
intent,
|
|
10674
|
+
baseFileText,
|
|
10675
|
+
headFileText: fileText
|
|
10676
|
+
});
|
|
10677
|
+
const capsule = compiled.capsule;
|
|
10678
|
+
const diffArgs = opts.base ? ["diff", "-U3", "-M", `${change.baseSha}..HEAD`] : ["diff", "-U3", "-M", "HEAD"];
|
|
10679
|
+
const removedLines = removedLinesFromDiff(run(diffArgs, repoRoot).stdout);
|
|
10680
|
+
const peerFileText = /* @__PURE__ */ new Map();
|
|
10681
|
+
const ROUTE_LAYERS = /* @__PURE__ */ new Set(["routing", "middleware", "presentation"]);
|
|
10682
|
+
for (const node2 of graph.nodes) {
|
|
10683
|
+
const rel = node2.file.replace(/\\/g, "/");
|
|
10684
|
+
if (peerFileText.has(rel) || fileText.has(rel)) continue;
|
|
10685
|
+
if (peerFileText.size >= 300) break;
|
|
10686
|
+
const cls = classifyFile(rel, "unknown");
|
|
10687
|
+
if (!cls || !ROUTE_LAYERS.has(cls.layer)) continue;
|
|
10688
|
+
try {
|
|
10689
|
+
const abs = path51.join(repoRoot, rel);
|
|
10690
|
+
const stat2 = fs12.statSync(abs);
|
|
10691
|
+
if (stat2.isFile() && stat2.size <= 512 * 1024) peerFileText.set(rel, fs12.readFileSync(abs, "utf8"));
|
|
10692
|
+
} catch {
|
|
10693
|
+
}
|
|
10694
|
+
}
|
|
10695
|
+
const similarity = new SimilarityIndex();
|
|
10696
|
+
const changedBodies = [];
|
|
10697
|
+
const FUNCTION_KINDS = /* @__PURE__ */ new Set(["function", "method"]);
|
|
10698
|
+
const sourceCache = /* @__PURE__ */ new Map();
|
|
10699
|
+
const linesOf = (rel) => {
|
|
10700
|
+
if (sourceCache.has(rel)) return sourceCache.get(rel);
|
|
10701
|
+
let value = null;
|
|
10702
|
+
try {
|
|
10703
|
+
const abs = path51.join(repoRoot, rel);
|
|
10704
|
+
const stat2 = fs12.statSync(abs);
|
|
10705
|
+
if (stat2.isFile() && stat2.size <= 1024 * 1024) value = fs12.readFileSync(abs, "utf8").split("\n");
|
|
10706
|
+
} catch {
|
|
10707
|
+
value = null;
|
|
10708
|
+
}
|
|
10709
|
+
sourceCache.set(rel, value);
|
|
10710
|
+
return value;
|
|
10711
|
+
};
|
|
10712
|
+
for (const node2 of graph.nodes) {
|
|
10713
|
+
if (!FUNCTION_KINDS.has(node2.kind)) continue;
|
|
10714
|
+
const rel = node2.file.replace(/\\/g, "/");
|
|
10715
|
+
if (!isComparable(rel)) continue;
|
|
10716
|
+
const lines = linesOf(rel);
|
|
10717
|
+
if (!lines) continue;
|
|
10718
|
+
const text = lines.slice(node2.span.start - 1, node2.span.end).join("\n");
|
|
10719
|
+
if (!text.trim()) continue;
|
|
10720
|
+
const body = {
|
|
10721
|
+
id: node2.id,
|
|
10722
|
+
name: node2.name,
|
|
10723
|
+
file: rel,
|
|
10724
|
+
startLine: node2.span.start,
|
|
10725
|
+
endLine: node2.span.end,
|
|
10726
|
+
text
|
|
10727
|
+
};
|
|
10728
|
+
if (changedPathSet.has(rel)) changedBodies.push(body);
|
|
10729
|
+
else similarity.add(body);
|
|
10730
|
+
}
|
|
10731
|
+
const scan = runScanners({
|
|
10732
|
+
capsule,
|
|
10733
|
+
config,
|
|
10734
|
+
removedLines,
|
|
10735
|
+
fileText,
|
|
10736
|
+
vulnerablePackages: vulnerablePackagesFromScan(repoRoot),
|
|
10737
|
+
votes: compiled.votes,
|
|
10738
|
+
peerFileText,
|
|
10739
|
+
similarity,
|
|
10740
|
+
changedBodies,
|
|
10741
|
+
dataAccess: compiled.dataAccess,
|
|
10742
|
+
changedPaths: changedPathSet
|
|
10743
|
+
});
|
|
10744
|
+
let findings = {
|
|
10745
|
+
schema_version: FINDINGS_SCHEMA,
|
|
10746
|
+
change_class: changeClassOf(capsule, {
|
|
10747
|
+
architecture: scan.architecture.length,
|
|
10748
|
+
security: scan.security.length
|
|
10749
|
+
}),
|
|
10750
|
+
architecture_findings: scan.architecture,
|
|
10751
|
+
security_findings: scan.security,
|
|
10752
|
+
unknowns: [.../* @__PURE__ */ new Set([...compiled.unknowns, ...scan.unknowns])],
|
|
10753
|
+
required_checks: scan.requiredChecks
|
|
10754
|
+
};
|
|
10755
|
+
let modelId = "none";
|
|
10756
|
+
let quantization = null;
|
|
10757
|
+
if (opts.explain) {
|
|
10758
|
+
const explainImpl = opts.explainImpl ?? (await import('./explain-EXPJTCB5.js')).explainFindings;
|
|
10759
|
+
const explained = await explainImpl(capsule, findings, { offline: opts.offline });
|
|
10760
|
+
findings = explained.findings;
|
|
10761
|
+
modelId = explained.model;
|
|
10762
|
+
quantization = explained.quantization;
|
|
10763
|
+
}
|
|
10764
|
+
const verification = verifyFindings(findings, capsule);
|
|
10765
|
+
const policy = applyReviewPolicy(capsule, findings, config, verification);
|
|
10766
|
+
const createdAt = opts.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10767
|
+
const capsuleDigest = digest(capsule);
|
|
10768
|
+
const findingsDigest = digest(findings);
|
|
10769
|
+
const evidenceDigest = digest(capsule.evidence);
|
|
10770
|
+
const protectedCount = [...findings.architecture_findings, ...findings.security_findings].filter(
|
|
10771
|
+
(f) => f.protected_finding === true
|
|
10772
|
+
).length;
|
|
10773
|
+
const receipt = {
|
|
10774
|
+
schema_version: RECEIPT_SCHEMA,
|
|
10775
|
+
receipt_id: receiptId(Date.parse(createdAt) || 0, `${capsuleDigest}${findingsDigest}`),
|
|
10776
|
+
created_at: createdAt,
|
|
10777
|
+
workspace_id: opts.workspaceId ?? null,
|
|
10778
|
+
repo: {
|
|
10779
|
+
name: change.remote ? change.remote.split("/").slice(-2).join("/") : path51.basename(repoRoot),
|
|
10780
|
+
remote: change.remote,
|
|
10781
|
+
repo_key: repoKey(change.remote, repoRoot)
|
|
10782
|
+
},
|
|
10783
|
+
git: {
|
|
10784
|
+
base_sha: change.baseSha,
|
|
10785
|
+
head_sha: change.headSha,
|
|
10786
|
+
merge_base: change.mergeBase,
|
|
10787
|
+
ref: change.ref,
|
|
10788
|
+
dirty: change.dirty,
|
|
10789
|
+
dirty_tree_hash: change.dirtyTreeHash
|
|
10790
|
+
},
|
|
10791
|
+
decision: policy.decision,
|
|
10792
|
+
enforcement: config.enforcement,
|
|
10793
|
+
quick_path: policy.quickPath,
|
|
10794
|
+
change_class: findings.change_class,
|
|
10795
|
+
counts: {
|
|
10796
|
+
architecture: findings.architecture_findings.length,
|
|
10797
|
+
security: findings.security_findings.length,
|
|
10798
|
+
protected: protectedCount,
|
|
10799
|
+
unknowns: findings.unknowns.length
|
|
10800
|
+
},
|
|
10801
|
+
findings,
|
|
10802
|
+
versions: {
|
|
10803
|
+
cli: VERSION,
|
|
10804
|
+
graph_schema: graph?.schemaVersion ?? "none",
|
|
10805
|
+
policy: REVIEW_POLICY_VERSION,
|
|
10806
|
+
model: modelId,
|
|
10807
|
+
quantization,
|
|
10808
|
+
capsule_schema: CAPSULE_SCHEMA
|
|
10809
|
+
},
|
|
10810
|
+
digests: {
|
|
10811
|
+
capsule: capsuleDigest,
|
|
10812
|
+
findings: findingsDigest,
|
|
10813
|
+
evidence: evidenceDigest,
|
|
10814
|
+
receipt: ""
|
|
10815
|
+
},
|
|
10816
|
+
verification: {
|
|
10817
|
+
protected_false_bless: policy.protectedFalseBless,
|
|
10818
|
+
schema_valid: verification.schema_valid,
|
|
10819
|
+
evidence_ids_valid: verification.evidence_ids_valid
|
|
10820
|
+
},
|
|
10821
|
+
signature: null
|
|
10822
|
+
};
|
|
10823
|
+
receipt.digests.receipt = receiptDigest(receipt);
|
|
10824
|
+
return {
|
|
10825
|
+
receipt,
|
|
10826
|
+
capsule,
|
|
10827
|
+
config,
|
|
10828
|
+
verification,
|
|
10829
|
+
reasons: policy.reasons,
|
|
10830
|
+
votes: compiled.votes,
|
|
10831
|
+
intent: {
|
|
10832
|
+
target: capsule.patterns.declared_target_pattern,
|
|
10833
|
+
sources: intent.sources
|
|
10834
|
+
},
|
|
10835
|
+
repoRoot,
|
|
10836
|
+
budget: {
|
|
10837
|
+
estimatedTokens: compiled.estimatedTokens,
|
|
10838
|
+
median: compiled.budget.median,
|
|
10839
|
+
cap: compiled.budget.cap,
|
|
10840
|
+
trimmed: compiled.trimmed
|
|
10841
|
+
}
|
|
10842
|
+
};
|
|
10843
|
+
}
|
|
10844
|
+
var CONTEXT_PATH = ".vibgrate/review-context.md";
|
|
10845
|
+
var BLOCK_START = "<!-- vibgrate:review-context:start -->";
|
|
10846
|
+
var BLOCK_END = "<!-- vibgrate:review-context:end -->";
|
|
10847
|
+
var MAX_CONVENTIONS = 8;
|
|
10848
|
+
var MAX_OPEN_FINDINGS = 10;
|
|
10849
|
+
function renderContext(input) {
|
|
10850
|
+
const { receipt, votes, declaredTarget } = input;
|
|
10851
|
+
const findings = [...receipt.findings.architecture_findings, ...receipt.findings.security_findings];
|
|
10852
|
+
const lines = [];
|
|
10853
|
+
lines.push("# Review context");
|
|
10854
|
+
lines.push("");
|
|
10855
|
+
lines.push(
|
|
10856
|
+
"Generated by `vg review --write-context`. It records what Vibgrate Review knows about this repository so an agent does not have to rediscover it. Commit it."
|
|
10857
|
+
);
|
|
10858
|
+
lines.push("");
|
|
10859
|
+
lines.push("## The architecture this repository is aiming at");
|
|
10860
|
+
lines.push("");
|
|
10861
|
+
if (declaredTarget) {
|
|
10862
|
+
lines.push(`**Declared target: \`${declaredTarget}\`.**`);
|
|
10863
|
+
if (input.intentSources.length > 0) {
|
|
10864
|
+
lines.push("");
|
|
10865
|
+
lines.push(`Declared in ${input.intentSources.map((s) => `\`${s}\``).join(", ")}.`);
|
|
10866
|
+
}
|
|
10867
|
+
lines.push("");
|
|
10868
|
+
lines.push(
|
|
10869
|
+
"Write new code to match this target, **not** to match whatever most of the existing code does. Where the two disagree, the target wins and the existing code is the thing being migrated."
|
|
10870
|
+
);
|
|
10871
|
+
} else {
|
|
10872
|
+
lines.push("**No target architecture has been declared.**");
|
|
10873
|
+
lines.push("");
|
|
10874
|
+
lines.push(
|
|
10875
|
+
"The conventions below are what the code currently does. They are an observation, not an instruction \u2014 they may be exactly what the team is trying to move away from. Set `target_pattern` in `.vibgrate/review.toml` to make the intended architecture explicit, and Review will hold changes to it."
|
|
10876
|
+
);
|
|
10877
|
+
}
|
|
10878
|
+
lines.push("");
|
|
10879
|
+
const decided = votes.filter((v) => v.reason === "dominant" && v.dominant).slice(0, MAX_CONVENTIONS);
|
|
10880
|
+
lines.push("## What the existing code does");
|
|
10881
|
+
lines.push("");
|
|
10882
|
+
if (decided.length === 0) {
|
|
10883
|
+
lines.push("No peer group was consistent enough to establish a convention.");
|
|
10884
|
+
} else {
|
|
10885
|
+
lines.push("| Peer group | Convention | Share | Follow the example in |");
|
|
10886
|
+
lines.push("|---|---|---:|---|");
|
|
10887
|
+
for (const vote of decided) {
|
|
10888
|
+
lines.push(
|
|
10889
|
+
`| \`${vote.group}\` | ${describePattern(vote.dominant)} | ${(vote.share * 100).toFixed(0)}% | ${vote.exemplars.slice(0, 2).map((e) => `\`${e}\``).join(", ") || "\u2014"} |`
|
|
10890
|
+
);
|
|
10891
|
+
}
|
|
10892
|
+
}
|
|
10893
|
+
const noConvention = votes.filter((v) => v.reason === "no_convention");
|
|
10894
|
+
if (noConvention.length > 0) {
|
|
10895
|
+
lines.push("");
|
|
10896
|
+
lines.push(
|
|
10897
|
+
`${noConvention.length} peer group(s) have **no** convention \u2014 the patterns are too evenly split for one to be normal. Pick the one that matches the declared target rather than copying the nearest file.`
|
|
10898
|
+
);
|
|
10899
|
+
}
|
|
10900
|
+
lines.push("");
|
|
10901
|
+
lines.push("## Open findings on the last reviewed change");
|
|
10902
|
+
lines.push("");
|
|
10903
|
+
lines.push(
|
|
10904
|
+
`Decision: \`${receipt.decision}\` \xB7 ${receipt.counts.architecture} architecture \xB7 ${receipt.counts.security} security \xB7 ${receipt.counts.protected} protected \xB7 at \`${receipt.git.head_sha.slice(0, 8)}\`.`
|
|
10905
|
+
);
|
|
10906
|
+
lines.push("");
|
|
10907
|
+
if (findings.length === 0) {
|
|
10908
|
+
lines.push("None.");
|
|
10909
|
+
} else {
|
|
10910
|
+
for (const f of findings.slice(0, MAX_OPEN_FINDINGS)) {
|
|
10911
|
+
const badge = f.protected_finding ? " **(protected \u2014 cannot be waived)**" : "";
|
|
10912
|
+
lines.push(`- \`${f.kind}\`${badge} \u2014 ${f.claim} \u2192 ${f.remediation}`);
|
|
10913
|
+
}
|
|
10914
|
+
if (findings.length > MAX_OPEN_FINDINGS) {
|
|
10915
|
+
lines.push(`- \u2026and ${findings.length - MAX_OPEN_FINDINGS} more (\`vg review\`).`);
|
|
10916
|
+
}
|
|
10917
|
+
}
|
|
10918
|
+
lines.push("");
|
|
10919
|
+
lines.push("## If you are an AI agent working in this repository");
|
|
10920
|
+
lines.push("");
|
|
10921
|
+
lines.push(
|
|
10922
|
+
declaredTarget ? `1. Match the declared \`${declaredTarget}\` architecture above, even where existing code does not.` : "1. There is no declared architecture. Match the nearest convention above, and say so in your PR description."
|
|
10923
|
+
);
|
|
10924
|
+
lines.push("2. Before writing a new function, check whether one already does the job \u2014 `vg review` reports re-implementations.");
|
|
10925
|
+
lines.push("3. A new mutating route needs an authorization guard unless the surrounding routes are deliberately public.");
|
|
10926
|
+
lines.push("4. Run `vg review` before you finish. A `protected` finding cannot be waived \u2014 it must be fixed.");
|
|
10927
|
+
lines.push("");
|
|
10928
|
+
lines.push(
|
|
10929
|
+
"<sub>Review reports change integrity. It does not prove code is secure, and absence of findings is not a certification.</sub>"
|
|
10930
|
+
);
|
|
10931
|
+
return lines.join("\n");
|
|
10932
|
+
}
|
|
10933
|
+
function writeContextFile(root3, input) {
|
|
10934
|
+
const target = path51.join(root3, CONTEXT_PATH);
|
|
10935
|
+
fs12.mkdirSync(path51.dirname(target), { recursive: true });
|
|
10936
|
+
fs12.writeFileSync(target, `${renderContext(input)}
|
|
10937
|
+
`, "utf8");
|
|
10938
|
+
return CONTEXT_PATH;
|
|
10939
|
+
}
|
|
10940
|
+
function injectContextBlock(existing, body) {
|
|
10941
|
+
const block = `${BLOCK_START}
|
|
10942
|
+
${body}
|
|
10943
|
+
${BLOCK_END}`;
|
|
10944
|
+
const startAt = existing.indexOf(BLOCK_START);
|
|
10945
|
+
const endAt = existing.indexOf(BLOCK_END);
|
|
10946
|
+
if (startAt !== -1 && endAt !== -1 && endAt > startAt) {
|
|
10947
|
+
return existing.slice(0, startAt) + block + existing.slice(endAt + BLOCK_END.length);
|
|
10948
|
+
}
|
|
10949
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
10950
|
+
return `${existing}${separator}${block}
|
|
10951
|
+
`;
|
|
10952
|
+
}
|
|
10953
|
+
var PREPARE_LOCK_STALE_MS = 10 * 60 * 1e3;
|
|
10954
|
+
async function ensureCodeMap(opts) {
|
|
10955
|
+
const { root: root3, graphPath } = opts;
|
|
10956
|
+
const haveMap = loadGraph(root3, graphPath) !== null;
|
|
10957
|
+
if (opts.autoBuild === false) {
|
|
10958
|
+
return { action: haveMap ? "up-to-date" : "skipped", reason: haveMap ? void 0 : "auto-build disabled" };
|
|
10959
|
+
}
|
|
10960
|
+
if (graphPath) {
|
|
10961
|
+
return { action: haveMap ? "up-to-date" : "skipped", reason: haveMap ? void 0 : "an explicit --graph path was given" };
|
|
10962
|
+
}
|
|
10963
|
+
const bar = opts.quiet ? void 0 : new ProgressBar("mapping the repository");
|
|
10964
|
+
const onParseProgress = (done, total) => {
|
|
10965
|
+
opts.onProgress?.(done, total);
|
|
10966
|
+
bar?.update(done, total);
|
|
10967
|
+
};
|
|
10968
|
+
try {
|
|
10969
|
+
if (haveMap) {
|
|
10970
|
+
const outcome = await refreshIfStale(root3, { onParseProgress });
|
|
10971
|
+
if (outcome.status === "refreshed") {
|
|
10972
|
+
return { action: "refreshed", files: outcome.totalFiles, ms: outcome.ms };
|
|
10973
|
+
}
|
|
10974
|
+
if (outcome.status === "error") {
|
|
10975
|
+
return { action: "skipped", reason: outcome.message };
|
|
10976
|
+
}
|
|
10977
|
+
return { action: "up-to-date" };
|
|
10978
|
+
}
|
|
10979
|
+
return await firstBuild(root3, onParseProgress);
|
|
10980
|
+
} finally {
|
|
10981
|
+
bar?.done();
|
|
10982
|
+
}
|
|
10983
|
+
}
|
|
10984
|
+
async function firstBuild(root3, onParseProgress) {
|
|
10985
|
+
const lock = path51.join(cacheDir(root3), "refresh.lock");
|
|
10986
|
+
if (!acquireLock(lock, PREPARE_LOCK_STALE_MS)) {
|
|
10987
|
+
return { action: "skipped", reason: "another vg process is building the map" };
|
|
10988
|
+
}
|
|
10989
|
+
const start = Date.now();
|
|
10990
|
+
try {
|
|
10991
|
+
const exclude = mergeExcludes(root3, void 0);
|
|
10992
|
+
const result = await buildGraph({ root: root3, exclude, onParseProgress });
|
|
10993
|
+
writeArtifacts(result.graph, { root: root3, html: false, report: false });
|
|
10994
|
+
writeSnapshot(root3, result.graph.provenance.corpusHash, result.fileStats, { exclude });
|
|
10995
|
+
return { action: "built", files: result.totalFiles, ms: Date.now() - start };
|
|
10996
|
+
} catch (err) {
|
|
10997
|
+
return { action: "skipped", reason: err.message };
|
|
10998
|
+
} finally {
|
|
10999
|
+
releaseLock(lock);
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
function reviewPolicyState(root3, base, run) {
|
|
11003
|
+
const inRef = (ref) => {
|
|
11004
|
+
const res = run(["show", `${ref}:${REVIEW_CONFIG_PATH}`], root3);
|
|
11005
|
+
return res.status === 0 && res.stdout.trim().length > 0;
|
|
11006
|
+
};
|
|
11007
|
+
if (inRef("HEAD")) return { present: true, where: "head" };
|
|
11008
|
+
if (fs12.existsSync(path51.join(root3, REVIEW_CONFIG_PATH))) {
|
|
11009
|
+
return { present: true, where: "working-tree" };
|
|
11010
|
+
}
|
|
11011
|
+
return { present: false, where: null };
|
|
11012
|
+
}
|
|
11013
|
+
function seedReviewPolicy(opts) {
|
|
11014
|
+
const target = path51.join(opts.root, REVIEW_CONFIG_PATH);
|
|
11015
|
+
const pattern = opts.observedPattern ?? null;
|
|
11016
|
+
if (fs12.existsSync(target)) return { written: false, path: REVIEW_CONFIG_PATH, targetPattern: pattern };
|
|
11017
|
+
fs12.mkdirSync(path51.dirname(target), { recursive: true });
|
|
11018
|
+
fs12.writeFileSync(target, renderReviewPolicy(pattern), "utf8");
|
|
11019
|
+
return { written: true, path: REVIEW_CONFIG_PATH, targetPattern: pattern };
|
|
11020
|
+
}
|
|
11021
|
+
function renderReviewPolicy(observedPattern) {
|
|
11022
|
+
const lines = [
|
|
11023
|
+
"# Vibgrate Review policy \u2014 written by `vg review` on its first run.",
|
|
11024
|
+
"# Commit this file: Review reads it from the *base branch*, so a pull",
|
|
11025
|
+
"# request cannot weaken the policy that judges it.",
|
|
11026
|
+
"",
|
|
11027
|
+
"[review]",
|
|
11028
|
+
'# "advisory" reports without ever gating. Switch to "enforced" when you',
|
|
11029
|
+
"# want `fail_on` to decide the exit code in CI.",
|
|
11030
|
+
'enforcement = "advisory"',
|
|
11031
|
+
'fail_on = "fail"',
|
|
11032
|
+
""
|
|
11033
|
+
];
|
|
11034
|
+
if (observedPattern) {
|
|
11035
|
+
lines.push(
|
|
11036
|
+
`# Derived from the layering this repository already exhibits. Change it`,
|
|
11037
|
+
`# to the shape you want \u2014 Review judges changes against this, not against`,
|
|
11038
|
+
`# the majority.`,
|
|
11039
|
+
`target_pattern = "${observedPattern}"`
|
|
11040
|
+
);
|
|
11041
|
+
} else {
|
|
11042
|
+
lines.push(
|
|
11043
|
+
"# No single layering shape dominates this repository yet, so nothing is",
|
|
11044
|
+
'# declared. Set one (e.g. "clean", "layered", "hexagonal") to have Review',
|
|
11045
|
+
"# judge layer traversals instead of reporting them as unknown.",
|
|
11046
|
+
'# target_pattern = "clean"'
|
|
11047
|
+
);
|
|
11048
|
+
}
|
|
11049
|
+
lines.push(
|
|
11050
|
+
"",
|
|
11051
|
+
"# Layer pairs an author may traverse without it counting as a regression.",
|
|
11052
|
+
"approved_exceptions = []",
|
|
11053
|
+
"",
|
|
11054
|
+
"# Protected findings can never be blessed into a pass.",
|
|
11055
|
+
"[review.protected]",
|
|
11056
|
+
"unguarded_entrypoint = true",
|
|
11057
|
+
"known_vulnerable_dependency = true",
|
|
11058
|
+
"validated_taint = true",
|
|
11059
|
+
""
|
|
11060
|
+
);
|
|
11061
|
+
return `${lines.join("\n")}`;
|
|
11062
|
+
}
|
|
11063
|
+
|
|
11064
|
+
// src/commands/review.ts
|
|
11065
|
+
var FORMATS = ["text", "json", "sarif", "md"];
|
|
11066
|
+
function registerReview(program) {
|
|
11067
|
+
const cmd = program.command("review").description("Vibgrate Review \u2014 architecture + security-control review of the current change, locally").option("--base <ref>", "review HEAD against the merge-base with <ref> (e.g. origin/main)").option("--format <fmt>", `output format (${FORMATS.join(" | ")})`, "text").option("-o, --out <file>", "write the formatted result to a file").option("--push", "send the receipt to Vibgrate Cloud (needs a DSN)").option(
|
|
11068
|
+
"--fail-on <level>",
|
|
11069
|
+
'gate CI on this decision level (none | fail | needs_review). Omit to use .vibgrate/review.toml; gating is off unless enforcement = "enforced"'
|
|
11070
|
+
).option("--explain", "add local-model explanations (requires a local model; fails closed without one)").option("--dsn <dsn>", "DSN token for --push (or use VIBGRATE_DSN / `vg login`)").option("--strict", "fail the command when --push fails").option("--include-spans", "include evidence line ranges in the pushed receipt").option("--include-snippets", "include capped source snippets in the pushed receipt (explicit opt-in)").option("--no-auto-build", "do not build or refresh the code map \u2014 fail with exit 6 when it is missing or stale").option("--no-setup", "do not write a starter .vibgrate/review.toml when the repository has no review policy").option("--write-context", "write .vibgrate/review-context.md \u2014 committed agent memory").option(
|
|
11071
|
+
"--inject-context [file]",
|
|
11072
|
+
"update the managed review block inside an instruction file (default: CLAUDE.md)"
|
|
11073
|
+
).action(async function(opts) {
|
|
11074
|
+
const global = readGlobal(this);
|
|
11075
|
+
const root3 = rootOf(global);
|
|
11076
|
+
if (!FORMATS.includes(opts.format)) {
|
|
11077
|
+
throw new CliError(`unknown --format "${opts.format}" (expected ${FORMATS.join(", ")})`, ExitCode.USAGE_ERROR);
|
|
11078
|
+
}
|
|
11079
|
+
const prepared = await ensureCodeMap({
|
|
11080
|
+
root: root3,
|
|
11081
|
+
graphPath: global.graph,
|
|
11082
|
+
autoBuild: opts.autoBuild,
|
|
11083
|
+
quiet: Boolean(global.quiet) || Boolean(global.json)
|
|
11084
|
+
});
|
|
11085
|
+
reportPrepare(prepared, Boolean(global.quiet) || Boolean(global.json));
|
|
11086
|
+
const result = await runReview({
|
|
11087
|
+
root: root3,
|
|
11088
|
+
base: opts.base,
|
|
11089
|
+
explain: opts.explain,
|
|
11090
|
+
offline: global.offline,
|
|
11091
|
+
graphPath: global.graph,
|
|
11092
|
+
generatedAt: global.generatedAt
|
|
11093
|
+
});
|
|
11094
|
+
if (opts.failOn && !FAIL_ON_LEVELS.includes(opts.failOn)) {
|
|
11095
|
+
throw new CliError(
|
|
11096
|
+
`unknown --fail-on "${opts.failOn}" (expected ${FAIL_ON_LEVELS.join(", ")})`,
|
|
11097
|
+
ExitCode.USAGE_ERROR
|
|
11098
|
+
);
|
|
11099
|
+
}
|
|
11100
|
+
const failOn = resolveFailOn(opts.failOn, result.config);
|
|
11101
|
+
const format = global.json ? "json" : opts.format;
|
|
11102
|
+
const rendered = render(result, format);
|
|
11103
|
+
if (opts.out) {
|
|
11104
|
+
const target = path51.resolve(root3, opts.out);
|
|
11105
|
+
fs12.mkdirSync(path51.dirname(target), { recursive: true });
|
|
11106
|
+
fs12.writeFileSync(target, `${rendered}
|
|
11107
|
+
`, "utf8");
|
|
11108
|
+
if (!global.quiet) {
|
|
11109
|
+
info(formatText(result));
|
|
11110
|
+
info(c.dim(` ${format} written to ${opts.out}`));
|
|
11111
|
+
}
|
|
11112
|
+
} else if (format === "text") {
|
|
11113
|
+
if (!global.quiet) info(rendered);
|
|
11114
|
+
} else {
|
|
11115
|
+
out(rendered);
|
|
11116
|
+
}
|
|
11117
|
+
maybeSeedPolicy(result, opts, Boolean(global.quiet) || Boolean(global.json));
|
|
11118
|
+
if (opts.writeContext || opts.injectContext) writeAgentContext(result, opts, Boolean(global.quiet));
|
|
11119
|
+
if (opts.push) {
|
|
11120
|
+
rejectPushWhenOffline(global.offline);
|
|
11121
|
+
await doPush(root3, result, opts, Boolean(global.quiet));
|
|
11122
|
+
}
|
|
11123
|
+
const code = exitCodeForDecision(result.receipt.decision, failOn);
|
|
11124
|
+
if (code !== 0) {
|
|
11125
|
+
info(c.red(`
|
|
11126
|
+
Failing: review decision is \`${result.receipt.decision}\` and the gate is \`${failOn}\`.`));
|
|
11127
|
+
process.exitCode = code;
|
|
11128
|
+
}
|
|
11129
|
+
});
|
|
11130
|
+
applyGlobalOptions(cmd);
|
|
11131
|
+
const explain = cmd.command("explain").description("show the evidence behind one finding from the current change").argument("<finding-id>", "a finding id from the last `vg review` run (e.g. arch-01)").option("--base <ref>", "review HEAD against the merge-base with <ref>").action(async function(findingId, opts) {
|
|
11132
|
+
const global = readGlobal(this);
|
|
11133
|
+
const explainRoot = rootOf(global);
|
|
11134
|
+
reportPrepare(
|
|
11135
|
+
await ensureCodeMap({
|
|
11136
|
+
root: explainRoot,
|
|
11137
|
+
graphPath: global.graph,
|
|
11138
|
+
quiet: Boolean(global.quiet) || Boolean(global.json)
|
|
11139
|
+
}),
|
|
11140
|
+
Boolean(global.quiet) || Boolean(global.json)
|
|
11141
|
+
);
|
|
11142
|
+
const result = await runReview({
|
|
11143
|
+
root: explainRoot,
|
|
11144
|
+
base: opts.base,
|
|
11145
|
+
offline: global.offline,
|
|
11146
|
+
graphPath: global.graph,
|
|
11147
|
+
generatedAt: global.generatedAt
|
|
11148
|
+
});
|
|
11149
|
+
const text = formatExplain(result, findingId);
|
|
11150
|
+
if (!text) {
|
|
11151
|
+
throw new CliError(
|
|
11152
|
+
`no finding "${findingId}" in this change set \u2014 run \`vg review\` to list the current findings`,
|
|
11153
|
+
ExitCode.NOT_FOUND
|
|
11154
|
+
);
|
|
11155
|
+
}
|
|
11156
|
+
if (global.json) {
|
|
11157
|
+
const all = [...result.receipt.findings.architecture_findings, ...result.receipt.findings.security_findings];
|
|
11158
|
+
const finding = all.find((f) => f.id === findingId);
|
|
11159
|
+
out(
|
|
11160
|
+
JSON.stringify(
|
|
11161
|
+
{
|
|
11162
|
+
finding,
|
|
11163
|
+
evidence: result.capsule.evidence.filter((e) => finding.evidence_ids.includes(e.id))
|
|
11164
|
+
},
|
|
11165
|
+
null,
|
|
11166
|
+
2
|
|
11167
|
+
)
|
|
11168
|
+
);
|
|
11169
|
+
return;
|
|
11170
|
+
}
|
|
11171
|
+
info(text);
|
|
11172
|
+
});
|
|
11173
|
+
applyGlobalOptions(explain);
|
|
11174
|
+
}
|
|
11175
|
+
function reportPrepare(prepared, quiet) {
|
|
11176
|
+
if (quiet) return;
|
|
11177
|
+
const secs = prepared.ms === void 0 ? "" : ` in ${(prepared.ms / 1e3).toFixed(1)}s`;
|
|
11178
|
+
if (prepared.action === "built") {
|
|
11179
|
+
info(c.dim(` code map built \u2014 ${prepared.files} files${secs}`));
|
|
11180
|
+
} else if (prepared.action === "refreshed") {
|
|
11181
|
+
info(c.dim(` code map refreshed \u2014 ${prepared.files} files${secs}`));
|
|
11182
|
+
} else if (prepared.action === "skipped" && prepared.reason) {
|
|
11183
|
+
info(c.yellow(` code map not built: ${prepared.reason}`));
|
|
11184
|
+
}
|
|
11185
|
+
}
|
|
11186
|
+
function maybeSeedPolicy(result, opts, quiet) {
|
|
11187
|
+
if (opts.setup === false || opts.base) return;
|
|
11188
|
+
if (result.config.source !== "defaults") return;
|
|
11189
|
+
if (reviewPolicyState(result.repoRoot, void 0, defaultRun).present) return;
|
|
11190
|
+
const seeded = seedReviewPolicy({
|
|
11191
|
+
root: result.repoRoot,
|
|
11192
|
+
observedPattern: result.capsule.patterns.observed_dominant_pattern
|
|
11193
|
+
});
|
|
11194
|
+
if (!seeded.written || quiet) return;
|
|
11195
|
+
info(
|
|
11196
|
+
c.green(`
|
|
11197
|
+
Review baseline ready \u2014 wrote ${seeded.path}`) + c.dim(
|
|
11198
|
+
seeded.targetPattern ? `
|
|
11199
|
+
target_pattern = "${seeded.targetPattern}" (derived from this repository). Edit it, commit it,
|
|
11200
|
+
and future changes are judged against a declared architecture rather than a guess.` : "\n No layering shape dominates yet, so target_pattern is left commented out.\n Set one and commit the file to have future changes judged against it."
|
|
11201
|
+
)
|
|
11202
|
+
);
|
|
11203
|
+
}
|
|
11204
|
+
function render(result, format) {
|
|
11205
|
+
if (format === "json") return JSON.stringify(result.receipt, null, 2);
|
|
11206
|
+
if (format === "sarif") return formatSarif(result.receipt);
|
|
11207
|
+
if (format === "md") return formatMarkdown(result);
|
|
11208
|
+
return formatText(result);
|
|
11209
|
+
}
|
|
11210
|
+
async function doPush(root3, result, opts, quiet) {
|
|
11211
|
+
const dsn = resolveDsn(opts.dsn);
|
|
11212
|
+
if (!dsn) {
|
|
11213
|
+
const message = "no DSN for --push \u2014 run `vg login`, set VIBGRATE_DSN, or pass --dsn";
|
|
11214
|
+
if (opts.strict) throw new CliError(message, ExitCode.USAGE_ERROR);
|
|
11215
|
+
info(c.yellow(` ${message}`));
|
|
11216
|
+
return;
|
|
11217
|
+
}
|
|
11218
|
+
const parsed = parseDsn$1(dsn);
|
|
11219
|
+
if (!parsed) {
|
|
11220
|
+
const message = "invalid DSN format (expected vibgrate+https://<key>:<secret>@<host>/<workspace>)";
|
|
11221
|
+
if (opts.strict) throw new CliError(message, ExitCode.USAGE_ERROR);
|
|
11222
|
+
info(c.yellow(` ${message}`));
|
|
11223
|
+
return;
|
|
11224
|
+
}
|
|
11225
|
+
const envelope = buildEnvelope(result.receipt, {
|
|
11226
|
+
workspaceId: parsed.workspaceId,
|
|
11227
|
+
pushedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11228
|
+
});
|
|
11229
|
+
const body = { ...envelope };
|
|
11230
|
+
const spans = collectSpans(root3, result.receipt, result.capsule, {
|
|
11231
|
+
includeSpans: opts.includeSpans,
|
|
11232
|
+
includeSnippets: opts.includeSnippets
|
|
11233
|
+
});
|
|
11234
|
+
if (spans) body.spans = spans;
|
|
11235
|
+
const res = await pushReceipt(parsed, body);
|
|
11236
|
+
if (!res.ok) {
|
|
11237
|
+
const message = `push failed (${res.status}) \u2014 ${res.detail ?? ""}`;
|
|
11238
|
+
if (opts.strict) throw new CliError(message, ExitCode.ERROR);
|
|
11239
|
+
info(c.yellow(` ${message}`));
|
|
11240
|
+
return;
|
|
11241
|
+
}
|
|
11242
|
+
if (!quiet) info(c.green("\u2714") + ` receipt pushed to ${res.host}`);
|
|
11243
|
+
}
|
|
11244
|
+
function writeAgentContext(result, opts, quiet) {
|
|
11245
|
+
const input = {
|
|
11246
|
+
receipt: result.receipt,
|
|
11247
|
+
votes: result.votes,
|
|
11248
|
+
declaredTarget: result.intent.target,
|
|
11249
|
+
intentSources: result.intent.sources
|
|
11250
|
+
};
|
|
11251
|
+
if (opts.writeContext) {
|
|
11252
|
+
const written = writeContextFile(result.repoRoot, input);
|
|
11253
|
+
if (!quiet) info(c.dim(` agent context written to ${written}`));
|
|
11254
|
+
}
|
|
11255
|
+
if (opts.injectContext) {
|
|
11256
|
+
const target = typeof opts.injectContext === "string" ? opts.injectContext : "CLAUDE.md";
|
|
11257
|
+
const abs = path51.resolve(result.repoRoot, target);
|
|
11258
|
+
const existing = fs12.existsSync(abs) ? fs12.readFileSync(abs, "utf8") : "";
|
|
11259
|
+
fs12.writeFileSync(abs, injectContextBlock(existing, renderContext(input)), "utf8");
|
|
11260
|
+
if (!quiet) info(c.dim(` managed review block updated in ${target}`));
|
|
11261
|
+
}
|
|
11262
|
+
}
|
|
9266
11263
|
var initCommand = new Command("init").description("Initialize vibgrate in a project").argument("[path]", "Path to initialize", ".").option("--baseline", "Create initial baseline after init").option("--yes", "Skip confirmation prompts").action(async (targetPath, opts) => {
|
|
9267
|
-
const rootDir =
|
|
9268
|
-
const vibgrateDir2 =
|
|
11264
|
+
const rootDir = path51.resolve(targetPath);
|
|
11265
|
+
const vibgrateDir2 = path51.join(rootDir, ".vibgrate");
|
|
9269
11266
|
await ensureDir(vibgrateDir2);
|
|
9270
11267
|
console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold(".vibgrate/")} directory`);
|
|
9271
|
-
const configPath =
|
|
11268
|
+
const configPath = path51.join(rootDir, "vibgrate.config.ts");
|
|
9272
11269
|
if (await pathExists(configPath)) {
|
|
9273
11270
|
console.log(chalk7.dim(" vibgrate.config.ts already exists, skipping"));
|
|
9274
11271
|
} else {
|
|
@@ -9276,7 +11273,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
9276
11273
|
console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold("vibgrate.config.ts")}`);
|
|
9277
11274
|
}
|
|
9278
11275
|
if (opts.baseline) {
|
|
9279
|
-
const { runBaseline } = await import('./baseline-
|
|
11276
|
+
const { runBaseline } = await import('./baseline-YL2IZZ4Y.js');
|
|
9280
11277
|
await runBaseline(rootDir);
|
|
9281
11278
|
}
|
|
9282
11279
|
console.log("");
|
|
@@ -9379,7 +11376,7 @@ function collectPackageImports(graph, ecosystem, pkg) {
|
|
|
9379
11376
|
};
|
|
9380
11377
|
}
|
|
9381
11378
|
async function firstReferenceLine(rootDir, relFile, identifier, readFile3) {
|
|
9382
|
-
const content = await readFile3(
|
|
11379
|
+
const content = await readFile3(path51.join(rootDir, relFile));
|
|
9383
11380
|
if (!content) return null;
|
|
9384
11381
|
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9385
11382
|
const re = new RegExp(`\\b${escaped}\\b`);
|
|
@@ -9417,9 +11414,9 @@ function sitesFromImportFiles(files, limit = 10) {
|
|
|
9417
11414
|
}
|
|
9418
11415
|
async function defaultReadFile(absPath) {
|
|
9419
11416
|
try {
|
|
9420
|
-
const stat2 = await
|
|
11417
|
+
const stat2 = await fs32.stat(absPath);
|
|
9421
11418
|
if (!stat2.isFile() || stat2.size > MAX_FILE_BYTES) return null;
|
|
9422
|
-
return await
|
|
11419
|
+
return await fs32.readFile(absPath, "utf8");
|
|
9423
11420
|
} catch {
|
|
9424
11421
|
return null;
|
|
9425
11422
|
}
|
|
@@ -9615,7 +11612,7 @@ var AI_ASSISTANT_DETECTORS = [
|
|
|
9615
11612
|
async function detectAiAssistant(rootDir) {
|
|
9616
11613
|
for (const detector of AI_ASSISTANT_DETECTORS) {
|
|
9617
11614
|
for (const file of detector.files) {
|
|
9618
|
-
if (await pathExists$1(
|
|
11615
|
+
if (await pathExists$1(path51.join(rootDir, file))) {
|
|
9619
11616
|
return detector.name;
|
|
9620
11617
|
}
|
|
9621
11618
|
}
|
|
@@ -9809,7 +11806,7 @@ function reportStandards(rootDir) {
|
|
|
9809
11806
|
}
|
|
9810
11807
|
async function printRuntimeSnapshot(rootDir, opts, already) {
|
|
9811
11808
|
try {
|
|
9812
|
-
const { attachVgd: attachVgd2 } = await import('./attach-
|
|
11809
|
+
const { attachVgd: attachVgd2 } = await import('./attach-JUSALQ75.js');
|
|
9813
11810
|
const { vgdRequest } = await import('./client-P5XSRFV4.js');
|
|
9814
11811
|
const { formatSemanticProgress } = await import('./semantic-status-2JFLIQQJ.js');
|
|
9815
11812
|
let repositoryId = already?.repositoryId;
|
|
@@ -9851,7 +11848,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
9851
11848
|
collectExcludes,
|
|
9852
11849
|
[]
|
|
9853
11850
|
).option("--concurrency <n>", "Max concurrent registry lookups", "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 DriftScore 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("--no-daemon", "Never auto-start or use the local runtime (vgd) \u2014 run everything in this process").option("--quiet", "Suppress promotional output (the free-plan tracking panel and the AI Context install prompt); scan results are unaffected").action(async (targetPath, opts) => {
|
|
9854
|
-
const rootDir =
|
|
11851
|
+
const rootDir = path51.resolve(targetPath);
|
|
9855
11852
|
if (!await pathExists$1(rootDir)) {
|
|
9856
11853
|
console.error(chalk7.red(`Path does not exist: ${rootDir}`));
|
|
9857
11854
|
process.exit(1);
|
|
@@ -9859,7 +11856,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
9859
11856
|
const hasDsn = !!resolveDsn(opts.dsn);
|
|
9860
11857
|
let willPush = !opts.offline && (opts.push || hasDsn);
|
|
9861
11858
|
const noLocalArtifacts = opts.localArtifacts === false;
|
|
9862
|
-
const isFirstRun = !await pathExists$1(
|
|
11859
|
+
const isFirstRun = !await pathExists$1(path51.join(rootDir, ".vibgrate", "scan_result.json"));
|
|
9863
11860
|
let pinnedRegion;
|
|
9864
11861
|
let planTier;
|
|
9865
11862
|
let upgradeUrl;
|
|
@@ -10018,7 +12015,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
10018
12015
|
const advanced = await loadAdvancedScanHook();
|
|
10019
12016
|
const artifact = await runCoreScan(rootDir, scanOpts, advanced);
|
|
10020
12017
|
if (wantGraph && builtGraph) {
|
|
10021
|
-
const { attachVgd: attachVgd2 } = await import('./attach-
|
|
12018
|
+
const { attachVgd: attachVgd2 } = await import('./attach-JUSALQ75.js');
|
|
10022
12019
|
const { ActivityLog: ActivityLog2 } = await import('./activity-MF2GNYAI.js');
|
|
10023
12020
|
const activity = new ActivityLog2();
|
|
10024
12021
|
const attached = await activity.time(
|
|
@@ -10186,11 +12183,11 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
10186
12183
|
scanned++;
|
|
10187
12184
|
if (scanned > MAX_ENTRIES2) break;
|
|
10188
12185
|
if (e.isDirectory()) {
|
|
10189
|
-
if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(
|
|
12186
|
+
if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path51.join(dir, e.name), depth + 1);
|
|
10190
12187
|
continue;
|
|
10191
12188
|
}
|
|
10192
12189
|
if (!WATCHED_FILES.has(e.name) && !WATCHED_EXT.test(e.name)) continue;
|
|
10193
|
-
const abs =
|
|
12190
|
+
const abs = path51.join(dir, e.name);
|
|
10194
12191
|
let mtimeMs;
|
|
10195
12192
|
try {
|
|
10196
12193
|
mtimeMs = fs12.statSync(abs).mtimeMs;
|
|
@@ -10199,7 +12196,7 @@ function scanStaleness(rootDir, artifactPath) {
|
|
|
10199
12196
|
}
|
|
10200
12197
|
if (mtimeMs > newest) {
|
|
10201
12198
|
newest = mtimeMs;
|
|
10202
|
-
newestChanged =
|
|
12199
|
+
newestChanged = path51.relative(rootDir, abs) || e.name;
|
|
10203
12200
|
}
|
|
10204
12201
|
}
|
|
10205
12202
|
};
|
|
@@ -10339,13 +12336,13 @@ function analyzeTree(root3, packages) {
|
|
|
10339
12336
|
if (filesSeen >= MAX_FILES) return;
|
|
10340
12337
|
if (entry.isDirectory()) {
|
|
10341
12338
|
if (SKIP_DIRS3.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
10342
|
-
walk2(
|
|
12339
|
+
walk2(path51.join(dir, entry.name), depth + 1);
|
|
10343
12340
|
continue;
|
|
10344
12341
|
}
|
|
10345
|
-
const fileEco = EXT_ECOSYSTEM[
|
|
12342
|
+
const fileEco = EXT_ECOSYSTEM[path51.extname(entry.name)];
|
|
10346
12343
|
if (!fileEco || fileEco === "unknown") continue;
|
|
10347
12344
|
filesSeen++;
|
|
10348
|
-
const full =
|
|
12345
|
+
const full = path51.join(dir, entry.name);
|
|
10349
12346
|
let content;
|
|
10350
12347
|
try {
|
|
10351
12348
|
if (fs12.statSync(full).size > MAX_FILE_BYTES2) continue;
|
|
@@ -10621,7 +12618,7 @@ function pmCommandFor(ecosystem, pkg, version, pm = "npm", opts = {}) {
|
|
|
10621
12618
|
return null;
|
|
10622
12619
|
}
|
|
10623
12620
|
}
|
|
10624
|
-
function
|
|
12621
|
+
function defaultRun3(cmd, cwd) {
|
|
10625
12622
|
const res = spawnSync(cmd.cmd, cmd.args, { cwd, stdio: "inherit", shell: false });
|
|
10626
12623
|
if (res.error) {
|
|
10627
12624
|
if (res.error.code === "ENOENT") {
|
|
@@ -10634,7 +12631,7 @@ function defaultRun(cmd, cwd) {
|
|
|
10634
12631
|
}
|
|
10635
12632
|
var ROOT_TARGET = [{ dir: ".", isWorkspaceRoot: false }];
|
|
10636
12633
|
function applyPlan(rootDir, upgrades, opts = {}) {
|
|
10637
|
-
const run = opts.run ??
|
|
12634
|
+
const run = opts.run ?? defaultRun3;
|
|
10638
12635
|
const resolveTargets = opts.resolveTargets ?? (() => ROOT_TARGET);
|
|
10639
12636
|
const results = [];
|
|
10640
12637
|
for (const u of upgrades) {
|
|
@@ -10660,7 +12657,7 @@ function applyPlan(rootDir, upgrades, opts = {}) {
|
|
|
10660
12657
|
}
|
|
10661
12658
|
const outcomes = effective.map((t) => {
|
|
10662
12659
|
const c2 = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager, { workspaceRoot: t.isWorkspaceRoot });
|
|
10663
|
-
return { t, res: run(c2,
|
|
12660
|
+
return { t, res: run(c2, path51.join(rootDir, t.dir)) };
|
|
10664
12661
|
});
|
|
10665
12662
|
const failures = outcomes.filter((o) => !o.res.ok);
|
|
10666
12663
|
if (failures.length === 0) {
|
|
@@ -10689,8 +12686,8 @@ function applyPlan(rootDir, upgrades, opts = {}) {
|
|
|
10689
12686
|
return results;
|
|
10690
12687
|
}
|
|
10691
12688
|
var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
|
|
10692
|
-
var CACHE_DIR =
|
|
10693
|
-
var CACHE_FILE =
|
|
12689
|
+
var CACHE_DIR = path51.join(os.homedir(), ".vibgrate");
|
|
12690
|
+
var CACHE_FILE = path51.join(CACHE_DIR, "update-check.json");
|
|
10694
12691
|
async function fetchLatestVersion() {
|
|
10695
12692
|
try {
|
|
10696
12693
|
const controller = new AbortController();
|
|
@@ -10717,8 +12714,8 @@ async function fetchLatestVersion() {
|
|
|
10717
12714
|
}
|
|
10718
12715
|
async function writeCache(data) {
|
|
10719
12716
|
try {
|
|
10720
|
-
await
|
|
10721
|
-
await
|
|
12717
|
+
await fs32.mkdir(CACHE_DIR, { recursive: true });
|
|
12718
|
+
await fs32.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
10722
12719
|
} catch {
|
|
10723
12720
|
}
|
|
10724
12721
|
}
|
|
@@ -10753,8 +12750,8 @@ function buildDeferredUpdateScript(opts) {
|
|
|
10753
12750
|
}
|
|
10754
12751
|
function scheduleDeferredUpdate(command, pid = process.pid, dir = os.tmpdir()) {
|
|
10755
12752
|
const stamp = `${pid}-${process.hrtime.bigint().toString(36)}`;
|
|
10756
|
-
const scriptPath =
|
|
10757
|
-
const logPath =
|
|
12753
|
+
const scriptPath = path51.join(dir, `vg-update-${stamp}.cmd`);
|
|
12754
|
+
const logPath = path51.join(dir, `vg-update-${stamp}.log`);
|
|
10758
12755
|
try {
|
|
10759
12756
|
fs12.writeFileSync(scriptPath, buildDeferredUpdateScript({ pid, command, logPath }), "utf8");
|
|
10760
12757
|
const child = spawn(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", scriptPath], {
|
|
@@ -10810,14 +12807,14 @@ async function findGlobalInstall(pkg = PACKAGE_NAME, run = defaultRunner) {
|
|
|
10810
12807
|
pm: "yarn",
|
|
10811
12808
|
root: () => {
|
|
10812
12809
|
const dir = tryRun("yarn global dir");
|
|
10813
|
-
return dir ?
|
|
12810
|
+
return dir ? path51.join(dir, "node_modules") : null;
|
|
10814
12811
|
}
|
|
10815
12812
|
},
|
|
10816
|
-
{ pm: "bun", root: () =>
|
|
12813
|
+
{ pm: "bun", root: () => path51.join(os.homedir(), ".bun", "install", "global", "node_modules") }
|
|
10817
12814
|
];
|
|
10818
12815
|
for (const probe of probes) {
|
|
10819
12816
|
const root3 = probe.root();
|
|
10820
|
-
if (root3 && await pathExists(
|
|
12817
|
+
if (root3 && await pathExists(path51.join(root3, pkg))) return probe.pm;
|
|
10821
12818
|
}
|
|
10822
12819
|
return null;
|
|
10823
12820
|
}
|
|
@@ -10836,15 +12833,15 @@ function getGlobalUpdateCommand(pm, pkg, version) {
|
|
|
10836
12833
|
}
|
|
10837
12834
|
}
|
|
10838
12835
|
async function detectPackageManager(cwd) {
|
|
10839
|
-
if (await pathExists(
|
|
10840
|
-
if (await pathExists(
|
|
10841
|
-
if (await pathExists(
|
|
12836
|
+
if (await pathExists(path51.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
12837
|
+
if (await pathExists(path51.join(cwd, "bun.lockb"))) return "bun";
|
|
12838
|
+
if (await pathExists(path51.join(cwd, "yarn.lock"))) return "yarn";
|
|
10842
12839
|
return "npm";
|
|
10843
12840
|
}
|
|
10844
12841
|
async function detectWorkspaceRoot(cwd) {
|
|
10845
|
-
if (await pathExists(
|
|
12842
|
+
if (await pathExists(path51.join(cwd, "pnpm-workspace.yaml"))) return true;
|
|
10846
12843
|
try {
|
|
10847
|
-
const pkgPath =
|
|
12844
|
+
const pkgPath = path51.join(cwd, "package.json");
|
|
10848
12845
|
const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
|
|
10849
12846
|
const pkg = JSON.parse(raw);
|
|
10850
12847
|
return pkg.workspaces != null;
|
|
@@ -10871,7 +12868,7 @@ function getInstallCommand(pm, pkg, version, isDev, opts = {}) {
|
|
|
10871
12868
|
}
|
|
10872
12869
|
async function getLocalDependencyState(cwd, pkg = PACKAGE_NAME) {
|
|
10873
12870
|
try {
|
|
10874
|
-
const pkgPath =
|
|
12871
|
+
const pkgPath = path51.join(cwd, "package.json");
|
|
10875
12872
|
const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
|
|
10876
12873
|
const parsed = JSON.parse(raw);
|
|
10877
12874
|
if (parsed.devDependencies?.[pkg]) return "dev";
|
|
@@ -10886,18 +12883,18 @@ async function confirmWorkspaceRoot(pm) {
|
|
|
10886
12883
|
async function confirm3(question) {
|
|
10887
12884
|
if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
|
|
10888
12885
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10889
|
-
return new Promise((
|
|
12886
|
+
return new Promise((resolve25) => {
|
|
10890
12887
|
rl.question(chalk7.yellow(question), (ans) => {
|
|
10891
12888
|
rl.close();
|
|
10892
12889
|
const t = ans.trim().toLowerCase();
|
|
10893
|
-
|
|
12890
|
+
resolve25(t === "y" || t === "yes");
|
|
10894
12891
|
});
|
|
10895
12892
|
});
|
|
10896
12893
|
}
|
|
10897
12894
|
var CAPTURED_OUTPUT_LIMIT = 64e3;
|
|
10898
12895
|
async function runInstall(cmd, cwd) {
|
|
10899
12896
|
const { spawn: spawn4 } = await import('child_process');
|
|
10900
|
-
return new Promise((
|
|
12897
|
+
return new Promise((resolve25) => {
|
|
10901
12898
|
const child = spawn4(cmd, {
|
|
10902
12899
|
cwd,
|
|
10903
12900
|
shell: true,
|
|
@@ -10916,15 +12913,15 @@ async function runInstall(cmd, cwd) {
|
|
|
10916
12913
|
};
|
|
10917
12914
|
tee(child.stdout, process.stdout);
|
|
10918
12915
|
tee(child.stderr, process.stderr);
|
|
10919
|
-
child.on("error", (err) =>
|
|
12916
|
+
child.on("error", (err) => resolve25({ ok: false, output: `${output}
|
|
10920
12917
|
${String(err)}` }));
|
|
10921
|
-
child.on("close", (code) =>
|
|
12918
|
+
child.on("close", (code) => resolve25({ ok: code === 0, output }));
|
|
10922
12919
|
});
|
|
10923
12920
|
}
|
|
10924
12921
|
async function releaseWindowsFileLocks(cwd, keepVersion) {
|
|
10925
12922
|
reapAndReport(cwd, keepVersion);
|
|
10926
12923
|
try {
|
|
10927
|
-
const { vgdSocketPath } = await import('./vgd-
|
|
12924
|
+
const { vgdSocketPath } = await import('./vgd-3A2TRNP5.js');
|
|
10928
12925
|
const result = await stopVgd(vgdSocketPath());
|
|
10929
12926
|
if (result === "stopped") {
|
|
10930
12927
|
console.log(chalk7.dim("Stopped the vgd daemon so its files can be replaced \u2014 it restarts on the new build after the install."));
|
|
@@ -11055,7 +13052,7 @@ var updateCommand = new Command("update").description("Update vibgrate to the la
|
|
|
11055
13052
|
);
|
|
11056
13053
|
async function rewarmVgdAndReport(cwd) {
|
|
11057
13054
|
try {
|
|
11058
|
-
const { vgdIsRunning, vgdRequest } = await import('./vgd-
|
|
13055
|
+
const { vgdIsRunning, vgdRequest } = await import('./vgd-3A2TRNP5.js');
|
|
11059
13056
|
if (!await vgdIsRunning()) return;
|
|
11060
13057
|
const loaded = await vgdRequest({ op: "load-graph", root: cwd });
|
|
11061
13058
|
if (!loaded.ok || !("stored" in loaded)) return;
|
|
@@ -11166,13 +13163,13 @@ function collectCandidates(artifact) {
|
|
|
11166
13163
|
return out2;
|
|
11167
13164
|
}
|
|
11168
13165
|
var defaultFixScanner = async (rootDir) => {
|
|
11169
|
-
const scratch =
|
|
13166
|
+
const scratch = path51.join(os.tmpdir(), `vg-fix-scan-${process.pid}.json`);
|
|
11170
13167
|
const scanOpts = { vibgrateVersion: VERSION, format: "json", out: scratch, quiet: true, concurrency: 8 };
|
|
11171
13168
|
const advanced = await loadAdvancedScanHook();
|
|
11172
13169
|
return runCoreScan(rootDir, scanOpts, advanced);
|
|
11173
13170
|
};
|
|
11174
13171
|
async function loadArtifact(rootDir, inFile, scan = defaultFixScanner) {
|
|
11175
|
-
const artifactPath =
|
|
13172
|
+
const artifactPath = path51.isAbsolute(inFile) ? inFile : path51.join(rootDir, inFile);
|
|
11176
13173
|
if (await pathExists(artifactPath)) {
|
|
11177
13174
|
const staleness = scanStaleness(rootDir, artifactPath);
|
|
11178
13175
|
if (!staleness.stale) {
|
|
@@ -11199,7 +13196,7 @@ async function repositoryIdentity(rootDir, override) {
|
|
|
11199
13196
|
}
|
|
11200
13197
|
}
|
|
11201
13198
|
var fixCommand = new Command("fix").description("Get ranked, risk-tiered upgrade plans from the hosted planner and optionally apply them (prompts before changing files; --yes applies non-interactively, --dry-run previews, --no-apply only prints). Requires login/DSN.").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", "Preview the recommended plan (or --plan <tier>) without applying; never prompts").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) => {
|
|
11202
|
-
const rootDir =
|
|
13199
|
+
const rootDir = path51.resolve(targetPath);
|
|
11203
13200
|
if (!await pathExists(rootDir)) {
|
|
11204
13201
|
console.error(chalk7.red(`Path does not exist: ${rootDir}`));
|
|
11205
13202
|
process.exit(1);
|
|
@@ -11360,7 +13357,7 @@ function emit(report, format) {
|
|
|
11360
13357
|
}
|
|
11361
13358
|
}
|
|
11362
13359
|
function relProjectDir(rootDir, projectPath) {
|
|
11363
|
-
const rel =
|
|
13360
|
+
const rel = path51.isAbsolute(projectPath) ? path51.relative(rootDir, projectPath) : projectPath;
|
|
11364
13361
|
return rel === "" ? "." : rel;
|
|
11365
13362
|
}
|
|
11366
13363
|
async function buildTargetResolver(rootDir, artifact) {
|
|
@@ -11401,14 +13398,14 @@ function promptPlanSelection(response) {
|
|
|
11401
13398
|
const defaultIdx = plans.findIndex((p) => p.tier === response.recommended);
|
|
11402
13399
|
const def = defaultIdx >= 0 ? defaultIdx + 1 : 1;
|
|
11403
13400
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
11404
|
-
return new Promise((
|
|
13401
|
+
return new Promise((resolve25) => {
|
|
11405
13402
|
rl.question(`Plan [1-${plans.length}, default ${def}, q to cancel]: `, (ans) => {
|
|
11406
13403
|
rl.close();
|
|
11407
13404
|
const t = ans.trim().toLowerCase();
|
|
11408
|
-
if (t === "q" || t === "quit") return
|
|
13405
|
+
if (t === "q" || t === "quit") return resolve25(null);
|
|
11409
13406
|
const n = t === "" ? def : Number.parseInt(t, 10);
|
|
11410
|
-
if (!Number.isInteger(n) || n < 1 || n > plans.length) return
|
|
11411
|
-
|
|
13407
|
+
if (!Number.isInteger(n) || n < 1 || n > plans.length) return resolve25(null);
|
|
13408
|
+
resolve25(plans[n - 1].tier);
|
|
11412
13409
|
});
|
|
11413
13410
|
});
|
|
11414
13411
|
}
|
|
@@ -11486,7 +13483,7 @@ Applied the ${chosen.label} plan:`));
|
|
|
11486
13483
|
}
|
|
11487
13484
|
if (failed.length) process.exitCode = 2;
|
|
11488
13485
|
}
|
|
11489
|
-
function
|
|
13486
|
+
function formatText2(artifact) {
|
|
11490
13487
|
const lines = [];
|
|
11491
13488
|
const teal2 = chalk7.hex("#3FB0A4");
|
|
11492
13489
|
const mint2 = chalk7.hex("#4FE3C1");
|
|
@@ -11839,10 +13836,29 @@ function formatArchitectureDiagram(arch) {
|
|
|
11839
13836
|
lines.push(chalk7.dim(` + ${arch.folders.length - 12} more`));
|
|
11840
13837
|
}
|
|
11841
13838
|
}
|
|
13839
|
+
if (arch.coverage) {
|
|
13840
|
+
const pct = Math.round(arch.coverage.ratio * 100);
|
|
13841
|
+
lines.push(
|
|
13842
|
+
` Coverage: ${pct}% (${arch.coverage.classified} classified, ${arch.coverage.unclassified} unclassified)`
|
|
13843
|
+
);
|
|
13844
|
+
const attributed = Object.entries(arch.coverage.bySource).filter(([, n]) => n > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([src, n]) => `${src} ${n}`);
|
|
13845
|
+
if (attributed.length > 0) {
|
|
13846
|
+
lines.push(chalk7.dim(` by source: ${attributed.join(", ")}`));
|
|
13847
|
+
}
|
|
13848
|
+
}
|
|
11842
13849
|
if (arch.unclassifiedFiles && arch.unclassifiedFiles.length > 0) {
|
|
11843
13850
|
const extra = arch.unclassified > arch.unclassifiedFiles.length ? ` of ${arch.unclassified}` : "";
|
|
11844
13851
|
lines.push(` Unclassified source (sample): ${arch.unclassifiedFiles.length}${extra}`);
|
|
11845
13852
|
}
|
|
13853
|
+
if (arch.unclassifiedFolders && arch.unclassifiedFolders.length > 0) {
|
|
13854
|
+
lines.push(" Unclassified by folder:");
|
|
13855
|
+
for (const folder of arch.unclassifiedFolders.slice(0, 8)) {
|
|
13856
|
+
lines.push(` ${String(folder.count).padStart(4)} ${folder.path}`);
|
|
13857
|
+
}
|
|
13858
|
+
if (arch.unclassifiedFolders.length > 8) {
|
|
13859
|
+
lines.push(chalk7.dim(` + ${arch.unclassifiedFolders.length - 8} more`));
|
|
13860
|
+
}
|
|
13861
|
+
}
|
|
11846
13862
|
lines.push("");
|
|
11847
13863
|
if (arch.layers.length > 0) {
|
|
11848
13864
|
for (const layer of arch.layers) {
|
|
@@ -12130,7 +14146,7 @@ function generatePriorityActions(artifact) {
|
|
|
12130
14146
|
}
|
|
12131
14147
|
|
|
12132
14148
|
// src/reporting/formatters/markdown.ts
|
|
12133
|
-
function
|
|
14149
|
+
function formatMarkdown2(artifact) {
|
|
12134
14150
|
const lines = [];
|
|
12135
14151
|
lines.push("# Vibgrate Drift Report");
|
|
12136
14152
|
lines.push("");
|
|
@@ -12243,7 +14259,7 @@ function formatMarkdown(artifact) {
|
|
|
12243
14259
|
|
|
12244
14260
|
// src/reporting/commands/report.ts
|
|
12245
14261
|
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) => {
|
|
12246
|
-
const artifactPath =
|
|
14262
|
+
const artifactPath = path51.resolve(opts.in);
|
|
12247
14263
|
if (!await pathExists(artifactPath)) {
|
|
12248
14264
|
console.error(chalk7.red(`Artifact not found: ${artifactPath}`));
|
|
12249
14265
|
console.error(chalk7.dim('Run "vibgrate scan" first to generate a scan artifact.'));
|
|
@@ -12252,14 +14268,14 @@ var reportCommand = new Command("report").description("Generate a drift report f
|
|
|
12252
14268
|
const artifact = await readJsonFile(artifactPath);
|
|
12253
14269
|
switch (opts.format) {
|
|
12254
14270
|
case "md":
|
|
12255
|
-
console.log(
|
|
14271
|
+
console.log(formatMarkdown2(artifact));
|
|
12256
14272
|
break;
|
|
12257
14273
|
case "json":
|
|
12258
14274
|
console.log(JSON.stringify(artifact, null, 2));
|
|
12259
14275
|
break;
|
|
12260
14276
|
case "text":
|
|
12261
14277
|
default:
|
|
12262
|
-
console.log(
|
|
14278
|
+
console.log(formatText2(artifact));
|
|
12263
14279
|
break;
|
|
12264
14280
|
}
|
|
12265
14281
|
});
|
|
@@ -12290,10 +14306,10 @@ function openUrl(url) {
|
|
|
12290
14306
|
}
|
|
12291
14307
|
|
|
12292
14308
|
// src/reporting/commands/login.ts
|
|
12293
|
-
var delay = (ms) => new Promise((
|
|
14309
|
+
var delay = (ms) => new Promise((resolve25) => setTimeout(resolve25, ms));
|
|
12294
14310
|
function isInside(dir, file) {
|
|
12295
|
-
const rel =
|
|
12296
|
-
return !!rel && !rel.startsWith("..") && !
|
|
14311
|
+
const rel = path51.relative(dir, file);
|
|
14312
|
+
return !!rel && !rel.startsWith("..") && !path51.isAbsolute(rel);
|
|
12297
14313
|
}
|
|
12298
14314
|
var loginCommand = new Command("login").description("Authenticate the CLI with your Vibgrate workspace via the browser").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", `Data residency region (${availableRegionIds().join(", ")})`, "us").option("--no-browser", "Do not attempt to open a browser automatically").option(
|
|
12299
14315
|
"--local",
|
|
@@ -12461,8 +14477,8 @@ var logoutCommand = new Command("logout").description("Clear stored Vibgrate log
|
|
|
12461
14477
|
});
|
|
12462
14478
|
|
|
12463
14479
|
// src/reporting/commands/locale/client.ts
|
|
12464
|
-
function buildUrl(scheme, host,
|
|
12465
|
-
const url = new URL(`${scheme}://${host}/v1/locale${
|
|
14480
|
+
function buildUrl(scheme, host, path53, query) {
|
|
14481
|
+
const url = new URL(`${scheme}://${host}/v1/locale${path53}`);
|
|
12466
14482
|
for (const [key2, value] of Object.entries(query ?? {})) {
|
|
12467
14483
|
if (value !== void 0 && value !== "") url.searchParams.set(key2, String(value));
|
|
12468
14484
|
}
|
|
@@ -12510,8 +14526,8 @@ function createLocaleClient(dsn) {
|
|
|
12510
14526
|
ExitCode.ERROR
|
|
12511
14527
|
);
|
|
12512
14528
|
}
|
|
12513
|
-
const request = async (method,
|
|
12514
|
-
const url = buildUrl(parsed.scheme, parsed.host,
|
|
14529
|
+
const request = async (method, path53, opts = {}) => {
|
|
14530
|
+
const url = buildUrl(parsed.scheme, parsed.host, path53, opts.query);
|
|
12515
14531
|
const body = opts.body === void 0 ? void 0 : JSON.stringify(opts.body);
|
|
12516
14532
|
const response = await fetch(url, {
|
|
12517
14533
|
method,
|
|
@@ -12527,16 +14543,16 @@ function createLocaleClient(dsn) {
|
|
|
12527
14543
|
};
|
|
12528
14544
|
return {
|
|
12529
14545
|
workspaceId: parsed.workspaceId,
|
|
12530
|
-
get: (
|
|
12531
|
-
post: (
|
|
12532
|
-
patch: (
|
|
12533
|
-
del: (
|
|
14546
|
+
get: (path53, query) => request("GET", path53, { query }),
|
|
14547
|
+
post: (path53, body) => request("POST", path53, { body }),
|
|
14548
|
+
patch: (path53, body) => request("PATCH", path53, { body }),
|
|
14549
|
+
del: (path53, body) => request("DELETE", path53, { body })
|
|
12534
14550
|
};
|
|
12535
14551
|
}
|
|
12536
14552
|
var LOCALE_CONFIG_FILENAMES = ["vibgrate.locale.yaml", ".vibgrate/locale.yaml"];
|
|
12537
14553
|
function findLocaleConfig(cwd) {
|
|
12538
14554
|
for (const name of LOCALE_CONFIG_FILENAMES) {
|
|
12539
|
-
const candidate =
|
|
14555
|
+
const candidate = path51.join(cwd, name);
|
|
12540
14556
|
if (fs12.existsSync(candidate)) return candidate;
|
|
12541
14557
|
}
|
|
12542
14558
|
return null;
|
|
@@ -12550,15 +14566,15 @@ function parseLocaleConfig(text) {
|
|
|
12550
14566
|
const items = value.filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
12551
14567
|
return items.length ? items : void 0;
|
|
12552
14568
|
};
|
|
12553
|
-
const
|
|
14569
|
+
const str2 = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
12554
14570
|
return {
|
|
12555
|
-
project:
|
|
12556
|
-
sourceLanguage:
|
|
14571
|
+
project: str2(obj.project),
|
|
14572
|
+
sourceLanguage: str2(obj.sourceLanguage),
|
|
12557
14573
|
targetLanguages: stringArray(obj.targetLanguages),
|
|
12558
|
-
format:
|
|
12559
|
-
path:
|
|
14574
|
+
format: str2(obj.format),
|
|
14575
|
+
path: str2(obj.path),
|
|
12560
14576
|
namespaces: stringArray(obj.namespaces),
|
|
12561
|
-
version:
|
|
14577
|
+
version: str2(obj.version)
|
|
12562
14578
|
};
|
|
12563
14579
|
}
|
|
12564
14580
|
function loadLocaleConfig(cwd) {
|
|
@@ -12568,7 +14584,7 @@ function loadLocaleConfig(cwd) {
|
|
|
12568
14584
|
return { config: parseLocaleConfig(fs12.readFileSync(file, "utf8")), file };
|
|
12569
14585
|
} catch (error) {
|
|
12570
14586
|
throw new Error(
|
|
12571
|
-
`Could not parse ${
|
|
14587
|
+
`Could not parse ${path51.relative(cwd, file)}: ${error instanceof Error ? error.message : String(error)}`
|
|
12572
14588
|
);
|
|
12573
14589
|
}
|
|
12574
14590
|
}
|
|
@@ -12583,8 +14599,8 @@ function serializeLocaleConfig(config) {
|
|
|
12583
14599
|
return stringify(out2);
|
|
12584
14600
|
}
|
|
12585
14601
|
function writeLocaleConfig(cwd, config, filename) {
|
|
12586
|
-
const target = filename ??
|
|
12587
|
-
fs12.mkdirSync(
|
|
14602
|
+
const target = filename ?? path51.join(cwd, LOCALE_CONFIG_FILENAMES[0]);
|
|
14603
|
+
fs12.mkdirSync(path51.dirname(target), { recursive: true });
|
|
12588
14604
|
fs12.writeFileSync(target, serializeLocaleConfig(config), "utf8");
|
|
12589
14605
|
return target;
|
|
12590
14606
|
}
|
|
@@ -12726,7 +14742,7 @@ function validatePathTemplate(template) {
|
|
|
12726
14742
|
if (!template.includes("{language}")) {
|
|
12727
14743
|
return 'path template must contain {language} (e.g. "src/locales/{language}/{namespace}.json").';
|
|
12728
14744
|
}
|
|
12729
|
-
if (
|
|
14745
|
+
if (path51.isAbsolute(template)) {
|
|
12730
14746
|
return "path template must be relative to the project directory.";
|
|
12731
14747
|
}
|
|
12732
14748
|
if (template.split(/[\\/]/).includes("..")) {
|
|
@@ -12742,7 +14758,7 @@ function resolveLocaleFiles(cwd, template, languages, namespaces) {
|
|
|
12742
14758
|
for (const language of languages) {
|
|
12743
14759
|
for (const namespace of namespaces) {
|
|
12744
14760
|
const relative13 = renderPath(template, language, namespace);
|
|
12745
|
-
const file =
|
|
14761
|
+
const file = path51.resolve(cwd, relative13);
|
|
12746
14762
|
out2.push({ language, namespace, file, relative: relative13, exists: fs12.existsSync(file) });
|
|
12747
14763
|
}
|
|
12748
14764
|
}
|
|
@@ -12764,7 +14780,7 @@ function writeCatalog(file, format, catalog, opts = {}) {
|
|
|
12764
14780
|
const existing = fs12.existsSync(file) ? fs12.readFileSync(file, "utf8") : null;
|
|
12765
14781
|
const changed = existing !== encoded;
|
|
12766
14782
|
if (changed && !opts.dryRun) {
|
|
12767
|
-
fs12.mkdirSync(
|
|
14783
|
+
fs12.mkdirSync(path51.dirname(file), { recursive: true });
|
|
12768
14784
|
fs12.writeFileSync(file, encoded, "utf8");
|
|
12769
14785
|
}
|
|
12770
14786
|
return { file, changed, bytes: Buffer.byteLength(encoded, "utf8") };
|
|
@@ -12785,7 +14801,7 @@ function diffCatalogs(before, after) {
|
|
|
12785
14801
|
// src/reporting/commands/locale/index.ts
|
|
12786
14802
|
var cwdOption = ["-C, --cwd <dir>", "Run against this directory (default: current)"];
|
|
12787
14803
|
function root(opts) {
|
|
12788
|
-
return
|
|
14804
|
+
return path51.resolve(opts.cwd ?? process.cwd());
|
|
12789
14805
|
}
|
|
12790
14806
|
function withCommonOptions(cmd) {
|
|
12791
14807
|
return cmd.option(...cwdOption).option("--dsn <dsn>", 'DSN token (or use VIBGRATE_DSN, or run "vg login")').option("--project <slug>", "Locale project slug or id").option("--format <format>", `Locale file format (${SUPPORTED_FORMATS.join(", ")})`).option("--path <template>", "Path template, e.g. src/locales/{language}/{namespace}.json").option("--namespace <name>", "Restrict to one namespace").option("--language <code>", "Restrict to one BCP-47 language").option("--ver <version>", "Target version (default: latest)").option("--json", "Machine-readable output").option("-q, --quiet", "Suppress non-essential output");
|
|
@@ -12855,7 +14871,7 @@ var configCmd = withCommonOptions(
|
|
|
12855
14871
|
if (!isSupportedFormat(next2.format)) codecFor(next2.format);
|
|
12856
14872
|
const written = writeLocaleConfig(dir, next2, file ?? void 0);
|
|
12857
14873
|
emit2(opts, { file: written, config: next2 }, () => {
|
|
12858
|
-
console.log(chalk7.green(`Wrote ${
|
|
14874
|
+
console.log(chalk7.green(`Wrote ${path51.relative(dir, written) || path51.basename(written)}`));
|
|
12859
14875
|
console.log(
|
|
12860
14876
|
chalk7.dim('Credentials are not stored here \u2014 run "vg login" or set VIBGRATE_DSN.')
|
|
12861
14877
|
);
|
|
@@ -13331,7 +15347,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
|
|
|
13331
15347
|
try {
|
|
13332
15348
|
const statements = [];
|
|
13333
15349
|
if (opts.from) {
|
|
13334
|
-
const fromPath =
|
|
15350
|
+
const fromPath = path51.resolve(opts.from);
|
|
13335
15351
|
if (!await pathExists(fromPath)) {
|
|
13336
15352
|
process.stderr.write(chalk7.red(`Statements file not found: ${fromPath}
|
|
13337
15353
|
`));
|
|
@@ -13349,9 +15365,9 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
|
|
|
13349
15365
|
});
|
|
13350
15366
|
const json2 = JSON.stringify(doc, null, 2);
|
|
13351
15367
|
if (opts.out) {
|
|
13352
|
-
await writeTextFile(
|
|
15368
|
+
await writeTextFile(path51.resolve(opts.out), json2 + "\n");
|
|
13353
15369
|
process.stderr.write(
|
|
13354
|
-
chalk7.green("\u2714") + ` Wrote OpenVEX document (${doc.statements.length} statement(s)) to ${
|
|
15370
|
+
chalk7.green("\u2714") + ` Wrote OpenVEX document (${doc.statements.length} statement(s)) to ${path51.resolve(opts.out)}
|
|
13355
15371
|
`
|
|
13356
15372
|
);
|
|
13357
15373
|
} else {
|
|
@@ -13534,7 +15550,7 @@ function formatDeltaText(base, current) {
|
|
|
13534
15550
|
return lines.join("\n");
|
|
13535
15551
|
}
|
|
13536
15552
|
async function readArtifactOrExit(filePath) {
|
|
13537
|
-
const absolutePath =
|
|
15553
|
+
const absolutePath = path51.resolve(filePath);
|
|
13538
15554
|
if (!await pathExists(absolutePath)) {
|
|
13539
15555
|
console.error(chalk7.red(`Artifact not found: ${absolutePath}`));
|
|
13540
15556
|
process.exit(1);
|
|
@@ -13551,7 +15567,7 @@ var exportCommand = new Command("export").description("Export scan artifact as S
|
|
|
13551
15567
|
const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
|
|
13552
15568
|
const body = JSON.stringify(sbom, null, 2);
|
|
13553
15569
|
if (opts.out) {
|
|
13554
|
-
await writeTextFile(
|
|
15570
|
+
await writeTextFile(path51.resolve(opts.out), body);
|
|
13555
15571
|
console.log(chalk7.green("\u2714") + ` SBOM written to ${opts.out}`);
|
|
13556
15572
|
} else {
|
|
13557
15573
|
console.log(body);
|
|
@@ -13562,7 +15578,7 @@ var deltaCommand = new Command("delta").description("Show SBOM delta between two
|
|
|
13562
15578
|
const current = await readArtifactOrExit(opts.to);
|
|
13563
15579
|
const report = formatDeltaText(base, current);
|
|
13564
15580
|
if (opts.out) {
|
|
13565
|
-
await writeTextFile(
|
|
15581
|
+
await writeTextFile(path51.resolve(opts.out), report);
|
|
13566
15582
|
console.log(chalk7.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
|
|
13567
15583
|
} else {
|
|
13568
15584
|
console.log(report);
|
|
@@ -13693,20 +15709,20 @@ function resolveRegime(id) {
|
|
|
13693
15709
|
return regime;
|
|
13694
15710
|
}
|
|
13695
15711
|
function evidenceDir(root3) {
|
|
13696
|
-
return
|
|
15712
|
+
return path51.join(root3, ".vibgrate", "evidence");
|
|
13697
15713
|
}
|
|
13698
15714
|
function orgPath(root3) {
|
|
13699
|
-
return
|
|
15715
|
+
return path51.join(evidenceDir(root3), "org.json");
|
|
13700
15716
|
}
|
|
13701
15717
|
function productsPath(root3) {
|
|
13702
|
-
return
|
|
15718
|
+
return path51.join(evidenceDir(root3), "products.json");
|
|
13703
15719
|
}
|
|
13704
15720
|
function releasesDir(root3) {
|
|
13705
|
-
return
|
|
15721
|
+
return path51.join(evidenceDir(root3), "releases");
|
|
13706
15722
|
}
|
|
13707
15723
|
function releasePath(root3, productId, version) {
|
|
13708
15724
|
const safe = `${productId}@${version}`.replace(/[^A-Za-z0-9._@-]/g, "_");
|
|
13709
|
-
return
|
|
15725
|
+
return path51.join(releasesDir(root3), `${safe}.json`);
|
|
13710
15726
|
}
|
|
13711
15727
|
var DEFAULT_ORG = {
|
|
13712
15728
|
responsiblePersons: [],
|
|
@@ -13737,7 +15753,7 @@ async function freezeRelease(root3, release) {
|
|
|
13737
15753
|
const p = releasePath(root3, release.productId, release.version);
|
|
13738
15754
|
if (await pathExists(p)) {
|
|
13739
15755
|
throw new CliError(
|
|
13740
|
-
`release ${release.productId}@${release.version} is already frozen and manifests are immutable \u2014 delete ${
|
|
15756
|
+
`release ${release.productId}@${release.version} is already frozen and manifests are immutable \u2014 delete ${path51.relative(root3, p)} only if it was frozen in error`,
|
|
13741
15757
|
ExitCode.USAGE_ERROR
|
|
13742
15758
|
);
|
|
13743
15759
|
}
|
|
@@ -13751,7 +15767,7 @@ async function loadReleases(root3, productId) {
|
|
|
13751
15767
|
const files = (await readdir(dir)).filter((f) => f.endsWith(".json")).sort();
|
|
13752
15768
|
const releases = [];
|
|
13753
15769
|
for (const f of files) {
|
|
13754
|
-
const rel = await readJsonFile(
|
|
15770
|
+
const rel = await readJsonFile(path51.join(dir, f));
|
|
13755
15771
|
if (!productId || rel.productId === productId) releases.push(rel);
|
|
13756
15772
|
}
|
|
13757
15773
|
return releases;
|
|
@@ -13912,9 +15928,9 @@ function derInt(n) {
|
|
|
13912
15928
|
if (bytes[0] & 128) bytes = Buffer.concat([Buffer.from([0]), bytes]);
|
|
13913
15929
|
return der(TAG.INTEGER, bytes);
|
|
13914
15930
|
}
|
|
13915
|
-
function buildTimeStampReq(
|
|
15931
|
+
function buildTimeStampReq(digest2) {
|
|
13916
15932
|
const algId = der(TAG.SEQUENCE, Buffer.concat([der(TAG.OID, OID_SHA256), der(TAG.NULL, Buffer.alloc(0))]));
|
|
13917
|
-
const messageImprint = der(TAG.SEQUENCE, Buffer.concat([algId, der(TAG.OCTET_STRING,
|
|
15933
|
+
const messageImprint = der(TAG.SEQUENCE, Buffer.concat([algId, der(TAG.OCTET_STRING, digest2)]));
|
|
13918
15934
|
const version = derInt(1);
|
|
13919
15935
|
const nonce = der(TAG.INTEGER, crypto3.randomBytes(16));
|
|
13920
15936
|
const certReq = der(TAG.BOOLEAN, Buffer.from([255]));
|
|
@@ -13993,9 +16009,9 @@ function verifyTimestamp(tsr, digestHex) {
|
|
|
13993
16009
|
reason: imprintMatches ? `RFC 3161 token binds to this result at ${token.genTime} (TSA signature chain not re-verified here \u2014 use \`openssl ts -verify\`)` : "RFC 3161 token imprint does not match this result"
|
|
13994
16010
|
};
|
|
13995
16011
|
}
|
|
13996
|
-
async function requestTimestamp(tsaUrl,
|
|
16012
|
+
async function requestTimestamp(tsaUrl, digest2) {
|
|
13997
16013
|
if (typeof fetch !== "function") throw new CliError("network fetch unavailable \u2014 cannot reach the TSA", ExitCode.ERROR);
|
|
13998
|
-
const req = buildTimeStampReq(
|
|
16014
|
+
const req = buildTimeStampReq(digest2);
|
|
13999
16015
|
let res;
|
|
14000
16016
|
try {
|
|
14001
16017
|
res = await fetch(tsaUrl, {
|
|
@@ -14009,7 +16025,7 @@ async function requestTimestamp(tsaUrl, digest) {
|
|
|
14009
16025
|
}
|
|
14010
16026
|
if (!res.ok) throw new CliError(`TSA ${tsaUrl} returned ${res.status}`, ExitCode.ERROR);
|
|
14011
16027
|
const body = Buffer.from(await res.arrayBuffer());
|
|
14012
|
-
const v = verifyTimestamp(body,
|
|
16028
|
+
const v = verifyTimestamp(body, digest2.toString("hex"));
|
|
14013
16029
|
if (!v.imprintMatches) throw new CliError(`TSA response did not bind to our digest (${v.reason})`, ExitCode.ERROR);
|
|
14014
16030
|
return body;
|
|
14015
16031
|
}
|
|
@@ -14446,12 +16462,12 @@ function verifyEvidenceEnvelope(env, opts = {}) {
|
|
|
14446
16462
|
var DEFAULT_KEY = "attest-key.pem";
|
|
14447
16463
|
function resolveSigningKey(root3, explicit) {
|
|
14448
16464
|
const chosen = process.env.VG_ATTEST_KEY;
|
|
14449
|
-
const keyPath = chosen ?
|
|
16465
|
+
const keyPath = chosen ? path51.resolve(chosen) : path51.join(root3, ".vibgrate", DEFAULT_KEY);
|
|
14450
16466
|
let minted = false;
|
|
14451
16467
|
if (!fs12.existsSync(keyPath)) {
|
|
14452
16468
|
if (chosen) throw new CliError(`signing key not found: ${chosen}`, ExitCode.USAGE_ERROR);
|
|
14453
16469
|
const kp = generateKeypair();
|
|
14454
|
-
fs12.mkdirSync(
|
|
16470
|
+
fs12.mkdirSync(path51.dirname(keyPath), { recursive: true });
|
|
14455
16471
|
fs12.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
|
|
14456
16472
|
fs12.writeFileSync(`${keyPath}.pub`, kp.publicPem);
|
|
14457
16473
|
minted = true;
|
|
@@ -14469,16 +16485,16 @@ function resolveSigningKey(root3, explicit) {
|
|
|
14469
16485
|
}
|
|
14470
16486
|
function writeBundle(input) {
|
|
14471
16487
|
const dir = input.outDir;
|
|
14472
|
-
fs12.mkdirSync(
|
|
14473
|
-
const write = (rel, data) => fs12.writeFileSync(
|
|
16488
|
+
fs12.mkdirSync(path51.join(dir, "inputs", "releases"), { recursive: true });
|
|
16489
|
+
const write = (rel, data) => fs12.writeFileSync(path51.join(dir, rel), `${JSON.stringify(data, null, 2)}
|
|
14474
16490
|
`);
|
|
14475
16491
|
write("result.json", input.result);
|
|
14476
|
-
write(
|
|
16492
|
+
write(path51.join("inputs", "advisory.json"), input.advisory);
|
|
14477
16493
|
for (const rel of input.releases) {
|
|
14478
16494
|
const safe = `${rel.productId}@${rel.version}`.replace(/[^A-Za-z0-9._@-]/g, "_");
|
|
14479
|
-
write(
|
|
16495
|
+
write(path51.join("inputs", "releases", `${safe}.json`), rel);
|
|
14480
16496
|
}
|
|
14481
|
-
write(
|
|
16497
|
+
write(path51.join("inputs", "datapack.lock"), { dataPackVersion: input.result.meta.dataPackVersion, kernelVersion: input.result.meta.kernelVersion });
|
|
14482
16498
|
write("provenance.json", {
|
|
14483
16499
|
method: "deterministic",
|
|
14484
16500
|
modelDrafted: [],
|
|
@@ -14494,13 +16510,13 @@ function writeBundle(input) {
|
|
|
14494
16510
|
disclaimer: input.regime.disclaimer
|
|
14495
16511
|
});
|
|
14496
16512
|
if (input.envelope) {
|
|
14497
|
-
fs12.writeFileSync(
|
|
16513
|
+
fs12.writeFileSync(path51.join(dir, "evidence.intoto.jsonl"), `${JSON.stringify(input.envelope)}
|
|
14498
16514
|
`);
|
|
14499
16515
|
}
|
|
14500
16516
|
if (input.timestampToken) {
|
|
14501
|
-
fs12.writeFileSync(
|
|
16517
|
+
fs12.writeFileSync(path51.join(dir, "timestamp.tsr"), input.timestampToken);
|
|
14502
16518
|
}
|
|
14503
|
-
fs12.writeFileSync(
|
|
16519
|
+
fs12.writeFileSync(path51.join(dir, "VERIFY.md"), verifyDoc(input));
|
|
14504
16520
|
return dir;
|
|
14505
16521
|
}
|
|
14506
16522
|
function verifyDoc(input) {
|
|
@@ -14561,13 +16577,13 @@ function undeterminedFields(result, regime) {
|
|
|
14561
16577
|
return gaps.filter((g) => regime.submission.requires.some((r) => g.startsWith(r)) || g.startsWith("exposure") || g.startsWith("member"));
|
|
14562
16578
|
}
|
|
14563
16579
|
async function recordDrill(root3, record) {
|
|
14564
|
-
const dir =
|
|
14565
|
-
const p =
|
|
16580
|
+
const dir = path51.join(evidenceDir(root3), "drills");
|
|
16581
|
+
const p = path51.join(dir, `${record.drillId}.json`);
|
|
14566
16582
|
await writeJsonFile(p, record);
|
|
14567
16583
|
return p;
|
|
14568
16584
|
}
|
|
14569
16585
|
async function hasRecentDrill(root3, asOf) {
|
|
14570
|
-
const dir =
|
|
16586
|
+
const dir = path51.join(evidenceDir(root3), "drills");
|
|
14571
16587
|
if (!await pathExists(dir)) return void 0;
|
|
14572
16588
|
const { readdir } = await import('fs/promises');
|
|
14573
16589
|
const { readJsonFile: readJsonFile2 } = await import('./fs-4G5A65KS.js');
|
|
@@ -14575,7 +16591,7 @@ async function hasRecentDrill(root3, asOf) {
|
|
|
14575
16591
|
if (files.length === 0) return void 0;
|
|
14576
16592
|
const cutoff = new Date(new Date(asOf).getTime() - 90 * 24 * 3600 * 1e3).toISOString();
|
|
14577
16593
|
for (const f of files) {
|
|
14578
|
-
const rec = await readJsonFile2(
|
|
16594
|
+
const rec = await readJsonFile2(path51.join(dir, f));
|
|
14579
16595
|
if (rec.ranAt >= cutoff) return true;
|
|
14580
16596
|
}
|
|
14581
16597
|
return false;
|
|
@@ -14655,7 +16671,7 @@ function formatRegimeList() {
|
|
|
14655
16671
|
|
|
14656
16672
|
// src/reporting/commands/evidence/index.ts
|
|
14657
16673
|
function root2(opts) {
|
|
14658
|
-
return
|
|
16674
|
+
return path51.resolve(opts.cwd ?? process.cwd());
|
|
14659
16675
|
}
|
|
14660
16676
|
function slug(name) {
|
|
14661
16677
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "product";
|
|
@@ -14743,7 +16759,7 @@ var releaseCmd = new Command("release").description("Freeze a shipped release in
|
|
|
14743
16759
|
const release = await buildRelease({
|
|
14744
16760
|
productId: product.id,
|
|
14745
16761
|
version,
|
|
14746
|
-
from:
|
|
16762
|
+
from: path51.resolve(rootDir, opts.from),
|
|
14747
16763
|
shipDate: opts.shipDate,
|
|
14748
16764
|
buildId: opts.buildId,
|
|
14749
16765
|
artefactDigest: opts.digest,
|
|
@@ -14751,7 +16767,7 @@ var releaseCmd = new Command("release").description("Freeze a shipped release in
|
|
|
14751
16767
|
frozenAt: nowIso()
|
|
14752
16768
|
});
|
|
14753
16769
|
const p = await freezeRelease(rootDir, release);
|
|
14754
|
-
console.log(chalk7.green("\u2714") + ` froze ${chalk7.bold(`${product.id}@${version}`)} \u2014 ${release.components.length} components, immutable at ${
|
|
16770
|
+
console.log(chalk7.green("\u2714") + ` froze ${chalk7.bold(`${product.id}@${version}`)} \u2014 ${release.components.length} components, immutable at ${path51.relative(rootDir, p)}`);
|
|
14755
16771
|
});
|
|
14756
16772
|
async function runExposure(rootDir, vuln, opts) {
|
|
14757
16773
|
const org = await loadOrg(rootDir);
|
|
@@ -14788,8 +16804,8 @@ var exposureCmd = new Command("exposure").description("Which shipped products co
|
|
|
14788
16804
|
const { result, regime, releases, advisory } = await runExposure(rootDir, vuln, opts);
|
|
14789
16805
|
let timestampToken;
|
|
14790
16806
|
if (opts.tsa) {
|
|
14791
|
-
const
|
|
14792
|
-
timestampToken = await requestTimestamp(opts.tsa,
|
|
16807
|
+
const digest2 = Buffer.from(exposureSubjectDigest(result), "hex");
|
|
16808
|
+
timestampToken = await requestTimestamp(opts.tsa, digest2);
|
|
14793
16809
|
result.meta.timestamp = { source: "rfc3161", value: parseTimestampToken(timestampToken).genTime };
|
|
14794
16810
|
}
|
|
14795
16811
|
if (opts.format === "json") {
|
|
@@ -14803,7 +16819,7 @@ var exposureCmd = new Command("exposure").description("Which shipped products co
|
|
|
14803
16819
|
console.log("\n" + buildPack(result, regime, org, stage));
|
|
14804
16820
|
}
|
|
14805
16821
|
if (opts.bundle) {
|
|
14806
|
-
const dir =
|
|
16822
|
+
const dir = path51.resolve(rootDir, opts.bundle, `evidence-${result.meta.evidenceId}`);
|
|
14807
16823
|
let envelope;
|
|
14808
16824
|
if (opts.sign !== false) {
|
|
14809
16825
|
const { key: key2, minted } = resolveSigningKey(rootDir);
|
|
@@ -14811,7 +16827,7 @@ var exposureCmd = new Command("exposure").description("Which shipped products co
|
|
|
14811
16827
|
envelope = signEvidenceStatement(buildEvidenceStatement(result, VERSION), key2);
|
|
14812
16828
|
}
|
|
14813
16829
|
writeBundle({ outDir: dir, result, advisory, releases, regime, envelope, timestampToken, cliVersion: VERSION });
|
|
14814
|
-
console.error(chalk7.green("\u2714") + ` evidence bundle written to ${
|
|
16830
|
+
console.error(chalk7.green("\u2714") + ` evidence bundle written to ${path51.relative(rootDir, dir)}${envelope ? " (signed)" : " (unsigned)"}${timestampToken ? " \xB7 RFC 3161" : ""}`);
|
|
14815
16831
|
}
|
|
14816
16832
|
process.exitCode = exposureExit(result.overallStatus);
|
|
14817
16833
|
});
|
|
@@ -14843,7 +16859,7 @@ var packCmd = new Command("pack").description("Build the submission pack a human
|
|
|
14843
16859
|
const stage = opts.stage ?? regime.clocks[0]?.stage ?? "early-warning";
|
|
14844
16860
|
const md = buildPack(result, regime, org, stage);
|
|
14845
16861
|
if (opts.out) {
|
|
14846
|
-
fs12.writeFileSync(
|
|
16862
|
+
fs12.writeFileSync(path51.resolve(rootDir, opts.out), md);
|
|
14847
16863
|
console.error(chalk7.green("\u2714") + ` pack written to ${opts.out}`);
|
|
14848
16864
|
} else {
|
|
14849
16865
|
console.log(md);
|
|
@@ -14885,18 +16901,18 @@ var drillCmd = new Command("drill").description("Timed dry-run of the determinat
|
|
|
14885
16901
|
}
|
|
14886
16902
|
});
|
|
14887
16903
|
var verifyCmd = new Command("verify").description("Verify an evidence bundle offline (no Vibgrate needed)").argument("<bundle>", "Path to an evidence bundle directory or evidence.intoto.jsonl").option("--pub <file>", "Public key PEM to pin the signer (establish trust)").action((bundlePath, opts) => {
|
|
14888
|
-
const abs =
|
|
14889
|
-
const envPath = fs12.statSync(abs).isDirectory() ?
|
|
16904
|
+
const abs = path51.resolve(bundlePath);
|
|
16905
|
+
const envPath = fs12.statSync(abs).isDirectory() ? path51.join(abs, "evidence.intoto.jsonl") : abs;
|
|
14890
16906
|
if (!fs12.existsSync(envPath)) throw new CliError(`no evidence.intoto.jsonl at ${bundlePath}`, ExitCode.NOT_FOUND);
|
|
14891
16907
|
const envelope = JSON.parse(fs12.readFileSync(envPath, "utf8").trim().split("\n")[0]);
|
|
14892
|
-
const resultPath =
|
|
16908
|
+
const resultPath = path51.join(path51.dirname(envPath), "result.json");
|
|
14893
16909
|
const result = fs12.existsSync(resultPath) ? JSON.parse(fs12.readFileSync(resultPath, "utf8")) : void 0;
|
|
14894
|
-
const publicKeyPem = opts.pub ? fs12.readFileSync(
|
|
16910
|
+
const publicKeyPem = opts.pub ? fs12.readFileSync(path51.resolve(opts.pub), "utf8") : void 0;
|
|
14895
16911
|
const v = verifyEvidenceEnvelope(envelope, { publicKeyPem, result });
|
|
14896
16912
|
const color = v.status === "verified" ? chalk7.green : v.status === "failed" ? chalk7.red : chalk7.yellow;
|
|
14897
16913
|
console.log(" " + color(v.status.toUpperCase()) + ` ${v.reason}`);
|
|
14898
16914
|
if (v.evidenceId) console.log(" " + chalk7.dim(`evidence ${v.evidenceId} \xB7 regime ${v.regime} \xB7 advisory ${v.advisoryId} \xB7 ${v.overallStatus}`));
|
|
14899
|
-
const tsrPath =
|
|
16915
|
+
const tsrPath = path51.join(path51.dirname(envPath), "timestamp.tsr");
|
|
14900
16916
|
if (fs12.existsSync(tsrPath) && result) {
|
|
14901
16917
|
const t = verifyTimestamp(fs12.readFileSync(tsrPath), exposureSubjectDigest(result));
|
|
14902
16918
|
const tcolor = t.imprintMatches ? chalk7.green : chalk7.red;
|
|
@@ -14965,12 +16981,12 @@ var exportCmd = new Command("export").description("Air-gap bundle of all evidenc
|
|
|
14965
16981
|
const releases = [];
|
|
14966
16982
|
for (const list of releasesByProduct.values()) releases.push(...list);
|
|
14967
16983
|
const report = computeReadiness({ regime, org, products, releasesByProduct, recentDrill: await hasRecentDrill(rootDir, today()) });
|
|
14968
|
-
const dir =
|
|
14969
|
-
await writeJsonFile(
|
|
14970
|
-
await writeJsonFile(
|
|
14971
|
-
for (const r of releases) await writeJsonFile(
|
|
14972
|
-
await writeJsonFile(
|
|
14973
|
-
console.error(chalk7.green("\u2714") + ` exported evidence state to ${
|
|
16984
|
+
const dir = path51.resolve(rootDir, opts.out);
|
|
16985
|
+
await writeJsonFile(path51.join(dir, "org.json"), org);
|
|
16986
|
+
await writeJsonFile(path51.join(dir, "products.json"), products);
|
|
16987
|
+
for (const r of releases) await writeJsonFile(path51.join(dir, "releases", `${r.productId}@${r.version}.json`.replace(/[^A-Za-z0-9._@-]/g, "_")), r);
|
|
16988
|
+
await writeJsonFile(path51.join(dir, "readiness.json"), report);
|
|
16989
|
+
console.error(chalk7.green("\u2714") + ` exported evidence state to ${path51.relative(rootDir, dir)} (${products.length} products, ${releases.length} frozen releases)`);
|
|
14974
16990
|
});
|
|
14975
16991
|
var pushCmd = new Command("push").description("Push the product registry (and an optional exposure result) to Vibgrate Cloud").option(...cwdOption2).option("--dsn <dsn>", "DSN token (or use VIBGRATE_DSN env / `vg login`)").option("--regime <id>", "Reporting regime").option("--result <file>", "An exposure result.json (or a bundle dir) to include").option("--signed", "Mark the included exposure result as signed").option("--strict", "Fail on push errors").action(async (opts) => {
|
|
14976
16992
|
const rootDir = root2(opts);
|
|
@@ -14996,8 +17012,8 @@ var pushCmd = new Command("push").description("Push the product registry (and an
|
|
|
14996
17012
|
}));
|
|
14997
17013
|
let exposure;
|
|
14998
17014
|
if (opts.result) {
|
|
14999
|
-
const raw =
|
|
15000
|
-
const resultPath = fs12.existsSync(raw) && fs12.statSync(raw).isDirectory() ?
|
|
17015
|
+
const raw = path51.resolve(rootDir, opts.result);
|
|
17016
|
+
const resultPath = fs12.existsSync(raw) && fs12.statSync(raw).isDirectory() ? path51.join(raw, "result.json") : raw;
|
|
15001
17017
|
if (!fs12.existsSync(resultPath)) throw new CliError(`no result.json at ${opts.result}`, ExitCode.NOT_FOUND);
|
|
15002
17018
|
exposure = JSON.parse(fs12.readFileSync(resultPath, "utf8"));
|
|
15003
17019
|
}
|
|
@@ -15060,6 +17076,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
15060
17076
|
"module",
|
|
15061
17077
|
"daemon",
|
|
15062
17078
|
"policy",
|
|
17079
|
+
"review",
|
|
15063
17080
|
"llm-host",
|
|
15064
17081
|
"hcs",
|
|
15065
17082
|
"install",
|
|
@@ -15129,6 +17146,7 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
|
|
|
15129
17146
|
registerPolicy(program);
|
|
15130
17147
|
registerLlmHost(program);
|
|
15131
17148
|
registerHcs(program);
|
|
17149
|
+
registerReview(program);
|
|
15132
17150
|
program.addCommand(initCommand);
|
|
15133
17151
|
program.addCommand(scanCommand);
|
|
15134
17152
|
program.addCommand(fixCommand);
|
|
@@ -15153,6 +17171,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
15153
17171
|
"--only",
|
|
15154
17172
|
"--exclude",
|
|
15155
17173
|
"--export",
|
|
17174
|
+
"--out",
|
|
15156
17175
|
"-o",
|
|
15157
17176
|
"--budget",
|
|
15158
17177
|
"-b"
|
|
@@ -15198,7 +17217,7 @@ function dispatch(argv, cwd) {
|
|
|
15198
17217
|
}
|
|
15199
17218
|
const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
|
|
15200
17219
|
if (looksLikeQuestion) return ["ask", ...args];
|
|
15201
|
-
if (fs12.existsSync(
|
|
17220
|
+
if (fs12.existsSync(path51.resolve(cwd, first))) {
|
|
15202
17221
|
return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
|
|
15203
17222
|
}
|
|
15204
17223
|
const moved = MOVED_COMMANDS[first];
|
|
@@ -15225,15 +17244,15 @@ function findFirstPositional(args) {
|
|
|
15225
17244
|
async function main(argv = process.argv) {
|
|
15226
17245
|
const raw = argv.slice(2);
|
|
15227
17246
|
if (process.env.NO_COLOR || raw.includes("--no-color")) disableColor();
|
|
15228
|
-
if (!raw.includes("--local")) kickRelevanceReadiness();
|
|
17247
|
+
if (!raw.includes("--local") && !raw.includes("--offline")) kickRelevanceReadiness();
|
|
15229
17248
|
const cwd = readCwd(raw);
|
|
15230
17249
|
const program = buildProgram();
|
|
15231
17250
|
program.exitOverride((err) => {
|
|
15232
17251
|
throw err;
|
|
15233
17252
|
});
|
|
15234
17253
|
program.configureOutput({
|
|
15235
|
-
writeErr: (
|
|
15236
|
-
writeOut: (
|
|
17254
|
+
writeErr: (str2) => process.stderr.write(str2),
|
|
17255
|
+
writeOut: (str2) => process.stdout.write(str2)
|
|
15237
17256
|
});
|
|
15238
17257
|
try {
|
|
15239
17258
|
const dispatched = dispatch(raw, cwd);
|
|
@@ -15244,9 +17263,9 @@ async function main(argv = process.argv) {
|
|
|
15244
17263
|
}
|
|
15245
17264
|
function readCwd(raw) {
|
|
15246
17265
|
const i = raw.findIndex((a) => a === "-C" || a === "--cwd");
|
|
15247
|
-
if (i >= 0 && raw[i + 1]) return
|
|
17266
|
+
if (i >= 0 && raw[i + 1]) return path51.resolve(raw[i + 1]);
|
|
15248
17267
|
const eq = raw.find((a) => a.startsWith("--cwd="));
|
|
15249
|
-
if (eq) return
|
|
17268
|
+
if (eq) return path51.resolve(eq.slice("--cwd=".length));
|
|
15250
17269
|
return process.cwd();
|
|
15251
17270
|
}
|
|
15252
17271
|
function handleError(err) {
|