@asrunjs/asrun 0.8.0 → 0.9.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 (36) hide show
  1. package/.claude/commands/explore-case.md +6 -2
  2. package/.claude/commands/gen-spec.md +4 -1
  3. package/.claude/commands/heal-spec.md +18 -7
  4. package/.claude/skills/exploring-cases/SKILL.md +19 -8
  5. package/.claude/skills/exploring-cases/references/open-questions.md +2 -2
  6. package/.claude/skills/generating-specs/SKILL.md +85 -30
  7. package/AGENTS.md +17 -9
  8. package/README.md +4 -3
  9. package/dist/ui/server/actions.js +9 -4
  10. package/dist/ui/shared/ai-commands.d.ts +6 -3
  11. package/dist/ui/shared/ai-commands.js +11 -7
  12. package/dist/ui/shared/types.d.ts +1 -1
  13. package/docs/ai-workflow.md +38 -11
  14. package/docs/theme-guide.html +93 -5
  15. package/docs/troubleshooting.md +7 -3
  16. package/docs/web-ui.md +28 -6
  17. package/package.json +1 -1
  18. package/templates/.claude/commands/explore-case.md +7 -2
  19. package/templates/.claude/commands/gen-spec.md +4 -1
  20. package/templates/.claude/commands/heal-spec.md +22 -12
  21. package/ui/dist/assets/{dist-ChBHX4rh.js → dist-B7HeglDd.js} +1 -1
  22. package/ui/dist/assets/{dist-CJiwxr1r.js → dist-B9_GXR2G.js} +1 -1
  23. package/ui/dist/assets/{dist-N-o4QIaZ.js → dist-Bb36rNUw.js} +1 -1
  24. package/ui/dist/assets/{dist-BpkTvixI.js → dist-DevASegc.js} +1 -1
  25. package/ui/dist/assets/{dist-DoChb6QQ.js → dist-DfBpjgZT.js} +1 -1
  26. package/ui/dist/assets/{dist-BgPbx2GZ.js → dist-Dq4Vnw5N.js} +1 -1
  27. package/ui/dist/assets/{dist-CavmZIv7.js → dist-Dqc_tfzC.js} +1 -1
  28. package/ui/dist/assets/{dist-Do_MhHKF.js → dist-G9KNeh6u.js} +1 -1
  29. package/ui/dist/assets/{dist-D6jmnDr-.js → dist-GvRUDL-2.js} +1 -1
  30. package/ui/dist/assets/{dist-BC_P1Gnf.js → dist-WvvCZeeu.js} +1 -1
  31. package/ui/dist/assets/index-BJ5D2mFK.js +346 -0
  32. package/ui/dist/assets/index-BXY-eKBn.css +2 -0
  33. package/ui/dist/help/case-explore.png +0 -0
  34. package/ui/dist/index.html +2 -2
  35. package/ui/dist/assets/index-Cy2l91qV.css +0 -2
  36. package/ui/dist/assets/index-DGQ5logJ.js +0 -338
