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

5.5 KiB

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 platforminitTRPC.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-middlewarePlan 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.tssrc/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:

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/scrubbeforeSend / 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).