@asrunjs/asrun 0.11.0 → 0.13.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 (65) hide show
  1. package/.claude/commands/explore-case.md +2 -1
  2. package/.claude/commands/gen-spec.md +2 -1
  3. package/.claude/commands/heal-spec.md +2 -1
  4. package/.claude/commands/make-case.md +2 -1
  5. package/.claude/skills/exploring-cases/SKILL.md +14 -6
  6. package/.claude/skills/exploring-cases/references/case-rules.md +5 -0
  7. package/.claude/skills/exploring-cases/references/open-questions.md +8 -0
  8. package/.claude/skills/exploring-cases/references/user-handoff.md +12 -8
  9. package/.claude/skills/generating-specs/SKILL.md +17 -10
  10. package/.claude/skills/generating-specs/references/conventions.md +6 -2
  11. package/.claude/skills/making-cases/SKILL.md +12 -5
  12. package/.claude/skills/making-cases/references/writes-and-email.md +5 -2
  13. package/AGENTS.md +20 -5
  14. package/README.md +10 -6
  15. package/dist/lib/case-doc.d.ts +1 -1
  16. package/dist/lib/config.d.ts +26 -0
  17. package/dist/lib/config.js +51 -0
  18. package/dist/lib/fixtures.d.ts +1 -1
  19. package/dist/lib/fixtures.js +7 -4
  20. package/dist/lib/records.d.ts +17 -2
  21. package/dist/lib/records.js +32 -2
  22. package/dist/lib/rules.d.ts +57 -0
  23. package/dist/lib/rules.js +140 -0
  24. package/dist/scripts/cleanup.js +10 -1
  25. package/dist/scripts/cli.js +9 -1
  26. package/dist/scripts/doctor.js +27 -48
  27. package/dist/scripts/lint-specs.js +23 -1
  28. package/dist/scripts/rules.d.ts +1 -0
  29. package/dist/scripts/rules.js +148 -0
  30. package/dist/ui/server/ai-stream.d.ts +2 -2
  31. package/dist/ui/server/ai-stream.js +190 -30
  32. package/dist/ui/shared/log-text.d.ts +13 -0
  33. package/dist/ui/shared/log-text.js +45 -0
  34. package/dist/ui/shared/types.d.ts +6 -1
  35. package/docs/agents/case-format.md +8 -5
  36. package/docs/ai-workflow.md +21 -12
  37. package/docs/cases.md +10 -6
  38. package/docs/commands.md +22 -4
  39. package/docs/install.md +2 -1
  40. package/docs/theme-guide.html +24 -3
  41. package/docs/troubleshooting.md +11 -2
  42. package/docs/web-ui.md +30 -9
  43. package/package.json +4 -1
  44. package/templates/.claude/commands/explore-case.md +3 -1
  45. package/templates/.claude/commands/gen-spec.md +3 -1
  46. package/templates/.claude/commands/heal-spec.md +3 -1
  47. package/templates/.claude/commands/make-case.md +3 -1
  48. package/templates/AGENTS.md +17 -8
  49. package/templates/cases/_template.md +3 -3
  50. package/templates/package.json +4 -0
  51. package/ui/dist/assets/{dist-BMmNFIUH.js → dist-BB2cF3XN.js} +1 -1
  52. package/ui/dist/assets/{dist-ByLbEOlF.js → dist-C46EmZuI.js} +1 -1
  53. package/ui/dist/assets/{dist-CQS0So-i.js → dist-D9GmSkjm.js} +1 -1
  54. package/ui/dist/assets/{dist-BXNaYHeh.js → dist-DGzg2gyA.js} +1 -1
  55. package/ui/dist/assets/{dist-DTYj2vUD.js → dist-DMiOs9Qj.js} +1 -1
  56. package/ui/dist/assets/{dist-mV0_nQZe.js → dist-Db_XhDui.js} +1 -1
  57. package/ui/dist/assets/{dist-CTrwRc3Q.js → dist-DrFzCwMc.js} +1 -1
  58. package/ui/dist/assets/{dist--BXJInTq.js → dist-Dy45nXf8.js} +1 -1
  59. package/ui/dist/assets/{dist-CK8mow8u.js → dist-P9roUi1O.js} +1 -1
  60. package/ui/dist/assets/{dist-C9ra6_BD.js → dist-_0g6iC9K.js} +1 -1
  61. package/ui/dist/assets/index-BN4ucmmn.js +376 -0
  62. package/ui/dist/assets/index-Bo4XKJvP.css +2 -0
  63. package/ui/dist/index.html +2 -2
  64. package/ui/dist/assets/index-BzMqkmQ9.js +0 -348
  65. package/ui/dist/assets/index-DcJR-8Mt.css +0 -2
@@ -6,7 +6,8 @@ Load the `exploring-cases` skill and explore: $ARGUMENTS
6
6
  In this repository the workspace is `examples/demo` (set `ASRUN_ROOT=examples/demo`); the framework is `.`.
7
7
  Rules combine in this order, and a later one wins where they conflict: the framework baseline `AGENTS.md`, the
8
8
  framework skill, `.claude/skills/exploring-cases.md` if it exists, then the workspace `AGENTS.md` §2 in `examples/demo`
9
- (Project rules). F1–F6 and R2 always win.
9
+ (Project rules). F1–F6 and R2 always win. The effective set is what `pnpm asrun rules` prints; §2 is applied as
10
+ written and is never turned into a question (a difference from a framework default is not a conflict).
10
11
  Arguments: `<case-id> <env> [--attended] [--profiles a,b] [--hints <file>]`. The case id is the path below cases/ without `.md` (it may include a group
11
12
  folder, e.g. documents/landing-page) and is required. `<env>` is `staging` or `production`; it is required and there is no
12
13
  default (without one, stop and say so). With `--attended` I am at the machine and the browser window is visible: when a step
