Files
James Wampler e1df79b31e
CI / build-and-push (push) Successful in 47s
CI / deploy (push) Successful in 10s
Update docs and solution file for the stdio MCP server's removal
The prior commit's git add silently dropped these five files because
one path in the same invocation didn't exist. Docs, solution file, and
example config still needed the paired update: Novelly.slnx drops the
Novelly.Mcp project entry, README and CLAUDE.md describe MCP as an
in-API HTTP endpoint instead of a stdio binary, .mcp.json.example uses
the type: http shape, and outline-importer.md's tool references are
fixed to real tool names.
2026-08-21 11:00:25 -07:00

109 lines
7.0 KiB
Markdown

# CLAUDE.md
Guidance for Claude Code (claude.ai/code) in repo.
## Project
Novelly: software plan + write novel. ASP.NET Core 10, C#, TypeScript, React, .NET Aspire. Chapter outlines, character dossiers, prose drafting, embedded Claude agent, MCP server over same API.
## Structure
- `src/Novelly.Api/` — whole back end, organised by feature, plus the MCP endpoint. One folder
per feature holds entity, DTOs, service, endpoints together: `Novels/`, `Characters/`,
`Chapters/`, `Beats/`, `Scenes/`, `Tags/`, `Agent/`, `Mcp/`. `Common/` holds what crosses
features; `Data/` holds `DbContext` + EF migrations.
- `src/Novelly.AppHost/` — .NET Aspire orchestration; run this to bring up API + web client
- `src/Novelly.ServiceDefaults/` — shared Aspire wiring: OpenTelemetry, health checks, service discovery
- `src/Novelly.Web/` — React + Vite client
- `tests/` — test suite
- `docs/` — documentation
- `scripts/ci/` — bash CI steps; `prepush.sh` = what Husky pre-push hook runs
## Best Practices
- Use latest .NET + latest supported nuget packages for that version
- Set `langVersion` latest in all csproj files; enable nullable
- Organize code by feature/area, not layer or type. New capability adds files to one feature folder
rather than row to each of entity/DTO/service/endpoint folder
- New features need unit tests covering logic much as possible
- Modified file: check missing test coverage, all tests pass
## Architecture
- React client, embedded agent, MCP server all go through same REST API. One source of truth — never let client reach past API to database.
- Agent tools + MCP tools call same application services endpoints do. New capability = new service method, then surface in all three.
## Logging
Serilog console via `AddSerilog` (not `UseSerilog` — keeps OTel provider for Aspire dashboard). `app.UseSerilogRequestLogging()` registered before `UseExceptionHandler` (outermost), else it logs raw exception status instead of handled one.
- Endpoints: Information via `RequestLoggingEndpointFilter` on each `MapGroup`. No per-lambda logging.
- Service public methods: Information at entry, ids/enums/counts as args.
- Deeper/private methods: Debug at start + end.
- Caught exceptions: `LogError(ex, ...)` with identifying values.
- Expected/recoverable (NotFound, validation, agent tool errors): Warning before throw/return.
- Structured templates only — `{ChapterId}`, never interpolation.
- Never log prose (summary/notes/content) or `ANTHROPIC_API_KEY`. Prose = length only.
# Coding
- No comments — no `///` XML doc, no `//` line comments, no `/* */` blocks, in C#, TS, or CSS. Unclear code → rename for
clarity or extract a well-named method instead of explaining it.
- Descriptive names all classes/methods. No generic: Provider, Manager, Helper
- Match formatting/style from `.editorconfig`
- Wrap lines at 220 chars, single line if fewer
- Interface implemented by single class → bottom of class file. Interface w/ multiple implementations → separate file.
- No tuples for return types. Prefer records or classes for multiple values
- Use `record` for data objects, `class` for objects with behavior. Avoid mutable state where possible.
- DTOs are records; entities are classes. DO NOT use Dto in names.
- `PATCH` requests partial: null field = leave alone, empty string = clear. Keep new update endpoints consistent with `Patch.Apply`.
- Enums cross wire as names, never ordinals
- All frontend components should have an id attribute that identifies them uniquely.
- Web client is keyboard-first: read `docs/keyboard.md` before adding any interactive UI (forms, editable rows, create flows).
## Testing
- Min 70% code coverage, target 90%. Unit tests focus end-user scenarios first.
- Don't write tests just for coverage. Call out missing coverage rather than cover stuff not valuable to end user.
- Code not cleanly unit-testable → mark `[ExcludeFromCodeCoverage]` or exclude namespace from coverage in .runsettings file
- BDD-style unit tests, end-to-end as possible. e.g. `Deleting_a_scene_leaves_its_beats_alone`
- NUnit. No FluentAssertions — assert with `Assert.That` + NUnit's constraint model:
`Assert.That(beat.SceneId, Is.Null)`, `Assert.That(listed, Has.Count.EqualTo(3))`,
`Assert.That(titles, Is.EqualTo(new[] { "First", "Second" }))`
- Grouping related asserts in `Assert.Multiple` beats chain that stops at first failure
- Expected exceptions: `Assert.That(() => service.Foo(), Throws.TypeOf<NotFoundException>())`
- Tests run against real in-memory SQLite via `TestDatabase`, not EF InMemory provider — cascade deletes + query translation must be exercised, InMemory provider fakes both
- Model calls faked at `IAgentModelClient` seam (see `ScriptedModelClient`). Never hit Anthropic API from test.
- No "Mock" in mocked object names
- No Arrange/Act/Assert comments
- All tests pass before commit
## Verifying
Build + tests passing ≠ working. Anything touching endpoint, agent loop, or MCP server — run it:
- Everything at once: `dotnet run --project src/Novelly.AppHost` — Aspire starts API on :5080 +
Vite dev server on :5173, dashboard for logs + traces
- API alone: `ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/Novelly.Api`, then exercise route with curl
- Web alone: `cd src/Novelly.Web && npm run dev` — proxies `/api` to :5080
- MCP: with the API running, drive `/mcp` over HTTP (`initialize``notifications/initialized``tools/list``tools/call`) — Streamable HTTP, so responses are SSE-framed and requests need `Accept: application/json, text/event-stream`
Several real bugs here — SQLite refusing ORDER BY DateTimeOffset, agent's model client throwing at construction + taking read-only endpoints down with it — passed build + test suite, only showed up when app actually ran.
## Claude
- Plans = `.md` files in `docs/plans/`. Web → `docs/plans/web/`, API → `docs/plans/api/`
- Split large plans into discrete chunks — each buildable + committable independently
- Plan generated from `docs/plans/<name>.md` → save as `docs/plans/<name>_plan.md`
- Plan implemented from `docs/plans/<name>.md` → save summary as `docs/plans/<name>_output.md`
## Stack
`.gitignore` set for .NET/Visual Studio (C#, NuGet, MSBuild) plus Node. Update if stack change.
- Anthropic model id lives in `appsettings.json` under `Agent:Model`. Don't hardcode.
- API key comes from `ANTHROPIC_API_KEY` or `Agent:ApiKey` — never commit one. App must stay fully usable without key; only agent endpoints require it.
- EF migrations: `dotnet ef migrations add <Name> -p src/Novelly.Api -o Data/Migrations`. API migrates on boot.
- Outline import root lives in `appsettings.json` under `Imports:RootPath` (`Imports__RootPath` env var). When set, it's the only folder the browse/upload import endpoints and the source picker can reach; unset, those endpoints are disabled and the dialog falls back to a typed path with no sandbox. Created at boot if missing.
- `git push` runs `scripts/ci/prepush.sh` through Husky: build, test, then web build. Run `npm install`
once at repo root to install hook.