Repository navigation
io-cli 0.33.0 - #85
Merged
Merged
Conversation
Question::choices is Vec<Choice> and Question is non_exhaustive, so the pin moves with its callers rather than ahead of them.
io skill add <dir>/SKILL.md wrote ~/.io-cli/skills/SKILL.md, and the folder guard then refused to remove it forever — about a folder that did not exist. The destination now comes from the resolved name, describe gains the parent-directory fallback io-harness itself applies, and is_bundle requires the parent to be the skill's own. A skill /import wrote as a folder inside io-cli's own skills directory is now removable and disable-able too; it never was, so the product shipped a verb creating state its management surface refused to manage. Three defects the repair would otherwise have introduced are closed with it: disabled_files now walks one level, so a disabled folder skill does not vanish from the surface that re-enables it; install's disabled-set check can see a parked folder; and disabling a loose SKILL.md is refused, because relocate keeps the name and it would have landed as disabled/SKILL.md — re-offered as a skill called disabled, taking every other parked skill's hiding place with it.
plugin add printed "plugin remove <id> takes it back out" and the verb resolved its argument as a path, so the sentence was false on the door that printed it. The path reading stays first and unchanged. Only when no configuration file declares that directory does the word match declared bundle ids, across loaded, disabled and refused entries. Hits are collected rather than taken first: an id is unique among loaded bundles only, and deleting the wrong [[plugin]] entry is silent. The declared set is passed in rather than read here, because Config::plugins is a full re-parse of every manifest and is confined to src/resolved.rs.
accepting_several opts a picker in; marked and chosen read the set as indices into the caller's unfiltered rows. Outcome is untouched — it is Copy and has six exhaustive matches — so a picker that never marks anything cannot observe this. Marks survive the query, because the operator narrows the list in order to find each row to mark. set_rows clears them, since a different list means the indices name different rows. set_unfold now stores one height per row, and the demand reserves the largest configured height, so neither the marker nor the query can move it.
io-harness 0.72.0 emits QuestionsAsked for several questions asked together and does not also emit QuestionAsked, so the transcript was blind to the exact ask this release exists to serve. The disposition is Line rather than Silent: an overlay draws a question, never an ask, and "these three arrived together" is the only fact the event carries. Routing it to a surface that does not hold it would have routed it to nothing. The count of declared event kinds moves 51 to 52, and its assertion now runs after the name sets — it stood first, so a harness bump failed saying a number moved and stopped before the list naming the new kind ever ran.
It sat on the bare /config list, which made a keystroke there read the network, write a scope file and reassign the running turn's configuration — and that is why /config could not run while a turn was in flight. configure::descent answers for prices.as_of and nothing else, keyed on the key rather than on Kind::Machine so a machine-written key added later inherits no act. The do-nothing row is at index 0, as it is in every other confirmation. configure::said is one speller for the two doors that now both report a setting. CATALOGUE is unchanged at 37 keys.
A batched ask parks as one row whose question column is numbered prose and whose choices column is empty, so a resumed batch showed a wall of text with nothing to pick. Pending::Question gains a questions field rather than a second variant: the harness parks one row with one question_id and the same four resume signatures, so a variant would assert a second driver that does not exist. It is empty for a singular ask, which is what the store actually holds. The headless door answers a batch with one --answer, because record_answer does one compare-and-swap — the fix was to the sentence, not the mechanism. It now says the row is N questions, that one text answers all of them, and to number them to match. sessions::note counts a batch instead of quoting it; the old arm pasted the numbered rendering into a one-line detail, which reads as if the first question were the whole ask. Known limitation, stated in the rustdoc: a singular multi-select's flag is destroyed before io-cli sees the row — PendingQuestion has no such column and put_question writes no questions value.
Eleven commands now run mid-turn. /config is admitted bare and refused argued, which is the first time runs_mid_turn has had a reason to read past the first word — it has always taken the whole line. This discharges US-IO-CLI-0.32.0-I11 by removing the write rather than guarding it. That iteration named one offending row; the bare list in fact had no report-only rows at all, because Left/Right called cycle_setting, which wrote the scope file on the keystroke with no confirmation — the only such write in the product. The arrows now open the value descent focused on the value in force, and Enter is the confirmation every other managed surface uses. cycle_setting is gone. The act was appended in two places; removing only the bare-list push would have looked right and been undone by the next arrow press, because the value-cycle redraw appended it again. Pick::Config gains a leave-it arm above the key arm, or row 0 of the new descent opens a scope picker for a setting called "leave it". The declared bundles reach manage::plan from the session's already-resolved set, and the argv door builds one only for the removal verb, so editing an MCP server does not pay for a plugin re-parse.
io-harness does not emit QuestionAsked for a batch, so an arm filtering only on it stops matching the moment the model picks ask_questions — and a live arm that stops matching goes quiet rather than red, which is indistinguishable from passing. Both arms now match either variant, and an ordinary offline gate reads this file's own source and requires the plural to appear as the other half of an alternation with a specific singular pattern position. A comment cannot satisfy it, and a watching count closes the hole where a rename leaves the loop finding nothing and passing. One new arm answers a three-question batch with a distinct answer per question and compares the recorded sequence against the typed one, so answering only the first, answering out of order, or one answer for all each fail rather than park quietly. 37 ignored, 1 offline gate passing.
…elves Responder::answer_all is overridden, so a batch crosses the channel as one unit rather than as N serial calls. The container lives in the library: src/main.rs is linked by no test binary, and a queue written there could be neither asserted nor sabotaged. A batch is reviewed in place as it is answered — one question on screen at a time, deciding one moves to the next undecided one, PgUp/PgDn walk them, a decided question re-opens with its answer back in the composer. No review pane, because one is a second rendering of state already on screen that can disagree with it, and it needs a submit key that answers nothing — the reflexive-Enter hazard the free-text marker rule exists for. On a single question none of this is drawn. A second question arriving while one was open used to REPLACE it, dropping the first question's reply channel and turning it silently into a pause. The guard is in App::open_intent rather than the driver, so a test can reach it. An offer draws its description under its label and unfolds its preview beneath itself, one at a time, in the blockquote the markdown renderer already uses — the diff gutter is private and shaped around an Edit. Heights are measured through rows::wrapped and reach set_unfold before the demand is read, so a gate written against lines.len() fails. Two defects caught while building it. Enter was routed on Picker::unfolded_now, which is true over any unfolding row — with previews that sent Enter over an open preview to the empty composer; it now asks whether the marker is on the row that takes typing. And a description is a heading row, so picker indices stopped being choice indices: indexing choices with a row would have answered the agent with a different offer of the same question, silently. One pure translation now does it. Multi-select marks with the spacebar and submits through Question::answer_of, the harness's own spelling, so two interfaces answer one question with one text. Stated cost: an offer with both a description and a preview draws the preview between the label and its description, because a picker opens a block under the row that owns it.
TurnFacts carried two summary sentences and last_run had none — an insertion put the struct between a sentence and the function it described. mid_turn_picker opened with the truncated tail of commit_image's doc, which has its own. And Picker::unfolded_now told the next caller to route keystrokes on it, which is the defect this release just fixed in the one caller that did. Nothing in the suite can see a rustdoc summary attached to the wrong item, so all three survived every gate. mid_turn_picker's count is corrected too: four picker kinds reach it now, not three.
Eleven commands run while a turn is in flight, not ten, and the excluded set is six rather than seven. The refused list in the keys guide named twenty-one where the code refuses twenty-two — /exit and /export had never been listed, and that was already false before this release. The keys guide argued the spacebar was unavailable on a picker because every printable character filters. This release put the spacebar arm above the printable one on a plural picker, so that reasoning was about to become false; it is rewritten, along with the arrow-cycling paragraph for a cycle that no longer exists. Thirty-four of the forty-six in-page links went nowhere, left over from 0.30.2's README split moving sections into pages while the anchors kept pointing inside the file they left. The link gate skipped fragment-only links by design, so nothing caught it. Every anchor now names its page, and the gate resolves both spellings. Every io-harness source citation was re-verified against 0.72.0 rather than version-bumped: nine of the ten cited files are byte-identical between the pins and run.rs gained one line above line 102, so four citations shift by exactly one and the rest stand. One claim was false — io-harness substitutes three forms, not two — and the correction already existed in the other file that carries it.
…ed nothing Found by an adversarial review and a sabotage pass over a fully green suite. Intent::resumed never read PendingQuestion::questions. A batch parks as one row whose question column is numbered prose and whose choices column is empty, so a resumed batch drew one run-on line with no offers, the first question's context presented as everyone's, and multiple defaulted to false — verbatim the failure this release exists to stop, and the failure src/resume.rs cites to argue the field must exist. Reachable through the release's own flow: decline one question, the run parks, resume. io exec reaches it on the first ask, since nothing there implements Responder. Both construction paths now end in one private constructor, so the struct literal that let them diverge is gone. Delivery stays singular — several answers are assembled into the one text the one parked row takes. F14 had no gate. Its criterion describes a count of configure::write call sites reachable from a key handler; no such test existed, only a prose assertion satisfiable by editing markdown. The gate now counts nineteen writes in the driver, names each by door, and slices the arrow arm to catch a write moved into it. Its cycle_setting assertion doubles as the comment-stripper's self-check. F17 was one empty slice from silent reversion. Passing one at either manage::plan call site returned plugin remove to path-only behaviour with the whole suite green — the widening shape, where nothing fails and the gate goes vacuous without going red. N6 named a prose sweep that did not exist, and the TOML invariant this crate states in several places was false: src/prices.rs parsed a whole document by turbofish while the gate banned two spellings out of eight. prices never needed a document, only a count of one table's keys, so edit gained the accessor rather than the gate gaining an exemption. Also: an arrow on a key whose values cannot be offered speaks again, restoring two sentences deleted with cycle_setting; the decline row's key is a sentinel prefixed like the act beside it, because a configuration file may hold a quoted bare key called "leave it"; both plugin doors answer the same question, through one staleness refresh rather than two, which keeps the once-per-session resolution the gate pins at two call sites; /settings is admitted mid-turn with /config and refused argued with it, since parse reads them as one command; and four vacuous assertions now fail on what they claim to gate.
The release matrix caught this with macOS and Linux green. Git checks these pages out with CRLF on Windows, and every helper in the file reasons about a bare newline — sharpest in paragraph(), which ends a paragraph at a blank line, found no boundary at all on a CRLF checkout, took the rest of the document, and then reported that a page naming eleven commands named all thirty-eight. Normalised once where the files are read rather than in each helper, so the rule is one line at the boundary instead of a guard every future reader has to remember. Proven against a genuine CRLF checkout of docs/guide/keys.md rather than only against the LF one this machine has.
io-cli 0.33.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.
The agent could ask you five questions and you answered them five times.
Questioncarried one question with bare-string choices and the harness blocked insideResponder::answerbefore a second could be asked, so an interface downstream could only render what reached it — one at a time, with nothing saying what any offer cost.io-harness 0.72.0 is the upstream that changes it, and this is the release that meets it.
What you get
Several questions arrive as one overlay, answered in place. One question on screen at a time on the identical surface a single question has; deciding one moves to the next undecided one,
PgUp/PgDnwalk them, and a decided question re-opens with its answer back in the composer. There is no review pane and no new submit key — a pane would be a second rendering of state already on screen, and it would need a submit key that answers nothing, which is the reflexive-Enterhazard the free-text marker rule exists for.An offer can explain itself. A description draws under its label; a preview unfolds beneath the focused offer in the blockquote this product already uses for somebody else's words, measured through
rows::wrappedand growing the viewport to hold it. One is open at a time.A question that takes several answers is answered with several. The spacebar marks and unmarks; the set is submitted through
Question::answer_of, io-harness's own spelling, so two interfaces answering one question produce one text.A parked batch resumes as the batch it was. And
/configruns while a turn is in flight.Three defects found by running the shipped binary
io skill add <dir>/SKILL.md— the dominant skill layout — wrote~/.io-cli/skills/SKILL.mdand the folder guard then refused to remove it forever, about a folder that did not exist./importwrote was a folder skill, and folder skills could not be removed or disabled at all. The product shipped a verb that created state its own management surface refused to manage.io plugin addprinted "plugin remove <id>takes it back out" and the verb read its argument as a path, so the sentence was false on the door that printed it./configmid-turn took removing a write, not guarding oneThe iteration that deferred this named one offending row. The bare picker in fact had no report-only rows:
←/→calledcycle_setting, which wrote the scope file on the keystroke with no confirmation — the only unconfirmed write in the product reachable from a bare arrow key. The act moved one descent belowprices.as_of, the date it writes; the arrows now open that descent focused on the value in force;cycle_settingis gone. Eleven commands run mid-turn, and the excluded set drops from seven to six.What the gates found behind a green suite
Every offline gate was green — 1,790 tests, clippy, fmt, doctests — over all of this.
Intent::resumednever readPendingQuestion::questions, so a resumed batch drew one run-on line with no offers: verbatim the failure this release exists to stop, and the failuresrc/resume.rscites to argue the field must exist.src/edit.rswas not the only file parsing TOML, and the gate enforcing it named two spellings out of eight.Evidence
io-harness pinned to 0.72.0, the newest published, with its breaking changes read against this crate's callers first — including two the upstream changelog omits.
The release record is sealed and pinned before this merge, at
sha256:3bf0f74d227a297a2b3f3caa2ad6043be6ad2bd2ae2f357b757aae8b11b34fd3;ultraship validateexits 0 over 97 checks.