hyperfixer
v0.1.13
Published
Layered verification pipeline for AI-agent-written code: cost-ordered gates, fail-fast, machine-readable verdicts for self-correction loops. Runs on Node, Bun and Deno.
Downloads
1,185
Maintainers
Readme
hyperfixer
📦 npm package · 📖 Documentation · 🤖 llms.txt · llms-full.txt
Layered verification pipeline for AI-agent-written code.
hyperfixer runs your project's quality gates in cost order, cheapest first, fail-fast, and produces a machine-readable verdict that any coding agent can consume to fix its own mistakes. Humans get a colored summary; agents get verdict.json and a one-line hint pointing at the first actionable problem.
✓ lint (64ms)
✓ typecheck (462ms)
✓ typetest (20ms)
✓ unit (22ms)
✓ pbt (34ms)
OK, all gates passed in 601msThe agent contract
Task runners that order and cache commands already exist. What hyperfixer adds is a contract a coding agent can rely on, and this is the part to build your loop around:
- Strict exit codes:
0verified,1your code is wrong (read the hint),2the setup is wrong (config error, empty pipeline, lock held),3gate infrastructure failed (timeout, tool crash), do not edit code for a 3. A broken config or a hung tool can never masquerade as a test failure. - Structured verdict on disk: per-gate status, structured
findingswith file and line, timings,outputTail,generatedAt. - One-line
hint: the first blocking problem, pre-formatted as[gate] file:line, message, the exact string to feed back to the agent. - Staleness guard by content: the verdict stores a fingerprint of every gate's inputs;
hintrecomputes it and warns only when the code actually changed, no clock guessing. - No false greens: an empty pipeline (over-filtered
--max-cost, all gates disabled) is exit 2, never a green; a config path passed with--configthat does not exist is exit 2 instead of a silent fallback to the built-in gates; a malformed gate field is exit 2 instead of a silently skipped gate; a gate whoseinputsmatch no file is never cached (a constant hash could never invalidate), anddoctorflags any pattern that resolves to nothing, because a gate whose inputs only partly resolve caches on a key blind to the rest; a filtered run recordsfilteredGatesso a partial green cannot pass for a full one; failures are never cached; and property tests print fast-check replay data (seed, path, counterexample) into the verdict so the agent can re-run the exact failing case.
Why layers
Agent-written code fails in layers, and each layer needs a different detector:
| Layer | Catches | Tool |
|---|---|---|
| Lint | Style drift, suspicious patterns | Biome |
| Typecheck | Type errors | tsc --noEmit (strict) |
| Type tests | Wrong public API types | expect-type + @ts-expect-error |
| Unit tests | Broken behavior | bun test |
| Property-based | Edge cases nobody thought to write | fast-check |
| Differential/parity | Two implementations disagreeing | project-defined oracle |
| Mutation | Tests that pass but assert nothing | Stryker |
Running everything on every change is wasteful; running only unit tests misses whole failure classes. hyperfixer orders gates by cost, stops at the first failure, and tells the agent exactly where to look, so the feedback loop is fast and honest.
Fast in practice: gates with equal cost run in parallel, and gates that declare inputs are cached by input hash, an unchanged project re-verifies in milliseconds:
✓ lint (0ms), cache hit
✓ typecheck (0ms), cache hit
✓ e2e (0ms), cache hit
OK, all gates passed in 2msRequirements
Runs on every major JavaScript runtime:
- Node.js >= 20 (
npm i -D hyperfixer,npx hyperfixer) - Bun >= 1.1 (
bun add -d hyperfixer,bunx hyperfixer) - Deno >= 2 (
deno run -A npm:hyperfixer)
Zero runtime dependencies, TypeScript types included.
Quick start
npm i -D hyperfixer # or: bun add -d hyperfixer, deno add npm:hyperfixer
npx hyperfixer init # detects your stack, writes hyperfixer.config.json
npx hyperfixer doctor # verify toolchain
npx hyperfixer run # run the pipeline
npx hyperfixer install-hooks # enforce on git commit and pushinit inspects the project (Biome or ESLint, tsconfig, Bun or vitest or npm test, test directories) and generates a tailored gate list instead of a generic default.
CLI
hyperfixer run [flags] run gates in cost order, write verdict
hyperfixer fix [flags] run every gate's fixCommand, then verify
hyperfixer init detect the stack, write hyperfixer.config.json
hyperfixer hint print first actionable fix from last verdict
hyperfixer doctor check toolchain and config health
hyperfixer install-hooks install git pre-commit and pre-push hooks
hyperfixer install-claude install Claude Code PreToolUse hook
hyperfixer claude-hook internal, PreToolUse entry point (stdin JSON)
Run flags:
--json machine-readable verdict on stdout
--quiet no human output (verdict.json still written)
--config <path> config file (default hyperfixer.config.json)
--only <a,b> run only the named gates
--max-cost <n> run only gates with cost <= n
--changed expand {changed} in commands to git-changed files
--no-cache ignore and do not update the input-hash cache
--no-fail-fast run all gates even after a failure
--out-dir <dir> verdict output directory (default .hyperfixer)Exit codes: 0 every gate that ran passed, 1 a gate failed (fix code), 2 setup problem (config error, missing --config file, empty pipeline, lock held), 3 gate infrastructure failed (timeout, tool crash, do not edit code). The contract is strict: agents branch on the exit code alone. --only and --max-cost make a run a subset, so a 0 from a filtered run only covers the gates it ran; the verdict names the rest in filteredGates and hint repeats them.
Configuration
hyperfixer.config.json:
{
"failFast": true,
"outDir": ".hyperfixer",
"gates": [
{ "name": "lint", "cost": 5, "command": ["bunx", "@biomejs/biome", "check", "."] },
{ "name": "typecheck", "cost": 10, "command": ["bunx", "tsc", "--noEmit", "--pretty", "false"], "parser": "tsc" },
{ "name": "unit", "cost": 30, "command": ["bun", "test"], "parser": "bun-test" },
{ "name": "pbt", "cost": 40, "command": ["bun", "test", "test/property"], "parser": "bun-test" },
{ "name": "mutation", "cost": 100, "command": ["bunx", "@stryker-mutator/core", "run", "--incremental"], "enabled": false, "optional": true }
]
}| Field | Meaning |
|---|---|
| cost | Relative cost; gates run in ascending order, --max-cost filters on it |
| command | Argv array; omit to skip the gate |
| parser | tsc, bun-test, fast-check, eslint-json, findings-json, or raw, extracts structured findings from output |
| optional | Skip (instead of error) when the command cannot start |
| enabled | false removes the gate from the pipeline |
| timeoutMs | Kill the gate after this many ms (default 600000) |
| inputs | Glob patterns this gate depends on; declaring them enables input-hash caching |
| fixCommand | Autofix argv run by hyperfixer fix before re-verifying (biome --write, eslint --fix) |
Gate names must be unique; duplicates are rejected at load time. A hanging gate cannot wedge an unattended agent loop: it is killed at timeoutMs (SIGTERM, then SIGKILL) and reported as error. Gates with equal cost run concurrently as a group; fail-fast blocks later groups only.
Caching: a gate that declares inputs stores its passing result keyed by a hash of command + file contents. Touching a file or a checkout that rewrites identical bytes never invalidates; a real content change always does. Failures are never cached; --no-cache bypasses it.
inputs must cover everything the command reads. The key is built from the files the patterns resolve to, so anything outside them, a source directory you forgot, a dependency, a config the tool loads itself, is a change the cache cannot see. hyperfixer doctor reports patterns that match nothing, and a gate whose patterns all match nothing is never cached at all.
Changed-only mode: put {changed} in a gate command and run with --changed; the token expands to the files git reports as modified, and the gate is skipped entirely when the tree is clean.
Works with any agent
hyperfixer is agent-agnostic by construction: the interface is a CLI with strict exit codes, a JSON verdict on disk, and git hooks. Claude Code, Codex, Cursor, Kimi, Grok, or a plain shell script all consume it the same way.
The universal loop (put this in your agent's instructions file, AGENTS.md or CLAUDE.md):
hyperfixer fix --quiet || hyperfixer hint
# => [typecheck] src/service.ts:42, Type 'string' is not assignable to type 'number'. (+3 more)
# fix, then re-run until exit 0Hard enforcement, works for every agent and every human:
hyperfixer install-hooks
# pre-commit: gates with cost <= 50 (fast)
# pre-push: full pipelineThe hooks are plain sh, refuse to overwrite hooks they did not write, and print the hint on failure so the blocked agent knows what to fix.
Claude Code, one command:
hyperfixer install-claudeThis writes (or merges into) .claude/settings.json a PreToolUse hook that intercepts every git commit and git push the agent attempts, runs the pipeline, and on failure blocks the call with the hint fed back to the agent, which fixes and retries. Non-git commands are never touched.
GitHub Actions, three lines in anyone's CI:
- uses: actions/checkout@v4
- uses: egeominotti/hyperfixer@main
with:
max-cost: "50" # optional, config: and working-directory: also availableThe verdict
hint prints the first actionable problem with file and line; the full structured verdict is in .hyperfixer/verdict.json:
{
"ok": false,
"generatedAt": "2026-07-24T10:30:00.000Z",
"inputsFingerprint": "sha256…", // content-based staleness guard
"failedGate": "typecheck",
"filteredGates": ["mutation"], // held back by --only or --max-cost
"hint": "[typecheck] src/service.ts:42, Type 'string' is not assignable…",
"durationMs": 533,
"gates": [
{
"gate": "typecheck",
"status": "fail", // pass | fail | skip | error
"durationMs": 458,
"exitCode": 2,
"findings": [
{ "file": "src/service.ts", "line": 42, "column": 5, "code": "TS2322", "message": "…" }
],
"findingsTotal": 4300, // only when findings were capped
"outputTail": "…",
"note": "3300 more findings not recorded" // why it was skipped, errored, capped or cached
}
]
}findings is capped at 1000 per gate so the file stays readable; when a tool reports more, findingsTotal and note carry the real count, and both hint and the human report quote it. cached: true marks a result served from the input-hash cache.
filteredGates is present only when gates were held back, by --only, --max-cost, or --changed when nothing a gate watches changed. A green verdict that carries it covers the gates that ran, nothing more, and hint says so.
hint warns on stderr when the verdict is older than 10 minutes, so an agent that edited code without re-running cannot trust a stale "OK".
Programmatic API
import { loadConfig, runPipeline } from "hyperfixer";
const verdict = await runPipeline(await loadConfig());
if (!verdict.ok) console.error(verdict.hint);Development
bun install
bun run check # hyperfixer verifying itself (lint, typecheck, typetest, unit, pbt)The repo dogfoods its own pipeline: property-based tests assert verdict invariants (ok if and only if no failing gate, fail-fast skips everything after the first failure), and type tests pin the public API. CI runs the pipeline on every push; a green build on main bumps the patch version, updates CHANGELOG.md and tags the release automatically.
License
MIT
