Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
148 commits
Select commit Hold shift + click to select a range
18782e9
docs: four parts join 2.0.0: Result.Catch, type-argument constraints,…
CaffeinatedCoder Oct 13, 2026
38b3ff0
spike: outline ResultCatch and TypeArgumentConstraints, the claims ea…
CaffeinatedCoder Oct 13, 2026
26f887a
docs: pin 2.0.0 to net10.0 and Metalama 2026.1, and order its build (…
CaffeinatedCoder Oct 13, 2026
c09b230
build: open 2.0.0, and move the package-validation baseline to 1.5.1
CaffeinatedCoder Oct 13, 2026
8d1fc4c
feat: the text guards, AsText() and AsIdentifier() on string, with Te…
CaffeinatedCoder Oct 13, 2026
9c7a314
spike: ResultCatch, run and answered: an exception enters a Result th…
CaffeinatedCoder Oct 13, 2026
4dbb592
feat: Result.Catch and CatchAsync, the one place an exception becomes…
CaffeinatedCoder Oct 13, 2026
5407327
feat: [RequireParameterlessConstructor] and CMTK0011, with the packag…
CaffeinatedCoder Oct 13, 2026
b04c40f
spike: TypeArgumentConstraints, run and answered: attributes constrai…
CaffeinatedCoder Oct 13, 2026
2963c92
feat: [Abstract], [Sealed], [Record], [Concrete] on type parameters, …
CaffeinatedCoder Oct 13, 2026
b8f4b4f
spike: MergePatch, run and answered: a merge-patch field under source…
CaffeinatedCoder Oct 13, 2026
c3c7a0c
feat: Patch<T>, a JSON Merge Patch field: absent, null or a value
CaffeinatedCoder Oct 13, 2026
979d8e5
spike: ClosedSets, run and answered: a closed set as a generated type…
CaffeinatedCoder Oct 13, 2026
780e6f8
build: move the plan to design/, so docs/ holds only what a consumer …
CaffeinatedCoder Oct 13, 2026
5d46fff
docs: topic pages under docs/, compiled like the READMEs, with a tabl…
CaffeinatedCoder Oct 13, 2026
09d8b78
feat: the closed-set family, IClosedSet<TSelf> generated by CodoMetis…
CaffeinatedCoder Oct 13, 2026
b514d10
fix: closed sets that compiled and did less, and Patch.IfPresent clea…
CaffeinatedCoder Oct 13, 2026
169a00a
fix: closed sets bound by position, refused by EF's compiled model, a…
CaffeinatedCoder Oct 13, 2026
5d2f583
fix: Result.Catch answers an HttpClient timeout as the error
CaffeinatedCoder Oct 13, 2026
b1bf428
fix: FirstOrNone on a query runs its predicate in the query, not on e…
CaffeinatedCoder Oct 13, 2026
9a00d2c
feat: a closed set parses through IParsable and binds from configuration
CaffeinatedCoder Oct 13, 2026
fbb7091
fix: Serilog logs a closed set as what it prints, not by its properties
CaffeinatedCoder Oct 13, 2026
a5d8e0c
feat: an Option continues asynchronously, and a pending one into a Re…
CaffeinatedCoder Oct 13, 2026
09f33e5
feat: SingleOrNone, ElementAtOrNone and Transpose, the options CMTK00…
CaffeinatedCoder Oct 13, 2026
3d2ef01
docs: merge the topic pages and the plan's move to design/ into 2.0.0
CaffeinatedCoder Oct 13, 2026
61b23b4
docs: the merge-patch and closed-set pages, compiled like the rest
CaffeinatedCoder Oct 13, 2026
ccd3787
feat: fault responses a client can act on, and a closed set as the fa…
CaffeinatedCoder Oct 13, 2026
07ffe04
feat: CMTK0003 is an Error, its gaps closed first, and the 1.5.1 base…
CaffeinatedCoder Oct 13, 2026
3457c2f
fix: Catch refuses a task in every form, the command form's value inc…
CaffeinatedCoder Oct 13, 2026
a99de55
fix: a patch's schema admits null beside every value its argument admits
CaffeinatedCoder Oct 13, 2026
a77bdda
fix: CMTK0010 reports a method group whose type arguments the delegat…
CaffeinatedCoder Oct 13, 2026
2fa0693
fix: a closed set's All is read-only, so no cast replaces a member
CaffeinatedCoder Oct 13, 2026
4008323
fix: AsText refuses a value that renders nothing, and a selector that…
CaffeinatedCoder Oct 13, 2026
59dff7f
docs: a 1.5-to-2.0 migration guide, the AOT query-helper limitation m…
CaffeinatedCoder Oct 13, 2026
501e2fd
feat: Choose, a sequential TraverseAsync, a patch's Map and Traverse,…
CaffeinatedCoder Oct 13, 2026
a8c87ee
feat: a closed set parses ignoring case when asked, and CMTK1021 refu…
CaffeinatedCoder Oct 13, 2026
2de3c9b
docs: the README knows 2.0.0, and compares closed sets with the smart…
CaffeinatedCoder Oct 13, 2026
78e6bb0
fix: a closed set in two files lists every member, a nested set is qu…
CaffeinatedCoder Oct 13, 2026
1e01a7e
fix: a closed set is a string enum as a parameter and a container's e…
CaffeinatedCoder Oct 13, 2026
d487fb4
fix: an index from the end on a query, a fault compared with a bare r…
CaffeinatedCoder Oct 13, 2026
37317de
fix: CMTK0010 skips doc-comment crefs, counts arrays sealed and follo…
CaffeinatedCoder Oct 13, 2026
f220e34
docs: the review's corrections, the package descriptions, and the pla…
CaffeinatedCoder Oct 13, 2026
4237abb
docs: the comparison moves to docs/comparison.md, the README keeps th…
CaffeinatedCoder Oct 13, 2026
cea8716
docs: design/plan.md becomes a map; the decisions are records, the ar…
CaffeinatedCoder Oct 13, 2026
bf67c3b
docs: no history page, since git and the changelog hold the phases; d…
CaffeinatedCoder Oct 13, 2026
731836a
test: an MVC closed-set parameter is the string enum, through the mod…
CaffeinatedCoder Oct 13, 2026
2e84368
docs: the FluentResults row says what a 2.0.0 Result carries beside o…
CaffeinatedCoder Oct 13, 2026
f8ff169
docs: the FluentResults row names what is still its alone, since a re…
CaffeinatedCoder Oct 13, 2026
eb51830
docs: a Cause on Fault<TFault> joins 2.1, as decision 52 and a roadma…
CaffeinatedCoder Oct 13, 2026
e9aedfc
docs: design notes for closed sets and Patch, page links for the old …
CaffeinatedCoder Oct 13, 2026
5ed845e
docs: correct what the second outside review found the pages say wron…
CaffeinatedCoder Oct 13, 2026
3a84bc3
fix: refuse an OpenAPI document where a nested type's component name …
CaffeinatedCoder Oct 13, 2026
2bfb1c7
fix: qualify a nested fault enum after its nesting chain, and check f…
CaffeinatedCoder Oct 13, 2026
4489c05
fix: name a type nested in a generic one in a Testing assertion's fai…
CaffeinatedCoder Oct 13, 2026
e303aa4
docs: the ASP.NET Core pages say what a problem carries, which except…
CaffeinatedCoder Oct 13, 2026
1d48edc
fix: a bare reason no longer converts to a Fault<TFault>
CaffeinatedCoder Oct 13, 2026
851e2f9
fix: a closed set refuses All and a name lookup while it is initialized
CaffeinatedCoder Oct 13, 2026
1ef30be
fix: an abstract type with a value-object marker is CMTK1022
CaffeinatedCoder Oct 13, 2026
50cd09b
fix: Patch<T>.IfValue is Tap, and Tap and IfPresent return the patch
CaffeinatedCoder Oct 13, 2026
e779dd3
fix: every async combinator refuses a delegate that returns no task
CaffeinatedCoder Oct 13, 2026
691cf94
docs: the diagnostic ranges include CMTK1022, and a fault's blank det…
CaffeinatedCoder Oct 13, 2026
780353b
fix: CMTK0003 sees ConfigureAwait and WaitAsync hand on the task they…
CaffeinatedCoder Oct 13, 2026
e269ec8
fix: CMTK0003 reports a call picked by ??, a dropped tuple and a drop…
CaffeinatedCoder Oct 13, 2026
e4361e9
fix: CMTK0010 checks extension blocks, generic attributes and inherit…
CaffeinatedCoder Oct 13, 2026
a0ddf61
fix: CMTK0010 reports a null-conditional call once, and neither a nam…
CaffeinatedCoder Oct 13, 2026
370414a
fix: CMTK0010 finds a base class's implementation through a type para…
CaffeinatedCoder Oct 13, 2026
579dc36
docs: the analyzer README says the two packages move together, and st…
CaffeinatedCoder Oct 13, 2026
51bfc20
docs: the generators README names both validation-free paths and stat…
CaffeinatedCoder Oct 13, 2026
e170af9
docs: decisions 53 to 55, a fault made by Fault.Of alone, an abstract…
CaffeinatedCoder Oct 13, 2026
2f15ff0
docs: lead with one set of rules carried from the request to the data…
CaffeinatedCoder Oct 13, 2026
63471e7
fix: .Value translates only on a column TypeKit's converter stores, a…
CaffeinatedCoder Oct 13, 2026
92dfe16
fix: a value-object key is generated at the root of a hierarchy, and …
CaffeinatedCoder Oct 13, 2026
e644a8a
fix: a value-object key over an unsigned integer, sbyte or char is ge…
CaffeinatedCoder Oct 13, 2026
717de54
fix: a query that compares or orders a value object with its own Comp…
CaffeinatedCoder Oct 13, 2026
da94806
docs: decisions 56 to 58, .Value translated only through TypeKit's co…
CaffeinatedCoder Oct 13, 2026
b14a95d
docs: the 2.0.0 changelog lists this review's changes, and 1.5.1 is w…
CaffeinatedCoder Oct 13, 2026
f8b3893
docs: the migration guide lists CMTK1022, the refusals and the CMTK00…
CaffeinatedCoder Oct 13, 2026
8af8136
fix: the problem schema a fault response refers to describes fault, a…
CaffeinatedCoder Oct 13, 2026
3ec4712
docs: a struct value object missing from an MVC query, header or form…
CaffeinatedCoder Oct 13, 2026
92e17a4
test: a group's and a controller's fault status, and a Fault's detail…
CaffeinatedCoder Oct 13, 2026
05da7ad
docs: MVC's 400 names a refused dictionary key, the one place a refus…
CaffeinatedCoder Oct 13, 2026
71a4565
fix: a chain of taps on a stored result or task no longer reports CMT…
CaffeinatedCoder Oct 13, 2026
107e5b8
fix: CMTK0012 reports a positional Match followed by another conditio…
CaffeinatedCoder Oct 13, 2026
7c30840
fix: CMTK0010 reports from generated code only where it maps to Razor…
CaffeinatedCoder Oct 13, 2026
44a31b1
fix: CMTK0011 never reports a struct, and says "an internal" constructor
CaffeinatedCoder Oct 13, 2026
1f8685a
test: the analyzer package is held to shipping its code-fix assembly
CaffeinatedCoder Oct 13, 2026
a7003bf
fix: a tap through WaitAsync on a held task no longer reports CMTK0003
CaffeinatedCoder Oct 13, 2026
84aaec2
fix: Result.Catch lets a cancellation that Wait() or .Result wraps th…
CaffeinatedCoder Oct 13, 2026
e5e1edd
docs: CatchAsync's error is what await throws, the first of a WhenAll…
CaffeinatedCoder Oct 13, 2026
bd4b17c
docs: an asynchronous callback runs where the task it waited for comp…
CaffeinatedCoder Oct 13, 2026
39633ff
fix: a value a Patch<T> cannot read is refused at the patched propert…
CaffeinatedCoder Oct 13, 2026
2a9f6c8
test: a blank fault detail and a null reason are refused, as Fault.Of…
CaffeinatedCoder Oct 13, 2026
da60907
fix: no string makes a text guard throw, or compose in quadratic time
CaffeinatedCoder Oct 13, 2026
0e450c9
fix: the text guards refuse the Khmer inherent vowels and the Khitan …
CaffeinatedCoder Oct 13, 2026
a6da5e3
docs: the text guards name everything they refuse, and that the form …
CaffeinatedCoder Oct 13, 2026
ddcb549
fix: AsText() throws in globalization-invariant mode rather than keep…
CaffeinatedCoder Oct 13, 2026
8f12e03
docs: the 2.0.0 changelog and the migration guide carry the review's …
CaffeinatedCoder Oct 13, 2026
d19972c
fix: Fault.Of(null) says a fault's reason is never null, not that an …
CaffeinatedCoder Oct 13, 2026
3bddba7
fix: a value-object key at the root of an abstract TPC hierarchy is g…
CaffeinatedCoder Oct 13, 2026
fe82f36
test: a value-object key that is also a foreign key is never generate…
CaffeinatedCoder Oct 13, 2026
2d8cc1f
test: ordering a hand-written CompareTo over a navigation is refused,…
CaffeinatedCoder Oct 13, 2026
719ee82
fix: a query filter that orders by a hand-written CompareTo is refuse…
CaffeinatedCoder Oct 13, 2026
a91ad7f
test: a facet configured on a closed set's column is kept on PostgreSQL
CaffeinatedCoder Oct 13, 2026
3737874
docs: UseTypeKit() says value objects are recognised by the generated…
CaffeinatedCoder Oct 13, 2026
deb6b71
test: a key over sbyte, ushort or uint is numbered by PostgreSQL's id…
CaffeinatedCoder Oct 13, 2026
8f6af8b
fix: ordering by a hand-written CompareTo through a key cast to objec…
CaffeinatedCoder Oct 13, 2026
d559dbd
docs: what a migration from a 1.5.1 model does to a key over sbyte, u…
CaffeinatedCoder Oct 13, 2026
a35f317
docs: AGENTS.md says when to use the code graph, what it measurably m…
CaffeinatedCoder Oct 13, 2026
cc57d09
feat: a group's or a controller's fault status applies where its faul…
CaffeinatedCoder Oct 13, 2026
50a4cff
docs: the changelog says where a group's or a controller's fault stat…
CaffeinatedCoder Oct 13, 2026
48db5e1
docs: the changelog says a query filter's hand-written ordering is re…
CaffeinatedCoder Oct 13, 2026
787ad30
fix: a value object over a closed set of its own project is refused (…
CaffeinatedCoder Oct 13, 2026
fade6bc
docs: say which text reads back, that number handling by attribute mi…
CaffeinatedCoder Oct 13, 2026
25abc08
test: pin a closed set's ignore-case parse to ordinal, every initiali…
CaffeinatedCoder Oct 13, 2026
53188ed
fix: a value object over object is refused (CMTK1005)
CaffeinatedCoder Oct 13, 2026
e4b751c
fix: declarations that failed as LAMA0611 or a bare CS0111 are refuse…
CaffeinatedCoder Oct 13, 2026
6805889
fix: a value object over a closed set of its own project is generated…
CaffeinatedCoder Oct 13, 2026
a4e2fd7
fix: an exception from Create leaves TryParse as it leaves Parse, wha…
CaffeinatedCoder Oct 13, 2026
bef5770
fix: a default instance over a reference type formats as an empty str…
CaffeinatedCoder Oct 13, 2026
0a00df7
docs: the discovery note says how closed sets are found, at compile t…
CaffeinatedCoder Oct 13, 2026
37da1c0
docs: the aspects note names a closed set as the wrapped type, not as…
CaffeinatedCoder Oct 13, 2026
88b074d
docs: the changelog and the migration guide carry the generators' ref…
CaffeinatedCoder Oct 13, 2026
7ce6bc4
docs: the package READMEs say a refused dictionary key is in the Json…
CaffeinatedCoder Oct 13, 2026
84e9776
fix: a value-object key over char is the application's to assign, and…
CaffeinatedCoder Oct 13, 2026
04dd9c2
docs: the changelog and the migration guide say which small-integer k…
CaffeinatedCoder Oct 13, 2026
94beab0
fix: CMTK1021 refuses two members whose Match parameters share a name…
CaffeinatedCoder Oct 13, 2026
649ee1e
spike: a member Metalama introduces carries no XML documentation, on …
CaffeinatedCoder Oct 13, 2026
390b0fc
fix: an inlined nullable closed set admits null in the OpenAPI docume…
CaffeinatedCoder Oct 13, 2026
3121d03
fix: a problem type is written as the URI it is, its escapes kept
CaffeinatedCoder Oct 13, 2026
800620f
docs: a fault's name reaches the client, and causes it must not tell …
CaffeinatedCoder Oct 13, 2026
9534751
fix: a nullable value object over an inlined enum admits null among t…
CaffeinatedCoder Oct 13, 2026
0d6ba1a
fix: CMTK0010 accepts an enum and a delegate as concrete, and a const…
CaffeinatedCoder Oct 13, 2026
aaae5b3
fix: the CMTK0011 code fix keeps the source as written, and is withhe…
CaffeinatedCoder Oct 13, 2026
28924be
docs: the 2.0.0 changelog section, which is the release notes, says e…
CaffeinatedCoder Oct 13, 2026
ee9214b
fix: a Patch<T>'s T is notnull, so every member hands the value as th…
CaffeinatedCoder Oct 13, 2026
189ab9f
fix: Choose(selector) reads a sequence whose elements may be null
CaffeinatedCoder Oct 13, 2026
9422c21
docs: a Func or a method group given to FirstOrNone on a query reads …
CaffeinatedCoder Oct 13, 2026
029af8b
docs: the roadmap keeps what is left for 2.1, and the changelog and g…
CaffeinatedCoder Oct 13, 2026
74f77d4
build: the test projects reference System.Security.Cryptography.Pkcs …
CaffeinatedCoder Oct 14, 2026
df4e806
fix: the CMTK0011 code fix is withheld where the added constructor's …
CaffeinatedCoder Oct 14, 2026
0bfa878
feat: CMTK0013 refuses a closed set's member written outside its decl…
CaffeinatedCoder Oct 14, 2026
79aaa97
test: the consumer smoke test holds CMTK0011 and CMTK0013 to the pack…
CaffeinatedCoder Oct 14, 2026
cdab810
fix: CMTK0010 no longer overflows the compiler's stack on a default i…
CaffeinatedCoder Oct 14, 2026
2e68a70
fix: a tap on a held result or task, returned as a plain Task or wait…
CaffeinatedCoder Oct 14, 2026
98f6aaf
build: SDK 10.0.402, Metalama 2026.1.30, TestHost 10.0.13 and JsonSch…
CaffeinatedCoder Oct 14, 2026
0835749
docs: CONTRIBUTING says what a machine needs: the pinned SDK from Mic…
CaffeinatedCoder Oct 14, 2026
92e3d6d
build: release 2.0.0
CaffeinatedCoder Oct 14, 2026
3821fc8
docs: 2.0.0 ships without a preview
CaffeinatedCoder Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
42 changes: 39 additions & 3 deletions .claude/skills/weaving-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: weaving-review
description: Review a change to CodoMetis.TypeKit for code that compiles and silently does less than it appears to — a type that is not generated, an analyzer that stops firing, a generated entry point that bypasses Create, an Option/Result that leaks its content or reads as success, a satellite that falls back to default behaviour. Use when changing Option/Result, an aspect, the transitive fabric, AspectOrder, the analyzer, the EF converter/convention/translators, the OpenAPI transformers, or package references — and before cutting a release.
description: Review a change to CodoMetis.TypeKit for code that compiles and silently does less than it appears to — a type that is not generated, an analyzer that stops firing, a generated entry point that bypasses Create, an Option/Result that leaks its content or reads as success, a satellite that falls back to default behaviour. Use when changing Option/Result, an aspect, the transitive fabric, AspectOrder, the analyzer, the EF converter/convention/translators, the OpenAPI transformers, closed sets, `Patch<T>`, the fault responses, the Serilog or Testing package, or package references — and before cutting a release.
---

# Weaving review
Expand All @@ -23,6 +23,8 @@ does, and what they expected.
nested type, a generic type, and a type in a project that reaches the package only
transitively.
- A type the fabric skips compiles cleanly. Is there a test or a diagnostic that would notice?
An **abstract** type with a marker, and a name that is a C# keyword (`namespace Shop.@event`, a
member `@class`), were both measured once as silent or as a Metalama crash rather than a CMTK error.
- A project that references only `CodoMetis.TypeKit` gets no aspects at all. Does CMTK0002
("marker without `.Generators`") still fire there?
- Is `AspectOrder` still complete? A new aspect class missing from it runs in an unspecified
Expand Down Expand Up @@ -69,15 +71,26 @@ likely.
match the snapshot? A translator that stops matching makes EF throw at best, or evaluate on the
client at worst.
- Is the comparer consistent with the type's equality?
- Does a translator check that the column's converter is **TypeKit's**? A user's own
`HasConversion` stores through their converter; a `.Value` translated as if TypeKit's were there
queries the wrong representation and answers wrong rows.
- Does a hand-written **`CompareTo`** reach the query? EF translates a comparison or an ordering to
the column's order, not the seam's.
- Is an integer key generated on add **wherever EF generates the wrapped type's**: every integer
type EF generates, the root of a hierarchy (TPH, TPT), on every provider the tests run?

## 5. OpenAPI

- For a value object used only as a property, as a list element, as a dictionary value and as a
route/query parameter: what schema does the document actually contain? Look at the emitted
JSON, not at whether the transformer ran. A schema transformer that registered, compiled and
never ran for a property-only type has been measured before (plan §7).
never ran for a property-only type has been measured before ([OpenAPI](../../../design/openapi.md)).
- Is the schema the **underlying type's** schema (format included: `uuid`, `date`, `int64`), and
does it match what the JSON converter writes?
- Can two types get **one component name**? A name built from a nesting chain (`Order.Status` →
`OrderStatus`) meets a top-level type of that name, and one component then describes both.
- Is every name a client matches on (a fault's qualified name, a component) unique for nested types
of one simple name?

## 6. Option and Result

Expand All @@ -88,7 +101,7 @@ likely.
a branch?
- Does every new combinator (`Map`, `Bind`, `Tap`, async variants) preserve the error or None
unchanged, and avoid invoking the callback on it?
- Is the change inside the non-goals in plan §9 (no Either, no effects, no LanguageExt drift)?
- Is the change inside the non-goals in [Option and Result](../../../design/option-result.md) (no Either, no effects, no LanguageExt drift)?

## 7. Packaging

Expand All @@ -99,3 +112,26 @@ likely.
- Do the EF and ASP.NET satellites depend on `CodoMetis.TypeKit` only, never on `.Generators`?
- Does the consumer smoke test build a project that references the packages **transitively**,
the case the fabric spike proved and a refactor could lose?

## 8. Closed sets, `Patch<T>`, faults, and the run-time satellites

- **Closed sets:** does anything generated that the set's own static field initializers can read
(`All`, `Parse`, `Name`) answer from a half-built set? Is every member found across partial files,
in declaration order? Does every entry point (JSON, EF, binding, the type converter, `IParsable`)
read ordinally, with ignoring case an overload the caller asks for?
- **`Patch<T>`:** does `default` still read as Absent, null as Null, and does a value object inside a
patch still go through its own converter? Is anything new registered so that Native AOT finds it
without `MakeGenericType`?
- **Faults:** can a `Fault<TFault>` be compared with a bare reason by any path (an operator, a
conversion, `Equals`, a collection's `Contains`, a test assertion), where a fault with a detail
then never equals it?
- **Serilog:** does a value object or a closed set log as what it prints, and does the policy honour
the logger's own limits (`ToMaximumStringLength`)?
- **Testing:** does an assertion's message print content, and does it survive every type name
(nested, generic, nested in a generic)?
- **Analyzer Errors freeze** ([decision 34](../../../design/adr/0034-errors-fixed-within-major-version-warnings.md)):
an Error that misses a form misses it for the whole major version, and one that reports correct
code breaks every consumer's build on upgrade. Check the neighbours of each form a rule reaches:
`??` beside `?:`, `ConfigureAwait`/`WaitAsync` on a held task beside `await held`, a tuple or a
dictionary beside a collection, an extension block, a generic attribute, a static abstract or
inherited interface implementation.
4 changes: 2 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
run: dotnet pack CodoMetis.TypeKit.slnx -c Release --no-build -o dist

# The smoke test publishes a consumer with Native AOT, which links with the platform's clang
# and zlib (docs/plan.md §11).
# and zlib (design/native-aot.md).
- name: Native AOT prerequisites
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends clang zlib1g-dev

Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
global-json-file: global.json

# The smoke test publishes a consumer with Native AOT, which links with the platform's clang
# and zlib (docs/plan.md §11).
# and zlib (design/native-aot.md).
- name: Native AOT prerequisites
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends clang zlib1g-dev

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ jobs:
run: dotnet pack CodoMetis.TypeKit.slnx -c Release --no-build -o dist

# The smoke test publishes a consumer with Native AOT, which links with the platform's clang
# and zlib (docs/plan.md §11).
# and zlib (design/native-aot.md).
- name: Native AOT prerequisites
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends clang zlib1g-dev

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ Thumbs.db

# Benchmark runs: the numbers worth keeping go into benchmarks/README.md
BenchmarkDotNet.Artifacts/

# The codebase-memory MCP server's local graph artifact: rebuilt per checkout, never committed
.codebase-memory/
65 changes: 56 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ that are written as one line and generated at compile time by Metalama.
- **Satellites** map value objects to EF Core columns, where `.Value` works in LINQ as it does in
memory, and to OpenAPI schemas.

The packages are being built phase by phase: **read [docs/plan.md](docs/plan.md) before changing
anything.** It holds the package map, the phase order and the decisions.
**Read [design/plan.md](design/plan.md) before changing anything.** It is the map of `design/`: the
[roadmap](design/roadmap.md), the [decision records](design/adr/README.md), one dated file per
decision with its evidence, and a design note per area.

This file is the canonical agent guide. There is no CLAUDE.md, on purpose.

Expand All @@ -40,12 +41,15 @@ This file is the canonical agent guide. There is no CLAUDE.md, on purpose.
- Central package management (`Directory.Packages.props`). The versions of shipping references
are floors for consumers, so raising one is a release decision. `spikes/` opts out of it and
pins what each spike measured.
- `test/CodoMetis.TypeKit.Samples` holds every C# block of the READMEs as compiled code, between
`// sample: {README}/{name}` and `// end sample`, beside tests that check the outputs the blocks
state in comments. `ReadmeSampleTests` (conventions) holds each block to exactly one region.
- `test/CodoMetis.TypeKit.Samples` holds every C# block of the READMEs and of the `docs/` pages as
compiled code, between `// sample: {page}/{name}` and `// end sample`, beside tests that check the
outputs the blocks state in comments. `{page}` is `README`, a package id, or a docs page's file name
without `.md`. `ReadmeSampleTests` (conventions) holds each block to exactly one region.
- `spikes/` holds experiments that decided something. Each one has a README with the question,
the answer and the evidence. Spikes are not in the main solution and are never packed.
- `docs/` holds the plan and the design notes.
- `docs/` holds the topic pages a consumer reads, one per topic, each linked from the root README's
"Documentation" table; `design/` holds the roadmap, the decision records and the design notes,
which no test compiles but whose links the conventions check.

Never hard-code a positional path (`../../`) to reach the repo root. Walk up to the
`CodoMetis.TypeKit.slnx` marker instead.
Expand All @@ -63,6 +67,45 @@ The SDK is pinned in `global.json` (10.0.4xx band, `latestPatch`). Tests run on
Platform (xunit.v3 4.x), so `dotnet test` takes `--solution`/`--project`, and TRX output is
`--report-xunit-trx`. The build treats warnings as errors, including CS1591 on shipping projects.

## Finding code

The codebase-memory MCP server keeps a code graph of this repository, the project
`CodoMetis.TypeKit`. It is the fastest way into the code, and it misses whole kinds of reference
here, so it leads and the source decides.

- **The graph first** to locate a symbol, list a type's implementers, trace callers and callees, and
size a change's impact (`search_graph`, `trace_path`, `query_graph`, `get_code_snippet`).
- **Then the source and its tests**, read before concluding or editing. An edge is a lead.
- **Text search** (`git grep`, `search_code`) for literals, messages, diagnostic ids,
configuration, documentation, and what the generators emit.
- **An empty result proves nothing.** Fall back to text search when the graph is unavailable, stale
or silent, and never conclude "unused", "no other caller" or "safe to change" from it alone.
Measured on 2026-10-13, the graph:
- finds types, interfaces, enums and ordinary methods, a type's implementers through generic
interfaces (84 of the Samples' 88 value objects), and the callers of an ordinary method across
files;
- has no node for a member of a C# 14 `extension(...)` block, which holds most of the `Option` and
`Result` extensions (`Choose`, `FirstOrNone`, `Transpose`, `Sequence`, the continuations of a
`Task<Result<...>>`) and `AsText`, and puts the calls inside one on the file;
- has nothing Metalama generates: none of the tests' 222 `.From(` calls has an edge, nor has a call
to `TryFrom`, `FromKnownGood`, `Parse` or a converter, and a template an aspect introduces by
`nameof` looks unreferenced. Read woven code as the Generators README's "Reading the generated
code" says;
- misses a reference that is not a call: a DI registration, a type argument, `typeof`, an attribute;
- resolves a call on a generic instance poorly (`Option<T>.Match` has one resolved caller, where
the repository writes `.Match(` about 200 times across its types) and guesses wrong callers: a low-confidence `suffix_match` edge, or a same-named method of another API
(Metalama's `Diagnostics.Report` listed as calling `GeneratedCode.Report`). Overloads share one
node.
- **Refresh it**, since it does not follow the working tree: `index_repository` with the repository
root as `repo_path` and `name: "CodoMetis.TypeKit"`, at the start of a session and after a branch
switch, a rebase, a merge or a pull (about 6 s). Without `name` it starts a second project under a
derived name. `index_status` reports the checkout's HEAD, not the commit the graph was built from,
so refresh rather than check.
- It indexes `spikes/` and leaves out `.claude/`, `bin` and `obj`, so an agent in a worktree under
`.claude/worktrees/` reads the main checkout's graph, not its own: index the worktree under a name
of its own and delete that project when done, or search the source. `.codebase-memory/`, the
tool's local artifact, is ignored by git.

## Rules that are easy to break

- **`CodoMetis.TypeKit` stays Metalama-free.** Nothing in the base package may reference
Expand Down Expand Up @@ -91,7 +134,7 @@ Platform (xunit.v3 4.x), so `dotnet test` takes `--solution`/`--project`, and TR
`JsonSerializer` call that takes only options, in the packages or in woven code: a typed call
(`GeneratedValueObjectAttribute.Accept`, `GeneratedJson.TypeInfo`) or an interface instead. An
`[UnconditionalSuppressMessage]` needs a measured reason, and the smoke test's `aot` consumer is
the measurement (docs/plan.md §11).
the measurement ([design/native-aot.md](design/native-aot.md)).
- **Aspects stay internal.** Their public surface is what they generate, and nothing else.
- **A value object's JSON is its wrapped type's, byte for byte**, under whatever options the
application uses, values and dictionary keys alike (`JsonParityTests`). Never write or read the
Expand All @@ -104,18 +147,22 @@ Platform (xunit.v3 4.x), so `dotnet test` takes `--solution`/`--project`, and TR
introduce is CMTK1011. A new generated member joins that check, or a hand-written one is kept silently.
- **Diagnostic ids (`CMTK`) are public contract.** Never renumber a shipped rule, and never ship a
new rule at Error severity in a minor version.
- **A README's C# block is compiled code.** Change it and its region in
- **A README's or a docs page's C# block is compiled code.** Change it and its region in
`test/CodoMetis.TypeKit.Samples` together, or `ReadmeSampleTests` fails; the region's scaffolding
(the types and variables it assumes) stays outside the markers. An output a block states in a comment
(SQL, JSON, a string) is read from the README by the test beside the region, so correct the comment,
never the test. A block that cannot compile on purpose says `<!-- not compiled: {reason} -->` above
its fence; an analyzer diagnostic in a comment is no reason, the sample suppresses it around the region.
A page in `docs/` is compiled by being there and must be linked from the root README's
"Documentation" table; a package README is the reference for its package, a docs page the guide for
a topic, and the same code block is never in both.
- **Silent is worse than broken.** A value object that compiles without being generated, an
analyzer that stops firing, or a converter that materialises `default` are this repo's
characteristic failures. Prefer a diagnostic or an exception to a plausible result.

## Workflow
1. Read `docs/plan.md` and the relevant spike README before starting.
1. Read `design/plan.md`, the roadmap, the decision records and the design note for the area you
touch, and the relevant spike README, before starting. A decision is changed by a new record.
2. Run `dotnet test` after each change.
3. **Prove every fix by reverting it.** See `.claude/skills/verify-the-guard`. The same applies to a
new analyzer test or a convention test: seed the defect it claims to catch.
Expand Down
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,51 @@ A version's heading reads `## <version> — Unreleased` until the release is pre
the release date. The release workflow refuses a tag whose section still says `Unreleased`, and the
section is the GitHub release's notes.

## 2.0.0 — 2026-10-14

Still `net10.0` and Metalama 2026.1. What builds or runs differently, and what to write instead:
[Migrating from 1.5 to 2.0](https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/docs/migrating-to-2.md).

### Breaking

- CMTK0003, a dropped `Result` or `Option`, is an Error, and reports more forms (`Wait()`, loops that never read, `??`, tuples).
- `FirstOrNone`/`FirstOrError` on an `IQueryable` run in the query (a `Func` variable still reads every row); `LastOrNone`/`LastOrError` on a query no longer compile.
- CMTK1022 refuses an abstract value object; CMTK1005 refuses one over `object` or `System.Enum`, or named as a generated member.
- A generated `TryParse` lets an exception from `Create` through instead of answering `false`.
- EF Core refuses `.Value` on a column with the application's own converter, and ordering by a hand-written `CompareTo`, in queries and query filters.
- EF Core: value-object keys over `sbyte`, `ushort`, `uint` and `ulong` are generated as EF generates the plain type, so a migration may alter the column.
- OpenAPI: a component name claimed by two types throws.
- Async combinators throw `ArgumentNullException` for a delegate that returns no task.
- `FaultProblem<TFault>` takes any `notnull` fault type.

### Added

- **Closed sets** (`IClosedSet<TSelf>`): a fixed set of named instances with `Match`, `Parse`, JSON, EF Core, OpenAPI and Serilog support; CMTK1013 to CMTK1021 refuse what cannot be generated.
- **Fault responses a client can act on**: `Fault<TFault>`, fault sets with a kind per member, `FaultResponse`, `WithFaultStatus`/`[FaultStatus<T>]` per endpoint, group or controller, `ToHttpResult(success)` and `IFaultFeature`.
- **`Patch<T>`**: a JSON Merge Patch field (absent, null or a value; `T` is `notnull`, so `Patch<string>` is a clearable string), with `Map`, `Traverse`, OpenAPI and test assertions.
- **`Result.Catch`/`CatchAsync`**: an exception as a result; a cancellation passes through, a timeout is the error.
- **Text guards**: `AsText()` and `AsIdentifier()` trim, compose to NFC (`AsText`) and refuse invisible characters.
- **Type constraints**: `[Abstract]`, `[Sealed]`, `[Record]`, `[Concrete]` (CMTK0010) and `[RequireParameterlessConstructor]` (CMTK0011, with a code fix).
- **CMTK0012**: a closed set's `Match` called by position, with a code fix.
- **CMTK0013**: a closed set's member written outside its declaration, after it was named.
- **`Option`**: async steps and `Task<Option<T>>` continuations, `SingleOrNone`, `ElementAtOrNone`, `Choose`, `Transpose`, `Traverse`; EF Core's `FirstOrNoneAsync` and `SingleOrNoneAsync`.
- **`TraverseAsync`** on a sequence: one at a time, cancellable.
- **Docs**: topic pages under `docs/`, every code block compiled.

### Changed

- The OpenAPI problem schema describes `fault`: regenerate clients.
- CMTK0003 no longer reports a chain of taps on a result the caller holds.
- EF Core: a `char` key stays the application's to assign; a default `ulong` key throws `OverflowException` at `Add` (use `ValueGeneratedNever()`).
- Testing failures name a nested type with its containing types.
- `CodoMetis.TypeKit.Generators` depends on `Metalama.Framework` 2026.1.30 or later (was 2026.1.29).

### Fixed

- A value object marked `[Obsolete(error: true)]` builds.
- A default value object over a reference type formats as `""` instead of throwing.
- Where the host inlines enums, a nullable value object over one admits null in the OpenAPI document.

## 1.5.1 — 2026-10-11

### Fixed
Expand Down
Loading
Loading