From 37e29fb05821630a98dc068d946ad0188c9216cc Mon Sep 17 00:00:00 2001 From: aakashpawar1999 Date: Mon, 17 Aug 2026 08:20:26 +0530 Subject: [PATCH 1/2] chore: gitignore .ultraship planning state UltraShip's tree is tool-written maintainer state, not a contributor-facing surface, and no established project ships one. A .gitignore entry alone cannot take effect on already-tracked files, so the previously committed files are removed from the index here. Nothing is deleted from disk and history is unchanged; only the evidence directory was ignored before. --- .gitignore | 6 +- .ultraship/checkpoints/.gitkeep | 0 .../2026-08-16T15-20-18Z-io-cli-0.1.0.yaml | 150 ---- .../20260817T020313Z-io-cli-0.2.0.yaml | 33 - .ultraship/decisions/.gitkeep | 0 .ultraship/iterations/.gitkeep | 0 .../iterations/US-IO-CLI-0.1.0-I01.yaml | 106 --- .ultraship/products/.gitkeep | 0 .../io-cli/execution/archive/0.1.0.yaml | 33 - .../io-cli/execution/archive/0.1.1.yaml | 30 - .../io-cli/execution/archive/0.2.0.yaml | 42 -- .../products/io-cli/execution/tasks.yaml | 577 --------------- .ultraship/products/io-cli/lifecycle.yaml | 4 - .ultraship/products/io-cli/product.yaml | 225 ------ .../products/io-cli/releases/0.1.0.yaml | 663 ------------------ .../products/io-cli/releases/0.1.1.yaml | 375 ---------- .../products/io-cli/releases/0.2.0.yaml | 639 ----------------- .ultraship/products/io-cli/roadmap.yaml | 117 ---- .ultraship/questions/.gitkeep | 0 .ultraship/releases.lock | 4 - .ultraship/ultraship.yaml | 20 - .ultraship/views/.gitkeep | 0 .ultraship/views/active-releases.md | 10 - .ultraship/views/master-roadmap.md | 23 - .ultraship/views/release-history.md | 87 --- .ultraship/views/workspace-brief.md | 21 - .ultraship/workspace.yaml | 12 - 27 files changed, 4 insertions(+), 3173 deletions(-) delete mode 100644 .ultraship/checkpoints/.gitkeep delete mode 100644 .ultraship/checkpoints/2026-08-16T15-20-18Z-io-cli-0.1.0.yaml delete mode 100644 .ultraship/checkpoints/20260817T020313Z-io-cli-0.2.0.yaml delete mode 100644 .ultraship/decisions/.gitkeep delete mode 100644 .ultraship/iterations/.gitkeep delete mode 100644 .ultraship/iterations/US-IO-CLI-0.1.0-I01.yaml delete mode 100644 .ultraship/products/.gitkeep delete mode 100644 .ultraship/products/io-cli/execution/archive/0.1.0.yaml delete mode 100644 .ultraship/products/io-cli/execution/archive/0.1.1.yaml delete mode 100644 .ultraship/products/io-cli/execution/archive/0.2.0.yaml delete mode 100644 .ultraship/products/io-cli/execution/tasks.yaml delete mode 100644 .ultraship/products/io-cli/lifecycle.yaml delete mode 100644 .ultraship/products/io-cli/product.yaml delete mode 100644 .ultraship/products/io-cli/releases/0.1.0.yaml delete mode 100644 .ultraship/products/io-cli/releases/0.1.1.yaml delete mode 100644 .ultraship/products/io-cli/releases/0.2.0.yaml delete mode 100644 .ultraship/products/io-cli/roadmap.yaml delete mode 100644 .ultraship/questions/.gitkeep delete mode 100644 .ultraship/releases.lock delete mode 100644 .ultraship/ultraship.yaml delete mode 100644 .ultraship/views/.gitkeep delete mode 100644 .ultraship/views/active-releases.md delete mode 100644 .ultraship/views/master-roadmap.md delete mode 100644 .ultraship/views/release-history.md delete mode 100644 .ultraship/views/workspace-brief.md delete mode 100644 .ultraship/workspace.yaml diff --git a/.gitignore b/.gitignore index 9bcaa9e..3cfa106 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,7 @@ node_modules/ .DS_Store .env -# UltraShip: transient deploy/publish hook output -.ultraship/products/*/evidence/ +# UltraShip: local planning state. Release contracts, iterations, checkpoints and +# gate evidence are the maintainer's working notes, written by tooling rather +# than by hand. They are not a contributor-facing surface and are never committed. +.ultraship/ diff --git a/.ultraship/checkpoints/.gitkeep b/.ultraship/checkpoints/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.ultraship/checkpoints/2026-08-16T15-20-18Z-io-cli-0.1.0.yaml b/.ultraship/checkpoints/2026-08-16T15-20-18Z-io-cli-0.1.0.yaml deleted file mode 100644 index 0d15d48..0000000 --- a/.ultraship/checkpoints/2026-08-16T15-20-18Z-io-cli-0.1.0.yaml +++ /dev/null @@ -1,150 +0,0 @@ -product: io-cli -version: 0.1.0 -timestamp: 2026-08-16T15:20:18Z -reason: task-complete -repository_state: passing -completed_acceptance: - - > - **F2 — the wizard writes what it showed.** A test drives the wizard over the `TestBackend` with - a scripted key sequence and asserts the resulting file parses under - `io_harness::Config::from_toml`, that `provider_spec()` returns the chosen variant with the - chosen model, and that the file's mode is `0600` on unix. Sabotage: write the file before the - confirmation screen, under which only F2 fails. - - > - **F3 — a bad key does not get past the wizard.** With a provider endpoint that rejects the - credential, the wizard returns to the credential step carrying the provider's own message, and - **no file is written**. Sabotage: treat any non-transport error as success, under which only F3 - fails. - - > - **F4 — finished content reaches scrollback, not the viewport.** After a scripted session, the - captured byte stream contains every finished message's text outside the viewport region, and the - viewport buffer at rest contains only the composer and the status line. Sabotage: render a - finished message into the viewport instead of committing it, under which only F4 fails. - - > - **F5 — no alternate screen and no mouse capture, ever.** Over the same captured byte stream, the - sequences `ESC[?1049h`, `ESC[?1047h`, `ESC[?1000h`, `ESC[?1002h` and `ESC[?1003h` do not appear. - This is the release's structural criterion and it is written so that no later release can - reintroduce fullscreen without failing it. Sabotage: call `EnterAlternateScreen`, under which - only F5 fails. - - > - **F6 — an interrupt stops the turn and keeps the session.** A scripted `Ctrl+C` during a - streaming turn causes `Steer::interrupt`, the turn ends, the partial output stays committed in - scrollback, and the composer accepts the next prompt. A second `Ctrl+C` at an idle composer - exits. Sabotage: drop the interrupt on the floor, under which only F6 fails. - - > - **F7 — the forbidden subsystems are absent.** A test over `Cargo.toml` asserts the dependency - set contains no HTTP client, no TLS stack, no SQL or database crate, and no sandboxing crate, - and a grep asserts the source contains no `async fn` that loops over provider responses. The - only path to a model is through `io_harness`. Sabotage: add `reqwest` as a direct dependency, - under which only F7 fails. This is the criterion that keeps the archived fork from growing back. - - > - **F8 — every event kind is accounted for.** A test enumerates the event kinds io-harness emits - and asserts the renderer's match is exhaustive with no wildcard that silently discards. An event - this release does not style renders as a muted line; none is dropped. Sabotage: add a catch-all - arm that returns without rendering, under which only F8 fails. - - > - **F9 — 80×24 is supported.** The renderer harness runs the scripted session at 80×24 and asserts - no panel is truncated mid-word, the composer remains usable, and the status line degrades to its - narrow form rather than wrapping. - - > - **N1 — io-cli contains no second harness.** `cargo tree --depth 1` is recorded in the release - evidence and reviewed: `io-harness`, `ratatui`, `crossterm`, `tui-textarea`, `clap`, `tokio`, - `serde`/`toml`, and nothing that constitutes a runtime, a client or a store. Any addition beyond - that list is argued in the release record or it does not ship. - - > - **N2 — the terminal is always restored.** Panic, `Ctrl+C` at any point, and a kill signal each - leave the terminal in cooked mode with the cursor visible and the scrollback intact. Asserted by - a test that panics inside a turn and inspects the restore path, and by manual check for the - signal case. - - > - **N3 — no full-screen clear during a session.** The captured byte stream contains no `ESC[2J` - after startup. This is the flicker criterion, and it is asserted structurally because flicker - itself cannot be asserted on a CI runner. - - > - **N4 — no timing assertion anywhere in this release's tests.** Startup cost and streaming - smoothness are **recorded as measurements** in the release evidence, not gated. This line of - repositories has paid for wall-clock assertions on CI runners more than for anything else. - - > - **N5 — the credential never reaches the screen, the scrollback, a log line or the trace.** The - captured byte stream from the F2 scripted wizard is searched for the test key and must not - contain it. Sabotage: echo the key in the confirmation screen, under which only N5 fails. - - > - **N6 — `NO_COLOR` produces a usable session.** With `NO_COLOR=1` the scripted session runs and - every state that colour distinguishes — success, warning, error, refusal — also carries a word. - - > - **N7 — an installer never runs an unverified binary.** Both scripts compute the artifact's - SHA-256 and compare it against the published `SHA256SUMS` before unpacking, and **abort non-zero - on a mismatch without leaving anything on disk**. Asserted by running each installer against a - deliberately corrupted artifact and checking both the exit status and that the target directory - is untouched. This is the one place in the product where a failure is a security failure rather - than an inconvenience, and `curl | sh` is a trust-the-publisher model regardless — the checksum - defends against a truncated download and a tampered asset, not against a compromised repository, - and the README says so rather than implying more. - - > - **N8 — an install needs no administrator rights.** Neither script writes outside the user's own - directories and neither invokes `sudo`. Asserted by running each as an unprivileged user with no - write access to any system path. - - > - **O2 — the binary builds in release profile.** `cargo build --release` is run and the binary - executed. CI builds the test profile only, so a release-profile break is invisible to it — this - has bitten this line of repositories before and is now a standing gate. -remaining_acceptance: - - > - **F1 — the live first run.** From an empty `$IO_CONFIG_HOME`, in a real repository, against a - real provider: the wizard is completed, the key is verified by a live call, the file is written, - a task is typed, the answer streams, a file is edited inside the sandbox, `Ctrl+C` interrupts a - second turn mid-flight, and `Ctrl+D` exits. Afterwards, in the same terminal, the transcript is - searched with the terminal's own search and a passage is selected with the mouse and pasted - elsewhere. Manual, recorded, and the recording is the release evidence. A fixture cannot prove - this; only a live run can. - - > - **O1 — CI green on macOS, Linux and Windows**, on the feature branch and again on the merge to - `develop`. Windows is not optional: it is where a terminal renderer's assumptions break, and it - is where this project has historically found what the other two hid. - - > - **O3 — the branch flow is followed exactly.** `feat/0.1.0` off `develop`, one pull request into - `develop`, merged with the PR button. `develop` to `main` by pull request. Nothing is pushed - directly to `develop` or `main` at any point. The tag `v0.1.0` is pushed on its own after the - merge — pushing the tag is what cuts the GitHub Release; creating the Release by hand ships it - with no assets. - - > - **O4 — the repository's own protections are in place or their absence is recorded.** The - rulesets that make O3 mechanical rather than voluntary require the repository to be public or on - a paid plan; see the open question. Until then O3 is a discipline, and that fact is stated in - the release record rather than left implied. - - > - **O5 — every artifact in the matrix exists and every installer works on a clean machine.** The - Release carries all four artifacts plus `SHA256SUMS`. `install.sh` is then run on a clean macOS - machine and in a clean Linux container, and `install.ps1` on a clean Windows machine, each - followed by running `io --version` **in a new shell** — which is what catches a script that - installed correctly and told the user nothing useful about `PATH`. An installer verified only on - the machine that built it is not verified. - - > - **O6 — the Release is cut by pushing the tag, and nothing else.** `gh release create` is never - used: it produces a Release with no assets. The workflow the tag triggers is what attaches them, - and the Release title is the bare form this line of repositories uses. -active_hypotheses: - - "F1 cannot be satisfied from this session and the remaining work is the owner's: no tty is - available here, script(1) refuses with \"tcgetattr/ioctl: Operation not supported on socket\", - and a bare pty has no terminal emulator behind it to answer the cursor-position query the inline - viewport needs. The halves of F1 that do not need a terminal were proved live instead — see - US-IO-CLI-0.1.0-T19." - - O1 is green on the feature branch across macOS, Linux and Windows, and needs one more green run - on the merge into develop, which the merge itself triggers. - - "O4 is answerable now rather than open: the repository is already public, so the branch rulesets - that would make \"nothing without a pull request\" mechanical are available. Whether to - configure them is the owner's call, and the release record states which it was." - - The contract's open question about a fifth target is still open and should stay open until the - x86 musl row has actually built on a Release, which is the first time it runs. - - io-harness has no turn entry point that takes both a caller's TaskContract and a steer inbox, so - [sandbox] limits, [run] budgets and AGENTS.md instructions are read by the harness and not - applied to an io-cli turn. Nothing in the 0.1.0 contract promised them, so this is not a scope - change — but it is the natural first item for a harness release and the owner should decide - whether it is one. -next_task: US-IO-CLI-0.1.0-T19 -required_context: - - .ultraship/products/io-cli/releases/0.1.0.yaml - - .ultraship/products/io-cli/execution/tasks.yaml - - .ultraship/products/io-cli/evidence/0.1.0/ - - tests/live.rs - - README.md diff --git a/.ultraship/checkpoints/20260817T020313Z-io-cli-0.2.0.yaml b/.ultraship/checkpoints/20260817T020313Z-io-cli-0.2.0.yaml deleted file mode 100644 index 9d0ff96..0000000 --- a/.ultraship/checkpoints/20260817T020313Z-io-cli-0.2.0.yaml +++ /dev/null @@ -1,33 +0,0 @@ -product: io-cli -version: 0.2.0 -timestamp: 2026-08-17T02:03:13Z -reason: blocker -repository_state: passing -completed_acceptance: - - > - F1, F2, F3, F4, F5, F6, F7, F8, F9 — every functional criterion but F10, each with a sabotage - arm run and each arm failing only its own criterion's tests. - - > - N1, N2, N3, N4, N5 — no test sleeps or reads a clock; the dependency set is the same eight - names plus tempfile; every structural property of 0.1.0 still holds; io-cli still contains no - policy engine; every new surface has a defined form at eighty columns. - - > - O1, first half — CI green on macOS, Linux and Windows on feat/0.2.0 at afcfb7c (PR #9), all - eight checks, no re-runs. - - O2 — cargo build --release exit 0; ./target/release/io --version prints io 0.2.0. - - O3, in progress — feat/0.2.0 is cut from develop and PR #9 is open into it. -remaining_acceptance: - - > - F10 — the owner runs the release binary under *ask before writes*, approves one write, denies - another, and says whether the overlay reads right. It cannot be delegated to a test: what this - release ships is a moment, and only a person can say whether a run stopping to ask reads as - clear or as alarming. - - O1, second half — CI green again on the merge to develop. - - O3 — develop to main by pull request, then the tag v0.2.0 pushed alone. - - O4 — the Release carries the same four artifacts and SHA256SUMS. -active_hypotheses: [] -next_task: US-IO-CLI-0.2.0-T10 -required_context: - - .ultraship/products/io-cli/releases/0.2.0.yaml - - .ultraship/products/io-cli/execution/tasks.yaml - - src/approval.rs diff --git a/.ultraship/decisions/.gitkeep b/.ultraship/decisions/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.ultraship/iterations/.gitkeep b/.ultraship/iterations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.ultraship/iterations/US-IO-CLI-0.1.0-I01.yaml b/.ultraship/iterations/US-IO-CLI-0.1.0-I01.yaml deleted file mode 100644 index 169f1ca..0000000 --- a/.ultraship/iterations/US-IO-CLI-0.1.0-I01.yaml +++ /dev/null @@ -1,106 +0,0 @@ -id: US-IO-CLI-0.1.0-I01 -timestamp: 2026-08-16T13:41:43Z -category: - - product-level - - release-level -trigger: > - The owner asked how io-cli would be distributed — a crates.io publish or GitHub Releases only. - Checking the registry before answering found that the crate name is already taken, by us, with a - published version history that reaches into the range this roadmap had just allocated. -evidence: - - > - `https://crates.io/api/v1/crates/io-cli` returns a crate owned by `aakashpawar1999` (Aakash - Pawar) whose `repository` field is `https://github.com/initorigin/io-cli` — the same repository - this workspace plans. It was created 2026-07-25 and last updated 2026-08-07. - - > - It carries **17 published versions — 0.1.0 through 0.14.0** — and **all 17 are yanked** - (`max_version` is reported as `0.0.0`, which is what crates.io returns when nothing is live). - Total downloads 281. - - > - A yank removes a version from resolution; it does not delete it and it does not free the version - number. `cargo publish` of `io-cli 0.1.0` would therefore be rejected as already uploaded, and so - would every number up to 0.14.0 — the entire range this roadmap allocates for 0.1.0 through - 0.9.0. - - > - The crate's published description still opens "`io-engine` runs an agent against the project you - are standing in", a naming that predates even the archived fork. Anything published under this - name inherits that page's history. - - > - Separately: `cargo install io-cli` requires a Rust toolchain on the user's machine and compiles - io-harness and its dependency tree from source. For a terminal application distributed to people - who may not have Rust, that is the slowest of the available install paths, not the primary one. -before: - assumptions: - - > - Distribution was an open question, with `delivery.target_mode: release-ready` chosen as the - cautious default and the crates.io decision deferred to the owner. - - > - `product.yaml` stated the deployment context as "Installed from crates.io or a release binary". - scope: - - > - The 0.1.0 contract described a tag cutting a GitHub Release but never said what assets that - Release would carry. There was no release-engineering scope in the release at all. - implementation: > - No installer, no cross-compilation matrix, no checksums, no defined set of target triples. -change: - summary: > - Distribution is **prebuilt binaries attached to the GitHub Release, installed by a `curl` shell - script on unix and a PowerShell script on Windows**. io-cli is not published to crates.io, not - published to Homebrew, and is not installable with `cargo install`. Because nothing is published - to the registry, the burned 0.1.0–0.14.0 range is irrelevant and the roadmap's numbering is - unaffected. - added: - - > - Release engineering enters 0.1.0's scope: a cross-compilation matrix producing four artifacts, - a `SHA256SUMS` file published with them, `install.sh`, and `install.ps1`. - - > - A new operational acceptance criterion requiring each installer to be exercised on a clean - machine per platform, and a non-functional criterion requiring checksum verification. - removed: - - > - The crates.io publish step, and with it the "does 0.1.0 publish to crates.io" open question, - which is now decided rather than open. - deferred: - - > - Homebrew, a Scoop or WinGet manifest, and `cargo install` support. Destination: recorded as - product `non_goals` — deliberately, since "for now" here means "not part of this product's - plan", and anything that changes it is a fresh iteration with its own record. - - > - Linux `aarch64`, and any macOS or Windows ARM target beyond `aarch64-apple-darwin`. - Destination: `open_questions` on the 0.1.0 contract, to be resolved when the matrix is first - built rather than guessed at now. - replaced: - - > - `product.yaml`'s `deployment_context` sentence "Installed from crates.io or a release binary" - is replaced with the installer path, because it is now factually wrong. - - > - `product.yaml`'s `public_contract` gains the installer scripts and the artifact naming, which - become part of what may be depended upon: a user's install command must keep working. -impact: - products: - - io-cli - versions: - - 0.1.0 - tasks: [] - dependencies: - - > - GitHub Releases becomes a hard distribution dependency rather than a convenience — it is now - the only channel. This is recorded on the 0.1.0 contract's `dependencies.services`. - resource_effect: > - Adds a cross-compilation matrix and two installer scripts to 0.1.0, which is real work and is not - free. It removes the registry publish, its irreversibility, and the renumbering that publishing - would have forced on all nine roadmap entries. Net: more work in this release, materially less - risk across the product. -versioning: - previous_target: 0.1.0 - new_target: 0.1.0 - consequence: no-version-change -approval: - source: user - reference: > - Owner, in session 2026-08-16, asked whether these would be crate-published or GitHub Releases - only, was shown the burned version range and the four distribution options, and answered: - "Want installations with curl and powershell only for now." -canonical_updates: - - products/io-cli/product.yaml - - products/io-cli/releases/0.1.0.yaml diff --git a/.ultraship/products/.gitkeep b/.ultraship/products/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.ultraship/products/io-cli/execution/archive/0.1.0.yaml b/.ultraship/products/io-cli/execution/archive/0.1.0.yaml deleted file mode 100644 index 61d0073..0000000 --- a/.ultraship/products/io-cli/execution/archive/0.1.0.yaml +++ /dev/null @@ -1,33 +0,0 @@ -product: io-cli -version: 0.1.0 -execution_state: DEVELOPING -checkpoint: null -blockers: [] -release_fit: - assessment: probable - reasons: - - The workspace records no time, token or monetary budget at all — `resource_profile.available` - is null on every field — so nothing caps this release except the work itself, and the contract - already carries a fallback scope for the case where it does. - - "The workspace records `preferences.minimize_cost: true` and `allow_parallel_agents: false`, - so this is a sequential build with no fan-out. That makes the schedule long rather than - uncertain: the tasks are ordered by dependency and each one is independently verifiable." - - Everything the release renders already exists in io-harness 0.60.1 — `Session::turn_steered` - already takes both an observer and a steer inbox, `ProviderSpec` already enumerates the four - provider variants the wizard offers, and `Config` already owns discovery, layering and the - `[app.]` section. No harness change is currently known to be required. - - The repository is empty — one commit, a licence and an empty README — so there is no existing - code, no existing test suite and no migration to work around. - major_cost_drivers: - - "The inline renderer: committing finished content to scrollback under a live viewport, and - getting resize and over-wide content right. Unverified in our own code and named the largest - technical risk in the contract." - - Windows, where a terminal renderer meets different key reporting, resize events and ANSI - support. - - The cross-compile matrix, and `x86_64-unknown-linux-musl` in particular, because io-harness - carries `rusqlite` and its bundled C SQLite needs a musl toolchain in the job. - - Verifying the installers on clean machines, which O5 deliberately makes unsatisfiable from the - build machine. - - Two new test harnesses — TestBackend rendering and byte-stream capture — without the second of - which F5 and N3 cannot be asserted at all. - recommended_scope_change: null diff --git a/.ultraship/products/io-cli/execution/archive/0.1.1.yaml b/.ultraship/products/io-cli/execution/archive/0.1.1.yaml deleted file mode 100644 index 77a7ba3..0000000 --- a/.ultraship/products/io-cli/execution/archive/0.1.1.yaml +++ /dev/null @@ -1,30 +0,0 @@ -product: io-cli -version: 0.1.1 -execution_state: DEVELOPING -checkpoint: null -blockers: [] -release_fit: - assessment: high - reasons: - - "The workspace records `preferences.minimize_cost: true` and `allow_parallel_agents: false`, - so this is a sequential build with no fan-out. Five small tasks against an existing suite fit - that limit comfortably: the release adds one seam, one field and one line format, and every - one of them is asserted in the harnesses 0.1.0 already built." - - The workspace records no time, token or monetary budget — `resource_profile.available` is null - on every field — so nothing caps this release except the work itself, and the contract carries - a fallback scope that drops the step rendering and then the spinner if it does. - - "The workspace constraint that io-cli contains no agent loop, no provider client and no - session store is not touched by this release. Everything here is rendering: a timer, a field - and a line order. No io-harness change is required and none is anticipated." - - The code this release changes is four files totalling under a thousand lines — `src/app.rs`, - `src/status.rs`, `src/events.rs` and the driver's select loop — and each already has a test - file pointed at it. - major_cost_drivers: - - The clock seam. The work is not the timer; it is making the tick assertable without asserting - on time, which is what keeps N1 true. Getting that seam wrong is the way this release grows a - sleeping test. - - Not repainting when idle. The easy implementation ticks always, and F2 exists because that is - the version that would ship by accident. - - "F3, which cannot be automated: a real turn against a live provider, watched by the owner, and - a screenshot recaptured from it." - recommended_scope_change: null diff --git a/.ultraship/products/io-cli/execution/archive/0.2.0.yaml b/.ultraship/products/io-cli/execution/archive/0.2.0.yaml deleted file mode 100644 index ba2634f..0000000 --- a/.ultraship/products/io-cli/execution/archive/0.2.0.yaml +++ /dev/null @@ -1,42 +0,0 @@ -product: io-cli -version: 0.2.0 -execution_state: DEVELOPING -checkpoint: null -blockers: [] -release_fit: - assessment: probable - reasons: - - "The workspace records `preferences.minimize_cost: true` and `allow_parallel_agents: false`, - so this is a sequential build with no fan-out. That limit is the reason the tasks below are - ordered as one vertical path rather than as a wide set: the approver seam, then the overlay - that renders on it, then the answers that resolve it, then the fields around it." - - The workspace records no time, token or monetary budget — `resource_profile.available` is null - on every field — so nothing caps this release except the work itself, and the contract carries - a fallback scope that drops the status fields and then the posture key if it does. - - "`probable` rather than `high`, and the reason is one task. Every release so far has been a - one-way render of an event that arrived; this one adds a callback that must ask a human and - block the agent's own task until it is answered. That is the first bidirectional seam in the - product and the first place it can deadlock. The other eight tasks are the shape of work this - repository has done twice." - - "The workspace constraint that io-cli contains no agent loop, no provider client and no policy - engine is not touched. Every value this release constructs — `Decision`, `Rule`, `Policy` — is - an io-harness type, and every verdict is still the harness's. No io-harness change is - required: the approver seam, the approval context carrying rule and layer, and the `Refused`, - `Contained`, `Compacted` and `Step` events all exist in the locked version, which was read - from the source rather than assumed." - - The code this release changes is the four files a session is drawn from — `src/app.rs`, - `src/status.rs`, `src/events.rs`, `src/picker.rs` — plus the driver, and each already has a - test file pointed at it. The new surface adds one module and one test file. - major_cost_drivers: - - The two-way seam. The approver runs on the run's own task and must ask, wait and answer while - the keyboard stays live so `Ctrl+C` still reaches the turn. F4 is the criterion that keeps it - honest, and it is asserted on a closed channel rather than on a timeout. - - A four-row viewport. An approval that shows a proposed file's content does not fit in it, and - what goes in the overlay versus what commits above it is a design decision the contract left - open on purpose. - - "`Decision::Approve { remember }` is run-scoped and dies with the turn. The obvious - implementation of *allow for the rest of this session* passes every visible test and asks - again on the next prompt; F5 exists because that is the version that would ship by accident." - - "F10, which cannot be automated: a real turn against a live provider under *ask before - writes*, with one write approved and one denied, watched by the owner." - recommended_scope_change: null diff --git a/.ultraship/products/io-cli/execution/tasks.yaml b/.ultraship/products/io-cli/execution/tasks.yaml deleted file mode 100644 index 05f74b1..0000000 --- a/.ultraship/products/io-cli/execution/tasks.yaml +++ /dev/null @@ -1,577 +0,0 @@ -product: io-cli -version: 0.2.0 -tasks: - - id: US-IO-CLI-0.2.0-T01 - summary: Build the approver seam — a request out, a decision back, and a denial when nobody answers. - why_required: "Every other task in this release renders or answers an approval, and none of them - can exist until an approval can reach the interface at all. It is also the one place the - product can deadlock: the approver runs on the agent's own task, so the release is not correct - unless a dropped answer denies rather than blocks." - status: done - depends_on: [] - acceptance_criteria: - - > - **F4 — an unanswered request denies rather than hangs.** Dropping the - interface end of the answer channel resolves the request as a denial. No - clock, no timeout, no sleep: the assertion is on what the approver returns - when the channel is closed. Sabotage: block on the receiver, under which only - F4 fails — and it fails by never finishing, which is why the arm is run under - the suite's own harness rather than by hand. - files: - - src/approval.rs - - src/lib.rs - - tests/approval.rs - evidence: - - "`Asker` is the approver and `Ask` is the question, with the way back inside it. Answering - consumes the `Ask`, so there is no way to answer twice and no way to hold one open by - accident — dropping it *is* the denial, which makes F4's behaviour a property of the type - rather than a rule somebody has to remember." - - "`cargo test --test approval` — 4 passed, 0 failed. Two of them are the denial: one drops the - whole receiver (the interface is gone), one takes the question and drops it (the interface - went away mid-question). No clock, no timeout, no sleep anywhere in the file." - - "Sabotage A2 — the unanswered path approves instead of denying: 2 failed, and they are - exactly the two denial tests. The other two passed, so the arm failed only its own criterion." - - "Sabotage A3, the arm the contract names — block on the receiver (`std::future::pending()` - on a closed channel): the suite never finished and had to be killed by a watchdog at 45 - seconds. It fails by not terminating, which is what a deadlock looks like and why the - watchdog is in the shell rather than in a test. N1 is untouched: no test measures anything." - - "Sabotage A1 was a finding rather than a failure. An early return on `send` error was - written first; removing it failed no test, because a failed `send` returns the `Ask` and - drops it, which closes the oneshot inside it — so both ways of losing the interface already - arrive at one `unwrap_or_else`. The branch was a second spelling of the same line and was - deleted, with the reasoning left in a comment where the next reader will look for it." - - "The question carries what the overlay needs: act, target, content, rule, layer and goal. A - fourth test asserts each, because `EventKind::ApprovalRequested` carries only act and target - — if the rule and the layer do not arrive on this seam they cannot be rendered at all." - - "`cargo fmt --all -- --check` exit 0; `cargo clippy --all-targets -- -D warnings` exit 0; - `cargo test --test timing --test dependencies` — 9 passed, so the new test file neither - sleeps nor reads a clock, and the eight-name dependency set is unchanged." - - - id: US-IO-CLI-0.2.0-T02 - summary: Render the question as a viewport overlay that commits nothing to scrollback. - why_required: "An approval that is committed to history can be scrolled away from a run that is - blocked on it, which is the failure the overlay exists to prevent. This is the structural - property the whole surface rests on." - status: done - depends_on: - - US-IO-CLI-0.2.0-T01 - acceptance_criteria: - - > - **F1 — an approval request opens an overlay and commits nothing to - history.** With a request delivered, the viewport holds the question and the - scrollback gains nothing at all. Sabotage: render the request as a committed - line, under which only F1 fails. This is the structural property the whole - surface rests on — a question in the scrollback is a question that can be - scrolled away from a run that is blocked on it. - - > - **F2 — the overlay states the act, then the target, then the rule and the - layer, in that order.** Asserted over the positions of the facts in the - rendered lines, never over their presence. Sabotage: put the rule first, - under which only F2 fails. - files: - - src/approval.rs - - src/picker.rs - - src/app.rs - - tests/approval.rs - evidence: - - "`Approval` owns the `Ask`, so the run stays paused for exactly as long as the overlay - exists and not one moment longer. `App::finished` drops it, which denies — the run it - belonged to has already ended, so there is nobody left to tell." - - "`cargo test --test approval` — 8 passed, 0 failed. F1 asserts twice: nothing is queued for - scrollback while a question is open, and the recorder's byte stream carries no committed - line for it either. F2 asserts by position, with a closure that looks up each fact's index - and compares them; a `contains` assertion would pass with the sentence inside out." - - "Sabotage F1 — commit a line when the question opens: 1 failed, and it is F1's test alone. - Sabotage F2 — move the rule and the layer to the front of the line: 1 failed, and it is F2's - ordering test alone. Neither arm touched the other's test, and the tier-default test stayed - green under both." - - "**The answers are one row, not a vertical `Picker` list, and the contract's scope sentence - assumed the latter.** A session viewport is four rows and ratatui fixes it at construction. - Title plus three list rows fills all four and leaves nothing for the target, the rule, the - layer or the content — the facts the overlay exists to show. So the layout is: the question - on row one, the proposed content in the rows between, and `› y allow once · a allow this - session · n deny` on the last. Nothing the contract promised was dropped; the overlay shows - strictly more than a picker could have. It still reads as one product: the same `›` marker, - the same ` · ` separator, the same `Tone` palette, and `picker::fit` — now public — is the - one fitting rule for a shortened path, a shortened row and a shortened line of content." - - "The content preview says what it cut (`⋯ 37 more lines`). A reader who believes they have - seen a whole file and has seen four lines of it is worse off than one who was told, and this - is the surface where that belief authorises a write." - - "The tier-default case is rendered as a sentence — `no rule named it: the tier default - decided` — rather than as a blank. io-harness's own documentation is explicit that an - unnamed action in the grey tier is the least vouched-for kind, so silence there would tell - the reader the opposite of what happened. Its own test." - - "`Ctrl+C` while a question is open interrupts the turn rather than answering it, which - settles one of the contract's open questions. The question is then denied as a consequence - of the turn ending — `App::finished` drops the overlay — rather than by giving one key two - meanings. Everything else on the keyboard goes to the overlay while it is up, because the - run is stopped and there is nothing to type at." - - "`cargo test --test picker --test scrollback --test narrow --test timing` — 14 passed, so - making `fit` public changed no picker behaviour, the scrollback discipline is intact, and no - clock or sleep entered the suite. `cargo clippy --all-targets -- -D warnings` exit 0." - - - id: US-IO-CLI-0.2.0-T03 - summary: Bind the three answers, commit one line per decision, and hand the harness a real approver. - why_required: "This is the task that closes the dead end 0.1.0 and 0.1.1 both shipped: until the - driver stops passing `DenyAll`, the *ask before writes* posture declines everything and the - overlay has nothing to open on." - status: done - depends_on: - - US-IO-CLI-0.2.0-T02 - acceptance_criteria: - - > - **F3 — each answer resolves the request as itself, and commits exactly one - line.** Allow once, allow for the rest of the session and deny each produce - the matching `io_harness::Decision`, and each closes the overlay and commits - one line naming the act, the target and the decision. Sabotage: map every key - to allow, under which only F3 fails. - files: - - src/approval.rs - - src/app.rs - - src/main.rs - - src/settings.rs - - tests/approval.rs - evidence: - - "`DenyAll` is gone from the driver. `turn` now builds an `approval::channel()` alongside the - steer channel and the observer bridge, and a new arm in the select loop opens the overlay - when a question arrives. The loop keeps turning while the run is stopped inside - `decide_in_context`, which is what leaves `Ctrl+C` reachable with a question on screen." - - "`cargo test --test approval` — 11 passed, 0 failed. F3 runs all three answers through the - same body: press the key, assert the overlay closed, assert the `io_harness::Decision` the - run was actually given, and assert exactly one line was committed and that it names the act - and the target. `Once` is asserted to remember *nothing*, which is the half a looser test - would miss." - - "The `Session` answer hands the run one rule — `Act::Write` on `src/main.rs`, the target as - written rather than a widened glob. A bare filename is matched against a basename too, so - `main.rs` as a pattern would have allowed that name anywhere in the tree." - - "Sabotage F3 — every character key answers `Once`: 2 failed, both of them F3's. The second - is the keyboard-capture test, which fails for the same reason and belongs to the same - criterion; nothing outside F3 moved." - - "The other way in is its own test: three `Right`s and `Enter` reaches `deny`, and the third - `Right` is clamped rather than wrapped. A surface that wraps from deny back to allow is one - where holding an arrow key approves a write. The overlay opens on `Once`, the least - committal answer, so `Enter` by reflex gives away the least." - - "A question takes the keyboard while it is up: two letters typed at an open overlay leave - the composer empty. The run is stopped, so a keystroke reaching the composer would be one - whose effect the operator never sees." - - "The wizard's own words are now true. The *ask before writes* posture described itself as - `declined until the approval surface lands` through two releases, because the approver was - `DenyAll`; it now reads `read freely; a write or a command stops and asks you first`." - - "`cargo test` — the whole suite, 121 passed, 0 failed, 4 ignored (`tests/live.rs`, ignored by - design). `cargo clippy --all-targets -- -D warnings` exit 0." - - - id: US-IO-CLI-0.2.0-T04 - summary: Carry a remembered allow into the Policy the next turn runs under. - why_required: "`Decision::Approve { remember }` installs a run-scoped layer that dies with the - turn. Without this task, *allow for the rest of this session* asks again on the next prompt and - reads as a defect in memory rather than as a scope this release chose." - status: done - depends_on: - - US-IO-CLI-0.2.0-T03 - acceptance_criteria: - - > - **F5 — *allow for the rest of this session* survives the turn it was given - in.** Asserted on the `Policy` handed to the *next* turn: it carries a rule - that the previous answer produced. The harness's own `remember` is - run-scoped and dies with the turn, so an implementation that uses only it - passes F3 and fails F5 — which is the whole reason this is its own criterion. - Sabotage: pass `remember` and keep nothing, under which only F5 fails. - files: - - src/approval.rs - - src/main.rs - - tests/approval.rs - evidence: - - "`approval::effective_policy(base, remembered)` is io-harness's own recipe rather than a - second one: a permissive layer named `remembered` carrying the rules, merged onto the base. - It was copied from `run.rs`, where the harness applies `remember` for the rest of a run, so - the semantics across a turn boundary are the same ones as within a turn." - - "The driver rebuilds it before every turn from `App::remembered()`. With nothing remembered - it returns the session's policy unchanged, asserted as equality rather than assumed." - - "`cargo test --test approval` — 14 passed, 0 failed. F5 is asserted as a *verdict*, not as a - label: the base policy answers `Ask` for a write to `src/main.rs`, and after the operator - answers `allow this session` the next turn's policy answers `Allow`. A rule that is carried - but never consulted would pass a label check and fail this one." - - "Narrowness has its own assertion: the same next-turn policy still answers `Ask` for - `src/other.rs`. Saying yes to one file is not saying yes to writing." - - "**A remembered allow cannot defeat a deny**, and that is its own test against the case that - matters — `.env`, which the harness's `builtin-secrets` layer denies. Feeding an explicit - `Allow` rule for `.env` through `effective_policy` still yields `Deny`. This is the property - that makes it safe for io-cli to construct rules at all." - - "Sabotage F5 — hand the harness `remember` and keep nothing on this side: 1 failed, F5's - test alone. F3's `Session` case stayed green, which is exactly what the contract predicted - and the reason F5 exists as a separate criterion: the obvious implementation passes every - visible test and asks again on the next prompt." - - "`cargo test` — 23 test binaries, all ok, 0 failures. `cargo clippy --all-targets -- -D - warnings` exit 0." - - - id: US-IO-CLI-0.2.0-T05 - summary: Cycle the posture on Shift+Tab, name the layer in force, and run the next turn under it. - why_required: "The roadmap's outcome is that the operator can *see and steer* the boundary. The - overlay is the answering half; this is the choosing half, and without the status field naming - the layer the key would be a mode indicator that indicates nothing." - status: done - depends_on: - - US-IO-CLI-0.2.0-T04 - acceptance_criteria: - - > - **F6 — `Shift+Tab` cycles the posture and the status line names the layer in - force.** Both spellings a terminal may send — `BackTab`, and `Tab` with the - shift modifier — reach the same action, or the key works in one class of - terminal and silently does nothing in the other. Sabotage: cycle without - updating the field, under which only F6 fails. - - > - **F7 — the cycled posture is what the next turn actually runs under.** - Asserted on the `io_harness::Policy` handed to the turn, not on the label - beside it. A mode indicator not backed by the thing it names is invisible to - any assertion on a rendered line. Sabotage: update the label only, under - which only F7 fails. - files: - - src/app.rs - - src/status.rs - - src/settings.rs - - src/commands.rs - - src/main.rs - - README.md - - tests/commands.rs - - tests/status.rs - - tests/policy.rs - evidence: - - "`Shift+Tab` reaches `App::cycle_posture` from BOTH spellings a terminal can send — - `KeyCode::BackTab` with no modifier, and `KeyCode::Tab` with shift, which is what a terminal - that has negotiated the Kitty keyboard protocol sends instead. Its own test, because binding - one alone ships a key that works on the developer's terminal and does nothing on somebody - else's, silently." - - "The status line's second field is `policy:workspace` / `policy:ask-writes` / - `policy:read-only` — second because what the agent is allowed to do outranks how long it has - been doing it, and second-to-last to be dropped when the terminal is narrow." - - "`approval::session_policy(base, posture, remembered)` replaces the policy's *tier defaults* - and nothing else, then layers the remembered allows on top. That is what makes a keystroke - safe here: a layer that denies a secret is not a default, so `no_posture_can_unlock_what_a_ - layer_denied` passes for all three postures against `.env`." - - "`cargo test --test policy` — 7 passed, 0 failed. F7 is asserted as a verdict on the policy - (`Allow`, then `Ask`, then `Deny` for a write to `src/main.rs` as the key is pressed), never - on the word beside it." - - "Sabotage F6 — cycle the posture without updating the field: 1 failed, F6's status test - alone; the F7 policy test stayed green, which is precisely the split the two criteria exist - to catch. Sabotage F7 — update the label and ignore the posture when building the policy: - 1 failed, F7's test alone, with every F6 test green." - - "A configuration file whose policy is none of the three reads `policy:custom` rather than - being labelled as one it is not; io-harness's own file can express far more than three - postures. The first press of the key then moves to a posture the operator did choose. - `Posture::of` is asserted exact in both directions." - - "Cycling changes this session, like `/theme` and `/model`, and takes effect on the next turn - because io-harness takes a `&Policy` per turn and offers no way to change one mid-flight. - The committed line says which posture and what it means, so the key teaches itself." - - "The key table grew two rows — `Shift+Tab` and `y / a / n` — in `commands.rs`, which is the - one list the README and `/help` both render. `tests/commands.rs` had to be updated in the - same commit because it asserts the count, which is the gate working rather than failing." - - "`cargo test` — 24 test binaries, all ok, 0 failures. `cargo fmt --all -- --check` exit 0; - `cargo clippy --all-targets -- -D warnings` exit 0." - - - id: US-IO-CLI-0.2.0-T06 - summary: Give a refusal its own cell — act, target, rule, layer, and the tier default said plainly. - why_required: "This is the product's third success measure written as a test, and the two facts - no competing core records. 0.1.0 shipped a plain notice as a placeholder and named this release - as the one that replaces it." - status: done - depends_on: - - US-IO-CLI-0.2.0-T05 - acceptance_criteria: - - > - **F8 — a refusal renders act, target, rule and layer, in that order, and an - absent rule reads as the tier default rather than as silence.** Asserted by - position, and asserted twice: once with a named rule and layer, once with - neither, because in io-harness `None` means the tier default decided and that - is a different sentence, not a missing one. Sabotage: drop the layer, under - which only F8 fails. - files: - - src/events.rs - - tests/events.rs - evidence: - - "A refusal now reads `write /etc/hosts · rule fs.deny · layer ops-baseline`, separated by - the same ` · ` the status line and the step line use. Act, target, rule, layer — and the - last two are the facts no other terminal agent can print." - - "`cargo test --test events` — 15 passed, 0 failed. The order is asserted by position with a - lookup closure, never by `contains`." - - "Three cases, three tests, because io-harness has three: a named rule in a named layer; a - rule with no layer; and neither, which means the *tier default* decided. The third renders - `no rule named it: the tier default decided` rather than a blank — the harness's own - documentation is explicit that an unnamed action in the grey tier is the least - vouched-for kind, so silence there would tell the reader the opposite of what happened." - - "Sabotage F8 — drop the layer from the line: 2 failed, F8's ordering test and the - pre-existing every-styled-kind test, which also asserts the layer. Both belong to this - criterion; nothing outside it moved." - - "`ApprovalRequested` stopped claiming what it used to. It said the release `answers from the - configured posture`, which was `DenyAll`; it now says `waiting for you` and stays one thin - line, because the event carries only the act and the target — the rule, the layer and the - content reach the overlay on the approver seam instead." - - "`ApprovalDecided` gained an arm of its own. It is io-harness's record of what it was told, - which is not the same line as io-cli's own; they agree because the answer travelled one way, - and if they ever disagree this is where it shows." - - "Two pre-existing tests pinned 0.1.1's wording — that every awaiting-help names `io setup` — - and 0.2.0 changes what the way out *is*, because an approval is now answered on screen. They - were widened to assert the property rather than the sentence: each help must name something - the operator can actually do (`io setup`, `Shift+Tab`, or answering in the next prompt). - `awaiting_answer` is still a dead end and still says so, because a question about intent - needs a responder on a caller-supplied contract — the entry point that would cost `Ctrl+C`." - - "`cargo test` — 24 test binaries, all ok. `cargo clippy --all-targets -- -D warnings` exit 0." - - - id: US-IO-CLI-0.2.0-T07 - summary: Fill the status line's remaining fields, each absent until something supplies it. - why_required: "The status line is where this product's differentiation is meant to be visible at - a glance, and it has carried three fields and four named placeholders since 0.1.0. A field that - invents a value it did not receive would be worse than the placeholder." - status: done - depends_on: - - US-IO-CLI-0.2.0-T06 - acceptance_criteria: - - > - **F9 — a status field with nothing behind it is absent, not zero.** Before - any `Contained` event the line carries no containment field; after one it - carries the mode and the backend together. The same for context pressure and - the token count. Sabotage: render a zero for an unknown value, under which - only F9 fails. A field that invents its own value is worse than no field. - files: - - src/status.rs - - src/app.rs - - tests/status.rs - evidence: - - "Three fields, each `Option` and each set only by the event that carries its fact: - `tokens` from `Step` (and from `Finished`, which is authoritative), `context` from - `Compacted`, `containment` from `Contained`. Nothing initialises to a default, so a fact - that has not arrived renders as nothing at all." - - "`cargo test --test status` — 12 passed, 0 failed. F9's own test asserts a fresh line - carries no `tok`, no `ctx` and no containment." - - "Sabotage F9 — initialise the three to `Some(0)`, `Some(0)` and `Some(\"unknown\")`: 3 - failed, all three of them status tests, including the narrow-form one, which is the same - criterion's other half. The rest of the suite was untouched." - - "**The elapsed field stays fourth and the new ones go to its right**, so the field 0.1.1 - exists for is not the first thing a narrow terminal drops. Asserted at forty columns: the - line stays one row, keeps the model, and has lost the containment field." - - "Containment renders as `workspace-write/seatbelt` — the mode asked for *and* the backend - that answered on this host. io-harness's own documentation is explicit that showing the mode - alone is reading an intention: `workspace-write` reaching a portable floor means resource - caps and nothing else. Its own test, against exactly that pair." - - "Context pressure's denominator is `io_harness::ContextBudget::default().effective_tokens - (None)` — asked of the harness rather than copied here. A `24_000` written into this - repository would be wrong after some harness patch, and wrong silently." - - "**The field is quiet by design and that is recorded rather than hidden.** `Compacted` is - the only event carrying an observation-section size, so between folds nothing on the stream - knows it and the field is absent. The per-step estimate is durable in the store as - `ContextEvent::est_tokens`; the upgrade path is one store read and is marked with a - `ponytail:` comment where the field is declared. The contract left this decision to the - first rendering against a real session, and this is it." - - "The token field is the session's running total rather than the last step's own count — a - field that swings rather than climbs cannot be read at a glance — and it is formatted - `12.4k`, because six digits are six columns of a line that must fit in eighty." - - "`cargo test` — 24 test binaries, all ok. `cargo clippy --all-targets -- -D warnings` exit - 0 (one collapsible-if moved into a match guard, which reads better anyway: a run reporting - no usage reports `0`, and overwriting a real total with it would turn a known number into a - wrong one). `cargo fmt --all -- --check` exit 0." - - - id: US-IO-CLI-0.2.0-T08 - summary: Give every new surface a defined form at eighty columns. - why_required: "`product.yaml` fixes 80x24 as a supported size rather than a degraded one. An - overlay carrying four facts and a file's content is the widest thing this product has drawn, - and the status line now has three more fields to drop." - status: done - depends_on: - - US-IO-CLI-0.2.0-T07 - acceptance_criteria: - - > - **N5 — every new surface has a defined form at eighty columns**, and the - status line still drops whole fields from the right rather than wrapping, - with three more fields to drop. `tests/narrow.rs` covers the line; the - overlay joins it, including the case where the proposed content is wider than - the terminal. - files: - - src/approval.rs - - tests/narrow.rs - evidence: - - "**This task found a real defect, and it is the reason the criterion exists.** The first - version of the narrow test passed while the overlay was silently broken: the rule and the - layer sat at the end of the question line, ratatui clipped the row at eighty columns rather - than complaining, and the two facts this release exists to show simply were not there. The - assertion `every line is at most eighty columns` was green *because* they had been cut." - - "The fix is layout, not truncation: the rule and the layer have a row of their own, so they - cannot be the part that goes. The target is what gets shortened — it is the only part whose - middle a reader can infer, and the act cannot be shortened at all." - - "That squeezed the content to one row in a four-row viewport, which exposed the second - defect: the `⋯ 40 more lines` marker took the only content row, so the overlay showed a - reader how many lines they were approving and not one of the lines. The count now rides the - end of the last shown line instead of taking a row." - - "`cargo test --test narrow` — 4 passed. The new case is a seventy-character path, a - two-hundred-character content line, a long glob and a named layer, at exactly eighty - columns: every row fits, the answers survive, the path is fitted with `…`, and - `ops-baseline` is still on screen." - - "`cargo test` — 24 test binaries, all ok. `cargo clippy --all-targets -- -D warnings` exit - 0; `cargo fmt --all -- --check` exit 0." - - "The status line's own narrow form was asserted with T07's fields, in `tests/status.rs`: - at forty columns the line stays one row, keeps the model, and has dropped the containment - field from the right." - - - id: US-IO-CLI-0.2.0-T09 - summary: Bring the documented surfaces and the structural gates up to what the release now is. - why_required: "The README's key table, `/help` and the CHANGELOG are checked mechanically against - the code, and the README has told every reader since 0.1.0 that the approval overlay is 0.2.0's. - The structural gates are what stop a release that adds an overlay from quietly adding a - dependency, a clock or a full-screen clear." - status: done - depends_on: - - US-IO-CLI-0.2.0-T08 - acceptance_criteria: - - > - **N1 — no timing assertion anywhere, a third time.** `tests/timing.rs` still - passes: no test sleeps, no test reads a clock, and `src/main.rs` is still the - only module that does. The approval path is the shape most likely to be - tested with a timeout, and F4 exists so that it is not. - - > - **N2 — the dependency set does not grow.** `cargo tree --depth 1` is still - the eight names 0.1.0's N1 permits, plus `tempfile` as a dev-dependency. An - approval channel is `tokio::sync::oneshot`, which is already here. - - > - **N3 — every structural property of 0.1.0 still holds.** No alternate - screen, no mouse capture, no full-screen clear, the terminal always restored, - the credential never rendered. The existing tests carry these, and an overlay - that shows a file's proposed content is exactly the kind of change that could - put a secret on screen — the credential test is not incidental here. - - > - **N4 — io-cli still contains no policy engine.** `tests/dependencies.rs` - still passes. This release renders a `Policy`, constructs `io_harness::Rule` - values from a human's answer, and relays that answer. It evaluates no rule - and decides no permission; every verdict is still the harness's. - - > - **O2 — the binary builds in release profile and runs.** Standing gate; CI - builds the test profile only. - files: - - README.md - - CHANGELOG.md - - Cargo.toml - - Cargo.lock - - tests/docs.rs - evidence: - - "`Cargo.toml` and `Cargo.lock` at `0.2.0`; `./target/release/io --version` prints `io - 0.2.0`. **O2** — `cargo build --release` exit 0, which CI does not cover because it builds - the test profile only." - - "The CHANGELOG's `0.2.0` section carries an `Added`, a `Changed` and a **`Not in this - release`** block. The last one names the spend field and why it is absent, the diff surface - and why the overlay shows content instead, and the two `Decision` shapes left unbound. A - reader deciding whether to upgrade learns what is missing from the changelog rather than - from a surprise." - - "The README's `What this release is not` no longer says the approval overlay is 0.2.0's - future work, and no longer ends `an action that needs approval is declined in this release` - — a sentence that has been true since 0.1.0 and is now false. It states the two absences - plainly instead." - - "The configuration section now says what `Shift+Tab` does to the policy it describes, and - that a cycled posture is not written back: a keystroke that rewrites a permission boundary - on disk is the opposite of what that key is for." - - "**N2** — `cargo tree --depth 1` is the same eight names 0.1.0's N1 permits (clap, - crossterm, io-harness, ratatui, serde, tokio, toml, tui-textarea) plus `tempfile` as a - dev-dependency. The approval channel is `tokio::sync::oneshot`, which was already here." - - "**N1** — `cargo test --test timing` passes: no test in the repository sleeps or reads a - clock, and `src/main.rs` is still the only module that reads one. The approval path was the - shape most likely to be tested with a timeout, and F4 is asserted on a closed channel." - - "**N3** — the whole existing structural suite passes unchanged: no alternate screen, no - mouse capture, no full-screen clear, the terminal always restored, the credential never - rendered. The last matters more this release than any before it, because the overlay is the - first surface that puts a file's contents on screen." - - "**N4** — `cargo test --test dependencies` passes. Every value this release constructs is - an io-harness type (`Decision`, `Rule`, `Policy`, `Defaults`) and every verdict is still the - harness's; io-cli evaluates no rule." - - "**The harness pin stays at 0.60.1, deliberately.** 0.60.2 and 0.60.3 are published and - their public API is identical, so nothing here needs them. Moving the pin would put an - untested dependency change on a release that already changes the permission surface, and - `cargo update` doing it as a side effect is exactly what the contract's open question - existed to prevent." - - "`cargo test` — 24 test binaries, all ok, 0 failures. `cargo fmt --all -- --check` exit 0; - `cargo clippy --all-targets -- -D warnings` exit 0." - - - id: US-IO-CLI-0.2.0-T10 - summary: The owner runs a real turn under ask-before-writes, approving one write and denying another. - why_required: "What this release ships is a moment — a run stopping to ask, and a person - answering it. A test can assert that a decision reached the harness and cannot say whether the - overlay read as clear or as alarming. The same human gate 0.1.0 and 0.1.1 both carried." - status: done - depends_on: - - US-IO-CLI-0.2.0-T09 - acceptance_criteria: - - > - **F10 — the owner runs a real turn under *ask before writes*, approves one - write, denies another, and says the overlay reads right.** Manual and - recorded. The same human gate 0.1.0 and 0.1.1 put on their look, for the same - reason: what ships here is a moment, and only a person can say whether a run - stopping to ask reads as clear or as alarming. - - > - **O1 — CI green on macOS, Linux and Windows**, on the feature branch and - again on the merge to `develop`. - files: - - .ultraship/products/io-cli/execution/tasks.yaml - - tests/live.rs - evidence: - - "**A live rehearsal for F10 exists, and it does not satisfy F10.** Two `#[ignore]`d tests - in `tests/live.rs`, in the pattern 0.1.0 established for its own manual gate: they prove - everything about the moment that a terminal is not needed for, so the owner's one run is - not the first time any of it has been exercised. The first drives a real turn against a - real provider under *ask before writes*, asserts the run actually reaches the approver, - prints what the overlay would have asked (act, target, rule, layer, content size), answers - `allow once`, and asserts the edit then happened. The second denies instead and asserts the - file is untouched — an approver that is consulted but whose `no` is not enforced is worse - than no approver at all." - - "**The rehearsal was run against a live provider** (`anthropic/claude-sonnet-4`, - OpenRouter) after the owner placed a key in the gitignored `.env`. Three live tests, all - passing, and they change what the owner should expect from the manual gate." - - "**The approve path works end to end.** Under *ask before writes* a real turn reached the - approver, the overlay's facts arrived (`write greeting.txt`, 7 bytes of content), the - answer was acted on, and the file ended as `goodbye`. `EventKind::ApprovalRequested` was - emitted for the transcript." - - "**The deny path works, and the transcript reads well.** The file was untouched - (`hello\n`), each attempt rendered `refused: write greeting.txt`, the agent tried - `write_file`, then `edit_file`, then `patch_file`, then `git_status` — which produced a - genuine `Refused` event rendering `exec git · no rule named it: the tier default decided` — - and finally asked a question, ending `awaiting_answer` with the help line pointing at the - next prompt. That is the documented 0.7.0 dead end, reached exactly as designed." - - "**`rule` and `layer` are `None` in the ordinary case, not the exotic one.** Every live - question and the live refusal came through with no rule named, because the posture's own - `write: Ask` default is what asks and no glob named the action. So the tier-default sentence - is the common rendering, which makes F8's second case the one that matters most — a blank - there would have been what almost every operator saw." - - "**A fourth live test was added and it earns its place.** F5 asserts what outlives a turn, - because that half is io-cli's. This asserts the half io-harness owns: answering `allow this - session` collapsed **three questions into one** inside a single turn. Under *allow once* the - same edit asked three times, because an agent does not write a file once — it tries - `write_file`, then `edit_file`, then `patch_file`, and each is its own question. Without the - collapse, `a` would be a key with no visible effect." - - "**`Stalled { steps: 11 }` is the model, not this release.** Every approve-path run ended - stalled after the edit had already succeeded — and so did the never-asks workspace path that - 0.1.0 shipped, on the same prompt and the same step count. The approval flow does not cause - it." - - "**A pre-existing live test fails and 0.2.0 did not cause it.** - `live_f1_a_real_turn_streams_and_edits_a_file` asserts the turn streamed assistant tokens; - this model stalled without a closing message, so it streamed none. The test's own comment - records that fragility — a turn that is nothing but tool calls emits no assistant text, and - that is the model's choice rather than a property of this interface. It is `#[ignore]`d, CI - never runs it, and it belongs to 0.1.0's F1 rather than to any criterion here. Left alone - rather than reworded into passing." - - "**F10 — completed by the owner, 2026-08-17.** They ran the release binary at a real - terminal under *ask before writes*, approved a write, and gave the verdict: `working! - approved`. That is the gate: a person watched a run stop to ask and said the moment reads - right. Recorded in their words rather than paraphrased into something more formal." - - "**The deny half of F10's wording was proved by the live rehearsal rather than by the - owner's own run**, and the record says so rather than implying otherwise: - `live_f10_a_denial_leaves_the_file_alone` denied every request against a live provider and - asserted the file was untouched. The owner's verdict covers how the surface reads, which is - the half no test can reach." - - "**O1 — CI green on macOS, Linux and Windows**, on `feat/0.2.0` at afcfb7c, PR #9. All eight - checks pass: `build and test` on macos-latest (36s), ubuntu-latest (39s) and windows-latest - (1m39s), plus `fmt and clippy`, `msrv`, `CodeQL`, `Analyze (actions)` and `Analyze (rust)`. - No re-runs and no flakes. The second half of O1 — green again on the merge to `develop` — - is outstanding until that merge happens." - - "**F10 is outstanding and is the only thing between this release and its record.** It is a - human gate by construction: the owner runs the release binary under *ask before writes*, - approves one write, denies another, and says whether the overlay reads clear or alarming. - No test can answer that, which is why 0.1.0 and 0.1.1 both carried one." - - "Noted while the checks ran, and not introduced by this release: GitHub reports one **low** - Dependabot advisory on the default branch — `lru` `IterMut` violates Stacked Borrows — - reached through `ratatui 0.29`, which `Cargo.toml` pins deliberately because `tui-textarea - 0.7` is built against exactly that pair. It predates 0.2.0 and is a dependency decision for - a release of its own, not a change to make inside this one." diff --git a/.ultraship/products/io-cli/lifecycle.yaml b/.ultraship/products/io-cli/lifecycle.yaml deleted file mode 100644 index 619f868..0000000 --- a/.ultraship/products/io-cli/lifecycle.yaml +++ /dev/null @@ -1,4 +0,0 @@ -product: io-cli -state: RELEASED -resumes_to: null -blockers: [] diff --git a/.ultraship/products/io-cli/product.yaml b/.ultraship/products/io-cli/product.yaml deleted file mode 100644 index 1a5245d..0000000 --- a/.ultraship/products/io-cli/product.yaml +++ /dev/null @@ -1,225 +0,0 @@ -id: io-cli -name: IO CLI -classification: independent-product -vision: > - The terminal agent that shows you what it is allowed to do, what it is spending, and what it - refused — while it works. Every other terminal agent renders a chat log with tool cells, because a - chat log with tool cells is all its core can describe. io-harness records the permission boundary, - the spend ceiling and the agent tree as first-class facts, and io-cli is the interface that makes - them visible. -users: - - > - The initorigin author, as the first and most demanding user — running real work in real - repositories, on macOS and Linux, over SSH and inside tmux. - - > - (later) A developer who owns their own provider keys and wants an agent they can watch, stop, - and audit, rather than one they have to trust. -problems: - - > - The archived io-cli was a fork of a 1.2M-line, 88-crate agent product. Roughly sixty of those - crates — the loop, the providers, the sandbox, the policy engine, the tool set, the session store - — duplicate io-harness. Its terminal interface could not be extracted from it: the TUI crate - alone is 229k lines across 402 files and depends on 41 sibling crates. Whitelabeling a second - harness in order to attach the first one is work with no end. - - > - The category's most-complained-about decision is the alternate screen buffer. Agents that take - the full terminal destroy native scrollback, terminal search, tmux copy-mode and mouse selection, - duplicate their output on resize, and corrupt multi-byte text on copy. Users open issues titled - as regressions and vendors ship environment variables to undo it. - - > - Terminal agents built on React-for-the-terminal repaint far more than changed, and strobe while - streaming. The flicker is structural, not a bug to be fixed. - - > - Capturing the mouse to implement scrolling breaks the terminal's own text selection, and the - workarounds are terminal-specific modifier keys or a toggle that trades scrolling for copying. - - > - No terminal agent in the field is usable with a screen reader. They hide the cursor — the only - focus indicator — animate constantly, re-render unchanged content, and put metadata ahead of - content. Blind developers report the whole category as effectively unusable. - - > - A first run typically fails on a missing environment variable, or funnels the user into one - vendor's login. There is no guided path from "installed" to "working" for a developer bringing - their own key. - - > - A developer running an agent on real work cannot see the boundary it is operating under. When an - action is refused they are told that it was, not which rule refused it or which layer that rule - came from — because no other core records it. -outcomes: - - > - A developer installs io-cli, is walked through provider, credential, model, theme and permission - posture in one guided flow, and completes a real task in the same session. - - > - The conversation is in the terminal's own scrollback when the session ends. It can be scrolled, - searched, selected and copied with the terminal's own facilities, because io-cli never took them - away. - - > - At any moment the operator can see, in one line, which model is answering, which policy layer is - in force, how full the context is, what has been spent against the ceiling, and whether the run - is contained. - - > - A refusal names the act, the target, the rule and the layer. An approval stops the run in an - overlay that cannot scroll away, shows the diff, and records the decision in both the transcript - and the durable trace. - - > - The same run, unattended, emits the same events as newline-delimited JSON with no TUI code on the - path — so what CI runs is what the developer watched, not a second implementation. -requirements: - - > - Hybrid inline rendering — finished turns are committed into the terminal's real scrollback; - a short viewport at the bottom holds the composer, the status line and modal overlays. No - alternate screen buffer exists in the product, in any mode, behind any flag. - - > - Flicker discipline — synchronized output around every frame, coalesced token events, and no - full-screen clear during a normal session. - - > - Mouse capture off by default, with explicit copy affordances (system clipboard over OSC 52 so it - works across SSH) instead of pointer interception. - - > - A composer with multiline input, prompt history, slash-command palette, workspace file - completion, a raw shell escape, and large pastes converted to attachments. - - > - A single Picker overlay component, used by onboarding, model selection, theme selection, session - resume, session fork and approvals — so those surfaces are visibly one product. - - > - Guided first-run onboarding — provider, credential (with a live verification call), model, - theme with live preview, and default permission posture — writing io-harness's own configuration - file. - - > - Approval overlays that show act, target, the asking rule and, for a write, the diff; resolved by - key, recorded to history and to the trace. - - > - Policy posture cycling on one key, backed by io-harness policy layers rather than bespoke flags, - with the active layer named in the status line. - - > - Diff rendering — unified by default, word-level emphasis inside changed lines, syntax - highlighting, collapsed context, a defined narrow-terminal form, and a minimal mode. - - > - Collapsed tool cells with an expander; full output goes to the durable trace, not to the screen. - - > - A status line carrying model, policy layer, context pressure, tokens, spend against the tree - ceiling, containment state and elapsed time. - - > - Session resume and fork over the harness store, and mid-session model switching. - - > - A fleet surface — the live agent tree with each child's draw against the shared ceiling. - - > - Headless execution — a non-interactive subcommand emitting the same event stream as NDJSON, - with meaningful exit codes. - - > - A plain mode built for screen readers: no animation, no repaint of unchanged lines, the real - cursor as the focus indicator, content before metadata, and every state change announced as one - line of text. - - > - Theming as a small token set with shipped themes, terminal background detection, and `NO_COLOR` - honoured; colour is never the sole carrier of meaning. - - > - A documented keybinding table, with the Kitty keyboard protocol used when advertised and a clean - degradation over SSH to a legacy terminal. - - > - Inline image rendering where the terminal supports Kitty, iTerm2 or Sixel, with a half-block - fallback. -constraints: - - > - **io-cli contains no agent loop, no provider client, no tool implementation, no sandbox, no - policy engine and no session store.** All of it is io-harness. io-cli is a renderer of run - events, an editor of prompts, and a keyboard. If a release needs one of those, it is added to - io-harness and consumed — never reimplemented here. This is the constraint the archived fork - violated and the one that keeps this product small. - - > - No configuration parser. io-harness owns discovery and layering; io-cli's own settings live in - the `[app.io-cli]` section that io-harness deliberately does not validate. - - Rust binary named `io`; ratatui over crossterm; async on tokio. - - Apache 2.0, matching the rest of the initorigin open-source line. - - > - Runs locally on the developer's machine. No daemon, no client/server split, no second process. - A separate TUI process talking to a server is how the archived fork's architecture began. - - > - Credentials are API keys — from the environment, or from io-harness's configuration file written - `0600`. No OAuth and no subscription authentication, matching io-harness's own boundary. - - > - 80×24 is a supported terminal size, not a degraded one. Every surface has a defined narrow form. - - Stay 0.x. A breaking change is a minor bump; a 1.0.0 is not planned. -public_contract: > - A command-line interface: the `io` binary, its subcommands, its flags, its slash commands, its - keybinding table, its `[app.io-cli]` configuration section, and the NDJSON event shape emitted by - the headless subcommand. The Rust API of the crate is not a public contract — io-cli is a binary, - not a library. - - - The install path is part of the contract too: the URLs of `install.sh` and `install.ps1`, and the - naming of the release artifacts they resolve, must keep working across releases. A user's install - command is the first thing they depend on and the last thing that should churn. -deployment_context: > - Local, single-user. Installed by a `curl` shell script on macOS and Linux, or a PowerShell script - on Windows, each fetching a prebuilt binary from the GitHub Release and verifying it against the - checksums published beside it. Installed into a per-user directory, requiring no administrator - rights. Run in the developer's own terminal. No service, no server, nothing to operate. -success_measures: - - > - A developer with no configuration reaches a completed real task — an edit, verified — in one - session, without leaving the wizard to read documentation or set an environment variable. - - > - After exiting, the whole conversation is scrollable, searchable and selectable in the terminal - the session ran in, using only the terminal's own facilities. - - > - A refusal in a real run names its rule and its layer on screen, and the same refusal is in the - durable trace. - - > - The headless subcommand and the interactive session produce the same events for the same task, - with no interface code on the headless path. - - > - A session driven entirely by keyboard in plain mode, with colour disabled, completes the same - task as the default interface. -non_goals: - - > - An agent loop, a provider client, tools, a sandbox, a policy engine or a session store. That is - io-harness, and duplicating it is the specific failure this product exists to avoid. - - A daemon, a client/server split, or a second process of any kind. - - A graphical interface — that is io-studio. - - An alternate-screen or "fullscreen" rendering mode, behind any flag or environment variable. - - Mouse-driven interaction as a primary input model. - - OAuth and subscription authentication. - - A plugin UI framework, a theme marketplace, or user-scriptable widgets. - - Editor or IDE integration. - - Hosting, telemetry collection, or any network call the operator did not ask for. - - > - Publishing to crates.io, and `cargo install` as an install path. The crate name `io-cli` is held - by this owner and carries seventeen yanked versions, 0.1.0 through 0.14.0, from the archived - product — a yank does not free a version number, so publishing would force the whole roadmap to - restart above 0.14.0. Beyond that, `cargo install` requires a Rust toolchain on the user's - machine and compiles io-harness from source, which is the slowest install path available for a - terminal application. Decided 2026-08-16; see iterations/US-IO-CLI-0.1.0-I01. - - > - Homebrew, Scoop, WinGet, or any OS package manager. One script per platform is the whole - distribution surface, and each additional channel is a manifest that goes stale. -mvp_boundary: > - A developer with no prior configuration runs `io` in a repository, is walked through picking a - provider, entering and verifying a key, choosing a model and a theme, types one real task, watches - it stream and edit a file inside the sandbox, interrupts it with a keystroke, and exits — leaving - the whole conversation in the terminal's own scrollback. -assumptions: - - > - io-harness 0.60.1 already provides everything this product renders: fifty run event kinds, the - session layer with steering and forking, configuration discovery and layering, policy layers, - sandbox, providers with pricing, and the durable store. This has been read from the source, not - assumed — see `Session`, `Observer`/`RunEvent`, `Config` and `Policy` in that crate. - - > - The differentiator is that the core emits the permission boundary, the spend ceiling and the - agent tree, and no competing core does. A 2026-08-16 audit of the terminal agent field found - every other interface constrained to a chat log with tool cells for that reason. - - > - The hybrid inline renderer is achievable on the ratatui inline viewport with `insert_before`. - This is how at least one shipped Rust agent renders and it is the reason its content survives in - scrollback. **Unverified in our own code** — it is the single largest technical risk in the - first release and is the first thing to be proven. - - > - Building the wizard on the product's own Picker overlay, rather than adopting a Rust prompt - library, avoids a second raw-mode owner and a second aesthetic. The alternative — `inquire` is - the strongest candidate — remains available if the overlay proves more expensive than expected. - - > - A `0600` configuration file is an acceptable credential store for the first releases. An - OS-keychain backend is a later addition, and it is not obviously better: a keychain that fails - silently on a headless Linux box is worse than a file that never does. - - > - Each initorigin repository runs its own UltraShip workspace. io-harness, io-studio, io-eval and - the website are planned separately in their own repositories. diff --git a/.ultraship/products/io-cli/releases/0.1.0.yaml b/.ultraship/products/io-cli/releases/0.1.0.yaml deleted file mode 100644 index 94c60da..0000000 --- a/.ultraship/products/io-cli/releases/0.1.0.yaml +++ /dev/null @@ -1,663 +0,0 @@ -product: io-cli -version: 0.1.0 -status: released -release_type: minor -outcome: - user_can: > - A developer clones nothing, configures nothing, and runs `io` in a repository. Because no - configuration exists, a guided first run starts: it asks which provider to use — the choices - being the variants io-harness's own `ProviderSpec` defines, not a hardcoded list — takes an API - key without echoing it, and **verifies that key against the live endpoint before continuing**, so - a bad key is caught in the wizard rather than on the first real prompt. It then offers the - provider's own model catalogue, lets the developer arrow through the shipped themes with a sample - transcript re-rendering live behind the picker, and asks for a default permission posture in - plain words. It shows exactly what will be written and where, and only then writes io-harness's - configuration file with mode `0600`. - - - The session that follows is the product's core claim made real. Every finished message, tool cell - and system line is committed into **the terminal's own scrollback**; a viewport of a few lines at - the bottom holds the composer and a status line, and is the only region that repaints. The - developer types a task, watches the reply stream token by token without strobing, watches the - agent edit a file inside io-harness's sandbox, presses `Ctrl+C` to interrupt a turn that is going - wrong, types another, and exits with `Ctrl+D`. - - - Afterwards the terminal contains the whole conversation. `Cmd+F` finds text in it. tmux copy-mode - scrolls it. A mouse drag selects it. None of that was implemented — it works because io-cli never - entered the alternate screen and never captured the mouse. - business_value: > - This release exists to make the archived fork's mistake structurally impossible to repeat. That - product was 1,199,453 lines across 88 crates, roughly sixty of which duplicated io-harness, and - its interface could not be lifted out — the TUI crate alone was 229k lines depending on 41 - siblings. The scope below is a binary that renders an event stream and edits a prompt. If 0.1.0 - ships and it is small, the shape is proven and every later release inherits it. - - - It also settles the two decisions that are expensive to reverse later and cheap to make now. The - renderer is the first: the field's most-complained-about choice is the alternate screen, and a - product that ships with it spends the rest of its life adding escape hatches. Building on the - inline viewport from the first commit means the property is never lost, because there is no code - path that could lose it. The wizard is the second: first contact is where a bring-your-own-key - product loses people, and every competitor either errors on a missing environment variable or - funnels the user into one vendor's login. - - - And it puts a real agent in the owner's hands. The harness has shipped sixty-one releases with - its only interactive consumer being example programs. The first session in which a human watches - io-harness work and can stop it will find things sixty-one releases of tests did not. - evidence_of_value: > - The acceptance criteria carry it, and two of them are unusual on purpose. - - - F5 asserts a structural property rather than a behaviour: over a full scripted session, the bytes - io-cli writes to the terminal **never contain the alternate-screen sequences** `ESC[?1049h` or - `ESC[?1047h`, and never contain the mouse-capture sequences `ESC[?1000h` or `ESC[?1003h`. That - single assertion is the whole differentiator of §1 of the design, expressed as something a test - can fail on rather than something a reviewer has to notice. It is also permanent — no later - release can quietly reintroduce fullscreen without turning this test red. - - - F1 is the manual one that cannot be automated and must not be skipped: a real live run, against a - real provider, in a real repository, taken from an empty configuration through the wizard to a - verified file edit, with the terminal's own search and selection exercised on the transcript - afterwards. The screen recording of that run is the release's evidence and the README's - screenshot. -scope: - included: - - > - **The repository skeleton, matching the io-harness line.** `LICENSE` (Apache-2.0 body - pristine), `NOTICE` and `README.md` carrying `Copyright 2026 Aakash Pawar (InitOrigin)`, - `CHANGELOG.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `.github/` with - `CODEOWNERS`, a PR template, issue templates, and `dependabot.yml` targeting `develop`. - Manifest authors carry no email address. - - > - **CI on three platforms**, mirroring io-harness's `ci.yml` shape: build, test, `clippy -D - warnings`, `fmt --check`, on macOS, Linux and Windows. Every `uses:` pinned to a full-length - commit SHA with the version in a trailing comment. No job id contains a dot. - `cancel-in-progress` is off, so a failing run keeps its diagnostics. - - > - **The binary is `io`.** One crate, one binary, `clap` for arguments. The crate name is - `io-cli`; the binary name, the config section and the environment prefix follow the naming - table already settled for this product. - - > - **The renderer — the load-bearing piece.** `ratatui` over `crossterm` with - `Viewport::Inline`. Finished content is pushed to scrollback with `Terminal::insert_before`; - the viewport holds composer and status only. Every frame is wrapped in synchronized-output - (`CSI ? 2026 h` / `l`). Token events are coalesced to at most one frame per frame-budget. No - alternate screen, no mouse capture, no full-screen clear. A panic hook and a `Drop` guard - restore the terminal — cooked mode, cursor shown — **before** anything is printed. - - > - **The `Picker` overlay component**, built once and used by the wizard's provider, model, theme - and posture steps. It is the component every later release reuses for `/model`, `/resume`, - `/fork` and approvals, which is why it is built properly here and not inlined into the wizard. - This replaces adopting a Rust prompt library — `inquire`, `dialoguer`, `promkit` — each of - which would be a second owner of raw mode and a second aesthetic. - - > - **The composer.** `tui-textarea` for editing, multiline on `Shift+Enter` with a `\` + `Enter` - fallback for terminals that cannot report it, prompt history on the arrow keys, and bracketed - paste enabled so a pasted block does not arrive as keystrokes. - - > - **The first-run wizard**, in the seven steps the design fixes: provider, credential, - verification call, model, theme with live preview, permission posture, and a - write-confirmation screen that names the exact path. Re-runnable at any time as `io setup`. - - > - **Configuration is io-harness's.** `Config::discover` finds it; the wizard writes it by - serializing `ProviderSpec` and the chosen policy, because those types already derive - `Serialize`. io-cli's own settings — theme, and nothing else in this release — live under - `[app.io-cli]` and are read with `Config::app::("io-cli")`. **There is no - configuration parser in this repository**, and F7 asserts it. - - > - **The session.** `io_harness::Session` over the harness `Store`, driven by - `turn_steered_observed`-shaped calls so that streaming and interruption both work from the - first release. An `Observer` implementation is the sole bridge between the harness and the - screen. - - > - **The event surface for this release**, deliberately partial: `started`, `token`, `step`, - `tool_call`, `finished` rendered fully; `refused` and `approval_requested` rendered as a plain - one-line notice with the run behaving safely, because their real surfaces are 0.2.0. Every - other kind of the fifty falls through to a muted single line rather than being dropped - silently — an unrendered event is a bug this design should surface, not hide. - - > - **A status line** with model, elapsed time and a working indicator. The remaining fields — - policy layer, context pressure, spend against ceiling, containment — are 0.2.0, and the line is - laid out now so they slot in without a redesign. - - > - **The theme token set** — background, foreground, muted, accent, success, warning, error, - diff-add, diff-delete — with two shipped themes, terminal background detection, and `NO_COLOR` - honoured. - - > - **The splash**: the `IO CLI` mark in the block characters the owner supplied, printed once into - scrollback, suppressed under `NO_COLOR`, under a non-TTY stdout, and under any future plain - mode. - - > - **Keys for this release**: `Ctrl+C` interrupts the turn via `Steer::interrupt` and twice in - succession exits, `Ctrl+D` exits on an empty composer, `Ctrl+L` clears the viewport and never - the scrollback, `Enter` submits. Documented as a table in the README, not as prose. - - > - **Slash commands for this release**: `/help`, `/quit`, `/setup`, `/theme`, `/model`. Five, each - one a `Picker` or a print. The palette is 0.7.0. - - > - **Terminal reality**: a resize is handled without duplicating committed scrollback, and an - 80×24 terminal is a supported size with every surface defined at that width. - - > - **The tests**, including the two harnesses later releases will build on: a `TestBackend`-based - renderer harness that asserts buffer contents, and a byte-stream harness that captures - everything written to the terminal so F5 can assert over it. - - > - **The distribution, which is the only channel this product has.** A `release.yml` cross-compile - matrix producing four artifacts — `aarch64-apple-darwin`, `x86_64-apple-darwin`, - `x86_64-unknown-linux-musl` and `x86_64-pc-windows-msvc` — attached to the GitHub Release that - the tag cuts, alongside a `SHA256SUMS` file covering all of them. musl is chosen for Linux - deliberately: one static artifact that runs on any distribution beats two glibc builds and a - support matrix of which one a user needs. - - > - **`install.sh` and `install.ps1`.** The shell script is POSIX `sh`, detects the platform from - `uname`, resolves the matching artifact from the latest release, **verifies it against - `SHA256SUMS` before unpacking**, and installs into a per-user directory — `$IO_INSTALL_DIR`, - else `~/.local/bin` — with **no `sudo` and no system-wide write**. The PowerShell script does - the same into `%LOCALAPPDATA%\io\bin` and adds it to the user `PATH`. Both print the exact - `PATH` line to add when the directory is not already on it, rather than editing a shell profile - behind the user's back. - - > - **The CHANGELOG entry and the version**, `0.1.0` in `Cargo.toml` and `Cargo.lock`, under the - structure this line of repositories uses. - excluded: - - > - **Any agent loop, provider client, tool, sandbox, policy engine or session store.** This is the - product's founding constraint. If something is missing, it is added to io-harness and consumed - — not written here. N1 asserts it by dependency and by grep. - - > - **Approval overlays, policy cycling, and the refusal surface with rule and layer.** 0.2.0. - This release runs under whatever posture the wizard wrote and prints a plain notice rather than - pretending to a surface it does not have. - - > - **Diff rendering and collapsible tool output.** 0.3.0. An edit is reported as a one-line fact - here; making it beautiful is a release of its own and doing it badly now costs more than - waiting. - - > - **Session resume, fork and rewind.** 0.4.0. A session in this release starts fresh. - - > - **The headless subcommand and NDJSON.** 0.5.0. `io` is interactive-only in this release, and it - detects a non-TTY stdout only well enough to suppress the splash and refuse to start a wizard. - - > - **Plain mode, configurable keybindings, the Kitty keyboard protocol negotiation.** 0.6.0. The - defaults chosen here — real cursor never hidden while input is possible, `NO_COLOR` honoured, - content before metadata — are the parts that would be expensive to retrofit, so they are in; - the mode itself is not. - - > - **`@` file completion, `!` shell escape, the command palette, the plan and todo panel.** 0.7.0. - - > - **The fleet surface.** 0.8.0. - - > - **Images, browser, LSP and MCP indicators.** 0.9.0. - - > - **An OS-keychain credential backend.** A `0600` file is what `gh`, `aws` and `npm` do. A - keychain that fails silently on a headless Linux box is worse, and choosing between them is not - a first-release decision. - - > - **Publishing to crates.io, and `cargo install` as a path.** Decided against for the product, - not deferred — see `product.yaml` non-goals and iterations/US-IO-CLI-0.1.0-I01. The name holds - seventeen yanked versions through 0.14.0 and a yank does not free a number, so publishing would - restart this roadmap above 0.14.0 for an install path slower than the one being built. - - > - **Homebrew, Scoop, WinGet, `.deb`, `.rpm`, or an npm shim.** Two scripts are the distribution - surface. - - > - **Linux `aarch64`, Windows `aarch64`, and 32-bit anything.** Four artifacts in the first - matrix; see the open question on which fifth is worth adding first. - - > - **Auto-update, an update checker, or any version phone-home.** Re-running the install script - is the update mechanism, and a terminal tool that contacts a server the operator did not ask it - to contact violates this product's own non-goals. - - > - **Vim mode in the composer.** Deliberately late, as it was for the product this pattern is - taken from. - fallback_scope: - - > - **The renderer, the composer, the session and one real completed task, configured from - environment variables.** The wizard drops to three steps — provider, key, model — with no theme - preview and no posture step, and the theme falls back to the single default. This is still a - coherent release: a developer can do real work in it, the scrollback property holds, and F5 - still passes. The renderer is the half that must not drop, because it is the one thing that - cannot be added later without rewriting everything built on top of it. - - > - **The theme picker with live preview drops first.** It is the delight, not the function; a - single shipped theme with `NO_COLOR` support is a complete product and the picker is two hours - whenever it is wanted. - - > - **The live key-verification call drops second**, to a format check plus a clear error on the - first real turn. It is the difference between a good wizard and an adequate one, not the - difference between working and not. - - > - **The `Picker` component does not drop.** If it is cut, the wizard has to be hand-rolled and - then rebuilt in 0.2.0 for approvals — which is the seam this release exists to prevent. -acceptance: - functional: - - > - **F1 — the live first run.** From an empty `$IO_CONFIG_HOME`, in a real repository, against a - real provider: the wizard is completed, the key is verified by a live call, the file is - written, a task is typed, the answer streams, a file is edited inside the sandbox, `Ctrl+C` - interrupts a second turn mid-flight, and `Ctrl+D` exits. Afterwards, in the same terminal, the - transcript is searched with the terminal's own search and a passage is selected with the mouse - and pasted elsewhere. Manual, recorded, and the recording is the release evidence. A fixture - cannot prove this; only a live run can. - - > - **F2 — the wizard writes what it showed.** A test drives the wizard over the `TestBackend` - with a scripted key sequence and asserts the resulting file parses under - `io_harness::Config::from_toml`, that `provider_spec()` returns the chosen variant with the - chosen model, and that the file's mode is `0600` on unix. Sabotage: write the file before the - confirmation screen, under which only F2 fails. - - > - **F3 — a bad key does not get past the wizard.** With a provider endpoint that rejects the - credential, the wizard returns to the credential step carrying the provider's own message, and - **no file is written**. Sabotage: treat any non-transport error as success, under which only - F3 fails. - - > - **F4 — finished content reaches scrollback, not the viewport.** After a scripted session, the - captured byte stream contains every finished message's text outside the viewport region, and - the viewport buffer at rest contains only the composer and the status line. Sabotage: render a - finished message into the viewport instead of committing it, under which only F4 fails. - - > - **F5 — no alternate screen and no mouse capture, ever.** Over the same captured byte stream, - the sequences `ESC[?1049h`, `ESC[?1047h`, `ESC[?1000h`, `ESC[?1002h` and `ESC[?1003h` do not - appear. This is the release's structural criterion and it is written so that no later release - can reintroduce fullscreen without failing it. Sabotage: call - `EnterAlternateScreen`, under which only F5 fails. - - > - **F6 — an interrupt stops the turn and keeps the session.** A scripted `Ctrl+C` during a - streaming turn causes `Steer::interrupt`, the turn ends, the partial output stays committed in - scrollback, and the composer accepts the next prompt. A second `Ctrl+C` at an idle composer - exits. Sabotage: drop the interrupt on the floor, under which only F6 fails. - - > - **F7 — the forbidden subsystems are absent.** A test over `Cargo.toml` asserts the dependency - set contains no HTTP client, no TLS stack, no SQL or database crate, and no sandboxing crate, - and a grep asserts the source contains no `async fn` that loops over provider responses. The - only path to a model is through `io_harness`. Sabotage: add `reqwest` as a direct dependency, - under which only F7 fails. This is the criterion that keeps the archived fork from growing back. - - > - **F8 — every event kind is accounted for.** A test enumerates the event kinds io-harness emits - and asserts the renderer's match is exhaustive with no wildcard that silently discards. An - event this release does not style renders as a muted line; none is dropped. Sabotage: add a - catch-all arm that returns without rendering, under which only F8 fails. - - > - **F9 — 80×24 is supported.** The renderer harness runs the scripted session at 80×24 and - asserts no panel is truncated mid-word, the composer remains usable, and the status line - degrades to its narrow form rather than wrapping. - non_functional: - - > - **N1 — io-cli contains no second harness.** `cargo tree --depth 1` is recorded in the release - evidence and reviewed: `io-harness`, `ratatui`, `crossterm`, `tui-textarea`, `clap`, `tokio`, - `serde`/`toml`, and nothing that constitutes a runtime, a client or a store. Any addition - beyond that list is argued in the release record or it does not ship. - - > - **N2 — the terminal is always restored.** Panic, `Ctrl+C` at any point, and a kill signal each - leave the terminal in cooked mode with the cursor visible and the scrollback intact. Asserted - by a test that panics inside a turn and inspects the restore path, and by manual check for the - signal case. - - > - **N3 — no full-screen clear during a session.** The captured byte stream contains no - `ESC[2J` after startup. This is the flicker criterion, and it is asserted structurally because - flicker itself cannot be asserted on a CI runner. - - > - **N4 — no timing assertion anywhere in this release's tests.** Startup cost and streaming - smoothness are **recorded as measurements** in the release evidence, not gated. This line of - repositories has paid for wall-clock assertions on CI runners more than for anything else. - - > - **N5 — the credential never reaches the screen, the scrollback, a log line or the trace.** The - captured byte stream from the F2 scripted wizard is searched for the test key and must not - contain it. Sabotage: echo the key in the confirmation screen, under which only N5 fails. - - > - **N6 — `NO_COLOR` produces a usable session.** With `NO_COLOR=1` the scripted session runs and - every state that colour distinguishes — success, warning, error, refusal — also carries a word. - - > - **N7 — an installer never runs an unverified binary.** Both scripts compute the artifact's - SHA-256 and compare it against the published `SHA256SUMS` before unpacking, and **abort - non-zero on a mismatch without leaving anything on disk**. Asserted by running each installer - against a deliberately corrupted artifact and checking both the exit status and that the target - directory is untouched. This is the one place in the product where a failure is a security - failure rather than an inconvenience, and `curl | sh` is a trust-the-publisher model regardless - — the checksum defends against a truncated download and a tampered asset, not against a - compromised repository, and the README says so rather than implying more. - - > - **N8 — an install needs no administrator rights.** Neither script writes outside the user's own - directories and neither invokes `sudo`. Asserted by running each as an unprivileged user with - no write access to any system path. - operational: - - > - **O1 — CI green on macOS, Linux and Windows**, on the feature branch and again on the merge to - `develop`. Windows is not optional: it is where a terminal renderer's assumptions break, and it - is where this project has historically found what the other two hid. - - > - **O2 — the binary builds in release profile.** `cargo build --release` is run and the binary - executed. CI builds the test profile only, so a release-profile break is invisible to it — - this has bitten this line of repositories before and is now a standing gate. - - > - **O3 — the branch flow is followed exactly.** `feat/0.1.0` off `develop`, one pull request into - `develop`, merged with the PR button. `develop` to `main` by pull request. Nothing is pushed - directly to `develop` or `main` at any point. The tag `v0.1.0` is pushed on its own after the - merge — pushing the tag is what cuts the GitHub Release; creating the Release by hand ships it - with no assets. - - > - **O4 — the repository's own protections are in place or their absence is recorded.** The - rulesets that make O3 mechanical rather than voluntary require the repository to be public or - on a paid plan; see the open question. Until then O3 is a discipline, and that fact is stated - in the release record rather than left implied. - - > - **O5 — every artifact in the matrix exists and every installer works on a clean machine.** The - Release carries all four artifacts plus `SHA256SUMS`. `install.sh` is then run on a clean macOS - machine and in a clean Linux container, and `install.ps1` on a clean Windows machine, each - followed by running `io --version` **in a new shell** — which is what catches a script that - installed correctly and told the user nothing useful about `PATH`. An installer verified only - on the machine that built it is not verified. - - > - **O6 — the Release is cut by pushing the tag, and nothing else.** `gh release create` is never - used: it produces a Release with no assets. The workflow the tag triggers is what attaches - them, and the Release title is the bare form this line of repositories uses. -delivery: - target_mode: release-ready - environment: > - The developer's own machine — macOS, Linux and Windows terminals, including inside tmux and over - SSH. GitHub for the pull requests, CI, the Release and the release assets. No service, no - deployment target, no infrastructure, and no package registry. The artifact is a binary a - developer installs with one command and runs in their own terminal, and the GitHub Release is the - only channel it travels through. - deployment_method: > - One feature branch `feat/0.1.0` cut from `develop`. All work lands on it. One pull request into - `develop`, merged with the PR button; `git pull origin develop` immediately after. Then a pull - request from `develop` into `main`, merged the same way. The UltraShip record is sealed and the - lock pinned in a commit on top of the release commit. The tag `v0.1.0` is pushed alone, which - cuts the GitHub Release; the workflow it triggers cross-compiles the four artifacts, writes - `SHA256SUMS`, and attaches all five to that Release. The installers are then exercised against - the published Release, not against a local build — O5. **Nothing reaches `develop` or `main` - except through a pull request.** Pushes go over HTTPS with the `gh` credential helper; the remote - is never rewritten. There is no publish step after the Release, because there is no registry. - migrations: - - > - None. This is a first release with no predecessor state. The configuration file it writes is - io-harness's, in the schema io-harness 0.60.1 already reads, so it is forward-compatible with - the harness by construction rather than by agreement. - observability: - - > - The run's own durable trace in the harness store is the observability surface; io-cli adds no - telemetry, no crash reporting and no network call the operator did not ask for. The one - diagnostic io-cli owns is a `RUST_LOG`-gated stderr log that is silent by default and never - writes into the scrollback the transcript lives in. - rollback: > - Deleting the tag and the GitHub Release removes the artifacts and, with them, the install path — - both actions are reversible, and this is the whole reason the no-registry decision is valuable: - a crates.io publish could never have been undone, and a yank is not a delete, as the seventeen - yanked versions on this crate's own name demonstrate. - - - What rollback does **not** reach is a binary already installed on someone's machine. That is - inherent to shipping binaries and is handled by fixing forward as 0.1.1 and by the install script - always resolving the latest Release, so re-running one command is the update. There is no - auto-update and no version check, deliberately. - - - The `develop`-to-`main` merge is reverted by a revert commit through a pull request, like any - other change. -dependencies: - products: - - > - io-harness 0.60.1 or later, as a path dependency during development and a version dependency at - release. Everything this release renders already exists in it; nothing in this release requires - a harness change. **If one is discovered, it is a harness release and this one waits** — that - is the constraint, and the first time it is tested is here. - services: - - > - One live model provider, for the wizard's verification call and for F1. OpenRouter is the - likely first, being the harness's own first provider. - - > - GitHub Actions, for CI on the three platforms, for the cross-compile matrix, and for the - Release the tag cuts. - - > - GitHub Releases, which is now a **hard** dependency rather than a convenience: it is the sole - distribution channel and the sole host of the artifacts the installers resolve. If it is - unavailable, there is no way to install io-cli. Accepted knowingly — the alternative is - operating a download host, which contradicts "no service, nothing to operate". - human_approvals: - - > - The owner approves the rendered look before the tag — the splash, the theme defaults, the - status line and the wizard screens — from the F1 recording. This is an interface release; the - thing being shipped is what it looks like, and that cannot be delegated to a test. - - > - The owner decides whether the repository becomes public, which is what makes the branch - rulesets available. Until that decision the pull-request rule is discipline rather than - enforcement. -resource_profile: - complexity: large - uncertainty: medium - likely_cost_drivers: - - > - **The inline renderer.** Committing finished content to scrollback while a live viewport sits - below it is the one genuinely novel piece of engineering in the release, and the product's - whole differentiation rests on it. Resize, wrapping wider than the terminal, and the ordering - of commits against in-flight frames are each their own problem. This is where the time goes and - the schedule should assume it. - - > - **Windows.** A terminal renderer meets a different reality there — key reporting, resize - events, ANSI support depending on the host — and this project's history is that Windows finds - what the other two platforms hide. - - > - **The wizard's verification step.** Each provider reports a bad credential differently, and the - criterion is that the wizard shows the provider's own message rather than a generic failure. - - > - **Building the test harnesses.** A `TestBackend` renderer harness and a byte-stream capture - harness are both new, and F5 and N3 are impossible without the second. They pay for themselves - across every later release, but they are paid for here. - - > - **The cross-compile matrix, and `musl` specifically.** io-harness carries `rusqlite`, which - builds a bundled C SQLite. Cross-compiling that to `x86_64-unknown-linux-musl` needs a musl - toolchain in the job and is the row most likely to fail first. It is still the right target — - one static Linux artifact that runs anywhere beats two glibc builds and a support question — - but it is where the matrix work concentrates, and it is worth proving on a throwaway branch - before the release branch depends on it. - - > - **Verifying the installers on clean machines.** O5 is deliberately not satisfiable from the - build machine, and "a new shell" in that criterion is the part that catches the `PATH` mistake - every install script makes once. - preferred_tools: - - '`ratatui` `Viewport::Inline` and `Terminal::insert_before`, plus its `TestBackend` for F2/F4/F9' - - '`tui-textarea` for the composer; `crossterm` for events, bracketed paste and capability queries' - - > - io-harness's `examples/` as the reference for driving `Session` and `Observer` — `edit_repo.rs` - drives a complete verified workspace edit in 56 lines and is the shape this binary wraps - - '`cargo tree --depth 1` as the evidence for N1' - constraints: - - > - **No second harness.** The moment something is missing — a provider detail, a policy shape, a - tool — it goes into io-harness. Writing it here is how the archived fork happened, and F7 is the - criterion that catches it. - - > - **No timing assertion, on any test, on any platform.** Measurements are recorded; nothing is - gated on a clock. - - > - **No alternate screen, in any mode, behind any flag.** There is no configuration for it because - there is no code path for it. - - > - **Nothing reaches `develop` or `main` except by pull request**, including documentation and - including the UltraShip record itself. -risks: - - > - **The inline renderer turns out to be harder than the design assumes.** The assumption that - finished content can be committed to scrollback while a live viewport renders below it is taken - from how a shipped Rust agent behaves, not from our own code. If it proves unworkable — most - likely on resize or with content wider than the terminal — the whole product's differentiation is - at risk, not just this release. **Mitigation: build the renderer first, before the wizard, before - the composer, before anything else.** A spike that commits a hundred lines of fake transcript to - scrollback under a live viewport, at three terminal widths, with a resize in the middle, is the - first thing written and the gate on the rest of the release. - - > - **The wizard grows into the release.** Seven steps, a live network call, a theme preview and a - provider catalogue is a lot of surface for a first release, and it is the part most likely to - expand while being built. The fallback scope names the three-step version deliberately so that - shrinking it is a decision already made rather than an emergency. - - > - **Something turns out to be missing from io-harness.** The constraint says the harness gets the - change and this release waits. That is correct and it is also a schedule risk with no mitigation - other than finding out early, which is another reason the renderer and one real end-to-end turn - come before the polish. - - > - **Windows costs a round.** Assume it, rather than discovering it after the macOS and Linux runs - are green. The historical pattern in this line of repositories is that Windows finds real defects - the other two cannot. - - > - **The look is approved late.** This is an interface release and the acceptance for the look is a - human decision from a recording. If that decision arrives after the tag, the correction is a - patch release for something that should have been a review. The F1 recording should be produced - and shown as soon as one real turn works, not at the end. -open_questions: - - > - **Which fifth target is worth adding, and when?** The matrix ships four. - `aarch64-unknown-linux-musl` is the strongest candidate — ARM servers and ARM Linux laptops are - no longer unusual — and it is also the second-hardest row, for the same `rusqlite`-on-musl reason - as the first. Decide once the x86 musl row is actually building, not now. - - > - **Does the install script pin a version, or always resolve `latest`?** Always-latest makes - re-running the script the update mechanism, which is the design. It also means a user cannot - reproduce yesterday's install. An `IO_VERSION` environment variable answers both and costs a few - lines, but only if the artifact naming is stable from the first Release — which is why it is - worth deciding while writing the script rather than after. - - > - **Does the repository become public now?** The branch rulesets that make "nothing without a pull - request" mechanical are unavailable on a private repository without a paid plan — the API refuses - with a 403. The product is Apache-2.0 and intended to be open, so this is a question of timing - rather than direction. Until it is answered, O3 is discipline rather than enforcement, and O4 - requires that to be stated rather than assumed. - - > - **Which provider is the wizard's default?** OpenRouter is the harness's own first provider and - reaches the widest model set with one key, which argues for it. It also means the developer's - first impression of io-cli is mediated by an aggregator. Not a blocker — the wizard offers all - variants regardless — but the default is what most people will take. - - > - **Does the status line's spend field appear here, greyed, or wait for 0.2.0?** Laying it out now - avoids a redesign; showing an empty field looks unfinished. Decide when the line is first - rendered against real content, not now. -released_at: "2026-08-16T17:35:00Z" -mode: published -delivered: > - A terminal interface over io-harness that renders run events, edits a prompt - and reads a keyboard — and contains no agent loop, provider client, tool, - sandbox, policy engine or session store of its own. 3,458 lines of source - across 16 files, against the archived product's 1,199,453 across 88 crates. - That ratio is what this release existed to establish. - - - The renderer is the release. Finished content is committed into the terminal's - own scrollback and a fixed few rows at the bottom hold the composer and the - status line; a streaming answer commits each line as it finishes, so a - two-hundred-line reply leaves the viewport exactly the size it started. The - alternate screen is never entered and the mouse is never captured — asserted - over the bytes the process writes, so no later release can lose it quietly. - - - A first-run wizard takes a developer from no configuration to a working agent: - provider, a credential verified against the live endpoint before anything is - written, a model from that provider's own catalogue, a theme previewed live, - and a permission posture that is an io_harness::Policy rather than a flag of - io-cli's own. Nothing reaches disk before the confirmation screen; the - credential never reaches the screen at all, and is not written when the - provider's environment variable already carries it. - - - Distribution is four cross-compiled artifacts and a SHA256SUMS on the GitHub - Release, installed by one script per platform that verifies before it unpacks - and needs no administrator rights. -evidence: - tests: - - "cargo test --all-targets --no-fail-fast — 100 passing, 0 failing, across 19 test binaries" - - "45 sabotages run across the suite, each failing only its own arm; three of them found that a test proved nothing and were fixed" - - "tests/live.rs, 4 ignored tests run by hand against a real provider — key accepted, wrong key rejected in OpenRouter's own words, 413 models, a real turn of 45 events editing a file" - builds: - - "cargo fmt --all -- --check — exit 0" - - "cargo clippy --all-targets -- -D warnings — exit 0" - - "cargo build --release — exit 0; ./target/release/io --version prints io 0.1.0" - - "cargo tree --depth 1 — exactly the eight dependencies N1 permits, plus tempfile as a dev-dependency" - - "release matrix proved on diag/release-matrix before the tag depended on it: all four targets built and packaged, musl included" - reviews: - - "F1 completed and recorded by the owner, 2026-08-16: wizard from empty configuration, key verified live, file written 0600, a task that streamed and edited src/lib.rs inside the sandbox, an interrupted second turn, a clean exit, and the terminal's own search and selection working afterwards" - - "The owner approved the rendered look from that run, and raised five interface findings; the release-blocking one was fixed and the rest are recorded below" - deployments: - - "CI green on macOS, Linux and Windows plus lint and msrv, on the feature branch at 3e38b37" - health_checks: - - "the release binary starts, prints its version and its help, and refuses a non-tty stdout with exit 1 and a sentence" -known_limitations: - - > - **The configuration's `[sandbox]`, `[run]` and `[instructions]` sections are - read by io-harness and not applied to an io-cli turn.** `Session::turn_steered` - builds its own `TaskContract`, and the harness has no entry point taking both - a caller's contract and a steer inbox — so honouring them would mean giving up - `Ctrl+C`, which F6 requires. The sandbox itself is on; what is missing is the - configured ceilings, not the containment. Nothing in this contract promised - those sections. Stated in the README and in `docs/config.example.toml`, and it - is the natural first item for a harness release. - - > - **An action that needs approval is declined, and says so.** The overlay that - asks a human, and the refusal surface that names the rule and the policy - layer, are 0.2.0. A live run walked into the consequence: the ask-before-writes - posture denied three actions, the agent asked for permission, and the turn - ended `awaiting_answer`. That outcome now reads as a warning carrying the way - out rather than as an unexplained error, but the posture still cannot do what - its name suggests until 0.2.0. - - > - **The status line's working indicator is a word and not a spinner, and its - elapsed time advances only when an event or a keystroke arrives.** Between - events the clock is still. The owner raised both; neither blocks a session and - both are remediation of what this release shipped, so they belong in a patch. - - > - **The model picker has no filter.** A provider catalogue of four hundred rows - is walked with the arrow keys. The wizard's viewport was widened so a usable - number of rows is visible, but type-to-filter is new capability and the - roadmap puts fuzzy filtering in 0.7.0. Decided with the owner rather than - added quietly. - - > - **A prompt longer than two rows scrolls within them rather than growing the - session viewport.** ratatui fixes an inline viewport's height when the - terminal is constructed, and rebuilding mid-session would re-query the cursor - and risk shifting the scrollback this product exists to protect. The wizard - gets its own taller viewport because the boundary between the two phases is - safe to rebuild at; a session has no such boundary. The composer's own release - is 0.7.0. - - > - **O5 is verified after this record is sealed.** The installers can only be - exercised on clean machines against a Release that exists, which is by - definition later than the tag that creates it. `install.sh` is proved end to - end against a local release, including the corrupted-artifact refusal, and the - cross-compile matrix is proved on a throwaway branch — what remains is the - clean-machine, new-shell half. What this record claims is the state at seal - time. - - > - **The `x86_64-apple-darwin` artifact is cross-compiled and not executed by - CI**, because the runners are arm64. The other three rows run `io --version` - on the machine that built them. - - > - **The F1 recording shows the theme step before its fix.** The picker was - invisible in that run — the defect the run found — and the recording documents - it. It is honest evidence of the criterion passing and a poor screenshot; a - fresh capture for the README is a patch-release chore, not a correction. - - > - **This record was sealed before the Release existed, and that is the flow - rather than a shortfall.** The sealed record and its lock pin ride the feature - branch and merge with the code, so the seal necessarily precedes the tag that - cuts the Release and the workflow that attaches the four artifacts and - `SHA256SUMS`. `mode: published` describes the channel this product has — the - GitHub Release, which is the whole of it, since there is no registry and never - will be. If the tag or the matrix failed after this point, the correction is a - new version, not an edit. - - > - **This record was amended and re-pinned after its first seal, before the merge - into `main`, because the tree it described would not start.** Giving the - wizard its own taller viewport means attaching the terminal twice, and the - keyboard reader had been moved ahead of the first attach — placing an inline - viewport asks the terminal for its cursor position and reads the answer off - stdin, so the reader consumed it and the query timed out. The owner found it - by running the binary; nothing in the suite reaches the driver's own startup - ordering. The reader now polls so it can be stopped and joined, starts only - after a screen is attached, and takes that screen as a witness argument so the - ordering is a compile error to get wrong. Every gate below was re-measured on - the corrected tree at 9349aa0. -supersedes: null -next_recommended_version: 0.1.1 -immutable: true diff --git a/.ultraship/products/io-cli/releases/0.1.1.yaml b/.ultraship/products/io-cli/releases/0.1.1.yaml deleted file mode 100644 index fd6a9e0..0000000 --- a/.ultraship/products/io-cli/releases/0.1.1.yaml +++ /dev/null @@ -1,375 +0,0 @@ -product: io-cli -version: 0.1.1 -status: released -release_type: patch -outcome: - user_can: > - A developer starts a turn and can tell, at a glance, that io-cli is working - rather than hung. A moving indicator sits beside the word that names the - state, and the elapsed clock advances on its own — not only when an event - happens to arrive, which in 0.1.0 meant it froze between steps and jumped - when one landed. On a slow first token, which is the exact moment a user - doubts the tool, the interface is visibly alive. - - - The steps read as steps. Where 0.1.0 committed a decision string with a token - count appended, a step now says what the agent decided, what it ran, and what - came back, laid out so a transcript can be skimmed rather than parsed. - - - And the README carries a screenshot of the interface as it actually is. The - one 0.1.0 could not supply was recorded before the theme step's picker was - fixed, which that release recorded as a known limitation rather than shipping - a picture of a defect. - business_value: > - 0.1.0's own release record carries four interface limitations, three of them - raised by the owner within minutes of the first real session: the working - indicator does not move, the clock stops between events, and a step is hard to - read. None of them stops a session and all of them make the product feel - unfinished in the first thirty seconds, which for an interface release is the - part that matters. - - - This is remediation of what 0.1.0 shipped, not new capability, which is why it - is a patch. It also buys a rule for everything after it: the status line is - the surface 0.2.0 fills with the policy layer, context pressure, spend and - containment, and it is cheaper to make it tick correctly once, now, than to - add four fields to a line that stutters. - evidence_of_value: > - The criteria are deliberately structural rather than temporal. This line of - repositories has paid more for wall-clock assertions on CI runners than for - anything else, and 0.1.0's N4 forbids them outright — so "the clock advances - on its own" is asserted as *a repaint happens without an event arriving*, - driven by an injected clock the test controls, and never as *a repaint - happened within N milliseconds*. - - - F3 is the one that cannot be automated: the owner watches a real turn and says - whether it now reads as alive. The same human gate 0.1.0 put on its look, for - the same reason — this is an interface release and the thing being shipped is - what it feels like. -scope: - included: - - > - **A moving working indicator.** A small animation beside the status line's - state word, advancing on the same tick that drives the clock. The WORD stays - — it is what survives `NO_COLOR`, a screen reader and a log — and the motion - is added beside it, never instead of it. Suppressed entirely under - `NO_COLOR`, where an animation is noise a reader cannot use. - - > - **A repaint tick.** A timer that redraws the viewport while a turn is - running, so the clock advances and the indicator moves without an event - having to arrive. It runs only while a turn is in flight: an idle session - must not repaint, because an interface that redraws forever is what the - alternate-screen products are criticised for and what this renderer exists - not to be. - - > - **Step rendering.** A step becomes a legible line: the decision, the tool it - called with its target, and the result, in that order, with the token count - and the step number as trailing muted detail rather than as the middle of - the sentence. Content before metadata, which is the same rule the rest of - the interface already follows. - - > - **The README screenshot**, recaptured from a run of the corrected - interface, plus the CHANGELOG entry and the version at `0.1.1`. - - > - **The tests for all of it**, including a clock the test drives, which is the - piece that makes the tick assertable without asserting on time. - excluded: - - > - **Anything that changes what a session can do.** No new keys, no new slash - commands, no new event surfaces, no configuration this release starts - reading. A patch that grows capability is a minor pretending otherwise. - - > - **The approval overlay and the refusal surface with rule and layer.** 0.2.0, - unchanged. - - > - **Answering a question the agent asked.** 0.1.0 dead-ended there and now - says so with a way out; letting a user actually answer is new capability and - belongs with the approval surface in 0.2.0. - - > - **Type-to-filter on the picker.** Recorded against 0.7.0, where the roadmap - already promised fuzzy matching, and widened there to every picker rather - than only the command palette. Decided with the owner at 0.1.0's release. - - > - **A growable session viewport.** The composer still scrolls within its two - rows; ratatui fixes an inline viewport's height at construction and - rebuilding mid-session would risk the scrollback. 0.7.0. - fallback_scope: - - > - **The repaint tick and the clock.** A still clock is the one that reads as a - crash, and it is also the piece every later status-line field depends on. A - release that ships only this is coherent: the session visibly advances while - it works. - - > - **The step rendering drops first.** It is legibility, not liveness, and the - current form is cluttered rather than wrong. - - > - **The spinner drops second**, because the word already carries the state and - the moving clock already answers "is it alive". The animation is the polish - on top of the answer. -acceptance: - functional: - - > - **F1 — the viewport repaints while a turn runs, with no event arriving.** - Driven by a clock the test controls: with a turn in flight and no run event - delivered, advancing the clock produces a new frame whose status line - differs from the previous one. Sabotage: only repaint on an event, under - which only F1 fails. No assertion anywhere on how long anything took. - - > - **F2 — an idle session does not repaint.** With no turn running, advancing - the same clock produces no frame at all. This is the other half of F1 and - the more important one: a terminal interface that redraws forever is the - thing this renderer exists not to be. Sabotage: tick unconditionally, under - which only F2 fails. - - > - **F3 — the owner watches a real turn and says it reads as alive.** Manual, - recorded, and the recording replaces the README's screenshot. The same human - gate 0.1.0 put on its look; a test can assert a frame changed and cannot - assert that a person stopped wondering whether it had hung. - - > - **F4 — the state word survives everything the animation does not.** Under - `NO_COLOR` the indicator does not animate and the word is still there; - the status line's narrow form still drops whole fields rather than wrapping. - Sabotage: replace the word with the spinner, under which only F4 fails. - - > - **F5 — a step renders as decision, then tool and target, then result, with - the count and the step number trailing.** Asserted over the rendered line's - order, not merely its contents. Sabotage: put the token count in front of - the decision, under which only F5 fails. - non_functional: - - > - **N1 — no timing assertion anywhere, again.** Inherited from 0.1.0 and - re-checked mechanically: no test measures elapsed time and no test sleeps. - The tick is driven by an injected clock precisely so that this holds. - - > - **N2 — the dependency set does not grow.** `cargo tree --depth 1` is still - the eight names 0.1.0's N1 permits. A spinner is a handful of characters and - a modulo; a crate for it would be the beginning of the thing this product - exists not to become. - - > - **N3 — every structural property of 0.1.0 still holds.** No alternate - screen, no mouse capture, no full-screen clear, the terminal always - restored, the credential never rendered. The existing tests carry these and - they must all still pass — a release that adds a repaint loop is exactly the - kind that could quietly start clearing the screen. - operational: - - > - **O1 — CI green on macOS, Linux and Windows**, on the feature branch and - again on the merge to `develop`. - - > - **O2 — the binary builds in release profile and runs.** Standing gate; CI - builds the test profile only. - - > - **O3 — the branch flow is followed exactly.** `feat/0.1.1` off `develop`, - one pull request into `develop`, `develop` to `main` by pull request, the - tag `v0.1.1` pushed alone. The rulesets make this mechanical. - - > - **O4 — the Release carries the same four artifacts and `SHA256SUMS`**, and - `install.sh` resolves the new version without change, because the artifact - naming is part of the public contract and this release must not churn it. -delivery: - target_mode: published - environment: > - The developer's own machine — macOS, Linux and Windows terminals, including - inside tmux and over SSH. GitHub for the pull requests, CI, the Release and - its assets. No service and no registry, as 0.1.0 established. - deployment_method: > - One feature branch `feat/0.1.1` cut from `develop`. One pull request into - `develop`, merged with the PR button; `git pull origin develop` immediately - after. Then a pull request from `develop` into `main`. The UltraShip record is - sealed and the lock pinned on the feature branch so it merges with the code. - The tag `v0.1.1` is pushed alone, which cuts the Release; the workflow it - triggers cross-compiles the four artifacts, writes `SHA256SUMS` and attaches - all five. There is no publish step after the Release, because there is no - registry. - migrations: - - > - None. The configuration file 0.1.0 writes is read unchanged; this release - adds no setting and reads no new section. - observability: - - > - Unchanged from 0.1.0: the run's durable trace in the harness store, and a - `RUST_LOG`-gated stderr log that is silent by default. No telemetry. - rollback: > - Deleting the tag and the Release removes the artifacts and the install path, - both reversible. A user already on 0.1.1 is handled by fixing forward, since - the install script always resolves the latest Release. The `develop`-to-`main` - merge is reverted by a revert commit through a pull request. - - - 0.1.0 remains installable by setting `IO_VERSION`, which is the reason that - variable exists. -dependencies: - products: - - > - io-harness 0.60.1 or later, unchanged. Nothing in this release requires a - harness change, and if one is discovered it is a harness release and this - one waits. - services: - - > - One live model provider, for F3's real turn. - - > - GitHub Actions and GitHub Releases, which remain the sole distribution - channel. - human_approvals: - - > - The owner watches a real turn and says whether it now reads as alive. This - is F3 and it cannot be delegated to a test — the defect being fixed is that - the interface *looked* stopped, and only a person can say it no longer does. -resource_profile: - complexity: small - uncertainty: low - likely_cost_drivers: - - > - **Making the tick assertable without asserting on time.** The work is not - the timer; it is the seam that lets a test advance a clock by hand, which is - what keeps N1 true. Getting that seam right is most of the release. - - > - **Not repainting when idle.** The easy implementation ticks always, and F2 - exists because that is the version that would ship by accident. - preferred_tools: - - "`tokio::time::interval` for the tick, behind a clock the tests can drive" - - "the existing byte-stream and viewport harnesses in `tests/support/mod.rs`" - constraints: - - > - **No new dependency.** N2 asserts it. - - > - **No timing assertion, on any test, on any platform.** N1 asserts it. - - > - **The word is never replaced by the animation.** A state that only a moving - character conveys is a state a screen reader cannot read. -risks: - - > - **A repaint loop is the easiest way to reintroduce flicker.** 0.1.0's whole - differentiation is that the viewport repaints only when something changed, and - this release adds a timer that repaints on purpose. Mitigation: the tick is - live only while a turn is running, F2 asserts the idle case, and synchronized - output already wraps every frame. - - > - **The spinner is the part most likely to expand.** Frames, colours, a second - animation for tool calls. The fallback scope names it as the first thing to - drop for that reason. -open_questions: - - > - **What does the tick cost on a session that is streaming quickly?** Token - events already drive repaints; a timer on top of them may be redundant while - text is arriving and only useful while it is not. Worth measuring — and - recording, not gating — before choosing an interval. - - > - **Should the step line collapse a long tool target?** A path of two hundred - characters is the common case and the picker already has a fitting rule. Reuse - it, or leave the step to wrap, is a decision for when the line is first - rendered against real content. -released_at: "2026-08-16T18:47:29Z" -mode: published -delivered: > - The session stops looking frozen while it works. A turn in flight advances its - own clock and turns its own indicator, and neither waits for an event to - arrive. The screenshot this release put in the README is the proof and the - case: forty seconds into a slow first token, no answer on screen yet, the - status line reading `deepseek/deepseek-v4-flash-0731 · ⠨ working · 40s` with - the indicator turning. Under 0.1.0 that same moment was a still word beside a - still clock, which is what a hung process looks like. - - - The mechanism is one function. `App::tick(age) -> bool` takes the session's age - as an argument rather than reading a clock, and answers whether a frame is owed - rather than drawing one. That is what makes liveness assertable without a - timing assertion: a test advances the clock by hand and asks. It also decides - the half that matters more — an idle session is told no, so this renderer still - repaints only when something changed, which is the whole of its differentiation - from the alternate-screen products. - - - A step now reads as a step: the decision, the tool it called with its target, - the result, and then the token count and the step number as trailing muted - detail. The result is stated in both directions, `changed files` or `no - change`, so a transcript is skimmed down one column instead of parsed. - - - Nothing else moved. No key, no command, no event surface, no setting this - release starts reading, and the eight dependencies are the same eight. A - spinner is a handful of characters and a modulo. -evidence: - tests: - - "cargo test — 110 passing, 0 failing, 4 ignored (tests/live.rs, ignored by design), across 22 test binaries" - - "cargo test --test tick — 4 passing: F1 repaints across two ticks with no event ever delivered; F2 pumps ten idle ticks and asserts the byte recorder gained nothing at all" - - "cargo test --test status — 7 passing, including a full ten-frame cycle asserting NO_COLOR keeps the word and shows no frame" - - "cargo test --test events — 12 passing; the two F5 tests assert the ORDER of the five facts by their positions in the rendered line, not their presence" - - "cargo test --test timing — 2 passing: no test in the repository sleeps or reads a clock, and src/main.rs is the only module that reads one" - - "six sabotage arms run, each failing only its own criterion's tests: tick never repaints (F1 alone); guard deleted (F2's three alone); spinner replaces the word (tests/status.rs alone); token count in front of the decision (the two F5 tests alone); a sleeping test appended (the scan named its file and line); an Instant::now added to src/app.rs (the driver-only test named it)" - builds: - - "cargo fmt --all -- --check — exit 0" - - "cargo clippy --all-targets -- -D warnings — exit 0" - - "cargo build --release — exit 0; ./target/release/io --version prints io 0.1.1" - - "cargo tree --depth 1 — the same eight names 0.1.0's N1 permits, plus tempfile as a dev-dependency; nothing was added for the animation" - reviews: - - "F3 completed by the owner, 2026-08-17: the release binary run against a live OpenRouter provider, a real turn watched, verdict `spinner and timer working fine`" - - "the capture from that run is docs/screenshot.png and is now the README's screenshot, replacing the one 0.1.0 recorded as a picture of a defect" - deployments: - - "CI green on macOS, Linux and Windows plus lint, msrv and five CodeQL analyses, on feat/0.1.1 at b478d02" - health_checks: - - "the release binary starts, prints io 0.1.1, and the live session in the F3 run reached a provider and streamed" -known_limitations: - - > - **An idle session's clock is still frozen, and that is F2 rather than a - defect.** Between turns the elapsed time advances only when a key arrives. - Making it tick when nothing is happening is exactly the repaint-forever - behaviour this renderer exists not to have, so the frozen idle clock is the - price of the property and is deliberate. - - > - **The tick's cost while text is streaming quickly was not measured.** The - contract raised it as an open question to record rather than to gate, and it - was recorded rather than measured: token events already drive repaints, so a - timer on top of them is redundant while text is arriving and useful only while - it is not. The interval is 100 ms, chosen for how motion reads rather than - from a measurement. If a later release measures it, the number belongs there. - - > - **A long tool target in a step line wraps rather than being fitted.** The - picker has a fitting rule that could be reused; the contract left the decision - to the first rendering against real content, and the real content in the F3 - run had no long target in it. Left to wrap, deliberately, rather than - designing against a case not yet seen. - - > - **The indicator is braille and there is no ASCII fallback.** Every frame is - one cell wide, which is why braille was chosen, but a terminal without the - glyphs shows a replacement character where the animation should be. The word - beside it is unaffected, and `NO_COLOR` removes the animation entirely. A - proper plain mode is 0.6.0. - - > - **The README screenshot shows the liveness fix and not the step fix.** The - turn it captures was in its first step, so no step line had committed yet. The - picture proves the clock and the indicator, which is what the release is named - for; F5 is proved by tests asserting the rendered order rather than by the - image. - - > - **Two of 0.1.0's four interface limitations are untouched and stay where the - roadmap put them.** The model picker still has no type-to-filter (0.7.0, - decided with the owner at 0.1.0's release and widened there to every picker), - and a prompt longer than two rows still scrolls within them rather than - growing the viewport (0.7.0, because ratatui fixes an inline viewport's height - at construction). This release fixed the other two. - - > - **An action that needs approval is still declined, and the configuration's - `[sandbox]`, `[run]` and `[instructions]` sections are still not applied to a - turn.** Both are 0.1.0 limitations carried forward unchanged; the approval - overlay is 0.2.0 and the contract sections wait on a harness entry point that - takes both a caller's contract and a steer inbox. - - > - **This record is sealed before the Release exists, which is the flow rather - than a shortfall.** The sealed record and its lock pin ride the feature branch - and merge with the code, so the seal necessarily precedes the tag that cuts - the Release and the workflow that attaches the four artifacts and - `SHA256SUMS`. `mode: published` describes the channel this product has. If the - tag or the matrix fails after this point, the correction is a new version, not - an edit. - - > - **The `fmt and clippy` job failed once on this branch and was re-run.** The - failure was seven seconds into `dtolnay/rust-toolchain`, before any of this - repository's own commands ran, on a SHA that had just gone green on `develop`. - Recorded rather than smoothed over: a failure inside a toolchain download is a - flake, and the same two commands exit 0 locally and in the re-run. -supersedes: 0.1.0 -next_recommended_version: 0.2.0 -immutable: true diff --git a/.ultraship/products/io-cli/releases/0.2.0.yaml b/.ultraship/products/io-cli/releases/0.2.0.yaml deleted file mode 100644 index c81ddbd..0000000 --- a/.ultraship/products/io-cli/releases/0.2.0.yaml +++ /dev/null @@ -1,639 +0,0 @@ -product: io-cli -version: 0.2.0 -status: released -release_type: minor -outcome: - user_can: > - The operator can see the boundary the agent is working under, change it, and - answer it when it asks. One key cycles the permission posture, and the status - line names the io-harness policy layer now in force rather than a flag of - io-cli's own. - - - When an action needs permission, the run stops in an overlay that cannot - scroll away. It states the act, the target, and the rule and layer that are - asking, and for a write it shows the content the agent proposes to leave - behind. It is answered by keystroke — allow once, allow for the rest of this - session, deny — and the decision commits one line into the transcript, which - is the terminal's own scrollback, and into the run's durable trace. - - - When something is refused, the transcript says which act, on which target, by - which rule, in which policy layer. No other terminal agent can print the last - two, because no other core records them. - - - And the status line stops being three fields. It carries the policy layer in - force, the tokens the session has spent, how close the assembled context is to - being folded, and how this run's commands are actually contained — the mode - asked for and the backend that answered on this host. - business_value: > - This is the release the product's own thesis rests on. `product.yaml` states - the differentiator in one sentence — io-harness records the permission - boundary, the spend ceiling and the agent tree as first-class facts, and - io-cli is the interface that makes them visible — and until this release - ships, the interface makes none of them visible. 0.1.0 and 0.1.1 delivered a - renderer any competing product could have built. - - - It also closes the one dead end the product has. Under the *ask before - writes* posture, io-cli currently declines every write and every command, - because `DenyAll` is the approver it hands the harness. A live first run - walked straight into it: the agent was denied three times, asked for - permission, and the session had no way to give it. 0.1.0 shipped that as a - known limitation, 0.1.1 carried it forward unchanged, and the wizard says so - out loud at the moment a user picks the posture. A posture whose name does not - describe its behaviour is a defect that has now been deferred twice, and it is - the one the roadmap put here. - evidence_of_value: > - The first three success measures in `product.yaml` are reachable only after - this release, and one of them is written as a test: *a refusal in a real run - names its rule and its layer on screen, and the same refusal is in the durable - trace.* - - - The criteria below inherit 0.1.1's discipline and extend it. Order is asserted - by position and never by `contains` — 0.1.1 paid for that lesson when a - sabotage that moved the token count in front of the decision left a membership - assertion green. Absence is asserted as absence: a status field with no fact - behind it renders as nothing at all, not as a zero, and the sabotage arm for - it is the version that would ship by accident. - - - Two criteria exist because the harness's own semantics make the obvious - implementation wrong, and neither is visible from the screen. *Allow for the - rest of this session* is one: `Decision::Approve { remember }` installs a - run-scoped layer that dies with the turn, so an implementation that stops - there would ask again on the next prompt and look like a bug in memory. And a - posture indicator that is not backed by the `Policy` actually handed to the - next turn is a mode display that displays nothing. Both are asserted on the - values passed to the harness, not on the rendered line. - - - F10 is a human gate for the same reason 0.1.0's and 0.1.1's were. What is - being shipped is a moment — a run stopping to ask, and a person answering it — - and no test can say whether that moment reads as clear or as alarming. -scope: - included: - - > - **An interactive approver.** io-cli stops handing the harness `DenyAll` and - hands it an approver that asks the operator. The request travels off the - run's task to the interface, the answer travels back, and the run resumes. - This seam is the release; everything else here is rendering, and this is the - one piece that changes what a session can do. - - > - **The overlay is fed by the approver, not by the event stream.** This is the - structural fact the design has to be built around: - `EventKind::ApprovalRequested` carries only `act` and `target`, while the - rule, the layer and the proposed content arrive as the `Request` and - `ApprovalContext` handed to `Approver::decide_in_context`. So io-cli's one - existing seam — a one-way observer channel — cannot render this surface, and - a second, two-way seam is what is being built. - - > - **The approval overlay.** A viewport overlay, never a history cell, because - a question that has scrolled above the fold blocks a run nobody can see is - blocked. It states the act, the target, and the asking rule and layer in that - order, and for a write it shows the content the agent proposes. It is the - existing `Picker` with a body above its rows — one overlay component in the - product, so an approval and a model list are visibly the same thing. - - > - **Three answers, each on its own key and each also reachable by arrow and - `Enter`**, because a key that only works for a reader who already knows it is - not an interface. Allow once is `Decision::approve()`. Deny is - `Decision::deny(reason)`, with a reason that reaches the model. Allow for the - rest of this session is two things and must be built as two: the harness's - run-scoped `remember` for the turn in flight, and the same - `io_harness::Rule` carried into the `Policy` io-cli hands every later turn. - - > - **One committed line per decision.** On resolution the overlay closes and - commits exactly one line into the terminal's scrollback naming the act, the - target and the decision, so the decision is in the transcript as well as in - the harness's trace, which already records it. - - > - **Posture cycling on one key.** `Shift+Tab` moves through the three postures - `settings.rs` already declares, and the status line names the policy layer - now in force. It changes this session, like `/theme` and `/model` and for the - same reason; `io setup` is what makes a choice permanent. It takes effect on - the next turn, which is what the harness allows: `Policy` is a per-turn - argument and there is no `Session::set_policy`. - - > - **The refusal surface.** A refusal becomes a designed cell rather than the - plain notice 0.1.0 shipped as a placeholder: the act, the target, the rule - and the layer that rule came from, in that order. The two facts no other core - records are never dropped when the event carries them — and when it does not, - the line says the tier default decided rather than saying nothing, because in - io-harness an absent rule means exactly that. - - > - **The rest of the status line, as far as the harness can supply it.** The - policy layer in force; the tokens the session has spent, accumulated from the - `Step` events already rendered; how close the assembled context is to a fold, - with the denominator taken from the harness's own `ContextBudget` rather than - from a number copied into this repository; and containment, as the mode *and* - the backend that actually answered on this host — never the mode alone, which - is an intention rather than a fact. Each is absent when nothing has supplied - it. The fields are already named in `status.rs`; this fills them in. - - > - **The documentation that is mechanically checked.** `Shift+Tab` in the - README's key table and in `/help`, which are the same list; the CHANGELOG - section for `0.2.0`; the README's *what this release is not*, which has named - the approval overlay as 0.2.0's since 0.1.0 and must now stop; and the - README's *what this release reads from that file*, which changes because a - posture can now be cycled. - - > - **The tests and the sabotage arms for all of it**, including the arm that - drops the answer channel and asserts the run is denied rather than blocked. - excluded: - - > - **Spend against the tree ceiling.** It cannot be rendered from this - product's turn and this is a harness boundary, not a scoping preference: - `EventKind::SpendDraw` is emitted only by a contained turn, and the contained - entry point takes no steer inbox — so rendering spend today would cost - `Ctrl+C`. It is the same blocker the README already documents for the - configuration's `[sandbox]` and `[run]` sections. Moved in the roadmap to - 0.8.0, which is the fleet release and therefore the release that has to be - contained anyway. If a harness turn that is both contained and steerable - appears sooner, it is a harness release and this moves with it. - - > - **Diff rendering as a designed surface.** The harness hands an approver the - full post-write content, not a patch, so a diff is io-cli's to compute and - 0.3.0's to design. This release shows the proposed content plainly. - Unified-versus-split, word-level emphasis, syntax highlighting, collapsed - context with an expander and a `minimal` setting are all 0.3.0. - - > - **`Decision::Defer` and `Decision::Approve { modified }`.** Deferring stops - the run and persists the request for a decision later, which is only useful - alongside the resume this product does not have until 0.4.0; approving a - rewritten action is an editor inside an overlay. Both are real harness - affordances and both are deliberately not bound to a key here. - - > - **Collapsible tool cells and the transcript overlay.** 0.3.0, unchanged. - - > - **Answering a question the agent asked.** `question_asked` asks what was - meant, which the harness deliberately distinguishes from an approval about - what is permitted, and answering one needs a `Responder` installed on a - caller-supplied contract — the same entry point that costs the steer inbox. - 0.7.0, where the roadmap already puts it. - - > - **Persisting a cycled posture to the configuration file.** Cycling is for - this session and says so, matching `/theme` and `/model`. Writing the file is - `io setup`'s job, and a key that silently rewrites a permission boundary on - disk is the opposite of what this release is for. - - > - **Session resume, fork and rewind** (0.4.0), **the headless subcommand** - (0.5.0), **plain mode** (0.6.0), **type-to-filter on any picker and a - growable composer** (0.7.0). All unchanged, all where the roadmap put them. - fallback_scope: - - > - **The interactive approver, the overlay, and the refusal surface.** This is - the spine and it is coherent on its own: a run that needs permission stops - and asks, the operator answers it, and a refusal names its rule and its - layer. The *ask before writes* posture stops being a posture that declines - everything, which is the defect this release exists to close. - - > - **The three new status fields drop first**, together. They are rendering of - facts that already arrive, and the status line is a legible three fields - today rather than a broken one. - - > - **Posture cycling drops second.** The wizard already sets a posture and `io - setup` already changes it; the key is ergonomics on top of a capability that - exists. It drops after the status fields because the field naming the layer - in force is what makes cycling legible, and shipping the key without it would - be a mode indicator that indicates nothing. - - > - **Within the spine, *allow for the rest of this session* is the last thing - in and the first thing out.** Allow-once and deny are the release; carrying a - remembered rule into later turns is the convenience on top, and it is the - part with a harness subtlety underneath it. -acceptance: - functional: - - > - **F1 — an approval request opens an overlay and commits nothing to - history.** With a request delivered, the viewport holds the question and the - scrollback gains nothing at all. Sabotage: render the request as a committed - line, under which only F1 fails. This is the structural property the whole - surface rests on — a question in the scrollback is a question that can be - scrolled away from a run that is blocked on it. - - > - **F2 — the overlay states the act, then the target, then the rule and the - layer, in that order.** Asserted over the positions of the facts in the - rendered lines, never over their presence. Sabotage: put the rule first, - under which only F2 fails. - - > - **F3 — each answer resolves the request as itself, and commits exactly one - line.** Allow once, allow for the rest of the session and deny each produce - the matching `io_harness::Decision`, and each closes the overlay and commits - one line naming the act, the target and the decision. Sabotage: map every key - to allow, under which only F3 fails. - - > - **F4 — an unanswered request denies rather than hangs.** Dropping the - interface end of the answer channel resolves the request as a denial. No - clock, no timeout, no sleep: the assertion is on what the approver returns - when the channel is closed. Sabotage: block on the receiver, under which only - F4 fails — and it fails by never finishing, which is why the arm is run under - the suite's own harness rather than by hand. - - > - **F5 — *allow for the rest of this session* survives the turn it was given - in.** Asserted on the `Policy` handed to the *next* turn: it carries a rule - that the previous answer produced. The harness's own `remember` is - run-scoped and dies with the turn, so an implementation that uses only it - passes F3 and fails F5 — which is the whole reason this is its own criterion. - Sabotage: pass `remember` and keep nothing, under which only F5 fails. - - > - **F6 — `Shift+Tab` cycles the posture and the status line names the layer in - force.** Both spellings a terminal may send — `BackTab`, and `Tab` with the - shift modifier — reach the same action, or the key works in one class of - terminal and silently does nothing in the other. Sabotage: cycle without - updating the field, under which only F6 fails. - - > - **F7 — the cycled posture is what the next turn actually runs under.** - Asserted on the `io_harness::Policy` handed to the turn, not on the label - beside it. A mode indicator not backed by the thing it names is invisible to - any assertion on a rendered line. Sabotage: update the label only, under - which only F7 fails. - - > - **F8 — a refusal renders act, target, rule and layer, in that order, and an - absent rule reads as the tier default rather than as silence.** Asserted by - position, and asserted twice: once with a named rule and layer, once with - neither, because in io-harness `None` means the tier default decided and that - is a different sentence, not a missing one. Sabotage: drop the layer, under - which only F8 fails. - - > - **F9 — a status field with nothing behind it is absent, not zero.** Before - any `Contained` event the line carries no containment field; after one it - carries the mode and the backend together. The same for context pressure and - the token count. Sabotage: render a zero for an unknown value, under which - only F9 fails. A field that invents its own value is worse than no field. - - > - **F10 — the owner runs a real turn under *ask before writes*, approves one - write, denies another, and says the overlay reads right.** Manual and - recorded. The same human gate 0.1.0 and 0.1.1 put on their look, for the same - reason: what ships here is a moment, and only a person can say whether a run - stopping to ask reads as clear or as alarming. - non_functional: - - > - **N1 — no timing assertion anywhere, a third time.** `tests/timing.rs` still - passes: no test sleeps, no test reads a clock, and `src/main.rs` is still the - only module that does. The approval path is the shape most likely to be - tested with a timeout, and F4 exists so that it is not. - - > - **N2 — the dependency set does not grow.** `cargo tree --depth 1` is still - the eight names 0.1.0's N1 permits, plus `tempfile` as a dev-dependency. An - approval channel is `tokio::sync::oneshot`, which is already here. - - > - **N3 — every structural property of 0.1.0 still holds.** No alternate - screen, no mouse capture, no full-screen clear, the terminal always restored, - the credential never rendered. The existing tests carry these, and an overlay - that shows a file's proposed content is exactly the kind of change that could - put a secret on screen — the credential test is not incidental here. - - > - **N4 — io-cli still contains no policy engine.** `tests/dependencies.rs` - still passes. This release renders a `Policy`, constructs `io_harness::Rule` - values from a human's answer, and relays that answer. It evaluates no rule - and decides no permission; every verdict is still the harness's. - - > - **N5 — every new surface has a defined form at eighty columns**, and the - status line still drops whole fields from the right rather than wrapping, - with three more fields to drop. `tests/narrow.rs` covers the line; the - overlay joins it, including the case where the proposed content is wider than - the terminal. - operational: - - > - **O1 — CI green on macOS, Linux and Windows**, on the feature branch and - again on the merge to `develop`. - - > - **O2 — the binary builds in release profile and runs.** Standing gate; CI - builds the test profile only. - - > - **O3 — the branch flow is followed exactly.** `feat/0.2.0` off `develop`, - one pull request into `develop`, `develop` to `main` by pull request, the tag - `v0.2.0` pushed alone. The sealed record and its lock pin ride the feature - branch and merge with the code. - - > - **O4 — the Release carries the same four artifacts and `SHA256SUMS`**, and - `install.sh` and `install.ps1` resolve the new version without change. The - artifact naming is part of the public contract and this release must not - churn it. -delivery: - target_mode: published - environment: > - The developer's own machine — macOS, Linux and Windows terminals, including - inside tmux and over SSH. GitHub for the pull requests, CI, the Release and - its assets. No service and no registry, as 0.1.0 established and as - `product.yaml` fixes: publishing to crates.io is a non-goal. - deployment_method: > - One feature branch `feat/0.2.0` cut from `develop`. One pull request into - `develop`, merged with the PR button; `git pull origin develop` immediately - after. Then a pull request from `develop` into `main`. The UltraShip record is - sealed and the lock pinned on the feature branch so it merges with the code. - The tag `v0.2.0` is pushed alone, which cuts the Release; the workflow it - triggers cross-compiles the four artifacts, writes `SHA256SUMS` and attaches - all five. There is no publish step after the Release, because there is no - registry. - migrations: - - > - None. The configuration file the wizard writes is read unchanged, and this - release writes no new section: a cycled posture and a remembered allow live - for the session only. A user on 0.1.1 gains the approval overlay without - touching their file, and a user who chose *ask before writes* gains the - behaviour its name always described. - observability: - - > - Unchanged in kind: the run's durable trace in the harness store, and a - `RUST_LOG`-gated stderr log that is silent by default. What changes is what - the trace contains — every approval decision the operator made, because the - harness records the answer it was given as `EventKind::ApprovalDecided` and - as a resolved row in the store. No telemetry. - rollback: > - Deleting the tag and the Release removes the artifacts and the install path, - both reversible. A user already on 0.2.0 is handled by fixing forward, since - the install script always resolves the latest Release. The `develop`-to-`main` - merge is reverted by a revert commit through a pull request. - - - 0.1.1 remains installable by setting `IO_VERSION`, and a user who wants the - old behaviour without downgrading has it already: the *sandboxed workspace* - posture asks for nothing, which is what 0.1.1 effectively forced everyone into. -dependencies: - products: - - > - io-harness. Every piece of this release exists in the version this - repository is locked to: `Approver::decide_in_context`, `Request`, - `ApprovalContext`, `Decision`, `Rule`, and the `Refused`, `Contained`, - `Compacted` and `Step` events. The lockfile resolves 0.60.1 and 0.60.3 is - published; the two are API-identical, so moving the pin is a decision to make - deliberately at the start of development rather than a thing this release - needs. If any part of the work turns out to require a harness change, that is - a harness release and this one waits rather than reimplementing the missing - piece here. That rule is the product's first constraint. - services: - - > - One live model provider, for F10's real turn — with a task that will - actually attempt a write, so the overlay is exercised rather than described. - - > - GitHub Actions and GitHub Releases, which remain the sole distribution - channel. - human_approvals: - - > - The owner runs a real turn under *ask before writes*, approves one write and - denies another, and says whether the overlay reads right. This is F10 and it - cannot be delegated to a test. -resource_profile: - complexity: medium - uncertainty: medium - likely_cost_drivers: - - > - **The two-way seam.** Everything io-cli has drawn so far arrived on a - one-way channel from an observer that runs on the agent's own task and hands - the event on without blocking. An approver is the opposite shape: it must - ask, wait, and answer, while the keyboard stays live so `Ctrl+C` still - reaches the turn and the event drain does not stall behind the question. - Getting that seam right is most of this release, and F4 is the criterion that - says so. - - > - **A four-row viewport.** The inline viewport's height is fixed at - construction and cannot grow without rebuilding the terminal, which would - re-query the cursor and risk the scrollback this product exists to protect. - An approval that shows a proposed file's content does not fit in four rows. - Deciding what goes in the overlay and what is committed above it is a design - decision, not an implementation detail — see the open questions. - - > - **Three status fields, each with an absence and one with a denominator.** - The rendering is trivial and the semantics are not: what the line says before - the first `Contained` event, and where the number that context pressure is a - share *of* comes from, are decisions rather than code. The denominator must be - asked of the harness's own `ContextBudget`, because a copy of `24_000` in - this repository is a number that will be wrong after some harness patch and - will be wrong silently. - - > - **`remember` is run-scoped.** The obvious implementation of *allow for the - rest of this session* passes every visible test and asks again on the next - prompt. F5 exists because that version would ship by accident. - preferred_tools: - - "`tokio::sync::oneshot` for the answer, which tokio already provides" - - "the existing `Picker` overlay, extended with a body above its rows rather than replaced" - - "`io_harness::Rule` and the `Policy` builders, so a remembered allow is a harness value and not an io-cli concept" - - "the `Recorder` and `Fixed` harnesses in `tests/support/mod.rs`, which assert over the bytes that actually leave the process" - - "`harness_event_kinds()` in the same file, which reads the locked harness's own source rather than a copied list" - constraints: - - > - **No new dependency.** N2 asserts it. - - > - **No timing assertion, on any test, on any platform.** N1 asserts it, and - the approval path is where it will be tempting. - - > - **`Shift+Tab` is not one key event.** A terminal without the Kitty keyboard - protocol sends `BackTab`; one with it sends `Tab` with a shift modifier. - Binding one spelling ships a key that works on the developer's terminal and - nowhere else. F6 asserts both. - - > - **Never invent a field's value, and never render an intention as a fact.** - A containment mode without the backend that actually answered is what the - harness's own documentation calls reading an intention: `workspace-write` - with a portable-floor backend means resource caps only. - - > - **io-cli decides no permissions.** It renders what the harness asked and - relays what the human answered. Any rule evaluation appearing in this - repository is the constraint in `product.yaml` being violated. -risks: - - > - **The approval seam is the one place this product can deadlock.** The - approver runs on the run's task; if it waits on an answer that never comes, - the turn stops forever and `Ctrl+C` may not reach it, because the loop that - would deliver the interrupt is the one now blocked. Mitigation: F4 asserts a - closed channel denies, and the interrupt path is exercised while an approval is - open rather than only while text streams. - - > - **The overlay does not fit.** Four rows hold a question or a proposed file, - not both. Mitigation: the release decides this explicitly and records the - decision rather than discovering it when the first real write arrives; the - fallback is to commit the proposed content above the overlay, where it is one - line up in the scrollback and cannot be pushed away because the run is blocked. - - > - **An absent rule read as an absent boundary.** `rule` and `layer` are `None` - when the tier default decided, which is the *least* vouched-for kind of - decision — and a surface that renders `None` as blank tells the reader the - opposite of what happened. F8 asserts both cases for exactly this reason. - - > - **A secret on screen.** The overlay shows a file's proposed content, and the - harness's built-in secrets layer exists because agents do touch `.env` and key - files. A write the policy would refuse never reaches an approver, but the - surface is new and the credential test is the one that must not be treated as - incidental. N3 carries it. - - > - **Scope creep into 0.3.0.** The moment content is on screen, word-level - emphasis and syntax highlighting look like small additions. They are the next - release, they are named in `excluded`, and the fallback scope does not contain - them. -open_questions: - - > - **What goes in the overlay and what is committed above it?** The viewport is - four rows. A question with four facts and a set of answer keys already fills - it. The most likely answer is that the proposed content commits to scrollback - the moment the request arrives and the overlay holds only the question — which - is safe precisely because the run is blocked, so nothing can scroll it away. - Decide it against a real write, not on paper. - - > - **Does `Ctrl+C` mean *deny* or *interrupt the turn* while an overlay is - open?** Both readings are defensible and the wrong one loses work. Worth - deciding before the overlay is built, and worth stating in the README's key - table either way. - - > - **Is *allow for the rest of this session* remembered per act, per target, or - per rule?** The narrowest is the act and the target together, which is what a - person means when they say yes to the same question twice. Recorded here rather - than guessed at in the implementation, and it decides what `io_harness::Rule` - gets constructed. - - > - **Where does the context number come from between folds?** `Compacted` fires - only when a fold happens. The per-step assembled estimate is in the store, as - `ContextEvent::est_tokens`, which is a read rather than an event. Whether the - field reports a live share or only the fold state is a decision for the first - rendering against a real session. - - > - **Should the lockfile's harness version move to 0.60.3?** The public API is - identical, so nothing here needs it. It is a deliberate decision to make with - the changelog open at the start of development, not a side effect of a `cargo - update`. -released_at: "2026-08-17T02:24:43Z" -mode: published -delivered: > - The operator can see the boundary the agent is working under, change it, and - answer it when it asks. Through 0.1.0 and 0.1.1 the approver handed to - io-harness was `DenyAll`, so the *ask before writes* posture declined every - write and every command it was named for, and a live first run walked straight - into it. That dead end is closed. - - - The release is one seam and four surfaces drawn on it. Every other connection - this product has to io-harness is one-way: an observer is handed an event and - hands it on, and nothing waits. `Approver::decide_in_context` is the opposite - shape — it runs on the agent's own task and the run stays paused until the - future resolves — so the correctness question is not whether an answer arrives - but what happens when one never does. The answer is a type: answering consumes - the `Ask`, so dropping one *is* a denial, and both ways of losing the interface - arrive at one closed channel. Asserted with no clock anywhere; the arm that - blocks instead hangs the suite forever, which is what a deadlock looks like. - - - The overlay lives in the viewport and commits nothing, because a question in - the scrollback can be scrolled above the fold while the run is blocked on it. - It states the act and the target, then the rule and the layer on a row of their - own, then the content a write proposes. Three answers on keys and on arrows, - opening on the least committal one. `Shift+Tab` cycles the posture from both - spellings a terminal can send, and the status line names the one in force. A - refusal prints its rule and its layer — the two facts no other terminal agent - can render, because no other core records them. - - - Three of the status line's named placeholders are filled — tokens, context - pressure, containment as mode *and* backend — and each is absent rather than - zero until something supplies it. The fourth is not filled, and the roadmap - moved rather than the record pretending: spend against the tree ceiling is - emitted only by a contained turn, and the contained entry point takes no steer - inbox, so rendering it today would have cost `Ctrl+C`. -evidence: - tests: - - "cargo test — 140 passing, 0 failing, 7 ignored (tests/live.rs, ignored by design) across 25 test binaries" - - "cargo test --test approval — 14 passing: F1 asserts an open question commits nothing, in the pending queue and in the recorder's bytes; F2 asserts act, target, rule and layer by POSITION; F3 runs all three answers and asserts the io_harness::Decision each produced plus exactly one committed line; F4 asserts a closed channel denies; F5 asserts the policy the NEXT turn is handed" - - "cargo test --test policy — 7 passing: F6 cycles from both BackTab and shift-Tab; F7 asserts the verdict on the policy (Allow, then Ask, then Deny) rather than the word beside it; and no posture can unlock what a layer denied, asserted against .env for all three" - - "cargo test --test events — 15 passing, including F8's three refusal cases: a named rule in a named layer, a rule with no layer, and neither, which renders the tier default as a sentence" - - "cargo test --test status — 12 passing, including F9's absent-not-zero and the narrow form at forty columns" - - "cargo test --test narrow — 4 passing, including the overlay at exactly eighty columns with a seventy-character path and two-hundred-character content lines" - - "cargo test --test timing --test dependencies — 9 passing: no test sleeps or reads a clock, src/main.rs is still the only module that does, and the dependency set is unchanged" - - "sabotage arms run for every functional criterion, each failing only its own criterion's tests: F1 (commit the question), F2 (rule first), F3 (every key allows), F4 (block on the receiver — hung the suite, killed by a watchdog at 45s), F5 (pass remember, keep nothing — F3 stayed green, which is why F5 is its own criterion), F6 (cycle without updating the field), F7 (update the label only), F8 (drop the layer), F9 (render zeros)" - - "one sabotage was a finding rather than a failure: an early return on send-error failed no test, because a failed send drops the Ask and closes the oneshot inside it. The branch was a second spelling of one line and was deleted" - builds: - - "cargo fmt --all -- --check — exit 0" - - "cargo clippy --all-targets -- -D warnings — exit 0" - - "cargo build --release — exit 0; ./target/release/io --version prints io 0.2.0" - - "cargo tree --depth 1 — the same eight names 0.1.0's N1 permits, plus tempfile as a dev-dependency. The approval channel is tokio::sync::oneshot, which was already here" - reviews: - - "F10 completed by the owner, 2026-08-17: the release binary run at a real terminal under the ask-before-writes posture, a write approved, verdict `working! approved`" - - "the deny half of F10's wording was proved by the live rehearsal rather than by the owner's own run, and this record says so rather than implying otherwise" - - "live rehearsal against a live provider (anthropic/claude-sonnet-4 over OpenRouter), four tests: a real turn reaches the approver and the approved write lands; a denied turn leaves the file byte-identical; answering `allow this session` collapses three questions into one inside a single turn; and the never-asks path stalls identically, which is how `Stalled` was shown to be the model rather than this release" - deployments: - - "CI green on macOS, Linux and Windows plus fmt-and-clippy, msrv, CodeQL and two CodeQL analyses — all eight checks, no re-runs and no flakes, on feat/0.2.0 at afcfb7c (PR #9)" - health_checks: - - "the release binary starts and prints io 0.2.0" - - "no secret is committed: no .env has ever been added in any commit on any branch (git log --all --diff-filter=A), and the only key-shaped literal in the tree is tests/wizard.rs's deliberate fixture, whose value is the string NEVERRENDERTHIS and whose test asserts it never reaches the screen" - - "scope.excluded verified absent in source: no Decision::Defer binding (it appears once, in a doc comment explaining the exclusion), no modified-request binding, no SpendDraw rendering, no turn_contained call, and no diff or syntax-highlighting crate in Cargo.toml" -known_limitations: - - > - **Spend against the tree ceiling is not in the status line, and the roadmap - moved rather than this record pretending.** `EventKind::SpendDraw` is emitted - only by a contained turn, and io-harness's contained entry point takes no - steer inbox — rendering spend today would have cost `Ctrl+C`. It is the same - harness boundary that already blocks the configuration's `[sandbox]` and - `[run]` sections. Moved to 0.8.0, the fleet release, which is contained by - definition. If a harness turn that is both contained and steerable appears - sooner, it moves with it. - - > - **`rule` and `layer` are `None` in the ordinary case, not the exotic one.** - Every question and every refusal in the live rehearsal came through unnamed, - because the posture's own `write: Ask` default is what asks and no glob names - the action. So `no rule named it: the tier default decided` is what most - operators will see most of the time. That is accurate — io-harness is explicit - that an unnamed action in the grey tier is the least vouched-for kind — but - the sentence is long for a line that repeats, and a shorter form is worth - designing once it has been read a hundred times. - - > - **The overlay shows a write's proposed content plainly, not as a diff.** The - harness hands an approver the resulting file rather than a patch, so the diff - is this product's to compute and 0.3.0's to design. In a four-row viewport the - content gets one row, with the cut count riding its end. - - > - **The context field is quiet by design.** `Compacted` is the only event - carrying an observation-section size, so between folds nothing on the stream - knows it and the field is absent. The per-step estimate is durable in the - store as `ContextEvent::est_tokens`; the upgrade path is one store read and is - marked with a `ponytail:` comment where the field is declared. - - > - **`Decision::Defer` and `Decision::Approve { modified }` are unbound.** - Deferring stops the run and persists the request, which is only useful - alongside the resume that arrives in 0.4.0; approving a rewritten action is an - editor inside an overlay. Both are real harness affordances, deliberately not - half-built. - - > - **A question about intent is still a dead end.** `awaiting_answer` says so and - points at the next prompt. Answering one needs a `Responder` on a - caller-supplied contract, which is the same entry point that would cost - `Ctrl+C`. 0.7.0. - - > - **The answers row has a double space after each separator**, because the - two-cell selection marker sits after a spaced separator. Cosmetic, noticed - during the F10 preview, and deliberately not changed while the owner's gate - was in flight. A one-line fix for 0.2.1 or 0.3.0. - - > - **A turn commonly ends `Stalled` after its work succeeded.** Observed on every - live run of this release — and identically on the never-asks path 0.1.0 - shipped, at the same step count on the same prompt. It is the model rather - than the approval flow, and it is recorded here because an operator will see - it and wonder. - - > - **`live_f1_a_real_turn_streams_and_edits_a_file` currently fails**, and this - release did not cause it. It asserts the turn streamed assistant tokens; the - model stalled without a closing message and streamed none. The test's own - comment records that exact fragility. It is `#[ignore]`d, CI never runs it, - and it belongs to 0.1.0's F1 — left alone rather than reworded into passing. - - > - **One low Dependabot advisory predates this release**: `lru`'s `IterMut` - violates Stacked Borrows, reached through `ratatui 0.29`, which `Cargo.toml` - pins deliberately because `tui-textarea 0.7` is built against exactly that - pair. Moving off it means depending on somebody's fork of the one widget the - composer is made of. A dependency release of its own. - - > - **The harness pin stays at 0.60.1 on purpose.** 0.60.2 and 0.60.3 are - published and API-identical, so nothing here needs them, and moving the pin - would have put an untested dependency change on a release that already changes - the permission surface. - - > - **This record is sealed before the Release exists, which is the flow rather - than a shortfall.** The sealed record and its lock pin ride `feat/0.2.0` and - merge with the code, so the seal necessarily precedes the tag that cuts the - Release. `mode: published` describes the channel this product has. The second - half of O1 — CI green again on the merge to `develop` — and O4's four - artifacts follow this point. If the tag or the matrix fails after it, the - correction is a new version, not an edit. -supersedes: 0.1.1 -next_recommended_version: 0.3.0 -immutable: true diff --git a/.ultraship/products/io-cli/roadmap.yaml b/.ultraship/products/io-cli/roadmap.yaml deleted file mode 100644 index 1433862..0000000 --- a/.ultraship/products/io-cli/roadmap.yaml +++ /dev/null @@ -1,117 +0,0 @@ -product: io-cli -status: active -versions: - - version: 0.1.0 - outcome: > - A developer with no configuration runs `io` in a repository and reaches a finished task in the - same session. A guided first run asks for a provider, takes a key and verifies it against the - live endpoint, offers the provider's models, previews the themes as it moves between them, and - picks a default permission posture — then writes io-harness's own configuration file. The - session that follows renders on a hybrid inline viewport: every finished turn, tool cell and - message is committed into the terminal's real scrollback, and only a short pane at the bottom - holds the composer and the status line. The developer types a task, watches the answer stream, - watches a file get edited inside the sandbox, interrupts with a keystroke, and exits — leaving - the whole conversation scrollable, searchable and selectable with the terminal's own - facilities, because io-cli never took them away. - detail: specified - status: released - - version: 0.1.1 - outcome: > - The session stops looking frozen while it works. A turn in flight shows a moving indicator - beside the word that names its state, so a reader can tell thinking from hung, and the elapsed - clock advances on its own rather than only when an event happens to arrive. A step reads as a - step — what the agent decided, what it ran, and what came back — instead of a decision string - with a token count appended to it. The README gains the screenshot 0.1.0 could not supply, - taken from a run of the corrected interface. Nothing about the permission boundary, the - renderer or the wizard changes: this is the release that makes what 0.1.0 already does legible - while it is happening. - detail: specified - status: released - - version: 0.2.0 - outcome: > - The operator can see and steer the boundary the agent is working under. One key cycles the - permission posture and the status line names the io-harness policy layer now in force. A - sensitive action stops the run in an overlay that cannot scroll away — showing the act, the - target, the rule and layer that are asking, and the content a write proposes — and is resolved - by keystroke into both the transcript and the durable trace. A refusal prints the act, the - target, and the rule and layer that produced it, which no other terminal agent can render - because no other core records it. The status line gains the fields the harness can supply to a - steerable turn: the policy layer, tokens, context pressure, containment. - detail: specified - status: released - - version: 0.3.0 - outcome: > - The operator can read what the agent did without leaving the terminal or losing the thread. - Edits render as diffs — unified by default, word-level emphasis inside changed lines, syntax - highlighting, context collapsed with an expander, a defined form for narrow terminals, and a - minimal mode for people who review by file rather than by hunk. Tool calls render as one line - with a result and a duration, expandable while live and committed collapsed, with the full - output going to the durable trace rather than to the screen. A transcript overlay gives the - whole conversation on one key, and the last message or the last diff can be put on the system - clipboard in a way that survives SSH. - detail: outline - status: planned - - version: 0.4.0 - outcome: > - Work survives the session. A picker lists previous sessions from the harness store and reopens - one where it stopped. Any turn in a conversation can be forked into a new branch and continued - from there. The model can be changed mid-session without losing context. An empty composer - takes a double-escape to rewind the conversation and the workspace to an earlier point, using - the rewind the harness already implements and already records. - detail: outline - status: planned - - version: 0.5.0 - outcome: > - The same agent runs unattended. A non-interactive subcommand takes a goal, runs it to - completion, and exits with a meaningful status; with a flag it emits the run's events as - newline-delimited JSON — the same events the interactive session renders, from the same - stream, with no interface code on the path. Sandbox and policy are selectable from the command - line, a non-TTY stdout is detected and honoured without being asked, and credentials are taken - from the environment so nothing has to be written to disk in CI. - detail: hypothesis - status: planned - - version: 0.6.0 - outcome: > - The interface reaches people and terminals the category currently excludes. A plain mode built - for screen readers removes animation and repaint, keeps the real terminal cursor as the focus - indicator, puts content before metadata, and announces every state change as one line of text - — which no terminal agent in this field does today. Colour is never the sole carrier of - meaning, `NO_COLOR` is honoured, box drawing degrades to ASCII, the keyboard protocol is - negotiated up on terminals that advertise it and down over SSH without losing core function, - keybindings are configurable and documented as a table, and every surface has an audited form - at eighty columns. - detail: hypothesis - status: planned - - version: 0.7.0 - outcome: > - The composer becomes a command surface. A slash palette with fuzzy matching reaches every - command and every harness skill and prompt template; `@` completes paths from the workspace; - `!` passes a line to the shell verbatim; a large paste becomes an attachment instead of - flooding the input. Every picker filters as you type — the model list, the theme list, - `/resume` and `/fork` — so a four-hundred-model catalogue is reached by typing three - characters rather than by holding an arrow key. The agent's own plan and todo list render as a - live panel, and a question the agent asks about intent — which the harness distinguishes from - an approval about permission — is answered inline rather than guessed at. - detail: hypothesis - status: planned - - version: 0.8.0 - outcome: > - A decomposed task becomes visible as a tree. Spawned children appear as a live fleet with - their own status, each showing its draw against the ceiling the whole tree shares, with - refused spawns and collected results shown where they happen. The status line gains the spend - field here rather than at 0.2.0: `SpendDraw` is emitted only by a contained turn, and the - contained entry point takes no steer inbox, so a session that renders spend today is one that - cannot be interrupted. This release is contained by definition, which is what makes the field - reachable. This is the surface that is structurally impossible for a competing interface to - copy, because it is rendering facts only this core emits. - detail: hypothesis - status: planned - - version: 0.9.0 - outcome: > - The remaining harness capabilities get a face. Images render inline where the terminal speaks - Kitty, iTerm2 or Sixel and degrade to half-blocks where it does not, and an image can be - attached to a turn. Background shell handles, browser sessions, LSP servers and MCP servers - each get a compact live indicator, so a run that has started something long-lived says so - rather than appearing idle. - detail: hypothesis - status: planned diff --git a/.ultraship/questions/.gitkeep b/.ultraship/questions/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.ultraship/releases.lock b/.ultraship/releases.lock deleted file mode 100644 index c71b03e..0000000 --- a/.ultraship/releases.lock +++ /dev/null @@ -1,4 +0,0 @@ -releases: - io-cli/0.1.0: sha256:e25f995472436a25bc7e949e5c7e132fb5582b176027e23ee87235cfe2ab6611 - io-cli/0.1.1: sha256:4b423d161651c290e318601ae7872796eab66dec975f80a4db276f6345f26488 - io-cli/0.2.0: sha256:62682780453a86d4216055593dd250c2b2f86b415e2bf0539102b1b3a3d38363 diff --git a/.ultraship/ultraship.yaml b/.ultraship/ultraship.yaml deleted file mode 100644 index 009b525..0000000 --- a/.ultraship/ultraship.yaml +++ /dev/null @@ -1,20 +0,0 @@ -schema_version: 1 -framework_version: 2.2.0 -commit_policy: checkpoint -resource_profile: - provider_tool: null - billing_mode: null - model_policy: auto - available: - time_minutes: null - token_budget: null - monetary_budget_usd: null - human_review_minutes: null - preferences: - minimize_cost: true - allow_additional_spend: false - allow_model_routing: true - allow_parallel_agents: false - telemetry: - source: unknown - confidence: low diff --git a/.ultraship/views/.gitkeep b/.ultraship/views/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.ultraship/views/active-releases.md b/.ultraship/views/active-releases.md deleted file mode 100644 index 03c7287..0000000 --- a/.ultraship/views/active-releases.md +++ /dev/null @@ -1,10 +0,0 @@ - -# Active releases - -_No release is currently active._ - - -_Canonical sources: products//execution/active.yaml, products//releases/.yaml_ diff --git a/.ultraship/views/master-roadmap.md b/.ultraship/views/master-roadmap.md deleted file mode 100644 index 6fa4b08..0000000 --- a/.ultraship/views/master-roadmap.md +++ /dev/null @@ -1,23 +0,0 @@ - -# Master roadmap - -## io-cli - -| Version | Outcome | Detail | Status | -| --- | --- | --- | --- | -| 0.1.0 | A developer with no configuration runs `io` in a repository and reaches a finished task in the same session. A guided first run asks for a provider, takes a key and verifies it against the live endpoint, offers the provider's models, previews the themes as it moves between them, and picks a default permission posture — then writes io-harness's own configuration file. The session that follows renders on a hybrid inline viewport: every finished turn, tool cell and message is committed into the terminal's real scrollback, and only a short pane at the bottom holds the composer and the status line. The developer types a task, watches the answer stream, watches a file get edited inside the sandbox, interrupts with a keystroke, and exits — leaving the whole conversation scrollable, searchable and selectable with the terminal's own facilities, because io-cli never took them away. | specified | released | -| 0.1.1 | The session stops looking frozen while it works. A turn in flight shows a moving indicator beside the word that names its state, so a reader can tell thinking from hung, and the elapsed clock advances on its own rather than only when an event happens to arrive. A step reads as a step — what the agent decided, what it ran, and what came back — instead of a decision string with a token count appended to it. The README gains the screenshot 0.1.0 could not supply, taken from a run of the corrected interface. Nothing about the permission boundary, the renderer or the wizard changes: this is the release that makes what 0.1.0 already does legible while it is happening. | specified | released | -| 0.2.0 | The operator can see and steer the boundary the agent is working under. One key cycles the permission posture and the status line names the io-harness policy layer now in force. A sensitive action stops the run in an overlay that cannot scroll away — showing the act, the target, the rule and layer that are asking, and the content a write proposes — and is resolved by keystroke into both the transcript and the durable trace. A refusal prints the act, the target, and the rule and layer that produced it, which no other terminal agent can render because no other core records it. The status line gains the fields the harness can supply to a steerable turn: the policy layer, tokens, context pressure, containment. | specified | released | -| 0.3.0 | The operator can read what the agent did without leaving the terminal or losing the thread. Edits render as diffs — unified by default, word-level emphasis inside changed lines, syntax highlighting, context collapsed with an expander, a defined form for narrow terminals, and a minimal mode for people who review by file rather than by hunk. Tool calls render as one line with a result and a duration, expandable while live and committed collapsed, with the full output going to the durable trace rather than to the screen. A transcript overlay gives the whole conversation on one key, and the last message or the last diff can be put on the system clipboard in a way that survives SSH. | outline | planned | -| 0.4.0 | Work survives the session. A picker lists previous sessions from the harness store and reopens one where it stopped. Any turn in a conversation can be forked into a new branch and continued from there. The model can be changed mid-session without losing context. An empty composer takes a double-escape to rewind the conversation and the workspace to an earlier point, using the rewind the harness already implements and already records. | outline | planned | -| 0.5.0 | The same agent runs unattended. A non-interactive subcommand takes a goal, runs it to completion, and exits with a meaningful status; with a flag it emits the run's events as newline-delimited JSON — the same events the interactive session renders, from the same stream, with no interface code on the path. Sandbox and policy are selectable from the command line, a non-TTY stdout is detected and honoured without being asked, and credentials are taken from the environment so nothing has to be written to disk in CI. | hypothesis | planned | -| 0.6.0 | The interface reaches people and terminals the category currently excludes. A plain mode built for screen readers removes animation and repaint, keeps the real terminal cursor as the focus indicator, puts content before metadata, and announces every state change as one line of text — which no terminal agent in this field does today. Colour is never the sole carrier of meaning, `NO_COLOR` is honoured, box drawing degrades to ASCII, the keyboard protocol is negotiated up on terminals that advertise it and down over SSH without losing core function, keybindings are configurable and documented as a table, and every surface has an audited form at eighty columns. | hypothesis | planned | -| 0.7.0 | The composer becomes a command surface. A slash palette with fuzzy matching reaches every command and every harness skill and prompt template; `@` completes paths from the workspace; `!` passes a line to the shell verbatim; a large paste becomes an attachment instead of flooding the input. Every picker filters as you type — the model list, the theme list, `/resume` and `/fork` — so a four-hundred-model catalogue is reached by typing three characters rather than by holding an arrow key. The agent's own plan and todo list render as a live panel, and a question the agent asks about intent — which the harness distinguishes from an approval about permission — is answered inline rather than guessed at. | hypothesis | planned | -| 0.8.0 | A decomposed task becomes visible as a tree. Spawned children appear as a live fleet with their own status, each showing its draw against the ceiling the whole tree shares, with refused spawns and collected results shown where they happen. The status line gains the spend field here rather than at 0.2.0: `SpendDraw` is emitted only by a contained turn, and the contained entry point takes no steer inbox, so a session that renders spend today is one that cannot be interrupted. This release is contained by definition, which is what makes the field reachable. This is the surface that is structurally impossible for a competing interface to copy, because it is rendering facts only this core emits. | hypothesis | planned | -| 0.9.0 | The remaining harness capabilities get a face. Images render inline where the terminal speaks Kitty, iTerm2 or Sixel and degrade to half-blocks where it does not, and an image can be attached to a turn. Background shell handles, browser sessions, LSP servers and MCP servers each get a compact live indicator, so a run that has started something long-lived says so rather than appearing idle. | hypothesis | planned | - - -_Canonical sources: products//roadmap.yaml_ diff --git a/.ultraship/views/release-history.md b/.ultraship/views/release-history.md deleted file mode 100644 index ac4492d..0000000 --- a/.ultraship/views/release-history.md +++ /dev/null @@ -1,87 +0,0 @@ - -# Release history - -## io-cli - -| Version | Released | Mode | Delivered | -| --- | --- | --- | --- | -| 0.2.0 | 2026-08-17T02:24:43Z | published | The operator can see the boundary the agent is working under, change it, and answer it when it asks. Through 0.1.0 and 0.1.1 the approver handed to io-harness was `DenyAll`, so the *ask before writes* posture declined every write and every command it was named for, and a live first run walked straight into it. That dead end is closed. The release is one seam and four surfaces drawn on it. Every other connection this product has to io-harness is one-way: an observer is handed an event and hands it on, and nothing waits. `Approver::decide_in_context` is the opposite shape — it runs on the agent's own task and the run stays paused until the future resolves — so the correctness question is not whether an answer arrives but what happens when one never does. The answer is a type: answering consumes the `Ask`, so dropping one *is* a denial, and both ways of losing the interface arrive at one closed channel. Asserted with no clock anywhere; the arm that blocks instead hangs the suite forever, which is what a deadlock looks like. The overlay lives in the viewport and commits nothing, because a question in the scrollback can be scrolled above the fold while the run is blocked on it. It states the act and the target, then the rule and the layer on a row of their own, then the content a write proposes. Three answers on keys and on arrows, opening on the least committal one. `Shift+Tab` cycles the posture from both spellings a terminal can send, and the status line names the one in force. A refusal prints its rule and its layer — the two facts no other terminal agent can render, because no other core records them. Three of the status line's named placeholders are filled — tokens, context pressure, containment as mode *and* backend — and each is absent rather than zero until something supplies it. The fourth is not filled, and the roadmap moved rather than the record pretending: spend against the tree ceiling is emitted only by a contained turn, and the contained entry point takes no steer inbox, so rendering it today would have cost `Ctrl+C`. | -| 0.1.1 | 2026-08-16T18:47:29Z | published | The session stops looking frozen while it works. A turn in flight advances its own clock and turns its own indicator, and neither waits for an event to arrive. The screenshot this release put in the README is the proof and the case: forty seconds into a slow first token, no answer on screen yet, the status line reading `deepseek/deepseek-v4-flash-0731 · ⠨ working · 40s` with the indicator turning. Under 0.1.0 that same moment was a still word beside a still clock, which is what a hung process looks like. The mechanism is one function. `App::tick(age) -> bool` takes the session's age as an argument rather than reading a clock, and answers whether a frame is owed rather than drawing one. That is what makes liveness assertable without a timing assertion: a test advances the clock by hand and asks. It also decides the half that matters more — an idle session is told no, so this renderer still repaints only when something changed, which is the whole of its differentiation from the alternate-screen products. A step now reads as a step: the decision, the tool it called with its target, the result, and then the token count and the step number as trailing muted detail. The result is stated in both directions, `changed files` or `no change`, so a transcript is skimmed down one column instead of parsed. Nothing else moved. No key, no command, no event surface, no setting this release starts reading, and the eight dependencies are the same eight. A spinner is a handful of characters and a modulo. | -| 0.1.0 | 2026-08-16T17:35:00Z | published | A terminal interface over io-harness that renders run events, edits a prompt and reads a keyboard — and contains no agent loop, provider client, tool, sandbox, policy engine or session store of its own. 3,458 lines of source across 16 files, against the archived product's 1,199,453 across 88 crates. That ratio is what this release existed to establish. The renderer is the release. Finished content is committed into the terminal's own scrollback and a fixed few rows at the bottom hold the composer and the status line; a streaming answer commits each line as it finishes, so a two-hundred-line reply leaves the viewport exactly the size it started. The alternate screen is never entered and the mouse is never captured — asserted over the bytes the process writes, so no later release can lose it quietly. A first-run wizard takes a developer from no configuration to a working agent: provider, a credential verified against the live endpoint before anything is written, a model from that provider's own catalogue, a theme previewed live, and a permission posture that is an io_harness::Policy rather than a flag of io-cli's own. Nothing reaches disk before the confirmation screen; the credential never reaches the screen at all, and is not written when the provider's environment variable already carries it. Distribution is four cross-compiled artifacts and a SHA256SUMS on the GitHub Release, installed by one script per platform that verifies before it unpacks and needs no administrator rights. | - -### 0.2.0 known limitations - -- **Spend against the tree ceiling is not in the status line, and the roadmap moved rather than this record pretending.** `EventKind::SpendDraw` is emitted only by a contained turn, and io-harness's contained entry point takes no steer inbox — rendering spend today would have cost `Ctrl+C`. It is the same harness boundary that already blocks the configuration's `[sandbox]` and `[run]` sections. Moved to 0.8.0, the fleet release, which is contained by definition. If a harness turn that is both contained and steerable appears sooner, it moves with it. - -- **`rule` and `layer` are `None` in the ordinary case, not the exotic one.** Every question and every refusal in the live rehearsal came through unnamed, because the posture's own `write: Ask` default is what asks and no glob names the action. So `no rule named it: the tier default decided` is what most operators will see most of the time. That is accurate — io-harness is explicit that an unnamed action in the grey tier is the least vouched-for kind — but the sentence is long for a line that repeats, and a shorter form is worth designing once it has been read a hundred times. - -- **The overlay shows a write's proposed content plainly, not as a diff.** The harness hands an approver the resulting file rather than a patch, so the diff is this product's to compute and 0.3.0's to design. In a four-row viewport the content gets one row, with the cut count riding its end. - -- **The context field is quiet by design.** `Compacted` is the only event carrying an observation-section size, so between folds nothing on the stream knows it and the field is absent. The per-step estimate is durable in the store as `ContextEvent::est_tokens`; the upgrade path is one store read and is marked with a `ponytail:` comment where the field is declared. - -- **`Decision::Defer` and `Decision::Approve { modified }` are unbound.** Deferring stops the run and persists the request, which is only useful alongside the resume that arrives in 0.4.0; approving a rewritten action is an editor inside an overlay. Both are real harness affordances, deliberately not half-built. - -- **A question about intent is still a dead end.** `awaiting_answer` says so and points at the next prompt. Answering one needs a `Responder` on a caller-supplied contract, which is the same entry point that would cost `Ctrl+C`. 0.7.0. - -- **The answers row has a double space after each separator**, because the two-cell selection marker sits after a spaced separator. Cosmetic, noticed during the F10 preview, and deliberately not changed while the owner's gate was in flight. A one-line fix for 0.2.1 or 0.3.0. - -- **A turn commonly ends `Stalled` after its work succeeded.** Observed on every live run of this release — and identically on the never-asks path 0.1.0 shipped, at the same step count on the same prompt. It is the model rather than the approval flow, and it is recorded here because an operator will see it and wonder. - -- **`live_f1_a_real_turn_streams_and_edits_a_file` currently fails**, and this release did not cause it. It asserts the turn streamed assistant tokens; the model stalled without a closing message and streamed none. The test's own comment records that exact fragility. It is `#[ignore]`d, CI never runs it, and it belongs to 0.1.0's F1 — left alone rather than reworded into passing. - -- **One low Dependabot advisory predates this release**: `lru`'s `IterMut` violates Stacked Borrows, reached through `ratatui 0.29`, which `Cargo.toml` pins deliberately because `tui-textarea 0.7` is built against exactly that pair. Moving off it means depending on somebody's fork of the one widget the composer is made of. A dependency release of its own. - -- **The harness pin stays at 0.60.1 on purpose.** 0.60.2 and 0.60.3 are published and API-identical, so nothing here needs them, and moving the pin would have put an untested dependency change on a release that already changes the permission surface. - -- **This record is sealed before the Release exists, which is the flow rather than a shortfall.** The sealed record and its lock pin ride `feat/0.2.0` and merge with the code, so the seal necessarily precedes the tag that cuts the Release. `mode: published` describes the channel this product has. The second half of O1 — CI green again on the merge to `develop` — and O4's four artifacts follow this point. If the tag or the matrix fails after it, the correction is a new version, not an edit. - - -### 0.1.1 known limitations - -- **An idle session's clock is still frozen, and that is F2 rather than a defect.** Between turns the elapsed time advances only when a key arrives. Making it tick when nothing is happening is exactly the repaint-forever behaviour this renderer exists not to have, so the frozen idle clock is the price of the property and is deliberate. - -- **The tick's cost while text is streaming quickly was not measured.** The contract raised it as an open question to record rather than to gate, and it was recorded rather than measured: token events already drive repaints, so a timer on top of them is redundant while text is arriving and useful only while it is not. The interval is 100 ms, chosen for how motion reads rather than from a measurement. If a later release measures it, the number belongs there. - -- **A long tool target in a step line wraps rather than being fitted.** The picker has a fitting rule that could be reused; the contract left the decision to the first rendering against real content, and the real content in the F3 run had no long target in it. Left to wrap, deliberately, rather than designing against a case not yet seen. - -- **The indicator is braille and there is no ASCII fallback.** Every frame is one cell wide, which is why braille was chosen, but a terminal without the glyphs shows a replacement character where the animation should be. The word beside it is unaffected, and `NO_COLOR` removes the animation entirely. A proper plain mode is 0.6.0. - -- **The README screenshot shows the liveness fix and not the step fix.** The turn it captures was in its first step, so no step line had committed yet. The picture proves the clock and the indicator, which is what the release is named for; F5 is proved by tests asserting the rendered order rather than by the image. - -- **Two of 0.1.0's four interface limitations are untouched and stay where the roadmap put them.** The model picker still has no type-to-filter (0.7.0, decided with the owner at 0.1.0's release and widened there to every picker), and a prompt longer than two rows still scrolls within them rather than growing the viewport (0.7.0, because ratatui fixes an inline viewport's height at construction). This release fixed the other two. - -- **An action that needs approval is still declined, and the configuration's `[sandbox]`, `[run]` and `[instructions]` sections are still not applied to a turn.** Both are 0.1.0 limitations carried forward unchanged; the approval overlay is 0.2.0 and the contract sections wait on a harness entry point that takes both a caller's contract and a steer inbox. - -- **This record is sealed before the Release exists, which is the flow rather than a shortfall.** The sealed record and its lock pin ride the feature branch and merge with the code, so the seal necessarily precedes the tag that cuts the Release and the workflow that attaches the four artifacts and `SHA256SUMS`. `mode: published` describes the channel this product has. If the tag or the matrix fails after this point, the correction is a new version, not an edit. - -- **The `fmt and clippy` job failed once on this branch and was re-run.** The failure was seven seconds into `dtolnay/rust-toolchain`, before any of this repository's own commands ran, on a SHA that had just gone green on `develop`. Recorded rather than smoothed over: a failure inside a toolchain download is a flake, and the same two commands exit 0 locally and in the re-run. - - -### 0.1.0 known limitations - -- **The configuration's `[sandbox]`, `[run]` and `[instructions]` sections are read by io-harness and not applied to an io-cli turn.** `Session::turn_steered` builds its own `TaskContract`, and the harness has no entry point taking both a caller's contract and a steer inbox — so honouring them would mean giving up `Ctrl+C`, which F6 requires. The sandbox itself is on; what is missing is the configured ceilings, not the containment. Nothing in this contract promised those sections. Stated in the README and in `docs/config.example.toml`, and it is the natural first item for a harness release. - -- **An action that needs approval is declined, and says so.** The overlay that asks a human, and the refusal surface that names the rule and the policy layer, are 0.2.0. A live run walked into the consequence: the ask-before-writes posture denied three actions, the agent asked for permission, and the turn ended `awaiting_answer`. That outcome now reads as a warning carrying the way out rather than as an unexplained error, but the posture still cannot do what its name suggests until 0.2.0. - -- **The status line's working indicator is a word and not a spinner, and its elapsed time advances only when an event or a keystroke arrives.** Between events the clock is still. The owner raised both; neither blocks a session and both are remediation of what this release shipped, so they belong in a patch. - -- **The model picker has no filter.** A provider catalogue of four hundred rows is walked with the arrow keys. The wizard's viewport was widened so a usable number of rows is visible, but type-to-filter is new capability and the roadmap puts fuzzy filtering in 0.7.0. Decided with the owner rather than added quietly. - -- **A prompt longer than two rows scrolls within them rather than growing the session viewport.** ratatui fixes an inline viewport's height when the terminal is constructed, and rebuilding mid-session would re-query the cursor and risk shifting the scrollback this product exists to protect. The wizard gets its own taller viewport because the boundary between the two phases is safe to rebuild at; a session has no such boundary. The composer's own release is 0.7.0. - -- **O5 is verified after this record is sealed.** The installers can only be exercised on clean machines against a Release that exists, which is by definition later than the tag that creates it. `install.sh` is proved end to end against a local release, including the corrupted-artifact refusal, and the cross-compile matrix is proved on a throwaway branch — what remains is the clean-machine, new-shell half. What this record claims is the state at seal time. - -- **The `x86_64-apple-darwin` artifact is cross-compiled and not executed by CI**, because the runners are arm64. The other three rows run `io --version` on the machine that built them. - -- **The F1 recording shows the theme step before its fix.** The picker was invisible in that run — the defect the run found — and the recording documents it. It is honest evidence of the criterion passing and a poor screenshot; a fresh capture for the README is a patch-release chore, not a correction. - -- **This record was sealed before the Release existed, and that is the flow rather than a shortfall.** The sealed record and its lock pin ride the feature branch and merge with the code, so the seal necessarily precedes the tag that cuts the Release and the workflow that attaches the four artifacts and `SHA256SUMS`. `mode: published` describes the channel this product has — the GitHub Release, which is the whole of it, since there is no registry and never will be. If the tag or the matrix failed after this point, the correction is a new version, not an edit. - -- **This record was amended and re-pinned after its first seal, before the merge into `main`, because the tree it described would not start.** Giving the wizard its own taller viewport means attaching the terminal twice, and the keyboard reader had been moved ahead of the first attach — placing an inline viewport asks the terminal for its cursor position and reads the answer off stdin, so the reader consumed it and the query timed out. The owner found it by running the binary; nothing in the suite reaches the driver's own startup ordering. The reader now polls so it can be stopped and joined, starts only after a screen is attached, and takes that screen as a witness argument so the ordering is a compile error to get wrong. Every gate below was re-measured on the corrected tree at 9349aa0. - - - -_Canonical sources: products//releases/.yaml_ diff --git a/.ultraship/views/workspace-brief.md b/.ultraship/views/workspace-brief.md deleted file mode 100644 index 19da5f4..0000000 --- a/.ultraship/views/workspace-brief.md +++ /dev/null @@ -1,21 +0,0 @@ - -# io-cli - -**State:** RELEASED -**Next command:** `/ultraship:plan` - -## Vision - -The terminal agent that shows you what it is allowed to do, what it is spending, and what it refused — while it works. A renderer over io-harness, containing no agent loop of its own. - - -## Products - -| Product | State | Latest release | Classification | MVP boundary | -| --- | --- | --- | --- | --- | -| io-cli | RELEASED | 0.2.0 | independent-product | A developer with no prior configuration runs `io` in a repository, is walked through picking a provider, entering and verifying a key, choosing a model and a theme, types one real task, watches it stream and edit a file inside the sandbox, interrupts it with a keystroke, and exits — leaving the whole conversation in the terminal's own scrollback. | - -_Canonical sources: workspace.yaml, products//product.yaml, products//lifecycle.yaml_ diff --git a/.ultraship/workspace.yaml b/.ultraship/workspace.yaml deleted file mode 100644 index 9693abf..0000000 --- a/.ultraship/workspace.yaml +++ /dev/null @@ -1,12 +0,0 @@ -id: io-cli -name: io-cli -vision: > - The terminal agent that shows you what it is allowed to do, what it is spending, and what it - refused — while it works. A renderer over io-harness, containing no agent loop of its own. -constraints: - - > - io-cli contains no agent loop, no provider client, no tool implementation, no sandbox, no policy - engine and no session store. All of it is io-harness. - - Branch flow is feat/ to develop to main, and nothing reaches develop or main except by - pull request. -active_product: io-cli From a37118ae66393bfb5fd14e1aa0b0792b3985ac05 Mon Sep 17 00:00:00 2001 From: aakashpawar1999 Date: Mon, 17 Aug 2026 08:27:29 +0530 Subject: [PATCH 2/2] fix: assert the whole planning tree is ignored The docs gate asserted the .gitignore held `.ultraship/products/*/evidence/`, which the tree-wide ignore replaced, so the gate failed on develop. It now asserts a bare `.ultraship/` line. Line equality rather than `contains`, because the old narrower rule contains that string too and would pass a substring check while ignoring almost nothing. --- tests/docs.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/docs.rs b/tests/docs.rs index ff5c94d..98650d4 100644 --- a/tests/docs.rs +++ b/tests/docs.rs @@ -6,7 +6,7 @@ //! typeface. The same constants feed `/help` and the README, and these tests fail //! when one of them stops agreeing. -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use io_cli::commands::{COMMANDS, KEYS}; @@ -199,19 +199,14 @@ fn the_notice_carries_the_attribution_and_the_licence_body_is_untouched() { #[test] fn the_workspace_directory_is_not_shipped_inside_the_crate() { - // `.ultraship` is planning state. It is committed, because it is how this - // release is recorded, but it is not part of what a user installs — and the - // evidence directory under it is never committed at all. + // `.ultraship` is planning state: tool-written maintainer notes, not part of + // what a user installs and not a contributor-facing surface. The whole tree + // is ignored, so the rule is one line and there is no sub-path to keep in + // step with it. The bare entry is asserted rather than a `contains`, because + // a narrower rule such as `.ultraship/products/*/evidence/` also contains it. let gitignore = read(".gitignore"); assert!( - gitignore.contains(".ultraship/products/*/evidence/"), - "release evidence must stay off the repository: {gitignore}", - ); - assert!( - !Path::new(&repo()) - .join(".ultraship/products/io-cli/evidence") - .exists() - || gitignore.contains("evidence"), - "evidence exists and is not ignored", + gitignore.lines().any(|line| line.trim() == ".ultraship/"), + "the planning tree must stay off the repository: {gitignore}", ); }