Intent
packages/contract exists to be the shared review contract, and its description says so: "Shared review contract (types + sanitizers) between the codesema CLI and codesema.com." The CLI consumes it (packages/cli/src/contract.ts is a one-line re-export).
The web UI does not. packages/web/package.json declares no dependency on @codesema/contract at all; instead packages/web/src/types.ts:43 redeclares ReviewRecord by hand, with the mirroring stated as a comment at :74:
// Mirrors packages/cli/src/serve.ts and partial.ts.
That mirror has already drifted. files_reviewed?: ReviewedFile[] exists in the contract (packages/contract/src/index.ts:98), is produced by the CLI (packages/cli/src/dual.ts:404) and is recomputed by the contract's own remapping logic (:731-744), but no such field exists in the web type. The typechecker cannot see the divergence, because there is no dependency edge for it to check.
The result is a contract that constrains the external consumer (codesema.com) while leaving the in-repo consumer free to drift — which is the reverse of what a shared contract is for, since the web UI is the one shipped inside the CLI tarball.
Scope
In scope:
- Review-shaped types in
packages/web/src/types.ts that mirror contract types.
- The missing dependency edge from
packages/web to @codesema/contract.
Out of scope:
- Web-only view types with no contract counterpart (component props, board state).
- The SSE frame types, unless they turn out to mirror contract types too.
- Any change to the contract's own shape.
Approach
Add @codesema/contract as a workspace dependency of packages/web, then delete the hand-mirrored declarations in favour of imports, one type at a time. Where the web genuinely needs a narrower view, derive it from the contract type (Pick/Omit) so the typechecker still fails when the source moves.
MODIFIED Requirements
Requirement: Review types MUST have a single source
The system SHALL define review-shaped types once, in @codesema/contract, and both the CLI and the web UI SHALL derive their types from it rather than restate them.
Scenario: a contract field is added
- GIVEN a new optional field added to the review type in
@codesema/contract
- WHEN the web package is typechecked
- THEN the web sees the new field without any manual edit to
packages/web/src/types.ts
Scenario: a contract field is renamed
- GIVEN a field renamed in
@codesema/contract
- WHEN the web package is typechecked
- THEN the build fails at every web site that referenced the old name
Tasks
1. Wire the edge
2. Remove the mirror
3. Close the known drift
Intent
packages/contractexists to be the shared review contract, and its description says so: "Shared review contract (types + sanitizers) between the codesema CLI and codesema.com." The CLI consumes it (packages/cli/src/contract.tsis a one-line re-export).The web UI does not.
packages/web/package.jsondeclares no dependency on@codesema/contractat all; insteadpackages/web/src/types.ts:43redeclaresReviewRecordby hand, with the mirroring stated as a comment at:74:// Mirrors packages/cli/src/serve.ts and partial.ts.That mirror has already drifted.
files_reviewed?: ReviewedFile[]exists in the contract (packages/contract/src/index.ts:98), is produced by the CLI (packages/cli/src/dual.ts:404) and is recomputed by the contract's own remapping logic (:731-744), but no such field exists in the web type. The typechecker cannot see the divergence, because there is no dependency edge for it to check.The result is a contract that constrains the external consumer (codesema.com) while leaving the in-repo consumer free to drift — which is the reverse of what a shared contract is for, since the web UI is the one shipped inside the CLI tarball.
Scope
In scope:
packages/web/src/types.tsthat mirror contract types.packages/webto@codesema/contract.Out of scope:
Approach
Add
@codesema/contractas a workspace dependency ofpackages/web, then delete the hand-mirrored declarations in favour of imports, one type at a time. Where the web genuinely needs a narrower view, derive it from the contract type (Pick/Omit) so the typechecker still fails when the source moves.MODIFIED Requirements
Requirement: Review types MUST have a single source
The system SHALL define review-shaped types once, in
@codesema/contract, and both the CLI and the web UI SHALL derive their types from it rather than restate them.Scenario: a contract field is added
@codesema/contractpackages/web/src/types.tsScenario: a contract field is renamed
@codesema/contractTasks
1. Wire the edge
@codesema/contractas aworkspace:*dependency ofpackages/web2. Remove the mirror
packages/web/src/types.tsthat mirror contract typesPick/Omitderived from it// Mirrors ...comment once nothing mirrors3. Close the known drift
files_reviewed, or explicitly omit it via a derived type