Open source · MIT · runs offline · Laravel & NestJS

Your AI said it followed your architecture. Check.

NoBypass fails the pull request when generated code works but skips the layers your team approved — a query in a controller, a repository injected where only a service belongs, a catch that swallows everything.

npx nobypass init

Static analysis only. No LLM, no API key, no account, no upload.

Controller Support Service Repository Model BND002 blocked
User::where() in a controller works. It also skips three approved layers.

Tests pass

The endpoint returns the right JSON. Nothing in the test suite cares which layer ran the query.

The linter passes

Formatting, types and unused imports are fine. Linters don't know your layering.

The architecture drifted

Ten agent sessions later, half the controllers talk to the database and the service layer is decoration.

Same feature, two versions

Real output from the fixtures in the repository. Switch the framework, then compare what the agent wrote with the fix.

app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreUserRequest;
use App\Models\User;
use App\Repositories\UserRepository;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;

class UserController extends Controller
{
    public function __construct(
        private readonly UserService $service,
        private readonly UserRepository $repository,
    ) {
    }

    public function index(): JsonResponse
    {
        // "Quick fix" from an AI agent: query straight from the controller.
        $users = User::where('active', true)->orderBy('name')->get();

        return response()->json($users);
    }

    public function store(StoreUserRequest $request): JsonResponse
    {
        return response()->json($this->service->register($request->validated()), 201);
    }
}
terminal
$ npx nobypass check

✖ BND003 error app/Http/Controllers/UserController.php:14
  controller depends directly on service: App\Services\UserService
  expected: service is reached only through controller → support → service
  fix:      Call the support layer instead and let it reach service.

✖ BND001 error app/Http/Controllers/UserController.php:22
  controller depends directly on model: App\Models\User

✖ BND002 error app/Http/Controllers/UserController.php:22
  controller performs data access: Eloquent query User::where()
  expected: Data access only in repository, reached via
            controller → support → service → repository
  fix:      Move the query into the repository layer.

⋮ 6 more findings in this fixture

9 files analyzed · 7 new errors · 2 new warnings · 0 known
✖ FAIL — 7 blocking findings.

Discover → Review → Approve → Enforce

NoBypass never decides your architecture for you. It proposes, you approve, then it holds every change to that decision.

  1. 01

    Discover

    init reads composer.json, PSR-4, package.json and tsconfig paths, starts from a preset and writes a proposal. It never treats the most common existing pattern as correct.

  2. 02

    Review

    discovery show lists the evidence: layers, coverage, ambiguous files and every place existing code contradicts the proposal.

  3. 03

    Approve

    approve is interactive only. A human signs off on .nobypass.yml; CI and agents can't.

  4. 04

    Enforce

    check --since origin/main fails only on violations new to the branch. Legacy debt goes into a reviewed baseline.

The contract is a file you can review

Layers are globs. allowed_direct lists which layer may depend on which — directly. A controller reaching the model through support, service and repository is fine. Importing it is not.

Unknown keys are rejected, exceptions need a written reason, and inline waivers exist only for the warning-level catch rule.

layers:
  controller: ["app/Http/Controllers/**/*.php"]
  support:    ["app/Support/**/*.php"]
  service:    ["app/Services/**/*.php"]
  repository: ["app/Repositories/**/*.php"]
  model:      ["app/Models/**/*.php"]
dependencies:
  allowed_direct:
    controller: [support]
    support:    [service]
    service:    [repository]
    repository: [model]
    model:      []
rules:
  direct_orm_access:
    deny_in:  [controller, support, service]
    allow_in: [repository]
  catch_policy:
    discourage_in: [service, repository]
    severity: warning

Seven rules, all deterministic

Every finding names the file, line, layers, contract edge, approved route and a concrete fix.

RuleCatchesDefault
BND001Forbidden direct dependency between layerserror
BND002Eloquent, DB::, Prisma or TypeORM access outside the approved layerserror
BND003Skipping an intermediate boundary — controller → service when the route is controller → support → serviceerror
BND004Dependency cycle between layerserror
EXC001catch where the contract discourages it, without a documented purposewarning
CFG001In-scope file that matches no layer, or severalwarning
ANL001File that couldn't be parsed or reference that couldn't be resolved — never a silent passerror

Agents get the rules. CI gets the last word.

An instruction file can be ignored. A required check can't. NoBypass does both.

nobypass agents generate --skill

Adds a delimited block to AGENTS.md (asking first if the file exists) and writes a portable SKILL.md. Works with any agent that reads AGENTS.md or Agent Skills.

Before you report a task as finished:
1. Run `npx nobypass check --since origin/main`.
2. If it fails, fix the violations (move code to
   the approved layer; don't work around the rule).

Never edit `.nobypass.yml`, the baseline or
`exceptions:` to make the check pass.

GitHub Actions + SARIF

Run it on every pull request, upload SARIF for inline annotations, make it a required check, and protect the contract with CODEOWNERS.

- uses: actions/checkout@v4
  with: { fetch-depth: 0 }
- run: npx --yes nobypass@0.1.0 check
    --since origin/${{ github.base_ref }}
    --format sarif --output nobypass.sarif
- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with: { sarif_file: nobypass.sarif }

What it will not claim

NoBypass proves that detectable violations are absent. That is narrower than "your code is correct", on purpose.

Read all known limitations →

Five minutes to the first check

Node 20 or newer. Nothing else to install.

  1. npx nobypass initWrite a proposal and a discovery report. Source files untouched.
  2. npx nobypass discovery showReview layers, coverage and contradictions.
  3. npx nobypass approveEdit the proposal if needed, then approve it yourself.
  4. npx nobypass agents generate --skillTell your agents about the contract.
  5. npx nobypass check --since origin/mainFail only on what's new.