Skip to main content

changing-public-api

Add, change or remove public API in the libphonenumber-csharp or libphonenumber-csharp.extensions packages. Use when introducing a public type or member, changing a signature, deciding which of the two packages new API belongs in, or when a build fails on package validation (PKV/CP diagnostics), TFM parity, trim/AOT analysis, or a missing netstandard2.0 fallback.

Jump to install

Source facts

Repository
twcclegg/libphonenumber-csharp
Last source activity
September 17, 2026 at 01:20
Detected SKILL.md language
English
Stars
920
Forks
157

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
changing-public-api
description
Add, change or remove public API in the libphonenumber-csharp or libphonenumber-csharp.extensions packages. Use when introducing a public type or member, changing a signature, deciding which of the two packages new API belongs in, or when a build fails on package validation (PKV/CP diagnostics), TFM parity, trim/AOT analysis, or a missing netstandard2.0 fallback.
# Changing the public surface Both packable projects have `EnablePackageValidation` on, so the build itself is the gate: an API that is inconsistent across target frameworks, or a breaking change against the published baseline, fails the build rather than shipping. ## First: is a new member authorised? **Adding a public member to `csharp/PhoneNumbers/` needs explicit sign-off from the user, as its own decision.** Package validation only catches breaks against the published baseline — never additions, so nothing automated will object. "It matches an existing pattern" is not permission — `IMetadataLoader`/`SetMetadataLoader` and `PrewarmRegionsAsync` were added on exactly that reasoning and both were regretted. Ask, every time. `PhoneNumbers.Extensions` is exempt and exists to grow. This is the same rule `AGENTS.md` states; keep the two copies identical. A task like "fix this perf issue" does not authorise a new public member as a side effect. If the answer is no, an `internal` member or a helper in `PhoneNumbers.Extensions` usually serves. ## Which package? - **`csharp/PhoneNumbers/`** (`libphonenumber-csharp`) — anything with a counterpart in the Java library. Keep the Java name and shape; see the `porting-upstream-changes` skill. - **`csharp/PhoneNumbers.Extensions/`** (`libphonenumber-csharp.extensions`) — C#-idiomatic helpers with *no* Java counterpart: the `TryParse`-style `PhoneNumber` static class, the `System.Text.Json` converter and source-generated context, `PhoneNumberAttribute`, `PhoneNumberTypeConverter`. Putting these in the core package makes every future upstream sync a diff to reconcile, so new non-ported conveniences belong here. ## The constraints Both projects target `netstandard2.0;net8.0;net10.0`. - **TFM parity is mandatory.** A member that exists on `net10.0` but not `netstandard2.0` fails package validation. If the implementation needs a modern BCL API, ship the same signature on every target with a fallback implementation — for `PhoneNumberUtil` that means the modern half in `PhoneNumberUtil.net.cs` and the netstandard2.0 half in `PhoneNumberUtil.netstandard.cs`. - **Trim and AOT.** `IsAotCompatible` is set on the modern TFMs and warnings are errors, so no reflection or dynamic code may be reachable from anything public. The JSON support in Extensions goes through a source-generated `JsonSerializerContext` for this reason. - **Nullable reference types** are enabled on every target but `netstandard2.0`. Annotate regardless — an unannotated new API is inconsistent with everything around it. - **Breaking changes.** Each pack is compared against `PackageValidationBaselineVersion` in the csproj (a real nuget.org release — restore downloads it). If a break is genuinely intended, that is a maintainer decision about the next version — raise it rather than suppressing the diagnostic or bumping the baseline to hide it. Moving the baseline forward *after* a release ships is a separate, routine chore: bump both csprojs together to the released version. - **Extensions is analysed more strictly** — `AnalysisMode=AllEnabledByDefault` there versus the default rule set in the core library, so code that builds in `PhoneNumbers/` may not in `PhoneNumbers.Extensions/`. - **XML docs.** `CS1591` (missing doc comment) is suppressed repo-wide, so nothing forces you to document a new member. Document it anyway; it is the only reference consumers get. ## Verify ```bash dotnet build csharp --no-restore # catches TFM parity, trim/AOT, validation dotnet test csharp/PhoneNumbers.slnx -p:TargetFrameworks=net10.0 dotnet pack -c Release csharp/PhoneNumbers # publish_nuget.yml adds -p:VersionPrefix=<tag minus "v"> dotnet pack -c Release csharp/PhoneNumbers.Extensions ``` `dotnet build` matters more than `dotnet test` here: the tests only run on `net8.0`/`net10.0`, so a netstandard2.0 gap is invisible to them and shows up only in the build. ## Test the new API Public entry points get hostile input from real callers. Beyond the normal ported tests, add the new surface to `csharp/PhoneNumbers.Test/TestPublicApiRobustness.cs` (curated hostile strings) and, where an invariant is worth asserting, `TestPhoneNumberProperties.cs` (FsCheck). Both run against the real shipped metadata, and that pair has already caught an unbounded `stackalloc` and a `KeyNotFoundException` that the well-formed-input suite missed. The `writing-tests` skill has the detail.
View on GitHub