@@ -6,7 +6,8 @@ Load the `generating-specs` skill and generate the spec for: $ARGUMENTS
6
6
  In this repository the workspace is `examples/demo` (set `ASRUN_ROOT=examples/demo`); the framework is `.`.
7
7
  Rules combine in this order, and a later one wins where they conflict: the framework baseline `AGENTS.md`, the
8
8
  framework skill, `.claude/skills/generating-specs.md` if it exists, then the workspace `AGENTS.md` §2 in `examples/demo`
9
- (Project rules). F1–F6 and R2 always win.
9
+ (Project rules). F1–F6 and R2 always win. The effective set is what `pnpm asrun rules` prints; §2 is applied as
10
+ written and is never turned into a question (a difference from a framework default is not a conflict).
10
11
  Arguments: `<case-id> [<env>] [--force] [--hints <file>]`. The case file is cases/<case-id>.md (the id may include one group folder, e.g.
11
12
  documents/landing-page). `<env>` is the environment to explore in (`staging` or `production`); there is no default. If the spec already exists, it is
12
13
  left alone unless `--force` is given; with `--force` it is regenerated and overwritten. `--hints <file>` (or free text
@@ -6,7 +6,8 @@ Load the `generating-specs` skill, section "Healing and refining a spec", and fo
6
6
  In this repository the workspace is `examples/demo` (set `ASRUN_ROOT=examples/demo`); the framework is `.`.
7
7
  Rules combine in this order, and a later one wins where they conflict: the framework baseline `AGENTS.md`, the
8
8
  framework skill, `.claude/skills/generating-specs.md` if it exists, then the workspace `AGENTS.md` §2 in `examples/demo`
9
- (Project rules). F1–F6 and R2 always win.
9
+ (Project rules). F1–F6 and R2 always win. The effective set is what `pnpm asrun rules` prints; §2 is applied as
10
+ written and is never turned into a question (a difference from a framework default is not a conflict).
10
11
  Arguments: $ARGUMENTS
11
12
  Format: `<case-id> <env> [--profiles a,b] [--report <run-id>] [--hints <file>]`, with optional free text after them.
12
13
  - `<case-id>` and `<env>` (`staging` or `production`) are required. There is no default environment. Without one, stop
@@ -6,6 +6,7 @@ Load the `making-cases` skill and turn this input into case documents: $ARGUMENT
6
6
  In this repository the workspace is `examples/demo` (set `ASRUN_ROOT=examples/demo`); the framework is `.`.
7
7
  Rules combine in this order, and a later one wins where they conflict: the framework baseline `AGENTS.md`, the
8
8
  framework skill, `.claude/skills/making-cases.md` if it exists, then the workspace `AGENTS.md` §2 in `examples/demo`
9
- (Project rules). F1–F6 and R2 always win.
9
+ (Project rules). F1–F6 and R2 always win. The effective set is what `pnpm asrun rules` prints; §2 is applied as
10
+ written and is never turned into a question (a difference from a framework default is not a conflict).
10
11
  The input is either text or a path to a file with the notes. Follow the skill's procedure exactly.
11
12
  Ask me the open questions before writing any case file. Do not generate specs; that is `/gen-spec`.
@@ -15,10 +15,15 @@ in the browser while the agent waits, and how it is recorded). All but the last
15
15
  too.
16
16
 
17
17
  ## Before you start
18
- Read the workspace `AGENTS.md`: its §2 holds the project rules, and it points at the framework baseline. If
19
- `.claude/skills/exploring-cases.md` exists in the workspace, read it and apply it after this skill. Then apply
20
- `AGENTS.md` §2, which wins over both (the baseline, "Precedence"). F1–F6 and R2 always win. This is not a stop
21
- condition: continue with the procedure below.
18
+ Run `pnpm asrun rules --json` (or `pnpm asrun rules` for people): it prints the framework baseline, the project's
19
+ `AGENTS.md` §2 rules and the project settings (the test-data prefix). Those are the effective rules; if the command is
20
+ unavailable, read the workspace `AGENTS.md` directly. Also read `AGENTS.md` §2, and `.claude/skills/exploring-cases.md`
21
+ in the workspace, applying the override after this skill and §2 after both (the baseline, "Precedence"). F1–F6 and R2
22
+ always win. **Never turn a project rule into a question:** a difference between §2 and a framework default is not a
23
+ conflict; apply §2 and, if a row's id does not fit its text, apply the text and report the mismatch. This is not a stop
24
+ condition: continue with the procedure below. If `pnpm asrun rules` exits 1 (an invalid §2 row or a prefix that the
25
+ document and `package.json` disagree on), do not guess and do not turn it into a case question: report its errors and
26
+ end with `RESULT: needs-input`; a person fixes `AGENTS.md` or `package.json` first.
22
27
 
23
28
  ## Where this skill sits
24
29
  Three steps, each learns one thing:
@@ -70,7 +75,8 @@ one exception is a handoff in an attended run, where waiting for the person is t
70
75
  - ambiguous steps and checks without an expected result;
71
76
  - steps that write data or need a choice: mark them `[writes]` / `[input]`. The front matter must list the
72
77
  environments the finished spec may write in (`writes: [staging]`, or `[staging, production]`) with a
73
- `writes-note`, the data must be named `[asrun] ...` and cleaned up, and a step that sends email is never
78
+ `writes-note`, the data must be named with the project's test-data prefix (default `[asrun]`, printed by
79
+ `pnpm asrun rules`) followed by a run stamp, and cleaned up; a step that sends email is never
74
80
  allowed in production and only in other environments when the profile has `email: true`;
75
81
  - missing or doubtful `suites`/`envs`, a `writes` environment that `envs` excludes, and visual checks that are
76
82
  too broad (screenshot a small element);
@@ -128,7 +134,9 @@ one exception is a handoff in an attended run, where waiting for the person is t
128
134
  `[blocking]` TODO, never a guess. Check the screenshots you saved: each must show what its card says.
129
135
  6. **Write the recipe lines** for every `[input]` or `[writes]` step that was handed off, from the card's `recorded`
