This file is the primary instruction set for agents working in this repository. Treat it as the source of truth for behavior, architecture context, testing standards, and completion criteria.
If another instruction file (for example .github/copilot-instructions.md) conflicts with this file, prefer this file unless that file explicitly states an exception.
Purview.ZodSharp is a high-performance schema validation library for C#, ported from TypeScript Zod. It uses struct-based rules and Span<T> to minimise allocations, and ships a compile-time source generator for maximum performance.
- The project is maintained at
purview-dev/zodsharp. - Public API namespaces are
ZodSharp.*; packages and assemblies are published under thePurview.ZodSharp.*package IDs. - Multi-targets
net8.0,net9.0,net10.0andnet11.0; the source generator targetsnetstandard2.0so it runs in any compiler host.
assets/ Package assets (purview-logo-light.png) referenced by Directory.Build.props
src/
ZodSharp.slnx Solution entry point
Directory.Build.props / Directory.Build.targets Purview.BuildSdk imports
src/
ZodSharp/ Core validation library + JSON Schema export (Z.ToJsonSchema)
SourceGenerators/ Compile-time [ZodSchema] generator (netstandard2.0, Roslyn)
SystemTextJson/ System.Text.Json integration + JSON Schema import (Z.FromJsonSchema)
NewtonsoftJson/ Newtonsoft.Json integration + JSON Schema import
AspNetCore/ ASP.NET Core ProblemDetails integration
Examples.CLI/ Usage examples
Benchmarks/ BenchmarkDotNet performance suite
tests/
*.UnitTests/ TUnit test projects (ZodSharp, SystemTextJson, NewtonsoftJson, AspNetCore, SourceGenerators)
cross-platform/ Output directory shared between the C# and TS cross-platform tests
src/ts/ TypeScript (Zod) schema + fixture generation; consumed by C# and vitest tests
tests/ts/ Vitest cross-platform tests
.agents/ Skills/prompts bundled from Purview.BuildSdk and Purview.SourceGeneratorFramework
The build is driven by Purview.BuildSdk, imported via src/Directory.Build.props (Sdk.props) and src/Directory.Build.targets (Sdk.targets). Key configuration:
NamespacePrefixisZodSharp.- Package version comes from
package.json(SDK version detection). - Package identities are
Purview.ZodSharp.*; the SDK derivesAssemblyName/PackageIdfromRootNamespaceunless overridden explicitly. - Package versions are managed centrally in
Directory.Packages.props(Central Package Management). global.jsonpins thePurview.BuildSdkMSBuild SDK version.- Pack layout for all packages is validated by
purview-build.json(PackValidation.RequiredContent).
Consult .agents/skills/sdk-configuration-reference/SKILL.md, .agents/skills/sdk-project-behavior-and-detection/SKILL.md, and .agents/skills/project-placement-defaults/SKILL.md before changing build/SDK configuration or adding/moving projects.
Build and test (see also the Justfile):
just build # dotnet build src/ZodSharp.slnx -c Debug
just test # dotnet test src/ZodSharp.slnx -c Debug --treenode-filter "/*/*/*/*"
just lint-check # dotnet csharpier check .
just lint-fix # dotnet csharpier format .
just pack # dotnet pack src/ZodSharp.slnx -c Debug -o artifacts
just perf-tests # dotnet run --project src/src/Benchmarks/Benchmarks.csproj -c Debug
just pipeline-pr # PR pipeline via Purview.Build (restore, build, lint, tests)
just pipeline-release # Release pipeline (pack, publish, GitHub release)TypeScript-side tooling uses Bun:
bun install
bun run test # vitest run (cross-platform TS tests)
bun run generate-fixtures # regenerates src/ts/fixtures/*.json from ZodFormatting is enforced with CSharpier; .editorconfig at the repo root defines style (tabs for code, 2-space for XML/JSON/YAML/markdown).
- Test framework is TUnit (SDK default;
TUnitandTUnit.Mockspackages inDirectory.Packages.props). Test projects live undersrc/tests/*.UnitTests. - Follow the repository's testing bar:
[Test]methods taking aCancellationToken cancellationTokenparameter.// Arrange,// Act,// Assertcomments.- Meaningful, descriptive method names (
Action_GivenCondition_ExpectedResult). - Treat work as incomplete until the relevant tests pass.
- For source generators, diagnostic analyzers, code fixes and refactorings, use the
Purview.SourceGeneratorFramework.Testing.TUnitbase classes (TUnitSourceGeneratorTestBase,TUnitDiagnosticAnalyzerTestBase,TUnitCodeFixTestBase,TUnitRefactoringTestBase) and assert withCodeQuery. Load.agents/skills/source-generator-testing/SKILL.mdand.agents/skills/tunit-test-authoring/SKILL.mdbefore writing or changing these tests.
The [ZodSchema] attribute (generated into the ZodSharp namespace) marks a class or struct for compile-time validator generation. The generator produces a {TypeName}Schema static class with Validate/Parse, optional value-first composition methods (ApplyAnd, ApplyOr, ApplyRefine, controlled by EnableComposition, default true), and optional IValidateOptions<T> support.
The generator and analyzer are built with Purview.SourceGeneratorFramework:
- Use
CodeWriteremission and incremental pipelines; load.agents/skills/source-generator-codewriter-modernization/SKILL.mdbefore implementing, reviewing, or refactoring generator/analyzer code. - Keep pipeline values immutable and value-equatable; never retain
ISymbol,Compilation,SemanticModel,IOperation,SyntaxNode, orLocationin pipeline models. - Use
ForAttributeWithMetadataNamefor attribute-driven discovery. - Test incrementally, not just generated text (see the skills above).
Validation rules follow a conventions analyzer (ValidationRuleConventionsAnalyzer, diagnostic ZODSGEN042): a source-declared rule (a type implementing ZodSharp.Core.IValidationRule<T>) must expose its error identity as public const string ErrorCode and const string MessageFormat constants, so tests can assert against the rule rather than duplicating literals. New built-in rules must follow the same convention; keep AnalyzerReleases.Shipped.md/AnalyzerReleases.Unshipped.md in sync when a diagnostic is added or changed.
The numeric rules are split by the constraint each family needs — pick the constraint that matches the operations, never widen a rule unnecessarily:
- Bound rules —
MinValueRule<T>,MaxValueRule<T>,GreaterThanRule<T>,LessThanRule<T>,GreaterThanOrEqualRule<T>,LessThanOrEqualRule<T>— are generic overT : IComparable<T>. Keep this constraint:ZodDatecloses the bound rules withDateTime, which does not implementINumber<T>. Do not change them toINumber<T>. - Arithmetic rules —
IntRule<T>,FiniteRule<T>,MultipleOfRule<T>,EvenRule<T>,OddRule<T>— are generic overT : INumber<T>so they close with any numeric type (int,long,double,decimal, …). SafeIntegerRuleis intentionallydouble-only, because "safe integer" is a JavaScriptNumberconcept (int.MinValue..int.MaxValue); do not make it generic.ZodNumbercloses the rules withdouble,ZodBigIntwithlong, andZodDatewithDateTime. Fluent methods onZodNumbertherefore useXxxRule<double>.- Any code that matches a rule by name (for example the JSON Schema converter) must tolerate the generic arity suffix: a generic rule's
Type.NameisIntRule\1, so match withStartsWith` rather than equality.
Generated rule attributes carry the rule's value parameters as a constructor: a parameter declared without a default is a required constructor argument (so it cannot be silently omitted), a parameter with a default keeps it, and message/code/origin stay properties. Attribute usages for required values are positional ([MinValue(3)], [Regex("^[a-z]+$")]); defaulted values may still be set by property name. When changing rule constructor parameters, update the affected generator tests and the wiki attribute tables.
Each package ships its own README.md, placed in the project's Sdk/ folder (for example src/src/ZodSharp/Sdk/README.md). The SDK's PurviewAutoSdkPack automatically maps Sdk/*.md to the package root and Sdk/buildTransitive/** to buildTransitive/, and the repo-root README.md is skipped when a package already packs its own README. Packages also ship purview-logo-light.png (linked via src/Directory.Build.props) and the core package ships buildTransitive/Purview.ZodSharp.props.
Keep purview-build.json's PackValidation requirements in sync with any packaging change.
Schemas and fixtures shared between TypeScript/Zod and C#/Purview.ZodSharp live under src/ts/. The fixture generator (bun run generate-fixtures) writes JSON fixtures and a manifest.json that the C# tests consume; the C# cross-platform tests write output to src/tests/cross-platform/output/, which the vitest tests (tests/ts/cross-platform.test.ts) read. Keep the TS UserSchema and the C# CrossPlatformUserSchema in sync.
Commits must follow Conventional Commits, enforced by Lefthook + Commitlint (see .config/lefthook.yml and commitlint.config.mts). Allowed types: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test.
just buildsucceeds with no new warnings/errors.- Relevant tests pass.
just lint-check(CSharpier) reports no formatting changes.- Generated code is deterministic and reviewable; no scope leaks in
CodeWriteroutput. - Packed packages match
purview-build.jsonPackValidation(including per-packageREADME.mdandpurview-logo-light.png).