IronCore v0.2.1
The fit-for-strangers release. 0.2.0 shipped the features; a six-lens release audit
then found that the seam a newcomer actually touches was the weak part — doctor
reported all-green against a server that could not serve a token, the offline demo
never reached an installed environment, an interrupted first-run probe could brick
the next boot, and docs/SAFETY.md documented a project-config autonomy ceiling the
code did not implement. All of that is fixed and regression-tested here. The app also
got a real visual design: one palette, colour used semantically and only semantically,
and nine screenshots in the README that show what you actually get.
Added
- Slash commands can return styled text, so the report card reads its own
verdicts (CONTRACTS.md §6, additive). A handler returnsstror
rich.text.Text;strstays the default and every existing command is
unchanged.commands.plain(result)is the accessor for anything that wants
characters rather than spans. The mechanism exists because the two screens
that carry a verdict had no way to say so:/enveloperendered
SELECTEDandREJECTED (0.19 short)in exactly the same grey, on the one
image the README uses to prove the product's thesis./envelopeis now colour-coded by outcome. The rung a measurement
selected is green, a rejected rung and its shortfall are red, the floor and
the also-ran fallback recede to grey, the honest-vs-advertised context gap is
graded, and the section headings and verdict carry weight. Green means one
thing only — a real measurement cleared a bar — so an unprobed or seeded
profile shows none of it: its floor selection, its verdict and its context
ratio all render amber and grey, because nothing there has been measured yet./goal checkcolours its payoff line:Goal stop-condition METgreen,
UNMETred. The line whose entire purpose is proving "done" is a test result
used to arrive in the same grey as the ack above it.- Colour never carries meaning alone, and the plain-text card is unchanged to
the byte:render_report_card(profile)is now literally
render_report_card_text(profile).plain, so the coloured card and the ASCII
one that gets piped, redirected or pasted into a GitHub issue cannot drift.
It stays ASCII, keeps every word, and every existing test pins both views. - Safety: styled results are composed programmatically (
Text.appendwith
an explicit style) and never viaText.from_markup. A model id of
[red]evil[/]prints those characters and arms no colour — pinned by
tests/test_report_card.py::test_report_card_never_interprets_markup.
Changed
-
ironcore demo,doctorand the report card read as the same product as the
app. The TUI got a palette; everything printed outside it was still one
undifferentiated grey, with======and+-/|ASCII furniture standing in
for structure.ironcore/term.pynow carries the same palette and the same
rules the app theme states, and the two are pinned together by
tests/test_term.pyso they cannot drift apart.ironcore demorenders the session the way the TUI renders it: a real
rule under the masthead, the autonomy mode as a chip, the request in the
accent, and each tool call as a card with a risk-coloured rule down its left
edge, aREAD/WRITEchip, muted arguments, a red/green diff and a
green✓ ok/ red✗ error.verify passedandstop_reason: doneare
green because they are the evidence the run turned on.ironcore doctorcolours its marker column — green[ok], steel[--],
amber[!!], bold red[FAIL]— and dims the indented follow-up under each
finding so advice reads as attached to the line it belongs to. The wording,
the markers and the exit codes are untouched: styling is derived from the
text, so it cannot reword what doctor says. The one exception is noted under
Fixed below.- The
/envelopereport card gains an outcome column: every ladder rung
now saysSELECTED,ok, fallback,REJECTED (0.19 short)or
floor (always works)instead of leaving a barebelowas the only sign a
rung was thrown out, and the columns line up. It stays ASCII and plain-text
on purpose — it is pasted into issues and rendered by the transcript. - Colour follows the stream: on a terminal you get it, piped or redirected you
do not, soironcore doctor > report.txtis plain text and every string the
test suite pins is byte-identical.NO_COLORandFORCE_COLORare obeyed
(docs/CONFIG.md §10). Box-drawing degrades to ASCII as a
set on a stream that cannot encode it (a redirected Windows console is
cp1252), so a redirect can never fail halfway through a transcript. - The screenshot generator captures the CLI shots with colour, by asking a
piped run for exactly the byte stream an interactive terminal would get; and
it now widens the exported SVG's font stack to terminal fonts that are
actually installed, so box-drawing rasterizes as continuous lines instead of
the dotted ones Courier New produced.
-
The report card no longer invents measurements it does not have. A ladder
rung that was never scored saidREJECTED (0.95 short), which reads as a
measurement that failed; it now saysnot probed, distinct from a probe that
really ran and scored 0.0. -
The TUI has a coherent visual design. It rendered as one flat grey wall:
tool cards were dim monochrome blue with no structure, mode changes and gate
outcomes were the same colour as everything else, and the approval modal's
thickborder drew as a chunky orange checkerboard. There is now one palette
(ironcore/tui/theme.py— a cool slate ground, one ember accent, semantic
green/red/blue) registered as a realtextual.theme.Theme, so the whole app
spends design tokens instead of ad-hoc colours.Colour is semantic only, and escalates rather than decorates — the calm,
common case stays flat and the elevated case fills, so a singleWRITEcard
stands out of a wall ofREADcards and the current autonomy posture is
unmissable. Nothing is encoded in colour alone: every chip keeps its word,
every result keeps itsok/errortext, every diff line keeps its+/-,
and Rich drops colour automatically when stdout is not a TTY. Specifically:- Tool cards get a risk-coloured left rule and a faint panel (not a
border — a bordered box costs two columns per card and a scrolling column
of boxes reads as a form), a bold tool name, aREAD/WRITE/EXEC/NET
chip, dim arguments and a green/red result line. - The approval modal loses the
thickborder for a risk-colouredround
one with a border title, and gains a plain-language statement of what the
risk class actually does ("this changes files in your workspace"). Its three
actions are flat text so the diff stays the loudest thing on screen, and the
scrim is translucent so the tool card that raised the ask stays readable. - The status bar's mode chip is coloured by autonomy:
planblue,
manualgrey,accept-editsfilled amber,autofilled red. - Mode changes are announced with the same colour the chip just took.
- The transcript gets vertical rhythm between turns, and system notes have
their leading[tag]lifted out (red when the tag reports a failure). - The empty state is a three-line masthead instead of a single grey line,
and the transcript is bottom-aligned like a shell, so a short session sits
above the input rather than stranded against a void.
- Tool cards get a risk-coloured left rule and a faint panel (not a
-
Slash commands are echoed into the transcript. A command session rendered
as an unbroken column of grey results with the question missing — you could
not tell which output answered what. The typed line is now shown as your own
line above its result. Display only: slash commands are still not recorded to
the session transcript. -
The README screenshots are reshot against the new design, and four of the
nine now show a fuller session. Several shots framed a modal or a command
against a mostly empty transcript, which read as a dead app rather than as
work in progress: the approval gate and the/goalrun now open on a real
session, the safety-mode cycle is followed by the two-turn investigation
instead of a single turn, and the resume picker lists nine recorded sessions
rather than four. These are scenario changes intools/make_screenshots.py
only — every pixel is still a render of the shipping UI, and nothing renders
differently for capture.
Fixed
- Doctor's "unprobed" line no longer wraps through its own marker column. It
was a single 133-character sentence, so in any normal terminal it folded back
to column 0 and broke the one column a reader scans. It is now a finding plus
an indented remedy, the shape every other multi-line check already used. - The README no longer documents an install command that 404s. The Install
section offered a concrete versioned wheel URL
(…/releases/download/v0.2.0/ironcore-0.2.0-py3-none-any.whl) hedged with
"once a release is tagged". There are no tags and no releases, so that URL was
dead — verified 404 — and a skimmer copies the concrete command, not the hedge.
Replaced with a link to the releases page phrased to read correctly both before
and after the first release exists, so it cannot rot into a lie either way. - A git-free install path is documented.
pip install git+https://…was the
only documented path and it shells out to git to clone, which contradicted the
neighbouring claim that git is a soft dependency. Both halves are now precise:
git is soft at runtime (no snapshots, so/undo//redodo nothing) but
required by thegit+httpsform, and
pip install https://github.com/RealDealCPA-VR/IronCore/archive/refs/heads/main.zip
installs the same code with no git at all. Verified in clean venvs with every
git directory stripped fromPATH: the zip form yieldsironcore 0.2.0, the
git+httpsform fails with "Cannot find command 'git'". - Three of the eight environment variables were unreadable in the README. Its
table compressed the role overrides into
IRONCORE_ROLE_PLANNER · _CODER · _SUMMARIZER · _VERIFIER, so
IRONCORE_ROLE_CODER,_SUMMARIZERand_VERIFIERappeared under no name you
could copy or grep. Each now has its own row. The guard derived from
_apply_env's own mapping covereddocs/CONFIG.mdonly; it now covers the
README too, matching full names, so this exact shorthand cannot come back. ironcore init --forceno longer silently eats a hand-edited config. It
now copies the existing file to a siblingconfig.toml.bakbefore writing the
template over it, and says on stdout where the backup went. A file that is
unchanged from the template is not backed up (nothing to lose, no litter), and
if the backup cannot be written — unwritable directory, a directory sitting on
the.bakpath — the overwrite is refused rather than performed, because
losing the original is the exact failure being prevented. One.bakslot,
overwritten each time: it always means "the config as it was before the last
--force". Found by a newcomer who lost theirmodel =edit re-runninginit
to start clean, then could not explain whydoctorhad regressed.
Changed
- The sdist is roughly 70% smaller — about 1.96MB → 0.59MB — by excluding
docs/imgfrom it. The nine README screenshots were 1.5MB of the tarball and
its single largest component, yet nothing in the distribution reads them: the
README references them by absoluteraw.githubusercontentURL so they render
on PyPI and any mirror. They remain in the repo and on GitHub. The wheel never
contained them and is unchanged in content. - A ninth screenshot shows
ironcore demo— the first command a newcomer
runs, and previously the only headline path with no picture. Real captured
output, generated bytools/make_screenshots.pylike the other eight.