Files
agentic-dev/packages/core-shared/AGENTS.md
Danijel Martinek b4ec48f058 docs(agents): bring agent-facing docs in line with the R44 fix
Six docs lagged after the post-merge R44 fix added withCapture +
reported-flag.ts. They mentioned withSpan only and described the
capture story as if it were inline in use-case / controller bodies.
This commit aligns them with what shipped.

CLAUDE.md (Key Conventions):
- "Spans applied at DI bind time" → "Spans + capture composed at DI
  bind time" with the withSpan(withCapture(factory)) sandwich and the
  outermost-span ordering note.
- "Capture at throw sites only" expanded to mention the
  __sentryReported flag, the three flag-checking sites (withCapture,
  SentryLogger, RecordingLogger), and where the helper lives.

AGENTS.md (Instrumentation conventions):
- Use case + controller wrapping example shows the full sandwich.
- Capture-rules table now explicitly says "via withCapture" for use
  cases and controllers, and "flag set, withCapture bails" for the
  bubbled cases.

packages/core-shared/AGENTS.md:
- "with-span.ts" entry split into a paired with-span + with-capture
  block, including the actual sandwich code.
- New entry for reported-flag.ts explaining the helper and why
  RecordingLogger inlines the check (boundary rule).
- Barrel re-export list updated.

docs/architecture/vertical-feature-spec.md (§16):
- The bind-production line now describes the withSpan(withCapture(...))
  sandwich, the outermost-span rationale, and the bubbled-error bail.

docs/architecture/dependency-flow.md (TRACER/LOGGER subsection):
- bindAll diagram updated: real repo line annotates inline calls; use
  case + controller lines show withSpan(withCapture(...)).

docs/guides/tdd-workflow.md (Asserting spans and captures):
- Direct-injection example shows the binder sandwich.
- Capture-assertion example explains the flag-bail behaviour and
  links to the new tests/r44-no-double-capture.test.ts e2e example.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 00:54:23 +02:00

91 lines
5.5 KiB
Markdown

