- name
- live-exercise
- description
- Use whenever any UI-bearing work touches a running instance — building or fixing a feature, ship-gating, auditing, OR debugging visible bugs (flaky behavior, intermittent rendering, "sometimes does X" reports, hover/focus/animation glitches, layout overflow). Adaptive depth from tactical fix-loop to ship-gate audit. Not for pure-logic diff review.
# /live-exercise — Live functionality, intent, and quality loop
Operate the feature as a user would. Measure live state, not diffs. Apply the four lenses (functional, intent, polish, improvement) every sweep. Fix in parallel, verify after rebuild, re-sweep. At end-of-loop, optionally simplify code drift and optionally sweep performance. Stop only when sweep + simplify + perf all produce nothing new.
**This is the normal working rhythm for UI-bearing work — from tactical bug-fix iterations through ship-gate audits. Adaptive depth; no upfront prompt.**
## When to use vs other skills
| Skill | Purpose | Mode |
| ---------------- | ------------------------------------------ | -------------------- |
| `/review` | Standards + completeness checklist | Static |
| `/deep-review` | Correctness tracing end-to-end | Static |
| `/ux-review` | User flows against a goals doc | Static |
| `/live-inspect` | Reference for `vscode-inspector` MCP tools | Primitive |
| `/live-exercise` | Live operation + audit + fix loop | **Live + iterative** |
Use `/live-exercise` any time you touch UI — adding a panel, fixing a mode, refactoring a webview, ship-gating a feature. Not as a one-off audit; as the default way to work on UI.
## Prerequisites
- `vscode-inspector` MCP connected (auto-discovered via `.mcp.json`)
- Build currently passes (`pnpm run build:quick`)
## Lenses — applied every sweep
| Lens | When applied | Blocking? |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| **L1 — Functional walk-through** | Always. Layout, console, pixel invariants + handler verification (exercise the action, verify the intended outcome actually happened). | Yes |
| **L2 — Intent compliance (cold-read)** | Always. Intent source order: `goals.md` → user-solicited → **inferred cold-read**. | Yes |
| **L3 — UX heuristics polish** | Always observe. Surface findings. Blocks exit only under ship-gate signal. | Conditional |
| **L4 — Opportunistic improvement hunting** | On signal (user says "polish/audit/ship-gate/elevate") OR when L2 surfaces ambiguous-intent / can't-form-intent findings. | When triggered |
### L1 — Functional walk-through
Question: **does every feature actually do what it says it does, end-to-end, as a user would use it?**
- Enumerate every entry point (command, menu item, button, keybinding, automatic trigger)
- Walk every primary path to completion
- Exercise every state transition (empty → populated, loading → error → recovered, collapsed → expanded, mode A → mode B)
- Verify each handler actually fires and produces the intended outcome — not just that the click registered
Evidence: `evaluate_in_webview` to confirm the action ran (command executed, state updated, event fired). Not "button clicked" — "button clicked AND the result is X." Plus the structural invariants — computed layout, console clean, pixel geometry.
### L2 — Intent compliance via cold-read
Question: **does the live behavior match what a user expects?**
Intent sources, in priority order:
1. **`goals.md`** — under `.work/dev/<id>/` or similar. Authoritative when present.
2. **User-solicited intent** — if the user has given 1–2 sentences of intent inline this session. Authoritative when provided.
3. **Inferred cold-read** — always applied, even when 1/2 are available. Approach the feature as a brand-new user with no context: read labels, icons, tooltips, position, naming, surrounding UI, convention matches. Form an expectation of what this thing should do.
**If it doesn't make sense to the model, it's unlikely to make sense to a user.** Cold-read failures are real discoverability findings.
#### Cold-read discipline
Every L2 finding powered by inference must state the inference chain explicitly, so the user can correct it:
> **Inferred intent**: "Share branch via Patch link" (from label "Share" + link icon + position in branch context menu + convention of adjacent Commit/Shelve actions).
>
> **Observed**: click opens a raw clipboard prompt with no patch formatting.
>
> **Divergence**: the inferred action is a curated share flow; the actual action is a generic copy.
Classification — every L2 finding is one of:
| Type | Severity |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Inferred ≠ observed** | 🟡 Gap. Feature looks like it should do X, actually does Y. |
| **Inferred is ambiguous** | 🟡 Gap. Surface doesn't telegraph what it does — a new user would be guessing. |
| **Can't form intent at all** | 🔴 Critical. The model couldn't infer purpose from labels/icons/context. No user will either. Discoverability failure. |
### L3 — UX heuristics polish
Question: **does it feel right?**
Apply these heuristics on the running UI (the same set `/ux-review` uses, observed live):
- **Feedback & responsiveness** — loading/progress, success confirmation, failure visible where the user is looking, transitions smooth not jarring
- **Discoverability** — right surface, findable names, affordances look interactive, disabled elements explain why
- **Consistency** — patterns, terminology, icons, visual language match the rest of GitLens
- **Workflow integration** — flow preservation, reversibility, focus management, no unnecessary interruption
- **Information design** — hierarchy, density, progressive disclosure, helpful empty states
- **Accessibility** — keyboard parity, ARIA labels/roles/states, focus trapping for modals, theme compliance
Measure, don't eyeball — "feels cramped" is not a finding; "`.card` padding 4px vs repo convention 8–12px" is.
### L4 — Opportunistic improvement hunting
Question: **could this be meaningfully better?**
Triggered by:
- User says "audit," "polish," "ship-gate," "elevate" in the invocation
- L2 surfaced ambiguous-intent or can't-form-intent findings (fixing those requires proposing changes, which IS L4)
- A `goals.md` is present under `.work/dev/<id>/` (suggests scoped feature worth elevating)
- Multiple consecutive `/live-exercise` invocations on the same feature (user is circling — polish time)
Discipline:
- **Bias toward coherent upgrades that match existing patterns.** Small changes that fit intent → dispatch liberty-fix with `decisions.md` entry. Larger scope-expanding proposals → `open-questions.md`.
- **Not drive-by churn.** Every improvement must map to a UX heuristic gap, an ambiguous-intent fix, or the feature's stated intent — not just "shuffle things around."
## Adaptive depth — no upfront prompt
The skill never asks "what mode?" at start. Tactical by default (L1 + L2 active, L3 observed, L4 off). The lens table above lists L4 triggers; when any fire, comprehensive mode activates automatically. Phase 6 and 7 gate at end-of-loop (see "Convergence" below).
**Trivial changes — skip the loop.** A single-line copy edit, color-token swap, icon rename, comment-only change, or any change with no state surface earns build + diff + one-click eyeball verification. No findings doc, no Phase 4 dispatch, no convergence check. The skill exists for state-bearing UI work; don't manufacture ceremony for changes that have nothing to converge on. If you can describe the change in one sentence and it touches no handlers, no state, no layout, and no async paths, you're trivial — verify quick and ship.
## The loop
### 1. Scope & spec
0. **Reach starting state without clicking the SUT.** Use `launch({ settings })`, command palette, direct method calls on the component instance, or feature simulators. UI-driven setup conflates the flow you're testing with the state you need — every accidental toggle costs an iteration, and the same icon often does double duty as both setup and SUT. Save real clicks for what you're actually exercising.
1. Read `goals.md` under the user's pointed `.work/dev/<id>/` (or wherever). If absent, ask the user:
> "No `goals.md` found. In 1–2 sentences: what's the intended user experience for this feature? What problem does it solve, and what does success look like from the user's POV?"
If the user declines to supply intent, proceed on cold-read inference alone (lens 2 still runs).
2. Launch **one `Explore` subagent** to map the feature's code surface. Don't self-read everything up front — read specific files only when you dig into an issue.
3. Note `[FUTURE]`/`TODO` markers — they're known-open, not new findings.
### 2. Live sweep (lens application)
> **Default when running on Opus or Fable: delegate the mechanical driving to the `inspector-driver` subagent (Sonnet 5).** You launch/own the instance and apply the lenses (L1–L4 are your judgment); dispatch `Agent({ subagent_type: "inspector-driver", model: "sonnet", prompt: <the states + exact probes to collect> })` to gather the raw evidence (geometry, state, console/log excerpts) across many states in one batched ask, then reason over what it returns. Tell the driver not to `launch`/`teardown`. Keep pixel judgment (L3 visual polish) on yourself — take those screenshots directly. See `/live-inspect` → "Delegate the driving to a Sonnet driver" for the full pattern and caveats.
`launch` VS Code, then for **every** distinct mode/state/context the feature exposes:
1. Maximize real estate via `execute_command` to close the aux bar/bottom panel — but mind that the Commit Graph's home is the GitLens _side bar_ (#5545): when auditing it (or anything in that container), widen the side bar instead of closing it (`workbench.action.decreaseViewWidth` — it always shrinks the editor part, handing the width to its neighbours, so close the aux bar first). Do NOT use `resize_window` to grow the window for general auditing — it resizes the actual Electron window and is clamped by the host display. Only resize when a lens explicitly requires testing a specific responsive breakpoint, and prefer sizes ≤ the launch window. For larger headless render surfaces, configure `launch({ screen_resolution })` at startup instead.
2. Navigate via `execute_command` or programmatic clicks. Shadow-DOM traversal is the hard part — use `evaluate_in_webview` to dispatch synthetic `MouseEvent`s with modifier keys when needed. Wrap multi-line scripts in `(() => { ... })()` and `JSON.stringify` non-primitive returns. `new Error().stack` is often empty in eval context — patch the source method to get useful stack frames.
3. Apply all active lenses per state:
- **L1**: exercise interactions, verify handlers fired (`evaluate_in_webview` → command executed / state updated / event dispatched). Capture layout invariants.
- **L2**: compare live behavior to goals/intent (or cold-read inference). State inference chains explicitly.
- **L3**: `aria_snapshot`, computed styles for consistency, check empty/loading/error states explicitly.
- **L4** (when active): actively look for coherent improvements.
- **Always**: `read_console { level: "error" }` and `read_logs` per state — errors can be load-order / state-dependent.
4. **Measure, don't eyeball.** "Looks off" is not an issue. "AI input at y=1306 in a 1308px viewport" is. "The button does nothing" → verify via `evaluate_in_webview` that the handler fired and state updated.
### 3. Compile findings
Write under `.work/live/<feature>-exercise/`:
**`findings.md`** — status table + severity sections.
```markdown
# <Feature> — Exercise Findings
## Status
| ID | Lens | Title | Status | Notes |
| ------ | ---- | ------------------------------------- | -------- | ---------------------------- |
| I1-C1 | L1 | Compose panel overflows viewport | ✅ fixed | `.compose-panel` set to flex |
| I1-G2 | L2 | Compose view missing drafts flow | open | goals §3.2 |
| I1-P1 | L3 | No loading feedback on push | open | >1s silent |
| I1-E1 | L4 | Add "retry" on transient fetch errors | open | see Q3 |
| I1-PR1 | Perf | +180ms hydration on Home view | open | baseline 220ms → 400ms |
## 🔴 Critical (L1 broken / L2 can't-form-intent)
## 🟡 Gap (L2 divergence/ambiguity)
## 🔵 Polish (L3)
## 🟣 Opportunity (L4)
## Perf (see Phase 7)
### I1-C1 — <Title>
- **Lens**: L1 — functional walk-through
- **File**: `path/to/file.ts:L42`
- **Repro**: one-line user action + measurable observation
- **Root cause**: what's wrong in code
- **Fix**: concrete approach, OR "needs design decision (see Q<n>)"
```
**ID scheme**: `I<iter>-<sev><n>` where sev ∈ {C (L1 critical), G (L2 gap), P (L3 polish), E (L4 enhancement), PR (perf regression), PC (perf convention), PS (perf speculation)}. Iteration number comes first so history stays legible.
**`open-questions.md`** — decisions the user must make:
```markdown
## Q1. <Topic>
Brief context (1–2 sentences).
**Option A** — … | **Option B** — … | **Option C** — …
**Recommendation**: A, because …
```
**`decisions.md`** — liberties taken (only created if any):
```markdown
## D1. Added loading indicator to push action
- **Finding**: I1-P1
- **Rationale**: GitLens convention is `gl-progress-spinner` on any action >500ms. Empty state had none.
- **Change**: `src/webviews/apps/plus/home/push.ts:L88` — added spinner, matches commit-view pattern.
- **Reversible?**: yes, one-line.
```
Rules:
- Every finding cites a file and a repro someone else can reproduce.
- Unambiguous bugs become IDs. Design questions become Q-numbers. Liberties become D-numbers.
- If in doubt, use `AskUserQuestion` before filing as a fix.
GitHub에서 보기