feat(scripts): add evaluate-library skill + supporting files

Adds the /evaluate-library skill runbook at .claude/skills/evaluate-library/
with SKILL.md (8-filter + 3-prompt protocol, collect-cheap-skip-expensive
ordering, trace-write step, skip sentinel), POLICY.md (ADR-022 summary
≤2 pages), TRACE-TEMPLATE.md (complete YAML frontmatter + 11 headings in
order), and EXAMPLES/ with one approved (clsx) and one rejected
(trpc-to-openapi, named-consumer: fail) worked trace.

Updates session-start.sh to surface the skill in session pointers.
The skill is auto-registered by the harness on SKILL.md creation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 05:45:43 +00:00
parent 2941122f66
commit b10ccba927
6 changed files with 646 additions and 1 deletions

View File

@@ -0,0 +1,91 @@
---
package: clsx
version: "^2.1.1"
tier: feature
decision: approved
date: 2026-05-14
deciders: [danijel, claude-sonnet-4-6]
adr: null
filter-results:
license: MIT
types: native
maintenance: active
boundary-fit: pass
shadow-check: pass
eu-residency: n/a
cve-scan: clean
named-consumer: pass
verification-commands:
- node -e "const p = JSON.parse(require('fs').readFileSync('./node_modules/clsx/package.json','utf8')); console.log(p.license)"
- ls node_modules/clsx/dist/clsx.d.ts
- npm view clsx time.modified
- pnpm audit --audit-level=moderate
accepted-cves: []
---
## Filter: license
<!-- Result: MIT -->
`package.json` declares `"license": "MIT"`. MIT is on the allowlist. Pass.
## Filter: types
<!-- Result: native -->
`clsx` ships its own `.d.ts` declarations at `dist/clsx.d.ts`. No `@types/clsx` package needed.
The TypeScript surface covers the full public API (`ClassValue`, overloads). Pass.
## Filter: maintenance
<!-- Result: active -->
Last npm release: `2.1.1` published 2024-02-06 (15 months ago at evaluation date — within the 18-month threshold). GitHub shows open PR/issue activity within the last 3 months. The library is small, intentionally stable, and actively maintained. Pass.
## Filter: boundary-fit
<!-- Result: pass -->
`clsx` is a pure string-concatenation utility. It imports nothing from Node.js or browser globals; it has zero transitive dependencies. Adding it to `packages/navigation` as a `feature`-tagged package introduces no boundary violations under ADR-006 or ADR-010. It does not import `@sentry/*`, `@opentelemetry/*`, or any core-reserved vendor SDK. Pass.
## Filter: shadow-check
<!-- Result: pass -->
The locked workspace must-haves are: `zod`, `inversify`, `payload`, `@trpc/server`, `superjson`, `reflect-metadata`. None of these perform CSS class-name composition. There is no existing utility in the workspace for this purpose. Pass.
## Filter: eu-residency
<!-- Result: n/a -->
`clsx` is a pure in-process string utility. It performs no network calls, transmits no user data, and has no SaaS endpoint. EU residency filter does not apply.
## Filter: cve-scan
<!-- Result: clean -->
`pnpm audit --audit-level=moderate` returns 0 vulnerabilities for `clsx@2.1.1` at evaluation time. No accepted advisories.
## Filter: named-consumer
<!-- Result: pass -->
Named consumer: `packages/navigation/src/ui/components/navigation-menu.tsx` — the `NavigationMenuLink` component must compute conditional class names for the active/inactive link state. Without `clsx` this is implemented as a ternary chain that becomes unreadable past three conditions. The component exists today; this is not a hypothetical future use case.
Secondary consumer: `packages/navigation/src/ui/components/mobile-nav.tsx` — open-state drawer overlay class computation. Both components are blocked on this adoption.
## Prompt: replaces
Replaces inline ternary chains like `` `base-class ${isActive ? 'active' : ''} ${isDisabled ? 'disabled' : ''}` ``. No library is being retired — this is a first-time adoption of a class-composition utility. No parallel adoption risk.
## Prompt: migration-cost-out
**Mechanical.** `clsx` is called only at the component leaf level. Removal means replacing `clsx(...)` calls with equivalent template-literal ternaries — a mechanical sed-style refactor bounded to the `packages/navigation/src/ui/` subtree. No data format dependencies, no vendor lock-in, no protocol coupling.
## Prompt: alternatives-considered
1. **`classnames`** — functional equivalent, MIT, widely used. Rejected in favour of `clsx` because `clsx` is the successor written by the same author with better TypeScript support and 2× faster benchmarks at comparable bundle size (330 B vs 440 B minzipped). `classnames` would also pass all eight filters; `clsx` is strictly preferable.
2. **Inline ternary chains (no library)** — the current approach. Adequate for one or two conditions; degrades rapidly past three. The `navigation-menu` component already has four conditional classes; this is the threshold where a utility library pays for itself. Rejected as the status quo.
3. **`tailwind-merge`** — superset of `clsx` that also de-duplicates conflicting Tailwind classes. Overkill for this use case (navigation components use a small, non-conflicting class set). Higher migration cost out (data-format dependency on Tailwind class semantics). Deferred.

