Omit needless code.
An editorial discipline for AI coding agents. Named after Strunk & White's Rule 17: "Omit needless words": applied to the way agents write software: they overwrite (bloat) and they overclaim (hallucinated shortcuts). omit fixes both.
Draft less. Cite everything. Cut last.
AI agents are prolific authors and terrible editors. Left alone they add abstractions nobody asked for, pull in dependencies for three lines of logic, and: when told to "keep it simple": confidently reach for stdlib APIs that don't exist. Minimalism-only rulesets fix the bloat and make the overclaiming worse: the pressure to write less rewards inventing shortcuts.
omit turns the agent from author into editor. Four parts:
| Part | What it does |
|---|---|
| The Seven Omissions | Before writing anything, try seven ways to not write it: omit the feature, the new code, the custom, the script, the dependency, the ceremony: stopping at the first omission that holds. What survives editing, ships. |
| The Fact-Check | No omission counts without a citation the tool can re-check itself: file:line:symbol for "the codebase has this", an executable snippet for "stdlib covers it", a declared dep + snippet for "the dep handles it". A citation only its author can read is a self-report, and a hallucinated shortcut is a fabricated citation. |
| The Final Draft | Working code is a first draft. After tests go green, one ruthless edit of the agent's own diff: then a net report: files, ±lines, new deps (target: 0). Done means final draft, not green tests. |
| Load-Bearing Lines | Editing cuts fat, not walls. Validation, error handling, security, accessibility, concurrency correctness, and explicit requests are never cut: and adding them is announced, never smuggled or skipped. |
Deliberate omissions go on the record as footnotes in the code:
// omitted: retries: single caller tolerates failure; add backoff if this goes multi-tenantEvery other skill in this genre is words the agent can ignore under context pressure. omit ships mechanisms that run outside the model:
| Mechanism | What it does |
|---|---|
| Command sentinel (hook) | Inspects every shell command BEFORE it runs and blocks the classic agent disasters: rm -rf ~, recursive deletes of system/drive roots, deletes through unset variables (rm -rf $OUT/* with $OUT empty), dd to block devices, mkfs, fork bombs. The user's machine is load-bearing. Waiving one takes a trailing comment carrying a real reason — # omit-allow: <reason>. The bare token, a token inside a string literal, and a reason-less marker are ignored. |
| Dep sentinel (hook) | A new dependency hits a manifest with no verified receipt in .omit/receipts.jsonl → the edit is objected to on the spot. The receipt has to name the omissions that were tried, and each one is re-checked: if an omission actually applies, the receipt is refuted and the dependency is refused. |
| Hazard sentinel (hook) | Hardcoded API keys/secrets and injection-prone patterns (string-built SQL, eval, shell concatenation, innerHTML, unsafe deserialization) are blocked the moment they land in a file. Secrets have no override — the secret rules run before any marker is consulted, so no omit-allow: waives one. Injection lines take a trailing comment with a real reason, and only that form. |
| Leak sentinel (hook) | Blocks shell commands that print an existing secret's raw value to stdout before they run, or a live key typed straight into the command line: macOS Keychain, Linux secret-tool/pass/gpg -d, 1Password/Vault/AWS/GCP/Azure/kubectl secret CLIs, bare env/printenv, env | grep-ing a KEY/TOKEN/SECRET/connection-string var, or cat-ing a .env/credentials/*.pem/id_rsa file. Redirecting to a real file or piping into a non-printing sink (clipboard, --password-stdin) is recognized as safe — the goal is keeping secrets out of the transcript, not off disk. Adversarially reviewed (3 lenses, every finding re-verified by execution, not inspection) before shipping — one known gap stays undetected on purpose rather than chasing a fragile fix: a for/do/done > file loop's trailing redirect isn't attributed back to the loop body. The agent's own transcript is not a safe place for a real key. |
| Lint sentinel (hook) | omit ships no lint rules. It detects the linter the repo already configured (eslint, biome, ruff, flake8) and runs it on every edited file, so the agent hears objections immediately instead of at CI time. A linter that is configured but cannot be run — no node_modules, nothing on PATH — is reported as not run with the reason, never as a pass; "no linter configured" is now reserved for a repo that genuinely has none. |
| Final Draft gate (hook) | The session cannot end with an edited tree and no current .omit/final-draft.md net report — and the report is read, not just stat'd. Its files/lines/deps counts are cross-checked against the actual diff, so a stub or a stale draft does not pass. The deletion pass is a gate, not a suggestion. |
| Receipts ledger | Every Fact-Check citation is appended to .omit/receipts.jsonl as a claim plus the evidence that settles it, and omit verify re-checks the lot. Run it on a PR: "17/17 claims survived" is a number a reviewer can act on, and "3 refuted" names exactly which shortcuts were invented. |
Hooks install automatically with the Claude Code plugin. For Codex, run npx @sriinnu/omit hook install codex to merge hooks into .codex/hooks.json; rerunning preserves existing hooks without duplicating omit's entries. Command and leak sentinels check Bash before execution. Dependency and hazard sentinels also check shell edits afterward. File sentinels read apply_patch from tool_input.command, checking all added/updated paths and move destinations while skipping deleted files. Malformed patch envelopes produce an objection. Payload regression tests cover these contracts; live Codex delivery and Stop-gate behavior still require end-to-end verification. These hooks do not impose a token budget or compact transcripts. Escape hatch for humans: OMIT_OFF=1.
Ribhu has shell hooks too, with its own file shape and payload: npx @sriinnu/omit hook install ribhu writes .ribhu/hooks.json, and a small adapter translates Ribhu's payload so the same sentinels run unchanged. Ribhu sends every tool call a code-mode script makes through those hooks, so a script's writes are checked like direct ones. The hooks are tested by firing each installed command the way Ribhu does, with payloads in its shape; they have not yet been watched firing inside a live Ribhu session.
A table of mechanisms invites you to read it as a guarantee. It isn't one, so here is the rest of it. Every item below is a known, reproduced limit, not a hypothetical.
- Writes whose path cannot be read out of the command reach no file sentinel.
python -c "open('f','w').write(...)",curl -o f,node -e fs.writeFileSync. A literal secret in such a command is still caught by the command-text scan; its injection patterns are not. The same goes for a file rewritten through a tool no sentinel is wired to. omit gateis a git hook.git commit --no-verifyskips it, andcore.hooksPathshadows it entirely —omit auditandomit gateboth report that in the verdict, andomit hook installwrites to whichever directory git actually runs hooks from, but nothing can stop you bypassing your own pre-commit hook.- The dependency allowlist is short.
setup.py,build.gradle,Package.swiftand*.csprojare dependency-shaped and unparsed. They are now reported (unparsed deps: …) rather than counted as zero, and a changed one suppresses the✅on the deps row — but the gate does not fail on them, because asetup.pyin a repo is not evidence of anything. - The lint sentinel runs your linter in your session, which executes your lint config — the same trust as running
npm run lintyourself. In the GitHub Action it is gated: with the defaultexec: false, CI does not run the linter at all, and the verdict says not run rather than claiming a pass. - Receipt evidence is bound to its claim textually. A
runsnippet must name what it exercises and the argv must mention it, which closes snippets that exit on demand (["true"],["false"]). It does not catch a snippet that names a symbol without calling it. Proving that would mean the verifier writing the snippet, and then it is testing the verifier. omit's own directory is exempt from the hazard scan, because the ledger holds evidence strings by construction. Its execution risk is whatOMIT_HOOK_EXECgates.- The Action does not run a PR's receipts unless you set
with: { exec: true }. A PR'srunsnippets are untrusted code, so the default is not to execute them.
// omitted: a mechanism for the first item above would be a shell parser, and a wrong shell parser is worse than none. It is left out on purpose.
Minimalism is a taste, and taste has no receipt. The failure mode underneath it does: the agent says "the codebase already does this" or "stdlib covers it", and nobody checks. Every rule file in this genre tells the agent not to lie. omit checks.
Each claim lands in .omit/receipts.jsonl with the evidence that settles it:
{"claim":"reuse","rung":2,"file":"src/util.ts","line":42,"symbol":"parseRange"}
{"claim":"stdlib","rung":3,"api":"crypto.randomUUID","run":["node","-e","crypto.randomUUID()"]}
{"claim":"installed-dep","rung":5,"dep":"zod","run":["node","-e","require('zod').object"]}
{"claim":"new-dep","rung":7,"dep":"left-pad","tried":[{"rung":2,"absent":"padTo"}]}$ npx @sriinnu/omit verify
✅ line 1 verified reuse
⛔ line 2 failed new-dep
[new-dep] tried[0] (rung 2) actually HOLDS: padTo is present at src/util.ts — so the omission applies and this dep is not needed
1/2 claims survived re-checking · 1 refuted · 0 unverifiable
That second one is the whole idea. A new-dep receipt exists to say "I tried the earlier omissions and none of them held" — so it cites the symbol it looked for, omit searches the tree for itself, and a symbol that is there refutes the agent's own conclusion. When the author supplies the question instead of the evidence, citing your way past an omission you never tried stops being possible.
Two bindings make that hold, and neither is decorative:
runmust name what it exercises (api, or the dependency), and the argv must mention it. Exit status alone proves nothing:["true"]exits 0 and["false"]exits 1 while testing neither the standard library nor anything else. A snippet that exits on demand is not evidence.- A
triedentry cites a search, not a location. Naming a file that merely doesn't exist proves nothing — and it used to be the cheapest possible way to fake "I tried reuse", because the check only asked whether it failed.
run is an argv array, never a shell string: nothing expands, nothing hides in an argument. .omit/receipts.jsonl is executable in the same sense a Makefile is — read it in a PR the way you'd read one. OMIT_NO_EXEC=1 downgrades those checks to "unverifiable" instead of executing them, and the dependency hook does not execute them at all unless you set OMIT_HOOK_EXEC=1: a Write of the ledger would otherwise run code as you, with no approval prompt.
Upgrading from 0.3.x: the shapes above are the 0.4.0 schema. A receipt written before it — a prose receipt string, or a tried entry citing a file — reads as unverifiable, which omit verify names individually. They are not silently accepted, and they are not silently dropped.
The enforcement logic lives in a zero-dependency CLI, not in any one vendor's hook system: Claude Code's and Codex's hooks are both just thin adapters over it. For Cursor, Copilot, or anything else, enforce at the two chokepoints every agent passes through:
npx @sriinnu/omit hook install # git pre-commit: audits the staged diff,
# fails on secrets, injections, uncited deps
npx @sriinnu/omit audit # net diff (untracked files included), new deps, hazards
npx @sriinnu/omit check <files> # hazard-scan specific files (wire into any hook system)
npx @sriinnu/omit lint [files] # run the repo's OWN linter on changed files
npx @sriinnu/omit verify # re-check every claim in .omit/receipts.jsonl
npx @sriinnu/omit guard "<cmd>" # is this shell command a disaster? (wire into any hook system)
npx @sriinnu/omit leak "<cmd>" # would this command print a real secret to stdout?
npx @sriinnu/omit gate # the pre-commit check, callable from anywhere
npx @sriinnu/omit hook install codex # write .codex/hooks.json — live sentinels inside Codex CLI
npx @sriinnu/omit hook install ribhu # write .ribhu/hooks.json — live sentinels inside Ribhu
And server-side, the GitHub Action comments the verdict on every PR regardless of what wrote the code:
# .github/workflows/omit.yml
on: pull_request
permissions: { contents: read, pull-requests: write }
jobs:
omit:
runs-on: ubuntu-latest
# A fork's PR gets a read-only token, so the comment could only fail there.
if: github.event.pull_request.head.repo.full_name == github.repository
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: sriinnu/[email protected]
# with: { exec: true } # execute receipts' `run` snippets to verify them
# fully. Off by default: a PR's receipts are
# untrusted code, and this runs on pull requests.### omit verdict
- net: +61 −204 lines across 4 files
- new deps: 0 ✅
- hazards: 0 ✅
- footnotes: 3 recorded · load-bearing: 1 marked
- receipts: 17/17 verified
- lint: eslint ✅
// omitted: a composite "omit score": any weight vector over these counts is
// invented, and footnotes could raise it by adding lines. The counts above are
// the receipts; a single number would just be another uncited claim.
An agent exploring a repo pays for every intermediate result: each read and each grep is a round-trip whose raw output lands in the transcript and is sent again on every later turn. With omit codemode the model writes a script, the reads happen inside it, and only what the script returns comes back.
omit codemode run <<'EOF'
const libs = (await tools.files({ under: 'lib' })).filter((f) => f.endsWith('.mjs'))
const sources = await Promise.all(libs.map((path) => tools.read({ path })))
return Object.fromEntries(libs.map((f, i) => [f, (sources[i].match(/^export /gm) ?? []).length]))
EOF- Read-only. Three tools:
files,read,grep. They read the tree through git, so ignored files stay out, and every path is confined to the directory the server started in: no.., no absolute path, no symlink out. - Sandboxed. The script runs in a QuickJS VM compiled to wasm (
@earendil-works/pi-codemode, the sandbox behind pi's codemode): no file system, no network, noprocess.node:vmis not isolation, and the receipt for this dependency runs the escape to show it. - Secrets stay in. A script may read a credentials file; it may not return it. Output that matches a secret rule is withheld whole.
- Bounded. A script has 60 seconds, and output past 20,000 characters loses its middle.
The sandbox is an optional peer that only this command loads, so omit itself still installs zero dependencies. It needs Node 22.19 or newer.
npm install -g @sriinnu/omit @earendil-works/pi-codemode
Any agent with a shell can use it as it stands: omit codemode run takes a script on stdin or from a file and prints the answer, and the omit-codemode skill (skills/omit-codemode/SKILL.md) teaches the agent when to reach for it and what a script can call. Nothing has to be registered.
For a host without a shell, or one where you would rather approve a single read-only tool, the same thing is an MCP server on stdio:
claude mcp add omit -- omit codemode # Claude Code
# Codex: ~/.codex/config.toml
[mcp_servers.omit]
command = "omit"
args = ["codemode"]// omitted: write and edit tools: a nested write is not seen by the host's
// hooks, so it has to carry the hazard, dependency and lint gates itself. Add
// them once bench/ shows the read side pays for the surface.
bench/ is METHODOLOGY.md made runnable: paired agentic runs of the same tasks under baseline, omit, or any competing skill, metrics computed from the actual git diffs, all transcripts kept. The category argues about self-reported numbers; omit ships the measuring instrument. See bench/README.md.
| Mode | Behavior |
|---|---|
margin |
Build as asked; note in the margin what could have been omitted |
redline |
Default. Full enforcement: Seven Omissions, Fact-Check, Final Draft |
rewrite |
Also question the assignment itself before building |
off |
Disabled until re-invoked |
Say omit redline (or any mode) in chat, or use /omit <mode> where slash commands are supported.
These features run locally with Node and do not call a model or provider API. Use the same CLI with any provider/model; hook installation targets the host, not the model. Codex and Claude adapters are included. Other hosts need the documented stdin/output contract below; automatic interception is not universal.
omit doctor # read-only hook inspection; never runs discovered commands
omit doctor --json # same findings for scripts; exit 1 on errors
omit init skill # discoverable .agents/skills/omit/SKILL.md; never overwrite
omit hook install codex --context
omit hook install claude --contextThe doctor inspects global Codex hooks plus the current directory's
.codex/hooks.json and .claude/settings.json. It reports missing scripts,
duplicate registrations, malformed matchers, some legacy shell-tool name
mismatches, and Git core.hooksPath overrides. It checks simple Node/Python
script commands statically; other command shapes are explicitly unchecked.
It does not resolve all parent layers, host trust, executable availability, or
prove that hooks fired. liveDelivery remains unverified.
--context adds advisory broad-read warnings and a text output guard. Default
installations keep the existing safety hooks without adding context controls.
At more than 6,000 characters, a plain-string shell result is archived privately
under the OS temporary directory and replaced with head/tail excerpts, selected
error lines, and its full-output path. Set OMIT_OUTPUT_CHARS to 1000–100000 to
change the excerpt character budget. The wrapper/path adds a small overhead.
Structured results (objects, arrays, MCP content) are left intact. Storage
failure preserves the original result and emits a diagnostic. Archives can
contain sensitive output: files use mode 0600 inside a mode-0700 directory on
POSIX systems. They persist until temporary storage is cleaned; no existing
transcript is rewritten. Inspect the archive when a missing middle section
matters; diagnostic extraction is heuristic and not exhaustive.
Three consecutive identical command/result pairs in the same cwd/session produce one advisory warning. A changed pair resets the counter. Only hashes and a bounded counter are stored for this check; it does not prove filesystem state is unchanged. Parallel invocations may undercount, so this is not a gate.
For another host, invoke omit context with JSON on stdin:
{"hook_event_name":"PostToolUse","tool_name":"Bash","session_id":"example","cwd":"/your/project","tool_input":{"command":"your command"},"tool_response":"plain text output"}Use PreToolUse for read-scope advice. Shell names Bash, exec_command, and
shell are accepted (tool_input.cmd is also accepted). systemMessage is
advisory; decision: "block" with reason requests post-tool feedback replacing
the result in Codex. Other hosts must translate that response into their own
replacement API: some only append feedback. Verify delivery and replacement
before claiming context savings. No model quota or token-saving guarantee is
inferred from character counts. Set OMIT_OFF=1 to bypass the hooks; remove only
the context-sentinel.mjs entries to uninstall these optional controls.
The packaged skill remains at skills/omit/SKILL.md; omit init skill copies it
to the shared project discovery path. Hosts with different discovery paths can
install the same skill there. No provider credentials are required.
New here? GETTING-STARTED.md has a copy-paste setup for every agent.
Claude Code (plugin marketplace): one command pair, gets you the skill plus /omit and /omit-edit:
/plugin marketplace add sriinnu/omit
/plugin install omit@omit
Any SKILL.md-aware agent (Claude Code, Codex, Cursor, and others, via skills.sh):
npx skills add sriinnu/omit
Global command: install once from GitHub, use everywhere:
npm install -g github:sriinnu/omit
omit init cursor # or: omit audit / omit gate / omit hook install
npm / npx: drops the right rule file into the current repo (never overwrites existing files):
npx @sriinnu/omit init # AGENTS.md (default)
npx @sriinnu/omit init claude # .claude/skills/omit/SKILL.md
npx @sriinnu/omit init cursor # .cursor/rules/omit.mdc
npx @sriinnu/omit init cline # .clinerules/omit.md
npx @sriinnu/omit init windsurf # .windsurf/rules/omit.md
npx @sriinnu/omit init all # everything above
npx @sriinnu/omit hook install # git pre-commit gate (works with ANY agent)
Claude Code (manual): copy the skill into your project or user skills directory:
skills/omit/SKILL.md → .claude/skills/omit/SKILL.md (project)
~/.claude/skills/omit/SKILL.md (all projects)
Codex / Takumi / any AGENTS.md-aware agent: copy AGENTS.md into your repo root (or append to an existing one), or npx @sriinnu/omit init codex.
GitHub Copilot: copy .github/copilot-instructions.md into your repo, or npx @sriinnu/omit init copilot.
Cursor: copy .cursor/rules/omit.mdc into your repo.
Cline: copy .clinerules/omit.md into your repo.
Windsurf: copy .windsurf/rules/omit.md into your repo.
Anything else: paste the contents of AGENTS.md into the agent's custom-instructions/rules mechanism. It's plain markdown; there is nothing to build.
// omitted: the discipline itself over MCP: MCP exposes tools and data; omit is
// a behavioral discipline, and rule files + skills already deliver it. The one
// MCP server omit ships is codemode, which is tooling.
/omit [margin|redline|rewrite|off]: switch or show the current mode/omit-edit: run an editor's pass over the current diff: flag bloat, uncited claims, missing footnotes, and cut opportunities
One command, three destinations (npm, GitHub, Homebrew):
npm run release -- patch # or minor / major
scripts/release.mjs runs the tests (via preversion), bumps the version,
and lands the bump on main through a PR (main takes no direct pushes).
Once checks pass and it merges, the script tags the merge (signed, per repo
policy) and pushes the tag — which triggers publish.yml, publishing to npm
with a provenance attestation. It then cuts the GitHub release with
generated notes, waits for the registry to serve the version, and updates the
omit formula in sriinnu/homebrew-tap,
again through a PR. A failed step aborts the release, in order.
Never npm publish by hand: a local publish cannot attach provenance, and
npm will not let the same version be republished to add one later. If the CI
publish fails, fix CI — don't work around it locally.
There is no npm token to keep alive. publish.yml uses npm's
trusted publishing: the workflow
proves its identity to npm with a short-lived token GitHub mints for that run,
and npm checks it against the publisher configured in the package's settings
(GitHub Actions, sriinnu/omit, publish.yml). If a publish is refused, that
form is the thing to check.
The minimalism-pressure idea was popularized by ponytail, which deserves its stars. omit differs where it matters: shortcuts require citations, the diff is edited after it works, safety lines are enumerated and never cut, and what's left out is footnoted instead of silent.
MIT