# Create Component Md

> Generate a single self-contained markdown specification for a Figma component covering API, structure, color, and screen-reader behavior. Reads a `_base.json` produced by the uSpec Extract plugin, runs four read-only interpretation skills in parallel, reconciles their outputs, and writes one `.md` to disk. Use when the user mentions "component md", "component markdown", "spec md", "source of truth", "create-component-md", or wants a portable Markdown spec that any LLM can build from. Use when this capability is needed.

- Skill: `tomevault-io/create-component-md` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/create-component-md`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/create-component-md/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/create-component-md

---


# Create Component Markdown (Orchestrator)

This skill consumes a `_base.json` produced by the uSpec Extract Figma plugin (`figma-plugin/`), runs four **read-only interpretation** skills (`extract-api`, `extract-structure`, `extract-color`, `extract-voice`), and renders their combined output into one self-contained Markdown file. The `.md` is the artifact; Figma is only the source of extraction.

**Do not call the `create-*` skills from here.** They render Figma frames that overlap and do not compose into a single file.

## Why this orchestrator exists

The four `create-*` skills each cost ~100k tokens per run because the majority of their weight is Figma rendering (`setProperties`, `createInstance`, `loadFontAsync`, layout math). The `extract-*` skills strip all rendering. Because the Figma plugin produces a single shared `_base.json`, the four interpretation skills also stop calling Figma — they read that file from disk. This removes most of the Figma-side work and keeps the orchestrator's parent context small by discarding each phase's detail after its one-line summary lands.

**Token model (approximate):**

| Phase | Peak context in parent |
|---|---|
| extract-api (runs first, inline) | instruction + `_base.json` read + interpretation |
| parallel fan-out (structure + color + voice, subagent each) | three one-line summaries (subagents hold their own context) |
| reconciliation (Step 8.5) | mismatch lists + api dictionary (small) |
| rendering | 4 JSON cache files + template + instruction |

`extract-api` runs first in the parent so its dictionary can steer the three downstream specialists. After the dictionary lands, the parent dispatches `extract-structure`, `extract-color`, and `extract-voice` as three **parallel subagents** (`subagent_type=generalPurpose`, single batch). Each subagent holds its own `_base.json` + dictionary context; the parent keeps only the returned one-line summaries and cache-file paths.

## Inputs Expected

- **`baseJsonPath`** (required): absolute or workspace-relative path to the `_base.json` file produced by the uSpec Extract Figma plugin. Must validate against [figma-plugin/docs/base-json-schema.md]({{repo:figma-plugin/docs/base-json-schema.md}}). If this is not provided, abort and instruct the user to run the uSpec Extract plugin (see `figma-plugin/README.md`).
- **`figmaLink`** (optional): URL to the component set or standalone component. Accept `figma.com/design/:fileKey/...` and branch URLs (`/branch/:branchKey/`). Only consulted if an interpretation skill needs a Step 3-delta MCP call.
- **`optionalContext`** (optional): free-form guidance (e.g., "this is a compact variant only", "skip error states"). If the plugin already captured it in `_meta.optionalContext`, that wins; otherwise the value passed here is used. Forwarded verbatim to every sub-skill.

No output path is required — the default is `./components/{componentSlug}.md` in the current working directory.

## Workflow

Copy this checklist and update as you progress:

```
Task Progress:
- [ ] Step 1: Preflight — read config, load + validate _base.json, resolve metadata from _meta
- [ ] Step 2: Resolve componentSlug and output path
- [ ] Step 3: Announce the plan
- [ ] Step 3.5: Composition classification (reasoning gate — internalize before Step 4.5 review)
- [ ] Step 4: Stage _base.json into cachePath
- [ ] Step 4.5: Post-extract review — confirm _childComposition (user-selected classifications skip override pass)
- [ ] Step 5: Run extract-api (reads _base.json, no Figma), flush, verify cache + api-dictionary.json
- [ ] Step 6: Parallel fan-out — dispatch extract-structure, extract-color, extract-voice as three subagents in a single batch; join on all three summaries
- [ ] Step 8.5: Reconciliation — typed disagreement handling with bounded serial retries
- [ ] Step 9: Render the .md (follow {{ref:component-md/agent-component-md-instruction.md}})
- [ ] Step 9.5: Integrity check — validate all cache files and reconciliation artifact before rendering
- [ ] Step 10: Audit output and return a one-line summary
- [ ] Step 10.5: Emit recursion manifest (constitutive children only)
```

### Step 1: Preflight

Read `uspecs.config.json` at the project root. Extract:

- `mcpProvider` (`figma-console` or `figma-mcp`). Only used if `figmaLink` is also provided AND an interpretation skill's Step 3-delta triggers.
- `environment` (used only if you need to emit provider-specific guidance).

**Load and validate `_base.json`.** `baseJsonPath` is required. If it is missing, abort with a one-line diagnostic: "run the uSpec Extract plugin in Figma and rerun with `baseJsonPath=<path>` — see `figma-plugin/README.md`."

- Read the file.
- Run the Ajv schema check at `figma-plugin/scripts/validate-base.mjs` (shell out with `node figma-plugin/scripts/validate-base.mjs <path>`) — any non-zero exit aborts with the validator's FAIL output.
- On success, read `_meta.fileKey`, `_meta.nodeId`, `_meta.componentSlug`, `_meta.optionalContext`, `_meta.extractionSource`.
- If the caller also passed `optionalContext` and `_meta.optionalContext` is null, stamp it onto the loaded base.
- The MCP connection check is deferred — it is not needed unless a sub-skill triggers a delta.

If `figmaLink` is also passed alongside `baseJsonPath`, parse it and stash `{fileKey, nodeId}` for potential delta use — but trust `_meta` as the source of truth when they disagree and log a `META_DISAGREES_WITH_LINK` warning for the final summary.

### Step 2: Resolve componentSlug and output path

1. Resolve the component name from `_meta.componentSlug` (plus `component.componentName` for display). No MCP call needed.
2. Resolve `outputPath`:
   - If user passed an explicit path, use as-is.
   - Otherwise, `./components/{componentSlug}.md` in cwd. Create the `./components/` directory (recursive mkdir) if it does not exist — the `components/` folder is tracked in version control (it holds the source-of-truth `.md` specs).
3. Resolve `cachePath = .uspec-cache/{componentSlug}/`. Create it (recursive mkdir). `.uspec-cache/` is gitignored.

**Do not proceed until all of `componentSlug`, `outputPath`, `cachePath` are resolved.** Every subsequent step reads from them.

### Step 3: Announce the plan (non-blocking)

Print a single informational message to the user and then **immediately continue** to Step 4. Do **not** call `AskQuestion`. Do **not** wait for input. The chain runs start-to-finish without interruption.

> Generating Markdown source of truth for **{ComponentName}** (`{nodeId}`).
>
> Running 4 interpretation passes (API → Structure → Color → Voice) against the provided `_base.json`. Cache: `{cachePath}`. Output: `{outputPath}`.
>
> No phase touches Figma unless an interpretation skill's Step 3-delta fires (read-only). Every pass flushes to disk to stay under token budget.

If the user wants to correct the target node or the extraction context, they must cancel this run and start a new one with different inputs. There is no mid-chain correction point.

### Step 3.5: Composition classification (reasoning gate)

Before you interpret any of the extracted data, you must internalize how to classify every top-level child instance of the component being spec'd. This classification decides how each child appears in the final `.md` and whether a follow-up `create-component-md` run is needed. The mechanics of reading `_childComposition` happen at Step 4.5 (after `_base.json` lands). This step is the *reasoning model* you carry into that review.

Ask this question, in order, for each top-level child instance you will see in `variants[<default>].treeHierarchical`:

**Q1. If I removed this child, would the component still be the thing the user is asking me to spec?**

- No → **constitutive** (part-of). It is part of what this component *is*. Spec it.
- Yes → continue to Q2.

**Q2. Does this child have a name/identity that a different consumer would recognize and use independently in a different context?**

- Yes → **referenced** (uses-a). It is a self-contained component this one happens to embed. Name it and document the configuration passed to it; do not re-spec it.
- No → **decorative** (has-no-identity). A vector, glyph, or layout frame. Fold into structure or color inline.

#### Patterns to match against, with one illustration each

The column you should pattern-match on is **Pattern** — the shape of the relationship. The `Illustration` column is one concrete example so you can anchor the shape; **the agreement is with the pattern, not the specific names**. When you spec any component, ask: which of these patterns does each child match?

| # | Pattern | Classification | Illustration (one of many) |
|---|---|---|---|
| P1 | Child is a fixed, named anatomical part of the parent. Remove it and the parent loses a defined slot in its own anatomy, not just a feature. | **constitutive** | A form control's "helper text" row. Without the row, the control's anatomy no longer matches its own definition. |
| P2 | Child's name is only meaningful *inside* the parent (e.g., `ParentName` + role suffix like `Item`, `Row`, `Cell`, `Step`, `Tab`, `Segment`, `Panel`). The child was designed to live inside this parent and nowhere else. | **constitutive** | A list-like parent whose repeated child is named after the parent's role taxonomy (e.g., the repeated "item" of a list-navigation component). |
| P3 | Child is a component the parent reuses from elsewhere to fulfill one of its own anatomical roles — the parent *is not a button / icon / input*, but it contains one because its definition requires that role to be filled. The child has its own spec. | **referenced** | A composite control that hosts a standard button in its action row. The composite is not a kind of button; it uses one. |
| P4 | Child is a generic system primitive (icon, divider, loading indicator, avatar, badge) that multiple unrelated components embed. Its identity is orthogonal to the parent. | **referenced** | Any component that embeds an instance from the design system's icon set. The icon has its own spec; the parent documents only *which* glyph and *when*. |
| P5 | Child is a peer component the parent composes alongside its own behavior — the parent could exist without it, but in this usage chooses to embed it. | **referenced** | A layout/form-style component that embeds N independent controls. The parent documents the layout contract; each embedded control keeps its own spec. |
| P6 | Child is a pure vector, text layer, or auto-layout frame with no component identity at all. | **decorative** | A decorative glyph drawn directly on the canvas (not an instance of anything). Document its fill/size inline. |
| P7 | Child is an instance-swap target (`INSTANCE_SWAP` property) whose concrete fill is consumer-provided. The parent defines the **slot contract** but does not own any particular instance. | **referenced** (document the contract) | Any component with a swap-able icon/avatar/thumbnail slot. Parent documents "accepts: Icon (size=M)"; each concrete consumer passes a different instance. |
| P8 | Child is a component whose name could stand alone in the design system catalog without referencing the parent. If you found this child on its own page, would you still know what it is? Yes → it is a referenced component, not a constitutive one, even if the current parent depends on it. | **referenced** | Any widely-reused primitive — buttons, inputs, menus, tooltips, popovers — embedded into a more specific composition. |

#### How to apply the patterns

Most children match exactly one pattern, and the classification follows. For the stubborn cases:

- **P1 vs. P2.** Both are constitutive. Distinction matters only for rendering: P1 children that are **not** themselves component sets fold into sub-component tables; P2 children (which usually are component sets, repeated) emit a recursion entry.
- **P2 vs. P8.** This is the sharpest judgment call. Run the standalone-catalog test: "If I saw this child's main component in the design system catalog with no parent context, would its name and purpose be self-explanatory?" If yes → P8 (referenced). If it would be confusing without parent context → P2 (constitutive). *Name suffixes like `Item`, `Row`, `Cell`, `Step`, `Tab`, `Segment` are strong P2 signals; names that match common design-system primitives are strong P8 signals.*
- **P3 vs. P5.** Both are referenced. The difference is intent: P3 says "the parent's anatomy requires this role to be filled by some reusable component" (the role is inherent to the parent); P5 says "the parent embeds arbitrary other components as its content" (like a modal hosting whatever the consumer passes). When unclear, default to P5 — it makes the fewest ownership claims.
- **P4 vs. P6.** If the child is an `INSTANCE` whose main component has a name, it's P4 (referenced). If it is a raw vector / frame / text node with no main-component reference, it's P6 (decorative).

#### Anti-patterns — failure modes to catch yourself doing

**A1. Waiting for the color-extraction "container hint."** The `_containerRerunHint` emitted by `extract-color` is a *symptom* (all visible colors live on sub-components), not a *cause*. By the time it fires you have already processed API, Structure, and Color under the wrong mental model and are one step away from writing property tables that belong to children. Classify at Step 3.5 (and confirm at Step 4.5) so every downstream interpretation runs with the right frame.

**A2. Type-based shortcutting.** Do not classify on the `COMPONENT_SET` / `COMPONENT` type alone. Many referenced primitives (P3, P4, P8) are also component sets, and many constitutive parts (P1) are not. The type is evidence, not the decision — always run Q1 and Q2.

**A3. Copying child properties into the parent API.** When a child is referenced (P3/P4/P5/P7/P8), do not lift its property table into the parent's `## API` section. The parent documents the **configuration it passes** to the child, not the child's full surface. The child's own spec is the source of truth for its properties.

