@asrunjs/asrun 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.claude/commands/explore-case.md +14 -5
  2. package/.claude/commands/gen-spec.md +12 -5
  3. package/.claude/commands/heal-spec.md +23 -8
  4. package/.claude/commands/make-case.md +1 -1
  5. package/.claude/skills/exploring-cases/SKILL.md +82 -37
  6. package/.claude/skills/exploring-cases/references/assertion-targets.md +8 -4
  7. package/.claude/skills/exploring-cases/references/case-rules.md +110 -0
  8. package/.claude/skills/exploring-cases/references/handoff-card.js +73 -11
  9. package/.claude/skills/exploring-cases/references/open-questions.md +60 -8
  10. package/.claude/skills/exploring-cases/references/readiness.md +4 -3
  11. package/.claude/skills/exploring-cases/references/tools.md +117 -0
  12. package/.claude/skills/exploring-cases/references/user-handoff.md +46 -17
  13. package/.claude/skills/generating-specs/SKILL.md +148 -55
  14. package/.claude/skills/generating-specs/references/conventions.md +4 -0
  15. package/.claude/skills/making-cases/SKILL.md +14 -7
  16. package/.claude/skills/making-cases/references/gaps.md +7 -4
  17. package/.claude/skills/making-cases/references/normalizing.md +1 -1
  18. package/AGENTS.md +68 -20
  19. package/README.md +8 -6
  20. package/bin/asrun.mjs +1 -0
  21. package/dist/lib/case-doc.d.ts +41 -4
  22. package/dist/lib/case-doc.js +109 -17
  23. package/dist/scripts/check-cases.js +89 -1
  24. package/dist/scripts/doctor.js +50 -3
  25. package/dist/scripts/markers-confirm.d.ts +1 -0
  26. package/dist/scripts/markers-confirm.js +139 -0
  27. package/dist/ui/server/actions.js +57 -18
  28. package/dist/ui/server/app.js +7 -2
  29. package/dist/ui/server/job-store.d.ts +60 -0
  30. package/dist/ui/server/job-store.js +198 -0
  31. package/dist/ui/server/jobs.d.ts +46 -4
  32. package/dist/ui/server/jobs.js +361 -59
  33. package/dist/ui/server/process-info.d.ts +17 -0
  34. package/dist/ui/server/process-info.js +126 -0
  35. package/dist/ui/server/result-state.d.ts +10 -0
  36. package/dist/ui/server/result-state.js +106 -0
  37. package/dist/ui/shared/ai-commands.d.ts +9 -3
  38. package/dist/ui/shared/ai-commands.js +25 -11
  39. package/dist/ui/shared/types.d.ts +18 -3
  40. package/docs/agents/case-format.md +39 -5
  41. package/docs/ai-workflow.md +80 -21
  42. package/docs/cases.md +28 -4
  43. package/docs/commands.md +6 -1
  44. package/docs/theme-guide.html +93 -5
  45. package/docs/troubleshooting.md +47 -3
  46. package/docs/web-ui.md +55 -10
  47. package/package.json +2 -1
  48. package/templates/.claude/commands/explore-case.md +14 -4
  49. package/templates/.claude/commands/gen-spec.md +12 -4
  50. package/templates/.claude/commands/heal-spec.md +27 -13
  51. package/templates/.claude/commands/make-case.md +1 -1
  52. package/templates/AGENTS.md +9 -6
  53. package/templates/cases/_template.md +7 -0
  54. package/templates/package.json +1 -0
  55. package/ui/dist/assets/{dist-ChBHX4rh.js → dist-2msWI-5M.js} +1 -1
  56. package/ui/dist/assets/{dist-DoChb6QQ.js → dist-4U_gPHXf.js} +1 -1
  57. package/ui/dist/assets/{dist-CJiwxr1r.js → dist-BMfJatHo.js} +1 -1
  58. package/ui/dist/assets/{dist-N-o4QIaZ.js → dist-BVvHVJ3N.js} +1 -1
  59. package/ui/dist/assets/{dist-BC_P1Gnf.js → dist-BmWaNjqO.js} +1 -1
  60. package/ui/dist/assets/{dist-Do_MhHKF.js → dist-BwNz_xW2.js} +1 -1
  61. package/ui/dist/assets/{dist-D6jmnDr-.js → dist-J36xayOv.js} +1 -1
  62. package/ui/dist/assets/{dist-CavmZIv7.js → dist-e-KhTWHJ.js} +1 -1
  63. package/ui/dist/assets/{dist-BpkTvixI.js → dist-sfZKBSMm.js} +1 -1
  64. package/ui/dist/assets/{dist-BgPbx2GZ.js → dist-wjB7g6OJ.js} +1 -1
  65. package/ui/dist/assets/index-CGbRJT3b.js +348 -0
  66. package/ui/dist/assets/index-CI0i0LWc.css +2 -0
  67. package/ui/dist/help/case-explore.png +0 -0
  68. package/ui/dist/index.html +2 -2
  69. package/ui/dist/assets/index-Cy2l91qV.css +0 -2
  70. package/ui/dist/assets/index-DGQ5logJ.js +0 -338
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generating-specs
3
- description: Turns a human-written test case (cases/<id>.md, the id may include a group folder such as documents/landing-page) into a Playwright spec (tests/<id>.spec.ts) for the multi-profile test suite, explores the real pages to learn the locators and values, and completes the case's own "## Values" block. It only generates: it never runs the tests. Use when asked to generate, update, fix or heal a spec, or for /gen-spec.
3
+ description: Turns a human-written test case (cases/<id>.md, the id may include a group folder such as documents/landing-page) into a Playwright spec (tests/<id>.spec.ts) for the multi-profile test suite, explores the real pages to learn the locators and values, and completes the case's own "## Values" block. It only generates: it never runs the tests. Healing (repairing a failing spec, or refining a case and its spec on request) is the exception. Use when asked to generate, update, fix, heal or refine a spec, or for /gen-spec or /heal-spec.
4
4
  ---
5
5
 
6
6
  # Generating specs from case documents
