component-distill · git:20260923.88b22e6 · 2026-09-23 · sha256 286f04dfb7cb79ee

component-distill git:20260923.88b22e6A

Immutable. This exact content never changes and is served at /api/v1/blob/286f04dfb7cb79ee.

---
name: component-distill
description: Cross-run component backflow. Derive the reusable components that have recurred across iterated pages and render a propose-only promotion proposal for user adjudication. Use when iterated pages may have produced components worth promoting into DESIGN.md, when the same component keeps being reused across runs/scenes, or when the user asks what the pages have taught the design system. Report-only — never writes DESIGN.md or any authority.
---

# component-distill

Distill the recurring components out of iterated pages and propose promoting them into the project `DESIGN.md`. This is the **backflow** leg of the pipeline: `design-baseline` reads the baseline into pages once; this skill reads the pages back toward the baseline. It is **propose-only and user-gated** — it never writes `DESIGN.md` or any authority; promotion is a user decision (governance log) executed only by `design_baseline.py promote` (ADR-0012).

This is a **cross-run** review, not a step of a single Design I/O run. It reads prior runs' `decision-report.md` files and emits a proposal artifact for user adjudication.

## Authority boundary

| This skill owns | Does not own |
| --- | --- |
| Deriving recurring-component candidates across runs | Durable write to `DESIGN.md` (`design_baseline.py promote`, T-070) |
| Rendering the propose-only promotion proposal | The user promotion decision (governance log, user-only) |
| Reporting distance-to-threshold for below-threshold signals | Component/template selection for a run (`ui-picker`) |

## Deep module (SSOT for deterministic work)

Derivation lives in [`../../scripts/component_candidates.py`](../../scripts/component_candidates.py):

```python
candidate_view(reports_by_run) -> dict   # qualifying + below_threshold + gaps
```

`reports_by_run` maps a run id to its `decision-report.md` text. The module parses each report's `components:` block, counts `reuse <path>` / `extend <path>` as one recurrence of that component path (a `new` entry is a gap, never a candidate), and qualifies a candidate when it recurs across **distinct runs ≥ 3 AND distinct scenes ≥ 2**. Every group is returned — qualifying first, then below-threshold signals with their gap list — so distance to qualification is visible, never silent.

## Workflow

1. **Discover runs.** Scan the project's `.scratch/<run>/` dirs for a `decision-report.md`. List dirs without one as skipped (they contribute 0 references). If fewer than 2 runs carry a decision report, report that count and stop — backflow needs history to mean anything.
2. **Derive.** Build `reports_by_run` and call `candidate_view`. Read-only: parse, never mutate.
3. **Render the proposal** (markdown, report header `component-distill/v1`) at the invocation-level output path the caller names (not a single run's `design-baseline/` dir — this is a cross-run artifact). Sections:
   - **Inclusion manifest** — `run | status` (included / skipped + reason).
   - **Qualifying candidates** — one block per candidate: component path, recurrence, distinct runs, distinct scenes, the contributing references (`run / role / action / scene`), and a **decision slot** for the user (`promote | reject | defer`).
   - **Below threshold** — candidates approaching the threshold with their gap list (e.g. `distinct_runs 2 < 3`), so the distance is visible.
   - **Coverage** — `runs_with_components` / `total_references`, so an empty corpus reads as "no qualifying candidates + why", never silence.
4. **Adjudicate (user).** Present the proposal; the user marks each qualifying candidate. Record the decision as a governance event (agent may append `promotion_candidate_opened`; only the user's `promotion_decided` carries `promote`/`reject`/`defer`). This skill stops at the proposal — it does not write `DESIGN.md`.

**Done when:** the proposal artifact exists at the named output path, qualifying candidates carry full provenance and a decision slot, below-threshold signals carry their gaps, and nothing under the project `DESIGN.md` was touched.

## Scope fence

| In | Out → |
| --- | --- |
| Cross-run component recurrence → proposal | Durable `DESIGN.md` merge → `design_baseline.py promote` (T-070) |
| Report-only candidate derivation | Recording the user decision → promotion governance log |
| Token candidates (counted) | Token promotion into baseline → T-072 |