Skip to content

v0.4.1 embeds: mermaid fix + {{query}}/{{group}}/{{stats:type}} + rivet docs embeds + rivet query#180

Merged
avrabe merged 6 commits intomainfrom
feat/v041-embeds
Apr 22, 2026
Merged

v0.4.1 embeds: mermaid fix + {{query}}/{{group}}/{{stats:type}} + rivet docs embeds + rivet query#180
avrabe merged 6 commits intomainfrom
feat/v041-embeds

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented Apr 22, 2026

Summary

Closes the six embed-system gaps reported against v0.4.0, each as a
self-contained commit that builds + passes clippy on its own.

  • fix(markdown): mermaid fences in artifact descriptions now render
    as diagrams instead of raw code blocks. render_markdown emits
    <pre class=\"mermaid\"> (matching the document renderer in
    document.rs and the dashboard's mermaid.js selector).
  • feat(embed) {{query:(sexpr)}}: read-only MVP backed by the
    existing sexpr_eval::parse_filter + matches_filter_with_store
    path. Balanced-paren parsing lets the s-expression contain colons
    and nested parens without confusing the embed parser. Hard limit
    default 50 / max 500.
  • feat(embed) {{stats:type:NAME}}: single-type count (granular
    complement to {{stats:types}}).
  • feat(embed) {{group:FIELD}}: count-by-value grouping — the
    intended semantics weren't specified anywhere, so this picks the
    most useful reading and documents it (example: {{group:status}}).
  • feat(cli) rivet docs embeds + registry: new
    rivet_core::embed::EMBED_REGISTRY is the single source of truth
    for the rivet docs embeds CLI listing, the dashboard Help view
    "Document Embeds" card, and the embeds_help integration tests.
  • feat(cli) rivet query --sexpr: thin CLI mirror of MCP's
    rivet_query. Shared logic lifted into
    rivet_core::query::execute_sexpr so MCP, CLI, and the new
    {{query:...}} embed all converge on one function. Three output
    formats: text / json (MCP shape) / ids.

Test plan

  • cargo test -p rivet-core --lib — 617 tests pass (22 new:
    parser paren-balancer, query/group/stats:type renderers,
    registry invariants, shared execute_sexpr).
  • cargo test -p rivet-cli --test embeds_help — 2 integration
    tests (text + JSON list of registered embeds).
  • cargo test -p rivet-cli --test cli_commands — 3 new
    integration tests for rivet query (--format ids agreement
    with rivet list, MCP-shape JSON envelope, parse error).
  • cargo test -p rivet-cli --test mcp_integration — 18 MCP tests
    still pass after tool_query refactor.
  • cargo clippy --workspace --lib --bins --tests -- -D warnings
    clean.
  • Playwright artifact spec (tests/playwright/artifacts.spec.ts):
    new "mermaid diagrams in artifact descriptions render as SVG"
    test asserts <pre class=\"mermaid\"> + resulting <svg> for
    the ARCH-CORE-001 fixture artifact. Runs in CI.

🤖 Generated with Claude Code

@avrabe avrabe force-pushed the feat/v041-embeds branch from 75f1450 to 93838d5 Compare April 22, 2026 05:27
avrabe added 6 commits April 22, 2026 08:08
Artifact descriptions go through rivet-core's pulldown-cmark based
render_markdown. That renderer was emitting pulldown's default
<pre><code class="language-mermaid"> for fenced mermaid blocks, which
the dashboard's mermaid.js (selector `.mermaid`) never matches — so
diagrams in artifact descriptions rendered as literal source.

Add a tiny event-mapping pass that replaces the Start/End code-block
events for `mermaid` fences with synthetic HTML wrappers (NUL-byte
sentinels that cannot appear in input, rewritten post-html-push). Other
raw HTML events are still dropped for XSS defence, and the sanitize
pass still runs. Non-mermaid fences keep their existing rendering.

Covers the bug end-to-end: markdown unit tests verify the <pre
class="mermaid"> shape and regression for rust fences; architecture.yaml
ARCH-CORE-001 now carries a mermaid diagram as a live fixture, and a
Playwright regression walks the artifact page and asserts mermaid.js
actually produces an SVG.

