Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 67 additions & 17 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,8 @@ honestly and never blocks a merge.
taint flow, a known-vulnerable dependency — carry `protected_finding: true`.
While one is unresolved, policy cannot emit `pass`: not via an approved
exception, not via low confidence, not via the quick path, and not via anything
the model says. Turn a rule off in `.vibgrate/review.toml` if it does not apply
to your repository; that is the only way to stop it gating.
the model says. Turn a rule off in the review policy (`review.protected`) if it
does not apply to your repository; that is the only way to stop it gating.

#### What it looks for

Expand All @@ -548,7 +548,7 @@ convention". Where peers are too evenly split to have one, Review says *no
convention* instead of naming a plurality winner.

**A majority is never treated as correct.** Peers establish what is *normal*;
only `target_pattern` establishes what is *right*. A file that goes through the
only the declared `targetPattern` establishes what is *right*. A file that goes through the
service layer while all its peers bypass it is the first one to improve — Review
will not flag it. That is the difference between this and a consistency scanner,
which by construction scores your best file worst.
Expand Down Expand Up @@ -653,25 +653,41 @@ to perpetuate the legacy it is migrating away from. `--inject-context` keeps the
same content in a marked block inside `CLAUDE.md`, leaving everything a human
wrote in that file untouched.

#### Configuration — `.vibgrate/review.toml`
#### Configuration — the `review` block