130
136
  result, as a general recipe under that step: one `- Recipe (phase): ...` line per phase. Format and rules are in
131
- `references/user-handoff.md`. A step that was not handed off gets no recipe line and a `[blocking]` TODO. A case
137
+ `references/user-handoff.md`. What the case creates is named with the project's prefix and the run stamp
138
+ (`testDataName`); the recipe records how to build it, never the stamp itself. A step that was not handed off gets
139
+ no recipe line and a `[blocking]` TODO. A case
132
140
  that still has the old `## Inputs` section: move the lines of the steps you touch under those steps as
133
141
  `- Recipe:`; `pnpm case:check` names the rest.
134
142
  7. **Resolve every open point** in the order of `references/open-questions.md`: answer it from the page, else apply
@@ -29,6 +29,9 @@ The section is optional. A case with no rules runs by the same rules as before.
29
29
  A rule is never a second source of truth. When a rule and a step, an expectation, a value or a target disagree, the
30
30
  structured section wins and the rule becomes a `🚫 TODO [blocking]` question. Never silently change either side.
31
31
 
32
+ This applies to a case's own `## Rules` only. The project's `AGENTS.md` §2 is not a case rule: it is a higher layer
33
+ that is applied as written, never turned into a question (`AGENTS.md`, "Precedence").
34
+
32
35
  ## The rule pass (before anything else)
33
36
 
34
37
  Read the whole `## Rules` section and, for each bullet, decide:
@@ -106,5 +109,7 @@ that is missing from the table is a rule the agent ignored.
106
109
 
107
110
  - A rule never authorizes a write, an email, a person's choice, accepting terms, or a shell command.
108
111
  - Never delete or rewrite a rule a person wrote. Add a TODO instead.
112
+ - A project rule (`AGENTS.md` §2) is applied, never questioned: it is not a case rule and a difference from the
113
+ framework default is not a conflict.
109
114
  - Never invent a rule the person did not ask for. `R4` allows only what a run needs and only in general terms.
110
115
  - Prose follows the artifact language of the workspace `AGENTS.md` §2 (English by default); the heading stays `## Rules`.
@@ -110,6 +110,12 @@ answer, adds `(page shows <x> on <profile-id>)` and reports it as a finding. It
110
110
  `[blocking]` question that now has an observation or a safe default is downgraded or closed in the file, and the
111
111
  edit is part of the report. Do not carry old tags over.
112
112
 
113
+ ## What is never a question
114
+ A rule in `AGENTS.md` §2 (a naming prefix, a language, a wait) is applied as written. A difference from the framework
115
+ default is not a conflict and is never a TODO, a marker or a question; a row whose id does not fit its text is applied
116
+ by its text and reported as a mismatch. A project-wide choice that the same answer would settle for every case belongs
117
+ in §2, not in one case's question.
118
+
113
119
  ## Resolution order (no question goes to the user while a better step exists)
114
120
  1. **The page answers it.** Facts such as exact labels, which controls exist, where the logo is, what a menu holds
115
121
  are answered by exploration (`/explore-case`; needs an environment and a logged-in profile; the agent only reads, a person does the steps that write). Rewrite the parent sentence
@@ -143,6 +149,8 @@ answer, adds `(page shows <x> on <profile-id>)` and reports it as a finding. It
143
149
  names the exact image, text, place or UI wanted and lists the candidates. Never a safe default, because a wrong
144
150
  guess passes silently.
145
151
  - An intent question the page cannot answer ("should this also work for agents?").
152
+ - A literal the runner enforces and nobody settled in the case or in §2 (a name prefix, a value the code validates):
153
+ a wrong pick fails the run, so it is `[blocking]`, not `[non-blocking]`.
146
154
  - No environment was given, or no profile is logged in, so a fact that needs the page cannot be observed.
147
155
 
148
156
  ## Final report
@@ -25,10 +25,12 @@ that nobody watches.
25
25
  ## Before the handoff
26
26
  1. Say what the user has to do and when it is finished, in one or two sentences ("Fill in the form, pick two
27
27
  items, then press Done"). Name the finish condition first (below).
28
- 2. Put every text the user has to type into `copy` (the `[asrun] ...` name of a document, a title), so it can be
29
- copied with one click.
28
+ 2. Put every text the user has to type into `copy` (the name of a document, a title), so it can be
29
+ copied with one click. Build a created name with the project's test-data prefix (default `[asrun]`, printed by
30
+ `pnpm asrun rules`) and a run stamp: `<prefix> <base> (<YYYY-MM-DD HH-mm-ss>, UTC)`, the same shape
31
+ `testDataName()` builds. The stamp is chosen now, not recorded in the recipe.
30
32
  3. Never ask the user to send email (production: never; other environments: only when `email` is `true` for the
31
- profile). Never ask for login or 2FA. Name everything the user creates `[asrun] <something>`.
33
+ profile). Never ask for login or 2FA. Name everything the user creates `<prefix> <something> (<run stamp>)`.
32
34
  4. Do not start a recording yourself: the card starts and stops it (see "Recording"). Never call
33
35
  `browser_start_recording` or `browser_stop_recording`: the `playwright-test` MCP lists them but cannot run them
34
36
  (`devtools` tools; see `tools.md`).
@@ -40,7 +42,7 @@ timeout), so no polling from outside is needed. The loop:
40
42
  - **Shows a small floating card, not a bar.** It sits in the bottom-left corner and only the card takes clicks, so
41
43
  nothing of the page is covered or blocked. The user can **drag** it by its title bar and **minimize** it to a small
42
44
  pill that still has a Done button. Position and minimized state survive a page reload (kept in `sessionStorage`).
43
- - **Click to copy.** Every text in `copy` (for example the `[asrun] ...` name the user has to type) is shown on its
45
+ - **Click to copy.** Every text in `copy` (for example the `<prefix> <name> (<run stamp>)` the user has to type) is shown on its
44
46
  own line with a copy icon; one click puts it on the clipboard and the icon shows "Copied". Pass only text that the