**A4. Over-recursing.** Just because a child is itself a `COMPONENT_SET` does not mean the parent's spec is incomplete without a dedicated child-spec run. If the child is referenced, the child already has (or will have) its own spec — one independent of this parent. Only constitutive children (P1/P2) drive the recursion manifest.

**A5. Under-recursing on name coincidence.** Do not skip recursion on a constitutive child just because its name matches a generic word. A child named `Row` nested inside a specific tabular parent, with no existence outside it, is still P2 — even though "Row" sounds generic. Run the standalone-catalog test (see "P2 vs. P8"), not the word-recognition test.

#### What each classification produces

- **Constitutive** → If the child is not itself a `COMPONENT_SET`, fold it into the parent's sub-component tables (current Step 5 behavior). If the child **is** a `COMPONENT_SET`, emit a "recurse" entry in Step 10.5's manifest. Either way, its properties are part of the parent's API section.
- **Referenced** → Emit a `Referenced components` subsection in the parent `.md`'s API body. One entry per referenced component containing: name, variant/props passed, Figma node ID of the referenced component set, and a link to its spec (expected path: `./{referenced-slug}.md`). **Do not include its property table.** The referenced component's `.md` is its own source of truth.
- **Decorative** → No entry. Shows up naturally in Structure dimensions and Color tokens.