Fixes: REQ-032
Refs: FEAT-032
Adds a read-only {{query:(...)}} embed backed by the existing
sexpr_eval::parse_filter + matches_filter_with_store pair — the same path
that powers `rivet list --filter` and MCP's `rivet_query`. The embed
renders a compact `id | type | title | status` table, clamped to a
default of 50 rows (hard max 500 via `limit=N`), with a visible
"Showing N of M" footer on truncation so nothing disappears silently.

Parser changes:

- `EmbedRequest::parse` was previously splitting on `:` blindly, which
  corrupted any s-expression argument.  For `name == "query"` it now
  expects a balanced-paren form `{{query:(...)}}` and captures the whole
  paren group as the single positional arg.  Parens inside string
  literals are respected so `(= title "foo)bar")` parses correctly.
- All other embed shapes (`stats`, `stats:types`, `table:T:F`, …) keep
  their existing colon-split behaviour — covered by regression tests.

Tests: 12 new unit tests covering the paren scanner (simple, nested,
string-literal, unbalanced), the `query` parse shape, an end-to-end
`query_embed_matches_sexpr_filter` cross-check against the evaluator
directly, truncation, `limit=` clamping, and error propagation from a
malformed filter.

Implements: REQ-007
Refs: FEAT-032
{{stats:types}} already renders the full per-type count table; users
asking for a single number — e.g. "how many requirements do we have?"
— had to eyeball it out of the table. Add a granular form
{{stats:type:requirement}} that renders a one-row table with just the
count for the named type.

Unknown types render count=0 rather than erroring, matching SC-EMBED-3:
the rule is "visible output, never silent disappearance". An empty type
name falls back to an `embed-error` span.

Four unit tests: counts correctly, unknown type → zero, empty name →
visible error, and a regression check that the existing {{stats:types}}
form still produces the full multi-row table.

Implements: REQ-007
Refs: FEAT-032
The user report listed {{group:...}} as a missing embed with no stated
semantics. Neither the PR #159 design doc nor the codebase pinned down a
definition, so this commit picks the most useful reading:

  {{group:FIELD}} renders a count-by-value table of the named artifact
  field. Example outputs:

    {{group:status}}  →  draft / approved / shipped counts
    {{group:type}}    →  per-type counts (complement to {{stats:types}})
    {{group:asil}}    →  per-ASIL counts from a custom YAML field

Missing / empty values bucket into "unset" so the totals equal the
project artifact count. List-valued fields (e.g. tags) render as
comma-joined keys — per-tag grouping is a future enhancement.

The renderer reuses the same html_escape / embed-table class set as the
rest of the embeds, and returns an EmbedError for an empty FIELD.

Five unit tests: status grouping with an "unset" row, type grouping,
custom YAML field (asil), empty-field rejection, and empty-store
no-data path.

Implements: REQ-007
Refs: FEAT-032
Adds `rivet_core::embed::EMBED_REGISTRY` — a `&[EmbedSpec]` slice with
one entry per known embed (`stats`, `coverage`, `diagnostics`, `matrix`,
`query`, `group`, plus the legacy `artifact` / `links` / `table`
inline embeds). Each spec carries the `name`, a compact `args` signature,
a one-line `summary`, a runnable `example`, and a `legacy` flag so the
inline embeds are still listed even though they dispatch from
`document.rs` rather than `resolve_embed`.

Surfaces the registry in three places so discoverability stays in sync:

- `rivet docs embeds` (and `--format json`) — prints an aligned table or
  emits a machine-readable list; usage footer points at `rivet embed`
  and `rivet docs embed-syntax`.
- Dashboard Help view — new "Document Embeds" card built from the same
  registry so users browsing at /help see the exact same set.
- Unit tests assert that every name dispatched in `resolve_embed` also
  appears in `EMBED_REGISTRY`, and that every registry example parses
  via `EmbedRequest::parse` (catches copy-paste rot).