45
47
  case says to type; leave `copy` empty when there is none.
46
48
  - **Resets the flag first**: `window.__handoff = null` on every page it paints. A flag left over from an earlier
@@ -71,7 +73,7 @@ badge, no count in the tab title and no notification (a HINT in the job log says
71
73
  async (page) => {
72
74
  page.context().__handoffCfg = {
73
75
  message: '<what the user does, one sentence>',
74
- copy: [], // texts to type, e.g. ['[asrun] published document']
76
+ copy: [], // texts to type, e.g. ['[autotest] published document (2026-11-09 12-34-56)']
75
77
  doneText: null, // text that appears when finished, or null
76
78
  maxMs: 600000,
77
79
  };
@@ -114,8 +116,9 @@ The Done button is always there (also on the minimized card) as the fallback, an
114
116
  - `outcome: 'timeout'` or `'skip'`: do not guess. Write a `TODO [blocking]` under the step and go on with what does
115
117
  not depend on it.
116
118
  - Take a snapshot, write down what the user did from the card's `recorded` (see "Recording"), then continue read-only.
117
- - At the end of the exploration hand off once more when the user created data: "Delete the [asrun] items you
118
- created, then press Done". The case must clean up after itself, and so must the exploration.
119
+ - At the end of the exploration hand off once more when the user created data: "Delete the test items you
120
+ created (they start with <the project's prefix> and carry this run's timestamp), then press Done". The case must
121
+ clean up after itself, and so must the exploration.
119
122
 
120
123
  ## Recording
121
124
  The card records what the person does: it starts the recording when it shows and stops it on every way out (Done,
@@ -134,7 +137,8 @@ Make the recipe as general as possible, because lists and data change:
134
137
  name or id. "Select items #1 to #3 from the list."
135
138
  - **Random is allowed only when the user asks for it** ("pick any 3"). The default is the position, because it can be
136
139
  repeated. A random pick must be written into the test's annotations when the spec runs.
137
- - **Text the case creates**: `[asrun] <something>`.
140
+ - **Text the case creates**: `<prefix> <something> (<run stamp>)` (prefix from `package.json`
141
+ `"asrun.testDataPrefix"`, default `[asrun]`). The recipe says how to build it; it never records the stamp itself.
138
142
  - **A value that has to exist** (an existing name, a search term): the recipe says what it must be like ("a name
139
143
  that returns a result") and notes the observed value as plain text `(seen on <profile>: "<value>")`. Do not put a
140
144
  brittle literal in as the only truth. One claim carries one signal: when a `[non-blocking]` TODO under the recipe
@@ -37,10 +37,15 @@ spec afterwards (see the report). Healing (below) is the exception: it reads the
37
37
  and the spec, and runs only the profiles that are logged in, and only when the report is not enough.
38
38
 
39
39
  ## Before you start
40
- Read the workspace `AGENTS.md`: its §2 holds the project rules, and it points at the framework baseline. If
41
- `.claude/skills/generating-specs.md` exists in the workspace, read it and apply it after this skill. Then apply
42
- `AGENTS.md` §2, which wins over both (the baseline, "Precedence"). F1–F6 and R2 always win. This is not a stop
43
- condition: continue with the procedure below.
40
+ Run `pnpm asrun rules --json` (or `pnpm asrun rules` for people): it prints the framework baseline, the project's
41
+ `AGENTS.md` §2 rules and the project settings (the test-data prefix). Those are the effective rules; if the command is
42
+ unavailable, read the workspace `AGENTS.md` directly. Also read `AGENTS.md` §2, and `.claude/skills/generating-specs.md`
43
+ in the workspace, applying the override after this skill and §2 after both (the baseline, "Precedence"). F1–F6 and R2
44
+ always win. **Never turn a project rule into a question:** a difference between §2 and a framework default is not a
45
+ conflict; apply §2 and, if a row's id does not fit its text, apply the text and report the mismatch. This is not a stop
46
+ condition: continue with the procedure below. If `pnpm asrun rules` exits 1 (an invalid §2 row or a prefix that the
47
+ document and `package.json` disagree on), do not guess and do not turn it into a case question: report its errors and
48
+ end with `RESULT: needs-input`; a person fixes `AGENTS.md` or `package.json` first.
44
49
 
45
50
  ## Inputs
46
51
  - The case id and the environment to explore in (`staging` or `production`). There is no default. If the
@@ -138,7 +143,7 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
138
143
  avoid. **Never perform a step that writes or needs a choice**: the agent does not write
139
144
  data, in staging or in production, and it does not hand off to a person here or wait for one. A rule never
140
145
  authorizes one of those steps. Write those steps
141
- of the spec from the `- Recipe:` lines under the step (positions, `[asrun]` names, control labels and roles that
146
+ of the spec from the `- Recipe:` lines under the step (positions, created names, control labels and roles that
142
147
  `/explore-case` recorded). Pages that only exist after such a step cannot be seen from here: write their locators
143
148
  from a fresh note first, then from the recipe, and mark them in the report as "observed by explore on <profile>" or
144
149
  "not verified on the page"; leave the first run of the spec (or `/heal-spec`) to confirm the latter. Do this for at
@@ -207,11 +212,13 @@ Decide this first, before exploring anything. The spec path is `tests/<id>.spec.
207
212
 
208
213
  ## Cases that write data
209
214
  Only when the case front matter lists environments under `writes` (for example `writes: [staging]`). Never add
210
- `--allow-writes` or set `ASRUN_ALLOW_WRITES`. Use the `records` fixture, `configureCase` and `emailStepAllowed` as
211
- described in `references/conventions.md`. The agent does not perform write steps while generating (see step 4); the
212
- spec does, when a person runs it in an environment that `writes` lists. Elsewhere the runner skips the case with a
213
- notice. Never send email in production; in other environments only if `profile.email` is true, otherwise skip that step and say so
214
- in the report.
215
+ `--allow-writes` or set `ASRUN_ALLOW_WRITES`. Use the `records` fixture, `configureCase`, `testDataName` and
216
+ `emailStepAllowed` as described in `references/conventions.md`. Every name the case creates is
217
+ `testDataName(expected.<key>)`, using the base literal from `## Values` and the project prefix from
218
+ `pnpm asrun rules`; use the composed value for the form, the locators and `records.track`. The agent does not perform
219
+ write steps while generating (see step 4); the spec does, when a person runs it in an environment that `writes` lists.
220
+ Elsewhere the runner skips the case with a notice. Never send email in production; in other environments only if
221
+ `profile.email` is true, otherwise skip that step and say so in the report.
215
222
 
216
223
  ## Healing and refining a spec
217
224
  Inputs: the case id and the environment (both required, there is no default: without an environment stop and say so),
@@ -47,8 +47,12 @@ and `@writes` for cases that write data (drives `--read-only`).
47
47
  A case that writes only in some environments is skipped (not failed) in the others, with a notice.
48
48
 
49
49
  ## Cases that write data (`writes: [<env>, ...]`)
50
- - Name everything the case creates `[asrun] <something>` (`TEST_DATA_PREFIX` in `@asrunjs/asrun/records`). `records.track`
51
- refuses other names, so cleanup can never touch real data.
50
+ - Name everything the case creates through `testDataName(base)` (`@asrunjs/asrun/records`), where `base` is the
51
+ literal from the case's `## Values`: it returns `<prefix> <base> (<UTC run stamp>)`, for example
52
+ `[asrun] Draft CMA (2026-11-09 12-34-56)`. The prefix is the project's `asrun.testDataPrefix` (default `[asrun]`,
53
+ printed by `pnpm asrun rules`). `records.track` refuses a name without the prefix, and the stamp keeps two runs
54
+ apart so a person can find and delete one run's data. Compose a name once and use that variable for the form, the
55
+ locators, the assertions and `records.track`; never pass a raw `expected.*` value as the created name.
52
56
  - Register data with the `records` fixture right after creating it, together with the function that removes it:
53
57
  `await records.track({kind: 'Document draft', name, where: '/app/documents'}, async () => {...delete it from the documents list...});`
54
58
  Everything tracked is removed after the test, newest first, also when the test failed. The remove function opens
@@ -16,10 +16,15 @@ Read the workspace `AGENTS.md` (hard rules and project rules) and `cases/_templa
16
16
  This skill writes `cases/*.md` only. It never writes specs, `profiles.yaml`, or anything under `tests/`.
17
17
 
18
18
  ## Before you start
19
- Read the workspace `AGENTS.md`: its §2 holds the project rules, and it points at the framework baseline. If
20
- `.claude/skills/making-cases.md` exists in the workspace, read it and apply it after this skill. Then apply `AGENTS.md`
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.
19
+ Run `pnpm asrun rules --json` (or `pnpm asrun rules` for people): it prints the framework baseline, the project's
20
+ `AGENTS.md` §2 rules and the project settings (the test-data prefix). Those are the effective rules; if the command is
21
+ unavailable, read the workspace `AGENTS.md` directly. Also read `AGENTS.md` §2, and `.claude/skills/making-cases.md`
22
+ in the workspace, applying the override after this skill and §2 after both (the baseline, "Precedence"). F1–F6 and R2
23
+ always win. **Never turn a project rule into a question:** a difference between §2 and a framework default is not a
24
+ conflict; apply §2 and, if a row's id does not fit its text, apply the text and report the mismatch. This is not a stop
25
+ condition: continue with the procedure below. If `pnpm asrun rules` exits 1 (an invalid §2 row or a prefix that the
26
+ document and `package.json` disagree on), do not guess and do not turn it into a case question: report its errors and
27
+ stop before writing anything; a person fixes `AGENTS.md` or `package.json` first.
23
28
 
24
29
  ## Inputs
25
30
  - Raw notes: pasted text or a file path. Any language, any quality.
@@ -43,7 +48,9 @@ continue with the procedure below.
43
48
  with a clear message until it is filled), never into `profiles.yaml`.
44
49
  5. **Ask before writing.** Show the user a short plan and the open questions in one message: the proposed split
45
50
  into cases, the classification (which steps write, which send email), and the gaps with your suggested default
46
- for each. Wait for answers. Do not write case files before that.
51
+ for each. Wait for answers. Do not write case files before that. **Never ask a project-wide question:** §2 and
52
+ `pnpm asrun rules` settle project policy (the test-data prefix, the language, a wait); apply them. Ask only about
53
+ this case's unknowns.
47
54
  6. **Split** (`references/splitting.md`) and **write** `cases/<id>.md` for each case from `cases/_template.md`.
48
55
  - Group folder: when the notes (or the user) name an area (documents, search, account...), put the cases in
49
56
  `cases/<group>/<name>.md` and set `id: <group>/<name>` in the front matter. Use one folder level and
@@ -14,9 +14,12 @@ dialog (read-only) but sending the share is `[writes]` and probably `[email]`. W
14
14
  ## What a case with writes must look like
15
15
  - Front matter: `writes: [<env>, ...]`, the environments the finished spec may write in (`[staging]`; add `production`
16
16
  only when the notes or the user say so), and a `writes-note` that says what is created, changed and removed, in one
17
- sentence ("Creates one draft document named '[asrun] ...', publishes it, deletes it at the end"). The `writes-note`
17
+ sentence ("Creates one draft document named '[asrun] ... (<run stamp>)', publishes it, deletes it at the end"). The `writes-note`
18
18
  is the only summary; there is no separate Data section, and the removal itself is a step in `## Steps`.
19
- - Everything created is named `[asrun] <something>` (the spec enforces this).
19
+ - Everything created is named `<prefix> <something> (<run stamp>)`, where the prefix is the project's
20
+ `asrun.testDataPrefix` (default `[asrun]`; `pnpm asrun rules` prints it) and the stamp is the UTC time of the run
21
+ (`testDataName()` builds it). The case's `## Values` holds the base without the stamp; the spec composes it, so two
22
+ runs never collide and a person can find one run's data in the product.
20
23
  - The case ends by deleting what it created. If the notes do not say how to delete it, that is a gap: ask, and
21
24
  never guess a delete path. Until it is known, write the likeliest delete step ("Select the 'Delete' option of the
22
25
  item's menu on the list page.") and put a `TODO [blocking]: How is <thing> deleted?` block under that step (see
package/AGENTS.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This file is the binding baseline for every project that uses the AsRun framework. It describes no particular
4
4
  product: a project adds its own rules in its own `AGENTS.md` §2, and they win where they differ (see "Precedence").
5
- Its hard rules carry ids (`F1`..`F10`, `B1`..`B4`, `A1`..`A6`).
5
+ Its hard rules carry ids (`F1`..`F11`, `B1`..`B4`, `A1`..`A6`, `R1`..`R4`).
6
6
 
7
7
  Paths are relative to the **workspace root**: the project that owns `profiles.yaml`, `cases/` and `tests/`. In a
8
8
  project that uses asrun, the framework files are in `node_modules/@asrunjs/asrun` (an installed dependency: never edit it).
@@ -19,9 +19,16 @@ Rules are applied in this order; a later layer wins where they conflict.
19
19
  own procedure.
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
- 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 and R2; `pnpm asrun doctor` checks the table. A rule file never
24
- renames the structural tokens under "Fixed tokens".
22
+ A project writes §2 only for what differs from the defaults here. A row names a baseline rule and says whether it
23
+ `narrows` (stricter) or `replaces` (a different default) it; a rule with no baseline counterpart gets a project id
24
+ (`P1`, `P2`, ...) and `adds`. `replaces` is refused for F1–F6 and R2; `pnpm asrun doctor` checks the table and
25
+ `pnpm asrun rules` prints the effective set. A rule file never renames the structural tokens under "Fixed tokens".
26
+
27
+ **A project rule is applied, never questioned.** §2 wins over the baseline defaults by definition, and a `P<n>` rule
28
+ stands next to them: a difference between §2 and a framework default (a naming prefix, a language, a wait) is not a
29
+ conflict and never becomes a TODO, a marker or a question in a case. The only refusal is a §2 row that weakens
30
+ F1–F6/R2, and `doctor` reports that before any run. Options the framework needs at run time (the test-data prefix)
31
+ live in `package.json` `"asrun"`, so the document and the code read the same value.
25
32
 
26
33
  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
34
  never what is true (`R1`), and a rule that conflicts with the rest of the case is a question, not an override.
@@ -57,6 +64,12 @@ never what is true (`R1`), and a rule that conflicts with the rest of the case i
57
64
  written in English by default. A project replaces this in §2 of its own `AGENTS.md` (for example Simplified Chinese
58
65
  for case prose and reports). Code, comments, identifiers, commit messages and the structural tokens under
59
66
  "Fixed tokens" always stay in English.
67
+ - **F11 — Test data is findable per run.** A step that writes names what it creates with the project's test-data
68
+ prefix and a run stamp: `testDataName(base)` in `@asrunjs/asrun/records` builds `<prefix> <base> (<YYYY-MM-DD
69
+ HH-mm-ss>)`, the UTC time of the run id (so it matches `reports/<runId>` and the ledger). The prefix is
70
+ `asrun.testDataPrefix` in the project's `package.json` (default `[asrun]`). Two runs never share a name, and a
71
+ person can search the product for one run's data to edit or delete it by hand. The case's `## Values` holds the base
72
+ literal without the stamp; the spec composes it, and a check on the created item uses the composed name.
60
73
 
61
74
  ## Browser exploration rules (`B`)
62
75
  These apply to every agent that drives a browser to explore pages for cases or specs (`making-cases`,
@@ -160,7 +173,7 @@ This file holds the rules. The exact shape those rules apply to is documented on
160
173
  - `.claude/skills/exploring-cases/references/tools.md`: which browser tools the `playwright-test` MCP can run, the
161
174
  substitute for each one it only lists, and the arguments every call has to carry. Read it before the first browser
162
175
  call of a run.
163
- - `docs/commands.md`: every command and flag.
176
+ - `docs/commands.md`: every command and flag, including `pnpm asrun rules`.
164
177
  - `docs/cases.md`: the same case format for the people who write cases by hand.
165
178
  - `docs/agents/framework-dev.md`: the framework's own layout and how to change it.
166
179
  The project's own `AGENTS.md` §2 (project rules) and §3 (project facts) still win over the documentation where they
@@ -173,6 +186,8 @@ differ, and `F1`–`F6` and `R2` win over everything.
173
186
  - `tests/**/*.spec.ts` generated Playwright specs (output of the AI workflow), one per case, same folder as the case,
174
187
  with their `*-snapshots/` baselines next to them
175
188
  - `profiles.yaml` one entry per environment: URL, account label, tags, environment (connection settings only)
189
+ - `package.json` npm scripts and the framework dependency; its `"asrun"` object holds project settings
190
+ (for example `testDataPrefix`)
176
191
  - `AGENTS.md` the project's own rules: they point at this baseline, and §2 holds what differs
177
192
  - `.auth/` `reports/` `.tmp/` `.explore/` local state, never committed
178
193
 
package/README.md CHANGED
@@ -138,11 +138,14 @@ Rules combine in this order, and a later layer wins where they conflict:
138
138
  2. The framework baseline, `node_modules/@asrunjs/asrun/AGENTS.md`.
139
139
  3. The framework skill.
140
140
  4. Your override, `.claude/skills/<skill>.md`.
141
- 5. Your own `AGENTS.md` §2: a table of rule ids (`F7`–`F10`, `R1`, `R3`, `R4`) with `narrows` or `replaces`, for example
142
- the artifact language.
141
+ 5. Your own `AGENTS.md` §2: a table of rule ids (`F7`–`F11`, `R1`, `R3`, `R4`) with `narrows` or `replaces`, and
142
+ project ids (`P1`, ...) with `adds` for a rule the baseline does not have, for example the artifact language or the
143
+ test-data prefix (`package.json` `"asrun"`). A difference from the baseline is applied, never turned into a
144
+ question.
143
145
  6. A case's `## Rules`: how to run that one case, never what is true.
144
146
 
145
- `pnpm asrun doctor` validates the table and lists the overrides it sees. An update never rewrites your rules:
147
+ `pnpm asrun doctor` validates the table and `pnpm asrun rules` prints the effective set (the AI steps read it before
148
+ they apply anything). An update never rewrites your rules:
146
149
  `init --force` refuses `AGENTS.md`, `CLAUDE.md` and `.claude/skills/**` without `--reset-rules`. Never edit the
147
150
  framework package itself: an update replaces it, and only the override files are yours. The AI tool stays configurable
148
151
  too: `.mcp.json` (Claude Code), `opencode.jsonc` (OpenCode and its model), `.claude/settings.json` (sandbox
@@ -193,7 +196,8 @@ the top bar. The check never blocks a command and stays quiet when the registry
193
196
  Tests run against real sites, so the defaults are strict:
194
197
 
195
198
  - **Read-only unless the case says otherwise.** A case may create, edit or delete data only in the environments it
196
- lists under `writes`, and only data of the test accounts, named `[asrun] ...`, which it must clean up. Elsewhere the
199
+ lists under `writes`, and only data of the test accounts, named with the project's prefix (default `[asrun]`) and a
200
+ run stamp such as `[autotest] Draft CMA (2026-11-09 12-34-56)`, which it must clean up. Elsewhere the
197
201
  case is skipped with a notice, not failed.
198
202
  - **A person confirms non-staging writes on every run** by typing the environment name, or in a dialog in the UI.
199
203
  `--read-only` leaves those cases out; `--allow-writes=<env>` is for unattended runs, and AI agents never use it.
@@ -202,8 +206,8 @@ Tests run against real sites, so the defaults are strict:
202
206
  the browser, in an attended exploration. Logins are always a person's job, 2FA included.
203
207
  - **A failing baseline is a person's decision.** An agent never updates a baseline to make a difference disappear.
204
208
 
205
- The rules that enforce this are `F1`–`F10` in [AGENTS.md](./AGENTS.md); a project can tighten or replace the last
206
- four, never the first six.
209
+ The rules that enforce this are `F1`–`F11` in [AGENTS.md](./AGENTS.md); a project can tighten or replace the last
210
+ five, never the first six.
207
211
 
208
212
  ## Documentation
209
213
 
@@ -131,7 +131,7 @@ export interface CaseReadySignal {
131
131
  }
132
132
  /**
133
133
  * One `- Recipe (phase): ...` bullet under a step: what a person did for that step during `/explore-case --attended`,
134
- * as a general recipe (positions, `[asrun]` names, control labels). It is not test data: the asserted values live in
134
+ * as a general recipe (positions, created names, control labels). It is not test data: the asserted values live in
135
135
  * `## Values`.
136
136
  */
137
137
  export interface CaseRecipe {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Project-wide settings that the framework needs at run time, read from the `asrun` object of the workspace's
3
+ * `package.json`:
4
+ *
5
+ * ```json
6
+ * "asrun": { "testDataPrefix": "[autotest]" }
7
+ * ```
8
+ *
9
+ * The file is the project's own: `pnpm asrun init` merges it and never replaces it. A project that does not set an
10
+ * option gets the default below, so an existing workspace keeps working unchanged.
11
+ */
12
+ export interface WorkspaceConfig {
13
+ /** The prefix every piece of data a case creates carries. Default `[asrun]`. */
14
+ testDataPrefix: string;
15
+ }
16
+ /** The frame's prefix: unmistakably test data, so cleanup can never touch real data. */
17
+ export declare const DEFAULT_TEST_DATA_PREFIX = "[asrun]";
18
+ /**
19
+ * Reads and validates the project settings. Throws an Error whose message says what to fix, so `doctor` and `rules`
20
+ * can report it and a spec that imports it fails with the same sentence instead of a confusing one.
21
+ */
22
+ export declare function readWorkspaceConfig(): WorkspaceConfig;
23
+ /** The prefix every name a case creates must start with. Reads `package.json`; default `[asrun]`. */
24
+ export declare function testDataPrefix(): string;
25
+ /** Forget the cached settings (a tool that edits `package.json` in the same process). */
26
+ export declare function resetWorkspaceConfig(): void;
@@ -0,0 +1,51 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { ROOT } from './paths.js';
4
+ /** The frame's prefix: unmistakably test data, so cleanup can never touch real data. */
5
+ export const DEFAULT_TEST_DATA_PREFIX = '[asrun]';
6
+ /** A bracketed lowercase word: `[asrun]`, `[autotest]`, `[qa_team]`. Easy to search for in the product. */
7
+ const PREFIX = /^\[[a-z0-9][a-z0-9_-]*\]$/i;
8
+ let cached = null;
9
+ function readPackageJson() {
10
+ try {
11
+ const parsed = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
12
+ return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
13
+ ? parsed
14
+ : null;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ /**
21
+ * Reads and validates the project settings. Throws an Error whose message says what to fix, so `doctor` and `rules`
22
+ * can report it and a spec that imports it fails with the same sentence instead of a confusing one.
23
+ */
24
+ export function readWorkspaceConfig() {
25
+ if (cached !== null)
26
+ return cached;
27
+ const pkg = readPackageJson();
28
+ const section = pkg?.asrun;
29
+ if (section !== undefined && (section === null || typeof section !== 'object' || Array.isArray(section))) {
30
+ throw new Error('package.json: "asrun" must be an object, for example "asrun": { "testDataPrefix": "[autotest]" }.');
31
+ }
32
+ const raw = section?.testDataPrefix;
33
+ if (raw !== undefined && typeof raw !== 'string') {
34
+ throw new Error('package.json: "asrun.testDataPrefix" must be a string like "[asrun]".');
35
+ }
36
+ const prefix = (raw ?? DEFAULT_TEST_DATA_PREFIX).trim();
37
+ if (!PREFIX.test(prefix)) {
38
+ throw new Error(`package.json: "asrun.testDataPrefix" is "${prefix}"; use a bracketed word such as "[asrun]" or "[autotest]" ` +
39
+ '(letters, digits, "-" and "_" inside the brackets).');
40
+ }
41
+ cached = { testDataPrefix: prefix };
42
+ return cached;
43
+ }
44
+ /** The prefix every name a case creates must start with. Reads `package.json`; default `[asrun]`. */
45
+ export function testDataPrefix() {
46
+ return readWorkspaceConfig().testDataPrefix;
47
+ }
48
+ /** Forget the cached settings (a tool that edits `package.json` in the same process). */
49
+ export function resetWorkspaceConfig() {
50
+ cached = null;
51
+ }
@@ -4,7 +4,7 @@ import { type Profile } from './profiles.js';
4
4
  export interface TrackedData {
5
5
  /** What it is, e.g. "Document draft". */
6
6
  kind: string;
7
- /** Its name in the product. Must start with `[asrun]`, so cleanup can never touch real data. */
7
+ /** Its name in the product. Must start with the project's prefix, so cleanup can never touch real data. */
8
8
  name: string;
9
9
  /** Where a person finds it by hand if the cleanup fails, e.g. "/app/documents". */
10
10
  where?: string;
@@ -2,7 +2,8 @@ import { expect, test as base } from '@playwright/test';
2
2
  import { getProfile } from './profiles.js';
3
3
  import { caseWrites, caseWritesIn, hasCaseFile, readCaseMeta } from './cases.js';
4
4
  import { acquireAccountLock } from './account-lock.js';
5
- import { ledgerFile, TEST_DATA_PREFIX, writeLedger } from './records.js';
5
+ import { testDataPrefix } from './config.js';
6
+ import { ledgerFile, writeLedger } from './records.js';
6
7
  import { caseIdFromFile } from './run-summary.js';
7
8
  import { ALLOW_WRITES_ENV, isProtectedEnv } from './write-gate.js';
8
9
  import { checkSession, isLoggedOutUrl, parseNotLoggedIn, sessionProblemMessage, signinCommand, } from './session.js';
@@ -76,9 +77,11 @@ export const test = base.extend({
76
77
  const save = () => writeLedger(file, entries.map((item) => item.entry));
77
78
  await use({
78
79
  async track(data, remove) {
79
- if (!data.name.startsWith(TEST_DATA_PREFIX)) {
80
- throw new Error(`Test data must be named with the prefix "${TEST_DATA_PREFIX}" (got "${data.name}"). ` +
81
- `Only data that is clearly a test's own may be created and removed.`);
80
+ const prefix = testDataPrefix();
81
+ if (!data.name.startsWith(prefix)) {
82
+ throw new Error(`Test data must be named with the project's prefix "${prefix}" (got "${data.name}"). ` +
83
+ `Set "asrun.testDataPrefix" in package.json when the project uses another marker, and build the name ` +
84
+ 'with testDataName() so two runs never share it.');
82
85
  }
83
86
  entries.push({
84
87
  entry: {
@@ -1,4 +1,7 @@
1
- /** Every piece of data a test creates carries this prefix, so it can never be mistaken for real data. */
1
+ /**
2
+ * The default prefix of data a test creates. The project's `package.json` may set another one
3
+ * (`"asrun": { "testDataPrefix": "[autotest]" }`); always read the effective value with `testDataPrefix()`.
4
+ */
2
5
  export declare const TEST_DATA_PREFIX = "[asrun]";
3
6
  export type RecordStatus = 'open' | 'cleaned' | 'leftover';
4
7
  /** One piece of test data a case created, as written to the ledger. */
@@ -10,7 +13,7 @@ export interface LedgerEntry {
10
13
  caseId: string;
11
14
  /** What kind of thing it is, e.g. "Document draft". */
12
15
  kind: string;
13
- /** Its name in the product; always starts with TEST_DATA_PREFIX. */
16
+ /** Its name in the product; always starts with the project's test-data prefix (`testDataPrefix()`). */
14
17
  name: string;
15
18
  /** Where a person can find and remove it by hand, e.g. "/app/documents". */
16
19
  where: string | null;
@@ -20,6 +23,18 @@ export interface LedgerEntry {
20
23
  createdAt: string;
21
24
  }
22
25
  export declare const RECORDS_DIR: string;
26
+ /** The run stamp appended to every name a test creates: the run id's UTC time (`2026-11-09 12-34-56`). */
27
+ export declare function testDataStamp(now?: Date): string;
28
+ /**
29
+ * The name of one piece of test data: `<base> (<run stamp>)`, for example
30
+ * `[autotest] Draft CMA (2026-11-09 12-34-56)`.
31
+ *
32
+ * The stamp is the run id's UTC time, so it matches the report folder and the ledger entry, and two runs never
33
+ * share a name: a person can search the product for the run's own data, edit it or delete it by hand. The base is
34
+ * the case's literal from `## Values`; it must start with the project's prefix. Calling this twice with the same base
35
+ * (a title before and after an edit, a retry) returns the same name.
36
+ */
37
+ export declare function testDataName(base: string): string;
23
38
  /** The ledger of one test: `.tmp/records/<runId>/<file>.json`. One file per test, so parallel workers never share a file. */
24
39
  export declare const ledgerFile: (runId: string, testId: string) => string;
25
40
  export declare function writeLedger(file: string, entries: LedgerEntry[]): void;