Skip to content

feat(scripts): typecheck-skill-examples + fix 8 skill imports#957

Merged
bokelley merged 1 commit into
mainfrom
bokelley/skill-example-typecheck
Apr 25, 2026
Merged

feat(scripts): typecheck-skill-examples + fix 8 skill imports#957
bokelley merged 1 commit into
mainfrom
bokelley/skill-example-typecheck

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Adds scripts/typecheck-skill-examples.ts — a deterministic CI harness that extracts every fenced TypeScript block from skills/**/*.md, compiles each as a standalone module against the published @adcp/client types, and fails on new typecheck errors. Catches the same class of drift that landed PR #945 (server.registerTool) at the cost of a single CI step (~5s, $0).

The harness paid for itself on first run: it found a real bug across 8 skill files importing verifyApiKey, verifyBearer, anyOf, bridgeFromTestControllerStore from @adcp/client (top-level) — those symbols only exist under @adcp/client/server. The seller skill had it right; 7 others had drifted. Agents copy-pasting those examples got Module has no exported member at compile time.

This is dx-expert priority #2 from the matrix-v18 review. Priority #1 (scripts/conformance-replay.ts) shipped in #945.

What's in this PR

  • scripts/typecheck-skill-examples.ts — extractor, classifier, runner
  • scripts/skill-examples.baseline.json — captured 142 currently-known errors so CI ships green and ratchets down over time
  • package.jsonnpm run typecheck:skill-examples shortcut
  • 8 skill files: import fix to @adcp/client/server for the affected symbols
  • Patch changeset (skills are in package.json files so changes ship)

