@vibgrate/cli 2026.826.1 → 2026.829.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/DOCS.md +204 -9
  2. package/README.md +2 -0
  3. package/dist/{agent-4RHH2FR2.js → agent-5GW7C7EB.js} +14 -14
  4. package/dist/{agent-4RHH2FR2.js.map → agent-5GW7C7EB.js.map} +1 -1
  5. package/dist/{approvals-XDFM346D.js → approvals-ISJFILVK.js} +5 -5
  6. package/dist/{approvals-XDFM346D.js.map → approvals-ISJFILVK.js.map} +1 -1
  7. package/dist/{attach-LGTCYNKC.js → attach-JUSALQ75.js} +3 -3
  8. package/dist/{attach-LGTCYNKC.js.map → attach-JUSALQ75.js.map} +1 -1
  9. package/dist/baseline-YL2IZZ4Y.js +9 -0
  10. package/dist/{baseline-PRY5HKL5.js.map → baseline-YL2IZZ4Y.js.map} +1 -1
  11. package/dist/{chunk-ZRDTI67N.js → chunk-5RW5PIBH.js} +3 -3
  12. package/dist/{chunk-ZRDTI67N.js.map → chunk-5RW5PIBH.js.map} +1 -1
  13. package/dist/{chunk-TUVB6Q6F.js → chunk-65YN3BG3.js} +1207 -40
  14. package/dist/chunk-65YN3BG3.js.map +1 -0
  15. package/dist/{chunk-TKUWEJZJ.js → chunk-B5VCPIKT.js} +7 -6
  16. package/dist/chunk-B5VCPIKT.js.map +1 -0
  17. package/dist/{chunk-WU2DVESP.js → chunk-BA6YBVNT.js} +3 -3
  18. package/dist/{chunk-WU2DVESP.js.map → chunk-BA6YBVNT.js.map} +1 -1
  19. package/dist/{chunk-IM7HME2T.js → chunk-BODXEZEY.js} +9 -9
  20. package/dist/{chunk-IM7HME2T.js.map → chunk-BODXEZEY.js.map} +1 -1
  21. package/dist/{chunk-PIB3YNJU.js → chunk-D3SV3JWB.js} +3 -3
  22. package/dist/{chunk-PIB3YNJU.js.map → chunk-D3SV3JWB.js.map} +1 -1
  23. package/dist/chunk-EGMBHPXV.js +127 -0
  24. package/dist/chunk-EGMBHPXV.js.map +1 -0
  25. package/dist/{chunk-7KOEEEIZ.js → chunk-FHSEK64W.js} +7 -7
  26. package/dist/{chunk-7KOEEEIZ.js.map → chunk-FHSEK64W.js.map} +1 -1
  27. package/dist/{chunk-PEHMRPYM.js → chunk-FKCN653F.js} +3 -3
  28. package/dist/{chunk-PEHMRPYM.js.map → chunk-FKCN653F.js.map} +1 -1
  29. package/dist/{chunk-K2IWOZEQ.js → chunk-FPIMNVLI.js} +12 -4
  30. package/dist/chunk-FPIMNVLI.js.map +1 -0
  31. package/dist/{chunk-BHDP7EVJ.js → chunk-HHMWE52J.js} +10 -135
  32. package/dist/chunk-HHMWE52J.js.map +1 -0
  33. package/dist/{chunk-5UG4UHKU.js → chunk-HUJZO4K5.js} +281 -106
  34. package/dist/chunk-HUJZO4K5.js.map +1 -0
  35. package/dist/{chunk-TQ75FB52.js → chunk-JWQ2JCQS.js} +4 -4
  36. package/dist/{chunk-TQ75FB52.js.map → chunk-JWQ2JCQS.js.map} +1 -1
  37. package/dist/{chunk-WGA4KUGS.js → chunk-K7QR7U3V.js} +12 -11
  38. package/dist/chunk-K7QR7U3V.js.map +1 -0
  39. package/dist/{chunk-E44I7635.js → chunk-KCKG3EEJ.js} +3 -3
  40. package/dist/{chunk-E44I7635.js.map → chunk-KCKG3EEJ.js.map} +1 -1
  41. package/dist/{chunk-WGSLN73L.js → chunk-MMIIVRQC.js} +7 -7
  42. package/dist/chunk-MMIIVRQC.js.map +1 -0
  43. package/dist/chunk-N6R4XPLX.js +27 -0
  44. package/dist/chunk-N6R4XPLX.js.map +1 -0
  45. package/dist/{chunk-WPQCJADP.js → chunk-NQQT75AN.js} +62 -22
  46. package/dist/chunk-NQQT75AN.js.map +1 -0
  47. package/dist/{chunk-TOHNVUDD.js → chunk-NSC7ZK62.js} +3 -3
  48. package/dist/{chunk-TOHNVUDD.js.map → chunk-NSC7ZK62.js.map} +1 -1
  49. package/dist/{chunk-LAYDXJEQ.js → chunk-OAYSRM7J.js} +12 -14
  50. package/dist/chunk-OAYSRM7J.js.map +1 -0
  51. package/dist/{chunk-R5BGRJ6X.js → chunk-QMROD7MI.js} +15 -8
  52. package/dist/chunk-QMROD7MI.js.map +1 -0
  53. package/dist/{chunk-SHD5TCZ5.js → chunk-R2FX4USU.js} +7 -7
  54. package/dist/chunk-R2FX4USU.js.map +1 -0
  55. package/dist/{chunk-7S4ELKK6.js → chunk-RL7JYG3D.js} +3 -3
  56. package/dist/{chunk-7S4ELKK6.js.map → chunk-RL7JYG3D.js.map} +1 -1
  57. package/dist/{chunk-QLNJ3VWO.js → chunk-RRSUNTZI.js} +3 -3
  58. package/dist/{chunk-QLNJ3VWO.js.map → chunk-RRSUNTZI.js.map} +1 -1
  59. package/dist/{chunk-QONHVC4H.js → chunk-TWFPHXPN.js} +3 -3
  60. package/dist/{chunk-QONHVC4H.js.map → chunk-TWFPHXPN.js.map} +1 -1
  61. package/dist/chunk-UGRUG2FA.js +6 -0
  62. package/dist/{chunk-JQ7OL4XG.js.map → chunk-UGRUG2FA.js.map} +1 -1
  63. package/dist/{chunk-XW4FCBL4.js → chunk-VOEEYCR3.js} +6 -6
  64. package/dist/{chunk-XW4FCBL4.js.map → chunk-VOEEYCR3.js.map} +1 -1
  65. package/dist/{chunk-WHNB3443.js → chunk-WBDRBP22.js} +4 -4
  66. package/dist/{chunk-WHNB3443.js.map → chunk-WBDRBP22.js.map} +1 -1
  67. package/dist/{chunk-NQU6E6OG.js → chunk-YL332BU2.js} +20 -15
  68. package/dist/chunk-YL332BU2.js.map +1 -0
  69. package/dist/{chunk-IEZCFMNL.js → chunk-ZQY5TWCB.js} +3 -3
  70. package/dist/{chunk-IEZCFMNL.js.map → chunk-ZQY5TWCB.js.map} +1 -1
  71. package/dist/cli.d.ts +16 -1
  72. package/dist/cli.js +2359 -340
  73. package/dist/cli.js.map +1 -1
  74. package/dist/{daemon-2YMCH7R2.js → daemon-NRIIDMX2.js} +10 -10
  75. package/dist/{daemon-2YMCH7R2.js.map → daemon-NRIIDMX2.js.map} +1 -1
  76. package/dist/embed-worker-main.js +3 -3
  77. package/dist/{ensure-map-S2GBW2UC.js → ensure-map-VAIYCCEJ.js} +17 -16
  78. package/dist/{ensure-map-S2GBW2UC.js.map → ensure-map-VAIYCCEJ.js.map} +1 -1
  79. package/dist/explain-EXPJTCB5.js +126 -0
  80. package/dist/explain-EXPJTCB5.js.map +1 -0
  81. package/dist/{graph-backend-QDEU5RIR.js → graph-backend-V5A2H5ZB.js} +7 -7
  82. package/dist/{graph-backend-QDEU5RIR.js.map → graph-backend-V5A2H5ZB.js.map} +1 -1
  83. package/dist/index.d.ts +217 -13
  84. package/dist/index.js +17 -16
  85. package/dist/index.js.map +1 -1
  86. package/dist/{interactive-GMGWUEQO.js → interactive-VQM2CTE2.js} +32 -31
  87. package/dist/interactive-VQM2CTE2.js.map +1 -0
  88. package/dist/load-UAVA732M.js +11 -0
  89. package/dist/{load-JXCTAAEI.js.map → load-UAVA732M.js.map} +1 -1
  90. package/dist/parse-worker.js +2 -2
  91. package/dist/{rules-ASYEUNB5.js → rules-TLRGB5L4.js} +12 -4
  92. package/dist/rules-TLRGB5L4.js.map +1 -0
  93. package/dist/{run-outcome-5NA7PI52.js → run-outcome-EATWIKLR.js} +5 -5
  94. package/dist/{run-outcome-5NA7PI52.js.map → run-outcome-EATWIKLR.js.map} +1 -1
  95. package/dist/{runtime-session-IEM6DZDP.js → runtime-session-Y2BRBXUW.js} +9 -9
  96. package/dist/{runtime-session-IEM6DZDP.js.map → runtime-session-Y2BRBXUW.js.map} +1 -1
  97. package/dist/{session-VYFLJ7KL.js → session-N2FTYIHQ.js} +9 -9
  98. package/dist/{session-VYFLJ7KL.js.map → session-N2FTYIHQ.js.map} +1 -1
  99. package/dist/{stream-json-2ISQLKPD.js → stream-json-GCZAHORX.js} +14 -14
  100. package/dist/{stream-json-2ISQLKPD.js.map → stream-json-GCZAHORX.js.map} +1 -1
  101. package/dist/version-XNZ4GLMQ.js +4 -0
  102. package/dist/{version-B62ELJRY.js.map → version-XNZ4GLMQ.js.map} +1 -1
  103. package/dist/{vg-mcp-bridge-CJIK5U56.js → vg-mcp-bridge-VDZWFEO2.js} +22 -11
  104. package/dist/vg-mcp-bridge-VDZWFEO2.js.map +1 -0
  105. package/dist/{vgd-4EB6SKFB.js → vgd-3A2TRNP5.js} +8 -8
  106. package/dist/vgd-3A2TRNP5.js.map +1 -0
  107. package/package.json +2 -2
  108. package/dist/baseline-PRY5HKL5.js +0 -9
  109. package/dist/chunk-5UG4UHKU.js.map +0 -1
  110. package/dist/chunk-BHDP7EVJ.js.map +0 -1
  111. package/dist/chunk-JQ7OL4XG.js +0 -6
  112. package/dist/chunk-K2IWOZEQ.js.map +0 -1
  113. package/dist/chunk-LAYDXJEQ.js.map +0 -1
  114. package/dist/chunk-NQU6E6OG.js.map +0 -1
  115. package/dist/chunk-R5BGRJ6X.js.map +0 -1
  116. package/dist/chunk-SHD5TCZ5.js.map +0 -1
  117. package/dist/chunk-TKUWEJZJ.js.map +0 -1
  118. package/dist/chunk-TUVB6Q6F.js.map +0 -1
  119. package/dist/chunk-WGA4KUGS.js.map +0 -1
  120. package/dist/chunk-WGSLN73L.js.map +0 -1
  121. package/dist/chunk-WPQCJADP.js.map +0 -1
  122. package/dist/chunk-YENM3HT7.js +0 -23
  123. package/dist/chunk-YENM3HT7.js.map +0 -1
  124. package/dist/interactive-GMGWUEQO.js.map +0 -1
  125. package/dist/load-JXCTAAEI.js +0 -11
  126. package/dist/rules-ASYEUNB5.js.map +0 -1
  127. package/dist/version-B62ELJRY.js +0 -4
  128. package/dist/vg-mcp-bridge-CJIK5U56.js.map +0 -1
  129. package/dist/vgd-4EB6SKFB.js.map +0 -1
