v0.0.2: agent ergonomics - #2
Merged
Merged
Conversation
…eometry, images, resolve-vars, README overhaul) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Task 1 of the v0.0.2 ergonomics plan (docs/specs/2026-08-17-v0.0.2-ergonomics.md
§2, §3, §6):
- query::subtree (`figmog dump` / figmog_subtree): full raw JSON,
children nested recursively in child-index order, depth-bounded,
fields-projected (id/name/type/children always survive). Registry
19 -> 20 tools.
- query::{text,find,search,where_} gain `under: Option<String>`: a
cycle-safe BFS scope over the children index, composing with `page`
by intersection. CLI --under flags + matching tool args.
- resolve_vars annotation (query::node/subtree/styles): joins
boundVariables binding sites against the variables table, emitting
{variable_id, variable_name, values_by_mode} or
{variable_id, source: "unresolved"} under a resolved_variables key,
never mutating raw data.
Folded in mid-task per coordinator scope addition (spec §2b, commit
6881cd7): every node-id-shaped argument (id/under/target, and the
matching CLI flags) now accepts a full Figma URL via
ident::parse_node_ref, not just a bare id. figmog serve's multi-file
session routing infers the target mirror from a URL's file key when
`file` is omitted (explicit `file` still wins on disagreement).
Updated every tool-count assertion the registry addition touches
(enumerated in the task report). 156 tests green; clippy/fmt/doc clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, full table collapsed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alization Task review fix round (3 Important, 2 bundled minors): - I1: query::search truncated to `limit` before applying `--under`'s scope filter, so a tight limit under a scope containing a real but globally-outranked match returned []. Bm25Reader::search already scores every matching doc internally regardless of `limit` (only its final sort truncates), so a scoped call now requests an effectively unlimited `limit`, filters to scope, then truncates — unscoped calls keep the direct top-k path. - I2: `--page` wasn't routed through the URL-aware normalize_node_ref (find/text/where_ used normalize_node_id, bare-id only). Fixed at all three call sites. - I3: the §2b explicit-file/URL-file mismatch note (serve.rs) compared raw `file` argument text against an already-normalized URL-derived key, false-positiving when the explicit file arg was itself a URL naming the same file in different spelling. Now compares normalized keys (parse_file_ref), falling back to raw text only when it can't parse at all. - dispatch.rs's module doc comment still said "16 read-only tools" (missed in the first pass); now 17. - New children_cycle_does_not_hang_under_scoping test: a hand-upserted mutual-parent pair (same construction as the existing parent-cycle test) derives a children-multimap cycle too; proves scope_ids's BFS terminates. 165 tests green; clippy/fmt/doc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ther, not both) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uary.computer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
figmog serve now also listens on <figmog-root>/serve.sock (spec §1): a listener thread accepts connections and forwards newline-delimited JSON-RPC frames into the existing single-threaded request loop, tagged with a connection id so responses route back to the right client. Startup does a stale-socket probe (connect refused ⇒ unlink+rebind, connect succeeds ⇒ clean "another figmog serve owns this root" error) and the socket file is unlinked on clean exit. --no-socket disables listening. Every CLI read command, `tools`, and `call` now try that socket first (when --db wasn't used) and become clients of the running serve — always-fresh answers, no store-lock contention, and `figmog call figmog_sync` now reaches the *owning* process instead of failing against its own lock. Unreachable or --no-socket falls back to the pre-existing direct store open, unchanged. pull's lock-error message now points at that escape hatch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on-blocking response writes Review fix round (2 Critical + 1 Important + bundled minors): - C1: routed reads/calls now inject .figmog/current's key as the `file` argument when the call omits one, so a socket-routed command targets the same mirror direct mode would instead of silently answering from serve's own default session (or hard-failing with no default in the zero-startup-file quick-start shape). Skipped for figmog_open, whose `file` is a required argument with its own meaning, not a routing hint. - C2: strip null-valued keys from a routed read's argument object before sending (json! always emits a null "equals" for an omitted --equals, which dispatch_read_tool's presence-based check misread as "match null"); dispatch.rs treats a present-but-null `equals` as absent too, as defense in depth. - I1: write_socket_response no longer holds the connection registry's mutex across a blocking write — it clones the connection handle out from behind the lock first — and every accepted connection now gets a bounded (5s) write timeout, so a client that never reads its responses can no longer stall the single-threaded loop (and therefore stdio) indefinitely. Socket frame reads are also now capped at 8MB. - m1: a routed `figmog status` (not `call figmog_status`) no longer carries the server-spliced `upstream` field, matching direct mode. - m2: a socket read timeout reports a specific, actionable message. - m3: the socket file guard only unlinks if it's still the same inode it bound, protecting a live socket from a racer or a manual rm+touch. - m4: figmog-root is chmod'd 0700 on every bind (socket file mode alone isn't the access boundary on macOS). - m5: an explicit --upstream/--no-upstream on a socket-routed tools/call now gets a one-line stderr note that serve's own settings apply. - m6: new e2e proves 8 read commands are byte-identical direct vs. socket-routed, including the exact where/status shapes C2 and m1 broke. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds `Id::MirrorConfig`/`Rec::MirrorConfig(MirrorConfigRec { geometry })`
(appended after ProxyCache, per the append-only rule) with its own
`mirror_config` sink, so `pull --geometry` can request Figma's
`?geometry=paths` vector path data without breaking postcard decoding of
existing stores. The flag is sticky: every re-pull path (cli pull,
figmog_sync, serve's watch tick, sessions auto-open, figmog_open's own
`geometry` arg) reads the stored config and unions it with any override,
so geometry never churns off a flag drift; `pull --fresh` is the
documented way to turn it back off. mirror_config is structurally
sweep-exempt, same as meta.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add Id::ImageBlob/Rec::ImageBlob (append-only), a new images Table sink, and images::scan/resolve fetch orchestration for the two Figma image endpoints (node renders, fill images), cache-first and version-gated exactly like proxy_cache. Wire figmog_images into the MCP tool registry (20 -> 21) and figmog serve's per-session dispatch, add the figmog images CLI command with socket-or-direct routing, and join stale image rows into the existing post-pull cache eviction at both call sites. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- images::scan no longer trusts the image_key hash alone: a keyed hit is only served when the row's kind/subject match (and, for renders, format/scale_milli too), mirroring cache::lookup's own collision discipline. Two new regression tests. - to_mcp_content now measures the base64-encoded payload against CONTENT_SIZE_CAP instead of raw bytes (base64 inflates ~4/3), encoding once and reusing the length for both the comparison and the oversized note. - SVG renders ship as MCP text blocks with raw markup (tagged mimeType: image/svg+xml) instead of base64 image blocks, sidestepping inconsistent client rendering of image/svg+xml and giving agents directly usable markup; the same encoded-length cap still applies. cli::images::from_socket_result updated to decode either payload shape by the mimeType marker rather than assuming type == "image". - figmog images now requires at least one id (clap), matching the tool path's existing rejection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README rewritten for a first-time reader per spec §7: positioning sentence, measured headline numbers, a Claude-first quick start (including removing the official Figma MCP server), the CLI path below it, a compact why-table, short feature sections linking into docs/SPEC.md, the full benchmark run collapsed, one install block, one limitations list, one license note, and a note for Figma. docs/SPEC.md updated for every v0.0.2 feature: the socket control plane (new §6a and the CLI side in §10), node addressing (§12), subtree dump, --under scoping and --resolve-vars (§13), sticky geometry and MirrorConfig (§14), images and ImageBlob (§15), sinks 14 to 16, registry 19 to 21, and version-keyed eviction covering both caches. CLAUDE.md gains mirror_config and images in the frozen sink list, the "new record kind, not a wider struct" rule, and the AGPL/CLA note. Cargo.toml 0.0.1 -> 0.0.2. src/serve.rs's module doc said 20 local tools; it is 21. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The collapsed block now carries the corpus and cold-sync lines, every per-tool column (calls, p50, p95, p99, max) from the 2026-08-17 run against the real 5,339-node file, and the load-phase plus API-comparison figures behind the headline numbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
socket_guard_does_not_unlink_a_file_replaced_at_the_same_path assumed a remove-then-recreate at the same path always yields a fresh inode, which holds on macOS's filesystems but not on ext4 (the common Linux CI filesystem), which recycles inode numbers immediately and can legitimately hand the recreated file the exact same (dev, ino) the guard bound — causing the test to fail on ubuntu-latest. Restructured to build the replacement at a sibling path while the original still exists (two simultaneously-live files are guaranteed distinct inodes on every platform), then rename(2) it over the guard's path — rename preserves the source inode across the move, so the file left at the guard's path is provably a different inode regardless of filesystem. No production behavior changed; added a doc-comment note on SocketGuard acknowledging this is a best-effort check, not a hard guarantee, given ext4's inode-reuse behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Field feedback from a design agent building a replica surfaced six ergonomic gaps (the mirror's fidelity was never the problem). This PR fixes all six plus a friendlier README:
serveover<root>/serve.sock; the store-locked error becomes nearly unreachable.figmog dump/figmog_subtree— full-fidelity subtree export with--depthand--fieldsprojection (one call instead of 204).--under <id>subtree scoping ontext/find/search/where.pull --geometry— vector path data, per-mirror stickiness via a new (append-only) MirrorConfig record.figmog images/figmog_images— node renders + image fills, version-cached in a newimagessink; explicit-only budget spend.--resolve-vars— design-token names joined onto bound values at read time.Spec: docs/specs/2026-08-17-v0.0.2-ergonomics.md. Task commits land on this branch with per-task adversarial reviews; final whole-PR review before merge; v0.0.2 pre-release tagged from main.
🤖 Generated with Claude Code