burgee
Concepts

The family and its layers

One job per package: six leaves that depend on nothing, three packages that compose them, dependencies that point one way, nothing installed from outside the family, and the locks that hold each rule.

What it is

burgee is one package of ten published from this repository. Each owns one job and replaces the incumbents that do that job today; the package map lists them. Nine have an API. The tenth, controlroom, is reserved at 0.0.1 with no API yet.

The nine split into two layers:

  • Six leaves depend on nothing: bellpull (subprocesses), closeout (exit handlers and terminal restore), linegauge (text width), paratext (terminal escapes beyond colour), roundel (colour and the output policy) and seniority (configuration).
  • Three compose them: burgee (the CLI framework), flagstaff (rendering) and caique (prompts).

Why it exists

A program can adopt one layer without the others: a CLI that only needs text measured installs linegauge and nothing else. That is only true while the split is real. The moment burgee measures a string's width itself, or reaches for chalk instead of roundel, the layers become a directory layout. The rules below are what keep it real, and each one is a test rather than a convention.

The graph

The family's dependency graph: burgee depends on bellpull, closeout, linegauge, roundel, seniority; caique depends on closeout, linegauge, paratext, roundel; flagstaff depends on closeout, linegauge, paratext, roundel. The 6 leaves depend on nothing.

3 packages compose, 6 are leaves, and 1 is reserved: 13 dependency edges inside the family, every one from a package that composes to a leaf, and none outside it. Generated by npm run layers:page from each public package's package.json; do not edit by hand.

PackageLayerDepends onUsed byOutside the family
burgeecomposesbellpull, closeout, linegauge, roundel, seniority—nothing
caiquecomposescloseout, linegauge, paratext, roundel—nothing
flagstaffcomposescloseout, linegauge, paratext, roundel—nothing
bellpullleafnothingburgeenothing
closeoutleafnothingburgee, caique, flagstaffnothing
linegaugeleafnothingburgee, caique, flagstaffnothing
paratextleafnothingcaique, flagstaffnothing
roundelleafnothingburgee, caique, flagstaffnothing
seniorityleafnothingburgeenothing
controlroomreserved, no API yetnothing—nothing

The rules

1. Nothing from outside the family. A published package's dependencies, peerDependencies and optionalDependencies name only packages published from this repository. What a caller installs comes from one repository and one supply chain. Pinned by installs nothing from outside this repository — dependencies, peers or optional (D-111) in package-shape-lock.test.ts, by holds zero external runtime dependencies across the family in layer-boundaries-lock.test.ts, and, against a real install of each package on its own, by installs nothing but itself and the in-family packages it declares in independence-install-lock.test.ts. A README, a description or a docs page that claims a dependency count the manifest contradicts fails dependency-claim-lock.test.ts.

2. Dependencies point one way. package-shape-lock reads the family bottom-up — linegauge, seniority, bellpull, closeout, paratext, roundel, flagstaff, caique, controlroom, burgee — and a package may depend only on one earlier in that order: has no external runtime dependencies, and same-repo ones only point up the family (K1, U1, U6). Five of the leaves are its foundation tier, graded by %s depends on nothing: it is the floor.

3. Never leaf to leaf. A leaf that depended on another leaf would stop being a leaf, and every package that uses it would sit a third tier up. The generator of the graph above refuses that: every edge must end at a package that depends on nothing in the family, and npm run layers:page -- --check runs in the fast job the required Quality Gate reads. paratext is the case that shows the cost: it is a leaf, so its hyperlink detection keeps a declared fork of supports-color rather than importing roundel.

4. No package depends on an incumbent a sibling replaces. The list of what each layer replaces is LAYERS in compat-oracle/src/demand.ts, and that list is the definition of each layer's job: needing string-width is the same thing as needing linegauge. layer-boundaries-lock derives the rule from it — %s depends on no incumbent another layer replaces — so the job table is written once.

5. A sibling owns each mechanism. A dependency is the easy half to check. The expensive half is writing a sibling's code by hand, which leaves no dependency behind. inline-implementation-lock.test.ts scans every package's source for 17 shapes that can only be a layer's job, and fails when a file outside the owner has one:

OwnerMechanisms it owns
bellpullspawning a subprocess; resolving an executable across platforms; looking a command up on PATH
closeoutregistering a signal handler; hiding or showing the cursor; switching raw mode; hooking process exit
roundelreading a colour variable (NO_COLOR, FORCE_COLOR, COLORTERM, CLICOLOR); deciding whether this is CI
senioritywalking up the directory tree; naming a .env file
caiquedecoding keypresses
flagstaffcarrying spinner frames
linegaugematching ANSI escapes with a regex; counting painted rows by their newlines; measuring, wrapping or stripping text by hand
paratext, linegauge, roundel, closeoutspelling a CSI or OSC sequence

A drop-in façade may reproduce an incumbent's API and behaviour; it may not reimplement the mechanism a layer owns (Drop-ins has the rule). The files that still do are rows in the lock's KNOWN table, each with its reason, and the table may only shrink: only goes down and keeps the list honest — an entry that no longer offends must be removed. The lock says plainly what it cannot see: measuring display text with .length looks like any other .length, and still needs a reader.

6. Every layer is used. A layer nothing else uses has never been proven to fit the stack. composition-lock.test.ts reads the band .sdlc/bands/composition.json: the count of edges inside the family may only go up (has at least as many edges as the last time this was recorded), and every package but burgee is used by another or is listed as awaiting one with a reason (%s is used by another package, or is declared as awaiting one). A package that gains a consumer must leave that list. Today flagstaff, caique and controlroom are awaiting: burgee renders no spinner, box or table, and draws no prompt, so an edge to either would be manufactured.

Run it

Each leaf installs and loads on its own. This file imports two leaves and nothing that composes them:

leaves.mjs
import { width } from 'linegauge';
import { colorLevel } from 'roundel/policy';

console.log(width('古池や'), width('\u001B[31mred\u001B[39m'));
console.log(colorLevel({ env: { FORCE_COLOR: '2' }, isTTY: { stdout: false } }));
node leaves.mjs
6 3
2

Where the rules live

On this page