The floor
The requirements every CLI on burgee meets, and who holds each — runtime, lint, or both. 74 of the floor's 114 are tabled here.
The floor is 114 requirements: the 109 rows of the requirement tables in
.sdlc/intents/burgee/spec.md
plus the five shape-lock rows Z1–Z5 above them, which is the count that spec states. This
page tables 74 of them — the families below. The spec is the complete list, and what is
built, requirement by requirement, is recorded there too.
Each requirement is held by the runtime, by the lint plugin
(eslint-plugin-cli-floor), or both. A rule with neither is a suggestion, and suggestions are
not a floor. Evidence for every line is an open issue in a leading CLI library — see
Research.
Discoverability
| ID | Requirement | Holds |
|---|---|---|
| F1 | --schema prints the full command tree — commands, options, types, defaults, env bindings, examples, deprecations, exit codes — as JSON | runtime |
| F2 | --help --json prints help as data; text help is rendered from that data | runtime |
| F3 | Every command declares a description and at least one single-line example | lint |
| F4 | Commands may be grouped and hidden; groups are stable in the schema | runtime |
Output
| ID | Requirement | Holds |
|---|---|---|
| O1 | --json on every command, one envelope { ok, data, error?, meta } | both |
| O2 | No ANSI, spinners, redraws or prompts when stdout is not a TTY or NO_COLOR is set; FORCE_COLOR overrides | both |
| O3 | Command code writes through the output layer, never console.* | lint |
| O4 | Colour via util.styleText; no chalk dependency | both |
| O5 | stdout is flushed before every exit path | runtime |
Errors and lifecycle
| ID | Requirement | Holds |
|---|---|---|
| E1 | Exit codes are a contract: 0 ok, 1 runtime, 2 usage, 3 config, 4 cancelled, 130 SIGINT | both |
| E2 | A runtime failure never prints help; a usage error never prints a stack | both |
| E3 | Every error carries code, message, hint and, where possible, fix | runtime |
| E4 | Explicit lifecycle: parse → config → validate → run → render → exit; validation failure stops the handler; async handlers are awaited | runtime |
| E5 | SIGINT restores the terminal and exits 130 | runtime |
Values and precedence
| ID | Requirement | Holds |
|---|---|---|
| V1 | Flags > env > config file > default, per command, without leaking env into unrelated commands | runtime |
| V2 | Any option may declare an env name; env-bound options appear in help and schema | both |
| V3 | --explain <option> reports where each value came from | runtime |
| V4 | name, version, description come from the owning package.json, not the monorepo root | runtime |
| V5 | Reserved option names cannot be redefined | lint |
Validation
| ID | Requirement | Holds |
|---|---|---|
| S1 | Options and positionals are declared once as a schema; types and help are derived | runtime |
| S2 | Relationships are first-class: exactly-one-of, at-least-one-of, implies, conflicts | runtime |
| S3 | Invalid numbers fail validation, never NaN; invalid types fail at definition time | runtime |
| S4 | - means stdin for file positionals; -- pass-through reaches child processes intact | runtime |
Prompts
| ID | Requirement | Holds |
|---|---|---|
| P1 | Every prompt is backed by a flag; a flag value skips the prompt | lint |
| P2 | In a non-TTY the prompt becomes an error naming the flag, exit 2 | runtime |
Deprecation and testing
| ID | Requirement | Holds |
|---|---|---|
| D1 | Deprecating anything requires a replacement, shown in help, schema and the warning | both |
| D2 | Completions for bash, zsh, fish and PowerShell are generated statically from the schema | runtime |
| T1 | A CLI runs in-process with injected argv, env, stdin, cwd and TTY-ness, returning { code, stdout, stderr, json } | runtime |
Validation, continued
| ID | Requirement | Holds |
|---|---|---|
| S5 | Every option has one declared type and one canonical camelCase key; kebab-case is derived | runtime |
| S6 | Relations are validated before choices and before the handler | runtime |
| S7 | A flag type never consumes a value | runtime |
| S8 | multiple options accept repetition and a declared separator | runtime |
Values, continued
| ID | Requirement | Holds |
|---|---|---|
| V6 | Config discovery order is fixed, documented, and shown by --explain | runtime |
| V7 | extends merges deeply and resolves from the extending file's node_modules | runtime |
Help
| ID | Requirement | Holds |
|---|---|---|
| H1 | Help is rendered from the manifest only | runtime |
| H2 | Examples are single-line and copy-pasteable | both |
| H3 | Width comes from the runtime, default 100 in non-TTY | runtime |
| H4 | Command options render before global options | runtime |
| H5 | Deprecations and env names render inline | runtime |
| H6 | Type hints are off by default | runtime |
Completions, continued
| ID | Requirement | Holds |
|---|---|---|
| D3 | Completions never execute the CLI unless an option is marked dynamic | runtime |
| D4 | Every shell script is snapshot-pinned and exercised by that shell in CI | runtime |
| D5 | A Fig spec is exported from the same node | runtime |
Prompts, continued
| ID | Requirement | Holds |
|---|---|---|
| P3 | Cancellation exits CANCELLED (4), never RUNTIME | runtime |
Modularity
| ID | Requirement | Holds |
|---|---|---|
| M1 | Every command carries a group | runtime |
| M2 | The manifest is complete before any handler module loads | runtime |
| M3 | Every plugin's contributions are attributed in the manifest | runtime |
| M4 | Shared options are declared once and copied per command | runtime |
| M5 | A deprecated command names its replacement in help, schema and warning | both |
| M6 | resolveCommand and runCommand are public | runtime |
Packaging
| ID | Requirement | Holds |
|---|---|---|
| K1 | No dependency outside the burgee family in any published package; hosts and UI libraries are peers | lock |
| K2 | ESM only, Node 20.19+ or 22.13+ (restated 2026-09-23, D-132; was Node 24 or newer) | lock |
| K3 | Node natives over packages | both |
| K4 | An artifact gate runs on the built output before publish | release |
| K5 | Per-package size budget, ratcheted | lock |
| K6 | Weight is paid per import: compatibility and host quirks live behind their own specifiers, never behind a runtime flag | lock |
Compatibility
Every claim about a host is graded by that host's own test suite, so "compatible" is a number rather than an adjective. The rate is published on every release.
| ID | Requirement | Holds |
|---|---|---|
| C1 | Every package declares a supported host range; the host's own suite runs against it at every supported major | CI |
| C2 | A compatibility front-end is graded by the host's own suite through a one-line shim; the rate is published per release | CI |
| C3 | Every package is tested on every Node LTS in its engines range, across Linux, macOS and Windows | CI |
| C4 | Every intentional divergence has an id, a written reason, and a test asserting it — an unlisted failure is a bug | lock |
| C5 | Pass rates ratchet; lowering one requires editing the baseline with a reason | CI |
| C6 | Vendored suites record their upstream commit; a scheduled job opens a pull request when the count changes | workflow |
Benchmarks
Four axes, because there are four kinds of public claim. Every number the project cites anywhere links back to the generated benchmarks page.
| ID | Requirement | Holds |
|---|---|---|
| B1 | Agent cost: tokens, turns and success per task, layer on versus off | band |
| B2 | Performance: cold start p50/p95 over 30 or more spawns, always including a bare-node floor row | band |
| B3 | Compatibility: per-host pass rate, read from the oracle, never recomputed | band |
| B4 | Weight: bundled size per entry point against a published target; a core-only import pulls zero front-end bytes | band |
| B5 | Every axis emits one JSON shape, so one collector reads all of them | lock |
| B6 | Performance, compatibility and weight gate every pull request; agent cost runs weekly | CI |
| B7 | Every public number links to the generated benchmarks page | lock |
Agent interface
The manifest already carries everything an MCP tool definition needs. These requirements turn it into a served interface rather than a document.
| ID | Requirement | Holds |
|---|---|---|
| N1 | --mcp serves the CLI over MCP stdio; tool definitions are generated from the manifest, never hand-written | runtime |
| N2 | A command is kept from agents only by a declaration: effects: 'withheld'. Every runnable command on burgee's own API declares its effects; a façade command that declared nothing is listed as effects: 'undeclared', with no hints | both |
| N3 | No dependency outside the burgee family: JSON-RPC over stdio against node:readline | lock |
| N4 | Tool results are the standard envelope, so MCP and --json callers see identical payloads | runtime |
| N5 | --mcp implies non-TTY: no prompts, no colour, structured errors | runtime |