/** * Mémoire Core Engine — Central orchestrator that ties together * Figma bridge, research, specs, codegen, and preview. */ import { ProjectContext } from "./project-context.js"; import { Registry, type DesignSystem } from "./registry.js"; import { FigmaBridge } from "../figma/bridge.js"; import { ResearchEngine } from "../research/engine.js"; import { CodeGenerator, type CodegenResult } from "../codegen/generator.js"; import { BidirectionalSync } from "./sync.js"; import { CodeWatcher } from "./code-watcher.js"; import { AgentRegistry } from "../agents/agent-registry.js"; import { TaskQueue } from "../agents/task-queue.js"; import { AgentBridge } from "../agents/agent-bridge.js"; import { EventEmitter } from "events"; import { NoteLoader } from "../notes/loader.js"; import { CanvasHealer } from "../figma/canvas-healer.js"; import { type WcagTokenReport } from "../figma/wcag-token-checker.js"; export interface MemoireConfig { projectRoot: string; figmaToken?: string; figmaFileKey?: string; previewPort?: number; anthropicApiKey?: string; } export interface MemoireEvent { type: "info" | "warn" | "error" | "success"; source: string; message: string; timestamp: Date; data?: unknown; } export type MemoireInitProfile = "minimal" | "registry" | "full"; export declare class MemoireEngine extends EventEmitter { readonly config: MemoireConfig; readonly log: import("pino").Logger; readonly registry: Registry; readonly figma: FigmaBridge; readonly research: ResearchEngine; readonly codegen: CodeGenerator; readonly notes: NoteLoader; readonly healer: CanvasHealer; readonly sync: BidirectionalSync; readonly codeWatcher: CodeWatcher; readonly agentRegistry: AgentRegistry; readonly taskQueue: TaskQueue; private _agentBridge; private pullCache; private static readonly PULL_CACHE_TTL_MS; private _project; private _initProfile; private _soul; /** Debounced auto-pull on Figma document changes */ private _docChangeTimer; private _docChangePulling; private static readonly DOC_CHANGE_DEBOUNCE_MS; constructor(config: MemoireConfig); /** Debounces auto-pull when the Figma document changes — waits DOC_CHANGE_DEBOUNCE_MS before pulling to coalesce rapid edits. */ private _onDocumentChanged; get project(): ProjectContext | null; /** * Returns a deep-cloned snapshot of the current design system from the registry. * * Used by `memi pull` and `executeRestPull` to capture state immediately before a * pull so the token-differ can produce a human-readable before/after diff. Callers * should snapshot, pull, then diff — never hold a snapshot across multiple pulls. * * @returns A deep copy of the current {@link DesignSystem} (tokens, components, styles). */ snapshotDesignSystem(): DesignSystem; /** * Run a WCAG 2.2 token audit against the design system currently held in the registry. * * Delegates to `auditTokensForWcag` so MCP tools, agents, and CLI commands can all * trigger the audit without re-implementing the logic. Always call after * `pullDesignSystem()` or `pullDesignSystemREST()` to ensure data is fresh. * * @returns A {@link WcagTokenReport} with per-token pass/warn/fail results and * an aggregate summary. `report.hasFailures` is `true` when at least one token * fails WCAG AA contrast requirements. */ auditDesignSystemWcag(): WcagTokenReport; /** * Returns the singleton {@link AgentBridge}, creating it on first access (lazy init). * * The bridge wires together the WebSocket server, the task queue, and the agent * registry so that plugin-side agent messages are routed to the right task slots. * Requires `connectFigma()` to have been called first so the ws-server exists. */ get agentBridge(): AgentBridge; /** * The project's design soul string, loaded from `.memoire/SOUL.md` during `init()`. * * Injected into agent prompts to steer output style (voice, density, aesthetic). * Empty string if the soul file does not exist yet. */ get soul(): string; /** * Initialize the Mémoire engine — must be called before methods that need project, * registry, notes, agents, or research state. * * Profiles: * - `minimal`: env, workspace skeleton, project detection, and project persistence. * - `registry`: minimal + registry/spec load, design soul, and sync state. * - `full`: registry + agent registry, task queue, health loop, and Notes. * * The default remains `full` for backward compatibility. Calls can upgrade an * existing engine from a lighter profile to a heavier one without re-running work. * * Full initialization performs the following in order: * 1. Loads `.env.local` then `.env` into `process.env` (FIGMA_TOKEN, FIGMA_FILE_KEY, * ANTHROPIC_API_KEY are merged into `this.config` if not already set). * 2. Creates `.memoire/` and initializes the workspace skeleton (SOUL.md, etc). * 3. Detects the project framework/type and persists `project.json`. * 4. Loads the existing design system registry from disk. * 5. Reads the design soul string. * 6. Loads bidirectional sync state, starts the agent registry health-check loop, * starts the task queue, and loads all installed Mémoire Notes. * * Idempotent — safe to call multiple times; subsequent calls are no-ops unless they * request a heavier profile than the one already loaded. */ init(profile?: MemoireInitProfile): Promise; private emitInitialized; /** * Start (or reuse) the Figma WebSocket bridge and return the port it is listening on. * * Preference order: * 1. Reuse an existing `memi connect` bridge found in `.memoire/bridge.json`. * 2. Reuse the daemon's bridge port from `.memoire/daemon.json`. * 3. Spin up a fresh bridge on the first available port in 9223-9232. * * Does NOT wait for a plugin to connect — call `ensureFigmaConnected()` for that. * * @returns The port number the bridge is (or was already) listening on. * @throws If no port in the scan range is available. */ connectFigma(): Promise; /** * Ensure a Figma plugin is actively connected, waiting up to `timeoutMs` if needed. * * If the bridge is not yet running it is started via `connectFigma()`. If the plugin * is already attached (`figma.isConnected`) this returns immediately. Otherwise it * registers a one-time `plugin-connected` listener before re-checking state to close * the race window, then rejects with a user-friendly message on timeout. * * Used by commands that require live plugin data (pull, sync, compose, etc). * * @param timeoutMs - Milliseconds to wait for a plugin connection (default 30 000). * @throws {Error} If no plugin connects within the timeout window. */ ensureFigmaConnected(timeoutMs?: number): Promise; /** Reads `.memoire/bridge.json` and returns the lock if the process is alive, or `null`. Delegates to the shared bridge-lock module which auto-deletes stale locks. */ private _readBridgeLock; /** * Returns `true` when a live `memi connect` bridge or a daemon with an active Figma * port is detected on the local machine. * * `memi pull` uses this to decide whether to wait for a plugin connection or to * immediately fall back to the REST API. A `false` return means no WebSocket server * is running — the user either hasn't run `memi connect` yet or the process died. * * @returns `true` if a running bridge or daemon lock file exists and the owning * process is still alive, `false` otherwise. */ hasRunningBridge(): Promise; /** Reads `.memoire/daemon.json` and returns `{ figmaPort }` if the daemon process is alive, or `null` if the file is absent or the PID is stale. */ private _readDaemonStatus; /** * Pull the design system from Figma via the active WebSocket plugin connection. * * Requires an open plugin connection (`figma.isConnected`). Extracts tokens, * components, and styles from the live Figma document, persists them to the registry, * and runs `autoSpec()` to create any missing component specs. * * Results are cached for 5 minutes (per `PULL_CACHE_TTL_MS`). Use `force = true` to * bypass the cache — e.g. when the user explicitly runs `memi pull --force`. * * Prefer this path over `pullDesignSystemREST()` when the plugin is available because * it captures richer data (node IDs, variants, layout properties) that the REST API * cannot provide on Free/Starter Figma plans. * * @param force - When `true`, skip the 5-minute pull cache and always re-fetch. * @throws {Error} If no plugin is currently connected. */ pullDesignSystem(force?: boolean): Promise; /** * Pull the design system from Figma via the REST API — no plugin or WebSocket required. * * Use this path in CI environments, headless machines, or when no `memi connect` * bridge is running. Fetches variables/tokens, published components, and styles in * parallel via three separate REST endpoints. Variables require a Figma Professional+ * plan — a Free plan returns a `FigmaPlanError` which is absorbed (tokens will be * empty but components/styles are still returned). * * Shares the same 5-minute pull cache as `pullDesignSystem()` so the two methods can * be used interchangeably within a session without triggering duplicate network calls. * * @param force - When `true`, skip the cache and always re-fetch. * @throws {FigmaConfigError} If `FIGMA_TOKEN` is invalid (401), the file key is wrong * (404), or the token lacks access to the file (403 on non-variables endpoints). * @throws {Error} If `FIGMA_TOKEN` or `FIGMA_FILE_KEY` are not set in config or env. */ pullDesignSystemREST(force?: boolean): Promise; /** * Automatically generate `ComponentSpec` stubs for any Figma components that do not * yet have a corresponding spec in the registry. * * Called automatically at the end of both `pullDesignSystem()` and * `pullDesignSystemREST()`. Can also be called directly after a manual registry * update. Components with names that cannot be turned into valid TypeScript identifiers * are skipped and logged. * * @returns The number of new specs written to disk (0 if nothing was new). */ autoSpec(): Promise; /** * Generate React + Tailwind code for a single named spec. * * Looks up the spec in the registry, runs the code generator with the current * project context and design system, writes files to `generated/`, and emits a * `success` event. The engine must be initialized before calling this method. * * A critical quality-gate finding blocks the write (result.blocked = true, * no files written, no generation recorded) unless opts.force is passed. * * @param specName - The spec name as stored in the registry (case-sensitive). * @param opts.force - Write files despite critical quality-gate findings. * @returns The full CodegenResult (entryFile, files, findings, blocked, critique). * @throws {Error} If the spec does not exist or the engine has not been initialized. */ generateFromSpec(specName: string, opts?: { force?: boolean; }): Promise; /** * Run a full sync pipeline: pull the design system from Figma then regenerate code * for all specs in the registry. * * Requires an active plugin connection (calls `pullDesignSystem()` internally). * Equivalent to running `memi pull && memi generate` in sequence. Blocked specs * are never force-written in bulk — they are skipped and counted separately * so the sync summary reports them instead of silently writing past a * critical finding. */ fullSync(): Promise; /** Persists the detected project context to `.memoire/project.json`, preserving the original `detectedAt` timestamp if the rest of the context is unchanged. */ private saveProjectContext; }