figjam-context-mcp 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +66 -0
- package/CHANGELOG.md +43 -0
- package/LICENSE +21 -0
- package/README.md +149 -2
- package/SECURITY.md +26 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/boardDiff.d.ts +55 -0
- package/dist/lib/boardDiff.js +285 -0
- package/dist/lib/boardDiff.js.map +1 -0
- package/dist/lib/cache.d.ts +9 -0
- package/dist/lib/cache.js +63 -0
- package/dist/lib/cache.js.map +1 -0
- package/dist/lib/connectorGraph.d.ts +32 -0
- package/dist/lib/connectorGraph.js +121 -0
- package/dist/lib/connectorGraph.js.map +1 -0
- package/dist/lib/docStructureMapper.d.ts +24 -0
- package/dist/lib/docStructureMapper.js +121 -0
- package/dist/lib/docStructureMapper.js.map +1 -0
- package/dist/lib/env.d.ts +2 -0
- package/dist/lib/env.js +25 -0
- package/dist/lib/env.js.map +1 -0
- package/dist/lib/evidence.d.ts +63 -0
- package/dist/lib/evidence.js +474 -0
- package/dist/lib/evidence.js.map +1 -0
- package/dist/lib/figmaApi.d.ts +24 -0
- package/dist/lib/figmaApi.js +173 -0
- package/dist/lib/figmaApi.js.map +1 -0
- package/dist/lib/llmClient.d.ts +61 -0
- package/dist/lib/llmClient.js +441 -0
- package/dist/lib/llmClient.js.map +1 -0
- package/dist/lib/llmOutputBudgets.d.ts +6 -0
- package/dist/lib/llmOutputBudgets.js +13 -0
- package/dist/lib/llmOutputBudgets.js.map +1 -0
- package/dist/lib/llmTimeouts.d.ts +4 -0
- package/dist/lib/llmTimeouts.js +10 -0
- package/dist/lib/llmTimeouts.js.map +1 -0
- package/dist/lib/modelRegistry.d.ts +12 -0
- package/dist/lib/modelRegistry.js +91 -0
- package/dist/lib/modelRegistry.js.map +1 -0
- package/dist/lib/nodeTree.d.ts +9 -0
- package/dist/lib/nodeTree.js +174 -0
- package/dist/lib/nodeTree.js.map +1 -0
- package/dist/lib/persistentCache.d.ts +32 -0
- package/dist/lib/persistentCache.js +301 -0
- package/dist/lib/persistentCache.js.map +1 -0
- package/dist/lib/spatialCluster.d.ts +58 -0
- package/dist/lib/spatialCluster.js +340 -0
- package/dist/lib/spatialCluster.js.map +1 -0
- package/dist/lib/visionInterpreter.d.ts +14 -0
- package/dist/lib/visionInterpreter.js +205 -0
- package/dist/lib/visionInterpreter.js.map +1 -0
- package/dist/schemas/answerFromBoard.d.ts +75 -0
- package/dist/schemas/answerFromBoard.js +28 -0
- package/dist/schemas/answerFromBoard.js.map +1 -0
- package/dist/schemas/common.d.ts +14 -0
- package/dist/schemas/common.js +59 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/schemas/diagnoseLlmConfig.d.ts +35 -0
- package/dist/schemas/diagnoseLlmConfig.js +20 -0
- package/dist/schemas/diagnoseLlmConfig.js.map +1 -0
- package/dist/schemas/diffBoard.d.ts +118 -0
- package/dist/schemas/diffBoard.js +61 -0
- package/dist/schemas/diffBoard.js.map +1 -0
- package/dist/schemas/getBoardContext.d.ts +255 -0
- package/dist/schemas/getBoardContext.js +77 -0
- package/dist/schemas/getBoardContext.js.map +1 -0
- package/dist/schemas/ingestBoard.d.ts +96 -0
- package/dist/schemas/ingestBoard.js +88 -0
- package/dist/schemas/ingestBoard.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +131 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/answerFromBoard.d.ts +9 -0
- package/dist/tools/answerFromBoard.js +239 -0
- package/dist/tools/answerFromBoard.js.map +1 -0
- package/dist/tools/diagnoseLlmConfig.d.ts +4 -0
- package/dist/tools/diagnoseLlmConfig.js +158 -0
- package/dist/tools/diagnoseLlmConfig.js.map +1 -0
- package/dist/tools/diffBoard.d.ts +11 -0
- package/dist/tools/diffBoard.js +49 -0
- package/dist/tools/diffBoard.js.map +1 -0
- package/dist/tools/getBoardContext.d.ts +3 -0
- package/dist/tools/getBoardContext.js +157 -0
- package/dist/tools/getBoardContext.js.map +1 -0
- package/dist/tools/ingestBoard.d.ts +8 -0
- package/dist/tools/ingestBoard.js +594 -0
- package/dist/tools/ingestBoard.js.map +1 -0
- package/dist/types.d.ts +145 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/pipeline.gif +0 -0
- package/package.json +73 -4
- package/server.json +48 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export const STRUCTURAL_TYPES = new Set(["DOCUMENT", "CANVAS", "PAGE", "FRAME", "GROUP", "SECTION"]);
|
|
3
|
+
export const MAX_BOARD_NODES = 100_000;
|
|
4
|
+
const MAX_BOARD_DEPTH = 1024;
|
|
5
|
+
const MAX_METADATA_DEPTH = 64;
|
|
6
|
+
function validateGeometry(node) {
|
|
7
|
+
const box = node.absoluteBoundingBox;
|
|
8
|
+
if ((box && (![box.x, box.y, box.width, box.height].every(Number.isFinite) || box.width < 0 || box.height < 0)) ||
|
|
9
|
+
(node.rotation !== undefined && !Number.isFinite(node.rotation))) {
|
|
10
|
+
throw new Error(`Board element ${node.id} has invalid geometry: positions and dimensions must be finite and sizes cannot be negative.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Bounds arbitrary paint/vector metadata before JSON's recursive serializer. */
|
|
14
|
+
function validateMetadataDepth(content, nodeId) {
|
|
15
|
+
const stack = [{ value: content, depth: 0 }];
|
|
16
|
+
while (stack.length > 0) {
|
|
17
|
+
const { value, depth } = stack.pop();
|
|
18
|
+
if (value === null || typeof value !== "object")
|
|
19
|
+
continue;
|
|
20
|
+
if (depth > MAX_METADATA_DEPTH) {
|
|
21
|
+
throw new Error(`Board element ${nodeId} contains metadata deeper than ${MAX_METADATA_DEPTH} levels. Simplify the element before ingesting it.`);
|
|
22
|
+
}
|
|
23
|
+
for (const child of Object.values(value)) {
|
|
24
|
+
if (child !== null && typeof child === "object")
|
|
25
|
+
stack.push({ value: child, depth: depth + 1 });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isRawFigmaNode(value) {
|
|
30
|
+
if (typeof value !== "object" || value === null)
|
|
31
|
+
return false;
|
|
32
|
+
const candidate = value;
|
|
33
|
+
return typeof candidate.id === "string" && typeof candidate.name === "string" &&
|
|
34
|
+
typeof candidate.type === "string";
|
|
35
|
+
}
|
|
36
|
+
function extractImageRef(node) {
|
|
37
|
+
return node.fills?.find((fill) => fill.type === "IMAGE" && fill.imageRef)?.imageRef;
|
|
38
|
+
}
|
|
39
|
+
/** Placement does not invalidate a summary; paint, shape and original text do. */
|
|
40
|
+
function fingerprint(node, childFingerprints = []) {
|
|
41
|
+
const omitted = new Set([
|
|
42
|
+
"id", "children", "absoluteBoundingBox", "absoluteRenderBounds", "relativeTransform",
|
|
43
|
+
"absoluteTransform", "rotation", "x", "y",
|
|
44
|
+
]);
|
|
45
|
+
const content = {};
|
|
46
|
+
for (const key of Object.keys(node).sort()) {
|
|
47
|
+
if (!omitted.has(key))
|
|
48
|
+
content[key] = node[key];
|
|
49
|
+
}
|
|
50
|
+
content.size = node.absoluteBoundingBox && {
|
|
51
|
+
width: node.absoluteBoundingBox.width, height: node.absoluteBoundingBox.height,
|
|
52
|
+
};
|
|
53
|
+
if (childFingerprints.length > 0)
|
|
54
|
+
content.children = childFingerprints;
|
|
55
|
+
validateMetadataDepth(content, node.id);
|
|
56
|
+
const serialized = JSON.stringify(content, (_key, value) => {
|
|
57
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
58
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
});
|
|
62
|
+
return createHash("sha256").update(serialized).digest("hex");
|
|
63
|
+
}
|
|
64
|
+
function cellText(entry, entries) {
|
|
65
|
+
if (entry.raw.characters !== undefined)
|
|
66
|
+
return entry.raw.characters;
|
|
67
|
+
const snippets = [];
|
|
68
|
+
const stack = [...entry.children].reverse();
|
|
69
|
+
while (stack.length > 0) {
|
|
70
|
+
const child = entries[stack.pop()];
|
|
71
|
+
if (child.raw.characters !== undefined) {
|
|
72
|
+
snippets.push(child.raw.characters);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
for (let i = child.children.length - 1; i >= 0; i--)
|
|
76
|
+
stack.push(child.children[i]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return snippets.join("\n");
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Iterative traversal preserves document order without recursive calls or wide
|
|
83
|
+
* argument spreads. Table cells remain original evidence on their parent TABLE;
|
|
84
|
+
* only the table's normal node ID is eligible for rendering and clustering.
|
|
85
|
+
*/
|
|
86
|
+
export function flattenNodeTree(rawFigmaJson) {
|
|
87
|
+
const document = rawFigmaJson?.document;
|
|
88
|
+
if (!isRawFigmaNode(document)) {
|
|
89
|
+
throw new Error("flattenNodeTree: expected a Figma file response with a document node");
|
|
90
|
+
}
|
|
91
|
+
const entries = [];
|
|
92
|
+
const roots = document.children ?? [];
|
|
93
|
+
if (roots.length > MAX_BOARD_NODES)
|
|
94
|
+
throw new Error(`Board exceeds the ${MAX_BOARD_NODES}-node limit`);
|
|
95
|
+
const stack = [];
|
|
96
|
+
for (let i = roots.length - 1; i >= 0; i--)
|
|
97
|
+
stack.push({ raw: roots[i] });
|
|
98
|
+
while (stack.length > 0) {
|
|
99
|
+
const item = stack.pop();
|
|
100
|
+
if (!isRawFigmaNode(item.raw))
|
|
101
|
+
throw new Error("flattenNodeTree: invalid child node");
|
|
102
|
+
validateGeometry(item.raw);
|
|
103
|
+
if (entries.length >= MAX_BOARD_NODES)
|
|
104
|
+
throw new Error(`Board exceeds the ${MAX_BOARD_NODES}-node limit`);
|
|
105
|
+
const parent = item.parent === undefined ? undefined : entries[item.parent];
|
|
106
|
+
const depth = (parent?.depth ?? 0) + 1;
|
|
107
|
+
if (depth > MAX_BOARD_DEPTH)
|
|
108
|
+
throw new Error(`Board exceeds the ${MAX_BOARD_DEPTH}-level nesting limit`);
|
|
109
|
+
const { raw } = item;
|
|
110
|
+
const index = entries.length;
|
|
111
|
+
const sectionIds = raw.type === "SECTION" ? [...(parent?.sectionIds ?? []), raw.id] : parent?.sectionIds ?? [];
|
|
112
|
+
const entry = {
|
|
113
|
+
raw, parent: item.parent, parentId: parent?.raw.id ?? document.id,
|
|
114
|
+
pageId: raw.type === "CANVAS" || raw.type === "PAGE" ? raw.id : parent?.pageId,
|
|
115
|
+
sectionIds, depth, children: [],
|
|
116
|
+
tableOwner: parent?.raw.type === "TABLE" ? item.parent : parent?.tableOwner,
|
|
117
|
+
keep: !STRUCTURAL_TYPES.has(raw.type) || Boolean(raw.characters?.trim()) || Boolean(extractImageRef(raw)),
|
|
118
|
+
};
|
|
119
|
+
entries.push(entry);
|
|
120
|
+
parent?.children.push(index);
|
|
121
|
+
const children = raw.children ?? [];
|
|
122
|
+
if (entries.length + stack.length + children.length > MAX_BOARD_NODES) {
|
|
123
|
+
throw new Error(`Board exceeds the ${MAX_BOARD_NODES}-node limit`);
|
|
124
|
+
}
|
|
125
|
+
for (let i = children.length - 1; i >= 0; i--)
|
|
126
|
+
stack.push({ raw: children[i], parent: index });
|
|
127
|
+
}
|
|
128
|
+
const cellsByTable = new Map();
|
|
129
|
+
// Hash table descendants bottom-up, so non-text visual edits also invalidate reuse.
|
|
130
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
131
|
+
const entry = entries[i];
|
|
132
|
+
const includeChildren = entry.raw.type === "TABLE" || entry.tableOwner !== undefined;
|
|
133
|
+
entry.contentFingerprint = fingerprint(entry.raw, includeChildren
|
|
134
|
+
? entry.children.map((child) => entries[child].contentFingerprint) : []);
|
|
135
|
+
if (entry.keep && entry.parent !== undefined)
|
|
136
|
+
entries[entry.parent].keep = true;
|
|
137
|
+
}
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
if (entry.tableOwner === undefined || entry.raw.type !== "TABLE_CELL")
|
|
140
|
+
continue;
|
|
141
|
+
const cell = { id: entry.raw.id, text: cellText(entry, entries) };
|
|
142
|
+
// Only report positions explicitly present in the source; IDs are opaque.
|
|
143
|
+
if (Number.isInteger(entry.raw.rowIndex) && Number(entry.raw.rowIndex) >= 0)
|
|
144
|
+
cell.row = Number(entry.raw.rowIndex);
|
|
145
|
+
if (Number.isInteger(entry.raw.columnIndex) && Number(entry.raw.columnIndex) >= 0)
|
|
146
|
+
cell.column = Number(entry.raw.columnIndex);
|
|
147
|
+
const cells = cellsByTable.get(entry.tableOwner) ?? [];
|
|
148
|
+
cells.push(cell);
|
|
149
|
+
cellsByTable.set(entry.tableOwner, cells);
|
|
150
|
+
}
|
|
151
|
+
const result = [];
|
|
152
|
+
entries.forEach((entry, index) => {
|
|
153
|
+
if (!entry.keep || entry.tableOwner !== undefined)
|
|
154
|
+
return;
|
|
155
|
+
const node = entry.raw;
|
|
156
|
+
const box = node.absoluteBoundingBox ?? { x: 0, y: 0, width: 0, height: 0 };
|
|
157
|
+
const cells = node.type === "TABLE" ? cellsByTable.get(index) ?? [] : undefined;
|
|
158
|
+
result.push({
|
|
159
|
+
id: node.id, name: node.name, type: node.type,
|
|
160
|
+
x: box.x, y: box.y, width: box.width, height: box.height,
|
|
161
|
+
rotation: node.rotation ?? 0, imageRef: extractImageRef(node),
|
|
162
|
+
text: cells ? [node.characters, ...cells.map((cell) => cell.text)].filter((text) => text !== undefined && text !== "").join("\n") : node.characters,
|
|
163
|
+
parentId: entry.parentId, pageId: entry.pageId, sectionIds: entry.sectionIds,
|
|
164
|
+
renderNodeId: node.id, table: cells ? { cells } : undefined,
|
|
165
|
+
contentFingerprint: entry.contentFingerprint,
|
|
166
|
+
connectorStartId: node.connectorStart?.endpointNodeId,
|
|
167
|
+
connectorEndId: node.connectorEnd?.endpointNodeId,
|
|
168
|
+
connectorStartArrowhead: node.connectorStartStrokeCap,
|
|
169
|
+
connectorEndArrowhead: node.connectorEndStrokeCap,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=nodeTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeTree.js","sourceRoot":"","sources":["../../src/lib/nodeTree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BoardData, DocStructureHint, IngestMode, NormalizedNode } from "../types.js";
|
|
2
|
+
export declare const CACHE_SCHEMA_VERSION = 4;
|
|
3
|
+
export declare const EXTRACTION_VERSION = 5;
|
|
4
|
+
export declare const PROMPT_VERSION = 7;
|
|
5
|
+
export interface BoardCacheIdentity {
|
|
6
|
+
fileKey: string;
|
|
7
|
+
figmaLastModified?: string;
|
|
8
|
+
nodeHash: string;
|
|
9
|
+
docStructureHint: DocStructureHint;
|
|
10
|
+
customPhases?: string[];
|
|
11
|
+
ingestMode: IngestMode;
|
|
12
|
+
}
|
|
13
|
+
export interface BoardHistoryEntry {
|
|
14
|
+
cacheKey: string;
|
|
15
|
+
refinementId?: string;
|
|
16
|
+
nodeHash: string;
|
|
17
|
+
snapshotId?: string;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function extractFigmaLastModified(raw: unknown): string | undefined;
|
|
21
|
+
export declare function hashClusterNodes(nodes: NormalizedNode[]): string;
|
|
22
|
+
export declare function hashNormalizedNodes(nodes: NormalizedNode[]): string;
|
|
23
|
+
export declare function buildSnapshotId(fileKey: string, nodeHash: string, extractionVersion?: number): string;
|
|
24
|
+
export declare function getRefinementSignature(): string;
|
|
25
|
+
export declare function buildBoardCacheKey(identity: BoardCacheIdentity): string;
|
|
26
|
+
export declare function readCachedBoard(cacheKey: string, fileKey?: string): Promise<BoardData | undefined>;
|
|
27
|
+
export declare function readLatestBoard(fileKey: string): Promise<BoardData | undefined>;
|
|
28
|
+
export declare function readBoardSnapshot(fileKey: string, snapshotId: string): Promise<BoardData | undefined>;
|
|
29
|
+
export declare function hasLegacyBoard(fileKey: string): Promise<boolean>;
|
|
30
|
+
export declare function readBoardHistory(fileKey: string): Promise<BoardHistoryEntry[]>;
|
|
31
|
+
/** One manifest rename publishes sources, refinement, latest pointer and history together. */
|
|
32
|
+
export declare function persistBoard(board: BoardData, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, open, readFile, readdir, rename, rm, stat, unlink } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { getModelConfigSignature } from "./modelRegistry.js";
|
|
8
|
+
import { MAX_BOARD_NODES } from "./nodeTree.js";
|
|
9
|
+
export const CACHE_SCHEMA_VERSION = 4;
|
|
10
|
+
export const EXTRACTION_VERSION = 5;
|
|
11
|
+
export const PROMPT_VERSION = 7;
|
|
12
|
+
const defaultRoot = process.platform === "darwin"
|
|
13
|
+
? path.join(homedir(), "Library", "Caches", "figjam-context-mcp")
|
|
14
|
+
: process.platform === "win32"
|
|
15
|
+
? path.join(process.env.LOCALAPPDATA ?? path.join(homedir(), "AppData", "Local"), "figjam-context-mcp")
|
|
16
|
+
: path.join(process.env.XDG_CACHE_HOME ?? path.join(homedir(), ".cache"), "figjam-context-mcp");
|
|
17
|
+
const CACHE_ROOT = process.env.FIGJAM_MCP_CACHE_DIR ?? defaultRoot;
|
|
18
|
+
const CACHE_DIR = path.join(CACHE_ROOT, "v4");
|
|
19
|
+
const LEGACY_ROOT = process.env.FIGJAM_MCP_CACHE_DIR ?? path.join(process.cwd(), ".cache", "figjam-mcp");
|
|
20
|
+
const HISTORY_LIMIT = 20;
|
|
21
|
+
let mutationQueue = Promise.resolve();
|
|
22
|
+
const key = z.string().regex(/^[A-Za-z0-9_-]{1,128}$/);
|
|
23
|
+
const finite = z.number().finite();
|
|
24
|
+
const extractionVersionSchema = z.number().int().min(4).default(4);
|
|
25
|
+
const nodeSchema = z.object({
|
|
26
|
+
id: z.string(), name: z.string(), type: z.string(), x: finite, y: finite,
|
|
27
|
+
width: finite, height: finite, rotation: finite, text: z.string().optional(),
|
|
28
|
+
imageRef: z.string().optional(), parentId: z.string().optional(), pageId: z.string().optional(),
|
|
29
|
+
sectionIds: z.array(z.string()).optional(), renderNodeId: z.string().optional(),
|
|
30
|
+
contentFingerprint: z.string().optional(),
|
|
31
|
+
connectorStartId: z.string().optional(), connectorEndId: z.string().optional(),
|
|
32
|
+
connectorStartArrowhead: z.string().optional(), connectorEndArrowhead: z.string().optional(),
|
|
33
|
+
table: z.object({ cells: z.array(z.object({ id: z.string(), text: z.string(), row: finite.optional(), column: finite.optional() })) }).optional(),
|
|
34
|
+
}).passthrough();
|
|
35
|
+
const edgeSchema = z.object({ connectorId: z.string(), fromNodeId: z.string(), toNodeId: z.string(), label: z.string().optional(), direction: z.enum(["forward", "reverse", "bidirectional", "undirected"]).optional() });
|
|
36
|
+
const clusterSchema = z.object({
|
|
37
|
+
id: z.string(), nodeIds: z.array(z.string()), confirmedNodeIds: z.array(z.string()),
|
|
38
|
+
label: z.string(), summary: z.string(), phase: z.string().optional(),
|
|
39
|
+
boundingBox: z.object({ x: finite, y: finite, width: finite, height: finite }),
|
|
40
|
+
summarySource: z.enum(["vision_llm", "text_llm", "deterministic", "cache"]).optional(),
|
|
41
|
+
modelId: z.string().optional(), contentHash: z.string().optional(), cacheHit: z.boolean().optional(),
|
|
42
|
+
incomplete: z.boolean().optional(), fallbackReason: z.string().optional(), retryAfter: finite.optional(),
|
|
43
|
+
}).passthrough();
|
|
44
|
+
const boardSchema = z.object({
|
|
45
|
+
schemaVersion: z.literal(4), extractionVersion: extractionVersionSchema, snapshotId: key, boardId: key, fileKey: key,
|
|
46
|
+
docStructureHint: z.enum(["freeform", "double_diamond", "lean_canvas", "retro", "user_journey"]),
|
|
47
|
+
nodes: z.array(nodeSchema).max(MAX_BOARD_NODES), clusters: z.array(clusterSchema).max(MAX_BOARD_NODES), createdAt: finite,
|
|
48
|
+
connectorEdges: z.array(edgeSchema).optional(),
|
|
49
|
+
clusterRelations: z.array(z.object({ fromClusterId: z.string(), toClusterId: z.string(), labels: z.array(z.string()), edgeCount: finite })).optional(),
|
|
50
|
+
customPhases: z.array(z.string()).optional(), ingestMode: z.enum(["balanced", "max_quality", "max_speed"]).optional(),
|
|
51
|
+
cacheKey: key.optional(), figmaVersion: z.string().optional(), figmaLastModified: z.string().optional(),
|
|
52
|
+
nodeHash: z.string().optional(), refinementSignature: z.string().optional(), freshnessCheckedAt: finite.optional(),
|
|
53
|
+
qualityReport: z.object({ modelsUsed: z.array(z.string()), cachedClusters: finite, deterministicClusters: finite, visionClusters: finite, fallbackCount: finite, reusedClusters: finite.optional(), incompleteClusters: finite.optional(), fallbackReasons: z.record(z.string(), finite).optional(), nextRetryAt: finite.optional() }).optional(),
|
|
54
|
+
}).passthrough();
|
|
55
|
+
const entrySchema = z.object({ cacheKey: key, refinementId: key.optional(), nodeHash: z.string(), snapshotId: key.optional(), createdAt: finite });
|
|
56
|
+
const manifestSchema = z.object({ schemaVersion: z.literal(4), latest: key.optional(), latestRevision: key.optional(), history: z.array(entrySchema).max(HISTORY_LIMIT) });
|
|
57
|
+
export function extractFigmaLastModified(raw) {
|
|
58
|
+
const value = raw?.lastModified;
|
|
59
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
60
|
+
}
|
|
61
|
+
export function hashClusterNodes(nodes) {
|
|
62
|
+
return hash(stableStringify(nodes.map(node => ({ id: node.id, name: node.name, type: node.type,
|
|
63
|
+
text: node.text, imageRef: node.imageRef, table: node.table, contentFingerprint: node.contentFingerprint,
|
|
64
|
+
pageId: node.pageId, sectionIds: node.sectionIds,
|
|
65
|
+
connectorStartId: node.connectorStartId, connectorEndId: node.connectorEndId,
|
|
66
|
+
connectorStartArrowhead: node.connectorStartArrowhead, connectorEndArrowhead: node.connectorEndArrowhead,
|
|
67
|
+
rotation: node.rotation,
|
|
68
|
+
})).sort((a, b) => a.id.localeCompare(b.id))));
|
|
69
|
+
}
|
|
70
|
+
export function hashNormalizedNodes(nodes) {
|
|
71
|
+
return hash(stableStringify([...nodes].sort((a, b) => a.id.localeCompare(b.id))));
|
|
72
|
+
}
|
|
73
|
+
export function buildSnapshotId(fileKey, nodeHash, extractionVersion = EXTRACTION_VERSION) {
|
|
74
|
+
return hash(JSON.stringify([fileKey, extractionVersion, nodeHash]));
|
|
75
|
+
}
|
|
76
|
+
export function getRefinementSignature() {
|
|
77
|
+
return hash(JSON.stringify([getModelConfigSignature(), process.env.LLM_BASE_URL ?? "", EXTRACTION_VERSION, PROMPT_VERSION]));
|
|
78
|
+
}
|
|
79
|
+
export function buildBoardCacheKey(identity) {
|
|
80
|
+
return hash(JSON.stringify({ ...identity, figmaLastModified: undefined, schemaVersion: CACHE_SCHEMA_VERSION,
|
|
81
|
+
customPhases: identity.customPhases ?? [], refinementSignature: getRefinementSignature() }));
|
|
82
|
+
}
|
|
83
|
+
/** Sources are immutable and stored separately from provider-dependent refinements. */
|
|
84
|
+
async function writeBoardLocked(cacheKey, revisionId, board, signal) {
|
|
85
|
+
key.parse(cacheKey);
|
|
86
|
+
const nodeHash = board.nodeHash ?? hashNormalizedNodes(board.nodes);
|
|
87
|
+
const snapshotId = buildSnapshotId(board.fileKey, nodeHash);
|
|
88
|
+
const normalized = boardSchema.parse({ ...board, schemaVersion: 4, extractionVersion: EXTRACTION_VERSION, snapshotId, nodeHash, cacheKey });
|
|
89
|
+
const { nodes, connectorEdges, ...derived } = normalized;
|
|
90
|
+
const sourcePath = path.join(CACHE_DIR, `source-${snapshotId}.json`);
|
|
91
|
+
// Rewriting identical source bytes repairs corruption without changing source identity.
|
|
92
|
+
await atomicWrite(sourcePath, { schemaVersion: 4, extractionVersion: EXTRACTION_VERSION, snapshotId, fileKey: board.fileKey, nodes, connectorEdges }, signal);
|
|
93
|
+
await atomicWrite(cachePath(revisionId), { schemaVersion: 4, revisionId, snapshotId, derived }, signal);
|
|
94
|
+
return normalized;
|
|
95
|
+
}
|
|
96
|
+
export async function readCachedBoard(cacheKey, fileKey) {
|
|
97
|
+
try {
|
|
98
|
+
key.parse(cacheKey);
|
|
99
|
+
let revisionId = cacheKey;
|
|
100
|
+
if (fileKey) {
|
|
101
|
+
const manifest = await readManifest(fileKey);
|
|
102
|
+
const entry = manifest?.history.findLast(item => item.cacheKey === cacheKey);
|
|
103
|
+
if (!entry)
|
|
104
|
+
return undefined;
|
|
105
|
+
revisionId = entry.refinementId ?? entry.cacheKey;
|
|
106
|
+
}
|
|
107
|
+
const envelope = z.object({ schemaVersion: z.literal(4), revisionId: key, snapshotId: key, derived: z.record(z.string(), z.unknown()) }).parse(await readJson(cachePath(revisionId)));
|
|
108
|
+
const source = z.object({ schemaVersion: z.literal(4), extractionVersion: extractionVersionSchema, snapshotId: key, fileKey: key, nodes: z.array(nodeSchema).max(MAX_BOARD_NODES), connectorEdges: z.array(edgeSchema).optional() }).parse(await readJson(path.join(CACHE_DIR, `source-${envelope.snapshotId}.json`)));
|
|
109
|
+
const board = boardSchema.parse({ ...envelope.derived, nodes: source.nodes, connectorEdges: source.connectorEdges });
|
|
110
|
+
if (envelope.revisionId !== revisionId || (fileKey && (board.fileKey !== fileKey || board.cacheKey !== cacheKey)) || board.snapshotId !== envelope.snapshotId || board.snapshotId !== source.snapshotId || board.extractionVersion !== source.extractionVersion || board.fileKey !== source.fileKey || board.boardId !== board.fileKey || buildSnapshotId(board.fileKey, hashNormalizedNodes(board.nodes), board.extractionVersion) !== board.snapshotId)
|
|
111
|
+
throw new Error("Cache identity mismatch");
|
|
112
|
+
return board;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (!isMissing(error))
|
|
116
|
+
console.error("Persistent cache entry is invalid; run ingest_board to rebuild it.");
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export async function readLatestBoard(fileKey) {
|
|
121
|
+
const manifest = await readManifest(fileKey);
|
|
122
|
+
if (!manifest?.latest)
|
|
123
|
+
return undefined;
|
|
124
|
+
const board = await readCachedBoard(manifest.latestRevision ?? manifest.latest);
|
|
125
|
+
return board?.fileKey === fileKey ? board : undefined;
|
|
126
|
+
}
|
|
127
|
+
export async function readBoardSnapshot(fileKey, snapshotId) {
|
|
128
|
+
key.parse(snapshotId);
|
|
129
|
+
const manifest = await readManifest(fileKey);
|
|
130
|
+
const entry = manifest?.history.findLast(item => item.snapshotId === snapshotId);
|
|
131
|
+
if (!entry)
|
|
132
|
+
return undefined;
|
|
133
|
+
const board = await readCachedBoard(entry.refinementId ?? entry.cacheKey);
|
|
134
|
+
return board?.fileKey === fileKey && board.snapshotId === snapshotId ? board : undefined;
|
|
135
|
+
}
|
|
136
|
+
export async function hasLegacyBoard(fileKey) {
|
|
137
|
+
key.parse(fileKey);
|
|
138
|
+
try {
|
|
139
|
+
await stat(path.join(LEGACY_ROOT, `latest-${fileKey}.json`));
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export async function readBoardHistory(fileKey) {
|
|
147
|
+
return (await readManifest(fileKey))?.history ?? [];
|
|
148
|
+
}
|
|
149
|
+
/** One manifest rename publishes sources, refinement, latest pointer and history together. */
|
|
150
|
+
export async function persistBoard(board, signal) {
|
|
151
|
+
if (!board.cacheKey)
|
|
152
|
+
throw new Error("Cannot persist board without cacheKey");
|
|
153
|
+
await withMutation(async () => {
|
|
154
|
+
await garbageCollect().catch(() => console.error("Snapshot cleanup deferred; committed snapshots are intact."));
|
|
155
|
+
signal?.throwIfAborted();
|
|
156
|
+
const revisionId = hash(randomUUID());
|
|
157
|
+
const normalized = await writeBoardLocked(board.cacheKey, revisionId, board, signal);
|
|
158
|
+
const manifest = await readManifest(board.fileKey) ?? emptyManifest();
|
|
159
|
+
manifest.latest = board.cacheKey;
|
|
160
|
+
manifest.latestRevision = revisionId;
|
|
161
|
+
updateHistory(manifest, { cacheKey: board.cacheKey, refinementId: revisionId, snapshotId: normalized.snapshotId, nodeHash: normalized.nodeHash, createdAt: board.createdAt });
|
|
162
|
+
signal?.throwIfAborted();
|
|
163
|
+
await atomicWrite(manifestPath(board.fileKey), manifest, signal);
|
|
164
|
+
}, signal);
|
|
165
|
+
}
|
|
166
|
+
function updateHistory(manifest, entry) {
|
|
167
|
+
const last = manifest.history.at(-1);
|
|
168
|
+
if (last && last.snapshotId === entry.snapshotId && (last.nodeHash === entry.nodeHash || last.cacheKey === entry.cacheKey)) {
|
|
169
|
+
manifest.history[manifest.history.length - 1] = { ...entry, createdAt: last.createdAt };
|
|
170
|
+
}
|
|
171
|
+
else
|
|
172
|
+
manifest.history.push(entry);
|
|
173
|
+
manifest.history = manifest.history.slice(-HISTORY_LIMIT);
|
|
174
|
+
}
|
|
175
|
+
function emptyManifest() { return { schemaVersion: 4, history: [] }; }
|
|
176
|
+
async function readManifest(fileKey) {
|
|
177
|
+
try {
|
|
178
|
+
return manifestSchema.parse(await readJson(manifestPath(fileKey)));
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (!isMissing(error))
|
|
182
|
+
console.error("Board cache manifest is invalid; run ingest_board to rebuild it.");
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function garbageCollect() {
|
|
187
|
+
const names = await readdir(CACHE_DIR);
|
|
188
|
+
const refinements = new Set();
|
|
189
|
+
const sources = new Set();
|
|
190
|
+
// Fail closed if any manifest is damaged. Legacy v3 files are outside v4 and never touched.
|
|
191
|
+
try {
|
|
192
|
+
for (const name of names.filter(name => name.startsWith("board-") && name.endsWith(".json"))) {
|
|
193
|
+
const manifest = manifestSchema.parse(await readJson(path.join(CACHE_DIR, name)));
|
|
194
|
+
if (manifest.latest)
|
|
195
|
+
refinements.add(manifest.latestRevision ?? manifest.latest);
|
|
196
|
+
for (const entry of manifest.history)
|
|
197
|
+
refinements.add(entry.refinementId ?? entry.cacheKey);
|
|
198
|
+
}
|
|
199
|
+
for (const ref of refinements) {
|
|
200
|
+
const envelope = z.object({ schemaVersion: z.literal(4), snapshotId: key }).parse(await readJson(cachePath(ref)));
|
|
201
|
+
sources.add(envelope.snapshotId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
console.error("Snapshot cleanup skipped: a cache reference is invalid.");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// Only collect our versioned artifacts; preserve recent unpublished writes from compatibility callers.
|
|
209
|
+
for (const name of names) {
|
|
210
|
+
const sourceId = /^source-([a-f0-9]{64})\.json$/.exec(name)?.[1];
|
|
211
|
+
const refinementId = /^refinement-([A-Za-z0-9_-]{1,128})\.json$/.exec(name)?.[1];
|
|
212
|
+
if ((!sourceId || sources.has(sourceId)) && (!refinementId || refinements.has(refinementId)))
|
|
213
|
+
continue;
|
|
214
|
+
const filename = path.join(CACHE_DIR, name);
|
|
215
|
+
// A short grace period protects readers that captured the previous manifest before a publish.
|
|
216
|
+
if (Date.now() - (await stat(filename)).mtimeMs < 60_000)
|
|
217
|
+
continue;
|
|
218
|
+
await unlink(filename);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function privateDirectory() {
|
|
222
|
+
await mkdir(CACHE_DIR, { recursive: true, mode: 0o700 });
|
|
223
|
+
await chmod(CACHE_DIR, 0o700);
|
|
224
|
+
await chmod(CACHE_ROOT, 0o700);
|
|
225
|
+
}
|
|
226
|
+
async function atomicWrite(filename, value, signal) {
|
|
227
|
+
const temporary = `${filename}.${randomUUID()}.tmp`;
|
|
228
|
+
const file = await open(temporary, "wx", 0o600);
|
|
229
|
+
try {
|
|
230
|
+
await file.writeFile(`${JSON.stringify(value)}\n`, "utf8");
|
|
231
|
+
await file.sync();
|
|
232
|
+
await file.close();
|
|
233
|
+
signal?.throwIfAborted();
|
|
234
|
+
await rename(temporary, filename);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
await file.close().catch(() => undefined);
|
|
238
|
+
await rm(temporary, { force: true });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function withMutation(operation, signal) {
|
|
242
|
+
const result = mutationQueue.then(async () => {
|
|
243
|
+
signal?.throwIfAborted();
|
|
244
|
+
await privateDirectory();
|
|
245
|
+
const lockPath = path.join(CACHE_DIR, ".write-lock");
|
|
246
|
+
const token = randomUUID();
|
|
247
|
+
const started = Date.now();
|
|
248
|
+
for (;;) {
|
|
249
|
+
signal?.throwIfAborted();
|
|
250
|
+
try {
|
|
251
|
+
await mkdir(lockPath, { mode: 0o700 });
|
|
252
|
+
try {
|
|
253
|
+
await atomicWrite(path.join(lockPath, "owner.json"), { pid: process.pid, token });
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (error.code !== "EEXIST")
|
|
263
|
+
throw error;
|
|
264
|
+
// Fail closed: deleting a seemingly stale lock races with another process
|
|
265
|
+
// acquiring it. Recovery requires stopping all servers before removing it.
|
|
266
|
+
if (Date.now() - started >= 10_000)
|
|
267
|
+
throw new Error("Cache is busy in another process. Retry ingest_board. If a writer crashed, stop all servers before removing v4/.write-lock from the cache directory.");
|
|
268
|
+
await delay(20, undefined, { signal });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
return await operation();
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
try {
|
|
276
|
+
const owner = await readJson(path.join(lockPath, "owner.json"));
|
|
277
|
+
if (owner.token === token)
|
|
278
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
console.error("Cache lock cleanup failed; stop all servers before removing v4/.write-lock.");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
mutationQueue = result.catch(() => undefined);
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
function manifestPath(fileKey) { key.parse(fileKey); return path.join(CACHE_DIR, `board-${fileKey}.json`); }
|
|
289
|
+
function cachePath(cacheKey) { key.parse(cacheKey); return path.join(CACHE_DIR, `refinement-${cacheKey}.json`); }
|
|
290
|
+
async function readJson(filename) {
|
|
291
|
+
const size = (await stat(filename)).size;
|
|
292
|
+
if (size > 128 * 1024 * 1024)
|
|
293
|
+
throw new Error("Cache file exceeds 128 MiB");
|
|
294
|
+
return JSON.parse(await readFile(filename, "utf8"));
|
|
295
|
+
}
|
|
296
|
+
function isMissing(error) { return error.code === "ENOENT"; }
|
|
297
|
+
function hash(value) { return createHash("sha256").update(value).digest("hex"); }
|
|
298
|
+
function stableStringify(value) {
|
|
299
|
+
return JSON.stringify(value, (_key, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b))) : item);
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=persistentCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistentCache.js","sourceRoot":"","sources":["../../src/lib/persistentCache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { NormalizedNode, Cluster } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Board-level entry point. Pages have independent coordinate systems and
|
|
4
|
+
* sections are explicit author-owned boundaries, so geometric components must
|
|
5
|
+
* never cross either. Cluster identity follows membership, not canvas order.
|
|
6
|
+
*/
|
|
7
|
+
export declare function partitionedBoardClusters(nodes: NormalizedNode[], options?: GeometricClusterOptions): Cluster[];
|
|
8
|
+
export interface GeometricClusterOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Max euclidean gap between node footprints to join a cluster (canvas px).
|
|
11
|
+
* Setting this disables adaptive estimation.
|
|
12
|
+
*/
|
|
13
|
+
gapThreshold?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Derive the threshold from the board's own density (median
|
|
16
|
+
* nearest-neighbor gap) instead of the fixed default. On by default;
|
|
17
|
+
* only applies when no explicit gapThreshold is set and the board has at
|
|
18
|
+
* least MIN_ADAPTIVE_NODES nodes.
|
|
19
|
+
*/
|
|
20
|
+
adaptiveGapThreshold?: boolean;
|
|
21
|
+
/** Maximum members in one returned cluster; larger groups are spatially bisected. */
|
|
22
|
+
maxClusterSize?: number;
|
|
23
|
+
/** Maximum spatial-hash cells occupied by one node before using the overflow path. */
|
|
24
|
+
maxGridCellsPerNode?: number;
|
|
25
|
+
/** Total footprint-to-cell entries allowed for the entire board partition. */
|
|
26
|
+
maxGridEntries?: number;
|
|
27
|
+
/** Hard work bound, including pairs already connected in the union-find. */
|
|
28
|
+
maxPairComparisons?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Groups NormalizedNode entries into spatial clusters purely by geometry
|
|
32
|
+
* (single-linkage: a node joins a cluster if it is within the gap threshold
|
|
33
|
+
* of ANY node already in it), before any vision-based refinement is applied
|
|
34
|
+
* in visionInterpreter.ts. Connected components above maxClusterSize are
|
|
35
|
+
* spatially bisected so one bridge/background node cannot create an unbounded
|
|
36
|
+
* downstream LLM unit.
|
|
37
|
+
*
|
|
38
|
+
* Rotation handling: Figma reports `x/y/width/height` as the axis-aligned
|
|
39
|
+
* bounding box (AABB), which over-approximates rotated nodes — a thin sticky
|
|
40
|
+
* rotated 45° gets a much larger AABB, whose far corners could falsely
|
|
41
|
+
* bridge two neighboring groups. For rotated nodes we therefore measure
|
|
42
|
+
* distance against a disc centered on the AABB center (which is exactly the
|
|
43
|
+
* rotated node's true center) with radius = mean AABB half-extent, a close
|
|
44
|
+
* approximation of the rotated footprint. Non-rotated nodes keep the exact
|
|
45
|
+
* rect-to-rect gap.
|
|
46
|
+
*
|
|
47
|
+
* Performance: instead of testing all O(n²) pairs, footprints are indexed
|
|
48
|
+
* into a uniform grid (spatial hash). Each footprint's bounds — inflated by
|
|
49
|
+
* half the gap threshold — are inserted into every cell they cover; two
|
|
50
|
+
* footprints within the threshold necessarily share a cell, so only
|
|
51
|
+
* cell-local pairs need the exact gap check. Footprints covering too many
|
|
52
|
+
* cells use a bounded pairwise overflow path instead. This stays near-linear
|
|
53
|
+
* on real boards and makes work depend on node count rather than canvas area.
|
|
54
|
+
*
|
|
55
|
+
* Returns coarse cluster candidates (IDs + bounding boxes only); labels and
|
|
56
|
+
* summaries are added later by the vision step.
|
|
57
|
+
*/
|
|
58
|
+
export declare function geometricPreCluster(nodes: NormalizedNode[], options?: GeometricClusterOptions): Cluster[];
|