@@ -8,31 +8,47 @@ description: Turns a human-written test case (cases/<id>.md, the id may include
8
8
  Read `references/conventions.md` first. It defines the spec shape, fixtures, assertion layers and
9
9
  the read-only rules. Read the workspace `AGENTS.md` for the hard rules and the project rules. Read `../exploring-cases/references/open-questions.md`: it
10
10
  defines how open questions are written into the case file, resolved and reported. Also read
11
+ `../exploring-cases/references/case-rules.md` (how a case's `## Rules` are read, applied and maintained),
11
12
  `../exploring-cases/references/assertion-targets.md` (the exact thing each check looks at) and
12
- `../exploring-cases/references/readiness.md` (wait for the content, verify downloads in five parts). All three are binding here.
13
+ `../exploring-cases/references/readiness.md` (wait for the content, verify downloads in five parts). All four are binding here.
13
14
 
14
15
  This skill never stops to ask the user in the chat, because it may run without anybody present (a one-shot
15
- `claude -p` / `opencode run`). Questions go into the case file; the final line is `RESULT: ready` or
16
- `RESULT: needs-input`.
16
+ `claude -p` / `opencode run`). This holds whatever mode or agent the run is in: plan mode changes what you may write,
17
+ never how you end. If you are not allowed to write files (plan mode, a read-only sandbox, a refused permission), say
18
+ what blocked you in one sentence and end with `RESULT: needs-input` instead of answering with a plan and a question.
19
+ Questions go into the case file; the final line is `RESULT: ready` or `RESULT: needs-input`.
17
20
 
18
21
  This skill trusts the case. What the pages show, what a person has to enter, and what the open questions are is
19
22
  settled by `/explore-case` and recorded in the case (`(observed, ...)` marks, the step recipes). Generating
20
23
  only finds locators and writes code: it replays the case and does not judge the case again.
21
24
 
25
+ The profile scope is the case's, not a selection. It is every profile id that appears in the case — the keys of
26
+ `## Values`, a target card's `Reference <profile-id>:` line, a step observation — plus the `default` entry. There is no
27
+ `--profiles` here: never narrow the scope to the profiles that are logged in, and never ask for a subset. The spec runs
28
+ once per profile in the environment, so it must cover every profile the case says something about. A profile the case
29
+ says nothing about runs on `default` alone; name those in the report. Runs of `/explore-case` add up, so a case explored
30
+ with A and B first and C later generates a spec for all three.
31
+
22
32
  Generating a spec and running it are separate processes. This skill only writes files: it does not run
23
33
  `pnpm asrun`, does not create screenshot baselines and does not judge whether the spec passes. The user runs the
24
- spec afterwards (see the report). Healing a failing spec (below) is the exception: it reads the last report first and runs only the profiles that are logged in, and only when the report is not enough.
34
+ spec afterwards (see the report). Healing (below) is the exception: it reads the last report first, may edit the case
35
+ and the spec, and runs only the profiles that are logged in, and only when the report is not enough.
25
36
 
26
37
  ## Before you start
27
38
  Read the workspace `AGENTS.md`: its §2 holds the project rules, and it points at the framework baseline. If
28
39
  `.claude/skills/generating-specs.md` exists in the workspace, read it and apply it after this skill. Then apply
29
- `AGENTS.md` §2, which wins over both (the baseline, "Precedence"). F1–F6 always win. This is not a stop condition:
30
- continue with the procedure below.
40
+ `AGENTS.md` §2, which wins over both (the baseline, "Precedence"). F1–F6 and R2 always win. This is not a stop
41
+ condition: continue with the procedure below.
31
42
 
32
43
  ## Inputs
33
44
  - The case id and the environment to explore in (`staging` or `production`). There is no default. If the
34
45
  environment is missing, stop before exploring and report `RESULT: needs-input` ("pass the environment").
35
46
  - Optionally `--force`: regenerate the spec from scratch and overwrite the existing one. See "An existing spec".
47
+ - Optionally `--hints <file>` (or free text after the arguments): what the person knows, or a change they want. Read it
48
+ before validating the case. It guides this run; after the work, a reusable instruction is written into `## Rules`
49
+ by the gate in `../exploring-cases/references/case-rules.md`, and a one-off clue stays in the report.
50
+ - No profile selection: the profile scope is the case's (see above). A case id and an environment are all this command
51
+ takes besides `--force` and `--hints`.
36
52
 
37
53
  A case id is its path below `cases/` without `.md`: `hub-landing`, or `documents/landing-page` when the case is in a
38
54
  folder (one level only). The spec is at the same path below `tests/` (`tests/documents/landing-page.spec.ts`); both import
@@ -68,14 +84,21 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
68
84
  2. **Validate the case.** Collect everything that is ambiguous, every check without an expected value and every
69
85
  missing per-profile value, including the `TODO` items already in the case. Resolve them in the order of
70
86
  `open-questions.md`; do not stop here:
87
+ - **Apply the rule pass (`R1`–`R4`).** Read `## Rules` before the steps. A rule that states an expectation, a
88
+ literal, a target or a real step belongs in that section: move it there (or ask). A rule that conflicts with the
89
+ case or the page is a `[blocking]` question, never an override. A rule that would write, send email or do a
90
+ person's step is never followed. Details: `case-rules.md`.
71
91
  - **Steps that write or need a choice (`[input]`, `[writes]`) need a recipe.** Each must have at least one
72
92
  `- Recipe (phase): ...` line under it in the case (written by `/explore-case`). An old case may still hold the
73
93
  same recipe in `## Inputs`: use that line until it is moved under the step. If one has none, the case was
74
94
  never explored with a person: stop before opening a browser, do not write the spec, and report `RESULT:
75
95
  needs-input` with exactly this: "step <n> has no recorded input; run `/explore-case <id> <env> --attended`
76
96
  first". Nothing else is written.
77
- - Facts the page can answer and that the case does not hold yet are read in step 4 and written into the case as
78
- `(observed, 🔍 to be confirmed by QA)`. A spec is generated from observed values.
97
+ - Facts the page can answer and that the case does not hold yet are read in step 4 and written into the case. Mark
98
+ them by the gate in `open-questions.md` ("Which sentences carry a marker"): a locator, a flow or a request is
99
+ `(seen on <profile>)`; a literal the spec asserts gets
100
+ `(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)`, and that is the only thing a person
101
+ has to settle. A spec is generated from observed values.
79
102
  - Safe defaults are applied: the suggestion is the sentence above the TODO block, and the block stays in the case
80
103
  as `❓ TODO [non-blocking]` with an empty `✍️ Answer:`. A sentence with no TODO block under it is confirmed text.
81
104
  - A non-empty `Answer:` is the user's answer: it wins over the sentence above it. Fold it into the case.
@@ -94,40 +117,52 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
94
117
  - Do not invent expected values. If the case's `## Values` and the spec disagree, the case wins: fix the spec and
95
118
  mention the change in the report.
96
119
  3. **Check sessions** if `login: required`: run `pnpm status --env <env>`. If a profile needs login, tell the
97
- user to run `pnpm signin <profile-id>` and continue with the profiles that are ready. If `pnpm status`
98
- cannot run (a sandbox error such as `listen EPERM` or a Chromium start failure), judge by the page instead: a page
99
- that lands on the login page means the profile is not logged in.
100
- 4. **Replay the case on the real page** with the `playwright-test` MCP. If its tools are not registered in this
101
- session (no `browser_*` / `generator_*` tool) or it reports that it failed to start, do not fall back to ad-hoc
102
- Playwright scripts: stop, tell the user which MCP server is missing and to run `pnpm asrun doctor`, write no spec,
103
- and end with `RESULT: needs-input`.
120
+ user to run `pnpm signin <profile-id>` and continue with the profiles that are ready. A profile the case holds
121
+ information for is never dropped because it is not logged in: write its part from the case and mark it
122
+ `not verified on the page` in the report. If `pnpm status` cannot run (a sandbox error such as `listen EPERM` or a
123
+ Chromium start failure), judge by the page instead: a page that lands on the login page means the profile is not
124
+ logged in.
125
+ 4. **Replay the case on the real page** with the `playwright-test` MCP. Read `../exploring-cases/references/tools.md`
126
+ first: that MCP lists tools it cannot run, and a call that answers `Tool "<name>" not found` means exactly that (the
127
+ server is up; use the substitute). If its tools are not registered in this session at all (no `browser_*` /
128
+ `generator_*` tool) or it reports that it failed to start, do not fall back to ad-hoc Playwright scripts: stop, tell
129
+ the user which MCP server is missing and to run `pnpm asrun doctor`, write no spec, and end with
130
+ `RESULT: needs-input`.
104
131
  Call `generator_setup_page` with
105
- `project=<profile-id>` (for example `example-staging`) and `seedFile=tests/seed.spec.ts`, then perform the
132
+ `project=<profile-id>` (for example `example-staging`), `seedFile=tests/seed.spec.ts` and `plan=""`, then perform the
106
133
  steps in the environment you were given (never ask for it; there is no default) to learn the locators.
107
- Do the read-only steps as written. **Never perform a step that writes or needs a choice**: the agent does not write
108
- data, in staging or in production, and it does not hand off to a person here or wait for one. Write those steps
134
+ Do the read-only steps as written, and follow the `## Rules` that say how to prepare, wait or which control to
135
+ avoid. **Never perform a step that writes or needs a choice**: the agent does not write
136
+ data, in staging or in production, and it does not hand off to a person here or wait for one. A rule never
137
+ authorizes one of those steps. Write those steps
109
138
  of the spec from the `- Recipe:` lines under the step (positions, `[asrun]` names, control labels and roles that
110
139
  `/explore-case` recorded). Pages that only exist after such a step cannot be seen from here: write their locators
111
140
  from what the recipe and the exploration notes say, mark them in the report as "not verified on the page", and
112
- leave the first run of the spec (or `/heal-spec`) to confirm them. Do this for at least one profile
113
- per domain (example.com and exampleshop.com differ in markup). Profiles that are not logged in are skipped and
114
- named in the report. Use this exploration to answer the open points of step 2, and look at every profile that has
115
- an entry in `## Values` to read its value.
141
+ leave the first run of the spec (or `/heal-spec`) to confirm them. Do this for at least one logged-in profile
142
+ per domain (example.com and exampleshop.com differ in markup). A profile the case holds information for that is
143
+ not logged in is skipped and named in the report; its part is written from the case and marked `not verified` in
144
+ the assertion table. Use this exploration to answer the open points of step 2, and read each profile's value from
145
+ the case's `## Values`: open that profile's page only when a value or a behavior still has to be resolved for it.
116
146
  Keep every browser call short (one action, about 60 s at most, a snapshot between steps, `{timeout: 5000}` when
117
147
  something may not appear, no third retry): see "Keep every browser call short" in `../exploring-cases/SKILL.md`.
118
- **Ready before you read, here too** (`readiness.md`): `browser_network_clear`, act, wait for the key requests
119
- (`browser_network_requests`) and the content signal, then snapshot. The request URL patterns for `actAndWait` and the
120
- `Ready when` signals of the case are taken from what you see here.
148
+ **Ready before you read, here too** (`readiness.md`): take a marker on `browser_network_requests`
149
+ (`static: false`), act, wait for the key requests above that mark and the content signal, then snapshot. The request
150
+ URL patterns for `actAndWait` and the `Ready when` signals of the case are taken from what you see here.
121
151
  **Check every target on the page:** for each target card, find its `Identified by` locator, confirm that it matches
122
- exactly one element and that this element is what `What` says (highlight it and read the screenshot). A locator that
123
- matches several elements or the wrong one is fixed or becomes a question, never `.first()`.
152
+ exactly one element and that this element is what `What` says (outline it with `locator.highlight()` and look at the
153
+ screenshot, see `tools.md`). A locator that matches several elements or the wrong one is fixed or becomes a
154
+ question, never `.first()`.
124
155
  Prefer `getByTestId` (the project uses `data-tn`), then role/label locators. No CSS chains or XPath.
125
- Update the case file with what you learned: fold observations into the right section marked
126
- `(observed, 🔍 to be confirmed by QA)`, remove the TODO blocks they answer (rewrite the sentence above the block),
127
- and narrow the ones that remain. Keep the shape of `open-questions.md` for every block. Edit the case
156
+ Update the case file with what you learned: fold observations into the right section, marked `(seen on <profile>)`
157
+ or, for a literal the spec asserts, `(observed on <profile>, 🔍 to be confirmed: <what to decide>)`; remove the
158
+ TODO blocks they answer (rewrite the sentence above the block), and narrow the ones that remain. Keep the shape of
159
+ `open-questions.md` for every block, and never put a marker inside one. Edit the case
128
160
  with targeted edits; never touch its `## Source` table.
129
- 5. **Complete the case's `## Values`**: for every profile the spec runs for, add the entry with the literals the spec
130
- asserts (the same keys as `default`, only what differs). Do not move values into `profiles.yaml`; it holds connection
161
+ 5. **Complete the case's `## Values`**: for every profile the case holds information for, add the entry with the
162
+ literals the spec asserts (the same keys as `default`, only what differs); put a literal that holds for every profile
163
+ in `default`. Do not add an entry for a profile the case says nothing about, and never invent a value to fill one: a
164
+ profile without an entry runs on `default` alone, and a literal the case does not hold is a question in the case.
165
+ Do not move values into `profiles.yaml`; it holds connection
131
166
  settings only. Mention every value you added or corrected in the report.
132
167
  6. **Write `tests/<id>.spec.ts`** (inside the group folder if the id has one, creating it if needed) following the conventions. One `test()` per outcome, and every
133
168
  case step wrapped in `test.step()` with the case's step text as the title, and the top-level
@@ -136,7 +171,10 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
136
171
  (`@asrunjs/asrun/values`), which merges `default` with the running profile's entry.
137
172
  Every action that loads something goes through `actAndWait` (`@asrunjs/asrun/ready`) with the key requests and the ready
138
173
  signal; every download goes through `verifyDownload` (`@asrunjs/asrun/downloads`); every check on a target has
139
- `// target: T<n>` on the line before it; every absence check has a `// ready: <signal>` before it. Never
174
+ `// target: T<n>` on the line before it; every absence check has a `// ready: <signal>` before it; code that exists
175
+ only because of a `## Rules` bullet carries `// rule: <short quote>` before it; an assertion whose literal comes
176
+ from a `🔍 to be confirmed` line carries `// unconfirmed: <that case sentence>` before it, so
177
+ `pnpm markers:confirm` can settle it from a passing run. Never
140
178
  `waitForTimeout`, `networkidle` or `.first()` to pick an image. A confirmation or modal that may or may not appear is
141
179
  never decided by one `isVisible()` look (its `timeout` option is ignored): use `clickOptionalUntilHidden`
142
180
  (`@asrunjs/asrun/ready`) or a waiting `expect(...).toBeVisible({timeout})`; see `readiness.md`, "Optional confirmations".
@@ -150,13 +188,18 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
150
188
  Do NOT run `pnpm asrun`, do not use
151
189
  `--update-snapshots` and do not create baselines: the first run of the spec does that, and a person reviews them.
152
190
  8. **Report** in the user's language: spec path, **an assertion table** (step, what is asserted, target card, ready signal, and
153
- `observed on the page` or `not verified`; no row may be missing), profiles explored, profiles skipped (and why), whether the case file was
154
- translated, what was added to the case file as observed, checks generated from observations (QA must confirm
155
- them), visual checks that need a baseline on the first run, and the remaining TODO blocks (`[blocking]` ones with
156
- the sentence that deleting the block would confirm). State that the spec was not run and give the command to run it:
191
+ `observed on the page` or `not verified`; no row may be missing), profiles explored, profiles skipped (and why),
192
+ the profiles of `profiles.yaml` the case holds no information for (they run on `default` alone), whether the case file was
193
+ translated, what was added to the case file as `(seen on <profile>)` and which lines still need a person
194
+ (`🔍 to be confirmed`, with the reason), checks generated from observations, visual checks that need a baseline on
195
+ the first run, and the remaining TODO blocks (`[blocking]` ones with
196
+ the sentence that deleting the block would confirm). Add the rule table of `case-rules.md`: every `## Rules`
197
+ bullet, where it was applied or why it was not, and which rules were added from `--hints`. State that the spec was
198
+ not run and give the command to run it:
157
199
  `pnpm asrun --env <env> --profile <ids> --grep <id>` (with `--update-snapshots` once if the spec has visual
158
200
  checks, then run `pnpm baselines:review <id> --new` and open every new image: it must show what its target card says). The last line is `RESULT: ready` (no `[blocking]` question left, spec
159
- written and the checks above pass) or `RESULT: needs-input`, as defined in `open-questions.md`.
201
+ written and the checks above pass) or `RESULT: needs-input`, as defined in `open-questions.md`: plain text on the
202
+ last line, never inside a code fence or in emphasis.
160
203
 
