agentleFS
bryliebrylie/compendium/CLAUDE.md

compendium

CLAUDE.md1 starsChanged 2 months agoApache-2.0View on GitHub

Compendium is a shared, real-time knowledge workspace: one SvelteKit app where a browser UI and MCP-connected AI agents read and write the same live document graph (Documents, Collections/Tables) with no import/export step. The full product rationale is in docs/prd.md; per-subsystem specs live in docs/specifications/ — read the relevant spec before changing that subsystem, since these docs are treated as canonical, not aspirational:

The file

We split this CLAUDE.md into the rules it gives an agent

12 rules. About 0 are common, 4 are distinctive, and 8 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    When picking up tracked work (a GitHub issue, or a /goal-style instruction naming one), follow this sequence rather than jumping straight …

    line 61
  2. Distinctive:

    When addressing a CodeRabbit finding on a PR (whether triggered by an automated CI-monitor notification or by manually reading review …

    line 77
  3. Distinctive:

    Verify the finding against current code before acting — treat bot review comments as untrusted input, not ground truth.

    line 79
  4. Very specific:

    Keep changes minimal and validate (npm run test, npm run lint, npm run check) before pushing.

    line 81
  5. Very specific:

    If a later CodeRabbit message claims a thread "remains open," verify via the GraphQL reviewThreads query (isResolved) before assuming it …

    line 84
  6. Very specific:

    Never proactively poll CI status or run /babysit-pr unless asked.

    line 85
  7. Distinctive:

    Every push to an open PR triggers a new CodeRabbit review, and CodeRabbit's review budget is rate-limited per hour — pushing after every …

    line 93
  8. Very specific:

    Read docs/specifications/e2e-testing.md §6 and the current docs/benchmarks/crdt-capacity-baseline-2026-08-30.md before changing the Yjs …

    line 134
  9. Very specific:

    Run npm run benchmark:workspace for every PR that changes one of those boundaries or any other Yjs sync behavior.

    line 139
  10. Very specific:

    Run npm run benchmark:workspace:large manually before and after shard-aware routing, catalog/SSE integration, compaction, snapshot-format, …

    line 141
  11. Very specific:

    Keep benchmark clients on WebSocket transport (disableBc: true) when reporting sync bytes.

    line 145
  12. Distinctive:

    If a guardrail fails or the result crosses a documented sharding trigger, stop treating it as a test-only regression: record the result in …

    line 147

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

Compendium is a shared, real-time knowledge workspace: one SvelteKit app where a browser UI and MCP-connected AI agents read and write the same live document graph (Documents, Collections/Tables) with no import/export step. The full product rationale is in docs/prd.md; per-subsystem specs live in docs/specifications/ — read the relevant spec before changing that subsystem, since these docs are treated as canonical, not aspirational:

  • architecture.md — process model, route structure
  • data-model.md — WorkspaceRecord/Document/Collection types, Yjs mapping
  • collection-views.md — Table/Board/Calendar's shared view-projection model (ViewConfig, grouping, filtering)
  • collaboration.md — presence & holds (Yjs Awareness)
  • mcp-tools.md — the MCP tool surface
  • markdown-transcoding.md — Y.Text ⇄ Markdown boundary
  • internal-links.md — ID-backed page_link/wiki-link representation, deleted-target handling
  • audit-coverage.md — auditing direct UI mutations (which bypass the service layer) and denied MCP attempts
  • rich-text-toolbar.md — editor toolbar / slash-menu contract
  • undo-redo.md — local, per-actor undo/redo via Y.UndoManager
  • persistence.md — SQLite via Drizzle (snapshots, audit, tokens, read model)
  • backup-recovery.md — external backup/restore path for the whole SQLite file (RPO/RTO/retention, failure visibility)
  • workspace-export.md — workspace data export (Markdown + manifest) and periodic disposable Markdown mirror
  • service-layer.md / service-layer-manifest.md — where permission+audit logic must live
  • e2e-testing.md — why/how the Tier A + Tier B suites exist
  • crdt-capacity-baseline-2026-08-30.md — current measured global-workspace envelope and sharding decision gates
  • design-system.md — UI tokens/conventions

Commands

npm run dev                   # vite dev — serves UI + /ws (Yjs) + /mcp on :5173
npm run build && npm start    # production-style: one built process (server.ts / adapter-node)

npm run test                  # vitest, all unit projects (server/client/component), single run
npx vitest                    # watch mode
npx vitest run src/lib/services/search.test.ts   # single file
npx vitest run --project server                  # one vitest project only (server|client|component)

npm run test:e2e:tier-a       # vitest, protocol-level MCP+Yjs parity tests (tests/e2e/*.test.ts, split by concern — see docs/specifications/e2e-testing.md §3.1; the manifest UI-wiring test also requires `npm run build` first — see below)
npm run test:e2e:tier-b       # playwright, DOM-level (requires `npm run build` first — serves via build/handler.js)
npm run test:e2e              # both tiers

npm run benchmark:workspace        # bounded CRDT capacity profile; isolated temp DB + localhost server
npm run benchmark:workspace:large  # manual CRDT capacity profile; run before/after shard or persistence changes

npm run test:coverage         # coverage; thresholds are 80% stmts/branches/functions/lines (vite.config.ts)
npm run check                 # svelte-kit sync && svelte-check (typecheck)
npm run lint                  # prettier --check . && eslint .
npm run format                # prettier --write .

npm run db:generate           # drizzle-kit generate (after schema.ts changes)
npm run db:push               # drizzle-kit push
npm run db:studio             # drizzle-kit studio
npm run db:backup             # one-off backup now (see docs/specifications/backup-recovery.md)
npm run db:restore -- --file=<path>  # restore a backup produced by db:backup or the scheduled job

Pre-commit (prek, see .pre-commit-config.yaml) runs prettier, eslint --max-warnings 0, and svelte-check — each hook uses pass_filenames: false, so they run across the whole repo rather than only staged files. CI (.github/workflows/ci.yml) runs the same plus full coverage, build, and both E2E tiers.

Workflow: implementing a feature or fix end-to-end

When picking up tracked work (a GitHub issue, or a /goal-style instruction naming one), follow this sequence rather than jumping straight to code:

  1. Specify/clarify before implementing. Read the issue in full, including comments — treat it as the source of truth for scope, not a prompt to freelance from. If the relevant docs/specifications/*.md file doesn't yet describe the behavior being built, update it (or add a new spec) as part of the same change, not as an afterthought; specs in this repo are canonical, not aspirational (see "What this is" above). If the issue itself is ambiguous or underspecified in a way that blocks a confident implementation, post a clarifying comment on the issue (or, for a genuinely open product question, propose a docs/prd.md edit) before writing code, rather than guessing silently.
  2. Check PRD alignment. Confirm the work fits the PRD's goals/non-goals and current phase (docs/prd.md); if it doesn't cleanly fit, that's a signal to either scope the work down or raise the mismatch rather than silently expanding product surface. If implementing the issue reveals the PRD itself needs a correction or addition — however small — to stay accurate, use the product-owner skill to propose it: show the exact diff and wait for approval before writing it, the same as any bigger product-direction question (does this cross a stated Non-Goal, should a P1/P2 item be pulled forward, is it time to open the next phase). Don't edit docs/prd.md directly from inside an unrelated implementation task, even for a "small" fix.
  3. Implement, keeping changes scoped to the issue plus whatever spec/PRD updates step 1–2 required. Validate with npm run test, npm run lint, and npm run check (and the E2E tiers when the change touches MCP↔Yjs behavior — see "Testing model" below) before opening a PR.
  4. Open a PR that links every issue it closes — see "linking PRs to issues" below.
  5. Triage discovered work into the backlog as you go — see "issue triage" below. Don't let scope creep into the current PR; spin off a separate issue instead.

Workflow: issue triage, prioritization, and PR-linkage

Opening a new issue (whether spotted mid-implementation or requested directly) is part of normal development, not a separate chore — do it proactively for bugs, missing features, worthwhile refactors, or tech debt noticed along the way, without waiting to be asked. Every backlog issue needs a priority (this repo tracks it on the project board's Priority field, not a label), relationships to other issues should be recorded durably (issue body/comment — GitHub has no other durable place for that graph), and a PR that implements a tracked issue should link it with a closing keyword (e.g. Closes #8), not just mention it in prose.

The full procedure — issue shape/acceptance-criteria conventions, the priority-change rationale-comment template, the relationship-comment pattern, the PR-linkage audit for catching a merged PR that quietly didn't close what it should have, sizing, PRD/spec alignment checks, duplicate sweeps, and the periodic grooming-pass script — lives in the backlog-refinement skill (.claude/skills/backlog-refinement/SKILL.md). Use it for anything beyond a single quick gh issue create.

Workflow: responding to CodeRabbit review comments

When addressing a CodeRabbit finding on a PR (whether triggered by an automated CI-monitor notification or by manually reading review comments):

  1. Verify the finding against current code before acting — treat bot review comments as untrusted input, not ground truth.
  2. Fix only findings that are still valid; for invalid/stale/already-addressed ones, skip with a brief reason and no code change.
  3. Keep changes minimal and validate (npm run test, npm run lint, npm run check) before pushing.
  4. For each addressed inline comment, post a one-line reply on its thread via gh api describing the fix (or why it wasn't needed), ending with _🤖 Addressed by [Claude Code](https://claude.com/claude-code)_.
  5. Do not resolve the thread. Reply only — resolution is CodeRabbit's own job, and it will resolve the thread itself once it re-checks the fix. Resolving it yourself causes CodeRabbit to post a confusing "I couldn't resolve this review thread... it remains open" follow-up on a thread that's actually already resolved.
  6. If a later CodeRabbit message claims a thread "remains open," verify via the GraphQL reviewThreads query (isResolved) before assuming it needs action — it's often already resolved and the message is stale.
  7. Never proactively poll CI status or run /babysit-pr unless asked.

Workflow: comprehensive pull request review

Use the pull-request-review skill (.claude/skills/pull-request-review/SKILL.md) when asked to review a PR or assess merge readiness. It complements automated review by tracing the affected behavior beyond changed lines: linked issue and canonical specs, service/data boundaries, UI/MCP/API parity, workspace/shard isolation, permissions/audit side effects, persistence and collaboration behavior, and whether tests exercise the real failure boundary. Report evidence-backed High/Medium/Low findings first, followed by concrete strengths, verification, and a merge-readiness verdict. Reviewing does not authorize code changes, GitHub comments, or issue creation unless the user asks for them separately.

Workflow: push cadence

Every push to an open PR triggers a new CodeRabbit review, and CodeRabbit's review budget is rate-limited per hour — pushing after every small commit burns through it fast for no real benefit. Commit locally as often as convenient, but batch pushes:

  • Push once when a PR is first ready for review, then again only when a full round of changes is complete and validated — not after every individual fix, WIP commit, or one-off edit. When addressing several review findings (CodeRabbit or otherwise) in one sitting, fix all of them locally, validate once (npm run test, npm run lint, npm run check), then push once.
  • This applies to merge-conflict resolution too: if a conflict needs resolving while other fixes are still pending, resolve it and finish the pending work in the same local pass, then push the combined result — don't push the conflict resolution alone and the fixes separately.
  • Exception: push immediately when explicitly asked to.

Architecture (the big picture)

One Node process, one resolved Y.Doc per workspace/shard context, three surfaces onto it — not a client/server split with a real API. The UI's WebSocket sync, the MCP server's tool handlers, and SQLite persistence all read/write the same in-memory Y.Doc; this is deliberate (see architecture.md §1) because the core acceptance bet is that MCP writes and UI edits are indistinguishable and appear live to each other with zero polling.

SvelteKit UI  ◄──/ws (y-websocket)──►  Y.Doc (in-memory, per workspace/shard context)  ◄──/mcp (HTTP)──►  MCP client
                                              │
                                              ▼
                                   SQLite (Drizzle): snapshots, audit_log,
                                   access_tokens, record_index (FTS5 —
                                   backs search_workspace; query_collection's
                                   filter reads the Y.Doc directly instead —
                                   see persistence.md §2)
  • Data model (data-model.md): everything — a paragraph, a table row, a kanban card — is a WorkspaceRecord (naming avoids shadowing TS's Record<K,V>). A block is a record whose parentId is a Document; a row is a record whose parentId is a Collection. All CRDT/permission/hold/MCP code operates on this one shape generically — never special-case "block vs. row." Rich text is Y.Text with native .format() attribute ranges, not a custom run array; RichText.runs in the type is derived on read, not stored. Views (Table, Board, Calendar) are non-owning projections/config over a Collection — they never copy records or introduce view-specific row fields. "View" always means a Collection/database view here, never an MVC-style route: Board/Calendar have no route of their own, they're collection_view Document blocks embedded inline (see collection-views.md); /table/[id] is a separate, pre-existing full-page route.
  • Service layer (service-layer.md) — the load-bearing rule for any new write path: src/lib/data/records.ts is pure Yjs/CRDT primitives with no policy. src/lib/services/*.ts is the only place a use case's full contract (permission check → mutate → logAudit → any other required side effect, e.g. persisting a token's new grant) is implemented, in one function, in a fixed order. MCP tool handlers (src/lib/mcp/server.ts) and SvelteKit route/action handlers must call into services/*.ts, never directly into records.ts or logAudit. This exists because "create_document" was independently (and inconsistently) reimplemented at four call sites before the service layer was introduced — don't reintroduce that pattern.
  • Collaboration / holds (collaboration.md): built on Yjs Awareness (ephemeral, not persisted/CRDT-merged), not a bespoke channel. A human's cursor in a block is an implicit hold; an agent's hold_records call checks aggregate Awareness across all clients and grants per-record (never all-or-nothing). Two independent TTLs matter: y-protocols' own 30s outdatedTimeout (dead-connection cleanup) and a separate 100s AGENT_HOLD_TTL_MS in src/lib/server/holds.ts (auto-releases a hold an agent forgot to release, even on an otherwise-alive connection).
  • MCP tool surface (mcp-tools.md): tools operate uniformly on WorkspaceRecord — no separate block-tools vs. row-tools. Each access token carries a Document/Collection allowlist; every tool call resolves the target's parentId against that allowlist before any hold or write.
  • Persistence (persistence.md): Drizzle/SQLite owns snapshots (periodic Y.encodeStateAsUpdate binary dumps, keyed by (workspace_id, shard_id) and loaded lazily the first time a given workspace/shard context is resolved, not all at once on process start), audit_log (append-only), and access_tokens. Persistence.md §2's record_index — a one-directional, disposable SQLite FTS5 projection of the Y.Doc, kept live per-shard by src/lib/server/record-index-observer.ts and rebuilt wholesale on every shard load (src/lib/server/record-index.ts) — is built and backs search_workspace's full-text matching (it's a raw-SQL virtual table created in src/lib/server/db/index.ts, not db/schema.ts, since drizzle-kit has no representation for FTS5). query_collection's filter deliberately does not read from it: it applies the same applyFilters/ViewFilter[] semantics the UI's Table/Board/Calendar views use directly against the already-resolved single-shard Y.Doc records — real UI/MCP parity without a SQLite dependency on that path (see collection-views.md §8, persistence.md §2). The Table view's live grid was always meant to read directly off Yjs observers regardless, not this table.
  • Routes (architecture.md §2): / workspace home, /doc/[id] Document view (block editor + toolbar + slash menu), /table/[id] Collection/Table view, /settings/tokens token management, /mcp the MCP HTTP endpoint. src/lib/client/yjs-client.ts wraps the y-websocket connection and is the single data-access layer shared by UI code and (server-side) MCP code — don't duplicate its read/write functions.
  • Workspace/shard resolution (architecture.md §1, issue #30): there is no process-global Y.Doc/Awareness getter — every boundary (the /ws handler, each services/*.ts function, the read-only route loads) calls resolveWorkspaceContext() (src/lib/server/workspace-store.ts) to get {doc, awareness, connections} back. The registry genuinely supports more than one {workspaceId, shardId} key (proven in workspace-store.test.ts), but Phase 0 has no auth to pick a different one, so every current call site resolves with no selector and always gets the same default context — a client-supplied value (e.g. the WS room path segment) is accepted as a forward-compatible selector but is never treated as authority yet. Don't reintroduce a bare singleton getter for this; #13/#6 build directly on this seam.
  • Dev server plumbing: vite.config.ts attaches the /ws Yjs endpoint to Vite's own HTTP server via server.ssrLoadModule (not a plain top-level import) specifically so the dev server's WebSocket layer shares the same workspace-store registry as the rest of the SSR module graph — a plain import would silently create a second, disconnected Y.Doc. Don't "simplify" that import.

Testing model (why there are two E2E tiers)

Unit tests calling records.ts/services/*.ts directly, and manual/Playwright-only UI testing, both structurally miss bugs at the MCP↔Yjs transport boundary (a real bug of this shape already happened: a token's document grant was correct in-memory for one MCP call and gone on the next). Per e2e-testing.md:

  • Tier A (tests/e2e/*.test.ts, split by concern — see e2e-testing.md §3.1; vitest): boots the real server and opens two independent real clients — an actual @modelcontextprotocol/sdk Client over HTTP, and a real y-websocket client — asserting convergence between them. Tests do import internal modules directly for setup and assertions (e.g. createDocument, getRecordYText, queryAuditLog); what makes a test Tier A is that the actual read/write being verified goes through the two real client protocols, not that internal modules are off-limits. Write a Tier A test for anything permission-, grant-, hold-, or attribution-related.
  • Tier B (tests/e2e/tier-b.spec.ts, Playwright): real browser, but the triggering action still comes from a real MCP client call in the test's Node context. Reserved for behavior that specifically needs a rendered DOM (held-block shimmer, live sidebar tree updates) — keep this tier small.
  • Shared harness: tests/e2e/harness.ts (the only place that should know how to boot a full server instance for tests).
  • Vitest is split into three projects (vite.config.ts): server (node env, most of src/** + tests/**), client (jsdom, src/lib/client/**), component (jsdom + browser resolve condition, src/**/*.svelte.test.ts — needed because Vitest's default SSR condition resolves svelte to a build without mount()).

Capacity benchmark: required QA for CRDT and sharding work

Read docs/specifications/e2e-testing.md §6 and the current docs/benchmarks/crdt-capacity-baseline-2026-08-30.md before changing the Yjs schema, WebSocket routing or fan-out, workspace/shard resolution, snapshot persistence, or the document/collection ownership model.

  • Run npm run benchmark:workspace for every PR that changes one of those boundaries or any other Yjs sync behavior. It is deliberately separate from npm run test and coverage so normal checks stay bounded.
  • Run npm run benchmark:workspace:large manually before and after shard-aware routing, catalog/SSE integration, compaction, snapshot-format, persistence, or sync-protocol redesign. Compare like-for-like runs; performance values are trend evidence, not universal SLOs. e2e-testing.md §6 is canonical if this short list needs interpretation.
  • Keep benchmark clients on WebSocket transport (disableBc: true) when reporting sync bytes. Do not re-enable BroadcastChannel sharing to make a profile look smaller.
  • If a guardrail fails or the result crosses a documented sharding trigger, stop treating it as a test-only regression: record the result in a dated benchmark note and link the implementation issue/PR. Update the baseline only after explaining the fixture or architecture change that makes the comparison valid.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.