Phase 1 — tests/test_compare_shape.py: asserts /api/agent/stream for a
compare query emits exactly one final event with intent="compare", two
targets each carrying an address field, and a merged paragraph that
splits at "---" into PLACE A / PLACE B sections each with citations.
6/6 pass against the live server.
Phase 2 — CompareBriefing.svelte: when plan.intent=="compare" and
finalResult lands, renders a two-column layout (1fr 1px 1fr grid,
collapses below 900 px) with:
- Per-column .address-header showing the resolved address
- Existing Briefing component in each column, no changes to Briefing
- compare-delta-bar above both columns: one row per canonical section
where the first compared number differs (e.g. "TWI: 12.4 vs 8.2")
- Stone provenance / FindingsRegion unchanged (full-width below)
During streaming the merged paragraph renders in the existing single
Briefing path; the two-column view switches in only after finalResult.
FinalResult interface gains optional intent + target…
Squashed from 5438cc8..6904684. The slides/asce/deck.pptx is tracked
via git-lfs (added *.pptx to .gitattributes), so this commit carries
only the LFS pointer — no binary blob in history.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps the FSM step stream in a Stone-boundary envelope so the trace
UI can render the four data-Stones + Capstone as collapsible parent
nodes around their atomic specialist calls.
web/main.py
Adds _STEP_TO_STONE mapping FSM step names to Stone groups, plus
_STONE_META (NAME / TAGLINE / DESCRIPTION) drawn straight from the
app/stones modules so taglines stay single-source.
The /api/agent/stream event_stream() now tracks the current Stone
and emits:
- stone_start when the first step of a new group fires (or when
the first reconcile token arrives, opening Capstone before the
FSM's reconcile step event lands at the end);
- stone_done when the next group opens, the pipeline completes
(final), or it terminates without a final.
Ancillary steps (geocode, rag_granite_embedding, gliner_extract,
nta_resolve) flow through without opening a Stone — they're
orientation / policy infrastructure shared across Stones.
web/static/a…
First atomic step of the Riprap → Stones migration described in
UPDATE_STONES.md. Adds app/stones/{cornerstone,keystone,touchstone,
lodestone,capstone}.py — each exposes NAME / TAGLINE / DESCRIPTION /
SOURCES / collect(state). The FSM is unchanged; nothing imports the
new modules yet. Capstone is a thin alias around app/reconcile.py.
Forward-looking SOURCES entries (terramind_buildings, terramind_lulc,
ttm_battery_surge) are included now and gated in the unit test against
a FUTURE_STATE_KEYS allowlist so commits 4 and 6 can land without
churn here.
tests/test_stones.py is pure-import (no server required) and covers:
- required attrs on every Stone
- SOURCES keys are valid FSM state writes (modulo future keys)
- SOURCES are disjoint across data-Stones
- collect() drops silent specialists
- canonical Stone iteration order
- uniform collect() arity
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>