canicode 0.4.1 → 0.5.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/README.md +38 -1
- package/dist/cli/index.js +120 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/mcp/server.js +81 -6
- package/dist/mcp/server.js.map +1 -1
- package/docs/CUSTOMIZATION.md +248 -0
- package/package.json +1 -1
package/docs/CUSTOMIZATION.md
CHANGED
|
@@ -198,6 +198,254 @@ Override score, severity, or enable/disable individual rules:
|
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Custom Rules
|
|
204
|
+
|
|
205
|
+
Add project-specific checks using declarative pattern matching. Custom rules evaluate conditions against each Figma node and flag violations when ALL conditions match.
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
canicode analyze <url> --custom-rules ./my-rules.json
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Rule Structure
|
|
212
|
+
|
|
213
|
+
Each custom rule is a JSON object with the following fields:
|
|
214
|
+
|
|
215
|
+
| Field | Type | Required | Description |
|
|
216
|
+
|-------|------|----------|-------------|
|
|
217
|
+
| `id` | `string` | Yes | Unique rule identifier (kebab-case recommended) |
|
|
218
|
+
| `category` | `string` | Yes | One of: `layout`, `token`, `component`, `naming`, `ai-readability`, `handoff-risk` |
|
|
219
|
+
| `severity` | `string` | Yes | One of: `blocking`, `risk`, `missing-info`, `suggestion` |
|
|
220
|
+
| `score` | `number` | Yes | Penalty score (integer, must be <= 0) |
|
|
221
|
+
| `match` | `object` | Yes | Conditions to evaluate (see below) |
|
|
222
|
+
| `message` | `string` | No | Custom message template. Supports `{name}` and `{type}` placeholders |
|
|
223
|
+
| `why` | `string` | Yes | Why this rule matters |
|
|
224
|
+
| `impact` | `string` | Yes | What happens if the issue is not fixed |
|
|
225
|
+
| `fix` | `string` | Yes | How to fix the issue |
|
|
226
|
+
|
|
227
|
+
### Match Conditions
|
|
228
|
+
|
|
229
|
+
ALL conditions in the `match` object must be satisfied for the rule to fire (AND logic). Omitted conditions are ignored (they always pass).
|
|
230
|
+
|
|
231
|
+
#### Node Type
|
|
232
|
+
|
|
233
|
+
| Condition | Type | Description |
|
|
234
|
+
|-----------|------|-------------|
|
|
235
|
+
| `type` | `string[]` | Node type must be one of these values |
|
|
236
|
+
| `notType` | `string[]` | Node type must NOT be one of these values |
|
|
237
|
+
|
|
238
|
+
Available node types: `FRAME`, `GROUP`, `SECTION`, `COMPONENT`, `COMPONENT_SET`, `INSTANCE`, `RECTANGLE`, `ELLIPSE`, `VECTOR`, `TEXT`, `LINE`, `BOOLEAN_OPERATION`, `STAR`, `REGULAR_POLYGON`, `SLICE`, `STICKY`, `TABLE`, `TABLE_CELL`
|
|
239
|
+
|
|
240
|
+
#### Node Name
|
|
241
|
+
|
|
242
|
+
| Condition | Type | Description |
|
|
243
|
+
|-----------|------|-------------|
|
|
244
|
+
| `nameContains` | `string` | Node name contains this substring (case-insensitive) |
|
|
245
|
+
| `nameNotContains` | `string` | Node name does NOT contain this substring (case-insensitive) |
|
|
246
|
+
| `namePattern` | `string` | Regex pattern to test against node name (case-insensitive) |
|
|
247
|
+
|
|
248
|
+
#### Size
|
|
249
|
+
|
|
250
|
+
| Condition | Type | Description |
|
|
251
|
+
|-----------|------|-------------|
|
|
252
|
+
| `minWidth` | `number` | Minimum width (node must have a bounding box) |
|
|
253
|
+
| `maxWidth` | `number` | Maximum width |
|
|
254
|
+
| `minHeight` | `number` | Minimum height |
|
|
255
|
+
| `maxHeight` | `number` | Maximum height |
|
|
256
|
+
|
|
257
|
+
#### Layout
|
|
258
|
+
|
|
259
|
+
| Condition | Type | Description |
|
|
260
|
+
|-----------|------|-------------|
|
|
261
|
+
| `hasAutoLayout` | `boolean` | `true` = node must have Auto Layout; `false` = must not |
|
|
262
|
+
| `hasChildren` | `boolean` | `true` = must have children; `false` = must not |
|
|
263
|
+
| `minChildren` | `number` | Minimum number of children |
|
|
264
|
+
| `maxChildren` | `number` | Maximum number of children |
|
|
265
|
+
|
|
266
|
+
#### Component
|
|
267
|
+
|
|
268
|
+
| Condition | Type | Description |
|
|
269
|
+
|-----------|------|-------------|
|
|
270
|
+
| `isComponent` | `boolean` | `true` = node type is COMPONENT or COMPONENT_SET |
|
|
271
|
+
| `isInstance` | `boolean` | `true` = node type is INSTANCE |
|
|
272
|
+
| `hasComponentId` | `boolean` | `true` = node has a componentId property |
|
|
273
|
+
|
|
274
|
+
#### Visibility
|
|
275
|
+
|
|
276
|
+
| Condition | Type | Description |
|
|
277
|
+
|-----------|------|-------------|
|
|
278
|
+
| `isVisible` | `boolean` | `true` = node is visible; `false` = node is hidden |
|
|
279
|
+
|
|
280
|
+
#### Style
|
|
281
|
+
|
|
282
|
+
| Condition | Type | Description |
|
|
283
|
+
|-----------|------|-------------|
|
|
284
|
+
| `hasFills` | `boolean` | `true` = node has fills; `false` = no fills |
|
|
285
|
+
| `hasStrokes` | `boolean` | `true` = node has strokes; `false` = no strokes |
|
|
286
|
+
| `hasEffects` | `boolean` | `true` = node has effects; `false` = no effects |
|
|
287
|
+
|
|
288
|
+
#### Depth
|
|
289
|
+
|
|
290
|
+
| Condition | Type | Description |
|
|
291
|
+
|-----------|------|-------------|
|
|
292
|
+
| `minDepth` | `number` | Minimum tree depth |
|
|
293
|
+
| `maxDepth` | `number` | Maximum tree depth |
|
|
294
|
+
|
|
295
|
+
### Examples
|
|
296
|
+
|
|
297
|
+
#### Flag icons that are not components
|
|
298
|
+
|
|
299
|
+
Small frames/groups named "icon" that should be componentized:
|
|
300
|
+
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"id": "icon-not-component",
|
|
304
|
+
"category": "component",
|
|
305
|
+
"severity": "blocking",
|
|
306
|
+
"score": -10,
|
|
307
|
+
"match": {
|
|
308
|
+
"type": ["FRAME", "GROUP"],
|
|
309
|
+
"maxWidth": 48,
|
|
310
|
+
"maxHeight": 48,
|
|
311
|
+
"hasChildren": true,
|
|
312
|
+
"nameContains": "icon"
|
|
313
|
+
},
|
|
314
|
+
"message": "\"{name}\" is an icon but not a component — should be componentized",
|
|
315
|
+
"why": "Icons that are not components cannot be reused consistently across the design system.",
|
|
316
|
+
"impact": "Developers will hardcode icon SVGs instead of using a shared component.",
|
|
317
|
+
"fix": "Convert this icon to a component and publish it to the design system library."
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### Flag large frames without Auto Layout
|
|
322
|
+
|
|
323
|
+
Frames with many children that should have Auto Layout applied:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"id": "large-frame-no-auto-layout",
|
|
328
|
+
"category": "layout",
|
|
329
|
+
"severity": "risk",
|
|
330
|
+
"score": -5,
|
|
331
|
+
"match": {
|
|
332
|
+
"type": ["FRAME"],
|
|
333
|
+
"minWidth": 200,
|
|
334
|
+
"minHeight": 200,
|
|
335
|
+
"minChildren": 3,
|
|
336
|
+
"hasAutoLayout": false
|
|
337
|
+
},
|
|
338
|
+
"message": "\"{name}\" is a large frame with {type} children but no Auto Layout",
|
|
339
|
+
"why": "Large frames with multiple children should use Auto Layout for maintainability.",
|
|
340
|
+
"impact": "Layout changes require manual repositioning of every child element.",
|
|
341
|
+
"fix": "Apply Auto Layout with appropriate direction and spacing."
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Flag deeply nested hidden layers
|
|
346
|
+
|
|
347
|
+
Hidden nodes buried deep in the tree that add noise:
|
|
348
|
+
|
|
349
|
+
```json
|
|
350
|
+
{
|
|
351
|
+
"id": "deep-hidden-layer",
|
|
352
|
+
"category": "ai-readability",
|
|
353
|
+
"severity": "suggestion",
|
|
354
|
+
"score": -2,
|
|
355
|
+
"match": {
|
|
356
|
+
"isVisible": false,
|
|
357
|
+
"minDepth": 4
|
|
358
|
+
},
|
|
359
|
+
"message": "\"{name}\" is hidden at depth {type} — consider removing it",
|
|
360
|
+
"why": "Hidden layers deep in the tree add noise and confuse AI code generators.",
|
|
361
|
+
"impact": "AI tools may generate unnecessary conditional rendering for hidden elements.",
|
|
362
|
+
"fix": "Delete hidden layers that are no longer needed, or move them to a separate page."
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### Flag instances without component binding
|
|
367
|
+
|
|
368
|
+
Detached instances that lost their component reference:
|
|
369
|
+
|
|
370
|
+
```json
|
|
371
|
+
{
|
|
372
|
+
"id": "detached-instance-custom",
|
|
373
|
+
"category": "component",
|
|
374
|
+
"severity": "risk",
|
|
375
|
+
"score": -5,
|
|
376
|
+
"match": {
|
|
377
|
+
"isInstance": true,
|
|
378
|
+
"hasComponentId": false
|
|
379
|
+
},
|
|
380
|
+
"message": "\"{name}\" is an INSTANCE without a component reference",
|
|
381
|
+
"why": "Detached instances cannot receive updates from the source component.",
|
|
382
|
+
"impact": "Design changes in the library will not propagate to this instance.",
|
|
383
|
+
"fix": "Re-link this instance to its source component or convert it to a new component."
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### Flag frames with default Figma names
|
|
388
|
+
|
|
389
|
+
Frames with auto-generated names like "Frame 1", "Group 2":
|
|
390
|
+
|
|
391
|
+
```json
|
|
392
|
+
{
|
|
393
|
+
"id": "default-frame-name",
|
|
394
|
+
"category": "naming",
|
|
395
|
+
"severity": "missing-info",
|
|
396
|
+
"score": -2,
|
|
397
|
+
"match": {
|
|
398
|
+
"type": ["FRAME", "GROUP"],
|
|
399
|
+
"namePattern": "^(Frame|Group|Rectangle|Ellipse)\\s\\d+$"
|
|
400
|
+
},
|
|
401
|
+
"message": "\"{name}\" has a default Figma name — rename it semantically",
|
|
402
|
+
"why": "Default names provide no context about the element's purpose.",
|
|
403
|
+
"impact": "Developers must guess what this element represents in the UI.",
|
|
404
|
+
"fix": "Rename to a descriptive, semantic name (e.g., 'Header', 'Card', 'NavItem')."
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### How to Write Custom Rules
|
|
409
|
+
|
|
410
|
+
Custom rules use AND logic: every condition in the `match` object must be satisfied for the rule to fire. Think of it as describing the exact node you want to catch.
|
|
411
|
+
|
|
412
|
+
**Step 1: Identify what you want to flag.** Be specific. "Small frames named icon that aren't components" is better than "bad icons."
|
|
413
|
+
|
|
414
|
+
**Step 2: Choose the right conditions.** Start with `type` to narrow down node types, then add name/size/layout conditions to refine.
|
|
415
|
+
|
|
416
|
+
**Step 3: Write the message.** Use `{name}` and `{type}` placeholders so the message is specific to each flagged node.
|
|
417
|
+
|
|
418
|
+
**Step 4: Classify severity and score.** Use `blocking` for things that prevent correct implementation, `risk` for maintainability issues, `missing-info` for missing context, and `suggestion` for nice-to-haves. Scores range from -1 (minor) to -15 (severe).
|
|
419
|
+
|
|
420
|
+
### LLM Prompt Template
|
|
421
|
+
|
|
422
|
+
Give this template to an LLM to generate rules from a natural language description:
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
I want a custom rule for CanICode that checks: [DESCRIBE WHAT TO CHECK]
|
|
426
|
+
|
|
427
|
+
Generate a JSON rule object with these fields:
|
|
428
|
+
- id: kebab-case identifier
|
|
429
|
+
- category: one of layout, token, component, naming, ai-readability, handoff-risk
|
|
430
|
+
- severity: one of blocking, risk, missing-info, suggestion
|
|
431
|
+
- score: negative integer (more negative = more severe)
|
|
432
|
+
- match: object with conditions (ALL must match). Available conditions:
|
|
433
|
+
- type/notType: array of node type strings
|
|
434
|
+
- nameContains/nameNotContains: substring match (case-insensitive)
|
|
435
|
+
- namePattern: regex pattern
|
|
436
|
+
- minWidth/maxWidth/minHeight/maxHeight: size constraints
|
|
437
|
+
- hasAutoLayout: boolean
|
|
438
|
+
- hasChildren: boolean, minChildren/maxChildren: number
|
|
439
|
+
- isComponent/isInstance/hasComponentId: boolean
|
|
440
|
+
- isVisible: boolean
|
|
441
|
+
- hasFills/hasStrokes/hasEffects: boolean
|
|
442
|
+
- minDepth/maxDepth: number
|
|
443
|
+
- message: string with {name} and {type} placeholders
|
|
444
|
+
- why: why this matters
|
|
445
|
+
- impact: what happens if not fixed
|
|
446
|
+
- fix: how to fix it
|
|
447
|
+
```
|
|
448
|
+
|
|
201
449
|
---
|
|
202
450
|
|
|
203
451
|
## Telemetry
|
package/package.json
CHANGED