# AGENTS.md — core-shared
Generic, reusable primitives with **zero business knowledge**. This package is the foundation for all other packages and exports utilities, Payload field/hook definitions, and tRPC initialization.
## Responsibilities
- **Generic primitives** — environment helpers, date utilities, type guards
- **Payload utilities** — field definitions (slug, SEO), blocks (CTA), access controls (is-admin), hooks (slugify, publish timestamp)
- **tRPC platform** — `initTRPC.create()`, shared context factory, procedure builders
- **No business domain knowledge** — no awareness of articles, users, media, or any feature
## Must NOT import
- Any feature package (`@repo/auth`, `@repo/blog`, etc.)
- Any app package
- Framework-specific code (Next.js, TanStack React Query)
## Public exports
From `package.json`:
- `.` — all utilities, Payload exports, tRPC init
- `./payload` — Payload field/hook/block utilities only
- `./trpc/init` — tRPC `initTRPC` instance + builders. **Plan 9:** also exports `t` (the raw `initTRPC.create({...})` instance) so feature packages can build their own procedures via `t.procedure.use(...)`
- `./trpc/context` — tRPC context factory only
- `./trpc/define-error-middleware`**Plan 9:** factory that builds a tRPC middleware translating domain errors to `TRPCError`. Takes `ReadonlyArray<readonly [ErrorCtor, TRPC_ERROR_CODE_KEY]>` tuples; uses `instanceof` discrimination; preserves the original error as `.cause`. **Owned by features:** each feature passes its own constructors in via `integrations/api/procedures.ts`. core-shared never enumerates feature-specific error classes — this stays boundary-clean
## Test conventions
- Tests colocated: `src/lib/slug-field.ts``src/lib/slug-field.test.ts`
- Vitest environment: `node`
- Alias: `@/` resolves to `src/`
- Run: `pnpm test --filter @repo/core-shared`
Covered areas:
- Slug field generation + validation
- Payload hooks (publish-at timestamp, slugify-if-missing)
- Access control helpers
## src/instrumentation/
**Two interfaces:** `ITracer` (in `tracer.interface.ts`) and `ILogger` (in `logger.interface.ts`).
**Three implementation pairs:**
- `NoopTracer` / `NoopLogger` — pass-through. Default everywhere.
- `SentryTracer` / `SentryLogger` — adapters over `@sentry/nextjs`. Live in `sentry/` subfolder. **The `sentry/` subfolder is the only path in `packages/` permitted to import `@sentry/*`** (R40), with the additional exception of `instrumentation/di/bind-sentry-instrumentation.{ts,test.ts}`.
- `RecordingTracer` / `RecordingLogger` — in `@repo/core-testing/instrumentation`, not here.
**`with-span.ts` + `with-capture.ts`:** two higher-order helpers used at DI binding time to wrap use case + controller factory results. The binders apply them as a sandwich — `withSpan` outermost, `withCapture` between span and factory:
```ts
const wrapped = withSpan(
tracer,
{ name: "blog.getArticles", op: "use-case" },
withCapture(
logger,
{ feature: "blog", layer: "use-case", name: "blog.getArticles" },
factory(deps),
),
);
```
`withSpan` is pure delegation to `tracer.startSpan`. `withCapture` catches thrown errors, calls `logger.captureException(err, { tags })`, marks the `__sentryReported` flag, and re-throws — but bails if the flag is already set so the inner-most layer wins.
**`reported-flag.ts`:** small module exporting `markReported(err)` and `isReported(err)`. Used by `withCapture` and `SentryLogger`. `RecordingLogger` carries an inlined copy (tooling → core import is disallowed by the boundary rule).
**Symbols:** `INSTRUMENTATION_SYMBOLS.TRACER`, `INSTRUMENTATION_SYMBOLS.LOGGER` (both `Symbol.for(...)` so cross-realm equality holds).
**`sentry/scrub.ts`:** PII scrubbers used by every `Sentry.init()` call across the monorepo. Substring-based key matching catches derived names (`userEmail`, `accessToken`, `apiKey`, `ipAddress`). IPv4/IPv6 are also redacted from string values via the `[redacted-ip]` token.
**`sentry/init-server.ts` + `init-client.ts`:** centralized init helpers (Next.js flavor) that hard-code R31 (`sendDefaultPii: false`), R32/R33 (scrubbers), R34/R35 (replay mask flags), R37 (sample-rate defaults). Apps call these from `instrumentation.ts` / `instrumentation-client.ts`.
**`sentry/init-server-node.ts` + `init-client-react.ts`:** Vite/non-Next variants used by `apps/web-tanstack`. Same R31/R32/R33/R34/R35/R37 posture; uses `@sentry/node` + `@sentry/react` instead of `@sentry/nextjs`.
**`di/bind-noop-instrumentation.ts` + `bind-sentry-instrumentation.ts`:** bind TRACER + LOGGER symbols to a Container. Returns the resolved instances so callers can use them without container lookup.
**Subpath exports** (`package.json#exports`):
- `./instrumentation` — barrel (interfaces + Noops + withSpan + withCapture + reported-flag helpers + symbols + binders + node/react init helpers)
- `./instrumentation/sentry/init-server` — Next.js server init helper
- `./instrumentation/sentry/init-client` — Next.js client init helper
- `./instrumentation/sentry/init-server-node``@sentry/node` server init (TanStack Start)
- `./instrumentation/sentry/init-client-react``@sentry/react` client init (TanStack Start)
- `./instrumentation/sentry/scrub``beforeSend` / `beforeSendTransaction` (used by per-app PII test)
**Boundaries:**
- `core-shared/instrumentation/sentry/**` MAY import from `@sentry/*`.
- Everything else in `packages/core-shared/src/` MUST NOT.
- The eslint rule in `core-eslint/base.js` enforces the broader monorepo boundary (R40).