@noemuch/bridge-ds 2.1.0 → 2.3.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/lib/cli.js +39 -3
- package/lib/scaffold.js +38 -3
- package/package.json +1 -1
- package/skills/design-workflow/references/actions/design.md +47 -1
- package/skills/design-workflow/references/actions/spec.md +34 -0
- package/skills/design-workflow/references/figma-api-rules.md +53 -0
- package/skills/design-workflow/references/templates/screen-template.md +25 -3
package/lib/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const { scaffold } = require('./scaffold');
|
|
2
|
+
const { scaffold, update } = require('./scaffold');
|
|
3
3
|
const { checkMcp, setupMcp } = require('./mcp-setup');
|
|
4
4
|
|
|
5
5
|
// ── Branding ──────────────────────────────────────────────
|
|
@@ -113,6 +113,35 @@ async function cmdInit() {
|
|
|
113
113
|
print('');
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
async function cmdUpdate() {
|
|
117
|
+
banner();
|
|
118
|
+
header('Updating Bridge DS skill files');
|
|
119
|
+
|
|
120
|
+
const cwd = process.cwd();
|
|
121
|
+
|
|
122
|
+
step(1, 2, 'Updating skill files');
|
|
123
|
+
const spinner = new Spinner('Updating SKILL.md, actions, rules, schemas, templates...').start();
|
|
124
|
+
const result = update(cwd);
|
|
125
|
+
|
|
126
|
+
if (result.error) {
|
|
127
|
+
spinner.stop();
|
|
128
|
+
error(result.error);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
spinner.stop(`${result.updated.length} files updated`);
|
|
133
|
+
|
|
134
|
+
for (const f of result.updated) {
|
|
135
|
+
muted(f);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
step(2, 2, 'Done!');
|
|
139
|
+
print('');
|
|
140
|
+
success('Skill files updated. Your knowledge base (registries, guides) was preserved.');
|
|
141
|
+
info('No need to re-run /design-workflow setup.');
|
|
142
|
+
print('');
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
function cmdHelp() {
|
|
117
146
|
banner();
|
|
118
147
|
print(`${C.bold} Commands:${C.reset}`);
|
|
@@ -120,13 +149,17 @@ function cmdHelp() {
|
|
|
120
149
|
print(` ${C.orange}init${C.reset} Initialize Bridge DS in current project`);
|
|
121
150
|
print(` Scaffolds skills, commands, and specs directories`);
|
|
122
151
|
print('');
|
|
152
|
+
print(` ${C.orange}update${C.reset} Update skill files to latest version`);
|
|
153
|
+
print(` Preserves your knowledge base (registries, guides)`);
|
|
154
|
+
print('');
|
|
123
155
|
print(` ${C.orange}help${C.reset} Show this help message`);
|
|
124
156
|
print(` ${C.orange}version${C.reset} Show version`);
|
|
125
157
|
print('');
|
|
126
158
|
print(`${C.bold} Usage:${C.reset}`);
|
|
127
159
|
print('');
|
|
128
|
-
print(` ${C.dim}npx bridge-ds init${C.reset} # Initialize in current project`);
|
|
129
|
-
print(` ${C.dim}bridge-ds
|
|
160
|
+
print(` ${C.dim}npx @noemuch/bridge-ds init${C.reset} # Initialize in current project`);
|
|
161
|
+
print(` ${C.dim}npx @noemuch/bridge-ds update${C.reset} # Update skill files only`);
|
|
162
|
+
print(` ${C.dim}bridge-ds help${C.reset} # Show help`);
|
|
130
163
|
print('');
|
|
131
164
|
print(`${C.bold} After init:${C.reset}`);
|
|
132
165
|
print('');
|
|
@@ -152,6 +185,9 @@ async function run(args) {
|
|
|
152
185
|
case 'init':
|
|
153
186
|
await cmdInit();
|
|
154
187
|
break;
|
|
188
|
+
case 'update':
|
|
189
|
+
await cmdUpdate();
|
|
190
|
+
break;
|
|
155
191
|
case 'help':
|
|
156
192
|
case '--help':
|
|
157
193
|
case '-h':
|
package/lib/scaffold.js
CHANGED
|
@@ -3,8 +3,9 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Recursively copy a directory, preserving structure.
|
|
6
|
+
* @param {string[]} skipDirs - directory names to skip (e.g., ['registries', 'guides', 'ui-references'])
|
|
6
7
|
*/
|
|
7
|
-
function copyDir(src, dest) {
|
|
8
|
+
function copyDir(src, dest, skipDirs = []) {
|
|
8
9
|
const created = [];
|
|
9
10
|
if (!fs.existsSync(dest)) {
|
|
10
11
|
fs.mkdirSync(dest, { recursive: true });
|
|
@@ -13,7 +14,8 @@ function copyDir(src, dest) {
|
|
|
13
14
|
const srcPath = path.join(src, entry.name);
|
|
14
15
|
const destPath = path.join(dest, entry.name);
|
|
15
16
|
if (entry.isDirectory()) {
|
|
16
|
-
|
|
17
|
+
if (skipDirs.includes(entry.name)) continue;
|
|
18
|
+
created.push(...copyDir(srcPath, destPath, skipDirs));
|
|
17
19
|
} else {
|
|
18
20
|
fs.copyFileSync(srcPath, destPath);
|
|
19
21
|
created.push(destPath);
|
|
@@ -117,4 +119,37 @@ function scaffold(projectDir) {
|
|
|
117
119
|
return { created };
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Update only the skill files (SKILL.md, actions, rules, schemas, templates).
|
|
124
|
+
* Preserves all user-generated data (registries, guides, ui-references, specs).
|
|
125
|
+
* Returns { updated: string[] } with relative paths of updated files.
|
|
126
|
+
*/
|
|
127
|
+
function update(projectDir) {
|
|
128
|
+
const updated = [];
|
|
129
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
130
|
+
|
|
131
|
+
const skillsSrc = path.join(pkgRoot, 'skills', 'design-workflow');
|
|
132
|
+
const skillsDest = path.join(projectDir, '.claude', 'skills', 'design-workflow');
|
|
133
|
+
|
|
134
|
+
if (!fs.existsSync(skillsDest)) {
|
|
135
|
+
return { updated: [], error: 'Bridge DS not initialized. Run: npx @noemuch/bridge-ds init' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Copy skill files, skipping user-generated KB data
|
|
139
|
+
const userDataDirs = ['registries', 'guides', 'ui-references'];
|
|
140
|
+
const files = copyDir(skillsSrc, skillsDest, userDataDirs);
|
|
141
|
+
updated.push(...files.map(f => path.relative(projectDir, f)));
|
|
142
|
+
|
|
143
|
+
// Update command file
|
|
144
|
+
const cmdSrc = path.join(pkgRoot, 'commands', 'design-workflow.md');
|
|
145
|
+
const cmdDest = path.join(projectDir, '.claude', 'commands', 'design-workflow.md');
|
|
146
|
+
if (fs.existsSync(cmdSrc)) {
|
|
147
|
+
fs.mkdirSync(path.dirname(cmdDest), { recursive: true });
|
|
148
|
+
fs.copyFileSync(cmdSrc, cmdDest);
|
|
149
|
+
updated.push(path.relative(projectDir, cmdDest));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { updated };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { scaffold, update };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noemuch/bridge-ds",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "AI-powered design generation in Figma — 100% design system compliant. Connects Claude Code to Figma via MCP for spec-first, token-bound, component-native design.",
|
|
5
5
|
"main": "lib/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -69,7 +69,53 @@ Key rules applied: {bullet list}
|
|
|
69
69
|
|
|
70
70
|
**GATE: If no pattern matches**, ask the user which existing pattern is closest.
|
|
71
71
|
|
|
72
|
-
### 1c.
|
|
72
|
+
### 1c. Reference Inspection (BLOCKING for screens with reference)
|
|
73
|
+
|
|
74
|
+
**If the spec has a "Reference Screen" section with a Figma URL/node ID, this step is MANDATORY.**
|
|
75
|
+
|
|
76
|
+
1. **Screenshot the reference** via `figma_take_screenshot({ node_id, file_key })`
|
|
77
|
+
2. **Inspect the reference structure** via `figma_execute` — extract the node tree:
|
|
78
|
+
```js
|
|
79
|
+
return (async function() {
|
|
80
|
+
var ref = await figma.getNodeByIdAsync("REFERENCE_NODE_ID");
|
|
81
|
+
if (!ref) return { error: "Node not found" };
|
|
82
|
+
var children = [];
|
|
83
|
+
for (var i = 0; i < ref.children.length; i++) {
|
|
84
|
+
var c = ref.children[i];
|
|
85
|
+
children.push({
|
|
86
|
+
name: c.name, type: c.type, id: c.id,
|
|
87
|
+
width: Math.round(c.width), height: Math.round(c.height),
|
|
88
|
+
isComponent: c.type === "INSTANCE" || c.type === "COMPONENT",
|
|
89
|
+
componentName: c.type === "INSTANCE" ? c.mainComponent.name : undefined
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return { name: ref.name, width: Math.round(ref.width), height: Math.round(ref.height), children: children };
|
|
93
|
+
})();
|
|
94
|
+
```
|
|
95
|
+
3. **Document findings** before proceeding:
|
|
96
|
+
```
|
|
97
|
+
Reference inspection:
|
|
98
|
+
- Shell structure: {sidebar Xpx | content FILL | buffer Xpx}
|
|
99
|
+
- DS components found: {list with node IDs}
|
|
100
|
+
- Local/unpublished elements: {list with node IDs — these need cloning}
|
|
101
|
+
- Logo variant: {exact name}
|
|
102
|
+
- Key differences from spec: {what content changes}
|
|
103
|
+
```
|
|
104
|
+
4. **Update generation strategy:**
|
|
105
|
+
- Elements found in reference → plan to **clone** (faster, guaranteed match)
|
|
106
|
+
- Elements in DS registry but not in reference → plan to **import**
|
|
107
|
+
- Update the pre-script audit to reflect clone vs import per element
|
|
108
|
+
|
|
109
|
+
**GATE:** Do not generate until reference inspection is complete and strategy is documented.
|
|
110
|
+
|
|
111
|
+
**If no reference provided (building from scratch):** Skip this step but warn the user:
|
|
112
|
+
```
|
|
113
|
+
No reference screen provided. Building layout from scratch.
|
|
114
|
+
This may require more iterations. For better results, provide a Figma URL
|
|
115
|
+
of an existing screen with a similar layout shell.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 1d. Canvas dimensions
|
|
73
119
|
|
|
74
120
|
- **Web**: 1440px wide
|
|
75
121
|
- **Mobile**: 390px wide
|
|
@@ -12,6 +12,17 @@ Ask or infer from context:
|
|
|
12
12
|
- **Component**: design system primitive, block, or composition (Button, Card, Tag...)
|
|
13
13
|
- **Screen**: full interface or page (dashboard, settings, onboarding flow...)
|
|
14
14
|
|
|
15
|
+
**If screen mode detected**, immediately ask the user:
|
|
16
|
+
```
|
|
17
|
+
This is a screen spec. For reliable generation, I need a reference:
|
|
18
|
+
|
|
19
|
+
→ Do you have a Figma URL of an existing screen with a similar layout?
|
|
20
|
+
(e.g., same sidebar + content structure, same navigation shell)
|
|
21
|
+
|
|
22
|
+
With a reference, I'll clone the shell and replace the content (fast, accurate).
|
|
23
|
+
Without one, I'll build from scratch (slower, may need iterations).
|
|
24
|
+
```
|
|
25
|
+
|
|
15
26
|
### 2. Gather context
|
|
16
27
|
|
|
17
28
|
- **Load design patterns** (MANDATORY for screens, recommended for components):
|
|
@@ -95,6 +106,29 @@ For each UI pattern described in the spec, check if it exists in `registries/com
|
|
|
95
106
|
- A navigation pattern not covered by existing nav components
|
|
96
107
|
- A data display pattern not covered by existing list/table components
|
|
97
108
|
|
|
109
|
+
### 3c. Auto-enrich DS Components Used (screen mode)
|
|
110
|
+
|
|
111
|
+
For every component listed in "DS Components Used", resolve the exact registry key:
|
|
112
|
+
|
|
113
|
+
1. Search `registries/components.json` by name → fill the `Key` column with the exact key
|
|
114
|
+
2. Search `registries/icons.json`, `registries/logos.json` if applicable
|
|
115
|
+
3. For each element, determine the **strategy**:
|
|
116
|
+
- Found in registry → `import`
|
|
117
|
+
- Not in registry but exists in reference screen → `clone` (note the reference node ID)
|
|
118
|
+
- Not in registry and no reference → flag as "New DS Component Required"
|
|
119
|
+
|
|
120
|
+
**Example enriched table:**
|
|
121
|
+
```
|
|
122
|
+
| Component | Variant | Key / Source | Strategy |
|
|
123
|
+
|-----------|---------|-------------|----------|
|
|
124
|
+
| Button | primary, large | components.json → key: abc123 | import |
|
|
125
|
+
| Logo | finary-one | logos.json → key: 78defbf4 | import |
|
|
126
|
+
| SideStepper | 5 steps | components.json → key: 1668b598 | clone (reference 9569:40240) |
|
|
127
|
+
| ProductBackground | finaryOne | NOT IN REGISTRY | clone (reference 9569:40233) |
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This removes ambiguity at design time and prevents wrong component selection.
|
|
131
|
+
|
|
98
132
|
### 4. Validate
|
|
99
133
|
|
|
100
134
|
**Token architecture:**
|
|
@@ -446,6 +446,59 @@ Same applies to `setFillStyleIdAsync`, `setStrokeStyleIdAsync`, `setEffectStyleI
|
|
|
446
446
|
|
|
447
447
|
---
|
|
448
448
|
|
|
449
|
+
## Rule 22: Clone-first for screens with reference
|
|
450
|
+
|
|
451
|
+
When a reference Figma node is available (from the spec's "Reference Screen" section), **clone the shell structure instead of rebuilding from scratch**.
|
|
452
|
+
|
|
453
|
+
### When to clone
|
|
454
|
+
|
|
455
|
+
Clone when the element is:
|
|
456
|
+
- A **layout shell** (sidebar + content + footer structure)
|
|
457
|
+
- A **complex pre-configured instance** (stepper with labels, navigation with active states)
|
|
458
|
+
- A **local/unpublished component** (backgrounds, decorative elements not in the DS library)
|
|
459
|
+
- An instance with **deeply nested properties** (3+ levels, properties on nested children not exposed at root)
|
|
460
|
+
|
|
461
|
+
### How to clone
|
|
462
|
+
|
|
463
|
+
```js
|
|
464
|
+
// Clone an entire subtree from a reference node
|
|
465
|
+
var ref = await figma.getNodeByIdAsync("REFERENCE_NODE_ID");
|
|
466
|
+
var clone = ref.clone();
|
|
467
|
+
|
|
468
|
+
// Position the clone
|
|
469
|
+
clone.x = 0;
|
|
470
|
+
clone.y = 0;
|
|
471
|
+
|
|
472
|
+
// Now modify the clone's content (replace text, swap instances, etc.)
|
|
473
|
+
var title = clone.findOne(function(n) { return n.name === "title" && n.type === "TEXT"; });
|
|
474
|
+
if (title) title.characters = "New Title";
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Clone vs Import decision
|
|
478
|
+
|
|
479
|
+
| Situation | Strategy |
|
|
480
|
+
|-----------|----------|
|
|
481
|
+
| Component in DS library, simple props | **Import** via `importComponentByKeyAsync` |
|
|
482
|
+
| Component in DS library, deeply nested | **Clone** from reference if available |
|
|
483
|
+
| Local/unpublished component | **Clone** from reference (only option) |
|
|
484
|
+
| Layout shell (sidebar + content + footer) | **Clone** the whole shell, replace content |
|
|
485
|
+
| Pre-configured stepper/nav with labels set | **Clone** (API can't easily override nested props) |
|
|
486
|
+
|
|
487
|
+
### Pre-script audit format for clone elements
|
|
488
|
+
|
|
489
|
+
```
|
|
490
|
+
PRE-SCRIPT AUDIT — Step {n}:
|
|
491
|
+
Spec requires: Source: Strategy:
|
|
492
|
+
─────────────────────────────────────────────────────────────────
|
|
493
|
+
Sidebar shell → reference node 9569:40232 → clone ✓
|
|
494
|
+
ProductBackground → reference node 9569:40233 → clone ✓
|
|
495
|
+
SideStepper (5 steps) → reference node 9569:40240 → clone + modify labels ✓
|
|
496
|
+
Button (primary) → components.json key: abc123 → import ✓
|
|
497
|
+
Input (default) → components.json key: def456 → import ✓
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
449
502
|
## Standard Script Boilerplate
|
|
450
503
|
|
|
451
504
|
```js
|
|
@@ -8,6 +8,23 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## Reference Screen (REQUIRED)
|
|
12
|
+
|
|
13
|
+
> A screen that already exists in Figma with the same layout shell (sidebar + content, topbar + main, etc.).
|
|
14
|
+
> Claude will clone the shell from this reference and replace the content.
|
|
15
|
+
> **Without a reference, generation is significantly less reliable.**
|
|
16
|
+
|
|
17
|
+
| | |
|
|
18
|
+
|---|---|
|
|
19
|
+
| **Reference URL** | {Figma URL of an existing screen with the same shell/layout} |
|
|
20
|
+
| **Reference node ID** | {nodeId extracted from URL} |
|
|
21
|
+
| **Shell elements to clone** | {list: e.g., "sidebar frame, product background, stepper, footer"} |
|
|
22
|
+
| **What changes vs reference** | {what content/sections differ from the reference} |
|
|
23
|
+
|
|
24
|
+
> If no existing screen uses this layout: write "None — new layout, building from scratch" and document the shell structure in detail in Layout Structure below.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
11
28
|
## Visual Reference
|
|
12
29
|
|
|
13
30
|
> Identifies which design pattern this screen follows.
|
|
@@ -67,10 +84,15 @@
|
|
|
67
84
|
|
|
68
85
|
> Look up component keys in `knowledge-base/registries/components.json`.
|
|
69
86
|
> Use `knowledge-base/guides/components/overview.md` decision tree to choose the right component.
|
|
87
|
+
> Mark each element as `import` (from DS library) or `clone` (from reference screen).
|
|
88
|
+
|
|
89
|
+
| Component | Variant/Size | Key / Source | Strategy | Location |
|
|
90
|
+
|-----------|-------------|-------------|----------|----------|
|
|
91
|
+
| `{Name}` | {variant} | {registry key or "reference node {id}"} | import / clone | {section} |
|
|
70
92
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
**Strategy guide:**
|
|
94
|
+
- `import` — Component exists in DS library → use `importComponentByKeyAsync(key)`
|
|
95
|
+
- `clone` — Component is local/unpublished or pre-configured in reference → clone from reference node
|
|
74
96
|
|
|
75
97
|
---
|
|
76
98
|
|