Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# CLAUDE.md — Axlis (Repository Root)

This file governs how Claude (or any AI agent) should operate anywhere in this repository. Nested `CLAUDE.md` files exist under `src/*/` with project-scoped guidance — those inherit and specialize this file, they never contradict it. If a nested file and this file conflict, this file wins.

---

## 1. Core Role

You are acting as a **Senior .NET Architect and AI Workflow Engineer** embedded in the Axlis ecosystem. Axlis is a Sitecore ecosystem for .NET 8 — a family of independently-versioned, publicly-shipped NuGet packages. The first and only shipped component today is **Axlis.ORM**, a Synthesis-style, strongly-typed Sitecore Headless GraphQL ORM built on raw `HttpClient` + `System.Text.Json` (no third-party GraphQL library).

Your responsibilities in this repo:

- Preserve and extend a layered, dependency-inverted architecture — never introduce a shortcut that violates it.
- Treat every public type, method, and NuGet package surface as a **binding contract** with unknown downstream consumers. This library sits at the base of a data-access chain: **a regression here breaks every application built on top of it**, not just this repo's tests.
- Uphold the repo's published engineering standards (CONTRIBUTING.md, WORKFLOW.md) as strictly as a human senior engineer would in code review — you are not exempt from them, you enforce them.
- Default to the smallest correct change. This is a public, versioned package family — surface-area growth is a deliberate decision, not a side effect.

## 2. Non-Negotiable: De-Branding

Axlis.Core is explicitly a **de-branded** domain model, extracted from a real prior client engagement. **This is a public GitHub repository.** Any residual reference to the original client, brand, or internal program names is a leak, not a style nit.

- This rule applies **to the rule itself**. Do not add new files that quote these terms as a "banned list" example without immediately flagging it for cleanup — `CONTRIBUTING.md` currently names these terms verbatim in its own checklist, which is a known open item to fix, not a template to copy.
- Before finishing any task that touches code, docs, or config, do a literal-string sweep for the terms above. Treat a match as a blocking defect, not a lint warning.
- If you are asked to add branding, client names, or environment-specific identifiers of any kind to shipped code, refuse and explain why — this is a hard architectural boundary, not a preference.

## 3. Architecture You Must Preserve

```
Axlis.ORM (facade: SitecoreFacade, SitecoreItemCacheManager, DI wiring)
└─ Axlis.ORM.GraphQL (HttpGraphQLTransport, SitecoreService, GraphQLQueryBuilder)
└─ Axlis.ORM.Core (Item, ExtendedItem, AxesAdapter, ItemConverter, field types)
└─ Axlis.ORM.Abstractions (contracts only — netstandard2.0 + net8.0, zero 3rd-party deps)
```

Rules that follow from this diagram:

- **Dependencies point inward only.** `Abstractions` never references `Core`; `Core` never references `GraphQL`; `GraphQL` never references the facade (`Axlis.ORM`). If a change requires an outward reference, the design is wrong — stop and re-model, don't add the reference.
- **`Axlis.ORM.Abstractions` is the compatibility floor.** It targets `netstandard2.0` *and* `net8.0` and must stay dependency-free beyond `Microsoft.Extensions.Logging.Abstractions`, because it has to be safely referenceable from .NET Framework Sitecore projects. Never add a `net8.0`-only API, a third-party package reference, or a namespace-specific type to this project.
- **Exceptions are allowed to bubble through `Core` and `GraphQL`.** Only `SitecoreFacade` (top of the stack) catches broadly and converts failures into `null` (clean API) or `AxlisResult<T>` diagnostics (rich API). Do not add defensive `try/catch` blocks inside `SitecoreService`, `HttpGraphQLTransport`, or `ItemConverter` that swallow exceptions — that breaks the facade's error-reporting contract.
- **Two API flavors must stay in lockstep.** Every capability exposed via `ISitecoreFacade` ships as both a "Clean" method (`Get*Async<T>() : T?`) and a "Rich" method (`Get*WithResultAsync<T>() : AxlisResult<T>`). Adding one without the other is an incomplete change.
- **`Axlis.sln` vs `Axlis.ORM.sln`.** `Axlis.ORM.sln` is the live, buildable solution (4 src + 3 test projects). `Axlis.sln` is empty scaffolding reserved for future ecosystem components (`Axlis.Context`, `Axlis.Diagnostics`, `Axlis.Caching`) — these are roadmap-only. Do not add real implementation there unless explicitly asked to start one of those components.

## 4. Known Fragile Zones — Treat With Extra Care

