canicode 0.10.4 → 0.11.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 +9 -2
- package/dist/cli/index.js +559 -141
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +254 -19
- package/dist/index.js +256 -73
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +242 -81
- package/dist/mcp/server.js.map +1 -1
- package/docs/CUSTOMIZATION.md +39 -3
- package/package.json +1 -1
- package/skills/canicode-gotchas/SKILL.md +17 -14
- package/skills/canicode-roundtrip/SKILL.md +12 -11
- package/skills/canicode-roundtrip/helpers.js +1 -1
- package/skills/cursor/canicode/SKILL.md +76 -0
- package/skills/cursor/canicode-gotchas/SKILL.md +199 -0
- package/skills/cursor/canicode-roundtrip/SKILL.md +618 -0
- package/skills/cursor/canicode-roundtrip/helpers.js +523 -0
package/docs/CUSTOMIZATION.md
CHANGED
|
@@ -104,7 +104,7 @@ Override score, severity, or enable/disable individual rules:
|
|
|
104
104
|
| Rule ID | Default Score | Default Severity |
|
|
105
105
|
|---------|--------------|-----------------|
|
|
106
106
|
| `fixed-size-in-auto-layout` | -6 | risk |
|
|
107
|
-
| `missing-size-constraint` | -
|
|
107
|
+
| `missing-size-constraint` | -1 | missing-info |
|
|
108
108
|
|
|
109
109
|
**Code Quality (4 rules)**
|
|
110
110
|
|
|
@@ -134,8 +134,8 @@ Override score, severity, or enable/disable individual rules:
|
|
|
134
134
|
|
|
135
135
|
| Rule ID | Default Score | Default Severity |
|
|
136
136
|
|---------|--------------|-----------------|
|
|
137
|
-
| `missing-interaction-state` | -1 |
|
|
138
|
-
| `missing-prototype`
|
|
137
|
+
| `missing-interaction-state` | -1 | missing-info |
|
|
138
|
+
| `missing-prototype` | -1 | missing-info |
|
|
139
139
|
<!-- RULE_TABLE_END -->
|
|
140
140
|
|
|
141
141
|
### Example Configs
|
|
@@ -176,6 +176,42 @@ Override score, severity, or enable/disable individual rules:
|
|
|
176
176
|
|
|
177
177
|
---
|
|
178
178
|
|
|
179
|
+
## Cursor MCP (canicode)
|
|
180
|
+
|
|
181
|
+
Configure the canicode MCP server so Cursor exposes `analyze`, `gotcha-survey`, and other tools.
|
|
182
|
+
|
|
183
|
+
### Project config (`.cursor/mcp.json`)
|
|
184
|
+
|
|
185
|
+
Create or merge into `.cursor/mcp.json` in your repository root:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"mcpServers": {
|
|
190
|
+
"canicode": {
|
|
191
|
+
"command": "npx",
|
|
192
|
+
"args": ["-y", "--package=canicode", "canicode-mcp"]
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Use long-form `--package` (short `-p` can confuse some parsers). Set your Figma token once with `npx canicode init --token figd_…` — the MCP server reads `~/.canicode/config.json`; you do not need `FIGMA_TOKEN` in the MCP block unless your team prefers env injection.
|
|
199
|
+
|
|
200
|
+
### Gotcha survey in Cursor
|
|
201
|
+
|
|
202
|
+
1. Add the MCP config above and restart Cursor (or reload MCP).
|
|
203
|
+
2. Run `npx canicode init --token … --cursor-skills` to install **canicode**, **canicode-gotchas**, and **canicode-roundtrip** (with `helpers.js`) under `.cursor/skills/`, and ensure the shared answer file exists at `.claude/skills/canicode-gotchas/SKILL.md` when needed (or run full `canicode init` and add `--cursor-skills`). Authoring is single-source under `.claude/skills/` in the repo; the npm build writes `skills/cursor/` (gotchas strip `# Collected Gotchas`; other skills are full copies).
|
|
204
|
+
3. In chat, @-mention **canicode**, **canicode-gotchas**, or **canicode-roundtrip** as needed. For roundtrip, the Figma MCP must expose **`use_figma`** in the session — same requirement as Claude Code.
|
|
205
|
+
|
|
206
|
+
### Manual test checklist (#407)
|
|
207
|
+
|
|
208
|
+
- [ ] MCP: Cursor shows `canicode` connected and the tools list includes `gotcha-survey` (and `analyze` if testing roundtrip Step 1).
|
|
209
|
+
- [ ] Figma MCP: `use_figma` is available when testing **roundtrip** (install + restart host if tools are missing).
|
|
210
|
+
- [ ] Calling `gotcha-survey` with a local fixture path returns JSON with `designGrade` and `questions` / `isReadyForCodeGen`.
|
|
211
|
+
- [ ] After the Q&A loop, `npx canicode upsert-gotcha-section …` succeeds and updates `.claude/skills/canicode-gotchas/SKILL.md`.
|
|
212
|
+
- [ ] Optional: @ **canicode-roundtrip** — Step 4 reads `helpers.js` from `.cursor/skills/canicode-roundtrip/helpers.js` after `canicode init --cursor-skills`.
|
|
213
|
+
|
|
214
|
+
---
|
|
179
215
|
|
|
180
216
|
## Telemetry
|
|
181
217
|
|
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: canicode-gotchas
|
|
3
|
-
description: Gotcha survey
|
|
3
|
+
description: Gotcha survey (Claude Code or Cursor) — Q&A workflow; answers accumulate in .claude/skills/canicode-gotchas/SKILL.md for figma-implement-design
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# CanICode Gotchas
|
|
6
|
+
# CanICode Gotchas — Design Gotcha Survey
|
|
7
7
|
|
|
8
|
-
Run a gotcha survey on a Figma design to
|
|
8
|
+
Run a gotcha survey on a Figma design to collect implementation context that Figma cannot encode natively, capture developer/designer answers, and upsert them into **`.claude/skills/canicode-gotchas/SKILL.md`** so downstream `figma-implement-design` runs have annotation-ready context. In this model, rules do rule-based best-practice detection, and gotcha is the annotation output from that detection. Some gotchas come from violation rules (what is wrong and how to resolve it); others come from info-collection rules (neutral context Figma cannot represent, like interaction intent/state).
|
|
9
|
+
|
|
10
|
+
**Install location:** The workflow prose may live under `.claude/skills/canicode-gotchas/SKILL.md` (default `canicode init`) or be copied to `.cursor/skills/canicode-gotchas/SKILL.md` (`canicode init --cursor-skills`). The **authoritative gotcha store** is always **`.claude/skills/canicode-gotchas/SKILL.md`** — the CLI `upsert-gotcha-section` writes there only. In the `.claude` copy, this file has two regions: the **Workflow** below (installed by `canicode init`, never overwritten manually) and the **Collected Gotchas** region at the bottom (one numbered section per design, replaced in place on re-runs).
|
|
9
11
|
|
|
10
12
|
## Prerequisites
|
|
11
13
|
|
|
12
|
-
- **canicode MCP
|
|
13
|
-
- **Without canicode MCP** (fallback):
|
|
14
|
-
- **FIGMA_TOKEN** configured for live Figma URLs
|
|
14
|
+
- **canicode MCP** (recommended): Register the server with your host — **Claude Code:** `claude mcp add canicode -- npx --yes --package=canicode canicode-mcp` — long-form flags only; the short-form `-y -p` collides with `claude mcp add`'s parser (#366); do **not** pass `-e FIGMA_TOKEN=…` here (#364). **Cursor / other hosts:** add `canicode-mcp` to your MCP config — see [Customization guide](https://github.com/let-sunny/canicode/blob/main/docs/CUSTOMIZATION.md#cursor-mcp-canicode) (`~/.cursor/mcp.json` or project `.cursor/mcp.json`). The MCP server reads `FIGMA_TOKEN` from `~/.canicode/config.json` or the environment.
|
|
15
|
+
- **Without canicode MCP** (fallback): `npx canicode gotcha-survey "<input>" --json` — same JSON shape as the MCP tool.
|
|
16
|
+
- **FIGMA_TOKEN** configured for live Figma URLs.
|
|
17
|
+
- **Gotcha destination on disk:** `.claude/skills/canicode-gotchas/SKILL.md` must exist before upsert — run `npx canicode init --token …` (add `--cursor-skills` if you also want the workflow file under `.cursor/skills/`).
|
|
15
18
|
|
|
16
19
|
## Workflow
|
|
17
20
|
|
|
@@ -38,12 +41,12 @@ Either channel returns:
|
|
|
38
41
|
|
|
39
42
|
If `isReadyForCodeGen` is `true` or `questions` is empty:
|
|
40
43
|
- Tell the user: "This design scored **{designGrade}** and is ready for code generation — no gotchas to resolve."
|
|
41
|
-
- Do NOT write to
|
|
44
|
+
- Do NOT write to `.claude/skills/canicode-gotchas/SKILL.md`.
|
|
42
45
|
- Stop here.
|
|
43
46
|
|
|
44
47
|
### Step 3: Present questions to the user
|
|
45
48
|
|
|
46
|
-
The survey response carries a pre-computed `groupedQuestions.groups[].batches[]` shape so
|
|
49
|
+
The survey response carries a pre-computed `groupedQuestions.groups[].batches[]` shape so this skill never has to sort, partition, or maintain a batchable-rule whitelist in prose. The sort key, `_no-source` sentinel, and batchable-rule list all live in `core/gotcha/group-and-batch-questions.ts` with vitest coverage (per ADR-016). Iterate over it:
|
|
47
50
|
|
|
48
51
|
For every `batch` in `groupedQuestions.groups.flatMap((g) => g.batches)`:
|
|
49
52
|
|
|
@@ -90,19 +93,19 @@ When applying the batched answer, expand back to per-question records in Step 4
|
|
|
90
93
|
|
|
91
94
|
### Step 4: Upsert the gotcha section
|
|
92
95
|
|
|
93
|
-
After collecting all answers, **upsert** this design's section into the `# Collected Gotchas` region at the bottom of
|
|
96
|
+
After collecting all answers, **upsert** this design's section into the `# Collected Gotchas` region at the bottom of:
|
|
94
97
|
|
|
95
98
|
```
|
|
96
99
|
.claude/skills/canicode-gotchas/SKILL.md
|
|
97
100
|
```
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
That path is in the **user's project** (current working directory), NOT in the canicode repo. If you are following this workflow from a copy under `.cursor/skills/`, still upsert into **`.claude/skills/...`** only — never write gotcha answers into the `.cursor` copy. The Workflow region in the `.claude` file **must never be modified manually** — only the `# Collected Gotchas` region is touched (via the CLI below).
|
|
100
103
|
|
|
101
104
|
#### Step 4a: Use the `designKey` from the survey response
|
|
102
105
|
|
|
103
106
|
`designKey` uniquely identifies the design so re-running on the same URL replaces the existing section in place. The survey response carries it on `survey.designKey` — read it directly. Do **not** parse the input URL in prose.
|
|
104
107
|
|
|
105
|
-
The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every shape with vitest coverage so
|
|
108
|
+
The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every shape with vitest coverage so this workflow stays ADR-016-compliant:
|
|
106
109
|
|
|
107
110
|
- **Figma URL** → `<fileKey>#<nodeId>` with `-` → `:` normalization on the nodeId. Example: `https://figma.com/design/abc123XYZ/My-File?node-id=42-100&t=ref` → `designKey = "abc123XYZ#42:100"`. Trailing query parameters (`?t=...`, `?mode=...`) are dropped.
|
|
108
111
|
- **Figma URL without `node-id`** → just `<fileKey>` (file-level key).
|
|
@@ -110,7 +113,7 @@ The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every sha
|
|
|
110
113
|
|
|
111
114
|
#### Step 4b: Upsert via the canicode CLI
|
|
112
115
|
|
|
113
|
-
File-state detection (4-way: missing / valid / missing-heading / clobbered) and section walking (find existing `## #NNN — ...` by `Design key` substring, otherwise compute the next monotonic zero-padded NNN) are deterministic markdown operations and live in `core/gotcha/upsert-gotcha-section.ts` with vitest coverage —
|
|
116
|
+
File-state detection (4-way: missing / valid / missing-heading / clobbered) and section walking (find existing `## #NNN — ...` by `Design key` substring, otherwise compute the next monotonic zero-padded NNN) are deterministic markdown operations and live in `core/gotcha/upsert-gotcha-section.ts` with vitest coverage — do not re-implement them in prose (per ADR-016).
|
|
114
117
|
|
|
115
118
|
Render the per-design section markdown using the **Output Template** below with the literal string `{{SECTION_NUMBER}}` in the header (the CLI substitutes the right NNN for you — preserves it on replace, computes the next monotonic value on append). Then invoke:
|
|
116
119
|
|
|
@@ -168,7 +171,7 @@ Each per-design section in the `# Collected Gotchas` region has this exact shape
|
|
|
168
171
|
| `analyzedAt` | Current timestamp (ISO 8601) |
|
|
169
172
|
| `ruleId` | `ruleId` from each question |
|
|
170
173
|
| `nodeName` | `nodeName` from each question |
|
|
171
|
-
| `severity` | `severity` from each question (blocking / risk) |
|
|
174
|
+
| `severity` | `severity` from each question (blocking / risk / missing-info — the last surfaces only for info-collection rules per #406) |
|
|
172
175
|
| `nodeId` | `nodeId` from each question |
|
|
173
176
|
| `instanceContext` | When present on the question, copy `parentInstanceNodeId`, `sourceNodeId`, `sourceComponentId`, `sourceComponentName` into the bullet above (roundtrip / Plugin apply) |
|
|
174
177
|
| `question` | `question` from each question |
|
|
@@ -186,7 +189,7 @@ This ensures the code generation agent knows the gotcha exists even if no answer
|
|
|
186
189
|
|
|
187
190
|
## Edge Cases
|
|
188
191
|
|
|
189
|
-
- **No questions returned**: The design is ready for code generation. Inform the user and stop (Step 2). Do not touch
|
|
192
|
+
- **No questions returned**: The design is ready for code generation. Inform the user and stop (Step 2). Do not touch `.claude/skills/canicode-gotchas/SKILL.md`.
|
|
190
193
|
- **Re-run on the same design**: Replace that design's section in place (matched by `Design key`) — preserve the original `#NNN` number. Do NOT append a duplicate.
|
|
191
194
|
- **Re-run on a different design**: Append a new section with the next `#NNN`. Prior designs' sections are untouched.
|
|
192
195
|
- **Workflow region**: Never modified. If you notice the Workflow region has been edited by the user, leave their edits alone — only the `# Collected Gotchas` region is under skill control.
|
|
@@ -10,8 +10,8 @@ Orchestrate the full design-to-code roundtrip: analyze a Figma design for readin
|
|
|
10
10
|
|
|
11
11
|
## Prerequisites
|
|
12
12
|
|
|
13
|
-
- **Figma MCP server** installed (provides `get_design_context`, `get_screenshot`, `use_figma`, and other Figma tools) — REQUIRED, there is no CLI fallback for `use_figma`
|
|
14
|
-
- **canicode MCP
|
|
13
|
+
- **Figma MCP server** installed (provides `get_design_context`, `get_screenshot`, `use_figma`, and other Figma tools) — REQUIRED, there is no CLI fallback for `use_figma`. Register it with your host (e.g. Claude Code: `claude mcp add -s project -t http figma https://mcp.figma.com/mcp`; Cursor: add the Figma MCP entry per host docs / project `.mcp.json`).
|
|
14
|
+
- **canicode MCP** (preferred): **Claude Code:** `claude mcp add canicode -- npx --yes --package=canicode canicode-mcp` — long-form flags only; short `-y -p` collides with `claude mcp add`'s parser (#366); do **not** pass `-e FIGMA_TOKEN=…` here (#364). **Cursor / other hosts:** add `canicode-mcp` to MCP config — see [Customization guide](https://github.com/let-sunny/canicode/blob/main/docs/CUSTOMIZATION.md#cursor-mcp-canicode). The server reads `FIGMA_TOKEN` from `~/.canicode/config.json` or the environment.
|
|
15
15
|
- **Without canicode MCP** (fallback): Steps 1 (analyze) and 3 (gotcha-survey) shell out to `npx canicode <command> --json` — same JSON shape as the MCP tools. Step 4 (apply to Figma) still requires Figma MCP `use_figma`.
|
|
16
16
|
- **FIGMA_TOKEN** configured for live Figma URLs
|
|
17
17
|
- **Figma Full seat + file edit permission** (required for `use_figma` to modify the design)
|
|
@@ -20,13 +20,13 @@ Orchestrate the full design-to-code roundtrip: analyze a Figma design for readin
|
|
|
20
20
|
|
|
21
21
|
### Step 0: Verify Figma MCP tools are loaded
|
|
22
22
|
|
|
23
|
-
Before Step 1, verify that `use_figma` is callable in **this** session — not merely listed in `.mcp.json`. Newly registered MCP servers
|
|
23
|
+
Before Step 1, verify that `use_figma` is callable in **this** session — not merely listed in `.mcp.json`. Newly registered MCP servers require a **host restart or MCP reload** so tools appear (e.g. Claude Code: restart after `claude mcp add …`; Cursor: restart Cursor or reload MCP after editing `.cursor/mcp.json`). Reading `.mcp.json` is not a substitute for checking the live tool list you have access to right now.
|
|
24
24
|
|
|
25
25
|
If `use_figma` is unavailable in the current session, **Do NOT proceed to Step 1**. Steps 1 (analyze) and 3 (gotcha-survey) spend real Figma API calls and 5–15 minutes of human survey time before Step 4 would otherwise discover `use_figma` is missing. Halt immediately and tell the user:
|
|
26
26
|
|
|
27
|
-
1. Confirm `.mcp.json` registers the Figma MCP entry (e.g. `figma` under `mcpServers`).
|
|
28
|
-
2. Restart
|
|
29
|
-
3. Re-invoke `/canicode-roundtrip
|
|
27
|
+
1. Confirm `.mcp.json` (project or user) registers the Figma MCP entry (e.g. `figma` under `mcpServers`).
|
|
28
|
+
2. Restart the IDE / agent host (or reload MCP) so the newly registered tools load.
|
|
29
|
+
3. Re-invoke the roundtrip (Claude Code slash command `/canicode-roundtrip`, or Cursor: @ **canicode-roundtrip** with the Figma URL).
|
|
30
30
|
|
|
31
31
|
See the Edge Case **No Figma MCP server** below for the one-way fallback when Figma MCP genuinely cannot be installed — the precheck above is for the common "installed but not restarted" case, not a replacement for that fallback.
|
|
32
32
|
|
|
@@ -227,7 +227,7 @@ The helper walks the tiers in order; variable binding is an alternative writeFn
|
|
|
227
227
|
|
|
228
228
|
1. **Scene (instance) node** — `await figma.getNodeByIdAsync(question.nodeId)` and apply the write inside `try/catch`. If the answer names a design-system token (`{ variable: "name" }`), the helper calls `setBoundVariable` / `setBoundVariableForPaint` first and that binding bypasses the override gate — otherwise it performs a raw-value write. Success → done (local change only). Mark result with ✅.
|
|
229
229
|
2. **Definition (source) node — opt-in only** — Runs only when the orchestrator passes `allowDefinitionWrite: true` on the helper context (after a batch-level confirmation naming the source component AND the propagation set). When the flag is off (the ADR-012 default), a recognized instance-override failure (override-error or silent-ignore) short-circuits here and routes directly to tier 3 — the definition node is never touched. When the flag is on, the helper loads `question.sourceChildId` (or walks `getMainComponentAsync()` if needed) and writes using the same bind-if-token-else-raw shape as tier 1; changes propagate to **every non-overridden instance** in the file (Experiment 10). Mark result with 🌐.
|
|
230
|
-
3. **Annotation fallback — default path** — Under the ADR-012 default this is where override-errors and silent-ignores land: the helper annotates the **scene** node with markdown that names the
|
|
230
|
+
3. **Annotation fallback — default path** — Under the ADR-012 default this is where override-errors and silent-ignores land: the helper annotates the **scene** node with markdown that names the actual no-op (the property silently ignored the write or the override was rejected) and points to the source component as the correct write target. When `allowDefinitionWrite` is on, this tier also catches any definition-tier throw (e.g. Experiment 10 external-library read-only case, `mainComponent.remote === true` / *"Cannot write to internal and read-only node"*, and the `mainComponent === null` branch where `getMainComponentAsync()` resolves with no definition to name — see Experiment 11 / ADR-011). Either way, mark result with 📝.
|
|
231
231
|
|
|
232
232
|
**Confirmation is a batch-level concern — and only needed when opting in.** A `use_figma` call runs one JavaScript batch and cannot pause mid-batch for user input. Under the ADR-012 default (`allowDefinitionWrite: false`), no propagation happens, so no confirmation is required — override-errors annotate and move on. The orchestrator sets `allowDefinitionWrite: true` only after enumerating the likely propagation set to the user up-front and collecting **one confirmation for the whole batch** that names the source component(s) and the affected instance set. When describing impact, note that the write reaches every **non-overridden** instance — any instance with a local override for the same property keeps its override. The helper below never prompts — it assumes that if the flag is on, confirmation already happened.
|
|
233
233
|
|
|
@@ -276,11 +276,11 @@ Branches on `probe`:
|
|
|
276
276
|
|
|
277
277
|
The probe is read-only and idempotent; running it before the picker adds one round-trip but saves the user a confusing "I opted in, why did I get annotations?" moment that #342 surfaced live on Simple Design System (Community).
|
|
278
278
|
|
|
279
|
-
**Shared helpers (bundled)** — the deterministic helpers live in TypeScript at `src/core/roundtrip/*.ts` and are bundled to a single IIFE
|
|
279
|
+
**Shared helpers (bundled)** — the deterministic helpers live in TypeScript at `src/core/roundtrip/*.ts` and are bundled to a single IIFE shipped next to this skill as `helpers.js`. `use_figma` only accepts a self-contained JS string, so the source of truth is TypeScript (with vitest coverage) and the bundle is the delivery artifact.
|
|
280
280
|
|
|
281
281
|
**Usage in a roundtrip session:**
|
|
282
282
|
|
|
283
|
-
1. Read `.claude/skills/canicode-roundtrip/helpers.js`
|
|
283
|
+
1. Read `helpers.js` from the same directory as this skill once at the start of Step 4 — typically `.claude/skills/canicode-roundtrip/helpers.js` (Claude Code / default `canicode init`) or `.cursor/skills/canicode-roundtrip/helpers.js` (Cursor with `canicode init --cursor-skills`).
|
|
284
284
|
2. Prepend its contents verbatim at the top of every `use_figma` batch body — it registers a single global `CanICodeRoundtrip`.
|
|
285
285
|
3. Reference exposed globals as `CanICodeRoundtrip.*`:
|
|
286
286
|
- `stripAnnotations(annotations)` — normalizes the D1 label/labelMarkdown mutex on readback.
|
|
@@ -402,7 +402,7 @@ Notes:
|
|
|
402
402
|
|
|
403
403
|
#### Strategy D: Auto-fix lower-severity issues from analysis
|
|
404
404
|
|
|
405
|
-
The gotcha survey covers
|
|
405
|
+
The gotcha survey covers blocking/risk severity plus `missing-info` severity from info-collection rules (#406 — currently `missing-prototype`, `missing-interaction-state`). All other lower-severity rules appear in `analyzeResult.issues[]` without a survey question. Each issue carries the same pre-computed fields (`applyStrategy`, `targetProperty`, `annotationProperties`, `suggestedName`, `isInstanceChild`, `sourceChildId`). The bundled helper handles the loop, the filter (`applyStrategy === "auto-fix"`), the naming-vs-annotation branch, and the per-issue outcome accumulator in one call:
|
|
406
406
|
|
|
407
407
|
```javascript
|
|
408
408
|
const outcomes = await CanICodeRoundtrip.applyAutoFixes(analyzeResult.issues, { categories });
|
|
@@ -577,6 +577,7 @@ Follow the **figma-implement-design** skill workflow to generate code from the F
|
|
|
577
577
|
|
|
578
578
|
- Gotchas with severity **blocking** MUST be addressed — the design cannot be implemented correctly without this information
|
|
579
579
|
- Gotchas with severity **risk** SHOULD be addressed — they indicate potential issues that will surface later
|
|
580
|
+
- Gotchas with severity **missing-info** from info-collection rules (`purpose === "info-collection"`, e.g. `missing-prototype`, `missing-interaction-state`) are annotation-primary (#406): the answer describes implementation context Figma cannot encode (click target, state variants). Treat them as code-generation context rather than violations to fix — the rule's score impact is minimal by design
|
|
580
581
|
- Reference the specific node IDs from gotcha answers to locate the affected elements in the design
|
|
581
582
|
- Pass the Figma URL or `survey.designKey` to `figma-implement-design` so it can grep the matching `## #NNN — …` section in `.claude/skills/canicode-gotchas/SKILL.md` instead of reading the whole accumulated file
|
|
582
583
|
|
|
@@ -613,5 +614,5 @@ Code: <files generated / next-step pointer from figma-implement-design>
|
|
|
613
614
|
- **Partial gotcha answers**: Apply only the answered questions. Skipped/n/a questions are neither applied nor annotated.
|
|
614
615
|
- **use_figma call fails for a node**: Report the error for that specific node, continue with other nodes. Failed property modifications become annotations so the context is not lost.
|
|
615
616
|
- **Re-analyze shows new issues**: Only address issues from the original gotcha survey. New issues may appear due to structural changes — report them but do not re-enter the gotcha loop.
|
|
616
|
-
- **Very large design (many gotchas)**: The gotcha survey already deduplicates sibling nodes and filters to blocking/risk
|
|
617
|
+
- **Very large design (many gotchas)**: The gotcha survey already deduplicates sibling nodes and filters to blocking/risk plus `missing-info` from info-collection rules (#406). If there are still many questions, ask the user if they want to focus on blocking issues only.
|
|
617
618
|
- **External library components**: Applies only when the orchestrator has set `allowDefinitionWrite: true`. Experiment 10's observed case is `getMainComponentAsync()` resolving with `mainComponent.remote === true` — writes then throw *"Cannot write to internal and read-only node"*. The `mainComponent === null` case is documented in the Plugin API but was not reproduced live in Experiment 10; Experiment 11 (#309) unit-test-covers the helper's routing for that branch (override-error + no `sourceChildId` → annotate with `could not apply automatically:` markdown — see ADR-011 Verification), so the code path is regression-locked while live Figma reproduction remains a manual fixture-seeding follow-up. Under the default (`allowDefinitionWrite: false`), the definition write never fires and this throw cannot surface. **The pre-flight `probeDefinitionWritability` (#357) detects both branches up-front** so the Definition write picker can drop the opt-in option entirely when every candidate is unwritable, saving the user a wasted decision before the runtime fallback kicks in.
|
|
@@ -96,7 +96,7 @@ ${footer}`;
|
|
|
96
96
|
if (ctx.categories) {
|
|
97
97
|
upsertCanicodeAnnotation(ctx.scene, {
|
|
98
98
|
ruleId: ctx.question.ruleId,
|
|
99
|
-
markdown: `
|
|
99
|
+
markdown: `The fix below could not be applied on this instance child \u2014 the property silently ignored the write or the override was rejected. Apply it on the source component **${componentName}** so every instance picks it up. Re-run with \`allowDefinitionWrite: true\` to let canicode propagate automatically.`,
|
|
100
100
|
categoryId: ctx.categories.fallback
|
|
101
101
|
});
|
|
102
102
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: canicode
|
|
3
|
+
description: Analyze Figma designs for development-friendliness and AI-friendliness scores
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CanICode -- Figma Design Analysis
|
|
7
|
+
|
|
8
|
+
Analyze Figma design files to score how development-friendly and AI-friendly they are. Produces actionable reports with specific issues and fix suggestions.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
This skill works with either channel — the CLI or the canicode MCP server. Both return the same analysis; pick whichever is already set up. Requires either:
|
|
13
|
+
- A **saved fixture** (from `canicode calibrate-save-fixture`)
|
|
14
|
+
- A **FIGMA_TOKEN** for live Figma URLs
|
|
15
|
+
|
|
16
|
+
## How to Analyze
|
|
17
|
+
|
|
18
|
+
### From a Figma URL
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx canicode analyze "https://www.figma.com/design/ABC123/MyDesign?node-id=1-234" --token YOUR_TOKEN
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or if FIGMA_TOKEN is set in environment:
|
|
25
|
+
```bash
|
|
26
|
+
npx canicode analyze "https://www.figma.com/design/ABC123/MyDesign?node-id=1-234"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### From a saved fixture
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx canicode analyze fixtures/my-design
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Save a fixture for offline analysis
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx canicode calibrate-save-fixture "https://www.figma.com/design/ABC123/MyDesign?node-id=1-234" --output fixtures/my-design
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Analysis Options
|
|
42
|
+
|
|
43
|
+
### Presets
|
|
44
|
+
- `--preset relaxed` — Downgrades blocking to risk, reduces scores by 50%
|
|
45
|
+
- `--preset dev-friendly` — Enables only pixel-critical and responsive-critical rules, disables the rest
|
|
46
|
+
- `--preset ai-ready` — Sets pixel-critical and token-management rule scores to 150% of defaults
|
|
47
|
+
- `--preset strict` — Increases all scores by 150%
|
|
48
|
+
|
|
49
|
+
### Config overrides
|
|
50
|
+
```bash
|
|
51
|
+
npx canicode analyze <input> --config ./my-config.json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### JSON output
|
|
55
|
+
```bash
|
|
56
|
+
npx canicode analyze <input> --json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Via MCP (when `canicode-mcp` is installed)
|
|
60
|
+
|
|
61
|
+
If the user has the canicode MCP server installed, prefer the MCP tool — it avoids the `npx` spawn overhead and reuses a warm Figma client:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
analyze({ input: "<figma-url-or-fixture-path>" })
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Options mirror the CLI: `preset`, `token`, `config`, `targetNodeId`, `json`. The `json` response field matches `npx canicode analyze --json` byte-for-byte, so downstream code can parse either source.
|
|
68
|
+
|
|
69
|
+
## What It Reports
|
|
70
|
+
|
|
71
|
+
16 rules across 6 categories: Pixel Critical, Responsive Critical, Code Quality, Token Management, Interaction, Semantic.
|
|
72
|
+
|
|
73
|
+
Each issue includes:
|
|
74
|
+
- Rule ID and severity (blocking / risk / missing-info / suggestion)
|
|
75
|
+
- Affected node with Figma deep link
|
|
76
|
+
- Why it matters, impact, and how to fix
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: canicode-gotchas
|
|
3
|
+
description: Gotcha survey (Claude Code or Cursor) — Q&A workflow; answers accumulate in .claude/skills/canicode-gotchas/SKILL.md for figma-implement-design
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CanICode Gotchas — Design Gotcha Survey
|
|
7
|
+
|
|
8
|
+
Run a gotcha survey on a Figma design to collect implementation context that Figma cannot encode natively, capture developer/designer answers, and upsert them into **`.claude/skills/canicode-gotchas/SKILL.md`** so downstream `figma-implement-design` runs have annotation-ready context. In this model, rules do rule-based best-practice detection, and gotcha is the annotation output from that detection. Some gotchas come from violation rules (what is wrong and how to resolve it); others come from info-collection rules (neutral context Figma cannot represent, like interaction intent/state).
|
|
9
|
+
|
|
10
|
+
**Install location:** The workflow prose may live under `.claude/skills/canicode-gotchas/SKILL.md` (default `canicode init`) or be copied to `.cursor/skills/canicode-gotchas/SKILL.md` (`canicode init --cursor-skills`). The **authoritative gotcha store** is always **`.claude/skills/canicode-gotchas/SKILL.md`** — the CLI `upsert-gotcha-section` writes there only. In the `.claude` copy, this file has two regions: the **Workflow** below (installed by `canicode init`, never overwritten manually) and the **Collected Gotchas** region at the bottom (one numbered section per design, replaced in place on re-runs).
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- **canicode MCP** (recommended): Register the server with your host — **Claude Code:** `claude mcp add canicode -- npx --yes --package=canicode canicode-mcp` — long-form flags only; the short-form `-y -p` collides with `claude mcp add`'s parser (#366); do **not** pass `-e FIGMA_TOKEN=…` here (#364). **Cursor / other hosts:** add `canicode-mcp` to your MCP config — see [Customization guide](https://github.com/let-sunny/canicode/blob/main/docs/CUSTOMIZATION.md#cursor-mcp-canicode) (`~/.cursor/mcp.json` or project `.cursor/mcp.json`). The MCP server reads `FIGMA_TOKEN` from `~/.canicode/config.json` or the environment.
|
|
15
|
+
- **Without canicode MCP** (fallback): `npx canicode gotcha-survey "<input>" --json` — same JSON shape as the MCP tool.
|
|
16
|
+
- **FIGMA_TOKEN** configured for live Figma URLs.
|
|
17
|
+
- **Gotcha destination on disk:** `.claude/skills/canicode-gotchas/SKILL.md` must exist before upsert — run `npx canicode init --token …` (add `--cursor-skills` if you also want the workflow file under `.cursor/skills/`).
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
### Step 1: Run the gotcha survey
|
|
22
|
+
|
|
23
|
+
If the `gotcha-survey` MCP tool is available, call it with the user's Figma URL:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
gotcha-survey({ input: "<figma-url-or-fixture-path>" })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Without canicode MCP** — shell out to the CLI. The `--json` output parses identically:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx canicode gotcha-survey "<figma-url-or-fixture-path>" --json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Either channel returns:
|
|
36
|
+
- `designGrade`: overall grade (S, A+, A, B+, B, C+, C, D, F)
|
|
37
|
+
- `isReadyForCodeGen`: whether the design can be implemented without gotchas
|
|
38
|
+
- `questions`: array of gotcha questions (may be empty)
|
|
39
|
+
|
|
40
|
+
### Step 2: Check if survey is needed
|
|
41
|
+
|
|
42
|
+
If `isReadyForCodeGen` is `true` or `questions` is empty:
|
|
43
|
+
- Tell the user: "This design scored **{designGrade}** and is ready for code generation — no gotchas to resolve."
|
|
44
|
+
- Do NOT write to `.claude/skills/canicode-gotchas/SKILL.md`.
|
|
45
|
+
- Stop here.
|
|
46
|
+
|
|
47
|
+
### Step 3: Present questions to the user
|
|
48
|
+
|
|
49
|
+
The survey response carries a pre-computed `groupedQuestions.groups[].batches[]` shape so this skill never has to sort, partition, or maintain a batchable-rule whitelist in prose. The sort key, `_no-source` sentinel, and batchable-rule list all live in `core/gotcha/group-and-batch-questions.ts` with vitest coverage (per ADR-016). Iterate over it:
|
|
50
|
+
|
|
51
|
+
For every `batch` in `groupedQuestions.groups.flatMap((g) => g.batches)`:
|
|
52
|
+
|
|
53
|
+
- **Single-question batch (`batch.questions.length === 1`)** — render the standard prompt for `batch.questions[0]`:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
**[{severity}] {ruleId}** — node: {nodeName}
|
|
57
|
+
|
|
58
|
+
{question}
|
|
59
|
+
|
|
60
|
+
> Hint: {hint}
|
|
61
|
+
> Example: {example}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- **Batch of N ≥ 2 with `batch.batchable === true`** (#369) — render one shared prompt covering every member:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
**[{severity}] {ruleId}** — {batch.questions.length} instances:
|
|
68
|
+
- {nodeName₁}
|
|
69
|
+
- {nodeName₂}
|
|
70
|
+
- …
|
|
71
|
+
|
|
72
|
+
{sharedQuestionPrompt}
|
|
73
|
+
|
|
74
|
+
Reply with one answer to apply to all {batch.questions.length}, or **split** to answer each individually.
|
|
75
|
+
|
|
76
|
+
> Hint: {hint}
|
|
77
|
+
> Example: {example}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Where `sharedQuestionPrompt` reuses the rule's `question` text with the per-node noun replaced by the rule's plural noun (e.g. "These layers all use FILL sizing without min/max constraints. What size boundaries should they share?" instead of repeating the singular phrasing N times).
|
|
81
|
+
|
|
82
|
+
- **Any batch with `batch.batchable === false`** is always rendered as a single-question prompt — the helper guarantees `questions.length === 1` for those (identity-typed answers like `non-semantic-name`, structural-mod rules).
|
|
83
|
+
|
|
84
|
+
Wait for the user's answer before moving to the next batch. The user may:
|
|
85
|
+
- Answer the question / batch directly
|
|
86
|
+
- Say **split** (batch only) to fall back to per-question prompting for that batch
|
|
87
|
+
- Say **skip** to skip the question / the entire batch
|
|
88
|
+
- Say **n/a** if the question / the entire batch is not applicable
|
|
89
|
+
|
|
90
|
+
When applying the batched answer, expand back to per-question records in Step 4 — the gotcha section format stores one record per `nodeId`.
|
|
91
|
+
|
|
92
|
+
> The `groupedQuestions.groups[].instanceContext` field exists for the `canicode-roundtrip` SKILL's "Instance note" hoist (#370). This skill ignores it — every record gets its own `Instance context` bullet in Step 4 anyway.
|
|
93
|
+
|
|
94
|
+
### Step 4: Upsert the gotcha section
|
|
95
|
+
|
|
96
|
+
After collecting all answers, **upsert** this design's section into the `# Collected Gotchas` region at the bottom of:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
.claude/skills/canicode-gotchas/SKILL.md
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
That path is in the **user's project** (current working directory), NOT in the canicode repo. If you are following this workflow from a copy under `.cursor/skills/`, still upsert into **`.claude/skills/...`** only — never write gotcha answers into the `.cursor` copy. The Workflow region in the `.claude` file **must never be modified manually** — only the `# Collected Gotchas` region is touched (via the CLI below).
|
|
103
|
+
|
|
104
|
+
#### Step 4a: Use the `designKey` from the survey response
|
|
105
|
+
|
|
106
|
+
`designKey` uniquely identifies the design so re-running on the same URL replaces the existing section in place. The survey response carries it on `survey.designKey` — read it directly. Do **not** parse the input URL in prose.
|
|
107
|
+
|
|
108
|
+
The `core/contracts/design-key.ts` helper (`computeDesignKey`) handles every shape with vitest coverage so this workflow stays ADR-016-compliant:
|
|
109
|
+
|
|
110
|
+
- **Figma URL** → `<fileKey>#<nodeId>` with `-` → `:` normalization on the nodeId. Example: `https://figma.com/design/abc123XYZ/My-File?node-id=42-100&t=ref` → `designKey = "abc123XYZ#42:100"`. Trailing query parameters (`?t=...`, `?mode=...`) are dropped.
|
|
111
|
+
- **Figma URL without `node-id`** → just `<fileKey>` (file-level key).
|
|
112
|
+
- **Fixture path / JSON file** → absolute path.
|
|
113
|
+
|
|
114
|
+
#### Step 4b: Upsert via the canicode CLI
|
|
115
|
+
|
|
116
|
+
File-state detection (4-way: missing / valid / missing-heading / clobbered) and section walking (find existing `## #NNN — ...` by `Design key` substring, otherwise compute the next monotonic zero-padded NNN) are deterministic markdown operations and live in `core/gotcha/upsert-gotcha-section.ts` with vitest coverage — do not re-implement them in prose (per ADR-016).
|
|
117
|
+
|
|
118
|
+
Render the per-design section markdown using the **Output Template** below with the literal string `{{SECTION_NUMBER}}` in the header (the CLI substitutes the right NNN for you — preserves it on replace, computes the next monotonic value on append). Then invoke:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npx canicode upsert-gotcha-section \
|
|
122
|
+
--file .claude/skills/canicode-gotchas/SKILL.md \
|
|
123
|
+
--design-key "<designKey from Step 4a>" \
|
|
124
|
+
--section - # then pipe the rendered section markdown through stdin
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The CLI prints a JSON result `{ state, action, sectionNumber, wrote, userMessage }`:
|
|
128
|
+
|
|
129
|
+
- `wrote: true` → success. `action` is `"replace"` (preserved `sectionNumber`) or `"append"` (next monotonic `sectionNumber`).
|
|
130
|
+
- `wrote: false` with `state: "missing"` → tell the user: *"Your gotchas SKILL.md is not installed yet. Run `canicode init` first, then re-invoke this skill."* Stop here.
|
|
131
|
+
- `wrote: false` with `state: "clobbered"` → tell the user: *"Your gotchas SKILL.md is missing the canicode YAML frontmatter (pre-#340 single-design clobber). Run `canicode init --force` to restore the workflow, then re-run this survey — your answers will land in a clean numbered section."* Stop here.
|
|
132
|
+
- `wrote: true` with `state: "missing-heading"` → silent recovery. The CLI injected the `# Collected Gotchas` heading and appended the section; the workflow region above is untouched.
|
|
133
|
+
|
|
134
|
+
The Workflow region above must never be touched. Do NOT copy Workflow prose into the per-design section; the section only carries metadata + gotcha answers.
|
|
135
|
+
|
|
136
|
+
## Output Template
|
|
137
|
+
|
|
138
|
+
Each per-design section in the `# Collected Gotchas` region has this exact shape:
|
|
139
|
+
|
|
140
|
+
````markdown
|
|
141
|
+
## #NNN — {designName} — {YYYY-MM-DD}
|
|
142
|
+
|
|
143
|
+
- **Figma URL**: {figmaUrl}
|
|
144
|
+
- **Design key**: {designKey}
|
|
145
|
+
- **Grade**: {designGrade}
|
|
146
|
+
- **Analyzed at**: {analyzedAt}
|
|
147
|
+
|
|
148
|
+
### Gotchas
|
|
149
|
+
|
|
150
|
+
#### {ruleId} — {nodeName}
|
|
151
|
+
|
|
152
|
+
- **Severity**: {severity}
|
|
153
|
+
- **Node ID**: {nodeId}
|
|
154
|
+
- **Instance context** (omit this bullet if `instanceContext` was not in the survey question): parent instance `parentInstanceNodeId`, source node `sourceNodeId`, component `sourceComponentName` / `sourceComponentId` when present — roundtrip apply uses this to write on the source definition when instance overrides fail.
|
|
155
|
+
- **Question**: {question}
|
|
156
|
+
- **Answer**: {userAnswer}
|
|
157
|
+
|
|
158
|
+
(repeat for each question)
|
|
159
|
+
````
|
|
160
|
+
|
|
161
|
+
### Field mapping
|
|
162
|
+
|
|
163
|
+
| Field | Source |
|
|
164
|
+
|-------|--------|
|
|
165
|
+
| `NNN` | `sectionNumber` — zero-padded three-digit index. Preserved on re-run, incremented on append. |
|
|
166
|
+
| `designName` | Figma file name or fixture name from the input |
|
|
167
|
+
| `YYYY-MM-DD` | Today's date (the day you are running the survey) |
|
|
168
|
+
| `figmaUrl` | The input URL or fixture path provided by the user |
|
|
169
|
+
| `designKey` | `survey.designKey` from the gotcha-survey response (see Step 4a) |
|
|
170
|
+
| `designGrade` | `designGrade` from gotcha-survey response |
|
|
171
|
+
| `analyzedAt` | Current timestamp (ISO 8601) |
|
|
172
|
+
| `ruleId` | `ruleId` from each question |
|
|
173
|
+
| `nodeName` | `nodeName` from each question |
|
|
174
|
+
| `severity` | `severity` from each question (blocking / risk / missing-info — the last surfaces only for info-collection rules per #406) |
|
|
175
|
+
| `nodeId` | `nodeId` from each question |
|
|
176
|
+
| `instanceContext` | When present on the question, copy `parentInstanceNodeId`, `sourceNodeId`, `sourceComponentId`, `sourceComponentName` into the bullet above (roundtrip / Plugin apply) |
|
|
177
|
+
| `question` | `question` from each question |
|
|
178
|
+
| `userAnswer` | The answer collected from the user in Step 3 |
|
|
179
|
+
|
|
180
|
+
### Skipped questions
|
|
181
|
+
|
|
182
|
+
If the user skipped a question or said "n/a", still include it in the section with:
|
|
183
|
+
|
|
184
|
+
```markdown
|
|
185
|
+
- **Answer**: _(skipped)_
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
This ensures the code generation agent knows the gotcha exists even if no answer was provided.
|
|
189
|
+
|
|
190
|
+
## Edge Cases
|
|
191
|
+
|
|
192
|
+
- **No questions returned**: The design is ready for code generation. Inform the user and stop (Step 2). Do not touch `.claude/skills/canicode-gotchas/SKILL.md`.
|
|
193
|
+
- **Re-run on the same design**: Replace that design's section in place (matched by `Design key`) — preserve the original `#NNN` number. Do NOT append a duplicate.
|
|
194
|
+
- **Re-run on a different design**: Append a new section with the next `#NNN`. Prior designs' sections are untouched.
|
|
195
|
+
- **Workflow region**: Never modified. If you notice the Workflow region has been edited by the user, leave their edits alone — only the `# Collected Gotchas` region is under skill control.
|
|
196
|
+
- **Pre-#340 clobbered file** (the YAML frontmatter was rewritten to a per-design variant, so the canonical `canicode-gotchas` frontmatter is missing): tell the user to run `canicode init --force` to restore the workflow, then re-run the survey. The prior single-design content cannot be automatically migrated into a `## #001` section — the user re-runs and gets a clean section.
|
|
197
|
+
- **MCP tool not available**: Fall back to `npx canicode gotcha-survey <input> --json` — the CLI returns the same `GotchaSurvey` shape. If the CLI is also unavailable (e.g. no node runtime), tell the user to install the canicode MCP server or the `canicode` npm package (see Prerequisites).
|
|
198
|
+
- **Partial answers**: If the user stops mid-survey, upsert the section with answers collected so far. Mark remaining questions as _(skipped)_.
|
|
199
|
+
- **Manual section deletion**: If the user deletes a middle section by hand, do not renumber existing sections. The next new section still gets `(highest existing number) + 1`; numeric gaps are acceptable (same pattern as `.claude/docs/ADR.md`).
|