```toml
[review]
enforcement = "advisory" # advisory | enforced
fail_on = "fail" # fail | needs_review
target_pattern = "layered" # the architecture you say you want
The review policy is the `review` block of the project config
(`.vibgrate/config.yml` or `vibgrate.config.json`, which take the same settings):

[review.protected]
unguarded_entrypoint = true
known_vulnerable_dependency = true
validated_taint = true
```yaml
# .vibgrate/config.yml
review:
enforcement: advisory # advisory | enforced
failOn: fail # none | fail | needs_review
targetPattern: layered # the architecture you say you want
protected:
unguardedEntrypoint: true
knownVulnerableDependency: true
validatedTaint: true
```

Read from the **trusted base branch** when `--base` is given, so a pull request
cannot weaken the policy applied to itself.
cannot weaken the policy applied to itself. It is read as data: a `review` block
in a `.ts`/`.js` config is never run, so keep it in YAML or JSON.

The first `vg review` in a repository with no policy writes an advisory starter
policy: a new `.vibgrate/config.yml` when there is no config, or a `review` block
added to an existing `.vibgrate/config.yml` / `vibgrate.config.json`. A `.ts` or
`.js` config is never rewritten, so those repositories get `.vibgrate/review.toml`.

`.vibgrate/review.toml` keeps working wherever the config has no `review` block.
Its keys are the snake_case forms of the ones above (`fail_on`, `target_pattern`,
`[review.protected]`). `vg doctor` says when a `review` block makes it redundant.

Team markdown packs live under `.vibgrate/review/` — the same tree the GitHub
App reads. `ignore.md` drops matching finding paths; `policy.md` is attached to
App reads. With `--base` they are read from the base branch, like the `review`
policy, so a change cannot add an `ignore.md` glob over the files it breaks or
delete a check to clear its own review; its edits apply after it merges.
Without `--base`, the files on disk are used. `ignore.md` drops matching finding paths; `policy.md` is attached to
the human report; `merge.md` is evaluated locally (docs-only may approve; a
change to `merge.md` itself is refused); `checks/*.md` each produce one CLI
pass or a skipped-with-reason line. Custom checks have no extra correctness
Expand All @@ -681,7 +697,7 @@ engine on the CLI either.
(from a known current → latest pair). It does not rewrite lockfiles, does not
open a hosted branch, and never starts unless you pass the flag.

Declaring `target_pattern` is what turns a layering observation into a
Declaring `targetPattern` is what turns a layering observation into a
*regression*. Without it, a dependency that skips a tier is reported as a medium
finding about the repository's own majority — because a majority is not the same
thing as a decision, and Review will not treat it as one.
Expand Down Expand Up @@ -2530,7 +2546,41 @@ const config: VibgrateConfig = {
export default config;
```

Also supports `vibgrate.config.js` and `vibgrate.config.json`.
Also supports `vibgrate.config.js`, `vibgrate.config.json`, and
`.vibgrate/config.yml` (or `.config.yaml`). A project has one config file:
Vibgrate reads the first it finds in the order `.vibgrate/config.yml`,
`.vibgrate/config.yaml`, `vibgrate.config.ts`, `vibgrate.config.js`,
`vibgrate.config.json`, and never merges two. YAML and JSON take exactly the
same settings; `vg doctor` names any config file that is present but ignored.

### Drift budget

`driftBudget` sets limits on DriftScore that `vg scan` and the Vibgrate GitHub
App check enforce:

```yaml
# .vibgrate/config.yml
driftBudget:
mode: warn # warn (default) | enforce | shadow
maxScore: 40 # DriftScore ceiling, 0-100
maxWorseningPercent: 5 # how much one change may worsen drift
agents:
maxWorseningPercent: 0 # stricter limit for bot and coding-agent pull requests
```

- `warn` prints a breach and exits `0`; `enforce` exits `2`; `shadow` reports only.
- `maxWorseningPercent` compares against `--baseline`; without one it is reported
as not evaluated, never as a failure.
- `agents.maxWorseningPercent` is checked by the GitHub App, which knows who
opened the pull request.
- `--drift-budget` / `--drift-worsening` still work; passing either uses the flags
and ignores `driftBudget`.
- Misspelt keys are reported, never silently ignored.

The GitHub App reads `driftBudget` and `review` from the pull request's base
branch, so a change cannot loosen the limits it is checked against. It reads
`.vibgrate/config.yml` or `vibgrate.config.json` only; it never runs a `.ts`/`.js`
config.

### Thresholds

Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ inputs:
image-tag:
description: 'Scanner image tag to run (defaults to a pinned, tested release).'
required: false
default: '2026.921.1' # vibgrate:cli-version — stamped by scripts/stamp-release-pins.mjs
default: '2026.930.1' # vibgrate:cli-version — stamped by scripts/stamp-release-pins.mjs
verify:
description: 'Verify the image cosign signature + provenance before running (requires cosign on the runner).'
required: false
Expand Down
2 changes: 1 addition & 1 deletion charts/vibgrate/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type: application
# stamped to the released @vibgrate/cli calendar version by
# scripts/stamp-release-pins.mjs (via the marker on the appVersion line below).
version: 0.1.2
appVersion: "2026.921.1" # vibgrate:cli-version — stamped by scripts/stamp-release-pins.mjs
appVersion: "2026.930.1" # vibgrate:cli-version — stamped by scripts/stamp-release-pins.mjs
home: https://vibgrate.com
icon: https://vibgrate.com/web-app-manifest-512x512.png
sources:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vibgrate/cli",
"version": "2026.921.1",
"version": "2026.930.1",
"description": "vg — local codebase intelligence CLI + MCP server for AI coding agents: deterministic code graph, drift reporting, and version-correct library docs (Apache-2.0)",
"//mcpName": "Official MCP registry ownership proof: the registry fetches the published npm package and requires this field to match the com.vibgrate/ai-context server entry (see docs/marketing/mcp-registry/README.md). Must ship in the published @vibgrate/cli package.json.",
"mcpName": "com.vibgrate/ai-context",
Expand Down
4 changes: 2 additions & 2 deletions packaging/homebrew-tap/Formula/vg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
class Vg < Formula
desc "Deterministic, no-API-key code graph for AI assistants (vg)"
homepage "https://vibgrate.com"
url "https://registry.npmjs.org/@vibgrate/cli/-/cli-2026.921.1.tgz"
sha256 "a7239fa8e0ff6cd0a2f64af43b47bfee02ffe6bdcce571f715c617ab7a054c4e"
url "https://registry.npmjs.org/@vibgrate/cli/-/cli-2026.914.1.tgz"
sha256 "21c164080d1ba33dc53d604a8754ffa0079daa9c8b771a9053c224a2c43877bf"
license "Apache-2.0"
depends_on "node"

Expand Down
2 changes: 1 addition & 1 deletion packaging/scoop-bucket/vg.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2026.921.1",
"version": "2026.914.1",
"description": "Deterministic, no-API-key code graph for AI assistants (vg)",
"homepage": "https://vibgrate.com",
"license": "Apache-2.0",
Expand Down
57 changes: 57 additions & 0 deletions releases/v2026.930.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Vibgrate CLI 2026.930.1

_Released 2026-09-30_

This release of the Vibgrate CLI introduces support for YAML configuration files and enhances security in the review process. Users can now manage settings more flexibly and benefit from improved enforcement of review policies.

## What changed

### New

- Vibgrate now supports reading settings from .vibgrate/config.yml alongside vibgrate.config.json.
- The new YAML configuration can include a driftBudget and your review policy.

### Improved

- Local reviews without --base continue to use the files on disk.

### Changed

- The first vg review writes its starter policy into the YAML config instead of a separate review.toml file.
- A base branch without a review policy defaults to standard settings rather than those added by the pull request.

### Fixed

- Existing review.toml files remain functional, and vg doctor will notify when a config file is ignored.

### Security

- vg review --base now reads team review files from the base branch, preventing pull requests from hiding findings.

## Benchmarks

Two-arm benchmark of this release against 2026.921.1, interleaved on one runner against the pinned corpus (236 metrics compared).

| Metric | Previous | This release |
| --- | --- | --- |
| Languages with extraction | 19 count | 19 count |
| Definitions extracted (corpus total) | 25880 count | 25880 count |
| Call edges extracted (corpus total) | 17611 count | 17611 count |
| Locate accuracy (top-1) | 0.94 ratio | 0.94 ratio |
| Dependency detection (authored manifest truth) | 0.96 ratio | 0.96 ratio |
| CLI startup (--version, median) | 450.70 ms | 454.20 ms |

2 regression(s) — published, not omitted:
- Tasks passed on both arms: 36 → 34 (-5.6%)
- Comparable-task rate (both arms passed / total): 0.95 → 0.89 (-5.6%)

Full report and methodology: https://vibgrate.com/cli/benchmarks

## Install or update

```sh
npm install -g @vibgrate/cli
vg
```

Full changelog: https://vibgrate.com/changelog/cli/2026.930.1
2 changes: 1 addition & 1 deletion src/code/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,7 @@
else if (n >= AGENT_NO_PROGRESS_NUDGE_AT) content += `\n\n(note: you have called this exact tool call ${n} times with no change — try a different approach, read more context, or call finish.)`;
}

if (MUTATION_TOOLS.has(call.name)) mutationToolCalls++;

Check warning on line 1330 in src/code/agent.ts

View workflow job for this annotation

GitHub Actions / Node 24

'mutationToolCalls' is assigned a value but never used. Allowed unused vars must match /^_/u
if (MUTATION_TOOLS.has(call.name) && !toolResult.finished) {
failedMutationPending = !toolResult.mutated;
}
Expand Down Expand Up @@ -1397,7 +1397,7 @@
return finish(
'max-steps',
`Stopped at the step limit (${maxSteps} steps) before the task was finished. ` +
'Re-run with `--max-steps <n>`, or set `maxSteps` in vibgrate.config.json, to give it more room.',
'Re-run with `--max-steps <n>`, or set `maxSteps` in .vibgrate/code.json, to give it more room.',
maxSteps,
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/code/long-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ describe('long-session gold — max-steps names how to raise the cap', () => {
expect(result.finalText).toMatch(/step limit \(2 steps\)/);
expect(result.finalText).toMatch(/--max-steps/);
expect(result.finalText).toMatch(/maxSteps/);
expect(result.finalText).toMatch(/vibgrate\.config\.json/);
// maxSteps lives in .vibgrate/code.json (loadCodeConfig), not the project config.
expect(result.finalText).toMatch(/\.vibgrate\/code\.json/);
});
});

Expand Down
4 changes: 2 additions & 2 deletions src/commands/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function registerCode(program: Command): void {
.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('--max-steps <n>', 'cap the number of agent steps (default 24; also settable as maxSteps in .vibgrate/code.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)')
Expand Down Expand Up @@ -388,7 +388,7 @@ export function registerCode(program: Command): void {
const auto = opts.auto ?? config.auto;
// No commander default here on purpose: a hard-coded default would always
// populate opts.maxSteps and silently shadow `maxSteps` in
// vibgrate.config.json, so a raised project cap never took effect.
// .vibgrate/code.json, so a raised project cap never took effect.
// Flag → config → the engine's own DEFAULT_MAX_STEPS (undefined).
const parsedMaxSteps = opts.maxSteps === undefined ? NaN : Number(opts.maxSteps);
const maxSteps =
Expand Down
73 changes: 73 additions & 0 deletions src/commands/config-readers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { afterEach, describe, expect, it } from 'vitest';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { readConfigExcludes } from '../engine/discover.js';
import { areaSkillsEnabled } from '../install/area-skills.js';
import { configNotes } from './doctor.js';

const roots: string[] = [];
function project(files: Record<string, string>): string {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'vg-config-readers-'));
roots.push(root);
for (const [rel, text] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), text);
}
return root;
}
afterEach(() => {
for (const r of roots.splice(0)) fs.rmSync(r, { recursive: true, force: true });
});

describe('settings read outside a scan follow the one config file', () => {
it('reads exclude and areaSkills from .vibgrate/config.yml', () => {
const root = project({ '.vibgrate/config.yml': 'areaSkills: true\nexclude:\n - legacy/**\n' });
expect(readConfigExcludes(root)).toEqual(['legacy/**']);
expect(areaSkillsEnabled(root)).toBe(true);
});

it('ignores vibgrate.config.json when a YAML config exists', () => {
const root = project({
'.vibgrate/config.yml': 'exclude: []\n',
'vibgrate.config.json': '{"areaSkills":true,"exclude":["from-json/**"]}',
});
expect(readConfigExcludes(root)).toEqual([]);
expect(areaSkillsEnabled(root)).toBe(false);
});

it('still reads vibgrate.config.json on its own', () => {
const root = project({ 'vibgrate.config.json': '{"areaSkills":true,"exclude":["legacy/**"]}' });
expect(readConfigExcludes(root)).toEqual(['legacy/**']);
expect(areaSkillsEnabled(root)).toBe(true);
});
});

describe('vg doctor config notes', () => {
it('is quiet for a single config', () => {
expect(configNotes(project({ '.vibgrate/config.yml': 'exclude: []\n' }))).toEqual([]);
expect(configNotes(project({}))).toEqual([]);
});

it('names a shadowed config file', () => {
const root = project({ '.vibgrate/config.yml': 'exclude: []\n', 'vibgrate.config.json': '{}' });
expect(configNotes(root)).toEqual(['vibgrate.config.json is ignored: .vibgrate/config.yml is the config in use']);
});

it('names legacy review files a review block replaces', () => {
const root = project({
'vibgrate.config.json': '{"review":{"enforcement":"advisory"}}',
'.vibgrate/review.toml': '[review]\n',
'.vibgrate/review/settings.md': 'mode: precise\n',
});
expect(configNotes(root)).toEqual([
'.vibgrate/review.toml is ignored: review settings come from the review block in vibgrate.config.json',
'.vibgrate/review/settings.md is ignored: review settings come from the review block in vibgrate.config.json',
]);
});

it('surfaces a config that does not parse', () => {
const root = project({ '.vibgrate/config.yml': 'exclude: [unclosed\n' });
expect(configNotes(root)[0]).toMatch(/\.vibgrate\/config\.yml is not valid YAML/);
});
});
Loading
Loading