161
204
  ## Cases that write data
162
205
  Only when the case front matter lists environments under `writes` (for example `writes: [staging]`). Never add
@@ -166,34 +209,54 @@ spec does, when a person runs it in an environment that `writes` lists. Elsewher
166
209
  notice. Never send email in production; in other environments only if `profile.email` is true, otherwise skip that step and say so
167
210
  in the report.
168
211
 
169
- ## Healing a failing spec
212
+ ## Healing and refining a spec
170
213
  Inputs: the case id and the environment (both required, there is no default: without an environment stop and say so),
171
214
  optionally `--profiles a,b`, `--report <run-id>` and `--hints <file>` (or free text after the arguments). Healing is
172
215
  the one case where this skill runs specs, and only in the environment given.
173
216
 
217
+ Healing has two jobs, and the same command does both: **repair** a spec that fails, and **refine** a case and its spec
218
+ when the user asks for a change. A passing run is never a reason to refuse a request, and "nothing failed" is never a
219
+ reason to do nothing when the user asked for something.
220
+
174
221
  Heal from evidence, not from a habit of rerunning. A report that already exists says what failed.
175
222
 
176
- 1. **Read the user's clues first** (the `--hints` file, or the free text). They say what to look at and may hold
177
- evidence the report lacks. They guide the diagnosis and nothing else: they never lower the bar for a product
178
- finding and never allow an expectation to be changed just to make a test pass. Quote the clue you used, or say that
179
- it did not hold up, in the report.
180
- 2. **Read the existing report**: run `pnpm heal:evidence --case <case-id> --env <env>` (add `--profile a,b` when `--profiles`
223
+ 1. **Read the user's request first** (the `--hints` file, or the free text). It says what to repair or improve and may
224
+ hold evidence the report lacks. Read the case's `## Rules` before the steps and apply the rule pass (`R1`–`R4`,
225
+ `../exploring-cases/references/case-rules.md`). Decide which kind of work it is:
226
+ - a failure to repair (the last run failed, or the user names one);
227
+ - an improvement, even when the last run passed: a check that is too loose, a step, expectation, value or target
228
+ that no longer matches the page, wording a person cannot follow;
229
+ - both.
230
+ Quote the request you used, or say that it did not hold up, in the report. It guides the work and nothing else: it
231
+ never lowers the bar for a product finding and never allows an expectation to be changed just to make a test pass.
232
+ - **No report and no request:** do not invent work. Say that nothing asks for a change, list what
233
+ `pnpm spec:lint <case-id>` and the last report call weak (as suggestions, not edits), and end with `RESULT: ready`.
234
+ - **A passing report and no request:** the same. Do not start an improvement of your own.
235
+ 2. **Read the existing report** if there is one: run `pnpm heal:evidence --case <case-id> --env <env>` (add `--profile a,b` when `--profiles`
181
236
  was given). It reads `reports/*/summary.json` and prints, per profile, the status, the failed step, the error, the call
