Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@

### Fixed

- **REQ-142 / #381 — s-expr filter parse error now shows the field-equality
form.** The filter dialect (shared by `query`, `list --filter`,
`export --filter`, `modify --where`) puts an operator in head position, but
the common first attempt `(status "draft")` (field name in head) failed with
only a list of head forms and no example. The `unknown head symbol` note now
states the head is an operator and shows `(= status "draft")` /
`(and (= type "requirement") (has-tag "safety"))` inline — reaching every
command that parses a filter, since the note is generated once in
`sexpr_eval`. `export --filter` help also gained an example.

- **REQ-139 — directory import no longer warns on legitimate non-artifact
YAML.** The generic-YAML directory load warned `[WARN] skipping <file>` for
*every* file it declined to load — including expected non-artifact YAML
Expand Down
36 changes: 36 additions & 0 deletions artifacts/requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3909,3 +3909,39 @@ artifacts:
links:
- type: traces-to
target: REQ-007

- id: REQ-142
type: requirement
title: "s-expr filter parse error shows the common field-equality example"
status: implemented
description: |
Self-reported via GitHub issue #381 (dogfooding). The s-expression
filter dialect (shared by `query`, `list --filter`, `export --filter`,
`modify --where`) puts an OPERATOR in head position, but the single most
common first attempt is `(status "draft")` — field name in head — which
fails with `unknown form 'status'`. The error listed every supported
head form but showed no example of the correct `(= field "value")`
shape, costing every first-time author (human or agent) a failed
round-trip + a grep through the test files to discover it.

The `unknown head symbol` note now states the head is an operator (not a
field name) and shows the field-equality form inline:
`(= status "draft")` / `(and (= type "requirement") (has-tag "safety"))`.
Because the note is generated once in `sexpr_eval`, the improvement
reaches every command that parses a filter. The `export --filter` help
also gained an example (it previously had none, unlike `list`/`query`).

Acceptance:
- `rivet list --filter '(status "draft")'` (or any command) prints a
note containing `(= status "draft")`.
- `cargo test -p rivet-core --lib
sexpr_eval::tests::parse_error_unknown_head_surfaces_note` passes
(asserts the example is present).
tags: [dx, agent-ux, discoverability, sexpr, user-reported, issue-381]
fields:
priority: should
category: functional
baseline: v0.15.0-track
links:
- type: traces-to
target: REQ-007
3 changes: 2 additions & 1 deletion rivet-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,8 @@ enum Command {
#[arg(long, default_value = "rivet")]
prefix: String,

/// S-expression filter to select artifact subset for export
/// S-expression filter to select artifact subset for export,
/// e.g. '(= type "requirement")' or '(and (= type "requirement") (= status "implemented"))'
#[arg(long)]
filter: Option<String>,

Expand Down
13 changes: 11 additions & 2 deletions rivet-core/src/sexpr_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,8 +794,11 @@ fn classify_filter_error(source: &str, message: &str) -> Option<String> {
let mut heads: Vec<&str> = HEADS.to_vec();
heads.sort_unstable();
return Some(format!(
"unknown head symbol; see docs/getting-started.md \
for the supported forms ({})",
"unknown head symbol — the head is an operator, not a field name. \
For field equality use `(= <field> \"<value>\")`, e.g. \
`(= status \"draft\")` or `(and (= type \"requirement\") \
(has-tag \"safety\"))`. See docs/getting-started.md for all \
supported forms ({})",
heads.join("/")
));
}
Expand Down Expand Up @@ -2090,6 +2093,12 @@ mod tests {
for op in ["and", "or", "not", "has-tag", "linked-via"] {
assert!(note.contains(op), "note should list `{op}`; got: {note}");
}
// #381: the note must show the common field-equality form so an
// author who guessed `(status "draft")` is corrected to `(= status …)`.
assert!(
note.contains("(= status \"draft\")"),
"note should show the `(= field \"value\")` example; got: {note}"
);
}

/// Valid s-expression input must not carry a note — classification
Expand Down
Loading