Skip to content

feat(modify): --where <s-expr> for query-driven bulk modify (REQ-141)#380

Merged
avrabe merged 1 commit into
mainfrom
feat/modify-where-bulk
Jun 2, 2026
Merged

feat(modify): --where <s-expr> for query-driven bulk modify (REQ-141)#380
avrabe merged 1 commit into
mainfrom
feat/modify-where-bulk

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented Jun 1, 2026

What

Bringing a real project's requirement statuses in line with shipped code (the bulk draftimplemented task in #353) had no first-class tool — only a shell loop of per-ID rivet modify calls, which the reporter found silently no-op'd under redirection (a suspected reload/rewrite race between rapid-succession subprocesses).

rivet modify --where '<s-expr>' --set-* selects every artifact matching the same s-expression filter rivet query uses and applies the change in a single in-process pass — load once, validate every target up front (all-or-nothing), then write each affected file once. No subprocess re-spawn, so it cannot race the way the shell loop could.

rivet modify --where '(= status "draft")' --set-status implemented            # bulk apply
rivet modify --where '(and (type "requirement") (status "draft"))' \
             --set-status implemented --dry-run                                # preview
  • --where is mutually exclusive with a positional <ID> (clap-enforced).
  • --dry-run previews the match set, writes nothing.
  • An empty match set is a loud no-op (no artifacts match the --where filter) — silence is never read as success.
  • Reuses the existing sexpr_eval engine — no new filter dialect (design on existing patterns).

Verification

End-to-end on a scratch project: dry-run leaves files byte-identical; bulk apply flips only the matches (non-matches untouched) and prints modified N artifacts; empty match → loud no-op; ID + --where rejected. New rivet-cli/tests/modify_where.rs (4 cases, all green); clippy --all-targets + fmt clean; rivet validate → PASS.

Triage context (#353)

This closes the bulk --set-status / --where feature ask and addresses Part 3 (the racy shell loop) at its root — by removing the need for the loop entirely. Earlier parts of #353 were already handled: Part 1 (silent file-skip) by REQ-062, Part 4 (WARN noise) by #379/REQ-139. The remaining open item is the cross-command parse inconsistency filed as REQ-140 (maintainer design call).

Implements: REQ-141

🤖 Generated with Claude Code

…353)

Bringing a real project's statuses in line with shipped code (bulk
draft->implemented) had no first-class tool — only a shell loop of
per-ID `rivet modify` calls, which #353 reported silently no-op'ing
under redirection (a suspected reload/rewrite race between
rapid-succession subprocesses).

Add `rivet modify --where '<s-expr>' --set-*`: select every artifact
matching the same s-expression filter `rivet query` uses and apply the
change in a SINGLE in-process pass — load once, validate every target up
front (all-or-nothing), then write each affected file once. No
subprocess re-spawn, so it cannot race the way the shell loop could.