182
237
  log, the screenshots and videos, and ends with `EVIDENCE: fresh | stale | no-failure | none`. With `--report <run-id>`,
183
238
  read `reports/<run-id>/summary.json` instead. Look at the screenshot when the error alone does not explain it. A profile
184
- that failed only because it was not logged in is no evidence about the spec.
239
+ that failed only because it was not logged in is no evidence about the spec. `EVIDENCE: none` or `no-failure` with an
240
+ improvement request is a starting point, not a stop.
185
241
  3. **Decide whether to rerun the case**. Do not rerun when the report is `fresh`, names the failing step and the error,
186
242
  and that is enough to tell whether the product changed or the test is wrong. Rerun when:
187
243
  - there is no report (`none`), or it is `stale` (the spec, the case or `profiles.yaml` changed after it);
188
244
  - the report does not explain the failure (a bare timeout with no locator, a failure before any step, a truncated log);
189
- - a clue from the user has to be checked against the real page;
245
+ - a clue or a requested improvement has to be checked against the real page, and no recent report shows it;
190
246
  - a profile that must be healed has no result in the report (for example it was not logged in then).
191
247
  Rerun with one plain command, as few profiles as possible, and say why in the report:
192
248
  `pnpm asrun --env <env> --logged-in --profile <ids> --grep <case-id>` (optionally piped to `tail`; do not chain it
193
249
  with other commands, or it runs inside Claude's sandbox and fails with `listen EPERM`). Always add `--logged-in`: it
194
250
  leaves out the profiles that are not logged in instead of failing them. A profile that is not logged in is never healed:
195
251
  name it in the report with `pnpm signin <profile-id>`.
196
- 4. **Audit the assertion before you blame anyone.** Read the `assertion-targets.md` and `readiness.md` sections on healing.
252
+ 4. **Look at the pages when the change depends on them.** Reading a page is not a run and is always allowed, also for an
253
+ improvement request with no failure behind it. Use the `playwright-test` MCP exactly as `/gen-spec` step 4 describes
254
+ (`generator_setup_page` with `project=<profile-id>`, one action per call, ready before you read). Do the read-only
255
+ steps only: **never perform a step that writes or needs a choice**, in any environment. What you see goes into the
256
+ case by the gate in `open-questions.md` ("Which sentences carry a marker"): `(seen on <profile>)` for a locator, a
257
+ flow or a request, and `(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)` only for a
258
+ literal the spec asserts, so the person settles that in the review gate below.
259
+ 5. **Audit the assertion before you blame anyone.** Read the `assertion-targets.md` and `readiness.md` sections on healing.
197
260
  - Is the locator still what the target card says (`What`, `Identified by`)? Open the failure screenshot and the
198
261
  baseline (`pnpm baselines:review <case-id>`) and look at each: a baseline that shows something other than the card
199
262
  (a content image instead of a logo) is a wrong test, and passing runs on it prove nothing. Delete it and report it; a
@@ -204,16 +267,46 @@ Heal from evidence, not from a habit of rerunning. A report that already exists
204
267
  - A download that fails says which stage failed (start, finish, exists, valid): fix the stage, do not loosen the check.
205
268
  - Doubt about which element is meant: write the question into the case (a `[blocking]` TODO naming the exact image,
206
269
  text, place or UI) instead of choosing; do not rewrite the card quietly.
207
- 5. **Decide whether the PRODUCT changed or the TEST is wrong.**
270
+ 6. **Decide what the change belongs to, and change the case first.**
208
271
  - Locator/timing problem: fix the spec.
209
272
  - A real product difference (wrong logo, wrong name): do NOT change the expectation to make it
210
273
  pass. Report it as a finding with the trace/screenshot path.
211
- 6. **After editing the spec, run it once to confirm** (same command as in step 3, for the profiles you healed). That rerun
212
- is the only one that is always allowed. Run `pnpm typecheck` too.
213
- 7. **Report** in the user's language: which report was read (or that there was none) and whether it was fresh or stale,
214
- whether the case was run again and why (or why not), what was changed, what is a product finding (with paths),
215
- which assertions were wrong or raced the page, which baselines are wrong and were deleted, what the user's clues contributed, which profiles were left out because they are not logged in, and the result of the
216
- confirming run.
274
+ - A request the case can settle: the case is the source of truth, so write the change into `cases/<case-id>.md`
275
+ first, then into the spec. That covers a step, an expectation, a value in `## Values`, a target card, a ready
276
+ signal and a recipe. The spec follows the case (its step titles copy the case's step text). When the case changed
277
+ more than a sentence (a new or removed step, different expected values, another target), regenerate the spec from
278
+ the case as "## Procedure" steps 5–6 describes instead of patching it; otherwise edit it in place. Say in the
279
+ report which of the two you did.
280
+ - A reusable instruction that is method, not an expectation: append it to `## Rules` under the four-question gate
281
+ of `case-rules.md`, in general terms, after checking that it is not already there. An agent-inferred rule
282
+ carries a marker only when it changes what is asserted; a method rule is `(seen on <profile>)` and a rule the
283
+ person stated carries nothing. A one-off request (this failure, this
284
+ account, this run) stays in the report. Never put a write, an email, a person's step, an expectation or a literal
285
+ into `## Rules`.
286
+ 7. **Stop at the review gate.** After editing the case, read it back. If **this run** left a `🚫 TODO [blocking]` or
287
+ `❓ TODO [non-blocking]` block, or a `🔍 to be confirmed` or `👁️ not observed` marker, do not edit or
288
+ regenerate the spec any further: a case that still asks something is not a source for a spec. Report exactly what
289
+ to review, that answering or deleting those blocks comes first, and that the next command is
290
+ `/explore-case <case-id> <env>` (with `--attended` when a step writes data), or
291
+ `pnpm markers:confirm --case <case-id> --env <env>` when a passing run already checks those lines. End with
292
+ `RESULT: needs-input`. Blocks and markers that were already in the case before this run and that it did not touch do
293
+ not stop it. A plain `(seen on <profile>)` note never stops it. One exception:
294
+ a `🔍 to be confirmed` marker inside `## Rules` does not stop the spec work unless the rule covers a literal the
295
+ spec asserts (a `🚫`/`❓` TODO block under a rule still does).
296
+ 8. **After editing the spec, run it once to confirm** (same command as in step 3, for the profiles you healed). That rerun
297
+ is the only one that is always allowed. Run `pnpm typecheck`, `pnpm spec:lint <case-id>` and `pnpm case:check`, and
298
+ fix every failure of the checks you touched. An improvement may make the run fail on purpose: a tightened check that
299
+ now fails is evidence about the product, so report it as a finding instead of loosening it again.
300
+ 9. **Report** in the user's language: which report was read (or that there was none) and whether it was fresh or stale,
301
+ whether the case was run again and why (or why not), what the request asked for and what came of it, what was
302
+ changed in the case and in the spec (and whether the spec was regenerated), what is a product finding (with paths),
303
+ which assertions were wrong or raced the page, which baselines are wrong and were deleted, what the user's request
304
+ contributed, the rule table of `case-rules.md` (every `## Rules` bullet, where it was applied or why it was not,
305
+ and which rules this run added or changed), which profiles were left out because they are not logged in, and the
306
+ result of the confirming run. The last line is `RESULT: ready` (nothing left for a person, the spec changed and its checks pass)
307
+ or `RESULT: needs-input` (the case waits for a review, as in step 7, or a person has to decide something). A run
308
+ that could not write files at all (plan mode, a read-only sandbox, a refused permission) is `needs-input` too:
309
+ never end with a plan and a question instead.
217
310
 
218
311
  A case that writes data in `production` needs a person's confirmation at run time, so the rerun is refused there:
219
312
  use the report, or heal in staging, and never add `--allow-writes`.
@@ -108,6 +108,10 @@ Binding rules are in `../exploring-cases/references/readiness.md` and `.../asser
108
108
  - A check on a logo, image, file or screenshot has `// target: T<n>` on the line before it (a card in the case's
109
109
  `## Targets`). The element is found by the card's `Identified by` (role and name, test id, alt/title), never `.first()` or
110
110
  a bare `img`/`svg`. Semantic assertion first (name/alt/src, where it is), pixels second, on that element only.
111
+ - An assertion whose literal comes from a `🔍 to be confirmed` line in the case carries
112
+ `// unconfirmed: <that case sentence>` before it (one line, the sentence without its marker). It is what
113
+ `pnpm markers:confirm --case <id> --env <env>` reads after a passing run to settle the marker, so the line must be
114
+ the case's own wording, not a paraphrase.
111
115
  - A check for absence ("no", "missing", "empty", "hidden") has `// ready: <signal>` in front of it and a positive wait
112
116
  before it. The page must have loaded before "nothing" means anything.
113
117
  - A download is `await verifyDownload(page, () => option.click(), {kind: 'pdf'})`. It checks, and fails the step on
@@ -10,14 +10,16 @@ Read the workspace `AGENTS.md` (hard rules and project rules) and `cases/_templa
10
10
  - `references/writes-and-email.md`: how to classify steps that write data or send email, and the data rules.
11
11
  - `references/gaps.md`: what to ask, and the default to suggest for each question.
12
12
  - `references/splitting.md`: how many cases to make.
13
+ - `../exploring-cases/references/case-rules.md`: what belongs in `## Rules` (a method constraint), and what stays in
14
+ Steps, Expected, Values or Targets.
13
15
 
14
16
  This skill writes `cases/*.md` only. It never writes specs, `profiles.yaml`, or anything under `tests/`.
15
17
 
16
18
  ## Before you start
17
19
  Read the workspace `AGENTS.md`: its §2 holds the project rules, and it points at the framework baseline. If
18
20
  `.claude/skills/making-cases.md` exists in the workspace, read it and apply it after this skill. Then apply `AGENTS.md`
19
- §2, which wins over both (the baseline, "Precedence"). F1–F6 always win. This is not a stop condition: continue with
20
- the procedure below.
21
+ §2, which wins over both (the baseline, "Precedence"). F1–F6 and R2 always win. This is not a stop condition:
22
+ continue with the procedure below.
21
23
 
22
24
  ## Inputs
23
25
  - Raw notes: pasted text or a file path. Any language, any quality.
@@ -34,10 +36,11 @@ the procedure below.
34
36
  creates, edits, publishes, shares, deletes or sends is not read-only, even if the notes do not say so.
35
37
  4. **Find the gaps** (`references/gaps.md`): missing expected results, unknown values a spec must assert, unclear starting
36
38
  state, steps that need data that may not exist, ordering that is not linear. Never invent an expected value.
37
- Unknown values are written as the suggested default sentence with a `❓ TODO` block under it (see `gaps.md`), or as an
38
- `Observed, 🔍 to be confirmed by QA` bullet. Every marker starts with its emoji. A literal nobody knows yet goes into
39
- the case's `## Values` as `TODO` (the spec then fails with a clear message until it is filled), never into
40
- `profiles.yaml`.
39
+ Unknown values are written as the suggested default sentence with a `❓ TODO` block under it (see `gaps.md`). A fact
40
+ from the notes that nobody questions is a plain note `(seen on <profile>)`; only a literal the spec will assert and
41
+ that would pass while wrong gets `(observed on <profile>, 🔍 to be confirmed: <what to decide>)`. A marker never
42
+ goes inside a TODO block. A literal nobody knows yet goes into the case's `## Values` as `TODO` (the spec then fails
43
+ with a clear message until it is filled), never into `profiles.yaml`.
41
44
  5. **Ask before writing.** Show the user a short plan and the open questions in one message: the proposed split
42
45
  into cases, the classification (which steps write, which send email), and the gaps with your suggested default
43
46
  for each. Wait for answers. Do not write case files before that.
@@ -48,10 +51,14 @@ the procedure below.
48
51
  ask in step 5. The group does not repeat in the name: `documents/landing-page`, not `documents/documents-landing-page`.
49
52
  - Front matter: `id`, `title`, `login`, `suites`, `writes`, `writes-note` (required when `writes` is not `none`),
50
53
  `envs` when a case must not run everywhere. When unsure: `suites: [regression]`, no `writes` (read-only), or `writes: [staging]` when a step writes.
51
- - Fill the sections that apply (Goal, Steps, Expected, Values, Visual checks, Source, Notes).
54
+ - Fill the sections that apply (Goal, Steps, Expected, Rules, Values, Visual checks, Source, Notes).
52
55
  Remove template placeholders you do not use. Use `## Values` only for the literals a spec asserts; `default`
53
56
  applies to every profile, and a profile id entry holds only what differs. Do not add `- Recipe:` lines: a person
54
57
  records them during `/explore-case --attended`.
58
+ - Put a note in `## Rules` only when it states a **method** constraint (dismiss a banner before step 1, never use
59
+ an entry that needs 2FA). A constraint that is part of the tested flow is a step; one that creates, changes or
60
+ deletes data is a tagged step with a later recipe. Never invent a rule, and never put an expectation or a literal
61
+ there: those go to `## Expected` or `## Values`. The note that became a rule is listed in its `## Source` row.
55
62
  - Case ids are lowercase kebab-case and name the thing, not the ticket: `landing-page`.