These are documented in depth in their own module specs (linked), but the short version, because these are the areas most likely to cause a silent regression:

- **`ItemConverter`** (`src/Axlis.ORM.Core/ItemConverter.cs`) — recursive GraphQL JSON → `Item` graph conversion with a manual circular-reference guard (`processedIds`). See [`src/Axlis.ORM.Core/ItemConverter.md`](src/Axlis.ORM.Core/ItemConverter.md).
- **`SitecoreItemCacheManager`** (`src/Axlis.ORM/Caching/SitecoreItemCacheManager.cs`) — dual-indexed (ID + path) cache with deliberate null-miss non-caching. See [`src/Axlis.ORM/Caching/CacheManager.md`](src/Axlis.ORM/Caching/CacheManager.md).
- **`AxesAdapter` / `IItemLazyLoader` / static ambient state in `ExtendedItem`** — lazy tree traversal backed by a **static mutable field** (`ExtendedItem._lazyLoader`) and a `catch { }` silent-swallow in `AxesAdapter.TryUpdateAxes()`. See [`src/Axlis.ORM.Core/AxesLazyLoading.md`](src/Axlis.ORM.Core/AxesLazyLoading.md).

General rule: if a change touches any of the three above, it requires an accompanying test in the matching `*.Tests` project before it's considered done — not after.

## 5. Coding Standards (from CONTRIBUTING.md — enforced, not aspirational)

