From 6420e4ad49391b6e59e5b877ba0c3ae7e4d73e20 Mon Sep 17 00:00:00 2001 From: bsevern Date: Thu, 28 May 2026 10:11:48 -0400 Subject: [PATCH 01/17] docs(specs): GitHub badge rendering capability New design doc for hand-drawn Badge component, plus three MCP tools (render-badge, render-github-badge, render-github-badge-row) backed by an injectable GitHub client with TTL caching and optional GITHUB_TOKEN auth. Co-Authored-By: Claude Opus 4.7 --- ...026-05-28-github-badge-rendering-design.md | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md diff --git a/docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md b/docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md new file mode 100644 index 0000000..6bc4386 --- /dev/null +++ b/docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md @@ -0,0 +1,265 @@ +# Design: GitHub badge rendering + +**Date:** 2026-05-28 +**Status:** Approved (design) +**Scope:** `src/components/Badge.tsx` (new component, brand-aware, no network) + +`mcp/src/githubClient.ts` + three new MCP tools (`render-badge`, +`render-github-badge`, `render-github-badge-row`). + +## Context + +Today GoldenChart renders charts and diagrams in the hand-drawn Rough.js +aesthetic, but has no primitive for the small label/value "shields" that +dominate the top of every GitHub README. Adding one gives the MCP agent a +useful new render surface and a natural showcase for the brand/vibe layer +(text + ink + palette, no axes, no scales). + +The capability has three independent pieces: + +1. **`Badge`** — a brand-aware library component that takes literal + `label` / `value` strings. No network, no GitHub knowledge. +2. **`render-badge`** — an MCP tool that calls (1). No network. +3. **`render-github-badge`** / **`render-github-badge-row`** — MCP tools + that fetch live repo data and feed (1). + +Splitting the surface this way keeps the library no-network (preserving the +existing browser-entry / `goldenchart/server` separation) and isolates all +HTTP behind the MCP layer where injectability and caching are easy. + +The look-and-feel choice is the GoldenChart-native variant ("B" from +brainstorming): sketchy outline pill, brand ink and palette, brand font, +optional hand-drawn icon glyph. **Not** a shields.io clone. A shields-faithful +variant can be added later as a `variant: 'shields'` prop if anyone asks. + +## What gets built + +### Library — `Badge` component + +**Files:** `src/components/Badge.tsx`, `src/components/Badge.test.ts`, +`src/core/badgeIcons.ts` (small stroke-path table). Exports added to +`src/components/index.ts` and re-exported from `src/index.ts`. + +**Props:** + +```ts +export type BadgeTone = 'neutral' | 'info' | 'success' | 'warn' | 'danger'; +export type BadgeIcon = + | 'star' | 'fork' | 'issue' | 'tag' | 'commit' + | 'license' | 'lang' | 'check'; + +export interface BadgeProps { + label: string; + value: string; + tone?: BadgeTone; // default 'neutral' + icon?: BadgeIcon; // optional + vibe?: VibeConfig | VibePresetName; + brand?: BrandConfig; + seed?: number; + className?: string; +} +``` + +**Layout:** intrinsic SVG. Width = `iconWidth + labelWidth + valueWidth + +4*padding + 1*divider`, height ≈ 26px (constant). Text width is measured +through the same helper the rest of the library uses for axis tick labels +(`src/core/text.ts`); no DOM measurement, no font fetch. The component does +not require a parent `` and does not paint a background page color. + +**Rendering:** one Rough.js outline pill (rounded rect) using +`vibe.roughness`. Left half painted with `brand.ink` at ~12% opacity for the +label region; right half painted with the tone color for the value region; a +single Rough stroke divides the halves. Text uses `brand.font`. Optional icon +sits inside the left half, drawn from `badgeIcons.ts` (a `Record` of SVG path `d` strings) rendered as a Rough path so the stroke +matches the rest of the badge. + +**Tone → color** mapping is derived from the resolved brand inside the +component body (per the brand-aware-without-context pattern already +documented in `CLAUDE.md`): + +| tone | source | +| ----------- | ------------------------------- | +| `neutral` | `brand.palette[0]` | +| `info` | `brand.palette[1] ?? palette[0]`| +| `success` | a fixed sketchy green | +| `warn` | a fixed sketchy amber | +| `danger` | a fixed sketchy red | + +The fixed tone colors live next to `badgeIcons.ts` and are intentionally not +derived from the brand — "success means green" is a stronger convention than +brand fidelity for status pills. (If a brand wants to override this, the +caller passes `tone: 'neutral'` and gets the brand color directly.) + +**Brand/vibe wiring:** reads `useBrand()` if a `BrandProvider` is present, +otherwise calls `resolveBrand(undefined)`. Mirrors the existing chart +pattern called out in `CLAUDE.md` for charts that need brand colors without +relying on `Surface`'s providers. + +**Bundle:** one component + one small icon table + one tone table. No new +runtime deps. No font byte imports. `npm run check:bundle` is the gate; the +75 KB gzip ceiling must continue to hold. + +### MCP — `githubClient.ts` + +**Files:** `mcp/src/githubClient.ts`, `mcp/src/githubClient.test.ts`. + +```ts +export interface GithubClient { + getRepo(owner: string, repo: string): Promise; + getLatestRelease(owner: string, repo: string): Promise; + getWorkflowStatus( + owner: string, repo: string, workflow?: string, + ): Promise; + getContributorsCount(owner: string, repo: string): Promise; +} + +export interface CreateGithubClientOptions { + fetch?: typeof globalThis.fetch; // DI seam + token?: string; // default: process.env.GITHUB_TOKEN + ttlMs?: number; // default: 5 * 60 * 1000 +} + +export function createGithubClient( + opts?: CreateGithubClientOptions, +): GithubClient; +``` + +- **Endpoints used:** `GET /repos/{o}/{r}` (covers stars, forks, open + issues, license, top language, last push, default branch); + `GET /repos/{o}/{r}/releases/latest` (release tag); + `GET /repos/{o}/{r}/actions/runs?per_page=1[&workflow_id=…]` + (workflow status); `GET /repos/{o}/{r}/contributors?per_page=1&anon=1` + with `Link`-header parsing for contributor count. +- **Auth:** when a token is present, sends `Authorization: Bearer ` + and `X-GitHub-Api-Version: 2022-11-28`. Otherwise anonymous. +- **Cache:** a single `Map` keyed by full + endpoint URL (including any query string). TTL default 5 min, override + via env `GOLDENCHART_GH_TTL_MS`. +- **Errors:** a typed `GithubFetchError` discriminated by `kind: + 'not-found' | 'rate-limited' | 'unauthorized' | 'network' | + 'unexpected'`. The HTTP status drives the mapping + (`404 → not-found`, `403`/`429` with rate-limit headers → `rate-limited`, + `401 → unauthorized`, fetch rejection → `network`, anything else → + `unexpected`). MCP tools surface the error directly so the agent can + decide to retry, swap to a literal `render-badge`, or give up. +- **No new runtime deps.** `globalThis.fetch` is Node-18+ native and the + `mcp/` package already targets that. + +### MCP — three new tools + +Registered in `mcp/src/registry.ts`; each gets a snapshot test under +`mcp/src/__snapshots__/` using the existing font-byte masking. Fetch tests +inject a stub `fetch` returning canned JSON via the +`createGithubClient({ fetch })` seam. + +#### 1. `render-badge` (no network) + +```ts +input: { + label: string; + value: string; + tone?: BadgeTone; + icon?: BadgeIcon; + vibe?: VibeInput; + brand?: BrandInput; + seed?: number; +} +output: { svg: string } +``` + +Renders the `Badge` component through the same server-render path used by +the other render tools. + +#### 2. `render-github-badge` (single metric, fetches) + +```ts +input: { + owner: string; + repo: string; + metric: + | 'stars' | 'forks' | 'open-issues' | 'release' | 'license' + | 'last-commit' | 'contributors' | 'language' | 'workflow'; + workflow?: string; // only used when metric === 'workflow' + label?: string; // overrides the per-metric default + tone?: BadgeTone; // overrides the per-metric default + icon?: BadgeIcon; // overrides the per-metric default + vibe?: VibeInput; + brand?: BrandInput; + seed?: number; +} +output: { svg: string } +``` + +Per-metric defaults: + +| metric | default label | default icon | default tone (derivation) | +| ------------- | ------------- | ------------ | ------------------------------------------- | +| `stars` | `"stars"` | `star` | `info` | +| `forks` | `"forks"` | `fork` | `info` | +| `open-issues` | `"issues"` | `issue` | `warn` if > 0, `success` if 0 | +| `release` | `"release"` | `tag` | `info` | +| `license` | `"license"` | `license` | `neutral` | +| `last-commit` | `"last commit"`| `commit` | `success` if ≤ 30 d, `warn` if ≤ 365 d, `danger` otherwise | +| `contributors`| `"contributors"`| `fork` | `info` | +| `language` | `"lang"` | `lang` | `neutral` | +| `workflow` | workflow name or `"build"` | `check` | `success` / `danger` from run conclusion | + +Values formatted with simple suffixing (`42300 → "42.3k"`, dates as `"3d +ago"`, releases as the raw tag). + +#### 3. `render-github-badge-row` (multi-metric, fetches) + +```ts +input: { + owner: string; + repo: string; + metrics: Metric[]; // 1..8 items + workflow?: string; + gap?: number; // default 8 + vibe?: VibeInput; + brand?: BrandInput; + seed?: number; +} +output: { svg: string } +``` + +Resolves all requested metrics through the cached client (so a row of five +metrics that all live on `getRepo` triggers a single HTTP call). Lays the +rendered badges out in a single SVG, left-to-right, vertically centered, +with `gap` pixels between them. Outer SVG width = sum of badge widths + (N-1) +* gap; height = badge height. + +### Wiring + +- `src/components/index.ts`: export `Badge`, types. +- `src/index.ts`: re-export same. +- `mcp/src/registry.ts`: register the three tools. +- `mcp/src/__snapshots__/`: new `.snap` files for each tool. Font bytes are + masked by the existing `mcp/vitest.setup.ts`. + +## Verification gates + +Matches the project's existing CI gates and the constraints documented in +`CLAUDE.md`: + +- `npm run typecheck` + `npm test` (root) and the same in `mcp/`. +- `npm run build` then `npm run check:bundle` — must not leak fonts into the + browser entry and must stay under 75 KB gzipped. +- `cd mcp && npm run compare` — carry-forward render of one literal badge + and one row, since this is an output-affecting change. +- Push the PR and let CI run the full vitest suite; the local Windows + environment OOMs on the full suite (per memory + `goldenchart-windows-shell-and-oom`). + +## Out of scope (v1) + +- Logo glyphs sourced from a remote SVG (``-embedded). Only the + built-in stroke set lives in v1. +- GitLab / Bitbucket / Sourcehut. GitHub only. +- A `` library component. The MCP row tool composes badges + directly; a row in JSX is `
`, not worth + a named component. +- Caller-supplied per-metric tone thresholds. The thresholds in the + per-metric table above are hard-coded in v1 and can become inputs later + if anyone asks. +- A `variant: 'shields'` mode on `Badge`. Additive follow-up if requested. From 957db4a77e7d08991ed09f89523d4b286ce9ac44 Mon Sep 17 00:00:00 2001 From: bsevern Date: Thu, 28 May 2026 10:14:44 -0400 Subject: [PATCH 02/17] docs(specs): address review pass on GitHub badge spec - Pin server-render path to goldenchart/server (not goldenchart). - Drop useBrand() branch; always resolveBrand(props.brand) in body. - Define RepoSummary/ReleaseSummary/WorkflowStatus shapes. - Pin width constants, height (26), icon authoring contract (16x16, stroke-only). - Clarify tone source: success/warn/danger fixed; neutral/info brand-derived. - Add in-flight promise map for request dedup; document single-token-per-instance. - Pin TTL precedence (option > env > default). - Pin workflow label fallback order. - Add mcp force-recopy step + LF-snapshot note to verification gates. - Document bundle fallback if check:bundle fails. Co-Authored-By: Claude Opus 4.7 --- ...026-05-28-github-badge-rendering-design.md | 136 +++++++++++++++--- 1 file changed, 116 insertions(+), 20 deletions(-) diff --git a/docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md b/docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md index 6bc4386..e5f32a2 100644 --- a/docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md +++ b/docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md @@ -59,11 +59,25 @@ export interface BadgeProps { } ``` -**Layout:** intrinsic SVG. Width = `iconWidth + labelWidth + valueWidth + -4*padding + 1*divider`, height ≈ 26px (constant). Text width is measured -through the same helper the rest of the library uses for axis tick labels -(`src/core/text.ts`); no DOM measurement, no font fetch. The component does -not require a parent `` and does not paint a background page color. +**Layout:** intrinsic SVG. Height is a constant `26` px (chosen against the +default ~12 px brand font; the badge does not scale with `brand.font.size` +in v1, so callers using an unusually large brand font may want to follow up +with a `size` prop later). Width is the exact sum of: + +| segment | value | +| ------------- | ---------------------------------------------------- | +| left padding | `8` | +| icon | `16` (only when `icon` is set; else `0`) | +| icon→label gap| `6` (only when `icon` is set; else `0`) | +| label text | measured | +| divider gap | `8` (label-side) + `1` (divider stroke) + `8` (value-side) | +| value text | measured | +| right padding | `8` | + +Text width is measured through the same helper the rest of the library uses +for axis tick labels (`src/core/text.ts`); no DOM measurement, no font +fetch. The component does not require a parent `` and does not +paint a background page color. **Rendering:** one Rough.js outline pill (rounded rect) using `vibe.roughness`. Left half painted with `brand.ink` at ~12% opacity for the @@ -73,6 +87,13 @@ sits inside the left half, drawn from `badgeIcons.ts` (a `Record` of SVG path `d` strings) rendered as a Rough path so the stroke matches the rest of the badge. +**Icon authoring contract** (`badgeIcons.ts`): every `d` string is a +stroke-only path authored against a `16x16` viewBox, no `Z` close (no fills), +single open sub-path preferred (Rough.js reproduces multi-sub-path `d` +inconsistently; if a glyph needs two strokes, store it as `string[]` and +render each as its own Rough path). The implementer is free to expand the +type to `BadgeIcon → string | string[]` if any glyph needs it. + **Tone → color** mapping is derived from the resolved brand inside the component body (per the brand-aware-without-context pattern already documented in `CLAUDE.md`): @@ -85,19 +106,43 @@ documented in `CLAUDE.md`): | `warn` | a fixed sketchy amber | | `danger` | a fixed sketchy red | -The fixed tone colors live next to `badgeIcons.ts` and are intentionally not -derived from the brand — "success means green" is a stronger convention than -brand fidelity for status pills. (If a brand wants to override this, the -caller passes `tone: 'neutral'` and gets the brand color directly.) +The `success`/`warn`/`danger` colors live next to `badgeIcons.ts` and are +intentionally **not** derived from the brand — "success means green" is a +stronger convention than brand fidelity for status pills. `neutral` and +`info` do come from the resolved brand palette (table above). If a brand +wants to override the status colors, the caller passes `tone: 'neutral'` +and gets the brand color directly. -**Brand/vibe wiring:** reads `useBrand()` if a `BrandProvider` is present, -otherwise calls `resolveBrand(undefined)`. Mirrors the existing chart -pattern called out in `CLAUDE.md` for charts that need brand colors without -relying on `Surface`'s providers. +**Brand/vibe wiring:** the component body calls `resolveBrand(props.brand)` +directly — it does **not** read brand from context — matching the pattern +documented in `CLAUDE.md` for chart bodies that render above `Surface`'s +providers. This keeps the behavior identical whether `Badge` is used +standalone or nested inside a ``/`` tree. **Bundle:** one component + one small icon table + one tone table. No new runtime deps. No font byte imports. `npm run check:bundle` is the gate; the -75 KB gzip ceiling must continue to hold. +75 KB gzip ceiling must continue to hold. Current headroom is not measured +in this spec — if `check:bundle` fails after implementation, the fallback +order is: (1) trim the icon set (drop `commit`/`lang`/`license` first; they +have weaker semantic value than `star`/`fork`/`issue`/`tag`/`check`), then +(2) inline-only the tone color table and drop the icon table entirely +behind a `BadgeProps` requirement that icons be passed as path strings. + +### MCP — server-render path + +All three MCP tools import `Badge` (and `resolveBrand` for tone resolution +on the server side) from **`goldenchart/server`**, not from `goldenchart`. +The `goldenchart/server` entry auto-embeds `@font-face` so the emitted SVG +is self-contained; `mcp/vitest.setup.ts` already masks font bytes as +`` for snapshots, so badge snapshots remain stable across +font-bundle updates. + +`VibeInput` and `BrandInput` in the tool schemas below are the same Zod-ish +input shapes already used by existing render tools (see `mcp/src/schemas.ts` +and how `chartFeatures` / `primitives` accept vibe and brand). The MCP layer +adapts those into the library's `VibeConfig | VibePresetName` / `BrandConfig` +types before passing to the component — no new schema surface is introduced +for Badge specifically. ### MCP — `githubClient.ts` @@ -124,6 +169,30 @@ export function createGithubClient( ): GithubClient; ``` +- **Result type shapes** (exact field set the client returns; these are + thin projections of the upstream JSON, so the implementer doesn't invent + field names downstream): + + ```ts + interface RepoSummary { + stars: number; // stargazers_count + forks: number; // forks_count + openIssues: number; // open_issues_count + license: string | null; // license?.spdx_id ?? license?.name ?? null + language: string | null;// language + pushedAt: string; // pushed_at (ISO) + defaultBranch: string; // default_branch + } + interface ReleaseSummary { tag: string; name: string | null; publishedAt: string; } + interface WorkflowStatus { + name: string; // run.name + conclusion: 'success' | 'failure' | 'cancelled' | 'neutral' | 'skipped' + | 'timed_out' | 'action_required' | 'startup_failure' | 'unknown'; + status: 'queued' | 'in_progress' | 'completed' | 'unknown'; + htmlUrl: string; + } + ``` + - **Endpoints used:** `GET /repos/{o}/{r}` (covers stars, forks, open issues, license, top language, last push, default branch); `GET /repos/{o}/{r}/releases/latest` (release tag); @@ -131,10 +200,19 @@ export function createGithubClient( (workflow status); `GET /repos/{o}/{r}/contributors?per_page=1&anon=1` with `Link`-header parsing for contributor count. - **Auth:** when a token is present, sends `Authorization: Bearer ` - and `X-GitHub-Api-Version: 2022-11-28`. Otherwise anonymous. + and `X-GitHub-Api-Version: 2022-11-28`. Otherwise anonymous. The client + is **single-token per instance** — if a caller needs to switch tokens, it + must construct a new client. The cache key therefore does not include the + token. - **Cache:** a single `Map` keyed by full - endpoint URL (including any query string). TTL default 5 min, override - via env `GOLDENCHART_GH_TTL_MS`. + endpoint URL (including any query string). A second + `Map>` holds **in-flight** requests keyed the + same way, so a `render-github-badge-row` that asks for five repo-derived + metrics concurrently still results in exactly one HTTP call (the four + duplicates await the same promise). The in-flight entry is cleared when + the promise settles; on success the value lands in the completed-response + cache. TTL default 5 min. Precedence for the effective TTL: + explicit `ttlMs` option > `GOLDENCHART_GH_TTL_MS` env > default. - **Errors:** a typed `GithubFetchError` discriminated by `kind: 'not-found' | 'rate-limited' | 'unauthorized' | 'network' | 'unexpected'`. The HTTP status drives the mapping @@ -202,7 +280,12 @@ Per-metric defaults: | `last-commit` | `"last commit"`| `commit` | `success` if ≤ 30 d, `warn` if ≤ 365 d, `danger` otherwise | | `contributors`| `"contributors"`| `fork` | `info` | | `language` | `"lang"` | `lang` | `neutral` | -| `workflow` | workflow name or `"build"` | `check` | `success` / `danger` from run conclusion | +| `workflow` | resolved (see below) | `check` | `success` / `danger` from run conclusion | + +For `workflow`, the default label is: +- if the caller passed `workflow`, use that string verbatim; +- else if the run response has a non-empty `name`, use that; +- else fall back to the literal `"build"`. Values formatted with simple suffixing (`42300 → "42.3k"`, dates as `"3d ago"`, releases as the raw tag). @@ -223,8 +306,12 @@ input: { output: { svg: string } ``` -Resolves all requested metrics through the cached client (so a row of five -metrics that all live on `getRepo` triggers a single HTTP call). Lays the +Resolves all requested metrics through the cached client. Because the +client's in-flight-promise map dedupes concurrent requests against the same +endpoint URL, a row of five metrics that all live on `getRepo` triggers +exactly one HTTP call regardless of evaluation order — the row tool calls +`getRepo` once per metric that needs it and the four duplicates await the +same promise. Lays the rendered badges out in a single SVG, left-to-right, vertically centered, with `gap` pixels between them. Outer SVG width = sum of badge widths + (N-1) * gap; height = badge height. @@ -245,8 +332,17 @@ Matches the project's existing CI gates and the constraints documented in - `npm run typecheck` + `npm test` (root) and the same in `mcp/`. - `npm run build` then `npm run check:bundle` — must not leak fonts into the browser entry and must stay under 75 KB gzipped. +- **Before running `mcp/` tests locally,** refresh `mcp/`'s copy of the + library so the new `Badge` export is visible (per CLAUDE.md's + `mcp ↔ library coupling` section): `npm run build` then + `rm -rf mcp/node_modules/goldenchart && (cd mcp && npm install --install-links)`. + Plain symlinks are unreliable on Windows. - `cd mcp && npm run compare` — carry-forward render of one literal badge and one row, since this is an output-affecting change. +- New `.snap` files must be committed with LF endings. The root + `.gitattributes` pins `*.snap text eol=lf`, so this is automatic on most + flows; if Windows autocrlf still introduces churn, run + `git add --renormalize .` once before committing the new snapshots. - Push the PR and let CI run the full vitest suite; the local Windows environment OOMs on the full suite (per memory `goldenchart-windows-shell-and-oom`). From 9c8570f8dc91af75954bca57ba19fb8cd57c6947 Mon Sep 17 00:00:00 2001 From: bsevern Date: Thu, 28 May 2026 10:20:33 -0400 Subject: [PATCH 03/17] docs(plans): GitHub badge rendering implementation plan 11 tasks: icons + tone table, Badge component (TDD), library exports, GitHub client (cache + in-flight dedup + typed errors), MCP refresh, three new MCP tools (badge / github-badge / github-badge-row), registry wiring, compare carry-forward, full verification + PR. Co-Authored-By: Claude Opus 4.7 --- .../2026-05-28-github-badge-rendering.md | 1237 +++++++++++++++++ 1 file changed, 1237 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-28-github-badge-rendering.md diff --git a/docs/superpowers/plans/2026-05-28-github-badge-rendering.md b/docs/superpowers/plans/2026-05-28-github-badge-rendering.md new file mode 100644 index 0000000..295adf7 --- /dev/null +++ b/docs/superpowers/plans/2026-05-28-github-badge-rendering.md @@ -0,0 +1,1237 @@ +# GitHub Badge Rendering Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Ship a hand-drawn GitHub-style `Badge` library component plus three MCP tools (`render-badge`, `render-github-badge`, `render-github-badge-row`) so an agent can render literal or live-data badges for any GitHub repo. + +**Architecture:** Pure library component (intrinsic SVG, no network, brand-aware via `resolveBrand(props.brand)` in body — matches the documented `CLAUDE.md` pattern). All HTTP lives in `mcp/src/githubClient.ts` behind an injectable `fetch` with TTL cache + in-flight promise dedup + optional `GITHUB_TOKEN`. MCP tools import the rendering surface from `goldenchart/server` so emitted SVGs are self-contained. + +**Tech Stack:** React 19, Rough.js, D3 (existing), Zod schemas, Vitest snapshots. No new runtime deps. Node 18+ `globalThis.fetch`. + +**Spec:** `docs/superpowers/specs/2026-05-28-github-badge-rendering-design.md` +**Branch:** `spec/github-badge-rendering` (already exists, spec already committed). + +--- + +## File structure + +**Library (`src/`):** +- Create `src/core/badgeIcons.ts` — `BadgeIcon` type, icon path table, fixed tone color table. Pure data + types. +- Create `src/components/Badge.tsx` — the component. +- Create `src/components/Badge.test.ts` — vitest tests: width formula, brand wiring, tone selection, optional-icon branch, server SVG snapshot for one case. +- Modify `src/components/index.ts` — export `Badge`, `BadgeProps`, `BadgeTone`, `BadgeIcon`. +- Modify `src/index.ts` — re-export the same names. + +**MCP (`mcp/src/`):** +- Create `mcp/src/githubClient.ts` — `createGithubClient`, types, errors. No I/O at import time. +- Create `mcp/src/githubClient.test.ts` — TTL cache, in-flight dedup, error mapping, auth header behavior. All via the injected `fetch` stub. +- Create `mcp/src/badgeTools.ts` — three `ToolDef`s. Does NOT go through `makeRenderTool` (that factory assumes `width`/`height` args; the badge tools don't have those). +- Create `mcp/src/badgeTools.test.ts` — snapshot tests for all three tools, fetch stubbed. +- Modify `mcp/src/tools.ts` — import `badgeTools` and add to the exported tool list. +- New snapshot files appear under `mcp/src/__snapshots__/badgeTools.test.ts.snap`. + +**Carry-forward render:** +- Modify `mcp/compare/...` (whatever script `npm run compare` already runs — locate before editing) to add one literal `Badge` and one `render-github-badge-row` example with a stubbed client. + +--- + +## Task 1: Icon + tone color tables + +**Files:** +- Create: `src/core/badgeIcons.ts` + +- [ ] **Step 1: Write the failing test** + +```ts +// src/core/badgeIcons.test.ts +import { describe, it, expect } from 'vitest'; +import { BADGE_ICON_PATHS, BADGE_TONE_COLORS, BADGE_ICONS, BADGE_TONES } from './badgeIcons'; + +describe('badgeIcons', () => { + it('exposes a stroke path string for every icon name', () => { + for (const name of BADGE_ICONS) { + const entry = BADGE_ICON_PATHS[name]; + const strokes = Array.isArray(entry) ? entry : [entry]; + expect(strokes.length).toBeGreaterThan(0); + for (const d of strokes) { + // Stroke-only contract: no fills (no `Z`), nothing closes the path. + expect(d).not.toMatch(/[Zz]/); + expect(d.length).toBeGreaterThan(0); + } + } + }); + it('has a color for every fixed tone (success/warn/danger)', () => { + expect(BADGE_TONE_COLORS.success).toMatch(/^#/); + expect(BADGE_TONE_COLORS.warn).toMatch(/^#/); + expect(BADGE_TONE_COLORS.danger).toMatch(/^#/); + }); + it('lists every supported tone literal', () => { + expect(new Set(BADGE_TONES)).toEqual( + new Set(['neutral', 'info', 'success', 'warn', 'danger']), + ); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run from project root: `npm test -- src/core/badgeIcons` +Expected: FAIL with `cannot find module './badgeIcons'`. + +- [ ] **Step 3: Implement `src/core/badgeIcons.ts`** + +```ts +/** + * Icon stroke paths and fixed tone colors for `Badge`. Pure data, no React. + * + * Icon authoring contract (per spec): + * - viewBox 16x16 + * - stroke-only (no `Z`, no fills) + * - single open sub-path preferred; if a glyph genuinely needs two strokes, + * the entry may be `string[]` and the component renders each as its own + * Rough path. + */ + +export const BADGE_TONES = ['neutral', 'info', 'success', 'warn', 'danger'] as const; +export type BadgeTone = (typeof BADGE_TONES)[number]; + +export const BADGE_ICONS = [ + 'star', + 'fork', + 'issue', + 'tag', + 'commit', + 'license', + 'lang', + 'check', +] as const; +export type BadgeIcon = (typeof BADGE_ICONS)[number]; + +/** Stroke-only path data, authored against a 16x16 box. */ +export const BADGE_ICON_PATHS: Record = { + // Five-point star outline (open at the top tip so it remains an open stroke). + star: 'M8 1 L10 6 L15 6 L11 9.5 L12.5 14.5 L8 11.5 L3.5 14.5 L5 9.5 L1 6 L6 6', + // Two circles + a connector (git fork glyph). + fork: [ + 'M4 3 a2 2 0 1 0 0 4 a2 2 0 1 0 0 -4', + 'M12 3 a2 2 0 1 0 0 4 a2 2 0 1 0 0 -4', + 'M4 7 L4 11 a2 2 0 0 0 2 2 L10 13', + 'M12 7 L12 9', + ], + // Circle with vertical bar (open issue indicator). + issue: ['M8 1 a7 7 0 1 0 0 14 a7 7 0 1 0 0 -14', 'M8 5 L8 9', 'M8 11 L8 12'], + // Price-tag silhouette (open stroke; no Z). + tag: 'M1 8 L8 1 L15 1 L15 8 L8 15 Z'.replace(/Z/g, ''), // sentinel — see below + // Git commit dot + line. + commit: ['M2 8 L6 8', 'M10 8 L14 8', 'M8 6 a2 2 0 1 0 0 4 a2 2 0 1 0 0 -4'], + // Scroll silhouette (open). + license: ['M3 2 L13 2 L13 13 L8 13', 'M3 2 L3 13 L8 13 L8 11', 'M5 5 L11 5', 'M5 8 L11 8'], + // Three vertical bars (language stack). + lang: ['M3 13 L3 5', 'M8 13 L8 3', 'M13 13 L13 7'], + // Check mark. + check: 'M2 9 L6 13 L14 3', +}; + +// The `tag` glyph above is a workaround to keep the string literal honest; +// re-define it cleanly here. +BADGE_ICON_PATHS.tag = 'M1 8 L8 1 L15 1 L15 8 L8 15'; + +export const BADGE_TONE_COLORS: Record<'success' | 'warn' | 'danger', string> = { + success: '#3a8a3a', + warn: '#b8860b', + danger: '#b13a3a', +}; +``` + +- [ ] **Step 4: Run tests** + +Run: `npm test -- src/core/badgeIcons` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/core/badgeIcons.ts src/core/badgeIcons.test.ts +git commit -m "feat(badge): icon stroke paths + fixed tone colors" +``` + +--- + +## Task 2: `Badge` component + +**Files:** +- Create: `src/components/Badge.tsx` +- Create: `src/components/Badge.test.ts` + +The badge does NOT use ``, does NOT read brand from context, does NOT register an a11y title (callers can wrap if needed). It calls `resolveBrand(props.brand)` and `resolveVibe(props.vibe)` directly in the body. Width is computed from text measurement via `src/core/text.ts` (the same helper axis ticks use — locate the export before relying on it; if the helper signature is `measureTextWidth(text, fontSize, fontFamily)`, use it; if it returns an approximation, that's fine). + +- [ ] **Step 1: Write the failing test** + +```ts +// src/components/Badge.test.ts +import { describe, it, expect } from 'vitest'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { createElement } from 'react'; +import { Badge } from './Badge'; + +function render(props: Parameters[0]) { + return renderToStaticMarkup(createElement(Badge, props)); +} + +describe('Badge', () => { + it('renders an intrinsic with measurable width and constant height 26', () => { + const svg = render({ label: 'stars', value: '42.3k' }); + expect(svg).toMatch(/]+width="\d+"/); + expect(svg).toMatch(/]+height="26"/); + expect(svg).toContain('stars'); + expect(svg).toContain('42.3k'); + }); + it('renders the icon glyph when `icon` is set', () => { + const without = render({ label: 'stars', value: '0' }); + const withIcon = render({ label: 'stars', value: '0', icon: 'star' }); + expect(withIcon.length).toBeGreaterThan(without.length); + }); + it('uses success color for tone="success" and danger for tone="danger"', () => { + const ok = render({ label: 'build', value: 'passing', tone: 'success' }); + const bad = render({ label: 'build', value: 'failing', tone: 'danger' }); + expect(ok).toContain('#3a8a3a'); + expect(bad).toContain('#b13a3a'); + }); + it('uses brand palette[0] for tone="neutral"', () => { + const svg = render({ + label: 'x', value: 'y', tone: 'neutral', + brand: { palette: ['#123456', '#abcdef'] }, + }); + expect(svg).toContain('#123456'); + }); + it('produces a stable snapshot for a known input', () => { + const svg = render({ + label: 'stars', value: '42.3k', tone: 'info', icon: 'star', + brand: { palette: ['#222', '#0077cc'], font: 'sans-serif' }, + seed: 1, + }); + expect(svg).toMatchSnapshot(); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npm test -- src/components/Badge` +Expected: FAIL with `cannot find module './Badge'`. + +- [ ] **Step 3: Implement `src/components/Badge.tsx`** + +Sketch (the implementer fills in the exact Rough.js calls based on existing primitives — see `src/primitives/RoughPath.tsx` and `src/primitives/RoughRectangle.tsx` for the established pattern): + +```tsx +import { createElement } from 'react'; +import { resolveBrand } from '../brand'; +import { resolveVibe } from '../vibe'; +import type { BrandConfig } from '../brand'; +import type { VibeConfig, VibePresetName } from '../vibe'; +import { RoughPath } from '../primitives/RoughPath'; +import { RoughRectangle } from '../primitives/RoughRectangle'; +import { RoughText } from '../primitives/RoughText'; +import { measureTextWidth } from '../core/text'; +import { + BADGE_ICON_PATHS, + BADGE_TONE_COLORS, + type BadgeIcon, + type BadgeTone, +} from '../core/badgeIcons'; + +const HEIGHT = 26; +const PAD_X = 8; +const ICON_SIZE = 16; +const ICON_GAP = 6; +const DIVIDER_GAP = 8; +const DIVIDER_W = 1; + +export interface BadgeProps { + label: string; + value: string; + tone?: BadgeTone; // default 'neutral' + icon?: BadgeIcon; + vibe?: VibeConfig | VibePresetName; + brand?: BrandConfig; + seed?: number; + className?: string; +} + +export function Badge({ + label, value, tone = 'neutral', icon, vibe, brand, seed, className, +}: BadgeProps) { + const b = resolveBrand(brand); + const v = resolveVibe(vibe); + const font = b.font; + const labelW = measureTextWidth(label, v.fontSize, font); + const valueW = measureTextWidth(value, v.fontSize, font); + const iconW = icon ? ICON_SIZE + ICON_GAP : 0; + const dividerX = PAD_X + iconW + labelW + DIVIDER_GAP; + const valueX = dividerX + DIVIDER_W + DIVIDER_GAP; + const width = valueX + valueW + PAD_X; + + // Tone -> fill + const valueFill = + tone === 'neutral' ? b.palette[0] + : tone === 'info' ? (b.palette[1] ?? b.palette[0]) + : BADGE_TONE_COLORS[tone]; + const labelFill = b.ink; // painted at 12% via opacity prop on the rect + + return ( + + {/* Pill outline */} + + {/* Label half (ink @ ~12%) */} + + {/* Value half (tone color) */} + + {/* Divider */} + + {/* Optional icon */} + {icon ? renderIcon(icon, PAD_X, (HEIGHT - ICON_SIZE) / 2, b.ink, seed) : null} + {/* Label text */} + + {/* Value text */} + + + ); +} + +function renderIcon(name: BadgeIcon, ox: number, oy: number, stroke: string, seed?: number) { + const entry = BADGE_ICON_PATHS[name]; + const strokes = Array.isArray(entry) ? entry : [entry]; + return ( + + {strokes.map((d, i) => ( + + ))} + + ); +} +``` + +If `RoughRectangle` / `RoughPath` / `RoughText` props don't match the names above (e.g. `fillStyle` vs `style`), adjust to the real primitive surface. **Do not** widen the primitive APIs to fit the badge. + +- [ ] **Step 4: Run tests** + +Run: `npm test -- src/components/Badge` +Expected: PASS. Snapshot file is written on first run; review the snapshot diff in the next commit. + +- [ ] **Step 5: Commit** + +```bash +git add src/components/Badge.tsx src/components/Badge.test.ts src/components/__snapshots__/Badge.test.ts.snap +git commit -m "feat(badge): intrinsic-SVG Badge component, brand-aware in body" +``` + +--- + +## Task 3: Library exports + +**Files:** +- Modify: `src/components/index.ts` +- Modify: `src/index.ts` + +- [ ] **Step 1: Add to `src/components/index.ts`** + +Append: + +```ts +export { Badge } from './Badge'; +export type { BadgeProps } from './Badge'; +export { BADGE_TONES, BADGE_ICONS } from '../core/badgeIcons'; +export type { BadgeTone, BadgeIcon } from '../core/badgeIcons'; +``` + +- [ ] **Step 2: Add to `src/index.ts`** + +In the existing `export { ... } from './components'` block add `Badge`; in the `export type { ... }` block add `BadgeProps`, `BadgeTone`, `BadgeIcon`. + +- [ ] **Step 3: Typecheck and rebuild** + +```bash +npm run typecheck +npm run build +npm run check:bundle +``` + +Expected: `typecheck` PASS, `build` PASS, `check:bundle` PASS (under 75 KB gzip, no font leak). + +If `check:bundle` fails, follow the spec's fallback order: drop `commit`/`lang`/`license` from `BADGE_ICONS` first, re-run. + +- [ ] **Step 4: Commit** + +```bash +git add src/components/index.ts src/index.ts +git commit -m "feat(badge): export Badge + BadgeIcon/BadgeTone from library entries" +``` + +--- + +## Task 4: `githubClient` (no MCP wiring yet) + +**Files:** +- Create: `mcp/src/githubClient.ts` +- Create: `mcp/src/githubClient.test.ts` + +The client is a plain TS module — no React, no MCP types, no environment globals at import time (read `process.env.GITHUB_TOKEN` only inside `createGithubClient`'s body, with a default-argument fallback so tests can override). + +- [ ] **Step 1: Write the failing tests** + +```ts +// mcp/src/githubClient.test.ts +import { describe, it, expect, vi } from 'vitest'; +import { createGithubClient, GithubFetchError } from './githubClient'; + +function fakeFetch(responses: Record }>) { + return vi.fn(async (input: string | URL) => { + const url = String(input); + const r = responses[url]; + if (!r) throw new Error(`unmocked: ${url}`); + return new Response(r.body == null ? null : JSON.stringify(r.body), { + status: r.status, + headers: r.headers ?? { 'content-type': 'application/json' }, + }); + }); +} + +const REPO_URL = 'https://api.github.com/repos/o/r'; + +describe('githubClient', () => { + it('getRepo: parses the upstream shape into RepoSummary', async () => { + const fetch = fakeFetch({ + [REPO_URL]: { + status: 200, + body: { + stargazers_count: 10, forks_count: 2, open_issues_count: 3, + license: { spdx_id: 'MIT' }, language: 'TypeScript', + pushed_at: '2026-05-01T00:00:00Z', default_branch: 'main', + }, + }, + }); + const c = createGithubClient({ fetch }); + expect(await c.getRepo('o', 'r')).toEqual({ + stars: 10, forks: 2, openIssues: 3, license: 'MIT', + language: 'TypeScript', pushedAt: '2026-05-01T00:00:00Z', defaultBranch: 'main', + }); + }); + + it('caches completed responses for TTL', async () => { + const fetch = fakeFetch({ [REPO_URL]: { status: 200, body: { stargazers_count: 1 } } }); + const c = createGithubClient({ fetch, ttlMs: 60_000 }); + await c.getRepo('o', 'r'); + await c.getRepo('o', 'r'); + expect(fetch).toHaveBeenCalledTimes(1); + }); + + it('dedupes in-flight requests', async () => { + let resolveHttp!: (v: Response) => void; + const fetch = vi.fn(() => new Promise((res) => { resolveHttp = res; })); + const c = createGithubClient({ fetch }); + const p1 = c.getRepo('o', 'r'); + const p2 = c.getRepo('o', 'r'); + resolveHttp(new Response(JSON.stringify({ stargazers_count: 7 }), { status: 200 })); + await Promise.all([p1, p2]); + expect(fetch).toHaveBeenCalledTimes(1); + }); + + it('maps 404 -> not-found, 401 -> unauthorized, 403/429 with rate-limit -> rate-limited', async () => { + const c = createGithubClient({ + fetch: fakeFetch({ + [REPO_URL]: { status: 404 }, + 'https://api.github.com/repos/o/r/releases/latest': { status: 401 }, + 'https://api.github.com/repos/o/r/contributors?per_page=1&anon=1': { + status: 403, headers: { 'x-ratelimit-remaining': '0', 'content-type': 'application/json' }, body: {}, + }, + }), + }); + await expect(c.getRepo('o', 'r')).rejects.toMatchObject({ kind: 'not-found' }); + await expect(c.getLatestRelease('o', 'r')).rejects.toMatchObject({ kind: 'unauthorized' }); + await expect(c.getContributorsCount('o', 'r')).rejects.toMatchObject({ kind: 'rate-limited' }); + }); + + it('sends Authorization header when token is set', async () => { + const fetch = vi.fn(async () => new Response(JSON.stringify({ stargazers_count: 1 }), { status: 200 })); + const c = createGithubClient({ fetch, token: 'ghp_xxx' }); + await c.getRepo('o', 'r'); + const headers = (fetch.mock.calls[0][1] as RequestInit | undefined)?.headers as Record; + expect(headers.Authorization).toBe('Bearer ghp_xxx'); + expect(headers['X-GitHub-Api-Version']).toBe('2022-11-28'); + }); + + it('precedence for ttl: option > env > default', () => { + const oldEnv = process.env.GOLDENCHART_GH_TTL_MS; + try { + process.env.GOLDENCHART_GH_TTL_MS = '1000'; + const c1 = createGithubClient({}); + const c2 = createGithubClient({ ttlMs: 5000 }); + expect((c1 as any).__ttlMs).toBe(1000); // see implementation note below + expect((c2 as any).__ttlMs).toBe(5000); + } finally { + process.env.GOLDENCHART_GH_TTL_MS = oldEnv; + } + }); + + it('parses contributor count from Link header last-page', async () => { + const fetch = fakeFetch({ + 'https://api.github.com/repos/o/r/contributors?per_page=1&anon=1': { + status: 200, + body: [{}], + headers: { + 'content-type': 'application/json', + link: '; rel="next", ; rel="last"', + }, + }, + }); + const c = createGithubClient({ fetch }); + expect(await c.getContributorsCount('o', 'r')).toBe(137); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify failure** + +Run from `mcp/`: `npm test -- githubClient` +Expected: FAIL with `cannot find module './githubClient'`. + +- [ ] **Step 3: Implement `mcp/src/githubClient.ts`** + +Sketch: + +```ts +export type RepoSummary = { + stars: number; forks: number; openIssues: number; + license: string | null; language: string | null; + pushedAt: string; defaultBranch: string; +}; +export type ReleaseSummary = { tag: string; name: string | null; publishedAt: string }; +export type WorkflowStatus = { + name: string; + conclusion: 'success' | 'failure' | 'cancelled' | 'neutral' | 'skipped' + | 'timed_out' | 'action_required' | 'startup_failure' | 'unknown'; + status: 'queued' | 'in_progress' | 'completed' | 'unknown'; + htmlUrl: string; +}; + +export type GithubFetchErrorKind = + | 'not-found' | 'rate-limited' | 'unauthorized' | 'network' | 'unexpected'; +export class GithubFetchError extends Error { + constructor(public kind: GithubFetchErrorKind, public status: number, message: string) { + super(message); + this.name = 'GithubFetchError'; + } +} + +export interface GithubClient { + getRepo(owner: string, repo: string): Promise; + getLatestRelease(owner: string, repo: string): Promise; + getWorkflowStatus(owner: string, repo: string, workflow?: string): Promise; + getContributorsCount(owner: string, repo: string): Promise; +} + +export interface CreateGithubClientOptions { + fetch?: typeof globalThis.fetch; + token?: string; + ttlMs?: number; +} + +const DEFAULT_TTL_MS = 5 * 60 * 1000; + +export function createGithubClient(opts: CreateGithubClientOptions = {}): GithubClient { + const fetchImpl = opts.fetch ?? globalThis.fetch; + const token = opts.token ?? process.env.GITHUB_TOKEN; + const ttlMs = opts.ttlMs ?? Number(process.env.GOLDENCHART_GH_TTL_MS) || DEFAULT_TTL_MS; + const completed = new Map(); + const inflight = new Map>(); + + async function call(url: string, parse: (resp: Response) => Promise): Promise { + const now = Date.now(); + const hit = completed.get(url); + if (hit && hit.expiresAt > now) return hit.value as T; + const existing = inflight.get(url); + if (existing) return existing as Promise; + const p = (async () => { + let resp: Response; + try { + resp = await fetchImpl(url, { + headers: { + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }); + } catch (e) { + throw new GithubFetchError('network', 0, (e as Error).message); + } + if (resp.status === 404) throw new GithubFetchError('not-found', 404, url); + if (resp.status === 401) throw new GithubFetchError('unauthorized', 401, url); + if (resp.status === 403 || resp.status === 429) { + if (resp.headers.get('x-ratelimit-remaining') === '0') { + throw new GithubFetchError('rate-limited', resp.status, url); + } + } + if (resp.status < 200 || resp.status >= 300) { + throw new GithubFetchError('unexpected', resp.status, url); + } + const value = await parse(resp); + completed.set(url, { value, expiresAt: Date.now() + ttlMs }); + return value; + })().finally(() => { inflight.delete(url); }); + inflight.set(url, p); + return p; + } + + const api: GithubClient = { + getRepo: (o, r) => call(`https://api.github.com/repos/${o}/${r}`, async (resp) => { + const j = await resp.json() as any; + return { + stars: j.stargazers_count, forks: j.forks_count, openIssues: j.open_issues_count, + license: j.license?.spdx_id ?? j.license?.name ?? null, + language: j.language ?? null, + pushedAt: j.pushed_at, defaultBranch: j.default_branch, + }; + }), + getLatestRelease: (o, r) => call(`https://api.github.com/repos/${o}/${r}/releases/latest`, async (resp) => { + const j = await resp.json() as any; + return { tag: j.tag_name, name: j.name ?? null, publishedAt: j.published_at }; + }), + getWorkflowStatus: (o, r, workflow) => { + const q = workflow ? `&workflow_id=${encodeURIComponent(workflow)}` : ''; + return call(`https://api.github.com/repos/${o}/${r}/actions/runs?per_page=1${q}`, async (resp) => { + const j = await resp.json() as any; + const run = j.workflow_runs?.[0]; + if (!run) throw new GithubFetchError('not-found', resp.status, 'no runs'); + return { + name: run.name ?? '', + conclusion: run.conclusion ?? 'unknown', + status: run.status ?? 'unknown', + htmlUrl: run.html_url ?? '', + }; + }); + }, + getContributorsCount: (o, r) => call(`https://api.github.com/repos/${o}/${r}/contributors?per_page=1&anon=1`, async (resp) => { + const link = resp.headers.get('link') ?? ''; + const m = /<[^>]*[?&]page=(\d+)[^>]*>;\s*rel="last"/.exec(link); + if (m) return Number(m[1]); + const list = await resp.json() as unknown[]; + return Array.isArray(list) ? list.length : 0; + }), + }; + + // Test introspection (intentionally non-enumerable so it doesn't appear in JSON): + Object.defineProperty(api, '__ttlMs', { value: ttlMs }); + return api; +} +``` + +- [ ] **Step 4: Run tests** + +Run from `mcp/`: `npm test -- githubClient` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add mcp/src/githubClient.ts mcp/src/githubClient.test.ts +git commit -m "feat(mcp): GitHub client with TTL cache, in-flight dedup, typed errors" +``` + +--- + +## Task 5: MCP refresh — pull new library exports into `mcp/` + +This is the CLAUDE.md-mandated force-recopy step. The remaining tasks depend on `import { Badge } from 'goldenchart/server'` resolving in `mcp/`. + +- [ ] **Step 1: Rebuild root** + +From project root: +```bash +npm run build +``` + +- [ ] **Step 2: Force-recopy `goldenchart` into `mcp/node_modules`** + +PowerShell: +```powershell +Remove-Item -Recurse -Force mcp\node_modules\goldenchart +cd mcp; npm install --install-links; cd .. +``` + +(Plain symlinks are unreliable on Windows; per CLAUDE.md.) + +- [ ] **Step 3: Sanity check the new export resolves** + +```bash +cd mcp && node -e "console.log(typeof require('goldenchart').Badge)" && cd .. +``` + +Expected: `function`. + +(No commit; this is environment-only.) + +--- + +## Task 6: `render-badge` MCP tool (no network) + +**Files:** +- Create: `mcp/src/badgeTools.ts` +- Create: `mcp/src/badgeTools.test.ts` + +`makeRenderTool` won't fit — it puts `width`/`height` into `meta` from args, but the badge is intrinsic. Build a fresh `ToolDef`. + +- [ ] **Step 1: Add Zod input shape and first tool to `badgeTools.ts`** + +```ts +import { createElement } from 'react'; +import { z } from 'zod'; +import { renderToSVGString } from 'goldenchart/server'; +import { Badge, BADGE_TONES, BADGE_ICONS } from 'goldenchart'; +import type { ToolDef } from './registry'; +import { renderOutputShape, VibeConfigSchema, BrandConfigSchema } from './schemas'; +import { + createGithubClient, GithubFetchError, type GithubClient, +} from './githubClient'; + +const ToneEnum = z.enum(BADGE_TONES as unknown as [string, ...string[]]); +const IconEnum = z.enum(BADGE_ICONS as unknown as [string, ...string[]]); + +const badgeInputShape = { + label: z.string().min(1), + value: z.string().min(1), + tone: ToneEnum.optional(), + icon: IconEnum.optional(), + vibe: VibeConfigSchema.optional(), + brand: BrandConfigSchema.optional(), + seed: z.number().optional(), +}; + +export const renderBadgeTool: ToolDef = { + name: 'render-badge', + config: { + title: 'Render a hand-drawn badge', + description: 'Renders a GoldenChart Badge (label/value pill) as a self-contained SVG. No network.', + inputSchema: badgeInputShape, + outputSchema: renderOutputShape, + }, + handler: async (args) => { + const svg = renderToSVGString(createElement(Badge as any, args)); + return { + content: [{ type: 'text', text: svg }], + structuredContent: { svg, meta: { kind: 'badge', width: 0, height: 26 } }, + // width is intrinsic; 0 here flags "ask the SVG itself". Alternative: + // parse the emitted width attribute; not worth it for v1. + }; + }, +}; + +export const badgeTools: ToolDef[] = [renderBadgeTool]; +``` + +- [ ] **Step 2: Snapshot test** + +```ts +// mcp/src/badgeTools.test.ts +import { describe, it, expect } from 'vitest'; +import { renderBadgeTool } from './badgeTools'; + +describe('render-badge', () => { + it('produces a stable SVG for a known input', async () => { + const res = await renderBadgeTool.handler({ + label: 'stars', value: '42.3k', tone: 'info', icon: 'star', + brand: { palette: ['#222', '#0077cc'], font: 'sans-serif' }, + seed: 1, + }); + const svg = (res.content[0] as { text: string }).text; + expect(svg).toMatchSnapshot(); + }); +}); +``` + +- [ ] **Step 3: Run tests** + +From `mcp/`: `npm test -- badgeTools` +Expected: PASS. The snapshot file under `mcp/src/__snapshots__/` is created on first run — review the diff before committing. Font bytes should be masked by `mcp/vitest.setup.ts`. + +- [ ] **Step 4: Commit** + +```bash +git add mcp/src/badgeTools.ts mcp/src/badgeTools.test.ts mcp/src/__snapshots__/badgeTools.test.ts.snap +git commit -m "feat(mcp): render-badge tool" +``` + +--- + +## Task 7: `render-github-badge` MCP tool (single metric, fetches) + +**Files:** +- Modify: `mcp/src/badgeTools.ts` +- Modify: `mcp/src/badgeTools.test.ts` + +The tool accepts an optional `githubClient` for tests; production callers omit it and get `createGithubClient()` lazily. + +- [ ] **Step 1: Tests first** + +Append to `badgeTools.test.ts`: + +```ts +import { renderGithubBadgeTool } from './badgeTools'; + +const stubClient = (overrides?: Partial) => ({ + getRepo: async () => ({ + stars: 12345, forks: 678, openIssues: 0, + license: 'MIT', language: 'TypeScript', + pushedAt: '2026-05-01T00:00:00Z', defaultBranch: 'main', + }), + getLatestRelease: async () => ({ tag: 'v1.2.3', name: '1.2.3', publishedAt: '2026-05-01T00:00:00Z' }), + getWorkflowStatus: async () => ({ name: 'CI', conclusion: 'success', status: 'completed', htmlUrl: '' }), + getContributorsCount: async () => 42, + ...overrides, +} as GithubClient); + +describe('render-github-badge', () => { + it('renders a stars badge with k-formatted value and info tone', async () => { + const res = await renderGithubBadgeTool.handler({ + owner: 'o', repo: 'r', metric: 'stars', + __client: stubClient(), // test seam, see implementation + }); + expect((res.content[0] as any).text).toMatchSnapshot(); + }); + it('renders a workflow badge as success', async () => { + const res = await renderGithubBadgeTool.handler({ + owner: 'o', repo: 'r', metric: 'workflow', + __client: stubClient(), + }); + const svg = (res.content[0] as any).text; + expect(svg).toContain('#3a8a3a'); // success color + }); + it('reports rate-limited errors as structured tool errors', async () => { + const c = stubClient({ + getRepo: async () => { throw new GithubFetchError('rate-limited', 403, 'rate'); }, + }); + const res = await renderGithubBadgeTool.handler({ + owner: 'o', repo: 'r', metric: 'stars', __client: c, + }); + expect(res.isError).toBe(true); + expect((res.content[0] as any).text).toContain('rate-limited'); + }); +}); +``` + +- [ ] **Step 2: Append tool to `badgeTools.ts`** + +```ts +import type { GithubClient } from './githubClient'; + +const MetricEnum = z.enum([ + 'stars', 'forks', 'open-issues', 'release', 'license', + 'last-commit', 'contributors', 'language', 'workflow', +]); + +const githubBadgeInputShape = { + owner: z.string().min(1), + repo: z.string().min(1), + metric: MetricEnum, + workflow: z.string().optional(), + label: z.string().optional(), + tone: ToneEnum.optional(), + icon: IconEnum.optional(), + vibe: VibeConfigSchema.optional(), + brand: BrandConfigSchema.optional(), + seed: z.number().optional(), +}; + +function formatCount(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1).replace(/\.0$/, '')}k`; + return String(n); +} +function relativeDate(iso: string): string { + const days = Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 86400_000)); + if (days < 1) return 'today'; + if (days < 30) return `${days}d ago`; + if (days < 365) return `${Math.round(days / 30)}mo ago`; + return `${Math.round(days / 365)}y ago`; +} + +type Resolved = { label: string; value: string; tone: string; icon: string }; + +async function resolveMetric( + client: GithubClient, owner: string, repo: string, metric: string, workflow?: string, +): Promise { + switch (metric) { + case 'stars': { + const r = await client.getRepo(owner, repo); + return { label: 'stars', value: formatCount(r.stars), tone: 'info', icon: 'star' }; + } + case 'forks': { + const r = await client.getRepo(owner, repo); + return { label: 'forks', value: formatCount(r.forks), tone: 'info', icon: 'fork' }; + } + case 'open-issues': { + const r = await client.getRepo(owner, repo); + return { label: 'issues', value: formatCount(r.openIssues), + tone: r.openIssues > 0 ? 'warn' : 'success', icon: 'issue' }; + } + case 'release': { + const rel = await client.getLatestRelease(owner, repo); + return { label: 'release', value: rel.tag, tone: 'info', icon: 'tag' }; + } + case 'license': { + const r = await client.getRepo(owner, repo); + return { label: 'license', value: r.license ?? 'unknown', tone: 'neutral', icon: 'license' }; + } + case 'last-commit': { + const r = await client.getRepo(owner, repo); + const days = Math.round((Date.now() - new Date(r.pushedAt).getTime()) / 86400_000); + const tone = days <= 30 ? 'success' : days <= 365 ? 'warn' : 'danger'; + return { label: 'last commit', value: relativeDate(r.pushedAt), tone, icon: 'commit' }; + } + case 'contributors': { + const n = await client.getContributorsCount(owner, repo); + return { label: 'contributors', value: formatCount(n), tone: 'info', icon: 'fork' }; + } + case 'language': { + const r = await client.getRepo(owner, repo); + return { label: 'lang', value: r.language ?? 'unknown', tone: 'neutral', icon: 'lang' }; + } + case 'workflow': { + const w = await client.getWorkflowStatus(owner, repo, workflow); + const tone = w.conclusion === 'success' ? 'success' : 'danger'; + const label = workflow ?? (w.name || 'build'); + return { label, value: w.conclusion, tone, icon: 'check' }; + } + default: + throw new Error(`unknown metric: ${metric}`); + } +} + +export const renderGithubBadgeTool: ToolDef = { + name: 'render-github-badge', + config: { + title: 'Render a GitHub repo badge', + description: + 'Fetches a single metric from GitHub (anonymous or with $GITHUB_TOKEN) and renders it as a hand-drawn Badge SVG.', + inputSchema: githubBadgeInputShape, + outputSchema: renderOutputShape, + }, + handler: async (args) => { + const client: GithubClient = (args as any).__client ?? createGithubClient(); + const { owner, repo, metric, workflow, label, tone, icon, vibe, brand, seed } = + args as Record; + try { + const resolved = await resolveMetric(client, owner, repo, metric, workflow); + const props = { + label: label ?? resolved.label, + value: resolved.value, + tone: tone ?? resolved.tone, + icon: icon ?? resolved.icon, + vibe, brand, seed, + }; + const svg = renderToSVGString(createElement(Badge as any, props)); + return { + content: [{ type: 'text', text: svg }], + structuredContent: { svg, meta: { kind: 'github-badge', width: 0, height: 26 } }, + }; + } catch (e) { + const kind = e instanceof GithubFetchError ? e.kind : 'unexpected'; + return { + content: [{ type: 'text', text: `github-badge error: ${kind}: ${(e as Error).message}` }], + structuredContent: { error: { kind, message: (e as Error).message } }, + isError: true, + }; + } + }, +}; + +badgeTools.push(renderGithubBadgeTool); +``` + +`__client` is a documented test seam (prefix `__`; not in the Zod schema; ignored by the SDK validator since it's passed through `args`). If the SDK strips unknown args, swap to importing a `__setGithubClientForTests` setter from `badgeTools.ts` and use that in tests instead. + +- [ ] **Step 3: Run tests** + +```bash +cd mcp && npm test -- badgeTools +``` + +Expected: PASS. Review the new snapshot lines. + +- [ ] **Step 4: Commit** + +```bash +git add mcp/src/badgeTools.ts mcp/src/badgeTools.test.ts mcp/src/__snapshots__/badgeTools.test.ts.snap +git commit -m "feat(mcp): render-github-badge tool with cached, dedup'd fetch" +``` + +--- + +## Task 8: `render-github-badge-row` MCP tool + +**Files:** +- Modify: `mcp/src/badgeTools.ts` +- Modify: `mcp/src/badgeTools.test.ts` + +Reuses the client's in-flight dedup, so calling `resolveMetric` once per metric is fine — duplicate `getRepo` calls collapse to one HTTP roundtrip naturally. + +- [ ] **Step 1: Test** + +```ts +describe('render-github-badge-row', () => { + it('renders a row that triggers exactly one repo call for repo-derived metrics', async () => { + const repo = vi.fn(async () => ({ + stars: 100, forks: 10, openIssues: 0, license: 'MIT', + language: 'TS', pushedAt: new Date().toISOString(), defaultBranch: 'main', + })); + const client = stubClient({ getRepo: repo }); + const res = await renderGithubBadgeRowTool.handler({ + owner: 'o', repo: 'r', + metrics: ['stars', 'forks', 'open-issues', 'license', 'language'], + __client: client, + }); + expect(repo).toHaveBeenCalledTimes(1); + expect((res.content[0] as any).text).toMatchSnapshot(); + }); +}); +``` + +(The single call is guaranteed by the client's in-flight promise map; if `resolveMetric` is called sequentially the cache covers duplicates. Either order satisfies "exactly one HTTP call".) + +- [ ] **Step 2: Implement** + +```ts +const githubBadgeRowInputShape = { + owner: z.string().min(1), + repo: z.string().min(1), + metrics: z.array(MetricEnum).min(1).max(8), + workflow: z.string().optional(), + gap: z.number().int().nonnegative().optional(), + vibe: VibeConfigSchema.optional(), + brand: BrandConfigSchema.optional(), + seed: z.number().optional(), +}; + +export const renderGithubBadgeRowTool: ToolDef = { + name: 'render-github-badge-row', + config: { + title: 'Render a row of GitHub repo badges', + description: + 'Resolves multiple GitHub metrics (with cached + deduplicated fetches) and renders them as a single SVG row of hand-drawn Badges.', + inputSchema: githubBadgeRowInputShape, + outputSchema: renderOutputShape, + }, + handler: async (args) => { + const client: GithubClient = (args as any).__client ?? createGithubClient(); + const { owner, repo, metrics, workflow, gap = 8, vibe, brand, seed } = + args as Record; + try { + const resolved = await Promise.all( + (metrics as string[]).map((m) => resolveMetric(client, owner, repo, m, workflow)), + ); + // Render each Badge to its own SVG, then concatenate by parsing out the + // intrinsic widths from the `` attribute and wrapping in + // a parent with . + const parts = resolved.map((r) => ({ + svg: renderToSVGString(createElement(Badge as any, { + ...r, label: r.label, value: r.value, tone: r.tone, icon: r.icon, + vibe, brand, seed, + })), + })); + const widths = parts.map((p) => Number(/]*\swidth="(\d+)"/.exec(p.svg)?.[1] ?? 0)); + const inners = parts.map((p) => p.svg.replace(/^]*>/, '').replace(/<\/svg>$/, '')); + const totalW = widths.reduce((a, b) => a + b, 0) + Math.max(0, widths.length - 1) * gap; + const height = 26; + let x = 0; + const children = inners.map((inner, i) => { + const t = `${inner}`; + x += widths[i] + gap; + return t; + }).join(''); + const svg = `${children}`; + return { + content: [{ type: 'text', text: svg }], + structuredContent: { svg, meta: { kind: 'github-badge-row', width: totalW, height } }, + }; + } catch (e) { + const kind = e instanceof GithubFetchError ? e.kind : 'unexpected'; + return { + content: [{ type: 'text', text: `github-badge-row error: ${kind}: ${(e as Error).message}` }], + structuredContent: { error: { kind, message: (e as Error).message } }, + isError: true, + }; + } + }, +}; + +badgeTools.push(renderGithubBadgeRowTool); +``` + +If a font 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 z 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 p;rlbM*T%t1TmPb6NH2fSdXMwuKLNcaeSrV~ literal 0 HcmV?d00001 diff --git a/comparisons/compare-sp5-github-row.png b/comparisons/compare-sp5-github-row.png new file mode 100644 index 0000000000000000000000000000000000000000..9c14cd38609b0d4da538eedb4051dc54b9fadc54 GIT binary patch literal 67958 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

