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