figmog: fold-backed local mirror of a Figma file - #1
Conversation
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>
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>
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>
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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ree/get/find) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…/styles/uses/vars/import-variables) and watch loop Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…key integrity)
Closes out the figmog final-wave review: watch's Tier-1 pull failures now
honor Retry-After and back off exponentially instead of hammering the
budget every interval; --json mode emits {"error":...} on stderr instead
of plain text; a failed pull no longer rewrites .figmog/current or leaves
a stale mirror pointer behind; watch's Wait line no longer claims
"rate limited" for ordinary backoff; find --type is case-insensitive like
styles --type; pull --from-file with no established key gets a
pull-specific error instead of "run pull first"; and the variable_edges
dedup comment now matches what the code actually does.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Spec §4/§6 now describe FileMeta.last_modified (the file endpoint's lastModified) as what's actually stored, and spell out that watch's change-detection compares it against the meta endpoint's last_touched_at across two different endpoints, with the manual live check extended to verify they agree on a real file. Drops the unimplemented "plus small jitter" note per the no-jitter ruling, and documents the pull-path backoff now applied to Tier-1 pull failures. Also fixes stale naming drift (.figmog/config -> .figmog/current, --interval 10s -> --interval 10) and adds the Tier-3 poll budget and a pull --fresh variables-wipe caveat to the README. 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>
…ions, structural query pack 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>
…paid-seat pivot) 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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A failed figmog_sync tool call left pull_backoff and next_deadline untouched, so a background watch tick could still fire back into a rate-limit window the server had just been told about. Reuse pull_failure_wait (same as the tick-triggered pull path) to advance the backoff and push next_deadline out on failure. 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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ough, error caching, do_pull eviction
- C1: cmd_call opened its own store before delegating figmog_sync to
do_pull (which opens the same path again) — fjall's single-open-per-
process rule turned that into a panic after the Tier-1 fetch was
already spent. figmog_sync now returns before cmd_call opens a store.
- C2 (controller-amended constraint, spec §11 point 1): proxied results
were double-wrapped as escaped text, mangling native output formats
(e.g. unrenderable image content). mcp::ToolOutput{Json,Raw} lets local
tools keep the text-block wrap while proxied calls pass through
verbatim.
- I3: upstream tool-level errors (isError: true) were being cached,
replaying a stale failure forever; cache-store is now skipped for them.
- I4 (controller ruling): stale proxy_cache eviction after a
version-changing pull now lives in do_pull itself, covering pull/watch/
call figmog_sync in one place; figmog serve's own inline blocks are
unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, cache collision guard) I-1: translate the fold locked-store panic (a CLI command opening a store `figmog serve`/`figmog watch` already holds) into a clean exit-1 error at every CLI store-opening call site, via a catch_unwind wrapper that only translates the lock case and re-raises any other panic unchanged; document the single-writer constraint in the README. I-2: capture the negotiated protocolVersion from the upstream's initialize response and send it as MCP-Protocol-Version on every later request, per the streamable-HTTP transport spec this client declares. I-3: cache::lookup now verifies the stored row's tool and args_canonical against the request before serving a hit, closing the FNV-64 key-collision gap where one tool's cached response could be served for another. Also documents that `figmog tools`/`figmog call` need a resolved mirror (--db or a prior pull). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
open_store_checked no longer swaps out the global panic hook: doing so silenced non-lock panics (corrupt store, disk error) entirely, turning a genuine bug into a silent exit 101 with zero stderr output, and mutating a process-global hook around one call was racy against other threads besides. The default hook now stays active throughout — a lock panic still prints fold's raw trace before the friendly STORE_LOCKED_MSG follows, and a non-lock panic prints and propagates exactly as it would with no wrapper at all. Also route run_serve's own long-lived store open through open_store_checked, closing the serve-vs-serve / serve-vs-watch double-owner case the CLI-read fix didn't cover: starting a second figmog serve or figmog watch against an already-owned store now gets the same clean locked-store error instead of a raw panic. 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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
New: One command, no setup: Real-file mode:
🤖 Generated with Claude Code |
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>
|
The bench now has a demo mode where you watch every request fire in real time: $ cargo run --release -p figmog -- bench --interactive
figmog> search hero button
# 1 figmog_search q="hero button" 1.82ms 4 hits
figmog> node 12:7
# 2 figmog_node id=12:7 1.13ms "Hero Frame"
figmog> run 500
# 3 figmog_search q="garden panel" 1.76ms 9 hits
# 4 figmog_where /layoutMode=VERTICAL 2.90ms 120 hits
…waterfall of 500 lines…
figmog> report # session-wide p50/p95/p99 per tool
figmog> quitEvery local tool has a shorthand ( 🤖 Generated with Claude Code |
…files 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>
…llowlisted, PATs rejected Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mog_files 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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…elease 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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Multi-file serve + graduation notice.
This project has graduated to its own repo: https://github.com/sanctuarycomputer/figmog — standalone crate, 🤖 Generated with Claude Code |
Hackathon submission
category
pick one:
team
what you built
figmog turns one Figma file into a local, lightning-fast, agent-queryable database — and then becomes the only Figma MCP server an agent needs: a cached proxy in front of Figma's native tooling, backed by a fold mirror.
The mirror (v1). Figma has no delta API and its file endpoints are Tier-1 rate limited. figmog synthesizes the missing delta API from fold's
KeyedStreamupsert semantics: poll the cheap Tier-3 metadata endpoint, spend one Tier-1 fetch only whenlast_touched_atmoves, flatten the document into deterministic per-node records, and upsert-diff them through one atomic transaction into 14 materialized sinks (node table, children multimap, BM25 text search, inverted indexes for instances/styles/variables/types, design-system tables, proxy cache). A byte-identical re-pull pushes zero deltas (delta-probe tested); one renamed layer costs exactly one retract/insert pair.The MCP server (v2).
figmog serveis an MCP stdio server with the sync loop built in — one process, always fresh. 17figmog_*tools answer from the mirror at zero API cost, including whole-file structural queries nothing else offers:figmog_search(BM25),figmog_where(match any JSON pointer across every node),figmog_at(spatial hit-test),figmog_stats,figmog_path,figmog_instances,figmog_vars, and more.The cached proxy (v3). With the Figma desktop app running (Dev/Full seat), figmog discovers the native Dev Mode MCP server's tools at startup and re-exposes them verbatim —
get_design_context,get_screenshot,get_variable_defs, all of them, native output formats passed through untouched — with responses cached by (tool, args, file version), so a screenshot of an unchanged frame is fetched once, ever. Agents connect to figmog alone and get both worlds; the initializeinstructionssteer tool choice. CLI parity viafigmog tools/figmog call <tool> --args '<json>'. Pull also opportunistically syncs Enterprisevariables/local(silently skipped on other plans; plugin-export import + boundVariables inference remain as fallbacks).Design docs:
docs/superpowers/specs/2026-08-15-figmog-build-design.md(+ plans indocs/superpowers/plans/).Upstream finding (not patched here): fold's
Table<K, V>panics whenK's postcard encoding is empty (e.g.K = ()) — lsm-tree forbids empty keys. figmog keys its singleton meta row byu8instead. Also: fold's store-open panics on lock contention; figmog translates these to clean errors at its own layer.how to run
Offline demo without a token: save any
GET /v1/files/:keyresponse as JSON, thenfigmog pull --from-file resp.json --db /tmp/demoandfigmog serve --no-upstream --no-watch --db /tmp/demo.demo / notes
cargo test -p figmog,cargo test -p fold,cargo clippy -p figmog --no-deps -- -D warnings,cargo fmt -p figmog --checkall green; zero diff to fold/ese/anny.checklist
🤖 Generated with Claude Code