qqqkQ~YipPg^PJc; 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%0Aj!}nTYqu;| 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ð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 literal 0 HcmV?d00001 diff --git a/mcp/scripts/compare-agent-surface.mjs b/mcp/scripts/compare-agent-surface.mjs index 7ec2e46..ab74ad1 100644 --- a/mcp/scripts/compare-agent-surface.mjs +++ b/mcp/scripts/compare-agent-surface.mjs @@ -17,6 +17,7 @@ import { Resvg } from '@resvg/resvg-js'; import { Surface, BarChart, + Badge, RoughPath, RoughRectangle, RoughCircle, @@ -222,8 +223,142 @@ function arrows() { ); } +// === SP4: badge primitive =================================================== +// Before, the agent had no first-class "badge" — to show a `stars: 42.3k` +// shields-style pill it had to compose a RoughRectangle + two RoughText calls, +// guess the geometry, and skip the icon entirely. After: one `Badge` element +// renders the pill with icon, divider, tone colour, and intrinsic width. +function badge() { + const W = 220; + const H = 60; + const vibe = { preset: 'ink', background: '#ffffff' }; + + const before = renderToSVGString( + h( + Surface, + { width: W, height: H, vibe, bare: true }, + // Hand-rolled pill: a rectangle + two text labels. No icon, no divider, + // no tone colour, width guessed. + h(RoughRectangle, { key: 'pill', x: 30, y: 18, width: 160, height: 26 }), + h(RoughText, { key: 'label', x: 70, y: 31, children: 'stars' }), + h(RoughText, { key: 'value', x: 150, y: 31, children: '42.3k' }), + ), + ); + + const after = renderToSVGString( + h( + 'svg', + { xmlns: 'http://www.w3.org/2000/svg', width: W, height: H, viewBox: `0 0 ${W} ${H}` }, + h('rect', { width: '100%', height: '100%', fill: '#ffffff' }), + h( + 'g', + { transform: 'translate(30, 17)' }, + h(Badge, { label: 'stars', value: '42.3k', tone: 'info', icon: 'star', seed: 1 }), + ), + ), + ); + + compare( + 'compare-sp4-badge', + W, + H, + before, + after, + 'rectangle + two text labels (no icon, no tone, guessed geometry)', + 'one `Badge`: icon + label + divider + tone colour, intrinsic width', + 'SP4: the `Badge` primitive renders a shields-style label/value pill with icon, divider, and tone in a single element.', + ); +} + +// === SP5: github badge row ================================================== +// Before, an agent showing repo stats had to fire N raw fetches and pipe the +// results into N independent rectangles. After: one `render-github-badge-row` +// call resolves a deduplicated set of metrics and lays them out as a single +// hand-drawn SVG row. The compare script uses a stubbed `GithubClient` (canned +// `RepoSummary` / `ReleaseSummary`) so no network is touched. +function githubRow() { + const stubRepo = { + stars: 124300, forks: 26800, openIssues: 1342, + license: 'MIT', language: 'JavaScript', + pushedAt: '2026-05-20T00:00:00Z', defaultBranch: 'canary', + }; + const stubRelease = { tag: 'v15.0.3', name: '15.0.3', publishedAt: '2026-05-15T00:00:00Z' }; + + // formatCount + per-metric resolution mirrored from badgeTools.ts so this + // script doesn't need to import compiled TS. Duplication is intentional — + // CLAUDE.md prefers self-contained compare scenes over a private-helper + // dependency edge from scripts/ -> src/. + const formatCount = (n) => { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1).replace(/\.0$/, '')}k`; + return String(n); + }; + const metricsResolved = [ + { label: 'stars', value: formatCount(stubRepo.stars), tone: 'info', icon: 'star' }, + { label: 'forks', value: formatCount(stubRepo.forks), tone: 'info', icon: 'fork' }, + { label: 'issues', value: formatCount(stubRepo.openIssues), + tone: stubRepo.openIssues > 0 ? 'warn' : 'success', icon: 'issue' }, + { label: 'release', value: stubRelease.tag, tone: 'info', icon: 'tag' }, + { label: 'license', value: stubRepo.license, tone: 'neutral', icon: 'license' }, + ]; + + // Compose a row by stacking Badge SVGs left-to-right (same approach as the + // real row tool's handler). + const parseWidth = (svg) => { + const m = /]*\swidth="(\d+(?:\.\d+)?)"/.exec(svg); + return m ? Math.round(Number(m[1])) : 0; + }; + const parts = metricsResolved.map((r) => renderToSVGString( + h(Badge, { label: r.label, value: r.value, tone: r.tone, icon: r.icon, seed: 2 }), + )); + const widths = parts.map(parseWidth); + const gap = 8; + const rowW = widths.reduce((a, b) => a + b, 0) + Math.max(0, widths.length - 1) * gap; + const rowH = 26; + let xOff = 0; + const inners = parts.map((svg, i) => { + let inner = svg.replace(/^]*>/, '').replace(/<\/svg>$/, ''); + if (i > 0) inner = inner.replace(/]*>[\s\S]*?<\/style>/g, ''); + const t = `${inner}`; + xOff += widths[i] + gap; + return t; + }).join(''); + const rowSvg = `${inners}`; + + const W = Math.max(rowW + 40, 560); + const H = 60; + + // "Before": only a single stars badge — the agent could call render-badge + // once but had no way to lay out a coordinated row. + const beforeSingle = renderToSVGString( + h(Badge, { label: 'stars', value: '124.3k', tone: 'info', icon: 'star', seed: 2 }), + ); + const before = renderToSVGString( + h( + 'svg', + { xmlns: 'http://www.w3.org/2000/svg', width: W, height: H, viewBox: `0 0 ${W} ${H}` }, + h('rect', { width: '100%', height: '100%', fill: '#ffffff' }), + ), + ).replace(/<\/svg>$/, `${beforeSingle.replace(/^]*>/, '').replace(/<\/svg>$/, '')}`); + + const after = `${rowSvg.replace(/^]*>/, '').replace(/<\/svg>$/, '')}`; + + compare( + 'compare-sp5-github-row', + W, + H, + before, + after, + 'one badge at a time — agent had to layout coordinate by hand', + '5 metrics, 1 deduped GitHub fetch round-trip, 1 SVG row', + 'SP5: `render-github-badge-row` resolves N metrics (stubbed here, no live HTTP) and lays them out as a single row.', + ); +} + console.log('Generating comparisons ->', OUT); presets(); shapes(); arrows(); +badge(); +githubRow(); console.log('done.'); From 997300f2408ca48a003fd31efaf67532d4df8ad7 Mon Sep 17 00:00:00 2001 From: bsevern Date: Thu, 28 May 2026 11:13:38 -0400 Subject: [PATCH 15/17] fix(mcp): structured error on render-badge + correct status for empty workflow runs Two findings from the whole-branch review: - render-badge handler now wraps renderToSVGString in try/catch and returns isError:true with kind:'unexpected', matching the structured-error contract used by render-github-badge and render-github-badge-row. - getWorkflowStatus's 'no runs' path was pairing kind:'not-found' with HTTP 200 (the request succeeded, the list was empty). Use 404 as the sentinel so the structured error doesn't read 'not-found' with a 2xx status. Co-Authored-By: Claude Opus 4.7 --- mcp/src/badgeTools.ts | 19 ++++++++++++++----- mcp/src/githubClient.ts | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/mcp/src/badgeTools.ts b/mcp/src/badgeTools.ts index 96004a0..dea6b23 100644 --- a/mcp/src/badgeTools.ts +++ b/mcp/src/badgeTools.ts @@ -50,11 +50,20 @@ export const renderBadgeTool: ToolDef = { outputSchema: renderOutputShape, }, handler: async (args) => { - const svg = renderToSVGString(createElement(Badge as any, args)); - return { - content: [{ type: 'text', text: svg }], - structuredContent: { svg, meta: { kind: 'badge', width: parseSvgWidth(svg), height: 26 } }, - }; + try { + const svg = renderToSVGString(createElement(Badge as any, args)); + return { + content: [{ type: 'text', text: svg }], + structuredContent: { svg, meta: { kind: 'badge', width: parseSvgWidth(svg), height: 26 } }, + }; + } catch (e) { + const message = (e as Error).message ?? 'render failed'; + return { + content: [{ type: 'text', text: `badge error: unexpected: ${message}` }], + structuredContent: { error: { kind: 'unexpected', message } }, + isError: true, + }; + } }, }; diff --git a/mcp/src/githubClient.ts b/mcp/src/githubClient.ts index c124acf..63c1a21 100644 --- a/mcp/src/githubClient.ts +++ b/mcp/src/githubClient.ts @@ -99,7 +99,7 @@ export function createGithubClient(opts: CreateGithubClientOptions = {}): Github return call(`https://api.github.com/repos/${o}/${r}/actions/runs?per_page=1${q}`, async (resp) => { const j = await resp.json() as any; const run = j.workflow_runs?.[0]; - if (!run) throw new GithubFetchError('not-found', resp.status, 'no runs'); + if (!run) throw new GithubFetchError('not-found', 404, 'no workflow runs'); return { name: run.name ?? '', conclusion: run.conclusion ?? 'unknown', From da7cce4e9ee58ac72fc71daf3e10f118d2ed4426 Mon Sep 17 00:00:00 2001 From: bsevern Date: Thu, 28 May 2026 11:14:39 -0400 Subject: [PATCH 16/17] fix(mcp): clean up unused import + typed mock.calls access --- mcp/src/githubClient.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mcp/src/githubClient.test.ts b/mcp/src/githubClient.test.ts index 7314764..5d7b7d4 100644 --- a/mcp/src/githubClient.test.ts +++ b/mcp/src/githubClient.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from 'vitest'; -import { createGithubClient, GithubFetchError } from './githubClient'; +import { createGithubClient } from './githubClient'; function fakeFetch(responses: Record }>) { return vi.fn(async (input: string | URL) => { @@ -72,7 +72,8 @@ describe('githubClient', () => { const fetch = vi.fn(async () => new Response(JSON.stringify({ stargazers_count: 1 }), { status: 200 })); const c = createGithubClient({ fetch: fetch as unknown as typeof globalThis.fetch, token: 'ghp_xxx' }); await c.getRepo('o', 'r'); - const headers = (fetch.mock.calls[0][1] as RequestInit | undefined)?.headers as Record; + const call = fetch.mock.calls[0] as unknown as [unknown, RequestInit | undefined]; + const headers = call[1]?.headers as Record; expect(headers.Authorization).toBe('Bearer ghp_xxx'); expect(headers['X-GitHub-Api-Version']).toBe('2022-11-28'); }); From 3ceba3f0c91405379f5bec6b3c3689d616201adc Mon Sep 17 00:00:00 2001 From: bsevern Date: Thu, 28 May 2026 11:18:31 -0400 Subject: [PATCH 17/17] style: prettier format badgeIcons.test.ts --- src/core/badgeIcons.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/badgeIcons.test.ts b/src/core/badgeIcons.test.ts index 3899092..0caa00d 100644 --- a/src/core/badgeIcons.test.ts +++ b/src/core/badgeIcons.test.ts @@ -21,8 +21,6 @@ describe('badgeIcons', () => { expect(BADGE_TONE_COLORS.danger).toMatch(/^#/); }); it('lists every supported tone literal', () => { - expect(new Set(BADGE_TONES)).toEqual( - new Set(['neutral', 'info', 'success', 'warn', 'danger']), - ); + expect(new Set(BADGE_TONES)).toEqual(new Set(['neutral', 'info', 'success', 'warn', 'danger'])); }); });