How the harness works

  1. Walks skills/**/*.md, extracts every ```typescript / ```ts block.
  2. Classifies each block as full-module or fragment. Full-module = has at least one import ... from '...' line and a top-level serve(, createAdcpServer(, createIdempotencyStore(, or createComplyController( call. Fragments (single handler shapes, object literals showing field structure) are skipped — trying to compile them standalone produces parser-error noise that drowns out real signal.
  3. Writes each full-module block to its own .cache/skill-examples/<flattened>.ts, generates a tsconfig.json resolving @adcp/client to the local dist/ (so we test the published surface), runs tsc --noEmit.
  4. Maps errors back to original .md:line:col for navigation. Compares each error against the baseline; new errors fail CI.

Skip markers: <!-- skill-example-skip: <reason> --> on the line before a fence skips that block. Use sparingly.

What's not in this PR

  • CI wiring. Same logic as chore(skills+scripts): drift fixes + conformance-replay harness (v0) #945: separate PR after this lands so reviewers see one thing at a time. Ready to wire into .github/workflows/ci.yml as a parallel job.
  • Baseline ratchet. 142 baselined errors are mostly placeholder identifiers in pseudo-code patterns (db, lookupKey, jwksUri) and untyped ctx.store.list returns. Tightening these is incremental work — do it as we slim skills (task Add Generated TypeScript Types for AdCP Tool Schemas (v0.2.3) #18).
  • Coverage of fragment blocks. 48 of 78 blocks are fragments and skipped. Cross-step context propagation (testing-expert v1 recommendation) and a "fragment-as-method-stub" wrapper would let us cover more, but trade-offs differ from the conformance-replay harness — defer.

Verification

# Build dist/ if you haven't
npm run build:lib

# Run the harness — should pass on a clean tree
npm run typecheck:skill-examples

# Expected output:
#   [typecheck-skill-examples] 30 compilable, 48 fragment, 0 marker-skip — of 78 total in 18 file(s)
#   ✓ no new errors (142 known baselined, 30 blocks compiled)

To verify it actually catches drift, intentionally re-introduce one of the bad imports (import { verifyApiKey } from '@adcp/client'; in a skill) and re-run — the harness will fail with a navigable error.

Test plan

  • npm run typecheck:skill-examples returns exit 0 with ✓ no new errors
  • npm run typecheck clean (project tsc unaffected)
  • Prettier clean
  • Bad-import drift is fixed (7 skills + deployment.md)
  • Reviewers run npm run typecheck:skill-examples locally to confirm green
  • CI: typecheck + unit tests + format + audit + changeset pass

Related

🤖 Generated with Claude Code

Adds scripts/typecheck-skill-examples.ts — extracts every fenced TypeScript
block from skills/**/*.md, compiles each as a standalone module against the
published @adcp/client types, and fails on new typecheck errors. Catches
the same drift class that landed PR #945 (server.registerTool) at the cost
of a single CI step (~5s, $0).

The harness immediately found a real bug across 8 skill files: imports of
verifyApiKey, verifyBearer, anyOf (and bridgeFromTestControllerStore in
seller) coming from '@adcp/client' (top-level) instead of '@adcp/client/server'.
The seller skill had it right; the other 7 had drifted. Agents copy-pasting
those examples would compile-fail with "Module has no exported member" —
the build-creative-agent skill matrix run that surfaced this in PR #945
explains why so many creative pairs hit auth=401 earlier.

How it works:
- Walks skills/**/*.md and extracts every ```typescript / ```ts block
- Classifies each as "full module" (has imports + a serve()/createAdcpServer()/
  createIdempotencyStore()/createComplyController() entry-point) or
  "fragment" (object literal, partial handler — won't parse standalone)
- Writes full modules to .cache/skill-examples/<flattened>.ts and runs tsc
  --noEmit against a generated tsconfig that resolves @adcp/client to dist/
  (testing the *published* surface)
- Reports errors mapped back to original .md file:line:column

Baseline mode (scripts/skill-examples.baseline.json):
- Captures the 142 currently-known errors so the script ships green
- Mostly placeholder identifiers in pseudo-code patterns (db, lookupKey,
  jwksUri) and untyped ctx.store.list returns. Real drift like the import
  bug above is NOT baselined — those got fixed first.
- New errors fail CI; baselined errors don't. Run with --update-baseline
  to tighten when fixes land.

Skip markers: <!-- skill-example-skip: <reason> --> on the line before a
``` fence skips that block. Use sparingly.

Run locally: npm run typecheck:skill-examples

This is dx-expert priority #2 from the matrix-v18 review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit d62da47 into main Apr 25, 2026
9 checks passed
bokelley added a commit that referenced this pull request Apr 25, 2026
…vendor pricing (#961)

* feat(types): strict discriminator unions — AssetInstance, sync rows, vendor pricing

The codegen emits strict per-variant interfaces (ImageAsset, CpmPricing,
etc.) but not the discriminated unions over them. Handlers that returned
Record<string, unknown> dodged the compile-time check and hit runtime
schema validation instead. This adds three hand-authored unions on top of
the generated bases so handler authors can opt into compile-time
discriminator checking.

Added:
- src/lib/types/asset-instances.ts — AssetInstance / CommonAssetInstance /
  AssetInstanceType discriminated unions over the 14 generated asset-type
  interfaces, keyed on asset_type
- src/lib/types/sync-rows.ts — SyncAccountsResponseRow + SyncGovernanceResponseRow
  named-type extractions of the inline accounts[] shapes, forcing the
  action / status literal-union discriminators at compile time
- src/lib/types/asset-instances.test.ts — type-level tests using
  // @ts-expect-error to lock in the constraints; if a future codegen
  regression loosens a discriminator, the now-unexpected error fails tsc

Re-export gaps closed in src/lib/index.ts:
- vendor-pricing: PerUnitPricing, CustomPricing, VendorPricing,
  VendorPricingOption (previously only CpmPricing /
  PercentOfMediaPricing / FlatFeePricing were exported)
- product-pricing: CPMPricingOption, VCPMPricingOption, CPCPricingOption,
  CPCVPricingOption, CPVPricingOption, CPPPricingOption,
  FlatRatePricingOption, TimeBasedPricingOption

This is dx-expert priority #3 from the matrix-v18 review (CI defenses #1
and #2 shipped in #945 and #957). Catches the same drift class the matrix
catches at runtime — discriminator omission, missing required fields,
wrong-shaped factory objects — at compile time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(types): code-review fixes — wire type-checks into typecheck

Code reviewer found three blockers in the previous commit:

1. The type-test file was excluded from typecheck. tsconfig.json and
   tsconfig.lib.json both excluded **/*.test.ts; npm test runs node's
   test runner against .test.js only. The @ts-expect-error safety net
   was a no-op. Fixed: rename to *.type-checks.ts so it's part of the
   normal typecheck. Add explicit exclude in tsconfig.lib.json so it
   doesn't ship in dist.

2. asset.format on VideoAsset doesn't exist (only container_format).
   Once typechecked, this would have failed. Fixed both the test file
   and the JSDoc example in asset-instances.ts.

3. Three @ts-expect-error directives were misplaced. Bare-const-assignment
   placement is fragile because TS reports object-literal-required-property
   errors at varying line/col positions. Restructured to use the function-
   return pattern: the directive lands immediately above the `return` line
   that triggers the error, which is what TS expects.

Smoke-tested: changing one of the negative-test field names to the
correct field causes the @ts-expect-error to become unused and tsc fails
with TS2578. The regression alarm is now real.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(types): expert-review fixes

Three reviewers (dx-expert, ad-tech-protocol-expert, javascript-protocol-
expert) green-lit shape but converged on six concrete improvements:

- Drop CommonAssetInstance. Excludes VAST which dominates third-party-
  served video (CTV, Magnite/FreeWheel) — calling it "common" misleads.
  Removing is the cleanest path; can re-add as HostedAssetInstance later
  if there's demand.
- Fix source-of-truth comment paths in asset-instances.ts and sync-rows.ts
  — header pointed to schemas/cache/{version}/bundled/... but the actual
  paths are schemas/cache/{version}/{creative,account}/...
- Fix changeset filename reference asset-instances.test.ts →
  asset-instances.type-checks.ts (ships into CHANGELOG verbatim).
- Add explicit exhaustiveness rail to describeAsset() switch via
  `const _exhaustive: never = asset` — current pattern only catches
  missing branches via noImplicitReturns, fragile to refactors that move
  returns out of switch arms.
- Drop redundant `void X;` lines in favor of file-level eslint disable
  + a single `_references` export holding all symbols.
- Surface SyncAccountsResponseRow in build-seller-agent SKILL.md
  alongside the existing pitfall about the action field. Without that,
  the type ships and nobody finds it.

Smoke-tested: typecheck clean, 5854/5862 tests pass, prettier clean,
typecheck-skill-examples baseline still 0 new errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request Apr 25, 2026
bokelley added a commit that referenced this pull request Apr 25, 2026
* feat(server): cross-domain specialism-declaration runtime check

When a domain handler group (creative, signals, brandRights) is wired
but capabilities.specialisms doesn't include any of that domain's
specialisms, createAdcpServer now logs an error explaining the drift
and pointing at the specialism IDs to add.

Drift class from matrix issue #785: ~30% of "agent built every tool
but storyboard reports no applicable tracks" cases trace to a missing
specialism claim. The conformance runner gates tracks on the claim, so
an agent with working tools but no claim grades as failing silently.

Logged via logger.error (matching the idempotency-disabled precedent)
rather than thrown — middleware-only test harnesses legitimately wire
handlers without declaring specialisms. Production agents will see the
warning in boot logs and conformance failure in the matrix.

mediaBuy is intentionally exempt: its specialism choices
(sales-non-guaranteed vs sales-guaranteed vs sales-broadcast-tv vs
sales-social etc.) are commercially significant and may legitimately
defer the declaration. The build-seller-agent skill already covers
the right declaration in its cross-cutting pitfalls section.

Tests in test/server-create-adcp-server.test.js lock the behavior
for creative, signals, brandRights, and the mediaBuy carve-out.

This is dx-expert priority #5; defenses #1#4 shipped in #945, #957,
#961, #970. Cheap-CI-defense ladder complete with this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server): expert-review fixes — governance + handler-detection

Two convergent findings from code-reviewer and dx-expert:

1. governance domain was missing from the cross-domain check despite
   five governance specialisms in the spec. Per protocol-expert: coarse
   domain-level check is fine for v0; per-handler-subgroup mapping
   (createPropertyList → property-lists, calibrateContent →
   content-standards, etc.) is a follow-up. The coarse rule catches
   "governance handlers wired, no claim at all" without false positives
   on legitimate cross-cutting reads.

2. Object.keys(config.creative).length > 0 returned true for
   { listCreativeFormats: undefined } — common in spread patterns like
   { ...maybeHandlers }. Replaced with isWired() helper that filters
   to function-valued keys.

Tests added for both:
- governance handlers without specialism → warning
- governance handlers with property-lists claim → no warning
- empty creative {} → no warning
- creative with all-undefined values → no warning

DX-expert verdict on the runtime-vs-typed trade-off: ship.
"Stop here on cheap CI defenses; pivot to skill slimming."

91 tests pass (4 new). Defense ladder #1#5 complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant