Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .agents/docs/asking-questions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
workhorse-version: 0.7.2
---

# Asking the user a question

You can ask the user a question in the middle of a turn and wait for the answer,
rather than asking in prose and ending your turn. Ask through the `AskUserQuestion`
tool. It presents one to four questions, each with two to four options, and the
turn parks until the user answers, so the answer is in hand before you continue.

The tool is named `AskUserQuestion` wherever it runs, so this guidance reads the
same whether the turn runs in Workhorse or in Claude Code. Do not test which
harness you are in or reach for a differently-named tool.

Four things decide whether a question belongs in the tool:

- **A decision between known alternatives goes to the tool; open probing stays in
prose.** Options are right when you can name the answers — an approach, a
library, a yes or no. They are wrong when the options would invent the answer
space rather than discover it, which is what open questions do. Ask those in
prose.
- **Batch independent questions into one call, up to four.** A question whose
shape depends on the answer to another is asked on its own, on a later turn,
once the earlier answer is in. Do not stack dependent questions into one call.
- **A short comparison rides on the option it belongs to.** A criterion phrased
two ways, or a brief snippet, goes in that option's description rather than in
the question text. Every option's description is on screen at once, which is
what a comparison needs.
- **A visual comparison, or anything longer than a snippet, goes to a mockup and
the question points at it.** The panel is not a comparison surface. A wording
decision on a spec you are already drafting is not one either: the text is
editable beside the chat, so settle it there.

