Everything a CLI needs that isn't your CLI. Written once, served to humans and agents alike.
One declaration, six surfaces. Declare a command once and burgee serves it as help, a --json envelope, a versioned --schema, an MCP server, shell completions and TypeScript types. Nothing is written twice, so no surface can drift from another.
Contracts an agent can act on. Exit code 2 means rewrite the command; any other failure means the command was right and the world was not. A mistyped command comes back with a fix: line the agent can run as written, and --explain says which flag, environment variable, config file or default each value came from.
One supply chain. Ten packages, one repository, one release pipeline, and no dependency outside the burgee family in any of them. Six take nothing at all; the other four take only each other.
Proved by the incumbents' own tests. Each drop-in path is graded by the original package's own test suite, run in CI beside a control that runs the real package: commander's 1360 of 1360, yargs' 816 of 816, and 36 more hosts on the compatibility page. The benchmarks below publish every comparison, the rows burgee loses included.
⭐ Star the repo · 👀 Watch releases · 🐛 Report a bug
📖 Docs: burgee.interlace.tools · llms.txt
Start here · Switch in one command · Measured · The family · Status · Repo map · Research
👋 Start here
npx burgee init my-cli && cd my-cli && npm install
init writes this file as cli.mjs, and a package.json that runs it. Nothing else.
#!/usr/bin/env node
// cli.mjs — the whole CLI
import { defineCommand, run } from 'burgee';
run(defineCommand({
name: 'greet',
description: 'Greet someone by name',
options: { name: { type: 'string', required: true, description: 'who to greet' } },
effects: 'read_only', // what running it does to the world; required, and what --mcp reads
run: ({ options }) => ({ greeting: `hello, ${options.name}` }),
}));
$ node cli.mjs --name ada
greeting: hello, ada
$ node cli.mjs --name ada --json
{"ok":true,"data":{"greeting":"hello, ada"},"meta":{"provenance":{"name":{"source":"flag","location":"--name"}}}}
$ node cli.mjs # exit 2, not 1
error: missing required option --name
hint: pass --name <value>
usage: greet [options]
options:
--name <value> who to greet (required)
That is the whole thing: one file, one declaration, and a CLI that already speaks to a person, to a script and to an agent.
A burgee is the small swallowtail flag a boat flies to say which club it belongs to — a flag of identity, not of instruction. That is what a command does here: declares itself once, and every surface is that declaration read by a different reader.
🎏 One declaration, every surface
defineCommand() ──▶ manifest ──┬──▶ human help
├──▶ --json one stable envelope
├──▶ --schema versioned, one document per surface
├──▶ --mcp an MCP server, generated
├──▶ completions bash · zsh · fish · pwsh
├──▶ TypeScript types
└──▶ docs + llms.txt
Surfaces cannot drift, because there is nothing to keep in sync. Adding the next one is an emitter, not a feature.
🤖 What an agent sees
The same command, asked for its own definition. No wrapper, no sidecar file, no scraping
--help:
$ node cli.mjs --schema # trimmed; arguments and examples also come back
{
"options": {
"name": { "type": "string", "required": true, "description": "who to greet" }
},
"inputSchema": { "type": "object", "properties": { "name": { "type": "string" } } }
}
$ node cli.mjs --mcp # the same command, served as an MCP server over stdio
Every --schema document validates against burgee/program-schema.json, the JSON Schema the
package publishes, so a project can pin its CLI's surface in CI without writing its own idea of
the shape.
Six issues across nine years of the commander and yargs trackers ask for machine-readable
command structure, and the state of the art today is a regex over --help
(tracker-gap-analysis.md). A manifest is not a
feature bolted on for agents — it is the thing help was rendered from in the first place.
What that is worth needs no model to measure. Ten tasks per variant, one spawn each, non-TTY with stdin closed — the only environment an agent gets — running the same demo program on each engine:
| Variant | hangs / 100 | exit code correct | --json answered |
|---|---|---|---|
| burgee | 0 | 100.0% | 100.0% |
| commander | 0 | 40.0% | 25.0% |
| yargs | 0 | 40.0% | 25.0% |
Exit code is the one that decides an agent's next move: 2 means rewrite the command, any
other non-zero means the command was fine and the world was not. This measures legibility,
not tokens. The token and turn halves of B1 run a model against the same program on every push
to main. What an agent sees quotes
those sessions and lists every run, including the ones where the claim in Measured
misses.
🔌 Extend every layer
Every released package in the family takes plugins, and all nine take them the same way. A plugin is a
plain object. Each package validates it against one published schema — the same
schema.json ships in every package — and each has a check command that shows what a plugin
contributes, or refuses it with a code and the fix, before it ships.
Each package reads its own key and ignores the rest, so one object can extend every layer at once: brand colours, a spinner, a terminal quirk, a config source, where the company's tools live, what to flush on exit, a prompt of its own, and a command every one of its CLIs gets.
| Package | A plugin adds | Check it |
|---|---|---|
bellpull |
resolvers |
npx bellpull check ./plugin.mjs |
burgee |
commands, hooks, locales |
npx burgee check ./plugin.mjs |
caique |
widgets, locales |
npx caique check ./plugin.mjs |
closeout |
handlers |
npx closeout check ./plugin.mjs |
controlroom |
keymaps, panes |
npx controlroom check ./plugin.mjs |
flagstaff |
tokens, glyphs, spinners, borders, components |
npx flagstaff check ./plugin.mjs |
linegauge |
widths |
npx linegauge check ./plugin.mjs |
paratext |
capabilities |
npx paratext check ./plugin.mjs |
roundel |
tokens |
npx roundel check ./plugin.mjs |
seniority |
sources |
npx seniority check ./plugin.mjs |
Where an incumbent has an extension point — commander's .hook(), yargs middleware,
cosmiconfig's loaders, inquirer's createPrompt — it lives in one program or one call. A
plugin here is written once and shared: across programs, across layers, and across commander,
yargs and native syntax alike. The plugins page has the
whole nine-layer example, which every released package's check accepts in CI, each incumbent's own
extension point beside ours, and what a plugin cannot do yet.
🔁 Switch in one command
npx burgee migrate --dry-run # what it would change, and nothing written
npx burgee migrate
burgee has drop-in paths for commander and yargs, and the family has them for chalk, ora,
string-width, cross-spawn, signal-exit and the other packages graded level with their own
suites. burgee migrate rewrites the import specifiers and nothing else, then
prints the install command to run next (Migrate). For
one commander program, that is one line:
- import { Command } from 'commander';
+ import { Command } from 'burgee/commander';
Your code and your tests are unchanged. Compatibility is not asserted here, it is graded — each host's own suite, vendored unmodified apart from the import specifier, pointed at burgee and run in CI against a control that runs the real host:
| Host | Front-end | burgee | rate |
|---|---|---|---|
| commander | burgee/commander |
1360 / 1360 | 100% |
| yargs | burgee/yargs |
816 / 816 | 100% |
Measured 2026-09-09 (yargs 2026-09-30, at 18.2.0); 36 more hosts are in
the full table, generated by
npm run compat:page and never hand-edited. Rates are measured against the control's total
rather than against registered tests, because a file that fails to import registers as one
test instead of its twenty and would flatter a partial implementation.
What changes after the switch is what the program can do. It answers --json, --schema and
--mcp, and it takes plugins. commander has no plugin API: its RFC
(#2505) was closed for inactivity on
2026-10-09 without one. yargs offers middleware on one program, not a plugin shared across
programs. A plugin contributes to
the manifest, and the manifest never records which façade filled it, so one plugin works on
commander syntax, yargs syntax and native alike.
🧭 It starts as one file
oclif has every capability on this page and does 10.9M downloads a week against
commander's 508M. The difference is not features, it is shape. So the shape is locked
first, ahead of every other requirement, and Z1 is a test: it installs the published
tarball into a temp directory, writes exactly one file, and runs it. The day that needs a
second file, a config or a build step, CI goes red.
Everything beyond that first rung — precomputed manifests, plugins, the dev loop, scaffolding — is additive and removable.
📏 Measured
2026-10-08 (ba8a89c), a GitHub Actions runner (AMD EPYC 7763, 4 cores), linux/x64, Node 24, 52 interleaved spawns per variant. † is a
published figure taken on another machine, not reproduced here.
| burgee | commander | yargs | @oclif/core | cac | |
|---|---|---|---|---|---|
| Runtime dependencies | 5, none outside the burgee family | 0 | 6 | 18 | 0 |
| Full CLI run over bare node | +20.3 ms | +14.8 ms | +75.0 ms | +131 ms † | +3.8 ms |
| Installed size | 1690 KB | 203 KB | 515 KB | 912 KB † | 40 KB |
The speed comes from node:util.parseArgs being in the standard library, not from a faster
language: burgee is TypeScript, like both incumbents.
Milliseconds are a property of the machine that produced them, so nothing gates on them. What is gated is the ratio between two spawns interleaved in the same run, which cancels the machine out. Ten of those gates are stated in public — eight are met, three of them as downward-only ratchets rather than the bar they started with; one is not met; and one is not settled yet, because its measure changed:
| Claim | Gate | Measured | |
|---|---|---|---|
| the core entry point is under 52 KB bundled | core-under-52kb-bundled |
24,484 bytes | ✅ met |
burgee/yargs is lighter in a user's bundle than yargs |
lighter-than-yargs |
0.974× | ✅ met |
burgee is lighter than cac plus what a cac user installs to match it |
lighter-than-cac-at-parity |
0.251× | ✅ met |
burgee/commander is lighter than commander plus the same |
lighter-than-commander-at-parity |
0.438× | ✅ met |
burgee/yargs is lighter than yargs plus the same |
lighter-than-yargs-at-parity |
0.547× | ✅ met |
burgee starts within 1.55× of cac, the lightest framework in the landscape (ratchet; lowered as it speeds up) |
cold-start-at-or-below-cac |
1.443× | ✅ met |
burgee/commander stays within 1.42× of commander alone in a user's bundle (ratchet; lowered as it shrinks) |
lighter-than-commander |
1.415× | ✅ met |
burgee stays within 2.35× of cac alone in a user's bundle (ratchet; lowered as it shrinks) |
lighter-than-cac |
2.343× | ✅ met |
| an agent spends ≥40% fewer tokens over the benchmark tasks (total over every task-run) | agent-tokens-40pct |
0.699× on the newest CI run (e6cff20); 0.549–0.699 over the 12 runs since B1 installs the demo under its printed name, met on 5 of them |
❌ not met |
| an agent takes ≥30% fewer turns over the benchmark tasks (total over every task-run) | agent-turns-30pct |
0.677× on the newest CI run (e6cff20); 0.583–0.703 over the same 12 runs, met on 9 of them |
unmeasured as a record until the next CI run; the figures are its per-task detail, summed |
The two agent claims changed measure on 2026-10-09 (D-20261009-b1-totals-and-explain). They compared pooled medians until then, and the median ratio was decided by commander's luck: burgee holds at 3 turns, while commander's median lands at 4 or 6 from run to run, so the same build read 0.5 or 0.75. They now compare the tokens and turns each CLI spends, summed over all 25 task-runs. The figures above are those sums, recomputed from each landed run's per-task detail. On the old measure the newest run read 0.492 tokens and 0.5 turns, both met.
The two ways to ask the bundle question, and why both are here
burgee is 24,484 bundled bytes and cac is 10,452, so the bare row reads 2.343× and it
stays on this page. It is also not the choice anyone makes. A program that picks cac and
then wants its config file read, its shutdown bounded on every path out, and its cursor handed
back on Ctrl-C installs three more packages — and that is what one import of burgee competes
with:
| the incumbent alone | + what you add to match burgee | ours | |
|---|---|---|---|
cac |
10,452 B | 97,692 B | 24,484 B |
commander |
39,084 B | 126,335 B | 55,293 B |
yargs |
111,686 B | 198,784 B | 108,736 B |
The additions are cosmiconfig (find and load a config file), exit-hook (run cleanup on
every path out, including a signal) and restore-cursor (hand the terminal back), bundled
together so a module two of them share is paid for once — which is what a real bundler does.
The rule that stops this being a rigged denominator: a package may only enter a stack where
this repository publishes a graded drop-in for it — a compat-oracle row whose pass rate
comes from that package's own test suite. seniority/cosmiconfig, closeout/exit-hook and
closeout/restore-cursor each have one, which is evidence we do the job rather than our word
for it, and parity.test.ts fails if an addition names a package
with no active row. It is self-limiting on purpose: we cannot pad a stack with packages we
merely resemble.
And the capabilities with nothing to add against — --schema, --mcp, the { ok, data }
envelope on every command, agent detection, option relations and Standard Schema — are priced
at zero. They are the part of the premium that is real and unpriced, and saying so is worth
more than finding a package to charge for them.
The three that are ratchets
cold-start-at-or-below-cac and the two bare weight rows began as ≤ 1 and have a
measured floor above it, and it is worth saying plainly rather than leaving as a to-do.
cac is 10,452 bytes of parser and help renderer; burgee's 24,484 is that plus coercion,
choices, relations, Standard Schema, configuration precedence, signal-bound shutdown, terminal
restore and agent detection.
Our commander/command.js is 33,487 bundled against commander's 27,226, and the front-end also
carries a cross-platform spawn that cannot go lazy without giving up parse()'s synchronous
contract and the 1360 / 1360 compat row that rests on it. And import 'cac' is one file in
4.2 ms where import 'burgee' is twenty-one in 20 ms. Closing them means deleting the product,
not optimising it: the analysis is D-102, and the shrink that moved every
non-default path behind await import() is D-148.
So a bar we will never meet is no longer what these rows publish. Since D-157 each is a downward-only ratchet: a ceiling just above today's measurement, enforced on every benchmark run, that may be lowered by anyone and raised only by a new decision.
| Gate | Was | Ceiling | Set from, on 2026-09-27 |
|---|---|---|---|
cold-start-at-or-below-cac |
≤ 1 | 1.55× | lowered 2026-10-11 after #898: the 26 CI runs since, mean 1.424, max 1.463; mean + 3 sd, rounded up to 0.05 |
lighter-than-commander |
≤ 1 | 1.42× | lowered 2026-10-19 after commander-front-end-diet: measured 1.416 in CI and locally, plus 80 B for CI's heavier build, rounded up to 0.005 (first set at 1.565) |
lighter-than-cac |
≤ 1 | 2.35× | measured 2.317, plus 80 B for CI's heavier build, rounded up to 0.05 |
The ceilings live in claim-ratchets.json with their
history, and claim-ratchets-lock.test.ts refuses a
step up that does not cite a newer decision. npm run ratchets:propose prints the lower
ceiling each can move to from the latest measurement. The size comparison to lead with is still
the at-parity rows above; the bare rows stay on the page because a suite that only showed the
comparisons we win would be lying by selection.
The agent rows are a different gap. B1 has run once, locally (D-147), and neither row becomes ✅ until the weekly CI job measures it, which waits on a credential only the owner can mint (D-150).
Installed size is our largest number and it is larger than commander's. It buys no dependency
outside the burgee family and six drop-in front ends, and it stays on the page either way: not met and
unmeasured are different outcomes and neither collapses into the other. Every bundle figure
in this section, including the ones that go against us, is written by npm run readme:gates
from a fresh bundle of the build, and a push whose build measures anything else is refused
(readme-gates-lock.test.ts). The millisecond and
installed-size figures come from the measurement published in
benchmarks.mdx and pinned in
comparison.mdx.
🔐 Supply chain and continuity
npm i burgee installs six packages, all of them from this repository, and installing any
family package runs no install script
(install-graph-lock). Every release is published
through npm trusted publishing with SLSA provenance and no npm token
(trusted-publishing-lock). Everything is MIT, and
each incumbent's own test suite is vendored in packages/compat-oracle,
so a fork keeps the conformance tests that tell it whether it still behaves the same.
Supply chain and continuity has the install
graph for each package and the check behind every statement.
Why it's built this way gives the reasoning behind
the family, and what it does not claim.
👥 Who is this for?
| You are… | What burgee gives you |
|---|---|
| Shipping your first CLI | One file, no dependency outside the burgee family, help and --json for free |
| Maintaining a commander CLI | A one-line import swap, graded against commander's own suite |
| Building for agents | --schema, --mcp and a stable envelope, projected — never hand-written |
| On a platform team | Plugins that work across commander, yargs and native syntax alike |
🧩 The family
One family, cut from one ink, with the Interlace mark on every one of them and one light crossing all ten. Only burgee flies the swallowtail: a roundel is rings, a flagstaff is a flag hoisted on a pole, a caique is a parrot, a line gauge is the printer's rule with its ticks cut through it, seniority is rank chevrons, a bellpull is the cord and its pull, a closeout is the double rule an accountant draws under a settled total, and a paratext is a page with its text cut away — only the margins are left — and a control room is a console screen with its panes cut through it. The light sweeps where a page can afford motion and parks itself under prefers-reduced-motion; the favicon and the npm READMEs take the still one. Every mark is burgee/brand output — ten shapes, one declaration — regenerated by npm run brand and drift-checked in CI.
burgee declares; the output stack is what a CLI shows; the foundation is what it stands on. Each package is an independent product with its own README, installs on its own, and declares no dependency outside the burgee family.
Ten packages, one repository, one release pipeline and one supply chain to audit, with a
single schema byte-identical in every tarball. Six leaves depend on nothing, and every other
dependency points one way, down the layers;
the locks that hold each rule are named there. Releases are published through npm trusted
publishing, with SLSA provenance and no npm token (trusted-publishing-lock). The case for one
codebase is the number of things a user has to trust, not convenience.
| Layer | Package | What the layer owns | Migrates from | Status | |
|---|---|---|---|---|---|
| Engine | argv, dispatch, manifest | burgee |
one declaration projected to help, --json, --schema, MCP, completions, types |
commander · yargs · meow | released — burgee@1.6.7 |
| Output stack | colour | roundel |
one output policy, nine semantic tokens, a contrast-checked theme, and chalk's API over them | chalk · picocolors | released — roundel@1.2.0 |
| render | flagstaff |
frame loop with a static projection; plugin host for spinners, progress, boxes, tables | ora · log-update · boxen · cli-table3 | released — flagstaff@1.7.3 |
|
| prompt | caique |
prompts that are flags first, and never hang | inquirer · clack · prompts | released — caique@1.4.5 |
|
| screen | controlroom |
keyboard-driven screens, inline or full-screen, over flagstaff's loop, each with a static projection | ink · @inkjs/ui | released — controlroom@1.0.6 |
|
| Foundation | text | linegauge |
measure, wrap, truncate and slice styled text without the edge fraying | string-width · wrap-ansi · strip-ansi · slice-ansi | released — linegauge@1.1.4 |
| config | seniority |
precedence across flag, env, project file, home file and default — with provenance | cosmiconfig · lilconfig · dotenv · rc | released — seniority@1.1.0 |
|
| process | bellpull |
run a subprocess; resolve the executable; return a result every caller can read | execa · cross-spawn · which | released — bellpull@1.0.9 |
|
| lifecycle | closeout |
exit handlers that run once on every path, terminal restore, bounded deadline | signal-exit · exit-hook · restore-cursor | released — closeout@1.1.7 |
|
| terminal | paratext |
hyperlinks, images, window title, clipboard, notifications, bell — each with a static fallback | ansi-escapes (OSC half) · terminal-link · term-img | released — paratext@1.0.10 |
All ten are released on npm at 1.x, with their public API under semver
(SECURITY.md says which releases are supported). Where an incumbent's own test suite has been vendored, the
compat oracle grades the drop-in path against it and publishes the rate — including the ones
not yet at 100% — on the compatibility page.
Released is not the same as accepted: the four
foundation packages began as 0.0.1 name reservations, were built out in waves F1–F4, and
their intents under .sdlc/intents/cli-foundation-stack/
are still at draft — the human gate on the design has not run. The bellpull intent carries a kill gate, because a zero-dependency
rival already holds the weight pitch in that layer, and its spec says plainly that the package
was built before that gate was evaluated. The measurements behind the layers are in
candidate-layers.md and
replacement-map.md.
📚 Repo map
| Path | Purpose |
|---|---|
packages/burgee/ |
The framework. burgee is the engine, burgee/commander the compat façade, burgee/testing the in-process harness (T1). |
packages/roundel/ |
roundel — the colours a CLI carries: roundel/policy (outputMode, colorLevel), roundel/tokens (nine semantic tokens), roundel/theme (fly(), contrast-checked), roundel/contrast, and roundel/chalk — chalk 6's API over the tokens, graded by chalk's own suite (58 / 58, against a 58 / 58 control). Intent in .sdlc/intents/roundel/. |
packages/flagstaff/ |
flagstaff — the staff the flag flies from: a frame loop with a static projection for agents, and the plugin host for spinners, progress, boxes and tables. Released; its one dependency is roundel. Intent in .sdlc/intents/flagstaff/. |
packages/caique/ |
caique — the parrot that always answers back: prompts that are flags first and never hang. Released, with graded caique/inquirer and caique/clack paths; intent in .sdlc/intents/caique/. |
packages/linegauge/ |
linegauge — a printer's rule for text: width, wrap, truncate and slice, grapheme-correct over Intl.Segmenter. Released; intent in .sdlc/intents/linegauge/. |
packages/seniority/ |
seniority — which source outranks which, with provenance for every resolved value. Released; intent in .sdlc/intents/seniority/. |
packages/bellpull/ |
bellpull — pull here, work happens there: subprocesses with a structured result and a static projection. Released; intent in .sdlc/intents/bellpull/. |
packages/closeout/ |
closeout — settle and finish: exit handlers that run once, terminal restore, and a deadline so shutdown cannot hang. Released; intent in .sdlc/intents/closeout/. |
packages/paratext/ |
paratext — everything around the output that is not the output: hyperlinks, images, window title, clipboard, notifications and the bell, each with a static fallback. Released; intent in .sdlc/intents/paratext/. |
packages/controlroom/ |
controlroom — where a system is run from: full-screen, keyboard-driven terminal screens with a static projection for every other caller, and controlroom/ink, ink's drop-in, graded by ink's own suite. Released; intent in .sdlc/intents/controlroom/. |
packages/compat-oracle/ |
Internal, never published. Grades compatibility using the hosts' own suites, plus reference drivers that run the real incumbents for byte-for-byte comparison. |
examples/ |
Demo CLIs and the conformance suite that runs every floor case on every host. |
apps/docs/ |
The front-door documentation site (Next.js + fumadocs), deployed at burgee.interlace.tools with llms.txt and a Markdown twin of every page. Every other package has its own site at https://<package>.interlace.tools — apps/docs-<package>/, on the shared chassis apps/docs-chassis/ — named once in .github/vercel-apps.json. |
.sdlc/intents/ |
Stage 1 + 2 artifacts of the AI-native SDLC: intent.md + spec.md per change, and the wave plan. |
.sdlc/research/ |
The evidence everything above rests on. |
.sdlc/brand/ |
What each package is and what its mark has to say (identity model), and the brief a designer would work from. |
The research
Every claim in the design traces to one of these, and each records what it could not determine as well as what it found.
competitor-landscape.md— the measured map: downloads, cold start, the compatibility bill, and why we implement rather than wrap.tracker-gap-analysis.md— every open item in both trackers. Commander has 8; yargs has 211, 86% predating 2023. Six issues over nine years ask for machine-readable command structure, and the state of the art is regex-scraping--help.agent-requirements.md— what agents need, and three widely-quoted claims that do not survive verification.cli-market-requirements.md— ten production CLIs taken apart, including what the security market requires and where both leading scanners get it wrong.architecture.md— the diagrams.lineage.md— the six projects we follow, each reduced to a commitment.
🚦 Status
| What | |
|---|---|
| ✅ Working today | The engine, the exit-code contract, the JSON envelope, help from the manifest, plugins with hook filters, and a burgee/commander façade that runs a real commander program |
| 🔨 Next | The compatibility oracle, and the remaining commander surface |
| ⛔ Not yet true | The dev loop, prompts, lazy commands and groups, and an adopter we did not write ourselves |
Four locks — shape, process-reference, weight per entry point, and the adoption ladder — are
each proven to fail before they passed. A compat façade does not reach 1.0 until its host's
own suite passes 100% (C7): both do today, which clears that gate and not the rest —
burgee's 1.0 waits on the floor, 114 requirements of
which the surfaces, the env/config/schema families and both façades are built. Until then the
rate is published rather than the word "compatible" claimed. linegauge and flagstaff have
already met every written 1.0 criterion and are 1.0: every requirement in each spec is built
(13 of 13 and 12 of 12), and each drop-in path passes its incumbent's own suite in full
(D-170).
🛠️ Working on burgee
npm install
npm test # every lock and unit test, exits non-zero on failure
npm run dev # docs on http://localhost:3100
npm run compat # grade burgee against commander's and yargs' own suites
npm run brand # regenerate every brand surface from one declaration
npm run brand:check # …and fail if any of them was hand-edited since
Dogfooding
This repo runs 12 Interlace ESLint plugins with every rule on at error and zero
warnings allowed:
| Scope | Plugins |
|---|---|
| Everywhere | secure-coding · node-security · conventions · import-next · maintainability · modernization · modularity · operability · reliability |
| The docs app | react-a11y · react-features · browser-security |
The rule list is computed from each plugin's own table, so a rule shipped in a plugin release
is on here the day it lands — browser-security alone brings 41. Every exception is named in
eslint.config.mjs with its reason.
The three scoped to apps/ are scoped for a reason: the engine never runs in a browser, so
storage, postMessage and DOM-sink rules have nothing to say about it — but the docs app is
a real browser application, and that is exactly the surface they grade.
It earns its keep. During this project those rules caught a prototype-pollution vector in
our own option parsing, a barrel import that cost ~5ms of startup, and a façade reaching for
process behind the runtime seam.
🤝 Contributing
The most useful thing anyone can send is an adopter we did not write: a real CLI ported over, and a report of what broke. That is the only compatibility evidence that counts, and every failure becomes a case in the conformance suite.
CONTRIBUTING.md has the loop, how a change is shaped before it is written, and what the checks will hold you to. SECURITY.md is the private reporting path — please do not open a public issue for a vulnerability.
- Ported a CLI? Tell us what broke — the highest-value report we get
- Found a bug? Open an issue with the Node version and the exact argv
- Have an idea? Open an issue
📄 License
MIT © Ofri Peretz — see LICENSE. Every published package ships the licence text in its tarball.
Flying the flag for CLIs that humans and agents can both read.