Repository navigation
io-cli 0.21.0 - #56
Merged
Merged
Conversation
Three things that turn out to be one: a surface can only be trusted to say what it will write if the writers behind it actually write. /import detects the other agents already on the machine — ~/.claude and the ~/.claude.json beside it, ~/.codex, ~/.gemini, a .cursorrules or CONVENTIONS.md in the repository — and offers to bring across the four things that transfer: instructions, MCP servers, skills and the model. The whole plan is built and drawn before a byte is written, every item defaults to off, and only the last row writes, so backing out is never a partial import. An allowlist is read, shown, and deliberately not translated: another tool spells a permission as a command and its arguments, io-harness's Act::Exec matches a binary name and nothing else, and a boundary half imported is worse than one left alone. No credential is read at any point — env values deserialise through IgnoredAny so they are never constructed, auth.json is never opened, and what gets recorded is the variable name the key already lives behind. A capability bundle's skills reach the model but appeared on no surface that listed them. They are on /skills and in the palette now, under the namespaced <id>__<name> the model actually addresses, with the bundle as their origin. Enable and disable are refused on them: the destination is computed from the file's own parent, so a bundle row reaching the move would have created disabled/ inside somebody else's directory and taken their file into it. And the defect underneath that one. Plugin::skills_dir does no existence check, discover_skills walks it with `?` before the first completion, so a bundle declaring a skills directory that is not on disk ended every turn of the session with an error naming a path the operator never chose. /skills and /plugin now name the bundle and say what it costs, from a single call so the two cannot disagree. /mcp and /provider write. The writers existed, were tested, and were called from nothing, while three places in the code said they add, edit and remove entries. Seven defects came out of wiring them up: add could not express args, env, http or a timeout; edit took a free key over a section exempt from deny_unknown_fields, so a misspelling was written, reported as saved and ignored by every turn after; demote took its bound from whatever the caller counted; add could build provider entries io-harness refuses and could not write a credential or a base URL at all; and both quoted implementations escaped two characters where TOML needs more. Positions are now At newtypes read from the target file's own bytes, so a row index from a merged three-scope view can no longer be spliced as an array position.
README gains /import and its promises, says the 64-skill ceiling is per
directory and that bundle directories are not bounded together, and documents
that /mcp and /provider write.
Four things already false, found while writing that and fixed here. io-harness
substitutes three forms and not two — ${cmd:...} is the third, and it is refused
in the project scope — which was stated wrongly in src/configure.rs, in the
README's skills row and in the shipped configuration example. /mcp was
documented as being able to disable a server: McpServer has no such key, and
serde(flatten) would have accepted an invented enabled = false that the harness
then ignores, so the word is gone rather than a control being built that reports
success and does nothing. /mcp was said to show one tool count where servers.rs
draws two different numbers. And four intra-doc links in context.rs, home.rs and
picker.rs have been broken under the nightly rustdoc gate, which is not in this
repository's ci.yml and has to be run by hand.
The configuration example gains a commented [[mcp]] entry in the shape /import
writes, with its credential as ${env:NAME} and no tilde in any path — the
harness expands no tilde, and this file shipped exactly that bug once before.
Every check was green — suite, clippy, rustdoc, formatting — and a read-only reviewer pointed at the diff found twelve defects. Two of them were severe. A silent wrong delete. Under a profile, /provider promote and remove addressed the file's top-level [[provider]] array while the rows on screen came from the resolved chain, and those are not the same list: a profile's provider entry replaces the top-level array rather than extending it, and io-harness then rewrites the origins so the deciding file still looks like this one. Every positional check passed. Removing the profile's only link would have deleted the operator's real primary provider, which was never on screen. The At newtype introduced to prevent exactly this was a wrapper rather than a guard — it bounds-checked the index it was handed instead of confirming what was at it. declared_at now verifies the entry's own model and kind against the file, and answers with no position when they disagree. A path traversal. A foreign skill's frontmatter name became a path component unchecked, so a third-party marketplace plugin declaring ../../../x or an absolute path could write a file anywhere the process can reach; the collision guard asks whether the target exists and creating a new file elsewhere answers that happily. Refused rather than sanitised: a name is how the model addresses a skill, and rewriting it would install one under a name the operator's other tool does not use. The rest. /import told the operator an imported model was in force when providers::add appends it as the last fallback — true only on an empty chain, which is the only case the fixtures built. A second /import appended a duplicate [[mcp]] id, and so did one import from two tools declaring the same server; io-harness validates no such thing, so the round trip accepted it. The palette went on offering a bundle's skills after /plugin stopped loading it, because the inventory was walked once at startup and reassigned only by the /skills toggle. The startup notice dropped the bundle failure that kills every turn of the session — the one moment it was cheapest to say. The write row counted items that write nothing, and rows that can never write are now drawn as findings rather than as checkboxes. The review surface never showed a destination, which is what made the traversal invisible. The first-run scan ran before the first paint, holding a blank terminal while it parsed a large JSON file and walked a plugins tree. And three doc comments had concatenated onto one function, leaving two others undocumented. Both severe fixes have a regression test, and each was sabotaged to prove it fails without the fix — one arm each, killing nothing else.
io-cli 0.21.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release PR for io-cli 0.21.0. Merged into
developas #55, 8/8 green on three platforms./importbrings an operator's instructions, MCP servers, skills and model across from Claude, Codex, Gemini,.cursorrulesorCONVENTIONS.md. The whole plan is shown before a byte is written, every item defaults to off, and only the last row writes. An allowlist is read and deliberately not translated — io-harness'sAct::Execmatches a binary name, not a command line, and a boundary half-imported is worse than one left alone. No credential is read at any point.Capability-bundle skills become visible on
/skillsand in the palette, under the namespaced<id>__<name>the model addresses — 0.20.0's F5, shipped unmet. Enable and disable are refused on them rather than reaching into somebody else's directory. And a bundle declaring a skills directory that is not on disk ended every turn of the session; both surfaces now name it, from one call so they cannot disagree./mcpand/providerwrite. The writers existed, were tested, and were called from nothing while three places in the code said otherwise. Seven defects surfaced in wiring them up.The adversarial review found twelve defects behind a fully green suite — a silent wrong delete of the operator's primary provider under a profile, and a path traversal from a foreign skill's frontmatter. Both fixed, both with a regression test, each sabotaged to prove it fails without the fix.
ultraship validateThe one live failure fails identically on
mainat the merge base — verified in a separate worktree, same line, same assertion. Pre-existing, recorded as a known limitation.io-harness pin stays
0.69, the newest published — the seventh release satisfied by not moving.