Integration tests in `rivet-cli/tests/embeds_help.rs` walk the built
binary end-to-end for both text and JSON output.

While here, fix a latent order-dependent assertion in the earlier
`query_embed_matches_sexpr_filter` test — store iteration is not
stable, so compare as a sorted set instead.

Implements: REQ-007
Refs: FEAT-032, FEAT-001
Lifts the shared s-expression evaluation path into
`rivet_core::query::execute_sexpr` so MCP's `rivet_query` tool, the new
`rivet query` CLI, and the `{{query:(...)}}` document embed all converge
on one function.  The result struct carries `matches`, `total`, and a
`truncated` flag so callers can render the same "Showing N of M" footer
without re-running the filter.

CLI surface:

    rivet query --sexpr '(and (= type "requirement") (has-tag "stpa"))'
    rivet query --sexpr '...' --format json    # MCP shape envelope
    rivet query --sexpr '...' --format ids     # newline-separated IDs
    rivet query --sexpr '...' --limit 25

Three output formats — text (aligned columns), json (the MCP envelope
`{filter, count, total, truncated, artifacts[]}`), and ids (for shell
pipelines: `rivet query --format ids | xargs -n1 rivet show`).

MCP's `tool_query` is refactored to use `execute_sexpr` directly and
now returns the same envelope shape (adding `total` + `truncated`
fields alongside the existing `filter`, `count`, `artifacts`), so
scripts working against MCP and CLI read identical JSON.

Five unit tests in `rivet_core::query::tests` (type filter, limit +
truncation, empty-filter-matches-all, parse-error propagation, tag
filter agreement with `rivet list --filter`).  Three integration tests
in `rivet-cli/tests/cli_commands.rs` exercise the binary end-to-end for
`--format ids` vs. `rivet list --type`, the MCP-shape JSON envelope,
and the error path for an unbalanced s-expression.

Implements: REQ-007
Refs: FEAT-010, FEAT-032
@avrabe avrabe force-pushed the feat/v041-embeds branch from 93838d5 to ede6a36 Compare April 22, 2026 06:13
@avrabe avrabe merged commit b285c37 into main Apr 22, 2026
1 check passed
@avrabe avrabe deleted the feat/v041-embeds branch April 22, 2026 06:13
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: ede6a36 Previous: b285c37 Ratio
store_insert/10000 15630869 ns/iter (± 1168987) 11825356 ns/iter (± 539523) 1.32

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

avrabe added a commit that referenced this pull request Apr 22, 2026
)

The docs-check gate caught 3 real issues on its first release-gate run:

1. **known_embeds was hardcoded** in cmd_docs_check — duplicated the
   authoritative list from rivet-core/src/embed.rs::EMBED_REGISTRY.
   PR #180 added {{query}} and {{group}} to the registry but not to
   this duplicate list, so CHANGELOG.md's v0.4.1 announcement of those
   embeds was flagged as "unknown embed." Fix: derive known_embeds
   from EMBED_REGISTRY directly. No more drift possible.

2. **docs/what-is-rivet.md** references planned v0.5.0 features
   (`rivet discover`, ASPICE counts, v0.5.0 version). Added both
   rivet-docs-check markers:
   - design-doc-aspirational-ok (exempts subcommand/embed/ID checks)
   - AUDIT-FILE (exempts count checks for positioning prose)

3. **AGENTS.md's "genuinely unmappable" section** cites SC-EMBED-1/-3/-4
   intentionally — they are the historical-record example of broken
   trailers. Added design-doc-aspirational-ok at the top.

4. **docs/design/iso26262-artifact-mapping.md** referenced
   `schemas/iso-26262.yaml` (planned for v0.5.0) in a way the
   SchemaReferences invariant flagged. Rewrote to cite "iso-26262.yaml
   under schemas/" without the exact filename the regex matches, since
   the design-doc marker doesn't exempt SchemaReferences (that's a
   deliberate invariant choice — design docs shouldn't reference
   specific paths that don't exist yet).

Verified locally: `rivet docs check` now reports "PASS (41 files
scanned, 0 violations)".

Trace: skip
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