saffron-ai 0.6.0 → 0.7.1
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/CHANGELOG.md +270 -0
- package/README.md +1 -0
- package/dist-pkg/cli.js +121 -112
- package/package.json +1 -1
- package/skills/saffron/SKILL.md +4 -1
- package/skills/saffron/references/config.md +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,270 @@ All notable changes to Saffron, in one place: the `saffron-ai` runner (npm),
|
|
|
4
4
|
the VS Code extension (`ChathurangaJayasinghe.saffron-vscode`) and the
|
|
5
5
|
JetBrains plugin (`ai.saffron.jetbrains`). Dates are publish dates.
|
|
6
6
|
|
|
7
|
+
## 0.7.1
|
|
8
|
+
|
|
9
|
+
Released 2026-09-20.
|
|
10
|
+
|
|
11
|
+
Runner
|
|
12
|
+
|
|
13
|
+
- **`expectUrl` checks the active page, not any open tab.** A URL sitting in
|
|
14
|
+
a leftover tab could satisfy the assertion for the wrong reason. *A
|
|
15
|
+
recording made before tab openers were marked, with a slow popup and
|
|
16
|
+
`retries: 0`, can now fail where it used to pass: re-record it, or leave
|
|
17
|
+
the default retry, which re-follows the newest tab.*
|
|
18
|
+
- **A changed table value only replays if the recording really uses it.**
|
|
19
|
+
Matching key sets used to be enough, so a recording that still carried the
|
|
20
|
+
old value as a literal, or never referenced the cell, stayed "valid" and
|
|
21
|
+
typed stale data. The old value is looked for only where a value can live
|
|
22
|
+
(typed, matched or named text, with tokens removed), so an empty cell or a
|
|
23
|
+
short value like "on" does not force a needless re-record.
|
|
24
|
+
- **Propagation never rewrites an assertion**, on either side: assertion
|
|
25
|
+
steps and assertion actions are skipped when a fix is extracted and when
|
|
26
|
+
it is applied, the step wording must match, and a locator inside a frame
|
|
27
|
+
is a different locator. When one heal sends the same locator to more than
|
|
28
|
+
one place, nothing propagates and `saffron accept` says so.
|
|
29
|
+
- **`saffron prune` and `saffron status` respect features outside the
|
|
30
|
+
configured directory.** A scenario run as `saffron run e2e/login.feature`
|
|
31
|
+
had its recordings listed as orphans. Feature files named by existing
|
|
32
|
+
recordings are now inspected too, and one that fails to parse blocks the
|
|
33
|
+
prune like any other.
|
|
34
|
+
- **Step reuse no longer brings back a stale table value.** Changing `alice`
|
|
35
|
+
to `bob` correctly invalidated the scenario's cache, and step reuse then
|
|
36
|
+
seeded the same actions straight back, matching on table keys alone: a
|
|
37
|
+
verified proposal at zero AI calls whose table said `bob` while its
|
|
38
|
+
actions still typed and asserted `alice`. Seeds now pass the same rule as
|
|
39
|
+
cache validation, and a step with no table of its own is refused when it
|
|
40
|
+
carries another step's old value as a literal. The old value is looked
|
|
41
|
+
for in locators built from data as well (a test id like `user-alice`,
|
|
42
|
+
fallback selectors, a frame, a response URL pattern), not only in typed
|
|
43
|
+
and named text: a fill can be parameterized while the assertion beside it
|
|
44
|
+
still targets the old user.
|
|
45
|
+
- **A feature edit cannot land on the wrong step.** Edits address steps by
|
|
46
|
+
index, so a step inserted while a proposal waited shifted every index
|
|
47
|
+
after it, and an edit meant for "I click login" rewrote the new step.
|
|
48
|
+
`saffron accept --with-feature-edit` now compares the scenario with the
|
|
49
|
+
steps the proposal was recorded against and refuses before anything is
|
|
50
|
+
consumed: the file, the cache and the proposal stay as they were.
|
|
51
|
+
- **A skipped feature edit no longer reaches the cache.** When one edit
|
|
52
|
+
applied, the accepted cache was given the new text of every edit,
|
|
53
|
+
including a Background edit that was correctly skipped, so the feature and
|
|
54
|
+
its cache disagreed at once. Only edits that landed are synced. Two edits
|
|
55
|
+
that reach the same StepSet line through two invocations are applied once
|
|
56
|
+
when they agree, and both refused when they do not, naming the two
|
|
57
|
+
wordings: the line can only say one thing.
|
|
58
|
+
- `saffron status` parses each feature file once. The vocabulary and the
|
|
59
|
+
orphan scan each parsed the whole project again; both now reuse what
|
|
60
|
+
status already read. IDE panels call status on every refresh.
|
|
61
|
+
|
|
62
|
+
Editors (VS Code extension 0.2.5)
|
|
63
|
+
|
|
64
|
+
- **Completion badges and duplicate-step diagnostics follow cache creation
|
|
65
|
+
and deletion.** Only changes to existing JSON files were watched, so
|
|
66
|
+
accepting a first recording or pruning one left the vocabulary stale until
|
|
67
|
+
something else triggered a rebuild. Recordings are watched for all three
|
|
68
|
+
events (and only recordings, not every JSON file), and the watcher is
|
|
69
|
+
disposed with the extension.
|
|
70
|
+
- **Vocabulary is per project in a multi-root workspace.** Step sets,
|
|
71
|
+
wording and recorded badges were built once for the whole workspace, so
|
|
72
|
+
project B's `Login` step set could answer go-to-definition in project A,
|
|
73
|
+
and a step recorded in A showed as recorded in B.
|
|
74
|
+
- **The inline diff button on a proposal row works.** An inline action
|
|
75
|
+
passes the tree item, not the file path the row click passes; both forms
|
|
76
|
+
are accepted, and the project guard applies to both.
|
|
77
|
+
|
|
78
|
+
## 0.7.0
|
|
79
|
+
|
|
80
|
+
Released 2026-09-19.
|
|
81
|
+
|
|
82
|
+
New: `saffron diff`, `saffron prune`, a configurable `pollIntervalMs`, and a
|
|
83
|
+
configuration reference in the docs. The rest of this release is correctness
|
|
84
|
+
work from several rounds of external review.
|
|
85
|
+
|
|
86
|
+
**Upgrading.** Three fixes can change what an existing suite does, all in
|
|
87
|
+
the direction of telling the truth:
|
|
88
|
+
|
|
89
|
+
- A scenario that was green because replay quietly clicked a near-match
|
|
90
|
+
("Delete all orders" for a missing "Delete"), or because a `Then` step
|
|
91
|
+
never checked anything, now fails and heals or asks to be re-recorded.
|
|
92
|
+
- Feature files in subdirectories get a new cache directory name (a short
|
|
93
|
+
digest of their path is appended), so their scenarios record again once.
|
|
94
|
+
Files directly under the features root are unaffected. `saffron prune`
|
|
95
|
+
lists the old directories afterwards.
|
|
96
|
+
- `saffron status --json` gains `cacheState` and `orphans`; nothing was
|
|
97
|
+
removed.
|
|
98
|
+
|
|
99
|
+
Runner
|
|
100
|
+
|
|
101
|
+
Correctness fixes from an external review of the runner. Four of these
|
|
102
|
+
could let a run look green or yellow when the goal was never checked.
|
|
103
|
+
|
|
104
|
+
- **Secrets could reach your feature files.** Machine-applicable feature
|
|
105
|
+
edits were the one agent output that skipped `{env:VAR}` masking, so
|
|
106
|
+
`saffron accept --with-feature-edit` could write a resolved password into
|
|
107
|
+
a scenario. Feature edits, the narrative and the suggested edit are now
|
|
108
|
+
masked everywhere they are stored or reported.
|
|
109
|
+
- **An assertion that recorded nothing now fails.** A `Then` step with an
|
|
110
|
+
empty action list used to replay as a pass, so a scenario could be green
|
|
111
|
+
without its verdict ever being checked. Replay fails such a step, and the
|
|
112
|
+
recording is filed unverified. *This can turn an existing green scenario
|
|
113
|
+
red: that assertion was never being checked. Re-record it.*
|
|
114
|
+
- **Negative assertions consider every recorded locator.** `expectNotVisible`
|
|
115
|
+
returned as soon as one candidate was hidden, so a real failure could hide
|
|
116
|
+
behind a missing primary locator while a fallback was visible.
|
|
117
|
+
- **Recorded names match exactly first.** A step recorded against "Save"
|
|
118
|
+
could click "Save draft" when it came first in the DOM. Substring matching
|
|
119
|
+
remains the fallback for reading the page. The agent is no longer told to
|
|
120
|
+
shorten names.
|
|
121
|
+
- **Cache files are unique per scenario.** The cache directory now includes
|
|
122
|
+
the feature file's directories, so two same-named files in different
|
|
123
|
+
folders stop overwriting each other's recordings. Flat layouts are
|
|
124
|
+
unchanged. A run refuses to start if two scenarios would still share a file.
|
|
125
|
+
- **Suspicious recordings are saved unverified.** The check ran after the
|
|
126
|
+
proposal was written, so the file said verified while the run said
|
|
127
|
+
otherwise, and `accept --all` took it.
|
|
128
|
+
- **An unverified proposal shows the proof replay's timeline**, not the
|
|
129
|
+
agent session's, so the report says which step could not replay instead
|
|
130
|
+
of marking every step passed.
|
|
131
|
+
- **A step that changes between an action and an assertion is stale.** Cache
|
|
132
|
+
matching compared the step's words but not its keyword, so turning
|
|
133
|
+
`When I see the total` into `Then I see the total` kept the old recording
|
|
134
|
+
and replayed the action instead of checking anything.
|
|
135
|
+
- **Waiting for a new tab happens where a tab opens.** Every click paid a
|
|
136
|
+
150ms wait, which taxed scenarios that never use tabs and still lost the
|
|
137
|
+
race with a slower popup. The proof replay now records which action opens
|
|
138
|
+
a tab, and only that action waits, for as long as any other action may
|
|
139
|
+
take. A five-step login scenario replays in about a third of the time.
|
|
140
|
+
- **`saffron status` distinguishes a valid cache from a stale one.** It
|
|
141
|
+
reported `cached` by asking whether a file existed, so a scenario whose
|
|
142
|
+
text had changed still looked recorded. Each scenario now carries
|
|
143
|
+
`cacheState`: `valid`, `stale`, `invalid` or `missing`, and the printed
|
|
144
|
+
view marks stale ones.
|
|
145
|
+
- **`saffron lsp` no longer reports a valid step set as defined twice** in
|
|
146
|
+
projects whose path contains a space: it compared a file system path with
|
|
147
|
+
a percent-encoded URL path.
|
|
148
|
+
|
|
149
|
+
- **`saffron diff` shows what a proposal changes**: the committed recording
|
|
150
|
+
against the proposed one, action by action, with a rewritten action shown
|
|
151
|
+
as one before-and-after pair and changed assertions counted separately.
|
|
152
|
+
The narrative is the agent's account of what it did; this is what a
|
|
153
|
+
reviewer is approving. It also appears in the HTML report and in both IDE
|
|
154
|
+
panels.
|
|
155
|
+
- **An assertion step must contain an assertion.** The earlier guard only
|
|
156
|
+
rejected an empty action list, so a `Then` that recorded a `waitFor` and
|
|
157
|
+
nothing else still passed.
|
|
158
|
+
- **A control that is found but cannot be used no longer hands the action to
|
|
159
|
+
a different control.** With a disabled "Save" next to an enabled "Save
|
|
160
|
+
draft", replay clicked the draft button. An interaction now stops at the
|
|
161
|
+
candidate that matched, so drift goes to the failure and healing path
|
|
162
|
+
instead of quietly doing something else.
|
|
163
|
+
- **Negative assertions check every match of every locator.** Checking only
|
|
164
|
+
the first match let a hidden first element vouch for a visible second one.
|
|
165
|
+
A locator that errors no longer counts as proof of absence.
|
|
166
|
+
- **Secrets are discovered through Examples rows.** A `{env:VAR}` reaching a
|
|
167
|
+
table cell or doc string through a placeholder was invisible to masking,
|
|
168
|
+
so it could survive into recordings, reports and feature edits.
|
|
169
|
+
- **Cache identity follows the path, not its spelling.** `admin/login.saffron`
|
|
170
|
+
and `admin-login.saffron` produced the same cache directory; nested files
|
|
171
|
+
now carry a digest of their path. The collision check also looks at the
|
|
172
|
+
whole project, because two features run separately never met before.
|
|
173
|
+
- **A recording that says a tab opens must see one.** The marker was
|
|
174
|
+
advisory: when no tab appeared the timeout was swallowed and the next
|
|
175
|
+
assertion ran against the old page. Tab openers are also captured while
|
|
176
|
+
recording now, not only during the proof replay, which could miss them.
|
|
177
|
+
- **An interaction never substitutes a different control.** A recorded name
|
|
178
|
+
that no longer matches exactly could still act on any control containing
|
|
179
|
+
it, so a missing "Delete" could click "Delete all orders" and the run
|
|
180
|
+
stayed green. Interactions now require the exact name; assertions and
|
|
181
|
+
captures keep the substring fallback, because reading the page is not
|
|
182
|
+
acting on it. *A scenario whose control was renamed now fails and heals
|
|
183
|
+
instead of passing.*
|
|
184
|
+
- **The proposal diff compares actions, not their descriptions.** Two
|
|
185
|
+
actions that read the same could differ where it counts: dropping a
|
|
186
|
+
response body check or repointing a selector from `#save` to `#delete`
|
|
187
|
+
both reported no change. Actions are compared field by field, and a
|
|
188
|
+
rewritten action now lists exactly which fields moved.
|
|
189
|
+
- **Repeated steps stay separate in the diff.** Steps were matched by their
|
|
190
|
+
wording, so a scenario that adds the same item twice showed no change when
|
|
191
|
+
the proposal dropped one of them.
|
|
192
|
+
- **A popup that arrives late is pinned on the action that opened it.** The
|
|
193
|
+
opener marker waited for the next recorded action, so a slow window was
|
|
194
|
+
blamed on whatever the agent did afterwards, and replay then waited for a
|
|
195
|
+
tab that action never opens. Only an interaction can be credited: a wait
|
|
196
|
+
or an assertion recorded between the click and the popup no longer takes
|
|
197
|
+
it, and the marker now follows the agent's actual tool calls instead of
|
|
198
|
+
inferring them. Any action marked as opening a tab waits for one, not
|
|
199
|
+
only clicks. A popup nobody can be credited with is dropped rather than
|
|
200
|
+
passed to a later action: one that arrives while the agent is idle and
|
|
201
|
+
the last interaction is too old, one that lands during a call that failed
|
|
202
|
+
or could have opened it itself, and one the agent opened with a new tab.
|
|
203
|
+
Only known observation tools (a snapshot, a wait, listing tabs) may vouch
|
|
204
|
+
for the click before them, and failed tool calls are now drained so they
|
|
205
|
+
cannot hold an attribution open.
|
|
206
|
+
- **`saffron prune` removes recordings nothing owns.** Deleting a scenario
|
|
207
|
+
or a feature file left its cache behind, where it stayed in git and no
|
|
208
|
+
reviewer could tell it from a live one. `saffron prune` lists every
|
|
209
|
+
orphaned cache and proposal with the reason, deletes nothing without
|
|
210
|
+
`--yes`, and `--check` exits 1 for CI. Ownership follows the path, so a
|
|
211
|
+
stale or unreadable cache that a live scenario claims is kept, and while
|
|
212
|
+
any feature file fails to parse prune refuses to judge anything.
|
|
213
|
+
`saffron status --json` carries the same list for IDE panels.
|
|
214
|
+
- **`pollIntervalMs` is configurable** (default 100ms, minimum 10). It sets
|
|
215
|
+
how often a polled assertion re-checks the page inside `actionTimeoutMs`:
|
|
216
|
+
lower reacts sooner, higher costs less on a busy CI box.
|
|
217
|
+
|
|
218
|
+
Docs
|
|
219
|
+
|
|
220
|
+
- **A configuration reference**, grouped by what you are tuning (timing,
|
|
221
|
+
project, runtime, the agent, gates) with every option's default, replacing
|
|
222
|
+
the paragraph that listed the keys in prose. It ends with how timing
|
|
223
|
+
actually works: Playwright's actionability wait, then polled assertions,
|
|
224
|
+
then action retries, and why there is no whole-scenario retry.
|
|
225
|
+
|
|
226
|
+
Editors (JetBrains plugin 0.2.2, VS Code extension 0.2.4)
|
|
227
|
+
|
|
228
|
+
- JetBrains plugin: **Accept Selected with nothing ticked accepted every
|
|
229
|
+
proposal.** Blank paths mean `--all` to the runner, so the deliberate
|
|
230
|
+
review workflow could be bypassed by clicking the wrong button. It now
|
|
231
|
+
does nothing and says so; bulk acceptance stays its own action.
|
|
232
|
+
- Both IDEs list **recordings no scenario owns any more**: a JetBrains
|
|
233
|
+
*Orphans* tab (grouped by cache and proposal, double-click to open one,
|
|
234
|
+
*Remove All* after a confirmation) and a VS Code *Orphaned recordings*
|
|
235
|
+
section with the same action in its title bar. Both name the reason, and
|
|
236
|
+
both say plainly when the installed runner is too old to report them
|
|
237
|
+
rather than claiming there are none. `prune` is also a JetBrains run
|
|
238
|
+
configuration command, where listing is the default.
|
|
239
|
+
- JetBrains plugin: **the Proposals tab could show an old diff beside a new
|
|
240
|
+
narrative.** Diffs were cached by file name, and a new run overwrites a
|
|
241
|
+
proposal under the same name, so Accept could promote something the
|
|
242
|
+
reviewer never read. A refresh now invalidates every diff, a late result
|
|
243
|
+
from before the refresh is discarded, and the cache lives on the UI thread.
|
|
244
|
+
- JetBrains plugin: **a path with a space stays one path.**
|
|
245
|
+
`features/order checkout.saffron` became two arguments, even for
|
|
246
|
+
right-clicked and ticked files. Paths are parsed like a command line, and
|
|
247
|
+
what the plugin writes itself is quoted to match.
|
|
248
|
+
- VS Code extension: **Run selected ran project B's files from project A**
|
|
249
|
+
in a multi-root workspace, with A's config and install. Selections are now
|
|
250
|
+
grouped by owning folder, one run per project, from the Explorer too. The
|
|
251
|
+
status-backed sections name the project they show, and *Select project*
|
|
252
|
+
switches it, instead of silently using the first folder. Switching
|
|
253
|
+
clears those sections at once rather than when the new status arrives:
|
|
254
|
+
rows left on screen stayed clickable, so a proposal ticked in project A
|
|
255
|
+
could be accepted in project B when both had the same relative file.
|
|
256
|
+
Ticks are bound to the project that listed them, and accept, reject,
|
|
257
|
+
run tagged and prune refuse to act while a status is still loading.
|
|
258
|
+
- VS Code extension: **the proposal diff could not start on Windows**, where
|
|
259
|
+
Node refuses to launch a `.cmd` without a shell. Status and diff now share
|
|
260
|
+
one launcher: the installed CLI runs under node with a plain argument
|
|
261
|
+
array, and only the npx fallback goes through cmd.exe, fully escaped.
|
|
262
|
+
- Both IDEs show the **proposal diff**: JetBrains under the narrative in the
|
|
263
|
+
Proposals tab, VS Code by opening the diff as a document when a proposal is
|
|
264
|
+
clicked or from its inline action.
|
|
265
|
+
- VS Code extension: runs the CLI as a **task with an argument array and the
|
|
266
|
+
owning folder's working directory**, instead of writing a command line
|
|
267
|
+
into a shared terminal. A file name with a dollar sign or a backtick is no
|
|
268
|
+
longer shell syntax, and a file from a second workspace folder runs
|
|
269
|
+
against that folder's config and install.
|
|
270
|
+
|
|
7
271
|
## 0.6.0
|
|
8
272
|
|
|
9
273
|
Released 2026-09-18.
|
|
@@ -20,6 +284,12 @@ Runner
|
|
|
20
284
|
- When a pending proposal fails its zero-AI replay and the agent records
|
|
21
285
|
again, the run now says which step failed and why.
|
|
22
286
|
|
|
287
|
+
Editors
|
|
288
|
+
|
|
289
|
+
- VS Code extension 0.2.3: the Marketplace listing is named "Saffron",
|
|
290
|
+
its short description fits without being cut off, and the overview
|
|
291
|
+
introduces the tool and how to get started before listing features.
|
|
292
|
+
|
|
23
293
|
## 0.5.7
|
|
24
294
|
|
|
25
295
|
Released 2026-09-17.
|
package/README.md
CHANGED