package/DOCS.md CHANGED
@@ -18,6 +18,7 @@ For a quick overview, see the [README](./README.md). This document covers everyt
18
18
  - [vg fix](#vg-fix)
19
19
  - [vg init](#vg-init)
20
20
  - [vg report](#vg-report)
21
+ - [vg review](#vg-review)
21
22
  - [vg sbom](#vg-sbom)
22
23
  - [vg scan](#vg-scan)
23
24
  - [Vulnerabilities and exposure attribution](#vulnerabilities-and-exposure-attribution)
@@ -420,6 +421,179 @@ vg report [--in <file>] [--format md|text|json]
420
421
  ---
421
422
 
422
423
 
424
+ ### vg review
425
+
426
+ **Vibgrate Review** — architecture and security-control review of the change you
427
+ just made, run locally from the code map. Where `vg scan` answers "how far behind
428
+ is the stack?", `vg review` answers a different question about the same
429
+ repository: *did this change move the system toward its declared architecture,
430
+ weaken a security control, or create an implication the author did not account
431
+ for?*
432
+
433
+ ```bash
434
+ vg review # the working tree + index, vs HEAD
435
+ vg review --base origin/main # merge-base of HEAD and the base branch
436
+ vg review explain arch-01 # the evidence behind one finding
437
+ ```
438
+
439
+ | Flag | Default | Description |
440
+ | --------------------- | ------- | -------------------------------------------------------------------------- |
441
+ | `--base <ref>` | — | Review HEAD against the merge-base with `<ref>` |
442
+ | `--format <fmt>` | `text` | `text`, `json` (the receipt), `sarif` (security findings only), `md` |
443
+ | `-o, --out <file>` | — | Write the formatted result to a file |
444
+ | `--push` | off | Send the receipt to Vibgrate Cloud (needs a DSN) |
445
+ | `--fail-on <level>` | from config | Gate CI on this decision: `none`, `fail`, or `needs_review` |
446
+ | `--explain` | off | Add local-model explanations. **Requires a local model; fails closed (exit 6) without one** |
447
+ | `--include-spans` | off | Include evidence line ranges in the pushed receipt |
448
+ | `--include-snippets` | off | Include capped source snippets in the pushed receipt (explicit opt-in) |
449
+ | `--offline` (global) | off | Never touch the network — no push, no model fetch |
450
+ | `--write-context` | off | Write `.vibgrate/review-context.md` — committed agent memory |
451
+ | `--inject-context [file]` | off | Update the managed review block inside `CLAUDE.md` (or a named file) |
452
+
453
+ `vg review` reads the code map, so run `vg` (or `vg build`) in the repository
454
+ first. Without a map it exits `6` — never `0`.
455
+
456
+ #### Decisions
457
+
458
+ Policy owns the decision, and it is the only layer that writes one. Findings —
459
+ from the scanners or from the optional local model — have nowhere to put a
460
+ verdict.
461
+
462
+ | Decision | Meaning |
463
+ | -------------- | ----------------------------------------------------------------------------- |
464
+ | `pass` | No material delta, or every finding is target-aligned |
465
+ | `fail` | An unresolved **protected** finding, or a high-severity finding above the confidence threshold |
466
+ | `needs_review` | Findings a human should look at |
467
+ | `undetermined` | Not enough evidence to decide — deliberately not a pass |
468
+
469
+ **Gating is opt-in, exactly as it is for [`vg scan`](#vg-scan).** By default
470
+ `vg review` reports the decision, writes it to the receipt, and exits `0` — even
471
+ on `fail`. It breaks the build only when you ask it to:
472
+
473
+ | Gate | Set by | Exits `2` on |
474
+ | --- | --- | --- |
475
+ | `none` | the default | never |
476
+ | `fail` | `--fail-on fail`, or `enforcement = "enforced"` with `fail_on = "fail"` | `fail` |
477
+ | `needs_review` | `--fail-on needs_review`, or the same in config | `fail`, `needs_review`, `undetermined` |
478
+
479
+ A gate failure exits `2` (`GATE_FAILED`) — the same code `vg scan`, `vg drift`
480
+ and `vg hcs gate` use. It is deliberately **not** `1`: across this CLI `1` means
481
+ the command itself errored, and a policy verdict of `fail` is the command
482
+ working correctly. Machines should branch on `decision` in the receipt, which is
483
+ unambiguous; the exit code exists for CI.
484
+
485
+ `enforcement = "advisory"` means no gate at all. That is what makes the setting
486
+ real rather than a label on the receipt — an advisory repository reports
487
+ honestly and never blocks a merge.
488
+
489
+ **Protected findings** — an unguarded entrypoint, a removed guard, a validated
490
+ taint flow, a known-vulnerable dependency — carry `protected_finding: true`.
491
+ While one is unresolved, policy cannot emit `pass`: not via an approved
492
+ exception, not via low confidence, not via the quick path, and not via anything
493
+ the model says. Turn a rule off in `.vibgrate/review.toml` if it does not apply
494
+ to your repository; that is the only way to stop it gating.
495
+
496
+ #### What it looks for
497
+
498
+ | Finding | What it means |
499
+ |---|---|
500
+ | `boundary_bypass` | A dependency breaks the declared layering, or skips a tier |
501
+ | `peer_deviation` | This file does something its peers do differently |
502
+ | `unguarded_entrypoint` | **Protected.** A mutating route has no authorization guard, where its peers do |
503
+ | `guard_removed` | **Protected.** A guard was deleted and nothing equivalent remains |
504
+ | `known_vulnerable_dependency` | **Protected.** A changed manifest declares a package with a known advisory |
505
+ | `duplicate_implementation` | This re-implements something the repository already has |
506
+ | `unverified_change` | Changed code with no test reaching it |
507
+
508
+ Two of these deserve a note, because they are what a linter cannot do:
509
+
510
+ **Peers are graph areas and roles, not folders.** A directory is a filing
511
+ decision; an area is a structural one. Recent files count roughly twice what
512
+ year-old ones do, so a large abandoned wing does not get to dictate "the
513
+ convention". Where peers are too evenly split to have one, Review says *no
514
+ convention* instead of naming a plurality winner.
515
+
516
+ **A majority is never treated as correct.** Peers establish what is *normal*;
517
+ only `target_pattern` establishes what is *right*. A file that goes through the
518
+ service layer while all its peers bypass it is the first one to improve — Review
519
+ will not flag it. That is the difference between this and a consistency scanner,
520
+ which by construction scores your best file worst.
521
+
522
+ **Routes get three answers, not two.** Every route is `auth`, `not-auth`, or
523
+ `unsure`, and `unsure` is never promoted. A guard Review does not recognise, or
524
+ a file it could not parse, becomes an honest unknown — never "this route is
525
+ open". Below four classified peer routes a finding is advisory and cannot gate.
526
+
527
+ #### Before you write it — `assess_change`
528
+
529
+ `vg review` asks "was that change sound?". The MCP tool `assess_change` asks
530
+ "would this be sound?", while an agent is still deciding — the only point at
531
+ which the fix is free.
532
+
533
+ ```jsonc
534
+ // one call, from any MCP client wired to `vg serve`
535
+ { "file": "src/routes/invoices.ts", "content": "<the proposed code>" }
536
+ ```
537
+
538
+ It returns the conflicts, anything the proposal duplicates, the convention its
539
+ peers follow with example files to open, and the declared target. One tool, not
540
+ a family of them: a family pushes the judgement into the model, and in this
541
+ product the model never decides.
542
+
543
+ #### Agent memory — `--write-context`
544
+
545
+ `vg review --write-context` writes `.vibgrate/review-context.md`: the declared
546
+ target, the conventions with exemplars, what is currently open, and what to do.
547
+ Commit it, and the next agent starts from the answer.
548
+
549
+ It tells the agent to follow the **declared target**, never "the majority" —
550
+ otherwise a repository mid-migration ends up with memory instructing every agent
551
+ to perpetuate the legacy it is migrating away from. `--inject-context` keeps the
552
+ same content in a marked block inside `CLAUDE.md`, leaving everything a human
553
+ wrote in that file untouched.
554
+
555
+ #### Configuration — `.vibgrate/review.toml`
556
+
557
+ ```toml
558
+ [review]
559
+ enforcement = "advisory" # advisory | enforced
560
+ fail_on = "fail" # fail | needs_review
561
+ target_pattern = "layered" # the architecture you say you want
562
+
563
+ [review.protected]
564
+ unguarded_entrypoint = true
565
+ known_vulnerable_dependency = true
566
+ validated_taint = true
567
+ ```
568
+
569
+ Read from the **trusted base branch** when `--base` is given, so a pull request
570
+ cannot weaken the policy applied to itself.
571
+
572
+ Declaring `target_pattern` is what turns a layering observation into a
573
+ *regression*. Without it, a dependency that skips a tier is reported as a medium
574
+ finding about the repository's own majority — because a majority is not the same
575
+ thing as a decision, and Review will not treat it as one.
576
+
577
+ #### What it does not claim
578
+
579
+ Review reports change integrity. It does not prove code is secure, correct, or
580
+ free of vulnerabilities; it does not replace Semgrep, CodeQL, a compiler, or your
581
+ tests; and **absence of findings is not a certification**.
582
+
583
+ #### Privacy
584
+
585
+ Source stays on the machine. `--push` sends the receipt — decisions, claims,
586
+ evidence ids, paths, digests, versions — never the analysis capsule and never
587
+ source text. Line ranges are opt-in (`--include-spans`); snippets are a second,
588
+ explicit opt-in (`--include-snippets`) and are capped. `--offline`, or simply
589
+ having no DSN, keeps the receipt on disk.
590
+
591
+ Ready-to-use workflows: `examples/github-actions/vibgrate-review.yml` and
592
+ `vibgrate-review-sarif.yml`.
593
+
594
+ ---
595
+
596
+
423
597
  ### vg sbom
424
598
 
425
599
  Export [SBOMs](https://vibgrate.com/glossary/sbom) from an existing scan artifact or compare two artifacts.
@@ -654,7 +828,7 @@ Ask the code map a question using hybrid lexical + structural + semantic search.
654
828
  vg ask "<question>"
655
829
  ```
656
830
 
657
- A local ONNX embedding model is downloaded once on first use, then cached and fully offline. Degrades gracefully to lexical-only under `--local` or `--no-semantic`.
831
+ A local ONNX embedding model is downloaded once on first use, then cached and fully offline. Degrades gracefully to lexical-only under `--offline` or `--no-semantic`.
658
832
 
659
833
  > **Semantic search is opt-in.** The embedding backend (`fastembed`, which pulls a native ONNX runtime) is declared as an **optional dependency**: package managers install it by default, but if it's absent — e.g. you installed with `--omit=optional`, or it failed to build on your platform — `vg ask` and `vg embed` transparently fall back to lexical + structural search. Nothing else in the CLI needs it, so `vg build`, `vg show`, `vg impact`, drift reporting, and MCP serving all run without it. If you never use semantic `ask`, you can install lean: `npm i @vibgrate/cli --omit=optional`. A host application that bundles the CLI without optional dependencies (Vibgrate for VS Code does this) can supply the backend from its own directory by setting `VIBGRATE_EMBEDDER_PATH` to a folder whose `node_modules` contains `fastembed`; when set, that copy is used first.
660
834
 
@@ -933,7 +1107,7 @@ With no `--provider`, `vg code` chooses from what you have already configured, b
933
1107
  | `--session` | — | With `--stream-json`: stay open for further turns instead of exiting after one |
934
1108
  | `--mock <file>` | — | Use a scripted reply instead of a model (offline; tests/CI/benchmarks) |
935
1109
  | `-o, --out <file>` | — | Write the JSON result to a file (for CI/benchmarks) |
936
- | `--local` (global) | — | On-device model backends only — no hosted model call, no catalog fetch |
1110
+ | `--local` (global) | — | On-device model backends only — never a hosted model. Implies `--offline` |
937
1111
 
938
1112
  `--stream-json` is the protocol **Vibgrate for VS Code** speaks to the CLI: the panel renders the agent's steps, approves or denies each mutating tool call over stdin, and with `--session` keeps one warm graph across turns. It is a supported surface for any host UI, not just ours.
939
1113
 
@@ -1260,7 +1434,7 @@ vg models catalog
1260
1434
  | `uninstall <name>` | Uninstall a local model — Ollama, LM Studio, or GGUF (`--dry-run` plan; TTY confirm or `--yes`; `--runtime` optional auto-detect) |
1261
1435
  | `host-bench` | Approach B measurement arms (`--simulate` default, `--mock`, `--live --model-path`, `--gate`) |
1262
1436
  | `coding-metrics` | Unified host-bench + Fusion FCS/ZNS report (`--out`, `--gate`, `--version`); publish path for release |
1263
- | `catalog` | Live hosted model catalog (cached; not used under `--local`) |
1437
+ | `catalog` | Live hosted model catalog (cached; not used under `--offline`) |
1264
1438
  | `--json` | Machine-readable JSON on stdout |
1265
1439
 
1266
1440
  ```bash
@@ -1338,7 +1512,7 @@ Add `--json` for machine-readable output.
1338
1512
 
1339
1513
  ### vg serve
1340
1514
 
1341
- Start [Vibgrate AI Context](https://vibgrate.com/library) — a local-first [MCP](https://vibgrate.com/glossary/model-context-protocol) serving your code map, drift, and version-correct docs to your AI assistant (fully offline under `--local`).
1515
+ Start [Vibgrate AI Context](https://vibgrate.com/library) — a local-first [MCP](https://vibgrate.com/glossary/model-context-protocol) serving your code map, drift, and version-correct docs to your AI assistant (fully offline under `--offline`).
1342
1516
 
1343
1517
  ```bash
1344
1518
  vg serve
@@ -1350,7 +1524,7 @@ vg serve
1350
1524
  | `--port <n>` | `7437` | Port for `--http` |
1351
1525
  | `--host <h>` | `127.0.0.1` | Host for `--http` |
1352
1526
  | `--savings` | — | Record local, counts-only usage savings (opt-in) |
1353
- | `--share-stats` | — | Also upload the counts-only usage ledger to Vibgrate to improve the local MCP (opt-in; off by default; implies `--savings`; disabled under `--local`) |
1527
+ | `--share-stats` | — | Also upload the counts-only usage ledger to Vibgrate to improve the local MCP (opt-in; off by default; implies `--savings`; disabled under `--offline`) |
1354
1528
  | `--dedup` | — | Collapse a node's heavy relation lists on repeat reads within a session, to save tokens (opt-in) |
1355
1529
  | `--no-refresh` | — | Serve the map as built; skip the auto-rebuild when files change |
1356
1530
 
@@ -1358,7 +1532,7 @@ Via stdio (default), your AI assistant spawns the server. Via `--http`, it runs
1358
1532
 
1359
1533
  **A live status display shows what the server is doing for you.** While `vg serve` runs in a terminal, a status block on stderr updates in place: uptime, which AI clients are connected (detected from the MCP handshake), calls and average response time per tool, and — for the navigation tools with a grep/read baseline — the context tokens served vs the estimated tokens a grep-and-read agent would have burned instead, with the estimated saving labelled as such. Outside a terminal (when your assistant spawns the server) it degrades to a quiet one-line heartbeat in the server logs every 15 minutes, and only when there has been activity. The display is in-memory only and always on — nothing is written to disk or uploaded (recording and sharing below stay opt-in) — and `--quiet` turns it off.
1360
1534
 
1361
- **Usage stats — local by default, sharing is opt-in.** `--savings` records a *counts-only* ledger under `.vibgrate/` — per navigation call: which tool, how it resolved (complete/partial/miss), the vg-vs-grep token figures, whether it came over the MCP (`mcp`) or the `vg` CLI (`cli`), and a coarse client label (which AI). `vg savings` reports it locally; nothing leaves your machine. `--share-stats` additionally uploads that same counts-only ledger to Vibgrate periodically, so we can see how the local MCP is used and improve it. It **never** sends code, file paths, question text, repo identity, or any credential — only counts, outcomes, token figures, the vg version, your OS/arch, and a random per-install id. It's off unless you pass the flag, is disabled entirely under `--local`, and the endpoint can be overridden with `VIBGRATE_STATS_ENDPOINT`.
1535
+ **Usage stats — local by default, sharing is opt-in.** `--savings` records a *counts-only* ledger under `.vibgrate/` — per navigation call: which tool, how it resolved (complete/partial/miss), the vg-vs-grep token figures, whether it came over the MCP (`mcp`) or the `vg` CLI (`cli`), and a coarse client label (which AI). `vg savings` reports it locally; nothing leaves your machine. `--share-stats` additionally uploads that same counts-only ledger to Vibgrate periodically, so we can see how the local MCP is used and improve it. It **never** sends code, file paths, question text, repo identity, or any credential — only counts, outcomes, token figures, the vg version, your OS/arch, and a random per-install id. It's off unless you pass the flag, is disabled entirely under `--offline`, and the endpoint can be overridden with `VIBGRATE_STATS_ENDPOINT`.
1362
1536
 
1363
1537
  **Attributing CLI calls.** The MCP path detects the calling client automatically from the connection handshake. For CLI calls, pass `--client=<ai>` (e.g. `vg "how does auth work" --client=claude`) so the call is attributed in `vg savings` and any shared stats — this is what `vg install` writes into each assistant's skill. Without `--client`, a bare `vg ask` records nothing.
1364
1538
 
@@ -1372,7 +1546,7 @@ The server exposes read-only tools your assistant can call over the code map and
1372
1546
  - `upgrade_impact` — what an upgrade will cost: version distance, how many files import the package, the vulnerabilities it fixes, and — with `changelog: true` — online breaking-change notes between your version and the latest.
1373
1547
  - `resolve_library`, `library_docs` — version-correct, drift-annotated library docs.
1374
1548
 
1375
- All tools are read-only. The server is local-first: it always answers from your machine when it can, and its only network touches are the embedder's one-time model fetch, `upgrade_impact`'s `changelog`, and `library_docs`' fall-through to the hosted catalog when the local docs for a library are thin or missing. `--local` is the hard airgap — it disables all three.
1549
+ All tools are read-only. The server is local-first: it always answers from your machine when it can, and its only network touches are the embedder's one-time model fetch, `upgrade_impact`'s `changelog`, and `library_docs`' fall-through to the hosted catalog when the local docs for a library are thin or missing. `--offline` is the hard airgap — it disables all three.
1376
1550
 
1377
1551
  ---
1378
1552
 
@@ -1660,7 +1834,7 @@ vg doctor --local
1660
1834
  | Flag | Description |
1661
1835
  |------|-------------|
1662
1836
  | `--json` | Machine-readable JSON on stdout |
1663
- | `--local` | Skip the hosted reachability probe |
1837
+ | `--offline` | Skip the hosted reachability probe |
1664
1838
  | `-C, --cwd <dir>` | Run as if started in that directory |
1665
1839
 
1666
1840
  ---
@@ -1682,7 +1856,7 @@ vg lsp --local
1682
1856
  | `--diagnostics` | Also publish Problems-panel diagnostics (EOL runtime, unmaintained packages, license change). **Off by default** — drift is not a defect, and the Problems panel is not filled by default. |
1683
1857
  | `--no-graph` | Skip the local code graph entirely: no background build; graph queries report it as turned off |
1684
1858
  | `--no-semantic` | Never use semantic search for graph queries (lexical only; embedding model is not downloaded) |
1685
- | `--local` | Never touch the network (air-gapped editor sessions) |
1859
+ | `--offline` | Never touch the network (air-gapped editor sessions) |
1686
1860
 
1687
1861
  The process owns stdin/stdout until the client sends `shutdown` + `exit`. Speaks standard LSP plus a custom `vibgrate/score` notification carrying the DriftScore and its **band** (never a colour) so clients can theme correctly.
1688
1862
 
@@ -2224,6 +2398,25 @@ What it **does** collect:
2224
2398
 
2225
2399
  ---
2226
2400
 
2401
+ ## `--offline` vs `--local`
2402
+
2403
+ Two different questions, so two flags:
2404
+
2405
+ | Flag | Question it answers | Effect |
2406
+ | --- | --- | --- |
2407
+ | `--offline` | May this reach the network? | No model download, no catalog fetch, no upload. Available on every command. |
2408
+ | `--local` | Where does inference run? | On-device backends only, never a hosted model. Consumed by [`vg code`](#vg-code). |
2409
+
2410
+ **`--local` implies `--offline`**, so anything that already passes `--local`
2411
+ keeps its exact behaviour. The converse does not hold, and deliberately so:
2412
+ forcing on-device inference is a choice you may want on a fully connected
2413
+ machine — for privacy, cost, or latency — and `--offline` would not say it.
2414
+
2415
+ `vg scan --offline` and `vg evidence --offline` mean the same thing they always
2416
+ have.
2417
+
2418
+ ---
2419
+
2227
2420
  ## Exit Codes
2228
2421
 
2229
2422
  CI and agents branch on these, so they are a stable contract.
@@ -2239,6 +2432,8 @@ CI and agents branch on these, so they are a stable contract.
2239
2432
  | `6` | `ENGINE_UNAVAILABLE` | A required optional module is not installed and could not be fetched (see [`vg module`](#vg-module)) |
2240
2433
 
2241
2434
  `6` is deliberately distinct from `2`: a CI gate must never read "engine missing" as a gate verdict.
2435
+ The same rule is why [`vg review`](#vg-review) exits `6` when there is no code map, and why `--explain`
2436
+ exits `6` rather than quietly producing a review no model contributed to.
2242
2437
 
2243
2438
  ---
2244
2439
 
package/README.md CHANGED
@@ -590,6 +590,7 @@ All HCS computation runs in an optional, separately-licensed engine module that
590
590
  | `vg fix` | Ranked, risk-tiered upgrade plans from the hosted planner — then apply the one you choose |
591
591
  | `vg init [path]` | Initialise config and `.vibgrate/` |
592
592
  | `vg report` | Generate a report from a scan artifact |
593
+ | `vg review` | **Vibgrate Review** — architecture + security-control review of the current change, locally. One decision (`pass` / `needs_review` / `fail` / `undetermined`) in a signed receipt; protected findings cannot be blessed into a pass. Reports change integrity, not a proof of security. Builds or refreshes the code map itself when it is missing or stale (`--no-auto-build` opts out) |
593
594
  | `vg sbom export` / `delta` / `vex` | Export CycloneDX/SPDX SBOM, diff two artifacts, or emit an OpenVEX document |
594
595
  | `vg scan [path]` | Scan for upgrade drift |
595
596
  | `vg scan --full` | Comprehensive scan: drift + vulnerabilities + a banned-dependency report |
@@ -684,6 +685,7 @@ When `@vibgrate/cli` is installed, it registers **both** bin entries uncondition
684
685
  | [**Vibgrate AI Context**](https://vibgrate.com/library) | `vg serve` — version-correct library docs, your code map, and offline drift, served to any assistant. [Supported assistants](https://vibgrate.com/skills) · [on mcp.so](https://mcp.so/servers/cli-a2b26f) |
685
686
  | [**Vibgrate Cloud MCP**](https://vibgrate.com/mcp) | The hosted MCP server over your workspace data (OAuth 2.1) |
686
687
  | [**Vibgrate Cloud**](https://vibgrate.com/cloud) | History, trends, and team rollups. [Create a workspace](https://dash.vibgrate.com) · [pricing](https://vibgrate.com/pricing) |
688
+ | [**Vibgrate Review**](https://vibgrate.com/cli/docs/review-command) | `vg review` — did this change move the system toward its declared architecture, or weaken a security control? Runs locally from the code map; the receipt, not the repository, is what gets pushed |
687
689
  | [**Vibgrate Evidence**](https://vibgrate.com/evidence) | `vg evidence` — freeze shipped releases, then answer "which shipped products contain this vulnerability?" as signed, offline-verifiable evidence. Jurisdiction-neutral regimes, [EU CRA](https://vibgrate.com/cra) first |
688
690
 
689
691
  **How the scores work:** [DriftScore](https://vibgrate.com/driftscore) · [RiskScore](https://vibgrate.com/riskscore) · [DriftRisk Index](https://vibgrate.com/driftrisk) · [published methodology](https://vibgrate.com/whitepapers/software-risk-and-drift-scoring-methodology) · [public index of real scans](https://vibgrate.com/driftrisk/index) · [README badges](https://vibgrate.com/badges)
@@ -1,31 +1,31 @@
1
- export { CONTEXT_POLICY_VERSION, DISCOVERY_TOOLS, MUTATION_TOOLS, compact, compactWithModel, runAgent } from './chunk-WPQCJADP.js';
1
+ export { CONTEXT_POLICY_VERSION, DISCOVERY_TOOLS, MUTATION_TOOLS, compact, compactWithModel, runAgent } from './chunk-NQQT75AN.js';
2
2
  import './chunk-543E6WDJ.js';
3
- import './chunk-PEHMRPYM.js';
4
- import './chunk-ZRDTI67N.js';
5
- import './chunk-SHD5TCZ5.js';
3
+ import './chunk-FKCN653F.js';
4
+ import './chunk-5RW5PIBH.js';
5
+ import './chunk-R2FX4USU.js';
6
6
  import './chunk-3HNPVXUK.js';
7
7
  import './chunk-5HYCB55P.js';
8
- import './chunk-R5BGRJ6X.js';
8
+ import './chunk-QMROD7MI.js';
9
9
  import './chunk-VIWZ4QT7.js';
10
- import './chunk-QONHVC4H.js';
11
- export { summarizeCapsule } from './chunk-LAYDXJEQ.js';
10
+ import './chunk-TWFPHXPN.js';
11
+ export { summarizeCapsule } from './chunk-OAYSRM7J.js';
12
12
  import './chunk-EZ7UC5Y5.js';
13
13
  import './chunk-6BEBJCK4.js';
14
14
  import './chunk-J7LKEB2R.js';
15
- import './chunk-NQU6E6OG.js';
15
+ import './chunk-YL332BU2.js';
16
16
  import './chunk-VHRSG6CF.js';
17
17
  import './chunk-6H2QV5DR.js';
18
- import './chunk-TOHNVUDD.js';
18
+ import './chunk-NSC7ZK62.js';
19
19
  import './chunk-T7RSQ5WD.js';
20
20
  import './chunk-6WRG7VDO.js';
21
- import './chunk-WU2DVESP.js';
22
- import './chunk-K2IWOZEQ.js';
21
+ import './chunk-BA6YBVNT.js';
22
+ import './chunk-FPIMNVLI.js';
23
23
  import './chunk-MVPRPSXU.js';
24
24
  import './chunk-O3LABX6I.js';
25
- import './chunk-JQ7OL4XG.js';
25
+ import './chunk-UGRUG2FA.js';
26
26
  import './chunk-XP6UUCZZ.js';
27
27
  import './chunk-JFGYT6BI.js';
28
28
  import './chunk-XQNKCA2J.js';
29
29
  import './chunk-Z6ZWNWWR.js';
30
- //# sourceMappingURL=agent-4RHH2FR2.js.map
31
- //# sourceMappingURL=agent-4RHH2FR2.js.map
30
+ //# sourceMappingURL=agent-5GW7C7EB.js.map
31
+ //# sourceMappingURL=agent-5GW7C7EB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-4RHH2FR2.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-5GW7C7EB.js"}
@@ -1,10 +1,10 @@
1
- export { addApprovalRule, approvalsPath, findMatchingRule, globMatch, loadApprovalRules, normalizeCommandPrefix, removeApprovalRule, ruleForAction, ruleKey, ruleLabel, saveApprovalRules } from './chunk-PEHMRPYM.js';
2
- import './chunk-K2IWOZEQ.js';
1
+ export { addApprovalRule, approvalsPath, findMatchingRule, globMatch, loadApprovalRules, normalizeCommandPrefix, removeApprovalRule, ruleForAction, ruleKey, ruleLabel, saveApprovalRules } from './chunk-FKCN653F.js';
2
+ import './chunk-FPIMNVLI.js';
3
3
  import './chunk-O3LABX6I.js';
4
- import './chunk-JQ7OL4XG.js';
4
+ import './chunk-UGRUG2FA.js';
5
5
  import './chunk-XP6UUCZZ.js';
6
6
  import './chunk-JFGYT6BI.js';
7
7
  import './chunk-XQNKCA2J.js';
8
8
  import './chunk-Z6ZWNWWR.js';
9
- //# sourceMappingURL=approvals-XDFM346D.js.map
10
- //# sourceMappingURL=approvals-XDFM346D.js.map
9
+ //# sourceMappingURL=approvals-ISJFILVK.js.map
10
+ //# sourceMappingURL=approvals-ISJFILVK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"approvals-XDFM346D.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"approvals-ISJFILVK.js"}
@@ -1,4 +1,4 @@
1
- export { attachVgd, daemonDisabledReason } from './chunk-E44I7635.js';
1
+ export { attachVgd, daemonDisabledReason } from './chunk-KCKG3EEJ.js';
2
2
  import './chunk-A3WWRW7U.js';
3
3
  import './chunk-VHRSG6CF.js';
4
4
  import './chunk-6H2QV5DR.js';
@@ -6,5 +6,5 @@ import './chunk-XP6UUCZZ.js';
6
6
  import './chunk-JFGYT6BI.js';
7
7
  import './chunk-XQNKCA2J.js';
8
8
  import './chunk-Z6ZWNWWR.js';
9
- //# sourceMappingURL=attach-LGTCYNKC.js.map
10
- //# sourceMappingURL=attach-LGTCYNKC.js.map
9
+ //# sourceMappingURL=attach-JUSALQ75.js.map
10
+ //# sourceMappingURL=attach-JUSALQ75.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"attach-LGTCYNKC.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"attach-JUSALQ75.js"}
@@ -0,0 +1,9 @@
1
+ export { baselineCommand, runBaseline } from './chunk-RRSUNTZI.js';
2
+ import './chunk-HUJZO4K5.js';
3
+ import './chunk-PVNALGTG.js';
4
+ import './chunk-CRAMOEBE.js';
5
+ import './chunk-T7RSQ5WD.js';
6
+ import './chunk-UGRUG2FA.js';
7
+ import './chunk-Z6ZWNWWR.js';
8
+ //# sourceMappingURL=baseline-YL2IZZ4Y.js.map
9
+ //# sourceMappingURL=baseline-YL2IZZ4Y.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"baseline-PRY5HKL5.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"baseline-YL2IZZ4Y.js"}
@@ -1,5 +1,5 @@
1
1
  import { resolveOne } from './chunk-J7LKEB2R.js';
2
- import { impactOf, queryGraph } from './chunk-NQU6E6OG.js';
2
+ import { impactOf, queryGraph } from './chunk-YL332BU2.js';
3
3
  import { vgdRequest } from './chunk-VHRSG6CF.js';
4
4
 
5
5
  // src/code/graph-backend.ts
@@ -132,5 +132,5 @@ function resolveSymbolId(graph, symbol) {
132
132
  }
133
133
 
134
134
  export { localGraphBackend, resolveGraphBackend, vgdGraphBackend };
135
- //# sourceMappingURL=chunk-ZRDTI67N.js.map
136
- //# sourceMappingURL=chunk-ZRDTI67N.js.map
135
+ //# sourceMappingURL=chunk-5RW5PIBH.js.map
136
+ //# sourceMappingURL=chunk-5RW5PIBH.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/code/graph-backend.ts"],"names":[],"mappings":"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","file":"chunk-ZRDTI67N.js","sourcesContent":["/**\n * Graph query backend for the agent tool layer (Fusion Runtime).\n *\n * Tools prefer a warm ActiveGraph in vgd when a daemon session is attached;\n * otherwise they fall back to the in-process {@link VgGraph}. Failures never\n * surface to the model as transport errors — they degrade to local query.\n */\n\nimport { queryGraph } from '../engine/query.js';\nimport { impactOf } from '../engine/impact.js';\nimport { resolveOne } from '../engine/lookup.js';\nimport { vgdRequest, type VgdClientOptions } from '../runtime/vgd/client.js';\nimport type { VgdRequest } from '../runtime/vgd/protocol.js';\nimport type { VgGraph } from '../schema.js';\n\nexport type GraphBackendSource = 'local' | 'vgd';\n\nexport interface GraphSearchHit {\n id: string;\n qualifiedName: string;\n kind: string;\n file: string;\n line: number;\n score: number;\n why: string;\n signature?: string | null;\n}\n\nexport interface GraphImpactHit {\n id: string;\n name: string;\n kind: string;\n file: string;\n line: number;\n depth: number;\n confidence: number;\n}\n\nexport interface GraphImpactResult {\n root: { id: string; name: string };\n depth: number;\n affected: GraphImpactHit[];\n direct: number;\n transitive: number;\n source: GraphBackendSource;\n}\n\nexport interface GraphSearchResult {\n matches: GraphSearchHit[];\n source: GraphBackendSource;\n}\n\nexport interface GraphBackend {\n readonly source: GraphBackendSource;\n search(query: string, opts?: { limit?: number }): Promise<GraphSearchResult>;\n impact(symbol: string, opts?: { depth?: number }): Promise<GraphImpactResult | null>;\n}\n\n/** Pure in-process backend over a loaded graph. */\nexport function localGraphBackend(graph: VgGraph): GraphBackend {\n return {\n source: 'local',\n async search(query, opts) {\n const res = queryGraph(graph, query, { budget: 1500, limit: opts?.limit ?? 10 });\n return {\n source: 'local',\n matches: res.matches.map((m) => ({\n id: m.node.id,\n qualifiedName: m.node.qualifiedName,\n kind: m.node.kind,\n file: m.node.file,\n line: m.node.span?.start ?? 0,\n score: m.score,\n why: m.why,\n signature: m.node.signature ?? null,\n })),\n };\n },\n async impact(symbol, opts) {\n const node = resolveSymbolId(graph, symbol);\n if (!node) return null;\n const imp = impactOf(graph, node, { depth: opts?.depth ?? 4 });\n return {\n source: 'local',\n root: imp.root,\n depth: imp.depth,\n affected: imp.affected.map((a) => ({\n id: a.id,\n name: a.name,\n kind: a.kind,\n file: a.file,\n line: a.line,\n depth: a.depth,\n confidence: a.confidence,\n })),\n direct: imp.direct,\n transitive: imp.transitive,\n };\n },\n };\n}\n\nexport interface VgdGraphBackendOptions {\n repositoryId: string;\n gitRef?: string;\n socketPath?: string;\n /** Always required for fall back when vgd has no slot or is down. */\n fallback: VgGraph;\n request?: (req: VgdRequest, opts?: VgdClientOptions) => Promise<unknown>;\n}\n\n/**\n * Prefer vgd query/impact; on any failure or empty daemon state, use local.\n * The returned `source` reflects which path answered.\n */\nexport function vgdGraphBackend(options: VgdGraphBackendOptions): GraphBackend {\n const local = localGraphBackend(options.fallback);\n const request = options.request ?? ((req, opts) => vgdRequest(req, opts));\n const clientOpts: VgdClientOptions = { socketPath: options.socketPath };\n\n return {\n source: 'vgd',\n async search(query, opts) {\n try {\n const res = (await request(\n {\n op: 'query-graph',\n repositoryId: options.repositoryId,\n query,\n limit: opts?.limit ?? 10,\n gitRef: options.gitRef,\n },\n clientOpts,\n )) as {\n ok?: boolean;\n matches?: Array<{\n id: string;\n qualifiedName: string;\n kind: string;\n file: string;\n line: number;\n score: number;\n why: string;\n }>;\n };\n if (res?.ok && Array.isArray(res.matches)) {\n return {\n source: 'vgd',\n matches: res.matches.map((m) => ({\n id: m.id,\n qualifiedName: m.qualifiedName,\n kind: m.kind,\n file: m.file,\n line: m.line,\n score: m.score,\n why: m.why,\n signature: null,\n })),\n };\n }\n } catch {\n /* fall through */\n }\n return local.search(query, opts);\n },\n async impact(symbol, opts) {\n try {\n const res = (await request(\n {\n op: 'impact-of',\n repositoryId: options.repositoryId,\n symbol,\n depth: opts?.depth ?? 4,\n gitRef: options.gitRef,\n },\n clientOpts,\n )) as {\n ok?: boolean;\n root?: { id: string; name: string };\n depth?: number;\n affected?: GraphImpactHit[];\n direct?: number;\n transitive?: number;\n };\n if (res?.ok && res.root && Array.isArray(res.affected)) {\n return {\n source: 'vgd',\n root: res.root,\n depth: res.depth ?? opts?.depth ?? 4,\n affected: res.affected,\n direct: res.direct ?? 0,\n transitive: res.transitive ?? 0,\n };\n }\n } catch {\n /* fall through */\n }\n return local.impact(symbol, opts);\n },\n };\n}\n\n/**\n * Pick a backend: vgd when repositoryId + socket are known, else local.\n */\nexport function resolveGraphBackend(input: {\n graph: VgGraph;\n repositoryId?: string | null;\n gitRef?: string | null;\n socketPath?: string | null;\n}): GraphBackend {\n if (input.repositoryId && input.socketPath) {\n return vgdGraphBackend({\n repositoryId: input.repositoryId,\n gitRef: input.gitRef ?? undefined,\n socketPath: input.socketPath,\n fallback: input.graph,\n });\n }\n return localGraphBackend(input.graph);\n}\n\nfunction resolveSymbolId(graph: VgGraph, symbol: string): string | null {\n if (graph.nodes.some((n) => n.id === symbol)) return symbol;\n const hit = resolveOne(graph, symbol);\n if (hit.node?.id) return hit.node.id;\n if (hit.candidates.length === 1 && hit.candidates[0]?.id) return hit.candidates[0].id;\n const q = symbol.toLowerCase();\n const byQn = graph.nodes.find((n) => n.qualifiedName?.toLowerCase() === q || n.name?.toLowerCase() === q);\n return byQn?.id ?? null;\n}\n"]}
1
+ {"version":3,"sources":["../src/code/graph-backend.ts"],"names":[],"mappings":";;;;;AA2DO,SAAS,kBAAkB,KAAA,EAA8B;AAC9D,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,OAAA;AAAA,IACR,MAAM,MAAA,CAAO,KAAA,EAAO,IAAA,EAAM;AACxB,MAAA,MAAM,GAAA,GAAM,UAAA,CAAW,KAAA,EAAO,KAAA,EAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,KAAA,IAAS,EAAA,EAAI,CAAA;AAC/E,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,OAAA;AAAA,QACR,OAAA,EAAS,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,UAC/B,EAAA,EAAI,EAAE,IAAA,CAAK,EAAA;AAAA,UACX,aAAA,EAAe,EAAE,IAAA,CAAK,aAAA;AAAA,UACtB,IAAA,EAAM,EAAE,IAAA,CAAK,IAAA;AAAA,UACb,IAAA,EAAM,EAAE,IAAA,CAAK,IAAA;AAAA,UACb,IAAA,EAAM,CAAA,CAAE,IAAA,CAAK,IAAA,EAAM,KAAA,IAAS,CAAA;AAAA,UAC5B,OAAO,CAAA,CAAE,KAAA;AAAA,UACT,KAAK,CAAA,CAAE,GAAA;AAAA,UACP,SAAA,EAAW,CAAA,CAAE,IAAA,CAAK,SAAA,IAAa;AAAA,SACjC,CAAE;AAAA,OACJ;AAAA,IACF,CAAA;AAAA,IACA,MAAM,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAM;AACzB,MAAA,MAAM,IAAA,GAAO,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AAC1C,MAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,MAAA,MAAM,GAAA,GAAM,SAAS,KAAA,EAAO,IAAA,EAAM,EAAE,KAAA,EAAO,IAAA,EAAM,KAAA,IAAS,CAAA,EAAG,CAAA;AAC7D,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,OAAA;AAAA,QACR,MAAM,GAAA,CAAI,IAAA;AAAA,QACV,OAAO,GAAA,CAAI,KAAA;AAAA,QACX,QAAA,EAAU,GAAA,CAAI,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,UACjC,IAAI,CAAA,CAAE,EAAA;AAAA,UACN,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,OAAO,CAAA,CAAE,KAAA;AAAA,UACT,YAAY,CAAA,CAAE;AAAA,SAChB,CAAE,CAAA;AAAA,QACF,QAAQ,GAAA,CAAI,MAAA;AAAA,QACZ,YAAY,GAAA,CAAI;AAAA,OAClB;AAAA,IACF;AAAA,GACF;AACF;AAeO,SAAS,gBAAgB,OAAA,EAA+C;AAC7E,EAAA,MAAM,KAAA,GAAQ,iBAAA,CAAkB,OAAA,CAAQ,QAAQ,CAAA;AAChD,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,KAAY,CAAC,KAAK,IAAA,KAAS,UAAA,CAAW,KAAK,IAAI,CAAA,CAAA;AACvE,EAAA,MAAM,UAAA,GAA+B,EAAE,UAAA,EAAY,OAAA,CAAQ,UAAA,EAAW;AAEtE,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,KAAA;AAAA,IACR,MAAM,MAAA,CAAO,KAAA,EAAO,IAAA,EAAM;AACxB,MAAA,IAAI;AACF,QAAA,MAAM,MAAO,MAAM,OAAA;AAAA,UACjB;AAAA,YACE,EAAA,EAAI,aAAA;AAAA,YACJ,cAAc,OAAA,CAAQ,YAAA;AAAA,YACtB,KAAA;AAAA,YACA,KAAA,EAAO,MAAM,KAAA,IAAS,EAAA;AAAA,YACtB,QAAQ,OAAA,CAAQ;AAAA,WAClB;AAAA,UACA;AAAA,SACF;AAYA,QAAA,IAAI,KAAK,EAAA,IAAM,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA,EAAG;AACzC,UAAA,OAAO;AAAA,YACL,MAAA,EAAQ,KAAA;AAAA,YACR,OAAA,EAAS,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,cAC/B,IAAI,CAAA,CAAE,EAAA;AAAA,cACN,eAAe,CAAA,CAAE,aAAA;AAAA,cACjB,MAAM,CAAA,CAAE,IAAA;AAAA,cACR,MAAM,CAAA,CAAE,IAAA;AAAA,cACR,MAAM,CAAA,CAAE,IAAA;AAAA,cACR,OAAO,CAAA,CAAE,KAAA;AAAA,cACT,KAAK,CAAA,CAAE,GAAA;AAAA,cACP,SAAA,EAAW;AAAA,aACb,CAAE;AAAA,WACJ;AAAA,QACF;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,KAAA,CAAM,MAAA,CAAO,KAAA,EAAO,IAAI,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,MAAM,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAM;AACzB,MAAA,IAAI;AACF,QAAA,MAAM,MAAO,MAAM,OAAA;AAAA,UACjB;AAAA,YACE,EAAA,EAAI,WAAA;AAAA,YACJ,cAAc,OAAA,CAAQ,YAAA;AAAA,YACtB,MAAA;AAAA,YACA,KAAA,EAAO,MAAM,KAAA,IAAS,CAAA;AAAA,YACtB,QAAQ,OAAA,CAAQ;AAAA,WAClB;AAAA,UACA;AAAA,SACF;AAQA,QAAA,IAAI,GAAA,EAAK,MAAM,GAAA,CAAI,IAAA,IAAQ,MAAM,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,EAAG;AACtD,UAAA,OAAO;AAAA,YACL,MAAA,EAAQ,KAAA;AAAA,YACR,MAAM,GAAA,CAAI,IAAA;AAAA,YACV,KAAA,EAAO,GAAA,CAAI,KAAA,IAAS,IAAA,EAAM,KAAA,IAAS,CAAA;AAAA,YACnC,UAAU,GAAA,CAAI,QAAA;AAAA,YACd,MAAA,EAAQ,IAAI,MAAA,IAAU,CAAA;AAAA,YACtB,UAAA,EAAY,IAAI,UAAA,IAAc;AAAA,WAChC;AAAA,QACF;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,KAAA,CAAM,MAAA,CAAO,MAAA,EAAQ,IAAI,CAAA;AAAA,IAClC;AAAA,GACF;AACF;AAKO,SAAS,oBAAoB,KAAA,EAKnB;AACf,EAAA,IAAI,KAAA,CAAM,YAAA,IAAgB,KAAA,CAAM,UAAA,EAAY;AAC1C,IAAA,OAAO,eAAA,CAAgB;AAAA,MACrB,cAAc,KAAA,CAAM,YAAA;AAAA,MACpB,MAAA,EAAQ,MAAM,MAAA,IAAU,MAAA;AAAA,MACxB,YAAY,KAAA,CAAM,UAAA;AAAA,MAClB,UAAU,KAAA,CAAM;AAAA,KACjB,CAAA;AAAA,EACH;AACA,EAAA,OAAO,iBAAA,CAAkB,MAAM,KAAK,CAAA;AACtC;AAEA,SAAS,eAAA,CAAgB,OAAgB,MAAA,EAA+B;AACtE,EAAA,IAAI,KAAA,CAAM,MAAM,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,MAAM,CAAA,EAAG,OAAO,MAAA;AACrD,EAAA,MAAM,GAAA,GAAM,UAAA,CAAW,KAAA,EAAO,MAAM,CAAA;AACpC,EAAA,IAAI,GAAA,CAAI,IAAA,EAAM,EAAA,EAAI,OAAO,IAAI,IAAA,CAAK,EAAA;AAClC,EAAA,IAAI,GAAA,CAAI,UAAA,CAAW,MAAA,KAAW,CAAA,IAAK,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA,EAAG,EAAA,EAAI,OAAO,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA,CAAE,EAAA;AACnF,EAAA,MAAM,CAAA,GAAI,OAAO,WAAA,EAAY;AAC7B,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,aAAA,EAAe,WAAA,OAAkB,CAAA,IAAK,CAAA,CAAE,IAAA,EAAM,WAAA,OAAkB,CAAC,CAAA;AACxG,EAAA,OAAO,MAAM,EAAA,IAAM,IAAA;AACrB","file":"chunk-5RW5PIBH.js","sourcesContent":["/**\n * Graph query backend for the agent tool layer (Fusion Runtime).\n *\n * Tools prefer a warm ActiveGraph in vgd when a daemon session is attached;\n * otherwise they fall back to the in-process {@link VgGraph}. Failures never\n * surface to the model as transport errors — they degrade to local query.\n */\n\nimport { queryGraph } from '../engine/query.js';\nimport { impactOf } from '../engine/impact.js';\nimport { resolveOne } from '../engine/lookup.js';\nimport { vgdRequest, type VgdClientOptions } from '../runtime/vgd/client.js';\nimport type { VgdRequest } from '../runtime/vgd/protocol.js';\nimport type { VgGraph } from '../schema.js';\n\nexport type GraphBackendSource = 'local' | 'vgd';\n\nexport interface GraphSearchHit {\n id: string;\n qualifiedName: string;\n kind: string;\n file: string;\n line: number;\n score: number;\n why: string;\n signature?: string | null;\n}\n\nexport interface GraphImpactHit {\n id: string;\n name: string;\n kind: string;\n file: string;\n line: number;\n depth: number;\n confidence: number;\n}\n\nexport interface GraphImpactResult {\n root: { id: string; name: string };\n depth: number;\n affected: GraphImpactHit[];\n direct: number;\n transitive: number;\n source: GraphBackendSource;\n}\n\nexport interface GraphSearchResult {\n matches: GraphSearchHit[];\n source: GraphBackendSource;\n}\n\nexport interface GraphBackend {\n readonly source: GraphBackendSource;\n search(query: string, opts?: { limit?: number }): Promise<GraphSearchResult>;\n impact(symbol: string, opts?: { depth?: number }): Promise<GraphImpactResult | null>;\n}\n\n/** Pure in-process backend over a loaded graph. */\nexport function localGraphBackend(graph: VgGraph): GraphBackend {\n return {\n source: 'local',\n async search(query, opts) {\n const res = queryGraph(graph, query, { budget: 1500, limit: opts?.limit ?? 10 });\n return {\n source: 'local',\n matches: res.matches.map((m) => ({\n id: m.node.id,\n qualifiedName: m.node.qualifiedName,\n kind: m.node.kind,\n file: m.node.file,\n line: m.node.span?.start ?? 0,\n score: m.score,\n why: m.why,\n signature: m.node.signature ?? null,\n })),\n };\n },\n async impact(symbol, opts) {\n const node = resolveSymbolId(graph, symbol);\n if (!node) return null;\n const imp = impactOf(graph, node, { depth: opts?.depth ?? 4 });\n return {\n source: 'local',\n root: imp.root,\n depth: imp.depth,\n affected: imp.affected.map((a) => ({\n id: a.id,\n name: a.name,\n kind: a.kind,\n file: a.file,\n line: a.line,\n depth: a.depth,\n confidence: a.confidence,\n })),\n direct: imp.direct,\n transitive: imp.transitive,\n };\n },\n };\n}\n\nexport interface VgdGraphBackendOptions {\n repositoryId: string;\n gitRef?: string;\n socketPath?: string;\n /** Always required for fall back when vgd has no slot or is down. */\n fallback: VgGraph;\n request?: (req: VgdRequest, opts?: VgdClientOptions) => Promise<unknown>;\n}\n\n/**\n * Prefer vgd query/impact; on any failure or empty daemon state, use local.\n * The returned `source` reflects which path answered.\n */\nexport function vgdGraphBackend(options: VgdGraphBackendOptions): GraphBackend {\n const local = localGraphBackend(options.fallback);\n const request = options.request ?? ((req, opts) => vgdRequest(req, opts));\n const clientOpts: VgdClientOptions = { socketPath: options.socketPath };\n\n return {\n source: 'vgd',\n async search(query, opts) {\n try {\n const res = (await request(\n {\n op: 'query-graph',\n repositoryId: options.repositoryId,\n query,\n limit: opts?.limit ?? 10,\n gitRef: options.gitRef,\n },\n clientOpts,\n )) as {\n ok?: boolean;\n matches?: Array<{\n id: string;\n qualifiedName: string;\n kind: string;\n file: string;\n line: number;\n score: number;\n why: string;\n }>;\n };\n if (res?.ok && Array.isArray(res.matches)) {\n return {\n source: 'vgd',\n matches: res.matches.map((m) => ({\n id: m.id,\n qualifiedName: m.qualifiedName,\n kind: m.kind,\n file: m.file,\n line: m.line,\n score: m.score,\n why: m.why,\n signature: null,\n })),\n };\n }\n } catch {\n /* fall through */\n }\n return local.search(query, opts);\n },\n async impact(symbol, opts) {\n try {\n const res = (await request(\n {\n op: 'impact-of',\n repositoryId: options.repositoryId,\n symbol,\n depth: opts?.depth ?? 4,\n gitRef: options.gitRef,\n },\n clientOpts,\n )) as {\n ok?: boolean;\n root?: { id: string; name: string };\n depth?: number;\n affected?: GraphImpactHit[];\n direct?: number;\n transitive?: number;\n };\n if (res?.ok && res.root && Array.isArray(res.affected)) {\n return {\n source: 'vgd',\n root: res.root,\n depth: res.depth ?? opts?.depth ?? 4,\n affected: res.affected,\n direct: res.direct ?? 0,\n transitive: res.transitive ?? 0,\n };\n }\n } catch {\n /* fall through */\n }\n return local.impact(symbol, opts);\n },\n };\n}\n\n/**\n * Pick a backend: vgd when repositoryId + socket are known, else local.\n */\nexport function resolveGraphBackend(input: {\n graph: VgGraph;\n repositoryId?: string | null;\n gitRef?: string | null;\n socketPath?: string | null;\n}): GraphBackend {\n if (input.repositoryId && input.socketPath) {\n return vgdGraphBackend({\n repositoryId: input.repositoryId,\n gitRef: input.gitRef ?? undefined,\n socketPath: input.socketPath,\n fallback: input.graph,\n });\n }\n return localGraphBackend(input.graph);\n}\n\nfunction resolveSymbolId(graph: VgGraph, symbol: string): string | null {\n if (graph.nodes.some((n) => n.id === symbol)) return symbol;\n const hit = resolveOne(graph, symbol);\n if (hit.node?.id) return hit.node.id;\n if (hit.candidates.length === 1 && hit.candidates[0]?.id) return hit.candidates[0].id;\n const q = symbol.toLowerCase();\n const byQn = graph.nodes.find((n) => n.qualifiedName?.toLowerCase() === q || n.name?.toLowerCase() === q);\n return byQn?.id ?? null;\n}\n"]}