56
63
  - Existing case files: do not overwrite. If a new case overlaps one (for example another case about the same page), say so,
57
64
  propose "keep", "merge" or "replace", and wait for the user.
@@ -17,7 +17,7 @@ single lines. Never ask a question whose answer is already in the notes, `AGENTS
17
17
  | Environments | Must this case run in production, or only staging? | All environments; production writes need the usual confirmation |
18
18
  | Suites | Smoke, regression, or both? | A read-only landing check: `[smoke, regression]`; a flow that writes: `[regression]` |
19
19
  | Profiles | Do all profiles see the same UI? | Yes; the exact literals that differ go in `## Values`, never in the steps |
20
- | Unclear words | What does "collaborator button" / "..." button open? | Describe only what is observed; mark `(observed, 🔍 to be confirmed by QA)` |
20
+ | Unclear words | What does "collaborator button" / "..." button open? | Describe the safest reading and hang a TODO under it; a detail nobody questions is a plain note `(seen on <profile>)` |
21
21
  | Unused lines | A statement that fits no case | Ask where it belongs; never drop it silently |
22
22
 
23
23
  ## Marking unknowns in a case
@@ -26,11 +26,14 @@ single lines. Never ask a question whose answer is already in the notes, `AGENTS
26
26
 
27
27
  ```
28
28
  - The preview header shows the product logo, visible and not empty.
29
- - ❓ TODO [non-blocking]: Which logo must it be? (👁️ not observed)
29
+ - ❓ TODO [non-blocking]: Which logo must it be? (the notes say only "the logo")
30
30
  - ✍️ Answer:
31
31
  ```
32
32
 
33
33
  The user accepts the default by deleting the block, or types an answer. Never write a bare `TODO: ...` line, a
34
- question as the only text, or `Suggested:`.
35
- - Seen on a page during exploration but not stated in the notes: `- <observation> (observed, 🔍 to be confirmed by QA)`.
34
+ question as the only text, or `Suggested:`. Never put a marker word inside the block: one claim carries one signal.
35
+ - Seen on a page during exploration but not stated in the notes: `- <observation> (seen on <profile>)`. A literal the
36
+ spec will assert is different: `(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)`, and only
37
+ when a wrong reading would still pass. The gate is "Which sentences carry a marker" in
38
+ `exploring-cases/references/open-questions.md`.
36
39
  - Never turn an observation into a hard requirement on your own.
@@ -34,6 +34,6 @@ Original notes, verbatim. Do not edit this table; it only exists so a reviewer c
34
34
  ```
