@asrunjs/asrun 0.11.0 → 0.12.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.
- package/.claude/commands/explore-case.md +2 -1
- package/.claude/commands/gen-spec.md +2 -1
- package/.claude/commands/heal-spec.md +2 -1
- package/.claude/commands/make-case.md +2 -1
- package/.claude/skills/exploring-cases/SKILL.md +14 -6
- package/.claude/skills/exploring-cases/references/case-rules.md +5 -0
- package/.claude/skills/exploring-cases/references/open-questions.md +8 -0
- package/.claude/skills/exploring-cases/references/user-handoff.md +12 -8
- package/.claude/skills/generating-specs/SKILL.md +17 -10
- package/.claude/skills/generating-specs/references/conventions.md +6 -2
- package/.claude/skills/making-cases/SKILL.md +12 -5
- package/.claude/skills/making-cases/references/writes-and-email.md +5 -2
- package/AGENTS.md +20 -5
- package/README.md +10 -6
- package/dist/lib/case-doc.d.ts +1 -1
- package/dist/lib/config.d.ts +26 -0
- package/dist/lib/config.js +51 -0
- package/dist/lib/fixtures.d.ts +1 -1
- package/dist/lib/fixtures.js +7 -4
- package/dist/lib/records.d.ts +17 -2
- package/dist/lib/records.js +32 -2
- package/dist/lib/rules.d.ts +57 -0
- package/dist/lib/rules.js +140 -0
- package/dist/scripts/cleanup.js +10 -1
- package/dist/scripts/cli.js +9 -1
- package/dist/scripts/doctor.js +27 -48
- package/dist/scripts/lint-specs.js +23 -1
- package/dist/scripts/rules.d.ts +1 -0
- package/dist/scripts/rules.js +148 -0
- package/docs/agents/case-format.md +8 -5
- package/docs/ai-workflow.md +21 -12
- package/docs/cases.md +10 -6
- package/docs/commands.md +22 -4
- package/docs/install.md +2 -1
- package/docs/theme-guide.html +24 -3
- package/docs/troubleshooting.md +11 -2
- package/docs/web-ui.md +12 -5
- package/package.json +2 -1
- package/templates/.claude/commands/explore-case.md +3 -1
- package/templates/.claude/commands/gen-spec.md +3 -1
- package/templates/.claude/commands/heal-spec.md +3 -1
- package/templates/.claude/commands/make-case.md +3 -1
- package/templates/AGENTS.md +17 -8
- package/templates/cases/_template.md +3 -3
- package/templates/package.json +4 -0
- package/ui/dist/assets/{dist--BXJInTq.js → dist-BX88bMYN.js} +1 -1
- package/ui/dist/assets/{dist-BMmNFIUH.js → dist-Bfv586Or.js} +1 -1
- package/ui/dist/assets/{dist-C9ra6_BD.js → dist-BjlbPf3t.js} +1 -1
- package/ui/dist/assets/{dist-ByLbEOlF.js → dist-CJnqQMvX.js} +1 -1
- package/ui/dist/assets/{dist-DTYj2vUD.js → dist-CaAcCNRS.js} +1 -1
- package/ui/dist/assets/{dist-BXNaYHeh.js → dist-Clj0wGFy.js} +1 -1
- package/ui/dist/assets/{dist-CTrwRc3Q.js → dist-D8TUm3Ai.js} +1 -1
- package/ui/dist/assets/{dist-CK8mow8u.js → dist-DUDqh_3e.js} +1 -1
- package/ui/dist/assets/{dist-CQS0So-i.js → dist-DkAx5a-Y.js} +1 -1
- package/ui/dist/assets/{dist-mV0_nQZe.js → dist-cuboTVh-.js} +1 -1
- package/ui/dist/assets/index-DaUQORCy.js +353 -0
- package/ui/dist/assets/index-j3W4BBCj.css +2 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-BzMqkmQ9.js +0 -348
- 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
|
-
|
|
19
|
-
|
|
20
|
-
`AGENTS.md`
|
|
21
|
-
|
|
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
|
|
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`.
|
|
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
|
|
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
|
|
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
|
|
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. ['[
|
|
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
|
|
118
|
-
created
|
|
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**:
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
`AGENTS.md`
|
|
43
|
-
|
|
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,
|
|
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
|
|
211
|
-
described in `references/conventions.md`.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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 `
|
|
51
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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`..`
|
|
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
|
|
23
|
-
`
|
|
24
|
-
|
|
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`–`
|
|
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
|
|
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
|
|
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`–`
|
|
206
|
-
|
|
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
|
|
package/dist/lib/case-doc.d.ts
CHANGED
|
@@ -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,
|
|
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
|
+
}
|
package/dist/lib/fixtures.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/lib/fixtures.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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: {
|
package/dist/lib/records.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
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
|
|
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;
|