Skip to content

feat(slash): /init command with SlashOutcome trait refactor#59

Merged
hakula139 merged 18 commits into
mainfrom
feat/slash-init
May 2, 2026
Merged

feat(slash): /init command with SlashOutcome trait refactor#59
hakula139 merged 18 commits into
mainfrom
feat/slash-init

Conversation

@hakula139

@hakula139 hakula139 commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

Adds /init — the first prompt-submit slash command. Typing /init shows the typed line, then forwards a synthesized prompt asking the model to author or update the project's AGENTS.md (or CLAUDE.md when one exists). The body adapts Claude Code's OLD_INIT_PROMPT for oxide-code's AGENTS.md-first instruction loader; the multi-phase NEW_INIT_PROMPT defers until AgentEvent::PromptRequest plumbing lands.

To carry prompt-submit cleanly, SlashCommand::execute now returns Result<SlashOutcome, String> with SlashOutcome::{ Local, Action(UserAction) }. /init returns Action(SubmitPrompt(body)); /clear (previously reaching into user_tx from inside execute) now returns Action(Clear). Slash impls never touch the agent channel — the trait return is the only seam.

Design decisions

  • Unified state-mutating path. Action(UserAction) replaces the prior asymmetry between /clear's side-channel send and /init's string-body return. SlashContext loses its user_tx field; the App-side dispatcher owns forwarding.
  • Race-free turn-start UI. For SubmitPrompt, the dispatcher flips input-disabled + Streaming before forwarding, so a typed prompt cannot squeeze in between dispatch and forward. /clear forwards as-is.
  • is_read_only=false so /init refuses mid-turn. Same gate /clear uses — running mid-turn would race the in-flight one.
  • Expanded body invisible in the live session. Only /init lands as a chat block; resumed transcripts show the full body via JSONL Message::user. Accepted trade-off.

Changes

File Description
slash/init.rs New: InitCmd returning Action(SubmitPrompt(PROMPT)); is_read_only=false; AGENTS.md-first prompt body.
slash/registry.rs SlashOutcome { Local, Action(UserAction) }; execute → Result<SlashOutcome, String>; register InitCmd.
slash/clear.rs Returns Action(Clear) instead of writing to user_tx.
slash/context.rs Drops user_tx field — slash impls never reach into the agent channel.
slash.rs dispatch returns Option<UserAction> for the App-side branch to forward.
slash/{config,diff,help,status,matcher}.rs Mechanical Ok(())Ok(SlashOutcome::Local).
agent/event.rs UserAction derives PartialEq for SlashOutcome equality.
tui/app.rs Slash branch flips input + status before forward_to_agent for SubmitPrompt; forwards other actions as-is.
docs/guide/slash-commands.md New user-facing guide for the slash-command surface.
docs/research/design/slash-commands/{init,README}.md Per-command design doc + cross-command notes.
docs/roadmap.md, README.md, CLAUDE.md Status / surface refresh.

Test plan

  • cargo fmt --all --check
  • cargo build / cargo clippy --all-targets -- -D warnings — zero warnings
  • cargo test — 1510 pass
  • cargo llvm-cov --ignore-filename-regex 'main\.rs'
  • pnpm lint / pnpm spellcheck
  • Manual TUI smoke: /init idle (typed line lands, agent receives body, status flips to Streaming, model authors the file); mid-turn (refuses with system message); popup row visible at /i.

