@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.
Files changed (129) hide show
  1. package/DOCS.md +204 -9
  2. package/README.md +2 -0
  3. package/dist/{agent-4RHH2FR2.js → agent-5GW7C7EB.js} +14 -14
  4. package/dist/{agent-4RHH2FR2.js.map → agent-5GW7C7EB.js.map} +1 -1
  5. package/dist/{approvals-XDFM346D.js → approvals-ISJFILVK.js} +5 -5
  6. package/dist/{approvals-XDFM346D.js.map → approvals-ISJFILVK.js.map} +1 -1
  7. package/dist/{attach-LGTCYNKC.js → attach-JUSALQ75.js} +3 -3
  8. package/dist/{attach-LGTCYNKC.js.map → attach-JUSALQ75.js.map} +1 -1
  9. package/dist/baseline-YL2IZZ4Y.js +9 -0
  10. package/dist/{baseline-PRY5HKL5.js.map → baseline-YL2IZZ4Y.js.map} +1 -1
  11. package/dist/{chunk-ZRDTI67N.js → chunk-5RW5PIBH.js} +3 -3
  12. package/dist/{chunk-ZRDTI67N.js.map → chunk-5RW5PIBH.js.map} +1 -1
  13. package/dist/{chunk-TUVB6Q6F.js → chunk-65YN3BG3.js} +1207 -40
  14. package/dist/chunk-65YN3BG3.js.map +1 -0
  15. package/dist/{chunk-TKUWEJZJ.js → chunk-B5VCPIKT.js} +7 -6
  16. package/dist/chunk-B5VCPIKT.js.map +1 -0
  17. package/dist/{chunk-WU2DVESP.js → chunk-BA6YBVNT.js} +3 -3
  18. package/dist/{chunk-WU2DVESP.js.map → chunk-BA6YBVNT.js.map} +1 -1
  19. package/dist/{chunk-IM7HME2T.js → chunk-BODXEZEY.js} +9 -9
  20. package/dist/{chunk-IM7HME2T.js.map → chunk-BODXEZEY.js.map} +1 -1
  21. package/dist/{chunk-PIB3YNJU.js → chunk-D3SV3JWB.js} +3 -3
  22. package/dist/{chunk-PIB3YNJU.js.map → chunk-D3SV3JWB.js.map} +1 -1
  23. package/dist/chunk-EGMBHPXV.js +127 -0
  24. package/dist/chunk-EGMBHPXV.js.map +1 -0
  25. package/dist/{chunk-7KOEEEIZ.js → chunk-FHSEK64W.js} +7 -7
  26. package/dist/{chunk-7KOEEEIZ.js.map → chunk-FHSEK64W.js.map} +1 -1
  27. package/dist/{chunk-PEHMRPYM.js → chunk-FKCN653F.js} +3 -3
  28. package/dist/{chunk-PEHMRPYM.js.map → chunk-FKCN653F.js.map} +1 -1
  29. package/dist/{chunk-K2IWOZEQ.js → chunk-FPIMNVLI.js} +12 -4
  30. package/dist/chunk-FPIMNVLI.js.map +1 -0
  31. package/dist/{chunk-BHDP7EVJ.js → chunk-HHMWE52J.js} +10 -135
  32. package/dist/chunk-HHMWE52J.js.map +1 -0
  33. package/dist/{chunk-5UG4UHKU.js → chunk-HUJZO4K5.js} +281 -106
  34. package/dist/chunk-HUJZO4K5.js.map +1 -0
  35. package/dist/{chunk-TQ75FB52.js → chunk-JWQ2JCQS.js} +4 -4
  36. package/dist/{chunk-TQ75FB52.js.map → chunk-JWQ2JCQS.js.map} +1 -1
  37. package/dist/{chunk-WGA4KUGS.js → chunk-K7QR7U3V.js} +12 -11
  38. package/dist/chunk-K7QR7U3V.js.map +1 -0
  39. package/dist/{chunk-E44I7635.js → chunk-KCKG3EEJ.js} +3 -3
  40. package/dist/{chunk-E44I7635.js.map → chunk-KCKG3EEJ.js.map} +1 -1
  41. package/dist/{chunk-WGSLN73L.js → chunk-MMIIVRQC.js} +7 -7
  42. package/dist/chunk-MMIIVRQC.js.map +1 -0
  43. package/dist/chunk-N6R4XPLX.js +27 -0
  44. package/dist/chunk-N6R4XPLX.js.map +1 -0
  45. package/dist/{chunk-WPQCJADP.js → chunk-NQQT75AN.js} +62 -22
  46. package/dist/chunk-NQQT75AN.js.map +1 -0
  47. package/dist/{chunk-TOHNVUDD.js → chunk-NSC7ZK62.js} +3 -3
  48. package/dist/{chunk-TOHNVUDD.js.map → chunk-NSC7ZK62.js.map} +1 -1
  49. package/dist/{chunk-LAYDXJEQ.js → chunk-OAYSRM7J.js} +12 -14
  50. package/dist/chunk-OAYSRM7J.js.map +1 -0
  51. package/dist/{chunk-R5BGRJ6X.js → chunk-QMROD7MI.js} +15 -8
  52. package/dist/chunk-QMROD7MI.js.map +1 -0
  53. package/dist/{chunk-SHD5TCZ5.js → chunk-R2FX4USU.js} +7 -7
  54. package/dist/chunk-R2FX4USU.js.map +1 -0
  55. package/dist/{chunk-7S4ELKK6.js → chunk-RL7JYG3D.js} +3 -3
  56. package/dist/{chunk-7S4ELKK6.js.map → chunk-RL7JYG3D.js.map} +1 -1
  57. package/dist/{chunk-QLNJ3VWO.js → chunk-RRSUNTZI.js} +3 -3
  58. package/dist/{chunk-QLNJ3VWO.js.map → chunk-RRSUNTZI.js.map} +1 -1
  59. package/dist/{chunk-QONHVC4H.js → chunk-TWFPHXPN.js} +3 -3
  60. package/dist/{chunk-QONHVC4H.js.map → chunk-TWFPHXPN.js.map} +1 -1
  61. package/dist/chunk-UGRUG2FA.js +6 -0
  62. package/dist/{chunk-JQ7OL4XG.js.map → chunk-UGRUG2FA.js.map} +1 -1
  63. package/dist/{chunk-XW4FCBL4.js → chunk-VOEEYCR3.js} +6 -6
  64. package/dist/{chunk-XW4FCBL4.js.map → chunk-VOEEYCR3.js.map} +1 -1
  65. package/dist/{chunk-WHNB3443.js → chunk-WBDRBP22.js} +4 -4
  66. package/dist/{chunk-WHNB3443.js.map → chunk-WBDRBP22.js.map} +1 -1
  67. package/dist/{chunk-NQU6E6OG.js → chunk-YL332BU2.js} +20 -15
  68. package/dist/chunk-YL332BU2.js.map +1 -0
  69. package/dist/{chunk-IEZCFMNL.js → chunk-ZQY5TWCB.js} +3 -3
  70. package/dist/{chunk-IEZCFMNL.js.map → chunk-ZQY5TWCB.js.map} +1 -1
  71. package/dist/cli.d.ts +16 -1
  72. package/dist/cli.js +2359 -340
  73. package/dist/cli.js.map +1 -1
  74. package/dist/{daemon-2YMCH7R2.js → daemon-NRIIDMX2.js} +10 -10
  75. package/dist/{daemon-2YMCH7R2.js.map → daemon-NRIIDMX2.js.map} +1 -1
  76. package/dist/embed-worker-main.js +3 -3
  77. package/dist/{ensure-map-S2GBW2UC.js → ensure-map-VAIYCCEJ.js} +17 -16
  78. package/dist/{ensure-map-S2GBW2UC.js.map → ensure-map-VAIYCCEJ.js.map} +1 -1
  79. package/dist/explain-EXPJTCB5.js +126 -0
  80. package/dist/explain-EXPJTCB5.js.map +1 -0
  81. package/dist/{graph-backend-QDEU5RIR.js → graph-backend-V5A2H5ZB.js} +7 -7
  82. package/dist/{graph-backend-QDEU5RIR.js.map → graph-backend-V5A2H5ZB.js.map} +1 -1
  83. package/dist/index.d.ts +217 -13
  84. package/dist/index.js +17 -16
  85. package/dist/index.js.map +1 -1
  86. package/dist/{interactive-GMGWUEQO.js → interactive-VQM2CTE2.js} +32 -31
  87. package/dist/interactive-VQM2CTE2.js.map +1 -0
  88. package/dist/load-UAVA732M.js +11 -0
  89. package/dist/{load-JXCTAAEI.js.map → load-UAVA732M.js.map} +1 -1
  90. package/dist/parse-worker.js +2 -2
  91. package/dist/{rules-ASYEUNB5.js → rules-TLRGB5L4.js} +12 -4
  92. package/dist/rules-TLRGB5L4.js.map +1 -0
  93. package/dist/{run-outcome-5NA7PI52.js → run-outcome-EATWIKLR.js} +5 -5
  94. package/dist/{run-outcome-5NA7PI52.js.map → run-outcome-EATWIKLR.js.map} +1 -1
  95. package/dist/{runtime-session-IEM6DZDP.js → runtime-session-Y2BRBXUW.js} +9 -9
  96. package/dist/{runtime-session-IEM6DZDP.js.map → runtime-session-Y2BRBXUW.js.map} +1 -1
  97. package/dist/{session-VYFLJ7KL.js → session-N2FTYIHQ.js} +9 -9
  98. package/dist/{session-VYFLJ7KL.js.map → session-N2FTYIHQ.js.map} +1 -1
  99. package/dist/{stream-json-2ISQLKPD.js → stream-json-GCZAHORX.js} +14 -14
  100. package/dist/{stream-json-2ISQLKPD.js.map → stream-json-GCZAHORX.js.map} +1 -1
  101. package/dist/version-XNZ4GLMQ.js +4 -0
  102. package/dist/{version-B62ELJRY.js.map → version-XNZ4GLMQ.js.map} +1 -1
  103. package/dist/{vg-mcp-bridge-CJIK5U56.js → vg-mcp-bridge-VDZWFEO2.js} +22 -11
  104. package/dist/vg-mcp-bridge-VDZWFEO2.js.map +1 -0
  105. package/dist/{vgd-4EB6SKFB.js → vgd-3A2TRNP5.js} +8 -8
  106. package/dist/vgd-3A2TRNP5.js.map +1 -0
  107. package/package.json +2 -2
  108. package/dist/baseline-PRY5HKL5.js +0 -9
  109. package/dist/chunk-5UG4UHKU.js.map +0 -1
  110. package/dist/chunk-BHDP7EVJ.js.map +0 -1
  111. package/dist/chunk-JQ7OL4XG.js +0 -6
  112. package/dist/chunk-K2IWOZEQ.js.map +0 -1
  113. package/dist/chunk-LAYDXJEQ.js.map +0 -1
  114. package/dist/chunk-NQU6E6OG.js.map +0 -1
  115. package/dist/chunk-R5BGRJ6X.js.map +0 -1
  116. package/dist/chunk-SHD5TCZ5.js.map +0 -1
  117. package/dist/chunk-TKUWEJZJ.js.map +0 -1
  118. package/dist/chunk-TUVB6Q6F.js.map +0 -1
  119. package/dist/chunk-WGA4KUGS.js.map +0 -1
  120. package/dist/chunk-WGSLN73L.js.map +0 -1
  121. package/dist/chunk-WPQCJADP.js.map +0 -1
  122. package/dist/chunk-YENM3HT7.js +0 -23
  123. package/dist/chunk-YENM3HT7.js.map +0 -1
  124. package/dist/interactive-GMGWUEQO.js.map +0 -1
  125. package/dist/load-JXCTAAEI.js +0 -11
  126. package/dist/rules-ASYEUNB5.js.map +0 -1
  127. package/dist/version-B62ELJRY.js +0 -4
  128. package/dist/vg-mcp-bridge-CJIK5U56.js.map +0 -1
  129. 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-WHNB3443.js';
