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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canicode",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "CLI tool that analyzes Figma design structures for development-friendliness and AI-friendliness scores",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",