Out of scope

  • Multi-phase interactive flow (NEW_INIT_PROMPT's AskUserQuestion clarifications, hook wiring, skill suggestions). Gates on AgentEvent::PromptRequest plumbing — see init.md § Deferred.
  • progressMessage — needs a status-bar variant; trivial to add when a second prompt-submit command warrants it.
  • Onboarding-state recording. Claude Code's maybeMarkProjectOnboardingComplete writes per-project state to ~/.claude.json; oxide-code's stance against silent mega-file writes rules it out by default.

hakula139 added 3 commits May 2, 2026 22:55
Adds the third command kind — prompt-submit — backing the new `/init`
command. `SlashCommand::execute` now returns
`Result<SlashOutcome, String>` where `SlashOutcome` carries either
`Local` (the existing client-side semantics) or `PromptSubmit(String)`
(`/init`'s synthesized body). The dispatcher forwards the synthesized
body via its return; the App-side wiring lands in a follow-up commit.

`/init` adapts Claude Code's `OLD_INIT_PROMPT` to the AGENTS.md /
CLAUDE.md convention oxide-code's instruction loader walks. Aliases
none; `is_read_only=false` so the busy-turn dispatcher refuses with
the standard system message instead of starting a parallel turn.

Existing impls (`/help`, `/clear`, `/status`, `/config`, `/diff`)
swap `Ok(())` for `Ok(SlashOutcome::Local)`; popup snapshots
regenerated to include the new `/init` row.
`apply_action_locally`'s slash branch now reads `slash::dispatch`'s
return: on `Some(prompt)` it flips input + status to `Streaming`
and forwards `UserAction::SubmitPrompt(prompt)` — turn-start UI
side effects mirror a typed prompt, so no race window where the
input could accept another submit before the synthesized prompt
hits the agent loop.

Extracts `forward_to_agent` from `dispatch_user_action` so the new
synthesized-prompt path reuses the same channel-error handling.
New per-command doc at `docs/research/design/slash-commands/init.md`
covering the prompt-body decision (OLD_INIT_PROMPT adapted for
AGENTS.md), the `SlashOutcome::PromptSubmit` send-after-dispatch
ordering, and what's deferred (the multi-phase interactive flow,
`progressMessage`, onboarding-state recording, harness-side
instruction-file detection).

Surface-level updates: slash-commands/README.md gains decision 12
(three command kinds via `SlashOutcome`), and refreshes the
oxide-code comparison row + Today prose to reflect the actual
shipped surface (six built-ins, not none). Roadmap moves `/init`
to Working Today and folds its multi-phase flow into Deferred.
CLAUDE.md crate tree picks up `slash/init.rs` and the updated
`registry.rs` description.
@hakula139 hakula139 added the enhancement New feature or request label May 2, 2026
@hakula139 hakula139 self-assigned this May 2, 2026
@hakula139 hakula139 added the enhancement New feature or request label May 2, 2026
@codecov

codecov Bot commented May 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

hakula139 added 14 commits May 2, 2026 23:28
The file lives only under `.claude/plans/` (gitignored), so readers
fetching the repo can't follow the links. Replace each citation with
the gating subsystem name (`AgentEvent::PromptRequest`) inline.
The /clear and /init Working-Today bullets and the Deferred bullet
each unspooled into 2-3 sentences with stacked parentheticals.
Compress to one short sentence per bullet; drop SlashOutcome
implementation details that belong in the design doc.
Restore three load-bearing rules dropped from the OLD_INIT_PROMPT
adaptation: forbid fabricated sections (no `Common Tasks` /
`Tips for Development` headers), forbid restating the same fact
across sections, and replace "prefer AGENTS.md when both exist"
with the three-case rule (neither / one / both) — the previous
default would push migration on a repo that already standardized
on CLAUDE.md.

Tighten rule 4 from "non-obvious gotchas" (overlapped rule 3) to
"external constraints the code can't reveal". Drop the
oxide-code-first parenthetical from the opener; alphabetize the
prefix-block AI assistant list. Hard-wrap to ~74 chars matching
prompt/sections.rs.

Drop "instruction file" trailing noun from the description (singular
read off when naming two files). Trim the module docstring to scope
+ design-doc pointer; let the registry comment own the trait
contract. Replace the PROMPT doc comment's `OLD_INIT_PROMPT`
reference with the gating subsystem (`AgentEvent::PromptRequest`).
Add docs/guide/slash-commands.md as the canonical user-facing
reference: built-in command table, autocomplete popup mechanics,
the `//foo` literal-escape, mid-turn refusal policy, and the
"no silent config writes" stance. Place it after Configuration in
the guide index — slash commands are everyday-use surface, more
frequently needed than instruction-file setup.

Roadmap's slash-command Working-Today bullets unspooled the user
guide's contents into seven dense paragraphs about UI mechanics,
parser policy, and alias display. Compress to three signal-only
bullets (commands shipped, autocomplete exists, design stance);
the user guide owns the rest.

Add the new doc to README.md and docs/guide/README.md.
The Documentation table mirrored docs/guide/README.md row-for-row.
Two indexes in two places drift apart — replace the README table
with a single link to docs/guide/, which is the canonical index.
The Status section enumerates shipped capability areas; slash
commands shipped under PR #58 (/clear) and #59 (/init) plus the
autocomplete popup but were missing from the list.
Drop the subsumed `execute_returns_prompt_submit_with_non_empty_body`
in init.rs — `execute_prompt_targets_agents_md_and_claude_md`
already implies a non-empty body. Drop the description-non-emptiness
check in `metadata_matches_built_ins_contract` for the same reason
(covered by the registry's built-ins contract test).

Strengthen `execute_prompt_says_do_not_overwrite_existing_file` to
`contains("already exists") && contains("not overwrite")`. The
previous `||` over two phrasings would pass a prompt that says
"please overwrite all existing files" since "overwrite" is retained.

Replace `let-else { panic! }` destructure patterns with
`assert!(matches!(...))` — drops dead-by-design sentinel arms that
codecov flags and tightens the diagnostic to one line.

Add `dispatch_prompt_submit_command_returns_synthesized_body` to
slash.rs — the public `dispatch` wrapper had no direct test for the
`Some(body)` return path; only `dispatch_with` was exercised via
synthetic registries. Add `"init"` to
`classify_built_in_state_mutating_command_is_mutating` — both
override `is_read_only=false` for different reasons but classify
the same.

Add a chat-block-count assertion to the busy-refuses init test:
the typed `/init` row should still land even when the synthesized
body is suppressed (3 entries: active prompt, typed `/init`,
refusal).

Rename `is_read_only_is_false_so_busy_dispatch_refuses_init` (and
the parallel `_clear`) to `is_read_only_is_false`. The original
names described a downstream consequence tested elsewhere; project
convention prefers scenario-side phrasing.
…ction

Replaces SlashOutcome::PromptSubmit(String) with Action(UserAction), so
/clear and /init route through the same trait return rather than the
prior asymmetry where /clear reached into user_tx itself while /init
returned a body string. SlashContext loses its user_tx field — slash
impls never reach into the agent channel; the App-side dispatcher owns
forwarding the synthesized UserAction.

The TUI dispatcher inspects the returned Action and flips input-disabled
+ Streaming status before forwarding SubmitPrompt, so a typed prompt
cannot squeeze in between dispatch and forward. /clear forwards as-is.

Collateral cleanup:
- UserAction derives PartialEq for SlashOutcome equality in tests.
- Drop Eq from SlashOutcome (UserAction holds a String body).
- Drop test_user_tx fixture and the channel-closed /clear test (the
  side-channel path is gone).
- Update slash-commands design docs and the CLAUDE.md crate tree to
  reflect the new shape.
BUILT_INS reorders to clear, config, diff, help, init, status. The
matcher already sorts alphabetically within each tier when filtering,
so aligning the slice removes the special case where the empty-query
popup rendered in a different order from filtered queries. Codex and
Claude Code's `/help` both sort alphabetically — this matches the
precedent and removes the curation cost as the registry grows.

Cascade through every site that listed commands in order: README
status bullet, roadmap working-today and current-focus mentions,
user-guide table and mid-turn list, design-doc README built-in
listing and decision-12 read-only enumeration, popup snapshots, and
the popup-tab / popup-enter app tests (now filter to /help with
`Char('h')` rather than relying on the first-row landing on /help).
The `is_read_only` and `execute` rustdocs still referenced
`SlashOutcome::PromptSubmit`, the variant name from before the
unification. `cargo doc` warns on the dead intra-doc link.
The slash branch reuses `forward_to_agent` for any `Action(_)`, but
the doc-comment only mentioned the `PromptSubmit` arm — `Action(Clear)`
flows through the same call too.
The test name and comment described the pre-refactor world where
`/clear` wrote to `user_tx` from `SlashContext`. After the unification
that bypass is gone — `/clear` returns `Action(Clear)` and the slash
branch forwards. The arm-level `false` return now guards future
callers that route `Clear` straight through `dispatch_user_action`.
The comment claimed "BUILT_INS today has 4 commands" — wrong since
the registry grew to six. The test still pins the right invariant
(`height == matches.len()` while below the cap); only the comment
needed updating to reference `MAX_VISIBLE_ROWS` instead of a count
that drifts every time a command lands.
The Workflow Skills bullet listed `/init`, `/review`, `/commit` as
"user-extensible commands backed by prompt templates" — but `/init`
already ships as a built-in under Working Today, contradicting the
section three screens above. Rewrote to describe the template
override surface as the deliverable; the existing built-ins are
called out by reference.
`dispatch_prompt_submit_command_returns_synthesized_body` still
destructured `UserAction::SubmitPrompt(body)` via let-else with a
`panic!` on the negative arm — the same dead-by-design pattern the
earlier coverage pass converted in four other sites. Folds the
destructure and the body-contains check into one `matches!` guard;
the failure message keeps the `{action:?}` debug print since `action`
is bound in the surrounding scope.
@hakula139 hakula139 merged commit e459827 into main May 2, 2026
4 checks passed
@hakula139 hakula139 deleted the feat/slash-init branch May 2, 2026 16:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant