@vibgrate/cli 2026.825.2 → 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-DHQYEHVW.js → agent-5GW7C7EB.js} +14 -14
  4. package/dist/{agent-DHQYEHVW.js.map → agent-5GW7C7EB.js.map} +1 -1
  5. package/dist/{approvals-KBM72NU2.js → approvals-ISJFILVK.js} +5 -5
  6. package/dist/{approvals-KBM72NU2.js.map → approvals-ISJFILVK.js.map} +1 -1
  7. package/dist/{attach-O74NL4M5.js → attach-JUSALQ75.js} +3 -3
  8. package/dist/{attach-O74NL4M5.js.map → attach-JUSALQ75.js.map} +1 -1
  9. package/dist/baseline-YL2IZZ4Y.js +9 -0
  10. package/dist/{baseline-4X6MQBDA.js.map → baseline-YL2IZZ4Y.js.map} +1 -1
  11. package/dist/{chunk-JAEHLTVX.js → chunk-5RW5PIBH.js} +3 -3
  12. package/dist/{chunk-JAEHLTVX.js.map → chunk-5RW5PIBH.js.map} +1 -1
  13. package/dist/{chunk-TT2J2TB6.js → chunk-65YN3BG3.js} +1207 -40
  14. package/dist/chunk-65YN3BG3.js.map +1 -0
  15. package/dist/{chunk-VMPKOMHG.js → chunk-B5VCPIKT.js} +7 -6
  16. package/dist/chunk-B5VCPIKT.js.map +1 -0
  17. package/dist/{chunk-FQHWPOTS.js → chunk-BA6YBVNT.js} +3 -3
  18. package/dist/{chunk-FQHWPOTS.js.map → chunk-BA6YBVNT.js.map} +1 -1
  19. package/dist/{chunk-3E7QT4NE.js → chunk-BODXEZEY.js} +9 -9
  20. package/dist/{chunk-3E7QT4NE.js.map → chunk-BODXEZEY.js.map} +1 -1
  21. package/dist/{chunk-BICZR6UJ.js → chunk-D3SV3JWB.js} +3 -3
  22. package/dist/{chunk-BICZR6UJ.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-DIZ5KI5Q.js → chunk-FHSEK64W.js} +7 -7
  26. package/dist/{chunk-DIZ5KI5Q.js.map → chunk-FHSEK64W.js.map} +1 -1
  27. package/dist/{chunk-E3XVIFSH.js → chunk-FKCN653F.js} +3 -3
  28. package/dist/{chunk-E3XVIFSH.js.map → chunk-FKCN653F.js.map} +1 -1
  29. package/dist/{chunk-OIUVWCKM.js → chunk-FPIMNVLI.js} +12 -4
  30. package/dist/chunk-FPIMNVLI.js.map +1 -0
  31. package/dist/{chunk-NS4DL3QR.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-HFQIYM6E.js → chunk-JWQ2JCQS.js} +4 -4
  36. package/dist/{chunk-HFQIYM6E.js.map → chunk-JWQ2JCQS.js.map} +1 -1
  37. package/dist/{chunk-27TS2AUJ.js → chunk-K7QR7U3V.js} +12 -11
  38. package/dist/chunk-K7QR7U3V.js.map +1 -0
  39. package/dist/{chunk-IJVWKZRS.js → chunk-KCKG3EEJ.js} +3 -3
  40. package/dist/{chunk-IJVWKZRS.js.map → chunk-KCKG3EEJ.js.map} +1 -1
  41. package/dist/{chunk-GWSJYDK7.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-GR7SFCQJ.js → chunk-NQQT75AN.js} +77 -27
  46. package/dist/chunk-NQQT75AN.js.map +1 -0
  47. package/dist/{chunk-6MIJLO5B.js → chunk-NSC7ZK62.js} +3 -3
  48. package/dist/{chunk-6MIJLO5B.js.map → chunk-NSC7ZK62.js.map} +1 -1
  49. package/dist/{chunk-SYMJSRYF.js → chunk-OAYSRM7J.js} +12 -14
  50. package/dist/chunk-OAYSRM7J.js.map +1 -0
  51. package/dist/{chunk-L7GCJNDT.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-7M4E7UGX.js → chunk-RL7JYG3D.js} +3 -3
  56. package/dist/{chunk-7M4E7UGX.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-NZES47ST.js → chunk-TWFPHXPN.js} +3 -3
  60. package/dist/{chunk-NZES47ST.js.map → chunk-TWFPHXPN.js.map} +1 -1
  61. package/dist/chunk-UGRUG2FA.js +6 -0
  62. package/dist/{chunk-G3ACLBMG.js.map → chunk-UGRUG2FA.js.map} +1 -1
  63. package/dist/{chunk-KYM55VZX.js → chunk-VOEEYCR3.js} +6 -6
  64. package/dist/{chunk-KYM55VZX.js.map → chunk-VOEEYCR3.js.map} +1 -1
  65. package/dist/{chunk-SIXWWQ6S.js → chunk-WBDRBP22.js} +4 -4
  66. package/dist/{chunk-SIXWWQ6S.js.map → chunk-WBDRBP22.js.map} +1 -1
  67. package/dist/{chunk-K3SXWOTT.js → chunk-YL332BU2.js} +20 -15
  68. package/dist/chunk-YL332BU2.js.map +1 -0
  69. package/dist/{chunk-2NUS5BOR.js → chunk-ZQY5TWCB.js} +3 -3
  70. package/dist/{chunk-2NUS5BOR.js.map → chunk-ZQY5TWCB.js.map} +1 -1
  71. package/dist/cli.d.ts +16 -1
  72. package/dist/cli.js +2362 -342
  73. package/dist/cli.js.map +1 -1
  74. package/dist/{daemon-RZS5FPIW.js → daemon-NRIIDMX2.js} +10 -10
  75. package/dist/{daemon-RZS5FPIW.js.map → daemon-NRIIDMX2.js.map} +1 -1
  76. package/dist/embed-worker-main.js +3 -3
  77. package/dist/{ensure-map-DL2S774J.js → ensure-map-VAIYCCEJ.js} +17 -16
  78. package/dist/{ensure-map-DL2S774J.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-JXARVT3R.js → graph-backend-V5A2H5ZB.js} +7 -7
  82. package/dist/{graph-backend-JXARVT3R.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-2XRBRFRB.js → interactive-VQM2CTE2.js} +37 -32
  87. package/dist/interactive-VQM2CTE2.js.map +1 -0
  88. package/dist/load-UAVA732M.js +11 -0
  89. package/dist/{load-7C5VETRD.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-TMB5TJNA.js → run-outcome-EATWIKLR.js} +5 -5
  94. package/dist/{run-outcome-TMB5TJNA.js.map → run-outcome-EATWIKLR.js.map} +1 -1
  95. package/dist/{runtime-session-Y23NVY7R.js → runtime-session-Y2BRBXUW.js} +9 -9
  96. package/dist/{runtime-session-Y23NVY7R.js.map → runtime-session-Y2BRBXUW.js.map} +1 -1
  97. package/dist/{session-KXT2ZTPE.js → session-N2FTYIHQ.js} +9 -9
  98. package/dist/{session-KXT2ZTPE.js.map → session-N2FTYIHQ.js.map} +1 -1
  99. package/dist/{stream-json-NW6AOE47.js → stream-json-GCZAHORX.js} +14 -14
  100. package/dist/{stream-json-NW6AOE47.js.map → stream-json-GCZAHORX.js.map} +1 -1
  101. package/dist/version-XNZ4GLMQ.js +4 -0
  102. package/dist/{version-7ABYXWOZ.js.map → version-XNZ4GLMQ.js.map} +1 -1
  103. package/dist/{vg-mcp-bridge-LXBHKEFR.js → vg-mcp-bridge-VDZWFEO2.js} +22 -11
  104. package/dist/vg-mcp-bridge-VDZWFEO2.js.map +1 -0
  105. package/dist/{vgd-RW7HCEIP.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-4X6MQBDA.js +0 -9
  109. package/dist/chunk-27TS2AUJ.js.map +0 -1
  110. package/dist/chunk-5UG4UHKU.js.map +0 -1
  111. package/dist/chunk-G3ACLBMG.js +0 -6
  112. package/dist/chunk-GR7SFCQJ.js.map +0 -1
  113. package/dist/chunk-GWSJYDK7.js.map +0 -1
  114. package/dist/chunk-K3SXWOTT.js.map +0 -1
  115. package/dist/chunk-L7GCJNDT.js.map +0 -1
  116. package/dist/chunk-NS4DL3QR.js.map +0 -1
  117. package/dist/chunk-OIUVWCKM.js.map +0 -1
  118. package/dist/chunk-SHD5TCZ5.js.map +0 -1
  119. package/dist/chunk-SYMJSRYF.js.map +0 -1
  120. package/dist/chunk-TT2J2TB6.js.map +0 -1
  121. package/dist/chunk-VMPKOMHG.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-2XRBRFRB.js.map +0 -1
  125. package/dist/load-7C5VETRD.js +0 -11
  126. package/dist/rules-ASYEUNB5.js.map +0 -1
  127. package/dist/version-7ABYXWOZ.js +0 -4
  128. package/dist/vg-mcp-bridge-LXBHKEFR.js.map +0 -1
  129. package/dist/vgd-RW7HCEIP.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-SIXWWQ6S.js';
3
- import { telemetryOptOut, StatsSharer, statsEndpoint, isCI } from './chunk-BICZR6UJ.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-27TS2AUJ.js';
6
- export { runBuild } from './chunk-27TS2AUJ.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-GWSJYDK7.js';
10
- import { CONTEXT_POLICY_VERSION, computeFcsReport, renderFcsReportMarkdown, createTrajectoryCollector, runAgent } from './chunk-GR7SFCQJ.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-E3XVIFSH.js';
13
- import './chunk-JAEHLTVX.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-DIZ5KI5Q.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-KYM55VZX.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-3E7QT4NE.js';
30
- import { refreshIfStale } from './chunk-VMPKOMHG.js';
31
- import { driftCount, ASSISTANTS, detectAssistants, isAssistantInstalled, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, buildGraph, writeSnapshot, probeFreshness, assistantById, SMALL_REPO_FILES } from './chunk-NS4DL3QR.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-TT2J2TB6.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-L7GCJNDT.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-NZES47ST.js';
38
- import { recordCliCall, CLI_TOOL_ALIASES, savingsLedgerPath, clearSavings, readSavings, readUsage, readModelSavings, CAPSULE_RANKING_VERSION, SAVINGS_TOOLS, sanitizeClient } from './chunk-SYMJSRYF.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-IJVWKZRS.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-K3SXWOTT.js';
52
- import { loadGraph, loadGraphPreferIndex, SCHEMA_VERSION } from './chunk-7M4E7UGX.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-6MIJLO5B.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-FQHWPOTS.js';
60
- import { resetRelevanceProviderCache, loadRelevanceProvider, resolveGraphPath, loadTopicTags, rankQuestion, renderReport, loadGraphFileWithSnapshot, writeArtifacts, vibgrateDir, mapFileExists, ensureVibgrateGitignore } from './chunk-OIUVWCKM.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-G3ACLBMG.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);
@@ -7282,7 +7286,7 @@ function discoverMcpServers(root3, ownServers) {
7282
7286
 
7283
7287
  // src/commands/code.ts
7284
7288
  function registerCode(program) {
7285
- const cmd = program.command("code").description("graph-grounded coding agent (guided mode with no instruction; every edit and command is approved)").argument("[instruction...]", "what to change, in plain language (omit for guided interactive mode)").option("--provider <id>", "backend: vibgrate-relay (Vibgrate Relay), ollama, lmstudio, foundry-local, openrouter, litellm, openai, together, llama-cpp").option("--model <id>", "model id (or set VG_CODE_MODEL). No model is hard-coded \u2014 pick the current best.").option("--mode <mode>", "Code Mode: spark | flow | forge (preferred over raw model names; auto-fits when omitted and no --model)").option("--model-path <gguf>", "gguf path for --provider llama-cpp (weights are never auto-downloaded)").option("-f, --file <path>", "restrict the edit surface to this file (repeatable)", collect, []).option("-b, --budget <n>", "approx context token budget", "3000").option("--apply", "one-shot path (--single/--mock) only: write the change (still requires --yes or an interactive confirm)").option("--yes", "consent to write / to a first-use package install, non-interactively").option("--auto", "autonomous agent: auto-approve every edit and command (use with care)").option("--max-steps <n>", "cap the number of agent steps", "24").option("--single", "one-shot planner (single edit) instead of the multi-step agent").option("--stream", "stream the model output live").option("--stream-json", "machine protocol: NDJSON agent events on stdout, approval decisions on stdin (for host UIs like the VS Code panel)").option("--session", "with --stream-json: stay open for further turns (warm graph and tools) instead of exiting after one").option("--verify [command]", "after the agent finishes, run tests and make it fix failures (uses the config testCommand if no command is given)").option("--continue [id]", "resume a session: the most recent one, or the given session id (recap + restore /undo)").option("--restore-checkpoint <commit>", "one-shot: restore the given --file paths from a checkpoint commit, then exit (for host UIs after the original session ended)").option("--reasoning-effort <level>", "how hard a reasoning-capable model should think: low | medium | high (ignored by models without the knob)").option("--worktree [id]", "run the session in an isolated git worktree under .vibgrate/worktrees (bare: create a new one; with id: reuse it)").option("--worktree-diff <id>", "one-shot: print the worktree's delta against its base as a patch, then exit").option("--worktree-apply <id>", "one-shot: apply the worktree's delta onto the main tree (git apply --3way), then exit").option("--worktree-remove <id>", "one-shot: remove the worktree checkout, then exit").option("--capsule", "use a source-bearing Task Capsule for first context (Fusion A/B; exact source ranges from the graph)").option("--no-capsule", "disable Task Capsule context even if enabled in .vibgrate/code.json").option("--security-tier <tier>", "shell isolation: L0 (host), L1 (Seatbelt/bubblewrap when available), L2/L3 reserved").option("--mock <file>", "use a scripted reply from <file> instead of a model (offline; for tests/CI)").option("-o, --out <file>", "write the JSON result to <file> (implies --json shape; for CI/benchmarks)").action(async function(instructionParts, opts) {
7289
+ const cmd = program.command("code").description("graph-grounded coding agent (guided mode with no instruction; every edit and command is approved)").argument("[instruction...]", "what to change, in plain language (omit for guided interactive mode)").option("--provider <id>", "backend: vibgrate-relay (Vibgrate Relay), ollama, lmstudio, foundry-local, openrouter, litellm, openai, together, llama-cpp").option("--model <id>", "model id (or set VG_CODE_MODEL). No model is hard-coded \u2014 pick the current best.").option("--mode <mode>", "Code Mode: spark | flow | forge (preferred over raw model names; auto-fits when omitted and no --model)").option("--model-path <gguf>", "gguf path for --provider llama-cpp (weights are never auto-downloaded)").option("-f, --file <path>", "restrict the edit surface to this file (repeatable)", collect, []).option("-b, --budget <n>", "approx context token budget", "3000").option("--apply", "one-shot path (--single/--mock) only: write the change (still requires --yes or an interactive confirm)").option("--yes", "consent to write / to a first-use package install, non-interactively").option("--auto", "autonomous agent: auto-approve every edit and command (use with care)").option("--max-steps <n>", "cap the number of agent steps (default 24; also settable as maxSteps in vibgrate.config.json)").option("--single", "one-shot planner (single edit) instead of the multi-step agent").option("--stream", "stream the model output live").option("--stream-json", "machine protocol: NDJSON agent events on stdout, approval decisions on stdin (for host UIs like the VS Code panel)").option("--session", "with --stream-json: stay open for further turns (warm graph and tools) instead of exiting after one").option("--verify [command]", "after the agent finishes, run tests and make it fix failures (uses the config testCommand if no command is given)").option("--continue [id]", "resume a session: the most recent one, or the given session id (recap + restore /undo)").option("--restore-checkpoint <commit>", "one-shot: restore the given --file paths from a checkpoint commit, then exit (for host UIs after the original session ended)").option("--reasoning-effort <level>", "how hard a reasoning-capable model should think: low | medium | high (ignored by models without the knob)").option("--worktree [id]", "run the session in an isolated git worktree under .vibgrate/worktrees (bare: create a new one; with id: reuse it)").option("--worktree-diff <id>", "one-shot: print the worktree's delta against its base as a patch, then exit").option("--worktree-apply <id>", "one-shot: apply the worktree's delta onto the main tree (git apply --3way), then exit").option("--worktree-remove <id>", "one-shot: remove the worktree checkout, then exit").option("--capsule", "use a source-bearing Task Capsule for first context (Fusion A/B; exact source ranges from the graph)").option("--no-capsule", "disable Task Capsule context even if enabled in .vibgrate/code.json").option("--security-tier <tier>", "shell isolation: L0 (host), L1 (Seatbelt/bubblewrap when available), L2/L3 reserved").option("--mock <file>", "use a scripted reply from <file> instead of a model (offline; for tests/CI)").option("-o, --out <file>", "write the JSON result to <file> (implies --json shape; for CI/benchmarks)").action(async function(instructionParts, opts) {
7286
7290
  const global = readGlobal(this);
7287
7291
  let instruction = (instructionParts ?? []).join(" ").trim();
7288
7292
  if (typeof opts.continue === "string") {
@@ -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-7C5VETRD.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;
@@ -7522,7 +7526,8 @@ function registerCode(program) {
7522
7526
  }
7523
7527
  }
7524
7528
  const auto = opts.auto ?? config.auto;
7525
- const maxSteps = opts.maxSteps ? Number(opts.maxSteps) : config.maxSteps;
7529
+ const parsedMaxSteps = opts.maxSteps === void 0 ? NaN : Number(opts.maxSteps);
7530
+ const maxSteps = Number.isFinite(parsedMaxSteps) && parsedMaxSteps > 0 ? Math.floor(parsedMaxSteps) : config.maxSteps;
7526
7531
  const capsule = typeof opts.capsule === "boolean" ? opts.capsule : !!config.capsule;
7527
7532
  const securityTier = resolveSecurityTierFlag(opts.securityTier, config.securityTier, !!auto);
7528
7533
  const contextBudget = contextBudgetFor(config);
@@ -7534,7 +7539,7 @@ function registerCode(program) {
7534
7539
  if (!(process.stdin.isTTY && process.stdout.isTTY)) {
7535
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)');
7536
7541
  }
7537
- const { runInteractive } = await import('./interactive-2XRBRFRB.js');
7542
+ const { runInteractive } = await import('./interactive-VQM2CTE2.js');
7538
7543
  await runInteractive(
7539
7544
  rootOf(global),
7540
7545
  global,
@@ -7564,10 +7569,10 @@ function registerCode(program) {
7564
7569
  }
7565
7570
  if (!instruction && !stdinFirstTurn) throw usageError('say what to change, e.g. `vg code "add a --timeout flag to the scan command"`');
7566
7571
  {
7567
- const { ensureCodeMap } = await import('./ensure-map-DL2S774J.js');
7572
+ const { ensureCodeMap: ensureCodeMap2 } = await import('./ensure-map-VAIYCCEJ.js');
7568
7573
  const mapRoot = rootOf(global);
7569
7574
  try {
7570
- await ensureCodeMap(mapRoot, global, {
7575
+ await ensureCodeMap2(mapRoot, global, {
7571
7576
  onProgress: (p) => {
7572
7577
  if (opts.streamJson) {
7573
7578
  process.stdout.write(
@@ -7624,7 +7629,7 @@ function registerCode(program) {
7624
7629
  const ensure = await ensureManagerRuntime({
7625
7630
  providerId: route.providers[0].id,
7626
7631
  model: route.providers[0].model,
7627
- offline: !!global.local,
7632
+ offline: !!global.offline,
7628
7633
  // Code Mode / explicit custom selection / --yes count as consent for
7629
7634
  // npm runtime deps (and ollama pull when the binary is already present).
7630
7635
  consent: !!(opts.yes || codeModeActive || customSelection || process.stdin.isTTY),
@@ -7649,20 +7654,20 @@ function registerCode(program) {
7649
7654
  process.stdout.write(JSON.stringify(line) + "\n");
7650
7655
  };
7651
7656
  try {
7652
- const { RunOutcomeRecorder, newOneShotRunId, turnRunId } = await import('./run-outcome-TMB5TJNA.js');
7657
+ const { RunOutcomeRecorder, newOneShotRunId, turnRunId } = await import('./run-outcome-EATWIKLR.js');
7653
7658
  const primarySlug = route.providers[0]?.model.includes("/") ? route.providers[0].model.split("/")[0] : route.providers[0]?.id;
7654
- const { runCodeStreamJson, parseHostMessage } = await import('./stream-json-NW6AOE47.js');
7655
- const { nodeCodeFs } = await import('./session-KXT2ZTPE.js');
7659
+ const { runCodeStreamJson, parseHostMessage } = await import('./stream-json-GCZAHORX.js');
7660
+ const { nodeCodeFs } = await import('./session-N2FTYIHQ.js');
7656
7661
  const readline6 = await import('readline');
7657
7662
  const child = await import('child_process');
7658
7663
  const { loadOrDiscoverFederation } = await import('./federation-M4ZNXOPX.js');
7659
7664
  const { resolveExecutionEnv } = await import('./execution-env-4MS5AYAN.js');
7660
7665
  const { resolveModelExecutionProfile, mergeModelExecutionProfile } = await import('./model-execution-profile-ELWF3Z6H.js');
7661
- const { startCodeRuntimeSession } = await import('./runtime-session-Y23NVY7R.js');
7662
- const { resolveGraphBackend } = await import('./graph-backend-JXARVT3R.js');
7666
+ const { startCodeRuntimeSession } = await import('./runtime-session-Y2BRBXUW.js');
7667
+ const { resolveGraphBackend } = await import('./graph-backend-V5A2H5ZB.js');
7663
7668
  const { detectGitRef } = await import('./git-ref-LMT3EXYY.js');
7664
7669
  const { repositoryIdFromRoot } = await import('./paths-RVK6FKRN.js');
7665
- const { vgdRequest } = await import('./vgd-RW7HCEIP.js');
7670
+ const { vgdRequest } = await import('./vgd-3A2TRNP5.js');
7666
7671
  const federation = loadOrDiscoverFederation(root3);
7667
7672
  const modelProfile = mergeModelExecutionProfile(
7668
7673
  resolveModelExecutionProfile({
@@ -7720,7 +7725,7 @@ ${res.stderr}` : ""),
7720
7725
  exitCode: res.status ?? 1
7721
7726
  };
7722
7727
  };
7723
- const { createVgBuiltinMcpTools, mergeExternalToolsets } = await import('./vg-mcp-bridge-LXBHKEFR.js');
7728
+ const { createVgBuiltinMcpTools, mergeExternalToolsets } = await import('./vg-mcp-bridge-VDZWFEO2.js');
7724
7729
  const vgBuiltin = createVgBuiltinMcpTools({
7725
7730
  getGraph: () => graph,
7726
7731
  root: root3,
@@ -7765,8 +7770,8 @@ ${res.stderr}` : ""),
7765
7770
  files,
7766
7771
  indexFile: checkpointIndex
7767
7772
  });
7768
- const { loadApprovalRules, addApprovalRule, ruleForAction, ruleLabel } = await import('./approvals-KBM72NU2.js');
7769
- 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');
7770
7775
  const projectRules = loadProjectRules(nodeRulesReader(root3));
7771
7776
  if (projectRules.sources.length && !global.quiet) {
7772
7777
  emitStream({
@@ -7823,8 +7828,8 @@ ${res.stderr}` : ""),
7823
7828
  };
7824
7829
  try {
7825
7830
  if (opts.session) {
7826
- const { runCodeStreamJsonSession, STREAM_JSON_PROTOCOL_VERSION } = await import('./stream-json-NW6AOE47.js');
7827
- const { runAgent: runAgent2 } = await import('./agent-DHQYEHVW.js');
7831
+ const { runCodeStreamJsonSession, STREAM_JSON_PROTOCOL_VERSION } = await import('./stream-json-GCZAHORX.js');
7832
+ const { runAgent: runAgent2 } = await import('./agent-5GW7C7EB.js');
7828
7833
  const {
7829
7834
  condenseSession,
7830
7835
  loadLatestSession,
@@ -7835,7 +7840,7 @@ ${res.stderr}` : ""),
7835
7840
  sessionsDir,
7836
7841
  summarizeSession
7837
7842
  } = await import('./session-store-AQUORKWB.js');
7838
- const { VERSION: VERSION2 } = await import('./version-7ABYXWOZ.js');
7843
+ const { VERSION: VERSION2 } = await import('./version-XNZ4GLMQ.js');
7839
7844
  const resumedRecord = typeof opts.continue === "string" ? loadSession(mainRoot, opts.continue) : opts.continue ? loadLatestSession(mainRoot) : void 0;
7840
7845
  if (typeof opts.continue === "string" && !resumedRecord) {
7841
7846
  emitStream({
@@ -7867,8 +7872,8 @@ ${res.stderr}` : ""),
7867
7872
  if (ended) return Promise.resolve(null);
7868
7873
  const queued = pendingTurns.shift();
7869
7874
  if (queued !== void 0) return Promise.resolve(queued);
7870
- return new Promise((resolve23) => {
7871
- wakeTurn = resolve23;
7875
+ return new Promise((resolve25) => {
7876
+ wakeTurn = resolve25;
7872
7877
  });
7873
7878
  };
7874
7879
  let turnIndex = 0;
@@ -8047,8 +8052,8 @@ ${res.stderr}` : ""),
8047
8052
  if (!autonomous && !tty) {
8048
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).");
8049
8054
  }
8050
- const { agentTask } = await import('./interactive-2XRBRFRB.js');
8051
- const { nodeCodeFs } = await import('./session-KXT2ZTPE.js');
8055
+ const { agentTask } = await import('./interactive-VQM2CTE2.js');
8056
+ const { nodeCodeFs } = await import('./session-N2FTYIHQ.js');
8052
8057
  const { TtyPrompter } = await import('./ui-K7D3BCHD.js');
8053
8058
  let priorSummary;
8054
8059
  let priorInstruction;
@@ -8063,7 +8068,7 @@ ${res.stderr}` : ""),
8063
8068
  }
8064
8069
  }
8065
8070
  const prompter = tty ? new TtyPrompter() : void 0;
8066
- const { createVgBuiltinMcpTools, mergeExternalToolsets } = await import('./vg-mcp-bridge-LXBHKEFR.js');
8071
+ const { createVgBuiltinMcpTools, mergeExternalToolsets } = await import('./vg-mcp-bridge-VDZWFEO2.js');
8067
8072
  const vgBuiltin = createVgBuiltinMcpTools({
8068
8073
  getGraph: () => graph,
8069
8074
  root: root3,
@@ -8087,11 +8092,11 @@ ${res.stderr}` : ""),
8087
8092
  const { loadFederation } = await import('./federation-M4ZNXOPX.js');
8088
8093
  const { resolveExecutionEnv } = await import('./execution-env-4MS5AYAN.js');
8089
8094
  const { resolveModelExecutionProfile, mergeModelExecutionProfile } = await import('./model-execution-profile-ELWF3Z6H.js');
8090
- const { startCodeRuntimeSession } = await import('./runtime-session-Y23NVY7R.js');
8091
- const { resolveGraphBackend } = await import('./graph-backend-JXARVT3R.js');
8095
+ const { startCodeRuntimeSession } = await import('./runtime-session-Y2BRBXUW.js');
8096
+ const { resolveGraphBackend } = await import('./graph-backend-V5A2H5ZB.js');
8092
8097
  const { detectGitRef } = await import('./git-ref-LMT3EXYY.js');
8093
8098
  const { repositoryIdFromRoot } = await import('./paths-RVK6FKRN.js');
8094
- const { vgdRequest } = await import('./vgd-RW7HCEIP.js');
8099
+ const { vgdRequest } = await import('./vgd-3A2TRNP5.js');
8095
8100
  const federation = loadFederation(root3);
8096
8101
  const modelProfile = mergeModelExecutionProfile(
8097
8102
  resolveModelExecutionProfile({
@@ -8237,11 +8242,11 @@ function resolveSecurityTierFlag(flag, fromConfig, auto) {
8237
8242
  return auto ? "L1" : "L0";
8238
8243
  }
8239
8244
  function confirm2(question) {
8240
- return new Promise((resolve23) => {
8245
+ return new Promise((resolve25) => {
8241
8246
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
8242
8247
  rl.question(`${question} [y/N] `, (answer) => {
8243
8248
  rl.close();
8244
- resolve23(/^y(es)?$/i.test(answer.trim()));
8249
+ resolve25(/^y(es)?$/i.test(answer.trim()));
8245
8250
  });
8246
8251
  });
8247
8252
  }
@@ -8350,9 +8355,9 @@ function registerLib(program) {
8350
8355
  const global = readGlobal(this);
8351
8356
  const root3 = rootOf(global);
8352
8357
  const [verb, ...rest] = args;
8353
- const network = !global.local;
8358
+ const network = !global.offline;
8354
8359
  if (!verb) return listCatalog(root3, global.json);
8355
- const online = (Boolean(opts.online) || network) && !global.local;
8360
+ const online = (Boolean(opts.online) || network) && !global.offline;
8356
8361
  if (verb === "add") return addCmd(root3, rest, { ...opts, online }, global.json);
8357
8362
  if (verb === "publish") return publishCmd(root3, rest, opts, global, global.json);
8358
8363
  if (verb === "resolve") return resolveCmd(root3, rest, global.json);
@@ -8399,7 +8404,7 @@ async function addCmd(root3, rest, opts, asJson) {
8399
8404
  }
8400
8405
  function readPackageVersion(root3) {
8401
8406
  try {
8402
- 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"));
8403
8408
  return typeof pkg.version === "string" ? pkg.version : void 0;
8404
8409
  } catch {
8405
8410
  return void 0;
@@ -8414,7 +8419,7 @@ function readOptionalFile(p) {
8414
8419
  }
8415
8420
  }
8416
8421
  async function publishCmd(root3, rest, opts, global, asJson) {
8417
- 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)");
8418
8423
  const name = (rest.join(" ") || opts.name || "").trim();
8419
8424
  if (!name) throw usageError("usage: vg lib publish <name> [--version <v>] [--readme <path>] [--dts <path>] [--language <lang>]");
8420
8425
  const dsn = resolveDsn();
@@ -8422,8 +8427,8 @@ async function publishCmd(root3, rest, opts, global, asJson) {
8422
8427
  if (!parsed) throw new CliError("publishing a private library requires a DSN \u2014 run `vibgrate login` or set VIBGRATE_DSN", ExitCode.USAGE_ERROR);
8423
8428
  const version = (opts.version || readPackageVersion(root3) || "").trim();
8424
8429
  if (!version) throw usageError("a --version is required (or run from a package whose package.json has a version)");
8425
- const readme = readOptionalFile(opts.readme ? path45__default.resolve(opts.readme) : path45__default.join(root3, "README.md"));
8426
- 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;
8427
8432
  if (!readme && !dts) throw usageError("nothing to publish \u2014 no README.md found and no --dts given");
8428
8433
  const result = await publishPrivateLibrary(
8429
8434
  { name, version, readme, dts, language: opts.language },
@@ -8546,7 +8551,7 @@ function registerExport(program) {
8546
8551
  const content = exportGraph(format, {
8547
8552
  graph,
8548
8553
  deps: needsDeps ? inventory(root3).records : void 0,
8549
- models: needsDeps && !global.local ? discoverModels() : void 0,
8554
+ models: needsDeps && !global.offline ? discoverModels() : void 0,
8550
8555
  generatedAt: graph.generatedAt,
8551
8556
  compact: opts.compact === true ? true : opts.compact === false ? false : void 0,
8552
8557
  slim: opts.slim === true
@@ -8555,7 +8560,7 @@ function registerExport(program) {
8555
8560
  out(content.trimEnd());
8556
8561
  return;
8557
8562
  }
8558
- fs12.mkdirSync(path45.dirname(path45.resolve(file)), { recursive: true });
8563
+ fs12.mkdirSync(path51.dirname(path51.resolve(file)), { recursive: true });
8559
8564
  fs12.writeFileSync(file, content);
8560
8565
  info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
8561
8566
  });
@@ -8566,49 +8571,49 @@ function detectFormat(file) {
8566
8571
  if (lower.endsWith(".cdx.json")) return "cyclonedx";
8567
8572
  if (lower.endsWith(".spdx.json")) return "spdx";
8568
8573
  if (file === "-") return "json";
8569
- return formatForExt(path45.extname(file));
8574
+ return formatForExt(path51.extname(file));
8570
8575
  }
8571
8576
  function registerBundle(program) {
8572
- 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) {
8573
8578
  const global = readGlobal(this);
8574
8579
  const root3 = rootOf(global);
8575
- const outDir = path45.resolve(root3, opts.out ?? "vg-bundle");
8580
+ const outDir = path51.resolve(root3, opts.out ?? "vg-bundle");
8576
8581
  let grammarFiles;
8577
8582
  try {
8578
8583
  grammarFiles = resolvedGrammarFiles();
8579
8584
  } catch {
8580
8585
  throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
8581
8586
  }
8582
- fs12.mkdirSync(path45.join(outDir, "grammars"), { recursive: true });
8587
+ fs12.mkdirSync(path51.join(outDir, "grammars"), { recursive: true });
8583
8588
  let grammarCount = 0;
8584
8589
  for (const { fileName, absPath } of grammarFiles) {
8585
- fs12.copyFileSync(absPath, path45.join(outDir, "grammars", fileName));
8590
+ fs12.copyFileSync(absPath, path51.join(outDir, "grammars", fileName));
8586
8591
  grammarCount++;
8587
8592
  }
8588
8593
  for (const id of Object.keys(TOOLCHAIN_GRAMMARS).sort()) {
8589
8594
  const absPath = resolveToolchainGrammar(id);
8590
8595
  if (!absPath) continue;
8591
- fs12.copyFileSync(absPath, path45.join(outDir, "grammars", path45.basename(absPath)));
8596
+ fs12.copyFileSync(absPath, path51.join(outDir, "grammars", path51.basename(absPath)));
8592
8597
  grammarCount++;
8593
8598
  }
8594
8599
  const included = [`grammars/ (${grammarCount} .wasm)`];
8595
8600
  const graphSrc = resolveGraphPath(root3, global.graph);
8596
8601
  if (fs12.existsSync(graphSrc)) {
8597
- fs12.copyFileSync(graphSrc, path45.join(outDir, "graph.json"));
8602
+ fs12.copyFileSync(graphSrc, path51.join(outDir, "graph.json"));
8598
8603
  included.push("graph.json");
8599
8604
  } else {
8600
8605
  const graph = loadGraphFileWithSnapshot(graphSrc);
8601
8606
  if (graph) {
8602
- fs12.writeFileSync(path45.join(outDir, "graph.json"), serializeGraph(graph));
8607
+ fs12.writeFileSync(path51.join(outDir, "graph.json"), serializeGraph(graph));
8603
8608
  included.push("graph.json");
8604
8609
  }
8605
8610
  }
8606
8611
  const cat = catalogPath(root3);
8607
8612
  if (fs12.existsSync(cat)) {
8608
- fs12.copyFileSync(cat, path45.join(outDir, "vibgrate.lib.json"));
8613
+ fs12.copyFileSync(cat, path51.join(outDir, "vibgrate.lib.json"));
8609
8614
  included.push("vibgrate.lib.json");
8610
8615
  if (fs12.existsSync(libDir(root3))) {
8611
- fs12.cpSync(libDir(root3), path45.join(outDir, "lib"), { recursive: true });
8616
+ fs12.cpSync(libDir(root3), path51.join(outDir, "lib"), { recursive: true });
8612
8617
  included.push("lib/");
8613
8618
  }
8614
8619
  }
@@ -8620,15 +8625,15 @@ function registerBundle(program) {
8620
8625
  included,
8621
8626
  usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
8622
8627
  };
8623
- fs12.writeFileSync(path45.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
8628
+ fs12.writeFileSync(path51.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
8624
8629
  `);
8625
8630
  if (global.json) {
8626
- json({ out: path45.relative(root3, outDir), ...manifest });
8631
+ json({ out: path51.relative(root3, outDir), ...manifest });
8627
8632
  return;
8628
8633
  }
8629
- info(`${c.cyan("vg bundle")} \xB7 wrote ${path45.relative(root3, outDir)}/`);
8634
+ info(`${c.cyan("vg bundle")} \xB7 wrote ${path51.relative(root3, outDir)}/`);
8630
8635
  for (const i of included) info(` ${c.green("\u2714")} ${i}`);
8631
- 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"`));
8632
8637
  });
8633
8638
  applyGlobalOptions(cmd);
8634
8639
  }
@@ -8644,9 +8649,9 @@ var REACH_TIMEOUT_MS = 3e3;
8644
8649
  var CONFIG_BASENAMES = ["vibgrate.config.ts", "vibgrate.config.js", "vibgrate.config.json"];
8645
8650
  async function runDoctor(global) {
8646
8651
  const root3 = rootOf(global);
8647
- const local = global.local === true;
8652
+ const local = global.offline === true;
8648
8653
  const graphPath = resolveGraphPath(root3, global.graph);
8649
- 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;
8650
8655
  const graph = loadGraph(root3, graphPath);
8651
8656
  let staleFiles = null;
8652
8657
  if (graph) {
@@ -8672,13 +8677,13 @@ async function runDoctor(global) {
8672
8677
  root: root3,
8673
8678
  configFile,
8674
8679
  map: {
8675
- path: path45.relative(root3, graphPath),
8680
+ path: displayGraphPath(root3, graphPath),
8676
8681
  built: graph !== null,
8677
8682
  generatedAt: graph?.generatedAt ?? null,
8678
8683
  staleFiles
8679
8684
  },
8680
8685
  libCatalog: {
8681
- path: path45.relative(root3, catalogPath(root3)),
8686
+ path: path51.relative(root3, catalogPath(root3)),
8682
8687
  present: fs12.existsSync(catalogPath(root3)),
8683
8688
  libraries: libCount
8684
8689
  },
@@ -8736,7 +8741,7 @@ async function runDoctor(global) {
8736
8741
  );
8737
8742
  info(
8738
8743
  ` host pool ${li.hostPool.size} warm session(s)` + (li.hostPool.sessions[0] ? c.dim(
8739
- ` \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}`
8740
8745
  ) : c.dim(" \xB7 none (load via vg code / vgd host-load)"))
8741
8746
  );
8742
8747
  if (li.dynamicOnToken || li.dynamicCustomSampler) {
@@ -8906,12 +8911,12 @@ function registerLlmHost(program) {
8906
8911
  let binding = null;
8907
8912
  const ensured = await ensurePackage("node-llama-cpp@^3", {
8908
8913
  consent: !!opts.yes,
8909
- local: !!global.local,
8914
+ local: !!global.offline,
8910
8915
  interactive: false
8911
8916
  });
8912
8917
  if (ensured.module) {
8913
8918
  binding = ensured.module;
8914
- } else if (opts.yes || !global.local) {
8919
+ } else if (opts.yes || !global.offline) {
8915
8920
  if (!global.json) {
8916
8921
  info(
8917
8922
  c.dim(
@@ -9043,7 +9048,7 @@ async function readStdin() {
9043
9048
  return Buffer.concat(chunks).toString("utf-8");
9044
9049
  }
9045
9050
  function readFileOrThrow(file, label) {
9046
- const p = path45.resolve(file);
9051
+ const p = path51.resolve(file);
9047
9052
  if (!fs12.existsSync(p)) throw usageError(`${label} file not found: ${p}`);
9048
9053
  return fs12.readFileSync(p, "utf-8");
9049
9054
  }
@@ -9055,8 +9060,8 @@ function writeOutput(outFile, content) {
9055
9060
  process.stdout.write(content.endsWith("\n") ? content : content + "\n");
9056
9061
  return;
9057
9062
  }
9058
- const outPath = path45.resolve(outFile);
9059
- const dir = path45.dirname(outPath);
9063
+ const outPath = path51.resolve(outFile);
9064
+ const dir = path51.dirname(outPath);
9060
9065
  if (!fs12.existsSync(dir)) throw usageError(`output directory does not exist: ${dir}`);
9061
9066
  fs12.writeFileSync(outPath, content.endsWith("\n") ? content : content + "\n", "utf-8");
9062
9067
  info(c.dim(`wrote ${outPath}`));
@@ -9091,7 +9096,7 @@ function registerHcs(program) {
9091
9096
  }
9092
9097
  const statePath = opts.previous ?? opts.out;
9093
9098
  const useIncremental = canIncremental && statePath !== void 0;
9094
- const root3 = path45.resolve(dir);
9099
+ const root3 = path51.resolve(dir);
9095
9100
  if (!fs12.existsSync(root3) || !fs12.statSync(root3).isDirectory()) {
9096
9101
  throw usageError(`not a directory: ${root3}`);
9097
9102
  }
@@ -9116,7 +9121,7 @@ function registerHcs(program) {
9116
9121
  }
9117
9122
  for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : 1)) {
9118
9123
  if (entry.name.startsWith(".")) continue;
9119
- const absChild = path45.join(abs, entry.name);
9124
+ const absChild = path51.join(abs, entry.name);
9120
9125
  const relChild = rel ? `${rel}/${entry.name}` : entry.name;
9121
9126
  if (entry.isDirectory()) {
9122
9127
  if (!SKIP_DIRS.has(entry.name)) walk2(absChild, relChild);
@@ -9124,7 +9129,7 @@ function registerHcs(program) {
9124
9129
  }
9125
9130
  if (!entry.isFile()) continue;
9126
9131
  if (!opts.includeTests && TEST_PATTERNS.some((p) => p.test(relChild))) continue;
9127
- const lang = extToLang.get(path45.extname(entry.name).toLowerCase());
9132
+ const lang = extToLang.get(path51.extname(entry.name).toLowerCase());
9128
9133
  if (!lang) continue;
9129
9134
  try {
9130
9135
  if (fs12.statSync(absChild).size > MAX_EXTRACT_FILE_BYTES) continue;
@@ -9158,7 +9163,7 @@ function registerHcs(program) {
9158
9163
  writeOutput(opts.out, parts2.join("\n"));
9159
9164
  return;
9160
9165
  }
9161
- const previousPath = path45.resolve(statePath);
9166
+ const previousPath = path51.resolve(statePath);
9162
9167
  const previousNdjson = !opts.full && fs12.existsSync(previousPath) ? fs12.readFileSync(previousPath, "utf-8") : "";
9163
9168
  if (opts.full) info(c.dim("running a full re-extraction (--full)"));
9164
9169
  else if (!previousNdjson) info(c.dim(`previous stream not found at ${previousPath} \u2014 running a full extraction`));
@@ -9262,12 +9267,2005 @@ function registerHcs(program) {
9262
9267
  });
9263
9268
  applyGlobalOptions(cmd);
9264
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
+ }
9265
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) => {
9266
- const rootDir = path45.resolve(targetPath);
9267
- const vibgrateDir2 = path45.join(rootDir, ".vibgrate");
11264
+ const rootDir = path51.resolve(targetPath);
11265
+ const vibgrateDir2 = path51.join(rootDir, ".vibgrate");
9268
11266
  await ensureDir(vibgrateDir2);
9269
11267
  console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold(".vibgrate/")} directory`);
9270
- const configPath = path45.join(rootDir, "vibgrate.config.ts");
11268
+ const configPath = path51.join(rootDir, "vibgrate.config.ts");
9271
11269
  if (await pathExists(configPath)) {
9272
11270
  console.log(chalk7.dim(" vibgrate.config.ts already exists, skipping"));
9273
11271
  } else {
@@ -9275,7 +11273,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
9275
11273
  console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold("vibgrate.config.ts")}`);
9276
11274
  }
9277
11275
  if (opts.baseline) {
9278
- const { runBaseline } = await import('./baseline-4X6MQBDA.js');
11276
+ const { runBaseline } = await import('./baseline-YL2IZZ4Y.js');
9279
11277
  await runBaseline(rootDir);
9280
11278
  }
9281
11279
  console.log("");
@@ -9378,7 +11376,7 @@ function collectPackageImports(graph, ecosystem, pkg) {
9378
11376
  };
9379
11377
  }
9380
11378
  async function firstReferenceLine(rootDir, relFile, identifier, readFile3) {
9381
- const content = await readFile3(path45.join(rootDir, relFile));
11379
+ const content = await readFile3(path51.join(rootDir, relFile));
9382
11380
  if (!content) return null;
9383
11381
  const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9384
11382
  const re = new RegExp(`\\b${escaped}\\b`);
@@ -9416,9 +11414,9 @@ function sitesFromImportFiles(files, limit = 10) {
9416
11414
  }
9417
11415
  async function defaultReadFile(absPath) {
9418
11416
  try {
9419
- const stat2 = await fs26.stat(absPath);
11417
+ const stat2 = await fs32.stat(absPath);
9420
11418
  if (!stat2.isFile() || stat2.size > MAX_FILE_BYTES) return null;
9421
- return await fs26.readFile(absPath, "utf8");
11419
+ return await fs32.readFile(absPath, "utf8");
9422
11420
  } catch {
9423
11421
  return null;
9424
11422
  }
@@ -9614,7 +11612,7 @@ var AI_ASSISTANT_DETECTORS = [
9614
11612
  async function detectAiAssistant(rootDir) {
9615
11613
  for (const detector of AI_ASSISTANT_DETECTORS) {
9616
11614
  for (const file of detector.files) {
9617
- if (await pathExists$1(path45.join(rootDir, file))) {
11615
+ if (await pathExists$1(path51.join(rootDir, file))) {
9618
11616
  return detector.name;
9619
11617
  }
9620
11618
  }
@@ -9808,7 +11806,7 @@ function reportStandards(rootDir) {
9808
11806
  }
9809
11807
  async function printRuntimeSnapshot(rootDir, opts, already) {
9810
11808
  try {
9811
- const { attachVgd: attachVgd2 } = await import('./attach-O74NL4M5.js');
11809
+ const { attachVgd: attachVgd2 } = await import('./attach-JUSALQ75.js');
9812
11810
  const { vgdRequest } = await import('./client-P5XSRFV4.js');
9813
11811
  const { formatSemanticProgress } = await import('./semantic-status-2JFLIQQJ.js');
9814
11812
  let repositoryId = already?.repositoryId;
@@ -9850,7 +11848,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
9850
11848
  collectExcludes,
9851
11849
  []
9852
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) => {
9853
- const rootDir = path45.resolve(targetPath);
11851
+ const rootDir = path51.resolve(targetPath);
9854
11852
  if (!await pathExists$1(rootDir)) {
9855
11853
  console.error(chalk7.red(`Path does not exist: ${rootDir}`));
9856
11854
  process.exit(1);
@@ -9858,7 +11856,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
9858
11856
  const hasDsn = !!resolveDsn(opts.dsn);
9859
11857
  let willPush = !opts.offline && (opts.push || hasDsn);
9860
11858
  const noLocalArtifacts = opts.localArtifacts === false;
9861
- 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"));
9862
11860
  let pinnedRegion;
9863
11861
  let planTier;
9864
11862
  let upgradeUrl;
@@ -10017,7 +12015,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
10017
12015
  const advanced = await loadAdvancedScanHook();
10018
12016
  const artifact = await runCoreScan(rootDir, scanOpts, advanced);
10019
12017
  if (wantGraph && builtGraph) {
10020
- const { attachVgd: attachVgd2 } = await import('./attach-O74NL4M5.js');
12018
+ const { attachVgd: attachVgd2 } = await import('./attach-JUSALQ75.js');
10021
12019
  const { ActivityLog: ActivityLog2 } = await import('./activity-MF2GNYAI.js');
10022
12020
  const activity = new ActivityLog2();
10023
12021
  const attached = await activity.time(
@@ -10185,11 +12183,11 @@ function scanStaleness(rootDir, artifactPath) {
10185
12183
  scanned++;
10186
12184
  if (scanned > MAX_ENTRIES2) break;
10187
12185
  if (e.isDirectory()) {
10188
- 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);
10189
12187
  continue;
10190
12188
  }
10191
12189
  if (!WATCHED_FILES.has(e.name) && !WATCHED_EXT.test(e.name)) continue;
10192
- const abs = path45.join(dir, e.name);
12190
+ const abs = path51.join(dir, e.name);
10193
12191
  let mtimeMs;
10194
12192
  try {
10195
12193
  mtimeMs = fs12.statSync(abs).mtimeMs;
@@ -10198,7 +12196,7 @@ function scanStaleness(rootDir, artifactPath) {
10198
12196
  }
10199
12197
  if (mtimeMs > newest) {
10200
12198
  newest = mtimeMs;
10201
- newestChanged = path45.relative(rootDir, abs) || e.name;
12199
+ newestChanged = path51.relative(rootDir, abs) || e.name;
10202
12200
  }
10203
12201
  }
10204
12202
  };
@@ -10338,13 +12336,13 @@ function analyzeTree(root3, packages) {
10338
12336
  if (filesSeen >= MAX_FILES) return;
10339
12337
  if (entry.isDirectory()) {
10340
12338
  if (SKIP_DIRS3.has(entry.name) || entry.name.startsWith(".")) continue;
10341
- walk2(path45.join(dir, entry.name), depth + 1);
12339
+ walk2(path51.join(dir, entry.name), depth + 1);
10342
12340
  continue;
10343
12341
  }
10344
- const fileEco = EXT_ECOSYSTEM[path45.extname(entry.name)];
12342
+ const fileEco = EXT_ECOSYSTEM[path51.extname(entry.name)];
10345
12343
  if (!fileEco || fileEco === "unknown") continue;
10346
12344
  filesSeen++;
10347
- const full = path45.join(dir, entry.name);
12345
+ const full = path51.join(dir, entry.name);
10348
12346
  let content;
10349
12347
  try {
10350
12348
  if (fs12.statSync(full).size > MAX_FILE_BYTES2) continue;
@@ -10620,7 +12618,7 @@ function pmCommandFor(ecosystem, pkg, version, pm = "npm", opts = {}) {
10620
12618
  return null;
10621
12619
  }
10622
12620
  }
10623
- function defaultRun(cmd, cwd) {
12621
+ function defaultRun3(cmd, cwd) {
10624
12622
  const res = spawnSync(cmd.cmd, cmd.args, { cwd, stdio: "inherit", shell: false });
10625
12623
  if (res.error) {
10626
12624
  if (res.error.code === "ENOENT") {
@@ -10633,7 +12631,7 @@ function defaultRun(cmd, cwd) {
10633
12631
  }
10634
12632
  var ROOT_TARGET = [{ dir: ".", isWorkspaceRoot: false }];
10635
12633
  function applyPlan(rootDir, upgrades, opts = {}) {
10636
- const run = opts.run ?? defaultRun;
12634
+ const run = opts.run ?? defaultRun3;
10637
12635
  const resolveTargets = opts.resolveTargets ?? (() => ROOT_TARGET);
10638
12636
  const results = [];
10639
12637
  for (const u of upgrades) {
@@ -10659,7 +12657,7 @@ function applyPlan(rootDir, upgrades, opts = {}) {
10659
12657
  }
10660
12658
  const outcomes = effective.map((t) => {
10661
12659
  const c2 = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager, { workspaceRoot: t.isWorkspaceRoot });
10662
- return { t, res: run(c2, path45.join(rootDir, t.dir)) };
12660
+ return { t, res: run(c2, path51.join(rootDir, t.dir)) };
10663
12661
  });
10664
12662
  const failures = outcomes.filter((o) => !o.res.ok);
10665
12663
  if (failures.length === 0) {
@@ -10688,8 +12686,8 @@ function applyPlan(rootDir, upgrades, opts = {}) {
10688
12686
  return results;
10689
12687
  }
10690
12688
  var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
10691
- var CACHE_DIR = path45.join(os.homedir(), ".vibgrate");
10692
- 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");
10693
12691
  async function fetchLatestVersion() {
10694
12692
  try {
10695
12693
  const controller = new AbortController();
@@ -10716,8 +12714,8 @@ async function fetchLatestVersion() {
10716
12714
  }
10717
12715
  async function writeCache(data) {
10718
12716
  try {
10719
- await fs26.mkdir(CACHE_DIR, { recursive: true });
10720
- 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");
10721
12719
  } catch {
10722
12720
  }
10723
12721
  }
@@ -10752,8 +12750,8 @@ function buildDeferredUpdateScript(opts) {
10752
12750
  }
10753
12751
  function scheduleDeferredUpdate(command, pid = process.pid, dir = os.tmpdir()) {
10754
12752
  const stamp = `${pid}-${process.hrtime.bigint().toString(36)}`;
10755
- const scriptPath = path45.join(dir, `vg-update-${stamp}.cmd`);
10756
- 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`);
10757
12755
  try {
10758
12756
  fs12.writeFileSync(scriptPath, buildDeferredUpdateScript({ pid, command, logPath }), "utf8");
10759
12757
  const child = spawn(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", scriptPath], {
@@ -10809,14 +12807,14 @@ async function findGlobalInstall(pkg = PACKAGE_NAME, run = defaultRunner) {
10809
12807
  pm: "yarn",
10810
12808
  root: () => {
10811
12809
  const dir = tryRun("yarn global dir");
10812
- return dir ? path45.join(dir, "node_modules") : null;
12810
+ return dir ? path51.join(dir, "node_modules") : null;
10813
12811
  }
10814
12812
  },
10815
- { 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") }
10816
12814
  ];
10817
12815
  for (const probe of probes) {
10818
12816
  const root3 = probe.root();
10819
- if (root3 && await pathExists(path45.join(root3, pkg))) return probe.pm;
12817
+ if (root3 && await pathExists(path51.join(root3, pkg))) return probe.pm;
10820
12818
  }
10821
12819
  return null;
10822
12820
  }
@@ -10835,15 +12833,15 @@ function getGlobalUpdateCommand(pm, pkg, version) {
10835
12833
  }
10836
12834
  }
10837
12835
  async function detectPackageManager(cwd) {
10838
- if (await pathExists(path45.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
10839
- if (await pathExists(path45.join(cwd, "bun.lockb"))) return "bun";
10840
- 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";
10841
12839
  return "npm";
10842
12840
  }
10843
12841
  async function detectWorkspaceRoot(cwd) {
10844
- if (await pathExists(path45.join(cwd, "pnpm-workspace.yaml"))) return true;
12842
+ if (await pathExists(path51.join(cwd, "pnpm-workspace.yaml"))) return true;
10845
12843
  try {
10846
- const pkgPath = path45.join(cwd, "package.json");
12844
+ const pkgPath = path51.join(cwd, "package.json");
10847
12845
  const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
10848
12846
  const pkg = JSON.parse(raw);
10849
12847
  return pkg.workspaces != null;
@@ -10870,7 +12868,7 @@ function getInstallCommand(pm, pkg, version, isDev, opts = {}) {
10870
12868
  }
10871
12869
  async function getLocalDependencyState(cwd, pkg = PACKAGE_NAME) {
10872
12870
  try {
10873
- const pkgPath = path45.join(cwd, "package.json");
12871
+ const pkgPath = path51.join(cwd, "package.json");
10874
12872
  const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
10875
12873
  const parsed = JSON.parse(raw);
10876
12874
  if (parsed.devDependencies?.[pkg]) return "dev";
@@ -10885,18 +12883,18 @@ async function confirmWorkspaceRoot(pm) {
10885
12883
  async function confirm3(question) {
10886
12884
  if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
10887
12885
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
10888
- return new Promise((resolve23) => {
12886
+ return new Promise((resolve25) => {
10889
12887
  rl.question(chalk7.yellow(question), (ans) => {
10890
12888
  rl.close();
10891
12889
  const t = ans.trim().toLowerCase();
10892
- resolve23(t === "y" || t === "yes");
12890
+ resolve25(t === "y" || t === "yes");
10893
12891
  });
10894
12892
  });
10895
12893
  }
10896
12894
  var CAPTURED_OUTPUT_LIMIT = 64e3;
10897
12895
  async function runInstall(cmd, cwd) {
10898
12896
  const { spawn: spawn4 } = await import('child_process');
10899
- return new Promise((resolve23) => {
12897
+ return new Promise((resolve25) => {
10900
12898
  const child = spawn4(cmd, {
10901
12899
  cwd,
10902
12900
  shell: true,
@@ -10915,15 +12913,15 @@ async function runInstall(cmd, cwd) {
10915
12913
  };
10916
12914
  tee(child.stdout, process.stdout);
10917
12915
  tee(child.stderr, process.stderr);
10918
- child.on("error", (err) => resolve23({ ok: false, output: `${output}
12916
+ child.on("error", (err) => resolve25({ ok: false, output: `${output}
10919
12917
  ${String(err)}` }));
10920
- child.on("close", (code) => resolve23({ ok: code === 0, output }));
12918
+ child.on("close", (code) => resolve25({ ok: code === 0, output }));
10921
12919
  });
10922
12920
  }
10923
12921
  async function releaseWindowsFileLocks(cwd, keepVersion) {
10924
12922
  reapAndReport(cwd, keepVersion);
10925
12923
  try {
10926
- const { vgdSocketPath } = await import('./vgd-RW7HCEIP.js');
12924
+ const { vgdSocketPath } = await import('./vgd-3A2TRNP5.js');
10927
12925
  const result = await stopVgd(vgdSocketPath());
10928
12926
  if (result === "stopped") {
10929
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."));
@@ -11054,7 +13052,7 @@ var updateCommand = new Command("update").description("Update vibgrate to the la
11054
13052
  );
11055
13053
  async function rewarmVgdAndReport(cwd) {
11056
13054
  try {
11057
- const { vgdIsRunning, vgdRequest } = await import('./vgd-RW7HCEIP.js');
13055
+ const { vgdIsRunning, vgdRequest } = await import('./vgd-3A2TRNP5.js');
11058
13056
  if (!await vgdIsRunning()) return;
11059
13057
  const loaded = await vgdRequest({ op: "load-graph", root: cwd });
11060
13058
  if (!loaded.ok || !("stored" in loaded)) return;
@@ -11165,13 +13163,13 @@ function collectCandidates(artifact) {
11165
13163
  return out2;
11166
13164
  }
11167
13165
  var defaultFixScanner = async (rootDir) => {
11168
- 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`);
11169
13167
  const scanOpts = { vibgrateVersion: VERSION, format: "json", out: scratch, quiet: true, concurrency: 8 };
11170
13168
  const advanced = await loadAdvancedScanHook();
11171
13169
  return runCoreScan(rootDir, scanOpts, advanced);
11172
13170
  };
11173
13171
  async function loadArtifact(rootDir, inFile, scan = defaultFixScanner) {
11174
- const artifactPath = path45.isAbsolute(inFile) ? inFile : path45.join(rootDir, inFile);
13172
+ const artifactPath = path51.isAbsolute(inFile) ? inFile : path51.join(rootDir, inFile);
11175
13173
  if (await pathExists(artifactPath)) {
11176
13174
  const staleness = scanStaleness(rootDir, artifactPath);
11177
13175
  if (!staleness.stale) {
@@ -11198,7 +13196,7 @@ async function repositoryIdentity(rootDir, override) {
11198
13196
  }
11199
13197
  }
11200
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) => {
11201
- const rootDir = path45.resolve(targetPath);
13199
+ const rootDir = path51.resolve(targetPath);
11202
13200
  if (!await pathExists(rootDir)) {
11203
13201
  console.error(chalk7.red(`Path does not exist: ${rootDir}`));
11204
13202
  process.exit(1);
@@ -11359,7 +13357,7 @@ function emit(report, format) {
11359
13357
  }
11360
13358
  }
11361
13359
  function relProjectDir(rootDir, projectPath) {
11362
- const rel = path45.isAbsolute(projectPath) ? path45.relative(rootDir, projectPath) : projectPath;
13360
+ const rel = path51.isAbsolute(projectPath) ? path51.relative(rootDir, projectPath) : projectPath;
11363
13361
  return rel === "" ? "." : rel;
11364
13362
  }
11365
13363
  async function buildTargetResolver(rootDir, artifact) {
@@ -11400,14 +13398,14 @@ function promptPlanSelection(response) {
11400
13398
  const defaultIdx = plans.findIndex((p) => p.tier === response.recommended);
11401
13399
  const def = defaultIdx >= 0 ? defaultIdx + 1 : 1;
11402
13400
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
11403
- return new Promise((resolve23) => {
13401
+ return new Promise((resolve25) => {
11404
13402
  rl.question(`Plan [1-${plans.length}, default ${def}, q to cancel]: `, (ans) => {
11405
13403
  rl.close();
11406
13404
  const t = ans.trim().toLowerCase();
11407
- if (t === "q" || t === "quit") return resolve23(null);
13405
+ if (t === "q" || t === "quit") return resolve25(null);
11408
13406
  const n = t === "" ? def : Number.parseInt(t, 10);
11409
- if (!Number.isInteger(n) || n < 1 || n > plans.length) return resolve23(null);
11410
- resolve23(plans[n - 1].tier);
13407
+ if (!Number.isInteger(n) || n < 1 || n > plans.length) return resolve25(null);
13408
+ resolve25(plans[n - 1].tier);
11411
13409
  });
11412
13410
  });
11413
13411
  }
@@ -11485,7 +13483,7 @@ Applied the ${chosen.label} plan:`));
11485
13483
  }
11486
13484
  if (failed.length) process.exitCode = 2;
11487
13485
  }
11488
- function formatText(artifact) {
13486
+ function formatText2(artifact) {
11489
13487
  const lines = [];
11490
13488
  const teal2 = chalk7.hex("#3FB0A4");
11491
13489
  const mint2 = chalk7.hex("#4FE3C1");
@@ -11838,10 +13836,29 @@ function formatArchitectureDiagram(arch) {
11838
13836
  lines.push(chalk7.dim(` + ${arch.folders.length - 12} more`));
11839
13837
  }
11840
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
+ }
11841
13849
  if (arch.unclassifiedFiles && arch.unclassifiedFiles.length > 0) {
11842
13850
  const extra = arch.unclassified > arch.unclassifiedFiles.length ? ` of ${arch.unclassified}` : "";
11843
13851
  lines.push(` Unclassified source (sample): ${arch.unclassifiedFiles.length}${extra}`);
11844
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
+ }
11845
13862
  lines.push("");
11846
13863
  if (arch.layers.length > 0) {
11847
13864
  for (const layer of arch.layers) {
@@ -12129,7 +14146,7 @@ function generatePriorityActions(artifact) {
12129
14146
  }
12130
14147
 
12131
14148
  // src/reporting/formatters/markdown.ts
12132
- function formatMarkdown(artifact) {
14149
+ function formatMarkdown2(artifact) {
12133
14150
  const lines = [];
12134
14151
  lines.push("# Vibgrate Drift Report");
12135
14152
  lines.push("");
@@ -12242,7 +14259,7 @@ function formatMarkdown(artifact) {
12242
14259
 
12243
14260
  // src/reporting/commands/report.ts
12244
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) => {
12245
- const artifactPath = path45.resolve(opts.in);
14262
+ const artifactPath = path51.resolve(opts.in);
12246
14263
  if (!await pathExists(artifactPath)) {
12247
14264
  console.error(chalk7.red(`Artifact not found: ${artifactPath}`));
12248
14265
  console.error(chalk7.dim('Run "vibgrate scan" first to generate a scan artifact.'));
@@ -12251,14 +14268,14 @@ var reportCommand = new Command("report").description("Generate a drift report f
12251
14268
  const artifact = await readJsonFile(artifactPath);
12252
14269
  switch (opts.format) {
12253
14270
  case "md":
12254
- console.log(formatMarkdown(artifact));
14271
+ console.log(formatMarkdown2(artifact));
12255
14272
  break;
12256
14273
  case "json":
12257
14274
  console.log(JSON.stringify(artifact, null, 2));
12258
14275
  break;
12259
14276
  case "text":
12260
14277
  default:
12261
- console.log(formatText(artifact));
14278
+ console.log(formatText2(artifact));
12262
14279
  break;
12263
14280
  }
12264
14281
  });
@@ -12289,10 +14306,10 @@ function openUrl(url) {
12289
14306
  }
12290
14307
 
12291
14308
  // src/reporting/commands/login.ts
12292
- var delay = (ms) => new Promise((resolve23) => setTimeout(resolve23, ms));
14309
+ var delay = (ms) => new Promise((resolve25) => setTimeout(resolve25, ms));
12293
14310
  function isInside(dir, file) {
12294
- const rel = path45.relative(dir, file);
12295
- return !!rel && !rel.startsWith("..") && !path45.isAbsolute(rel);
14311
+ const rel = path51.relative(dir, file);
14312
+ return !!rel && !rel.startsWith("..") && !path51.isAbsolute(rel);
12296
14313
  }
12297
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(
12298
14315
  "--local",
@@ -12460,8 +14477,8 @@ var logoutCommand = new Command("logout").description("Clear stored Vibgrate log
12460
14477
  });
12461
14478
 
12462
14479
  // src/reporting/commands/locale/client.ts
12463
- function buildUrl(scheme, host, path47, query) {
12464
- 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}`);
12465
14482
  for (const [key2, value] of Object.entries(query ?? {})) {
12466
14483
  if (value !== void 0 && value !== "") url.searchParams.set(key2, String(value));
12467
14484
  }
@@ -12509,8 +14526,8 @@ function createLocaleClient(dsn) {
12509
14526
  ExitCode.ERROR
12510
14527
  );
12511
14528
  }
12512
- const request = async (method, path47, opts = {}) => {
12513
- 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);
12514
14531
  const body = opts.body === void 0 ? void 0 : JSON.stringify(opts.body);
12515
14532
  const response = await fetch(url, {
12516
14533
  method,
@@ -12526,16 +14543,16 @@ function createLocaleClient(dsn) {
12526
14543
  };
12527
14544
  return {
12528
14545
  workspaceId: parsed.workspaceId,
12529
- get: (path47, query) => request("GET", path47, { query }),
12530
- post: (path47, body) => request("POST", path47, { body }),
12531
- patch: (path47, body) => request("PATCH", path47, { body }),
12532
- 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 })
12533
14550
  };
12534
14551
  }
12535
14552
  var LOCALE_CONFIG_FILENAMES = ["vibgrate.locale.yaml", ".vibgrate/locale.yaml"];
12536
14553
  function findLocaleConfig(cwd) {
12537
14554
  for (const name of LOCALE_CONFIG_FILENAMES) {
12538
- const candidate = path45.join(cwd, name);
14555
+ const candidate = path51.join(cwd, name);
12539
14556
  if (fs12.existsSync(candidate)) return candidate;
12540
14557
  }
12541
14558
  return null;
@@ -12549,15 +14566,15 @@ function parseLocaleConfig(text) {
12549
14566
  const items = value.filter((v) => typeof v === "string" && v.trim().length > 0);
12550
14567
  return items.length ? items : void 0;
12551
14568
  };
12552
- 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;
12553
14570
  return {
12554
- project: str(obj.project),
12555
- sourceLanguage: str(obj.sourceLanguage),
14571
+ project: str2(obj.project),
14572
+ sourceLanguage: str2(obj.sourceLanguage),
12556
14573
  targetLanguages: stringArray(obj.targetLanguages),
12557
- format: str(obj.format),
12558
- path: str(obj.path),
14574
+ format: str2(obj.format),
14575
+ path: str2(obj.path),
12559
14576
  namespaces: stringArray(obj.namespaces),
12560
- version: str(obj.version)
14577
+ version: str2(obj.version)
12561
14578
  };
12562
14579
  }
12563
14580
  function loadLocaleConfig(cwd) {
@@ -12567,7 +14584,7 @@ function loadLocaleConfig(cwd) {
12567
14584
  return { config: parseLocaleConfig(fs12.readFileSync(file, "utf8")), file };
12568
14585
  } catch (error) {
12569
14586
  throw new Error(
12570
- `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)}`
12571
14588
  );
12572
14589
  }
12573
14590
  }
@@ -12582,8 +14599,8 @@ function serializeLocaleConfig(config) {
12582
14599
  return stringify(out2);
12583
14600
  }
12584
14601
  function writeLocaleConfig(cwd, config, filename) {
12585
- const target = filename ?? path45.join(cwd, LOCALE_CONFIG_FILENAMES[0]);
12586
- 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 });
12587
14604
  fs12.writeFileSync(target, serializeLocaleConfig(config), "utf8");
12588
14605
  return target;
12589
14606
  }
@@ -12725,7 +14742,7 @@ function validatePathTemplate(template) {
12725
14742
  if (!template.includes("{language}")) {
12726
14743
  return 'path template must contain {language} (e.g. "src/locales/{language}/{namespace}.json").';
12727
14744
  }
12728
- if (path45.isAbsolute(template)) {
14745
+ if (path51.isAbsolute(template)) {
12729
14746
  return "path template must be relative to the project directory.";
12730
14747
  }
12731
14748
  if (template.split(/[\\/]/).includes("..")) {
@@ -12741,7 +14758,7 @@ function resolveLocaleFiles(cwd, template, languages, namespaces) {
12741
14758
  for (const language of languages) {
12742
14759
  for (const namespace of namespaces) {
12743
14760
  const relative13 = renderPath(template, language, namespace);
12744
- const file = path45.resolve(cwd, relative13);
14761
+ const file = path51.resolve(cwd, relative13);
12745
14762
  out2.push({ language, namespace, file, relative: relative13, exists: fs12.existsSync(file) });
12746
14763
  }
12747
14764
  }
@@ -12763,7 +14780,7 @@ function writeCatalog(file, format, catalog, opts = {}) {
12763
14780
  const existing = fs12.existsSync(file) ? fs12.readFileSync(file, "utf8") : null;
12764
14781
  const changed = existing !== encoded;
12765
14782
  if (changed && !opts.dryRun) {
12766
- fs12.mkdirSync(path45.dirname(file), { recursive: true });
14783
+ fs12.mkdirSync(path51.dirname(file), { recursive: true });
12767
14784
  fs12.writeFileSync(file, encoded, "utf8");
12768
14785
  }
12769
14786
  return { file, changed, bytes: Buffer.byteLength(encoded, "utf8") };
@@ -12784,7 +14801,7 @@ function diffCatalogs(before, after) {
12784
14801
  // src/reporting/commands/locale/index.ts
12785
14802
  var cwdOption = ["-C, --cwd <dir>", "Run against this directory (default: current)"];
12786
14803
  function root(opts) {
12787
- return path45.resolve(opts.cwd ?? process.cwd());
14804
+ return path51.resolve(opts.cwd ?? process.cwd());
12788
14805
  }
12789
14806
  function withCommonOptions(cmd) {
12790
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");
@@ -12854,7 +14871,7 @@ var configCmd = withCommonOptions(
12854
14871
  if (!isSupportedFormat(next2.format)) codecFor(next2.format);
12855
14872
  const written = writeLocaleConfig(dir, next2, file ?? void 0);
12856
14873
  emit2(opts, { file: written, config: next2 }, () => {
12857
- 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)}`));
12858
14875
  console.log(
12859
14876
  chalk7.dim('Credentials are not stored here \u2014 run "vg login" or set VIBGRATE_DSN.')
12860
14877
  );
@@ -13330,7 +15347,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
13330
15347
  try {
13331
15348
  const statements = [];
13332
15349
  if (opts.from) {
13333
- const fromPath = path45.resolve(opts.from);
15350
+ const fromPath = path51.resolve(opts.from);
13334
15351
  if (!await pathExists(fromPath)) {
13335
15352
  process.stderr.write(chalk7.red(`Statements file not found: ${fromPath}
13336
15353
  `));
@@ -13348,9 +15365,9 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
13348
15365
  });
13349
15366
  const json2 = JSON.stringify(doc, null, 2);
13350
15367
  if (opts.out) {
13351
- await writeTextFile(path45.resolve(opts.out), json2 + "\n");
15368
+ await writeTextFile(path51.resolve(opts.out), json2 + "\n");
13352
15369
  process.stderr.write(
13353
- 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)}
13354
15371
  `
13355
15372
  );
13356
15373
  } else {
@@ -13533,7 +15550,7 @@ function formatDeltaText(base, current) {
13533
15550
  return lines.join("\n");
13534
15551
  }
13535
15552
  async function readArtifactOrExit(filePath) {
13536
- const absolutePath = path45.resolve(filePath);
15553
+ const absolutePath = path51.resolve(filePath);
13537
15554
  if (!await pathExists(absolutePath)) {
13538
15555
  console.error(chalk7.red(`Artifact not found: ${absolutePath}`));
13539
15556
  process.exit(1);
@@ -13550,7 +15567,7 @@ var exportCommand = new Command("export").description("Export scan artifact as S
13550
15567
  const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
13551
15568
  const body = JSON.stringify(sbom, null, 2);
13552
15569
  if (opts.out) {
13553
- await writeTextFile(path45.resolve(opts.out), body);
15570
+ await writeTextFile(path51.resolve(opts.out), body);
13554
15571
  console.log(chalk7.green("\u2714") + ` SBOM written to ${opts.out}`);
13555
15572
  } else {
13556
15573
  console.log(body);
@@ -13561,7 +15578,7 @@ var deltaCommand = new Command("delta").description("Show SBOM delta between two
13561
15578
  const current = await readArtifactOrExit(opts.to);
13562
15579
  const report = formatDeltaText(base, current);
13563
15580
  if (opts.out) {
13564
- await writeTextFile(path45.resolve(opts.out), report);
15581
+ await writeTextFile(path51.resolve(opts.out), report);
13565
15582
  console.log(chalk7.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
13566
15583
  } else {
13567
15584
  console.log(report);
@@ -13692,20 +15709,20 @@ function resolveRegime(id) {
13692
15709
  return regime;
13693
15710
  }
13694
15711
  function evidenceDir(root3) {
13695
- return path45.join(root3, ".vibgrate", "evidence");
15712
+ return path51.join(root3, ".vibgrate", "evidence");
13696
15713
  }
13697
15714
  function orgPath(root3) {
13698
- return path45.join(evidenceDir(root3), "org.json");
15715
+ return path51.join(evidenceDir(root3), "org.json");
13699
15716
  }
13700
15717
  function productsPath(root3) {
13701
- return path45.join(evidenceDir(root3), "products.json");
15718
+ return path51.join(evidenceDir(root3), "products.json");
13702
15719
  }
13703
15720
  function releasesDir(root3) {
13704
- return path45.join(evidenceDir(root3), "releases");
15721
+ return path51.join(evidenceDir(root3), "releases");
13705
15722
  }
13706
15723
  function releasePath(root3, productId, version) {
13707
15724
  const safe = `${productId}@${version}`.replace(/[^A-Za-z0-9._@-]/g, "_");
13708
- return path45.join(releasesDir(root3), `${safe}.json`);
15725
+ return path51.join(releasesDir(root3), `${safe}.json`);
13709
15726
  }
13710
15727
  var DEFAULT_ORG = {
13711
15728
  responsiblePersons: [],
@@ -13736,7 +15753,7 @@ async function freezeRelease(root3, release) {
13736
15753
  const p = releasePath(root3, release.productId, release.version);
13737
15754
  if (await pathExists(p)) {
13738
15755
  throw new CliError(
13739
- `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`,
13740
15757
  ExitCode.USAGE_ERROR
13741
15758
  );
13742
15759
  }
@@ -13750,7 +15767,7 @@ async function loadReleases(root3, productId) {
13750
15767
  const files = (await readdir(dir)).filter((f) => f.endsWith(".json")).sort();
13751
15768
  const releases = [];
13752
15769
  for (const f of files) {
13753
- const rel = await readJsonFile(path45.join(dir, f));
15770
+ const rel = await readJsonFile(path51.join(dir, f));
13754
15771
  if (!productId || rel.productId === productId) releases.push(rel);
13755
15772
  }
13756
15773
  return releases;
@@ -13911,9 +15928,9 @@ function derInt(n) {
13911
15928
  if (bytes[0] & 128) bytes = Buffer.concat([Buffer.from([0]), bytes]);
13912
15929
  return der(TAG.INTEGER, bytes);
13913
15930
  }
13914
- function buildTimeStampReq(digest) {
15931
+ function buildTimeStampReq(digest2) {
13915
15932
  const algId = der(TAG.SEQUENCE, Buffer.concat([der(TAG.OID, OID_SHA256), der(TAG.NULL, Buffer.alloc(0))]));
13916
- 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)]));
13917
15934
  const version = derInt(1);
13918
15935
  const nonce = der(TAG.INTEGER, crypto3.randomBytes(16));
13919
15936
  const certReq = der(TAG.BOOLEAN, Buffer.from([255]));
@@ -13992,9 +16009,9 @@ function verifyTimestamp(tsr, digestHex) {
13992
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"
13993
16010
  };
13994
16011
  }
13995
- async function requestTimestamp(tsaUrl, digest) {
16012
+ async function requestTimestamp(tsaUrl, digest2) {
13996
16013
  if (typeof fetch !== "function") throw new CliError("network fetch unavailable \u2014 cannot reach the TSA", ExitCode.ERROR);
13997
- const req = buildTimeStampReq(digest);
16014
+ const req = buildTimeStampReq(digest2);
13998
16015
  let res;
13999
16016
  try {
14000
16017
  res = await fetch(tsaUrl, {
@@ -14008,7 +16025,7 @@ async function requestTimestamp(tsaUrl, digest) {
14008
16025
  }
14009
16026
  if (!res.ok) throw new CliError(`TSA ${tsaUrl} returned ${res.status}`, ExitCode.ERROR);
14010
16027
  const body = Buffer.from(await res.arrayBuffer());
14011
- const v = verifyTimestamp(body, digest.toString("hex"));
16028
+ const v = verifyTimestamp(body, digest2.toString("hex"));
14012
16029
  if (!v.imprintMatches) throw new CliError(`TSA response did not bind to our digest (${v.reason})`, ExitCode.ERROR);
14013
16030
  return body;
14014
16031
  }
@@ -14445,12 +16462,12 @@ function verifyEvidenceEnvelope(env, opts = {}) {
14445
16462
  var DEFAULT_KEY = "attest-key.pem";
14446
16463
  function resolveSigningKey(root3, explicit) {
14447
16464
  const chosen = process.env.VG_ATTEST_KEY;
14448
- 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);
14449
16466
  let minted = false;
14450
16467
  if (!fs12.existsSync(keyPath)) {
14451
16468
  if (chosen) throw new CliError(`signing key not found: ${chosen}`, ExitCode.USAGE_ERROR);
14452
16469
  const kp = generateKeypair();
14453
- fs12.mkdirSync(path45.dirname(keyPath), { recursive: true });
16470
+ fs12.mkdirSync(path51.dirname(keyPath), { recursive: true });
14454
16471
  fs12.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
14455
16472
  fs12.writeFileSync(`${keyPath}.pub`, kp.publicPem);
14456
16473
  minted = true;
@@ -14468,16 +16485,16 @@ function resolveSigningKey(root3, explicit) {
14468
16485
  }
14469
16486
  function writeBundle(input) {
14470
16487
  const dir = input.outDir;
14471
- fs12.mkdirSync(path45.join(dir, "inputs", "releases"), { recursive: true });
14472
- 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)}
14473
16490
  `);
14474
16491
  write("result.json", input.result);
14475
- write(path45.join("inputs", "advisory.json"), input.advisory);
16492
+ write(path51.join("inputs", "advisory.json"), input.advisory);
14476
16493
  for (const rel of input.releases) {
14477
16494
  const safe = `${rel.productId}@${rel.version}`.replace(/[^A-Za-z0-9._@-]/g, "_");
14478
- write(path45.join("inputs", "releases", `${safe}.json`), rel);
16495
+ write(path51.join("inputs", "releases", `${safe}.json`), rel);
14479
16496
  }
14480
- 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 });
14481
16498
  write("provenance.json", {
14482
16499
  method: "deterministic",
14483
16500
  modelDrafted: [],
@@ -14493,13 +16510,13 @@ function writeBundle(input) {
14493
16510
  disclaimer: input.regime.disclaimer
14494
16511
  });
14495
16512
  if (input.envelope) {
14496
- 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)}
14497
16514
  `);
14498
16515
  }
14499
16516
  if (input.timestampToken) {
14500
- fs12.writeFileSync(path45.join(dir, "timestamp.tsr"), input.timestampToken);
16517
+ fs12.writeFileSync(path51.join(dir, "timestamp.tsr"), input.timestampToken);
14501
16518
  }
14502
- fs12.writeFileSync(path45.join(dir, "VERIFY.md"), verifyDoc(input));
16519
+ fs12.writeFileSync(path51.join(dir, "VERIFY.md"), verifyDoc(input));
14503
16520
  return dir;
14504
16521
  }
14505
16522
  function verifyDoc(input) {
@@ -14560,13 +16577,13 @@ function undeterminedFields(result, regime) {
14560
16577
  return gaps.filter((g) => regime.submission.requires.some((r) => g.startsWith(r)) || g.startsWith("exposure") || g.startsWith("member"));
14561
16578
  }
14562
16579
  async function recordDrill(root3, record) {
14563
- const dir = path45.join(evidenceDir(root3), "drills");
14564
- 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`);
14565
16582
  await writeJsonFile(p, record);
14566
16583
  return p;
14567
16584
  }
14568
16585
  async function hasRecentDrill(root3, asOf) {
14569
- const dir = path45.join(evidenceDir(root3), "drills");
16586
+ const dir = path51.join(evidenceDir(root3), "drills");
14570
16587
  if (!await pathExists(dir)) return void 0;
14571
16588
  const { readdir } = await import('fs/promises');
14572
16589
  const { readJsonFile: readJsonFile2 } = await import('./fs-4G5A65KS.js');
@@ -14574,7 +16591,7 @@ async function hasRecentDrill(root3, asOf) {
14574
16591
  if (files.length === 0) return void 0;
14575
16592
  const cutoff = new Date(new Date(asOf).getTime() - 90 * 24 * 3600 * 1e3).toISOString();
14576
16593
  for (const f of files) {
14577
- const rec = await readJsonFile2(path45.join(dir, f));
16594
+ const rec = await readJsonFile2(path51.join(dir, f));
14578
16595
  if (rec.ranAt >= cutoff) return true;
14579
16596
  }
14580
16597
  return false;
@@ -14654,7 +16671,7 @@ function formatRegimeList() {
14654
16671
 
14655
16672
  // src/reporting/commands/evidence/index.ts
14656
16673
  function root2(opts) {
14657
- return path45.resolve(opts.cwd ?? process.cwd());
16674
+ return path51.resolve(opts.cwd ?? process.cwd());
14658
16675
  }
14659
16676
  function slug(name) {
14660
16677
  return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "product";
@@ -14742,7 +16759,7 @@ var releaseCmd = new Command("release").description("Freeze a shipped release in
14742
16759
  const release = await buildRelease({
14743
16760
  productId: product.id,
14744
16761
  version,
14745
- from: path45.resolve(rootDir, opts.from),
16762
+ from: path51.resolve(rootDir, opts.from),
14746
16763
  shipDate: opts.shipDate,
14747
16764
  buildId: opts.buildId,
14748
16765
  artefactDigest: opts.digest,
@@ -14750,7 +16767,7 @@ var releaseCmd = new Command("release").description("Freeze a shipped release in
14750
16767
  frozenAt: nowIso()
14751
16768
  });
14752
16769
  const p = await freezeRelease(rootDir, release);
14753
- 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)}`);
14754
16771
  });
14755
16772
  async function runExposure(rootDir, vuln, opts) {
14756
16773
  const org = await loadOrg(rootDir);
@@ -14787,8 +16804,8 @@ var exposureCmd = new Command("exposure").description("Which shipped products co
14787
16804
  const { result, regime, releases, advisory } = await runExposure(rootDir, vuln, opts);
14788
16805
  let timestampToken;
14789
16806
  if (opts.tsa) {
14790
- const digest = Buffer.from(exposureSubjectDigest(result), "hex");
14791
- timestampToken = await requestTimestamp(opts.tsa, digest);
16807
+ const digest2 = Buffer.from(exposureSubjectDigest(result), "hex");
16808
+ timestampToken = await requestTimestamp(opts.tsa, digest2);
14792
16809
  result.meta.timestamp = { source: "rfc3161", value: parseTimestampToken(timestampToken).genTime };
14793
16810
  }
14794
16811
  if (opts.format === "json") {
@@ -14802,7 +16819,7 @@ var exposureCmd = new Command("exposure").description("Which shipped products co
14802
16819
  console.log("\n" + buildPack(result, regime, org, stage));
14803
16820
  }
14804
16821
  if (opts.bundle) {
14805
- const dir = path45.resolve(rootDir, opts.bundle, `evidence-${result.meta.evidenceId}`);
16822
+ const dir = path51.resolve(rootDir, opts.bundle, `evidence-${result.meta.evidenceId}`);
14806
16823
  let envelope;
14807
16824
  if (opts.sign !== false) {
14808
16825
  const { key: key2, minted } = resolveSigningKey(rootDir);
@@ -14810,7 +16827,7 @@ var exposureCmd = new Command("exposure").description("Which shipped products co
14810
16827
  envelope = signEvidenceStatement(buildEvidenceStatement(result, VERSION), key2);
14811
16828
  }
14812
16829
  writeBundle({ outDir: dir, result, advisory, releases, regime, envelope, timestampToken, cliVersion: VERSION });
14813
- 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" : ""}`);
14814
16831
  }
14815
16832
  process.exitCode = exposureExit(result.overallStatus);
14816
16833
  });
@@ -14842,7 +16859,7 @@ var packCmd = new Command("pack").description("Build the submission pack a human
14842
16859
  const stage = opts.stage ?? regime.clocks[0]?.stage ?? "early-warning";
14843
16860
  const md = buildPack(result, regime, org, stage);
14844
16861
  if (opts.out) {
14845
- fs12.writeFileSync(path45.resolve(rootDir, opts.out), md);
16862
+ fs12.writeFileSync(path51.resolve(rootDir, opts.out), md);
14846
16863
  console.error(chalk7.green("\u2714") + ` pack written to ${opts.out}`);
14847
16864
  } else {
14848
16865
  console.log(md);
@@ -14884,18 +16901,18 @@ var drillCmd = new Command("drill").description("Timed dry-run of the determinat
14884
16901
  }
14885
16902
  });
14886
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) => {
14887
- const abs = path45.resolve(bundlePath);
14888
- 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;
14889
16906
  if (!fs12.existsSync(envPath)) throw new CliError(`no evidence.intoto.jsonl at ${bundlePath}`, ExitCode.NOT_FOUND);
14890
16907
  const envelope = JSON.parse(fs12.readFileSync(envPath, "utf8").trim().split("\n")[0]);
14891
- const resultPath = path45.join(path45.dirname(envPath), "result.json");
16908
+ const resultPath = path51.join(path51.dirname(envPath), "result.json");
14892
16909
  const result = fs12.existsSync(resultPath) ? JSON.parse(fs12.readFileSync(resultPath, "utf8")) : void 0;
14893
- 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;
14894
16911
  const v = verifyEvidenceEnvelope(envelope, { publicKeyPem, result });
14895
16912
  const color = v.status === "verified" ? chalk7.green : v.status === "failed" ? chalk7.red : chalk7.yellow;
14896
16913
  console.log(" " + color(v.status.toUpperCase()) + ` ${v.reason}`);
14897
16914
  if (v.evidenceId) console.log(" " + chalk7.dim(`evidence ${v.evidenceId} \xB7 regime ${v.regime} \xB7 advisory ${v.advisoryId} \xB7 ${v.overallStatus}`));
14898
- const tsrPath = path45.join(path45.dirname(envPath), "timestamp.tsr");
16915
+ const tsrPath = path51.join(path51.dirname(envPath), "timestamp.tsr");
14899
16916
  if (fs12.existsSync(tsrPath) && result) {
14900
16917
  const t = verifyTimestamp(fs12.readFileSync(tsrPath), exposureSubjectDigest(result));
14901
16918
  const tcolor = t.imprintMatches ? chalk7.green : chalk7.red;
@@ -14964,12 +16981,12 @@ var exportCmd = new Command("export").description("Air-gap bundle of all evidenc
14964
16981
  const releases = [];
14965
16982
  for (const list of releasesByProduct.values()) releases.push(...list);
14966
16983
  const report = computeReadiness({ regime, org, products, releasesByProduct, recentDrill: await hasRecentDrill(rootDir, today()) });
14967
- const dir = path45.resolve(rootDir, opts.out);
14968
- await writeJsonFile(path45.join(dir, "org.json"), org);
14969
- await writeJsonFile(path45.join(dir, "products.json"), products);
14970
- for (const r of releases) await writeJsonFile(path45.join(dir, "releases", `${r.productId}@${r.version}.json`.replace(/[^A-Za-z0-9._@-]/g, "_")), r);
14971
- await writeJsonFile(path45.join(dir, "readiness.json"), report);
14972
- 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)`);
14973
16990
  });
14974
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) => {
14975
16992
  const rootDir = root2(opts);
@@ -14995,8 +17012,8 @@ var pushCmd = new Command("push").description("Push the product registry (and an
14995
17012
  }));
14996
17013
  let exposure;
14997
17014
  if (opts.result) {
14998
- const raw = path45.resolve(rootDir, opts.result);
14999
- 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;
15000
17017
  if (!fs12.existsSync(resultPath)) throw new CliError(`no result.json at ${opts.result}`, ExitCode.NOT_FOUND);
15001
17018
  exposure = JSON.parse(fs12.readFileSync(resultPath, "utf8"));
15002
17019
  }
@@ -15059,6 +17076,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
15059
17076
  "module",
15060
17077
  "daemon",
15061
17078
  "policy",
17079
+ "review",
15062
17080
  "llm-host",
15063
17081
  "hcs",
15064
17082
  "install",
@@ -15128,6 +17146,7 @@ ${aliasNote}Docs: https://vibgrate.com/help`;
15128
17146
  registerPolicy(program);
15129
17147
  registerLlmHost(program);
15130
17148
  registerHcs(program);
17149
+ registerReview(program);
15131
17150
  program.addCommand(initCommand);
15132
17151
  program.addCommand(scanCommand);
15133
17152
  program.addCommand(fixCommand);
@@ -15152,6 +17171,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
15152
17171
  "--only",
15153
17172
  "--exclude",
15154
17173
  "--export",
17174
+ "--out",
15155
17175
  "-o",
15156
17176
  "--budget",
15157
17177
  "-b"
@@ -15197,7 +17217,7 @@ function dispatch(argv, cwd) {
15197
17217
  }
15198
17218
  const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
15199
17219
  if (looksLikeQuestion) return ["ask", ...args];
15200
- if (fs12.existsSync(path45.resolve(cwd, first))) {
17220
+ if (fs12.existsSync(path51.resolve(cwd, first))) {
15201
17221
  return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
15202
17222
  }
15203
17223
  const moved = MOVED_COMMANDS[first];
@@ -15224,15 +17244,15 @@ function findFirstPositional(args) {
15224
17244
  async function main(argv = process.argv) {
15225
17245
  const raw = argv.slice(2);
15226
17246
  if (process.env.NO_COLOR || raw.includes("--no-color")) disableColor();
15227
- if (!raw.includes("--local")) kickRelevanceReadiness();
17247
+ if (!raw.includes("--local") && !raw.includes("--offline")) kickRelevanceReadiness();
15228
17248
  const cwd = readCwd(raw);
15229
17249
  const program = buildProgram();
15230
17250
  program.exitOverride((err) => {
15231
17251
  throw err;
15232
17252
  });
15233
17253
  program.configureOutput({
15234
- writeErr: (str) => process.stderr.write(str),
15235
- writeOut: (str) => process.stdout.write(str)
17254
+ writeErr: (str2) => process.stderr.write(str2),
17255
+ writeOut: (str2) => process.stdout.write(str2)
15236
17256
  });
15237
17257
  try {
15238
17258
  const dispatched = dispatch(raw, cwd);
@@ -15243,9 +17263,9 @@ async function main(argv = process.argv) {
15243
17263
  }
15244
17264
  function readCwd(raw) {
15245
17265
  const i = raw.findIndex((a) => a === "-C" || a === "--cwd");
15246
- 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]);
15247
17267
  const eq = raw.find((a) => a.startsWith("--cwd="));
15248
- if (eq) return path45.resolve(eq.slice("--cwd=".length));
17268
+ if (eq) return path51.resolve(eq.slice("--cwd=".length));
15249
17269
  return process.cwd();
15250
17270
  }
15251
17271
  function handleError(err) {