fix(docs): widen the version-claim gate and correct AGENTS.md's coverage floor (#15966)

Merged via /merge-batch (2026-10-19). Maintainer merge of the current base on top: the README compatibility table had already been rebuilt on the base (kept), the v3.8.20 → v3.8.50 highlight was already fixed, and the single-bold-version rule now skips markdown table rows (the backticked `vX+` cell still carries the current-version claim) so the correct `**v3.8.51**` last-shipped column is not flagged; new test covers both directions. Evidence: PR test 16/16, check-docs-counts-sync, check:docs-all, typecheck core + open-sse, test-masking, 715 related docs/i18n/README/AGENTS tests green on the merged head. Remaining reds (lint unused-suppression entries, i18n drift) reproduce on the bare tip. Thanks @jonlwheat2-gif!
This commit is contained in:
WebPerson 2026-10-18 21:13:48 -05:00 • committed by GitHub
parent d13f37687e
commit 7d7ed3f3af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 466 additions and 83 deletions

View file

@ -16,7 +16,7 @@ npm run build:release # Release build
npm run lint # ESLint (0 errors expected; warnings are pre-existing)
npm run typecheck:core # TypeScript check (should be clean)
npm run typecheck:noimplicit:core # Strict check (no implicit any)
npm run test:coverage # Unit tests + coverage gate (60/60/60/60 — statements/lines/functions/branches)
npm run test:coverage # Unit tests + coverage gate (67.33 / 67.33 / 72.02 / 65.08 — statements/lines/functions/branches, enforced by the blocking ratchet in config/quality/quality-baseline.json; test:coverage itself hardcodes 60)
npm run check # lint + test combined
npm run check:cycles # Detect circular dependencies (advisory — lists the SCCs)
npm run check:cycles:ratchet # Same scan, blocking above the quality-baseline ceiling
@ -464,16 +464,16 @@ For any non-trivial change, read the matching deep-dive first:
## Testing
| What | Command |
| ----------------------- | ----------------------------------------------------------------------------- |
| Unit tests | `npm run test:unit` |
| Single file | `node --import tsx/esm --test tests/unit/your-file.test.ts` |
| Vitest (MCP, autoCombo) | `npm run test:vitest` |
| E2E (Playwright) | `npm run test:e2e` |
| Protocol E2E (MCP+A2A) | `npm run test:protocols:e2e` (CI job `test-protocols-e2e`, advisory — #10049) |
| Ecosystem | `npm run test:ecosystem` (CI job `test-ecosystem`, blocking) |
| Coverage gate | `npm run test:coverage` (60/60/60/60 — statements/lines/functions/branches) |
| Coverage report | `npm run coverage:report` |
| What | Command |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Unit tests | `npm run test:unit` |
| Single file | `node --import tsx/esm --test tests/unit/your-file.test.ts` |
| Vitest (MCP, autoCombo) | `npm run test:vitest` |
| E2E (Playwright) | `npm run test:e2e` |
| Protocol E2E (MCP+A2A) | `npm run test:protocols:e2e` (CI job `test-protocols-e2e`, advisory — #10049) |
| Ecosystem | `npm run test:ecosystem` (CI job `test-ecosystem`, blocking) |
| Coverage gate | `npm run test:coverage` (67.33 / 67.33 / 72.02 / 65.08 — statements/lines/functions/branches, enforced by the blocking ratchet in config/quality/quality-baseline.json; test:coverage itself hardcodes 60) |
| Coverage report | `npm run coverage:report` |
**PR rule**: If you change production code in `src/`, `open-sse/`, `electron/`, or `bin/`, you must include or update tests in the same PR.

View file

@ -1028,7 +1028,7 @@ omniroute
```
> 💡 See `npm warn ERESOLVE` or peer-dep warnings? [They're harmless](docs/guides/TROUBLESHOOTING.md#npm-install-warnings-eresolve--peer--deprecated).
> **Using npm 11 or later?** npm may block package lifecycle scripts unless they are allowed. OmniRoute's `postinstall` (`node scripts/build/postinstall.mjs`) is required to prepare its native runtime files. Allow the packages named in npm's warning when installing globally. For the package set reported by OmniRoute 3.8.51:
> **Using npm 11 or later?** npm may block package lifecycle scripts unless they are allowed. OmniRoute's `postinstall` (`node scripts/build/postinstall.mjs`) is required to prepare its native runtime files. Allow the packages named in npm's warning when installing globally. For the package set reported by OmniRoute 3.8.52:
>
> ```bash
> npm install -g --allow-scripts=omniroute,keytar,onnxruntime-node,@parcel/watcher,@swc/core,protobufjs,esbuild omniroute

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -278,7 +278,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -274,7 +274,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
└── .env.example # Environment variable template
```
## Key Features (v3.8.50)
## Key Features (v3.8.52)
### Core Proxy
- **375 AI providers** with automatic format translation

View file

@ -384,16 +384,128 @@ export function readPackageVersion() {
}
}
/**
* The BLOCKING coverage floors from config/quality/quality-baseline.json.
*
* Read from the baseline rather than hardcoded so G-08's validator and the gate
* that actually blocks can never drift apart: a rebaseline moves both at once.
*
* @returns {{statements:number,lines:number,functions:number,branches:number}}
*/
export function readCoverageRatchetFloors() {
const fallback = { statements: 60, lines: 60, functions: 60, branches: 60 };
try {
const baseline = JSON.parse(
fs.readFileSync(path.join(ROOT, "config/quality/quality-baseline.json"), "utf8")
);
const metrics = baseline?.metrics ?? {};
const pick = (key) => metrics[key]?.value;
return {
statements: pick("coverage.statements") ?? fallback.statements,
lines: pick("coverage.lines") ?? fallback.lines,
functions: pick("coverage.functions") ?? fallback.functions,
branches: pick("coverage.branches") ?? fallback.branches,
};
} catch {
return fallback;
}
}
function isTableRowAt(content, index) {
const lineStart = content.lastIndexOf("\n", index - 1) + 1;
return content.slice(lineStart, index).trimStart().startsWith("|");
}
export function makeVersionClaimValidator(expected) {
// G-06 (#15159): the original list matched only TWO literal phrasings, so the
// gate saw at most one claim per file and four stale strings shipped through
// it — README.md "reported by OmniRoute 3.8.51", the compatibility table's
// "**v3.8.50**", "Recent highlights from v3.8.20 → v3.8.50", and llm.txt's
// "## Key Features (v3.8.50)".
//
// The load-bearing lesson: fixing those four STRINGS is not enough. Any new
// phrasing nobody anticipated slips through again.
//
// The subtlety that makes widening safe: a version NUMBER alone is not a
// defect. `v3.8.49` in a compatibility table, `v3.9.0 LTS` in a roadmap, and
// the `v3.8.20` end of a "from X → Y" range are all CORRECT. Only a claim
// that asserts which version is CURRENT is checkable. So each pattern below
// is anchored to a context that marks the version as the current one, and
// captures exactly the asserting version:
//
// 1. the product name immediately before it "OmniRoute 3.8.51"
// 2. an explicit "Current version:" label
// 3. a bold range — emphasis picks out the target, so capture the SECOND
// endpoint ("**v3.8.20 → v3.8.50**" asserts the right one is current)
// 4. a single bold version — emphasis marks the notable/current one
// (outside markdown table rows; see isTableRowAt below)
// 5. a version inside a heading's parentheses ("## Key Features (v3.8.50)")
// 6. an inline range's target ("from v3.8.20 → v3.8.50")
// 7. a backticked floor ("`v3.8.51+`") — a floor below current understates
// what users on that version actually get
//
// A naive `/\d+\.\d+\.\d+/` would redden on Node (22.22.2), TypeScript and
// every dependency version; that was the failure mode avoided here.
const SEMVER = String.raw`\d+\.\d+\.\d+`;
// Written as ordinary escaped strings, NOT String.raw templates: a template
// literal ending in `\*\*` immediately before the closing backtick makes the
// `**` ambiguous to the reader and easy to corrupt (it once produced `s***`
// and a "Nothing to repeat" SyntaxError at import time).
const BOLD = "\\*\\*";
const B = "(?:\\u2192|->|\\u2014|\\u2013)";
const PATTERNS = [
/OmniRoute v(\d+\.\d+\.\d+)/g,
/Current version:\*{0,2}\s*\*{0,2}(\d+\.\d+\.\d+)/g,
// 1. product-named
new RegExp(`OmniRoute\\s+v?(${SEMVER})`, "g"),
// 2. explicit label
new RegExp(`[Cc]urrent\\s+version:?\\s*\\**\\s*v?(${SEMVER})`, "g"),
// 3. bold range — capture the TARGET (last endpoint)
new RegExp(`${BOLD}\\s*v?${SEMVER}\\s*${B}\\s*v?(${SEMVER})\\s*${BOLD}`, "g"),
// 4. single bold version
new RegExp(`${BOLD}\\s*v?(${SEMVER})\\s*${BOLD}`, "g"),
// 5. heading with a parenthesised/backticked version
new RegExp(`^#{1,6}[^\\n]*?[\\(\\\`]\\s*v?(${SEMVER})`, "gm"),
// 6. inline range target
new RegExp(`v${SEMVER}\\s*${B}\\s*v?(${SEMVER})`, "g"),
// 7. backticked floor, e.g. `v3.8.51+`
new RegExp("\\`\\s*v?(" + SEMVER + ")\\s*\\+\\s*\\`", "g"),
];
// Ranges are matched by patterns 3 and 6, but their LEFT endpoint also looks
// like a plain bold/inline version. Record the ranges first and ignore any
// other pattern's match that falls inside one, so "**v3.8.20 → v3.8.52**"
// yields a single claim (3.8.52) instead of also claiming 3.8.20.
const RANGE_RE = new RegExp(`v?${SEMVER}\\s*${B}\\s*v?${SEMVER}`, "g");
return (content) => {
if (!expected) return { ok: false, detail: "package.json version could not be read" };
const rangeSpans = [];
for (const m of content.matchAll(RANGE_RE)) {
rangeSpans.push([m.index ?? 0, (m.index ?? 0) + m[0].length]);
}
const insideRange = (index) => rangeSpans.some(([s, e]) => index >= s && index < e);
const claims = [];
for (const pattern of PATTERNS)
for (const m of content.matchAll(pattern)) claims.push({ value: m[1], text: m[0].trim() });
for (const pattern of PATTERNS) {
for (const m of content.matchAll(pattern)) {
const index = m.index ?? 0;
const value = m[1];
// Patterns 3 and 6 capture a range's TARGET, which legitimately sits
// inside the range span. Every other match inside a range is that
// range's left endpoint being double-counted — skip it.
const isRangeTarget = pattern === PATTERNS[2] || pattern === PATTERNS[5];
if (insideRange(index) && !isRangeTarget) continue;
// A bold version inside a markdown TABLE row is a column marker, not a
// current-version claim: the README compatibility table bolds the last
// SHIPPED release (`| v3.8.50 | **v3.8.51** | \`v3.8.52+\` |`) while
// package.json already carries the in-development version. In such a
// row the current version is asserted by the backticked floor
// (pattern 7), which is still checked.
if (pattern === PATTERNS[3] && isTableRowAt(content, index)) continue;
claims.push({ value, text: m[0].trim(), index });
}
}
if (!claims.length) return { ok: true, detail: "no version claim in this file" };
const stale = claims.filter((c) => c.value !== expected);
if (!stale.length)
@ -410,6 +522,72 @@ export function makeVersionClaimValidator(expected) {
};
}
/**
* G-08 (#15159): validate a documented coverage floor against the BLOCKING
* ratchet, not against `npm run test:coverage`.
*
* AGENTS.md advertised `60/60/60/60` (statements/lines/functions/branches).
* That number is accurate in isolation — it is what test:coverage hardcodes —
* but the blocking gate enforces 67.33 / 67.33 / 72.02 / 65.08 from
* config/quality/quality-baseline.json. So an agent reading AGENTS.md alone
* would run the floor below the real one and be surprised by a red quality-gate
* on correct work. AGENTS.md is also an agent-instruction surface: a wrong floor
* there misleads every future session, not just a human reading docs.
*
* Deliberately narrow: it only fires on an explicit four-number coverage-floor
* claim, so ordinary prose about coverage is untouched.
*
* @param {{statements:number,lines:number,functions:number,branches:number}} floors
* @returns {(content: string) => {ok: boolean, detail: string}}
*/
export function makeCoverageFloorClaimValidator(floors) {
// "60/60/60/60", "67.33 / 67.33 / 72.02 / 65.08", "60 / 60 / 60 / 60".
const PATTERN =
/(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)/g;
return (content) => {
const claims = [];
for (const m of content.matchAll(PATTERN)) {
claims.push({
values: [m[1], m[2], m[3], m[4]].map(Number),
text: m[0],
index: m.index ?? 0,
});
}
if (!claims.length) return { ok: true, detail: "no coverage-floor claim in this file" };
const required = [floors.statements, floors.lines, floors.functions, floors.branches];
const problems = [];
for (const claim of claims) {
// Only compare against the requirement it can actually fail: a floor claim
// sits BELOW the blocking floor, so any component below its requirement is
// a real understatement. Being above is harmless (stricter than required).
const shortfalls = required
.map((required_, i) => ({ required: required_, claimed: claim.values[i] }))
.filter((c) => c.claimed < c.required);
if (shortfalls.length) {
// Show the surrounding line so the report names the offending text.
const lineStart = content.lastIndexOf("\n", claim.index) + 1;
const lineEnd = content.indexOf("\n", claim.index);
const line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd).trim();
problems.push(
`${claim.text} understates the blocking coverage floor ` +
`(${shortfalls
.map((c) => `claims ${c.claimed}, gate requires ${c.required}`)
.join("; ")}) — ${line}`
);
}
}
if (!problems.length)
return {
ok: true,
detail: `${claims.length} coverage-floor claim(s) meet or exceed the blocking ratchet`,
};
return { ok: false, detail: problems.join(" | ") };
};
}
// --- Generic numeric-claim gate ---------------------------------------------
// Same principle as the free-tier headline: docs legitimately carry numbers that are
// NOT the aggregate being gated (per-module tool counts like "Memory tool definitions
@ -618,6 +796,18 @@ export function buildChecks() {
files: ["README.md", "llm.txt"],
validate: makeVersionClaimValidator(readPackageVersion()),
},
{
// G-08 (#15159): AGENTS.md advertised `60/60/60/60`, which is what
// test:coverage hardcodes but NOT what the BLOCKING ratchet enforces. Read
// the real floors from quality-baseline.json rather than hardcoding them
// here, so a future rebaseline cannot leave the docs silently wrong.
label: "Coverage floor (docs vs blocking ratchet)",
actual: Object.values(readCoverageRatchetFloors()).join(" / "),
docKey: "coverage floor",
strict: true,
files: ["AGENTS.md", "README.md"],
validate: makeCoverageFloorClaimValidator(readCoverageRatchetFloors()),
},
{
label: "i18n locales count",
actual: countLocales(),

View file

@ -0,0 +1,193 @@
import { test } from "node:test";
import assert from "node:assert";
import fs from "node:fs";
import path from "node:path";
import {
makeVersionClaimValidator,
makeCoverageFloorClaimValidator,
} from "../../scripts/check/check-docs-counts-sync.mjs";
// Regression guard for audit #15159 — G-06 (stale version strings the validator
// cannot see) and G-08 (AGENTS.md advertises the wrong coverage floor).
//
// G-06: makeVersionClaimValidator matched exactly TWO literal phrasings:
//
// /OmniRoute v(\d+\.\d+\.\d+)/
// /Current version:\*{0,2}\s*\*{0,2}(\d+\.\d+\.\d+)/
//
// so it saw at most one claim per file. Four stale strings shipped straight
// through it (audit-measured):
//
// README.md:1012 "reported by OmniRoute 3.8.51"
// README.md:64 "v3.8.49 | **v3.8.50** | v3.8.51+"
// README.md:571 "Recent highlights from **v3.8.20 → v3.8.50**"
// llm.txt:277 "## Key Features (v3.8.50)"
//
// None of those phrasings match either pattern, so the gate reported OK while
// the docs named a version that was not package.json's. Widening the regex is
// load-bearing: fixing the strings alone lets the next phrasing slip through.
//
// G-08: AGENTS.md documents `60/60/60/60` (statements/lines/functions/branches).
// That IS what `npm run test:coverage` hardcodes, so the number is not wrong in
// isolation — but the BLOCKING ratchet in config/quality/quality-baseline.json
// enforces 67.33 / 67.33 / 72.02 / 65.08. An agent reading AGENTS.md alone reds
// the quality-gate job below the real floor. Worse, AGENTS.md was not in the
// validator's file list at all, so nothing checked it.
type Verdict = { ok: boolean; detail: string };
const validateVersion = makeVersionClaimValidator as (
expected: string
) => (content: string) => Verdict;
const validateCoverage = makeCoverageFloorClaimValidator as (floors: {
statements: number;
lines: number;
functions: number;
branches: number;
}) => (content: string) => Verdict;
const VERSION = "3.8.52";
// --- G-06: the four phrasings the audit found slipping through ---
const SHIPPED_STALE: [string, string][] = [
["README.md:1012", "For the package set reported by OmniRoute 3.8.51:"],
["README.md:64", "| | v3.8.49 | **v3.8.50** | `v3.8.51+` |"],
["README.md:571", "Recent highlights from **v3.8.20 → v3.8.50**."],
["llm.txt:277", "## Key Features (v3.8.50)"],
];
for (const [where, line] of SHIPPED_STALE) {
test(`G-06: the ${where} phrasing is now detected as a stale version claim`, () => {
const result = validateVersion(VERSION)(line);
assert.equal(
result.ok,
false,
`validator must flag ${where} as stale on a ${VERSION} tree, got: ${result.detail}`
);
assert.match(result.detail, /stale version/);
});
}
test("G-06: a claim matching the CURRENT version is accepted in every phrasing", () => {
// Widening the patterns must not make the gate reject correct text.
for (const [, line] of SHIPPED_STALE) {
const updated = line.replace(/3\.8\.\d+/g, VERSION);
const result = validateVersion(VERSION)(updated);
assert.equal(result.ok, true, `current-version phrasing rejected: ${result.detail}`);
}
});
test("G-06: a 'v3.8.51+' lower-bound claim is caught, not treated as a range", () => {
// The `v3.8.51+` cell is a compatibility floor, not the shipped version. It
// must still be reported: the audit listed it as one of the four leaks.
const result = validateVersion(VERSION)("| | v3.8.49 | **v3.8.52** | `v3.8.51+` |");
assert.equal(result.ok, false);
});
test("G-06: a compatibility-table row bolding the last SHIPPED release is accepted", () => {
// Mid-cycle the README table bolds the last released column while package.json
// already carries the in-development version; the row's current-version claim
// is the backticked floor, which must match.
const row = "| | v3.8.50 | **v3.8.51** | `v3.8.52+` |";
const result = validateVersion(VERSION)(row);
assert.equal(result.ok, true, `table column marker treated as a claim: ${result.detail}`);
// The same row with a stale floor is still caught.
const staleFloor = validateVersion(VERSION)("| | v3.8.50 | **v3.8.51** | `v3.8.51+` |");
assert.equal(staleFloor.ok, false);
// A bold version OUTSIDE a table row is still a claim.
assert.equal(validateVersion(VERSION)("Now shipping **v3.8.51**.").ok, false);
});
test("G-06: unrelated version numbers in docs are not flagged", () => {
// The gate must not become so greedy it reddens on legitimate prose — node
// versions, dependency versions, historical notes.
const result = validateVersion(VERSION)(
[
"Requires Node.js >=22.22.2 <23.",
"TypeScript 6.0+, target ES2022.",
"Released 2024 for the 1.0 series.",
].join("\n")
);
assert.equal(result.ok, true, `false positive: ${result.detail}`);
});
test("G-06: prose with no version claim at all is accepted", () => {
const result = validateVersion(VERSION)("OmniRoute is a unified AI proxy/router.");
assert.equal(result.ok, true);
});
// --- G-06: the real files must now be clean ---
test("G-06: README.md and llm.txt carry no stale version claim", () => {
const root = path.resolve(import.meta.dirname, "..", "..");
const validator = validateVersion(VERSION);
for (const rel of ["README.md", "llm.txt"]) {
const result = validator(fs.readFileSync(path.join(root, rel), "utf8"));
assert.equal(result.ok, true, `${rel} has a stale version claim — ${result.detail}`);
}
});
// --- G-08: the coverage floor AGENTS.md advertises ---
const REAL_FLOORS = { statements: 67.33, lines: 67.33, functions: 72.02, branches: 65.08 };
test("G-08: a doc claiming the blocking ratchet floor is accepted", () => {
const line =
"coverage gate (67.33 / 67.33 / 72.02 / 65.08 — statements/lines/functions/branches)";
assert.equal(validateCoverage(REAL_FLOORS)(line).ok, true);
});
test("G-08: AGENTS.md claiming 60/60/60/60 is now flagged against the blocking floor", () => {
// This is the defect. Accurate for `npm run test:coverage`, misleading for the
// ratchet an agent will actually hit.
const line =
"| Coverage gate | `npm run test:coverage` (60/60/60/60 — statements/lines/functions/branches) |";
const result = validateCoverage(REAL_FLOORS)(line);
assert.equal(result.ok, false, `must flag 60/60/60/60 as below the blocking floor`);
assert.match(result.detail, /60/);
});
test("G-08: the coverage validator reports WHICH floor is missing", () => {
const result = validateCoverage(REAL_FLOORS)("coverage gate (60/60/60/60)");
assert.match(result.detail, /blocking/i);
});
test("G-08: a docs file with no coverage claim is accepted", () => {
assert.equal(validateCoverage(REAL_FLOORS)("nothing to see here").ok, true);
});
test("G-08: AGENTS.md no longer advertises a floor below the blocking ratchet", () => {
// The end state: the instruction surface an agent reads first must not send
// them below the floor the blocking gate enforces.
const root = path.resolve(import.meta.dirname, "..", "..");
const agents = fs.readFileSync(path.join(root, "AGENTS.md"), "utf8");
const result = validateCoverage(REAL_FLOORS)(agents);
assert.equal(
result.ok,
true,
`AGENTS.md advertises a coverage floor below the blocking ratchet — ${result.detail}`
);
});
test("G-08: the blocking ratchet floors in quality-baseline.json are what we validate against", () => {
// Pin the source of truth so a future rebaseline that lowers these floors is a
// deliberate act visible in a test diff, not a silent doc/gate divergence.
const root = path.resolve(import.meta.dirname, "..", "..");
const baseline = JSON.parse(
fs.readFileSync(path.join(root, "config/quality/quality-baseline.json"), "utf8")
);
assert.equal(baseline.metrics["coverage.statements"].value, REAL_FLOORS.statements);
assert.equal(baseline.metrics["coverage.lines"].value, REAL_FLOORS.lines);
assert.equal(baseline.metrics["coverage.functions"].value, REAL_FLOORS.functions);
assert.equal(baseline.metrics["coverage.branches"].value, REAL_FLOORS.branches);
});