@@ -8,16 +8,25 @@ so the procedure lives in one place and a project can refine it (see
8
8
  | Command | What it does |
9
9
  |---|---|
10
10
  | `/make-case <notes-or-path>` | Turn raw notes into case documents. Optional step. |
11
- | `/explore-case <case-id> <env> [--attended]` | Open the real pages and write what they show into the case. |
11
+ | `/explore-case <case-id> <env> [--attended] [--profiles a,b]` | Open the real pages and write what they show into the case. |
12
12
  | `/gen-spec <case-id> <env> [--force]` | Write `tests/<case-id>.spec.ts` and fill the case's `## Values`. |
13
- | `/heal-spec <case-id> <env> [--profiles a,b] [--report <run-id>] [--hints <file>]` | Diagnose a failing spec from its last report and fix it. |
13
+ | `/heal-spec <case-id> <env> [--profiles a,b] [--report <run-id>] [--hints <file>]` | Repair a failing spec, or refine a case and its spec on request, from the last report and what you asked for. |
14
14
 
15
15
  The environment is **required** by every command except `/make-case`, which does not open pages. There is no default.
16
16
 
17
+ `--profiles` exists on `/explore-case` and `/heal-spec`, not on `/gen-spec`: a spec runs once per profile in the
18
+ environment, so it is generated from every profile the case holds information for, never from a chosen subset.
19
+
17
20
  Every command ends with `RESULT: ready` or `RESULT: needs-input`. `needs-input` means somebody has to answer a
18
21
  question in the case file (or, in the web UI, in the job's **Answer** sheet). A one-shot run cannot ask in the chat:
19
22
  it writes its questions into the case and stops.
20
23
 
24
+ A one-shot run is a **build** run: it writes the case or the spec, never a plan. AsRun starts OpenCode with
25
+ `--agent build`, because OpenCode keeps the agent (build or plan) on the session and a plan session would only propose
26
+ changes and ask a question nobody can answer. Whatever mode a run finds itself in, it still ends with the `RESULT:`
27
+ line: a run that was not allowed to write files (plan mode, a read-only sandbox) is `needs-input`, not a plan and a
28
+ question.
29
+
21
30
  In the web UI you never type a command: choose the step in a case toolbar and press **Run AI**, or paste notes on
22
31
  **Cases > From memo**. The UI runs the selected AI tool and streams the output into the Jobs bar.
23
32
 
@@ -55,6 +64,11 @@ publishes, shares or deletes anything, in staging or in production. It:
55
64
  - lists the candidates instead of guessing a target (`## Targets`);
56
65
  - edits the case file only (plus reference screenshots under `.explore/targets/`).
57
66
 
67
+ With many profiles, `--profiles a,b` opens only those (ids from `profiles.yaml`, comma-separated). The others are not
68
+ opened and nothing is written about them, so a case can be explored in passes: A and B first, C later. Runs add up; the
69
+ case keeps what the earlier runs recorded. In the web UI the profile picker of the toolbar does the same: every profile
70
+ that is logged in in that environment is ticked, and the list you tick is remembered for the next run there.
71
+
58
72
  **To accept the suggestion, delete the whole block.** **To answer, write after `Answer:`.** Then run the command
59
73
  again. In the web UI, the run's **What to do next** bar offers **Answer**: the question list, an answer box,
60
74
  **Use the suggested defaults** and **Generate spec**. A continued job keeps the same conversation and permissions, and
@@ -92,8 +106,14 @@ Skip this for a `login: none` case. Sessions are per profile; see [profiles.md](
92
106
  ```
93
107
 
94
108
  The agent replays the case on the real pages with your saved sessions (it never writes data) and writes
95
- `tests/header-logo.spec.ts`. It fills the case's `## Values` for the profiles it explored and checks the files
96
- (`pnpm typecheck`, `pnpm case:check`; `pnpm spec:lint <case-id>` must have no failure).
109
+ `tests/header-logo.spec.ts`. It fills the case's `## Values` for the profiles the case holds information for and checks
110
+ the files (`pnpm typecheck`, `pnpm case:check`; `pnpm spec:lint <case-id>` must have no failure).
111
+
112
+ Its profile scope comes from the case, not from a selection: every profile id the case names — the keys of
113
+ `## Values`, a target card's `Reference <profile-id>:` line, an observation — plus `default`. That is why one run per
114
+ subset adds up: explore A and B first and C later, and `/gen-spec` covers all three. A profile the case says nothing
115
+ about runs on `default` alone, and the report names it. A profile that is not logged in is not dropped: its part is
116
+ written from the case and marked `not verified on the page`.
97
117
 
98
118
  It does **not** run the spec and does **not** create screenshot baselines: running is a separate step (6).
99
119
 
@@ -125,7 +145,7 @@ profile. Baselines are the source of truth for pixel checks.
125
145
  Then run all profiles and read the report: the video of each test, screenshots on failure, and the trace with the page
126
146
  and the network requests. See [commands.md](./commands.md) for filters and baselines.
127
147
 
128
- ## Healing a failing spec
148
+ ## Healing or refining a spec
129
149
 
130
150
  ```
131
151
  /heal-spec header-logo staging
@@ -133,14 +153,15 @@ and the network requests. See [commands.md](./commands.md) for filters and basel
133
153
 
134
154
  or **Heal spec** in the case toolbar, or **Heal this failure** on a failed result of the Reports page.
135
155
 
136
- The agent decides whether the test needs updating or whether it found a real product problem. It **never** edits an
137
- expectation just to make a failure disappear.
156
+ The command does two jobs: it repairs a spec that fails, and it refines a case and its spec when you ask for an
157
+ improvement. A passing last run is not a reason to refuse a request. It decides whether the test needs updating or
158
+ whether it found a real product problem, and it **never** edits an expectation just to make a failure disappear.
138
159
 
139
160
  - The environment is required. Only the profiles that are logged in there run (`--logged-in` leaves the others out
140
161
  instead of failing them).
141
- - The web UI first opens a dialog: which profiles to heal (every logged-in profile ticked) and an optional text box
142
- for what you want healed, with evidence or clues. The text is stored in `.tmp/hints/` and passed as `--hints`.
143
- Clues guide the diagnosis; they never lower the bar for a product finding.
162
+ - The web UI first opens a dialog: which profiles to run (every logged-in profile ticked) and an optional text box
163
+ for what should be repaired or improved, with evidence. The text is stored in `.tmp/hints/` and passed as `--hints`.
164
+ It guides the work; it never lowers the bar for a product finding.
144
165
  - It reads the existing report first:
145
166
 
146
167
  ```bash
@@ -149,7 +170,13 @@ expectation just to make a failure disappear.
149
170
 
150
171
  It prints status, failed step, error, call log and screenshots per profile, and whether the report is fresh or
151
172
  stale. The case is run again only when there is no report, the report is stale, it does not explain the failure, or
152
- a clue has to be checked. After editing the spec, it runs the case once to confirm.
173
+ a request has to be checked. It reads the pages (read-only) when a change has to be judged on them, and it never
174
+ performs a step that writes data. After editing the spec, it runs the case once to confirm.
175
+ - The case is the source of truth: a change to a step, an expectation, a value or a target is written into the case
176
+ first, then into the spec. A large change regenerates the spec from the case.
177
+ - **Review gate.** If the change leaves an open question or an unconfirmed observation in the case, healing stops
178
+ before the spec: you review the case, answer or accept what it asks, and run `/explore-case` again. The spec is
179
+ generated after that, not before.
153
180
 
154
181
  ## Why a generated spec can be trusted
155
182
 
@@ -257,6 +257,29 @@
257
257
  .side .item { padding: 8px 16px 8px 12px; border-bottom: 1px solid var(--line); border-left: 4px solid transparent; font-family: var(--mono); font-size: 14px; }
258
258
  .side .item.selected { background: var(--theme-bg); border-left-color: var(--theme); }
259
259
 
260
+ /* Filter bar (mirror components/filter-chips.tsx and components/search-field.tsx). */
261
+ .filters { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
262
+ .filters .group-label { margin-right: 2px; font-size: 12px; color: var(--ink-3); }
263
+ .filter-chip {
264
+ display: inline-flex; align-items: center; gap: 4px; height: 24px; padding: 0 6px; border-radius: 2px;
265
+ background: var(--card); color: var(--ink-2); font-size: 12px; box-shadow: inset 0 0 0 1px var(--line-mid);
266
+ }
267
+ .filter-chip .count { color: var(--ink-3); }
268
+ .filter-chip.on {
269
+ background: var(--theme-btn); color: #fff; font-weight: 500;
270
+ box-shadow: inset 0 0 0 1px var(--theme-btn);
271
+ }
272
+ .filter-chip.on .count { color: rgb(255 255 255 / .75); }
273
+ .filter-chip.on.production { background: var(--jiang); box-shadow: inset 0 0 0 1px var(--jiang); }
274
+ .filter-chip.on.staging { background: var(--cheng); box-shadow: inset 0 0 0 1px var(--cheng); }
275
+ .filter-chip.on.other { background: var(--gold-deep); box-shadow: inset 0 0 0 1px var(--gold-deep); }
276
+ .filter-search { position: relative; width: 260px; max-width: 100%; }
277
+ .filter-search svg { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); color: var(--ink-3); }
278
+ .filter-search .fake {
279
+ display: flex; align-items: center; height: 32px; padding: 0 8px 0 28px; border: 1px solid var(--line-mid);
280
+ border-radius: 4px; background: #fff; color: var(--ink-3); font-size: 14px;
281
+ }
282
+
260
283
  .seal-stamp {
261
284
  display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 3px;
262
285
  background: var(--seal); color: var(--paper); font-family: var(--serif); font-weight: 400; font-size: 16px; line-height: 1;
@@ -302,6 +325,7 @@
302
325
  <a href="#badges">Badges</a>
303
326
  <a href="#action-colors">Action colors</a>
304
327
  <a href="#dropdowns">Dropdowns</a>
328
+ <a href="#filter-bars">Filter bars</a>
305
329
  <a href="#multi-value">Multi-value fields</a>
306
330
  <a href="#icons">Icons</a>
307
331
  <a href="#tooltips">Tooltips</a>
@@ -503,6 +527,7 @@
503
527
  <tr><td><code>info</code> (light blue)</td><td>A view toggle that must be easy to find, not an action on data: "Full screen" / "Restore" in the Jobs drawer bar. Blue = read-only.</td></tr>
504
528
  <tr><td><code>secondary</code> (white)</td><td>Everything else: Verify, Visit, Discard, Copy, navigation.</td></tr>
505
529
  <tr><td><code>danger</code> (white, red text)</td><td>Cancel a running job, destructive confirmations.</td></tr>
530
+ <tr><td><code>invalid</code> (red field)</td><td>A control that is required and still empty, drawn as a button because it opens a panel instead of a native list (the environment picker of the case toolbar). The same red as an invalid dropdown; it is not an action.</td></tr>
506
531
  </tbody>
507
532
  </table>
508
533
  <p class="small muted">Hover lifts the button 1px; disabled is 50% opacity with no shadow. With an icon, add <code>gap-1.5</code> to the button.</p>
@@ -522,7 +547,7 @@
522
547
  <tbody>
523
548
  <tr><td><span class="badge green">green</span></td><td>ok: Logged in</td><td>Succeeded</td></tr>
524
549
  <tr><td><span class="badge red">red</span></td><td>expired, missing</td><td>Failed</td></tr>
525
- <tr><td><span class="badge amber">amber</span></td><td>unverified, error</td><td>Needs your input (an AI run that ended with <code>RESULT: needs-input</code>)</td></tr>
550
+ <tr><td><span class="badge amber">amber</span></td><td>unverified, error</td><td>Needs your input (an AI run that ended with <code>RESULT: needs-input</code>) or No result (a run that printed no <code>RESULT:</code> line at all)</td></tr>
526
551
  <tr><td><span class="badge blue">blue</span></td><td>-</td><td>Running</td></tr>
527
552
  <tr><td><span class="badge gray">gray</span></td><td>-</td><td>Cancelled</td></tr>
528
553
  </tbody>
@@ -584,7 +609,7 @@
584
609
  <tbody>
585
610
  <tr><td><code>explore-case</code></td><td>blue (qing)</td><td>Edits only the case file (wording, observations, open questions, inputs). The agent only reads pages; you do the steps that write data.</td></tr>
586
611
  <tr><td><code>gen-spec</code></td><td>amber (gold)</td><td>Writes: creates the spec and fills the case's <code>## Values</code>. Does not run the tests.</td></tr>
587
- <tr><td><code>heal-spec</code></td><td>red (seal)</td><td>Rewrites an existing spec and runs the tests.</td></tr>
612
+ <tr><td><code>heal-spec</code></td><td>red (seal)</td><td>Repairs the spec or refines the case and the spec, then runs the tests.</td></tr>
588
613
  </tbody>
589
614
  </table>
590
615
  <p class="small muted">
@@ -600,6 +625,14 @@
600
625
  <code>Select</code> is a button that draws its own list under it, in a <code>card-popup</code> panel, which is what
601
626
  gives every dropdown the same raised look.
602
627
  </p>
628
+ <p class="small muted">
629
+ Two controls put their label inside the button and open a panel with a title rather than a bare list: the
630
+ environment picker of the case toolbar (<code>Env: staging</code>, <code>components/env-picker.tsx</code>) and the
631
+ profile picker beside it (<code>Profiles: 2 of 3</code>, <code>components/profile-picker.tsx</code>). They are
632
+ buttons because their list needs a title and a line of explanation, not a <code>Select</code>; they keep this
633
+ section's height, <code>card-popup</code> panel, keyboard and click-outside rules. Do not turn them back into a
634
+ <code>Select</code> that repeats the label on every row.
635
+ </p>
603
636
  <div class="demo">
604
637
  <span class="select"><span class="fake">staging</span><svg class="chev" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg></span>
605
638
  <span class="select focus"><span class="fake">staging</span><svg class="chev" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg></span>
@@ -654,6 +687,60 @@
654
687
  </tbody>
655
688
  </table>
656
689
 
690
+ <h3 id="filter-bars">Filter bars</h3>
691
+ <p>
692
+ A list that can get long (the profiles of the workspace, the profiles of a case's Values table) gets a filter bar
693
+ above it: <code>SearchField</code> and <code>FilterChips</code> from <code>ui/web/src/components/</code>. They are
694
+ controls only; the page decides what a value means and filters its own rows.
695
+ </p>
696
+ <div class="demo" style="display:block">
697
+ <div class="filter-search" style="margin-bottom:8px">
698
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
699
+ <span class="fake">atpil</span>
700
+ </div>
701
+ <div style="display:flex;flex-wrap:wrap;gap:8px 16px">
702
+ <div class="filters">
703
+ <span class="group-label">Env</span>
704
+ <span class="filter-chip">All <span class="count">17</span></span>
705
+ <span class="filter-chip on production">PRODUCTION <span class="count">7</span></span>
706
+ <span class="filter-chip on staging">STAGING <span class="count">8</span></span>
707
+ <span class="filter-chip">SANDBOX <span class="count">2</span></span>
708
+ </div>
709
+ <div class="filters">
710
+ <span class="group-label">Tag</span>
711
+ <span class="filter-chip">All <span class="count">17</span></span>
712
+ <span class="filter-chip on">atp <span class="count">6</span></span>
713
+ <span class="filter-chip">compass <span class="count">4</span></span>
714
+ <span class="filter-chip">corp <span class="count">4</span></span>
715
+ </div>
716
+ </div>
717
+ <p class="small muted" style="margin:8px 0 0">Showing 6 of 17 profiles · <span style="text-decoration:underline">Clear filters</span></p>
718
+ </div>
719
+ <p class="small muted">
720
+ Search "atpil" with the dashes left out, both environments pressed and one tag: the two groups are combined with
721
+ AND.
722
+ </p>
723
+ <pre class="card mono" style="overflow:auto">import {SearchField} from '../components/search-field';
724
+ import {FilterChips} from '../components/filter-chips';
725
+
726
+ &lt;SearchField value={query} onChange={setQuery} ariaLabel="Search profiles" placeholder="Search id, name..." /&gt;
727
+ &lt;FilterChips label="Env" selected={envFilter} onChange={setEnvFilter} allCount={profiles.length}
728
+ options={envs.map((env) =&gt; ({value: env, label: env.toUpperCase(), count: countOf(env), tone: envFillClass(env)}))} /&gt;</pre>
729
+ <table>
730
+ <thead><tr><th>Part</th><th>Rule</th></tr></thead>
731
+ <tbody>
732
+ <tr><td>Search</td><td>One <code>SearchField</code> at the top. It looks in id, name, account, environment, tags and address; every word of the query must match one of them. A compact copy without <code>-</code>, <code>_</code>, <code>.</code> or spaces lets <code>atpil</code> find <code>atp-il-staging</code>.</td></tr>
733
+ <tr><td>Groups</td><td>One <code>FilterChips</code> per kind of value (environment, tag). Inside a group a row matches <b>any</b> pressed value; the groups are combined with <b>AND</b>. An empty group filters nothing.</td></tr>
734
+ <tr><td>All</td><td>The first chip of a group clears that group only. It is pressed while the group has no selection, so "no filter" can always be told from "everything selected".</td></tr>
735
+ <tr><td>Counts</td><td>Each option shows how many rows it would leave, and <code>All</code> shows the group's total. Counts come from the unfiltered list, so they do not dance while a value is pressed.</td></tr>
736
+ <tr><td>Pressed look</td><td>Neutral chips take the page accent (<code>theme-btn</code>, white text). Environment chips pass <code>envFillClass(env)</code>, the same solid marker as <code>EnvBadge</code>, so color means the environment here too.</td></tr>
737
+ <tr><td>State</td><td>The chip is the button: <code>aria-pressed</code>, a tooltip with its name (and "pressed" while on), and the count as a second, quieter part of the label.</td></tr>
738
+ <tr><td>Feedback</td><td>While a filter is active, show <code>Showing N of M</code> and <b>Clear filters</b>. When nothing matches, the empty state repeats the clear action instead of leaving a blank table.</td></tr>
739
+ <tr><td>Not remembered</td><td>A filter is a way of looking, not a setting: do not put it in <code>localStorage</code>. A reload shows everything, so nothing looks missing after lunch.</td></tr>
740
+ <tr><td>In the case panel</td><td>The Values table adds a two-button <b>Rows: Keys / Profiles</b> switch beside the search as soon as a case names two profiles, and shows the search and the environment chips only when it names more than three. The filters and the switch belong to that case; opening another case starts unfiltered.</td></tr>
741
+ </tbody>
742
+ </table>
743
+
657
744
  <h2 id="multi-value">Multi-value fields</h2>
658
745
  <p>
659
746
  A field whose value is a list is never a <a href="#dropdowns">Dropdown</a> plus an "Add" text input: that mixes a
@@ -829,11 +916,12 @@
829
916
  </p>
830
917
  <p class="muted">
831
918
  The preview block that holds the editor caret is shaded with <code>theme-bg</code> at 60% and a 2px <code>theme</code> left
832
- border (an overlay, so it follows the page theme). The editor and the preview scroll together and a click in the preview
833
- moves the caret; the logic is in <code>ui/web/src/lib/preview-sync.ts</code>.
919
+ border (an overlay, so it follows the page theme). The overlay paints <b>behind</b> the text, never over it: the
920
+ highlighted block keeps its own text color instead of looking washed out. The editor and the preview scroll together
921
+ and a click in the preview moves the caret; the logic is in <code>ui/web/src/lib/preview-sync.ts</code>.
834
922
  </p>
835
923
  <p class="muted">
836
- The previous / next buttons of the case toolbar move the caret to the next marker and blink that marker for a moment in
924
+ The previous / next buttons at the right end of the editor toolbar move the caret to the next marker and blink that marker for a moment in
837
925
  both panes (<code>cm-kw-flash</code> in the editor, <code>kw-flash</code> over the <code>&lt;mark&gt;</code> of the
838
926
  document): <code>gold-glow</code> at 65% behind it and a 2px <code>dai-deep</code> ring with a wider
839
927
  <code>gold-glow</code> glow around it, the ring blinking on and off three times (0.45&nbsp;s each), then it goes away.
@@ -69,6 +69,10 @@ is recorded.
69
69
  checkout has the card but not the screenshot. Run the exploration again on this machine (or look at the card and the
70
70
  report's baseline).
71
71
 
72
+ **`/explore-case` takes long with many profiles** — pass `--profiles a,b` (the toolbar's profile picker does the same):
73
+ only those profiles are opened, and a later run with the others adds what it finds. `/gen-spec` has nothing to pick: it
74
+ covers every profile the case names.
75
+
72
76
  ## Installing
73
77
 
74
78
  **`pnpm add` installs the previous release instead of `latest`** — pnpm holds back versions published in the last
@@ -131,6 +135,6 @@ read it. `doctor` lists the overrides it sees.
131
135
  project. Add `--reset-rules` to replace them with the template, and expect to lose the project's rules.
132
136
 
133
137
  **The AI did something you did not expect** — exploring and generating only read pages and edit the case file; healing
134
- edits the spec and runs it under the same write gate as any run (staging writes are allowed, any other environment
135
- needs a person's confirmation). None of them writes product data on its own, and none updates a baseline. If a step
136
- needs a person to write, run it with `--attended`; that is where a person acts, in the browser.
138
+ edits the case and the spec and runs the spec under the same write gate as any run (staging writes are allowed, any
139
+ other environment needs a person's confirmation). None of them writes product data on its own, and none updates a
140
+ baseline. If a step needs a person to write, run it with `--attended`; that is where a person acts, in the browser.
package/docs/web-ui.md CHANGED
@@ -36,6 +36,10 @@ The UI's look is the **Inkstone** theme; the palette, buttons, badges and icons
36
36
 
37
37
  One row per profile, with the same login check as `pnpm status`.
38
38
 
39
+ - **Filters** above the table: a search over id, name, account, environment, tags and address (a word may leave out
40
+ the dashes, so `atpil` finds `atp-il-staging`), plus multi-select chips for environments and tags. Every chip shows
41
+ how many profiles it leaves; the bar shows `Showing N of M profiles` and **Clear filters** while it is active. The
42
+ filters are not remembered, so a reload shows every profile again.
39
43
  - **Log in** starts `pnpm signin <profile-id>`: log in in the browser window that opens, then press **Done, save the
40
44
  session** on the card in that window (the job's "I've logged in, save the session" button does the same). A
41
45
  logged-in profile shows `Login YES`.
@@ -53,7 +57,7 @@ One row per profile, with the same login check as `pnpm status`.
53
57
 
54
58
  The case list is grouped by folder and collapses. The editor opens in **Single** with the **Panel** full width; the
55
59
  toolbar switches to **Split** (source left, one pane right; drag the divider to resize, double-click to reset) or, in
56
- Single, to **Editor** or **Doc**.
60
+ Single, between **Panel**, **Editor** and **Doc**.
57
61
 
58
62
  - **Source and preview** scroll together, the preview block at the caret is shaded, and a click in the preview puts
59
63
  the caret on that line. `Ctrl/Cmd+S` saves.
@@ -66,7 +70,11 @@ Single, to **Editor** or **Doc**.
66
70
  answer and the AI folds it in). Then the front matter (title, login, suites, writes and its note, environments), the
67
71
  steps (sentence, `[writes]` / `[input]` / `[email]` tags, `- Ready when:` lines, `- Recipe:` lines), the
68
72
  `## Expected` and `## Visual checks` bullets (drag to reorder), the `## Values` table (one column per profile; an
69
- empty cell inherits `default`), and the `## Targets` cards read-only, with what each is missing. `[T1]` chips open
73
+ empty cell inherits `default`), and the `## Targets` cards read-only, with what each is missing. With two or more
74
+ profiles the Values section offers a **Rows: Keys / Profiles** switch, and from four profiles on it also adds a
75
+ search and environment chips. With more profiles than keys it starts as one row per profile, so every name and
76
+ environment stays readable instead of being squeezed into a narrow column. Both the header and the profile/key
77
+ column stay in place while the table scrolls. `[T1]` chips open
70
78
  the card: what it is, where, how it is identified, what it must not be, and its reference screenshots. Every section
71
79
  collapses and has a **?** with a short help card. Panel edits land in the same draft as the source editor, so one
72
80
  save covers both, and **Discard** throws them away together.
@@ -76,7 +84,9 @@ Single, to **Editor** or **Doc**.
76
84
  the spec is rewritten. **Delete** (type the id, or fill it in with the feather icon) removes the case, its spec and
77
85
  its baselines; both dialogs list exactly what they touch, and are refused while an AI or test run is in progress.
78
86
  - **Toolbar:** command (explore / generate / heal, explore by default), **Environment** (required, no default),
79
- **Attended** (only for explore, ticked by default), **Run AI**, copy the shell command, open the AI guide.
87
+ **Profiles** (explore only: every profile that is logged in is ticked, the choice is remembered per environment, and
88
+ only the ticked ones are opened), **Attended** (only for explore, ticked by default), **Run AI**, copy the shell
89
+ command, open the AI guide.
80
90
  - **From memo** opens a Markdown editor with a preview for raw notes. **Make cases** saves them to `.tmp/memos/` and
81
91
  runs `/make-case` one-shot (defaults for gaps, `TODO` for unknown values, no page exploration, existing cases never
82
92
  overwritten); **Copy /make-case** copies the memo for the interactive dialogue. When the run ends, **What to do
@@ -117,9 +127,21 @@ cancelled there. Opening it shrinks the page instead of covering it.
117
127
  `.tmp/ui-jobs/`.
118
128
  - When an AI job ends, a thin **What to do next** bar appears under the log (or **Details** opens it in a sheet):
119
129
  open questions with an answer box and **Use the suggested defaults**, the confirmations the exploration marked, and
120
- the next action (**Generate spec**, **Open Run tests**). A run that ends with `needs-input` gets an amber *Needs
121
- your input* badge; sending answers continues the same conversation, once per turn and only while the UI server has
122
- not restarted. After a restart, answer in the case file and run the command again.
130
+ the next action (**Generate spec**, **Open Run tests**, **View spec**, **Re-explore**). A run that ends with
131
+ `needs-input` gets an amber *Needs your input* badge; sending answers continues the same conversation, once per turn
132
+ and only while the UI server has not restarted. After a restart, answer in the case file and run the command again.
133
+ - A run that ends without its `RESULT:` line gets an amber *No result* badge and a banner under the command instead of
134
+ a "what to do next" bar: nothing is known about what it did, so read the log before trusting the case or the spec.
135
+ A run that is not allowed to write files (plan mode, a read-only sandbox) ends that way; the framework starts
136
+ OpenCode with `--agent build` so that a one-shot run is never a plan.
137
+ - A **Generate spec** or **Re-explore** button opens the case with its Run AI button already on that command, in the
138
+ case view: the button gets a dark ring and an amber pulse, and a gold strip under the toolbar says what is still
139
+ missing (an environment, a save), until the run starts or you pick another command.
140
+ - After **Generate spec**, **View spec** opens the case on the spec editor. After **Heal spec**, the bar says either
141
+ that the case and the spec are up to date (with **View spec**), or that the case changed and needs your review: then
142
+ it counts the must-answer questions, suggestions and lines to confirm, and offers **Review the questions** /
143
+ **Review the marked lines** and **Re-explore** — healing does not generate a spec from a case that still asks
144
+ something.
123
145
 
124
146
  ## Help and AI guide
125
147
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asrunjs/asrun",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Multi-profile end-to-end test suite: plain-language cases, AI-written Playwright specs, evidence for every run.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Zoro Chen",
@@ -8,15 +8,20 @@ Rules combine in this order, and a later one wins where they conflict: the frame
8
8
  `{{framework}}/AGENTS.md`, the framework skill, `.claude/skills/exploring-cases.md` if it exists, then the workspace
9
9
  `AGENTS.md` §2 (Project rules). F1–F6 always win. Project facts are in `AGENTS.md` §3.
10
10
 
11
- Arguments: `<case-id> <env> [--attended]`. The case id is the path below `cases/` without `.md` (it may include a
11
+ Arguments: `<case-id> <env> [--attended] [--profiles a,b]`. The case id is the path below `cases/` without `.md` (it may include a
12
12
  group folder, e.g. `documents/landing-page`) and is required. `<env>` is `staging` or `production`; it is required and there
13
13
  is no default (without one, stop and say so). With `--attended` I am at the machine and the browser window is
14
14
  visible: when a step writes data or needs a choice, hand it to me as the skill describes, wait for me, and record
15
15
  what I did as a general recipe under the step it belongs to (`- Recipe:` lines). Without `--attended`, never wait for me.
16
+ `--profiles a,b` explores only those profiles (comma-separated ids from `profiles.yaml`); without it, at least one
17
+ logged-in profile per domain. A profile the list leaves out is not opened and nothing is written about it: runs add up,
18
+ so a later run covers the rest.
16
19
 
17
20
  Follow `{{framework}}/.claude/skills/exploring-cases/references/assertion-targets.md` (list the candidates, never
18
21
  pick when in doubt, ask for the exact image/text/place/UI) and `readiness.md` (wait for the key requests and the
19
22
  content before you read anything) of the skill.
20
23
 
21
24
  This command edits `cases/<case-id>.md` only (plus screenshots under `.explore/targets/`). Do not ask me questions in
22
- the chat: write open questions into the case file as the skill describes, and end with the `RESULT:` line.
25
+ the chat: write open questions into the case file as the skill describes, and end with the `RESULT:` line. If you are
26
+ not allowed to write files at all (a plan-mode run, a read-only sandbox), say what blocked you and end with
27
+ `RESULT: needs-input`.
@@ -12,10 +12,13 @@ Rules combine in this order, and a later one wins where they conflict: the frame
12
12
  Arguments: `<case-id> [<env>] [--force]`. The case file is `cases/<case-id>.md` (the id may include one group folder,
13
13
  e.g. `documents/landing-page`). `<env>` is the environment to explore in (`staging` or `production`); there is no default. If
14
14
  the spec already exists, it is left alone unless `--force` is given; with `--force` it is regenerated and overwritten.
15
+ There is no profile option: the spec is generated for every profile the case holds information for (its `## Values`
16
+ keys, its target cards and its observations), not for a chosen subset and not only for the profiles that are logged in.
15
17
 
16
18
  Follow the skill's procedure exactly, including its `assertion-targets.md` and `readiness.md`; run
17
19
  `pnpm spec:lint <case-id>`. Only generate the spec: do not run `pnpm asrun` (running specs is a separate
18
20
  process), and do not create baselines.
19
21
 
20
22
  Do not ask me questions: resolve ambiguity as the skill describes (explore the page, use observed values marked for
21
- QA, write the remaining questions into the case file), and end with the `RESULT:` line.
23
+ QA, write the remaining questions into the case file), and end with the `RESULT:` line. If you are not allowed to write
24
+ files at all (a plan-mode run, a read-only sandbox), say what blocked you and end with `RESULT: needs-input`.
@@ -1,8 +1,8 @@
1
1
  ---
2
- description: Diagnose and fix a failing spec from its last report (without hiding real product regressions)
2
+ description: Repair a failing spec, or refine a case and its spec on request, without hiding real product regressions
3
3
  ---
4
4
 
5
- Load the framework skill `{{framework}}/.claude/skills/generating-specs/SKILL.md`, section "Healing a failing
5
+ Load the framework skill `{{framework}}/.claude/skills/generating-specs/SKILL.md`, section "Healing and refining a
6
6
  spec", and follow it exactly.
7
7
 
8
8
  Rules combine in this order, and a later one wins where they conflict: the framework baseline
@@ -13,19 +13,29 @@ Arguments: $ARGUMENTS
13
13
  Format: `<case-id> <env> [--profiles a,b] [--report <run-id>] [--hints <file>]`, with optional free text after them.
14
14
  - `<case-id>` and `<env>` (`staging` or `production`) are required. There is no default environment. Without one,
15
15
  stop and say so.
16
- - `--profiles`: the profiles to heal. Without it, every profile that is logged in in `<env>`.
16
+ - `--profiles`: the profiles to run when evidence is needed. Without it, every profile that is logged in in `<env>`.
17
17
  - `--report`: the report (folder name under `reports/`) to read first. Without it, the newest report of this case.
18
- - `--hints <file>`: a file with what the user knows (what to heal, evidence, clues). Read it first. Any free text
19
- after the arguments is the same kind of clue. Both are optional.
18
+ - `--hints <file>`: a file with what the user wants repaired or improved, with any evidence. Read it first. Any free
19
+ text after the arguments is the same kind of request. Both are optional.
20
20
 
21
- Before you decide "product or test", audit the assertion itself (target card, baseline content, readiness): see
22
- step 4 of the skill's healing section, `assertion-targets.md` and `readiness.md`. Run `pnpm spec:lint <case-id>`.
21
+ A passing run is not a reason to refuse a request: healing also refines a case and its spec. Never lower the bar for a
22
+ product finding to make a test pass.
23
+
24
+ Do not ask me questions in the chat: this may run unattended. Resolve what the report, the case and the page can
25
+ resolve, and write what still needs a person into the case as a `[blocking]` question. End with the `RESULT:` line:
26
+ `ready` when the healing or the refinement is finished, `needs-input` when something is still open. If you could not
27
+ write files at all (a plan-mode run, a read-only sandbox, a refused permission), say in one sentence what blocked you
28
+ and end with `RESULT: needs-input`: never answer with a plan and a question instead.
29
+
30
+ Before you decide "product or test", audit the assertion itself (target card, baseline content, readiness): see step 5
31
+ of the skill's healing section, `assertion-targets.md` and `readiness.md`. Run `pnpm spec:lint <case-id>`.
23
32
  Read the existing report first (`pnpm heal:evidence --case <case-id> --env <env>`), and run the case again only when
24
- the skill says it is needed. Always run with `--logged-in`. Tell me whether it is a test problem or a product
25
- finding.
33
+ the skill says it is needed. Always run with `--logged-in`. If the change leaves an open question or an unconfirmed
34
+ marker in the case, stop there, tell me to review the case and run `/explore-case`, and end with
35
+ `RESULT: needs-input`. Otherwise tell me whether it is a test problem or a product finding.
26
36
 
27
37
  Run `pnpm asrun ...`, `pnpm heal:evidence ...`, `pnpm spec:lint ...`, `pnpm baselines:review ...` and
28
38
  `pnpm status ...` as plain commands, optionally piped to `tail` or `head`, and do not chain other commands with them
29
- (`;` or `&&`). Only these exact forms run outside Claude Code's sandbox, which tsx and the browser need. If a
30
- command still fails with `listen EPERM` or a sandbox error, stop and report it. Do not ask to disable the sandbox,
31
- and never add `--allow-writes` or set `ASRUN_ALLOW_WRITES`.
39
+ (`;` or `&&`). Only these exact forms run outside Claude Code's sandbox, which tsx and the browser need. If a command
40
+ still fails with `listen EPERM` or a sandbox error, stop and report it. Do not ask to disable the sandbox, and never
41
+ add `--allow-writes` or set `ASRUN_ALLOW_WRITES`.