#### What to do with ambiguity

If Q1 and Q2 produce contradictory answers, default to **referenced** and log the reasoning in `_base.json._childComposition.ambiguousChildren[]` (Step 4.5 persists this back to disk). Over-referencing is safer than over-spec'ing — a referenced child's spec can be promoted to constitutive later; a child whose property table has been mistakenly copied into the parent is much harder to disentangle.

### Step 4: Stage `_base.json` into the cache

1. Copy the file at `baseJsonPath` to `{cachePath}/{componentSlug}-_base.json`. If the source is already inside `{cachePath}` (user already moved it), skip the copy.
2. If the caller passed an `optionalContext` and `_meta.optionalContext` is null, update the copied file's `_meta.optionalContext` in place. This is the **only** mutation the orchestrator may do to `_base.json` at this step; the Step 4.5 `_childComposition` rewrite is the other.
3. Emit a one-line summary:

   ```
   base: variants=<V>, bytes=<B>, warnings=<W> → {cachePath}/{componentSlug}-_base.json
   ```

   `V` and `B` come from `variants.length` and the serialized byte count; `W` from `_extractionNotes.warnings.length`.
4. **Flush phase context.** Keep only: `_base.json` path + the one-line summary.
5. Verify the file has `_meta`, `component`, `variantAxes`, `propertyDefinitions`, `variants[]`, `ownershipHints[]`, `_childComposition`. `crossVariant` is allowed to be `null` (single-variant-axis component). If any required top-level key is missing, abort with a diagnostic asking the user to re-run the uSpec Extract plugin.
6. If `_extractionNotes.warnings.length > 0`, surface the warnings to the user before continuing. Common codes include `HIERWALK_MISSING_CHILDREN` and walk-validation warnings like "Walked tree is missing children for constitutive instance(s)" — both mean the `.md` may be incomplete and the user should re-extract with a corrected selection.
7. **Render-meta freshness check.** Spot-check `_base.json.variants[<defaultVariantName>].layoutTree`: if it is an object whose root entry has no `id` field (or `id === undefined`), the file was produced by a pre-render-meta plugin build. Render-meta will still be emitted at Step 9, but every `sectionTargets[*].nodeId` and `groupTargets[*][*].nodeId` will be `null` and the Step 9.5 integrity gate will fail those rules. Surface a warning right here so the user has time to re-extract before the run completes: "render-meta: `_base.json` was produced by a pre-render-meta plugin build (no `id` on `layoutTree` nodes); re-extract with the current uSpec Extract plugin to populate `sectionTargets[*].nodeId` and `groupTargets[*][*].nodeId`. The `.md` will still render, but the downstream `create-*` skills consume this render-meta to resolve sections/groups/markers — without the node ids they must name-walk the live layer tree (a degraded, ambiguous path), so re-extract to get reliable ids." Note: the `create-*` skills no longer have a full-extraction fallback — they require the `.md` and fail fast (plus a bounded per-skill whitelist of minimal reads), so missing render-meta ids degrade their identity resolution rather than triggering a re-extraction.

### Step 4.5: Post-extract review (composition classification)

Read `_base.json._childComposition`. The plugin UI asks the designer to resolve every top-level **INSTANCE** child at extraction time, so every entry in `children[]` with `nodeType === "INSTANCE"` carries `classificationEvidence: ["user-selected"]` and `ambiguousChildren[]` is empty by construction. Non-INSTANCE entries (layout-wrapper FRAMEs with evidence `["layout-wrapper"]`, raw vectors/text with evidence `["not-instance"]`) are auto-classified as `decorative` by the plugin and are not surfaced for designer confirmation.

Treat the designer's choice as authoritative and **skip the override pass entirely**. Briefly cross-check by running Q1 from Step 3.5 on each INSTANCE entry — if any obviously wrong assignment jumps out (e.g., a generic Icon instance marked `constitutive`), raise it as a warning to the user in the Step 10 summary but leave the `_childComposition` untouched. The designer's choice wins; the orchestrator's job is to surface, not override.

If `_childComposition.children[]` contains any entry where `nodeType === "INSTANCE"` and `classificationEvidence` does not include `"user-selected"`, or if `ambiguousChildren[]` is non-empty, the file is malformed (the plugin UI did not reach the designer's confirmation step). Flag it as a warning and apply the Step 3.5 reasoning model to resolve each affected INSTANCE entry before continuing. Non-INSTANCE entries (wrappers / vectors / text) keep their plugin-assigned `decorative` classification and are skipped by the override pass:

1. For each child in `children[]`, read `classification` and run Q1/Q2 from Step 3.5. If the classification is wrong, override it and append `"agent-override"` to `classificationEvidence[]`.
2. Walk `ambiguousChildren[]`. For each entry, make a decision via Q1/Q2 and the P1–P8 patterns, then **move** the entry into `children[]` with the resolved `classification`. On uncertainty, default to `referenced` and note the reasoning in `classificationReason`.
3. Persist the revised `_childComposition` back to `{cachePath}/{componentSlug}-_base.json`. This is the one explicit exception to the "`_base.json` is immutable" invariant.

Flush the review's working context. Keep only: the final classification counts (`constitutive=N, referenced=M, decorative=K, ambiguous=0`) as a one-line summary. Downstream interpretation skills will read the updated `_childComposition` from disk.

If `constitutive > 0` **and** every constitutive child has a non-null `subCompSetId`, this is a composition-heavy parent — Step 10.5's manifest will emit follow-up runs. If `referenced > 0`, the rendered `.md` will include a `### Referenced components` subsection (see Change 4 / `{{ref:component-md/agent-component-md-instruction.md}}`).

### Step 5: Run extract-api (inline, in the parent)

Follow `.cursor/skills/extract-api/SKILL.md` inline (not as a subagent — the parent needs the resulting dictionary directly). Pass `cachePath`, `componentSlug`, `optionalContext`, `deltaAvailable` (true only when the user provided a `figmaLink`), and — when `deltaAvailable` is true — `fileKey`, `nodeId`, and `mcpProvider` (from `uspecs.config.json`). When the input is plugin-only (no `figmaLink`), explicitly instruct the skill that delta calls are **not available** — any gap surfaces as `_deltaExtractions[]` with `unavailable: "no-figma-link"`.

The skill:

- Reads `_base.json` from disk (no fresh Figma walk).
- Optionally issues a tiny Step 3-delta call when `deltaAvailable` is true and a fact is genuinely missing.
- Writes `{cachePath}/{componentSlug}-api.json`.
- **Writes `{cachePath}/{componentSlug}-api-dictionary.json`** (Step 7.5 of `extract-api`) — the canonical vocabulary projected from api.json.
- Returns a single-line summary ending with `(+ dictionary at <path>)`.

**Flush phase context.** Keep only: the two paths + one-line summary. Verify both files exist and have `_meta` + `data`. Abort with a diagnostic if the dictionary file is missing — the parallel fan-out requires it.

Set `apiDictionaryPath = {cachePath}/{componentSlug}-api-dictionary.json` for use in Step 6.

### Step 6: Parallel fan-out (extract-structure + extract-color + extract-voice)

With the API dictionary on disk, dispatch **three subagents in a single batch** using the `Task` tool, `subagent_type="generalPurpose"`. Each subagent runs exactly one extract skill, holds its own `_base.json` + dictionary context, and returns a single-line summary to the parent. The parent keeps only those three strings, never three full contexts.

**Mandatory invocation shape** (all three tool calls go in one assistant message — do NOT dispatch sequentially):

For each specialist in `[extract-structure, extract-color, extract-voice]`:

- `description` — 3–5 words (e.g., `"Structure pass for Button"`).
- `subagent_type` — `"generalPurpose"`.
- `run_in_background` — `false`. The parent must wait for all three returns before proceeding.
- `prompt` — a fully self-contained instruction with **these seven fields** (no prior chat context is available to the subagent):
  1. The absolute path to the extract skill's SKILL.md file (e.g., `.cursor/skills/extract-structure/SKILL.md`). Instruct the subagent to read it and follow every step.
  2. `componentSlug` (string).
  3. `cachePath` (string).
  4. `apiDictionaryPath` — the path produced by Step 5.
  5. `optionalContext` — the orchestrator's `optionalContext` string (or `"none"` when absent).
  6. `mcpProvider` — the value from `uspecs.config.json`.
  7. `deltaAvailable` — boolean. `false` when the user provided only a `baseJsonPath`.
  - Instruct the subagent to return a single-line summary matching the skill's declared return format, followed by the written cache path. Nothing else — not the full payload, not checklists, not screenshots.

**Expected return shapes (the parent parses these):**

- Structure: `Structure extracted: N sections, M sub-components, K slot contents → <path>`
- Color: `Color extracted: strategy=<A|B>, N sections, M unique tokens, modes=[...] → <path>`
- Voice: `Voice extracted: N focus stops, M states, platforms=[VoiceOver, TalkBack, ARIA] → <path>`

**Join before proceeding.** Wait for all three subagent returns. Do NOT advance to Step 8.5 until every return has landed. If any subagent fails (returns an error or no path), abort with a diagnostic naming the failing specialist — partial completion is never silently tolerated.

**Container hint collection.** After all three subagents return, open `{cachePath}/{componentSlug}-color.json` with a targeted read and check `data._containerRerunHint`. If non-null, surface it to the user using the neutral, authoritative framing below. **Do not** describe the parent's color spec as informational, provisional, placeholder, or pending — the parent's tokens are flattened from `colorWalk[]` and the parent .md is shippable as-is.

> Constitutive sub-components detected (`<subCompSetNames joined>`). The parent .md fully documents the parent's own color tokens (authoritative, measured from `colorWalk[]`). The recursion manifest in Step 10.5 lists each constitutive child as an **optional follow-up** if you also want a per-child canonical color spec; running them is not required for the parent .md to be complete.

Collect the hint for inclusion in the Step 10 Follow-ups (not Known gaps). Continue past this — do not abort.

**Flush phase context.** Keep only: the three cache-file paths + three one-line summaries. The subagents already discarded their internal context when they returned; the parent was never exposed to it.

### Step 8.5: Reconciliation (typed disagreement handling)

After the three specialist caches land and before the Step 9.5 integrity gate, run a deterministic reconciliation pass. This step is **typed dispatch, not reasoning** — it recognizes exactly 4 classes of disagreement (3 actionable + 1 benign) between the dictionary and the specialist outputs, and dispatches the right response for each.

**Load the dictionary + the three mismatch lists.**

1. Read `{cachePath}/{componentSlug}-api-dictionary.json` (`ApiDictionary`).
2. Read the `data._extractionArtifacts.dictionaryMismatches[]` arrays from the three specialist caches (`structure.json`, `color.json`, `voice.json`). Missing or empty arrays are fine — they indicate the specialist agreed with the dictionary.
3. Read `reconciliation.autoRetry` from `uspecs.config.json` (default: `true` when the key is absent). When `false`, skip retries and surface coverage gaps directly as `high` Known-gaps entries. Still run auto-rewrites for vocabulary drift.

**Classify every mismatch** into one of four buckets:

| Class | Detection rule | Response |
|---|---|---|
| **Vocabulary drift** | Dictionary has `{axis.name, value.figmaValue, value.runtimeCondition}`; specialist emitted a column / label that matches `figmaValue` but the dictionary canonical is `runtimeCondition` (or vice versa — a simple rename). Or the specialist's sub-component name matches a dictionary `subComponents[].name` modulo casing / whitespace / suffix. | **Auto-rewrite in place.** Open the specialist's cache file, rewrite the drifted labels/columns/section names to the dictionary canonical, add a reconciliation log entry. No retry. |
| **Coverage gap (scope miss)** | Dictionary lists a value/sub-component/state that has no corresponding row/column/section in the specialist's cache AND the value exists somewhere in `_base.json` as real evidence (variant option, revealed sub-component, etc.). | **Re-dispatch the specialist as a single subagent** with `optionalContext = "create-component-md retry: <comma-list of missing items>"` prepended to the original `optionalContext`. Max 1 retry per specialist per invocation. After the retry returns, re-run this entire Step 8.5 from the top — a successful retry may resolve gaps that would otherwise trigger further retries. |
| **Benign value-extra** | A `kind: "value-extra"` mismatch (specialist documented something the dictionary's `axes[]` does not list) where the observed item **resolves to a real part of the API surface**: it matches a `dictionary.booleanProps[]` entry by camelCase name (a top-level boolean like `isDisabled`/`isLoading`), or a `dictionary.states[]` entry by `figmaValue` / `apiAssignments` key, or a `_base.json` variant axis option. The specialist was correct to document it; the only "gap" is that booleans/decomposed states are intentionally absent from `axes[]`. | **Log to `reviewedBenign[]` and move on.** No rewrite, no retry, NO Known-gaps entry. This is the disposition for the systemic false-positive that the specialist-side `value-extra` guard (extract-* Step 2.5) now prevents at the source; this bucket catches any that still arrive from older or standalone caches. |
| **Semantic conflict** | Dictionary and specialist disagree on a fact that cannot be auto-rewritten AND the observed item resolves to nothing in the dictionary or `_base.json` (e.g., API declares `size: small \| medium \| large`; Structure measured `compact` + `regular` which have no `figmaValue` in the dictionary and no backing axis option). | **Surface as `high` Known gap immediately.** NEVER auto-resolve. NEVER retry (a retry will not fix a semantic disagreement — it needs human judgment). |

A `value-extra` that does NOT resolve to `booleanProps[]` / `states[]` / a variant axis is a **semantic conflict**, not benign — it goes to `high`. Benign classification requires the observed item to trace to a real API-surface element; never use `reviewedBenign[]` as a dumping ground for unexplained extras.

**Retry loop (bounded, serial, no declared specialist priority):**

Maintain a retry counter per specialist (`structureRetries`, `colorRetries`, `voiceRetries`), each starting at 0 and capped at 1.

```
while (any specialist has unresolved coverage gaps AND its retryCount < 1 AND reconciliation.autoRetry is true):
  pick the next specialist with unresolved coverage gaps, in the original run order
    (Structure → Color → Voice); this is iteration order, NOT a priority relationship
  re-dispatch ONE specialist as a generalPurpose subagent (serial — never parallel)
    prompt includes the same invocation contract as Step 6, plus
      optionalContext = "create-component-md retry: <missing items>\n\n<original optionalContext>"
  wait for its return
  increment that specialist's retryCount
  re-read the specialist's cache (it overwrote its own file)
  re-run Step 8.5 classification on all three specialists
    — a successful retry may resolve gaps in other specialists too,
      shortening the retry chain
  loop back
```

**After the loop terminates** (no specialist has unresolved coverage gaps OR every specialist with one has already retried once):

- Any remaining coverage gap becomes a `high` Known-gaps entry (the auto-retry couldn't recover it).
- Every semantic conflict is already a `high` Known-gaps entry.
- Every vocabulary drift has already been auto-rewritten in place; it becomes a `low`-severity informational entry in the auto-reconciled list (see renderer).
- Every benign value-extra is already logged to `reviewedBenign[]`; it produces NO Known-gaps entry and NO confidence downgrade — it is an audit-trail note only.

**Write the reconciliation artifact.** Every pass (auto-rewrite, retry, unresolved) is logged to `{cachePath}/{componentSlug}-reconciliations.json`. Envelope:

```json
{
  "_meta": {
    "schemaVersion": "1",
    "generatedAt": "<ISO 8601 timestamp>",
    "componentSlug": "<slug>",
    "autoRetryEnabled": <value read from uspecs.config.json reconciliation.autoRetry; default true>
  },
  "data": {
    "autoReconciled": [
      {
        "class": "vocabulary-drift",
        "specialist": "structure" | "color" | "voice",
        "before": "<drifted label>",
        "after": "<dictionary canonical>",
        "location": "<cache path + JSONPath>",
        "source": "<dictionary axis / sub-component / state reference>"
      }
    ],
    "retries": [
      {
        "specialist": "structure" | "color" | "voice",
        "missingItems": ["..."],
        "attemptedAt": "<ISO 8601>",
        "outcome": "resolved" | "still-missing",
        "retryCount": 1
      }
    ],
    "unresolved": [
      {
        "class": "coverage-gap" | "semantic-conflict",
        "specialist": "structure" | "color" | "voice",
        "detail": "<scannable summary ≤160 chars>",
        "severity": "high"
      }
    ],
    "reviewedBenign": [
      {
        "class": "value-extra",
        "specialist": "structure" | "color" | "voice",
        "observed": "<the value-extra the specialist emitted>",
        "resolvesTo": "booleanProps:isDisabled" | "states:loading" | "variantAxis:<name>=<option>",
        "note": "<why it is part of the API surface and not a gap; ≤160 chars>"
      }
    ]
  }
}
```

`reviewedBenign[]` is the formal home for the **Benign value-extra** class — it is an audit trail only, never a defect list. The renderer does NOT surface it in Known gaps. When there is nothing benign to log, emit `reviewedBenign: []`. Do **not** invent ad-hoc keys (e.g. a leading-underscore `_reviewedBenign`) — `reviewedBenign[]` is the schema-defined bucket and Step 9.5 validates its shape.

When `data.retries[].outcome === "resolved"`, the corresponding mismatch disappears from the specialist's cache on the next classification pass — that is how the re-run loop confirms success. When `"still-missing"`, the item is promoted into `unresolved[]` with severity `high`.

**Abort conditions:**

- A subagent returns a failure line → abort the whole orchestrator with a diagnostic naming the failing specialist and retry number. Do NOT continue past Step 8.5 with a half-broken cache set.
- A specialist's retry overwrites its cache file with a malformed envelope (missing `_meta`/`data`) → abort with the same diagnostic.

**Flush the reconciliation working context.** Keep only: the path `{cachePath}/{componentSlug}-reconciliations.json` + counts `(auto-rewrites=<A>, retries=<R>, unresolved=<U>, benign=<B>)`.

**Render-meta handoff note.** Step 9's render-meta builder consumes `data.autoReconciled[]` to do drift-aware name lookups when resolving `sectionTargets[*].nodeId` / `groupTargets[*][*].nodeId` against `_base.json.variants[<default>].layoutTree`. When a section/group label was rewritten here (e.g., `"Clear button"` → `"clear (X) button"`), render-meta retries the layer lookup with both `entry.before` and `entry.after` so a successful auto-rewrite never silently breaks ID resolution. This is a downstream-consumer relationship only — render-meta does NOT itself trigger further reconciliation, and Step 8.5 owns every rewrite that lives in the cache files.

### Step 9: Render the Markdown

Now the parent's context should contain:

- 4 domain cache-file paths (api, structure, color, voice). `_base.json` remains on disk at `{cachePath}/{componentSlug}-_base.json` for **targeted reads only** during rendering — see the expanded narrow-read list below. Everything else renderable was already lifted into the four domain JSONs.
- `fileKey`, `nodeId`, `componentSlug`, `outputPath`, `cachePath`, `optionalContext`.
- Any container-rerun hints or delta-extraction records collected during Steps 5–8.
- Not much else.

Read:

1. All four **domain** JSON cache files in full.
2. `_base.json` — only the narrow fields the renderer consumes (do not load wholesale):
   - `_meta` (full) — for render-meta `extractedAt`, `fileKey`, `nodeId`, `sourceHash` recomputation
   - `_childComposition` — for Composition subsection + Referenced components + Known gaps + render-meta `subComponents[]`
   - `_extractionNotes.warnings` — for Known gaps
   - `component` (full) — for cross-check + render-meta `component`
   - `variantAxes` + `defaultVariant` — for variant-axes summary + render-meta `variantAxes` / `variantAxesDefaults`
   - `propertyDefinitions` (full) — for render-meta `propertyDefs` + `booleanDefs` + `slotContents`
   - `slotHostGeometry` — for render-meta `slotContents.preferredComponents` enrichment
   - `variants[<defaultVariantName>].layoutTree` (full walk) — for render-meta `sectionTargets[*].nodeId` + `groupTargets[*][*].nodeId` resolution
   - `variants[<defaultVariantName>].treeHierarchical` (full walk) — for the `{{ANATOMY_SCAFFOLD}}` layer-composition tree (mechanical pass-through; see `agent-component-md-instruction.md` > ## Anatomy scaffold)
   - `variants[*]._selfCheck.missingChildren` — for Known gaps
   - `subComponentVariantWalks` (when present) — for render-meta `subComponents[*].subCompVariantAxesDefaults`
   - This widening from the prior `_childComposition` + `_extractionNotes.warnings` + `component.componentName` + `_selfCheck` set is intentional: render-meta needs the canonical pre-interpretation shape and the four domain caches do not preserve it. Read these fields once, then re-flush.
3. `{cachePath}/{componentSlug}-reconciliations.json` — for render-meta name-lookup retries (read `data.autoReconciled[]` for `before`/`after` pairs). When the file is absent, treat as empty (no rewrites).
4. `{{ref:component-md/component-md-template.md}}`.
5. `{{ref:component-md/agent-component-md-instruction.md}}`.

Follow `agent-component-md-instruction.md` section by section to produce:

- `{{COMPONENT_NAME}}`, `{{FIGMA_URL}}`, `{{GENERATED_AT}}`, `{{OPTIONAL_CONTEXT}}`, `{{NODE_ID}}`, `{{FILE_KEY}}`, `{{CACHE_PATH}}` (mechanical).
- `{{OVERVIEW_PARAGRAPH}}` (synthesis — 2–4 sentences drawn from all four `data` objects).
- `{{VARIANT_AXES_SUMMARY}}` (one-liner from the structure axes).
- `{{COMPOSITION_SUBSECTION}}` (rendered from `_base.json` top-level `_childComposition`).
- `{{ANATOMY_SCAFFOLD}}` (mechanical pass-through of `variants[<default>].treeHierarchical` into a `### Anatomy` tree at the top of the Structure section — see `agent-component-md-instruction.md` > ## Anatomy scaffold).
- `{{API_BODY}}`, `{{STRUCTURE_BODY}}`, `{{COLOR_BODY}}`, `{{VOICE_BODY}}` (per-section renderers). API body's Referenced components subsection consumes `_base.json._childComposition.children[]` filtered to `classification === "referenced"`. The Voice body ends with the hidden `<!-- voice-render-meta v=1 ... -->` focus-stop layer-name carry (see `agent-component-md-instruction.md` > Voice body rendering step 5) so `create-voice` can resolve focus markers from the `.md` alone.
- `{{CROSS_SECTION_INVARIANTS}}` and `{{CROSS_REFERENCES}}` (computed from the `_extractionArtifacts` blocks).
- **`{{RENDER_META_JSON}}`** — built per `agent-component-md-instruction.md > ## RENDER_META_JSON`. Source: `_base.json` (the narrow fields above) + structure cache `data.sections[]` — modern caches stamp `section._anchor` and group-header `row._layerName` / `row._layerId` directly, which the resolver consumes as mechanical pass-through (preferred path). Reconciliations cache is consulted only by the legacy name-walk fallback for caches produced before identity stamping. Render-meta is mechanical pass-through — never read it from `api.json` even where the fields overlap.

Write the result to `outputPath` using UTF-8.

**Do not skip the audit checklist** at the end of `agent-component-m

…(truncated)
