fix(qavis-advisory): say why the advisory was skipped instead of failing open silently - #178
Merged
Merged
Conversation
…ing open silently The gate shells `qavis route --staged --gate` and fails OPEN when qavis can't be reached — correct, but it did so with zero output. `defaultRouteVerdict` collapsed every failure into a bare `null`, which `runQavisAdvisory` then handled identically to SILENT, so three states were indistinguishable at the terminal: 'nothing to QA', 'qavis not on PATH', and 'route blew up'. A dead gate looked exactly like a healthy quiet one, and could stay that way for months. ## Change The route result now carries its reason, so there is one printer and no detail is discarded: - `RouteResult` replaces `'ADVISE' | 'SILENT' | null`; the null arm holds `skip`, the human phrase for why the advisory didn't run. - `ENOENT` is discriminated from a qavis that ran and exited non-zero (which throws with `status` set), separating 'never installed' from 'installed but broken'. - The third silent null — exit 0 with unparseable stdout — is now reported as `qavis route printed no verdict (…)` rather than read as SILENT. - On a skip it prints one stderr line plus the existing mute, and still returns 0 on a plain commit and under a strict ship alike. An advisor's own failure must never cost an exit code, so the line is the only signal there is. ``` qavis-advisory: skipped — qavis not on PATH. (.qavis/recipe.json is present, so this repo expects it; mute with GUARD_NO_QAVIS_ADVISORY=1.) ``` A repo WITHOUT `.qavis/recipe.json` stays entirely silent — the zero-weight path for non-qavis consumers is unchanged and test-asserted. ## Contract Untouched: 0 = continue, 3 = ADVISE under a strict ship. No exit 1, no fail-closed. `cli.mts` and the husky fragments needed no change. The new stderr text collides with none of the grep attributions in `commit-with-gate-capture.sh`. ## Verification Typecheck + lint clean; suite green. Exercised end-to-end against this repo: healthy (silent, rc=0); qavis hidden from PATH (warns, rc=0); hidden + GUARD_AI_STRICT=1 (still rc=0); muted (nothing at all); a stub qavis exiting 7 (`qavis route failed: …`, rc=0). ## Follow-up A `devkit doctor` advisory line reporting recipe + binary presence is deliberately NOT in this PR: `cli/commands/doctor.mts` is grandfathered at exactly 894 lines in `eslint/baselines/size-lines.json` (shrink-only, zero headroom), so it needs an extraction to pay for itself. That lands separately.
Contributor
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughChangesQavis advisory routing and diagnostics
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant runQavisAdvisory
participant defaultRoute
participant qavis
participant stderr
runQavisAdvisory->>defaultRoute: request advisory verdict
defaultRoute->>qavis: execute qavis route
qavis-->>defaultRoute: verdict or skip condition
defaultRoute-->>runQavisAdvisory: RouteResult
runQavisAdvisory->>stderr: report skip reason when verdict is null
runQavisAdvisory-->>runQavisAdvisory: return exit code 0 for fail-open skip
Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Jul 22, 2026
norvalbv
added a commit
that referenced
this pull request
Jul 22, 2026
fix(qavis-advisory): say why the advisory was skipped instead of failing open silently
norvalbv
added a commit
that referenced
this pull request
Jul 22, 2026
Two pieces of housekeeping found while cleaning the working tree after PR #178. ## 1. The committed skill projections disagree with their own source `skills/using-devkit/SKILL.md` (the source) and `.devkit/skills-manifest.json` both hash to `d678d168…`, but the committed `.claude/` and `.cursor/` projections are still `65a1e814…` — an earlier revision. A source change landed without its re-synced projections, so `devkit doctor` on a fresh clone reports skills DRIFT against the manifest that ships beside them. These are the `devkit sync-skills` output, restoring the invariant that source, manifest, and both projections agree. The content they were missing is the ship argument-ordering guidance: the positional `<branch> "<title>"`-before-flags rule, the linked-worktree row, and the `--pr` row for a branch that already exists on origin. No source change here — only the projections move, and they move to what the manifest already claims. ## 2. Track `search-code.config.json` It was sitting untracked and unignored, so it surfaced as permanent noise in every `git status`. It is deliberate, documented configuration rather than machine state: it pins the dup-gate describer to `mode: remote` (through the Claude Code login — zero API spend and no local GPU dependency, after local describes were measured at ~78s/chunk and blocked a ship for 40+ minutes), and mirrors `guard.config.json`'s `scanRoots` in `indexing.sourceRoots` so the matcher scans the authored surface only. Embeddings stay on-device, so the index and its provenance stamp are unchanged. Committing it makes that the shared default instead of one machine's local tuning.
norvalbv
added a commit
that referenced
this pull request
Jul 22, 2026
…tchet by extracting the asset checks Follow-up to #178. That PR made the qavis-advisory gate name its own failure at commit time; this one makes the same signal visible from `devkit doctor`, so a dead gate is discoverable without waiting for a commit. ## The advisory line `printQavisAdvisoryHealth` prints one line in all three doctor modes (package, overlay, self-host) when `qavis-advisory` is a selected guard: ``` ✓ qavis-advisory: qavis on PATH (.qavis/recipe.json present) · qavis-advisory: .qavis/recipe.json present but qavis is NOT on PATH — the QA advisory is skipped on every commit (install qavis, or drop the guard) · qavis-advisory: no .qavis/recipe.json — gate inert (nothing to QA) ``` Deliberately **advisory**: never a `CheckResult`, never in `results`, never a `--fix` target, so it cannot move doctor's exit code. A repo that keeps the guard selected but does not install qavis made a choice, not drift. Resolved against the **git root** — that is the cwd the husky fragment shells the gate from, so doctor reports what the hook would actually see rather than what this cwd sees. The probe (`qavisOnPath`) is a plain PATH scan. It never calls `qavis route`, so doctor stays free of model spend. ## Paying the size ratchet `cli/commands/doctor.mts` is grandfathered at **894 lines** in `eslint/baselines/size-lines.json` — shrink-only, and it was sitting at 893. There was no room for the feature, so this PR pays for it first: - `cli/lib/doctor/asset-checks.mts` — the four synced-asset drift checks (`checkSkills`, `checkAgents`, `checkAgentHookScripts`, `checkRegistrations`). They share one contract (a `.devkit/*-manifest.json` sha256 per file; drift is any disagreement between record, bundled source, and consumer copy) and are **leaf** checks that call nothing else in doctor — so moving them leaves the command's orchestration, which its own comments deliberately keep together, untouched. - `cli/lib/doctor/check-result.mts` — the `CheckResult` primitive. `doctor.mts` goes **893 → 771**, comfortably back under the cap with room for the feature. A bonus falls out: `checkCommitMsgHook` previously restated `CheckResult` structurally because importing it from `doctor.mts` would have been a cycle. With the type in its own module it just imports it — one real structural duplicate removed. `cli/lib` sits at exactly the 12-file fan-out cap, so this lands as a **registered subfolder** rather than flat files: `doctor` is added to `structure.trees[cli].libDomains.lib` in `guard.config.json`. ## Verification `vitest run` 1954 passed / 0 failed, `typecheck`, `lint`, `lint:structure` all clean. `bun cli/index.mts doctor` on this repo (self-host, qavis installed) prints the ✓ line. New test in `init-doctor.test.mts` covers both the inert arm and the NOT-on-PATH arm — the latter with a scrubbed PATH — and asserts the exit code is unmoved by either. `qavisOnPath` is unit-tested against a synthetic PATH.
norvalbv
added a commit
that referenced
this pull request
Jul 22, 2026
Minor bump: recent changes to the review process, plus the ship-correctness fixes merged since v0.37.2: - #163 — reject flags in the <branch>/<title> positional slots; base the review judges on the ship's resolved base rather than origin/HEAD. - #167 — link gate deps (.husky/_, node_modules, coverage) from the main worktree when the ship is run from a linked worktree, instead of failing closed. - #178/#179/#180 — qavis-advisory loud-fail-open fix, using-devkit projection re-sync, doctor qavis-advisory liveness reporting. package.json only, matching prior `chore: bump version to X` commits; devkitRef and dist/ are handled by the release step.
norvalbv
added a commit
that referenced
this pull request
Jul 24, 2026
fix(qavis-advisory): say why the advisory was skipped instead of failing open silently
norvalbv
added a commit
that referenced
this pull request
Jul 24, 2026
Two pieces of housekeeping found while cleaning the working tree after PR #178. ## 1. The committed skill projections disagree with their own source `skills/using-devkit/SKILL.md` (the source) and `.devkit/skills-manifest.json` both hash to `d678d168…`, but the committed `.claude/` and `.cursor/` projections are still `65a1e814…` — an earlier revision. A source change landed without its re-synced projections, so `devkit doctor` on a fresh clone reports skills DRIFT against the manifest that ships beside them. These are the `devkit sync-skills` output, restoring the invariant that source, manifest, and both projections agree. The content they were missing is the ship argument-ordering guidance: the positional `<branch> "<title>"`-before-flags rule, the linked-worktree row, and the `--pr` row for a branch that already exists on origin. No source change here — only the projections move, and they move to what the manifest already claims. ## 2. Track `search-code.config.json` It was sitting untracked and unignored, so it surfaced as permanent noise in every `git status`. It is deliberate, documented configuration rather than machine state: it pins the dup-gate describer to `mode: remote` (through the Claude Code login — zero API spend and no local GPU dependency, after local describes were measured at ~78s/chunk and blocked a ship for 40+ minutes), and mirrors `guard.config.json`'s `scanRoots` in `indexing.sourceRoots` so the matcher scans the authored surface only. Embeddings stay on-device, so the index and its provenance stamp are unchanged. Committing it makes that the shared default instead of one machine's local tuning.
norvalbv
added a commit
that referenced
this pull request
Jul 24, 2026
…tchet by extracting the asset checks Follow-up to #178. That PR made the qavis-advisory gate name its own failure at commit time; this one makes the same signal visible from `devkit doctor`, so a dead gate is discoverable without waiting for a commit. ## The advisory line `printQavisAdvisoryHealth` prints one line in all three doctor modes (package, overlay, self-host) when `qavis-advisory` is a selected guard: ``` ✓ qavis-advisory: qavis on PATH (.qavis/recipe.json present) · qavis-advisory: .qavis/recipe.json present but qavis is NOT on PATH — the QA advisory is skipped on every commit (install qavis, or drop the guard) · qavis-advisory: no .qavis/recipe.json — gate inert (nothing to QA) ``` Deliberately **advisory**: never a `CheckResult`, never in `results`, never a `--fix` target, so it cannot move doctor's exit code. A repo that keeps the guard selected but does not install qavis made a choice, not drift. Resolved against the **git root** — that is the cwd the husky fragment shells the gate from, so doctor reports what the hook would actually see rather than what this cwd sees. The probe (`qavisOnPath`) is a plain PATH scan. It never calls `qavis route`, so doctor stays free of model spend. ## Paying the size ratchet `cli/commands/doctor.mts` is grandfathered at **894 lines** in `eslint/baselines/size-lines.json` — shrink-only, and it was sitting at 893. There was no room for the feature, so this PR pays for it first: - `cli/lib/doctor/asset-checks.mts` — the four synced-asset drift checks (`checkSkills`, `checkAgents`, `checkAgentHookScripts`, `checkRegistrations`). They share one contract (a `.devkit/*-manifest.json` sha256 per file; drift is any disagreement between record, bundled source, and consumer copy) and are **leaf** checks that call nothing else in doctor — so moving them leaves the command's orchestration, which its own comments deliberately keep together, untouched. - `cli/lib/doctor/check-result.mts` — the `CheckResult` primitive. `doctor.mts` goes **893 → 771**, comfortably back under the cap with room for the feature. A bonus falls out: `checkCommitMsgHook` previously restated `CheckResult` structurally because importing it from `doctor.mts` would have been a cycle. With the type in its own module it just imports it — one real structural duplicate removed. `cli/lib` sits at exactly the 12-file fan-out cap, so this lands as a **registered subfolder** rather than flat files: `doctor` is added to `structure.trees[cli].libDomains.lib` in `guard.config.json`. ## Verification `vitest run` 1954 passed / 0 failed, `typecheck`, `lint`, `lint:structure` all clean. `bun cli/index.mts doctor` on this repo (self-host, qavis installed) prints the ✓ line. New test in `init-doctor.test.mts` covers both the inert arm and the NOT-on-PATH arm — the latter with a scrubbed PATH — and asserts the exit code is unmoved by either. `qavisOnPath` is unit-tested against a synthetic PATH.
norvalbv
added a commit
that referenced
this pull request
Jul 24, 2026
Minor bump: recent changes to the review process, plus the ship-correctness fixes merged since v0.37.2: - #163 — reject flags in the <branch>/<title> positional slots; base the review judges on the ship's resolved base rather than origin/HEAD. - #167 — link gate deps (.husky/_, node_modules, coverage) from the main worktree when the ship is run from a linked worktree, instead of failing closed. - #178/#179/#180 — qavis-advisory loud-fail-open fix, using-devkit projection re-sync, doctor qavis-advisory liveness reporting. package.json only, matching prior `chore: bump version to X` commits; devkitRef and dist/ are handled by the release step.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gate shells
qavis route --staged --gateand fails OPEN when qavis can't be reached — correct, but it did so with zero output.defaultRouteVerdictcollapsed every failure into a barenull, whichrunQavisAdvisorythen handled identically to SILENT, so three states were indistinguishable at the terminal: 'nothing to QA', 'qavis not on PATH', and 'route blew up'. A dead gate looked exactly like a healthy quiet one, and could stay that way for months.Change
The route result now carries its reason, so there is one printer and no detail is discarded:
RouteResultreplaces'ADVISE' | 'SILENT' | null; the null arm holdsskip, the human phrase for why the advisory didn't run.ENOENTis discriminated from a qavis that ran and exited non-zero (which throws withstatusset), separating 'never installed' from 'installed but broken'.qavis route printed no verdict (…)rather than read as SILENT.A repo WITHOUT
.qavis/recipe.jsonstays entirely silent — the zero-weight path for non-qavis consumers is unchanged and test-asserted.Contract
Untouched: 0 = continue, 3 = ADVISE under a strict ship. No exit 1, no fail-closed.
cli.mtsand the husky fragments needed no change. The new stderr text collides with none of the grep attributions incommit-with-gate-capture.sh.Verification
Typecheck + lint clean; suite green. Exercised end-to-end against this repo: healthy (silent, rc=0); qavis hidden from PATH (warns, rc=0); hidden + GUARD_AI_STRICT=1 (still rc=0); muted (nothing at all); a stub qavis exiting 7 (
qavis route failed: …, rc=0).Follow-up
A
devkit doctoradvisory line reporting recipe + binary presence is deliberately NOT in this PR:cli/commands/doctor.mtsis grandfathered at exactly 894 lines ineslint/baselines/size-lines.json(shrink-only, zero headroom), so it needs an extraction to pay for itself. That lands separately.Summary by CodeRabbit
Bug Fixes
SILENTandADVISEoutcomes remain quiet and preserve their existing exit behavior.Documentation