mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-10-21 04:54:04 +00:00
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:
parent
d13f37687e
commit
7d7ed3f3af
71 changed files with 466 additions and 83 deletions
22
AGENTS.md
22
AGENTS.md
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
2
llm.txt
2
llm.txt
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
193
tests/unit/check-docs-version-and-coverage-floor-15159.test.ts
Normal file
193
tests/unit/check-docs-version-and-coverage-floor-15159.test.ts
Normal 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);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue