# OpenLife v1.15 — Changelog

**Branch:** `main`
**Predecessor:** v1.14.1 (tagged 2026-05-21)
**Released:** 2026-05-21

v1.15 is the **Design.md Hub** milestone. It lifts the existing
DesignMd subsystem (a single `DesignMdImporter` + skeleton vendor of
70 brand names) into a real hub:

1. 5-brand catalog bundled with rich artifacts (DESIGN.md + tokens.json
   + preview.html + render-contract + style-fingerprint + quality-score).
2. URL-to-DESIGN extractor skill bundled — Alan Nicolas'
   [design-md](https://github.com/oalanicolas) Claude Code skill
   integrated as `.catalog/skills/design-md-extractor/` with a
   TypeScript wrapper handling provider routing + URL safety + cache +
   catalog publish.
3. Search / diff / drift operations on the registry — token-level
   queries become first-class.

This is **Track A** of the v1.15→v1.18 SaaS Forge arc — design
infrastructure first because it has the smallest scope and the
broadest immediate utility.

## What landed by subsystem

### Phase A1 — Bundled brand catalog

| Component | Surface |
|---|---|
| `vendor/awesome-design-md-core/design-md/` | 5 bundled brands (apple, claude, stripe, vercel, linear.app) shipping in the npm tarball with full artifacts. Total impact: +300KB. |
| `DesignMdImporter.pullCatalog(catalog, opts)` | New method. `core` → bundled; `full` → `OPENLIFE_DESIGN_CATALOG_PATH` env or `opts.source`; `<path>` → arbitrary local dir. Returns `DesignProfileMeta[]`. |
| `DesignMdImporter` rich-artifacts mode | When importing from a richer source (or via `pullCatalog`), `tokens.json`, `preview.html`, `quality-score.json`, `render-contract.json`, `style-fingerprint.json` propagate alongside DESIGN.md into `design/profiles/<id>/`. |
| `DesignProfileMeta` schema extension | New optional fields: `archetype`, `qualityScore`, `artifacts[]`. Surfaced from `style-fingerprint.json` + `quality-score.json` at import time so the registry can filter without re-reading every artifact. |

### Phase A2 — URL extraction skill

| Component | Surface |
|---|---|
| `.catalog/skills/design-md-extractor/` | Bundled the design-md Claude Code Skill (Alan Nicolas / @oalanicolas, MIT). 25 files / 544KB after pruning test/macOS noise. `run.cjs` (1100 lines), 17 lib modules, 2 providers (claude-cli, openrouter), data templates. |
| `src/design/DesignMdExtractor.ts` | TS wrapper that adds: URL safety (SSRF guards), provider routing via ModelTierRouter, 24h result cache keyed by URL hash, optional `--catalog` publish into design/profiles/. |
| `openlife design extract <url>` | New CLI. Options: `--out`, `--catalog`, `--provider`, `--model`, `--compare`, `--no-cache`, `--no-content-gate`, `--timeout-ms`. |
| New deps | `cheerio@^1.2`, `turndown@^7.2`, `yaml@^2.8` (the skill's required runtime). axios + js-yaml were already present. |

### Phase A3 — Search / diff / drift

| Component | Surface |
|---|---|
| `DesignMdRegistry.search(query, filters)` | Full-text across id/title/archetype/source meta + fallback DESIGN.md body. Scoring: id/title=3, meta=2, body=1. Filters: source / archetype / qualityScore. |
| `DesignMdRegistry.findByArchetype(name)` | Direct filter helper. |
| `DesignMdRegistry.findByTokenColor(hex, deltaE)` | RGB-distance filter over tokens.json + DESIGN.md frontmatter colors. Useful for "designs with blue". |
| `DesignMdRegistry.diff(a, b)` | Token-level diff. Returns `{ colors[], typography[], archetypeChanged, qualityScoreChanged, metaDelta }`. Reads tokens.json first, falls back to DESIGN.md frontmatter parsing. |
| `openlife design search <query>` | CLI. Filters: `--archetype`, `--source`, `--quality`. `--json` for machine output. |
| `openlife design diff <a> <b>` | CLI. Human-readable side-by-side or `--json`. |
| `openlife design drift <profile> <url>` | CLI. Re-extracts URL into a fresh slug, then `diff(profile, slug)` and emits verdict: `in-sync ✓` / `minor drift` / `notable drift` / `major drift ⚠`. |

## Security model

- **Extractor URL safety:** `validateExtractUrl` blocks `localhost`,
  127/8, 10/8, 192.168/16, 172.16-31/12, 169.254/16 (link-local + cloud
  metadata), `::1`, fe80::/fc00::/fd00::, `*.local`, `*.internal`.
  Also blocks non-http(s) protocols. This is **looser** than
  `SecurityDownloadGuard.validateUrl` (which whitelists trusted package
  hosts) because read-only HTML extraction has a different threat
  model than package download — no code execution.
- **Provider key handling:** `OPENROUTER_API_KEY` read from env only.
  Never logged, never persisted, never echoed in CLI output.
- **Catalog `pull --catalog=full`** without env: fails-closed with
  an actionable error message (does not silently fetch from default
  source). Remote pull lands in v1.15.1.

## Tests (3 new files, 22 scenarios)

| Test | Scenarios |
|---|---|
| `test_design_catalog_import.ts` | 5 — rich-artifact import, checksum stability, path override, full-without-source error, real bundled core. |
| `test_design_extractor.ts` | 9 — SSRF blocklist (11 vectors), public URL allowlist, slug derivation, provider override, openrouter fallback, no-key path, bundled skill discovery, short-circuit on bad URL, no-provider error. |
| `test_design_registry_search.ts` | 8 — meta-driven ranking, body fallback, filter narrowing, archetype filter, color filter (RGB distance), diff structure, missing-profile graceful, real-catalog end-to-end (apple↔claude delta=167). |

All 22 pass. No regression in 13 adjacent v1.11 / v1.14 tests verified.

## Tarball impact

```
v1.14.1 → v1.15.0
package size:    1.4MB → 1.9MB  (+500KB)
unpacked size:   5.2MB → 7.2MB  (+2.0MB)
total files:     1016  → 1166   (+150)
```

Breakdown of the +500KB:
- 300KB: 5 bundled brands (apple, claude, stripe, vercel, linear.app)
- 200KB: design-md-extractor skill (post-prune of test files)

## Upgrade

```bash
openlife update --global
# or:
npm install -g @openlife/cli@latest

# Verify
openlife --version              # 1.15.0
openlife design --help          # shows pull/list/show/search/diff/drift/extract
openlife design pull            # imports the 5 bundled brands
openlife design search stripe   # ranked match
```

## Out of scope (deferred to v1.15.1+)

- Remote `pull --catalog=full` over HTTP/git (today requires
  `OPENLIFE_DESIGN_CATALOG_PATH` env to a local clone of the full 72)
- CIE76/CIE2000 perceptual color distance (current uses simple RGB
  euclidean — fine for coarse "near this color" but not for accessibility-grade)
- Conversational design intents (`/(descobrir|comparar)/i` regex in
  `tryHandleDesignConversation`) — planned but defer to v1.16 alongside
  scaffold-time auto-suggestions
- Multi-source diff (diff against a non-imported live URL without
  fully publishing as a profile) — currently `drift` re-publishes the
  fresh extract as a transient slug

## File summary

**New (5):**
- `vendor/awesome-design-md-core/design-md/{apple,claude,linear.app,stripe,vercel}/`
- `.catalog/skills/design-md-extractor/` (full skill bundle)
- `src/design/DesignMdExtractor.ts`
- `src/cli/DesignCommand.ts`
- `docs/v1.15-changelog.md`, `docs/design-hub.md`

**Tests (3):**
- `src/test_design_catalog_import.ts`
- `src/test_design_extractor.ts`
- `src/test_design_registry_search.ts`

**Modified (4):**
- `src/design/DesignMdImporter.ts` (rich-artifacts + pullCatalog)
- `src/design/DesignMdRegistry.ts` (search/diff/findBy)
- `src/index.ts` (register DesignCommand)
- `package.json` (5 test scripts, files[] for vendor/, 3 new deps)

## Commits

To be assembled at ship time.

## Roadmap continuation

- **v1.16.0** — Scaffold + 2 templates (next-supabase-clerk-stripe, sveltekit-postgres-lucia)
- **v1.17.0** — Autonomous Story Loop
- **v1.18.0** — Ship + Deploy + SaaS Skills

See [`/home/rafaleao/.claude/plans/fuzzy-cooking-clock.md`](../../../home/rafaleao/.claude/plans/fuzzy-cooking-clock.md) for the full plan.
