Skip to content

feat(npm): add npm package publishing with CLI entry point - #15

Merged
diegosouzapw merged 1 commit into
mainfrom
feature/npm-package
Feb 14, 2026
Merged

diegosouzapw merged 1 commit into
mainfrom
feature/npm-package

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Changes

  • bin/omniroute.mjs — CLI entry point with banner, auto-open browser, graceful shutdown
  • scripts/prepublish.mjs — Build script to generate Next.js standalone in app/
  • .github/workflows/npm-publish.yml — Auto-publish to npm on GitHub Release
  • package.json — name=omniroute, bin, files, engines, keywords, prepublishOnly
  • next.config.mjs — Added output: 'standalone'
  • LICENSE — MIT
  • .npmignore / .gitignore — Updated for app/ build artifact

Usage after publish

npm install -g omniroute
omniroute

Auto-publish workflow

When creating a GitHub Release (e.g. v0.1.0), npm publish runs automatically via GitHub Actions.

- Add bin/omniroute.mjs CLI with banner, auto-open browser, graceful shutdown
- Add scripts/prepublish.mjs to build Next.js standalone into app/
- Add .github/workflows/npm-publish.yml for automated publish on release
- Update package.json: name=omniroute, bin, files, engines, keywords, prepublishOnly
- Add output: 'standalone' to next.config.mjs
- Add MIT LICENSE
- Update .npmignore and .gitignore for app/ build artifact
Copilot AI review requested due to automatic review settings February 14, 2026 07:14
@github-actions

Copy link
Copy Markdown
Contributor

@codex review

@diegosouzapw
diegosouzapw merged commit 1f5dfc3 into main Feb 14, 2026
3 checks passed
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@diegosouzapw
diegosouzapw deleted the feature/npm-package branch February 14, 2026 07:14
@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds npm package publishing capabilities to OmniRoute, transforming it from a development-only project into an installable CLI tool. The implementation uses Next.js standalone mode to create a self-contained build that can be globally installed via npm.

Changes:

  • Added CLI entry point (bin/omniroute.mjs) with server lifecycle management, argument parsing, and auto-browser opening
  • Created build script (scripts/prepublish.mjs) to generate Next.js standalone output and copy runtime dependencies to app/ directory
  • Configured automated npm publishing via GitHub Actions workflow triggered on release creation
  • Updated package metadata for public npm distribution with proper bin configuration, files array, and keywords
  • Enabled Next.js standalone mode and improved environment variable handling
  • Added MIT license and updated ignore files for build artifacts

Reviewed changes

Copilot reviewed 6 out of 9 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
bin/omniroute.mjs CLI entry point that spawns Next.js standalone server with banner, port configuration, graceful shutdown, and browser auto-open
scripts/prepublish.mjs Build script that creates standalone Next.js build, copies static assets, MITM utilities, and shared dependencies to publishable app/ directory
package.json Updated package name to "omniroute", added bin entry, files array, engines requirement, keywords, license, and prepublishOnly hook
package-lock.json Reflects package.json changes including name, version, license, bin, engines, and marks fsevents as dev dependency
next.config.mjs Enabled standalone output mode for npm packaging and removed hardcoded NEXT_PUBLIC_CLOUD_URL in favor of .env configuration
.github/workflows/npm-publish.yml Automated npm publishing on GitHub release with version sync, standalone build, and provenance
LICENSE Added MIT license with 2026 copyright
.npmignore Configured to exclude source files and include only bin/ and app/ directories in published package
.gitignore Added /app directory to ignore list since it's a build artifact

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/prepublish.mjs
Comment on lines +90 to +91
// ── Step 10: Ensure data/ directory exists ──────────────────
mkdirSync(join(APP_DIR, "data"), { recursive: true });

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build script creates an empty data/ directory in the published package. When users install via npm, this directory will be in their global npm packages directory (e.g., /usr/local/lib/node_modules/omniroute/app/data/), which may not have write permissions for all users. The application should handle the case where this directory is not writable and potentially use a user-specific data directory (e.g., ~/.omniroute/data/ or respect the DATA_DIR environment variable from .env.example). Verify that the application properly handles data directory initialization and permissions at runtime.

Suggested change
// ── Step 10: Ensure data/ directory exists ──────────────────
mkdirSync(join(APP_DIR, "data"), { recursive: true });
// ── Note: data/ directory is initialized at runtime ────────
// Do not create a writable data/ directory inside app/ here, as it
// would reside in a potentially non-writable global npm directory.

Copilot uses AI. Check for mistakes.
Comment thread .npmignore
open-sse/
cloud/
images/
logs/

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package.json excludes scripts/ in the .npmignore but includes scripts/prepublish.mjs that won't be in the published package. This is intentional since the build script is only needed during the publishing process (via prepublishOnly), not after installation. However, consider adding a comment in package.json or the build script explaining that prepublishOnly runs before publishing to npm, not during user installation, to clarify the build workflow.

Suggested change
logs/
logs/
# Note: scripts/ (e.g. scripts/prepublish.mjs) is only used during npm publish via `prepublishOnly`,
# so it is intentionally excluded from the published package.

Copilot uses AI. Check for mistakes.
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow uses Node.js 22, but the package.json specifies engines.node: ">=18.0.0". While this is not necessarily wrong (testing on a higher version that still meets the minimum requirement), consider whether the package should be tested on Node.js 18 as well to ensure compatibility with the minimum supported version, especially since users might install it on Node.js 18.

Suggested change
node-version: 22
node-version: 18

Copilot uses AI. Check for mistakes.
Comment thread scripts/prepublish.mjs
Comment on lines +31 to +37
// ── Step 2: Install dependencies ───────────────────────────
console.log(" 📦 Installing dependencies...");
execSync("npm install", { cwd: ROOT, stdio: "inherit" });

// ── Step 3: Build Next.js ──────────────────────────────────
console.log(" 🏗️ Building Next.js (standalone)...");
execSync("npx next build --webpack", { cwd: ROOT, stdio: "inherit" });

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build script uses execSync with stdio: "inherit" but doesn't wrap it in try-catch blocks. If any command fails (npm install, next build), the script will crash with an unhandled exception. While this might be acceptable since the process should exit on build failure anyway, consider adding try-catch blocks with clearer error messages to help diagnose build failures, especially for the critical Next.js build step.

Copilot uses AI. Check for mistakes.
Comment thread .npmignore
Comment on lines 25 to 27
eslint.config.mjs
prettier.config.mjs
postcss.config.mjs

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .npmignore excludes all .env* files including .env.example. Users installing the package via npm won't have a reference for required environment variables. Consider either: (1) including .env.example in the published package by adding !.env.example to .npmignore, (2) documenting required environment variables in the README, or (3) having the CLI create a default configuration on first run. This is especially important for variables like JWT_SECRET, INITIAL_PASSWORD, and DATA_DIR from the .env.example file.

Suggested change
eslint.config.mjs
prettier.config.mjs
postcss.config.mjs
!.env.example
eslint.config.mjs
prettier.config.mjs

Copilot uses AI. Check for mistakes.
# Remove 'v' prefix if present (v0.1.0 -> 0.1.0)
VERSION="${VERSION#v}"
npm version "$VERSION" --no-git-tag-version --allow-same-version
echo "Publishing version: $VERSION"

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow synchronizes the version from the GitHub release tag, which overwrites the version in package.json. This means the version "0.1.0" in package.json is only a placeholder and will be replaced during publishing. Consider adding a comment in package.json near the version field to clarify that the actual version is set from the GitHub release tag during publishing, to avoid confusion for maintainers.

Suggested change
echo "Publishing version: $VERSION"
echo "Publishing version (from GitHub release tag, overriding package.json placeholder): $VERSION"

Copilot uses AI. Check for mistakes.
Comment thread .npmignore
# Development
# Source code (pre-built app/ is published instead)
src/
open-sse/

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .npmignore file excludes the open-sse/ workspace directory, which might cause issues if the Next.js standalone build doesn't properly bundle this workspace dependency. Verify that @omniroute/open-sse is correctly included in the standalone output at .next/standalone/node_modules/@omniroute/open-sse or adjust the build script to explicitly copy it. The transpilePackages configuration in next.config.mjs should handle this, but it should be tested to ensure the published package works correctly.

Suggested change
open-sse/

Copilot uses AI. Check for mistakes.
Comment thread scripts/prepublish.mjs
*/

import { execSync } from "node:child_process";
import { existsSync, mkdirSync, cpSync, rmSync, writeFileSync, readFileSync } from "node:fs";

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeFileSync import is unused in this script. It's imported on line 13 but never called. Consider removing it to keep the imports clean.

Suggested change
import { existsSync, mkdirSync, cpSync, rmSync, writeFileSync, readFileSync } from "node:fs";
import { existsSync, mkdirSync, cpSync, rmSync, readFileSync } from "node:fs";