3
- import { telemetryOptOut, StatsSharer, statsEndpoint, isCI } from './chunk-PIB3YNJU.js';
4
- import { loadAdvancedScanHook, baselineCommand } from './chunk-QLNJ3VWO.js';
5
- import { sha256Hex, generateKeypair, dssePae, DSSE_PAYLOAD_TYPE, keyId, registerBuild, LiveStatus, ProgressBar, printLogo, runBuild } from './chunk-WGA4KUGS.js';
6
- export { runBuild } from './chunk-WGA4KUGS.js';
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-WGSLN73L.js';
10
- import { CONTEXT_POLICY_VERSION, computeFcsReport, renderFcsReportMarkdown, createTrajectoryCollector, runAgent } from './chunk-WPQCJADP.js';
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-PEHMRPYM.js';
13
- import './chunk-ZRDTI67N.js';
14
- import { parseReasoningEffort, summarizeDiffs, ScriptedProvider } from './chunk-SHD5TCZ5.js';
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-7KOEEEIZ.js';
19
- import { readGlobal, applyGlobalOptions } from './chunk-YENM3HT7.js';
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-XW4FCBL4.js';
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-IM7HME2T.js';
30
- import { refreshIfStale } from './chunk-TKUWEJZJ.js';
31
- import { driftCount, ASSISTANTS, detectAssistants, isAssistantInstalled, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, buildGraph, writeSnapshot, probeFreshness, assistantById, SMALL_REPO_FILES } from './chunk-BHDP7EVJ.js';
32
- 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, publishPrivateLibrary, assessDocQuality, symbolsFromApi, fetchHostedDocsCached, selectForBudget, hostedBase, readStoredCredentials, projectCredentialsPath, homeCredentialsPath } from './chunk-TUVB6Q6F.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-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, boundaryProfileRules } from './chunk-5UG4UHKU.js';
35
- import './chunk-R5BGRJ6X.js';
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-QONHVC4H.js';
38
- import { recordCliCall, CLI_TOOL_ALIASES, savingsLedgerPath, clearSavings, readSavings, readUsage, readModelSavings, CAPSULE_RANKING_VERSION, SAVINGS_TOOLS, sanitizeClient } from './chunk-LAYDXJEQ.js';
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-E44I7635.js';
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-NQU6E6OG.js';
52
- import { loadGraph, loadGraphPreferIndex, SCHEMA_VERSION } from './chunk-7S4ELKK6.js';
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-TOHNVUDD.js';
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-WU2DVESP.js';
60
- import { resetRelevanceProviderCache, loadRelevanceProvider, resolveGraphPath, loadTopicTags, rankQuestion, renderReport, loadGraphFileWithSnapshot, writeArtifacts, vibgrateDir, mapFileExists, ensureVibgrateGitignore } from './chunk-K2IWOZEQ.js';
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-JQ7OL4XG.js';
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 path45 from 'path';
71
- import path45__default from 'path';
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 fs26 from 'fs/promises';
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 = path45.resolve(global.cwd ?? ".");
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(path45.join(cacheDir(root3), "parse-cache.json"));
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: path45.relative(process.cwd(), root3) || ".",
117
- graphPath: path45.relative(root3, 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(path45.relative(root3, graphPath))}`);
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 ${path45.relative(root3, graphPath)}`);
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.local;
187
+ const wantSemantic = opts.semantic !== false && !global.offline;
187
188
  let result;
188
189
  let mode = "lexical";
189
190
  let note = "";
190
- if (!global.local) await ensureRelevanceModule();
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.local,
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.local) note = "semantic skipped under --local; used lexical";
246
- activity.add("answer", "skip", global.local ? "lexical only (--local)" : "lexical only (--no-semantic)");
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 || path45.join(os.homedir(), ".cache");
313
- return path45.join(base, "vibgrate", "models");
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 path45.join(dir, `embed-health-${process.platform}-${process.arch}.json`);
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 = path45.dirname(fileURLToPath(from));
337
+ const dir = path51.dirname(fileURLToPath(from));
337
338
  for (const name of ["embeddings.js", "index.js"]) {
338
- const candidate = path45.join(dir, name);
339
+ const candidate = path51.join(dir, name);
339
340
  if (fs12.existsSync(candidate)) return pathToFileURL(candidate).href;
340
341
  }
341
- return pathToFileURL(path45.join(dir, "index.js")).href;
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((resolve23) => {
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
- resolve23({ signal, status, stdout });
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.local) {
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.local,
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 path45.join(cacheDir(root3), "serve-live");
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(path45.join(dir, name), "utf8"));
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 = path45.join(dir, `serve-${this.pid}.json`);
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 = path45.join(this.dir, name);
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.local === true;
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((resolve23) => httpServer.listen(port, host, resolve23));
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 ? path45.resolve(root3, rel) : path45.resolve(root3);
1830
+ return rel ? path51.resolve(root3, rel) : path51.resolve(root3);
1830
1831
  }