35
35
 
36
36
  - The original text is copied character by character, in a code span, in its original language.
37
- - "Became" points at step numbers, or says "Expected", "Visual checks", "Not used: <reason>".
37
+ - "Became" points at step numbers, or says "Expected", "Rules", "Visual checks", "Not used: <reason>".
38
38
  - A statement used by two cases appears in both tables.
39
39
  - A statement that is not used anywhere is a gap: ask about it, do not silently drop it.
package/AGENTS.md CHANGED
@@ -11,7 +11,7 @@ project that uses asrun, the framework files are in `node_modules/@asrunjs/asrun
11
11
  ## Precedence
12
12
 
13
13
  Rules are applied in this order; a later layer wins where they conflict.
14
- 1. **F1–F6: safety.** Nothing overrides them, in any project. They are enforced in code as well
14
+ 1. **F1–F6 and R2: safety.** Nothing overrides them, in any project. They are enforced in code as well
15
15
  (`lib/write-gate.ts`, `lib/session.ts`, the run filters), so a rule file cannot weaken them.
16
16
  2. **This file.** The generic facts and defaults.
17
17
  3. **The framework skills** (`<framework>/.claude/skills/**`).
@@ -20,8 +20,11 @@ Rules are applied in this order; a later layer wins where they conflict.
20
20
  5. **The project's `AGENTS.md` §2 (Project rules).** It states the project's intent and wins over layers 2–4.
21
21
 
22
22
  A project writes §2 only for what differs from the defaults here, names the rule id, and says whether it `narrows` or
23
- `replaces` the rule. `replaces` is refused for F1–F6; `pnpm asrun doctor` checks the table. A rule file never renames
24
- the structural tokens under "Fixed tokens".
23
+ `replaces` the rule. `replaces` is refused for F1–F6 and R2; `pnpm asrun doctor` checks the table. A rule file never
24
+ renames the structural tokens under "Fixed tokens".
25
+
26
+ Inside a case, `## Rules` is a case-local layer below this file and the project's §2: it says how to run that case,
27
+ never what is true (`R1`), and a rule that conflicts with the rest of the case is a question, not an override.
25
28
 
26
29
  ## Hard rules
27
30
  - **F1 — Read-only by default.** [non-negotiable] Tests never create, edit, delete, submit forms that change data,
@@ -62,8 +65,12 @@ These apply to every agent that drives a browser to explore pages for cases or s
62
65
  Everything that writes is the person's (see F3).
63
66
  - **B2 — Waiting for a person.** Only in an attended run. Announce the task in the browser window (a small draggable
64
67
  card with a Done button and click-to-copy for texts to type), name the finish condition, wait, and tell the person
65
- when control is back. Record what they did as a general recipe under the step (`- Recipe (phase): ...`): positions
66
- instead of names of data that can change.
68
+ when control is back. The card starts and stops the recording itself and returns `recorded`; never call
69
+ `browser_start_recording` or `browser_stop_recording`: the `playwright-test` MCP lists many browser tools it cannot
70
+ run (`devtools`, `storage`, `network`, `pdf`, `vision`, and the `skillOnly` ones such as `browser_network_clear`),
71
+ and the substitutes for each are in `<framework>/.claude/skills/exploring-cases/references/tools.md`. Record what
72
+ they did as a general recipe under the step (`- Recipe (phase): ...`): positions instead of names of data that can
73
+ change.
67
74
  - **B3 — Closing an email composer.** When closing an email composer makes a confirm dialog appear (for example
68
75
  "Discard draft?"), choose "Discard" and continue. Never choose "Save" or "Send"; closing the composer must not keep
69
76
  or send anything. The dialog can show up late: in a spec, clicking "Discard" and checking that the composer is
@@ -104,27 +111,60 @@ Details: `<framework>/.claude/skills/exploring-cases/references/assertion-target
104
111
  - **A6 — Observe first, delete last.** Everything that needs the data (preview, share menus, composers, downloads) is
105
112
  observed before the person deletes it.
106
113
 
114
+ ## Case rules (`R`, binding for exploring, generating, healing and making cases)
115
+ A case document may hold a `## Rules` section: case-local instructions a person writes for the agents. They say **how**
116
+ to run that case, never **what is true**. Details: `<framework>/.claude/skills/exploring-cases/references/case-rules.md`.
117
+ - **R1 — Rules describe the method, not the outcome.** `## Steps`, `## Expected`, `## Values` and `## Targets` stay the
118
+ only place for behaviour, expectations and literals. A rule that contradicts them is a `🚫 TODO [blocking]` question,
119
+ never a silent override. A sentence that says something must be true belongs in `## Expected` (with a target card when
120
+ it looks at an image, file, PDF, screenshot or repeated text).
121
+ - **R2 — Rules never authorize what F1–F6 forbid.** [non-negotiable] A rule cannot allow creating, changing or
122
+ deleting data, sending email, accepting terms, or doing a person's step. Those stay tagged steps (`[writes]`,
123
+ `[email]`, `[input]`) with their `- Recipe:` lines and the front-matter `writes`/`writes-note` the case needs. The
124
+ agent never performs them while exploring or generating, whatever a rule says.
125
+ - **R3 — Every AI step reads every rule and accounts for it.** `/explore-case`, `/gen-spec` and `/heal-spec` (and
126
+ `/make-case` when it edits an existing case) read the whole `## Rules` section before they start, apply it, and list
127
+ each rule in the final report with where it was applied or why it was not. Exploration re-checks each rule against
128
+ the page: a rule that no longer holds gets a TODO with evidence, never silent deletion or rewriting. The agent may fix
129
+ a typo and may add a question under a rule; it never changes what a rule says.
130
+ - **R4 — Only reusable method is written back.** When a person supplies instructions before or during a run (`--hints`,
131
+ free text, an answer in the case), the agent appends to `## Rules` only what a future run of the same case needs,
132
+ stated in general terms (no account, date, run id or one-off failure), and only when it is method rather than an
133
+ expectation. A rule the agent inferred from a page carries a marker only when the rule changes what is asserted
134
+ (`(observed on <profile>, 🔍 to be confirmed: <what a person must decide>)`); a method rule whose wrong version would
135
+ fail the next run is a plain note `(seen on <profile>)`, and a rule the person stated carries nothing. Run-specific
136
+ information stays in the report. A marker inside `## Rules` does not stop spec generation the way an open question
137
+ about an assertion does.
138
+
107
139
  ## Fixed tokens
108
140
  `lib/case-doc.ts`, the scripts and the web panel parse these literals. A project language never translates them, and
109
141
  a project rule never renames them.
110
- - Section headings: `## Goal`, `## Steps`, `## Expected`, `## Values`, `## Visual checks`, `## Targets`, `## Notes`,
111
- `## Source` (only when the case was made from notes).
142
+ - Section headings: `## Goal`, `## Rules`, `## Steps`, `## Expected`, `## Values`, `## Visual checks`, `## Targets`,
143
+ `## Notes`, `## Source` (only when the case was made from notes).
112
144
  - A step's recipe lines: `- Recipe (phase): ...` under the step it belongs to.
113
145
  - `### T<n>` target cards and the `[T<n>]` references to them.
114
146
  - Question blocks: `🚫 TODO [blocking]`, `❓ TODO [non-blocking]`, `✍️ Answer:` (with its emoji), and the markers
115
- `🔍 to be confirmed by QA` / `👁️ not observed`.
147
+ `🔍 to be confirmed` and `👁️ not observed`. A marker may carry its reason and a screenshot in the same
148
+ parentheses: `(observed on <profile>, 🔍 to be confirmed: <what a person must decide>, evidence: <path>)`.
149
+ `(seen on <profile>)` is not a token: it is an ordinary note.
116
150
  - Step tags `[input]`, `[writes]`, `[email]`, and the last line `RESULT: ready` / `RESULT: needs-input`.
151
+ The RESULT line stands on its own: nothing in front of it on that line (no label, in any language), nothing after
152
+ it but closing punctuation, and plain text: never inside a code fence or in emphasis.
117
153
  - Front-matter keys: `id`, `title`, `login`, `suites`, `writes`, `writes-note`, `envs`.
118
154
 
119
155
  ## Where the details are
120
156
  This file holds the rules. The exact shape those rules apply to is documented once, and not repeated here:
121
- - `docs/agents/case-format.md`: the case document contract (front matter, steps, `## Values`, target cards, the
122
- spec's consumption of them, the write gate). Read it before editing a case document or a generated spec.
157
+ - `docs/agents/case-format.md`: the case document contract (front matter, steps, `## Rules`, `## Values`, target cards,
158
+ the spec's consumption of them, the write gate). Read it before editing a case document or a generated spec.
159
+ - `.claude/skills/exploring-cases/references/case-rules.md`: how a case's `## Rules` are read, applied and maintained.
160
+ - `.claude/skills/exploring-cases/references/tools.md`: which browser tools the `playwright-test` MCP can run, the
161
+ substitute for each one it only lists, and the arguments every call has to carry. Read it before the first browser
162
+ call of a run.
123
163
  - `docs/commands.md`: every command and flag.
124
164
  - `docs/cases.md`: the same case format for the people who write cases by hand.
125
165
  - `docs/agents/framework-dev.md`: the framework's own layout and how to change it.
126
166
  The project's own `AGENTS.md` §2 (project rules) and §3 (project facts) still win over the documentation where they
127
- differ, and `F1`–`F6` win over everything.
167
+ differ, and `F1`–`F6` and `R2` win over everything.
128
168
 
129
169
  ## Layout
130
170
 
@@ -143,14 +183,16 @@ The framework folder is read-only in a project: never edit it. Its own layout an
143
183
  Three steps, each learns one thing: the intent, the facts and inputs, the implementation.
144
184
  1. `making-cases` (or `/make-case <notes>`) turns raw notes into case documents. It writes `cases/*.md` only and
145
185
  never opens a browser. Check them with `pnpm case:check`.
146
- 2. `exploring-cases` (or `/explore-case <case-id> <env> [--attended]`) explores the real pages for one case. It
147
- edits that case file only: wording, observations, TODO blocks, and, with `--attended`, the `- Recipe:` lines of
148
- the steps that write or need a choice.
186
+ 2. `exploring-cases` (or `/explore-case <case-id> <env> [--attended] [--profiles a,b]`) explores the real pages for one
187
+ case. It edits that case file only: wording, observations, TODO blocks, and, with `--attended`, the `- Recipe:` lines
188
+ of the steps that write or need a choice. `--profiles` narrows the run to those profiles; runs add up, so the case
189
+ can be explored in passes.
149
190
  3. `generating-specs` (or `/gen-spec <case-id> <env>`) turns the case into a spec. It replays the case and its
150
191
  step recipes and `## Values`, writes the spec (values stay in the case document), but never runs the tests, never
151
- writes data and never creates baselines. A tagged step with no `- Recipe:` line (an old `## Inputs` line counts
152
- until it is moved) stops with `needs-input`: run `/explore-case` first. Running specs is a separate step
153
- (`pnpm asrun`, or the Run page).
192
+ writes data and never creates baselines. There is no profile selection here: it covers every profile the case names,
193
+ which is why a case explored in passes generates one spec for all of them. A tagged step with no `- Recipe:` line
194
+ (an old `## Inputs` line counts until it is moved) stops with `needs-input`: run `/explore-case` first. Running specs
195
+ is a separate step (`pnpm asrun`, or the Run page).
154
196
  Workspace maintenance is separate from the AI steps: `pnpm asrun upgrade` installs the newest framework, refreshes the
155
197
  generated files and applies the release's workspace migrations (it checks the target release for breaking changes
156
198
  before installing), `pnpm asrun init [--update] [--force]` writes or refreshes the workspace files, `pnpm asrun
@@ -161,9 +203,15 @@ Do not hand-write specs unless asked. Do not edit `lib/` to make a single spec p
161
203
  Every case step is wrapped in `test.step('<n>. <case step text>', ...)` so reports and traces show the
162
204
  case's own steps (see the skill's `references/conventions.md`).
163
205
  Every AI command needs an environment (`--env`/`<env>`); there is no default and no "no environment" mode.
164
- `/heal-spec <case-id> <env>` reads the existing report first (`pnpm heal:evidence --case <id> --env <env>`), reads the
165
- user's optional clues (`--hints <file>`), runs only the profiles that are logged in (`--logged-in`) and runs the case
166
- again only when the report is missing, stale or not enough.
206
+ A one-shot AI run is a build run: it writes the case or the spec, never a plan. AsRun starts OpenCode with
207
+ `--agent build`, because OpenCode keeps the agent (build or plan) on the session and a plan session would only propose
208
+ changes and ask a question nobody can answer. Whatever mode or sandbox a run finds itself in, it still ends with the
209
+ `RESULT:` line: a run that was not allowed to write files is `needs-input`, never a plan and a question.
210
+ `/heal-spec <case-id> <env>` repairs one failing spec or refines a case and its spec on request (a passing run is not
211
+ a reason to refuse): it reads the existing report first (`pnpm heal:evidence --case <id> --env <env>`), the user's
212
+ request (`--hints <file>`), runs only the profiles that are logged in (`--logged-in`) and runs the case again only
213
+ when the report is missing, stale or not enough. A change that leaves an open question or an unconfirmed observation
214
+ in the case stops before the spec: the person reviews the case and runs `/explore-case` again.
167
215
  Run `pnpm typecheck`, `pnpm spec:lint <case-id>` and `pnpm case:check` before reporting that a spec was generated. Run
168
216
  `pnpm asrun --env <env> --profile <id> --grep <case-id>` only when healing a spec or when asked to.
169
217
  Pick `--env staging` while developing. A case that does not list `production` under `writes` is skipped in