View File

@@ -0,0 +1,99 @@
---
package: trpc-to-openapi
version: "^1.2.0"
tier: core
decision: rejected
date: 2026-05-14
deciders: [danijel, claude-sonnet-4-6]
adr: null
filter-results:
license: MIT
types: native
maintenance: active
boundary-fit: pass
shadow-check: pass
eu-residency: n/a
cve-scan: clean
named-consumer: fail
verification-commands:
- npm info trpc-to-openapi license
- npm info trpc-to-openapi time.modified
- pnpm audit --audit-level=moderate
accepted-cves: []
---
## Filter: license
<!-- Result: MIT -->
`package.json` declares `"license": "MIT"`. On the allowlist. Pass.
## Filter: types
<!-- Result: native -->
`trpc-to-openapi` ships TypeScript declarations (`.d.ts`) alongside the compiled output. Full API surface typed. Pass.
## Filter: maintenance
<!-- Result: active -->
Last npm release: `1.2.0` published within the past 12 months at evaluation date. GitHub shows active issue triage. Pass.
## Filter: boundary-fit
<!-- Result: pass -->
`trpc-to-openapi` would land in a `core`-tagged package alongside the tRPC router configuration. Core packages are permitted to hold tRPC-adjacent tooling. The library imports `@trpc/server` (already a workspace must-have) and standard `zod` types. No boundary violations under ADR-006 or ADR-010. Pass.
## Filter: shadow-check
<!-- Result: pass -->
No existing workspace library performs OpenAPI spec generation from tRPC routers. `trpc-to-openapi` does not duplicate any locked must-have. Pass.
## Filter: eu-residency
<!-- Result: n/a -->
`trpc-to-openapi` is a pure in-process code-generation utility. It produces an OpenAPI JSON spec at build time or request time; it transmits nothing to a vendor endpoint. EU residency filter does not apply.
## Filter: cve-scan
<!-- Result: clean -->
`pnpm audit --audit-level=moderate` returns 0 vulnerabilities at evaluation time. Pass.
## Filter: named-consumer
<!-- Result: fail -->
**No named consumer exists.**
The proposal arose during a 2026-05-14 grill session exploring whether to expose the tRPC router surface as a REST API for external consumers. The session established that **all current callers are TypeScript** and use `createCaller` directly — there are no HTTP REST clients calling the API, and no external consumers are blocked waiting for an OpenAPI spec.
The hypothetical consumers cited were:
- "External partners might want a REST API someday" — speculative; no partner is waiting.
- "A mobile client might prefer REST over tRPC-HTTP" — hypothetical; no mobile client exists.
- "OpenAPI docs improve DX for third-party integrations" — no third-party integration is in flight.
The grill-session question "who calls this code path today, or who is blocked waiting for it?" had the honest answer: nobody. The library would have shipped approximately 30 lines of `.meta({...})` annotations per router and a `superjson`-incompatible HTTP handler configuration in exchange for zero downstream consumers — pure carrying cost.
This trace exists as a permanent record per ADR-022 §4 so future agents do not re-evaluate `trpc-to-openapi` without first answering whether a named consumer now exists. If a concrete external integration is later planned, re-open this evaluation with the integration as the named consumer, re-run all eight filters, and write a new trace.
## Prompt: replaces
Nothing is being retired. The adoption would have been additive alongside the existing `createCaller` usage path.
## Prompt: migration-cost-out
**Hard.** Once `.meta({...})` annotations are added to tRPC procedures, they accumulate across routers over time. Removal requires stripping those annotations, deleting the OpenAPI spec generation step, and coordinating with any REST consumers that may have formed since adoption. The `superjson`-incompatible HTTP handler creates a parallel request path that would need to be decommissioned. Hard-rated because of the scattered annotation surface.
## Prompt: alternatives-considered
1. **`@anatine/zod-nestjs` + NestJS** — full REST framework alternative; overkill for a tRPC-native repo and would require replacing the tRPC layer entirely. Not a serious alternative for this use case.
2. **Custom OpenAPI spec, hand-authored** — maintain a `openapi.yaml` alongside the tRPC router. Zero runtime cost; no dependency; the spec is always exactly what consumers need. Viable if a named consumer materialises and the schema surface is stable. The correct path when named-consumer passes.
3. **No action (status quo)** — current approach: TypeScript callers use `createCaller`; no REST surface exposed. Correct given that no named consumer exists today. This is the chosen outcome.