1831
1832
  function projectForFile(root3, projects, filePath) {
1832
- const dir = path45.dirname(path45.resolve(filePath));
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 + path45.sep)) && projDir.length > bestLen) {
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 path45.join(root3, ".vibgrate", "score-history.jsonl");
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(path45.dirname(file), { recursive: true });
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 path45.join(root3, ".vibgrate", "ignore.json");
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 path45.join(root3, ".vibgrate", "inline-inventory.json");
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 = path45.relative(this.opts.root, path45.dirname(path45.resolve(filePath)));
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(path45.dirname(filePath), scanOpts, advanced);
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 = path45.resolve(this.opts.root, relManifest);
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) => path45.normalize((p === "" ? "." : p).replace(/\\/g, "/"));
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 path45.normalize(location) === path45.normalize(projectPath);
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 = path45.resolve(rootDir, relManifest);
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 join34 = (name) => base ? path45.join(base, name) : name;
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 join34("package.json");
3601
+ return join39("package.json");
3598
3602
  case "python":
3599
- return join34("pyproject.toml");
3603
+ return join39("pyproject.toml");
3600
3604
  case "go":
3601
- return join34("go.mod");
3605
+ return join39("go.mod");
3602
3606
  case "rust":
3603
- return join34("Cargo.toml");
3607
+ return join39("Cargo.toml");
3604
3608
  case "ruby":
3605
- return join34("Gemfile");
3609
+ return join39("Gemfile");
3606
3610
  case "php":
3607
- return join34("composer.json");
3611
+ return join39("composer.json");
3608
3612
  case "java":
3609
3613
  case "kotlin":