- `--where` is mutually exclusive with a positional <ID> (clap-enforced).
- `--dry-run` previews the match set, writes nothing.
- An empty match set is a loud no-op ("no artifacts match the --where
  filter") so an agent never reads silence as success.
- Reuses the existing `sexpr_eval` engine — no new filter dialect.

Verified end-to-end on a scratch project (dry-run byte-identical, bulk
apply flips only matches, empty-match no-op, ID+--where rejected). New
`modify_where.rs` integration test (4 cases); clippy --all-targets +
fmt clean; `rivet validate` PASS.

Implements: REQ-141, REQ-007
Refs: REQ-141

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

📐 Rivet artifact delta

Change Count
Added 1
Removed 0
Modified 0
Downstream impacted (depth ≤ 5) 0

Graph

graph LR
  REQ_141["REQ-141"]:::added
  classDef added fill:#d4edda,stroke:#28a745,color:#155724
  classDef removed fill:#f8d7da,stroke:#dc3545,color:#721c24
  classDef modified fill:#fff3cd,stroke:#ffc107,color:#856404
  classDef overflow fill:#e2e3e5,stroke:#6c757d,color:#495057,stroke-dasharray: 3 3
Loading
Added
  • REQ-141

📎 Full HTML dashboard attached as workflow artifact rivet-delta-pr-380download from the workflow run.

Posted by rivet-delta workflow. The graph shows only changed artifacts; open the HTML dashboard (above) for full context.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rivet Criterion Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: f29cd39 Previous: ab298f5 Ratio
store_lookup/100 2222 ns/iter (± 73) 1664 ns/iter (± 4) 1.34
store_lookup/1000 25996 ns/iter (± 128) 19453 ns/iter (± 50) 1.34
traceability_matrix/1000 61475 ns/iter (± 166) 41005 ns/iter (± 99) 1.50
query/100 749 ns/iter (± 1) 612 ns/iter (± 3) 1.22
query/1000 7376 ns/iter (± 33) 5134 ns/iter (± 100) 1.44

This comment was automatically generated by workflow using github-action-benchmark.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@avrabe avrabe merged commit 0f60ad4 into main Jun 2, 2026
21 of 39 checks passed
@avrabe avrabe deleted the feat/modify-where-bulk branch June 2, 2026 00:04
avrabe added a commit that referenced this pull request Jun 2, 2026
…#382)

The `modify --where` after_help examples I added in #380 used an invalid
filter shorthand — `(type "requirement")` / `(status "draft")` — where
the head symbol must be an operator, not the field name. Run verbatim
they error with "unknown form 'type'/'status'". Corrected to the
canonical `(= field "value")` form, both examples now run clean:

  rivet modify --where '(= status "draft")' --set-status implemented
  rivet modify --where '(and (= type "requirement") (= status "draft"))' …

Verified both examples execute (dry-run) without a parse error. The
broader discoverability gap (the parse error shows no example of the
common field-equality form) is tracked in #381.

Implements: REQ-141
Refs: REQ-007

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
avrabe added a commit that referenced this pull request Jun 2, 2026
…ke (#389) (#390)

`graph_type_filter_renders_when_under_budget` fetched
`/graph?types=requirement` with the default 5s read timeout. That
endpoint does a BFS + layout over the dogfood corpus (~742 nodes / 1477
edges), which on a loaded CI runner can brush past 5s; `fetch_with_timeout`
parses a timed-out/empty response's status as 0, so the timeout surfaced
as `status == 0` and failed the `== 200` assertion. Same chronic flake
the focus-graph tests already fixed with a 15s budget — this one test
was missed. It falsely failed CI on #380, #387, and #388.

Switch it to `fetch_with_timeout(..., 15s)`, matching the other graph
endpoints. The test asserts on the response shape (SVG or budget
message), not a hard latency bound, so the wider timeout is safe.
Verified locally: passes 3/3 runs.

Trace: skip
Closes: #389

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
avrabe added a commit that referenced this pull request Jun 2, 2026
…REQ-151, #353) (#399)

#353 feature ask: every invocation can emit WARN-level log lines before a
command's real output (e.g. "could not load externals: …"), noise for
scripted/agent consumption — especially with `--format json`. There was
`-v/--verbose` to raise the log level but no way to lower it below the
default `warn`.

Add a global `-q/--quiet` (mutually exclusive with `--verbose`) that sets
the log filter to `error`: the WARN preamble is suppressed while the
command's own stdout and hard-error reporting stay intact. Pairs with
`--format json` for clean machine-consumable output.

Verified: on a project with a misconfigured external, default emits
`[WARN rivet] could not load externals: …` but `--quiet` emits nothing on
stderr and stdout still lists the artifact; a hard config error is still
reported under `--quiet`. New `cli_commands::quiet_suppresses_warn_preamble`
test; clippy --all-targets + fmt clean; rivet validate + docs check PASS.

Also records (verified this iteration, no code needed): #353 part 3 (the
"tight modify loop silently no-op'd" race) does NOT reproduce on current
main — 90/90 rapid sequential `modify`s succeeded and persisted across 3
trials; superseded anyway by `modify --where` (#380).

Implements: REQ-151
Refs: REQ-007

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant