Changelog
Every release of roundel, newest first, from its CHANGELOG.md — what changed and the pull request it came from.
2.0.0
Major Changes
- #1491
5054fb7Thanks @ofri-peretz! - Requires Node 22.13 or later; Node 18 and 20 are end-of-life.engines.nodeis now>=22.13.0(was^20.19.0 || >=22.13.0), and Node 22.13, 24 and 26 are the versions tested.burgee migratenow warns when a project'sengines.nodeadmits a Node below 22.13.0, and its suggested fix isnpm pkg set "engines.node=>=22.13.0".
1.3.2
Patch Changes
- #1483
3e69fb2Thanks @ofri-peretz! - The package manifest names its author and publishes withpublishConfig{ "access": "public", "provenance": true }. burgee'shomepageis its landing page, https://burgee.interlace.tools. oxmutate gainskeywords, so npm search finds it, and asideEffectslist (its CLI, its worker and its Vitest runner run on load), so a bundler can drop the rest. No code changes.
1.3.1
Patch Changes
-
#1472
99fefcfThanks @ofri-peretz! - One fix.- A grey hex token at 16 colours is the name chalk paints it.
fly()rounded each channel of a grey straight to the sixteen, so#e6e6e6–#f8f8f8flew aswhiteBrightand#7f7f7fasblack. ansi-styles, androundel/chalkwith it, send a grey through the 24-step ramp first, whose lightest entry is#eeeeee: those greys arewhite. Every grey from#000000to#ffffffnow flies as chalk paints it, and every colour that is not a grey is unchanged.
- A grey hex token at 16 colours is the name chalk paints it.
1.3.0
Minor Changes
-
#1233
c92e417Thanks @ofri-peretz! - New entry pointroundel/colorette: colorette 2's forty-one styles as named exports,isColorSupportedandcreateColors({ useColor }), graded 67 / 67 by colorette 2.0.20's own suite, level with colorette itself, and bundled at 2,425 B against colorette's 2,602. Colour is colorette's own rule, decided once at import — aNO_COLORorFORCE_COLORthat is set at all,--no-colorand--color, Windows, a terminal with aTERM, andCIon GitHub Actions, GitLab or CircleCI — notroundel/policy's; with colour off every style isString, as colorette's are.roundel/policydoes not change. -
#1255
676279aThanks @ofri-peretz! - New entry pointsroundel/kleurandroundel/kleur/colors: kleur 4's two modules, graded 15 / 15 by kleur 4.1.5's own suite, level with kleur itself, and bundled at 1,049 B against kleur's 1,839 and 930 B against kleur/colors' 997.roundel/kleuris kleur's one object —enabledand twenty-six chainable styles,kleur.red().bold('x')— andrequire('roundel/kleur')returns it, asrequire('kleur')does;roundel/kleur/colorsis kleur's plain functions and$. Colour is kleur's own rule, decided once at import and kept on the switch a caller assigns — a truthyNODE_DISABLE_COLORS, aNO_COLORthat is set at all,TERM=dumb, thenFORCE_COLORother than0or a terminal on stdout — notroundel/policy's.roundel/policydoes not change. -
#1218
6213a30Thanks @ofri-peretz! - New entry pointroundel/yoctocolors: yoctocolors 2's 61 style functions as named exports and as the default namespace, graded 78 / 78 by yoctocolors 2.2.0's owntest.js, level with yoctocolors itself, and bundled at 3,275 B against yoctocolors' 3,395. Colour is yoctocolors' own rule, Node'shasColors()decided once at import, notroundel/policy's; with colour off each function returns its input untouched, as yoctocolors' does.roundel/policydoes not change.
1.2.1
Patch Changes
-
#1258
e3b3a41Thanks @ofri-peretz! -checkreads a.jsonplugin file as data. It handed every file toimport(), and Node refuses a.jsonmodule without an import attribute (ERR_IMPORT_ATTRIBUTE_MISSING), so a plugin written as JSON got Node's message and no verdict, from the host's owncheckand throughburgee check, which runs it. -
#1266
02e6f9fThanks @ofri-peretz! - The family plugin schema (schema.json, identical in every plugin host) now describesformats, burgee's output-format key. This package reads nothing new, and a plugin carryingformatsregisters here as before. -
#1252
efc994eThanks @ofri-peretz! - Every benchmark figure in the README, a ratio, a byte count or a cold-start time, now links to the benchmarks page where it is measured.
1.2.0
Minor Changes
- #1177
122635dThanks @ofri-peretz! - New entry pointroundel/supports-color: supports-color 11's default{ stdout, stderr }and itscreateSupportsColor(stream, options), graded 55 / 55 by supports-color 11.0.0's owntest.js, level with supports-color itself, and bundled at 1,781 B against supports-color's 2,145. It answers in supports-color's order, which differs fromroundel/policyin two places on purpose: a setFORCE_COLORoutranks--no-colorthere, andNO_COLORis not read.roundel/policydoes not change.
Patch Changes
- #1186
af24328Thanks @ofri-peretz! - Published declarations now keep each doc comment's first paragraph, its code examples and its@param,@returns,@throws,@example,@deprecated,@defaultand@seetags, and drop the longer design notes below them, so the tarballs are smaller (burgee by 5.7% gzipped, bellpull and closeout by about a fifth). Types are unchanged, and hover text in an editor keeps its summary.burgee's README also namesburgee/program-schema.json, the JSON Schema every--schemadocument validates against.
1.1.3
Patch Changes
- #1160
0b381a0Thanks @ofri-peretz! - Export./package.json.require('<package>/package.json')threwERR_PACKAGE_PATH_NOT_EXPORTED, which broke tools that read a dependency's manifest that way: bundlers, license scanners and version checks.
1.1.2
Patch Changes
- #1130
c05c86eThanks @ofri-peretz! - Every drop-in subpath now type-checks undermoduleResolution: node10, and undermodule: commonjswith no resolution set, which is TypeScript's default there.import … from 'burgee/commander'used to fail with TS2307 in such a project, althoughrequire()of the same specifier worked. Each package now declarestypesand atypesVersionsmap, derived fromexports, with one key per typed subpath pointing at the fileexportsalready names.exportsis unchanged, sonode16,nodenextandbundlerread exactly what they read before.controlroom/ink's declarations no longer name the untypedreact-reconciler, which was TS7016 for consumers withskipLibCheck: false.
1.1.1
Patch Changes
- #1045
c4fd9a3Thanks @ofri-peretz! - The family plugin schema (schema.json, identical in every plugin host) now describeslocales, burgee's locale-pack key. A plugin carryinglocalesvalidates against the shared schema as it does againstburgee/locale-pack-schema.json, which lists every message id. This package reads nothing new, and a plugin carryinglocalesregisters here as before.
1.1.0
Minor Changes
-
#1014
65afee5Thanks @ofri-peretz! - A quiet runtime. roundel'sRuntimegains an optionalquietfield, and flagstaff draws nothing when it istrue: no spinner, no progress, no status line, and the frame clock never armed. Data and errors stay the caller's to print and are never quieted. burgee's global--quietsets it. -
#1008
bcc438aThanks @ofri-peretz! - New entry pointroundel/picocolors: picocolors' default export, itscreateColorsand itsisColorSupported, graded 1 / 1 by picocolors 1.1.1's owntests/test.jsand bundled at 1,607 B against picocolors' 2,739. Its nine environments fromtests/environments.jsare held byte-equal to picocolors' own source inpicocolors.test.ts. The forty-one styles are on the default object, not named exports, as with picocolors itself under Node's ES loader.
Patch Changes
- #1022
ecb0a00Thanks @ofri-peretz! - The tarball no longer carries declaration files that noexportsentry reaches. TypeScript could never open them, because a deep import is refused byexports. Every entry's types are unchanged. The ten packages drop 71 files and 118,312 bytes between them, burgee 35 files and 76,504 bytes, which takes its tarball from 251,814 to 225,278 bytes gzipped.
1.0.4
Patch Changes
- #938
9babb6dThanks @ofri-peretz! - npm description and README: the description says what the package does, then its dependency fact in one of two wordings ("Zero dependencies." or "No dependency outside the burgee family."), then its drop-in paths. The README's family section states how the family is built — the leaves, one-way dependencies, nothing from outside the family, trusted publishing with provenance — and its table column is "Migrates from". No API, compatibility grade or benchmark changes.
1.0.3
Patch Changes
- #917
735f0f6Thanks @ofri-peretz! - README: the opening paragraph says what the package does first. The drop-in path follows it as one line, naming the incumbent andnpx burgee migrate, and every compatibility row and benchmark is unchanged.
1.0.2
Patch Changes
- #897
f29f159Thanks @ofri-peretz! - controlroom has its own docs site at https://controlroom.interlace.tools: its README's Docs line, itshomepage, and every family table now link there.
1.0.1
Patch Changes
-
#848
6ae533eThanks @ofri-peretz! - The familyschema.jsondescribes what five values look like.contractis1, and burgee refuses a plugin that declares none. A caique widget'sstatic(spec)gets{ kind, message, ...sample.done }. A seniorityranksits between the built-in layers at flag 0, environment 10, config file 20,package.json30 and default 40. A burgee hook'sfilteris{ command: RegExp }, now withcommandrequired, so the schema and the host refuse the same filters. -
#857
d80b2d0Thanks @ofri-peretz! - The familyschema.jsoncloses a bellpull resolver'swhenwithadditionalProperties: false, so the schema and bellpull's own validation refuse the same unknown keys.
1.0.0
Major Changes
- #830
1945d65Thanks @ofri-peretz! - roundel 1.0.0. The spec is fully built, every drop-in passes its incumbent's own suite at the latest release level with the incumbent itself, and coverage is 100% on all four measures (D-20261006-foundation-1-0-evidence). 1.0 makes the published entries, the pluginschema.jsonand theroundelbin a semver contract. No API changed in this release.
0.6.3
Patch Changes
- #816
5230016Thanks @ofri-peretz! - controlroom hosts plugins (R10):keymapsandpanesregister throughcontrolroom/plugin'sregister()against the family schema, a screen takes either by name, andcontrolroom check <plugin-file>reports what a plugin contributes. The family schema every host ships gains thekeymapsandpanesdefinitions.
0.6.2
Patch Changes
-
#783
08976aeThanks @ofri-peretz! -ctx.interactiveis roundel'sinteractive(), the family's one rule for whether a person may be asked: a terminal on stdin, noCI, and no detected agent, withFORCE_TTY=1over all three. caique's prompts already ask it, so a handler and a prompt now agree about the same shell.It used to read stdout and ignore
CI. Three things change for a handler that reads it:- Under a non-empty
CIit isfalse, even on a pseudo-terminal. - With stdin piped it is
false, even when stdout is a terminal. - With stdout piped and stdin a terminal (
tool deploy | tee log) it istrue, because an answer can still be typed.
burgee loads
roundel/terminalin a chunk of its own (ctx.js, 330 B), only on the path that runs a handler. Help,--version,--schema,--mcpand failures never load it. The root entry shrinks (35,629 → 35,437 B on disk), and so does the bundled initial load ofimport { run } from 'burgee'(24,297 → 24,278 B).ctx.agent,detectAgent,AGENT_PROBESand help's colour, which reads stdout, are unchanged.runBurgeeforwardsttyonto stdin too, sotty: trueis still a terminal a person can answer on.The agent variables stay at five (
AI_AGENT,CLAUDECODE,CURSOR_AGENT,CODEX_THREAD_ID,GEMINI_CLI). One joins only when it uniquely identifies an agent, soCURSOR_TRACE_ID, which Cursor sets in every integrated terminal, never will. roundel'sAGENTSdocuments that rule, and a test pins that a person in Cursor is asked. - Under a non-empty
0.6.1
Patch Changes
-
#770
6195b99Thanks @ofri-peretz! -roundel/chalkis faster.- A builder keeps the escapes it opens and closes with, built one pair at a time as its chain grows, instead of rebuilding them on every call.
- It reads its links as kept properties rather than through a Proxy trap on every access, and it skips the re-open and line-break passes a string doesn't need.
- In B5, ours ÷ chalk on the 10k-string workload went from 3.55× to 1.17×.
roundel/tokensexports the pieces:painter(at, pair),painted(painter, text)andUNPAINTED.sgr(chain, text)is unchanged../chalkstays under chalk 6.0.0's own source size (9,361 of 9,370 B).
0.6.0
Minor Changes
-
#760
a115799Thanks @ofri-peretz! -roundel/import:fromBase16(scheme)andfromITerm(plist)read a Base16 scheme (its YAML or JSON text, or the object a reader made of it) or an iTerm2.itermcolorsfile into the themefly()takes.error,warn,ok,flagandvaluetake the ANSI hue each one's default names, andgroundthe background; the mapping is exported asBASE16_SLOTSandITERM_SLOTS. The theme is checked byaudit()before it is returned, so a scheme that does not read on its own background is refused with anImportErrornaming the slot, and whatever an importer returns,fly()accepts. Every refusal carries acode(E_IMPORT_FORMAT,E_IMPORT_SLOT,E_IMPORT_CONTRAST) and afix. No network and no bundled corpus: the file is yours to supply. Its own subpath, not re-exported fromroundel.roundel/chalkis now graded by chalk 6.0.1's own suite: 59 / 59, level with chalk itself.
0.5.6
Patch Changes
- #752
22e6daeThanks @ofri-peretz! - A plugin that declarescontract: 0or a negative contract is now refused withE_PLUGIN_CONTRACT, asschema.json's minimum of 1 always said. These hosts checked only that a contract was not newer than the one they know, so 0 and below registered.
0.5.5
Patch Changes
-
#674
e9f45d8Thanks @ofri-peretz! - README: family header, badges, install, migrating, the family table.Every package README now opens the same way — lockup, tagline, one badge row in one order (npm version, downloads, Quality Gate, the package's own coverage, OpenSSF Scorecard, unpacked size, dependencies, types, Node, licence, npm provenance), a row of compatibility badges read from the graded baseline — and carries the same sections in the same order: Install for npm, pnpm, yarn and bun, Quick start, Migrating as a before/after diff, Compatibility, Benchmarks, For agents, API, and a generated table of the nine packages. Links are absolute, so they work on npm as well as GitHub.
-
#710
620fc74Thanks @ofri-peretz! - Three code paths no input could reach are removed; behaviour is unchanged.roundel checkno longer carries a "(replaces …)" suffix it could never print, because it loads one plugin into an emptied registry. The 16-colour fallback no longer carries a bright-black branch or a lookup default, because black returns before the bright form is built and three bits index all eight names. -
#678
088ceccThanks @ofri-peretz! - Whether anybody is there, whether to colour, and whether a tick can be drawn are roundel's questions, and three packages answered them by hand.roundel/terminal(new subpath, 878 B, reaching nothing):interactive(rt)— a terminal on stdin, noCI, and no agent variable (CLAUDECODE,AI_AGENT,CURSOR_AGENT,CODEX_THREAD_ID,GEMINI_CLI, exported asAGENTS), withFORCE_TTY=1as the override — andunicode(rt), is-unicode-supported 2.1.0's table over{ env, platform }.caique/decidenow depends onroundeland asksinteractive(). Behaviour change: under an agent that has a terminal —CLAUDECODE=1and a TTY on stdin — a missing required value is refused with a usage error naming the flag (--x is required when nobody is there to answer) instead of prompting and hanging the agent.FORCE_TTY=1now prompts even without a terminal on stdin, as it does for burgee.caique/inquirer's tick andflagstaff/ora's log symbols and spinner fallback use roundel'sunicode(). caique's copy was a four-condition subset: the Linux console (TERM=linux) now gets√rather than✔, and ConEmu/Cmder, Terminus, Alacritty, rxvt-unicode and JetBrains' terminal on Windows now get✔, asfiguresdraws them.burgeehelp colour is roundel'scolorLevel(rt) > 0. Behaviour changes:NO_COLORnow beatsFORCE_COLOR;--no-colorand--color=…on the command line are honoured;CLI_ACCESSIBLEturns help colour off; and a terminal that sets noTERM(Windows' conhost) gets plain help unlessFORCE_COLOR,--colororCOLORTERMasks for colour.burgee/contrastrounds with roundel'sround2; no output changes.paratext: the supports-color fork behindparatext/terminal-linkis unchanged, and now held to roundel's policy by a parity test everywhere their two incumbents agree.
0.5.4
Patch Changes
-
#627
4a629a9Thanks @ofri-peretz! - Lint with every published Interlace ESLint plugin, and fix what the upgrade surfaced.- caique: the inquirer theme merge skips
__proto__,constructorandprototypekeys, so a theme object cannot swap the merged object's prototype. - burgee: last-element reads use
.at(-1). - burgee, closeout, flagstaff, roundel: helpers that capture nothing from their enclosing function move to module scope.
- seniority: suppression comments name the
no-dynamic-requirerule that now reports the config loader's dynamicrequire.
No public API or output changes.
- caique: the inquirer theme merge skips
0.5.3
Patch Changes
- #604
0e7b1e8Thanks @ofri-peretz! - Each README links to its migration guides under the docs link: "Migrating from: chalk", "ora · log-update · boxen · cli-table3", and so on. That puts a path from the npm page to the guide for the library you are replacing. No code changes.
0.5.2
Patch Changes
-
#474
1955419Thanks @ofri-peretz! - burgee plugins can hook two more stages.parseruns before the command is resolved: it receives argv and may return a replacement, which is how an alias plugin mapsdtodeploy.shutdownruns once as the program exits, whether the command succeeded or failed. The familyschema.jsonshipped in every package now describes both stages. -
#519
77ff1cbThanks @ofri-peretz! - The drop-ins now export their incumbents' type names, so a TypeScript program migrates by its import alone:roundel/chalkgains chalk'sColor,ForegroundColor,BackgroundColor,ModifiersandOptions;flagstaff/oragainsSpinner,PrefixTextGeneratorandSuffixTextGenerator;flagstaff/boxengainsOptions,CustomBorderStyleandBoxes;flagstaff/log-update,linegauge,linegauge/wrapandcloseout/exit-hookgainOptions;burgee/yargs/parsergainsArguments,OptionsandConfiguration. Types only — no runtime bytes.
0.5.1
Patch Changes
- #508
1aae1e2Thanks @ofri-peretz! -<package> --helpand--versionanswer instead of crashing. The bin took its first argument as the plugin file to import, soroundel --helpfailed withCannot find module '…/--help'and exit 1.-h/--helpnow print usage and exit 0,-V/--versionprint the version and exit 0, and any other flag where the plugin file belongs is a usage error, exit 2.
0.5.0
Minor Changes
- #507
b8e97dcThanks @ofri-peretz! - Runs on Node 20 and 22, not just 24+:engines.nodeis now^20.19.0 || >=22.13.0. Those are the first releases whererequire(esm)loads without a warning, so the CommonJSrequire()path keeps working. Every package's test suite runs on exactly 20.19.0 and 22.13.0, on Linux, macOS and Windows. caique's prompts no longer callPromise.withResolvers, which Node 20 doesn't have.
0.4.3
Patch Changes
- #494
f7f6d4bThanks @ofri-peretz! - Each package'shomepageand README docs link now point at its own documentation site,https://<package>.interlace.tools, instead of a page on burgee's site. The oldburgee.interlace.tools/docs/packages/<package>URLs answer with a 301 to the new host, so nothing already linked breaks. closeout's README override example also resolves to the current release again (npm:closeout@^0.4; the 0.4.0 release left it at^0.3).
0.4.2
Patch Changes
- #465
acf98f3Thanks @ofri-peretz! - Each README now opens with the incumbent it replaces and the agent surface it serves (--json, an agent event, or a static projection), so npm shows both above the fold. README text only; no code changed.
0.4.1
Patch Changes
-
#454
b4584e7Thanks @ofri-peretz! - Every package's npmhomepagenow points at its page on the docs site,https://burgee.interlace.tools/docs/packages/<name>, and each README links it under the header. The keywords add what people and models search for:burgeegainscli-framework,argument-parser,subcommands,json-schema,mcp-server,model-context-protocol,ai-agent,llm,shell-completion,typescript,zero-dependency,commander-alternativeandyargs-alternative; the other eight gainagent,ai-agent,non-tty,jsonandzero-dependencywhere the package does that —zero-dependencyonly on the six that install nothing at all.burgee's README gains a short FAQ (commander alternative, agent use, MCP, dependencies) and states the compatibility counts the oracle holds — 1,360 / 1,360 of commander's tests and 804 / 804 of yargs' — where it had said 1,215 and 1,185.caique's README no longer calls a released package pre-release. -
#442
bdaf364Thanks @ofri-peretz! - Every package now listsplugin,pluginsandextensiblein its npm keywords, because every package takes plugins through one shared contract.A plugin is a plain object, validated against the
schema.jsonthat ships in every package, and checked with the package's owncheckcommand. Each package reads its own key and ignores the rest, so one object can extend any subset of the family. The plugins page has a nine-layer example that every package'scheckaccepts in CI. -
#435
7888524Thanks @ofri-peretz! -schema.jsonnow describes every plugin host in the family.The one schema each package ships as its plugin contract used to cover only four hosts: roundel's
tokens, flagstaff'sglyphs,spinners,bordersandcomponents, paratext'scapabilities, and linegauge'swidths. Five hosts validated their keys in their own code, but the file an author (or a model) writes against said nothing about them. It now describes all of them:- bellpull
resolvers, including the absolute-path rule onpaths - caique
widgets - closeout
handlers, including the phases a plugin may use - seniority
sources, including the rank bounds - burgee
commands,hooksandenforce
Where the schema can express a rule, it gives the same verdict as the host's own validator, and a test holds the two together. Function-valued fields (
static,run,read,handler) are described and required, but not typed, because JSON Schema can't say "function".flagstaff now validates a plugin against only its own keys, not the whole family schema. It no longer refuses a plugin over another host's key, which lets one plugin object contribute to several hosts. Its entry points are also 4.7–5.9 KB lighter for it.
- bellpull
-
#445
dac303eThanks @ofri-peretz! - Every package now declaressideEffectstruthfully, so bundlers can drop what you don't import.Six packages declared nothing, so no bundler could drop any of their modules. A named import from the root now bundles to the same bytes as the same import from its subpath:
import before after import { explain } from 'seniority'2,939 B 1,067 B import { decide } from 'caique'1,235 B 734 B import { strip } from 'linegauge'1,102 B 940 B import { once } from 'closeout'353 B 235 B flagstaff and roundel used to declare
false, but each ships acheckcommand whose file runs when loaded. Each now lists that file, which is the true statement. paratext also lists the two modules that register its built-in capabilities when they load.
0.4.0
Minor Changes
-
#421
db3c59eThanks @ofri-peretz! - Every plugin host has acheckcommand.npx linegauge check ./my-widths.mjs npx burgee check ./my-plugin.mjs --jsonPRINCIPLES 7 asks three things of an extension surface: the plugin is data validated against one published schema, there is a
checkcommand that shows it every way it can be seen, and the bar is measured. The first was built in all nine hosts; the second existed inflagstaffalone. So an author writing a plugin for any other host found out what it did by shipping it into a program — and a surface nobody can check is a surface nobody outside this repository can write against.Each command validates, registers, and shows what the host does with the plugin, in the host's own terms: linegauge measures each code point before and after the override, paratext shows a capability's
encodeand itsfallback, roundel each token and what it replaced, caique each widget's static projection rendered with its own sample. burgee's returns a document rather than printing one, soburgee check --jsonis the form an agent that just wrote a plugin reads.They share one contract with the author, held identically across all nine:
- a readable report, contribution by contribution, with
okas the last line; - a refusal with a code from the family's vocabulary and a
fix, exit 1; E_NO_CONTRIBUTIONfor a plugin that contributes nothing to this host — the schema allows unknown keys so one object registers everywhere, which makes a misspelled key silent, and this is how that typo tells on itself;- exit 2 with no file.
Each host also gains an eval case measuring the one-turn claim, proved to discriminate before it was committed: green against a correct plugin, red against the same plugin with one field broken.
- a readable report, contribution by contribution, with
Patch Changes
-
#430
4d1b2b3Thanks @ofri-peretz! -checknow reports every refusal with its code and its fix, wherever it was raised.Some plugin files register themselves on import: they call
register()at the top of the module and export the result. Until now, when such a file was refused, the error was thrown insidecheck'simport(), before the onlytrythat turns aPluginErrorintoE_PLUGIN_SCHEMA: …plus afix:line. The author got the bare message on stderr, with no code and no fix. Now the whole ofcheckruns inside that one handler, so every refusal comes out the same way on every host.
0.3.1
Patch Changes
-
#326
88f7ba6Thanks @ofri-peretz! - OneRuntimeseam per package, and one file in each that names the process (Y9).roundel/src/runtime.tsandflagstaff/src/runtime.tseach declare aRuntime— the slice of the world that package actually needs — and aprocessRuntime()that is the only place the real process is named. Six files stop naming it:roundel/chalk, and flagstaff'scli,ora,boxen,cursorandlog-update.Nothing about the ports' behaviour moved, and the shape of each seam is what holds that. roundel's returns a literal, because chalk's contract is to detect the terminal once at import; flagstaff's hands back the live process narrowed to the interface, because its incumbents read the process at call time — boxen takes
stdout.columnsevery time a box is drawn, so a box drawn after a resize still uses the new width, and ora still hooks the real stream objects and still looks upkillwhen it re-signals a swallowed Ctrl+C. The compatibility rows are unchanged: chalk 58/58, ora 99/99, log-update 99/99, boxen 84/84, restore-cursor 6/6. -
#294
3f92a60Thanks @ofri-peretz! - paratext validates against the family plugin schema, with its shape undercapabilities.paratext shipped its own
schema.jsonwhose root was one capability, so the family had three plugin schemas where the contract says one (PRINCIPLES 14,plugin-contractR2). The capability shape is now$defs/capabilityof the shared file, reached through acapabilitieskey besidespinners,tokensandcomponents, andpackages/*/src/schema.jsonhashes to one value. flagstaff and roundel ship the same bytes: their published./schema.jsongains the capability definitions and nothing about what they validate changes.check()follows the schema's$defs/capabilityDocumentand takes either shape:- a plugin carrying its capabilities under
capabilities, which is where they live from now on, and whose problems are reported atcapabilities.<key>; - deprecated — one capability written as the whole document, which is what a 0.2
capability file looks like. It still validates, and
check()returns adeprecated:line saying to move it undercapabilities. paratext 1.0 stops accepting it (.sdlc/PLAN.mdD2).
refusals()andisDeprecation()are exported to tell the two kinds of line apart; the lines that are not deprecations are the ones that block, and the onesregister()throws on.register(capability)is unchanged: it takes one capability, not a document, so it neither reports nor accepts the document-level deprecation. - a plugin carrying its capabilities under
-
#339
f295630Thanks @ofri-peretz! -schema.jsonconstrains token names, because it was promising something no host honours.tokenswas described as any name to a#rrggbbcolour.roundel'svalidate()accepts ten semantic names —error,warn,ok,hint,muted,command,flag,value,heading,ground— and throws on everything else. So a plugin author doing exactly what their ownE_PLUGIN_SCHEMAerror tells them, comparing their object againstroundel/schema.json, got a green from the schema and"accent" is not a tokenfromregister(). Measured 2026-09-16 with{ accent: '#336699' }.The schema now carries
propertyNames.enum, andscripts/plugin-contract-lock.test.tspins the enum and the runtime set to each other from both sides, so neither can grow a name the other does not know.Every host ships a byte-identical copy of this file (
plugin-schema-lock.test.tsasserts it), which is why nine packages are listed. Only the keyroundelowns is constrained: describingwidgets,handlers,sources,resolversorcommandsin a file all eight hosts share is what made flagstaff start validating caique's key last time (PluginError: plugin.widgets.later: expected object, got boolean), and those stay inplugin-schema-lock'sUNDESCRIBEDlist with that reason.linegaugeis in the list for a different change:ceilings.json's R9 block now records the bar as D1's tree-inclusive ceiling — 83,538 against 170,342, a ratio of 0.4904 — and keeps the supersededget-east-asian-widthbar beside it with the count of entries that cleared it.
0.3.0
Minor Changes
-
#218
214f6f8Thanks @ofri-peretz! -audit()andreportTheme()— ask whether your colouring meets WCAG AA and get rows back instead of an exception. Two rows per hex token,truecolorand256, because those are the two colours a terminal can be sent; none for 16, whose values are the user's own theme.fly()is now a filter overaudit(), so the refusal and the report cannot disagree. -
#218
214f6f8Thanks @ofri-peretz! - Colour correctness, in three parts. The contrast check now covers the 256-colour entry the terminal actually receives, not just the hex an author wrote — 167 hexes in an sRGB sweep read at truecolor and failed at 256. That substitution is chosen by nearest-in-OKLab among entries that clear the floor, which is perceptually closer than per-channel rounding and readable by construction rather than by luck. AndTheme.conformancetakes'AA'(default) or'AAA', raising the floor for the check and the search together.
0.2.0
Minor Changes
-
#78
7c5eeb0Thanks @ofri-peretz! - Addroundel/plugin: a plugin ships a theme, and roundel reads it.tokenshas been in the plugin schema all along, described as "a roundel theme", with nothing to read it — a plugin that shipped one was validated and then ignored.register()now collects them,theme()hands the result tofly(), andcontributions()reports which plugin won each token and which it shadowed.The same plugin object works on any subset of the family: keys roundel does not understand —
glyphs,spinners,components— are ignored, not refused. A misspelt token name is refused, naming the ten valid ones, because a silently droppederrrorlooks like it worked.Registering does not fly the theme; the program still calls
fly()once, and a plugin token below 4.5:1 throws there exactly as a hand-written one does. Nothing is imported from flagstaff — the plugin shape is declared structurally, so no package in the family requires another. The subpath reaches no module at all: 2,812 B, most of it refusal messages.
Patch Changes
-
#82
61bd11bThanks @ofri-peretz! - One door into the registry, and acheckthat grades what it actually found.registered()handed out the live registry behind aReadonly<Registry>type that freezes property bindings, not theMaps behind them — soregistered().spinners.set(…)put a spinner with no static projection wherespinner()would find it, and.clear()removed the built-ins. It now returns a copy over the frozen objectsregister()stores, which makes U3's "a contribution without a static projection is refused at the door" a property of the code rather than advice. Registering also copies: edit your plugin object afterwards and the registry does not change.flagstaff checkopens with a census of the contributions it found and closes with the verdict, sookis never printed before the rendering that would justify it. A plugin whose keys are misspelled — the schema allows unknown keys on purpose, for the rest of the family — is nowE_NO_CONTRIBUTIONand exit 1 with the unknown keys named, rather thanokand exit 0. Each component block states the state it was rendered with: a component may declaresample: { running, done }, and without one the assumed{ phase }shape is said out loud instead of silently invented. Astaticthat throws isE_COMPONENT_THREWwith a fix and the modes it broke in, rather than an uncaught crash after anok.roundel is bumped with it because the plugin schema is hosted in both packages and both publish it:
packages/roundel/src/schema.jsongained the samesamplekey, andplugin-schema-lock.test.tsrequires the two to be byte-identical. Without a roundel release the copies would agree in git and disagree in the registry — the contract's own "byte-identical in every tarball" rule holding in the repository and breaking where anyone would actually read it. This is the first contract change since roundel became a plugin host, so the pairing is worth establishing now rather than after the second one.Both of those codes are now members of the exported
PluginErrorCode, which is the union every refusal in the family comes from. They were bare string literals insidecli.ts, so a second host could have spelled either one its own way and nothing would have noticed — the plugin contract's "one error vocabulary" held only as long as nobody tested it.refuse()takesPluginErrorCoderather thanstring, and a repo lock reads each host's declaration out of its source and refuses anyE_…literal that is not in it. The union is a type, so this costs no bytes on any subpath.
0.1.0
Minor Changes
-
#44
183ebc9Thanks @ofri-peretz! -roundel/chalk: chalk 6's public API — the chainable builder with every modifier, colour,Brightvariant, background and underline style,rgb/hex/ansi256(andbg/underlineforms) downsampled as chalk does, the mutablelevel,new Chalk({ level }),chalkStderr,supportsColor, the name lists — over the tokens' one emitter and the policy's level, detected once at import. Graded by chalk's own suite vendored into compat-oracle: 58 / 58.roundel/tokensgainssgr(), the SGR emitter the façade composes with.roundel/policy:colorLevel()now obeys the user's explicit colour instruction in any output mode, not only on a TTY (design R2, revised 2026-09-08).NO_COLORstill wins outright;FORCE_COLORnames an exact level (FORCE_COLOR=2is 2, not "2 or better") or, astrue/empty, only enables colour and letsTERM/COLORTERMdecide; the--colorflags are read from a new optionalargvon the policy's runtime shape and outrank a numericFORCE_COLOR. A pipe nobody asked to colour is still 0 (Azure Pipelines excepted, where chalk excepts it), but a run that does ask now gets its CI vendor's level — soFORCE_COLOR=trueon GitHub Actions gives truecolor logs. The output mode still decides redraws, and--jsonis still always 0. -
#25
c65bad8Thanks @ofri-peretz! - First working release:roundel/policy(outputMode,colorLevel),roundel/tokens(nine semantic tokens overutil.styleText),roundel/theme(fly(), the burgee brand by default, hex → nearest 256/16 fallback) androundel/contrast(the WCAG mathsfly()refuses a theme with) — zero dependencies, each subpath weighed and isolated by its own lock.