Copilot uses AI. Check for mistakes.
Comment thread package.json
},
"files": [
"bin/",
"app/",

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The files array in package.json includes app/ which will contain the standalone Next.js build and all dependencies. Verify that this directory doesn't accidentally include large or unnecessary files that could bloat the package size. The standalone build typically includes node_modules, but only production dependencies. Consider checking the final package size before publishing and potentially adding more exclusions to .npmignore if needed.

Suggested change
"app/",
"app/.next/standalone/",

Copilot uses AI. Check for mistakes.
diegosouzapw added a commit that referenced this pull request May 15, 2026
…ROUTE_CLI_SALT, tray PNG, preservePatterns defaults, missing docs

- management.ts: replace === with timingSafeEqual for CLI token comparison
- machineToken.ts: salt upgraded to omniroute-cli-auth-v1; OMNIROUTE_CLI_SALT env
  var honoured for rotation; full 64-char SHA-256 hex token
- tray.ps1: accept .png via GDI+ Bitmap->Icon handle; Windows tray works without .ico
- tray.ts: getIconPath() tries icon.ico then icon.png on Windows
- compression/types.ts: DEFAULT_CAVEMAN_CONFIG.preservePatterns filled with
  six defaults (fenced code, inline code, URLs, paths, error lines, stack traces)
- CLAUDE.md: Hard Rule #15 — spawn-capable routes must use isLocalOnlyPath()
- .env.example + docs/reference/ENVIRONMENT.md: document OMNIROUTE_CLI_SALT
- docs/security/CLI_TOKEN.md: new (was referenced in changelog but missing)
- docs/security/ROUTE_GUARD_TIERS.md: new (was referenced in changelog but missing)
- tests/unit/lib/machineToken.test.ts: updated for 64-char token; added
  OMNIROUTE_CLI_SALT env-var rotation test
diegosouzapw added a commit that referenced this pull request May 15, 2026
- COMPRESSION_ENGINES.md: add MCP accessibility-tree filter section
  with config reference, algorithm description, and comparison table
- COMPRESSION_LANGUAGE_PACKS.md: document SHARED_BOUNDARIES clause
  (6 patterns × 6 languages × 3 intensities, preservePatterns defaults)
- MCP-SERVER.md: add accessibility-tree filter note in Compression Tools
- CONTRIBUTING.md: fix coverage gate (60%→75/70), add Hard Rules #15/#16
  to PR checklist, add links to new security/ops docs
diegosouzapw added a commit to guanbear/OmniRoute that referenced this pull request Jun 1, 2026
…tream-ca routes (F5)

12 REST routes under /api/tools/agent-bridge/ covering all AgentBridge
backend surfaces: server lifecycle, per-agent state/DNS/mappings/detect,
cert status/download/regenerate, bypass pattern CRUD, upstream CA config.
All routes use Zod validation and route errors through sanitizeErrorMessage.

feat(authz): mark agent-bridge LOCAL_ONLY + SPAWN_CAPABLE (F5)

Adds /api/tools/agent-bridge/ to both LOCAL_ONLY_API_PREFIXES and
SPAWN_CAPABLE_PREFIXES in routeGuard.ts — satisfying Hard Rules diegosouzapw#15 + diegosouzapw#17.
diegosouzapw referenced this pull request in oyi77/OmniRoute Jun 6, 2026
… listener leak

- routeGuard: classify POST /api/providers/{id}/login as LOCAL_ONLY via a new
  regex matcher (LOCAL_ONLY_API_PATTERNS) so loopback is enforced before auth —
  it launches a headful Playwright Chromium (child process). Scoped to the
  /login sub-route so ordinary /api/providers/* CRUD stays remotely reachable
  (Hard Rules #15 + #17). TDD: route-guard-provider-login-local-only.test.ts
  pins the gate AND the non-over-match (fails without the matcher).
- login/route.ts: route both error bodies through sanitizeErrorMessage so raw
  err.message never leaks in the HTTP response (Hard Rule #12).
- electron/main.js: register the loginManager 'status' listener once instead of
  inside the login:start handler (was attaching a new listener per call); drop
  the dead require('./sqlite-inspection').persistSecret.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
diegosouzapw added a commit that referenced this pull request Jun 9, 2026
…symbols, route-guard, complexity, docs-symbols, db-rules)

Deterministic gates, each freezing pre-existing violations in a documented allowlist (ratchet) so they pass now and block only NEW regressions:
- check-error-helper (Rule #12): 7 executors/handlers forwarding raw err.message frozen
- check-public-creds (Rule #11): 5 literal client_ids (Claude/Codex/Qwen/Kimi/Copilot) frozen
- check-migration-numbering: gaps 026/055 + dup 041 frozen (prevents the git-rm-deleted-migration incident)
- check-known-symbols: 93 executors conformance + 15 combo strategies + 18 translator pairs
- check-route-guard-membership (#15/#17): all 25 spawn-capable routes verified local-only (0 gaps)
- check-complexity: cyclomatic>15 / fn-length>80 ratchet (baseline 1739)
- check-docs-symbols: 30 stale doc /api refs frozen (docs hallucination)
- check-db-rules (#2/#5): 25 unexported db modules + 15 raw-SQL routes frozen
Wired into CI (lint / docs-sync-strict / quality-gate jobs). 115 TDD tests, all green. ESLint ratchet held at 3482.
diegosouzapw added a commit that referenced this pull request Jun 9, 2026
…ment guardrails (Phases 0-6) (#3471)

* feat(quality): generic ratchet comparator (multi-metric, regression-only)

* chore(ci): Fase 0 quality-gate fixes — reconcile coverage gate (40->60), tier npm audit, wire orphaned contract gates, re-enable cheap husky pre-commit

* feat(quality): ratchet engine (collector + frozen baseline + CI job) and provider-consistency gate

- collect-metrics.mjs: emits quality-metrics.json (ESLint warnings + coverage when present)
- quality-baseline.json: frozen baseline (eslintWarnings=3482, regression-only)
- ci.yml: quality-gate job (ratchet + step summary + artifact) and check:provider-consistency in lint job
- check-provider-consistency.ts: every REGISTRY id must be a canonical provider (found krutrim half-registered → allowlisted as known pre-existing, blocks any NEW orphan)
- TDD: 9 tests (5 ratchet + 4 provider-consistency)

* feat(quality): Fase 2 anti-hallucination gates — fetch-targets, openapi-routes, deps allowlist

- check-fetch-targets: every dashboard fetch(/api/...) resolves to a real route.ts; found 7 pre-existing dashboard->route mismatches frozen as KNOWN_MISSING for triage
- check-openapi-routes: every openapi.yaml path resolves to a real route; found 1 stale spec entry (agent-bridge agents/{id}/state) frozen as KNOWN_STALE_SPEC
- check-deps: anti-slopsquatting allowlist (105 deps); new deps need explicit human-reviewed entry
- all wired into CI lint/docs jobs; TDD +12 tests (21 total across 5 gates)

* docs(quality): add quality-gates report + implementation plan to repo root

* feat(quality): Fase 3a — file-size ratchet (freeze 91 files >800 LOC, cap 800 for new)

- check-file-size.mjs: frozen files can only shrink; new files must be <= cap (kills the next 12k-line god-component)
- file-size-baseline.json: 91 files frozen at current LOC (largest 12883)
- wired into CI lint job; TDD 5 tests; --update ratchets the baseline down on shrink

* feat(quality): Fase 3b — duplication ratchet (jscpd@4, baseline 5.72%)

- check-duplication.mjs: runs jscpd@4 (pinned; v5 is an incompatible Rust rewrite) over src+open-sse, fails if duplication % rises vs frozen baseline (5.72%, measured: 1358 clones / 22967 dup lines). Targets the executor copy-paste (48/50 override execute() wholesale)
- wired into the parallel quality-gate CI job (off the lint critical path); TDD 4 tests; --update ratchets down
- snapshot now complete: coverage ~82.6%, eslint 3482 (98.5% no-explicit-any), duplication 5.72%, 91 files >800 LOC

* feat(quality): Fase 4a — anti test-masking gate

- check-test-masking.mjs: for each MODIFIED test file in a PR, flags net assert removal + new assert.ok(true) tautologies (base...HEAD diff). Directly enforces CLAUDE.md 'never weaken asserts to go green'
- wired into pr-test-policy CI job (reuses base fetch); no-op outside PR; TDD 5 tests

* feat(quality): Fase 4b — coverage ratchet (conservative floors, CI consumes merged coverage)

- quality-baseline.json: coverage.{statements,lines,functions,branches} floors (80/80/82/73, real ~82.58/82.58/84.23/75.22 with margin; tighten via --update after a green main run)
- check-quality-ratchet.mjs: --allow-missing (local quality:gate skips coverage.* without a coverage run; CI runs strict)
- ci.yml quality-gate job: needs test-coverage + downloads merged coverage-report so the ratchet enforces 'coverage cannot drop'
- TDD +1 test (6 total)

* feat(quality): Fase 6 — 8 new gates (Rule #11/#12, migrations, known-symbols, route-guard, complexity, docs-symbols, db-rules)

Deterministic gates, each freezing pre-existing violations in a documented allowlist (ratchet) so they pass now and block only NEW regressions:
- check-error-helper (Rule #12): 7 executors/handlers forwarding raw err.message frozen
- check-public-creds (Rule #11): 5 literal client_ids (Claude/Codex/Qwen/Kimi/Copilot) frozen
- check-migration-numbering: gaps 026/055 + dup 041 frozen (prevents the git-rm-deleted-migration incident)
- check-known-symbols: 93 executors conformance + 15 combo strategies + 18 translator pairs
- check-route-guard-membership (#15/#17): all 25 spawn-capable routes verified local-only (0 gaps)
- check-complexity: cyclomatic>15 / fn-length>80 ratchet (baseline 1739)
- check-docs-symbols: 30 stale doc /api refs frozen (docs hallucination)
- check-db-rules (#2/#5): 25 unexported db modules + 15 raw-SQL routes frozen
Wired into CI (lint / docs-sync-strict / quality-gate jobs). 115 TDD tests, all green. ESLint ratchet held at 3482.

* docs(quality): Phase 7 plan (security/dead-code/mutation/community tooling) — GATED to 2026-06-16

Stored, not active. 7 suggested gates + all discussed OSS/Community tools (SonarQube Community + osv-scanner + CodeQL + knip + sonarjs + type-coverage + lockfile-lint + Stryker + size-limit + axe-core + semcheck + agent-lsp + Qlty). Activation gate: do not start before 2026-06-16 (use Phases 0-6 in production for 1 week, validate in practice, then evolve).
diegosouzapw pushed a commit that referenced this pull request Aug 13, 2026
…sibling sweep

Real production defects fixed (all red on the pure tip, each with its origin):
- routeGuard.ts: #8949 accidentally DELETED the /api/providers/[id]/login
  local-only pattern — the route spawns a browser, so the loopback gate for a
  process-spawning route was gone (Hard Rules #15/#17); restored (314 guard
  tests green)
- agentSkills generator: #9058's category dispatch gave the config category an
  empty body, wiping skills/config-codex-cli/SKILL.md at the #10131 sync;
  fixed + SKILL.md regenerated via the official generator
- imageRegistry: #9982 broke same-provider bare aliasing (antigravity preview
  id sent upstream unresolved); new resolveSameProviderBareAlias() keeps the
  fal cross-provider fix intact
- imageRegistry: #9982's prefix strip handed the bare nano-banana ids to fal-ai,
  violating the pinned 2026-07-31 operator decision (adobe-firefly owns them);
  fal entries made prefix-only (dispatch already re-prefixes)
- mediaGeneration/fal.ts: the missing-credential 401 guard was lost when #10198
  deleted the superseded falHandler — tests were hitting the live network
- bottleneckPatch/rateLimitManager: #9041's merge clobbered #9604, resurrecting
  the Bottleneck v2.19.5 heartbeat bug (reservoir never refills); patched the
  library defect at the root and re-aligned chat-rate-limit-body-lock to the
  working reservoir contract
- processSupervisor.mjs: #9761 regressed the Node spawn to bare "node" (the
  #9156 launchd bug) and dropped #9209's ipv4first args; both restored
- openai-responses/pureHelpers: #9423's Agent null-sentinel was unreachable on
  the schemaless JSON-string path; gate extended
- i18n en.json: #8222's regen reverted the #9976 unclosed-tag fix and #8559's
  combo-cooldown copy; #9038 shipped 40 t() calls with no messages (runtime
  MISSING_MESSAGE); all restored/added + official sync-ui stamps, and vi's
  zero-marker policy re-established via the sanctioned translation backend

Stale sibling tests aligned (movers cited inline): chat-helpers (#9447),
executor-antigravity (#9351), video-fal-grok (#9982), visionBridge (#9759),
web-session-credentials (#8974), production-build-module-integrity (positive
anchor added), agentSkills-generator/skillManifestsLint/skills-injection/
agentSkillTools-mcp/listCapabilities-a2a (#9058), memory-settings (#10010),
model-catalog-policy-invalidation (#8906), model-alias-seed (#9485),
reactive-context-compaction (#8949), combo-provider-wildcard (broken upsert
helper), oauth-google-loopback (43-locale resurrected-key removal)

Validation: 501/501 across the 47 touched test files; typecheck:core, lint,
file-size, docs-sync all green.

Refs #9985
diegosouzapw added a commit that referenced this pull request Aug 13, 2026
* fix(ci): clear base-reds on release/v3.8.50 (round 3)

- CHANGELOG.md: restore the top [Unreleased] section dropped by the #10189
  reconcile (docs-sync gate: first section must be Unreleased)
- env-doc-sync: document CONDUCTOR_ORCHESTRATOR_TOKEN + CONDUCTOR_SPOKESPERSON_URL
  in .env.example/ENVIRONMENT.md; allowlist the CI-only GITHUB_STEP_SUMMARY and
  TS7_BASE_REF (ts7 ratchet signals); drop a stray merge artifact line
- providers: restore the audited chatanywhere metadata entry that base-reds
  round 2 dropped together with its duplicate — the provider was half-wired
  (registry+endpoint without APIKEY metadata), which is what the wave3 test
  catches; re-pin providers-constants-split at the measured 228
- docs counts: 338 -> 339 (today's +2 void-ai/helixmind, -1 Puter) via
  gen:provider-reference + README/AGENTS/llm.txt/package.json/diagrams/i18n mirrors
- file-size ratchet: annotated rebaseline for the two pre-existing drifts
  (ModelSelectModal 1138, gateways 1250) following the 2026-08-11 precedent

Refs #9985

* fix(ci): base-reds round 3b — stale sibling tests + mode-pack weight contract

- check-docs-counts-sync.test.ts: drop the imports/subtests of the four helpers
  #10196 removed from the gate script (readMcpFactsFromSource, listLocalizedDocs,
  makeRequiredCountsValidator, checkFreeTierInventory) — the new-API tests that
  #10196 added stay; the file now loads again under the node runner
- quota-connection-recovery.test.ts: convert from vitest APIs to node:test —
  the file lives in tests/unit/*.test.ts (node-runner glob) and the vitest
  runtime crashes when imported outside vitest, killing the whole shard entry
- modePacks.ts: re-normalize all six mode packs to sum 1.0 — #8940 added
  sessionAvailability: 0.05 to every pack without rebalancing (1.05 total);
  ratios preserved exactly (÷1.05), so post-normalizeScoringWeights behavior
  is unchanged; restores the declared sum-to-1.0 contract the 4235 test pins

Refs #9985

* fix(ci): base-reds round 3c — vitest siblings, weights default, secrets FP, mutation tap

- DistributeProxiesButton.test.tsx: wrap renders in NextIntlClientProvider —
  #9245 localized the component (useTranslations) and left the test without
  the intl context, failing all 14 cases
- scoring.ts: re-normalize DEFAULT_WEIGHTS to sum 1.0 (same #8940 class as the
  mode packs — sessionAvailability added without rebalancing; ratios preserved)
- .gitleaks.toml: generalize the kimi sponsor-banner localStorage-key allowlist
  to -v\d+ — #10200 bumped v1→v2 and the stale regex regressed the secrets
  ratchet with a false positive
- stryker.conf.json: register 6 covering unit tests in tap.testFiles (4 modules)
  so their mutant kills count — unblocks check:mutation-test-coverage --strict

Refs #9985

* fix(ci): base-reds round 3d — inspector factor gap, stale registry/gap tests, i18n key sync

- comboScoringInspector: add cacheAffinity/sessionAvailability/connectionDensity
  to FACTOR_KEYS + the factor-key type — calculateScore() weighs them but the
  breakdown omitted them, so the explained contributions never summed to the
  reported score (inspector bug, red on the pure tip)
- combo-scoring-inspector.test: make the explicit-weights override sum-neutral
  (±0.05 shift) so it stays valid for any DEFAULT_WEIGHTS values — the hardcoded
  override only summed to 1.0 against the pre-#8940 defaults, which is also why
  explicit weights silently fell back to 'default' on the tip
- unorouter-registry.test: align to the canonical .com host (api.unorouter.ai
  301-redirects there, verified live) and to wave4's live model discovery
  (passthrough, no static seed) — the .ai/auto-model expectations were stale
- check-migration-numbering.test: 147 left KNOWN_GAPS when
  147_api_keys_model_access_mode.sql landed — assert absent (same as 143)
- i18n: sync-ui pass — 35,914 missing UI keys stamped as __MISSING__ placeholders
  across 42 locales (mechanical; greens the pt-BR key-presence integrity test;
  coverage pct unchanged by design — translation is a separate workstream)

Refs #9985

* fix(ci): base-reds round 3e — 2 real defects + 14 stale sibling tests (waves A-E)

Real defects fixed:
- src/lib/db/apiKeys.ts: #9313's empty-allowlist early return bypassed the group
  permission check, silently disabling group deny rules (#8817) for every key
  without a per-key allowlist; fall-through restored, restricted+[] deny-all kept
- open-sse/utils/proxyFetch.ts: #10032 re-appended the raw transport error to the
  propagated message, reintroducing the proxy user:password leak #9837 closed;
  new redactProxyDetailsInMessage() keeps the reason, redacts URL/credentials
- .github/workflows/quality.yml: #10134 added the TS7 ratchet as a separate
  blocking step AFTER the aggregated gates — the exact #8542 masking mechanism;
  folded into the non-fail-fast loop (still blocking, still PR-only) ⚠️ CI edit,
  gate-strengthening — explicit owner sign-off requested on the PR
- src/i18n/messages/ko.json: 3 machine-mistranslation regressions caught by the
  #8244 glossary checker (장애인→비활성화됨, 양말5://→socks5://, 비클로드→Claude가 아닌)

Stale sibling tests aligned to deliberately-moved contracts (each cites its mover):
request-log-detail-layout + -stream (#9245 intl provider), repro-8542 pin update,
quality-rail-gate-membership (#10134 shape), agentSkills-routes 45→46 (#9058),
cloudflare-ai-catalog-8717 (#8804 supersedes #8808), executor-xai (#9994),
vision-bridge-claude-wire (#9463 minimax→openai), sse-auth forced-pin (#8893),
tls-proxy-context (strengthened leak guards), rate-limit-local-error-classification
(#9164/#9342), minimax-thinking-signature (#9463), codebuddy-cn (#9723 +1 test),
github-copilot-custom-model (#9050), providers-g4f-batch3 (#9584),
synced-capability-warmup (#9199, stricter), sidebar-tools-group (#8221),
oauth-modal-grok-cli-paste (#9245); agentSkills/catalog.ts comment 45→46;
file-size rebaseline for proxyFetch (+19, annotated)

Refs #9985

* fix(ci): base-reds round 3f — waves F-J: 9 more real defects + stale sibling sweep

Real production defects fixed (all red on the pure tip, each with its origin):
- routeGuard.ts: #8949 accidentally DELETED the /api/providers/[id]/login
  local-only pattern — the route spawns a browser, so the loopback gate for a
  process-spawning route was gone (Hard Rules #15/#17); restored (314 guard
  tests green)
- agentSkills generator: #9058's category dispatch gave the config category an
  empty body, wiping skills/config-codex-cli/SKILL.md at the #10131 sync;
  fixed + SKILL.md regenerated via the official generator
- imageRegistry: #9982 broke same-provider bare aliasing (antigravity preview
  id sent upstream unresolved); new resolveSameProviderBareAlias() keeps the
  fal cross-provider fix intact
- imageRegistry: #9982's prefix strip handed the bare nano-banana ids to fal-ai,
  violating the pinned 2026-07-31 operator decision (adobe-firefly owns them);
  fal entries made prefix-only (dispatch already re-prefixes)
- mediaGeneration/fal.ts: the missing-credential 401 guard was lost when #10198
  deleted the superseded falHandler — tests were hitting the live network
- bottleneckPatch/rateLimitManager: #9041's merge clobbered #9604, resurrecting
  the Bottleneck v2.19.5 heartbeat bug (reservoir never refills); patched the
  library defect at the root and re-aligned chat-rate-limit-body-lock to the
  working reservoir contract
- processSupervisor.mjs: #9761 regressed the Node spawn to bare "node" (the
  #9156 launchd bug) and dropped #9209's ipv4first args; both restored
- openai-responses/pureHelpers: #9423's Agent null-sentinel was unreachable on
  the schemaless JSON-string path; gate extended
- i18n en.json: #8222's regen reverted the #9976 unclosed-tag fix and #8559's
  combo-cooldown copy; #9038 shipped 40 t() calls with no messages (runtime
  MISSING_MESSAGE); all restored/added + official sync-ui stamps, and vi's
  zero-marker policy re-established via the sanctioned translation backend

Stale sibling tests aligned (movers cited inline): chat-helpers (#9447),
executor-antigravity (#9351), video-fal-grok (#9982), visionBridge (#9759),
web-session-credentials (#8974), production-build-module-integrity (positive
anchor added), agentSkills-generator/skillManifestsLint/skills-injection/
agentSkillTools-mcp/listCapabilities-a2a (#9058), memory-settings (#10010),
model-catalog-policy-invalidation (#8906), model-alias-seed (#9485),
reactive-context-compaction (#8949), combo-provider-wildcard (broken upsert
helper), oauth-google-loopback (43-locale resurrected-key removal)

Validation: 501/501 across the 47 touched test files; typecheck:core, lint,
file-size, docs-sync all green.

Refs #9985

* fix(ci): base-reds round 3g — wave K/L: 4 more real defects + stale alignments

Real defects:
- base/reasoningEffort.ts: the stale duplicate cherry-pick #9612 re-added the
  codex minimal→low rewrite that #9883 had deliberately removed (OMP minimal
  passthrough); block removed again
- cursorImages.ts: #9840 wired prepareCursorImageForWire (sharp re-encode,
  fail-closed) into the SHARED resolveCursorImages, breaking zai-web and
  conol-web image uploads (HTTP 400 'undecodable'); new prepareForWire opt-out,
  Cursor default path unchanged (8 cursor suites green)
- modelCapabilities/snapshot: catalog prepare still issued 323 per-model reads
  of model_context_overrides + max_input_tokens overrides, violating #9199's
  bulk-load contract; both now resolve from the snapshot single pass
- v1-models-discovery-conformance: re-pinned to the bounded 30s SWR window
  (#9199/#10198) — the old 'stale-first regardless of age' contract is gone

Stale tests aligned (movers cited inline): codex-tools-strict-default (#9828
redundant-oneOf strip), devin-providers (#9245 i18n), db-migrationrunner-
constants-split (147→151 renumber #8228), gitlab-duo-oauth-setup (#9245),
chatcore-extracted-modules (#9161 outbound-protocol keying)

compression-api CI failures were cascade artifacts of codex-tools-strict-default
failing in the same force-exit shard process — no own defect (171/171 local).

Refs #9985

* fix(test): compression-api — register both describes before the runner starts

The DATA_DIR setup + route/db top-level awaits sat BETWEEN the two describes;
under --test-force-exit (the CI unit-runner flag) the process exits once the
already-registered tests finish, so on slow CI machines the whole second
describe died as 'Promise resolution is still pending' — the recurring
CI-only shard-2 failure that never reproduced locally without the flag.
Moved to the top of the file; 10/10 under --test-force-exit locally.

Refs #9985

* fix(quality): freeze modelCapabilities.ts at 1006 (annotated) — snapshot routing growth

Refs #9985

* fix(quality): move the modelCapabilities freeze into the frozen map (nested schema)

Refs #9985

* fix(i18n): translate all 39,718 pending UI keys across 42 locales (owner-approved)

Mass-translated every __MISSING__ placeholder via the official i18n:sync-ui
--translate-markers pipeline (operator backend), restoring i18nUiCoverage to the
100 baseline (was 89.9 after the merge-storm UI landings + the 42 keys #9038
never shipped).

Post-pass repairs, all caught by the existing gates:
- glossary: retired renderings the machine reintroduced normalized again
  (提供商→提供者 zh-CN/zh-TW, 鏈接→連結, 文檔→文件, 調用→呼叫, 供應商→提供者,
  響應→回應, 不活躍→未啟用 zh-TW; 클로드→Claude, 옴니루트→OmniRoute ko);
  DATA_DIR forbidden rendering avoided via 数据文件夹 rephrase
- ICU integrity: 120 values with renamed/dropped {params} repaired (39
  positional renames, 81 reset to the en source — functional over fluent)

Validation: glossary/pt-BR/vi/deno-relay/settings-keys/value-drift/google-
loopback suites 76/76; placeholder diff en×42 locales = 0; worst-locale
coverage = 100.0%.

Refs #9985

---------

Co-authored-by: backryun <bakryun0718@proton.me>
diegosouzapw pushed a commit that referenced this pull request Aug 23, 2026
…ocess-spawning endpoints (#11189)

Validated on the combined batch board (gates + typecheck clean) and this branch: security-route-guard-tiers green. Regression coverage for the Hard Rule #15/#17 contract — Tier 1 process-spawning prefixes (/api/services/, /api/mcp/, /api/cli-tools/runtime/) must stay LOCAL_ONLY before any auth check. Conflict with the tip was only stale provider-count docs. Thank you @rqzbeh!
diegosouzapw pushed a commit that referenced this pull request Aug 25, 2026
Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused tests: tests/unit/authz/route-guard-tunnel-processes-local-only.test.ts + tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts — 6+ pass
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for closing the process-spawning tunnel routes to local-only (Hard Rule #15/#17 territory) while preserving authenticated remote read access to status endpoints.
PauloFH added a commit to PauloFH/OmniRoute that referenced this pull request Sep 2, 2026
Two Fast Quality Gates failures on diegosouzapw#12400.

`check:test-discovery` reported all three new suites as orphans: the unit
runner's subdirectory glob is an explicit allowlist
(`tests/unit/{api,auth,authz,...}/**`) and `deploy` is not in it, so nothing
under `tests/unit/deploy/` was ever collected. The suites passed locally and in
no CI job — the exact failure mode that gate was added for after the 2026-06-09
audit found ~135 orphaned tests, one of them a Hard Rules diegosouzapw#15/diegosouzapw#17 check whose
asserts were already failing unnoticed.

Moved the three files to `tests/unit/` rather than widening the allowlist:
adding a directory there changes the runner for every contributor, which is not
this PR's business. Import depths adjusted; the fourth suite was already at the
collected level.

`check:complexity-ratchets` flagged `buildDeployment` at 83 lines against a
limit of 80. The pod template is almost entirely the container object, so that
object moved to `buildContainer`. No behaviour change — the generator's output
is byte-identical, which the parity test against deploy/kubernetes/base pins.

Both gates verified locally: test-discovery OK, complexityNewCode=0.
Raudbjorn added a commit to Raudbjorn/OmniRoute that referenced this pull request Sep 7, 2026
- chore(changelog): drop four stale fragments already in CHANGELOG (diegosouzapw#9435,
  diegosouzapw#9550, diegosouzapw#9568, diegosouzapw#9575) — stale-fragment gate exits nonzero on duplicate
- fix(translator/claudeHelper): re-filter empty messages after the Pass 1.4
  nameless-tool_use drop so non-final assistant messages whose only content
  was a nameless block do not survive into the request (#5, #6)
- fix(ws/responses-ws-proxy): serialize concurrent forwardClientMessage calls
  via dispatchQueue so a second client message arriving during ensureUpstream
  does not drop the late frame (diegosouzapw#7)
- fix(ws/v1-ws-bridge): close the session with code 1002 after a frame decode
  failure so an undecodable byte run does not loop forever (diegosouzapw#8)
- fix(ws/both): send the WebSocket close frame BEFORE flipping this.closed
  so the sendFrame guard does not turn the close handshake into a no-op
  (diegosouzapw#9)
- fix(oauth/freebuff): add 'network_error' to freebuffPollResponseSchema so
  the network-error return path typechecks (diegosouzapw#10)
- fix(oauth/freebuff): declare onProgress on FreebuffPollOptions so the
  poll-loop callback typechecks (diegosouzapw#11)
- fix(freebuff/cliEmulator): add readonly ok to FreebuffHttpResponse and
  populate it in tlsClientFetch, wreqFetch, and globalFetch so successful
  responses are not misclassified as failures (diegosouzapw#12)
- test(translator/openai-to-claude): defer content_block_start until a
  name arrives and throw on a permanently nameless streamed call so the
  Muse/GLM diegosouzapw#2077 sequence and the nameless-rejection test both pass (diegosouzapw#16)
- test(9568): resolve the three merge-marker regions in favor of HEAD —
  the test reflects the post-diegosouzapw#10392 translator behavior (diegosouzapw#13, diegosouzapw#14, diegosouzapw#15)
Raudbjorn added a commit to Raudbjorn/OmniRoute that referenced this pull request Sep 7, 2026
* fix(oauth): Kiro import token endpoint no longer overwrites existing connection when using shared cached OIDC clientId (diegosouzapw#9435)

Closes diegosouzapw#9435

(cherry picked from commit 9edefd4)

* fix(translator): add case-insensitive fallback for upstream tool call name lookups (diegosouzapw#9575)

Closes diegosouzapw#9575

(cherry picked from commit c9a3361)

* fix(translator): restore original tool name casing in Gemini response translators (diegosouzapw#9568)

Closes diegosouzapw#9568

(cherry picked from commit c9debe9)

* fix(model): add aq alias for amazon-q provider so parseModel resolves it instead of falling back to OpenAI (diegosouzapw#9550)

Closes diegosouzapw#9550

(cherry picked from commit f338363)

* fix(translator): reject nameless streaming tool calls

(cherry picked from commit e274819)

* fix(devin-desktop): sanitize Claude Code prompt triggers and enforce tool budget

(cherry picked from commit 8c1514a)
(cherry picked from commit a18771b)

* fix(devin-desktop): sanitize interactive Claude Code CLI identity declarations

(cherry picked from commit e5553c1)
(cherry picked from commit db089c8)

* fix(claude-code): format context overflow as 'Prompt is too long' to trigger reactive compact

(cherry picked from commit b5fec6b)
(cherry picked from commit 5e83dc6)

* fix(chat): import getCachedSettings from readCache

(cherry picked from commit 999c708)
(cherry picked from commit d8329dd)

* fix(image): add 'agy' alias for antigravity image generation models

(cherry picked from commit 2ac3e0d)
(cherry picked from commit 203cdb3)

* fix(translator): subtract cachedContentTokenCount from input_tokens in gemini-to-claude translator to avoid double-counting in Claude Code

(cherry picked from commit 4c5ec77)
(cherry picked from commit d82672d)

* fix(translator): add cache_creation_input_tokens, update tests, and set auto-compression defaults

(cherry picked from commit 9b68a4b)
(cherry picked from commit 6f4e1f4)

* fix(translator,compression): sanitize token metrics with Number.isFinite and restore opt-in compression defaults

(cherry picked from commit 985c3c2)
(cherry picked from commit cf9da8e)

* fix(vision-bridge): cross-check agy/antigravity aliases and declare SWE vision capabilities

(cherry picked from commit d1f2f5a)
(cherry picked from commit a80b6df)

* fix(vision-bridge): return skip if mixed combo has vision capable target

(cherry picked from commit a049fbb)
(cherry picked from commit fd2d093)

* fix(ratelimit): set 120s maxWaitMs floor for long-context providers to prevent 504 on 100k+ tokens

(cherry picked from commit 98cc4b0)
(cherry picked from commit 6195ebf)

* fix(ratelimit): clean up redundant zai-web entry, add null safety and long-context override tests

(cherry picked from commit 743a118)
(cherry picked from commit 4421ceb)

* test(ratelimit): add direct assertion for opencode in queue wait floor test

(cherry picked from commit dda584d)
(cherry picked from commit 5b987d9)

* fix(translator): synthesize fallback text block from reasoning when model emits only thinking for compaction

(cherry picked from commit 0e13723)
(cherry picked from commit bc9e1c5)

* fix(devin-desktop): classify content policy trailer errors as deterministic 400 and bypass cooldown

(cherry picked from commit 8820f15)
(cherry picked from commit f8a4a3b)

* feat(pricing): add SWE-1.7 Lightning pricing for devin-desktop and windsurf providers

(cherry picked from commit 11ec472)
(cherry picked from commit 0956840)

* feat(pricing): set swe-1-7 price at half Lightning rate (.25/zsh.5/.25 per 1M)

(cherry picked from commit 60def8f)
(cherry picked from commit 526c814)

* feat(models): add Gemini 3.8 Flash tiers and pricing

Introduce Gemini 3.8 Flash across OmniRoute on top of the existing
3.6/3.7 Flash support:

- modelSpecs: gemini-3.8-flash{,-high,-medium,-low,-tiered}
  with 1M context, 64K output, defaultReasoningEffort=medium.
- pricing: promo $0.75/$3.75 per 1M for gemini, antigravity/agy,
  frontier-labs, cheaperinference, orcarouter, and inference-hosts.
- provider registries: gemini, gemini-web, github, ghe-copilot,
  cursor, cheaperinference, kilocode, opencode/zen, orcarouter,
  raycast, tinycms, lmarena.
- AGY/Antigravity catalogs, aliases and free-model catalog.
- Fallback services: conol, githubCopilot, notionWeb, promptql.
- CLI tool model aliases and modelAliasSeed.
- modelFamilyFallback chains for the 3.8 Flash family.
- geminiRateLimits.json.

Tests: t28, models-catalog-route, model-capabilities-registry,
pricing-ag-flash-tiers, agy-gemini-3696-tier-passthrough and
model-family-fallback-notation all pass. npm run typecheck:core
and npm run lint pass.

(cherry picked from commit 7e44a6d)
(cherry picked from commit c8391ff)

* fix(chat): return resource pressure guard as a normal result and avoid cooldown

executeChatWithBreaker previously returned { localResourcePressureResult }
when the resource pressure guard tripped, but the caller in chat.ts only
destructures { result, tlsFingerprintUsed } and immediately reads
result.success. That caused a TypeError ('Cannot read properties of undefined
(reading "success")') and made combo fallback logs show a confusing crash.

Now the pressure guard is returned as a normal { result } with
errorCode/errorType "resource_pressure", and chat.ts surfaces the 503
immediately without marking the connection unavailable.

(cherry picked from commit 22b641e)
(cherry picked from commit ee2af6f)

* ops(docker): raise runtime heap to 4096MB and pin tls-client native version

OMNIROUTE_MEMORY_MB 1024MB was too tight for production traffic with large
fusion-combo panels, causing constant GC pressure and PSI CPU/memory critical
states. Raise the default runtime heap ceiling to 4096MB.

Also pin TLS_CLIENT_VERSION=1.15.1 so the tls-client-node postinstall does not
fetch an upstream "latest" release that may be missing the Linux .so asset.

(cherry picked from commit db021d9)
(cherry picked from commit 8583cfe)

* fix(providers): harden empty responses and oauth test classification

- stream: inject a space instead of empty text for the synthetic Claude
  empty-response fallback so clients do not trip on zero-length
  completions (502 noise seen in production call_logs).
- providers/test: return statusCode 400 for unsupported provider tests;
  add custom-oauth-connection-proxy to OAUTH_TEST_CONFIG (checkExpiry
  only — the proxy has no userinfo endpoint).
- resolveRoutingModel: default subagent routing model claude-sonnet-5
  -> claude-haiku-4.5 to reduce pressure on the antigravity lane.

(cherry picked from commit c219b43)
(cherry picked from commit 2892e9d)

* feat: port subagent routing + tool-call shim + devin/transport hardening from local production

(cherry picked from commit 2cb7b19)

* test: fix gemini-to-claude usage test fixture, drop repairedRaw tests

(cherry picked from commit 53462ff)

* chore: prune unused eslint suppressions

(cherry picked from commit 8515409)

* fix(docker): relax healthcheck to tolerate event-loop stalls under load

The 5s timeout / 3 retries / 15s start-period was too strict for this
single-process app: /healthz probes timed out during startup module
loading and under sustained heavy /v1/messages traffic (~180k-token
requests), flipping the container unhealthy and causing Docker/Dokploy
to kill mid-session — the observed restart loop / 502 source.

start-period=120s covers cold start; timeout=15s + retries=5 tolerate
transient event-loop stalls (~150s) while still catching dead processes.

(cherry picked from commit c749903)

* fix(devin-desktop): accumulate id-less tool-call argument deltas

Devin Desktop streams tool-call arguments as separate proto messages
carrying only field 3 (arguments) with no id/name. The decoder dropped
them (`if (toolCall.id)`), so clients received tool_use blocks with
empty input {} — Claude Code then rejected ~50% of tool calls with
InputValidationError, and the empty calls in history taught the model
to emit empty arguments itself, causing a self-reinforcing error loop.

Keep argument-only deltas and append them to the most recently started
tool call so input_json_delta reaches the client.

(cherry picked from commit 8a82a13)

* fix(translator): purge never-succeedable empty tool_use pairs from history

Clients whose history was poisoned by lost tool-call arguments keep
replaying tool_use {input:{}} + InputValidationError pairs, teaching the
model to emit empty arguments on new calls (self-reinforcing loop).

When translating a Claude-format request, drop tool_use blocks whose
input is empty AND whose tool schema declares required params (such a
call could never have succeeded), along with their matching tool_result
blocks and "(empty response)" placeholder texts. Tools with no required
params (e.g. chrome_read_page) and tools absent from the tools list are
left untouched.

(cherry picked from commit 164c61d)

* fix(review): adversarial-review hardening + deterministic stream error status

Code-review patches:
- chatDispatch: detect resource_pressure via typed error fields (dead
  localResourcePressureResult check evicted session affinity wrongly)
- chat: return resource-pressure 503 before safeLogEvents so it is not
  logged as an upstream error; fix stale "retrying once" log message
- stop.mjs: share isTestEnvironment() guard across POSIX and Win32 kill paths
- supervisor: mkdir log dir before createWriteStream (ENOENT crash)
- resolveRoutingModel: trim SUBAGENT_DEFAULT_MODEL env (whitespace-safe)
- claudeHelper: drop string-content "(empty response)" messages; normalize
  Claude Code file-unchanged sentinel for non-Anthropic Claude-shape targets
- toolCallShim: AskUserQuestion option objects emit only schema fields,
  question arrays join cleanly, options-only calls get a fallback question
- devin-desktop: sanitizeDevinPrompt scoped to system/developer roles only;
  document parallel tool-call arg-delta attribution limitation
- Dockerfile: healthcheck start-period 120s -> 60s (faster dead-detection,
  stall tolerance kept via timeout/retries)

Compact-incident fix (session log analysis):
- streamReadiness: a stream ending with only a deterministic upstream
  diagnostic (content policy / safety filter) now surfaces its real 400
  status instead of a retryable 502 STREAM_EARLY_EOF — stops the observed
  ~9x/10min retry loop on payloads that can never succeed
- sameAccountTransportRetry: 403 only retryable for the Devin
  permission_denied-internal-error quirk; content-policy text never retries
- test: bound assertion follows SAME_ACCOUNT_TRANSPORT_RETRY_MAX

(cherry picked from commit e37ae2b)

* fix(translator): error event for upstream-truncated tool-call arguments

When the upstream stream ends mid-tool-call (observed: Devin Desktop Connect
streams cut during argument deltas), the accumulated arguments buffer is
unparseable JSON. Closing the tool_use block normally handed the client a
truncated partial_json - Claude Code raised InputValidationError, and the
model retried with hallucinated stub args (seen across recent session logs).

The openai-to-claude finish path now validates each non-shimmed tool call's
argBuffer and emits a terminal error event instead of closing the block, so
the turn is retried rather than executed as a truncated command. Shimmed
tools are exempt (their shim repairs the buffer at close).

(cherry picked from commit 4dca2f3)

* feat(translator): schema-driven tool-call argument repair

Models that emit wrong-typed fields, extra keys, or missing required args hit
client-side InputValidationError retry loops (observed across recent Claude
Code session logs). Add a generic sanitizer that repairs parsed tool arguments
against the client-declared input_schema: coerce unambiguous type mismatches
(stringified numbers/booleans, scalar to array, JSON-stringified objects),
drop undeclared keys on closed schemas, fill missing required fields with
type-correct empties - nested and depth-capped.

openai-to-claude now buffers arguments for any tool with a declared schema
(previously only named-shim tools) and emits one corrected input_json_delta at
block close. The finish path also validates every accumulated argBuffer and
emits a terminal error event when upstream truncated the JSON mid-call, so the
turn is retried instead of executing a cut command.

chatCore now passes whichever body still carries tools into the stream state -
executor paths that consume/re-key the tools array (e.g. Devin Desktop) left
the dispatched body without client schemas.

(cherry picked from commit 84754f5)

* fix(devin-desktop): encode CompletionConfig to stop silent 1024-token output cap

The Devin Desktop Connect GetChatMessage wire was missing the
CompletionConfiguration sub-message (field diegosouzapw#8). Without it, the upstream
server defaulted output to ~1024 tokens for swe-1-7, which truncated
Claude Code multi-tool turns (e.g. five Agent calls), produced empty
Agent payloads, and led to the mid-response server error.

Encode field diegosouzapw#8 with the OpenAI request's max_tokens (default 8192),
temperature clamped to >=0.001, top_p, and top_k. Set max_newlines to
a safe context-window value so it cannot accidentally become the binding
output cap. Log the resolved max_tokens for monitoring.

Update the golden fixture and add a focused test for custom values and
the temperature=0 clamp.

(cherry picked from commit 62effb4)

* feat: add Kiro PWA bridge and websocket proxy support

Publish the Kiro OAuth/PWA bridge flow together with websocket proxy scripts and route updates so the branch can be shared for iteration before full test validation completes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit 056a413)

* feat(kiro): implement automatic account deactivation on quota exhaustion

(cherry picked from commit 6a98790)

* fix(health): add public /api/health route for external liveness probes

- Add '/api/health' to PUBLIC_READONLY_API_ROUTE_PREFIXES so the
  auth pipeline classifies it as public (GET-only).
- Create src/app/api/health/route.ts with a lightweight { status, timestamp }
  response, parallel to the existing /api/health/ping handler.

External healthcheck/monitoring services probing /api/health were
returning HTTP 401 (AUTH_001) because the route was not in any public
prefix list. /api/monitoring/health was public but /api/health was not.

(cherry picked from commit 6dac784)

* feat: implement Freebuff CLI emulator and provider meta-service for connection and quota management

(cherry picked from commit 1ac3383)

* feat(freebuff): flip default from opt-in to opt-out

Previously the Freebuff provider required FREEBUFF_ENABLED=1 to be
exposed under /providers/freebuff. This commit flips the default so the
provider is on by default (no env var required) and FREEBUFF_ENABLED=0
disables it explicitly.

Changes:
- base.ts: isFreebuffEnabled() returns true when env var is unset,
  empty, or any value other than 0/false.
- registry.ts: error messages reference FREEBUFF_ENABLED=0 instead of =1.
- en.json / fr.json: notEnabled.description updated to match.
- .env.example + docs/providers/freebuff*.md: documentation reflects the
  new default and the disable-by-setting-FREEBUFF_ENABLED=0 flow.
- freebuff-base.test.ts + freebuff-registry.test.ts: tests for the new
  default behaviour.

(cherry picked from commit 050a34c)

* fix(security): resolve freebuff public client credential and satisfy db re-export gate

(cherry picked from commit a5b141f)

* fix(review): address PR #1 review threads

- chore(changelog): drop four stale fragments already in CHANGELOG (diegosouzapw#9435,
  diegosouzapw#9550, diegosouzapw#9568, diegosouzapw#9575) — stale-fragment gate exits nonzero on duplicate
- fix(translator/claudeHelper): re-filter empty messages after the Pass 1.4
  nameless-tool_use drop so non-final assistant messages whose only content
  was a nameless block do not survive into the request (#5, #6)
- fix(ws/responses-ws-proxy): serialize concurrent forwardClientMessage calls
  via dispatchQueue so a second client message arriving during ensureUpstream
  does not drop the late frame (diegosouzapw#7)
- fix(ws/v1-ws-bridge): close the session with code 1002 after a frame decode
  failure so an undecodable byte run does not loop forever (diegosouzapw#8)
- fix(ws/both): send the WebSocket close frame BEFORE flipping this.closed
  so the sendFrame guard does not turn the close handshake into a no-op
  (diegosouzapw#9)
- fix(oauth/freebuff): add 'network_error' to freebuffPollResponseSchema so
  the network-error return path typechecks (diegosouzapw#10)
- fix(oauth/freebuff): declare onProgress on FreebuffPollOptions so the
  poll-loop callback typechecks (diegosouzapw#11)
- fix(freebuff/cliEmulator): add readonly ok to FreebuffHttpResponse and
  populate it in tlsClientFetch, wreqFetch, and globalFetch so successful
  responses are not misclassified as failures (diegosouzapw#12)
- test(translator/openai-to-claude): defer content_block_start until a
  name arrives and throw on a permanently nameless streamed call so the
  Muse/GLM diegosouzapw#2077 sequence and the nameless-rejection test both pass (diegosouzapw#16)
- test(9568): resolve the three merge-marker regions in favor of HEAD —
  the test reflects the post-diegosouzapw#10392 translator behavior (diegosouzapw#13, diegosouzapw#14, diegosouzapw#15)

* fix(review): address PR #1 review round 2

- docs(freebuff-api): replace nonexistent provider-specific routes with the
  generic [provider]/{models,limits,chat,embeddings,images} surface so the
  doc no longer documents 404-bound URLs (#1)
- fix(translator/claudeHelper): scope the '(empty response)' placeholder
  drop to placeholders whose adjacent message actually had a paired
  tool_use/tool_result that this pass dropped; previously every historical
  placeholder was removed whenever any declared tool had required fields,
  silently deleting unrelated content and reordering turns
- fix(translator/openai-to-claude): suppress input_json_delta passthrough
  while content_block_start is pending; replay the accumulated argBuffer in
  one shot after the late name arrives so the diegosouzapw#2077 Muse sequence produces
  a single start event with the full argument history instead of orphan
  deltas (diegosouzapw#16 follow-up)
- fix(guardrails/visionBridge): a mixed combo (some targets lack vision)
  now returns 'process' instead of 'skip' so images are described before
  fan-out; matches the contract test at
  tests/unit/guardrails/visionBridge-combo-reroute.test.ts:147
- refactor(freebuff/httpClient): remove the tls-client-node fallback — it
  is not a declared dependency and tests/unit/tls-client-wreq-residue.test.ts
  asserts it stays absent; only wreq-js + global fetch remain as backends
- fix(publicApiRoutes): remove '/api/health' from the CORS-relaxed list —
  the file's own comment says it must stay only in
  PUBLIC_READONLY_API_ROUTES_EXACT to avoid widening CORS
- feat(pwa-register): wire shouldEnablePwaRegistration and
  isOmniRouteServiceWorkerScript into PwaRegister; loopback hostnames now
  unregister only matching OmniRoute workers rather than every worker and
  every cache entry
- fix(oauth/kiro): extend CREDITS_EXHAUSTED_SIGNALS with Kiro-specific
  patterns ('usage limit exceeded', 'ThrottlingException') so the
  auto-deactivate path detects Kiro quota exhaustion; anchored on
  Kiro-distinguishing language to avoid broadening generic 429
  classification
- test(oauth-kiro-poll): drop 'as any' on connections[0] in favor of the
  inferred Record<string, unknown> from getProviderConnections

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Thinh <thinh0704hcm@users.noreply.github.com>
Co-authored-by: Vonic <phanquochoipt@gmail.com>
Co-authored-by: Aminetwiti <93622642+Aminetwiti@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Moutia <114811455+Moutia-Ben-Yahia@users.noreply.github.com>
Co-authored-by: amine <amine@TweeDev.localdomain>
Co-authored-by: Mohamed Benyahia <benyahiamohamd@gmail.com>
Co-authored-by: Raudbjorn <Raudbjorn@users.noreply.github.com>
diegosouzapw added a commit that referenced this pull request Sep 15, 2026
…LOCAL_ONLY (#13745)

GHSA-35fw-cv32-2373 and GHSA-jx89-f37j-pq89 — the same defect class as
/api/acp/agents (GHSA-hf57): a route whose handler chain spawns a host process
was classified Tier 3 MANAGEMENT only, and requireManagementAuth() waives auth
when requireLogin=false. Hard Rules #15/#17 require the LOCAL_ONLY gate, which
runs on the stamped real peer before any auth check.

cli-tools (GHSA-35fw): 14 routes reach
getCliRuntimeStatus() -> locateCommand() -> runProcess("sh", ["-c",
'command -v -- "$1"']) -> spawn(), exactly like their six gated siblings
(forge/grok-build/jcode/qwen/omp/letta-settings):
all-statuses, status, and the claude/cline/codewhale/codex/crush/deepseek-tui/
droid/kilo/openclaw/pi/smelt-settings routes. The advisory counted 13; it
missed /api/cli-tools/detect, which is heavier — detectAllTools() runs
execFile(binary, ["--version"]) and execFile("which") per tool.

skills (GHSA-jx89): POST /api/skills/install stores the request's handlerCode
verbatim as the skill handler with no allowlist, so a value equal to a built-in
name (execute_command / eval_code) aliases the real sandboxed built-in;
POST /api/skills/executions then runs it. The sandbox is a real container, but
the spawn is transitive, which is why the 6A.8 source scan never flagged it.

Entries are exact paths, not a /api/cli-tools/ blanket prefix: apply, backups,
config, guide-settings, hermes-agent-settings, keys, logs, openclaw/auto-order
and codex-profiles do not spawn and remote dashboards use them. All 16 are
mirrored into SPAWN_CAPABLE_PREFIXES (no manage-scope bypass) and added to the
route-guard-membership roots so the gate enforces them from now on.

Functional trade-off, same one already accepted for grok/forge/jcode/qwen: a
dashboard served through a tunnel no longer shows the CLI Tools status badges.

Tests are red-first. Two existing negative controls pointed at routes that turn
out to spawn (/api/cli-tools/all-statuses, /api/skills/install); they now point
at routes that genuinely do not (/api/cli-tools/config, /api/skills/marketplace,
/api/skills/skillssh/install), so the non-over-gating assertions are kept.
diegosouzapw added a commit to HouMinXi/OmniRoute that referenced this pull request Sep 16, 2026
…apw#15/diegosouzapw#17)

runManagedDbHealthCheck() now forks native diagnostics into a child
process via healthCheckRunner.ts. Every process-spawning route must be
classified isLocalOnlyPath() so a leaked JWT via tunnel cannot trigger
process spawning, matching the /api/db-backups/exportAll precedent.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw pushed a commit that referenced this pull request Sep 16, 2026
Merged after a maintainer rework that kept every one of @HouMinXi's commits intact.

**What the rework added on top of the contribution:** the new DB health-check behaviour is gated behind a default-off feature flag (`src/shared/constants/featureFlagDefinitions.ts`, `defaultValue: "false"`), documented in `docs/reference/FEATURE_FLAGS.md` with the description key carried into all 66 locales, so the release default is unchanged and the new bounds only apply when an operator opts in. The optional-FTS5 migration set was reconciled by hand with the "180" entry that landed meanwhile (`src/lib/db/migrationRunner/constants.ts`).

**Carried from your rebased head:** the `/api/db/health` local-only classification in `src/server/authz/routeGuard.ts` plus its `routeGuard` assertion — `runManagedDbHealthCheck()` forks native diagnostics into a child process, so Hard Rules #15/#17 apply. Re-verified here: 37 pass / 0 fail.

Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thank you for the depth of this one — the resource-bounds suite and the sql.js startup/backup coverage are the kind of tests that keep a database layer honest.
PauloFH added a commit to PauloFH/OmniRoute that referenced this pull request Sep 22, 2026
Two Fast Quality Gates failures on diegosouzapw#12400.

`check:test-discovery` reported all three new suites as orphans: the unit
runner's subdirectory glob is an explicit allowlist
(`tests/unit/{api,auth,authz,...}/**`) and `deploy` is not in it, so nothing
under `tests/unit/deploy/` was ever collected. The suites passed locally and in
no CI job — the exact failure mode that gate was added for after the 2026-06-09
audit found ~135 orphaned tests, one of them a Hard Rules diegosouzapw#15/diegosouzapw#17 check whose
asserts were already failing unnoticed.

Moved the three files to `tests/unit/` rather than widening the allowlist:
adding a directory there changes the runner for every contributor, which is not
this PR's business. Import depths adjusted; the fourth suite was already at the
collected level.

`check:complexity-ratchets` flagged `buildDeployment` at 83 lines against a
limit of 80. The pod template is almost entirely the container object, so that
object moved to `buildContainer`. No behaviour change — the generator's output
is byte-identical, which the parity test against deploy/kubernetes/base pins.

Both gates verified locally: test-discovery OK, complexityNewCode=0.
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…loopback-only) + dashboard UI (diegosouzapw#5939)

* feat(discovery): Phase 2 reporter — discoveryResults DB module + service wiring

Adds src/lib/db/discoveryResults.ts (CRUD over the discovery_results table
from migration 074) and wires the opt-in discovery service to persist and read
findings through it: persistDiscoveryResult / getDiscoveryResults /
getDiscoveryResultById / markVerified / deleteDiscoveryResult, with
(provider, method, endpoint) upsert de-duplication. Re-exported from localDb.

The service stays opt-in / default-off. The /api/discovery/* routes and the
dashboard UI tab are intentionally deferred to Phase 2b — they need the
local-only enforcement model (Hard Rules diegosouzapw#15/diegosouzapw#17 territory) decided first.

TDD: tests/unit/db/discovery-results.test.ts (8 cases, DB + service delegation),
isolated DATA_DIR with resetDbInstance cleanup.

* feat(discovery): Phase 2b — /api/discovery/* routes (strict loopback-only)

Adds the discovery HTTP surface on top of the reporter DB module:
  GET    /api/discovery/results            list findings (optional ?providerId)
  GET    /api/discovery/results/:id        one finding (404 if absent)
  DELETE /api/discovery/results/:id        delete a finding
  POST   /api/discovery/scan               scan a provider + persist findings
  POST   /api/discovery/verify/:id         mark a finding verified

Authorization: strict loopback-only. "/api/discovery/" is added to
LOCAL_ONLY_API_PREFIXES so the central authz pipeline (proxy.ts →
runAuthzPipeline → managementPolicy) rejects non-loopback callers with a 403
LOCAL_ONLY before any handler runs. It is deliberately NOT in
LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES — no remote manage-scope bypass —
because POST /scan issues outbound probes to provider endpoints (SSRF-adjacent)
and must never be tunnel-reachable. Handlers also call requireManagementAuth
(defense in depth) and return sanitized errors via createErrorResponse.

Tests:
- tests/unit/authz/discovery-routes-local-only.test.ts (8) — security guard:
  isLocalOnlyPath true + not manage-scope-bypassable for all four paths.
- tests/unit/api/discovery-routes.test.ts (6) — handler integration over an
  isolated DATA_DIR: list/filter, by-id 200/404/400, scan persist + 400 on
  empty/malformed body, verify 200/404, delete 200/404, no stack-trace leak.

* feat(discovery): Phase 2c — dashboard UI tab (Tools → Discovery)

Adds the /dashboard/discovery page (DiscoveryPageClient) that consumes the
Phase 2b /api/discovery/* routes: scan a provider, list findings, verify or
delete them. Registered in the sidebar under the Tools group (icon
travel_explore) and given a "discovery" i18n namespace + sidebar keys in
en.json (other locales fall back to en via next-intl until synced — the
locale files are in a pre-existing coverage deficit unrelated to this change).

Registers the UI test path in vitest.config.ts (advisory ui suite).

Tests: src/app/(dashboard)/dashboard/discovery/__tests__/DiscoveryPageClient.test.tsx
(3 cases: loads+renders results, empty state, fetches /api/discovery/results on
mount; stable useTranslations mock to avoid the fetch-loop). NOTE: the ui vitest
suite cannot run in this workspace — @testing-library/dom (a @testing-library/
react peer dep) is absent from node_modules, which fails ALL existing ui tests
equally; the test runs in CI. Component verified locally via typecheck + lint.

* test(discovery): register discovery-routes-local-only in stryker tap.testFiles

The mutation-test-coverage gate (--strict) flags any unit test covering a
mutated module that isn't listed in stryker.conf.json tap.testFiles. This PR's
tests/unit/authz/discovery-routes-local-only.test.ts covers src/server/authz/
routeGuard.ts (a mutated module, which this PR edits by adding the
/api/discovery/ local-only prefix), so it must be registered for its mutant
kills to count. No behavior change.

* refactor(discovery): split DiscoveryPageClient to satisfy max-lines-per-function

The complexity ratchet (max-lines-per-function: 80) flagged the single
184-line DiscoveryPageClient function (+1 over baseline). Extract the data
layer into two hooks (useDiscoveryResults for list/loading/feedback,
useDiscoveryActions for scan/verify/delete), a shared callApi helper, and two
presentational sub-components (DiscoveryScanForm, DiscoveryResultCard). Every
function is now under the 80-line ceiling; complexity gate back to baseline
1995. No behavior change — same exported component, same endpoints, same props.

* test(sidebar): include discovery in omni-proxy item-order snapshot

Adding the Discovery item to the Tools group (this PR's sidebar entry) extends
the ordered omni-proxy section list. Update the exact-match deepEqual snapshot
in sidebar-visibility.test.ts to include "discovery" in its position (after
traffic-inspector). The assertion stays exact — this reflects the intentional
new item, it does not weaken the check.

* docs(changelog): restore release bullets eaten by merge auto-resolve; re-add discovery bullet additively

* chore(quality): bump testFrozen for translator-openai-responses-req.test.ts (1097 -> 1172)

Base-red inherited from diegosouzapw#5933, which grew the test file to 1171 lines
(Hard Rule diegosouzapw#18 regression tests) without adjusting the frozen cap. The
release tip itself fails check:file-size; this unblocks every PR into
release/v3.8.44. File untouched by this PR.

* chore(quality): restore stryker tap.testFiles entries eaten by merge auto-resolve

The merge of origin/release/v3.8.44 silently dropped the 3 entries added
on the release side (diegosouzapw#5903, clinepass, diegosouzapw#5923). Took the release version
verbatim and re-added only this PR's entry (discovery-routes-local-only)
in alphabetical order. check:mutation-test-coverage green locally.

* chore(quality): reconcile inherited v3.8.44 merge-burst drift + include discovery in tools-group order test

- complexity 1995->2003 and cognitive 856->859: both measure IDENTICAL on
  the pristine release tip (947a0b0) and this PR's merged HEAD — the PR
  is complexity-net-zero; drift is from the 2026-07-02 merge burst
  (notes added to both baselines, same family as prior reconciliations).
- sidebar-tools-group.test.ts: append 'discovery' to the expected
  TOOLS_GROUP order — the intentional new sidebar item this PR adds
  (same expected-value update already made in sidebar-visibility.test.ts).
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…ecar supported) (diegosouzapw#4649)

Integrated into release/v3.8.37 — headroom proxy lifecycle (status/start/stop, local-only + spawn-capable per Rules diegosouzapw#15/diegosouzapw#17). Cherry-picked onto release tip; lifecycle 7/7 + route-guard 43/43 + check:cycles green.
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…te-guard) (diegosouzapw#5070)

The Cursor auto-import route runs execFile("which", ["cursor"]) to verify a
local Cursor install before importing credentials — a child-process spawn. The
check:route-guard-membership gate (Hard Rules diegosouzapw#15/diegosouzapw#17) flagged it as an
unclassified spawn-capable route: reachable past the loopback gate, an
RCE-via-tunnel surface (a leaked JWT over a tunnel could trigger the spawn).

Classify the specific path in LOCAL_ONLY_API_PREFIXES so loopback enforcement
runs unconditionally before any auth check. Scoped to the exact path — the rest
of /api/oauth/ (browser redirect/callback flows) stays remote-reachable.

TDD: added a failing-then-passing assertion in route-guard-local-prefix.test.ts
(classification + an over-broadening guard proving sibling OAuth paths stay
remote). check:route-guard-membership now reports 0 new gaps.
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…ment guardrails (Phases 0-6) (diegosouzapw#3471)

* feat(quality): generic ratchet comparator (multi-metric, regression-only)

* chore(ci): Fase 0 quality-gate fixes — reconcile coverage gate (40->60), tier npm audit, wire orphaned contract gates, re-enable cheap husky pre-commit

* feat(quality): ratchet engine (collector + frozen baseline + CI job) and provider-consistency gate

- collect-metrics.mjs: emits quality-metrics.json (ESLint warnings + coverage when present)
- quality-baseline.json: frozen baseline (eslintWarnings=3482, regression-only)
- ci.yml: quality-gate job (ratchet + step summary + artifact) and check:provider-consistency in lint job
- check-provider-consistency.ts: every REGISTRY id must be a canonical provider (found krutrim half-registered → allowlisted as known pre-existing, blocks any NEW orphan)
- TDD: 9 tests (5 ratchet + 4 provider-consistency)

* feat(quality): Fase 2 anti-hallucination gates — fetch-targets, openapi-routes, deps allowlist

- check-fetch-targets: every dashboard fetch(/api/...) resolves to a real route.ts; found 7 pre-existing dashboard->route mismatches frozen as KNOWN_MISSING for triage
- check-openapi-routes: every openapi.yaml path resolves to a real route; found 1 stale spec entry (agent-bridge agents/{id}/state) frozen as KNOWN_STALE_SPEC
- check-deps: anti-slopsquatting allowlist (105 deps); new deps need explicit human-reviewed entry
- all wired into CI lint/docs jobs; TDD +12 tests (21 total across 5 gates)

* docs(quality): add quality-gates report + implementation plan to repo root

* feat(quality): Fase 3a — file-size ratchet (freeze 91 files >800 LOC, cap 800 for new)

- check-file-size.mjs: frozen files can only shrink; new files must be <= cap (kills the next 12k-line god-component)
- file-size-baseline.json: 91 files frozen at current LOC (largest 12883)
- wired into CI lint job; TDD 5 tests; --update ratchets the baseline down on shrink

* feat(quality): Fase 3b — duplication ratchet (jscpd@4, baseline 5.72%)

- check-duplication.mjs: runs jscpd@4 (pinned; v5 is an incompatible Rust rewrite) over src+open-sse, fails if duplication % rises vs frozen baseline (5.72%, measured: 1358 clones / 22967 dup lines). Targets the executor copy-paste (48/50 override execute() wholesale)
- wired into the parallel quality-gate CI job (off the lint critical path); TDD 4 tests; --update ratchets down
- snapshot now complete: coverage ~82.6%, eslint 3482 (98.5% no-explicit-any), duplication 5.72%, 91 files >800 LOC

* feat(quality): Fase 4a — anti test-masking gate

- check-test-masking.mjs: for each MODIFIED test file in a PR, flags net assert removal + new assert.ok(true) tautologies (base...HEAD diff). Directly enforces CLAUDE.md 'never weaken asserts to go green'
- wired into pr-test-policy CI job (reuses base fetch); no-op outside PR; TDD 5 tests

* feat(quality): Fase 4b — coverage ratchet (conservative floors, CI consumes merged coverage)

- quality-baseline.json: coverage.{statements,lines,functions,branches} floors (80/80/82/73, real ~82.58/82.58/84.23/75.22 with margin; tighten via --update after a green main run)
- check-quality-ratchet.mjs: --allow-missing (local quality:gate skips coverage.* without a coverage run; CI runs strict)
- ci.yml quality-gate job: needs test-coverage + downloads merged coverage-report so the ratchet enforces 'coverage cannot drop'
- TDD +1 test (6 total)

* feat(quality): Fase 6 — 8 new gates (Rule diegosouzapw#11/diegosouzapw#12, migrations, known-symbols, route-guard, complexity, docs-symbols, db-rules)

Deterministic gates, each freezing pre-existing violations in a documented allowlist (ratchet) so they pass now and block only NEW regressions:
- check-error-helper (Rule diegosouzapw#12): 7 executors/handlers forwarding raw err.message frozen
- check-public-creds (Rule diegosouzapw#11): 5 literal client_ids (Claude/Codex/Qwen/Kimi/Copilot) frozen
- check-migration-numbering: gaps 026/055 + dup 041 frozen (prevents the git-rm-deleted-migration incident)
- check-known-symbols: 93 executors conformance + 15 combo strategies + 18 translator pairs
- check-route-guard-membership (diegosouzapw#15/diegosouzapw#17): all 25 spawn-capable routes verified local-only (0 gaps)
- check-complexity: cyclomatic>15 / fn-length>80 ratchet (baseline 1739)
- check-docs-symbols: 30 stale doc /api refs frozen (docs hallucination)
- check-db-rules (diegosouzapw#2/diegosouzapw#5): 25 unexported db modules + 15 raw-SQL routes frozen
Wired into CI (lint / docs-sync-strict / quality-gate jobs). 115 TDD tests, all green. ESLint ratchet held at 3482.

* docs(quality): Phase 7 plan (security/dead-code/mutation/community tooling) — GATED to 2026-06-16

Stored, not active. 7 suggested gates + all discussed OSS/Community tools (SonarQube Community + osv-scanner + CodeQL + knip + sonarjs + type-coverage + lockfile-lint + Stryker + size-limit + axe-core + semcheck + agent-lsp + Qlty). Activation gate: do not start before 2026-06-16 (use Phases 0-6 in production for 1 week, validate in practice, then evolve).
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…ired, 2 production bug fixes, vitest in CI (diegosouzapw#3536)

check-test-discovery gate (TDD; 195 orphans found, 135 re-wired into the node runner, 60 frozen+annotated). Triage fixed 2 real production bugs: missing BYPASS_PREFIX_NOT_ALLOWED zod refine (spawn-capable prefixes accepted into the bypass list, Hard Rules diegosouzapw#15/diegosouzapw#17) and resetDbInstance not firing stateReset resetters (stale schema memo → 503 instead of 403; also hit backup-restore). New test-vitest CI job: test:vitest blocking (146/146), test:vitest:ui informational (14 pre-existing fails, triage 2026-06-16).
diegosouzapw added a commit to mrnasil/OmniRoute that referenced this pull request Sep 24, 2026
…-shell install

Add /api/cli-tools/whycodes-settings to LOCAL_ONLY_API_PREFIXES,
SPAWN_CAPABLE_PREFIXES and the route-guard membership audit (Hard Rules
diegosouzapw#15/diegosouzapw#17), replace the curl|bash install step with a link to the vendor
docs, and move the whycodes catalog test to a collected path.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants