# Changelog All notable changes to Reasonix. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.52.0] — 2026-05-26 **Ink renderer in-tree as `@esengine/ink`.** The vendored Ink fork now lives as a workspace package (#1847), unblocking TUI-specific renderer tweaks (focus, IME, Static append) without forking upstream. TUI polish lands on top: guard ASCII IME enter commits so JP/CN IME atomic commits don't double-submit (#1874), preserve undo state across session context switches (#1810), incremental repaint coverage (#1873). **Desktop — clipboard image paste.** Paste screenshots / copied images directly into the composer (#1889); paste path hardened against oversized + non-image clipboard payloads (#1897). Sessions exported by other local AI apps can be imported into Reasonix (#1894). **Desktop — JumpBar polish + drag perf.** JumpBar now highlights the active message bar and follows the latest one as the thread grows (#1813); dot styling pulled into a shared helper (#1892). Sidebar drag writes CSS vars directly to the DOM with `content-visibility` on messages (#1812) and unit-suffixes the vars to dodge a Chromium recompute storm (#1875). **Desktop — smaller fixes.** - Search-engine API key UI + slash command interception (#1867) - Mention `@` popup stays stable while filtering (#1850); split kind/length effects in the popup (#1859) - Update banner floats above the thread instead of pushing layout (#1865) - `memoryDetail` / `onReadMemory` wired through test fixtures so the desktop memory browser is exercisable in unit tests **Config — base URL + proxy.** `DEEPSEEK_API_BASE_URL` accepted as an env alias to the existing base-url override (#1876, #1887), and `proxy.url` is now a first-class field in `config.json` (#1881). MCP `initialize` no longer leaks LSP-only fields that some servers reject (#1861). **Providers.** Ollama usage parsing tolerates the field-name variants recent Ollama versions emit (#1886). **i18n.** - Russian (`ru`) language pack added (#1879) - German corrections — capitalization, terminology, coverage (#1878) - `/qq` command hints + argsHint localized; English fallback for zh-CN (#1857, #1862) **Dashboard / UI.** - Sync TUI and Web modal state so confirm gate doesn't desync between surfaces (#1831, #1866) - Highlight active plan edit mode (#1863) - `useEditHistory` no longer re-exports undo-context through itself (#1854) ## [0.51.0] — 2026-05-25 **First paint — session restore 4.5s → 430ms.** Progressive `` mount streams scrollback in chunks instead of mounting the full history synchronously (#1761). Combined with index-backed `mutateCard` + O(n) `plan.drop` + cursor elide (#1769) and the bump to `ink@7.0.4` (which ships the flicker fix we'd been carrying as a patch — patch-package machinery is gone, #1809), large session restores no longer block the event loop on startup. **Perf — tokenizer + markdown + search.** BPE tokenizer caches its table + bounds per-content counts with a fast-path truncate (#1741), so long sessions don't re-tokenize the world on every turn. CodeBlock syntax highlight is memoized (#1743). Literal-pattern search bypasses the regex worker entirely (#1748). Windows terminal repaint cadence is throttled (#1750) and a BEL → ST swap on Windows stdout stops `cmd.exe` from beeping on every render frame (#1795). **Cross-platform robustness.** - `EXDEV` fallback for atomic config writes on OneDrive / NTFS reparse points (#1778) — config saves no longer fail on Windows + cloud-sync - Apple Terminal mouse-reset crash guarded (#1764); legacy X10 mouse reports without ESC are dropped (#1723); `ESC`-less CSI recovery is refused when followed by typed text (#1771) - CLI startup gates on unsupported Node versions with a clear error instead of crashing in the middle of bootstrap (#1757) - Desktop bundled Node inherits parent TCC grants on macOS (#1749) so file / microphone permission prompts attach to the app, not the helper - Crash-safe atomic rename for edit-block writes (#1721); completed plans archive instead of persisting across sessions (#1700) - `.devenv` and `.direnv` are skipped by glob / search / directory_tree (#1706) **Memory — CLAUDE.md import for Claude Code migration.** Loading `CLAUDE.md` and `~/.claude/CLAUDE.md` alongside the existing `REASONIX.md` (#1694) lets Claude Code users carry their project / user memory directly into Reasonix without rewriting it. **i18n — German.** Full German (`de`) locale added (#1773) with a follow-up correction pass (#1797). **Desktop polish.** - JumpBar message navigation dock (#1725, #1729) - File-change summary card after each assistant turn (#1691) - Self-heal stale model id; expose plan as 4th editMode (#1699) - Restore aborted prompt drafts on relaunch (#1693) - Recalc thread width on resize (#1789); right panel scrollbar draggable (#1765); contain long MCP spec strings (#1736) - Sync slash setting commands across surfaces (#1724) - Surface desktop startup failures instead of silent exit (#1759) **TUI polish.** - Plan-mode `[PLAN]` / `[计划]` badge in input bar (#1742) - Forward-delete for `Delete` key (#1735) - IME candidate window cursor alignment (#1754) - Coalesce prompt cursor sync to reduce flicker (#1768) - Demote stuck `running` plan step on turn end (#1784) - Hide `max` effort on non-DeepSeek endpoints (#1798) - Drop stale "press Cmd-C to copy" startup hint (#1692) **Dashboard / web mode.** - Bootstrap mcp / skills / memory panels in web mode (#1775) - Expose new memory browser to web (#1779) - Replay active modal on SSE connect so confirm gate isn't stuck (#1770) - Restore scroll and memory visibility (#1766) - Docs responsive spacing polish (#1785) **Smaller fixes.** - Preserve full tool results when truncated (#1615) - Trim long-session retained payloads to bound memory growth (#1799) - Drain steer queue on `/new` + `/cwd` so prior intent doesn't leak (#1774) - Suppress DeepSeek-specific 5xx hints when host isn't DeepSeek (#1716) - `normalizeSemanticEmbeddingUserConfig` preserves `timeoutMs` (#1788) - `REASONIX_ACP_SYSTEM_APPEND` env var for system-prompt injection (#1737) - Configurable cost-display currency with click-to-toggle (#1710) **Refactors / internals.** - Extract `LruCache` + `TtlLruCache` + `lazy()` helpers from ad-hoc duplicates (#1746) - Split stream aggregator + chunked dispatch out of `step()` (#1739) - Per-component render trace gated by `REASONIX_TRACE_RENDERS` (#1747); `readStats` API on render trace + real-numbers large-session probe (#1751) ## [0.50.1] — 2026-05-24 **Install fix — `postinstall` no longer crashes on `npx`.** The `postinstall` hook added in 0.50.0 (#1584) ran `npm ci` inside `dashboard/` and `desktop/` to set up the dev workspace, but those directories aren't shipped in the published tarball. Result: `npx reasonix@0.50.0 code` failed during install with a `dashboard/package.json not found` error and the CLI never started. `postinstall` now delegates to `scripts/postinstall.mjs`, which exits 0 when `dashboard/package.json` is missing (i.e. anywhere except a git checkout). 0.50.0 is deprecated on npm. ## [0.50.0] — 2026-05-24 **Desktop dashboard — unified on the CLI-hosted React surface.** The old Tauri+Preact dashboard inside the desktop bundle is gone (#1418). Desktop now embeds the same CLI-hosted dashboard the browser surface already uses, so there's one React app rendering chat across all three surfaces (CLI, browser, desktop) instead of two divergent implementations drifting apart. Cuts the duplicate rendering paths that caused tool cards, code blocks, and compacted history to look subtly different depending on where you opened the same session. **Presets — flash/pro are the only knobs now.** The preset abstraction is removed (#1657, #1630): config now exposes `model` + `effort` directly instead of indirecting through `preset: auto | flash | pro`. The `/pro` switch is dropped too — flash and pro remain as direct model selections. Per-skill flash/pro override lands with a Settings UI (#1632) so subagents can opt into a heavier model without reconfiguring the parent. **Cumulative usage stats — persisted + auto-restored.** Session token totals and accumulated cost now write to the session meta on each turn and reload on startup (#1667, #1680 follow-up). Cost telemetry survives a CLI restart or desktop relaunch instead of resetting to zero, and the context-size readout shows the current window, not the all-time cumulative cache count (#1643). `SessionStats` caps at 200 turns with cost carryover so long-lived sessions don't unbound (#1628). **Plans — `editMode="plan"` enforced at the dispatch gate.** Plan mode now blocks write tools at the `ToolRegistry` dispatch layer (#1681), not as a per-tool soft check. Plan steps also advance correctly when intermediate actions are skipped (#1629), so the "current step" indicator no longer stalls on a step the model already completed. **Context — fold once at turn start.** The pre-flight fold pass is gone — context folding now happens once at the start of each turn instead of being threaded through mid-turn checks (#1642, #1646). The obsolete byte-ceiling check is dropped too. Compacted history renders as a single collapsible card on all three surfaces (#1649), so the fold is visible without dominating the scrollback. **Desktop polish.** - Draggable sidebar resize + responsive conversation panel width (#1688) - Responsive sidebar collapse on narrow windows (#1585) - Copy/edit overlay, message-history navigation, IME guard, card collapse (#1645) - `Esc` closes the open modal instead of aborting the model turn (#1685) - QQ turn routing isolated by tab so cross-tab messages don't bleed (#1672) - Discard explicitly aborted turns instead of replaying them on resume (#1687) - Render `edit_file` / `multi_edit` results as DiffCard, matching CLI (#1662) - Code-block syntax highlighting follows light/dark theme (#1655) - System events surface as a show/hide toggle (#1654, #1650) - File paths + completion feedback surface in tool results (#1644) - Block right-click context menu in release builds (#1641) - Preserve code operators when rendering inline code (#1640) - Compact composer default preserved across relaunches (#1594) - Stale events no longer corrupt UI after a session switch (#1582) - macOS bundled Node inherits parent TCC grants so file/microphone permission prompts attach to the desktop app, not the helper (#1614) - New `dashboard.enabled` config to suppress auto-start when the dashboard isn't wanted (#1612) - Localize edit approval/discard messages + event errors with zh-CN (#1617) **Dashboard polish.** - Persistent active-session URL — shareable + refresh-safe (#1586, #1589, #1599) - Code-block syntax highlighting follows light/dark theme (#1664) - IME confirm-enter no longer submits mid-composition (#1689) - Code-fence language detection fix mirrored from desktop (#1677, #1661) - Heavy vendor libs split into separate chunks for faster cold load (#1587) - Tool cards no longer stuck spinning when the turn ends without an explicit completion event (#1676) - Markdown tables get a horizontal scrollbar instead of overflowing the chat column (#1562) - Search-engine switcher restored in dashboard + desktop settings (#1618) - Composer queue-count label was reading the wrong variable name (#1674) - Long file paths stop stretching the chat bubble width (#1590) - Don't synthesize `$turn_complete` from per-iter `assistant_final` — let the real completion event fire (#1673) **TUI.** - New `Alt+S` hotkey stashes the current input buffer and recalls it later, like a one-slot clipboard for the composer - Static history is isolated from input re-renders, so typing during a model turn doesn't repaint the whole scrollback (#1635) - Legacy mouse reports (X10) are dropped so terminals that emit them by default no longer flood the input stream (#1637, #1648) - `multi_edit` is gated in review mode to match the single-edit approval flow (#1647) - Shell command failure output is surfaced to the user instead of silently truncated (#1608) **Diff rendering — CJK column-border alignment.** `SplitDiff` no longer misaligns the column border when CJK characters appear in either side of the diff (#1686). The width math now accounts for double-width graphemes when laying out the gutter. **Subagents — per-skill model override.** Each named skill (`/explore`, `/research`, `/review`, `/security-review`) can now override flash/pro independently (#1632), with a Settings UI to manage the overrides. Defaults flow from the parent session. **MCP.** - Workspace roots are passed to MCP servers on connect (#1625), so servers that scope by root see the right project - `codeCommand` honors the full `normalizeMcpConfig` pipeline including inline `mcpServers` blocks (#1603, follow-up to #1568) **Config.** - `(baseUrl, apiKey)` is resolved as a tuple instead of two independent fields (#1658), so a partial override on one no longer silently falls through to the env value of the other - Stale model IDs self-heal on load; v3 IDs are dropped from the picker fallback list (#1663) - Theme rename follow-up from #1666 closes out the prompt-input + user card token migration (#1678, #1666, #1623) **Memory.** `JobRegistry` self-cleans entries for completed jobs (#1620), so long sessions don't accumulate dead job records that never release their references. **Other.** - `chore(loop)` drop redundant abort warning, keep only the synthetic final event (#1659) - `chore(theme)` finish theme rename follow-up after #1666 (#1678) - `chore(ui)` drop redundant `overflow-wrap` from inherited descendants (#1675) - `fix(presets)` drop stale "5/31 discount" framing in pro cost copy (#1583) - `chore(install)` include desktop deps in root postinstall (#1584) - `test(lifecycle)` cover deterministic policy corpus + npm package aliases (#1574, #1621) ## [0.49.0] — 2026-05-22 **TUI — Static-history renderer.** The history pane is now rendered exclusively through Ink's `` (#1529, stages 1–4). The previous virtual-viewport machinery — viewport-budget row allocator, byte-virtual frame layer, manual scroll math — is gone. Net: less code on the hot path, no more "frame drift" on very long sessions, and the rewrite stays Ink-native instead of fighting it. Behind `REASONIX_STATIC_HISTORY` at stage 1, default-on by stage 2, scaffolding removed by stage 4. **Chat — queued mid-turn steers.** Typing while the model is mid-turn no longer fights the live render or drops the keystroke. New input queues, surfaces a small "queued" hint, and feeds into the next user turn cleanly (#1501). Pairs with the static-history switch so the queued line stays visible across re-renders. **Web search — Bing default, dashboard engine switcher.** Default web backend switches from Mojeek to Bing (#1558); the dashboard gains a visible engine switcher so you can flip between configured backends without editing config. Mojeek is dropped from the bundled set entirely in this release. **Plans — lifecycle evidence summaries.** Plan acceptance now surfaces the evidence captured at each lifecycle step (#1500), so the "why this plan is ready to accept" question has a structured answer instead of implicit trust. Builds on the strict-lifecycle observability that landed in 0.48.1. **Desktop — approval + completion notifications.** Native OS notifications for approval prompts and turn-complete events (#1519), so a long-running turn can land in the background and still surface. Uses `tauri-plugin-notification`. **i18n — CLI command output.** `/mcp`, `/sessions`, `/prune`, `/theme` output is now translated (#1524), closing the remaining English-only gap in CLI command surfaces. Approval-prompt confirm titles and action labels also get zh-CN (#1560/#1561). **Security.** - `web_fetch` now blocks SSRF — private/loopback/link-local targets are rejected at the boundary (#1544) - Edit snapshots can no longer read outside the workspace (#1454) - Shell redirects (`>`, `>>`, `<`) are constrained to the sandbox boundary (#1457) - New Task integrity guardrail catches objective simplification — i.e. the model trying to redefine a hard requirement away (#1516) **Tools.** `run_command` description now actively discourages shell-based file edits in favor of the structured `edit` tool (#1514). A per-turn dispatch-rate limit lands for tool calls (#1356), keeping runaway loops from burning a quota slice in one turn. **TUI polish.** - `Ctrl+Enter` now inserts a newline (#1513) — matching how every modern composer treats the modifier - `mouseWheelRows` config knob tunes scroll velocity per terminal (#1511) so trackpad vs. wheel mouse don't fight the default - `@mention` file search is restricted to the current directory prefix (#1548) so the picker doesn't dump the whole repo - Paste-sentinel label no longer claims `^O` expands the paste — the shortcut never existed (#1517) **Context.** Pinned-constraint blocks are now preserved across context folds (#1515) and the fold tail captures all of them, not just the last (#1552). Memory constraints survive the fold instead of silently dropping out under pressure. **Client.** - DeepSeek 429s are translated into an actionable "concurrency limit hit" hint instead of a raw HTTP error (#1526) - `timeoutMs` is now wired to `fetch` when the caller passes an `AbortSignal` (#1535) — previously the timeout was silently ignored - `--no-proxy` / `REASONIX_NO_PROXY` opt-out is honored for the DeepSeek direct-route default (#1507) **Files.** Read/edit/restore now preserve the source file's encoding (GB18030, UTF-8 BOM, etc.) instead of normalizing to UTF-8 on write (#1518). Important for CJK Windows projects. **Web/Dashboard.** - Path-access approval modal is bridged to the dashboard (#1538) so the browser surface can resolve the same prompts as CLI/Desktop - `@mention` file picker popover lands in the dashboard ChatPane (#1546), gated on attached mode (#1549) **Config.** Atomic config writes restore the preset-write diagnostic trace (#1555), so when a preset write goes wrong you can actually see why. **Refactor.** `lifecycle` extracts a standalone risk policy module (#1557), shrinking the orchestrator and unblocking risk policy reuse from non-lifecycle callers. **Edit safety.** `read-before-edit` is now enforced at the dispatch gate, not as a per-tool check (#1563) — `edit` / `multi_edit` refuse to fire on a file the model hasn't read this session, closing the race where a SEARCH block could match stale bytes after an external write. **Cache.** Fold-summary requests now reuse the live agent's system prompt + tool list as their prefix, so the cached bytes the main agent just paid for carry over to the summary call (#1565). Measured on a 48.7K-token fold: 0% → 99.6% cache hit, ~89% input cost reduction per fold. **Other:** - `fix(desktop)` drop `workspace:*` protocol for core-utils — desktop isn't a workspace member - `fix(desktop)` tsc errors blocking desktop bundle build ## [0.48.1] — 2026-05-21 **`dsnix` — short alias.** A new `dsnix` shim package lands alongside `reasonix` (#1440), so `npx dsnix` is now equivalent to `npx reasonix` for users who'd rather type five characters than eight. The shim is just a forwarding binary — same CLI, same release cadence — and ships on npm with the same version pinning (#1442). The root README documents the alias. **Lifecycle — observability + corpus tests.** Strict lifecycle now surfaces step-level evidence and rail decisions through a structured observability channel (#1426), so what the loop is doing at each step is legible instead of inferred. Two corpus tests lock the behavior in: multi-file refactor flow (#1427) and config-migration replay (#1473). **Approval prompt unification.** Phase 3 of the pause-request work (#1322) lands a single `PauseRequest` → UI data model shared by ACP, CLI, and Desktop (#1443) — the three surfaces now render the same prompt from the same payload instead of each translating in their own way. Net: pause prompts stay in sync across surfaces when fields are added. **i18n.** Builtin skill descriptions are now translated (#1468), and MCP error toasts + status labels carry zh-CN (#1481). The remaining hardcoded English surface in skill picker and MCP error toasts is gone. **Composer redesign + Ctrl+P shortcut modal.** The input area is rebuilt with a denser layout, a clearer affordance for multi-line input, and a Ctrl+P shortcut-help modal that lists every key binding in one place (#1483). The desktop composer also autosizes for multi-line input properly (#1439). **Java tool — cheaper.** The Java source/decompile tool description and output are pruned (#1485), shaving cached prefix tokens. Pairs with the #1390 buffer raise from 0.48.0. **Security.** `metaso` web-search no longer ships with a baked API key — it now requires the user-configured key like every other backend (#1488). Project-level hooks are trusted without an extra prompt provided they live inside the working directory. **YOLO mode polish.** `run_command` and `run_background` approval gates now auto-resolve in YOLO mode (#1489) so power users don't get a modal for every shell call after explicitly opting in. **Perf — folding thresholds.** Normal fold threshold raised 50% → 75%, aggressive 70% → 78% (#1461) — the loop now spends less time folding context that didn't need to be folded. **Preflight.** The cap that previously only counted tokens now also gates on raw JSON body bytes (#1451), catching oversized payloads that weren't reflected in the token count. **Desktop.** Error cards are dismissable and recoverable variants toned down (#1487); orphaned pause-gate modals are cleared on `$turn_complete` so a stale gate can't block the next turn (#1484); session titles are renameable from the sidebar (#1478); `/btw` now echoes input, surfaces busy state, and routes through the side-question prompt instead of silently dropping the keystroke (#1472). **TUI.** SGR mouse mode stays on for Windows Terminal (#1486) — alternate-scroll silently breaks the wheel there. The default for non-WT terminals switches to alternate-scroll (`?1007h`) (#1477) to avoid the click-eats-selection problem on conhost-class hosts. **Other:** - `fix(cost)` show session-aggregate cache hit so `/cost` matches the status-bar number (#1482) - `fix(plan)` Accept executes immediately when the plan has no open questions instead of waiting on a non-existent answer (#1480) - `fix` resolve root-relative edit-block paths (#1452) - `fix(tests)` bump chat-mcp-startup timeout to 30s for Windows test matrix (#1471) ## [0.48.0] — 2026-05-20 **Proxy-aware networking.** `NO_PROXY` is now honored end-to-end and the DeepSeek host is baked into the default bypass whitelist so a machine-wide `HTTPS_PROXY` no longer routes API traffic through an unwanted proxy (#1373). A `--no-proxy` CLI flag, `REASONIX_NO_PROXY` env, and `cfg.proxy` field give per-invocation control (#1374), and `/doctor` surfaces the resolved `NO_PROXY` value plus the routing decision Reasonix made for each host (#1375). If proxy behavior ever looks wrong, `/doctor` will tell you why in one screen. **Code intelligence boost.** Two new tree-sitter–powered tools land: `get_symbols` and `find_in_code` cover TS/JS/Python/Go/Rust/Java (#1387), giving Reasonix structural code navigation instead of plain grep. Java gets a dedicated `java_source` tool that reads `.java` sources and decompiles `.class` / inner jar entries via `javap` (#1344), with output stripped of noise and the read buffer raised so large classes fit in one round (#1390). Web search adds Perplexity and Exa backends alongside the existing Mojeek / Tavily set (#1350). **Lifecycle round two.** Checkpoints now gate mutating tools during restore so an in-flight edit can't race a rollback (#1389), state is preserved across plan revisions (#1388), and step evidence is summarized at the plan card so progress is legible without expanding each step (#1391). `/plan` is excluded from the strict-rails cache constraint that would otherwise force a re-prefix on every revision (#1407, #1386), and a strict end-to-end harness covers the whole lifecycle path (#1424). **Subagent UX.** Long-running `explore` / `research` / `review` are now top-level slash commands with live byte-progress in the status row instead of a frozen spinner (#1422). The agent loop is hardened: `_turnAbort` resets in `finally` so a consumer break can't lock the session (#1421), ambiguous search-replace edit blocks are rejected instead of silently miss-applying (#1414), repeated gate rejections no longer ping-pong (#1392), and FS tools walk `.reasonix/` so user skills are reachable from any subdir (#1371). **Desktop.** QQ sessions are now usable from the desktop app — the QQ Adapter routes events through the same RPC bridge as CLI tabs (#1334). Custom font support + file-link rendering improvements ship (#1376). Reliability fixes: `rpc_spawn` is idempotent and a `desktop_resync` rehydrate path recovers cleanly after a renderer reload (#1428); F5 / Ctrl+R are swallowed in packaged builds so a stray refresh can't wipe the chat (#1430); React error #31 with non-string MCP config items is fixed (#1394); the context-token meter syncs after `/compact` (#1399); the cost meter and session cost card apply USD→CNY conversion (#1369); `augmentProcessPath()` lost in the #1334 rebase is restored (#1361); desktop stdout partial writes are handled (#1405); `/btw` on empty payload now surfaces a usage hint instead of dropping the keystroke (#1383). **TUI / UI.** `/about` is a new slash command showing version, website, GitHub, and license (#1434). Remaining hardcoded strings in edit history and MCP browse are now i18n'd (#1433). Reasoning, streaming, and diff content beyond the recent window are elided so long sessions don't tank scroll perf (#1432). Wheel scroll is less sensitive and per-card stdout resize listeners collapse to one (#1423); terminal mouse tracking now has a CLI opt-out for users on mouse-hostile terminals (#1345). **Settings / config.** `config.json` with a UTF-8 BOM is tolerated instead of crashing the loader (#1431); preset-trace diagnostic retired (#1431). The dashboard can now clear `baseUrl` to fall back to the bundled default (#1425). `string[]` config fields are sanitized at the `readConfig` boundary so a malformed entry can't propagate into the running session (#1396). **Other:** - `fix(test)` switch vitest to forks pool to stop heap OOM on many-core hosts (#1408) - `chore(shell)` trim `run_command` param description (saves 119 tokens of cached prefix) (#1403) - `chore(scripts)` measure per-tool token cost end-to-end (#1402) - `test(tools)` close dispatch-level E2E gaps for shell jobs, web, and scaffold (#1400) - `test(shell-tools)` raise `run_background` dispatch test timeout to 15s (#1406) - `test(web)` cover Perplexity + Exa backends (#1366) - `docs(qq)` add desktop quick start and trim README surface (#1401) ## [0.47.2] — 2026-05-19 **Hotfix — `npx reasonix@latest` install.** `0.47.1` shipped with `@reasonix/core-utils` listed under runtime `dependencies` as `workspace:*`. Because the workspace package is `private: true`, `npm publish` did not rewrite the protocol, so consumers hit `EUNSUPPORTEDPROTOCOL: Unsupported URL Type "workspace:"` during install. The bundled `dist/` already inlines core-utils at build time, so the dependency belongs under `devDependencies`. Moved. `0.47.1` is deprecated on npm; please install `0.47.2`. ## [0.47.1] — 2026-05-19 **Desktop shakeout.** Follow-ups to the 0.47.0 desktop launch: inherit login-shell `PATH` so `nvm`/`asdf`/`fnm`-installed Node is visible to `run_command` (#1331); stop claiming "Connected" on the QQ settings page since the desktop is config-editor-only, not a live bridge (#1326); re-enable devtools + right-click context menu in release builds; route the in-app update check through the updater plugin so CSP doesn't block it (#1298); prevent the browser-level Select-All when the composer isn't focused (#1314); add unified `button:disabled` style; ship selectable theme styles (#1291); a few follow-up bug fixes from the release. **Prompt budget — round one.** `codeSystemPrompt` compressed -51% (~3.1k tokens/request, #1323) and tool spec descriptions -28% (~2.7k tokens/request, #1321), with a byte-budget regression test locked in (#1320). The cache-prefix shrinks accordingly — cheaper first turn, same behavior. **TUI / shell-exec.** Per-call shell-exec approval flow now surfaces on the first run instead of failing silently (#1278); spinner + stream tick rate dropped on legacy Windows conhost; light-theme picker rows highlight on selection (#1330); `//` line-comment input no longer parses as a slash command (#1288). **Other:** - `feat(core-utils)` new `@reasonix/core-utils` workspace package — Phase 1 internal split, no public-API change (#1328) - `feat(lifecycle)` opt-in engineering lifecycle (#1306) - `feat(tools)` ordered interceptor chain (#1301) - `feat(plan)` persist step evidence metadata (#1302) - `feat(read_file)` lower outline threshold 512 KiB → 64 KiB (#1324) - `fix(multi_edit)` roll back files that may have been modified on write failure (#1325) - `fix(permissions)` strip literal `*` from always-allow prefix in Desktop/ACP (#1312) - `fix(client)` skip `extra_body` for Azure endpoints (#1316) - `fix(search)` honor `tavily` engine + read engine at call time - `fix(cli)` resolve heap reexec entrypoint (#1303) - `fix(docs)` point download mirrors at `desktop-v*` tag namespace ## [0.47.0] — 2026-05-18 **Desktop matures.** The Tauri app picks up the polish it was missing — About modal with one-click update check, KaTeX math rendering in chat, window/tab/session/scroll restore across relaunch, /compact /retry /btw /feedback slash commands, per-message copy + file-export actions, QQ settings entry, platform-aware keyboard shortcut hints. The auto-updater now reads `latest.json` from the R2 mirror so an npm-only `v*` release can no longer brick the in-app update banner; the banner also surfaces download progress while the bundle pulls. Universal Node is bundled on macOS so Intel Macs stop blank-screening at launch. **TUI — composer round two.** Composer keys and visuals re-align with Claude Code: rounded border, status row at the bottom, input-box background fill, left stripe on user card. `ctrl+r` toggles verbose (reasoning + raw tool I/O); `esc-esc` opens a rewind picker over the last 5s; `u` undo is now gated to the same 5s window so stale taps can't revert. SGR mouse wheel is on by default again, IME cursor stays glued to `▌`, WelcomeBanner centers vertically on empty chats. **Search hardening.** Regex now runs in a worker thread so a ReDoS pattern can be terminated cleanly; worker deadline 5s → 60s and walk deadline 15s → 120s for legitimately large repos. Tavily added as a `web_search` backend — escape hatch when Mojeek 403s. `search_content` gets a walk-level deadline and ESC preempts queued tool calls. **Claude-ecosystem compat.** Reasonix now reads `.mcp.json` and `.claude/skills/` from the repo so existing Claude Code setups work without a second copy; common skill fields (`type`/`context`/`agent`) are aliased. **Other:** - `feat(embedding)` batch OpenAI-compat embedding requests to respect provider batch limits - `feat(memory)` inject ancestor `AGENTS.md` into `list_directory`, not only `read_file` - `perf(session)` skip cross-workspace jsonls + byte-scan line count — sidebar list ~10× faster - `feat(config)` support pricing overrides and per-model rpm limits - `feat(dashboard)` new / switch / delete sessions in-place, no CLI bounce - `fix(mcp)` runtime schema validation for MCP server responses; ESC aborts hung handshakes during startup; surface the real bridge- failure reason in the dashboard - `fix(cli)` preserve flash-preset auto-escalate semantics - `ci` split desktop + npm releases into separate tag namespaces: `v*` → npm, `desktop-v*` → Tauri bundles ## [0.46.0] — 2026-05-17 **Breaking — Rust renderer removed.** Reasonix is back to a pure Ink/Node TUI. The `reasonix-render` ratatui crate, the five `@reasonix/render-{platform}-{arch}` optional sub-packages, the NAPI loader (`src/cli/ui/scene/`), and the `--node` opt-out flag are all gone. Cross-terminal compat issues (Termux #1149 / #1026, mac eager-spawn chain, Windows alt-screen handoff, integrated-mode keyboard collisions) disappear with them — at the cost of the streaming/animation perf the rust renderer was supposed to buy us. Notes for users: - `REASONIX_RENDERER`, `REASONIX_RENDER_BIN`, `REASONIX_RENDER_CMD`, `REASONIX_INPUT_CMD`, `REASONIX_RENDERER_INTEGRATED` env vars are no-ops — drop them from your shell config. - `--node` flag is no-op (and unrecognized; will error). Just drop it. - `--no-alt-screen` / `--no-mouse` flags also gone — Ink defaults take over end-to-end. - `npm install reasonix@0.46.0` no longer pulls a per-platform binary — one dep tree on every OS, no optional resolution step. **TUI overhaul** — same pass that removed the rust path also cleared the Ink-side workarounds we'd accumulated on top of it: - Card primitive drops the left `▎` stripe; cards are plain column boxes with a top margin - CardHeader drops the colored pill background behind titles - Single consistent glyph vocabulary: `●` running, `✓` done, `✗` failed, `⊘` aborted, `○` queued, `⚠` warn, `⎿` child row, `█`/`░` bars, braille spinner - Composer wrapped in a rounded border box; status row moved to the bottom; on narrow terminals status pills wrap to a new line instead of being truncated away (#1149) - `/dashboard` URL surfaces as a startup info row (was easy to miss) - `/copy` permanently in the startup hint list — vim-style copy mode was undiscoverable before ## [0.44.2-rc.2] — 2026-05-17 **Fix:** macOS hang on `npx reasonix@next code` — keep-alive interval from rc.1 prevented Node from exiting, but the rust child was never actually spawned. Root cause: spawn was triggered by a React `useEffect` (`useSceneTrace` → `emitSceneMessage` → `trace.ts` `ensureInitialized`), and that effect simply never fired in some macOS npx contexts. Node sat alive with the keep-alive holding the event loop open, nothing on screen, no rust process. Fix: spawn rust eagerly in `chat.tsx` via new `ensureSceneTraceReady()` export, after `setIntegratedEventHandler` so the integrated event callback is wired before spawn. Also: - `renderer-process.ts` synthesizes an `exit` event when the rust child dies on its own (panic / SIGKILL / terminal close) so the integrated event handler tears Node down instead of hanging forever. - `trace.ts` now logs a clear warning to stderr when `resolveRenderer` returns no usable command, instead of bailing silently — anyone hitting "TUI never appears" can find the cause in `~/.reasonix/rust-render-stderr.log`. ## [0.44.2-rc.1] — 2026-05-17 **Fix:** macOS `npx reasonix code` (default rust + integrated TUI) exited back to the shell prompt immediately without rendering. Root cause: `makeNullStdin` / `makeNullStdout` are pure-JS Node streams with no underlying libuv handles, so they don't keep the event loop alive. The rust trace child is spawned by a React `useEffect` (via `useSceneTrace` → `emitSceneMessage`) — that effect is enqueued microseconds AFTER `render()` returns, but on macOS the event loop sees no active handles in that window and exits before the effect runs. Linux/Windows happen to keep the loop alive via other handles in the boot path; macOS doesn't. Defensive `setInterval(()=>{}, 0x7fffffff)` keep-alive held for the lifetime of `waitUntilExit()` and cleared in `finally`. Same renderer binary as 0.44.0; only chat.tsx changed. ## [0.44.1] — 2026-05-17 **Fix:** Mac/Linux `npx reasonix@latest` failed with `EACCES` when spawning the rust renderer (`spawn ... reasonix-render EACCES`). `actions/download-artifact@v4` strips Unix file modes during the release pipeline's artifact round-trip, so the binaries in the published 0.44.0 subpackages landed without the executable bit. Two-layer fix: - Each `@reasonix/render-*` subpackage now declares its binary in the `bin` field of `package.json`, so `npm install` chmod +x's the file during extraction (standard npm behavior for bin-declared files). - The publish workflow explicitly `chmod +x`'s the non-Windows binaries after staging from artifacts, so the tarball itself ships with the right mode regardless of npm version. No code changes — same renderer binary as 0.44.0, just a republish with correct file modes. ## [0.44.0] — 2026-05-17 **Headline:** The Rust TUI is now the default TUI. `npx reasonix@latest` on any supported platform (win32-x64, linux-x64, linux-arm64, darwin-x64, darwin-arm64) pulls a pre-built ratatui binary as an `optionalDependencies` sub-package and renders the full agent loop natively — no cargo, no toolchain setup, no opt-in flag. The Ink/Node TUI is still shipped and reachable via `--node` for users who hit a regression or run on an unsupported platform; the renderer-resolver auto-falls-back to Ink with a one-line stderr hint when no rust binary is locatable. Inside the rust TUI, **integrated mode** is the default too (rust owns keyboard + mouse + composer directly), which fixes the multi-press Ctrl+D + dropped preset clicks + Ctrl+C terminal-state leak that the split keystroke-bus path had. Bare `reasonix` (no subcommand) routes to `code` in the cwd instead of chat — explicit `reasonix chat` still works. **Note:** Existing users with `REASONIX_RENDERER=rust` or `REASONIX_RENDERER_INTEGRATED=1` set in their shell rc will see no behavior change (both are now no-ops with the value `=node` / `=0` opting back to the old behaviors). **Features:** - feat(rust): rust TUI is the default. `--node` flag (or `REASONIX_RENDERER=node`) opts back to the Ink/Node renderer. Auto- fallback to Ink with a stderr hint when no rust binary is found (#1081) - feat(rust): integrated mode is the default once the rust renderer is active. `REASONIX_RENDERER_INTEGRATED=0` opts back to the --emit-input split keystroke bus (debug only) (#1081) - feat(cli): bare `reasonix` (no subcommand) now launches `code` in the cwd. Drops the project-marker heuristic that used to route to `chat` outside of project dirs; `reasonix chat` stays explicit (#1081) - feat(scene): renderer-resolver picks the binary by priority chain — `REASONIX_RENDER_CMD` / `REASONIX_INPUT_CMD` env (full-command override) > `REASONIX_RENDER_BIN` env (single binary path) > `@reasonix/render-{platform}-{arch}` optional dependency > source- tree `target/release/reasonix-render(.exe)` > `target/debug/...` > `cargo run --bin reasonix-render` (source tree + cargo on PATH) > missing → Ink fallback (#1081) - feat(scene): Setup wizard works end-to-end under the rust renderer. First-launch with no saved API key shows the masked input prompt in rust and accepts keystrokes via the input child / integrated event path (#1081) - feat(scene): dashboard URL with a long token wraps inside the boot panel instead of bleeding into the sidebar. `paint_link_wrapped` emits each wrapped chunk as its own OSC 8 hyperlink so click-to-open still hits the full URL (#1081) - feat(release): per-platform `@reasonix/render-*` subpackages published as `optionalDependencies` of the main package. CI workflow cross-compiles the rust renderer for 5 targets on tag push, uploads one binary per subpackage, and `npm publish`es the 5 subpackages + the main package together. `scripts/sync-render-versions.mjs` keeps every subpackage version + the main `optionalDependencies` pins lockstep, guarded by a `verify` + `prepublishOnly` `--check` (#1084) - feat(dev): `npm run dev` auto-runs `cargo build --release` ahead of tsx so contributors never spawn a stale rust binary after editing rust source. Silent no-op when cargo is missing (TS-only devs). Added `npm run rust:build` for explicit rebuilds (#1081) - feat(ui): Ctrl+D wired to quit in the Ink TUI as well — the boot banner already advertised it but the binding was never connected. Integrated rust mode already handled it directly (#1081) **Fixes (inherited from #1070, included for 0.44.0 baseline):** - fix(rust): zero-width character handling + tab expansion in the paint engine — control chars no longer overwrite adjacent cells, combining marks / ZWJ / variation selectors are skipped, `\t` expands to 2 spaces. Resolves the `tau-benc / tatus]` style card- body fragmentation seen in 0.43.0 (#1070) ## [0.43.0] — 2026-05-14 **Headline:** Desktop client graduates — `v0.42.0-*` prereleases hardened the bundle pipeline (sync-from-tag versioning, signed Windows + signed + notarized macOS, Linux deb/AppImage, bundled-Node sidecar, R2-mirrored installers reachable from the new GitHub Pages landing), and 0.43.0 is the first stable cut where the installer is the recommended path for non-Node users. The subagent loop turns its iteration cap from a hard budget into a checkpoint cadence — pause/resume lets long jobs survive restarts, and the cap goes 32 → 256 (validated end-to-end on real API runs). Skills pick up a `max-iters` frontmatter knob so per-skill budget overrides don't need a config edit, and `/skill` arg picker autocompletes skill names. The CLI gets `--profile` for V8 CPU profiles (send the file with a perf bug report; no instrumentation needed). On the daily-driver side: `/btw` arg picker and `/skill` autocomplete in the slash menu, a context-usage pill in the TUI status row, full i18n sweep (settings UI, dashboard tool descriptions, residual hardcoded strings), and ~20 desktop UX fixes (HiDPI window clamping, @-mention drag-drop + tree picker, font scale + family pickers, language picker synced to ``, theme toggle, skill-running indicator, memory panel mid-turn refresh, plan-checkpoint modal in dashboard, and several keyboard / pause-state regressions caught in the field). **Note:** 0.41.0's `ReadOnlyLoopTracker` (auto-escalate flash → pro on read-only sequences) is removed in 0.43.0 — the heuristic fired on legitimate skim-mode reads and the cost ceiling didn't justify the behavior change. Preset escalation now happens only on explicit `/preset` or `--auto-escalate`. (#860) **Features:** - feat(subagent): pause/resume on the subagent loop. The iteration cap is now a checkpoint cadence — when it hits, the loop persists state (including `partial_summary` in the paused result) and a follow-up call resumes from there. Cap raised 32 → 256, validated end-to-end against the real API. (#822, #823, #838, #839) - feat(skills): `max-iters` frontmatter on skill files raises the subagent tool budget for that skill only — no config edit, no global bump. (#791) - feat(cli): `--profile ` records a V8 CPU profile for the session and writes it on exit (flushed before `useQuit`'s `process.exit` so the file actually lands). Attach to perf bug reports without rebuilding with instrumentation. (#846, #847) - feat(slash): `/skill` arg picker autocompletes skill names. (#805) - feat(tui): context-usage pill in the status row — see remaining context budget at a glance instead of doing the math from cache hit %. (#828, #837) - feat(web): landing + download site, ported from the design mockup and served from `docs/` via GitHub Pages. Installer links resolve to the R2-mirrored bundles. (#815, #817) - feat(desktop): @-mention UX — drag-drop files, tree picker, and a TTL'd file index so large repos don't re-walk on every keystroke. (#826) - feat(desktop): localize settings UI; language picker syncs `` so the OS-level locale fall-through works. (#841, #842) - feat(desktop): font scale slider + family picker, with API key / base URL always visible in general settings (was hidden behind a toggle that surprised first-run users). (#877, #879) - feat(desktop): visible "skill running" indicator on the composer while a user skill executes — no more silent waits when a skill takes 10+ seconds. (#884) - feat(i18n): residual hardcoded CLI + desktop strings extracted into the i18n system; dashboard tool descriptions translated to zh-CN. (#850, #858) - feat(skills): `/btw` and `/search-engine` zh-CN keys filled; `/help` hanging-indent wrap fixed for long arg lists. (#875) **Fixes:** - fix(desktop): catch unknown `PauseKind` in the IPC bridge so the agent loop doesn't silently hang when the desktop ships ahead of a new pause variant. (#873) - fix(desktop): per-tab keyboard shortcuts gate on `active` — a background tab no longer reacts to global hotkeys meant for the foreground session. (#874) - fix(desktop): bundled CLI loads on installed targets (was looking for the dev path); CDN fonts dropped in favor of the @fontsource bundle so first paint works offline. (#806) - fix(desktop): kill the bundled Node child on Tauri exit — added `rpc_kill` so the front-end can request a clean shutdown. (#792) - fix(desktop): clamp the initial window to fit HiDPI / small monitors — startup no longer opens off-screen on 1366×768 laptops. (#833) - fix(desktop): refresh the memory panel mid-turn after `remember` / `forget` — the right-panel was stale until the next user input. (#829) - fix(desktop): @-mention UX — surface user skills in the `/` autocomplete popup; normalize workspace path so the session sidebar filter matches sessions opened from a different cwd casing. (#881, #883) - fix(desktop): wire `path_access` gate and scope the right-panel to the active session — cross-session leakage on workdir switch. (#787) - fix(desktop): composer-foot overflows on narrow center column. (cfd9db2) - fix(desktop): honor preset `autoEscalate` when building the loop — was always reading the global default. (#819) - fix(loop): preserve skill bodies verbatim across context fold — a fold that cut into a skill body could drop the body's leading fence, breaking the next subagent run. (#871) - fix(loop): yield reasoning before content so transition chunks don't fragment — long reasoning runs no longer get split across two message bubbles. (#859) - fix(tui): drop ESC-stripped SGR mouse reports instead of inserting them as keypresses. (#872) - fix(tui): strip bidi + zero-width controls from pasted text — RTL override and zero-width joiners no longer survive the round-trip. (#876) - fix(shell): route `2>nul` and `2>/dev/null` to the OS null device cross-platform, so the same shell line works in WSL and Windows cmd. (#821) - fix(code): honor the configured preset instead of hardcoding flash. (#824) - fix(cli): route bare project launches (`reasonix` with no subcommand, in a project directory) to `code` mode. (#812) - fix(policy): `--yolo` bypasses `path_access` too, parity with the shell `allowAll` shortcut. (#786) - fix(dashboard): plans API timed out on users with many sessions — switched to a paginated query + cache. (#880) - fix(dashboard): broadcast plan-checkpoint pause so the web UI actually shows the modal (was only firing in the desktop). (#832, #840) - fix(dashboard): swallow third-party-origin errors in the global overlay — extension scripts no longer surface as Reasonix crashes. (#825) **Polish / refactor:** - chore(desktop): theme toggle in settings; deduplicate the settings entry point so the dialog renders the same component everywhere. (#852) - chore(desktop): drop the fake traffic-light dots from the titlebar — they didn't wire to any window controls and read as broken on Linux. (#830, #831) - perf(desktop): paint the UI shell before `buildCodeToolset` finishes — first frame ~200 ms earlier on cold start. (#836) - perf(desktop): memoize the message list so historical messages skip re-renders on each new chunk. (#844, #845) - perf(ui): incremental wrap cache for streaming cards — long reasoning blocks no longer re-wrap from scratch on every chunk. (#800) - chore(release): sync desktop version from tag (single source of truth for tauri.conf.json + Cargo.toml + desktop/package.json), set minimum CSP. (#789) - ci(release): split the bundle step so unsigned builds skip the codesign envs; expose secret presence via job-level env so step `if:` can read it; pass `GITHUB_TOKEN` to tauri-action; switch macOS to universal-apple-darwin and drop the macos-13 shard (queue waits ran to hours). (#794, #795, #796, #799) - ci(release): mirror published releases to R2 (Gitee mirror tried for ~6 fixes, dropped — 90 MB cap, 200+null on missing tags, 3-min timeout per upload all stacked up; R2 is enough). (#804, #808, #809, #810, #811, #813, #814) - docs: render CLI reference and architecture pages in-site; repoint configuration CTAs at the new in-site pages. (#848, 06bae63) - docs(readme): announce the desktop client (prerelease at the time); install instructions. (#788, #793) ## [0.41.0] — 2026-05-13 **Headline:** ACP graduates — three stages of work on the headless entrypoint (NDJSON framing, `tool_call` streaming, `session/request_permission` bridging) plus three flags (`--transcript`, `--yolo`, `--mcp` / `--mcp-prefix`) make `reasonix acp` ready for non-interactive callers like CI harnesses and cost-tracking adapters. The loop picked up auto-escalation on read-only sequences (caps the dead-end fishing-trip cost), `read_file` now outlines four more languages, and `wait_for_job` got an exit-only mode so chatty long jobs (curl, wget, big installs) stop burning one tool call per progress tick. On the daily-driver side: `/btw` for side questions that don't pollute context, user-defined memory types with priority + expiry, status-bar field toggles, and field-reported fixes for `/new` silently keeping the old REASONIX.md, the dashboard search toggle's one-way-trap UX, preset/effort persistence on auto-preset switch, plus a stack of desktop UI surfaces gaining real data. **Features:** - feat(acp): three-stage rollout of `reasonix acp`, the headless ACP entrypoint. Stage 1 brings the NDJSON framing + `initialize` / `session/new` / `session/prompt` round-trip with `agent_thought_chunk` / `agent_message_chunk` streaming. Stage 2 wires `tool_call` / `tool_call_update` notifications. Stage 3 bridges shell + plan permission gates to `session/request_permission`. (#714, #715, #717) - feat(acp): `--transcript ` writes JSONL receipts with `usage` / `cost` / `prefixHash`, mirroring the same flag on `chat` / `code`. (#766) - feat(acp): `--yolo` flag bypasses plan checkpoints without mutating `~/.reasonix/config.json` — process-per-task callers don't have to rewrite user config to skip the modal. (#767) - feat(acp): `--mcp ` (repeatable) + `--mcp-prefix ` for headless callers — domain-specific tools (custom MCP servers, downstream cost-tracking adapters) bridge cleanly into the cache key without config mutation. Shared `loadMcpServers` helper de-duplicates the spec/transport logic across `acp` / `chat` / `run`. (#780) - feat(loop): auto-escalate flash → pro on consecutive read-only tool calls. Caps the cost of dead-end fishing-trip turns where flash loops on file reads without ever committing; pro takes over. (#681, #768) - feat(read_file): outline support extended to Python, Go, Rust, and Markdown — the same skim-mode that worked on TypeScript / JavaScript files now answers "what's in here?" for the rest of the stack. (#769) - feat(bg-jobs): `wait_for_job` gains a `waitFor: 'exit'` mode (default) that doesn't wake on every output chunk — a 5-minute download costs ONE tool call, not one per progress line. `timeoutMs` cap raised to 300_000. (#777) - feat(chat): `/btw ` asks a side question without polluting the main turn's context — the answer shows in scrollback, but the message log stays clean. (#736) - feat(chat): hint when no MCP servers are configured — the marketplace pointer is visible from inside `chat` so users don't have to discover `/mcp marketplace` cold. (#716) - feat(memory): user-defined memory types with priority + expires. Type tags steer recall order and per-entry expiry kicks in so stale notes don't outlive the work they describe. (#711) - feat(config): `statusBar` toggles for each individual field (`showBalance` / `showSessionCost` / `showTurnCost` / `showCacheHit` / `showVersion` / `showFeedbackHint`) — hide what doesn't matter, keep the bar dense for what does. (#626, #712) - feat(desktop): rebuild the UI on a card-based design system with splash intro, cmdk palette, workdir popover, statusbar workspace switch, and the right-panel Files / Tools / Memory tabs now showing real data (file status dots, dir expand/collapse, per-server MCP status, memory store entries). Reserved-tokens bucket added to the context meter; model cards updated to v4 names + 1M context. (#710, #718, #722, #741, #748, #750, #751, #758, #759, #761, #762) - feat(i18n): zh-CN overlay covers the new marketplace flow and closes remaining TUI gaps from the prior round. (#706) **Fixes:** - fix(loop): `/new` now re-runs the system-prompt builder, so editing REASONIX.md and starting a fresh conversation actually picks up the new content. Previously the prompt was baked into `ImmutablePrefix.system` at startup and `clearLog()` never touched it — users had to restart. Cache-key invalidation happens only when the file actually changed. (#781) - fix(settings): dashboard search toggle no longer one-way-traps users whose config has no `"search"` field. The runtime default is on, but the missing field made the button render ON before any explicit value was set, so a single "enable it" click flipped to OFF. GET now persists the default on first read; subsequent clicks toggle a real boolean. (#782) - fix(preset): switching preset (e.g. `/preset auto`) no longer clobbers the persisted `reasoningEffort` — a `/effort high` set before a preset switch used to silently revert. (#775) - fix(healing): bare `tool_calls` without an `id` now get a fallback stamp so the next DeepSeek call doesn't 400 on validation. (#713) - fix(markdown): emit OSC-8 hyperlinks for `[text](url)` so the rendered markdown is clickable in supporting terminals (iTerm2, WezTerm, Kitty, recent Windows Terminal). (#730) - fix(wallet): pick the largest balance from `balance_infos[]` instead of trusting `[0]` — the upstream can return multiple buckets in any order. (#731) - fix(embed): tolerate providers that drop inputs from a batch embedding response — fall back to per-input retry instead of failing the whole batch. (#732) - fix(composer): keep the prompt cursor visible when the ticker suspends. (#733) - fix(plan): stabilize the expanded approval modal layout so body, options, and footer no longer reflow on rerender. (#734) - fix(session): persist new sessions opened via `/session ` instead of dropping them on the next launch. (#740) - fix(jobs): settle the running state on `exit` (not just `close`) and after a taskkill timeout, so Windows + Node ≥ 24 no longer ghost-run a job after `stop_job` returns. (#746) - fix(mcp): silence the race-leg derivative on initialize timeout — the loser of the timer-vs-init race no longer logs a spurious abort. (#747) - fix(at-mention): allow CJK and other Unicode letters in `@`-mention paths — previously the regex narrowed to ASCII and silently dropped any path with non-ASCII characters. (#764) - fix(ui): capture keystrokes while scrolled up in history — typing no longer disappears when the user is browsing the scrollback. (#760) - fix(desktop): drop stale literals from preset, plan badge, and tool card — copy reads from i18n consistently. (#771) - fix(desktop): replace MCP "loading" copy with "configured" once the server actually loads. (#752) - fix(web): align the Changes page chat with `ChatPanel` core logic + poll `/overview` every 2.5s. (#719, #720) - fix(dashboard): memoise chat feed + rails to kill input-typing lag — large message histories no longer make the composer feel glued. (#729) - fix(changes-ui): remove dead `+` button, widen editor / diff line number gutters, file tab click exits review mode. (#707) **Polish / refactor:** - refactor(ui): extract `HistoryTypingCapture` so `AppInner` stops subscribing to `pinned` — fewer renders, same behavior. (#763) - chore(plan): document magic numbers + swap the hand-rolled ANSI-strip for the standard `strip-ansi` package. (#738) - chore: oosmetrics badges + weekly health-check workflow; point Star History at the canonical DeepSeek-Reasonix slug. (#772, #773) - docs(readme): badge color pass; thank AIGC Link for XiaoHongShu promotions. (#726) ## [0.40.0] — 2026-05-12 **Headline:** npm-only release. The repo also gained a Tauri desktop client (`reasonix desktop`) wired against the agent loop with multi- tab concurrent runtimes — the source is here but the macOS / Windows / Linux installer bundles are NOT shipping this round; they'll get their own release once the signing pipeline is settled. The CLI side picked up a session-scoped checkpoint API + a git-changes panel in the dashboard, a shared pause-gate policy module so the desktop's auto-resolve rules don't drift from the CLI TUI's, plus a wave of field-reported fixes: the long-standing `Maximum update depth` crash inside CardStream is finally gone (this time for good — quantized window), `reasonix code` no longer boots dead when a user has only configured their key via `reasonix setup`, and a half-dozen MCP / shell / stdin / scroll papercuts are gone. **Features:** - feat(desktop): new Tauri client with multi-tab concurrent runtimes, bundled Node, native filesystem tools, and one persistent agent loop per tab. Source ships with this release; installer bundles ship separately. (#689) - feat(desktop): wallet balance + version chip in the sidebar, active- plan rail (collapsible, per-step risk + completion results), abortable pause-gates, checkpoint / revision cards, ⌘K palette, edit-gate pill, en + zh-CN i18n with a live toggle in Settings. (#701) - feat(changes): in-process checkpoint API + a git-changes panel in the embedded dashboard — zero external git deps, snapshots are session-scoped, restore is one click. (#682) - feat(sandbox): outside-sandbox file access (reads or writes that resolve outside the project root) is now gated behind an approval modal — protects against the model wandering into `/etc`, `~`, or a sibling repo by mistake. (#696) - feat(mcp): loading pill on each MCP server while tools are still being inspected; tool dispatch is gated on readiness so the model can't fire a call into a half-loaded server. (#687) - feat(loop): `--escalate-after ` flag + `escalation.failureThreshold` config field to tune how many repair signals it takes before flash → pro auto-escalates. Defaults unchanged. (#699) - feat(status): preset is now visible in the status bar; the model pin survives manual `/model` picks instead of being clobbered on the next preset reload. (#668) - feat(tools, loop): `readOnlyCheck` thrown errors are warned instead of silently swallowed — bugs in custom tool definitions surface immediately. (#670, #673) - feat(tools/web): `web_search` / `web_fetch` now bucket 5xx errors separately with a transient-retry hint so the model retries instead of giving up on a status code. (#676) - feat(i18n): zh-CN coverage extended to MCP lifecycle events, slow-network toast, external-editor messages. (#679) **Fixes:** - fix(tui): CardStream window position is now quantized to `VISIBLE_BUFFER_ROWS` buckets — sub-bucket `scrollRows` / `outer.height` wiggles no longer flip a boundary card live↔spacer, which was the residual `Maximum update depth exceeded` crash inside `useBoxMetrics` (the #549 fix only patched one instance of the pattern). (#700, #702) - fix(code): `reasonix code` now calls `loadDotenv()` + bridges `~/.reasonix/config.json` → `process.env.DEEPSEEK_API_KEY` like `chat` / `desktop` / `run` already did. Subagent's `DeepSeekClient` is constructed lazily so a missing key doesn't kill boot before the setup wizard can prompt. (#703) - fix(scroll): pinned-mode shrinks now coalesce — a burst of card- teardown re-measurements during an Esc-abort used to snap scrollRows N times, producing a visible flicker. Trailing-edge flush. (#653, #666) - fix(stdin): generic modifyOtherKeys / Kitty CSI envelopes are now decoded correctly (Ctrl/Alt + character keys on terminals that wrap modifier presses in `\e[27;` or `\e[u` sequences). (#692) - fix(ui/shell-confirm): long command previews clamp to the available width so the Allow / Allow-prefix / Deny options stay visible instead of scrolling off-screen. (#691) - fix(skills, memory): frontmatter parser handles BOM, folded lines, and quoted values; previously a BOM-prefixed `REASONIX.md` was silently dropped. (#690) - fix(mcp): inspect failures are classified into network / TLS / HTTP buckets — the error in the UI is now actionable instead of a raw Node `fetch failed`. (#688) - fix(mcp): debug log when the bridge receives a malformed JSON-RPC frame instead of crashing the parser silently. (#669) - fix(i18n): session-pruned banners and overflow errors now point at `/new` and `/sessions` (the live commands) rather than the deprecated `/forget` and `/sessions delete`. (#698) - fix(prompt): system prompt explicitly documents that the model may see OS-absolute paths (Windows `C:\...`) and forbids filesystem- refusal hallucinations like "I don't have access to your filesystem" when filesystem tools are clearly registered. (#667) **Polish:** - card border + composer cursor + shell modal info-row alignment all hand-tuned; nothing structural, just paper-cuts. (#672) ## [0.39.1] — 2026-05-11 **Hotfix:** `0.39.0` shipped with a `postinstall: patch-package` hook but neither `patch-package` (in `devDependencies` only) nor the `patches/` directory (omitted from `files`) made it into the published tarball. Fresh installs failed with `'patch-package' is not recognized as an internal or external command`, taking the whole install down. `0.39.0` is deprecated on npm. The `patch-package` approach was wrong by design — it patches `./node_modules/`, but npm hoists `ink` to the consumer's top-level `node_modules/ink`, so patch-package never finds the target when reasonix is installed as a dependency. The install-time machinery is removed entirely. The ink alt-screen render fix (#639) will return via a forked `ink` package and an `npm:` alias in a later release; until then the alt-screen ghosting on CJK terminals matches 0.38.0 behavior. Tracked at #663. Three feature/fix PRs that were in flight piggyback on this release. **Features:** - feat(composer): Ctrl+X opens `$EDITOR` with the current input — drafts long prompts in vim/code/etc. instead of fighting the in-terminal editor. (#647, #661) - feat(composer): Ctrl+P / Ctrl+N navigate input-prefix pickers when open — slash, `@`-mention, slash-arg pickers move up/down with the same keys as ↑/↓ when active, fall through to history recall when closed. (#647, #662) **Fixes:** - fix(pkg): drop `postinstall: patch-package`, the `patches/` directory and the `patch-package` dependency. Fresh installs no longer fail on the missing patch-package binary. - fix(web): `web_search` / `web_fetch` errors now carry an actionable ` — try: …` tail so the model self-corrects (back off on 429, switch engine on 403, pick a smaller URL on oversize, etc.) instead of giving up on a bare status code. (#16, #632) ## [0.39.0] — 2026-05-11 **Headline:** field-bug week — a wave of user-reported rendering, tool and network bugs all landed with focused fixes. Two structural wins: the open `AGENTS.md` spec now works without anyone having to create a separate `REASONIX.md`, and `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` are honored across every fetch path (DeepSeek API, web search, MCP, doctor) — Node's built-in fetch silently ignored them before. zh-CN coverage is now essentially complete for the TUI surface. **Features:** - feat(memory): `readProjectMemory` walks `REASONIX.md` → `AGENTS.md` → `AGENT.md` and picks the first that exists; writes target whichever file is already on disk. Projects on the open [agents.md](https://agents.md) spec no longer need a separate reasonix-only memory file. (#635, #636) - feat(net): HTTPS_PROXY / HTTP_PROXY / ALL_PROXY are now honored across every fetch path — undici's global dispatcher gets a `ProxyAgent` at CLI entry, before any client constructs. `/doctor` reports the active proxy URL (credentials redacted). (#646, #650) - feat(mcp): per-server env config via `mcpEnv` — set custom env vars on individual MCP server invocations instead of relying on the shell inherited environment. (#627) - feat(dashboard): `--dashboard-port ` flag + `dashboard.port` config field to pin the embedded dashboard to a fixed port; required for stable SSH tunnels. (#624, #625) - feat(doctor): `--json` flag for structured machine-readable output — scriptable / pipeable for CI checks and aggregator dashboards. (#620) - feat(tools): JSON-schema validation for tool-call arguments before dispatch. (#621) - feat(plan): `/plans done ` and `/plans done all` — manual escape valve when the model forgets to call `mark_step_complete`, so the resume banner doesn't get stuck at `0/N done`. (#641, #645) - feat(i18n): zh-CN now covers setup wizard, theme picker, plan editor, MCP hub, MCP marketplace, MCP browser, checkpoint picker, plan revise, diff/replay — every modal, picker, marketplace, and tool surface routes through `t()` with consistent translations. (#622, #654) **Fixes:** - fix(render): force per-frame clear in alt-screen — kills `log-update` line-count drift that produced duplicated status bars at the bottom of the viewport for users with CJK / ambiguous-width content on East Asian terminals (Windows Terminal at 120×30 + zh-CN was the field repro). Patched via `patch-package` against ink@7.0.2. (#639, #640) - fix(ui): reset chat scroll to bottom when a confirm modal mounts — shell-command, plan, checkpoint, revision, and ask_choice pickers all appeared off-screen for users who had scrolled up, and arrow-key inputs were captured by the picker so the user looked stuck. (#642, #643) - fix(ui): user-message cards render verbatim, not through Markdown — pasted code / stack traces / paths no longer get silently reformatted (asterisks → italic, leading `#` → H1, `[label](url)` → link with URL hidden, etc.). Assistant turns keep Markdown rendering. (#655, #656) - fix(markdown): decode HTML entities in code blocks and inline code — models sometimes HTML-escape JSON / HTML / XML output (`"` instead of `"`); terminals don't render entities so they leaked visibly. Common named + numeric forms decode at the rendering boundary; unknown names pass through. (#657, #658) - fix(config): `loadEditMode` preserves `yolo` instead of demoting it to `review` — the load path had only recognized `auto` and dropped every other value back to the safe default, so `/mode yolo` survived one process but reset on the next launch and the shell tool's `allowAll` getter never returned true. (#644, #648) - fix(net): same as above but listed under features — the proxy fix is both a new feature (we never had support) and a fix for a class of silent network failures. - fix(tools): short-circuit a 2nd consecutive identical malformed tool call. The model was caught calling `read_file({})` twice in a row with the same missing-required-param error in between; the existing `StormBreaker` threshold of 3 missed this shape. `ToolRegistry` now tracks per-tool fingerprints of validation failures and returns a sharper "DO NOT retry with identical args" error on the second identical malformed call. (#651, #652) - fix(ci): `issue-labeler.yml` now uses `/pattern/i` literal regex form instead of the PCRE `(?i)` inline flag, which JavaScript's RegExp doesn't accept. The auto-label workflow was dying on every issue before this. (#637) **Internal:** - chore(ci): add issue-triage workflows — auto-label by topic match, surface similar prior issues on new reports. (#634) ## [0.38.0] — 2026-05-10 **Headline:** new `/copy` slash command — a vim/tmux-style copy mode that gives users a keyboard path to yank chat text from the alt-screen buffer, where terminal drag-select can't extend past the visible viewport. Plus a long-overdue `docs/CLI-REFERENCE.md` covering every shell subcommand, every slash, and every keybinding, linked from both READMEs and the website footer. **Features:** - feat(ui): `/copy` enters a frozen-snapshot copy mode. `j`/`k` (or arrows) move the cursor by line; `v` toggles a selection anchor; `y`/`Enter` yanks via the existing OSC 52 path (with the temp-file fallback for >75 KB or terminals that don't honour OSC 52); `g`/`G` jump to top/bottom; `q`/`Esc` exits without yanking. Snapshot spans user / streaming / reasoning cards — tool / diff / etc. are skipped; headers are navigable but excluded from yank, so cross-card selections come out clean. Solves the SSH / mosh / tmux drag-select pain where alt-screen has nothing scrollable above the viewport for the terminal to extend the selection into. (#614, #616) **Docs:** - docs: `docs/CLI-REFERENCE.md` mirrors `/help` + `/keys` so the surface is greppable from the repo, indexable on the website, and printable for offline reference. Linked from `README.md`, `README.zh-CN.md`, `docs/index.html` (footer), and `docs/configuration.html` (outro CTA), with EN + zh strings in both website i18n dictionaries. (#616) ## [0.37.0] — 2026-05-10 **Headline:** boot splash + zh-CN status bar, MCP-handshake stall on launch is gone (bridging deferred to first paint), card virtualization keeps long sessions snappy, and four field-reported bugs that all shared a "silent failure" shape — `/new` was overwriting the live session file so prior transcripts vanished from the Sessions tab, flat-format skills (`/.md`) didn't appear in the dashboard even though `/skill ` ran them, skills missing a `description:` frontmatter were silently dropped from the prefix index so a new session claimed they didn't exist, and the escalation contract told every session it was running on flash so `/preset pro` self-reported as flash when asked. **Fixes:** - fix(loop): `/new` truncated `~/.reasonix/sessions/code-.jsonl` in place — multiple `/new`s in a project produced exactly one Sessions row and every prior turn was destroyed without warning. `clearLog` now rotates the live jsonl plus sidecars to `__archive_` via `archiveSession` so the prior conversation survives in the dashboard. The `__archive_` infix sits outside the `${name}-` resume-prefix matcher so archives don't auto-resume on next launch. `sessionName` is unchanged so the cache-first prefix invariant holds. (#587, #590) - fix(dashboard): `/api/skills` only walked folder-format skills (`//SKILL.md`); flat-format skills (`/.md`) worked from `/skill ` in the TUI but the dashboard tab was silently empty for users who installed them flat. The listing now dispatches on `Dirent` and resolves both layouts; read / save / delete share the same resolver so a flat skill can be edited or removed from the dashboard without spawning a duplicate folder entry. (#586, #589) - fix(skills): a skill whose frontmatter omitted `description:` worked in the install session (because `/skill ` calls `store.read` directly) and silently disappeared the next session (because `applySkillsIndex` filtered it out of the prefix). Two-layer fix: the dashboard install POST validates frontmatter via the new `validateSkillFrontmatter()` and returns 400 instead of writing a skill the model will never see; `applySkillsIndex` now lists blank- description skills with a placeholder line so the model can name them and tell the user how to fix the frontmatter. (#583, #591) - fix(prompt): `ESCALATION_CONTRACT` was a module-level const with `deepseek-v4-flash` baked into the literal — interpolated into `DEFAULT_SYSTEM`, `CODE_SYSTEM_PROMPT`, and `DEFAULT_SUBAGENT_SYSTEM` at module load. A pro session got told it was running on flash and answered honestly when asked which model it was. `escalationContract` is now a function: pro tier gets a short "you are the escalation tier; <<>> is a no-op" note (no ladder, since pro can't escalate to itself), other tiers get the full contract with the actual model id interpolated plus an explicit "if asked which model you are, answer ``" line. The three system-prompt sites thread the resolved session model through. The public `CODE_SYSTEM_PROMPT` const is preserved for backward compat. (#582, #592) - fix(ui): pressing `/` on the empty home screen left the bordered WelcomeBanner mounted while `SlashSuggestions` rendered below — both occupied the same flex column so the frame buffer interleaved them and the welcome card border drew through the menu rows. The empty- state guard now also requires `slashMatches === null`, so the welcome card yields the moment the menu opens and returns when it closes. (#594) - fix(ui): wheel-up felt laggy because `schedule()` was trailing-edge — every tick paid a 16 ms timer before any visual feedback, and on top of Ink reconcile + Yoga layout a single tick cost 30-50 ms before the frame moved. `schedule()` is now leading-edge so the first delta lands immediately; subsequent calls inside the window accumulate. Wheel/PgUp/PgDn step jumps from 3 → 8 rows so each tick travels roughly a third of a viewport. (#571) - fix(ui): the default frame flush was 16ms (60Hz), which on winpty / MINTTY / ConEmu / tmux / high-latency SSH couldn't atomically swap the cursor-up rewrite — the previous frame's bottom rows briefly bled through every redraw, visible as vertical bobbing. Default is now 50ms (20Hz); still reads as continuous streaming, no bob on any affected terminal. The `REASONIX_UI=plain` escape hatch (which suppressed every live row) is removed since the new default addresses the same terminals without losing the spinner / status line / live cards. Override via `REASONIX_FLUSH_MS=16` for terminals with atomic frame swap. (#570) **Features:** - feat(ui): boot splash for `reasonix code` / `reasonix chat`. Cold launch used to flash the alt-screen blank for a few hundred ms before AppInner's first paint completed; users read that as a freeze. The splash holds for one whale-spout cycle (~1.4s) so the REASONIX wordmark lands cleanly and AppInner's heavy first-paint cost (~150 hooks + several disk reads) hides under it. ANSI Shadow block letters in brand color; three-tone shaded whale silhouette with a 7-frame spout cycle and a shifting wave below. Setup screen and SessionPicker bypass the splash. (#588) - feat(i18n): status bar, input placeholder, edit-mode hints, and composer prompts route through `t()` with zh-CN coverage. Final pieces of the chat surface that were still hardcoded English — turn / cache / spent / left / slow / disconnect labels in StatusRow, the "ask anything..." placeholder and "⏎ send · ^C quit" hint in PromptInput, and the REVIEW / AUTO / YOLO mode label in LiveRows. (#584) **Perf:** - perf(boot): MCP bridging moved from `chatCommand`'s pre-render serial loop to an App.tsx mount-time effect that runs in the background. Each `runtime.addSpec(raw)` handshake is 100ms-2s; users with several servers configured used to watch a black alt-screen until the last one finished. The UI now paints immediately, MCP lifecycle events surface as in-app toasts via `log.pushInfo` / `log.pushWarning`, and `loop.prefix.addTool` hot-adds tools as they bridge — first turn after bridging is one cache-miss, same as the existing `/mcp browse install` path. (#585) - perf(ui): card virtualization. Yoga used to lay out every card in CardStream's inner Box on every scroll tick — for a 50-card history that's hundreds of rows re-measured per tick. Each card now reports its measured height to the chat-scroll store and CardStream collapses off-viewport ranges into a single spacer Box, so only the 5-10 cards under the viewport (± a 30-row buffer) go through Yoga per scroll. Streaming and freshly-mounted cards always render live for measurement. (#574) - perf(ui): scroll state isolated from App.tsx via `chat-scroll-store` (same `useSyncExternalStore` pattern as the agent store). Wheel/arrow ticks no longer re-render AppInner's 3,800 lines / 122 hooks per tick — only `CardStream` and the position indicator. The static `↑ earlier` hint is now a live position indicator (`↑ N / M rows above — K more`) that briefly highlights on each applied delta so the user gets instant confirmation. (#573) ## [0.36.2] — 2026-05-09 **Headline:** stability sweep on field-reported crashes and freezes — TUI no longer tears down on `/model` / `/sessions`, Esc and `/new` recover from a stuck plan checkpoint, the dashboard chat tab survives long streaming turns, plan-card spinners can't strand themselves on a missed end-event, and the model can't infer its identity from a foreign agent platform's data dir at the workspace root. New `/theme` picker for one-keystroke theme switching. **Fixes:** - fix(tui): a card-stream layout feedback loop (the `↑ earlier` hint conditionally rendered as a sibling of the measured outer Box) tied `outer.height` to `scrollRows`. Opening `/model` or `/sessions` — which mounts a picker that shrinks the outer column by 10+ rows in a single commit — could stack the cycle deep enough to trip React's `MAX_NESTED_UPDATES = 50`, raising "Maximum update depth exceeded" inside ink's `useBoxMetrics` and tearing down the TUI. The hint row is now reserved unconditionally so its visibility no longer feeds back into measurement. (#549) - fix(tui): `pauseGate.ask` ignored AbortSignal — when a tool was awaiting the gate (e.g. `mark_step_complete` → `plan_checkpoint`) and the user pressed Esc, the gate's promise stayed pending forever, `busy` stayed true, the prompt stayed disabled, and `/new` was silently dropped by `handleSubmit`'s `if (busy) return` guard. New `pauseGate.cancelAll()` resolves every outstanding request with its kind's safe-cancel verdict; Esc-during-busy and `/new` both flush pending modals through it so the awaiting tool fn returns cleanly and the user can recover. (#552) - fix(prompt): when the workspace root contained another agent platform's config (`SOUL.md`, `skills/`, `memories/`, a foreign `REASONIX.md`) the model would browse those files and claim a layered architectural relationship — "the underlying runtime is Hermes Agent" or similar. Top-of-prompt identity guard names the failure mode: workspace files describe the user's project, never what Reasonix is; identity questions are answered from the prompt, not from `ls`. Plus a launch-time detector that warns when those markers sit at the workspace root, suggesting `--dir `. (#555) - fix(dashboard): the embedded chat tab triggered Chrome's "Page not responding" dialog during long sessions and concurrent jobs. Each `assistant_delta` (~20/sec, more under fan-in) called setState synchronously, re-rendering every historical `ChatMessage` with no memoization — every delta re-ran `marked.parse` and `hljs.highlight` on unchanged content. Memoized `ChatMessage` via `preact/compat` `memo`, stabilised the per-row `streaming` prop so memo's shallow compare actually bails out, and rAF-coalesced delta accumulation so the streaming bubble re-renders at most once per frame regardless of delta volume. (#560) - fix(loop): tool-card spinners occasionally kept spinning after the underlying work had finished — the `running` flag was set imperatively from paired events, and any exit path that forgot to emit the closing event (storm-breaker, network drop, parent abort propagating, hook block) left the card stuck. Replaced with a finally-guaranteed `InflightSet` on the loop: tools are added at dispatch entry and deleted in `finally` regardless of how the call exits. UI tool cards consult the set via `useIsInflight(card.id)` for the spinner, decoupling running-or-not from end-event delivery. (#566) **Features:** - feat(ui): bare `/theme` opens a SingleSelect picker listing `auto` + every registered theme; `/theme ` keeps its existing persist-and-report behaviour. (#543, contributed by @J3y0r; re-landed via #567 after rebasing onto current main) ## [0.36.1] — 2026-05-09 **Fixes:** - fix(slash): the slash-suggestion picker sorts by usage frequency, but the Enter-time substitution recomputed the list without that sort, so the shared selection index dereferenced a differently-ordered list — the highlighted row and the command that ran could disagree. Both calls now share the same ordering. (#547) ## [0.36.0] — 2026-05-09 **Headline:** terminal-compatibility + interaction-loss fixes from 0.35.0 field reports. Mouse wheel now scrolls chat on cloud / web / SSH terminals (xterm.js, code-server, Cloud Shell, mobile SSH apps, tmux without `mouse on`) via DECSET 1007 alternate-scroll, with native drag-to-select restored on Konsole and friends — no Shift bypass needed because we're not enabling full mouse tracking. Render ghosting on CJK / emoji-heavy output goes away (Ink incrementalRendering off so each frame is a single full-screen redraw inside the BSU/ESU envelope). Pasting a multi-line block stops firing one agent call per line on hosts where bracketed-paste markers get stripped — the parser now wraps unbracketed multi-line chunks in synthetic markers so the existing accumulator delivers exactly one paste event. Plan-mode Refine finally pipes the user's typed feedback to the model instead of dropping it on the floor (PlanVerdict was missing a feedback field, the rich `synthetic` text was built and discarded). Web dashboard recovers canonical state on SSE reconnect so a missed end-of-turn event no longer wedges the page on busy=true forever. Plus a setup-wizard theme-picker step with live preview, "did you mean /…?" suggestions on slash typos, install-source-aware `reasonix update` (no more forced `npm install -g` for bun/pnpm users), zh-CN coverage extended to the card components, Windows PATH normalized before `spawn`, slash-popover windowing stabilized, semver compare on the dashboard up-to-date check, and self-hosted DeepSeek endpoints with non-standard key prefixes accepted. **Features:** - feat(ui): nearest-slash-command suggestion on typos. Slash typos produce an inline "did you mean `/`?" hint instead of silently dropping. (#302) - feat(wizard): theme-picker step with live preview during setup. Previously users had to learn `/theme` after the fact and try themes blind. (#518) - feat(update): `reasonix update` respects the install source (npm / yarn / pnpm / bun) instead of always forcing `npm install -g`. Stops bun-installed users from getting a stale global from a different package manager. (#511) - feat(i18n): card component labels route through zh-CN. Final TUI surface (status / context / streaming / tool / search / reasoning / sub-agent / usage cards) localized — closes the English-residue gap from prior i18n passes. (#526) **Fixes:** - fix(slash): hoist hooks above early returns. SlashSuggestions had `useColor` / `useStdout` / `useState` before two early-return branches and `useEffect` after, so when matches flipped between non-empty and null/empty across renders React saw a different hook count and threw "Rendered more hooks than during the previous render", killing the entire TUI mid-session. Triggered by everyday slash editing (typo → backspace → typing again). Hoisted the effect + windowStart math above the returns. (#538) - fix(tui): wheel scroll on cloud / web / SSH terminals via DECSET 1007. Old code relied on the implicit "terminal translates wheel→↑/↓ in alt-screen" behavior — only on by default in xterm / iTerm / Windows Terminal / Alacritty / Kitty. Web/cloud terminals ship with it off, leaving the wheel as a dead key. Explicit DECSET 1007 alternate-scroll routes wheel through the existing ↑/↓ chat- scroll handler without enabling full mouse tracking, so native drag-select + right-click stay 100% intact (no Shift bypass). Paired with `incrementalRendering: false` to drop render ghosting on CJK / emoji-heavy output. `--no-mouse` opts out. (#529, partial mitigation for #412, fixes #519, #531) - fix(tui): rescue unbracketed pastes so multi-line content stops firing N submits. Bracketed-paste markers (DECSET 2004) don't reach the parser on every host — multiplexers strip them, some web-SSH gateways drop them, certain Windows pipes never forward them. Without them, each `\r` in a paste fires an Enter event and the loop submits the partial buffer per line. Heuristic at the parser entry wraps multi-line chunks in synthetic paste markers when 2+ line breaks (or 1 break with text on both sides) are present and no ESC bytes appear. Bare `\r` and `\r\n` stay typed-Enter; "abc\r" stays type-then-Enter. (#536, closes #522) - fix(plan): pipe user feedback through the Refine / Approve / Cancel gate. PlanVerdict didn't carry a `feedback` field, so the rich text typed in PlanRefineInput was built into a `synthetic` string and never sent. Model received bare "user requested refinement" tool error and proposed a near-identical plan, looking like the suggestion was ignored. PlanVerdict now matches CheckpointVerdict's shape and surfaces feedback as the tool result string. (#534, closes #533) - fix(dashboard): resync canonical state on SSE reconnect. The `/api/events` stream snapshots only `busy-change` on (re)connect. When the connection dropped during a long task — proxy timeout, browser background-tab throttle, Node event loop blocked past the 25s ping window during heavy work — every assistant_delta / assistant_final / tool / modal event fired during the disconnect window was lost. If the disconnect happened before `busy-change(false)`, the UI wedged on busy forever. EventSource `onopen` now refetches `/api/messages` + `/api/modal` on every reconnect. (#532, closes #521) - fix(tui): drop xterm mouse tracking — restore native copy/paste, rebind keys. Multiple users reported they couldn't copy text or scroll with SGR mouse-tracking modes enabled. ↑/↓ always scroll chat now; Ctrl+P / Ctrl+N take over what ↑/↓ used to do in PromptInput (cursor up/down inside multi-line draft, falls back to prompt history). Pickers still own ↑/↓ while open. Superseded by #529's DECSET 1007 approach but the rebinding stands. (#514) - fix(shell): normalize Windows PATH env before spawn. PowerShell passed PATH with trailing semicolons that broke `where` and downstream tool resolution on certain Windows builds. (#525, closes #520) - fix(slash): stabilize suggestions windowing + isolate status row layout. Slash-suggestion popover was reflowing on every typed character; status row width changes were leaking up into the composer. (#516) - fix(config): honor `config.baseUrl` + accept self-hosted key formats. Self-hosted DeepSeek-compatible endpoints with non- standard key prefixes were rejected by client-side validation. (#513) - fix(dashboard): use semver compare for up-to-date check. Lexical string compare flagged 0.35.0 as older than 0.5.10. (#512) - fix(semantic): unblock Build when daemon is up but binary lookup fails. Build path was throwing on daemon start when the embedding binary wasn't where the registry expected it. (#507) **Performance:** - perf(tui): streaming flush rate tuned to 60Hz default. Earlier landed at 20Hz to suppress repaint glitches on fragile terminals then raised to 60Hz once frame pacing was proven stable. `REASONIX_FLUSH_MS` overrides for hosts that need it. (#515, #517) ## [0.35.0] — 2026-05-09 **Headline:** the agent gains the ability to extend itself from chat, and bug reporting collapses from a multi-tab scavenger hunt into one slash. `create_skill` and `add_mcp_server` are first-class tools — "add a skill that runs typecheck before commits" or "wire up a postgres MCP server" now works as a normal chat request, with structured args (description / `runAs` / `allowed-tools` / `model` for skills; transport + command + args + catalog hydrate for MCP) so the model never writes raw YAML or hand-crafts a `name=…` spec. Both reuse the same persistence paths the wizard / `/skill new` already use, so on-disk shape stays one source of truth. `/feedback` opens GitHub's new-issue page with an 11-field diagnostic block (version + latest-version compare + platform + terminal env markers including WT_SESSION/TMUX/SSH/WSL + cols×rows + theme + edit / plan mode + MCP count + session) **pre-filled in the textarea via `?body=`** — clipboard stays as belt-and-suspenders. The status row shows a `v · ⚑ /feedback` chip at cols ≥ 100 for discoverability. Diagnostic block is locked by a test that pins the exact field set so future additions can't sneak in unannounced. Plan mode finally surfaces the open-questions block it was already flagging. The banner detected `Open Questions` / `Risks` / `Unknowns` headings since 0.30, but the actual questions were swallowed by either the step list or the 24-line body cap. Now the extracted block renders under the banner regardless, and refines pre-fill the questions above the input. Whole plan flow (PlanConfirm / PlanRefineInput / PlanCheckpointConfirm / PlanStepList) moves through `t()` — the i18n gap the issue called out is closed. Read tooling gets sharper: `read_file` auto-preview now embeds a top-level export outline so callers can pick a `range` without a follow-up grep, and `search_content` adds a per-file cap + a histogram fallback so a single high-frequency hit can't drown the result. The subagent loop now sees its own iter budget and gets a near-cap countdown. Plus: dashboard typography pass (sidebar 240→260px column, body 12.5→15px, section headers tightened), cache-hit percentages now show 1-decimal precision across CLI + dashboard, Usage panel chart fully i18n'd, `spawn_subagent` tool result body finally renders as markdown instead of literal `**`/`##`/code-fences in the JSON envelope. **Features:** - feat(tools): `create_skill` + `add_mcp_server` — let the model scaffold from chat. `create_skill` pre-fills frontmatter (`description` / `runAs` / `allowed-tools` / `model`) from structured args; `add_mcp_server` builds `name=…` specs for stdio / sse / streamable-http with `from_catalog` shortcut for bundled entries, runs the existing preflight, refuses name collisions. Both register alongside native filesystem / shell tools in `reasonix code`. (#498, closes #494) - feat(ui): `/feedback` + version badge in the status row. Slash collects an 11-field diagnostic (terminal env / size / theme / edit + plan mode / MCP / model + effort / version-vs-latest / session), opens GitHub's new-issue URL with the body pre-filled via `?body=`, falls back to clipboard. StatusRow shows `v` at cols ≥ 70 and adds a `· ⚑ /feedback` hint at cols ≥ 100. Field set is locked by test. (#501, closes #499) - feat(tools): `read_file` auto-preview embeds a top-level export outline. When the file is > 200 lines and no `head` / `tail` / `range` was given, the elision marker now also lists function / class / const / interface / type / enum names with their line numbers (capped at 30 entries with elision). Callers can pick a meaningful `range` without a follow-up `search_content`. (#490, closes #487) - feat(search): `search_content` per-file cap + histogram fallback. When a single file dominates the result (typical: a generated lock file or a long log), the new per-file cap clips its share and the histogram footer shows the per-file distribution so callers can re-query against a specific file instead of widening the cast. (#495, closes #489) - feat(subagent): tell the child its iter budget; warn near the cap. The child loop now sees its `maxToolIters` budget in the system prompt (replaces the static "Cap at 6-8 tool calls" prose), and the parent injects a remaining-iter hint into tool results once budget is tight (`[budget: 3 of 20 tool calls left — wrap up soon]`). Stops the explore-burns-17-iters-then-truncates-mid-thought failure mode. (#493, closes #488) **Fixes:** - fix(plan): surface the open-questions block under the banner; i18n the plan flow. The `Open Questions` / `Risks` / `Unknowns` detection regex already fired but the block was swallowed by the step list or the 24-line body cap. Extract via `extractOpenQuestionsSection` and render under the banner regardless of `steps` / cap; thread the questions into `PlanRefineInput` above the input on `mode === "refine"`. Move `PlanConfirm` / `PlanRefineInput` / `PlanCheckpointConfirm` / `PlanStepList` strings through `t()` under a new `planFlow` namespace in EN + zh-CN. Replace the blank-refine synthetic that asks the model to re-derive questions with one that tells it to pick safe defaults. (#497, closes #477) - fix(ui): render `spawn_subagent` tool result body as markdown. `formatSubagentResult` returns a JSON envelope with the child's final answer in `output`; `ToolCard` rendered the JSON-stringified body as raw ``, so `## headers`, `**bold**`, fenced code blocks all leaked through as literal characters. Special-case `card.name === "spawn_subagent"`: parse the envelope, pass `output` through the same `Markdown` component the streaming reply uses; fall back to the line-tail loop on parse failures and `success: false`. (#496, closes #491) - fix(dashboard): bump doc-chrome typography; widen sidebar column. Sidebar 240 → 260px (so 2–3 word section labels fit without mid-word wraps), section headers 10 → 12px with tracking 0.14em → 0.08em, links 12.5 → 14px with `line-height: 1.4` and `overflow-wrap: anywhere`, body copy 12.5 → 15px, `.swatch .hex` / `.scale-row .lbl` 10.5 → 11.5px. Mirrored verbatim into `docs/design/agent-dashboard.html`. (#500, closes #461) - fix(ui): improve cache hit percentage display + Usage chart i18n. Cache-hit ratio now shows 1-decimal precision (85.6% rather than 86%) across the dashboard sidebar, the Stats panel, and `/status`. Usage panel chart axes (`USD` / `turns` / `time`) and series labels (`cost` / `cache saved` / `turns`) move through `t()` — they were hardcoded English. Adds the missing `colWindow` header (was an empty ``), promotes numeric columns to right-aligned tabular numerals at the header level, not just the body. Thanks @kabaka9527. (#503) ## [0.34.1] — 2026-05-09 **Headline:** scroll lag fix for long sessions. `useChatScroll` was calling `setScrollRows` synchronously on every PgUp / PgDn / arrow / wheel tick, so a single mouse-wheel gesture (10–30 events on Windows) triggered 10–30 full Yoga layout passes over the entire `CardStream` subtree. Layout cost scales linearly with card count — that's why the lag worsened the longer the session ran. Coalesce deltas into a ref and flush once per ~16ms; one scroll burst now produces one render regardless of event volume. `End` (`jumpToBottom`) cancels any pending delta so it stays instant. Reported in #482 by @GyroChen. The deeper fix — pre-rendering cards to a row buffer so Yoga isn't on the scroll/streaming hot path at all — is tracked separately and covers the streaming-redraw lag too. **Fixes:** - `chat-scroll`: coalesce wheel/key events into one render per ~16ms frame; long-session scroll no longer scales O(history) (#485, closes #482) ## [0.34.0] — 2026-05-09 **Headline:** two big UX shifts in the composer. The `@`-mention picker is rebuilt as a streaming file browser — `@` alone shows the immediate directory listing, anything you type fires a cancelable walk that streams matches in as it finds them, with a `searching… N scanned` footer. Fixes the unusable-on-large-repos behavior reported by @xlingyun8-maker (5000 files would evict 90% before ranking, picker showed nothing). The mouse wheel now scrolls chat history regardless of where the cursor is, via SGR mouse tracking — wheel events route through `mouseScrollUp/Down` instead of being mistranslated as ↑/↓ by Windows Terminal / ConPTY. The supporting cast: a structured `TipCard` variant replaces the multi-line text crammed into a step-progress card (the existing edit-gate hint reported as ugly), a real `/keys` command with the full keyboard + mouse + copy-paste reference (was a dangling reference in the edit-gate tip footer for months), and a one-time mouse/clipboard tip on first launch so users don't think the prompt is broken when right-click stops doing the terminal's native paste. Critical bug fix at the bottom: dashboard was silently overwriting CLI-side `/language` changes by pushing localStorage back to the server on every page load. **Features:** - feat(at-picker): rebuild as file browser with streaming search. Empty / trailing-slash queries (`@`, `@some/dir/`) browse one directory level via a single `readdir` — folders selectable, drill with Tab. Any non-slash filter (`@foo`, `@auth/log`) kicks off a cancelable streaming walk across the full tree, matches batch into the popup as the walker finds them, footer shows scan progress in flight. Drops the 500-file walker cap; cancellation bounds work instead. New public API: `walkFilesStream` (streaming + abort), `listDirectory` (single-level browse), `parseAtQuery` (dir/filter split with trailing-slash awareness). `expandAtUrls` + helpers split into `at-mentions-url.ts` to keep `at-mentions.ts` under the 800-line ceiling. (#479, closes #478) - fix(scroll): route mouse wheel via SGR mouse tracking. Enable DECSET 1006 + 1000 at startup so the terminal reports wheel events as `\x1b[` with column alignment driven by `string-width` (CJK-correct), footer separated from body by a blank row, no border. (#480) - feat(ui): `/keys` reference + first-run mouse/clipboard tip. `/keys` was already referenced in the edit-gate tip's footer ("Run /keys anytime for the full list") but no handler existed; typing `/keys` hit the unknown-command branch. Adds a multi-section TipCard with the full keyboard / mouse / copy-paste / edit-gate reference. Adds a first-run mouse + clipboard tip mirroring the edit-gate pattern (suppressed thereafter via a `mouseClipboardHintShown` flag) so users don't think the prompt is broken when right-click stops doing the terminal's native paste. TipCard now supports multiple sections; existing single- section tips are unchanged. New i18n helper `tObj(path)` for structured translation entries. (#481) **Bug fixes:** - fix(dashboard): stop pushing localStorage lang back to server on init. The dashboard's `initLangFromServer()` had a one-way sync rule: when localStorage's lang differed from server config AND localStorage was tagged "explicit", it POSTed localStorage's value back, silently clobbering CLI-side `/language` changes whenever the dashboard tab next loaded (including auto-restored tabs from previous browser sessions). Server config is the single source of truth now; localStorage stays as a render-cache to avoid first-paint flicker but is never pushed back. Removes `EXPLICIT_KEY` / `isExplicit` / `markExplicit` entirely. (#483) ## [0.33.2] — 2026-05-09 **Headline:** two bug fixes for #468 reported by @dacec354. **Bug fixes:** - fix(ui): ↑/↓ on an empty buffer recalls prompt history again. The binding was unbound from arrows back in 9254d3a because Windows Terminal + ConPTY can translate mouse-wheel events to ↑/↓ keystrokes (wheel-up was clobbering the prompt with a recalled message); history moved to Ctrl+P / Ctrl+N. That was right for legacy ConPTY but broke the universal CLI convention for everyone else (bash / zsh / fish all bind ↑ to history). Restored ↑/↓ on empty buffer = history; Ctrl+P / Ctrl+N stays as the wheel-immune fallback. Dead `chatScrollHandoff` plumbing dropped. (#475, closes part 1 of #468) - fix(doctor): tokenizer check now finds the file. The runtime resolver in `tokenizer.ts` had three candidates including a `createRequire("reasonix/package.json")` probe and worked reliably; the doctor had its own copy of the path math that walked `dist/cli/commands/doctor.js → ../../../data/`. After the lazy-import refactor in #467 the doctor compiles to `dist/cli/doctor-HASH.js` (one level shallower), so three `..` walked above the package root and reported "tokenizer not found" even when the npm tarball had it. Reuse the runtime resolver so the two paths can never disagree. (#475, closes part 2 of #468) ## [0.33.1] — 2026-05-09 **Headline:** the bottom status row now shows the wallet. Both `status.balance` and `status.sessionCost` were already being populated by the reducer (refreshed on every submit), and a `balanceColor()` helper with red/orange thresholds had been sitting unused in the theme — but `StatusRow` only ever rendered the per-turn cost and cache-hit pills. Pure plumbing gap; users had to type `/cost` to see the running spend or remaining DeepSeek balance. Plus a small polish pass on the prompt input footer. **UI:** - feat(ui): wallet pill on the status row. New segment renders right of the cache pill: `⛁ ¥1.20 spent / ¥45.32 left`. Spent shows when `sessionCost > 0`, balance shows when known; the separator only renders when both are present. Balance is colored via `balanceColor()` (red <¥5, orange <¥20, brand otherwise). Hidden on terminals narrower than 90 cols so the row doesn't wrap. (#473) - feat(ui): friendlier prompt input. Placeholder reads "ask anything · slash for commands · at-sign for files" instead of "type a message". Hint footer extracted into a `HintRow` component with keycap/label spacing — keys (⏎ ⇧⏎ ↑↓ esc ^C) in `FG.meta`, labels in `FG.faint`. Replaces `shift/alt+⏎` with `⇧⏎` and `ctrl-c` with `^C`. (#473) ## [0.33.0] — 2026-05-09 **Headline:** the filesystem toolbelt grew a hand. Three new tools — `multi_edit` for atomic multi-site SEARCH/REPLACE in one file (or across files in one call), `todo_write` for lightweight in-session intent tracking, and `glob` for mtime-sorted file walks with picomatch syntax — close the gaps where the model was either round-tripping eight `edit_file` calls or losing its plan to a context fold. `search_content` also gains `-C N` context lines. The other half is cold-start surgery (#464). Stage 1 adds a zero-cost profiler gated behind `REASONIX_PROFILE_STARTUP=1`. Stage 2 lazy- imports every per-command module and the dashboard server, paying for the chat UI only when `reasonix code` actually runs. `reasonix version` and `reasonix --help` drop ~290ms (~440ms → ~140ms); `reasonix code` is unchanged on the hot path. Critical bug fix at the bottom: a long-session OOM where every tool result was retained indefinitely in a useRef array left behind when `/tool` was deleted. **Features:** - feat(tools): `multi_edit` — atomic batch SEARCH/REPLACE. N edits apply sequentially against an in-memory buffer with one write at the end; any failure (empty edits, search not found, ambiguous match) leaves the file untouched. Edit N+1 can match text inserted by edit N (composable refactors). Cuts the round-trip cost of multi-site rewrites and removes the half-applied-edit failure mode of looping `edit_file`. (#458) - feat(tools): `multi_edit` cross-file mode. Same atomicity guarantee extended across files: dry-run all targets, then write. One failure rolls the whole batch back. (#462) - feat(tools): `todo_write` — in-session task tracker. Replace-set semantics (full list every call), no approval gate, no file writes. Each item is `{ content, status, activeForm }` with `status: pending | in_progress | completed`. Validated: at most one `in_progress` at a time. Empty list signals work-done. Sits between `submit_plan` (heavy: approval + checkpoints) and prose lists (lost on history fold). Stays callable in plan mode (`readOnly: true`). (#460) - feat(tools): `glob` — mtime-sorted file walks. Picomatch syntax (`**/`, `*.{ts,tsx}`); defaults to `sort: "mtime"` so "what did I touch lately" works without arguments. `sort: "name"` for deterministic listings. Skips deps by default, capped at 200 (1000 max) with overflow notice. (#462) - feat(tools): `search_content` gains `context: N`. Semantics match `grep -C N`; output uses ripgrep convention (`:` after match line, `-` after context). (#462) **Performance:** - perf(cli): `REASONIX_PROFILE_STARTUP=1` cold-start profiler. Marks at `cli_module_loaded`, `chat_command_enter`, `config_loaded`, `mcp_launch`, `mcp_connected_M_of_N`, `code_command_enter`, `semantic_bootstrap_start`/`_done_*`, `ink_render_complete`. Single env-var read when off; dumps to stderr at first paint when on. Stage 1 of #464. (#466) - perf(cli): lazy-import every per-command module. Each `reasonix ` only loads its own command's chunk. tsup splits, Node loads on first invocation. `reasonix version` and `reasonix --help` drop ~290ms (~440ms → ~140ms); `reasonix code` hot path unchanged (within noise). Stage 2 of #464. (#467) - perf(cli): lazy-import dashboard server. ~4200 LOC of HTTP / static asset code (`startDashboardServer`) moved to a dynamic `await import()` inside the App startup IIFE — only loads when the user actually opens the dashboard. Two new App marks (`app_render_start`, `app_inner_start`) clarify the first-paint delta. (#469) **Bug fixes:** - fix(ui): drop dead `toolHistoryRef` leak. `/tool` was removed in #453 but its supporting plumbing stayed behind: every tool result was being pushed into a useRef array with no consumer reading it, so long sessions retained the full text of every Read / Grep / Bash call indefinitely. Reported by @trytsomile as a `FATAL ERROR: Ineffective mark-compacts near heap limit` crash after ~2.6h on v0.31.0 (V8's 4GB ceiling). 48 lines deleted across 4 files; `state.cards[].output` (which actually drives scrollback rendering) is untouched. (#471, closes #465) - fix(/cwd): re-bootstrap `semantic_search` on workspace swap. FS / shell / memory tools re-registered against the new root, but `semantic_search` kept pointing at the old one — queries silently hit the previous project's index, or the tool stayed registered when the new directory had no index. Split the async re-bootstrap out of the sync `reregisterTools` callback; App.tsx fires `void reBootstrapSemantic(root).then(postInfo)` so the slash dispatch returns synchronously. Tail of #459. (#470) - fix(ux): fuzzy `@`-mention ranking. The picker's substring-only ranker rejected typo'd subsequences — `@atmnt` returned nothing for `at-mentions.ts`. Adds a fuzzy-subsequence fallback that triggers only when the substring lookup misses; substring hits still win (classes 0/1/2 cap at 29_999, subseq starts at 30_000). Also adds the `/cwd` slash for in-session workspace swap. Parts 1+2 of #459. (#463) ## [0.32.0] — 2026-05-08 **Headline:** the slash surface lost weight. Eleven redundant commands gone (`/clear`, `/keys`, `/models`, `/effort`, `/rename`, `/forget`, `/think`, `/tool`, `/apply-plan`, `/semantic`, `/resume`), the unified preset+model picker replaces three near-identical commands, and the two heaviest features almost nobody opted into — `/harvest` (Pillar-2 plan-state extraction) and `/branch` (parallel-sample selector) — are deleted along with their backing modules, events, transcript fields, and CLI flags. The four-pillar architecture collapses to three. The slash registry now carries a `group` tag (chat / setup / info / session / extend / code / jobs / advanced) and bare-`/` suggestions render those groups with advanced rows hidden behind a `+ N advanced · type to search` footer. A new `~/.reasonix/slash-usage.json` counter sorts frequent commands first within a prefix. The other half of the release is plan-mode UX. PlanLiveRow had nothing to dock — a code path that should have materialized an "active" plan card on approval was missing, so the bottom strip stayed empty after `/plan`. Fixed. And the per-step "Checkpoint — step done" picker fired in auto/yolo too, defeating the whole point of those modes; auto/yolo now resolve "continue" without prompting while still creating per-step rollback snapshots so `/restore` granularity stays intact. Plus a long-standing `@`-mention bug: typing `@docs/` produced an empty `not-file` placeholder. It now expands to a recursive `` listing respecting the project's gitignore, and symlinked source files finally appear in the `@`-picker. **Features:** - feat(semantic): OpenAI-compatible embedding provider. Configure custom API URL / key / model / request body for embeddings, replacing the Ollama-only setup. Dashboard semantic panel adds a provider dropdown with "OpenAI-Compatible" alongside Ollama, clearer status messages, and detailed indexing-job phases (scanning / embedding / writing). Community contribution from @kabaka9527. (#424) - feat(slash): unified preset+model picker. `ModelPicker` shows the three presets at the top with cost/headline copy and the model catalog below; cursor lands on the active row (auto-detects which preset matches the loop's current model + effort + autoEscalate). Both `/preset` (no arg) and `/model` (no arg) open it. (#453) - feat(slash): grouped suggestions + usage telemetry. `SlashCommandSpec` gains a `group` field; suggestion palette renders section headers on bare `/` with advanced rows hidden behind a footer. New `~/.reasonix/slash-usage.json` counter (read-modify-write, atomic rename) feeds `suggestSlashCommands` so frequent commands sort first; `slash.invoked` events emit to events.jsonl for cross-session analysis. `/help` walks the same grouped registry so there's one source of truth. (#453) **Bug fixes:** - fix(plan): dock active plan card. `case "plan_proposed"` had been dropping the gate payload's `steps`/`summary`, and the approve path never dispatched `plan.show` — so no card with `variant: "active"` ever existed and `isActivePlanInFlight` returned null. PlanLiveRow now docks correctly after approval, and the dock tracks tail rewrites on revise-accept. (#454) - fix(plan): auto/yolo skip the per-step checkpoint picker. The "Checkpoint — step done" picker fired after every `mark_step_complete` regardless of edit mode — shell and edit-gate already self-skip in auto/yolo, but plan checkpoints kept stopping the model. The gate handler now checks `editModeRef` and resolves "continue" without UI; per-step rollback snapshot still runs so `/restore` granularity is preserved. `review` mode is unchanged. (#454) - fix(at-mentions): `@` expands to a recursive listing. Was treated as a `not-file` skip, leaving the model with an empty placeholder. Walks the project root with the existing gitignore layers, filters to entries under the directory, and inlines a `` block capped at `DEFAULT_AT_DIR_MAX_ENTRIES` (200). `@docs/` and `@docs` resolve identically. (#455, closes #451) - fix(at-mentions): symlinks-to-files appear in the `@`-picker. `Dirent.isFile()` returns false for symlinks, so symlinked source files never showed up in completions. Both `listFilesWithStatsSync` and `listFilesWithStatsAsync` now stat through symlinks; symlinks-to-files come back, symlinks-to-dirs stay dropped (cycle risk), broken links stay dropped (nothing to point at). (#455, closes #451) **Removals — slash commands:** - `/clear` (merged into `/new` as alias — was the most common source of "what's the difference?" confusion) - `/models` (picker covers it) - `/keys` (folded into `/help`) - `/resume` (sessions picker has switch action) - `/semantic` (folded into `/doctor`) - `/effort` (preset locks effort) - `/rename` and `/forget` (sessions picker actions) - `/apply-plan` (plan picker handles the fallback path) - `/think` and `/tool` (debug-only; events.jsonl records both) - `/mcp browse` entry (handler still routes `["browse"]`) **Removals — features:** - `/harvest` (Pillar-2 plan-state extraction): `src/harvest.ts`, `--harvest` CLI flag, `harvestedTurns` transcript field. - `/branch` (parallel-sample selector): `src/consistency.ts`, `src/loop/branch.ts`, `BranchCard`, `branch_start/progress/done` events, `--branch` CLI flag. - `benchmarks/harvest/` deleted; `ARCHITECTURE.md` collapses from four pillars to three; README + zh-CN + `dashboard/PARITY.md` updated. **Removals — public API:** - `src/index.ts` drops `harvest`, `runBranches`, `aggregateBranchUsage`, `defaultSelector`, `emptyPlanState`, `isPlanStateEmpty`, and the `TypedPlanState`, `HarvestOptions`, `BranchSample`, `BranchSummary`, `BranchProgress`, `BranchOptions`, `BranchResult`, `BranchSelector` types. Consumers depending on these break intentionally — they were experimental from the start and never met the cache-first cost target this project gates on. ## [0.31.0] — 2026-05-08 **Headline:** a Mac user reported a DeepSeek 503 day where Reasonix showed a wall of raw `DeepSeek 503: ...` and they couldn't tell if our agent had crashed or the upstream API was down. Two threads of work fell out of that single bug: a friendly outage notice with a 1.5s reachability probe to `/user/balance` (so we can say "DS main API answered, but /chat/completions is failing — their problem") and a full sweep of every hardcoded English string a Chinese user could hit. ~150 strings across 8 files moved into the `loop.*` / `errors.*` / `app.*` / `hooks.*` / `summary.*` / `wizard.*` namespaces with zh-CN translations. The setup wizard now opens with a language picker defaulting to `detectSystemLanguage()` — for the case where `Intl.DateTimeFormat().resolvedOptions().locale` returns the wrong locale and a user shouldn't have to discover `/language` after finishing setup in English. The other half of the release is dashboard parity work — picker modals (sessions / checkpoint / MCP marketplace), viewer modal for `/replay`, plus cockpit / budget gauge / model picker / loop control panel / `/pro` one-shot — closing buckets B-E of the #369 web-parity tracker. **Features:** - feat(loop): friendly DeepSeek 5xx error with reachability probe. When the chat endpoint returns 5xx (after retry.ts has already retried 4× with backoff), `formatLoopError` now spawns a 1.5s `/user/balance` probe and renders one of three messages: no probe (generic outage notice), reachable (main API up but /chat dying), unreachable (DS or your network is down). All three say "this is a DeepSeek-side problem, not Reasonix" and link https://status.deepseek.com. Removes the misleading file header in `loop/errors.ts` that claimed retry.ts swallowed all 5xx — it doesn't, and never did. (#440) - feat(wizard): first-launch language picker + full i18n. New `language` step before `apiKey`, cursor defaults to `detectSystemLanguage()` marked `(detected)`. Selection saves immediately so all later wizard screens render in the chosen language. Re-running `reasonix setup` opens at the same step with the cursor on the saved language so Enter is a no-op. The wizard's ~30 hardcoded strings (welcome, prompts, validation errors, MCP catalog hints, review labels, save errors, saved screen) all moved to a new `wizard.*` namespace with zh-CN. (#442) - feat(dashboard): picker modal protocol for web parity. New `picker` modal kind drives sessions, checkpoint, and MCP marketplace pickers from the same protocol. Closes the gap where TUI-only modals stayed inaccessible from `/dashboard`. (#417, #418, #419, #420) - feat(dashboard): viewer modal kind for `/replay`. Loads an archived plan into a read-only time-travel snapshot, mirrors the TUI replay experience. (#421) - feat(dashboard): cockpit tile + budget gauge + 14-day cost trend. At-a-glance current-session telemetry on the overview panel. (#431) - feat(dashboard): editable model picker in settings + `/pro` one-shot panel + loop control (start / stop / countdown). The settings tab is now the single place to flip model preset, arm `/pro` for the next turn, or start an autonomous loop without switching to the TUI. (#430, #432, #433) - feat(dashboard): server surface for `/pro` / `/budget` / `/model` / `/loop`. POST endpoints under `/api/cockpit/*` carry the mutations the panels above need. (#429) **i18n sweep:** - i18n(loop/errors): localize DeepSeek error messages — context overflow, 401/402/422/400, 5xx (with the new reachability probe variants), reason prefixes for budget/aborted/context-guard/stuck. 20 keys + zh-CN. (#444) - i18n(loop): 14 user-facing yields in `step()` — budget exhausted / 80% warning, /pro armed, aborted-at-iter, tool-budget warning, preflight fold/no-fold, flash + auto escalation, storm-broken, history compaction (regular + aggressive), forcing-summary. The `loop.*` namespace had 7 dead keys defined but never wired — removed and replaced with 20 that match the actual yield shapes. (#445) - i18n(hooks/summary): hook outcome formatter (`hook PreToolUse/Bash \`cmd\` block (output truncated at 256KB)`) and the force-summary status / hallucinated-fallback / failed-fallback strings now go through `t()`. New `hooks.*` and `summary.*` namespaces. (#446) - i18n(app): ~26 hardcoded strings in `App.tsx` plus seven existing `ui.*` keys that had been declared but never called (same dead-key pattern as `loop.*`). New `app.*` namespace covers walk modal, edit-mode cycle (review/auto/yolo), edit gate, dashboard stopped, hash-memory note, bash-mode failures, hook header rows, @mentions / @url, shell confirm, checkpoint saved, plan continue / stop / revise. (#447) - i18n(slash): four lagging slash handlers — `web-search-engine.ts` was 0% localized, plus `mcp.ts` / `plans.ts` / `semantic.ts` had small gaps. ~22 new keys. (#448) - i18n(dashboard): translate the plan `idle` status pill — the `active` / `done` pills already used `t()` but the third branch was hardcoded English. (#443) **Bug fixes:** - fix(search): honor abort during recursive fs scans — Esc during a large `search` tool call now exits promptly instead of finishing the walk. (#400) - fix(ui): refresh model badge on dashboard preset change and /pro turns — the header pill stayed stale across server-side preset/pro switches. (#403) - fix(permissions): match Windows project keys case-insensitively — the project allowlist hashed `C:\Foo` differently from `c:\foo`, causing entries to "disappear" depending on which case the cwd carried. (#402) - fix(prompt): inline short single-line pastes verbatim — the long- paste collapser was firing on tiny one-liners and burying them behind a "(N chars pasted)" placeholder. (#397) **Tests / refactor / docs:** - test(mcp): cover startup summary states (#396) - chore: improve loop.ts tests (#271) - refactor(ui): quiet chat-screen chrome — fewer always-on rows on the welcome card so the prompt stays close to the top. (#411) - docs(readme): canonical install + subcommand cheatsheet (#408) - docs(issues): split off display/rendering template, collect terminal host info inline. (#412) - docs(dashboard): PARITY.md audit — bucket E of #369. (#439) ## [0.30.5] — 2026-05-07 **Headline:** three contributor-led follow-ups from the #350 RFC plus the #366 onboarding piece. The repeat-loop storm guard now exempts obviously-safe inspector tools (`read_file`, `list_directory`, `job_output`, `list_jobs`) so a model intentionally re-reading state isn't flagged as stuck. A new `wait_for_job(jobId, timeoutMs?)` tool replaces N-iteration polling loops with a single blocking call — returns the moment the job exits or emits new output. And `/skill new ` finally provides the missing creation entry-point for user skills, scaffolding a stub with the right frontmatter so first-time users don't have to read the source to author a skill. **Features:** - feat(storm): add `stormExempt` flag on `ToolDefinition`, set on `read_file`, `list_directory`, `job_output`, `list_jobs`. Cheap state-inspection no longer trips the repeat-loop guard. Mutating tools and unknown tools still go through the existing window-and- threshold check. (#350, PR #388 by @ctharvey) - feat(jobs): new `wait_for_job(jobId, timeoutMs?)` shell tool — blocks until the job exits or emits new output, bounded by `timeoutMs` (default 5000, clamped to 0..30000). Returns `{ exited, exitCode, latestOutput }`; `latestOutput` is the delta since the call started, not the full buffer. Rides the existing job registry's exit + output events; one call replaces N polling iterations and is token-cheaper than the prior re-call-job_output loop. (#350, PR #390 by @ctharvey) - feat(skills): `/skill new ` scaffolds a stub at `/.reasonix/skills/.md` with minimal frontmatter + a comment block listing the optional knobs (`runAs`, `allowed-tools`, `model`). `/skill new --global` writes under `~/.reasonix/skills/` for cross-project use; auto-falls- back to global when there's no project root. The empty `/skill list` now ends with an explicit "no remote registry yet — scaffold one with `/skill new `" line so users don't hunt for a marketplace that doesn't exist. (#366, PR #394) **Bug fixes:** - fix(skills): atomic create with `wx` flag — close the TOCTOU race between `existsSync(...)` and `writeFileSync(...)` that CodeQL flagged. The existence check IS the atomic write now; `EEXIST` from a parallel writer surfaces as the same "skill already exists" error instead of silently overwriting. (PR #394) ## [0.30.4] — 2026-05-07 **Headline:** sweep of the user-reported bug + onboarding queue from the 0.30.2 / 0.30.3 launch day. Resume now restores the full session state (cache hit %, cost, last context bar — previously they all showed zero on a fresh boot until the first turn landed). The model pill on assistant cards reflects the model that actually answered after `/model` or `/preset` switches it. Bare `/model` opens an interactive picker — typed-id entry stays for power users. PowerShell users get Shift+Tab back via three additional encodings (modifier-encoded back-tab, modifyOtherKeys, Kitty keyboard). And a class of "junk text after exit" on Linux/fish (terminal-feature replies leaking into the parent shell) gets a defensive stdin drain in the exit path. `--dir` is now discoverable for beginners — surfaced in the welcome banner, the `/status` panel, the filesystem sandbox-escape error, and a Getting Started callout in both READMEs. **Bug fixes:** - fix(stats): persist cache totals + `lastPromptTokens` across resume. `SessionMeta` only carried `totalCostUsd` / `turnCount`, so on every resume `/status` showed 0 context + 0% cache hit until the first turn actually fired (even though the prefix was already cached, costing $0.01 per turn). Three new fields are persisted per-turn and seeded into `SessionStats` on resume; the existing carryover plumbing now covers cache + last context. (#364, PR #384) - fix(ui): `/model ` and `/preset {auto,flash,pro}` now update the active model in the agent store so the next assistant card pill reflects the new selection. Previously `state.session.model` was set once in `initialState()` and never mutated, so the pill showed the launch-time model regardless of what actually answered the turn. New `session.model.change` event; cards already opened keep their captured model so mid-turn auto-escalation doesn't retroactively relabel. (#372, PR #385) - fix(input): recognize three additional Shift+Tab encodings for PowerShell hosts and modern terminals — `\x1b[1;2Z` (modifier- encoded back-tab some PowerShell hosts emit), `\x1b[27;2;9~` (modifyOtherKeys level 2, which we already enable on startup), `\x1b[9;2u` (Kitty keyboard envelope). Without these the edit- mode cycle was silently dropped on PowerShell. `/mode` typed fallback continues to work. (#373, PR #386) - fix(tty): drain pending feature-detection replies on exit. Linux reporters saw `^[]11;rgb:...^[\^[[33;1R^[[?62;1;4c` printed by fish / bash after exiting reasonix — those bytes are responses to OSC 11 / CPR / DA1 queries the runtime emits during startup that sit in stdin's queue until exit. New `drainTtyResponses(50ms)` reads-and-discards anything queued before control returns to the parent shell. Layered on top of 0.30.3's alt-screen mitigation (`--no-alt-screen` users get the fix too). (#365, PR #391) **Features:** - feat(ui): bare `/model` opens an interactive model picker — arrow- key list, current model marked, `[r]` refreshes the catalog, esc cancels. Seeds from the live DeepSeek catalog (`useSessionInfo.listModels()`); falls back to the four known DeepSeek ids when the catalog hasn't loaded yet so the picker isn't empty on first open. The current id is always included even when the API didn't return it. `/model ` typed entry stays for power users. (#371, PR #387) - feat(ui): surface `--dir` / pinned workspace for first-time users. WelcomeBanner shows the workspace + relaunch hint in code mode; `/status` adds a `workspace · pinned at launch` line; the filesystem sandbox-escape error points at `reasonix code --dir ` instead of just dropping a raw error; both READMEs gain a Getting Started subsection on `--dir`. No new slash command — mid-session retargeting is intentionally not supported (the message log + memory paths get tangled with stale roots). (#370, PR #389) ## [0.30.3] — 2026-05-07 **Headline:** the chat scroll rewrite lands. Ink 5.2 → 7.0.2 / React 18.3 → 19.2, the cell-diff renderer is retired, and `reasonix code` / `reasonix chat` default to alt-screen with row-precision virtual scroll. PgUp / PgDn / mouse wheel scroll history; an empty prompt + ↑ also scrolls (Ctrl+P / Ctrl+N still recalls prompt history). When scrolled away from bottom, the prompt hides and a `📖 reading history — End / PgDn to return` hint appears. Resize-ghost dividers and ``-related scroll-yank artifacts are gone with the renderer that produced them. `--no-alt-screen` keeps the legacy in-shell-scrollback behavior. `web_search` gains a configurable backend — Mojeek stays the default, but `/web-search-engine searxng ` switches to a self-hosted SearXNG instance for users whose network blocks Mojeek. And the MCP filesystem sandbox now fails with an actionable `mkdir -p ''` hint instead of a raw Node stack when the configured directory doesn't exist; the wizard offers to create it inline at config time. **Features:** - feat(ui): row-precision virtual scroll on Ink 7 + React 19. `` retired (incompatible with alt-screen reflow); `React.memo(CardRenderer)` plus reference-stable cards in the reducer skip the reconciler on unchanged history. `useChatScroll` drives an outer `overflow=hidden` clip + inner `marginTop=-N` slide; `useBoxMetrics` reports inner / outer heights so bounds clamp and auto-pin to bottom on new content. `App` owns PgUp/PgDn/End/wheel; PromptInput hands off ↑/↓ on empty buffer when pinned + idle. Ticker migrated to Ink 7's shared `useAnimation`. (PR #380) - feat(web): configurable `web_search` backend with SearXNG support. `/web-search-engine` shows / switches the active engine; URL is persisted to `~/.reasonix/config.json`. Mojeek remains the default; the original Mojeek path is preserved as `searchMojeek()`. Protocol auto-normalizes (`localhost:8080` → `http://...`); an unreachable SearXNG endpoint surfaces an install hint instead of a raw fetch error. (PR #338) **Bug fixes:** - fix(mcp): preflight the filesystem sandbox directory before spawning `@modelcontextprotocol/server-filesystem`. Missing directories now throw `MCP filesystem sandbox '' does not exist — create it with: mkdir -p ''` instead of a raw Node stack from inside `npx`'s child. The init wizard adds an inline `[Y] create it (mkdir -p) / [N] enter a different path` confirm step when the user types a path that doesn't exist, so bad config never reaches disk. Spawn-time path deliberately does not auto-mkdir — by then the user may not remember writing the config. (#362, PR #379) - fix(readme): website URLs corrected from `/reasonix/` to `/DeepSeek-Reasonix/`. (PR #375) **Chores:** - chore(issue-template): bug template now asks for shell + terminal, and the model-id examples track the current DeepSeek model lineup. (PR #378) ## [0.30.2] — 2026-05-07 **Headline:** five user-visible polish items from the @dacec354 triage batch. The streaming reply now carries a live `42 t/s` throughput pill (plus a `1.2k tok · 42 t/s` summary on settled), and `ctrl-o` toggles a full-tail view so a long plan / todo can be read while it's still being written. The auto-mode undo banner gains a `space`-to-pause keybind for users who want a beat to think before the 5-second window expires. SessionPicker and the dashboard's session-cost displays both respect the user's wallet currency now — USD wallets see `$0.05`, CNY wallets see `¥0.36` end-to-end. And a long-standing scrollback bug that left the "reasoning…" spinner spinning forever after reasoning ended is fixed. **Features:** - feat(ui): live `42 t/s` pill on the streaming reply card; settled card shows `1.2k tok · 42 t/s` summary. Computed via the bundled DeepSeek tokenizer; gated below 4 tokens / 500 ms so the first chunk doesn't print bogus rates. Re-renders ride the slow tick so the rate keeps updating during chunk silence. (#334, PR #356) - feat(ui): `space` toggles pause / resume on the auto-mode 5-second undo countdown. While paused the bar freezes at the captured fraction, the badge swaps to `Ns · paused`, and pressing `space` again resumes from where it stopped. The `u` and `space` keybinds share the same modal-and-prompt-empty gating. (#337, PR #356) - feat(ui): `ctrl-o` toggles "expanded" mode on the live streaming card. Expanded shows up to 60 visual lines (capped so the card can't swallow the whole viewport) plus a `⋯ N earlier lines above` hint when content overflows. Auto-resets to collapsed at turn end. A `expanded ⌃o` / `preview ⌃o` pill in the card header advertises the keybind. (#335, #337, PR #359) **Bug fixes:** - fix(ui): `splitCardStream` only treated the LAST card as live, committing every earlier card to Ink's ``. When the model streamed reasoning then content (or kicked off a tool card), the reasoning card was no longer last — it got frozen into `` while still `streaming: true`. `` doesn't re-render frozen items, so when `reasoning.end` later set `streaming: false`, the spinner kept spinning forever. The split now scans for the first unsettled card and keeps everything from that index onward live; a card only commits to `` once it's settled AND every earlier card is too. (PR #358) - fix(ui): SessionPicker hardcoded `¥` and ran USD → CNY itself, so USD-wallet users saw `¥X.XX` in the session list. `SessionMeta` gains `balanceCurrency`; App.tsx writes the live wallet currency alongside `totalCostUsd` on each turn save. Picker accepts a `walletCurrency` prop and falls back to each row's stored currency. Cost rendering routes through the shared `formatCost()` helper. (#312, PR #357) - fix(dashboard): cost displays were hardcoded to `$` via `fmtUsd()`, so a CNY-wallet user saw `session $0.5190` in the dashboard while the same session read `¥0.024` in the CLI — both the symbol AND the magnitude diverged because no conversion happened. Dashboard now has its own `fmtCost(usd, currency)` mirroring the CLI's conversion (CNY × 7.2). Overview current-session cost, cost-trend day average, and the chat panel rail / status-bar costs all thread the wallet currency from the cockpit balance. Claude- equivalent comparisons in `usage.ts` stay USD by design — Claude's API is USD-priced regardless of the user's wallet. (PR #360) ## [0.30.1] — 2026-05-07 **Headline:** two TUI ghost-rendering fixes for issues that only showed up on the published binary. The CLI bundle now uses real Ink in production instead of the cell-diff renderer that source mode never exercised, eliminating a whole class of bugs invisible to `npx tsx` repros. The `submit_plan` approval picker no longer leaves a duplicated row behind when arrow-navigating choices — the live tool card above the modal is suppressed while the picker owns the screen. **Bug fixes:** - fix(renderer): drop the `tsup` `ink → ink-compat` alias and the `noExternal` for `ink` / `ink-text-input`. The CLI bundle keeps `from "ink"` external; `ink` and `ink-text-input` move to runtime `dependencies` so npm install pulls the real package. The cell-diff renderer is no longer on the user-facing path; it's retained only for direct test imports. Same behavior as `npx tsx src/cli/index.ts` mode — TUI bug repros from source mode are now valid for the published binary again. (#346, PR #354) - fix(ui): `CardStream` accepts a `suppressLive` flag; `App.tsx` computes a `modalOpen` flag from the union of pending modal states and passes it through. While any picker / confirm modal owns the screen, the unsettled live tool card above it stops repainting, removing the rerender competition that left stale rows during arrow-key navigation. (#352, PR #353 — thanks @ctharvey) ## [0.30.0] — 2026-05-06 **Headline:** slash commands grow first-class aliases, and the cell-diff renderer hardens column targeting against per-cell width miscounts. `/quit` and `/q` now resolve to `/exit` from a single declaration on the spec instead of ad-hoc handler mirrors; `/?` → `/help`, `/reset` → `/new`, `/lang` → `/language` follow the same path. The renderer's `moveTo()` now uses CHA absolute (`\x1b[N+1G`) for column targeting instead of CUF relative (`\x1b[NC`), making the diff stream immune to the cursor-drift class of bug Anthropic documented in `claude-code#14208`. **Features:** - feat(slash): `aliases?: readonly string[]` on `SlashCommandSpec`. Adding a new alias is now a one-line edit to the canonical command — dispatch, autocomplete, arg-context resolution, and the dashboard `/api/slash` response all route through one `resolveSlashAlias()` map built from `SLASH_COMMANDS` at module init. Suggestion rows display aliases dimly (` · /quit /q`) so they stay discoverable without doubling the autocomplete list. Removes the per-handler alias mirrors that used to live in `handlers/basic.ts` and `handlers/language.ts`. (#332, PR #347) **Bug fixes:** - fix(renderer): switch the X-axis branch of `moveTo()` from CUF relative (`\x1b[NC`) to CHA absolute (`\x1b[N+1G`). Y-axis stays on CUU/CUD since we don't track absolute terminal rows. Relative column moves accumulate drift across frames whenever an earlier write miscounts cell width — `▸` (U+25B8) rendered 2-cell on fonts with East Asian fallback, ambiguous-width chars on terminals that font-detect width, OSC8 hyperlinks parsed as visible chars, etc. The next CUF lands at the wrong column, ghost rows leak into adjacent hint lines, and the modal "shifts" as users navigate. CHA targets the absolute column regardless of what the terminal thinks — immune to the desync chain. Same fix Anthropic shipped in claude-code per their issue #14208 post-mortem. (#346, PR #348) ## [0.29.1] — 2026-05-06 **Headline:** four user-reported bugs from the 0.29.0 release window. The markdown renderer no longer turns English abbreviations like `e.g.` into broken hyperlinks (which on cmd.exe / non-OSC-8 terminals showed up as visible `]8;;file://e.g…` garbage and on the cell-diff side desynced the renderer's prev-frame model). The cell-diff renderer now defensively trail-clears any row whose content shrank between frames. Resumed sessions keep their cumulative session cost instead of resetting to `$0`. The Approve plan modal now shows the plan body inline when the model didn't supply structured steps. Wide markdown tables fall back to row-grouped key/value lines instead of the previous column-grouped output. **Bug fixes:** - fix(markdown): stop linkifying English abbreviations + drop OSC 8 escape emission. The `FILE_REF_RE` extension class was too loose (`{1,6}`), so `e.g`, `i.e`, `a.m` matched as file paths; `osc8()` baked OSC 8 escape bytes into Text content, which the cell-diff renderer's wrapLine stripped of zero-width chars but kept the printable body — producing visible `]8;;file://e.ge.g]8;;` garbage on every terminal. Tightened the regex (now requires path-shape, line-number suffix, or extension >= 2 chars) and removed the OSC 8 escape — file refs still stand out via color + underline. (#330, PR #341) - fix(renderer): trail-clear rows that shrank between frames in the cell-diff diff. The diff skipped cells where prev and next were byte-equal (including trailing EMPTY cells), so any earlier ANSI desync left stale chars in shrunken rows — manifested as the shell-confirm modal showing `allow always` + `mand, ask again next time` after Up/Down navigation. New `clearToEOL` patch type and a per-row sweep after `diffEach`. (#330, PR #341) - fix(stats): carry session cost / turn count across resume. The TUI's `$X session` figure reset to `$0` on every resume even though the disk meta still held the cumulative `totalCostUsd`. `SessionStats` gains `seedCarryover()`; `CacheFirstLoop` reads the meta on resume and seeds the carryover when prior messages exist. (#333, PR #342) - fix(plan): show the plan body in the Approve plan modal. When the model called `submit_plan` with a markdown body but no structured `steps`, the modal showed only the choice list — users had no way to see what they were approving without scrolling back. The modal now renders the body via `MarkdownView`, capped at 24 lines with an overflow hint. (#336, PR #343) - fix(markdown): row-group the table fallback layout. When a table was too wide for the viewport, the fallback flattened it as N "Component:" lines, then N "What:" lines, then N "Manual TCs:" lines — the reader couldn't tell which value belonged to which row. Swapped to row-first iteration with a blank separator between rows. (#340, PR #344) ## [0.29.0] — 2026-05-06 **Headline:** tool dispatch is no longer strictly serial. When the model emits multiple `parallelSafe`-annotated tool calls in one turn (multiple `read_file`, multiple `spawn_subagent`, etc.), the loop now races them together via `Promise.allSettled`; a non-`parallelSafe` call ends the chunk and runs alone, so read-after-write ordering still holds. Tool yields and history append still land in declared order regardless of which call settles first — the model and UI see the same shape they would under serial dispatch. The TUI's `SubagentRow` becomes `SubagentLiveStack`, rendering 1 → rich card, 2..max → compact rows, > max → "+N more running…" fold. Closes umbrella #325. **Tool dispatch:** - feat(tools): `ToolDefinition.parallelSafe?: boolean` — opt-in annotation, default `false`. `ToolRegistry.isParallelSafe(name)` for the dispatcher to query; unknown / unannotated tools resolve to `false` so third-party MCP tools must explicitly opt in. Built-in read-only filesystem (`read_file`, `list_directory`, `directory_tree`, `search_files`, `search_content`, `get_file_info`), web (`web_search`, `web_fetch`), `recall_memory`, `semantic_search`, isolated child loops (`run_skill`, `spawn_subagent`), and in-memory job queries (`job_output`, `list_jobs`) are annotated. Mutating tools stay default. (PR #326) - feat(loop): chunked parallel tool dispatch. Replaces `for...of + await` in the dispatch loop with a chunking loop that groups consecutive `parallelSafe` calls and races them; unsafe calls form serial barriers. `runOneToolCall` extracts per-call lifecycle (PreToolUse + dispatch + PostToolUse) so the chunk can fan out via `Promise.allSettled` while the loop body keeps yielding events in declared order. Two new env knobs: `REASONIX_PARALLEL_MAX` (chunk size cap, default 3, hard max 16) and `REASONIX_TOOL_DISPATCH=serial` (escape hatch). Tests cover parallel timing, serial barrier on mixed safe/unsafe, declared-order yields under racey completion, and both env-knob overrides. (PR #327) **TUI:** - feat(ui): `SubagentEvent` carries a stable `runId` per spawn so the sink can key concurrent runs apart instead of overwriting one shared row. `useSubagent` keeps an array of in-flight activities; `SubagentLiveStack` renders 1 → rich card (unchanged), 2..max → compact rows with per-row spinner + iter + last tool, > max → compact rows + "+N more running…" fold. (PR #327) **Docs:** - docs(architecture): `docs/ARCHITECTURE.md` Pillar 1 gains a "Parallel tool dispatch" section explaining the chunking rule, both env knobs, and the list of built-in tools that opt in. (PR #328) ## [0.28.0] — 2026-05-06 **Headline:** subagent capability sharpened on three axes — skills can now scope a child to a specific tool subset via `allowed-tools` frontmatter, callers can request a per-spawn iter budget via the new `max_iters` arg (clamped 1-32), and two built-in personas (`explore`, `verify`) are selectable inline via a `type` arg without writing a skill. Closes umbrella #316. **Subagent:** - feat(subagent): honor skill `allowed-tools` frontmatter when forking the child registry. The field was parsed but ignored ("Unused in v1"); now it scopes the subagent to the named tools only. New `forkRegistryWithAllowList` helper alongside `forkRegistryExcluding`; `NEVER_INHERITED` (`spawn_subagent` / `submit_plan`) still wins so depth=1 + plan-mode guarantees hold even if a skill names them. An allow-list naming a tool the parent doesn't have returns a structured error result (no API call burned). (#317, PR #320) - feat(subagent): expose `max_iters` on the `spawn_subagent` tool schema. Clamped to 1-32 at the boundary; floats round down; non-numeric / missing falls back to the registration-time default (still 16). Verify-style tasks can ask for 6-8, explore-style can ask for 24+. (#318, PR #321) - feat(subagent): two built-in personas selectable via `type` arg — `explore` (wide-net read-only investigation, 20-iter budget) and `verify` (narrow yes/no with evidence, 8-iter budget). Caller's explicit `system` / `max_iters` override the type's defaults. Prompts live in new `src/tools/subagent-types.ts` so `subagent.ts` stays under the 500-line target. (#319, PR #322) ## [0.27.3] — 2026-05-06 **Headline:** USD-account users now see `$` instead of `¥` everywhere money is shown in the TUI — wallet balance, turn cost, session cost, top-bar cost label, subagent end-event cost suffix, and the UsageCard header / body / wallet line. Pre-fix a USD wallet rendered `¥0.0352 turn · ¥0.461 session · wallet ¥0.91`; now it renders `$0.0308 turn · $0.064 session · wallet $0.91`. The display follows the wallet currency reported by the DeepSeek API (`currency: "USD"|"CNY"`), not the UI language — a CNY account on an English UI still sees `¥`, and vice versa. Originally reported in #278 by @Explosion-Scratch. **UI / currency:** - fix(ui): USD wallets render `$` for wallet balance, turn cost, and session cost. State + event schemas now carry `balanceCurrency` through `App.tsx → reducer → StatusBar` so every render site sees the wallet symbol the API reported. Originally drafted by @wviana in #272; the TUI plumbing through state.ts / cards.ts / events.ts / reducer.ts / useScrollback.ts / slash/types.ts was the bulk of the fix. - fix(ui): balance color threshold checks USD against the CNY scale (`$0.91 ≈ ¥6.55`) rather than treating `0.91` as `< ¥5 → red`. USD wallets now correctly show yellow at low-but-not-empty balances. - fix(ui): `StatsPanel.ChromeRow` cost label and `useSubagent` end-event cost suffix follow the wallet currency too — pre-fix these always rendered `$`. (#313) - refactor(ui): seven currency helpers in `theme/tokens.ts` (`formatCNY` / `formatBalance` / `formatBalanceLabel` / `formatWalletDisplay` / `formatCost` / `balanceColorCny` / `balanceColorForBalance`) collapsed to three: `formatBalance`, `formatCost`, `balanceColor`. Undefined currency defaults to CNY (matches pre-fix unconditional `¥`) so the transient first-turn case where balance arrived but currency hasn't is consistent. - chore(ui): remove orphan `ChromeBar.tsx` (258 lines). `App.tsx` mounts `StatsPanel`'s diverged `ChromeRow`, which is the bar users actually see. The two formatter helpers ChromeBar once owned now live in `theme/tokens.ts`. (#314) **Loop:** - refactor(loop): `loop.ts` 1331 → 1219 (−112). Three sibling files under `src/loop/`: `messages.ts` (pure ChatMessage builders), `turn-failure-tracker.ts` (per-turn failure count + threshold tipping), `force-summary.ts` (forced-summary generator behind a small DI context). Continues the #308 / #309 cadence — small per-helper extractions, no behavior change. (#311) **Known follow-up:** `SessionPicker` still hardcodes `¥` for per-session cost in the session-history list, tracked in #312 (good-first-issue). ## [0.25.1] — 2026-05-05 **Headline:** `run_command` learns the four common shell chain operators (`|`, `||`, `&&`, `;`) and the seven file redirect operators (`>`, `>>`, `<`, `2>`, `2>>`, `2>&1`, `&>`). Parsed and spawned natively — no shell is invoked, so semantics are identical on Windows / macOS / Linux; PowerShell 5.1's `&&` parse error and the object-vs-bytes pipe gap are sidestepped. Each chain segment is allowlist-checked independently, so `git status | grep main` now auto-runs when both halves are individually allowed. Driven by discussion #231. **Shell:** - feat(shell): support `|`, `||`, `&&`, `;` chain operators in `run_command` via split-and-spawn. The chain is segmented at whitespace-bounded operators (preserves embedded `&` / `|` inside arg values like `--flag=1&2`), each segment runs through the existing lenient tokenizer, and segments are executed with proper short-circuit semantics for `&&` / `||`. Each segment hits the allowlist independently — `git status | grep main` runs when both halves are allowed individually. (#233, #234) - feat(shell): support file redirects in `run_command` — `>` (truncate), `>>` (append), `<` (stdin from file), `2>` (stderr truncate), `2>>` (stderr append), `2>&1` (merge stderr into wherever stdout points), `&>` (both → file). Targets resolve relative to the project root. Mid-pipe `2>&1` correctly merges stderr into the next segment's stdin without truncating on stdout-end. (#235) - fix(shell): chain parser stays consistent with the project's long-standing lenient tokenizer — `cargo run -- --flag=1&2` and similar embedded-operator args stay literal instead of getting POSIX-strict-rejected. shell-quote dependency dropped; `splitOnChainOps` is whitespace-bounded like the existing `detectShellOperator`. (#234) ## [0.24.1] — 2026-05-04 **Headline:** Two TUI fixes on top of the 0.24.0 cell-diff renderer. Frame writes are now wrapped in DEC 2026 synchronized-output markers so supporting terminals can't paint a half-cleared intermediate state, and `marked` is bumped to v15 to stop pre-escaping inline text into HTML entities — which both displayed wrong and miscalculated wrap widths. **Renderer:** - fix(renderer): wrap commit writes in DEC 2026 sync to suppress flicker. The commit / static / resize paths buffered bytes into a single write but the terminal could still paint the cleared-then- repainted intermediate state. Each frame now goes out wrapped in `\x1b[?2026h…l`; supporting terminals (Windows Terminal ≥1.18, iTerm2, Kitty, Wezterm, alacritty, foot) swap frames atomically, others ignore the unknown CSI. Resize's screen clear is also folded into the next commit so clear+repaint is one sync block. Closes #225. **Markdown:** - fix(deps): bump `marked` to v15 — v12 pre-escaped inline text to HTML entities (`<` → `<`, `"` → `"`), which displayed wrong in the TUI and miscalculated cell widths so content past the wrap edge could be clipped. v15 keeps `token.text` literal and only escapes at the HTML renderer layer, which matches our actual rendering path. ## [0.23.1] — 2026-05-02 **Headline:** Two follow-up fixes to 0.23.0 — the `ReasoningCard` and `StreamingCard` get a card-aligned redesign so they share the `CardBox` + `Pill` primitives the rest of the run cards already use, and the repair-storm detector now grants the loop one self-correction attempt on the first storm before bailing the turn. **TUI:** - fix(tui): redesign reasoning + streaming cards. Both cards now sit inside the shared `CardBox` with a tier-aware accent and a `Pill` header, replacing the ad-hoc layout that didn't line up with `ToolCard` / done-assistant rendering. New `primitives/CardBox.tsx` and `primitives/Pill.tsx` are reused by the broader card family. Closes #133. (#136) **Loop:** - fix(loop): repair-storm detector now self-corrects once before stopping. A single short repeat-loop sequence (e.g. one retry of the same tool call) used to abort an otherwise recoverable turn; the loop now gets one self-correction attempt and only bails on the second storm. (#134) ## [0.23.0] — 2026-05-02 **Headline:** TUI quality-of-life pass driven by RFC discussion #20. A read-only **context sidebar** on the right surfaces the active plan + running tools (`Ctrl+\` toggle, plan-only auto-show), assistant replies get a left **accent bar** so long answers are scannable in scrollback, the viewport gains a single **row-budget allocator** that ends the jitter when an approval modal mounts mid-stream, the prompt input grows a full **readline vocabulary** (`Home` / `End` / `Ctrl+K` / `Alt+B/F` / `Alt+Backspace`), and the `@`-picker honors **nested `.gitignore`** instead of dropping files past a 500-result cap on Flutter / iOS projects. **TUI:** - feat(tui): right-side context panel showing the active plan (windowed ±5 around the running step) and any running tool / subagent. Auto-shows when a plan starts running, hides on cancel via a new `plan.drop` reducer action; manual `Ctrl+\` toggle persists in `~/.reasonix/config.json.sidebarOpen`. Refuses below 88 cols total; sidebar divider uses `borderTop` so the line auto-fills the panel width. (#127) - feat(cards): done assistant Markdown gets a brand-toned `borderLeft` accent. Picked over `backgroundColor` because Ink's `` doesn't accept it — a left bar works on light + dark themes equally per lamyc's RFC #20 callout. (#126) - fix(tui): `StreamingCard`, `EditConfirm`, `ShellConfirm`, `PlanCheckpointConfirm`, `PlanConfirm`, `ChoiceConfirm`, `PromptInput` now declare their height to a single `ViewportBudgetProvider` instead of each reading `stdout.rows` and guessing. Modal-vs-streaming row race that produced visible vertical jitter mid-turn (lamyc's video) is gone. Pure allocator in `src/cli/ui/layout/viewport-budget.tsx` is priority-greedy (`modal > plan-card > status > input > stream`). (#124) - feat(prompt): full readline shortcut set wired into the prompt input — `Home` / `End` (line jumps, joins existing `Ctrl+A` / `Ctrl+E`), `Ctrl+K` (kill to end of line), `Alt+B` / `Alt+F` (word back / forward), `Alt+Backspace` (alias for the existing `Ctrl+W`). `Ctrl+U` keeps Reasonix's "clear whole buffer" behaviour, not readline's "kill to start" — clearing a large paste needs a single ergonomic key. (#123) **Bug fixes:** - fix(at-mention): @-picker walker now honors **nested** `.gitignore` (root + every subdirectory, layered like git itself) and bumps the default result cap from 500 → 2000. On Flutter / iOS projects with a built `ios/Pods/` directory the alphabetical walk used to burn the cap before reaching `lib/` and every `@` query returned "no files match". The new `src/gitignore.ts` util is shared with the semantic chunker — single source of truth for "walk a dir respecting `.gitignore`". Supports negation (`!keep.log`) and `respectGitignore: false` opt-out. (#129) **Internal:** - test: focused unit coverage for `resolvePreset` / `canonicalPresetName` + invariant check that every preset keeps `harvest: false` and `branch: 1` (the rule that branch and harvest are never silently auto-enabled). (#125) ## [0.22.0] — 2026-05-02 **Headline:** Live MCP-server reconnect — `/mcp reconnect ` (and the `r` keybind in the `/mcp` browser modal) tear down a stuck client, hand- shake a fresh one, and accept either identity or append-drift mid-session without breaking the prompt prefix cache. The `d` keybind in the same modal persists `mcpDisabled` for the selected server. The reconnect work was driven by an empirical DeepSeek cache spike (`benchmarks/spike-mcp-reconnect/`) that overturned the original RFC's "any drift = full miss" framing — the cache is chunk-keyed, so an appended tool costs only the new chunks (~95% hit retained). The full graduated-permissive design lives in #110. **MCP UX:** - feat(mcp): new `/mcp reconnect ` slash subcommand. Re-handshakes the named server's transport and swaps the underlying `McpClient` through a new `McpClientHost` indirection so existing tool closures keep working without re-bridging. Identity-drift is always accepted; append-drift (server added new tools at the end of its tool list) is accepted mid-session via `applyMcpAppend`, which calls `prefix.addTool` + `registry.register` for each new tool. Edit / reorder / remove drift is refused with a clear "restart Reasonix to apply" message — those are catastrophic for the cache and would need new `ImmutablePrefix` API surface (`replaceTool` / `removeTool`). (#115, #117) - feat(mcp): activate `r` (reconnect) and `d` (disable) keybinds in the `/mcp` browser modal. Both surfaces now route through one shared helper (`kickOffMcpReconnect` / `toggleMcpDisabled`) so the slash command and the modal stay byte-identical in behaviour. (#116, #118) - feat(mcp): new `reconnect` lifecycle state added to the formatter — `⌘ MCP · ↻ reconnect… tearing down · re-handshake · listing tools` per design §37. **Internal architecture:** - `src/mcp/registry.ts` — extracted `registerSingleMcpTool(mcpTool, env)` + new `BridgeEnv` type. `bridgeMcpTools` now exposes a `host` parameter (mutable client holder) and returns the resolved env so reconnect can register newly-added tools with the same options. (#115) - `src/mcp/reconnect.ts` (new) — opens a fresh transport, classifies drift via `classifyToolListDrift`, swaps `host.client` only on accepted drift kinds, closes the new client cleanly on refusal so the old one stays untouched. - `src/mcp/drift.ts` (new) — `classifyToolListDrift(before, after)` returns `{ kind, added, removed, edited }` over the five drift taxonomy buckets (identity / append / edit / reorder / remove). Pure function. (#114) - `McpServerSummary.client?: McpClient` replaced by `host: McpClientHost` + `bridgeEnv: BridgeEnv`. Internal-only (the type isn't in the public package surface). **Tests / spikes:** - `tests/mcp-reconnect-prefix-invariant.test.ts` (new) — six structural cases pinning `ImmutablePrefix.fingerprint` behaviour under every drift the reconnect path can produce. Locks the bytes-equal claim the design rests on. (#112) - `benchmarks/spike-mcp-reconnect/` (new) — live `deepseek-chat` spike + captured results: confirms DeepSeek's cache is chunk-keyed (~128 tokens), so appended-tool drift retains 94.8% hit and a description edit on the first tool retains 84.1% hit. Drives the graduated-permissive policy. (#113) - `tests/mcp-drift.test.ts`, `tests/mcp-reconnect.test.ts`, `tests/mcp-append.test.ts` (new) — unit coverage for the classifier, reconnect early-returns, and the append handler. **Deferred (filed as catastrophic-cache-cost cases):** - Edit-drift mid-session (needs `ImmutablePrefix.replaceTool`) - Reorder-drift mid-session (needs `removeTool` + cache-reset card) - Remove-drift mid-session (same) - `--strict` flag to refuse even append-drift Each is structurally a guaranteed cache miss and refused-with-restart is the right default; the follow-up issues will land if real demand surfaces. ## [0.21.0] — 2026-05-02 **Headline:** MCP CLI surfaces realigned with `docs/design/agent-tui-terminal.html` sections 24, 32, and 37. Lifecycle messages get the documented vocabulary (`↻ handshake…` / `✓ connected` / `✖ failed` / `○ disabled`), `/mcp` opens an interactive browser modal instead of dumping text to scrollback, named servers can be skipped on launch via `/mcp disable `, and a per-server p95 latency tracker emits a one-line warn toast when a server consistently goes slow. **MCP UX:** - feat(mcp): lifecycle line cards now match design §37 byte-for-byte — `⌘ MCP · ✓ connected 12 tools · 8 resources · 142ms` on bridge success, `↻ handshake…` before initialise, `✖ failed` with reason in the catch path. New `src/cli/ui/mcp-lifecycle.ts` is the single formatter shared by `chat` and `run`. (#106) - feat(mcp): `/mcp` opens a keyboard-driven browser modal per design §24, showing server name + health badge + tool / resource / prompt counts + capability list under the active row. `/mcp text` keeps the printed-card form for non-TTY / replay contexts. (#107) - feat(mcp): `/mcp disable ` and `/mcp enable ` slash subcommands persist a `mcpDisabled` list to `~/.reasonix/config.json`. Disabled named servers are skipped on the next launch and surface as `⌘ MCP · ○ disabled via /mcp disable ` in startup output. Anonymous servers (no `name=`) aren't toggleable, by design. (#108) - feat(mcp): per-server p95 latency tracker fires a one-line warn toast once when p95 over the last five calls crosses `mcpSlowThresholdMs` (default 4000) — `⚠ MCP \`\` slow · 8.4s p95 over the last 5 calls`. Idempotent: re-fires only after p95 dips below and crosses back. New `src/mcp/latency.ts` + `src/cli/ui/mcp-toast.ts`. (#109) **Deferred:** - `/mcp reconnect ` (live tool-list teardown) split out as RFC #110. The naïve implementation breaks the byte-stable prompt prefix when the reconnected server's tool surface drifts; needs a design call between refuse-on-drift / permissive-with-warn / `--force` flag before code. The `r` keybind in the `/mcp` browser is a labelled stub waiting for this RFC. ## [0.20.0] — 2026-05-02 **Headline:** Drops Node 20 support (EOL'd 2026-04-30). The README has been overhauled with hero-terminal / hero-stats / feature-grid SVGs that match the design-doc palette, plus contributor-avatar grid, Code of Conduct, and SECURITY policy. **Breaking:** - `engines.node` bumped from `>=20.10` to `>=22`. Node 20 reached end-of-life on 2026-04-30; `npm install reasonix` on Node 20 will now print an `EBADENGINE` warning. Tested CI surface trimmed to a single Node 22 job. (#98) **Fixes:** - fix(code): `reasonix code` now bridges MCP servers from `~/.reasonix/config.json`, matching `reasonix chat` behaviour. Previously any servers defined in config were silently skipped in code-mode sessions. (#91) - fix(mcp): `NAME_PREFIX` regex in `parseMcpSpec` accepts hyphens, so kebab-case server names like `sage-wiki=npx -y @scope/sage-wiki` parse correctly. Previously the entire string was treated as a raw command path. Regression test in `tests/mcp-spec.test.ts`. (#96) **Docs / project hygiene:** - docs(readme): introduce three new SVG assets that anchor the README's visual rhythm to the design-doc palette — `hero-terminal.svg` (faithful to `formatPendingPreview` unified-diff output), `hero-stats.svg` (94% / ~30× / MIT), and `feature-grid.svg` (six-card 3×2 grid). Bilingual `*.zh-CN.svg` siblings ship for the zh README. All SVGs live under `docs/assets/`. (#102) - docs(readme): designer pass — drop redundant `# Reasonix` H1 (the logo wordmark says it), drop the duplicated tagline, center the badges + description under one column, trim the comparison table to differentiating rows only, drop the `--system-append` doc subsection (lives in `--help`). (#102) - docs: design mockups (`agent-dashboard.html`, `agent-tui-terminal.html`) moved into `docs/design/` so README links resolve to the rendered GitHub Pages page instead of HTML source view. (#102) - docs(readme): replace the hardcoded `good-first-issue` ticket list with a single label-filter link — auto-fresh as tickets close. (#99) - docs(readme): drop "DeepSeek free credit on signup" claim from README, website, TUI Setup / Wizard prompts — perk no longer offered. (#102) - docs(readme): add `contrib.rocks` contributor-avatar grid; add GitHub stars + Discussions badges. (#102) - docs: add `CODE_OF_CONDUCT.md` (Contributor Covenant 2.1) and `SECURITY.md` (private-disclosure policy with explicit scope). (#102) ## [0.17.1] — 2026-04-29 **Headline:** Fix a render crash in the dashboard's Editor that triggered when toggling Edit / Split / Preview on a markdown file. Mixing the CodeMirror-managed DOM with sibling `dangerouslySetInnerHTML` while the host element changed shape across modes confused Preact's reconciler (`Failed to execute 'insertBefore' on 'Node'`). - fix(dashboard): Editor mode toggle no longer restructures the DOM. CM container and markdown preview are now always rendered at the same vnode positions; `data-mode` on a single `.editor-stage` wrapper drives visibility via CSS. CM stays mounted across mode switches and is poked with `requestMeasure()` when it becomes visible again. ## [0.17.0] — 2026-04-29 **Headline:** `reasonix index` is now config-driven — what gets walked is defined entirely by `~/.reasonix/config.json` (with sensible defaults), `.gitignore` is honoured by default, and the dashboard Semantic tab gains a Settings card to view, edit, and dry-walk-preview the rules without leaving the browser. The previous behaviour hardcoded skip lists in `chunker.ts` and duplicated them in `directory_tree`; both now read from a single shared source. - feat(index): new `index` block in `ReasonixConfig` (`excludeDirs`, `excludeFiles`, `excludeExts`, `excludePatterns`, `respectGitignore`, `maxFileBytes`). Any field present fully replaces its default; absent fields keep the default. - feat(index): nested `.gitignore` honoured by default — each subdirectory's rules apply scoped to that subdir, so `pkg-a/.gitignore` doesn't leak into `pkg-b/`. - feat(index): glob excludes via `picomatch` syntax in `excludePatterns` (e.g. `**/*.gen.ts`, `vendor/**`, with `!negation` supported). - feat(cli): `reasonix index` success line now prints a per-reason skip breakdown (`gitignore: A · pattern: B · defaultDir: C · …`) so users see what was filtered and why. - feat(dashboard): Semantic tab gains a collapsible **Excludes** card with editable lists, gitignore toggle, max-file-size input, **Save** / **Reset** / **Preview** buttons, and a per-reason sample drilldown in the Preview panel. - feat(server): `GET /api/index-config` returns user/resolved/defaults; `POST /api/index-config` persists; `POST /api/index-config/preview` dry-walks the project root with a draft config and returns sample paths + skip buckets. - refactor(tools): `directory_tree` now reuses `DEFAULT_INDEX_EXCLUDES` from `src/index/config.ts` instead of its own copy of the dir/binary lists; the two were already drifting. - deps: `picomatch ^4`, `ignore ^7`, `@types/picomatch ^4`. ## [0.16.1] — 2026-04-29 **Headline:** Fix a tool-loop regression on `deepseek-chat` introduced by DeepSeek's V4 rollout. The model now returns non-empty `reasoning_content` even with `extra_body.thinking.type = "disabled"`, and the API rejects round-trips that drop the field ("reasoning_content in the thinking mode must be passed back to the API"). Reasonix's whitelist-by-model in `assistantMessage()` was too narrow — it stamped reasoning_content only for `deepseek-reasoner` / `deepseek-v4-flash` / `deepseek-v4-pro`. Caught by re-running τ-bench on v0.16.0: 24/24 reasonix runs were failing. - fix(loop): `assistantMessage()` now preserves `reasoning_content` whenever the producer emitted non-empty content, regardless of the model name. The whitelist still applies to synthetic messages (empty stamp for thinking-mode endpoints) so non-thinking sessions stay clean. - test(loop): regression case in `loop-r1-reasoning.test.ts` — deepseek-chat returning non-empty `reasoning_content` round-trips the field on the next request. - bench(tau): full re-run on the fix — 100% pass · 90.2% cache hit (vs 32.8% baseline) · $0.000593 / task. Mean cost is ~62% lower than the 0.2.1 snapshot, mostly from DeepSeek's price moves. ## [0.16.0] — 2026-04-29 **Headline:** Mouse drag in the log now selects text directly, with the log auto-scrolling when the drag hits the viewport edge. Releasing the button copies the selection to the system clipboard via OSC 52 plus a tempfile fallback for terminals that don't honor it. The whole flow stays inside the alt-screen TUI — no more `/copy` dance to dump the log to main buffer. - feat(ui): app-owned mouse selection. Plain drag paints a reverse- video highlight across the selected rows; the selection follows scroll naturally because rows are tracked in absolute log-row coordinates, not viewport-relative. Dragging past the top or bottom edge of the content area starts a 60ms-tick auto-scroll that keeps extending the selection while the cursor stays at the edge. Releasing copies the plain-text rendering via OSC 52 (system clipboard) plus a `/reasonix-clip-.txt` fallback for terminals or remote sessions that drop OSC 52. Shift+drag still bypasses tracking so the terminal's native selection remains available for visible-only copies. - feat(infra): `stdin-reader` now surfaces `mouseDrag` (SGR button 32) and `mouseRelease` (tail `m`) events; previously dropped silently. `alt-screen` switches from mode 1000 (press/release only) to mode 1002 (button-event tracking with drag motion). - feat(ui): `log-frame` extends `AtomViewport` with `firstRowAbs` so the keystroke layer can map mouse coordinates back to absolute log rows. New `extractSelection(atoms, sel)` walks the cell grid and produces UTF-8 text honoring 2-wide chars (CJK / emoji) with ANSI styling stripped. - chore(ui): `/copy` slash command, the `copyMode` lifecycle, the alt-screen exit + main-buffer dump, and the `setMouseTracking` / `isMouseTrackingOn` helpers all removed. The new flow doesn't need to leave alt-screen, doesn't pollute main scrollback, and doesn't have the "two histories stacked" bug the dump approach kept hitting. ## [0.15.0] — 2026-04-29 **Headline:** Event-log sidecar lands as a real kernel artifact and gets its first consumer — `replay()` reads `events.jsonl` and runs the same pure reducers `apply()` does in-process. First external PR merged: deny-with-context, pressing Tab on a tool-confirm modal lets the user type *why* they're refusing, forwarded to the model verbatim. Comment policy now enforced by `tests/comment-policy.test.ts` under `npm run verify`; companion sweep dropped 6.3k LoC of module-essay docstrings, banner separators, and incident-history narrative across 148 source files. - feat(core): `events.jsonl` sidecar — every kernel `Event` is appended to `.events.jsonl` next to the legacy `LoopEvent` log. Append-only, durable, no behavior change for in-process consumers. Unblocks the v0.14 architecture migration: any view (CLI, dashboard, replay) can now reconstruct state from the sidecar without the loop running. - feat(core): `replay()` reads the sidecar and runs the same pure reducers as in-process `apply()`. First proof that the projection layer is genuinely deterministic — `replay(events)` matches `apply(...)` for the conversation / budget / plan / workspace / capabilities / status / session-meta views. - feat(cli): `reasonix events ` — inspect any session's event stream from the command line. Filters by event variant (`reasonix events ToolCallStarted`), tail mode, JSON output for piping into `jq`. Plus a kernel sweep removing the dead-comment layer that accumulated during the LoopEvent → Event transition. - feat(ui): deny-with-context (PR #1, by @wviana). On any tool-confirm modal (`ShellConfirm`, `WorkspaceConfirm`, edit review), pressing Tab on the Deny option opens inline editing — type a reason, Enter submits. The reason is appended to the synthetic `I denied running …` message so the model knows *why* and can adjust course instead of plowing ahead. Edit-review path uses a dedicated `DenyContextInput` modal (n hotkey opens the reason input, Esc returns to the diff). Bracketed-paste support in the inline editor so multi-line context can be pasted in one go. - chore(ui): removed obsolete `/mouse` slash command and the misleading "drag to select & copy" prompt hint — both predated `/copy` and gave wrong guidance now that the proper flow is alt-screen-exit + scrollback dump. - chore(comments): `tests/comment-policy.test.ts` pins six rules derived from `CLAUDE.md`: ≤2-line module headers, no Phase-N narrative, no version refs in comments, no incident history (`user reported`, `screenshot showed`, `fix for #N`), no banner separators (`// ─── helpers ───`), ≤3-line block comments. Runs under `npm run verify`, which is the pre-push gate. Companion sweep: 116 module-essay headers compressed to one line, 577 over-long block comments distilled or deleted, 44 banner separators stripped. Net −6,367 LoC of dead-weight comments across 148 files; zero behavior change, full lint/typecheck/test green. ## [0.14.0] — 2026-04-29 **Headline:** Two real bug fixes (post-shell-confirm session lockup, post-workspace-switch ENOENT on edit_file), a new `/copy` mode for copying across multi-screen log content, an always-on context-pressure footer above the prompt, and width-aware chrome that stops dropping pills when there's clearly room. Plus a quiet refactor: shared UI primitives, dead-code purge in StatsPanel. - fix(loop): streaming-abort path now resets `_turnAbort` before returning. Without this, a queued-submit triggered by App.tsx (ShellConfirm "run once" → `loop.abort()` + `setQueuedSubmit`) produced a spurious `aborted at iter 0/64 — stopped without producing a summary` the moment the synthetic message reached the loop, locking the session until the user `/retry`'d. - fix(tui): `edit_file` interceptor now reads the workspace root via `currentRootDirRef` instead of capturing `currentRootDir` in a stale closure. Workspace switch (`change_workspace` → modal approve) rebound `read_file` / `run_command` to the new root but left the interceptor pointing at the old one — `edit_file` wrote to the old path while `read_file` looked in the new one, surfacing as a mysterious ENOENT for a file the model had just successfully edited. - feat(tui): `/copy` exits the alt-screen, dumps the rendered log to the main screen, and listens for any keystroke to restore. Native terminal scrollback + drag-select work on the dump — solves the "can't copy text that scrolled past the viewport" problem alt-screen introduced. Re-entering alt-screen and bumping React state forces Ink to redraw the TUI cleanly. Multiple enter/exit cycles per session; React tree, event log, model session, prompt draft all preserved across the toggle. - feat(tui): always-on context-pressure footer above the prompt — `ctx ▰▰▰▱▱▱▱▱▱▱▱▱▱▱ 14K/977K · 1% · sys 5.8K · tools 6.1K · log 0`. Single-row layout matches the chrome bar's `▰▱` visual language. Width-aware shed for the breakdown segments (input → log → tools → sys). `/context` toggles visibility (default on); the rich 4-color stacked breakdown is still pushed to scrollback for headless / replay surfaces that don't carry the toggle callback. - feat(tui): chrome bar pill rendering switches from preemptive `narrow = cols < 120` to width-aware greedy shed. Optional pills (balance > cache > session > update) drop in priority order only when `string-width` math says they won't fit — at 100 cols all five render where the old code dropped three. Cache pill is now default-on (cold-start dim treatment instead of hiding). - refactor(ui): `Bar`, `formatTokens`, `ChromeRule`, `ContextCell` promoted to `src/cli/ui/primitives.tsx` (were duplicated 2-3× across `StatsPanel` / `ChromeBar` / `EventLog` / `log-frame`). `CtxBreakdownBlock` + `computeCtxBreakdown` extracted to `src/cli/ui/ctx-breakdown.tsx` so `/context` and the footer share the same compute path. `StatsPanel` shrunk from 769 → ~280 lines (dead helpers from the chrome redesign era removed). - feat(core): v0.14 architecture scaffold — `src/core/events.ts` (25-variant Event union + 7 view types), `src/core/reducers.ts` (pure projections + `apply` / `replay` combinators), `src/ports/*.ts` (6 ports: ModelClient, ToolHost, EventSink, MemoryStore, HookRunner, CheckpointStore). Types only; zero behavior change. 19 reducer tests pin the conversation / budget / plan / workspace / capabilities / status / session-meta projections and prove `replay()` determinism. ## [0.13.5] — 2026-04-29 **Headline:** TUI overhaul. Chrome reverts to native Ink Box + flexGrow (Phase 6a's Frame-compiler chrome was clipping pills on Windows Terminal / ConPTY). Vertical scrollbar replaced with a `[↑ N%]` chrome pill + horizontal mini-bar in the bottom hint — column-aligned scrollbars are unreliable while some log atoms still render through legacy ReactElements. Streaming gains the design's `responding ░▒▓█▓▒░░░░` marquee and a `▌` cursor blink at end-of-body. - chrome: `ChromeBar` uses native flex; preset pill (`[auto]` / `[flash]` / `[pro]`) replaces edit-mode pill (edit mode still surfaces via `ModeStatusBar`); CNY balance renders as `w ¥8.50`; cost pill includes inline budget when set. - streaming: full body text streams in (was 140-char tail) with a blinking primary-color cursor; `responding` row shows a 12-cell marching wave (`░▒▓█▓▒`) at 120ms ticks. Matches `design/tui-redesign-ink.html`. - scroll: vertical `ScrollBar` removed; chrome shows `[↑ N%]` when scrolled, `BottomHint` shows `↑ N · ▕──●──▏ X% · ↓ M · End`. - frame: `src/frame/width.ts` delegates to the `string-width` package; hand-rolled width tables removed. - chore: project `CLAUDE.md` codifies code/comment conventions (terse comments, no Phase-N essays, libraries over hand-rolled unicode math). ## [0.12.15] — 2026-04-28 **Headline:** Every user-facing string that still said `fast / smart / max` is now `auto / flash / pro` — the canonical names presets have used since the autoEscalate split. CLI flags (`chat --preset`, `run --preset`), `/help`'s preset table, `/preset`'s argHint and completer, the slash handler's `usage:` line, and the `code` command description all updated. Old `config.json` files keep working: `resolvePreset` still maps `fast → flash·effort=high`, `smart → auto`, `max → pro`. What changed is the interactive surface — `/preset fast` now prints usage instead of silently doing the right thing, so the in-chat vocabulary matches what's documented. ## [0.12.14] — 2026-04-28 **Headline:** Three TUI confirmations the dashboard couldn't see — `change_workspace`, plan checkpoints, plan revisions — now mirror to the web modal layer with the same Switch/Deny/Continue/Revise/Stop/ Accept/Reject choices the terminal exposes. Plus: a deferred-dispatch fix for parallel tool calls that was silently writing files into the old workspace, and the in-flight row finally tells you _what tool_ is running, not just "waiting". ### Loop — workspace-switch parallel-batch fix When DeepSeek emits `change_workspace + write_file` in one assistant message, every call dispatched in sequence — write_file fired against the OLD sandbox before the user had a chance to approve the modal, silently dropping the new file in the wrong project. Every subsequent call in the same batch now gets a synthetic "deferred — re-issue on your next turn" result; tool_call ↔ tool pairing stays valid for DeepSeek's next-turn validator. Test in `tests/loop.test.ts` locks it. ### Server / context - `ActiveModal` gains three new shapes: `workspace`, `checkpoint`, `revision`. `getActiveModal` returns them so a freshly-connected client paints the right modal mid-prompt. - `DashboardContext` adds `resolveWorkspaceConfirm`, `resolveCheckpointConfirm` (with optional `text` for revise-with- feedback in one shot), and `resolveReviseConfirm`. - `/api/modal/resolve` accepts the three new `kind`s with their per-shape choice validation. 503 when a resolver isn't wired. ### App.tsx wiring - `pendingWorkspace`, `pendingCheckpoint`, `pendingRevision` each broadcast `modal-up`/`modal-down` SSE events. - Web's "revise + feedback in one shot" path bypasses the TUI's staged-input two-step by accepting an explicit snap override on `handleCheckpointReviseSubmit` — no more setStagedX → re-render → ref-mirror microtask race. ### Dashboard SPA - New `WorkspaceModal`, `CheckpointModal`, `RevisionModal` Preact components. Modal switch dispatches them by `modal.kind`. - In-flight row now shows the active tool + key args (path / command truncated to 80 chars / char count) once `tool_start` fires — `write_file → /path/to/foo (12,345 ch)` instead of "waiting…". - Tool-start no longer pushes a placeholder info row. The InFlightRow carries the live state; the result card replaces it on `tool`. - ErrorBoundary stops auto-recovering after 3 catches and renders a manual "Try again" button — no more silent flickering loop. - `.modal-cmd` gets `overflow-x: auto` + `max-height: 240px` so a pathological multi-kilobyte command can't push the rest of the panel offscreen. ## [0.12.13] — 2026-04-28 **Fix:** the chat feed kept yanking the user back to the bottom during streaming — wheel-up didn't stick. Two bugs stacked: 1. The scroll listener attached to `document.querySelector(".chat-feed")` on first mount, but the `.chat-feed` div was conditionally rendered (only when at least one message existed). On a fresh session the listener never attached, so the "is the user scrolled away?" flag was never flipped to `false`. 2. Even after the listener attached, the auto-scroll effect's own `el.scrollTop = el.scrollHeight` write fires a `scroll` event that re-snaps the flag back to `true`. Manual wheel scrolls were racing the next streaming delta's auto-snap. Both fixed: - `.chat-feed` is now always rendered (the empty-state copy moved inside it). A `feedRef` ref attaches the scroll listener on first paint. - A new `autoScrollInFlight` ref gates the listener: events observed during a programmatic scroll write are ignored, so only genuine user wheel/drag flips the auto-scroll guard. ## [0.12.12] — 2026-04-28 **Headline:** Indexing from the dashboard now actually wires up `semantic_search` for the running session — no more "build the index, restart, build again" dance — and a dismissible Chat banner steers users to the Semantic panel when no index exists. ### Loop / prefix - `ImmutablePrefix` gains an `addTool(spec)` method that pushes a new tool spec onto the live prefix. The class name is now a half-truth (toolSpecs is exposed via getter, backed by a mutable array) but the rationale is documented inline: a one-time cache miss is cheaper than asking users to restart the session. - New `DashboardContext.addToolToPrefix(spec)` callback. Wired from `App.tsx` to `loop.prefix.addTool`. ### Server - `runIndex` (the dashboard's buildIndex wrapper) calls `registerSemanticSearchTool(ctx.tools, …)` after a successful build, then `ctx.addToolToPrefix(spec)` so the model sees `semantic_search` from the next turn. Failures are non-fatal — the index is still on disk, the next session bootstrap picks it up. - `/api/overview` returns `semanticIndexExists` (`true`/`false`/ `null`) so the Chat panel can render the banner without an extra round-trip. ### Dashboard — Chat panel - New top-of-Chat banner: `≈ Semantic search isn't enabled for this project — Build it →` with a dismiss `×`. Visible only when `semanticIndexExists === false` and not previously dismissed (state in `localStorage` as `rx.semanticBannerDismissed`). - Click "Build it →" fires `appBus.dispatchEvent("navigate-tab")` with `tabId: "semantic"` — the existing nav handler picks it up. ## [0.12.11] — 2026-04-28 **Headline:** Tell users what to do when Ollama isn't installed yet. The 0.12.9 Semantic panel just said "not reachable" with a generic copy-this-command blurb — the new flow distinguishes "binary missing" from "daemon down" from "model not pulled" and offers a one-click action for each level it can resolve. ### Server - `GET /api/semantic` now returns the full `checkOllamaStatus` payload — `binaryFound`, `daemonRunning`, `modelPulled`, `modelName`, `installedModels` — instead of the raw probe. - New endpoints: - `POST /api/semantic/ollama/start` — runs `startOllamaDaemon` (15s timeout). Returns `{ ready, pid }`. - `POST /api/semantic/ollama/pull` — fire-and-forget `pullOllamaModel`. Per-model `PULLS` map tracks status + last log line; `/api/semantic` includes it as `pull`. ### Dashboard — Semantic panel Tri-state Ollama section: - **No binary** → red "not installed" pill + Install Ollama card with macOS / Windows / Linux install instructions. We deliberately don't run package managers for the user. - **Binary, daemon down** → yellow "daemon down" pill + "Start daemon" button (calls `ollama/start`). - **Daemon up, model missing** → "not pulled" pill + "Pull " button. Live status row during the pull (latest ollama output line, elapsed seconds, success/error pill). - **Everything ready** → green pill, Index buttons enable. Polling speeds up to 1.2s while a pull or build job is running. ## [0.12.10] — 2026-04-28 **Headline:** Move the in-flight indicator out of the top-left corner and put the live counters next to it. Previously the spinner appeared above the message stream — far from where the user's eyes already were (input + status bar) — and the only moving signal during a turn was the streaming text itself. ### Chat panel - New **InFlightRow** rendered just above the ChatStatusBar whenever a turn is in flight. Format: `⠋ thinking · 2.3s · reasoning 1,204 ch · out 0 ch · [Abort]` - Phase auto-flips between `thinking` (only reasoning growing), `streaming` (text growing), and `waiting` (neither — model is thinking with no token output yet, e.g. before the first delta arrives). - Elapsed seconds tick every 500ms via a per-turn interval so the user sees motion even when the model is in a long pause between deltas. - Character counts come from the existing `streaming` state — no new wire fields, just rendering data we already have. - Top "turn in flight" row is gone; only `statusLine` notices still render up there when not busy. ## [0.12.9] — 2026-04-28 **Headline:** Semantic indexing without leaving the session. Previously you had to exit the TUI, run `reasonix index`, wait, then re-enter — every change. Now there's a Semantic panel in the dashboard that drives `buildIndex` in the background and shows live progress. ### Server - `src/server/api/semantic.ts` — new endpoint set: - `GET /api/semantic` → Ollama probe + index existence + current job snapshot - `POST /api/semantic/start` → kick off `buildIndex({ rebuild })` fire-and-forget, returns 202 - `POST /api/semantic/stop` → flag job as aborting (advisory; `buildIndex` doesn't honor a signal yet, lands when it does) - Per-root `JobRecord` map (module-scoped) tracks phase (scan/embed/write/done/error) + counters (filesScanned, chunksTotal, chunksDone, …) updated via `onProgress`. ### Dashboard - New **Semantic** sidebar tab. Polls `/api/semantic` every 1.2s while a job is running, every 5s when idle. - Surfaces Ollama daemon reachability + listed models, current index existence, and the live job: phase pill, file/chunk counters, percentage progress bar, elapsed seconds, last result on completion, error text on failure. - Buttons: **Index (incremental)**, **Rebuild (wipe + full)**, **Stop**. Disabled appropriately when Ollama isn't reachable or another job is running. Inline guidance on missing daemon. - Standalone `reasonix dashboard` mode shows a polite "code-mode required" empty state — no project root, nothing to index. ## [0.12.8] — 2026-04-28 **Fix:** the dashboard row in 0.12.7 collapsed the URL and description onto one Box; on terminals that hide the OSC 8 escape, Ink's text-width measurement counted the escape bytes as visible characters and the description wrapped through the middle of the URL. Split into two stacked rows: ``` ◇ web open the dashboard in a browser (chat · files · stats · settings) http://127.0.0.1:NNNN/?token=… ``` URL still wrapped in the OSC 8 hyperlink — but it's the only content on its row, so a width miscount can't clobber anything. ## [0.12.7] — 2026-04-28 **Headline:** Dashboard discoverability. Most users had no idea `/dashboard` existed — the URL is now visible from the first turn, on its own row in the status panel, with a one-line description of what the dashboard actually offers. Clickable in OSC-8-aware terminals (iTerm2, WezTerm, Windows Terminal, VS Code, recent gnome-terminal); copy-pasteable everywhere else. ### TUI - Auto-launch the embedded dashboard when `reasonix code` / `reasonix chat` mount. Failures are silent (a missing dashboard never blocks the TUI), tear-down still happens on unmount / `/dashboard stop`. - `--no-dashboard` opts out per-session (CI, hardened environments, anyone allergic to a localhost listener). - New status-panel row: `◇ web http://127.0.0.1:NNNN/?token=… open the dashboard in a browser (chat · files · stats · settings)` rendered between the header and the metrics so it never fights for space. - URL wrapped in an OSC 8 hyperlink — Cmd/Ctrl-click in any terminal that supports the escape; bare text otherwise. - `App` gains a `noDashboard` prop, `StatsPanel` a `dashboardUrl` prop. Both threaded through `chatCommand` / `codeCommand`. ## [0.12.6] — 2026-04-28 **Headline:** Bigger fixes for the things you actually look at: the edit-review modal is now a real side-by-side diff, the sidebar collapses to icons, and the call-storm breaker stops mistaking legitimate read → edit → verify cycles for storms. ### Edit review modal - Two-column **side-by-side diff** ("before" left, "after" right) with hljs syntax highlighting per the file's language. Adjacent removed/added line runs pair into rows so the change reads cleanly across the gutter. - Red tint + `−` marker on the removed side; green tint + `+` on the added side; context lines render unchanged. - Modal payload (`{ kind: "edit-review" }`) gained `search` and `replace` fields holding the full block contents — the old truncated `preview` string stays alongside for older clients. `src/cli/ui/App.tsx` and `src/server/context.ts` updated. ### Sidebar — icon-only collapse - New `◀ collapse` button at the bottom of the sidebar shrinks it from 220px → 52px and hides every label, leaving just the glyphs. `▶ expand` brings labels back. Choice persists in `localStorage` (`rx.sidebarCollapsed`). - Tabs in the collapsed state center the glyph and keep the primary-color active indicator. ### Call-storm breaker — false-positive fix The `read → edit → verify → edit → verify` pattern was tripping the storm protection (3 identical `read_file` calls within the window). The fix sources its "did this call mutate state?" signal from the existing ToolRegistry — each tool already declares `readOnly` / `readOnlyCheck` for plan-mode gating, so no new flag was added. The breaker now: - Tags every buffer entry as read-only or mutating based on the predicate the loop wires in (`def.readOnly === true`, with `readOnlyCheck` taking precedence on the actual args). - On a mutating call, drops prior read-only entries from the window — a re-read after `edit_file` is fresh, not a repeat. - Keeps mutator entries alongside, so a model looping on identical `edit_file` calls still trips on the threshold. `StormBreaker(window, threshold, isMutating?)` is the public shape; `ToolCallRepair` accepts an `isMutating` predicate. Without one (older callers, isolated tests) every call counts — back-compat preserved. Three new storm tests cover the cases. ## [0.12.5] — 2026-04-28 **Headline:** Stop loading CodeMirror from a CDN, fix the legacy preset migration that broke 2 CI tests, and replace the markdown preview toggle with a proper Edit / Split / Preview tri-state. ### Editor — local CodeMirror bundle - `scripts/bundle-codemirror.mjs` — esbuild-based bundler that pulls every `@codemirror/*` package from `node_modules` and produces `dashboard/codemirror.js` (~937 KB minified ESM). - `npm run build:cm` rebuilds it. Output is committed so a fresh `npm install` doesn't have to run esbuild. - `dashboard/app.js` now does `import("/assets/codemirror.js")` instead of 21 `import("https://esm.sh/...")` calls. One copy of every package = no Tag identity issues, no transitive-version drift between cold loads. - `serveAsset` learns to serve `codemirror.js`. `package.json` ships the bundle in `files`. Biome ignores the minified file. - `@codemirror/*` + `esbuild` added to devDependencies — they feed the bundler, they don't end up in the runtime install. ### Editor — markdown view modes - Replaced the `Preview`/`Edit` boolean with a three-state segmented control: **Edit** (source only, default), **Split** (source on the left, rendered on the right, with a divider), **Preview** (rendered only). Buttons live in the editor bar and are markdown-only — non-md tabs hide the group entirely. - The CodeMirror remount effect now keys on `viewMode`, so flipping between Edit and Split doesn't leave a stale view. ### Preset rework — CI fix `resolvePreset` was collapsing every legacy name (`fast`, `smart`, `max`) to `auto`, which made two `tests/resolve.test.ts` cases fail because they assert the legacy mapping that older config files depend on. Restored the original semantics: - `fast` → flash with `effort: high` (no auto-escalate) - `smart` → auto (flash + max + auto-escalate) - `max` → pro Anything else still collapses to auto. Suite back to 1568 / 1568. ## [0.12.4] — 2026-04-28 **Headline:** The two real editor problems that 0.12.2/3 didn't actually fix: highlighting was still missing for every language, and the new markdown preview produced a half-rendered page where the bottom got dumped into a `
`.

### Editor

- **Pin `@lezer/highlight` + `@lezer/common` in the esm.sh
  `?deps=` list.** The silent-no-highlights failure was caused by
  duplicated `@lezer/highlight` instances across CodeMirror
  packages: `tags.keyword` etc. are JS objects compared by
  identity, so when the language pack and the theme each loaded
  their own copy, the parser produced tags the theme didn't
  recognize, and all coloring quietly went away. Pinning common
  + highlight forces every package to share one set.
- **Separate `Marked` instance for the markdown preview
  (`previewMarked`).** The chat renderer is loaded with custom
  `code` handling for SEARCH/REPLACE diffs and edit:foo/path
  fence syntax — that ran on every preview too, occasionally
  swallowing the rest of the document into one `
` block on
  certain inputs. Preview now uses a vanilla marked + a slim
  hljs-only `code` override.

## [0.12.3] — 2026-04-28

**Headline:** Editor as a first-class sidebar tab. The drawer was
the only way in, which meant you had to start a chat and click a
file path before you could browse anything. Now there's a sidebar
entry that opens the file tree directly.

### Editor

- New **Editor** tab in the sidebar (after Chat). Mounts the
  `EditorPanel` full-width inside `.main` — same file tree,
  tabs, CodeMirror — no drawer chrome.
- `.main` gets a `main-editor` modifier when the editor tab is
  active, dropping the 28×36 panel padding and letting the
  editor fill the viewport.
- The chat drawer entry point still works (clicking a path in a
  tool card slides the drawer in over the current tab). Drawer
  and sidebar Editor are separate instances; their tab state
  doesn't share yet — revisit if it becomes annoying.

## [0.12.2] — 2026-04-28

**Headline:** Editor polish pass. Tabs at the top span the full
editor width like VS Code, syntax highlighting actually shows up,
the gutter/line numbers match the dark theme, autocomplete pops
on every keystroke instead of waiting for a manual trigger.

### Editor

- **Tabs on top, full width** — moved out of `.editor-main` and
  into a sibling `.editor-tabs` that sits above the side+main
  body row. Active tab gets a primary-color top border and the
  editor's own background, so it visually merges into the code
  surface (VS Code pattern). The file panel can collapse and the
  tab bar stays put.
- **Highlighting works** — `oneDark` already ships its own
  HighlightStyle; the existing `defaultHighlightStyle` wrap was
  fine but ordered before `oneDark`, so it didn't cover languages
  oneDark misses. Reordered to fall back AFTER oneDark and added
  `highlightActiveLineGutter` so the active row stands out in the
  gutter too.
- **Gutter restyled** — `.cm-gutters` gets a darker `#21252b`
  background, line numbers use the muted `#495162` for inactive
  rows and `#abb2bf` for the active row, with a 40px min-width
  and 16px right-padding. Fold gutter ships alongside (click the
  arrow next to a brace to fold).
- **Autocomplete** — `autocompletion({ activateOnTyping: true,
  closeOnBlur: true, maxRenderedOptions: 30 })` so suggestions
  pop while you type. Added `completionKeymap` so Tab/Enter pick
  the highlighted entry. Popup styled to the dark palette.
- **Tab close ergonomics** — close button has a fixed 18px box
  so the tab doesn't jump width when the dirty dot toggles.

All edits in `dashboard/app.js` `EditorPanel` + `dashboard/app.css`.

## [0.12.1] — 2026-04-28

**Headline:** Editor v2 — VS Code-style file tree, collapsible file
panel, wider drawer. The 0.12.0 editor opened on the right at 50%
with a flat alphabetical file list; on a normal-width window that
felt cramped and the file list scrolled forever.

### Editor

- **File tree** — flat path list collapses into a recursive folder
  tree. Folders sort first, files alphabetically; click `▶` to
  expand, `▼` to collapse. The expanded set lives in panel state so
  it survives drawer close/reopen within a session.
- **Collapsible side panel** — `◀` button at the top of the file
  panel hides everything except a thin `▶` button that brings it
  back. Editor area gets the full drawer width when files are out
  of the way.
- **Wider drawer** — `.editor-drawer-host.open` bumped from 50% →
  65% (min-width 360 → 420) so the editor breathes.
- **Filter still flat** — when the search box has text, the tree
  view collapses to the existing flat filtered list (paths are
  more useful than indented names when you're searching).

No backend changes. All edits in `dashboard/app.js` `EditorPanel`
+ `dashboard/app.css`.

## [0.12.0] — 2026-04-28

**Headline:** Web dashboard. A top-tier local control plane that lives
alongside the TUI — chat, files, MCP, skills, hooks, settings, all on
one URL. Plus auto/flash/pro preset rework so model commitment is
something you actually understand.

### Web dashboard (`/dashboard` slash)

A full-screen browser app, embedded HTTP server, 12 panels, modal
mirroring back to the live TUI. 127.0.0.1 only, ephemeral token in
the URL, CSRF on every mutation.

**Foundation (v0.12 base)**
- HTTP server in `src/server/` — Node native `http`, zero new deps
- Token + CSRF auth, audit log per mutation
- Preact 10 + HTM SPA (no build step), CSS lifted from `src/cli/ui/theme.ts`
- 12 panels, all functional: Chat / Overview / Usage / Sessions /
  Plans / Tools / Permissions / System / MCP / Skills / Memory /
  Hooks / Settings

**Chat parity (v0.13a)**
- POST `/submit` routes through `handleSubmit` so slash commands,
  `!cmd`, `@path` work identically; SSE `/events` streams loop
  events live; `/abort` mirrors Esc; `/messages` snapshots the
  log; `/modal/resolve` lets web pick a ShellConfirm /
  ChoiceConfirm / PlanConfirm / EditConfirm — either surface
  resolves, the other's modal disappears
- Web: marked.js + highlight.js 38-language pack, GFM tables,
  custom diff renderer for SEARCH/REPLACE blocks (red `-` / green
  `+`) and unified diffs, kind-specific tool cards (edit_file,
  read_file, write_file, run_command), markdown-styled assistant
  messages with reasoning blockquote, blinking cursor while
  streaming, scroll lock when user reads above bottom, custom
  scrollbars in brand palette

**Observability (v0.13b)**
- Sessions browser — list / read any saved session
- Plans archive — replay archived plans with risk pills
- Usage time-series chart (uPlot) — daily cost / cache-saved / turns
- System health — disk usage, version check, jobs

**Mutation surface (v0.14)**
- MCP — list bridged servers + add/remove specs to config
- Skills — list, edit body, create new, delete
- Memory — REASONIX.md + global / project private memory editor
- Hooks — settings.json hook block editor + reload
- Settings — API key (write-only), base URL, preset, effort, search

**Polish (v0.15)**
- Mobile responsive: sidebar collapses to drawer with hamburger,
  metric grid drops to 2 columns, header stacks vertically
- Animations: fade-in for messages, slide-in for modals + toasts,
  `prefers-reduced-motion` respected
- Toast system (top-right, auto-dismiss)
- Global error overlay — `window.error` + `unhandledrejection` +
  Preact ErrorBoundary all funnel into a full-screen card with
  copy-details + "Report on GitHub" prefilled-issue button

**Editor drawer (post-v0.15)**
- Click any path in chat tool cards → CodeMirror 6 drawer slides
  in from the right (50% width, full-screen on mobile)
- Multi-tab, dirty flag, Cmd/Ctrl+S save, syntax highlighting in
  14 languages, gitignore-aware file picker
- Drawer state persists across sidebar tab switches

**Live status bar (in Chat)**
- model · ctx token gauge · cache hit % · turn cost · session
  cost · DeepSeek balance — 2.5s poll, mirrors TUI StatsPanel

**Live mode pickers (in Chat)**
- Edit mode (review/auto/yolo) — instant
- Effort (high/max) — applies next turn, also flippable from `/effort`
- Preset (auto/flash/pro) — applies next turn via `applyPresetLive`
- New / Clear conversation buttons (route through `/new` and `/clear`)

### Preset rework — auto / flash / pro

**Headline:** old `fast / smart / max` collapsed into model-commitment
vocabulary that actually says what it does.

- **`auto`** — flash baseline, auto-escalates to pro on
  `<<>>` markers or after 3+ tool failure signals.
  The default — covers ~96% of turns at flash cost.
- **`flash`** — flash always. No auto-escalation. `/pro` still
  works for one-shot manual escalation.
- **`pro`** — pro always. No downgrade. ~3× flash at the 5/31
  discount window, ~12× outside it.

`autoEscalate: boolean` added to the loop (constructor + reconfigure)
gates both auto-escalation paths (NEEDS_PRO marker scavenge +
failure-count threshold). `flash` and `pro` presets pass `false`,
locking the running session to one model.

Legacy `fast / smart / max` names: still parse from existing
config files but collapse to `auto` — simpler than mapping the old
semantics onto the new vocabulary, user re-picks if they want flash
or pro explicitly.

`applyPresetLive` callback in `DashboardContext` flips the live
loop's model + autoEscalate + reasoningEffort the moment the user
clicks a preset in the web Chat picker — no session restart.

### Other

- `cacheSavingsUsd(model, hitTokens)` in `src/telemetry.ts` — USD
  the prompt cache shaved off the bill (miss-price minus hit-price
  for cached tokens). Surfaced in `reasonix stats` dashboard +
  `/api/usage` rolled buckets + the Usage chart.
- Built-in shell allowlist (`BUILTIN_ALLOWLIST`) re-exported for
  the dashboard's Permissions panel listing.
- `removeProjectShellAllowed` + `clearProjectShellAllowed` in
  `src/config.ts`.
- StreamableHttpTransport (MCP 2025-03-26) — already shipped in
  0.11.3 but documented here for completeness; this release adds
  the Mcp panel UI on top.
- `DashboardEvent` + `ActiveModal` types exported from
  `src/server/context.ts` for downstream tooling.

### Tests

1568 vitest tests pass. New test files: `tests/server-dashboard.test.ts`
(40 tests covering auth/CSRF, every endpoint shape, SSE round-trip,
mid-modal mutations).

## [0.11.3] — 2026-04-27

**Headline:** Two long-deferred items land — `/permissions` makes the
shell allowlist auditable and editable from inside the TUI, and
Streamable HTTP MCP transport (2025-03-26 spec) clears the last debt
from the v0.3 deferred queue.

### Added

- **`/permissions`** — list / add / remove / clear the shell
  allowlist without leaving the session. Bare `/permissions` shows
  the current edit mode (review / auto / yolo with a yolo-bypasses-
  allowlist banner), the per-project entries with 1-based indices,
  and the read-only builtin list grouped by leading verb. Subcommands:
  `/permissions add ` (multi-token OK), `/permissions remove
  ` (literal match or list index), `/permissions clear
  confirm`. Refuses to add a prefix that's already in the builtin
  list (no redundant project entry) and refuses to remove a builtin
  (read-only). Mutating subcommands require code mode. `perms`
  registered as alias.
- **`removeProjectShellAllowed` + `clearProjectShellAllowed`**
  exported from `src/config.ts`. The remove helper does literal-
  prefix match (not prefix-of-prefix), so dropping `git` doesn't
  accidentally remove `git push origin main` if both were stored.
- **MCP Streamable HTTP transport (2025-03-26 spec)** —
  `src/mcp/streamable-http.ts` implements the new single-endpoint
  protocol. POSTs JSON-RPC frames, handles all three response shapes
  (202 Accepted for notifications, `application/json` for single
  responses, `text/event-stream` for multi-frame streams covering
  progress + response). Captures `Mcp-Session-Id` from the first
  response that hands one out and echoes it on every subsequent
  request; surfaces 404-with-session as a "session expired" error
  so callers know to reinitialize. Long-lived GET stream for
  unsolicited server-initiated frames is deliberately deferred —
  POST-only handles full request/response/notification traffic
  for every server we'd realistically point at today.
- **Spec parser** — `streamable+http(s)://` prefix routes to the
  new transport (`{ transport: "streamable-http", url, name }`).
  Plain `http(s)://` still routes to SSE (2024-11-05) so existing
  `--mcp` config entries keep working without surprise upgrades.
  Wired through `chat.tsx`, `run.ts`, and `reasonix mcp inspect`.
  Public API gains `StreamableHttpTransport` + the
  `StreamableHttpMcpSpec` type re-export.

### Tests

- `tests/permissions-slash.test.ts` — 16 tests covering listing,
  add, remove (by prefix and by 1-based index), clear, mode banner,
  builtin-collision rejection, codeRoot guard, alias.
- `tests/config.test.ts` — 6 new tests for `removeProjectShellAllowed`
  / `clearProjectShellAllowed` (literal-only matching, scoping per
  project, idempotent counts).
- `tests/mcp-streamable-http.test.ts` — 8 tests against an in-process
  `http.Server` fake that speaks the 2025-03-26 wire shape: JSON
  response delivery, 202 ack as no-op, session-id capture+echo,
  multi-frame SSE ordering (progress → response), full McpClient
  initialize → tools/list round-trip, 404+session = "expired",
  500-as-error from `send()`, `close()` unblocks idle iterators.
- `tests/mcp-spec.test.ts` — 4 tests for the new prefix parsing.

1521 tests pass (+24). Lint / typecheck / build clean.

## [0.11.2] — 2026-04-27

**Headline:** `/init` synthesizes a baseline REASONIX.md so a new
project starts with context instead of cold. Closes the gap with
Claude Code's `/init`, scoped to the structure REASONIX expects.

### Added

- **`/init`** — model-driven REASONIX.md generator. The slash
  emits a structured user-turn prompt (via the `resubmit` channel)
  that hard-constrains the model to a fact-only document with
  Stack / Layout / Commands / Conventions / Watch out for sections,
  capped at 80 lines / 3KB so REASONIX.md doesn't bloat the system
  prompt every launch. Reuses the existing filesystem tools (no new
  pipeline) and the result lands as a pending edit in the normal
  review queue, so the user audits before it hits disk. Refuses to
  overwrite an existing REASONIX.md without `/init force`. Removes
  the friction of having to hand-author a project memory file.

## [0.11.1] — 2026-04-27

**Headline:** Workspace-switching, end to end. Four real-use bugs
that all hit the same scenario — `Esc` poisoned the next turn,
Chinese-Windows shell errors came back as mojibake, the markdown
renderer ate `\TEST` out of `F:\TEST1`, and the model had no idea
how to change directories. Plus two new ways to do it: `/cwd `
the user types, and `change_workspace` the model calls (always
gated on an explicit confirmation modal — no auto-switching).

### Fixed

- **`Esc` poisoned the next turn.** The loop's user-Esc abort branch
  processed the cancel correctly but left `_turnAbort` in an aborted
  state on its way out. The carry-abort logic at `step()` entry then
  re-aborted at iter 0 on every subsequent turn, so the user typed
  a fresh prompt and saw "stopped without producing a summary"
  before any model call ran. The session was effectively dead until
  restart. Fix: reset `_turnAbort` to a fresh controller before
  returning from the abort branch — the across-turn race that the
  carry logic guards against still works because a new `abort()`
  fired between turns aborts the new controller. Regression test
  added (`tests/loop.test.ts`).
- **Mojibake on Chinese / Japanese / Korean Windows shell errors.**
  `runCommand` decoded child output as UTF-8 incrementally per
  chunk. Two failure modes:
  1. `cmd.exe`'s OWN error messages (e.g. "'sed' is not recognized
     as an internal or external command") come from a localized
     resource DLL and ignore `chcp 65001`, so on Chinese Windows
     the bytes are CP936/GBK and decoded as UTF-8 produced
     unreadable garbage.
  2. Multi-byte sequences could split across chunk boundaries and
     corrupt before the second half arrived.
  Fix: collect raw `Buffer[]` chunks and decode once at close via
  a new `smartDecodeOutput` — strict UTF-8 first; on Windows fall
  back to GB18030 (GBK superset) when UTF-8 rejects the bytes;
  last resort lossy UTF-8 keeps the structural exit-code marker
  intact. PowerShell's existing `injectPowerShellUtf8` prelude
  still covers the PS path; this fixes the path where the model
  invokes a native EXE directly (`run_command sed …`).
- **Markdown renderer ate `\TEST` out of `F:\TEST1`.** `stripMath`'s
  catch-all LaTeX command stripper (`\\[a-zA-Z]+` → `""`) deleted any
  backslash-followed-by-letters sequence — fine for an invented
  `\textbf{…}` the model emitted, catastrophic for Windows paths in
  prose. `F:\TEST1` rendered as `F:1`. Fix: gate the entire
  `stripMath` pipeline on a math-marker pre-check (`$`, `\(`, `\[`,
  known LaTeX commands, `^{…}`/`_{…}`, Pandoc super/subscripts). When
  none are present we return the string untouched. Mixed inputs (a
  path AND real math in the same message) still run the pipeline —
  math correctness wins over path preservation in that rare collision.
- **Model didn't know `/cwd` existed.** When asked to switch to a
  project on another drive, the model fumbled with `pwd`,
  `cd /d F:\TEST1`, and `2>&1` shell tricks (none of which work —
  `cd` doesn't carry across `run_command` calls and `2>&1` is rejected
  as a shell operator by design). The code-mode system prompt now has
  a "When the user wants to switch project / working directory"
  section telling the model to surface `/cwd ` once and stop,
  instead of trying to do it itself.

### Added

- **`change_workspace` tool** — model-callable workspace switching,
  gated on a confirmation modal. The tool fn validates the target,
  resolves it (absolute / `~`-expanded / relative-to-launch-cwd), then
  always throws a `WorkspaceConfirmationError` with the absolute
  path. App.tsx detects the marker and mounts a Switch / Deny modal;
  on approval it calls the same `applyCwdChange` path that drives
  `/cwd` (re-registers filesystem / shell / memory tools, reloads
  hooks, syncs the loop's hookCwd). On denial the model gets a
  synthetic "user refused, continue without it" message. No
  "always allow" option — workspace switches are per-target by
  nature. The code-mode system prompt now tells the model to call
  this tool (rather than fumble with `cd /d`) when the user asks
  to change projects, and to STOP after the call instead of chaining
  more tools before the user has confirmed.
- **`/cwd `** — switch the session's working directory mid-
  session. Validates the target (must exist, must be a directory),
  expands `~`, then atomically: updates the hook cwd, memory root,
  project shell allowlist, `@file` mention root, and re-registers
  filesystem / shell / memory / `run_skill` tools against the new
  path so file reads, edits, and shell commands all land in the
  new sandbox. MCP servers stay anchored to the original cwd
  (their stdio child was spawned with the launch root and there's
  no standard reconnect handshake) — the slash output flags this
  explicitly when MCP servers are present. The system prompt's
  gitignore-aware project tour is also frozen at launch so the
  prefix cache stays valid; the slash output notes it for users
  switching to a structurally different project.

## [0.11.0] — 2026-04-27

**Headline:** Local semantic search lands as an opt-in pillar — Ollama-
backed embedding index, `reasonix index` CLI with progress spinner, a
`/semantic` slash for status, and bilingual (zh/en) prompts. Plus a
trio of subagent abort races that made `Esc` silently fail to stop a
running subagent.

### Added — Pillar 5: local semantic search

- **`reasonix index`** — new CLI command that walks the project, line-
  windows source files, embeds via Ollama (`nomic-embed-text` by
  default, ~274 MB once), and persists a JSONL index at
  `.reasonix/semantic/`. Incremental by default (mtime-based), with
  `--rebuild` for a full wipe. Per-chunk failures are logged + skipped
  so one bad file doesn't kill a 30-minute build.
- **Preflight prompts** — detects missing Ollama binary / daemon /
  model and offers to start `ollama serve` or `ollama pull `
  with `[Y/n]` confirms. `--yes` for scripts. Non-TTY exits cleanly
  with a remediation hint.
- **TTY progress spinner** — Braille `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏` ticks every
  120ms via `setInterval`, INDEPENDENT of progress events. Builds
  that take 30+ seconds never look hung. Non-TTY mode keeps phase
  lines + heartbeats for parseable CI logs.
- **Chunker safety** — `maxChunkChars` cap (default 4000 ≈ 1000
  tokens) with line-boundary splitting for oversized windows and
  hard-truncation for single overlong lines. Fixes Ollama 500 "the
  input length exceeds the context length" on minified / dense files.
- **`semantic_search` tool** — registered in `reasonix code` only when
  an index exists. Tool description is now directive ("FIRST CHOICE
  for descriptive queries"); the code-mode system prompt grows a
  `# Search routing` fragment when the tool is registered, telling
  the model to prefer semantic_search for intent-style questions
  and fall back to grep for exact tokens.
- **`/semantic` slash** — shows status (built? Ollama installed?
  daemon up?) plus how-to-enable hints. Fire-and-forget pattern, same
  as `/kill` — sync placeholder, async post via `ctx.postInfo`.
- **Bilingual UI** — `src/index/semantic/i18n.ts` with EN/ZH dicts
  for every preflight + `/semantic` + progress label. Locale
  detection: `REASONIX_LANG` override → `LANG`/`LC_ALL`/`LC_MESSAGES`
  (Unix) → `Intl.DateTimeFormat` (Windows fallback) → `en`. Tool
  descriptions and CLI `--help` stay English on purpose (model-facing
  text aligns with training distribution; commander's --help is
  registered once at boot).
- **Startup is silent** — no auto-prompt on `reasonix code` launch.
  If an index exists, the tool registers; otherwise the bootstrap
  is a no-op. Discovery happens via `/semantic` when the user is
  curious, or via the explicit `reasonix index` command.

### Fixed — subagent `Esc` abort races

- **`addEventListener("abort", …)` doesn't replay aborts** — DOM
  semantics: an already-aborted signal won't fire the abort event
  again, so a parent that aborted before `spawnSubagent` attached
  its listener silently lost the cancel. Sync-check `.aborted` at
  attach and forward immediately to `childLoop.abort()`.
- **`step()` was overwriting aborted state** — at the top of
  `step()` we reassign `_turnAbort = new AbortController()`. If
  `loop.abort()` had been called BEFORE `step()` ran, the prior
  aborted controller was discarded and the fresh one started clean.
  Carry the aborted bit forward so the iter-0 check still bails.
- **`forcedSummary` was treated as success** — when the loop aborted
  it yielded a synthetic `assistant_final` with `forcedSummary: true`
  and content `"[aborted by user (Esc) — no summary produced.]"`.
  The subagent stuffed that into `final` and returned `success: true`,
  so `/skill` cheerfully reported "subagent finished" with the abort
  message as the answer. Now `forcedSummary` routes to `errorMessage`
  → `success: false` → caller renders the error.

### Added — docs / website

- **GitHub Pages site under `docs/`** — bilingual landing page (auto-
  detect via `navigator.language`, manual EN/中文 toggle, persisted
  per-browser), brand-gradient dark theme, hero terminal animation
  that mirrors the real TUI rendering primitives (◇/◆ role glyphs,
  yellow tool pills, rounded cyan EditBlockRow with `- old` red /
  `+ new` green diff lines, info-row pending/applied status).
- **`README.zh-CN.md`** — full Chinese mirror of `README.md`. Both
  READMEs now carry a language switcher header at the top.

### Tests (+27, 1441 → 1468)

- `tests/semantic-chunker.test.ts` — line-window splitting, overlap,
  forward-slash path normalization, NUL-byte sniff; the new
  `chunkText` cap behavior (multi-line split + hard-truncate-overlong-
  line + idempotent passthrough).
- `tests/semantic-store.test.ts` — JSONL roundtrip, cosine ranking,
  minScore threshold, dim-mismatch refusal, model-mismatch refusal,
  remove + wipe, fileMtimes.
- `tests/semantic-embed-tolerant.test.ts` — `embedAll` returns
  `Array` on per-chunk error (mocked Ollama 500),
  abort still throws globally, all-fail surface, progress fires once
  per chunk regardless of outcome.
- `tests/semantic-i18n.test.ts` — locale detection precedence,
  override env var, placeholder substitution, ZH dict.
- `tests/semantic-bootstrap.test.ts` — registers when index exists,
  silent skip otherwise (no startup prompt).
- `tests/semantic-slash.test.ts` — `/semantic` status renderer,
  enabled / not-built / Chinese-locale paths.
- `tests/semantic-launcher.test.ts` — `findOllamaBinary` contract.
- `tests/code-prompt.test.ts` — search-routing fragment is absent by
  default and present + ordered before .gitignore when the flag is on.
- `tests/subagent.test.ts` — regression: parent signal already aborted
  at dispatch time (race we previously dropped on the floor).

### Refactored

- **`src/code/prompt.ts`** — `codeSystemPrompt(rootDir, opts?)` grew
  a `hasSemanticSearch` flag; the routing fragment is appended only
  when the tool is actually registered. Cache prefix stays stable per
  session because the flag is captured at launch.

## [0.6.0] — 2026-04-24

**Headline:** Cost control becomes a first-class pillar. Default flips
flash-first, `v4-pro` is opt-in, tool results auto-compact between
turns, and the TUI grows per-turn cost visibility + a `/pro`
one-shot upgrade. Month-over-month cost on an active coding project
drops ~6–10× in practice.

### ⚠ Breaking (behavior, not API)

- **Default model is now `deepseek-v4-flash`**, not `deepseek-v4-pro`.
  `reasonix code`, `reasonix chat`, and subagents all land on flash
  by default. Users who need the frontier tier:
  `/preset max`, `/pro`, or `--model deepseek-v4-pro` on CLI.
- **Preset defaults changed**. None of the three presets auto-enable
  `branch` or `harvest` anymore — both were hidden multipliers. The
  new matrix:
  | preset | model | effort | harvest | branch |
  |---|---|---|---|---|
  | fast | v4-flash | high | off | 1 |
  | smart (default) | v4-flash | max | off | 1 |
  | max | v4-pro | max | off | 1 |
  Users who want branching still get it via `/branch N`; users who
  want harvest still get it via `/harvest on`. Neither is implicit.
- **Default preset is now `smart` (was `fast`).** Flash + full
  thinking budget is the best price/quality point for coding.
- **`deepseek-chat` / `deepseek-reasoner` aliases scheduled for
  removal.** Still accepted (they map to flash non-thinking /
  thinking), but every user-facing surface (`/models`, setup wizard,
  `--help`) now advertises `v4-flash` / `v4-pro` only.

### Added — Cost control (Pillar 4)

- **`/pro` single-turn arming** — queue v4-pro for just the next
  turn; auto-disarms after. Separate from `/preset max` (persistent)
  so "this one task is hard" doesn't require a preset round-trip.
  Status bar shows `⇧ pro armed` in yellow while queued, `⇧ pro
  escalated` in red while the turn is actually running on pro.
- **Failure-triggered auto-escalation** — the loop tracks
  `edit_file` SEARCH-not-found errors + ToolCallRepair fires per
  turn. 3+ signals flip the rest of the turn to `v4-pro` with a
  visible warning row. Counter resets at every turn start. No
  silent cost surprises.
- **Model self-report escalation (`<<>>`)** — system
  prompt teaches the model that when a task CLEARLY exceeds flash's
  capability (complex architecture, subtle correctness, genuine
  design trade-offs), emit the marker as the first line of its
  response. The loop aborts that call, retries this turn on pro,
  one shot. Guarded against infinite retry (pro never self-
  escalates) and streaming output is buffered so the marker never
  flickers on-screen before the retry fires.
- **Turn-end auto-compaction** — every tool result over 3000 tokens
  gets shrunk to a cap at turn end. Biggest win for long sessions:
  a 12KB `read_file` output stops re-paying its cost on every
  future prompt. The proactive in-turn threshold also dropped from
  60% → 40% so the reactive 80% path rarely fires.
- **Forced-summary + truncation-repair auxiliary calls hard-route to
  flash+effort=high** regardless of the main-turn tier. No reason to
  pay pro rates for "paraphrase these tool results into prose" or
  "close this truncated JSON."
- **Subagent default flipped to `v4-flash` + `effort=high`**. Skill
  frontmatter `model:` / `effort:` remain the per-skill override.
- **StatsPanel cost badges** — per-turn cost alongside session total.
  Colored thresholds: turn green under $0.05, yellow $0.05–0.20,
  red ≥$0.20; session same scale ×10.

### Added — UX

- **Plan body now flows into scrollback**, not inside the modal.
  `submit_plan` pushes a dedicated `role: "plan"` row into the
  Static log (rendered via the full markdown pipeline, never
  truncated); the PlanConfirm modal below shrinks to a tight
  approve/refine/cancel picker. Long plans are fully readable via
  terminal scrollback.
- **Shared prompt fragments** — `TUI_FORMATTING_RULES` and
  `NEGATIVE_CLAIM_RULE` live once in `src/prompt-fragments.ts`,
  embedded into every system prompt (main code, default chat,
  subagent, built-in skills). Three near-identical copies
  collapsed; subagents gain the "don't assert absence without
  checking" guardrail they previously lacked.

### Fixed

- **`run_skill` accepts decorated names.** The Skills index wrote
  entries like `- 🧬 explore`, and models copied the whole thing
  verbatim into `run_skill({name:"🧬 explore"})`. The index now
  uses a trailing `[🧬 subagent]` tag after the name, and
  `run_skill` normalizes inputs by stripping bracketed tags +
  leading emoji before lookup. Handles `"🧬 explore"`,
  `"[🧬 subagent] explore"`, `"explore [🧬 subagent]"`, etc.
- **`edit_file` result no longer shown twice.** The interceptor's
  `applyNow` was pushing an info row, and the loop's tool event
  re-displayed the same text as a proper tool row. Dropped the info
  row push; the tool row alone carries the content.
- **`run_command` / `run_background` descriptions teach their shell
  constraints upfront.** Explicit list of rejected operators
  (`&&`, `||`, `|`, `;`, `>`, `<`, `2>&1`), the `cd` doesn't-persist
  rule, a warning against unbounded-output commands (`netstat -ano`,
  `find /`), and concrete alternatives (`npm --prefix`, `cargo -C`,
  `git -C`). Models stop burning turns rediscovering these via
  error replies.

### Refactored (no behavior change)

- **App.tsx split** from 2931 → ~1980 lines by extracting
  `LiveRows.tsx`, `edit-history.ts`, `useEditHistory.ts`,
  `useCompletionPickers.ts`, `useSessionInfo.ts`, and
  `useSubagent.ts`. Every hook under 310 lines.
- **slash.ts split** from 1786 → 20-line barrel. Types,
  SLASH_COMMANDS data + parse helpers, shared utility helpers, a
  handler registry (`dispatch.ts`), and 10 per-topic handler files
  all under `src/cli/ui/slash/`. Adding a command now means editing
  one handler file + one registry line.

### Docs

- **`docs/ARCHITECTURE.md` rewritten** for v0.6. The four pillars,
  current module layout (slash + handlers + hooks all reflected),
  design-evolution timeline replacing the stale roadmap,
  non-goals updated to call out "automatic cost escalation without
  user-visible announcement" as explicitly rejected.

## [0.5.24] — 2026-04-24

**Headline:** `reasonix code` gets a proper review gate, background
process support, and aggressive context hygiene so long coding
sessions stop bleeding money.

### Added

- **Edit-gate modes (review / auto)** — `edit_file` and `write_file`
  tool calls now route through a user gate. `review` (default) pops
  an `EditConfirm` modal with a scrollable diff + `y/n/a/A/Esc`
  keys; `auto` applies immediately and arms a 5-second undo banner.
  `Shift+Tab` cycles, `/mode` sets explicitly. Persisted to
  `~/.reasonix/config.json`.
- **Session edit history** — every applied batch lands in an
  in-memory ring. `/history` lists them, `/show [id] [path]` dumps
  a stored diff (per-file when path given), `/undo [id] [path]`
  rolls back at any granularity (latest batch, specific batch,
  single file inside a batch). `u` keybind reaches back past the
  5-second banner as long as history has a non-undone entry.
- **Background processes** — new `run_background` / `job_output` /
  `stop_job` / `list_jobs` tools for dev servers and watchers. Spawn
  returns after a ready-signal match (`listening on`, `Local:`,
  `compiled successfully`, …) or `waitSec` seconds. `/jobs` /
  `/kill ` / `/logs ` surface them to the user. Cleanup on
  SIGINT / SIGTERM / exit kills every child.
- **Per-edit review modal (`src/cli/ui/EditConfirm.tsx`)** — diff
  viewport sized to terminal rows; `↑↓/j/k/Space/PgUp/PgDn/g/G`
  scroll a big diff in place. `a` applies rest of turn, `A` flips
  to AUTO for the session.
- **Bottom mode status bar** — always-visible line above the prompt
  shows mode / pending count / Shift+Tab hint / running-jobs tag;
  flashes on mode change.
- **Onboarding tip** — first `reasonix code` launch after upgrade
  posts the edit-gate keybindings once; suppressed after via the
  `editModeHintShown` flag.

### Changed

- **`read_file`** — adds `range:"A-B"` param (1-indexed, inclusive).
  Files longer than 200 lines with no scope return an auto-preview
  (head 80 + tail 40 + "N lines omitted" marker) instead of dumping
  everything. One `read_file` used to burn 6.5K tokens on a fat
  file; scoped reads cut that 3-5×.
- **`directory_tree`** — default `maxDepth` 4 → 2; skips
  `node_modules`, `.git`, `dist`, `build`, `out`, `.next`, `.nuxt`,
  `target`, `.venv`, `venv`, `__pycache__`, `.pytest_cache`,
  `.mypy_cache`, `.cache`, `coverage` unless `include_deps:true`;
  collapses any directory past 50 children with a nudge toward
  `list_directory`.
- **Auto-compact tool-call args** — after every `tool` response, the
  loop shrinks that call's `arguments` JSON if it exceeds 800
  tokens. Paths and short fields stay verbatim; long SEARCH /
  REPLACE / content strings get replaced with a `[…shrunk: N chars,
  M lines — tool already responded, see result]` marker. Cuts
  stale-args drag across every subsequent turn.
- **`/compact`** — now covers both tool results (existing) and
  tool-call args (new) in one pass.
- **`reasoningEffort` persistence** — `/effort high` now writes the
  choice to `~/.reasonix/config.json` and the loop picks it up at
  launch. Earlier versions silently reverted to `max` every relaunch.
- **Prompt scope discipline** — code-mode prompt tells the model to
  stop after "run / start / launch" tasks instead of proactively
  refactoring, running tsc, or chasing unused imports.

### Fixed

- **`run_background` confirmation path** — TUI now pops the shell
  confirm modal for `run_background` (not just `run_command`). A
  `kind` field on `pendingShell` routes approval to
  `JobRegistry.start()` so approving doesn't synchronously block on
  a dev server that never exits.
- **`/kill` actually kills the tree** — Windows `taskkill /T /F`,
  POSIX `process.kill(-pid, …)` on a detached child. Earlier
  `SIGTERM` only killed the `npm.cmd` wrapper; `node → vite →
  esbuild` survived. `/kill` also posts a late "job N exit M" row
  when the stop resolves, so the user doesn't have to poll `/jobs`.

## [0.4.24] — 2026-04-22

**Headline:** `reasonix stats` is now a cross-session cost dashboard.

Every turn `reasonix chat|code|run` executes now appends one line to
`~/.reasonix/usage.jsonl` carrying tokens + cost + the equivalent
Claude Sonnet 4.6 cost. `reasonix stats` (no arg) rolls that log up
into today / week / month / all-time windows:

```
Reasonix usage — /Users/you/.reasonix/usage.jsonl (2.3 KB)

            turns  cache hit    cost (USD)      vs Claude     saved
----------------------------------------------------------------------
today           8      95.1%     $0.004821        $0.1348      96.4%
week           34      93.8%     $0.023104        $0.6081      96.2%
month         127      94.2%     $0.081530        $2.1452      96.2%
all-time      342      94.0%     $0.210881        $5.8934      96.4%

most used model:   deepseek-reasoner (84% of turns)
top session:       default (214 turns)
tracked since:     2026-04-20
```

Pillar 1's pitch (94–97% cost reduction vs Claude) turns from a
blog number into a fact users can check on their own machine. The
savings column is derived per turn (not synthesized) from the
existing `claudeEquivalentCost()` helper in `src/telemetry.ts`.

Back-compat: `reasonix stats ` still works — passing a
path falls back to the old per-file summary (assistant turns + tool
calls). No arg → dashboard.

Privacy: the log contains tokens + costs + the user-chosen session
name, nothing else. No prompts, no completions, no tool args.

### Added

- **`/stats` slash** — same dashboard, in-session. Reads
  `~/.reasonix/usage.jsonl` and renders via the shared
  `renderDashboard` pure function, so the shell command and the
  slash stay in sync by construction.
- **`src/usage.ts`** — `appendUsage` (best-effort JSONL write,
  swallows disk failures so a read-only `~/` never breaks the
  turn), `readUsageLog` (malformed-line tolerant), `aggregateUsage`
  (rolling windows: 24h / 7d / 30d / all, plus model + session
  histograms), `bucketCacheHitRatio`, `bucketSavingsFraction`,
  `formatLogSize`.
- **Wire-up** in `src/cli/ui/App.tsx` (assistant_final event) and
  `src/cli/commands/run.ts` (CI / scripting turns land in the same
  log as TUI turns).
- **Upgraded `reasonix stats`**. No-arg → dashboard; transcript arg
  → legacy per-file summary. `renderDashboard(agg, path)` is an
  exported pure function so tests can assert the string output.

### Tests (+15, suite 708 → 723)

- `tests/usage.test.ts` covers: appendUsage round-trip, empty
  log / malformed-line tolerance / parent-dir auto-creation / silent
  write-failure (points path at a regular file), aggregateUsage
  (empty, rolling-window bucketing, cross-record sums, byModel +
  bySession sort + (ephemeral) grouping), bucket helpers with zero
  denominators, renderDashboard (row labels + em-dash fallback).

---

## [0.4.23] — 2026-04-22

**Headline:** Hooks — user-defined automation that fires at four
well-known points in the loop. Same two-scope layout (project +
global) as memory and skills.

A hook is a shell command. Reasonix invokes it with stdin = a JSON
envelope describing the event. The exit code drives the decision:
`0` = pass, `2` = block (only on `PreToolUse` / `UserPromptSubmit`),
anything else = warn (rendered inline as a yellow row, the loop
keeps going). Block on a tool event swaps the dispatch for a
synthetic tool result carrying the hook's stderr — the model sees
a structured refusal instead of a silent omission, and can
reason about what to do next.

Settings file:

```json
// /.reasonix/settings.json   ← committable
// ~/.reasonix/settings.json           ← per-user
{
  "hooks": {
    "PreToolUse":       [{ "match": "edit_file|write_file", "command": "bun scripts/guard.ts" }],
    "PostToolUse":      [{ "match": "edit_file", "command": "biome format --write" }],
    "UserPromptSubmit": [{ "command": "echo $(date +%s) >> ~/.reasonix/prompts.log" }],
    "Stop":             [{ "command": "bun test --run", "timeout": 60000 }]
  }
}
```

Project hooks fire before global hooks. `match` is anchored regex
on the tool name (`*` or omitted = match every tool); ignored for
prompt / Stop events. Per-hook `timeout` overrides the defaults
(5s for blocking events, 30s for logging events). The CLI loads
both files at App mount; `/hooks` lists what's active and
`/hooks reload` re-reads disk without tearing down the running
loop (so the append-only log is preserved).

Deliberate non-goals for v1: workflow DSL, conditional chaining,
hook templates. Hooks are shell commands — the user already has
a programming language, we don't need to invent one.

### Added

- **`src/hooks.ts`** — `loadHooks` (project + global merge),
  `runHooks` (event filter + stdin JSON + spawn dispatch),
  `decideOutcome` (pure exit-code → decision matrix), `matchesTool`
  (anchored-regex name filter), `formatHookOutcomeMessage` (single
  source of truth for the warning row text). Spawner is injectable
  for tests; default uses `shell: true` so `&&`, pipes, env
  expansion all behave the way they do in the user's terminal.
- **`CacheFirstLoopOptions.hooks` + `hookCwd`**. Loop dispatches
  `PreToolUse` (around line 866 in `src/loop.ts`) and `PostToolUse`
  (immediately after dispatch). `loop.hooks` is mutable so
  `/hooks reload` can swap the list without rebuilding the loop.
- **App-level `UserPromptSubmit` + `Stop`**. `App.tsx` calls
  `runHooks` before pushing the user message (block = drop the
  prompt) and after `loop.step` resolves (warnings only, since the
  turn already ended).
- **`/hooks` slash command**. `list` (default) groups loaded hooks
  by event with scope tags; `reload` re-reads settings.json from
  disk via the App-provided `reloadHooks` callback.
- **`/update` slash command**. Shows current vs the last-resolved
  latest (piggybacks on App.tsx's mount-time background check) and
  prints the exact shell command to upgrade. Deliberately does NOT
  spawn `npm install` from inside the TUI — stdio:inherit into a
  running Ink renderer corrupts the display, and on Windows the
  currently-running binary can be locked. Users exit the session
  and run `reasonix update` in a fresh shell.

### Tests (+36, suite 672 → 708)

- `tests/hooks.test.ts` — `loadHooks` (empty / project+global / array
  order / ignore malformed entries / tolerate malformed JSON / no
  project root → global only / path helpers), `matchesTool` (`*` /
  anchored regex / substring rejected / malformed regex falls back
  to no-match / non-tool events ignore match), `decideOutcome`
  (exit 0 / exit 2 / non-zero / timeout / spawn error per event),
  `runHooks` (filters by event+match before running, stops at first
  block, doesn't stop on warn, stdin envelope shape, cwd routing,
  default timeouts, per-hook timeout override), `formatHookOutcomeMessage`
  (pass → empty / non-pass includes scope+command+detail / 60-char
  truncation).
- `tests/loop-hooks.test.ts` — `CacheFirstLoop` accepts a hook list,
  default empty, `loop.hooks` is mutable, `hookCwd` defaults to
  `process.cwd()` and honors override, no-tool turn doesn't fire
  PreToolUse hooks.
- `tests/slash.test.ts` — updated `suggestSlashCommands("h")` to
  include the new `hooks` command; added 4 tests for `/update`
  (pending / up-to-date / upgrade-available / suggest-surfaces-it).

---

## [0.4.22] — 2026-04-22

**Headline:** Version display in the TUI header + `reasonix update`
self-upgrade command.

Two small quality-of-life additions. The stats panel now carries the
running version (`Reasonix v0.4.22 · model …`) so users can tell at
a glance whether they're on the latest build; a 24-hour background
check against the npm registry quietly surfaces a yellow
`update: X.Y.Z` nudge on the right side of the same row when a
newer version has been published. The nudge never blocks startup —
the fetch is bounded at 2s with a 24h on-disk cache, and any
failure (offline, firewall, registry hiccup) is silent by design.

`reasonix update` is the command form: detects whether you're
running a global install vs an ephemeral `npx` spawn, and either
spawns `npm install -g reasonix@latest` for the former or prints a
cache-refresh hint for the latter. `--dry-run` prints the plan
without executing.

The `VERSION` constant now sources from `package.json` at runtime
(walking up from `import.meta.url`) instead of a hand-maintained
literal, so it can never drift again — it was stale at `0.4.20`
before this release. Tests assert they stay in sync.

### Added

- **`src/version.ts`** — exports `VERSION`, `compareVersions`,
  `getLatestVersion`, `isNpxInstall`, and the
  `LATEST_CACHE_TTL_MS` / `LATEST_FETCH_TIMEOUT_MS` constants.
  `getLatestVersion` caches to `~/.reasonix/version-cache.json`
  (24h TTL) and returns `null` on any failure.
- **`reasonix update`** subcommand (`src/cli/commands/update.ts`).
  `planUpdate()` is the pure decision function, `updateCommand()`
  is the CLI orchestrator with test seams (`fetchLatest`, `isNpx`,
  `spawnInstall`, `write`, `exit`).
- **StatsPanel header shows `v${VERSION}`** inline, plus an
  `update: X` badge (yellow, bold) on the right when
  `updateAvailable` is passed. App.tsx fires the registry check
  in a background `useEffect` on mount; only a version strictly
  newer than the running one flips the state.

### Fixed

- **Drifted `VERSION` constant.** `src/index.ts` hard-coded
  `"0.4.20"` while `package.json` was on `0.4.21`. Replaced with a
  re-export from `src/version.ts`, which reads the manifest on
  first access. A regression test pins them together.

### Tests (+19, suite 588 → 607)

- `tests/version.test.ts` — `VERSION === package.json.version`,
  `compareVersions` covers numeric + pre-release ordering,
  `isNpxInstall` covers the three detection paths,
  `getLatestVersion` covers cache hit / force-refresh / expired
  entry / network failure / bad body / cache-write failure.
- `tests/update-command.test.ts` — `planUpdate` returns the
  correct action for all four decision quadrants; `updateCommand`
  respects every seam: no-spawn on up-to-date, no-spawn on npx,
  spawns on global-behind-latest, honors `--dry-run`, exits
  non-zero on registry failure, surfaces npm's non-zero exit.

---

## [0.4.21] — 2026-04-22

**Headline:** Skills — user-authored prompt packs, two-scope layout
matching user-memory.

Reasonix discovers skills under `/.reasonix/skills/` (project
scope) and `~/.reasonix/skills/` (global scope). Project wins on name
collisions — per-repo overrides of a global skill work the way users
expect. Deliberately NOT tied to any other tool's directory
convention (`.claude/`, `.glm/`, etc.): Reasonix is model-agnostic at
the conversation layer, so coupling the skill filesystem to one
vendor would break anyone running a different backend.

The pinned index (names + one-line descriptions) lives in the
immutable system prefix; bodies stay lazy and enter the append-only
log only when invoked — either by the model calling the new
`run_skill` tool or by the user typing `/skill  [args]`. No
DAG engine, no workflow DSL — the model reads the skill's prose and
continues the normal tool-use loop from there. Pillar 1's cache
invariants are preserved: adding skills grows the pinned index
(under a 4k char cap, with a truncation marker) but never alters
the rest of the prefix.

### Added

- **`src/skills.ts`** — `SkillStore` with `SkillScope` of `"project"`
  or `"global"`, both layouts recognized (`{name}/SKILL.md` and flat
  `{name}.md`). `applySkillsIndex` composer is pinned into
  `applyMemoryStack` alongside REASONIX.md + user memory, receiving
  the same `rootDir` so the project scope picks up
  `/.reasonix/skills/`.
- **`run_skill` tool** (`src/tools/skills.ts`) — read-only, returns
  the full markdown body plus an optional forwarded `Arguments:` line.
  Registered in `reasonix chat` (global only) and `reasonix code`
  (project + global).
- **`/skill` slash command** — `list` / `show ` / bare
  ` [args]` form. The bare form injects the skill body as a
  user turn via the same `resubmit` hook `/apply-plan` uses. Reads
  project scope from `ctx.codeRoot`, mirroring how `/memory` behaves.

### Notes

- Each skill's `allowed-tools` frontmatter is parsed but **ignored**
  in v1. Reasonix's tool namespace (`filesystem`, `shell`, `web`)
  doesn't one-to-one map onto other clients' names; the model reads
  the prose instructions and picks our equivalents. Will revisit
  once the tradeoffs are clearer.
- What we explicitly did **not** add: workflow DSL, DAG scheduler,
  parallel branches, sub-agents. Skills are prose; the model does the
  sequencing. This keeps single-loop + append-only + cache-first
  intact — the architectural non-goal "no multi-agent orchestration"
  stands.

### Fixed

- **`ShellConfirm` "always allow" did not take effect until relaunch.**
  The `run_command` tool captured `extraAllowed` as a snapshot at
  registration time, so a prefix the user approved mid-session was
  written to `~/.reasonix/config.json` but the in-memory tool still
  refused it — the next invocation re-triggered the confirmation
  modal. `ShellToolsOptions.extraAllowed` now accepts a getter in
  addition to a static array; `reasonix code` passes
  `() => loadProjectShellAllowed(rootDir)` so the allowlist is
  re-read from disk on every dispatch. Static-array callers keep
  working unchanged.
- **Windows cmd.exe built-ins (`dir`, `echo`, `type`, `ver`, …)
  crashed with ENOENT.** These aren't standalone executables, so
  `PATH × PATHEXT` lookup misses and `spawn dir` fails. `prepareSpawn`
  now routes bare unresolved Windows commands through
  `cmd.exe /d /s /c " "` with verbatim-args + manual
  metacharacter quoting — same wrapping strategy we already use for
  `.cmd`/`.bat` files. Built-ins resolve correctly; genuinely unknown
  commands get the standard "'foo' is not recognized as an internal
  or external command" message instead of a raw spawn error.
  Already-extensioned names (`node.exe`) and paths-with-separators
  (`C:\tool.exe`) still pass through unwrapped so an explicit "I
  know where this is" invocation fails loudly when it's missing.

## [0.4.19] — 2026-04-22

**Headline:** Windows shell hotfix + StormBreaker visibility.
`reasonix code` now runs `npm`, `npx`, `tsc`, `yarn`, `pnpm`, `bun`,
`pytest`, and every other `.cmd` / `.bat` wrapper on Windows — both
under Node 18/20 (broken by missing PATHEXT resolution) and Node
21.7.3+/24 (broken by CVE-2024-27980's prohibition on direct
`.cmd`/`.bat` spawns with `shell: false`). Unix behavior unchanged.
Plus: the StormBreaker anti-loop-detector no longer silently halts
a turn — when it fires it emits a visible warning row explaining
what was suppressed and what the user should do next, and its
sliding window resets on each new user message so a new intent
doesn't inherit the previous turn's repeat patterns.

### Fixed

- **`spawn npm ENOENT` on Windows** — `child_process.spawn` with
  `shell: false` uses `CreateProcess`, which ignores PATHEXT. Bare
  `npm` failed because no `npm.exe` exists — only `npm.cmd`. New
  `resolveExecutable(cmd)` walks `PATH × PATHEXT` manually and
  returns the full resolved path (`C:\Program Files\nodejs\npm.CMD`)
  before handing to spawn. Keeps `shell: false` (no shell expansion
  of piped / chained commands — the whole reason we avoided
  `shell: true` to begin with).
- **`spawn npm EINVAL` on Node ≥ 21.7.3 / 24** — even with the
  resolved `.cmd` path, Node's post-CVE-2024-27980 patch refuses to
  execute `.cmd` / `.bat` files via direct spawn. Second layer:
  `prepareSpawn()` detects a `.cmd` / `.bat` target on Windows and
  rewrites the invocation to `cmd.exe /d /s /c " "`
  with `windowsVerbatimArguments: true`. Each arg is routed through
  `quoteForCmdExe()`, which wraps in double quotes when the arg
  contains whitespace or cmd.exe metacharacters
  (`" & | < > ^ % ( ) , ; !`) and doubles embedded quotes per
  cmd.exe's `""` escape rule. Arguments like `a&b` stay literal;
  they don't become shell operators.

### Added

- **`resolveExecutable(cmd, opts?)`** — exported from `src/tools/shell.ts`.
  Windows PATH × PATHEXT resolver. Opts lets tests inject `platform`,
  `env`, and `isFile` so the Windows-specific path can be exercised
  from a Linux CI runner without touching real fs.
- **`prepareSpawn(argv, opts?)`** — exported. Returns the
  `(bin, args, spawnOverrides)` tuple that runCommand should pass to
  `child_process.spawn`. On non-Windows it's a passthrough; on
  Windows it applies the PATHEXT lookup and the `cmd.exe` wrapping
  when needed. Unit-tested without spawning real processes.
- **`quoteForCmdExe(arg)`** — exported. The per-arg quoting
  function. Round-trip tested against realistic argvs
  (`npm install`, paths with spaces, args containing
  `& | < > ^`, empty strings, embedded double quotes).

- **Silent storm-break**. When `StormBreaker` caught a repeated
  `(tool, args)` pattern it dropped the offending call but emitted
  nothing user-visible beyond a small `[repair] broke 1 storm` note
  on the assistant row. If the suppressed call was the only tool
  call of the turn, the turn just ended — no explanation of why
  nothing happened. Now the loop yields a dedicated `warning` event
  (same channel as Esc-abort and budget warnings) with an
  actionable message, distinguishing "all calls suppressed (stuck
  retry)" from "some calls suppressed" cases.
- **StormBreaker state bleeds across user turns**. The sliding
  window of recent signatures persisted for the lifetime of the
  loop, so a stuck pattern from an earlier intent could false-
  positive against the user's legitimate new "try again with
  different input" request. `CacheFirstLoop.step()` now calls
  `repair.resetStorm()` on every new user turn — the window
  repopulates naturally as the new turn's tool calls fire, and
  genuine repeats still trip after the usual 3-in-a-row pattern.

### Added

- **`ToolCallRepair.resetStorm()`** — exposes StormBreaker.reset
  through the repair facade. Called by the loop at each user turn;
  library consumers that drive `repair.process` manually can use it
  too if they wrap their own turn semantics.

### Tests (+22, suite 566 → 588)

- `tests/shell-tools.test.ts` (+21) — `resolveExecutable` on
  non-Windows (passthrough), PATHEXT walk (first-hit ordering,
  whitespace-tolerant PATHEXT entries), absolute-path / slash /
  already-extensioned passthrough, empty input, missing PATH /
  PATHEXT. `quoteForCmdExe` (simple identifiers unquoted, whitespace
  + metachars quoted, embedded quotes doubled, empty string
  → `""`). `prepareSpawn` (unix passthrough, `.cmd` wraps via
  cmd.exe, `.bat` wraps too, `.exe` direct, metachar args quoted,
  PATHEXT miss falls through).
- `tests/repair/pipeline.test.ts` (+1) — `resetStorm` clears the
  repeat-window so post-reset calls aren't suppressed.
- `tests/loop.test.ts` — the iter-budget warning test refined to
  filter by the iter-specific pattern, since identical fixture
  calls now also trip the (correct) storm warning.

### Internals

- `runCommand` in `src/tools/shell.ts` now calls `prepareSpawn`
  instead of spawning `argv[0]` directly. Every codepath that was
  going through `spawn` still does; the `bin` / `args` /
  `spawnOverrides` it receives are platform-normalized.
- Existing allowlist + `readOnlyCheck` plan-mode gate + timeout /
  output-cap / AbortSignal wiring is untouched.
- `CacheFirstLoop.step()` now resets the StormBreaker at the top of
  each turn AND emits a `warning` event after `repair.process()`
  when `report.stormsBroken > 0`. The existing `repair` field on
  `assistant_final` still carries the count for historical records
  / transcripts.

---

## [0.4.18] — 2026-04-22

**Headline:** Plan Mode — the model can propose a markdown plan
autonomously for large tasks (multi-file refactors, architecture
changes, ambiguous requests), and you can also force a read-only
exploration phase via `/plan`. Picker shows Approve / Refine / Cancel.
Approve pushes a synthetic "implement now" message; Refine keeps the
model exploring; Cancel drops the plan. Designed around Pillar 1 —
tool specs stay pinned, so the cache prefix doesn't break when plan
mode toggles.

### Added

- **`submit_plan` tool** (`src/tools/plan.ts`) — registered by default
  in `reasonix code`. Throws `PlanProposedError` carrying the plan
  text via the new `toToolResult()` protocol on ToolRegistry. Fires
  the picker whether or not plan mode is active — the model is
  expected to propose plans on its own for large tasks; `/plan` is
  the *stronger* constraint that forces the model into read-only.
- **`/plan` slash** (code mode only) — toggles read-only plan mode.
  `/plan on`, `/plan off`, or `/plan` to flip. While on, the registry
  refuses non-read-only dispatch; while off, the model can still
  propose plans autonomously via submit_plan. `/status` surfaces the
  state; `StatsPanel` shows a red `PLAN` tag.
- **`/apply-plan` slash** (code mode only) — force-approve fallback.
  Clears plan mode, clears the pending-plan picker state, and
  resubmits the implement-now synthetic via the existing `resubmit`
  mechanism. Useful when the model wrote the plan in assistant text
  instead of calling submit_plan, or when you want to keyboard-only
  the approval without the picker.
- **`ToolDefinition.readOnly` + `readOnlyCheck`** — declarative gate
  used by `ToolRegistry.dispatch` when plan mode is on. Read tools
  (`read_file`, `list_directory`, `search_files`, `directory_tree`,
  `get_file_info`, `web_search`, `web_fetch`) run normally. Write
  tools bounce with a refusal the model reads and learns from.
  `run_command` uses a dynamic `readOnlyCheck` so allowlisted
  invocations (`git status`, `cargo check`, `npm test`, `grep`, …)
  still work during planning — exploration isn't gated. Non-allowlisted
  commands refuse just like other writes.
- **`ToolRegistry.setPlanMode(on)` / `.planMode`** — the enforcement
  switch + accessor. Mirrored onto the UI's `planMode` React state so
  the StatsPanel badge stays in sync.
- **`toToolResult()` extension protocol** on Error subclasses —
  `ToolRegistry.dispatch` calls it if present when an error is thrown,
  serializing custom fields alongside `error`. Used by
  `PlanProposedError` to ferry the plan text to the UI without
  regex-scraping the error message. Falls back safely on serialization
  failure.
- **`PlanConfirm.tsx`** — 3-option Ink picker (Approve / Refine /
  Cancel) with the plan rendered as **live Markdown** (via the
  existing `Markdown` component — headings, lists, code, bold all
  formatted, not raw text) in a cyan-bordered panel above. 2 400-char
  rendered cap; longer plans get a "use /tool for full" truncation
  marker. Live rows hidden while the picker is up, matching
  `ShellConfirm`'s behavior. When the plan contains headings like
  "Open questions", "Risks", "Assumptions", "待确认", "开放问题", "风险",
  "未知", "假设", "不确定", the picker auto-selects the Refine option
  by default and shows a yellow "▲ the plan has open questions —
  pick Refine to answer them" hint above the options.
- **`PlanRefineInput.tsx`** — inline text input that appears after
  the user picks either **Approve** or **Refine**. Picking Approve
  lets the user type last-minute instructions or answers to the
  model's open questions (blank Enter = approve as-is). Picking
  Refine requires specifics — the input collects them and includes
  them verbatim in the synthetic sent to the model, so "refine"
  actually means "revise with this feedback" instead of the generic
  "try again" message the first cut sent. Esc returns to the picker
  without resuming the loop.
- **System-prompt guidance** (`CODE_SYSTEM_PROMPT`) — teaches the
  model when to call submit_plan autonomously (big / risky / ambiguous
  tasks) vs. just making the change (typos, obvious one-line fixes),
  and how `/plan` mode adds the stronger dispatch gate on top.

### Tests (+24, suite 542→566)

- `tests/plan.test.ts` (+17) — ToolRegistry plan-mode gate
  (default-off, toggle, block non-read-only, allow read-only, honor
  `readOnlyCheck` per-args, precedence over `readOnly`, off-mode
  noop); `toToolResult` protocol (serializes custom fields, falls
  back on serializer failure); `PlanProposedError` carries plan +
  STOP directive; `registerPlanTool` registers submit_plan as
  read-only, fires picker both in and out of plan mode, rejects
  empty plans, trims whitespace.
- `tests/slash.test.ts` (+7) — `/plan` registry entries + required
  commands check; `/plan` toggle / on / off / true / false / 0 / 1;
  `/plan` info text explicit about the stronger-constraint
  relationship; `/apply-plan` code-mode gating; `/apply-plan` flips
  mode + clears pending + resubmits; works without optional
  `clearPendingPlan` callback; `/status` plan-mode line appears
  iff on.

### Internals

- `src/tools/filesystem.ts` — read_file / list_directory /
  directory_tree / search_files / get_file_info tagged readOnly.
- `src/tools/shell.ts` — run_command gets `readOnlyCheck` tied to
  the existing `isAllowed` check + `allowAll` escape hatch.
- `src/tools/web.ts` — web_search / web_fetch tagged readOnly.
- `src/cli/commands/code.tsx` — `registerPlanTool(tools)` added after
  the filesystem and shell registrations so the tool is always in
  the pinned spec list (prefix cache stays stable across
  plan-mode toggles).
- `src/index.ts` — re-exports `PlanProposedError`, `registerPlanTool`,
  `PlanToolOptions` for library consumers.

---

## [0.4.17] — 2026-04-22

**Headline:** Project memory — drop a `REASONIX.md` in your project
root and its contents are pinned into the immutable-prefix system
prompt for every session in that directory. Persistent project
context (house conventions, domain glossary, gotchas the model keeps
forgetting) without eating per-turn context budget, and the prefix
cache stays warm as long as the file is stable.

### Added

- **`src/project-memory.ts`** — `readProjectMemory(rootDir)`,
  `applyProjectMemory(basePrompt, rootDir)`, `memoryEnabled()`. One
  source, one mental model: `REASONIX.md` at the project root, read
  once at session start, appended as a fenced "# Project memory"
  block after the base system prompt. Truncates at 8 000 chars
  (≈ 2k tokens) with a visible marker; `.gitignore` gets 2 000
  because it's a constraint dump, memory gets more headroom because
  it's deliberate instructions. Re-exported from `src/index.ts` for
  library consumers.
- **Auto-applied at every CLI entry** — top-level `reasonix`,
  `reasonix chat`, `reasonix run`, and `reasonix code` all honor
  the file. `code` resolves it against the rooted directory; the
  others against `process.cwd()` at launch.
- **`/memory` slash command** — prints the resolved file path +
  full contents (or a how-to stub when absent), so you can verify
  what the model is actually seeing without reading the system
  prompt blob. Reminds you changes take effect on the next launch
  or `/new`; the system prompt is hashed once per session to keep
  the prefix cache warm.
- **`REASONIX_MEMORY=off|false|0` env opt-out** — for CI or
  intentional offline reproducibility. `rm REASONIX.md` is the
  other opt-out.

### Tests (+25, suite 517→542)

- `tests/project-memory.test.ts` (+15) — absent / empty /
  whitespace-only / normal / oversized file paths;
  `memoryEnabled` env-value matrix; `applyProjectMemory` no-ops on
  missing/disabled; determinism (identical input ⇒ identical
  output, cache-prefix-safe); `codeSystemPrompt` stacks base →
  memory → .gitignore in the right order when all three exist.
- `tests/slash.test.ts` (+4) — `/memory` prints the how-to when no
  file, contents when present, "disabled" when env-off, "no root"
  when `memoryRoot` is absent from the SlashContext. Registry
  check updated to require `/memory`.

---

## [0.4.16] — 2026-04-22

**Headline:** Native `run_command` shell tool so the model can run
its own tests and verify its work (Claude Code / Aider parity).
3-choice picker for every unknown command — "run once", "always
allow in this project" (persists to `~/.reasonix/config.json`), or
"deny". Plus a session picker on startup so `reasonix code` stops
silently resuming the last conversation, and a Windows backspace fix.

### Added

- **`src/tools/shell.ts`** — `run_command(command, timeoutSec?)`
  registered by default in `reasonix code`. Read-only / testing
  commands (`git status`, `ls`, `cat`, `grep`, `rg`, `npm test`,
  `pytest`, `cargo test`, `cargo check`, `cargo clippy`, `go test`,
  `deno test`, `bun test`, `ruff`, `mypy`, `npx tsc --noEmit`,
  `npx biome check`, language `--version` probes) auto-run. Anything
  else goes through the ShellConfirm picker. 60s default timeout,
  32k-char output cap. `shell: false` in the child_process spawn
  so the model can't pipe / redirect / chain its way past the
  allowlist.
- **`src/cli/ui/ShellConfirm.tsx`** — 3-option SingleSelect modal
  that renders when the model asks to run a non-allowlisted
  command. Borders + color so it's impossible to miss. Arrow-key
  navigation; Enter confirms. No `y/n` hotkey — too easy to trigger
  by accident mid-typing.
- **`src/cli/ui/SessionPicker.tsx`** — on `reasonix chat` /
  `reasonix code` startup, if the session has prior messages, show
  a 3-option picker: **New** (default, safer), **Resume** (continue
  where you left off), **Delete and start new**. Flags `--resume`
  / `--new` bypass the picker for CI / muscle-memory.
- **Per-project persistent allowlist** — `config.projects[].shellAllowed`
  stores prefixes the user approved via "always allow". On next
  `reasonix code` in that dir they auto-run. Helpers
  `loadProjectShellAllowed` / `addProjectShellAllowed` exported.

### Fixed

- **Backspace dead on some Windows terminals.** Certain Git Bash /
  winpty combos report plain Backspace with `key.delete=true` and
  `key.backspace=false`; the 0.4.15 cursor reducer split the two
  and treated `delete` as forward-delete, which is a no-op when the
  cursor is at the end of the buffer — so pressing Backspace did
  nothing and Ctrl+Backspace (reported differently) was the only
  way to delete. Now both flags collapse to backward-delete, plus
  raw DEL (0x7f) and BS (0x08) bytes in `key.input` are honored as
  backspace too.

### Tests (+43, suite 474→517)

- `tests/shell-tools.test.ts` (+27) — tokenizer (quoting, escapes,
  unclosed-quote rejection); allowlist matching (exact / prefix /
  whitespace normalization / extras); `runCommand` against real
  child processes (stdout, stderr, cwd, timeout kill, output cap,
  empty-command rejection); registry dispatch (auto-run, refusal
  via `NeedsConfirmationError`, `allowAll: true` bypass);
  `formatCommandResult`; `NeedsConfirmationError` name/message
  invariants (no stale `/apply-shell` reference).
- `tests/shell-confirm.test.ts` (+4) — `derivePrefix` picks one or
  two tokens based on known wrappers and normalizes whitespace.
- `tests/config.test.ts` (+3) — `loadProjectShellAllowed` defaults
  to `[]`; `addProjectShellAllowed` persists and dedups per-project;
  ignores empty prefixes.
- `tests/multiline-keys.test.ts` (+2) — raw DEL/BS bytes are
  treated as backspace; `key.delete` unified with `key.backspace`.

---

## [0.4.15] — 2026-04-22

**Headline:** Web search + fetch tools (on by default, zero
configuration) plus real cursor editing in the prompt box (←/→,
Backspace/Delete mid-string, multi-line ↑/↓ navigation).

### Fixed

- **PromptInput was append-only** — cursor was always pinned to
  the end of the buffer, so the only way to fix a typo was
  backspacing back through everything after it. Now:
  - `←` / `→` move the cursor one column (clamped to buffer).
  - `↑` / `↓` move across lines in a multi-line buffer, preserving
    column when possible, clamping when the target line is shorter.
  - `Ctrl+A` / `Ctrl+E` jump to start / end of the current line.
  - `Backspace` deletes the char before the cursor; `Delete`
    deletes the char under the cursor.
  - Printable input inserts at the cursor (including multi-char
    paste bursts).
  - `Shift+Enter` / `Ctrl+J` insert a newline at the cursor.
- **History recall no longer steals arrow keys from mid-edit.**
  `↑` / `↓` only trigger prior-prompt recall when the buffer is
  empty. A non-empty buffer keeps the arrows for cursor motion so
  typed text isn't clobbered.

### Added

Web search + fetch tools are registered by default on `reasonix
chat` and `reasonix code`. The model calls `web_search` /
`web_fetch` on its own whenever a question needs fresher info than
its training data. Backed by **Mojeek**'s public search page — no
API key, no signup. Same Cache-First + repair + context-safety
plumbing as every other tool.

Implementation note: the first cut of this feature used DuckDuckGo,
but a live probe from the dev machine confirmed DDG now serves
HTTP 202 anti-bot pages for every unauthenticated POST regardless
of UA. Mojeek is an independent-index engine that's been stable
against the same probe (3/3 success on three queries spaced 3s
apart). Real-browser `User-Agent` string avoids Mojeek's
fast-path scraper filter.


- **`src/tools/web.ts`** — two functions + one registration helper:
  - `webSearch(query, opts?)` — fetches DDG's HTML endpoint, parses
    ranked results (title + url + snippet). `topK` is clamped to
    [1, 10]. Parser decodes DDG's `uddg=` redirect wrapper and
    common HTML entities.
  - `webFetch(url, opts?)` — HTTP GET + HTML-to-text extraction
    (scripts/styles/nav/footer/aside/svg stripped, paragraph breaks
    preserved, entities decoded). 15s timeout, 32k-char cap (matches
    tool-result budget), forwards caller's AbortSignal so Esc during
    a long fetch is honored.
  - `registerWebTools(registry, opts?)` — registers both as
    ToolRegistry entries the model can invoke. Tool descriptions
    guide the model to call search whenever training data might be
    stale.
- **`ReasonixConfig.search`** + **`searchEnabled()`** — a simple
  boolean. Default on. Turn off with `search: false` in config or
  `REASONIX_SEARCH=off|false|0` in env. No API keys, no provider
  picker — one switch.
- **Auto-registered in chat/code.** `reasonix chat` and
  `reasonix code` register `web_search` + `web_fetch` by default.
  Zero setup: after the normal wizard, the model can already reach
  the web.

### Tests (+18, suite 444→462)

- `tests/web-tools.test.ts` (+13) — htmlToText strips
  scripts/styles/nav/footer + decodes entities + collapses
  whitespace; `parseDuckDuckGoResults` decodes redirect URLs + entities
  + returns empty on unexpected markup; `webSearch` hits the DDG
  endpoint with a browsery UA, respects topK, clamps to [1, 10],
  throws on non-2xx; `formatSearchResults` renders the expected
  layout; `registerWebTools` registers both verbs; `web_fetch` refuses
  non-http(s) URLs; `webFetch` extracts title + body, truncates at
  the cap with a visible marker, surfaces 404s.
- `tests/config.test.ts` (+5) — `searchEnabled` defaults to true;
  honors `search: false` in file; honors `REASONIX_SEARCH=off|false|0`;
  stays true for unrelated env values; env off beats config true.

---

## [0.4.14] — 2026-04-22

**Headline:** Render-load reductions for Windows terminals where
Ink's cursor-up repaint leaves ghost artifacts (winpty / MINTTY /
Git Bash). No single bug fix — a set of pressure reductions plus an
explicit opt-out for the terminals where nothing else helps.

### Fixed

- **`patchConsole: false`** on every `render()` call (chat, setup,
  replay, diff). We never log to console during the TUI, so the
  patch was pure overhead and a known redraw-glitch source on
  wrapped-ANSI terminals.
- **Consolidated every animated component onto a single 120ms tick.**
  Previously `Pulse` (500ms), `Elapsed` × 2 (1000ms each), `StatusRow`
  (120ms + 1000ms), `OngoingToolRow` (120ms + 1000ms), and
  `PromptInput` cursor blink (500ms) each owned a private
  `setInterval`. On a streaming turn that's 6-10 uncoordinated
  re-render sources firing into Ink's patch loop. New
  `TickerProvider` / `useTick` / `useElapsedSeconds` in
  `src/cli/ui/ticker.tsx` collapses all of them to one shared
  counter — same visible behavior, ~5× fewer React re-renders per
  second.
- **Flush interval 60ms → 100ms.** 10 Hz still feels live while
  giving slow terminals more headroom per repaint. The prior 60ms
  rate queued patches faster than some Windows terminals could
  process them, manifesting as visible duplicates in scrollback.
- **`reasonix --version` no longer reports 0.4.3 forever.** The
  hardcoded `VERSION` in `src/index.ts` had been stale since April
  21; now matches `package.json`.

### Added

- **`REASONIX_UI=plain` env opt-out.** Suppresses every transient
  row in the render tree (streaming preview, ongoing-tool spinner,
  status line, processing fallback) AND disables the ticker
  entirely. Only `` committed events + the input prompt are
  drawn. Trades liveness for stability; use when the default TUI
  produces ghost rendering on your terminal.

---

## [0.4.13] — 2026-04-22

**Headline:** Two streaming-row bugs that made `reasonix code` feel
broken: the spinner froze for the entire duration of a large
`edit_file` call, and multi-iteration turns displayed the previous
iteration's body text concatenated into the next one.

### Fixed

- **Streaming row no longer freezes during a large tool-call.** When
  the model streams `tool_calls[].function.arguments` (kilobytes of
  SEARCH/REPLACE for a big `edit_file`) there are zero `content` or
  `reasoning_content` bytes, so the label sat on "writing response ·
  N chars" untouched — indistinguishable from a hung network. The
  loop now yields a new `tool_call_delta` event carrying the growing
  cumulative argument-char count, and the TUI surfaces it either as
  a dedicated "assembling tool call  · N chars of arguments"
  phase (magenta) when content/reasoning are empty, or as an extra
  segment on the "writing response" line when content is also
  streaming.
- **Multi-iteration turns no longer concat prior iterations' text
  into the next row.** A single `handleSubmit` can span N iterations
  (each tool_call loops us around the model), and the streaming
  buffer wasn't reset between them. If an iteration returned empty
  content (pure tool_calls), the historical entry fell back to the
  streaming-buffer's accumulated text — yielding an assistant block
  that read like a concatenation of every prior iteration's reply.
  Fix: clear `streamRef.text` / `.reasoning` / `.toolCallBuild` and
  the per-flush buffers on every `assistant_final`.
- **Unique `` key per iteration.** A single turn's multiple
  assistant_final events used to share one React key, which Ink
  dedupes; the iteration counter fixes it.

### Added

- `LoopEvent` role `tool_call_delta` with field `toolCallArgsChars`
  (cumulative arguments-string length for the call being assembled).
  Useful for any UI consumer, not just the TUI.

### Tests (+1, suite 443→444)

- `tests/loop.test.ts` — new streaming test: fake SSE body streams a
  tool_call across multiple chunks; asserts `tool_call_delta` events
  carry a strictly-growing `toolCallArgsChars` and that the id-only
  opener (name still empty) does not emit an event.

---

## [0.4.12] — 2026-04-22

**Headline:** Bulletproof tool_calls ↔ tool pairing so corrupted
session files can't keep 400ing forever. Auto-compact attempt
before forcing summary on context-guard so a single oversized
turn doesn't eat your entire session.

### Fixed

- **DeepSeek 400 "insufficient tool messages following tool_calls"**
  after a forced-summary on context-guard. Root cause: the loop
  appended `assistant.tool_calls` and then bailed to summary BEFORE
  dispatching the tools, leaving the log in a shape DeepSeek's API
  validator rejects. Fix: strip the dangling tail before calling
  summary, and defensively validate at every `buildMessages` call.
- **DeepSeek 400 "tool must be a response to a preceding tool_calls"**
  when typing anything after the above error. Root cause: partial
  fixes left stray tool messages or half-matched tool_calls in the
  log. Fix: `healLoadedMessages` now runs a full pairing validator
  — any `assistant.tool_calls` whose response set is incomplete is
  dropped along with its partial responses; any stray tool message
  is dropped. Runs on session load (with disk rewrite to persist the
  heal) AND on every outgoing API call (defensive).
- **Auto-compact before forcing summary** on context-guard trip.
  Previously the loop immediately forced a summary at 80% context —
  users lost a full turn of work. Now it first tries shrinking
  oversized tool results; if that drops enough tokens, the turn
  continues normally and the user can keep asking. Falls back to
  forced summary only when compaction has nothing to shrink.
- **`CacheFirstLoop.compact()` no longer strips structural tail** —
  split the "shrink oversized tool payloads" concern out from the
  full load-time heal. `/compact` during a live session only
  shrinks, never touches tool_calls/tool pairing (those edges are
  legitimate mid-turn state).

### Internals

- New exported `shrinkOversizedToolResults(messages, cap)` for the
  shrink-only concern. `healLoadedMessages` now composes
  `shrinkOversizedToolResults` + the full pairing validator.
- Session load heal now rewrites the session file on disk when
  anything was healed, so the damage doesn't re-surface every
  restart.

### Tests (+5, 4 reshaped, suite 436→443)

- `tests/loop-error.test.ts` (+5) — `healLoadedMessages` drops a
  stray tool without preceding tool_calls; drops an
  assistant.tool_calls whose response set is incomplete; 4 existing
  tests reshaped to use valid tool_call pairings (stray tools now
  correctly get pruned by the validator).
- `tests/loop.test.ts` (+2) — context-guard auto-compacts oversized
  tool results and continues instead of forcing summary; dangling
  assistant-with-tool_calls tail stripped defensively at
  buildMessages time.

---

## [0.4.11] — 2026-04-22

**Headline:** Real git-diff-style output for `edit_file`, `/new`
command that actually drops context (unlike `/clear`), clearer
phase labels on the streaming row.

### Added

- **LCS line-level diff for `edit_file`** — unchanged lines now
  render as ` ` context (dim), removed as `-` (red), added as `+`
  (green). Previously a one-line search with a multi-line replace
  would show the unchanged line as both `-` and `+`, which was
  just noise.
- **Git-style hunk header** (`@@ -42,1 +42,4 @@`) above each
  `edit_file` diff showing where in the file the change lands and
  how many lines it affects. Matches the `git diff` convention.
- **`edit_file` results never truncated** in the EventLog. Other
  tools keep the 400-char clip + `/tool N` escape, but edit diffs
  always show the full change so `/apply` decisions are informed.
- **`/new` slash command** (alias `/reset`) that drops the
  in-memory message log AND rewrites the session file to empty.
  Unlike `/forget` (deletes the session), `/new` keeps the session
  name, model, and config — just starts a fresh conversation.
  `CacheFirstLoop.clearLog()` is the backing public API.
- **Clearer streaming-row phase labels** — replaced the cryptic
  "streaming · 391 + think 4506 chars" with explicit state text:
  - yellow "request sent · waiting for server" pre-first-byte
  - cyan "R1 reasoning · N chars of thought" during reasoning-only
  - green "writing response · N chars · after M chars of reasoning"
    during content phase. Colored so the eye catches the phase at
    a glance instead of decoding dim text.

### Changed

- **`/clear` now advertises what it does NOT do** — users kept
  expecting it to clear context. It still clears only the visible
  scrollback, but the returned info line now says so explicitly
  and points at `/new` for context drop.
- App.tsx now renders the info line from a clear-plus-info slash
  result (previously `clear: true` short-circuited and ate any
  accompanying message).

### Tests (+8, suite 427→436 — some existing `/clear` test adjusted for new info output)

- `tests/filesystem-tools.test.ts` (+3) — `edit_file` returns a
  proper LCS diff with context lines (user's real case of one-line
  search + multi-line replace no longer double-counts); git-style
  `@@` hunk header with starting-line number from the original
  file.
- `tests/filesystem-tools.test.ts` — dedicated `lineDiff` test
  block (+5) covering pure insertion, pure deletion, substitution
  order (-/+ matches git-diff convention), identical-arrays as
  all-context, empty-search all-additions, the user-reported real
  case.
- `tests/slash.test.ts` (+3, 1 changed) — `/new` drops log + clears
  scrollback; `/reset` alias; `/help` distinguishes `/clear` vs
  `/new`; `/clear` now surfaces an explanatory info line.

---

## [0.4.10] — 2026-04-22

**Headline:** Fills the "silent wait" gaps users were hitting —
transient status indicator between iterations + before harvest, live
stats refresh per iter (not per turn), account balance cell,
in/out cost split, Esc now interrupts harvest too, `edit_file`
returns a real diff. Drops the misleading "vs Claude / saving"
numbers.

### Added

- **`status` loop event** + `StatusRow` component — a magenta
  spinner row that fills silent phases with explicit text:
  - `"thinking about the tool result…"` between iterations, while
    R1 reasons about a just-finished tool output before emitting
    the next turn's first streaming byte
  - `"extracting plan state from reasoning…"` right before the
    silent harvest round-trip (1-10s on the cheap model)
  - `"summarizing what was gathered…"` before the forced-summary
    call (budget / context-guard)
  Auto-clears on the next primary event.
- **Account balance cell** in the stats panel. `DeepSeekClient.getBalance()`
  hits `/user/balance` (separate endpoint, no billing impact).
  Fetched at launch + refreshed after each completed turn. Hides
  the cell on failure so the session works without it.
- **Input / output cost split** — panel now reads
  `cost $X (in $Y · out $Z)` so users can see where their spend
  lands without guessing. `SessionSummary` gains `totalInputCostUsd`
  and `totalOutputCostUsd`; `inputCostUsd()` and `outputCostUsd()`
  exposed as library utilities.
- **Inline diff in `edit_file` tool result** — every edit returns a
  unified-style `- old / + new` block so you can see *what* changed
  without running `git diff`. Long blocks are truncated in the
  spinner row with a `… (N more lines)` marker; `/tool N` still
  shows the full result.
- **Live stats refresh per assistant_final** — previously the
  panel only updated in the `finally` block at end-of-turn;
  multi-iter tool chains stayed frozen at the prior turn's numbers
  for 30-60s at a time. Now the cost/ctx/cache hit gauges update
  as each iteration's usage is recorded.
- **Stronger pre-first-byte hint** — streaming row now reads
  `(request sent · waiting for server)` with a concrete estimate,
  replacing the ambiguous `(streaming · 0 chars)`.

### Changed

- **Esc now also interrupts `harvest()`.** The cheap-model
  round-trip that extracts plan state was the last remaining
  un-signaled API call. Threaded `AbortSignal` through. Fast-path
  returns `emptyPlanState` when the signal is already aborted so
  the caller unblocks without a network burn.

### Removed

- **"vs Claude / saving" cells from the panel.** The savings
  percentage was a synthetic ratio against static Claude pricing,
  not a measured comparison — users fairly pointed out it reads
  like made-up marketing. The summary shape still carries
  `claudeEquivalentUsd` + `savingsVsClaudePct` for benchmark /
  replay compat but they're deprecated and no longer surfaced in
  chat.

### Also added in 0.4.10 (same release)

- **GFM markdown tables** in assistant output. `parseBlocks` now
  recognizes `| col | col |` + separator + data rows and renders
  them as aligned columns with `│` dividers. Handles alignment
  colons (`:---`, `---:`), escaped pipes, and leading-pipe-free
  variants. CJK-width-aware column padding so Chinese and English
  tables both align correctly.
- **"processing…" fallback indicator** — if the loop is busy but
  none of the targeted indicators (streaming row, ongoingTool,
  statusLine) are visible, a generic magenta spinner row fills the
  gap. Belt-and-suspenders: no more silent clock-ticks.
- **Clearer between-iter status wording** — changed from "thinking
  about the tool result…" (which sounded like a model-only phase)
  to "tool result uploaded · model thinking before next response…"
  so it's obvious the wait covers both the upload round-trip and
  the model's thinking time.

### Tests (+11, suite 416→427)

- `tests/telemetry.test.ts` (+4) — `inputCostUsd` covers cache-hit
  + cache-miss but not completion; `outputCostUsd` covers
  completion only; both return 0 for unknown models;
  `totalInputCostUsd + totalOutputCostUsd == totalCostUsd`.
- `tests/filesystem-tools.test.ts` (+2) — `edit_file` returns an
  inline `- search / + replace` diff; huge edit blocks get
  `… (N more lines)` marker in the middle.
- `tests/markdown.test.ts` (+5) — simple table with CJK header +
  cells, alignment-colon separators accepted, pipe-less headers
  accepted, bare `|` in prose doesn't false-trigger, escaped `\|`
  preserved inside cells.

---

## [0.4.9] — 2026-04-22

**Headline:** Three user-reported issues fixed together: Esc now
really stops (not "after the tool finishes"), `reasonix code` drops
the filesystem MCP subprocess for native tools with an R1-friendly
`edit_file` shape, and the placeholder cursor renders in the right
place. Plus a `slow_count` demo tool so progress bars are testable.

### Changed

- **Esc is now an immediate cancel**, not "cancel at the next iter
  boundary." The loop now threads an AbortController through every
  I/O path it can:
  - `DeepSeekClient.chat`/`.stream` already accepted `signal` — now
    wired at every call site (normal turn, branch sampling, forced
    summary), so Esc closes the HTTP/SSE stream immediately.
  - `ToolRegistry.dispatch` accepts `{ signal }` and passes a
    `ToolCallContext` to the tool's `fn`. Existing tools that don't
    consume the ctx keep working.
  - `McpClient.callTool({ signal })` sends an MCP
    `notifications/cancelled` for the in-flight request AND rejects
    the pending promise right away — no "wait for subprocess."
    Late responses are swallowed by `dispatch` because the id is
    already gone from `pending`.
  - `bridgeMcpTools` forwards `ctx.signal` straight into
    `client.callTool`, so MCP tools inherit the cancellation path.
- **Built-in filesystem tools** replace the
  `@modelcontextprotocol/server-filesystem` subprocess inside
  `reasonix code`. Ten tools — `read_file` (head/tail), `write_file`,
  `edit_file` (flat SEARCH/REPLACE, not the JSON-in-string array
  shape that triggered R1 DSML hallucinations), `list_directory`,
  `directory_tree`, `search_files`, `get_file_info`,
  `create_directory`, `move_file`. Sandbox enforcement on every
  path. New CLI output: `▸ reasonix code: … · 10 native fs tool(s)`.
  Library API: `registerFilesystemTools(registry, { rootDir })`.
  `ChatOptions` gains `seedTools: ToolRegistry` so callers can
  pre-register tools and still bridge MCP on top.

### Fixed

- **Placeholder cursor now renders at position 0**, not after the
  dimmed hint text. Matches "you're about to type here," not "you
  typed the placeholder." Only affects the empty-input view; when
  there's real content the cursor still follows the last char.

### Added

- **`slow_count` demo tool** in `examples/mcp-server-demo.ts` that
  emits real `notifications/progress` frames (1/N, 2/N, …) with
  300 ms pauses. Progress-bar plumbing from 0.4.8 is now testable
  end-to-end: `reasonix chat --mcp "demo=node --import tsx examples/mcp-server-demo.ts"` then ask the model to
  "please use slow_count to count to 5" → bar fills in the spinner.
- **`ToolCallContext`** public type (`{ signal?: AbortSignal }`),
  passed to every tool's `fn`. Re-exported from `src/index.ts`.

### Tests (+29, suite 387→416)

- `tests/filesystem-tools.test.ts` (new, +26) — read/write/edit
  happy paths, head/tail line selection, truncation on oversize,
  directory refusal, sandbox escape rejection (both relative `../`
  and absolute `/etc/…`), search case-insensitivity, empty-result
  formatting, `edit_file` multi-match refusal, move across dirs,
  `create_directory` idempotence, `allowWriting: false` trims the
  write-side tool set.
- `tests/mcp.test.ts` (+3) — AbortSignal rejects the pending
  promise, emits `notifications/cancelled` with the correct id,
  rejects immediately when called with an already-aborted signal.

---

## [0.4.8] — 2026-04-21

**Headline:** MCP progress notifications — long-running tool calls
now stream incremental progress into the spinner row instead of
sitting silent for minutes. "▸ tool\ running… 42s" grows
to "[█████░░░░░░░░░░░░░░░] 500/2000 25%  reading src/…"  as the
server reports.

### Added

- **`McpClient.callTool(name, args, { onProgress })`** — attaches
  a fresh `_meta.progressToken` per call; server-emitted
  `notifications/progress` frames are routed to the handler until
  the final response arrives. Handler is dropped on completion or
  timeout — no leaks, late frames are silently swallowed.
- **Dispatch routing for `notifications/progress`** in the client's
  reader loop. Other server-initiated notifications are still
  dropped (list_changed frames not implemented yet).
- **`bridgeMcpTools({ onProgress })`** — pipes the per-call
  callback through to bridged tools. The info object includes the
  *registered* (prefix-applied) tool name so multi-server UIs can
  attribute progress correctly.
- **Progress bar in `OngoingToolRow`** — when a frame arrives with
  `total`, renders `[███░░░░░░] n/total pct%  message`. Without
  `total`, falls back to `progress: n  message`. Resets on each
  new tool call so stale progress doesn't linger.
- **Public types in `src/mcp/types.ts`**: `McpProgressHandler`,
  `McpProgressInfo`, `ProgressNotificationParams`. Re-exported
  from `src/index.ts` for library consumers.

### Tests (+5, suite 382→387)

- `tests/mcp.test.ts` (+5) — progress frames routed to onProgress
  in order; `_meta.progressToken` omitted when no callback is
  given; distinct token when present; late frames after resolution
  silently swallowed; `bridgeMcpTools` forwards progress with the
  prefixed tool name.

---

## [0.4.7] — 2026-04-21

**Headline:** Multi-line input in the chat TUI. Paste a code block
without it getting chopped on the first newline; compose structured
prompts across multiple lines; still hit Enter once to send.

### Added

- **Multi-line prompt input** replacing the old single-line
  `ink-text-input`. Newline-insertion paths, in order of terminal
  reliability:
  - `Ctrl+J` — universal (real ASCII LF), works on every terminal
  - `Shift+Enter` — works on terminals that enable CSI-u modifier
    reporting (iTerm2 with that setting on, WezTerm, Ghostty, etc.)
  - `\` — bash-style line continuation, always works as a
    portable fallback
  - Pasted multi-line text lands intact instead of submitting on
    the first embedded `\r`.
- **Visible blinking cursor** on the active line so the input box
  looks alive even when you stop typing mid-compose.
- **`processMultilineKey` pure reducer** in `src/cli/ui/multiline-keys.ts`.
  Keystroke → action function that's fully unit-testable; the
  React component is a thin wrapper. Parent-owned keys (Tab for
  slash auto-complete, ↑/↓ for slash-nav + history, Esc for abort,
  left/right/page arrows) are no-ops in the reducer so the buffer
  never eats a stray control sequence when both parent and child
  `useInput` fire on the same event.

### Design notes

- No mid-string insertion cursor. Edits are cursor-at-end (backspace
  to delete, paste to insert). Matches how readline-in-raw-mode
  feels, covers ~95% of prompt-composition cases, and skips a pile
  of complexity (arrow-key cursor nav, selection, kill/yank) that
  would collide with the parent's arrow-key handling for slash-nav
  and history recall.
- `ink-text-input` is still used by `Wizard`, `Select`, `Setup` — it
  fits those single-line forms fine and didn't need replacing.

### Tests (+18, suite 364→382)

- `tests/multiline-keys.test.ts` (new) — printable input, multi-char
  paste, Enter-submit, Shift+Enter-newline, Ctrl+J (raw LF and
  normalized `ctrl+'j'`), bash continuation, backspace across
  newlines, delete, tab/arrows/esc/ctrl-letter/meta all ignored,
  empty-buffer edge cases.

---

## [0.4.6] — 2026-04-21

**Headline:** Slash-command UX overhaul + MCP discovery closes in
two places. Typing `/` now pops an IntelliSense-style suggestion
list you can walk with ↑/↓ and pick with Enter or Tab — no more
memorizing commands or reading a cluttered footer. The footer is
gone. `/mcp` inside chat now shows each server's tools + resources
+ prompts in one grouped view. For scripting/CI there's a new
`reasonix mcp inspect ` CLI doing the same.

### Added

- **Slash autocomplete popup.** When the input starts with `/` and
  matches exist, a floating panel lists commands (name + args hint
  + one-line summary). ↑/↓ navigate the list; Tab inserts the
  highlighted name into the input; Enter runs it directly. Leaves
  slash mode the moment you type a space — then ↑/↓ goes back to
  shell-style prompt history as before. Registry lives in
  `SLASH_COMMANDS` and gates code-mode-only entries (`/apply`,
  `/discard`, `/undo`, `/commit`) behind the TUI's `codeMode` flag.
- **`/mcp` is now the discovery view.** Rich output per connected
  server: name + version + spec, tool count, resources list, prompts
  list. Unsupported sections collapse to `(not supported)` so a
  tools-only server still reads clean. Inspection happens once at
  chat startup and flows through `SlashContext.mcpServers` — the
  slash handler stays sync.
- **`reasonix mcp inspect `**. CLI counterpart to `/mcp`, for
  running outside chat (CI, scripting, "does this server even
  work?"). Same spec grammar as `--mcp`; `--json` emits the full
  report.
- **`inspectMcpServer(client)`** public API in `src/mcp/inspect.ts`.
  Pure function — testable against any `McpClient` instance; returns
  an `InspectionReport` with per-section `{supported, items}` or
  `{supported: false, reason}`. Re-exported from `src/index.ts`.
- **`McpClient.serverInfo` + `.protocolVersion` + `.serverInstructions`**.
  The full initialize handshake result is now exposed, not just
  `.serverCapabilities`. Needed by any UI that wants to surface
  "connected to X v1.2.3".

### Removed

- **Static command-strip footer under the input.** Took 3-4 dimmed
  lines listing a random subset of commands; superseded by the
  on-demand slash popup that only surfaces when the user asks for
  it (by typing `/`).

### Tests (+11, suite 353→364)

- `tests/mcp-inspect.test.ts` (new, +5) — full-support server,
  -32601 → `supported: false`, non-32601 forwarded as the section
  reason, serverInfo/protocolVersion/instructions accessors,
  undefined-instructions fallback.
- `tests/slash.test.ts` (+6) — `SLASH_COMMANDS` contains every
  handler case, `suggestSlashCommands` prefix + case + empty-string
  behavior, code-mode gating, `/mcp` rich view renders tools +
  resources + prompts grouped per server, `/mcp` spec-only fallback.

---

## [0.4.5] — 2026-04-21

**Headline:** Two protocol-level completions bundled together. (1)
DSML-hallucinated tool calls are now **recovered** (not just stripped
from display) — when R1 emits its chat-template markup in the content
channel instead of the proper `tool_calls` field, the repair pipeline
parses it back into a real ToolCall and executes it. (2) The MCP
client gains `resources/*` and `prompts/*` — the remaining method
families needed for spec parity beyond tools.

### Added

- **DSML invoke parser in `scavengeToolCalls`.** Pattern A in
  `src/repair/scavenge.ts` now recognizes `<|DSML|invoke name="X">…` blocks with nested `<|DSML|parameter name="k" string="true|false">v` children. `string="true"` → literal; `string="false"` → JSON. Both full-width `|` and ASCII `|` variants accepted. Malformed JSON under `string="false"` falls back to a literal string so data isn't lost.
- **Content-channel scavenge.** `ToolCallRepair.process` now takes an
  optional third arg `content` and scans both reasoning + content for
  leaked calls. The loop wires `assistantContent` through. This closes
  the hole noted in the v0.4 deferred queue: before, DSML in a regular
  turn was stripped from display but the tool never ran.
- **MCP `resources/list` + `resources/read`** on `McpClient`. Types:
  `McpResource`, `McpResourceContents` (text + blob shapes),
  `ListResourcesResult`, `ReadResourceResult`. Pagination cursor
  supported.
- **MCP `prompts/list` + `prompts/get`** on `McpClient`. Types:
  `McpPrompt`, `McpPromptArgument`, `McpPromptMessage`,
  `McpPromptResourceBlock`, `ListPromptsResult`, `GetPromptResult`.
- **Initialize capabilities** now advertise `resources` and `prompts`
  alongside `tools`. Servers that don't implement them respond with
  −32601 method-not-found; client surfaces that as a thrown Error.

### Tests (+13, suite 340→353)

- `tests/repair/scavenge.test.ts` (+5) — DSML with string + JSON
  params, ASCII-pipe variant, allow-list skip, `string="false"`
  malformed-JSON fallback, no double-counting via Pattern B.
- `tests/repair/pipeline.test.ts` (+2) — content-channel DSML yields
  scavenged call; no double-count when DSML appears in both channels.
- `tests/mcp.test.ts` (+6) — list+read resources, method-not-found
  on unsupported server, capabilities payload advertises all three,
  cursor round-trip; list+get prompts with args, argument omission.

---

## [0.4.4] — 2026-04-21

**Headline:** `/tool` slash command — inspect the full untruncated
output of any tool call this session. The `EventLog` renderer has
always clipped tool results at 400 chars for display; when the model
says "I read your file, it says …", users had no way to verify that
claim against what the tool actually returned. Now they do.

### Added

- **`/tool`** (no arg) — list up to 10 most recent tool calls with
  tool name, char count, and a one-line preview. `#1` is the most
  recent; older entries are paged behind a "… (N earlier)" hint.
- **`/tool N`** — dump the Nth-most-recent tool result in full,
  untruncated. Reads from an in-memory ref populated as each `tool`
  event lands in `App.tsx`. Not persisted across process restarts
  (resumed sessions don't rebuild the history — the tool messages
  are still in the session log for the model's sake, but `/tool`
  history is per-process).
- **`SlashContext.toolHistory` callback** — the TUI passes
  `() => toolHistoryRef.current`; pure `handleSlash` tests stub
  an array directly. Keeps `slash.ts` stateless.

### Tests (+8, suite 332→340)

- `tests/slash.test.ts` (+8) — empty-history message, list ordering
  (most recent first), `/tool 1` dumps full content, `/tool 2`
  reaches one back, out-of-bounds message, non-numeric → usage,
  list pagination at 15 entries, `/help` mentions `/tool`.

---

## [0.4.3] — 2026-04-21

**Headline:** Seven more UX improvements on top of 0.4.2. Layered in
after live `reasonix code` sessions surfaced pain points: R1 fake
tool-call hallucinations leaking into forced summaries, no quick
retry, /status too thin, tool errors blending in, no prompt history,
no one-key pending-edit confirmation, and — critically — Esc
blocking for 30-90s on a reasoner call the user never asked for.

### Added

- **`/retry` slash command.** Truncates the log back to just before
  your last user message, then re-submits so the model runs a fresh
  turn from a clean slate. Persists the truncation to the session
  file. `SlashResult` grows a `resubmit?: string` field the TUI
  honors after displaying `info`.
- **`/status` is now a real situation-report.** Labeled table:
  model, harvest/branch/stream flags, last-turn context usage
  against the window (`42k/131k (32%)`), MCP server + tool counts,
  session name + log length + resumed-count, pending edit count.
- **Prompt history with ↑/↓.** Shell-style recall. Lives in an
  `App.tsx` ref; cursor −1 = live input, 0+ walks back. Process-
  scoped — no cross-run persistence.
- **Y/N fast-path for pending edits.** When pending count > 0,
  `y` + Enter = `/apply`, `n` + Enter = `/discard`. Doesn't
  interfere otherwise. Preview message ends with `(or y / n)`.

### Changed

- **Tool errors render red + ✗**, not yellow + →. Tool results
  prefixed `ERROR:` (from `flattenMcpResult` on `isError`) now
  visually distinguish from success. A failure needs different
  attention than a directory listing.
- **Esc abort no longer forces another API call.** Previously:
  Esc → `warning: aborted at iter N/M — forcing summary` → another
  full reasoner call that took 30-90s → done. Users reported the
  wait was the opposite of "cancel." Now: Esc → quick warning →
  synthetic `assistant_final` ("no summary produced — ask again
  or `/retry` when ready") → done. Takes milliseconds. Prior tool
  output stays in the log so a follow-up question hits the warm
  prefix cache. Budget / context-guard still call `forceSummary`
  because there the user didn't choose to stop; we did.

### Fixed

- **Forced-summary path no longer leaks DSML tool-call markup as
  prose.** Passing `tools: undefined` wasn't enough — R1 primed
  for tool use still emitted `<|DSML|function_calls>…
  ` as plain text. Two layers: (1) append
  an explicit user-role instruction at the end of the forced-summary
  message list ("summarize in plain prose, do NOT emit any tool
  calls or function-call markup"); (2) post-hoc strip known
  envelopes (DSML full-width, DSML ASCII, Anthropic
  ``, truncated un-closed DSML openers) from the
  response. Exported as `stripHallucinatedToolMarkup`. Fallback
  message when stripping leaves nothing points at `/retry` and
  `/think`.

### Tests (+13, suite 319→332)

- `tests/slash.test.ts` (+8) — `/think` empty/populated/help,
  `/retry` happy path + empty-log + help listing, `/status` new
  format + pending-edit suppression at count 0.
- `tests/loop-error.test.ts` (+5) — `stripHallucinatedToolMarkup`
  live R1 DSML shape, Anthropic-style, truncated un-closed opener,
  plain prose passthrough, all-markup edge case.
- `tests/loop.test.ts` — abort test rewritten to confirm no extra
  API call is made (previously asserted a "partial findings"
  summary from the never-needed follow-up).

---

## [0.4.2] — 2026-04-21

**Headline:** Three small but visible UX improvements from a real
session: tool-call spinner now shows elapsed time + meaningful args
(not raw JSON), reasoning preview shows the *tail* instead of the
head (where the decision actually lives), and a `/think` slash
command dumps the full R1 reasoning for the most recent turn.

### Changed

- **Tool-running row surfaces elapsed seconds + per-tool argument
  summary.** Instead of `⠋ tool running… 
  {"path":"F:\\testtest\\index.html","edits":[…]}`, you now see:
    ```
    ⠋ tool running… 3s
      path: F:\testtest\index.html (2 edits)
    ```
  Per-tool summarizers for `read_file`, `write_file`, `edit_file`,
  `list_directory`, `directory_tree`, `search_files`, `move_file`,
  `get_file_info`. Matches on suffix (`_read_file`) so namespaced
  servers (`filesystem_read_file`) and anonymous servers both work.
  Unknown tools fall back to a truncated raw-JSON preview — better
  than nothing.
- **Reasoning preview shows the tail, not the head.** R1 opens every
  turn with the same "let me look at the structure…" scaffolding, so
  previously the `↳ thinking: …` line repeated across turns and hid
  the real content in `(+N chars)`. Now the preview window shows the
  last ~260 chars — which is where the model actually decides what
  to do next. Users reported the head-only preview made R1 turns
  look identical; this fixes the underlying information-hiding bug.

### Added

- **`/think` slash command.** Dumps the full raw reasoning text from
  the most recent turn (read from `loop.scratch.reasoning`). Intended
  for when the 260-char tail isn't enough and you want to see R1's
  actual chain. Reports a helpful message if no reasoning is cached
  (e.g. the current model is `deepseek-chat`, which doesn't produce
  `reasoning_content`). Also listed as an alias `/reasoning`.
- **`/retry` slash command.** Truncates the log back to just before
  your last user message, then re-submits it so the model runs a
  fresh turn from a clean slate. Persists the truncation to the
  session file so reload doesn't rehydrate the stale exchange.
  Useful to resample R1 when the first try was off, without typing
  the question again. `SlashResult` grows a `resubmit?: string` field
  the TUI honors after displaying the result's `info` line.
- **`/status` is now a real situation-report.** Previously it was
  four key=value pairs on one line; now it's a labeled table
  covering model, harvest/branch/stream flags, last turn's context
  usage against the window (`42k/131k (32%)`), MCP server + tool
  counts, session name + log length + resumed-count, and pending
  edit count in code mode. One command, whole state.
- **Prompt history with ↑/↓.** Shell-style recall of previously
  submitted prompts. Lives in a ref in `App.tsx`; ↑ walks back, ↓
  walks forward (empty input at cursor=-1). Scoped to the current
  session process — no cross-launch persistence. Fast path for
  iterating on the same question with small tweaks.
- **Y/N fast-path for pending edits.** When edit blocks are waiting
  for `/apply` or `/discard`, typing just `y` or `n` + Enter maps
  to those commands. Doesn't interfere with normal input because
  the branch only triggers when pending count > 0. Preview line
  now ends with `(or y) … (or n)` so users know the shortcut exists.

### Changed

- **Tool-running row surfaces elapsed seconds + per-tool argument
  summary.** Instead of `⠋ tool running…
  {"path":"F:\\testtest\\index.html","edits":[…]}`, you now see:
    ```
    ⠋ tool running… 3s
      path: F:\testtest\index.html (2 edits)
    ```
  Per-tool summarizers for `read_file`, `write_file`, `edit_file`,
  `list_directory`, `directory_tree`, `search_files`, `move_file`,
  `get_file_info`. Matches on suffix (`_read_file`) so namespaced
  servers (`filesystem_read_file`) and anonymous servers both work.
  Unknown tools fall back to a truncated raw-JSON preview — better
  than nothing.
- **Reasoning preview shows the tail, not the head.** R1 opens every
  turn with the same "let me look at the structure…" scaffolding, so
  previously the `↳ thinking: …` line repeated across turns and hid
  the real content in `(+N chars)`. Now the preview window shows the
  last ~260 chars — which is where the model actually decides what
  to do next. Users reported the head-only preview made R1 turns
  look identical; this fixes the underlying information-hiding bug.
- **Tool errors render red, not yellow.** Tool results whose content
  starts with `ERROR:` (the prefix `flattenMcpResult` adds when the
  server reports `isError: true`) now show as a red `tool  ✗`
  header + red body, instead of the same yellow `→` as successful
  results. A failure needs different attention than "here's your
  directory listing."

### Fixed

- **Forced-summary no longer leaks DSML tool-call markup as prose.**
  When the loop forces a no-tools summary (Esc / budget /
  context-guard), passing `tools: undefined` turned out not to be
  enough — R1 primed for tool use would still emit
  `<|DSML|function_calls>…` as plain text,
  which rendered verbatim in the TUI. Fix is two layers:
    1. Inject an explicit user-role instruction at the end of the
       forced-summary message list ("summarize in plain prose, do
       NOT emit any tool calls or function-call markup").
    2. Post-hoc strip known hallucinated envelopes (DSML full-width,
       DSML ASCII, Anthropic-style ``, and
       truncated un-closed DSML openers) from the model's response
       before yielding. Exported as `stripHallucinatedToolMarkup(s)`
       so library callers building their own UIs can apply the same
       cleanup.
  When stripping leaves nothing behind, the loop emits a clear
  fallback message pointing at `/retry` and `/think` rather than
  showing an empty assistant turn.

### Tests (+13, suite 319→332)

- `tests/slash.test.ts` (+8) — `/think`, `/retry` happy path +
  empty-log path + help listing, `/status` new format with rich
  rows, `/status` pending-edit suppression at count 0.
- `tests/loop-error.test.ts` (+5) — `stripHallucinatedToolMarkup`
  against the live R1 DSML shape, Anthropic-style
  ``, truncated unpaired DSML opener, plain prose
  passthrough, and the all-markup-no-prose edge case.

---

## [0.4.1] — 2026-04-21

**Headline:** `reasonix code` grows `/undo`, `/commit`, `.gitignore`
awareness — and, **critically, stops auto-writing edits to disk.** A
real-session bug ("I asked to analyze the project, it silently edited
a file") exposed that v0.4.0's auto-apply was the wrong default.
Edits now sit as **pending** until the user says `/apply`. This
release also replaces the fixed iter-count budget with a
token-context guard, which you were right to call out as the correct
abstraction from the start.

### Fixed (behavior change for code-mode users)

- **Edits are now gated behind `/apply`.** Each assistant turn's
  SEARCH/REPLACE blocks are parsed and shown as a preview line
  (`▸ N pending edit block(s) — /apply to commit, /discard to drop`)
  with per-block `path  (-N +M lines)`. Nothing touches disk without
  explicit `/apply`. Pending state survives across user messages —
  you can keep chatting and land the batch later. Aider's model, which
  we should have picked from the start.
- **Forced-summary events are tagged `forcedSummary: true` on
  `LoopEvent`.** The code-mode edit applier ignores tagged events
  entirely. Without this, a budget / abort / context-guard summary
  could dump SEARCH/REPLACE blocks into output and silently turn
  "analysis" into "edit". This was the root-cause bug for the
  real-session report.
- **Token-context guard replaces iter count as the primary stop.**
  After every model response, if `promptTokens / contextWindow > 0.8`
  the loop emits a yellow warning, skips executing the tool calls the
  model just proposed, and diverts to the no-tools summary path
  (`reason: "context-guard"`). Iter cap bumped 24 → 64 as a
  last-resort backstop — the real constraint is the 131k-token
  window, not a magic iteration count.
- **Stray `EditSummary` / `summarizeEdit` reverted** from
  `src/code/edit-blocks.ts`. v0.4.0's auto-apply let the model write
  it during a failed forced-summary run. Nothing referenced it.
  Removed.
- **SEARCH/REPLACE blocks render as a real diff, not mangled prose.**
  Previously the Markdown renderer fed SEARCH/REPLACE content through
  the paragraph path — which joined lines with spaces and let the
  inline bold/italic regex eat `*` characters inside JSDoc `/** … */`
  comments. Output looked like `/** Edit landed on disk. /` with
  trailing `*` consumed and newlines flattened. Now the parser
  recognizes the `` / `<<<<<<< SEARCH` / `=======` /
  `>>>>>>> REPLACE` envelope and emits a dedicated `edit-block` block
  kind, rendered as `- ` / `+ ` diff rows with the filename on top
  and (new file) tagged for empty-SEARCH creations. No inline
  markdown inside — content is shown verbatim.
- **"Reasoning before it speaks" UX no longer looks frozen.** Under
  `deepseek-reasoner`, R1 streams `reasoning_content` first and
  `content` only after — often 20-90 seconds of silence from the
  user's perspective. The streaming preview used to show
  `(waiting for first token…)` during that window, making the app
  look hung. Now:
    - A cyan braille-spinner pulse ticks at 500 ms so the heartbeat
      is visible regardless of stream bursts.
    - Label switches `streaming` → `reasoning` while body is empty.
    - The "waiting" line is replaced with an explicit
      `R1 is thinking before it speaks — body text starts when
      reasoning completes (typically 20-90s)` so the user knows to
      wait, not to bail.
- **Tool calls now show a spinner while dispatching.** The loop
  gains a new `tool_start` event yielded *before* `await
  tools.dispatch(...)`, separate from the existing `tool` event
  yielded with the result. The TUI renders a
  `⠋ tool running…` row (with a short args
  preview) while the Promise is pending. Without this, a multi-KB
  edit could sit for a full second with no visual feedback — the
  streaming block was already cleared on `assistant_final` and the
  input was disabled. Transcripts still only record the `tool`
  result event (not `tool_start`), so replay/diff output is
  unchanged.

### Added (code mode)

- **`/apply`** — commits pending edit blocks, snapshots for `/undo`,
  per-block status.
- **`/discard`** — forgets pending edits without writing.
- **`/undo`** — roll back the *last applied* edit batch. Restores
  files to their pre-`/apply` content, deletes any file the batch had
  just created. One level of history for now, Aider-style.
- **`/commit "msg"`** — `git add -A && git commit -m "msg"` inside
  the code-mode rootDir. Surfaces git's stderr on failure (hooks,
  nothing staged, detached HEAD, etc.).
- **.gitignore awareness** — `reasonix code` reads the project's
  `.gitignore` on launch and injects it into the system prompt as
  "don't traverse or edit these paths unless asked". Hard-coded
  baseline ignores (`node_modules`, `dist`, `.git`, `.venv`, etc.) are
  also baked into the base prompt for projects without a `.gitignore`.
  Stops the model wasting 5 tool calls listing `node_modules`.

### Tightened

- **`CODE_SYSTEM_PROMPT` gains a "when to edit vs. when to explore"
  section.** Explicitly tells the model: only propose edits when the
  user asks to change / fix / add / remove / refactor. For analyze /
  explain / describe, stay read-only. Belt-and-braces with the
  `/apply` gate below.

### Tests (+35, suite 292→318)

- `tests/edit-blocks.test.ts` (+5) — `snapshotBeforeEdits` +
  `restoreSnapshots` round-trip: restore modified file, delete
  newly-created file on undo, de-dup per path in batches, refuse
  path-escape in snapshots.
- `tests/code-prompt.test.ts` (+4 new file) — `.gitignore` injection:
  no-file case, happy path, truncation over 2KB, base prompt still
  names the built-in ignores.
- `tests/slash.test.ts` (+13) — `/apply`, `/discard`, `/undo`,
  `/commit`: inside vs. outside code mode, usage hint on empty
  message, double-quote stripping, help listing all of them.
- `tests/loop.test.ts` (+1) — context-guard warning + forced-summary
  flag when prompt tokens exceed 80% of the window.
- `tests/markdown.test.ts` (+5) — `parseBlocks` extracts SEARCH/
  REPLACE into `edit-block` blocks, preserves multi-line JSDoc
  verbatim, handles new-file (empty SEARCH), rejects stray markers
  without close, multi-block responses interleaved with prose.
- `tests/loop.test.ts` (+1) — `tool_start` precedes `tool` for each
  dispatch, so UI consumers can pair them.

### Notes

- If you relied on 0.4.0's auto-apply behavior in scripts, that's
  gone. For automation, call `applyEditBlocks` directly from the
  library — the CLI TUI is for interactive use where the new gate
  is correct.

---

## [0.4.0] — 2026-04-21

**Headline:** `reasonix code` — a new subcommand that turns Reasonix
into a coding assistant. Auto-bridges the filesystem MCP at your
working directory, teaches the model to emit Aider-style
SEARCH/REPLACE blocks, applies them to disk after each turn. The
"cheap Claude Code" pitch becomes real.

### Added

- **`npx reasonix code [dir]`** — opinionated wrapper around chat:
  - Filesystem MCP auto-bridged at `[dir]` (default CWD). No wizard,
    no config merge. Out-of-box ready.
  - Code-specialized system prompt that teaches SEARCH/REPLACE.
  - Reasoner + harvest on by default (coding tasks repay R1 thinking).
  - Per-directory session name (`code-`) so different
    projects don't share history.
- **SEARCH/REPLACE edit blocks** (`src/code/edit-blocks.ts`). The
  model emits:
    ```
    path/to/file.ts
    <<<<<<< SEARCH
    (exact existing lines)
    =======
    (replacement)
    >>>>>>> REPLACE
    ```
  Reasonix parses them from `assistant_final`, applies them under
  the root dir, reports each result (`✓ applied`, `✓ created`,
  `✗ not-found`, `✗ path-escape`, …) as an info line in the TUI.
  Empty SEARCH creates a new file (Aider convention). SEARCH must
  match byte-for-byte; we never fuzzy-match, because a silently wrong
  edit is worse than a loud rejection.
- **New public API** on the library: `parseEditBlocks`,
  `applyEditBlock`, `applyEditBlocks`, `CODE_SYSTEM_PROMPT`, and the
  types `EditBlock` / `ApplyResult` / `ApplyStatus`. Anyone building
  their own code-assistant UX can compose from these.
- **`ChatOptions.codeMode`** — opt-in flag to enable edit-block
  processing inside the existing TUI event loop. Plain `reasonix chat`
  leaves it off.

### Why 0.4.0 (minor, not patch)

This is a new user-facing primitive, not a bug fix or UX polish. The
library exports grow; the `ChatOptions` interface gains a field.
Nothing breaks for existing 0.3.x users — `reasonix chat` behaves
exactly as before when `codeMode` is absent. But the SemVer convention
is: additive new surface = minor bump.

### Tests (+13, suite 279→292)

- `tests/edit-blocks.test.ts` (+13 new file). `parseEditBlocks`
  round-trips single + multi + multi-line + empty-SEARCH blocks, and
  ignores stray 7-char runs in arbitrary prose. `applyEditBlock`
  covers happy path, new-file creation, not-found rejection,
  file-missing, path-escape defense, first-occurrence semantics.
  Batch `applyEditBlocks` confirms failures don't cascade.

### Notes

- v1 scope is deliberately narrow: no `/commit`, no `/undo`, no
  .gitignore filtering, no diff preview. The user's own `git diff` +
  `git checkout` is the review + undo surface — and we run inside a
  git repo by convention.
- The ctx gauge + Esc + /compact safety net from 0.3.1/0.3.2 applies
  equally to code mode. Exploring a large repo now has visible
  progress and a hard off-switch.

---

## [0.3.2] — 2026-04-21

**Headline:** Long exploration sessions are now interruptible and
self-announcing. 0.3.1's forced-summary was a terminal safety net;
this release turns it into an interactive budget with a visible warning
at 70% and `Esc` to cash out early. Plus a README rewrite so new users
actually know the new UX exists.

### Added

- **Esc while thinking → force a summary now.** `CacheFirstLoop` grows
  an `abort()` method; the TUI's `useInput` wires Esc to it during
  busy state (guarded by a once-per-turn flag). The loop checks the
  abort flag at each iteration boundary, lets any in-flight tool call
  complete, then diverts to the same no-tools summary path introduced
  in 0.3.1 — prefixed `[aborted by user (Esc) — summarizing what I
  found so far]`.
- **Yellow warning at 70% of tool-call budget.** New `"warning"`
  `EventRole` + `DisplayRole`, yielded once per step when tool-iter
  count reaches `Math.floor(maxToolIters * 0.7)`. TUI renders it
  yellow in the event log with the "Press Esc to summarize now" hint.
  The command strip under the prompt also advertises the Esc hotkey.
- **README hero rewrite.** `npx reasonix` (no flags) is now the first
  code block, with the wizard story in prose; `--mcp`/`--preset`
  moved to an "Advanced — CLI subcommands and flags" section.
  What-you-get table gains *Setup wizard*, *Context safety net*
  (tool-result cap + heal-on-load + `/compact` + ctx gauge + Esc),
  and merges the MCP transports into one row. Non-goals and
  configuration sections trimmed to match the new flow.

### Tests (+2, suite 277→279)

- `tests/loop.test.ts` (+2) — warning fires exactly once at the 70%
  threshold and the content carries `N/budget tool calls used` +
  `Esc`. `abort()` mid-step pulls the loop into the summary path,
  surfacing an `aborted by user` prefix on the final event.

---

## [0.3.1] — 2026-04-21

**Fixes a silent stop** that surfaced on the first real MCP exploration
task after 0.3.0 shipped: the reasoner chained 8 filesystem tool calls
against a project and the loop quietly exited at the `maxToolIters`
ceiling without showing the user any answer — no error, no summary,
just a hung-looking terminal.

### Fixed

- **Tool-call budget now produces a summary instead of stopping silent.**
  When `maxToolIters` is exhausted with tool calls still pending, the
  loop now makes one final call *with tools disabled*, forcing the
  model to produce a text answer from everything it gathered. Yielded
  as a normal `assistant_final` event prefixed with
  `[tool-call budget (N) reached — forcing summary from what I found]`.
- **Default `maxToolIters` raised from 8 → 24.** Eight was never enough
  for real filesystem / MCP work (read_file → list → read_file chains
  easily top that). Twenty-four is a workable ceiling that still caps
  the damage from a confused model. Pass a number to
  `new CacheFirstLoop({ maxToolIters: N })` to tune per call site.

### Tests

- `tests/loop.test.ts` (+1) — tight `maxToolIters: 2` scenario where
  every step still wants to call tools, proves the summary call fires,
  the annotated `assistant_final` contains the fallback text, and the
  stream still ends with `done`.
- Suite: **277 passing** (was 276).

---

## [0.3.0] — 2026-04-21

**Stable.** MCP (stdio + SSE, multi-server) + first-run wizard +
context-safety (result cap + auto-heal + `/compact`). The `0.3.0-alpha.*`
series graduates — `npm install reasonix@latest` now pulls this.

### Added — since 0.2.2

- **MCP client**: stdio + HTTP+SSE transports, tools/list + tools/call,
  repeatable `--mcp` flag with `name=` namespacing, curated catalog
  (`reasonix mcp list`), bundled demo server.
- **`reasonix setup` wizard**: API key → preset pick → MCP multi-select
  → per-server args → `~/.reasonix/config.json`. `npx reasonix` with
  no args launches this on first run and drops into chat afterward.
- **Config-backed defaults**: `preset`, `mcp`, `session` persist across
  launches; CLI flags override; `--no-config` escape hatch.
- **Context gauge in StatsPanel** (NEW this release): `ctx 42k/131k
  (32%)` next to cache/cost. Turns yellow at 50%, red at 80%, adds a
  `· /compact` nudge at red.
- **`/compact` slash** (NEW this release): shrinks every oversized
  tool result in the log with a tighter 4k cap (configurable via
  `/compact `), rewrites the session file on disk. Reports
  `▸ compacted N tool result(s), saved M chars (~T tokens)`.
- **`/mcp` and `/setup` slashes**: inspect attached servers, point at
  the reconfigure command.

### Fixed — since 0.2.2

- `shellSplit` no longer mangles Windows paths outside quotes.
- Windows `--mcp "npx ..."` works via automatic `.cmd`/`.bat` resolution.
- `@modelcontextprotocol/server-fetch` and `server-sqlite` removed from
  the catalog (Python-only reference impls, not on npm).
- One broken MCP server no longer kills the chat — per-spec failures
  print `▸ MCP setup SKIPPED` and the session continues.
- Tool results capped at 32k chars by default (override via
  `bridgeMcpTools(client, { maxResultChars: N })`). Sessions from
  pre-alpha.6 clients auto-heal on load — `▸ session "X": healed N
  oversized tool result(s)…`.
- DeepSeek 400 `maximum context length` errors now decorate with
  actionable advice + pretty-printed token figure.

### Tests

- Suite: **276 passing** (was 224 at 0.2.2).
- New files this release: `tests/resolve.test.ts`, `tests/wizard.test.ts`,
  `tests/loop-error.test.ts`, `tests/mcp-sse.test.ts`.

### Breaking changes

None against a 0.2.2 user. The config schema grew, but missing fields
fall through to defaults. MCP-specific API additions (`McpSpec` is now
a discriminated union, `FlattenOptions`, `DEFAULT_MAX_RESULT_CHARS`)
are all new surface.

### Deprecated

None.

---

## [0.3.0-alpha.6] — 2026-04-21

**Headline:** A single oversized tool result (e.g. `read_file` on a big
file) used to silently poison a session — the 3 MB payload landed in
history and every subsequent turn 400'd with *"maximum context length
is 131072 tokens. However, you requested 929,452 tokens."* Fixed at
both ends: prevent it, and diagnose it.

### Fixed

- **MCP tool results are now capped at 32,000 chars by default.**
  Oversized results are sliced head + 1 KB tail and separated by a
  `[…truncated N chars…]` marker so the model still sees both ends
  (common case: error messages appended after a stack trace). Override
  via `bridgeMcpTools(client, { maxResultChars: N })`. Rationale: ~8k
  English tokens or ~16k CJK tokens — fits with headroom across 5–10
  tool calls even at the context limit.
- **Heal-on-load: poisoned sessions from older clients auto-repair.**
  On session resume, every tool-role message whose content exceeds the
  cap is truncated with the same head + tail policy. A stderr line
  `▸ session "X": healed N oversized tool result(s)…` names the scope
  of the repair. User and assistant messages are untouched — the
  conversation flow is preserved, only the bloat from a past
  `read_file` (etc.) shrinks. Without this, any session built with
  pre-alpha.6 clients would tip over the 131k-token limit *on the very
  first new prompt*, before the new 32k cap could matter.
- **`DeepSeek 400: maximum context length` errors now show actionable
  advice** instead of a raw JSON blob. The decorated message points at
  the heal-on-load behaviour, `/forget` (nuke the session file) and
  `/clear` (drop the display history), and pretty-prints the
  requested-token figure.

### Added

- `DEFAULT_MAX_RESULT_CHARS` (= 32,000) export for callers that want
  to raise or lower the cap programmatically.
- `truncateForModel(s, maxChars)` helper export — same head + tail
  policy, usable by non-MCP tool adapters that want the same protection.
- `FlattenOptions` type export (just `{ maxChars? }` today).
- `formatLoopError(err)` export — the error-decorator used by the loop,
  exposed so library callers get the same advice when catching errors
  outside the TUI.
- `healLoadedMessages(messages, maxChars)` export — the session-heal
  helper, exposed so library callers who build their own resume flows
  can apply the same policy.

### Tests (+9, suite 262→271)

- `tests/mcp.test.ts` (+3) — truncation with head + tail preserved,
  no-op below cap, end-to-end `bridgeMcpTools` dispatch capped by
  default.
- `tests/loop-error.test.ts` (+6 new file) — overflow annotation with
  token figure, non-overflow passthrough, overflow without a figure,
  heal-on-load truncating tool-role messages while leaving user and
  assistant messages intact, no-op when all messages fit, multi-hit
  healing across several oversized rows.

### Migration note

This is a silent behaviour change for any library user whose MCP tool
was counting on >32k-char results making it to the model verbatim. If
that's you, pass `maxResultChars: Infinity` (or a higher explicit
value) to `bridgeMcpTools`.

---

## [0.3.0-alpha.5] — 2026-04-21

**Headline:** `reasonix setup` replaces the CLI-flag maze. New users run
one command, pick from an arrow-key checklist, and every later launch
remembers what they chose. The `--mcp "name=npx -y @scope/pkg /path"`
syntax still works for scripts and power users — it's just no longer
the *only* way to turn MCP on.

### Added

- **`reasonix setup`** — interactive Ink wizard:
  1. Paste API key (skipped if already set via env or previous run)
  2. Pick a preset: `fast` / `smart` / `max` (bundles of model +
     harvest + branch budget — no more "what's the right model id?")
  3. Multi-select MCP servers from the curated catalog (space to
     toggle, enter to confirm). Per-server parameters (filesystem
     directory, sqlite path) are prompted inline.
  4. Review + save to `~/.reasonix/config.json`.
  Re-run any time to reconfigure — existing selections are pre-checked.
- **`reasonix` with no subcommand** — launches the wizard on first run,
  drops straight into chat afterwards using saved defaults. Designed
  so a brand-new user can `npx reasonix` and be chatting in 30s
  without reading `--help`.
- **`--preset `** on both `chat` and `run`. Picks the
  same bundles the wizard offers. Individual flags (`--model`,
  `--harvest`, `--branch`) still override when you want to be specific.
- **`--no-config`** escape hatch on `chat` and `run` — ignore
  `~/.reasonix/config.json` entirely (useful for CI, reproducing
  a bug report against default settings, or isolating shared boxes).
- **`/mcp` slash command** — shows the spec strings attached to the
  current session and the tool registry (handy mid-chat when you want
  to remember what a tool is called).
- **`/setup` slash command** — prints instructions to exit and re-run
  `reasonix setup`. Live reconfiguration mid-session is out of scope:
  changing the tool set would reset the byte-stable prefix and
  invalidate the cache-first guarantees that define Reasonix.

### Changed

- **`ReasonixConfig` schema** grows: `preset`, `mcp` (spec strings),
  `session`, `setupCompleted`. Previous configs (apiKey-only) still
  load; missing fields fall through to hardcoded defaults.
- `reasonix chat` / `reasonix run`: when a flag is not passed, the
  value comes from `~/.reasonix/config.json`. Explicit flags still
  win. `--no-config` short-circuits this.
- Slash handler signature: `handleSlash(cmd, args, loop, ctx?)` — the
  new `ctx` carries per-session state like `mcpSpecs`. Old callers
  that passed three args continue to compile.

### Tests (+21)

- `tests/resolve.test.ts` (+11) — precedence order: flag → --preset
  → config.preset → fast defaults; `--no-config`, `--no-session`,
  `--branch` cap and off cases.
- `tests/config.test.ts` (+2) — full `ReasonixConfig` round-trip,
  `session: null` interpreted as ephemeral.
- `tests/slash.test.ts` (+4) — `/mcp` empty + populated, `/setup`
  prints the reconfigure hint, help lists both.
- `tests/wizard.test.ts` (+4) — `buildSpec` → `parseMcpSpec`
  round-trip on filesystem / memory / spaces-in-path / unknown-entry
  degrade-gracefully.
- Suite: **262 passing** (was 241).

### Fixed

- **Catalog no longer lists Python-only servers.** `fetch` and `sqlite`
  reference MCP servers are distributed as `pip install
  mcp-server-fetch` / `mcp-server-sqlite`, not npm packages. They
  were in the catalog by mistake, which meant picking them in the
  wizard produced a spec that always 404'd on `npm install` when the
  child was spawned. Removed. The remaining five entries
  (`filesystem`, `memory`, `github`, `puppeteer`, `everything`) are
  verified-on-npm as of this release.
- **One broken MCP server no longer kills the whole chat/run.** Before:
  any spawn or initialize failure on any server called
  `process.exit(1)`, losing the session and the other working servers.
  Now: each failure prints a `▸ MCP setup SKIPPED` line pointing at
  `reasonix setup` and the session continues with whatever succeeded.

### Notes

- The wizard's Ink rendering is verified manually — unit-testing
  arrow-key handling would mean pulling in `ink-testing-library`
  (another dev dep) to exercise mechanically obvious `setState`
  calls. The pure data layer (what gets written to config.json) is
  tested end-to-end via `buildSpec → parseMcpSpec`.
- Existing `npm publish --tag alpha` users: if you published
  alpha.4 already, alpha.5 is a *pure additive* upgrade — config
  files written by alpha.4 continue to work; `setupCompleted: false`
  is assumed on migration so the wizard offers itself on first launch.

---

## [0.3.0-alpha.4] — 2026-04-21

**Headline:** MCP over HTTP+SSE. Bridge *remote* / hosted MCP servers,
not just local subprocesses. Pass a URL to `--mcp` and Reasonix opens
an SSE stream and POSTs JSON-RPC to the endpoint the server advertises.

### Added

- **`SseTransport`** (`src/mcp/sse.ts`) — 2024-11-05 HTTP+SSE wire:
  GET the SSE URL, wait for `event: endpoint`, POST every outgoing
  JSON-RPC frame to that URL, read responses off the SSE channel.
  Headers are passthrough, so `Authorization: Bearer ...` works for
  hosted servers behind auth.
- **`--mcp` now accepts URLs.** The parser routes anything starting
  with `http://` or `https://` to `SseTransport`; everything else is
  stdio as before. Both namespaced and anonymous forms work:
    ```
    reasonix chat --mcp "kb=https://mcp.example.com/sse"
    reasonix run  --mcp "http://127.0.0.1:9000/sse" --task "..."
    ```
- `McpSpec` is now a discriminated union:
  `{ transport: "stdio", command, args } | { transport: "sse", url }`.
  Callers who inspected `spec.command` / `spec.args` need to branch on
  `spec.transport` first — not a concern for `--mcp` CLI users.
- `src/index.ts` exports `SseTransport`, `SseTransportOptions`,
  `parseMcpSpec`, and the `McpSpec` union types.

### Tests

- `tests/mcp-sse.test.ts` (+4) — in-process `http.Server` fake that
  implements the SSE wire. Covers: relative-path endpoint resolution,
  absolute endpoint URLs, a full `McpClient.initialize` →
  `listTools` round-trip over SSE, and handshake-failure propagation.
- `parseMcpSpec` SSE cases (+4) — anonymous URL, namespaced URL,
  case-insensitive scheme, and `ws://` staying routed to stdio (no
  surprise detection beyond the two supported schemes).
- Suite: **241 passing** (was 233).

### Notes

- Still targeting MCP protocol `2024-11-05`. The 2025-03-26 spec's
  "Streamable HTTP" transport (single endpoint, no separate SSE GET)
  is a separate body of work — deferred until there's a server in
  the wild worth testing against.

---

## [0.3.0-alpha.3] — 2026-04-22

**Headline:** multi-server MCP + discovery command. Bridge two or more
MCP servers into one chat session, and stop guessing what servers exist
— `reasonix mcp list` prints a curated catalog with copy-paste commands.

### Added

- **Repeatable `--mcp`** — pass the flag multiple times to bridge
  multiple MCP servers into the same `ToolRegistry`. New spec syntax:
    `"name=cmd args..."`   → tools land namespaced as `name_toolname`
    `"cmd args..."`        → anonymous (tools keep native names)
  Example:
    ```
    reasonix chat \
      --mcp "fs=npx -y @modelcontextprotocol/server-filesystem /tmp/safe" \
      --mcp "mem=npx -y @modelcontextprotocol/server-memory"
    ```
  Tools show up as `fs_read_file`, `mem_set`, etc.
- **`reasonix mcp list`** — curated catalog of popular official MCP
  servers (filesystem / fetch / github / memory / sqlite / puppeteer /
  everything) with ready-to-paste `--mcp` commands. Hardcoded because
  the list changes slowly; fetching over the network would make it
  flaky offline. `--json` prints the machine-readable form.
- `src/mcp/spec.ts::parseMcpSpec` — small helper exposed if library
  callers want the same `name=cmd` parsing. Not exported from the
  barrel yet; can be promoted when there's demand.
- `src/mcp/catalog.ts::MCP_CATALOG` — the curated list.

### Fixed

- **`shellSplit` mangled Windows paths outside quotes.** Backslashes
  were being treated as POSIX escape chars, so `C:\path\to\dir` turned
  into `C:pathtodir`. Now backslashes only escape inside double
  quotes; outside, they pass through literally. Matches user
  expectation on Windows; POSIX users who want escape-a-space should
  quote the arg instead.

### Tests

- `parseMcpSpec` (+8) — name=cmd form, anonymous form, Windows drive
  letters (must not look like namespace), identifier edge cases,
  empty / malformed input.
- Multi-server integration test (+1) — spawn two demo subprocesses
  concurrently with different prefixes, dispatch to each, verify no
  cross-talk.
- `shellSplit` Windows-path behavior (+1).
- Suite: **233 passing** (was 224).

---

## [0.3.0-alpha.2] — 2026-04-22

**Headline:** Windows `--mcp` actually works now, plus a second live
data point through the *official* `@modelcontextprotocol/server-filesystem`.

### Fixed

- **Windows `npx`/`pnpm` MCP launch**. `StdioTransport` now defaults to
  `shell: true` on win32 so `.cmd` shims (npx.cmd, pnpm.cmd) resolve.
  Previously `--mcp "npx -y ..."` failed with EPIPE on Windows because
  `spawn("npx")` couldn't find `npx.cmd` without a shell. POSIX behavior
  unchanged.
- **Silenced Node's `DEP0190` deprecation warning.** Under `shell: true`
  with an args array, Node concatenates args without quoting — unsafe
  if any arg contains shell metacharacters. We now build a quoted
  command line ourselves (command bare so PATH lookup works, args
  platform-quoted) and pass it as a single string. No more warning on
  `--mcp` runs.

### Added

- **`StdioTransportOptions.shell?: boolean`** — explicit opt-in/out of
  shell-mode spawning. Platform default still wins when omitted.
- **Second reference transcript** —
  `benchmarks/tau-bench/transcripts/mcp-filesystem.jsonl`. Live run
  through `@modelcontextprotocol/server-filesystem` (14 external tools,
  code we don't control): **5 turns, 4 tool calls, cache 96.7%,
  cost $0.00124, 97% cheaper than Claude** at equivalent tokens. The
  run includes a deliberate permission-denied recovery to show
  cache-first holds under realistic agent messiness.
- README table now shows both MCP data points side-by-side (bundled
  demo vs official external server).

### Tests

- Integration tests explicitly set `shell: false` (they spawn `node.exe`
  by absolute path — no shim needed). Suite still 224/224.

---

## [0.3.0-alpha.1] — 2026-04-22

**Headline:** MCP client lands. Any
[Model Context Protocol](https://spec.modelcontextprotocol.io/) server's
tools now flow through the Cache-First Loop automatically — cache-hit and
repair benefits extend to the entire MCP ecosystem.

Verified end-to-end on live DeepSeek: `reasonix run --mcp "..."` spawns an
MCP server, bridges its tools, calls them from the model. The follow-up
turn after the tool call hit **96.6% cache**, 94% cheaper than Claude at
same token counts. Reference transcript committed at
`benchmarks/tau-bench/transcripts/mcp-demo.add.jsonl`.

### Added

- **`reasonix chat --mcp ""`** and **`reasonix run --mcp ""`** —
  spawn an MCP server and bridge its tools into the Cache-First Loop.
  Shell-quoted command; use `--mcp-prefix` to namespace tool names when
  mixing servers.
- **Hand-rolled MCP client** (`src/mcp/`) — zero runtime deps. JSON-RPC
  2.0 + MCP initialize / tools/list / tools/call over stdio NDJSON.
  Official `@modelcontextprotocol/sdk` deliberately not used; see
  `src/mcp/README.md` for the reasoning.
- **`bridgeMcpTools(client)`** — walk an MCP server's tools/list result
  and register each into a Reasonix `ToolRegistry`. MCP tools become
  indistinguishable from native tools to the loop, inheriting
  Cache-First + repair (scavenge / flatten / storm) automatically.
- **Bundled demo MCP server** — `examples/mcp-server-demo.ts`, ~160
  lines, zero deps. Exposes `echo` / `add` / `get_time`. Lets any user
  try the whole integration locally with no external install.
- **`shellSplit()`** — small shell-style command parser used by the
  `--mcp` flag. Respects single/double quotes, backslash escapes,
  tab-space runs. Throws on unterminated quotes.
- Library exports: `McpClient`, `StdioTransport`, `bridgeMcpTools`,
  `flattenMcpResult`, `MCP_PROTOCOL_VERSION`, and related types.

### Tests

- **+21 tests**:
  - `tests/mcp.test.ts` (10) — in-process fake transport covering
    handshake, list, call, errors, bridge, name prefixing, result
    flattening.
  - `tests/mcp-shell-split.test.ts` (9) — quote handling, escapes,
    unterminated-quote error, whitespace-only input.
  - `tests/mcp-integration.test.ts` (2) — real subprocess against
    the bundled demo server via `node --import tsx …` (cross-platform,
    avoids Windows `.cmd` resolution).
- Suite: **224 passing** (was 203 at v0.2.2).

### Known limits (next alpha)

- No SSE transport — stdio only.
- No resources / prompts methods — tool-use only.
- No progress notifications — tool calls are assumed complete on first
  response.
- No streaming tool results.

### Also in this release

- **harvest-bench 18-run data + findings** (no release on its own —
  data was illuminating, conclusion was "V3 is strong enough that
  harvest doesn't differentiate on common math", see
  `benchmarks/harvest/report.md`). Informed the decision to ship MCP as
  the v0.3 headline rather than a harvest-accuracy claim.
- **`--timeout` flag** on harvest-bench runner, default 300s. Fixes
  120s-default client timeout on long R1 + harvest runs.

---

## [0.2.2] — 2026-04-21

**Headline:** 48-run bench data (3 repeats × 8 tasks × 2 modes). Reasonix
now scores **100% pass rate (24/24)** against 96% baseline; cache-hit
delta holds at **+47.7pp** with variance well under the last single-run
numbers.

### Fixed

- **t05 predicate relaxed** (`benchmarks/tau-bench/tasks.ts`). The task
  required "no refund on a processing order" and formerly also required
  status to stay `processing`, penalizing an agent who offered
  cancellation as a helpful alternative. The new predicate passes iff
  no refund row is written AND the order ends in `{processing, cancelled}`
  — either refusal or helpful substitution counts. Cancellation was
  marking reasonix as fail on its single run in v0.1; with this fix
  reasonix now passes every refusal task in every repeat.

### Changed

- **README headline numbers updated** to the 48-run set. Baseline shows
  one failure out of 24 (a `t07_wrong_identity` run where baseline
  skipped identity verification); Reasonix held the guardrail on every
  run.
- **`benchmarks/tau-bench/report.md`** regenerated from the 48-run
  results. Cost estimate vs Claude Sonnet 4.6 stays at ~96% cheaper
  per task.
- **`benchmarks/tau-bench/results.json`** replaced with the 48-run data.

### Tests

- +3 tests pinning the three t05 outcomes (refuse / cancel / illegally
  refund). Suite: **172 passing** (was 169).

---

## [0.2.1] — 2026-04-21

**Headline:** v0.2 grows eyes. `reasonix replay` and `reasonix diff` now
open interactive Ink TUIs by default. The stdout paths still work when
piped, so CI / `less` / markdown-export workflows aren't disturbed.

### Added

- **Interactive `reasonix replay `** — Ink TUI with
  per-turn navigation (`j`/`k`/space/arrows, `g`/`G` for jump-to-edge,
  `q` to quit). Sidebar re-renders cumulative cost / cache / prefix
  stability as the cursor moves, so "how did the cache hit rate climb
  over the conversation?" is answered visually instead of in
  aggregate.
- **Interactive `reasonix diff  `** — split-pane Ink TUI. Both
  sides scroll together; `n` / `N` jump the cursor to the next / prev
  divergent turn (the whole point of a diff tool). Cursor defaults to
  the first divergence so you skip the "identical setup turns".
- **Shared `RecordView` component** (`src/cli/ui/RecordView.tsx`)
  used by both TUIs — consistent visual grammar (user cyan, assistant
  green with cache badge, tool yellow, error red). Replaces the
  inline renderer in `ReplayApp`.
- **Pure navigation helpers** in `src/diff.ts`:
  `findNextDivergence(pairs, fromIdx)` and
  `findPrevDivergence(pairs, fromIdx)`. Unit-testable without Ink.
  Both guard against out-of-bounds `fromIdx`.
- **Pure replay nav helpers** in `src/replay.ts`:
  `groupRecordsByTurn(records)` and `computeCumulativeStats(pages, upToIdx)`.
  Used by the TUI sidebar; also individually testable.
- **New CLI flags** on both commands:
  - `reasonix replay --print` — force stdout pretty-print (auto when
    stdout isn't a TTY, or when `--head` / `--tail` is passed).
  - `reasonix diff --print` — force stdout table.
  - `reasonix diff --tui` — force Ink TUI even when piped (rare
    escape hatch).

### Changed

- **`reasonix replay` default** is now the TUI. Old stdout behavior
  reachable via `--print` or by piping. Non-TTY detection
  automatically flips to stdout mode, so shell pipelines behave as
  they did in 0.2.0.
- **`reasonix diff` default** picks itself from context:
  - `--md ` → write markdown + print summary (unchanged).
  - `--print` or piped stdout → stdout summary table.
  - TTY, no `--md`, no `--print` → TUI.

### Tests

- +10 new tests (`replay.test.ts` +6: `groupRecordsByTurn` +
  `computeCumulativeStats`; `diff.test.ts` +4: divergence navigation).
  Suite: **169 passing** (was 159).

---

## [0.2.0] — 2026-04-21

**Headline:** v0.2 makes the v0.1 cache-hit claim *auditable*. Any reader
can now verify the 94.3% / −42% numbers from committed JSONL transcripts
— no API key required.

### Added

- **`reasonix replay `** — pretty-print a past transcript and
  rebuild its full session summary (turns, tool calls, cache hit, cost,
  prefix stability) offline. No API calls.
- **`reasonix diff  `** — compare two transcripts: aggregate deltas,
  first divergence (with Levenshtein similarity for text + exact match
  for tool-name / args), prefix-stability story. Optional `--md `
  writes a blog-ready markdown report.
- **`benchmarks/tau-bench/transcripts/`** — committed reference transcripts
  (baseline + reasonix on `t01_address_happy`) so anyone can clone the
  repo and run `reasonix replay` / `diff` immediately, without running
  the bench.
- **Bench runner gains `--transcripts-dir `** — emits one JSONL
  per `(task, mode, repeat)` tuple for replay/diff.
- New library exports: `computeReplayStats`, `replayFromFile`,
  `diffTranscripts`, `renderDiffSummary`, `renderDiffMarkdown`,
  `parseTranscript`, `recordFromLoopEvent`, `writeRecord`.

### Changed

- **Transcript format bumped (backward-compatible)**. Records now carry
  `usage`, `cost`, `model`, `prefixHash` (reasonix only), and `toolArgs`.
  All fields optional on read — v0.1 transcripts still parse (cost/cache
  shown as n/a). A `_meta` line at the top records source/model/task
  metadata.
- **Baseline bench runner now emits per-sub-call transcripts**. Previously
  wrote one aggregated record per user turn, which made diff's
  apples-to-apples "model calls" count off. Now both modes emit at the
  same granularity.
- **Diff rendering label change**: "turns (assistant)" → "model calls",
  with "user turns" as a separate row in the summary table. Removes the
  ambiguity that hit when comparing baseline vs reasonix.
- **Top-level README**: `validated numbers` table now shows the 16-run
  τ-bench-lite results (94.3% cache, −42% cost) and links to the
  committed reference transcripts.
- **Exposed `LoopEvent.toolArgs`** so transcript writers can persist
  *what* the model sent to each tool, not just the result.

### Fixed

- Windows-only entrypoint bug in the bench runner
  (`import.meta.url === file://${argv[1]}`) — replaced with
  `pathToFileURL(argv[1]).href` so `main()` actually runs on Windows.

### Tests

- 17 new tests across `transcript.test.ts` (3), `replay.test.ts` (3),
  and `diff.test.ts` (11). Total suite: 159 passing.

---

## [0.1.0] — 2026-04-21

**Headline:** first reproducible evidence for Pillar 1 (Cache-First Loop).

### Added

- **`benchmarks/tau-bench/`** — τ-bench-lite harness. 8 retail-flavored
  multi-turn tool-use tasks with a DeepSeek V3 user simulator,
  deterministic DB-end-state success predicates (no LLM judge), and a
  cache-hostile naive baseline runner. Schema mirrors Sierra's τ-bench
  so upstream tasks can drop in.
- **`benchmarks/tau-bench/runner.ts`** — orchestrator with
  `--task` / `--mode` / `--repeats` / `--dry` / `--verbose` flags.
- **`benchmarks/tau-bench/report.ts`** — renders results JSON into a
  blog-ready markdown summary with explicit scope caveats.
- **Live bench numbers** published in `benchmarks/tau-bench/report.md`:
  - cache hit: baseline 43.9% → reasonix **94.3%** (+50.3pp)
  - cost/task: baseline $0.00278 → reasonix **$0.00162** (−42%)
  - vs Claude Sonnet 4.6 (token-count estimate): **~96% cheaper**
  - pass rate: 100% (baseline) vs 88% (reasonix; 1 predicate too strict,
    documented)

### Tests

- 8 new tests in `tests/benchmarks.test.ts` covering DB isolation,
  check-predicate satisfiability, and tool guards — all runnable without
  an API key. Total suite at this release: 143 passing.

---

Earlier `0.0.x` versions covered Pillar 1 + Pillar 3 internals, retry
layer, first-run API key prompt, harvest MVP, self-consistency
branching, and session persistence. They're not reflected as individual
entries above because the `0.1.0` bench harness is what first produced
*externally verifiable* evidence for their value.

[0.3.0-alpha.3]: https://github.com/esengine/reasonix/releases/tag/v0.3.0-alpha.3
[0.3.0-alpha.2]: https://github.com/esengine/reasonix/releases/tag/v0.3.0-alpha.2
[0.3.0-alpha.1]: https://github.com/esengine/reasonix/releases/tag/v0.3.0-alpha.1
[0.2.2]: https://github.com/esengine/reasonix/releases/tag/v0.2.2
[0.2.1]: https://github.com/esengine/reasonix/releases/tag/v0.2.1
[0.2.0]: https://github.com/esengine/reasonix/releases/tag/v0.2.0
[0.1.0]: https://github.com/esengine/reasonix/releases/tag/v0.1.0