Add dspack contract surface to cross-surface drift analysis#3
Merged
Conversation
Add a read-only dspack contract surface so `af design drift` can compare
component metadata across Figma, Storybook, and code against a declared
design-system contract (a dspack v0.1/v0.2 file committed to source control).
This is the first code-level integration between AF and the dspack ecosystem
(dspack / dspack-export / ds-mcp).
- shared: widen drift types with a DriftSurfaceId union ('figma' |
'storybook' | 'code' | 'contract'), optional contract surface/finding
members, ContractComponentData, and a 'contract' SurfaceType (descriptor
only). All additive — no-contract reports are shape-identical to before,
regression-guarded by test.
- shared: new contract.dspackPath config section (af.config.json).
- watcher: new contractSurface/ module — Ajv loader mirroring ds-mcp's
reference semantics with vendored v0.1/v0.2 schemas (pinned to dspack
commit 7008c3e), and read-only component/prop/enum-variant accessors.
Deliberately NOT a DesignAdapter and never registered in the adapter
registry.
- watcher: compareContractSurface() in the drift engine — presence, prop
inventory vs code, and enum-variant coverage vs code and Figma. Contract
declaring something a surface lacks is drift (warn); code having something
the contract lacks is a staleness signal (contract-staleness:* findings,
info) pointing at dspack-export regeneration. Existing comparison
functions untouched.
- CLI: `af design drift --dspack <file>`, contract counts toward the
2-surface availability gate, contract-as-inventory fallback when
Storybook is down, Contract status segment, one staleness remediation
hint per report.
- tests: 33 new (loader, mapping, analyzer, CLI e2e against the committed
shadcn-demo fixture); full watcher suite 1986/1986.
Out of scope by design: token drift, CI gating, reconciliation changes
(Phase 14F semantics untouched), write paths, dspack generation, ds-mcp
involvement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new read-only dspack contract surface to AF’s cross-surface drift analysis so af design drift can compare component inventories/props/enum variants across Figma, Storybook, code (AST), and an optional dspack JSON contract (v0.1/v0.2), with config + CLI flag support and comprehensive watcher tests.
Changes:
- Introduces
packages/watcher/src/contractSurface/with Ajv-based loader + vendored dspack schemas and mapping helpers to the drift engine vocabulary. - Extends the drift engine/types to include a
contractsurface (DriftSurfaceId) and adds contract-aware comparisons + CLI output/inventory behavior when Storybook/Figma are unavailable. - Adds config/docs/tests and watcher dependency updates to support the new surface end-to-end.
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates CLI command description to mention dspack contract surface. |
| pnpm-lock.yaml | Locks new watcher dependency (ajv). |
| packages/watcher/src/crossSurfaceDrift/normalize.ts | Widens surface ID typing to include contract. |
| packages/watcher/src/crossSurfaceDrift/cliCrossSurfaceDrift.ts | Adds --dspack flag/config loading, contract+code-only mode, and reporting. |
| packages/watcher/src/crossSurfaceDrift/analyze.ts | Adds contract snapshot construction and contract-vs-live comparisons. |
| packages/watcher/src/crossSurfaceDrift/tests/contractDrift.test.ts | Unit tests for contract drift semantics + no-contract regression guard. |
| packages/watcher/src/crossSurfaceDrift/tests/cliContractE2e.test.ts | CLI e2e tests for --dspack behavior with live surfaces down. |
| packages/watcher/src/contractSurface/types.ts | Defines minimal dspack document types used by the watcher. |
| packages/watcher/src/contractSurface/surface.ts | Maps dspack component entries to drift SurfaceProp + derived variants. |
| packages/watcher/src/contractSurface/schema/README.md | Documents schema provenance and pinning strategy. |
| packages/watcher/src/contractSurface/schema/dspack.v0.2.schema.json | Vendored dspack v0.2 JSON schema. |
| packages/watcher/src/contractSurface/schema/dspack.v0.1.schema.json | Vendored dspack v0.1 JSON schema. |
| packages/watcher/src/contractSurface/loadContract.ts | Implements file loader + Ajv validation for dspack v0.1/v0.2. |
| packages/watcher/src/contractSurface/index.ts | Exposes contract surface public API (load + accessors). |
| packages/watcher/src/contractSurface/tests/surface.test.ts | Tests mapping/lookup rules for contract surface accessors. |
| packages/watcher/src/contractSurface/tests/loadContract.test.ts | Tests loader acceptance/rejection paths and error messages. |
| packages/watcher/src/fixtures/contract/shadcn-demo.dspack.json | Adds a committed dspack fixture for self-contained tests. |
| packages/watcher/src/fixtures/contract/README.md | Documents contract fixture files used by tests. |
| packages/watcher/src/fixtures/contract/invalid-version.dspack.json | Fixture for unsupported dspack version rejection. |
| packages/watcher/src/fixtures/contract/invalid-schema.dspack.json | Fixture for schema violation rejection. |
| packages/watcher/package.json | Adds ajv dependency (watcher-only). |
| packages/shared/src/surfaceMetadata.ts | Extends surface taxonomy with surfaceType: 'contract'. |
| packages/shared/src/crossSurfaceDrift.ts | Adds contract surface identifiers, findings fields, and contract data types. |
| packages/shared/src/configLoader.ts | Adds default + validation/merge support for contract.dspackPath. |
| packages/shared/src/config.ts | Adds config schema for contract surface (contract.dspackPath). |
| packages/cli/src/commands/design.ts | Updates af design drift help text to mention contract + example flag. |
| docs/cli-reference.md | Documents af design drift + --dspack behavior and semantics. |
| docs/adapter-model.md | Documents contract surface and why it is not a DesignAdapter. |
| claude.md | Updates repo guidance to mention contract surface constraints. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
This was referenced Jun 12, 2026
ryandmonk
added a commit
that referenced
this pull request
Jun 12, 2026
…ew-followup fix: land dspack contract-surface review fixes that missed PR #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a read-only dspack contract surface to the cross-surface drift engine.
af design driftcan now compare component metadata across Figma, Storybook, and code against a declared design-system contract — a dspack v0.1/v0.2 file committed to source control:af design drift Button --dspack ./my-system.dspack.json # or set contract.dspackPath in af.config.jsonThis is the first code-level integration between AF and the dspack ecosystem, completing the loop:
dspack-export generate→ commit the snapshot → serve it to agents via ds-mcp → AF polices it over time.How it works
packages/watcher/src/contractSurface/(new): Ajv loader that validates against vendored v0.1/v0.2 schemas (pinned to dspack commit7008c3e, provenance inschema/README.md), mirroring ds-mcp's reference loader semantics — a file that loads in ds-mcp loads here. Read-only accessors map dspack component entries into the drift engine's prop/variant vocabulary (enum values → high-confidence constrained comparisons).compareContractSurface()covers component presence, prop inventory vs. code, and enum-variant coverage vs. code and Figma. The three pre-existing comparison functions are untouched.warn); code having something the contract lacks is a staleness signal (contract-staleness:*findings,info) with a one-line remediation hint pointing at dspack-export regeneration.Contract ✓/✗status segment.Boundaries preserved
DesignAdapterand is never registered in the adapter registry (the drift CLI treats the registry's first available adapter as the Figma surface). Rationale documented indocs/adapter-model.md.surfaceType: 'contract',read-only,external-non-authoritative— the descriptor layer carries no authority into reconciliation.Backward compatibility
Dormant by default: with no
--dspackflag and nocontractconfig key, no new code path executes. All type widenings are additive, and a regression-guard test asserts no-contract reports contain no contract keys, no contract findings, and only the three legacy surface keys.Testing
main()against the committed shadcn-demo fixture (copied from dspack-export's golden fixture, self-contained — no cross-repo reference at test time) and the realdemos/react-demo-appcode surfacepnpm typecheckcleanajv@^8.17.0(watcher only)Reviewer notes
contract-mismatchDriftTypeis defined but unused in this slice — reserved for value-level comparisons (e.g., default-value mismatch) in a follow-up.🤖 Generated with Claude Code