Files
agentic-dev/packages/core-shared/AGENTS.md
Danijel Martinek 841655573b docs(adr): rename ADR-012 — drop Lazar; update title + content + cross-refs
- Rename docs/decisions/adr-012-lazar-conformance.md → adr-012-feature-conventions.md
- Strip "Lazar", "Plan 8/9/10/11", "refactor-logs" refs from all ADRs,
  architecture docs, HTML explainers, and feature/core AGENTS.md files
- Update all incoming links in docs/, packages/*/AGENTS.md, HTML explainers

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:07:37 +02:00

6.2 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; 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 — 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/

Substrate: OpenTelemetry SDK (ADR-017). Sentry is the exporter via @sentry/opentelemetry. Feature packages depend only on the interfaces below — no Sentry or OTel SDK imports.

Three interfaces: ITracer (tracer.interface.ts), ILogger (logger.interface.ts), IMetrics (metrics.interface.ts).

Three implementation pairs:

  • NoopTracer / NoopLogger / NoopMetrics — pass-through. Default everywhere.
  • OtelTracer / OtelLogger / OtelMetrics — emit via OTel API (@opentelemetry/api, @opentelemetry/api-logs). Live in otel/ subfolder. The otel/ subfolder is the only path in packages/ permitted to import @opentelemetry/sdk-* or @sentry/opentelemetry (R52).
  • RecordingTracer / RecordingLogger / RecordingMetrics — 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 OtelLogger. RecordingLogger carries an inlined copy (tooling → core import is disallowed by the boundary rule).

Symbols: INSTRUMENTATION_SYMBOLS.ITracer, INSTRUMENTATION_SYMBOLS.ILogger, INSTRUMENTATION_SYMBOLS.IMetrics (all Symbol.for(...) so cross-realm equality holds).

otel/pii-fields.ts: vendor-neutral PII substring list (PII_KEY_SUBSTRINGS, PII_QUERY_PARAM_SUBSTRINGS). Used by otel/pii-scrub-processor.ts (server) and imported by sentry/init-client*.ts (browser).

otel/pii-scrub-processor.ts: PiiScrubSpanProcessor + PiiScrubLogRecordProcessor. Registered FIRST in the OTel processor chain so all downstream exporters (Sentry) see scrubbed data. Replaces Sentry's beforeSend/beforeSendTransaction hooks on the server side (ADR-017 §7).

sentry/init-server.ts + sentry/init-client.ts: centralized init helpers (Next.js flavor). init-server.ts calls Sentry.init with sendDefaultPii: false (R31) — no beforeSend hook (PII scrubbed at OTel layer). init-client.ts retains beforeSend/beforeSendTransaction because browser does not use the OTel pipeline.

sentry/init-server-node.ts + sentry/init-client-react.ts: Vite/non-Next variants used by apps/web-tanstack. Same posture as their Next.js counterparts.

di/bind-noop-instrumentation.ts + bind-otel-instrumentation.ts: bind ITracer + ILogger + IMetrics symbols to a Container. Returns the resolved instances so callers can use them without container lookup. bindSentryInstrumentation kept as a deprecated alias for one release.

Subpath exports (package.json#exports):

  • ./instrumentation — barrel (interfaces + Noops + OtelTracer/OtelLogger + withSpan + withCapture + reported-flag + symbols + binders)
  • ./instrumentation/otel — OTel init helper + resource builder barrel
  • ./instrumentation/otel/init-server-nodeinitOtelServerNode (app bootstrap, server-side OTel SDK)
  • ./instrumentation/sentry/init-server — Next.js server Sentry.init helper
  • ./instrumentation/sentry/init-client — Next.js browser Sentry.init helper
  • ./instrumentation/sentry/init-server-node@sentry/node server init (TanStack Start)
  • ./instrumentation/sentry/init-client-react@sentry/react browser init (TanStack Start)

Boundaries:

  • core-shared/instrumentation/otel/** MAY import from @opentelemetry/sdk-* and @sentry/opentelemetry.
  • core-shared/instrumentation/sentry/** MAY import from @sentry/* (browser init files).
  • Everything else in packages/core-shared/src/ MUST NOT.
  • ESLint rules R40 + R52 in core-eslint/base.js enforce the broader monorepo boundary.