figmog: fold-backed local mirror of a Figma file - #21
Closed
hhff wants to merge 56 commits into
Closed
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>
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>
…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>
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.
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