@atezer/figma-mcp-bridge 1.9.5 → 1.9.7
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/CHANGELOG.md +114 -0
- package/dist/core/blocking-tracker.d.ts +68 -0
- package/dist/core/blocking-tracker.d.ts.map +1 -0
- package/dist/core/blocking-tracker.js +152 -0
- package/dist/core/blocking-tracker.js.map +1 -0
- package/dist/core/bootstrap-injector.d.ts +50 -0
- package/dist/core/bootstrap-injector.d.ts.map +1 -0
- package/dist/core/bootstrap-injector.js +134 -0
- package/dist/core/bootstrap-injector.js.map +1 -0
- package/dist/core/embedded-skills.d.ts +17 -0
- package/dist/core/embedded-skills.d.ts.map +1 -0
- package/dist/core/embedded-skills.js +817 -0
- package/dist/core/embedded-skills.js.map +1 -0
- package/dist/core/plugin-bridge-connector.d.ts +10 -0
- package/dist/core/plugin-bridge-connector.d.ts.map +1 -1
- package/dist/core/plugin-bridge-connector.js +7 -0
- package/dist/core/plugin-bridge-connector.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/local-plugin-only.d.ts.map +1 -1
- package/dist/local-plugin-only.js +98 -3
- package/dist/local-plugin-only.js.map +1 -1
- package/f-mcp-plugin/code.js +307 -2
- package/f-mcp-plugin/ui.html +19 -1
- package/package.json +3 -2
- package/skills/fmcp-intent-router/SKILL.md +56 -9
- package/skills/fmcp-screen-orchestrator/SKILL.md +26 -0
package/f-mcp-plugin/code.js
CHANGED
|
@@ -6,7 +6,95 @@
|
|
|
6
6
|
|
|
7
7
|
// v1.8.0+: Plugin version reported in WebSocket "ready" handshake.
|
|
8
8
|
// Keep in sync with package.json and src/core/version.ts.
|
|
9
|
-
var FMCP_PLUGIN_VERSION = '1.9.
|
|
9
|
+
var FMCP_PLUGIN_VERSION = '1.9.7';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* v1.9.6: Post-execute scan — figma_execute sonrasında oluşturulan node'ları
|
|
13
|
+
* otomatik tarayıp unbound fill/padding/radius/text-style tespit eder.
|
|
14
|
+
* Skill direktifi "return { createdNodeIds: [...] }" diyor — plugin bunu okuyup
|
|
15
|
+
* subtree'yi tarar. Violations varsa response'a _postExecuteScan field'ı eklenir,
|
|
16
|
+
* server bunu _DESIGN_SYSTEM_VIOLATIONS_BLOCKING'e dönüştürür.
|
|
17
|
+
*/
|
|
18
|
+
async function postExecuteScan(result) {
|
|
19
|
+
var nodeIds = [];
|
|
20
|
+
if (result && typeof result === 'object') {
|
|
21
|
+
if (Array.isArray(result.createdNodeIds)) nodeIds = result.createdNodeIds;
|
|
22
|
+
else if (Array.isArray(result.nodeIds)) nodeIds = result.nodeIds;
|
|
23
|
+
else if (Array.isArray(result.ids)) nodeIds = result.ids;
|
|
24
|
+
else if (typeof result.frameId === 'string') nodeIds = [result.frameId];
|
|
25
|
+
else if (typeof result.rootId === 'string') nodeIds = [result.rootId];
|
|
26
|
+
else if (typeof result.nodeId === 'string') nodeIds = [result.nodeId];
|
|
27
|
+
}
|
|
28
|
+
if (nodeIds.length === 0) return null;
|
|
29
|
+
|
|
30
|
+
var violations = [];
|
|
31
|
+
var maxViolations = 10;
|
|
32
|
+
var maxNodes = 500; // safety cap per subtree
|
|
33
|
+
var totalChecked = 0;
|
|
34
|
+
|
|
35
|
+
for (var i = 0; i < nodeIds.length && violations.length < maxViolations; i++) {
|
|
36
|
+
var root;
|
|
37
|
+
try { root = await figma.getNodeByIdAsync(nodeIds[i]); } catch (e) { continue; }
|
|
38
|
+
if (!root) continue;
|
|
39
|
+
var stack = [root];
|
|
40
|
+
var visited = 0;
|
|
41
|
+
while (stack.length > 0 && visited < maxNodes) {
|
|
42
|
+
var n = stack.pop(); visited++; totalChecked++;
|
|
43
|
+
if (!n) continue;
|
|
44
|
+
|
|
45
|
+
// Fill check (skip instances — their bindings come from main component)
|
|
46
|
+
if (Array.isArray(n.fills) && n.type !== 'INSTANCE') {
|
|
47
|
+
for (var fi = 0; fi < n.fills.length; fi++) {
|
|
48
|
+
var fl = n.fills[fi];
|
|
49
|
+
if (fl && fl.visible !== false && fl.type === 'SOLID') {
|
|
50
|
+
var fbv = n.boundVariables && n.boundVariables.fills;
|
|
51
|
+
if (!(fbv && (Array.isArray(fbv) ? fbv[fi] : true)) && violations.length < maxViolations) {
|
|
52
|
+
violations.push({ nodeId: n.id, nodeName: n.name, category: 'UNBOUND_FILL', hint: 'setBoundVariableForPaint cagrisi eksik' });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if ((n.type === 'FRAME' || n.type === 'COMPONENT') && n.boundVariables !== undefined) {
|
|
59
|
+
var padProps = ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'];
|
|
60
|
+
for (var j = 0; j < padProps.length && violations.length < maxViolations; j++) {
|
|
61
|
+
if (typeof n[padProps[j]] === 'number' && n[padProps[j]] > 0 && !(n.boundVariables && n.boundVariables[padProps[j]])) {
|
|
62
|
+
violations.push({ nodeId: n.id, nodeName: n.name, category: 'UNBOUND_PADDING', prop: padProps[j], value: n[padProps[j]] });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
var radProps = ['cornerRadius', 'topLeftRadius', 'topRightRadius', 'bottomLeftRadius', 'bottomRightRadius'];
|
|
66
|
+
for (var k = 0; k < radProps.length && violations.length < maxViolations; k++) {
|
|
67
|
+
if (typeof n[radProps[k]] === 'number' && n[radProps[k]] > 0 && !(n.boundVariables && n.boundVariables[radProps[k]])) {
|
|
68
|
+
violations.push({ nodeId: n.id, nodeName: n.name, category: 'UNBOUND_RADIUS', prop: radProps[k], value: n[radProps[k]] });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (typeof n.itemSpacing === 'number' && n.itemSpacing > 0 && !(n.boundVariables && n.boundVariables.itemSpacing) && violations.length < maxViolations) {
|
|
72
|
+
violations.push({ nodeId: n.id, nodeName: n.name, category: 'UNBOUND_ITEMSPACING', value: n.itemSpacing });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (n.type === 'TEXT' && !(n.textStyleId && typeof n.textStyleId === 'string' && n.textStyleId !== '') && violations.length < maxViolations) {
|
|
77
|
+
violations.push({ nodeId: n.id, nodeName: n.name, category: 'UNBOUND_TEXTSTYLE', sample: String(n.characters || '').slice(0, 30) });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (n.children) {
|
|
81
|
+
for (var ci = 0; ci < n.children.length; ci++) stack.push(n.children[ci]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
scanned: true,
|
|
88
|
+
rootsScanned: nodeIds.length,
|
|
89
|
+
totalChecked: totalChecked,
|
|
90
|
+
violationCount: violations.length,
|
|
91
|
+
violations: violations,
|
|
92
|
+
passed: violations.length === 0,
|
|
93
|
+
hint: violations.length > 0
|
|
94
|
+
? '❌ v1.9.6 post-execute scan: ' + violations.length + ' unbound node tespit edildi. Kodu duzelt — her node icin setBoundVariable/setTextStyleIdAsync cagrisi eksik.'
|
|
95
|
+
: '✅ v1.9.6 post-execute scan: Tum node\'lar bound.',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
10
98
|
|
|
11
99
|
// v1.9.5: Metadata summary builder for returnMode='summary' (screenshotsuz)
|
|
12
100
|
async function buildNodeSummary(node, maxDepth) {
|
|
@@ -642,6 +730,17 @@ figma.ui.onmessage = async (msg) => {
|
|
|
642
730
|
safeResult = { __serializationError: true, message: 'Result could not be serialized: ' + (serErr.message || String(serErr)), resultType: typeof result };
|
|
643
731
|
}
|
|
644
732
|
|
|
733
|
+
// v1.9.6: Post-execute scan — oluşturulan node'larda unbound tespit
|
|
734
|
+
var postScan = null;
|
|
735
|
+
try {
|
|
736
|
+
postScan = await postExecuteScan(result);
|
|
737
|
+
if (postScan && postScan.violationCount > 0) {
|
|
738
|
+
console.warn('🌉 [F-MCP v1.9.6] Post-execute scan: ' + postScan.violationCount + ' unbound node detected');
|
|
739
|
+
}
|
|
740
|
+
} catch (psErr) {
|
|
741
|
+
console.warn('🌉 [F-MCP v1.9.6] Post-execute scan failed:', psErr && psErr.message ? psErr.message : String(psErr));
|
|
742
|
+
}
|
|
743
|
+
|
|
645
744
|
figma.ui.postMessage({
|
|
646
745
|
type: 'EXECUTE_CODE_RESULT',
|
|
647
746
|
requestId: msg.requestId,
|
|
@@ -652,7 +751,8 @@ figma.ui.onmessage = async (msg) => {
|
|
|
652
751
|
fileContext: {
|
|
653
752
|
fileName: figma.root.name,
|
|
654
753
|
fileKey: figma.fileKey || null
|
|
655
|
-
}
|
|
754
|
+
},
|
|
755
|
+
_postExecuteScan: postScan // v1.9.6: BLOCKING signal source for server
|
|
656
756
|
});
|
|
657
757
|
|
|
658
758
|
} catch (error) {
|
|
@@ -2839,6 +2939,211 @@ figma.ui.onmessage = async (msg) => {
|
|
|
2839
2939
|
}
|
|
2840
2940
|
}
|
|
2841
2941
|
|
|
2942
|
+
// ============================================================================
|
|
2943
|
+
// CREATE_MINI_DS (v1.9.7) — Boş dosyada minimal Design System kur.
|
|
2944
|
+
// 2-phase batched: Phase 1 = Variable Collections + Variables + Text Styles
|
|
2945
|
+
// Phase 2 = Components (Button/Input/Card)
|
|
2946
|
+
// Her phase ayrı promise chain, timeout 30s içinde tamamlanır.
|
|
2947
|
+
// ============================================================================
|
|
2948
|
+
else if (msg.type === 'CREATE_MINI_DS') {
|
|
2949
|
+
try {
|
|
2950
|
+
console.log('🌉 [F-MCP v1.9.7] CREATE_MINI_DS starting...');
|
|
2951
|
+
|
|
2952
|
+
var primaryColor = msg.primaryColor || '#1464FF';
|
|
2953
|
+
var fontFamily = msg.fontFamily || 'Inter';
|
|
2954
|
+
var dsName = msg.name || 'Mini DS';
|
|
2955
|
+
var includeComponents = msg.includeComponents !== false;
|
|
2956
|
+
|
|
2957
|
+
// ---------- Hex → RGB ----------
|
|
2958
|
+
function hexToRgb(hex) {
|
|
2959
|
+
var h = hex.replace('#', '');
|
|
2960
|
+
if (h.length === 3) h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2];
|
|
2961
|
+
var r = parseInt(h.slice(0,2), 16) / 255;
|
|
2962
|
+
var g = parseInt(h.slice(2,4), 16) / 255;
|
|
2963
|
+
var b = parseInt(h.slice(4,6), 16) / 255;
|
|
2964
|
+
return { r: r, g: g, b: b };
|
|
2965
|
+
}
|
|
2966
|
+
function mix(a, b, t) { return { r: a.r*(1-t)+b.r*t, g: a.g*(1-t)+b.g*t, b: a.b*(1-t)+b.b*t }; }
|
|
2967
|
+
|
|
2968
|
+
var primaryRgb = hexToRgb(primaryColor);
|
|
2969
|
+
var black = { r: 0, g: 0, b: 0 };
|
|
2970
|
+
var white = { r: 1, g: 1, b: 1 };
|
|
2971
|
+
|
|
2972
|
+
// ---------- PHASE 1: Variable Collections + Variables + Text Styles ----------
|
|
2973
|
+
var colorCollection = figma.variables.createVariableCollection(dsName + ' — Colors');
|
|
2974
|
+
var modeId = colorCollection.defaultModeId;
|
|
2975
|
+
var colorVars = {};
|
|
2976
|
+
|
|
2977
|
+
function addColorVar(name, rgb) {
|
|
2978
|
+
var v = figma.variables.createVariable(name, colorCollection, 'COLOR');
|
|
2979
|
+
v.setValueForMode(modeId, rgb);
|
|
2980
|
+
colorVars[name] = v;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
addColorVar('primary/default', primaryRgb);
|
|
2984
|
+
addColorVar('primary/hover', mix(primaryRgb, black, 0.1));
|
|
2985
|
+
addColorVar('primary/disabled', mix(primaryRgb, white, 0.5));
|
|
2986
|
+
addColorVar('bg/level-0', white);
|
|
2987
|
+
addColorVar('bg/level-1', { r: 0.97, g: 0.97, b: 0.98 });
|
|
2988
|
+
addColorVar('bg/level-2', { r: 0.93, g: 0.94, b: 0.96 });
|
|
2989
|
+
addColorVar('text/primary', { r: 0.1, g: 0.1, b: 0.1 });
|
|
2990
|
+
addColorVar('text/secondary', { r: 0.4, g: 0.4, b: 0.45 });
|
|
2991
|
+
addColorVar('text/on-primary', white);
|
|
2992
|
+
addColorVar('success/default', { r: 0.18, g: 0.73, b: 0.39 });
|
|
2993
|
+
addColorVar('error/default', { r: 0.91, g: 0.22, b: 0.27 });
|
|
2994
|
+
addColorVar('warning/default', { r: 0.98, g: 0.69, b: 0.13 });
|
|
2995
|
+
|
|
2996
|
+
var sizingCollection = figma.variables.createVariableCollection(dsName + ' — Sizing');
|
|
2997
|
+
var sizingMode = sizingCollection.defaultModeId;
|
|
2998
|
+
var sizingVars = {};
|
|
2999
|
+
function addSizingVar(name, value) {
|
|
3000
|
+
var v = figma.variables.createVariable(name, sizingCollection, 'FLOAT');
|
|
3001
|
+
v.setValueForMode(sizingMode, value);
|
|
3002
|
+
sizingVars[name] = v;
|
|
3003
|
+
}
|
|
3004
|
+
addSizingVar('spacing/xs', 4);
|
|
3005
|
+
addSizingVar('spacing/sm', 8);
|
|
3006
|
+
addSizingVar('spacing/md', 16);
|
|
3007
|
+
addSizingVar('spacing/lg', 24);
|
|
3008
|
+
addSizingVar('spacing/xl', 32);
|
|
3009
|
+
addSizingVar('radius/sm', 4);
|
|
3010
|
+
addSizingVar('radius/md', 8);
|
|
3011
|
+
addSizingVar('radius/lg', 16);
|
|
3012
|
+
|
|
3013
|
+
// Text Styles (font loading required)
|
|
3014
|
+
await figma.loadFontAsync({ family: fontFamily, style: 'Semi Bold' }).catch(function() {});
|
|
3015
|
+
await figma.loadFontAsync({ family: fontFamily, style: 'Regular' }).catch(function() {});
|
|
3016
|
+
|
|
3017
|
+
function createTextStyle(name, fontSize, lineHeight, fontStyle) {
|
|
3018
|
+
var ts = figma.createTextStyle();
|
|
3019
|
+
ts.name = name;
|
|
3020
|
+
try {
|
|
3021
|
+
ts.fontName = { family: fontFamily, style: fontStyle };
|
|
3022
|
+
ts.fontSize = fontSize;
|
|
3023
|
+
ts.lineHeight = { unit: 'PIXELS', value: lineHeight };
|
|
3024
|
+
} catch (e) {
|
|
3025
|
+
console.warn('TextStyle ' + name + ' font set failed:', e.message);
|
|
3026
|
+
}
|
|
3027
|
+
return ts;
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
var titleStyle = createTextStyle(dsName + '/Title/Large', 24, 32, 'Semi Bold');
|
|
3031
|
+
var bodyStyle = createTextStyle(dsName + '/Body/Default', 14, 20, 'Regular');
|
|
3032
|
+
var captionStyle = createTextStyle(dsName + '/Caption/Small', 12, 16, 'Regular');
|
|
3033
|
+
|
|
3034
|
+
console.log('🌉 [F-MCP v1.9.7] Phase 1 done — variables + text styles created');
|
|
3035
|
+
|
|
3036
|
+
// ---------- PHASE 2: Components ----------
|
|
3037
|
+
var componentIds = {};
|
|
3038
|
+
if (includeComponents) {
|
|
3039
|
+
// Button component
|
|
3040
|
+
var buttonFrame = figma.createFrame();
|
|
3041
|
+
buttonFrame.name = dsName + '/Button/Primary';
|
|
3042
|
+
buttonFrame.layoutMode = 'HORIZONTAL';
|
|
3043
|
+
buttonFrame.primaryAxisSizingMode = 'AUTO';
|
|
3044
|
+
buttonFrame.counterAxisSizingMode = 'AUTO';
|
|
3045
|
+
buttonFrame.primaryAxisAlignItems = 'CENTER';
|
|
3046
|
+
buttonFrame.counterAxisAlignItems = 'CENTER';
|
|
3047
|
+
buttonFrame.setBoundVariable('paddingLeft', sizingVars['spacing/md']);
|
|
3048
|
+
buttonFrame.setBoundVariable('paddingRight', sizingVars['spacing/md']);
|
|
3049
|
+
buttonFrame.setBoundVariable('paddingTop', sizingVars['spacing/sm']);
|
|
3050
|
+
buttonFrame.setBoundVariable('paddingBottom', sizingVars['spacing/sm']);
|
|
3051
|
+
buttonFrame.setBoundVariable('cornerRadius', sizingVars['radius/md']);
|
|
3052
|
+
buttonFrame.fills = [figma.variables.setBoundVariableForPaint({ type: 'SOLID', color: primaryRgb }, 'color', colorVars['primary/default'])];
|
|
3053
|
+
|
|
3054
|
+
var buttonText = figma.createText();
|
|
3055
|
+
try {
|
|
3056
|
+
buttonText.fontName = { family: fontFamily, style: 'Semi Bold' };
|
|
3057
|
+
buttonText.characters = 'Button';
|
|
3058
|
+
await buttonText.setTextStyleIdAsync(bodyStyle.id).catch(function() {});
|
|
3059
|
+
buttonText.fills = [figma.variables.setBoundVariableForPaint({ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }, 'color', colorVars['text/on-primary'])];
|
|
3060
|
+
} catch (e) { console.warn('Button text fail:', e.message); }
|
|
3061
|
+
buttonFrame.appendChild(buttonText);
|
|
3062
|
+
var buttonComp = figma.createComponentFromNode(buttonFrame);
|
|
3063
|
+
componentIds.button = buttonComp.id;
|
|
3064
|
+
|
|
3065
|
+
// Input component
|
|
3066
|
+
var inputFrame = figma.createFrame();
|
|
3067
|
+
inputFrame.name = dsName + '/Input/Default';
|
|
3068
|
+
inputFrame.resize(240, 40);
|
|
3069
|
+
inputFrame.layoutMode = 'HORIZONTAL';
|
|
3070
|
+
inputFrame.primaryAxisAlignItems = 'CENTER';
|
|
3071
|
+
inputFrame.setBoundVariable('paddingLeft', sizingVars['spacing/md']);
|
|
3072
|
+
inputFrame.setBoundVariable('paddingRight', sizingVars['spacing/md']);
|
|
3073
|
+
inputFrame.setBoundVariable('cornerRadius', sizingVars['radius/md']);
|
|
3074
|
+
inputFrame.fills = [figma.variables.setBoundVariableForPaint({ type: 'SOLID', color: { r: 0.97, g: 0.97, b: 0.98 } }, 'color', colorVars['bg/level-1'])];
|
|
3075
|
+
inputFrame.strokes = [figma.variables.setBoundVariableForPaint({ type: 'SOLID', color: { r: 0.9, g: 0.9, b: 0.92 } }, 'color', colorVars['bg/level-2'])];
|
|
3076
|
+
inputFrame.strokeWeight = 1;
|
|
3077
|
+
|
|
3078
|
+
var placeholder = figma.createText();
|
|
3079
|
+
try {
|
|
3080
|
+
placeholder.fontName = { family: fontFamily, style: 'Regular' };
|
|
3081
|
+
placeholder.characters = 'Placeholder';
|
|
3082
|
+
await placeholder.setTextStyleIdAsync(bodyStyle.id).catch(function() {});
|
|
3083
|
+
placeholder.fills = [figma.variables.setBoundVariableForPaint({ type: 'SOLID', color: { r: 0.4, g: 0.4, b: 0.45 } }, 'color', colorVars['text/secondary'])];
|
|
3084
|
+
} catch (e) {}
|
|
3085
|
+
inputFrame.appendChild(placeholder);
|
|
3086
|
+
var inputComp = figma.createComponentFromNode(inputFrame);
|
|
3087
|
+
componentIds.input = inputComp.id;
|
|
3088
|
+
|
|
3089
|
+
// Card component
|
|
3090
|
+
var cardFrame = figma.createFrame();
|
|
3091
|
+
cardFrame.name = dsName + '/Card/Default';
|
|
3092
|
+
cardFrame.layoutMode = 'VERTICAL';
|
|
3093
|
+
cardFrame.primaryAxisSizingMode = 'AUTO';
|
|
3094
|
+
cardFrame.counterAxisSizingMode = 'AUTO';
|
|
3095
|
+
cardFrame.setBoundVariable('paddingLeft', sizingVars['spacing/lg']);
|
|
3096
|
+
cardFrame.setBoundVariable('paddingRight', sizingVars['spacing/lg']);
|
|
3097
|
+
cardFrame.setBoundVariable('paddingTop', sizingVars['spacing/lg']);
|
|
3098
|
+
cardFrame.setBoundVariable('paddingBottom', sizingVars['spacing/lg']);
|
|
3099
|
+
cardFrame.setBoundVariable('itemSpacing', sizingVars['spacing/sm']);
|
|
3100
|
+
cardFrame.setBoundVariable('cornerRadius', sizingVars['radius/lg']);
|
|
3101
|
+
cardFrame.fills = [figma.variables.setBoundVariableForPaint({ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }, 'color', colorVars['bg/level-0'])];
|
|
3102
|
+
cardFrame.strokes = [figma.variables.setBoundVariableForPaint({ type: 'SOLID', color: { r: 0.93, g: 0.94, b: 0.96 } }, 'color', colorVars['bg/level-2'])];
|
|
3103
|
+
cardFrame.strokeWeight = 1;
|
|
3104
|
+
|
|
3105
|
+
var cardTitle = figma.createText();
|
|
3106
|
+
try {
|
|
3107
|
+
cardTitle.fontName = { family: fontFamily, style: 'Semi Bold' };
|
|
3108
|
+
cardTitle.characters = 'Card title';
|
|
3109
|
+
await cardTitle.setTextStyleIdAsync(titleStyle.id).catch(function() {});
|
|
3110
|
+
cardTitle.fills = [figma.variables.setBoundVariableForPaint({ type: 'SOLID', color: { r: 0.1, g: 0.1, b: 0.1 } }, 'color', colorVars['text/primary'])];
|
|
3111
|
+
} catch (e) {}
|
|
3112
|
+
cardFrame.appendChild(cardTitle);
|
|
3113
|
+
var cardComp = figma.createComponentFromNode(cardFrame);
|
|
3114
|
+
componentIds.card = cardComp.id;
|
|
3115
|
+
|
|
3116
|
+
console.log('🌉 [F-MCP v1.9.7] Phase 2 done — 3 components created');
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
figma.ui.postMessage({
|
|
3120
|
+
type: 'CREATE_MINI_DS_RESULT',
|
|
3121
|
+
requestId: msg.requestId,
|
|
3122
|
+
success: true,
|
|
3123
|
+
dsName: dsName,
|
|
3124
|
+
variableCollectionIds: { colors: colorCollection.id, sizing: sizingCollection.id },
|
|
3125
|
+
textStyleIds: { title: titleStyle.id, body: bodyStyle.id, caption: captionStyle.id },
|
|
3126
|
+
componentIds: componentIds,
|
|
3127
|
+
summary: {
|
|
3128
|
+
colorVariableCount: Object.keys(colorVars).length,
|
|
3129
|
+
sizingVariableCount: Object.keys(sizingVars).length,
|
|
3130
|
+
textStyleCount: 3,
|
|
3131
|
+
componentCount: Object.keys(componentIds).length,
|
|
3132
|
+
},
|
|
3133
|
+
hint: "v1.9.7 Mini DS hazir. Ekran kurmak icin figma_execute kullan: importVariableByKeyAsync + importComponentByKeyAsync ile key'leri al, instance + setBoundVariable yaparak olustur.",
|
|
3134
|
+
});
|
|
3135
|
+
} catch (error) {
|
|
3136
|
+
var errorMsg = error && error.message ? error.message : String(error);
|
|
3137
|
+
console.error('🌉 [F-MCP v1.9.7] CREATE_MINI_DS error:', errorMsg);
|
|
3138
|
+
figma.ui.postMessage({
|
|
3139
|
+
type: 'CREATE_MINI_DS_RESULT',
|
|
3140
|
+
requestId: msg.requestId,
|
|
3141
|
+
success: false,
|
|
3142
|
+
error: errorMsg,
|
|
3143
|
+
});
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
2842
3147
|
// ============================================================================
|
|
2843
3148
|
// DELETE_NODE - Delete a node
|
|
2844
3149
|
// ============================================================================
|
package/f-mcp-plugin/ui.html
CHANGED
|
@@ -424,7 +424,7 @@
|
|
|
424
424
|
window.__figmaFileName = null;
|
|
425
425
|
// v1.8.0+: Plugin version reported in WebSocket "ready" handshake.
|
|
426
426
|
// Keep in sync with package.json, src/core/version.ts, f-mcp-plugin/code.js.
|
|
427
|
-
var FMCP_PLUGIN_VERSION = '1.9.
|
|
427
|
+
var FMCP_PLUGIN_VERSION = '1.9.7';
|
|
428
428
|
|
|
429
429
|
// v1.9.2+ Startup log — Figma plugin iframe'de yeni kod yüklendiğini doğrulamak için.
|
|
430
430
|
// Console'da bu log görünüyorsa plugin güncel. Görünmüyorsa iframe eski kopyayı tutuyor
|
|
@@ -778,11 +778,28 @@
|
|
|
778
778
|
if (options.format) params.format = options.format; // PNG, JPG
|
|
779
779
|
if (options.scale != null) params.scale = options.scale; // 1, 2, 4, etc.
|
|
780
780
|
if (options.jpegQuality != null) params.jpegQuality = options.jpegQuality; // 30-100 (v1.8.0)
|
|
781
|
+
// v1.9.5 method selection params
|
|
782
|
+
if (options.returnMode) params.returnMode = options.returnMode;
|
|
783
|
+
if (options.regionStrategy) params.regionStrategy = options.regionStrategy;
|
|
784
|
+
if (options.maxRegions != null) params.maxRegions = options.maxRegions;
|
|
785
|
+
if (options.sliceHeight != null) params.sliceHeight = options.sliceHeight;
|
|
786
|
+
if (options.requestedSlices) params.requestedSlices = options.requestedSlices;
|
|
781
787
|
}
|
|
782
788
|
return window.sendPluginCommand('CAPTURE_SCREENSHOT', params, 30000)
|
|
783
789
|
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
784
790
|
};
|
|
785
791
|
|
|
792
|
+
// v1.9.7: Create Mini DS — variables + text styles + components in one call
|
|
793
|
+
window.createMiniDs = (params) => {
|
|
794
|
+
return window.sendPluginCommand('CREATE_MINI_DS', {
|
|
795
|
+
primaryColor: params.primaryColor || '#1464FF',
|
|
796
|
+
fontFamily: params.fontFamily || 'Inter',
|
|
797
|
+
name: params.name || 'Mini DS',
|
|
798
|
+
includeComponents: params.includeComponents !== false,
|
|
799
|
+
}, 45000) // 45s — font loading + 3 component creation takes time
|
|
800
|
+
.catch(function(err) { return { success: false, error: err.message || String(err) }; });
|
|
801
|
+
};
|
|
802
|
+
|
|
786
803
|
// Batch export nodes as SVG/PNG/JPG/PDF with base64
|
|
787
804
|
window.batchExportNodes = (nodeIds, format, scale, svgOutlineText, svgIncludeId, svgSimplifyStroke) => {
|
|
788
805
|
return window.sendPluginCommand('BATCH_EXPORT_NODES', {
|
|
@@ -1881,6 +1898,7 @@
|
|
|
1881
1898
|
if (method === 'setNodeCornerRadius') return window.setNodeCornerRadius(params.nodeId, params.radius);
|
|
1882
1899
|
if (method === 'cloneScreenToDevice') return window.cloneScreenToDevice(params);
|
|
1883
1900
|
if (method === 'validateScreen') return window.validateScreen(params);
|
|
1901
|
+
if (method === 'createMiniDs') return window.createMiniDs(params);
|
|
1884
1902
|
if (method === 'cloneNode') return window.cloneNode(params.nodeId);
|
|
1885
1903
|
if (method === 'deleteNode') return window.deleteNode(params.nodeId);
|
|
1886
1904
|
if (method === 'renameNode') return window.renameNode(params.nodeId, params.newName);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atezer/figma-mcp-bridge",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.7",
|
|
4
4
|
"description": "F-MCP ATezer: MCP server and Figma plugin bridge for Claude/Cursor. No REST token required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/local-plugin-only.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
|
-
"prepublishOnly": "npm run build && npm run validate:fmcp-skills",
|
|
30
|
+
"prepublishOnly": "npm run generate:embedded-skills && npm run build && npm run validate:fmcp-skills",
|
|
31
|
+
"generate:embedded-skills": "node scripts/generate-embedded-skills.mjs",
|
|
31
32
|
"dev": "node dist/local-plugin-only.js",
|
|
32
33
|
"build:run": "npm run build && node dist/local-plugin-only.js",
|
|
33
34
|
"build": "tsc -p tsconfig.local.json",
|
|
@@ -28,18 +28,32 @@ Bu SKILL bu üç soruyu **upstream** çözer. Claude intent'i netleştirmeden hi
|
|
|
28
28
|
|
|
29
29
|
Kullanıcı F-MCP ile ilgili herhangi bir talep yaptığında Claude bu 9 adımı SIRAYLA uygular. Hiçbir adım atlanmaz.
|
|
30
30
|
|
|
31
|
-
### 🚨 Adım 0 — DS GATE (v1.9.
|
|
31
|
+
### 🚨 Adım 0 — DS GATE + BLANK FILE CHECK (v1.9.7+ MUTLAK İLK KAPI)
|
|
32
32
|
|
|
33
33
|
**Herhangi bir intent analysis veya figma_* tool çağrısından ÖNCE:**
|
|
34
34
|
|
|
35
|
-
1.
|
|
36
|
-
2. `
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
1. `figma_get_status` çağır — plugin bağlı mı, response'ta `_bootstrap` varsa direktifleri OKU (v1.9.7)
|
|
36
|
+
2. `.claude/design-systems/active-ds.md` dosyasını oku
|
|
37
|
+
3. `Status:` alanını kontrol et:
|
|
38
|
+
- **`✅ Aktif`** → DS net, Blank File Sub-Check'e geç (madde 5)
|
|
39
|
+
- **`❌ Henüz seçilmedi`** VEYA dosya yok → kullanıcıya DS sorusu sor (madde 4)
|
|
40
|
+
4. **DS Sorusu (klasik):** "Aktif bir design system belirlenmemiş. Hangi DS ile ilerlemek istersiniz? (SUI / Material / kendi library)"
|
|
41
|
+
|
|
42
|
+
5. **BLANK FILE SUB-CHECK (v1.9.7, ZORUNLU):** `figma_get_design_system_summary` çağır.
|
|
43
|
+
- `components === 0 && componentSets === 0 && variableCollections.length === 0` ise **BOŞ DOSYA** tespit edildi.
|
|
44
|
+
- `_nextStep: "BLANK_FILE_DIALOG_REQUIRED"` response'ta görünüyorsa, **kullanıcıya 4 seçenek sun** (AskUserQuestion tek call, 4 option):
|
|
45
|
+
```
|
|
46
|
+
Q: "Bu dosyada henüz Design System yok. Nasıl ilerleyelim?"
|
|
47
|
+
(a) Team library import — "Hangi library? SUI, Material 3, iOS HIG, veya kendi library'niz?"
|
|
48
|
+
(b) Mini DS kur otomatik — figma_create_mini_ds tool'u çağrılır (12 color + 8 sizing + 3 text style + Button/Input/Card)
|
|
49
|
+
(c) Referans DS kopyala — "Material 3 template / iOS HIG template"
|
|
50
|
+
(d) DS'siz ilerle — linter tolerant mode, hardcoded değerler kabul (explicit acceptance)
|
|
51
|
+
```
|
|
52
|
+
- **Seçim yapılmadan `figma_execute createFrame` YASAK.** Claude ham createFrame denerse plugin `_DESIGN_SYSTEM_VIOLATIONS_BLOCKING` flag'i döndürür, retry zorunlu olur.
|
|
53
|
+
- Kullanıcı "(b)" derse Claude `figma_create_mini_ds({ primaryColor, fontFamily, name })` tek tool çağrısı yapar, sonra ekran üretimine geçer.
|
|
54
|
+
- Kullanıcı "(a)" derse Claude `figma_get_library_variables(libraryName)` ile listeleme yapar, seçenek sunar.
|
|
55
|
+
|
|
56
|
+
6. Seçim sonrası `active-ds.md`'yi güncelle (`Status: ✅ Aktif`, `Library Name: <isim>`), sonra Adım 1'e geç.
|
|
43
57
|
|
|
44
58
|
**Neden bu kadar katı:**
|
|
45
59
|
- DS belirsizken `figma_search_assets`, `figma_get_file_data`, `figma_get_library_variables` çağırmak = kullanıcının istemediği library'leri enumere etmek = **token israfı + UX bozulması**
|
|
@@ -235,6 +249,39 @@ Missing list'teki tüm soruları **TEK** `AskUserQuestion` çağrısında toplay
|
|
|
235
249
|
- Sorulan her soru ≤30 kelime — uzun elicitation yasak.
|
|
236
250
|
- Soruyu sormanın context maliyeti ≥ yanıtın değeri mi? Değilse sorma.
|
|
237
251
|
|
|
252
|
+
### v1.9.6 Negative Intent Detection (KRITIK)
|
|
253
|
+
|
|
254
|
+
Kullanıcı şu paternlerle **negatif/dışlayıcı** talimat verebilir — bunları ZORUNLU parse et:
|
|
255
|
+
|
|
256
|
+
| Kullanıcı paterni | Anlam | Nasıl handle et |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| "X'i atla" / "X'i kullanma" / "X'ten bahsetme" | X referansını DIŞLA | `exclude_references: ["X"]` state'e yaz, o node/page/frame'e referans verme |
|
|
259
|
+
| "X'e bakma" / "X yok say" / "X'i unut" | X sıfır-referans | Aynı |
|
|
260
|
+
| "Y benzetme" / "Y gibi olmasın" | Y anti-pattern | `anti_pattern_refs: ["Y"]` — Y'ye ters tasarım yap |
|
|
261
|
+
| "X dışında" / "X hariç" | Whitelist exclusion | `exclude_references: ["X"]` |
|
|
262
|
+
| "Sıfırdan" / "baştan" / "yeni tasarım" | Mevcut iterasyonları atla | `start_fresh: true`, ideation/iterasyon referansları atla |
|
|
263
|
+
|
|
264
|
+
**Örnek:**
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
Kullanıcı: "SUI Alt 3'e bakma, sıfırdan yeni bir Anasayfa tasarla"
|
|
268
|
+
|
|
269
|
+
PARSE:
|
|
270
|
+
- exclude_references: ["SUI Alt 3", "241:11896"] (SUI Alt 3 frame ID'si de dahil)
|
|
271
|
+
- start_fresh: true
|
|
272
|
+
- target_screen: "Anasayfa"
|
|
273
|
+
- DS: active-ds.md'den çek
|
|
274
|
+
|
|
275
|
+
SONUÇ:
|
|
276
|
+
- SUI Alt 3 sayfası referans alınmaz
|
|
277
|
+
- Mevcut "SUI Alt 3 — Anasayfa" frame'i açılmaz, screenshot alınmaz
|
|
278
|
+
- ideation sayfası (ilham) kullanılabilir ama SUI Alt 3 bölümü atlanır
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Anti-pattern:** Kullanıcı "SUI Alt 3 atla" dedikten sonra onu screenshot alıp referans göstermek. v1.9.6'da bu explicit yasak — pre-flight'ta `exclude_references` kontrolü yapılır.
|
|
282
|
+
|
|
283
|
+
**State persist:** `exclude_references` last-intent.md'ye yazılır; aynı oturum boyunca geçerli.
|
|
284
|
+
|
|
238
285
|
**Örnek — generate-figma-screen:**
|
|
239
286
|
|
|
240
287
|
```
|
|
@@ -144,6 +144,32 @@ Benchmark/görselden DEĞER alma YASAK. Sadece NİYET: layout yönü, hiyerarşi
|
|
|
144
144
|
2. **Content** — DS instance yerleşimi → screenshot → onay
|
|
145
145
|
3. **Polish** — spacing, states, edge cases → son screenshot → audit
|
|
146
146
|
|
|
147
|
+
### v1.9.7 Anti-Suppression Kuralı (MUTLAK — HARD enforcement)
|
|
148
|
+
|
|
149
|
+
BLOCKING response gördüğünde YASAK ifadeler:
|
|
150
|
+
- "bu projede geçerli değil"
|
|
151
|
+
- "dosyada DS yok, bu uyarılar önemsiz"
|
|
152
|
+
- "şimdilik skip edelim"
|
|
153
|
+
- "sonra düzeltiriz"
|
|
154
|
+
- "yine de devam ediyorum"
|
|
155
|
+
|
|
156
|
+
**Server seviyesi enforcement (Katman 3):** Claude BLOCKING flag gördükten sonra **aynı nodeId** üzerinde ikinci `figma_execute` mutation yaparsa server **HARD_ERROR** döndürür — tool fail eder, response değil error. Claude bunu skip edemez.
|
|
157
|
+
|
|
158
|
+
**Override escape hatch:** Sadece kullanıcı onayı ile `// FORCE_OVERRIDE` comment'i kod başına ekleyerek bypass edilebilir.
|
|
159
|
+
|
|
160
|
+
**ZORUNLU davranış BLOCKING gördüğünde:**
|
|
161
|
+
1. BLOCKING'i OKU — hangi nodeId, hangi kategori? (_postExecuteViolations.violations)
|
|
162
|
+
2. Kök nedeni analiz et:
|
|
163
|
+
- UNBOUND_FILL → setBoundVariableForPaint eksik
|
|
164
|
+
- UNBOUND_PADDING → setBoundVariable eksik
|
|
165
|
+
- UNBOUND_TEXTSTYLE → setTextStyleIdAsync eksik
|
|
166
|
+
- NO_INSTANCE_USAGE → DS component yok, import edilmeli
|
|
167
|
+
3. Kullanıcıya sor SADECE şu koşullarda:
|
|
168
|
+
- DS gerçekten yok → Adım 0 Blank File sub-check'e dön (4 option)
|
|
169
|
+
- Override gerekli → "BLOCKING'i geçici bypass etmek istiyor musun?"
|
|
170
|
+
4. Kullanıcı override isterse `// FORCE_OVERRIDE` comment ile kod tekrar çalıştır
|
|
171
|
+
5. Aksi takdirde kodu DÜZELT ve retry
|
|
172
|
+
|
|
147
173
|
### v1.9.5 Discovery Budget Rule (SERT)
|
|
148
174
|
|
|
149
175
|
- **Maks 3 discovery çağrısı** (figma_get_*, figma_search_*, figma_execute read-only) sonra plan sun.
|