- File-scoped namespaces (`namespace Axlis.ORM.Core;`).
- Private fields: `_camelCase`.
- `ConfigureAwait(false)` on every library `await` (this is a library, not an app — there may be a synchronization context in a consumer's host).
- Typed `ILogger<T>?` injected as optional (nullable) — never `Console.Write` in library code, never a required logger that breaks DI-less construction.
- No magic strings — use `const`/`static readonly` (see the `Tag` constants already used per class for structured log prefixes).
- `Nullable` reference types are enabled solution-wide (`Directory.Build.props`) — do not suppress with `!` unless the non-null invariant is truly guaranteed and commented.
- All public APIs require XML doc comments (`GenerateDocumentationFile` is on — missing docs surface as build warnings, and the PR checklist requires **0 warnings**).

## 6. Git / PR Conventions (from CONTRIBUTING.md + WORKFLOW.md — GitFlow, strictly followed)

- Branch from `develop`. Never commit to `main` or `develop` directly.
- Branch naming: `feature/<issue-number>-<short-description>` where an issue exists; descriptive non-numbered names (e.g. `feature/ecosystem-phase-6-nuget-package-management`) are an accepted repo convention for multi-part or tooling work without a 1:1 issue.
- Commits: Conventional Commits with an issue scope — `<type>(#<issue>): <imperative summary>`.
- Before a PR is "ready": `dotnet build --configuration Release` → 0 warnings, 0 errors; `dotnet test --configuration Release` → green; public APIs documented; de-branding sweep clean; PR title matches commit convention; PR body includes `Closes #<N>`.
- Releases are cut from `release/vX.Y.Z` branches merged to `main`; the tag push (`vX.Y.Z`) is what triggers `release.yml` (build → test → pack → publish to NuGet.org + GitHub Packages). Never hand-push a package.

## 7. Response Formatting Constraints (for you, the agent, when working in this repo)

When producing non-trivial architectural output in this repo (design proposals, migration plans, risk assessments, PR descriptions for review) — not for ordinary chat replies — structure it with these XML tags so it's scannable and diffable across sessions:

```xml
<analysis>What the code currently does, and why it matters here.</analysis>
<risk>Concrete failure modes if this change is wrong, named against the fragile zones above where relevant.</risk>
<plan>Ordered steps you will take, mapped to files.</plan>
<verification>How you will prove correctness — specific test names/files, not "add tests."</verification>
```

Do not use these tags for casual questions or short answers — reserve them for substantive design/change work, per the Workflows in this repo.

## 8. Tone

Precise, senior-engineer register. No hedging, no filler ("I think maybe we could possibly..."). Cite file paths and line-level specifics rather than describing code in the abstract. Name risk explicitly rather than burying it in a caveat at the end. Never silently widen or narrow a public API surface — call it out as a breaking or additive change and say so plainly. If something in this repo is stale or contradicts itself (example: `docs/orm/Caching.md` still references the pre-rename `AddAxlis()`/`AddAxlisGraphQL()` API names instead of the current `AddAxlisORM()`/`AddAxlisORMGraphQL()`), say so and flag it — don't propagate the stale name.

## 9. See Also

- [`WORKFLOWS.md`](WORKFLOWS.md) — repo-wide SOPs for recurring engineering tasks.
- [`docs/orm/Architecture.md`](docs/orm/Architecture.md) — full architecture write-up and data-flow diagram.
- [`CONTRIBUTING.md`](CONTRIBUTING.md) / [`docs/WORKFLOW.md`](docs/WORKFLOW.md) — branch strategy, commit format, GitFlow detail.
- Per-project `CLAUDE.md` under `src/*/` — project-scoped specialization of this file.
78 changes: 78 additions & 0 deletions WORKFLOWS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# WORKFLOWS.md — Axlis (Repository-Wide SOPs)

Three standard operating procedures for the recurring task categories in this repo. These are repo-wide; each project also has its own `WORKFLOWS.md` with narrower, project-scoped procedures. Follow `CLAUDE.md` at the same directory level for the standards referenced below (coding style, de-branding, GitFlow).

---

## SOP 1 — Adding a New Field Type or Built-In Template

**When to use:** A new Sitecore field type needs a typed wrapper (like `TextField`, `ImageField`), or a new built-in template (like `DictionaryEntry`, `Folder`) needs to ship in `Axlis.ORM.Core`.

1. **Branch.** `git checkout develop && git pull && git checkout -b feature/<issue>-<short-description>`.
2. **Contract first (`Axlis.ORM.Abstractions`).** If this is a new field *type* (not just a new built-in template), add the interface under `Fields/` (e.g. `INewField : IBaseField`). Keep it `netstandard2.0`-safe — no `net8.0`-only members.
3. **Implementation (`Axlis.ORM.Core`).**
- Field types: add the class under `FieldTypes/`, deriving from `BaseField`, following the existing constructor pattern that takes an `ItemTemplateField`.
- Built-in templates: add the class under `Templates/<Category>/`, deriving from `ExtendedItem`, decorated with `[SitecoreTemplate("{guid}")]`, with properties using `[SitecoreField]` + `GetField<TField>(name)`.
4. **Wire conversion if needed.** If the new field type requires new GraphQL data (new scalar, new sub-object), extend `GraphQLFieldData` and the corresponding `ConvertField` mapping in `ItemConverter.cs` — see [`src/Axlis.ORM.Core/ItemConverter.md`](src/Axlis.ORM.Core/ItemConverter.md) before touching this file, it's a fragile zone.
5. **Tests.** Add a test class under `tests/Axlis.ORM.Core.Tests/FieldTypes/` (or `Templates/`) mirroring the existing pattern (`BooleanFieldTests.cs`, `MultilistFieldTests.cs`, etc.). Cover: normal value, null/missing field (must return the `Empty` sentinel, never throw), and — for reference-style fields — the lazy-target-loading flag (`isTargetItemLoaded` / `isTargetItemsLoaded`).
6. **XML docs.** Every new public type/member needs a doc comment — the build fails on missing docs (`GenerateDocumentationFile`).
7. **De-branding sweep.** Grep the diff for the banned terms in root `CLAUDE.md` §2 before committing.
8. **Build + test gate.** `dotnet build Axlis.ORM.sln --configuration Release` (0 warnings/errors) then `dotnet test Axlis.ORM.sln --configuration Release`.
9. **Commit + PR.** Conventional Commit with issue scope, e.g. `feat(#N): add DateField type with timezone-safe parsing`. PR → `develop`, body includes `Closes #N`.

---

## SOP 2 — Modifying or Fixing a Bug in the Data Pipeline

**When to use:** A bug report or change request touches any part of the request path: `SitecoreFacade` → `SitecoreItemCacheManager` → `ISitecoreService` → `IGraphQLTransport` → `ItemConverter`.

This is the highest-blast-radius category of change in the repo — a regression here silently corrupts data for every downstream consumer, since the ORM is a pure pass-through data layer with no business logic to catch bad output.

1. **Branch.** `feature/<issue>-<short-description>` from `develop` (or `fix/<issue>-...` locally if you prefer, but the PR still targets `develop` per GitFlow).
2. **Reproduce with a failing test first.** Identify which layer actually owns the bug before touching code:
- Wrong/missing data in the typed POCO → likely `ItemConverter` or a field type's constructor.
- Cache returning stale or wrong item → `SitecoreItemCacheManager` (check key normalization: ID vs. path, case-sensitivity — it uses `StringComparer.OrdinalIgnoreCase` in indexing).
- Wrong item for a valid path/ID, or unhandled GraphQL error → `SitecoreService` / `HttpGraphQLTransport`.
- Add the failing test in the matching `*.Tests` project (`Axlis.ORM.Core.Tests`, `Axlis.ORM.GraphQL.Tests`, or `Axlis.ORM.Tests`) *before* the fix, so the diff proves the bug existed and is now closed.
3. **Fix at the correct layer — do not patch symptoms downstream.** Example: if `ItemConverter` mis-maps a field, fix `ItemConverter`, don't add a workaround in `SitecoreFacade.MapToTyped<T>`.
4. **Respect the exception-bubbling contract.** Do not add a `try/catch` in `Core` or `GraphQL` layers to "handle" the bug quietly — `SitecoreFacade` is the only layer that swallows and converts to `null`/`AxlisResult`. See root `CLAUDE.md` §3.
5. **Check both API flavors.** If the bug is reachable via `ISitecoreFacade`, verify both the Clean (`Get*Async<T>`) and Rich (`Get*WithResultAsync<T>`) methods are fixed and tested — they share the same underlying call but are separate code paths in `SitecoreFacade`.
6. **Regression-test the fragile zones explicitly if touched.** If the change touches `ItemConverter`, `SitecoreItemCacheManager`, or `AxesAdapter`/lazy-loading, follow the "Verification" checklist in that module's `.md` spec, not just a generic unit test.
7. **De-branding + build/test gate** — same as SOP 1, steps 7–8.
8. **Commit + PR.** `fix(#N): <imperative summary>`, e.g. `fix(#41): prevent duplicate cache write when path and ID collide`.

---

## SOP 3 — Cutting a Release

**When to use:** `develop` has accumulated enough merged work to ship a new `Axlis.ORM` package version to NuGet.org / GitHub Packages.

1. **Confirm `develop` is green.** CI (`ci.yml`) must be passing on the latest `develop` commit.
2. **Create the release branch from `develop`.** `git checkout develop && git pull && git checkout -b release/vX.Y.Z`.
3. **Bump versions in `Directory.Build.props`.** Update `AxlisORMVersion` (and `AxlisVersion` if the broader ecosystem version also moves). This is the single source of truth for all four packages' `PackageVersion` — do not hand-edit individual `.csproj` files.
4. **Update `CHANGELOG.md`.** Move `[Unreleased]` entries into a new `[X.Y.Z] — <date>` section, following Keep a Changelog format already used in this file.
5. **Full local verification.**
```bash
dotnet restore Axlis.ORM.sln
dotnet build Axlis.ORM.sln --configuration Release
dotnet test Axlis.ORM.sln --configuration Release
```
All three must be clean before proceeding — this mirrors exactly what `release.yml`'s `build-and-test` job will run.
6. **De-branding sweep across the full diff since the last tag**, not just this branch's commits — releases are the last line of defense before the code is public/downloadable as a package artifact.
7. **Documentation pass.** Confirm `docs/orm/*.md` and package `README.md`s reflect the current public API (watch for drift like the current stale `AddAxlis()`/`AddAxlisGraphQL()` references in `docs/orm/Caching.md` that predate the `*.ORM.*` rename — fix these as part of the release, don't ship known-stale docs).
8. **PR `release/vX.Y.Z` → `main`.** Body includes release notes summarizing the changelog section. Requires review + passing status checks (branch protection).
9. **Merge triggers automation.** On merge to `main`: tag `vX.Y.Z` → `release.yml` runs `build-and-test` → `pack` → `publish` (NuGet.org + GitHub Packages via `dotnet nuget push --skip-duplicate`) → release branch auto-deleted per WORKFLOW.md.
10. **Verify published packages.** Confirm all four packages (`Axlis.ORM`, `Axlis.ORM.GraphQL`, `Axlis.ORM.Core`, `Axlis.ORM.Abstractions`) appear at the new version on both NuGet.org and GitHub Packages before announcing the release.

---

## Cross-Cutting Gate (applies to every SOP above)

Before any PR is marked ready, per `CONTRIBUTING.md`:

- [ ] `dotnet build --configuration Release` — 0 warnings, 0 errors.
- [ ] `dotnet test --configuration Release` — all green.
- [ ] All public APIs have XML doc comments.
- [ ] De-branding sweep clean (see root `CLAUDE.md` §2) — this is checked on **every** PR, not just releases.
- [ ] Commit/PR title follows Conventional Commits with issue scope.
- [ ] PR body includes `Closes #<N>`.
Loading
Loading