3610
3614
  case "scala":
3611
3615
  case "groovy":
3612
- return join34("pom.xml");
3616
+ return join39("pom.xml");
3613
3617
  case "dotnet":
3614
- return join34(proj.name ? `${proj.name}.csproj` : "Directory.Build.props");
3618
+ return join39(proj.name ? `${proj.name}.csproj` : "Directory.Build.props");
3615
3619
  case "dart":
3616
- return join34("pubspec.yaml");
3620
+ return join39("pubspec.yaml");
3617
3621
  case "elixir":
3618
- return join34("mix.exs");
3622
+ return join39("mix.exs");
3619
3623
  default:
3620
- return join34("package.json");
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 ? path45.join(dir, name) : name;
3655
+ const rel = dir ? path51.join(dir, name) : name;
3652
3656
  try {
3653
- if (fs12.existsSync(path45.join(rootDir, rel))) return rel;
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 = path45.resolve(root3, relFile);
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.local === true,
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 = path45.join(root3, ".claude", "settings.json");
3974
- const rel = path45.join(".claude", "settings.json");
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 = path45.join(root3, ".claude", "settings.json");
4016
- const rel = path45.join(".claude", "settings.json");
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(path45.dirname(file), { recursive: true });
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 path45.join(modulesBaseDir(), "hcs");
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 = path45.resolve(custom);
4171
+ const p = path51.resolve(custom);
4168
4172
  try {
4169
- if (fs12.statSync(p).isDirectory()) return [path45.join(p, "index.js")];
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 [path45.join(hcsModuleDir(), "index.js")];
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((resolve23) => {
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
- resolve23(answer.trim() === "" || /^y(es)?$/i.test(answer.trim()));
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 = path45.join(root3, ".git");
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 = path45.join(root3, ".vibgrate");
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(path45.join(dir, ".gitignore"), `${lines.join("\n")}
4424
+ fs12.writeFileSync(path51.join(dir, ".gitignore"), `${lines.join("\n")}
4421
4425
  `);
4422
4426
  }
4423
4427
  function writeGitAttributes(root3) {
4424
- const file = path45.join(root3, ".gitattributes");
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 = path45.join(hooksDir, "pre-commit");
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 = path45.join(hookDir(root3), "pre-commit");
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 = path45.join(root3, ".gitattributes");
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 path45.resolve(root3, configured);
4488
+ if (configured) return path51.resolve(root3, configured);
4485
4489
  } catch {
4486
4490
  }
4487
- return path45.join(root3, ".git", "hooks");
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) => path45.resolve(root3, 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) => path45.relative(root3, p) || ".").join(", ")} (debounce ${debounceMs}ms) \xB7 Ctrl+C to stop`));
4570
- await new Promise((resolve23) => {
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
- resolve23();
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 = path45__default.join(root3, rel);
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.local) {
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.local) await enrichOnline(inv.records);
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 path47 = `${modelPath}#${arm.id}`;
5624
- const session = await acquireHostSession(lib, path47);
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 resolve23 = 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) {
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(resolve23);
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.local,
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.local,
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.local,
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.local,
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 = path45.resolve(opts.out);
6992
- fs12.mkdirSync(path45.dirname(outPath), { recursive: true });
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("--offline", "never touch the network \u2014 use the cache (or the curated fallback)").option("--refresh", "bypass the cache and refresh from the network").action(async function(opts) {
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: opts.offline, noCache: opts.refresh });
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((resolve23) => {
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
- resolve23(/^y(es)?$/i.test(answer.trim()));
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 = path45.basename(wantLc);
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) || path45.basename(n) === base;
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) => path45.basename(m.name).toLowerCase() === base);
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 path45.join(root3, ".vibgrate", "code.json");
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(path45.join(root3, rel));
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.local) {
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-JXCTAAEI.js');
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-GMGWUEQO.js');
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-S2GBW2UC.js');
7572
+ const { ensureCodeMap: ensureCodeMap2 } = await import('./ensure-map-VAIYCCEJ.js');
7569
7573
  const mapRoot = rootOf(global);
7570
7574
  try {
7571
- await ensureCodeMap(mapRoot, global, {
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.local,
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-5NA7PI52.js');
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-2ISQLKPD.js');
7656
- const { nodeCodeFs } = await import('./session-VYFLJ7KL.js');
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-IEM6DZDP.js');
7663
- const { resolveGraphBackend } = await import('./graph-backend-QDEU5RIR.js');
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-4EB6SKFB.js');
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-CJIK5U56.js');
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-XDFM346D.js');
7770
- const { loadProjectRules, nodeRulesReader } = await import('./rules-ASYEUNB5.js');
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-2ISQLKPD.js');
7828
- const { runAgent: runAgent2 } = await import('./agent-4RHH2FR2.js');
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-B62ELJRY.js');
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((resolve23) => {
7872
- wakeTurn = resolve23;
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-GMGWUEQO.js');
8052
- const { nodeCodeFs } = await import('./session-VYFLJ7KL.js');
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-CJIK5U56.js');
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-IEM6DZDP.js');
8092
- const { resolveGraphBackend } = await import('./graph-backend-QDEU5RIR.js');
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-4EB6SKFB.js');
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((resolve23) => {
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
- resolve23(/^y(es)?$/i.test(answer.trim()));
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.local;
8358
+ const network = !global.offline;
8355
8359
  if (!verb) return listCatalog(root3, global.json);
8356
- const online = (Boolean(opts.online) || network) && !global.local;
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(path45__default.join(root3, "package.json"), "utf8"));
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.local) throw usageError("vg lib publish uploads to the hosted catalog \u2014 it needs network (remove --local)");
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 ? path45__default.resolve(opts.readme) : path45__default.join(root3, "README.md"));
8427
- const dts = opts.dts ? readOptionalFile(path45__default.resolve(opts.dts)) : void 0;
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.local ? discoverModels() : void 0,
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(path45.dirname(path45.resolve(file)), { recursive: true });
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(path45.extname(file));
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("--offline", "offline bundle (default; present for clarity)").option("-o, --out <dir>", "output directory", "vg-bundle").action(function(opts) {
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 = path45.resolve(root3, opts.out ?? "vg-bundle");
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(path45.join(outDir, "grammars"), { recursive: true });
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, path45.join(outDir, "grammars", fileName));
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, path45.join(outDir, "grammars", path45.basename(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, path45.join(outDir, "graph.json"));
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(path45.join(outDir, "graph.json"), serializeGraph(graph));
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, path45.join(outDir, "vibgrate.lib.json"));
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), path45.join(outDir, "lib"), { recursive: true });
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(path45.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
8628
+ fs12.writeFileSync(path51.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
8625
8629
  `);
8626
8630
  if (global.json) {
8627
- json({ out: path45.relative(root3, outDir), ...manifest });
8631
+ json({ out: path51.relative(root3, outDir), ...manifest });
8628
8632
  return;
8629
8633
  }
8630
- info(`${c.cyan("vg bundle")} \xB7 wrote ${path45.relative(root3, outDir)}/`);
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 "${path45.relative(root3, outDir)}/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.local === true;
8652
+ const local = global.offline === true;
8649
8653
  const graphPath = resolveGraphPath(root3, global.graph);
8650
- const configFile = CONFIG_BASENAMES.find((f) => fs12.existsSync(path45.join(root3, f))) ?? null;
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: path45.relative(root3, graphPath),
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: path45.relative(root3, catalogPath(root3)),
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 ${path45.basename(li.hostPool.sessions[0].modelPath)} kv=${li.hostPool.sessions[0].kvBlocks}`
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.local,
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.local) {
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 = path45.resolve(file);
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 = path45.resolve(outFile);
9060
- const dir = path45.dirname(outPath);
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 = path45.resolve(dir);
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 = path45.join(abs, entry.name);
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(path45.extname(entry.name).toLowerCase());
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 = path45.resolve(statePath);
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 = path45.resolve(targetPath);
9268
- const vibgrateDir2 = path45.join(rootDir, ".vibgrate");
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 = path45.join(rootDir, "vibgrate.config.ts");
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-PRY5HKL5.js');
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(path45.join(rootDir, relFile));
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 fs26.stat(absPath);
11417
+ const stat2 = await fs32.stat(absPath);
9421
11418
  if (!stat2.isFile() || stat2.size > MAX_FILE_BYTES) return null;
9422
- return await fs26.readFile(absPath, "utf8");
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(path45.join(rootDir, file))) {
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-LGTCYNKC.js');
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 = path45.resolve(targetPath);
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(path45.join(rootDir, ".vibgrate", "scan_result.json"));
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-LGTCYNKC.js');
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(path45.join(dir, e.name), depth + 1);
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 = path45.join(dir, e.name);
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 = path45.relative(rootDir, abs) || e.name;
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(path45.join(dir, entry.name), depth + 1);
12339
+ walk2(path51.join(dir, entry.name), depth + 1);
10343
12340
  continue;
10344
12341
  }
10345
- const fileEco = EXT_ECOSYSTEM[path45.extname(entry.name)];
12342
+ const fileEco = EXT_ECOSYSTEM[path51.extname(entry.name)];
10346
12343
  if (!fileEco || fileEco === "unknown") continue;
10347
12344
  filesSeen++;
10348
- const full = path45.join(dir, entry.name);
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 defaultRun(cmd, cwd) {
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 ?? defaultRun;
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, path45.join(rootDir, t.dir)) };
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 = path45.join(os.homedir(), ".vibgrate");
10693
- var CACHE_FILE = path45.join(CACHE_DIR, "update-check.json");
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 fs26.mkdir(CACHE_DIR, { recursive: true });
10721
- await fs26.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
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 = path45.join(dir, `vg-update-${stamp}.cmd`);
10757
- const logPath = path45.join(dir, `vg-update-${stamp}.log`);
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 ? path45.join(dir, "node_modules") : null;
12810
+ return dir ? path51.join(dir, "node_modules") : null;
10814
12811
  }
10815
12812
  },
10816
- { pm: "bun", root: () => path45.join(os.homedir(), ".bun", "install", "global", "node_modules") }
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(path45.join(root3, pkg))) return probe.pm;
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(path45.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
10840
- if (await pathExists(path45.join(cwd, "bun.lockb"))) return "bun";
10841
- if (await pathExists(path45.join(cwd, "yarn.lock"))) return "yarn";
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(path45.join(cwd, "pnpm-workspace.yaml"))) return true;
12842
+ if (await pathExists(path51.join(cwd, "pnpm-workspace.yaml"))) return true;
10846
12843
  try {
10847
- const pkgPath = path45.join(cwd, "package.json");
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 = path45.join(cwd, "package.json");
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((resolve23) => {
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
- resolve23(t === "y" || t === "yes");
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((resolve23) => {
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) => resolve23({ ok: false, output: `${output}
12916
+ child.on("error", (err) => resolve25({ ok: false, output: `${output}
10920
12917
  ${String(err)}` }));
10921
- child.on("close", (code) => resolve23({ ok: code === 0, output }));
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-4EB6SKFB.js');
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-4EB6SKFB.js');
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 = path45.join(os.tmpdir(), `vg-fix-scan-${process.pid}.json`);
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 = path45.isAbsolute(inFile) ? inFile : path45.join(rootDir, inFile);
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 = path45.resolve(targetPath);
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 = path45.isAbsolute(projectPath) ? path45.relative(rootDir, projectPath) : projectPath;
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((resolve23) => {
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 resolve23(null);
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 resolve23(null);
11411
- resolve23(plans[n - 1].tier);
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 formatText(artifact) {
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 formatMarkdown(artifact) {
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 = path45.resolve(opts.in);
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(formatMarkdown(artifact));
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(formatText(artifact));
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((resolve23) => setTimeout(resolve23, ms));
14309
+ var delay = (ms) => new Promise((resolve25) => setTimeout(resolve25, ms));
12294
14310
  function isInside(dir, file) {
12295
- const rel = path45.relative(dir, file);
12296
- return !!rel && !rel.startsWith("..") && !path45.isAbsolute(rel);
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, path47, query) {
12465
- const url = new URL(`${scheme}://${host}/v1/locale${path47}`);
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, path47, opts = {}) => {
12514
- const url = buildUrl(parsed.scheme, parsed.host, path47, opts.query);
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: (path47, query) => request("GET", path47, { query }),
12531
- post: (path47, body) => request("POST", path47, { body }),
12532
- patch: (path47, body) => request("PATCH", path47, { body }),
12533
- del: (path47, body) => request("DELETE", path47, { body })
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 = path45.join(cwd, name);
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 str = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
14569
+ const str2 = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
12554
14570
  return {
12555
- project: str(obj.project),
12556
- sourceLanguage: str(obj.sourceLanguage),
14571
+ project: str2(obj.project),
14572
+ sourceLanguage: str2(obj.sourceLanguage),
12557
14573
  targetLanguages: stringArray(obj.targetLanguages),
12558
- format: str(obj.format),
12559
- path: str(obj.path),
14574
+ format: str2(obj.format),
14575
+ path: str2(obj.path),
12560
14576
  namespaces: stringArray(obj.namespaces),
12561
- version: str(obj.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 ${path45.relative(cwd, file)}: ${error instanceof Error ? error.message : String(error)}`
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 ?? path45.join(cwd, LOCALE_CONFIG_FILENAMES[0]);
12587
- fs12.mkdirSync(path45.dirname(target), { recursive: true });
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 (path45.isAbsolute(template)) {
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 = path45.resolve(cwd, relative13);
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(path45.dirname(file), { recursive: true });
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 path45.resolve(opts.cwd ?? process.cwd());
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 ${path45.relative(dir, written) || path45.basename(written)}`));
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 = path45.resolve(opts.from);
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(path45.resolve(opts.out), json2 + "\n");
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 ${path45.resolve(opts.out)}
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 = path45.resolve(filePath);
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(path45.resolve(opts.out), body);
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(path45.resolve(opts.out), report);
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 path45.join(root3, ".vibgrate", "evidence");
15712
+ return path51.join(root3, ".vibgrate", "evidence");
13697
15713
  }
13698
15714
  function orgPath(root3) {
13699
- return path45.join(evidenceDir(root3), "org.json");
15715
+ return path51.join(evidenceDir(root3), "org.json");
13700
15716
  }
13701
15717
  function productsPath(root3) {
13702
- return path45.join(evidenceDir(root3), "products.json");
15718
+ return path51.join(evidenceDir(root3), "products.json");
13703
15719
  }
13704
15720
  function releasesDir(root3) {
13705
- return path45.join(evidenceDir(root3), "releases");
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 path45.join(releasesDir(root3), `${safe}.json`);
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 ${path45.relative(root3, p)} only if it was frozen in error`,
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(path45.join(dir, f));
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(digest) {
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, digest)]));
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, digest) {
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(digest);
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, digest.toString("hex"));
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 ? path45.resolve(chosen) : path45.join(root3, ".vibgrate", DEFAULT_KEY);
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(path45.dirname(keyPath), { recursive: true });
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(path45.join(dir, "inputs", "releases"), { recursive: true });
14473
- const write = (rel, data) => fs12.writeFileSync(path45.join(dir, rel), `${JSON.stringify(data, null, 2)}
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(path45.join("inputs", "advisory.json"), input.advisory);
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(path45.join("inputs", "releases", `${safe}.json`), rel);
16495
+ write(path51.join("inputs", "releases", `${safe}.json`), rel);
14480
16496
  }
14481
- write(path45.join("inputs", "datapack.lock"), { dataPackVersion: input.result.meta.dataPackVersion, kernelVersion: input.result.meta.kernelVersion });
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(path45.join(dir, "evidence.intoto.jsonl"), `${JSON.stringify(input.envelope)}
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(path45.join(dir, "timestamp.tsr"), input.timestampToken);
16517
+ fs12.writeFileSync(path51.join(dir, "timestamp.tsr"), input.timestampToken);
14502
16518
  }
14503
- fs12.writeFileSync(path45.join(dir, "VERIFY.md"), verifyDoc(input));
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 = path45.join(evidenceDir(root3), "drills");
14565
- const p = path45.join(dir, `${record.drillId}.json`);
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 = path45.join(evidenceDir(root3), "drills");
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(path45.join(dir, f));
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 path45.resolve(opts.cwd ?? process.cwd());
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: path45.resolve(rootDir, opts.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 ${path45.relative(rootDir, p)}`);
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 digest = Buffer.from(exposureSubjectDigest(result), "hex");
14792
- timestampToken = await requestTimestamp(opts.tsa, digest);
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 = path45.resolve(rootDir, opts.bundle, `evidence-${result.meta.evidenceId}`);
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 ${path45.relative(rootDir, dir)}${envelope ? " (signed)" : " (unsigned)"}${timestampToken ? " \xB7 RFC 3161" : ""}`);
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(path45.resolve(rootDir, opts.out), md);
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 = path45.resolve(bundlePath);
14889
- const envPath = fs12.statSync(abs).isDirectory() ? path45.join(abs, "evidence.intoto.jsonl") : abs;
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 = path45.join(path45.dirname(envPath), "result.json");
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(path45.resolve(opts.pub), "utf8") : void 0;
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 = path45.join(path45.dirname(envPath), "timestamp.tsr");
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 = path45.resolve(rootDir, opts.out);
14969
- await writeJsonFile(path45.join(dir, "org.json"), org);
14970
- await writeJsonFile(path45.join(dir, "products.json"), products);
14971
- for (const r of releases) await writeJsonFile(path45.join(dir, "releases", `${r.productId}@${r.version}.json`.replace(/[^A-Za-z0-9._@-]/g, "_")), r);
14972
- await writeJsonFile(path45.join(dir, "readiness.json"), report);
14973
- console.error(chalk7.green("\u2714") + ` exported evidence state to ${path45.relative(rootDir, dir)} (${products.length} products, ${releases.length} frozen releases)`);
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 = path45.resolve(rootDir, opts.result);
15000
- const resultPath = fs12.existsSync(raw) && fs12.statSync(raw).isDirectory() ? path45.join(raw, "result.json") : raw;
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(path45.resolve(cwd, first))) {
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: (str) => process.stderr.write(str),
15236
- writeOut: (str) => process.stdout.write(str)
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 path45.resolve(raw[i + 1]);
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 path45.resolve(eq.slice("--cwd=".length));
17268
+ if (eq) return path51.resolve(eq.slice("--cwd=".length));
15250
17269
  return process.cwd();
15251
17270
  }
15252
17271
  function handleError(err) {