diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index bd2fb0a2..00000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"57f44d3f-2540-472d-bc33-d4a87322ae05","pid":1372647,"procStart":"48543280","acquiredAt":1777290170782} \ No newline at end of file diff --git a/.claude/skills/mewbo-cli-smoketest/SKILL.md b/.claude/skills/mewbo-cli-smoketest/SKILL.md index da7419bb..abb12af2 100644 --- a/.claude/skills/mewbo-cli-smoketest/SKILL.md +++ b/.claude/skills/mewbo-cli-smoketest/SKILL.md @@ -1,176 +1,226 @@ --- name: mewbo-cli-smoketest -description: End-to-end smoke testing of the Mewbo CLI via tmux. Use this skill when asked to test the CLI, verify CLI behavior after changes, smoke-test the agent loop, check for regressions, or validate MCP/plugin/session features work correctly through the terminal interface. Also use when debugging CLI crashes, MCP connection issues, or session lifecycle problems that need live reproduction. +description: End-to-end smoke testing of the Mewbo CLI via tmux. Use this skill when asked to test the CLI, verify CLI behavior after changes, smoke-test the agent loop, check for regressions, or validate MCP/plugin/session features work correctly through the terminal interface. Also use when validating the live-streaming transcript ("glide"), the activity footer/spinner, or the fleet sidebar + per-agent drill-in (epic #161), and when debugging CLI crashes, MCP connection issues, or session lifecycle problems that need live reproduction. --- # Mewbo CLI Smoke Test via Tmux -Automate end-to-end CLI testing by running `mewbo` inside a tmux pane, sending commands/queries, and analyzing verbose output for errors, warnings, and regressions. +Automate end-to-end CLI testing by running `mewbo` inside a tmux pane, driving it with commands/queries, and analyzing both the rendered TUI frame and the verbose log for errors, warnings, and regressions. ## Why tmux -The CLI is a full-screen Rich/Textual TUI. You cannot run it directly via Bash tool because it requires a PTY and renders interactive widgets. Tmux gives you a real terminal to drive the CLI while capturing output programmatically via `tmux capture-pane`. +Since epic #149 the CLI is **one full-screen Textual `App`** (not the old Rich `Live` + prompt stack), and #161 layered live streaming + a per-agent fleet sidebar on top. You cannot run it via the Bash tool because it requires a PTY and renders interactive widgets on the **alternate screen**. Tmux gives you a real terminal to drive the app while capturing output programmatically. -## Setup +## Two capture channels (read this first) + +A Textual app paints the **alternate screen**, so the two things you want to observe come from two different places: -Find a running tmux session and create a new window: +1. **The rendered TUI frame** — `tmux capture-pane -p` returns the *current visible frame only*. The alt-screen keeps **no scrollback**, so `-S -100` does **not** give you history the way it does for a plain shell; it just returns the visible grid. To inspect what scrolled past, drive the app to re-render (e.g. scroll the transcript) or rely on the log channel below. +2. **The verbose log** — route logs to a file so they don't corrupt the TUI, then `grep` the file. **Do not** use `--log-console` with the TUI (it paints over the widgets). Launch with: ```bash -tmux list-sessions -tmux new-window -t : -n mewbo-test +tmux send-keys -t :mewbo-test \ + "mewbo -vv --auto-approve --log-file /tmp/mewbo-test.log --log-overwrite" Enter ``` -Launch with maximum verbosity and auto-approve (skips permission prompts): +Then, after each step: `tmux capture-pane -p -t :mewbo-test` for the **frame**, and `grep -nE 'WARNING|ERROR|Traceback|Failed to' /tmp/mewbo-test.log` for **diagnostics**. The two are complementary — a clean frame can still hide a logged reconnect error, and a busy log can accompany a correctly-rendered frame. -```bash -tmux send-keys -t :mewbo-test "uv run mewbo -vv --auto-approve" Enter -``` +> Use the installed `mewbo` (from `uv tool install .`) or `uv run mewbo` from the repo root. The installed binary tests what the user actually runs. -Wait for startup (MCP connections, plugin loading, skill discovery). Startup typically takes 5-10 seconds depending on MCP server count. Capture and verify the banner appears: +## Setup ```bash -sleep 10 && tmux capture-pane -t :mewbo-test -p -S -100 +tmux list-sessions +tmux new-window -t : -n mewbo-test +# width matters: the sidebar/fleet only render legibly with enough columns +tmux resize-window -t :mewbo-test -x 200 -y 50 2>/dev/null || true +tmux send-keys -t :mewbo-test \ + "mewbo -vv --auto-approve --log-file /tmp/mewbo-test.log --log-overwrite" Enter +sleep 10 && tmux capture-pane -p -t :mewbo-test ``` -Look for the ready banner showing model, session ID, tool counts, and `mewbo>` prompt. If the prompt hasn't appeared, wait longer — MCP servers may take time to connect. +Startup (MCP pool connect, plugin load, skill discovery) takes 8-10s. Verify the frame shows the **header** (model, session id, tool counts), the **transcript** region, the **sidebar** on the right, the **input** box, and the **status line** at the very bottom. If widgets are missing, the window is probably too narrow — resize and re-capture. + +### Screen anatomy (widget IDs / regions) + +The layout (`app.py` `compose`) is the map for where to look in a captured frame: + +| Region | id | Widget | What lives there | +|---|---|---|---| +| top | `#header` | `HeaderWidget` | model · session id · tool/skill counts | +| body left | `#transcript` | `TranscriptView` | the conversation + **streaming text** + tool cards + the **activity spinner** at its foot | +| body right | `#sidebar` | `SidebarView` | faceted sections: **FLEET · PLAN · CONTEXT** | +| below body | `#input` | `InputArea` | the `mewbo>` prompt | +| bottom bar | `#statusline` (in `#footerbar`) | `StatusLine` | `user@host · model · cwd · branch · stash · session tokens (↑in ↓out)` | + +Global keys: `ctrl+c` quit, `shift+tab` cycle permission mode. In a drill-in view (below): `esc` / `backspace` returns to chat. ## Capture timing -This is the critical non-obvious part. Different operations need different wait times before capturing output: +Single `sleep N && tmux capture-pane` commands — never split them. Different operations need different waits: -| Operation | Wait (seconds) | Why | +| Operation | Wait (s) | Why | |---|---|---| -| Startup | 8-10 | MCP pool connects, plugins load, skills discover | -| Slash command (`/help`, `/status`) | 2-3 | Local only, no LLM call | -| Interactive command (`/mcp`, `/models`) | 2-3 | Opens TUI picker — must send `Escape` to dismiss before next command | -| Simple query (no tools) | 10-15 | Action plan + LLM call + response | -| Tool-using query | 15-25 | Plan + LLM + tool execution + synthesis | -| MCP tool query | 25-40 | Plan + LLM + MCP network call + synthesis | -| `/compact` | 10-15 | Rebuilds summary via LLM call | +| Startup | 8-10 | MCP pool, plugins, skills | +| Slash command (`/help`, `/status`) | 2-3 | local only | +| Interactive picker (`/mcp`, `/models`) | 2-3 | opens a modal — `Escape` to dismiss before next input | +| Simple query (no tools) | 10-15 | plan + LLM + response | +| Tool-using query | 15-25 | plan + LLM + tool + synthesis | +| MCP tool query | 25-40 | plan + LLM + network + synthesis | +| Sub-agent / parallel-fleet query | 30-60 | spawns populate the FLEET section incrementally | +| `/compact` | 10-15 | rebuilds summary via LLM (re-inits subsystems) | -Always use `sleep N && tmux capture-pane` as a single command — do not separate them. Adjust the scroll buffer depth (`-S -N`) based on expected output verbosity. `-S -60` is usually sufficient; use `-S -100` for startup output. +For **streaming** and **fleet** behavior, a single end-of-turn capture is not enough — you must capture **mid-turn** (see below) to prove things render *incrementally*, not just at the end. -## Test progression +--- -Test in layers, from cheapest to most expensive. If an early layer fails, later layers will too. +# Test progression -### Layer 1: Slash commands (no LLM, no network) +Layers run cheapest → most expensive. An early failure cascades, so stop and fix before climbing. -These validate the CLI framework, config loading, and plugin discovery: +## Layer 1 — Slash commands (no LLM, no network) + +Validate the framework, config loading, and plugin/skill discovery: ``` -/help — all commands listed, no crashes -/status — session JSON with valid ID and idle state -/session — session ID matches banner -/tokens — budget table renders, context window > 0 -/skills — skill count matches banner, names listed -/plugins — installed plugins table renders (note any WARNING lines) +/help · /status · /session · /tokens · /skills · /plugins ``` -**What to look for in verbose output**: `WARNING` or `ERROR` log lines during plugin/skill loading. Common issues: -- `Failed to parse manifest` — stale plugin cache, missing files -- `No YAML frontmatter` — agent definition files missing required format -- `Missing or invalid 'name'` — skill SKILL.md files lacking name field +In `/tmp/mewbo-test.log` look for `WARNING`/`ERROR` during plugin/skill load: `Failed to parse manifest` (stale plugin cache), `No YAML frontmatter` (agent def format), `Missing or invalid 'name'` (skill `SKILL.md`). Non-fatal but they mark integration gaps. + +## Layer 2 — Interactive pickers -These warnings are non-fatal but indicate plugin integration gaps. +`/mcp` and `/models` open modals. Capture to confirm the modal renders, then **dismiss with `Escape`** before the next input or it goes to the modal, not the prompt. -### Layer 2: Interactive commands +## Layer 3 — The streaming transcript ("glide") — #152/#161 -Commands that open TUI pickers need special handling: +This is the headline #161 surface: assistant text must **glide in incrementally**, tool cards must **mutate in place**, and everything must appear in **exact emission order**. Send a tool-using query and capture **mid-turn**: ```bash -# /mcp opens a selector — verify it renders, then dismiss -tmux send-keys -t ... "/mcp" Enter -sleep 3 -# Capture to verify the picker rendered with server list -tmux capture-pane -t ... -p -S -40 -# Dismiss the picker -tmux send-keys -t ... Escape -sleep 1 +tmux send-keys -t ... "List the files in the current directory, then summarize what this project is" Enter +sleep 4 && tmux capture-pane -p -t ... # mid-turn frame #1 +sleep 6 && tmux capture-pane -p -t ... # mid-turn frame #2 +sleep 12 && tmux capture-pane -p -t ... # settled frame ``` -### Layer 3: Simple query (tool-use loop, no MCP) +**What to expect, by phase:** -Send a query that exercises the core loop with a local tool: +| Phase | In the frame | In the log | +|---|---|---| +| turn starts | activity spinner appears at transcript foot: ` Working… (Ns)` (frames `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`) | — | +| thinking | spinner label flips to `thinking` (`set_status("thinking")`) | assistant deltas arriving | +| text glides | partial assistant text grows token-by-token **above** the spinner; the visible tail extends between frames #1 and #2 without the whole block flickering (stable-prefix: only the trailing partial re-renders) | — | +| tool runs | a tool card appears (`🔧 `), spinner label flips to `running ` | `Running ...` | +| tool settles | the **same** card mutates to its done/error state and dims (`.t-settled`, ~65% opacity) — it must **not** spawn a second card | — | +| turn ends | spinner collapses into a settled summary line `done · N.Ns` (muted); the next turn mounts a fresh spinner below it | — | -``` -List the files in the current directory -``` +**Nuance to actually catch regressions:** +- **Incrementality**: compare frame #1 vs #2 — the assistant text tail must be *longer* in #2. If text only appears in the settled frame, streaming is unwired (the #1 bug this epic fixed). +- **Order/provenance invariant**: text, tool cards, and spawn markers must be interleaved in **arrival order**, never bucketed by type (all-text-then-all-tools = regression). +- **Mutate-in-place**: a tool that runs then finishes is **one** card that changes state, not two stacked cards. +- **Settled vs running styling**: running content is full-weight; completed/failed content is dimmed/muted. A finished turn whose spinner keeps animating, or whose old cards stay full-bright, is a lifecycle bug. -This tests: action plan generation, tool binding, `aider_list_dir_tool` execution, response synthesis. Wait 20 seconds. Verify: -- Action plan box rendered -- Tool call shown (look for the tool emoji line) -- Response box rendered with coherent content -- No Python tracebacks in verbose output +## Layer 4 — Footer activity + status line -### Layer 4: MCP tool query +The foot-of-transcript **activity spinner** is distinct from the bottom **status line**: +- **Activity spinner** (in `#transcript`): live step label — `Working` → `thinking` → `running ` → settles to `done · N.Ns`. It must always end settled (never spin forever after the response renders). +- **Status line** (`#statusline`): `user@host · model · cwd(~) · branch · stash · ↑` session tokens. After queries, `↑/↓` token counts must be **non-zero** and grow across turns. Cross-check with `/tokens` and `/budget`. -Send a query that forces an MCP tool call: +## Layer 5 — Fleet sidebar + per-agent drill-in — #161 Phase 2+4 -``` -Use deepwiki to look up the architecture of bearlike/Assistant +Trigger sub-agents so the **FLEET** section populates, then drill in. Use a query that fans out: + +```bash +tmux send-keys -t ... "Spawn two subagents in parallel: one to list the python packages under packages/, one to count the test files under tests/. Then combine their findings." Enter +sleep 8 && tmux capture-pane -p -t ... # fleet populating +sleep 25 && tmux capture-pane -p -t ... # agents running/completing ``` -This tests: MCP tool routing, connection pool, external network call, large response handling. Wait 30-40 seconds. **Critical signals to watch for**: +**Faceted sidebar (Phase 4):** the right column shows uppercase `$accent` section headers **FLEET**, **PLAN**, **CONTEXT**, each a titled block (`SidebarSection`). PLAN is the todo dock; CONTEXT is the context/cost gauge. -- `Connected to MCP server` — new on-demand connections (the pool connects lazily for project-level servers) -- `Disconnected from MCP server` — connection churn during tool-use loop -- `Failed to reconnect MCP server` — config merge or library compatibility issues -- `_create_streamable_http_session() got an unexpected keyword argument` — config normalization bug (type/transport collision) -- `Configuration error: Missing 'transport' key` — plugin MCP config not normalized +**Fleet rows (Phase 2):** the FLEET section is a selectable `OptionList` with **one row per agent** in the hypervisor tree (root + every sub-agent + parallel agents), tree-ordered and indented. Each row is a **one-glance summary**, never a tool-name dump: -These MCP errors often surface only on the SECOND tool call or during `/compact`, because `refresh_if_config_changed` triggers config re-merge. The first call may succeed using the initially-connected pool, while reconnection uses the merged config (which may include CWD `.mcp.json` and plugin configs with different schemas). +``` +