Pre-commit humanizer for AI-shaped code.
Deterministic detection on every commit — API calls only when it matters.
Español · Quick start · How it works · Configuration · CLI · Development · AGENTS.md (for coding agents)
BypasserAI is a Git pre-commit hook and CLI that:
- Scores staged source files for common AI-generated patterns (no API, deterministic).
- Rewrites files above your threshold through any OpenAI-compatible chat API, guided by the bundled humanizer skill (español).
- Re-stages successful rewrites so the commit uses the humanized version.
It is language-agnostic (JS/TS, Python, Go, Rust, Java, C#, PHP, Ruby, and more) and project-agnostic — install it once per repo, point it at your provider, and forget it until the toast says otherwise.
Scope: Natural Git authorship and code quality — not academic plagiarism evasion. See Limits.
| Free detection | Heuristic scorer runs locally on every commit; no tokens burned on clean files. |
| Smart rewrite scope | Small files → full file; large files → diff hunks or declaration chunks (rewriteScope: auto). Hunks closer than contextLines are merged into a single API call, so overlapping context windows never re-send already-rewritten lines. |
| Safety first | Truncation, indent, structure, and syntax guards; semantic drift review (a second model pass rejects a rewrite that changed behavior); atomic restage with .bak rollback — originals win on doubt. Fragment rejections surface via sanitizerWarning so bypasser stats and --strict see them. |
| Parallel rewrites | Configurable concurrency across staged files (rewriteConcurrency). |
| Optional notifications | Native toast on Windows, shell notify on macOS/Linux; disable with notifications. |
| Any OpenAI-compatible API | OpenAI, Groq, OpenRouter, Ollama, LM Studio, local proxies, etc. |
| Measured detector | Annotated corpus + benchmark (npm run bench) reports precision/recall/F1 and locks scores with a golden snapshot. |
| String/comment masking | Signals run on masked views, so a // in a string or a function inside a regex literal never inflates the score. |
| CI gate | bypasser audit --strict fails the job when an AI-shaped file could not be humanized; reusable GitHub Action included. |
| Audit telemetry | bypasser stats aggregates a structured .bypasser.log.jsonl — success rate by provider/model, rejection reasons, repeat offenders. |
| Live panel in OpenCode | Optional TUI plugin shows the last run, per-family signals, trend and hotspots inside the OpenCode sidebar. |
| Agent-friendly | AGENTS.md documents architecture and invariants for AI coding tools. |
npm install -g bypasser-aiIn your application repo (not inside BypasserAI):
cd /path/to/my-app
bypasser init
export BYPASSER_API_KEY=sk-... # PowerShell: $env:BYPASSER_API_KEY = "sk-..."
bypasser install
git commit -m "test" # hook runs automaticallySet BYPASSER_VERBOSE=1 if you want per-signal detail in hook output.
git commit
│
▼
pre-commit hook → bypasser audit --pre-commit
│
▼
staged files (respecting ignore globs & min changed lines)
│
▼
detector scores FULL file content (0–1, deterministic, no API)
│
├─ score < threshold ──► pass through
│
└─ score ≥ threshold ──► OpenAI-compatible rewrite (humanizer skill)
│
▼
integrity guards + semantic review → restage on success; keep original on failure
│
▼
commit proceeds (hook never blocks on tool/API errors)
Detection always uses the full staged file so a tiny edit in a large AI file still counts. The git diff drives rewrite strategy (full file vs hunks vs chunks), not the score.
- Node.js 18+
- A Git repository
- An API key for an OpenAI-compatible provider (env var recommended)
npm install -g bypasser-ai
# or run without installing: npx bypasser-ai <command>git clone https://github.com/Pacoaldev/BypasserAI.git
cd BypasserAI
npm install
npm run build
npm link # exposes the `bypasser` command globallyAfter changing
src/in this repo, runnpm run buildagain. The hook everywhere executesdist/cli.jsfrom your linked copy — staledist/= stale behavior.
Run inside the repo you want to protect:
bypasser init- Creates
.bypasser.jsonwith sensible defaults. - Does not overwrite an existing config (protects
apiKeyand custom settings). Usebypasser init --forceto regenerate. - Appends
.bypasser.log,.bypasser.log.jsonl,.bypasser.state.json,*.bypasser.tmp, and*.bakto.gitignore.
Example config (omitted keys fall back to runtime defaults in loadConfig()):
{
"baseURL": "https://api.openai.com/v1",
"model": "gpt-4o-mini",
"threshold": 0.65,
"maxTokens": 16384,
"temperature": 0.4,
"ignore": [],
"thresholds": [],
"timeoutMs": 120000,
"timeoutPer1kLinesMs": 30000,
"maxTimeoutMs": 600000,
"maxFileLines": 2000,
"rewriteConcurrency": 3,
"rewriteScope": "auto",
"rewriteFullFileBelowLines": 400,
"contextLines": 60,
"maxChunkLines": 450,
"structuralCheck": true,
"semanticReview": true,
"semanticReviewModel": "",
"semanticReviewMaxLines": 1200,
"notifications": "auto"
}export BYPASSER_API_KEY=sk-...
bypasser installIf a repo already has its own pre-commit workflow, do not install BypasserAI there. Set excluded folder names (basename, lowercase) before install:
export BYPASSER_HOOK_EXCLUDED_REPOS="my-repo,legacy-monolith"
bypasser install # throws inside those directoriesIf the repo uses husky, lefthook, pre-commit.com, or anything else that sets core.hooksPath to a directory other than .git/hooks, bypasser install refuses instead of silently writing a file git will never execute. Add a bypasser audit --pre-commit line to the existing pre-commit workflow, or unset core.hooksPath and re-run install. bypasser uninstall still cleans a legacy bypasser hook from .git/hooks even when core.hooksPath is set — it never touches the foreign hooks directory.
Details: AGENTS.md → Hook install exclusions. bypasser uninstall still removes our hook if it was installed by mistake.
To keep the hook installed but make it a complete no-op in a specific repo — the tool's own source repo, or any repo that ships an equivalent workflow — set disable in that project's .bypasser.json:
{ "disable": true }The audit then scans nothing, rewrites nothing and notifies nothing, and exits 0 so the commit is never blocked or slowed. It short-circuits before the API-key check, so a disabled repo needs no key. BYPASSER_DISABLE=1 forces it on regardless of the file. .bypasser.json is normally gitignored, so this is a per-machine, per-project switch that never leaks into the public source.
| Channel | What you see |
|---|---|
| Hook stdout | Compact per-file score + humanized / ok / skipped / failed |
.bypasser.log |
Timestamped history with fired signals (great tab to leave open) |
| Desktop notification | Clean / Humanized / Rewrite failed (optional; notifications: auto by default, important for humanized + failures only) |
Example log excerpt:
── 2026-09-28 14:32:11 ─────────────────────────────────────
src/utils.ts: 72% [███████░░░] ✓ humanized & re-staged
↳ [naming] Over-descriptive variable names
src/index.ts: 31% [███░░░░░░░] ✓ ok
→ 1 file(s) humanized and re-staged
Manual audit with details:
bypasser audit --verbose
# or: BYPASSER_VERBOSE=1 git commitIf you use OpenCode, a bundled TUI plugin shows your
BypasserAI data live in the right sidebar — last run (files, scores, per-family
signals, delta vs previous run), project history (success rate, trend sparkline,
hotspots) and the project config. It reads .bypasser.log.jsonl locally — no
network, no API key.
node integrations/opencode/install.mjs # or: npm run install-panelThen restart OpenCode. See integrations/opencode/README.md for details, manual install, and uninstall.
| Command | Description |
|---|---|
bypasser init |
Create .bypasser.json (skip if exists) |
bypasser init --force |
Regenerate config from defaults |
bypasser install / uninstall |
Add or remove pre-commit hook |
bypasser detect |
Score staged files |
bypasser detect --all |
Score working tree without staging |
bypasser detect --verbose |
Show fired signals |
bypasser rewrite <file> |
Rewrite one file via API |
bypasser audit |
Detect + rewrite + restage staged files |
bypasser audit --dry-run |
Detect only, no API |
bypasser audit --verbose |
Audit with signal details |
bypasser audit --strict |
Exit non-zero if any AI-shaped file was not humanized (CI gate) |
bypasser stats |
Summarize the structured audit log (.bypasser.log.jsonl) |
| Field | Default | Description |
|---|---|---|
baseURL |
https://api.openai.com/v1 |
OpenAI-compatible endpoint |
model |
gpt-4o-mini |
Chat model name at that endpoint |
threshold |
0.65 |
Rewrite when AI score ≥ this (0–1) |
maxTokens |
16384 |
Max completion tokens per rewrite |
temperature |
0.4 |
Rewrite sampling temperature |
ignore |
[] |
Extra globs to never rewrite |
thresholds |
[] |
Per-glob overrides, e.g. [{ "pattern": "legacy/**", "value": 0.3 }] |
timeoutMs |
120000 |
Base rewrite timeout (ms) |
timeoutPer1kLinesMs |
30000 |
Added per 1000 lines over 1000 |
maxTimeoutMs |
600000 |
Hard timeout cap |
maxFileLines |
2000 |
Skip full-file rewrite above this; diff/chunk still apply in auto |
rewriteConcurrency |
3 |
Parallel API calls per audit |
rewriteScope |
auto |
file · diff · chunk · auto |
rewriteFullFileBelowLines |
400 |
Full-file cutoff in auto |
contextLines |
60 |
Context around diff hunks |
maxChunkLines |
450 |
Max lines per chunk |
structuralCheck |
true |
Reject rewrites that drop too many top-level declarations |
semanticReview |
true |
Second model pass that rejects a rewrite that changed behavior |
semanticReviewModel |
"" |
Model for that second pass; empty → same model as the rewrite |
semanticReviewMaxLines |
1200 |
Skip the second pass above this file size (0 = no cap) |
disable |
false |
Make the audit a complete no-op for this repo (hook stays installed) |
notifications |
auto |
auto (notify every outcome) · important (only humanized + rewrite failures) · off (never) |
Environment variables (override file config; preferred for secrets):
| Variable | Purpose |
|---|---|
BYPASSER_API_KEY |
API key |
BYPASSER_BASE_URL |
Base URL |
BYPASSER_MODEL |
Model |
OPENAI_API_KEY |
Fallback key |
BYPASSER_VERBOSE |
1 → verbose audit/hook output |
BYPASSER_DISABLE |
1 → force-disable the audit for this run |
BYPASSER_HOOK_EXCLUDED_REPOS |
Comma-separated repo folder names to block install |
BYPASSER_SKILL_LOCALE |
es → Spanish humanizer prompt (SKILL.es.md); default English |
BYPASSER_NOTIFICATIONS |
auto | important | off → override the notification mode |
BYPASSER_SEMANTIC_REVIEW |
0 → turn off the semantic review pass for this run |
The rewriter expects the same fragment back, humanized — not a compressed summary. Models that return ~10–35% of the input (dropping def, loops, types) are rejected by design; the file commits unchanged and it looks like “the hook did nothing.”
Before relying on a model for large files, sanity-check: output line count ≈ input, declarations intact. gpt-4o-mini is a reasonable default; for local or proxy setups, pick a model that round-trips full fragments.
| Provider | baseURL |
|---|---|
| OpenAI | https://api.openai.com/v1 |
| Groq | https://api.groq.com/openai/v1 |
| OpenRouter | https://openrouter.ai/api/v1 |
| Ollama | http://localhost:11434/v1 |
| LM Studio | http://localhost:1234/v1 |
| Local proxy (e.g. 9Router) | http://localhost:20128/v1 |
Local proxies often accept any non-empty BYPASSER_API_KEY (e.g. local).
Full template for bulk sync across repos: scripts/canonical-bypasser.json.
Built-in ignores include lockfiles, dist/**, build/**, .next/**, minified assets, most *.json / *.yaml / *.toml, and bypasser artifacts (*.bak, *.bypasser.tmp, .bypasser.log.jsonl). Extend with ignore in .bypasser.json.
Six signal families (naming, structure, comments, error-handling, abstraction, uniformity) fire on language-aware profiles — not English-only heuristics. Fired weights combine through a saturating curve 1 - e^(-w/2.5) so realistic AI clusters land around 70–85% while typical human code stays below ~50% at the default threshold 0.65.
Informative docblocks (@param, multi-line rationale) are not penalized; only terse restating docblocks on nearly every function count as an AI tell.
Before scoring, code and comments are split into masked views (src/mask.ts), so a // inside a string, a function inside a regex literal, or a URL containing // never count as code. Detector behaviour is pinned by an annotated corpus + benchmark (npm run bench) — see CI gate & detector calibration.
src/
cli.ts Command router
config.ts .bypasser.json + env + scope resolution
detector.ts Deterministic scorer (language profiles)
mask.ts Masked code/comments/documented views (strings & comments out of scoring)
rewriter.ts API client, humanizer prompt, sanitizers
rewrite-validate.ts Indent / structure heuristics
semantic-drift.ts Second model pass: did the rewrite change behavior? (fail-open)
rewrite-constants.ts Shared ratio thresholds
syntax-guard.ts Python / JSON parse backstop on assembled output
diff-hunks.ts Unified diff parse, slice, splice
chunk-split.ts Top-level declaration chunking
concurrency.ts Bounded parallel rewrite pool
git.ts Staged content, batch cached diff, restage, tracked files
installer.ts Hook install/remove + excluded repos
audit.ts detect → rewrite → restage → log → notify; selectUnresolvedFiles() for --strict
logger.ts .bypasser.log + structured .bypasser.log.jsonl (incl. per-file signals) + .bypasser.state.json cache (batch + prune)
stats.ts Aggregates the structured log for `bypasser stats`
secrets.ts Detects an inline apiKey at risk of being committed
notifier.ts Cross-platform notify (Windows toast + macOS/Linux shell)
index.ts Public library exports
docs/
SKILL.en.md Humanizer prompt (default at runtime)
SKILL.es.md Humanizer prompt (Spanish)
SKILL.md Bilingual index + locale notes
scripts/
bench-detector.ts Detector benchmark + golden snapshot (test/corpus/corpus.json)
test.js Cross-platform test runner
test/
corpus/ Annotated AI/human samples + detector-snapshot.json
integrations/
opencode/ Optional OpenCode TUI panel (install.mjs + bypasser-panel.tsx)
On any doubt, the original file wins:
finish_reason: length→ reject- Unbalanced brackets / suspicious line collapse → reject
- Flattened indentation or dropped declarations → reject
checkSyntax()on assembled output (Python / JSON when available)- Semantic drift review: a second model pass compares the original against the rewrite and rejects it when observable behavior changed (a flipped comparison, a swapped return value). Fail-open: if the reviewer is unreachable, the rewrite is kept. Disable with
semanticReview: false restageFile(): no empty overwrite, severe-shrink guard, atomic write,.bak, rollback ifgit addfails
Failed rewrites do not block the commit; check .bypasser.log and hook output.
Detector quality is measured, not assumed. test/corpus/corpus.json holds
annotated AI/human samples across languages; npm run bench runs the detector
over them and reports a confusion matrix, precision/recall/F1, and the threshold
that maximises F1. test/bench-detector.test.ts asserts a quality floor and
locks every sample's score against a golden snapshot — a weight tweak that moves
a score by more than 0.02 fails CI until it is consciously re-recorded
(BYPASSER_UPDATE_SNAPSHOT=1 npm test).
Strict gate for CI (not the hook — the hook never blocks a commit):
bypasser audit --strict # exits 1 if an AI-shaped file was not humanizedA ready-made composite Action wraps this for any repo:
- uses: Pacoaldev/BypasserAI/.github/actions/bypasser-strict@main
with:
api-key: ${{ secrets.BYPASSER_API_KEY }}npm install
npm run build # required after src/ changes
npm test # node:test via scripts/test.js (Node >= 20.6 for tsx)
npm run bench # detector precision/recall/F1 over the annotated corpus
npm run typecheck
npm run lint
npm run formatCI runs lint, typecheck, tests, and build on Node 18 / 20 / 22 (see .github/workflows/ci.yml).
Contributions welcome — open an issue or PR.
- Skips config, locks, and generated artifacts by convention
- Never weakens security or correctness to “look human”
- Heuristic detector — tune
threshold/thresholdsif needed - Hook and CLI work on Windows/macOS/Linux; desktop notifications are optional (
notifications: offdisables them)

