# p4api.net/ — managed wrapper patterns

Scoped instructions for the C# wrapper assembly (`Perforce.P4` namespace). Loads when you read/edit files under `p4api.net/p4api.net/`.

Project overview, build/test recipes, and cross-cutting rules live in the **root `CLAUDE.md`** and `.claude/rules/`.

## Layered architecture

Flow is **Repository → P4Server → P4Bridge (PInvoke) → libp4bridge → Perforce C++ API**. Do not skip layers.

1. **Repository** (`Repository.*.cs`) — high-level CRUD over Perforce specs. One file per spec type (`Repository.Client.cs`, `Repository.Changelist.cs`, `Repository.Depot.cs`, `Repository.Job.cs`, etc.).
2. **P4Server** (`P4Server.cs`, `P4Server.internal.cs`, `P4ServerMT.cs`) — connection lifecycle, command dispatch, multi-threading.
3. **P4Bridge** (`P4Bridge.cs`, `P4BridgeLoader.cs`) — PInvoke surface. **Every `[DllImport]` lives here and mirrors `p4bridge/p4bridge-api.cpp` exactly.**

## Domain file pairings

For every public spec type, keep these in sync:
- `<Type>.cs` — the model
- `Repository.<Type>.cs` — CRUD operations
- `../p4api.net-unit-test/<Type>Test.cs` — MSTest cases

Example: `Client.cs` ↔ `Repository.Client.cs` ↔ `ClientTest.cs`.

## Recurring pitfalls (the `/review` skill checks these)

- **PInvoke signature drift.** Every `[DllImport]` in `P4Bridge.cs` must match `p4bridge/p4bridge-api.cpp` exactly. Marshalling attributes (`[MarshalAs]`, `CharSet`, `CallingConvention`) must agree. Mismatches silently corrupt the stack — they don't fail loudly. See `.claude/rules/pinvoke.md` and the `pinvoke_sync_check` hook.
- **Cross-TFM accidents.** Code compiles against `netstandard2.0`, `net462`, `net80`, `net10.0`. APIs from later TFMs (`Span<T>` extensions, `IAsyncEnumerable<T>`, `System.Text.Json`, `Index`/`Range`, `init`, records, `required`) need `#if NET8_0_OR_GREATER` gating. See `.claude/rules/cross-target.md`.
- **New exception type.** Managed-visible errors from native code go through `P4Exception` (or a subclass in `P4Exception.cs`). Don't invent a new exception class. Don't catch `P4Exception` and rethrow as `Exception` — that loses the Perforce error code.
- **Public API surface drift.** Renaming or signature-changing a `public` member breaks NuGet consumers. Add a new overload; don't change existing signatures. See `.claude/rules/api-stability.md`.
- **Strong-name key tampering.** `MyKey.snk` and `MyKey.publickey` are externally managed — refuse and ask.
- **Bridge auto-copy.** `p4api.net.csproj` has `<Content Include="..\p4bridge\out\<preset>\**\*.so|dylib|dll">` rules that auto-copy the native bridge into the bin dir on `dotnet build`. Don't copy it manually — that hides build-config bugs.

## Namespace & style

- Namespace `Perforce.P4` for all types.
- PascalCase public; camelCase locals; SCREAMING_SNAKE_CASE for constants.
- Match the brace style of the surrounding file — the codebase isn't uniformly formatted.
- `#nullable enable` is not opted into — don't add it per-file.
- XML doc comments on `public` APIs (these ship in `p4api.net.xml`).

## When you change something here, also touch…

| You edit | Also update |
|---|---|
| A `[DllImport]` in `P4Bridge.cs` | The matching export in `../p4bridge/p4bridge-api.cpp` (same commit) |
| A new public method on `<Type>.cs` | `../p4api.net-unit-test/<Type>Test.cs` with a test that fails without the change |
| `Repository.<Type>.cs` CRUD | Verify the matching `<Type>Test.cs` covers the new path |
| Anything exception-related | Check the catch sites in `Repository.*.cs` still preserve the `P4Exception` chain |