Write whatever the decision rests on into your reply before the call. Your
reasoning is not shown to the user, so a question that refers back to it ("given
those costs") arrives with nothing behind it. The question and its options are
read after that reply, not instead of it.

Only ask when you cannot proceed without the answer: the turn is parked and the
user is blocked on you while it is open.
86 changes: 86 additions & 0 deletions .agents/docs/browser-rig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
workhorse-version: 0.7.2
---

# Driving the app in a real browser

How an agent gets a browser, runs the workspace's app and drives it, in a workspace it has never seen. `qa-loop` and `hands-free-feature` depend on it. Nothing here is guaranteed by the platform: a browser, the app's dependencies, a database and network access are all things to check, and a check that fails is reported, never assumed away.

## Where a browser can run

- **On the user's device**, in a local conversation: Bash is unrestricted, so everything below applies.
- **On the cloud**: Bash is restricted and there is no browser. Say so at the start of the run, do every check that needs no browser, and list each browser check as not verified. Do not substitute reading the code for running it.

## 1. Preflight: prove you have a browser

Work down the list and stop at the first that passes the smoke test.

1. **The repo's own Playwright** (`@playwright/test` or `playwright` in its `package.json`): use its version and config, with `npx playwright install chromium` if its browser is missing.
2. **System Chrome or Chromium**, driven by `playwright-core` with `channel: 'chrome'` (or `executablePath`). Check the usual places: `/Applications/Google Chrome.app` on macOS, `google-chrome`, `chromium` or `chromium-browser` on the path on Linux, `Program Files\Google\Chrome` on Windows.
3. **Playwright's own Chromium**, installed into its cache by `npx playwright-core install chromium` (about 100 MB, needs the network, and is the same cache every Playwright project on the machine uses). A cache that already holds some Chromium is not enough: `playwright-core` wants the build matching its own version, and a mismatch fails with "Executable doesn't exist". Install, rather than reusing what is there.

Install `playwright-core` in the rig directory (below), not in the repo, so no dependency lands in the user's `package.json`.

**The smoke test is the only judge.** Launch headless, open a `data:` URL, set CPU throttling over CDP, take a screenshot and close, within about 30 seconds:

```js
import { chromium } from 'playwright-core'
const browser = await chromium.launch({ headless: true, channel: process.env.CHANNEL })
const page = await browser.newPage()
await page.goto('data:text/html,<h1>ok</h1>')
const cdp = await page.context().newCDPSession(page)
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 4 })
await page.screenshot({ path: 'smoke.png' })
await browser.close()
```

Leave `CHANNEL` unset for Playwright's Chromium and set it to `chrome` for system Chrome. If nothing passes, write "no browser" and the reason in the loop log, mark every browser check not verified, and say in the final report what the user would need to install. Do not stop to ask mid-run.

## 2. The rig directory

One durable directory holds everything the run needs, outside the repo and outside `/tmp`, which the operating system cleans mid-run:

- macOS: `~/Library/Caches/workhorse-rig/{repo}/{card-id}`
- Linux: `${XDG_CACHE_HOME:-~/.cache}/workhorse-rig/{repo}/{card-id}`
- Windows: `%LOCALAPPDATA%\workhorse-rig\{repo}\{card-id}`

It holds `playwright-core`, the scripts, screenshots and filmstrips, the seed and its dump, the reports, and a short `README.md` naming the ports, process ids and how to restart it. Record its path in the loop log, and have every agent reuse it rather than building its own. Agents that start their own databases and servers collide on ports and test the wrong thing.

## 3. Running the app

Do not assume how. Find out, in this order:

1. The repo's own instructions: `CLAUDE.md`, `AGENTS.md`, `README`, `package.json` scripts, `Makefile`, `docker-compose`, a project skill for running the app, and the `run` skill where it exists.
2. A production build served detached from a copy of the worktree (excluding build output, with its own installed dependencies) when the app has one, because the development server's behaviour differs on exactly the things feel depends on. Otherwise the development server.

Rules for every run:

- **Bind to `127.0.0.1`** on a free port of your own choosing, and record it. Never use a port another process holds.
- **A throwaway database** with a seed, restored from a dump before each run, so a run starts from known data. Never point the app at real data or real credentials. Use the repo's own test-database conventions where it has them.
- **Fake external services** (mail, payments, third-party APIs) rather than the real ones.
- **Sign-in** through whatever the repo offers for tests: a seeded session, a test login or a dev bypass. Seed two users with different permissions where the surface is multi-user.
- **Kill only what you started.** Record the process ids and stop those. Never `pkill -f` on a pattern: the shell running the command matches too.

If the app cannot be started after a fair attempt (missing services, secrets only the user has), say what blocked it, and test the layers that do run: unit, integration, API through the test harness.

## 4. Driving it

Script every flow with Playwright, kept in the rig's `scripts/` folder.

- **Time input to result:** from the input event to the first animation frame in which the visible result is true (`requestAnimationFrame` after the change, or `performance.now()` across a mutation observer).
- **Throttle twice.** Run each flow locally and again on Fast 3G with 4x CPU: `Emulation.setCPUThrottlingRate` with `rate: 4`, and `Network.emulateNetworkConditions` with `latency: 150`, `downloadThroughput: 204800`, `uploadThroughput: 96000` (bytes per second, so about 1.6 and 0.75 Mbit/s).
- **Record on every step:** console errors and warnings, `pageerror`, failed and 4xx or 5xx requests, layout shift (a `PerformanceObserver` on `layout-shift`), and the focused element (`document.activeElement`).
- **Filmstrip an interaction:** a screenshot per frame, or a trace, for anything that animates, drags or reflows. Look at each frame for a flicker, a double paint, a jump, or a frame holding both the old and the new state.
- **Viewports:** 1440, 1024 and 390 wide, at a device scale factor of 2 when measuring.
- **Two people:** two browser contexts, signed in as different users, for anything shared or live.
- **Break the connection and the session:** `context.setOffline(true)`, reload mid-action, Back and forward, an expired session, a server restart.
- **Keyboard only:** drive a flow with no pointer, and note where focus goes after each action.
- **Accessibility:** inject `axe-core` from the rig where it installs, and read the accessibility tree with `page.locator('body').ariaSnapshot()` for names, roles and state.

Keep every script. A finding is only as good as the script that reproduces it.

## 5. Evidence and tests

- Each browser finding names the script, the viewport and throttling it ran under, and the screenshots, so a fixer and a re-checker can run the same thing.
- Looking at screenshots is the orchestrator's job. Capturing them is an agent's.
- The rig is scaffolding and is not committed. A defect it finds that the repo's own tests did not cover gets a committed test in the repo, written in the repo's style at the lowest level that would have caught it. A defect only a browser shows (layout, focus, timing) gets an end-to-end test in the repo's own browser test framework, if it has one. If it has none, record the case in the test-cases file and as a follow-up, and do not introduce a framework silently.
28 changes: 28 additions & 0 deletions .agents/docs/card-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
workhorse-version: 0.7.2
---

# Establishing card context outside Workhorse

Skills are discovered natively by Claude Code, so they can be invoked in an external tool with no Workhorse session around them. Inside Workhorse the system prompt injects the card's context — its title, identifier, and description. Outside it, that context is absent, and a skill that needs to know which card it is working on has to establish that context before doing its work.

Follow this process whenever a skill directs you here.

## When this applies

- Apply it only when you do **not** already have the card's context — no title, identifier, or description available to you. This is the case when you are running outside Workhorse
- Inside Workhorse the card's context is always present, so this process never runs there
- If you already established the card's context earlier in this conversation, reuse it — do not ask again

## Establishing context

When you have no card context, ask the user to establish it before you start the skill's work. Offer two paths:

1. **Linear card** — the user gives you a Linear card code and connects the Linear MCP, so you can read the card directly. Read whatever detail helps the task at hand — at minimum the card's title and description. If the Linear MCP is not connected, walk the user through connecting it
2. **Paste** — the user pastes the card's title and description directly into the chat

Also ask the user for the card's **identifier**. It is used for the file paths of mockups (`.workhorse/design/mockups/{card-id}/`), plans (`.workhorse/plans/{card-id}/`), and test cases (`.workhorse/test-cases/{card-id}/`). Every card has one — a Linear card code supplies it directly. If no Linear or Workhorse ticket backs the work, ask the user to create one so the work is tracked; you can offer to create it for them through the Linear MCP.

## When context is incomplete

If the user cannot or will not supply full context, do the best you can with what is available — ask them for whatever detail they can provide to work from, and proceed on that. Don't block on missing context you can reasonably work around.
Loading