chore(work): decompose product-analytics-channel epic

Generated 1 epic + 9 stories under docs/work/epics/ from the approved
PRD docs/work/prds/product-analytics-channel.prd.md. Stories follow the
sequencing hints from the PRD (brand+wrapper → manifest+wireUseCase →
assertFeatureConformance → BindContext+ESLint → React provider → docs).
Ready for `pnpm work dispatch --execute` to begin implementation.
This commit is contained in:
2026-05-18 12:46:25 +02:00
parent 1595c9198e
commit b8d4cfe907
11 changed files with 618 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
{
"updated_at": "2026-05-18T10:35:02.002Z",
"updated_at": "2026-05-18T10:46:27.408Z",
"epics": {
"binder-wrap-helper": {
"status": "done",
@@ -240,9 +240,180 @@
"blocks": []
}
}
},
"product-analytics-channel": {
"status": "in-progress",
"title": "Product analytics as a fourth capture channel (ADR-024 implementation)",
"prd": "docs/work/prds/product-analytics-channel.prd.md",
"stories": {
"01-scaffold-core-analytics-package": {
"status": "in-progress",
"title": "Scaffold @repo/core-analytics package with IAnalytics and NoopAnalytics",
"ac_total": 2,
"ac_completed": 0,
"depends_on": [],
"blocks": []
},
"02-recording-analytics": {
"status": "todo",
"title": "Add RecordingAnalytics to @repo/core-testing",
"ac_total": 1,
"ac_completed": 0,
"depends_on": [
"01-scaffold-core-analytics-package"
],
"blocks": [
"08-react-provider"
]
},
"03-analyzed-brand-and-with-analytics-wrapper": {
"status": "todo",
"title": "Add Analyzed brand and withAnalytics wrapper",
"ac_total": 2,
"ac_completed": 0,
"depends_on": [
"01-scaffold-core-analytics-package"
],
"blocks": [
"04-manifest-schema-and-wire-use-case"
]
},
"04-manifest-schema-and-wire-use-case": {
"status": "todo",
"title": "Extend manifest schema and wireUseCase with analyticsEvents",
"ac_total": 2,
"ac_completed": 0,
"depends_on": [
"03-analyzed-brand-and-with-analytics-wrapper"
],
"blocks": []
},
"05-assert-feature-conformance-analyzed": {
"status": "todo",
"title": "Extend assertFeatureConformance to check Analyzed brand",
"ac_total": 1,
"ac_completed": 0,
"depends_on": [
"04-manifest-schema-and-wire-use-case"
],
"blocks": []
},
"06-analytics-protocol-bind-context": {
"status": "todo",
"title": "Add AnalyticsProtocol and BindContext.analytics",
"ac_total": 1,
"ac_completed": 0,
"depends_on": [
"05-assert-feature-conformance-analyzed"
],
"blocks": [
"09-documentation"
]
},
"07-eslint-rule-no-undeclared-analytics-event": {
"status": "todo",
"title": "Add no-undeclared-analytics-event ESLint rule to @repo/core-eslint",
"ac_total": 1,
"ac_completed": 0,
"depends_on": [
"05-assert-feature-conformance-analyzed"
],
"blocks": [
"09-documentation"
]
},
"08-react-provider": {
"status": "todo",
"title": "Add React provider to @repo/core-analytics",
"ac_total": 1,
"ac_completed": 0,
"depends_on": [
"02-recording-analytics"
],
"blocks": [
"09-documentation"
]
},
"09-documentation": {
"status": "todo",
"title": "Documentation — analytics.md, conformance-quickref, CLAUDE.md, template-tiers",
"ac_total": 2,
"ac_completed": 0,
"depends_on": [],
"blocks": []
}
}
}
},
"ready": [],
"blocked": [],
"ready": [
{
"epic": "product-analytics-channel",
"story": "01-scaffold-core-analytics-package",
"title": "Scaffold @repo/core-analytics package with IAnalytics and NoopAnalytics"
},
{
"epic": "product-analytics-channel",
"story": "09-documentation",
"title": "Documentation — analytics.md, conformance-quickref, CLAUDE.md, template-tiers"
}
],
"blocked": [
{
"epic": "product-analytics-channel",
"story": "02-recording-analytics",
"title": "Add RecordingAnalytics to @repo/core-testing",
"waiting_on": [
"product-analytics-channel/01-scaffold-core-analytics-package"
]
},
{
"epic": "product-analytics-channel",
"story": "03-analyzed-brand-and-with-analytics-wrapper",
"title": "Add Analyzed brand and withAnalytics wrapper",
"waiting_on": [
"product-analytics-channel/01-scaffold-core-analytics-package"
]
},
{
"epic": "product-analytics-channel",
"story": "04-manifest-schema-and-wire-use-case",
"title": "Extend manifest schema and wireUseCase with analyticsEvents",
"waiting_on": [
"product-analytics-channel/03-analyzed-brand-and-with-analytics-wrapper"
]
},
{
"epic": "product-analytics-channel",
"story": "05-assert-feature-conformance-analyzed",
"title": "Extend assertFeatureConformance to check Analyzed brand",
"waiting_on": [
"product-analytics-channel/04-manifest-schema-and-wire-use-case"
]
},
{
"epic": "product-analytics-channel",
"story": "06-analytics-protocol-bind-context",
"title": "Add AnalyticsProtocol and BindContext.analytics",
"waiting_on": [
"product-analytics-channel/05-assert-feature-conformance-analyzed"
]
},
{
"epic": "product-analytics-channel",
"story": "07-eslint-rule-no-undeclared-analytics-event",
"title": "Add no-undeclared-analytics-event ESLint rule to @repo/core-eslint",
"waiting_on": [
"product-analytics-channel/05-assert-feature-conformance-analyzed"
]
},
{
"epic": "product-analytics-channel",
"story": "08-react-provider",
"title": "Add React provider to @repo/core-analytics",
"waiting_on": [
"product-analytics-channel/02-recording-analytics"
]
}
],
"needs_prd_ship": []
}

View File

@@ -0,0 +1,52 @@
---
id: 01-scaffold-core-analytics-package
epic: product-analytics-channel
title: Scaffold @repo/core-analytics package with IAnalytics and NoopAnalytics
type: technical-story
status: in-progress
feature: core-analytics
depends-on: []
blocks:
[
02-recording-analytics,
03-analyzed-brand-and-with-analytics-wrapper,
06-analytics-protocol-bind-context,
08-react-provider,
]
created: 2026-05-18T12:00:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Produce a green `@repo/core-analytics` package — generator-scaffolded skeleton, then fleshed out with `IAnalytics` interface (`track`, `identify`, `pageView`, `flush`), `NoopAnalytics` default implementation, supporting types (`AnalyticsAttributeValue`, `AnalyticsUser`), and full method-level test coverage. The package is vendor-neutral: no third-party analytics SDK is bundled.
## Why
All downstream stories depend on `IAnalytics` existing as the contract surface. Landing this first means every subsequent story compiles and imports from a real package rather than a placeholder.
## Done when
- `packages/core-analytics/` exists as a workspace package `@repo/core-analytics`.
- `IAnalytics`, `AnalyticsAttributeValue`, `AnalyticsUser` are exported from the package root.
- `NoopAnalytics` implements `IAnalytics`; `flush()` resolves with a microtask (`Promise.resolve()`).
- Sibling tests cover all four interface methods on `NoopAnalytics`.
- `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff` all pass.
## In scope
- `packages/core-analytics/` — package scaffold, `IAnalytics` interface, `AnalyticsAttributeValue` + `AnalyticsUser` types, `NoopAnalytics` impl, sibling tests, root barrel export.
- No subpath exports in this story (`./react` lands in Story 08).
- No `withAnalytics` wrapper in this story (Story 03).
## Out of scope
- `RecordingAnalytics` (Story 02).
- `Analyzed` brand / `withAnalytics` wrapper (Story 03).
- React provider (Story 08).
- Backend vendor integration — template ships Noop only.
## Tasks
- [ ] Run `pnpm turbo gen core-package analytics` to scaffold `@repo/core-analytics` — verify the generator output compiles and all gates pass on this commit alone.
- [ ] Replace generator placeholder content with `IAnalytics` interface (`track`, `identify`, `pageView`, `flush`), `AnalyticsAttributeValue` + `AnalyticsUser` types, and `NoopAnalytics` implementation (`flush()` returns `Promise.resolve()`); add sibling tests covering all four methods; export everything from the package root barrel — all gates pass on this commit.

View File

@@ -0,0 +1,42 @@
---
id: 02-recording-analytics
epic: product-analytics-channel
title: Add RecordingAnalytics to @repo/core-testing
type: technical-story
status: todo
feature: core-testing
depends-on: [01-scaffold-core-analytics-package]
blocks: [08-react-provider]
created: 2026-05-18T12:01:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Add `RecordingAnalytics` to `@repo/core-testing` as an in-memory test double that implements `IAnalytics`, parallel to `RecordingAuditLog`. Consumers use it in unit tests and the React provider test.
## Why
Every story that needs to assert analytics behaviour (including the React provider test in Story 08) requires a deterministic implementation. `RecordingAnalytics` provides the same recording double pattern already established by `RecordingAuditLog`.
## Done when
- `RecordingAnalytics` implements `IAnalytics` and records calls to `tracked`, `identified`, `pageViewed` arrays.
- `flush()` resolves with a microtask and clears or drains the in-memory buffer.
- Exported from `@repo/core-testing`'s barrel.
- Sibling test covers all four methods and the flush drain behaviour.
- All gates pass.
## In scope
- `packages/core-testing/src/recording-analytics.ts` + sibling test.
- Export line in `packages/core-testing/src/index.ts`.
## Out of scope
- React provider wiring (Story 08).
- Any vendor-specific recording behaviour.
## Tasks
- [ ] Add `RecordingAnalytics` implementing `IAnalytics` to `@repo/core-testing` — recorded arrays for `track`, `identify`, `pageView`; `flush()` returns `Promise.resolve()`; sibling test covers all methods and flush; export added to barrel — all gates pass on this commit.

View File

@@ -0,0 +1,46 @@
---
id: 03-analyzed-brand-and-with-analytics-wrapper
epic: product-analytics-channel
title: Add Analyzed brand and withAnalytics wrapper
type: technical-story
status: todo
feature: core-shared
depends-on: [01-scaffold-core-analytics-package]
blocks: [04-manifest-schema-and-wire-use-case]
created: 2026-05-18T12:02:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Add the `Analyzed<F>` brand to `core-shared/conformance/` and the `withAnalytics(analytics, factory)` higher-order wrapper to `@repo/core-analytics`. Together they enable brand-based conformance detection at DI bind time, mirroring the `Audited` / `withAudit` pattern.
## Why
`wireUseCase` (Story 04) and `assertFeatureConformance` (Story 05) both key off the `Analyzed` brand at runtime. The brand must exist in `core-shared` (so the assertion layer can import it without a circular dep) while the wrapper lives in `core-analytics` (analytics is optional; can't pollute core-shared with the implementation).
## Done when
- `Analyzed<F> = F & { readonly __analyzed: true }` exists in `packages/core-shared/src/conformance/brands.ts` and `isAnalyzed` guard exists in `packages/core-shared/src/conformance/brand-runtime.ts`.
- `withAnalytics(analytics, factory)` lives in `packages/core-analytics/src/with-analytics.ts`, attaches `Analyzed` via `attachBrand` from `core-shared/conformance/brand-runtime`, and is exported from `@repo/core-analytics`.
- `with-analytics.test.ts` asserts brand is present after wrapping and absent before.
- All gates pass on each commit independently.
## In scope
- `packages/core-shared/src/conformance/brands.ts``Analyzed<F>` type addition.
- `packages/core-shared/src/conformance/brand-runtime.ts``isAnalyzed` guard.
- `packages/core-core-shared/src/conformance/index.ts` — export additions.
- `packages/core-analytics/src/with-analytics.ts` + sibling test.
- `packages/core-analytics/src/index.ts` — export addition.
## Out of scope
- `wireUseCase` composition (Story 04).
- `assertFeatureConformance` extension (Story 05).
- `analyticsEvents` manifest field (Story 04).
## Tasks
- [ ] Add `Analyzed<F>` brand type to `packages/core-shared/src/conformance/brands.ts` and `isAnalyzed(f): f is Analyzed<F>` guard to `brand-runtime.ts`; export both from the conformance index — all gates pass on this commit.
- [ ] Add `withAnalytics(analytics, factory)` wrapper to `packages/core-analytics/src/with-analytics.ts` using `attachBrand` from `core-shared/conformance/brand-runtime` + `with-analytics.test.ts` asserting brand attached after wrapping and absent before; export from `@repo/core-analytics` root barrel — all gates pass on this commit.

View File

@@ -0,0 +1,50 @@
---
id: 04-manifest-schema-and-wire-use-case
epic: product-analytics-channel
title: Extend manifest schema and wireUseCase with analyticsEvents
type: technical-story
status: todo
feature: core-shared
depends-on: [03-analyzed-brand-and-with-analytics-wrapper]
blocks:
[
05-assert-feature-conformance-analyzed,
07-eslint-rule-no-undeclared-analytics-event,
]
created: 2026-05-18T12:03:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Add `analyticsEvents: string[]` (default `[]`) to the per-use-case manifest schema in `define-feature.ts`, update the feature generator template to emit the field, and extend `wireUseCase` to compose `withAnalytics` when an `analytics` arg is provided and the use case declares at least one analytics event.
## Why
The manifest field is the declaration surface the ESLint rule (Story 07) and boot assertion (Story 05) both read from. `wireUseCase` is the composition site that attaches the `Analyzed` brand; it must compose analytics in the right position (`factory → withAnalytics → withAudit → withCapture → withSpan`).
## Done when
- `useCases.<name>.analyticsEvents: string[]` is valid in every feature manifest; existing manifests stay green with an absent or empty field.
- The feature generator template emits `analyticsEvents: []` for each scaffolded use case.
- `wireUseCase({ ..., analytics? })` composes `withAnalytics` when `analytics` is provided; existing callers without `analytics` are unaffected.
- `wire-use-case.test.ts` covers the analytics path (brand present) and the no-analytics path (brand absent).
- All gates pass on each commit independently.
## In scope
- `packages/core-shared/src/conformance/define-feature.ts` — schema extension.
- Feature generator template (`turbo/generators/`) — `analyticsEvents: []` added to use-case scaffold output.
- `packages/core-shared/src/conformance/wire-use-case.ts``analytics?` arg + `withAnalytics` composition.
- `packages/core-shared/src/conformance/wire-use-case.test.ts` — analytics path coverage.
## Out of scope
- `assertFeatureConformance` extension (Story 05).
- ESLint rule (Story 07).
- Migrating existing feature manifests (out of scope per PRD).
## Tasks
- [ ] Add `analyticsEvents: string[]` (default `[]`) to the use-case schema in `define-feature.ts` and update the feature generator template to emit the field — existing manifests remain valid; all gates pass on this commit.
- [ ] Extend `wireUseCase` to accept optional `analytics` arg and compose `withAnalytics` (innermost, before `withAudit`) when provided + update `wire-use-case.test.ts` with analytics path (Analyzed brand present) and no-analytics path (Analyzed brand absent) — all gates pass on this commit.

View File

@@ -0,0 +1,45 @@
---
id: 05-assert-feature-conformance-analyzed
epic: product-analytics-channel
title: Extend assertFeatureConformance to check Analyzed brand
type: technical-story
status: todo
feature: core-shared
depends-on: [04-manifest-schema-and-wire-use-case]
blocks:
[
06-analytics-protocol-bind-context,
07-eslint-rule-no-undeclared-analytics-event,
]
created: 2026-05-18T12:04:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Extend `assertFeatureConformance` to reject a binding at boot time when the manifest's use case declares `analyticsEvents.length > 0` but the bound function does not carry the `Analyzed` brand. Error message names the use case and the missing brand.
## Why
This is the boot-time gate that prevents analytics events from being declared in the manifest but silently skipped at runtime. Mirrors the existing `Audited` check for the same guarantee at the same latency.
## Done when
- `assertFeatureConformance` throws `ConformanceError` naming the missing `Analyzed` brand when `analyticsEvents.length > 0` and the bound function is not `isAnalyzed`.
- A synthetic conformance test (parallel to `assert-bindings.test.ts`) covers: passes when Analyzed present + events declared, throws with message naming `Analyzed` when events declared + brand missing, passes when events empty + brand absent.
- All gates pass.
## In scope
- `packages/core-shared/src/conformance/assert-bindings.ts` — Analyzed check addition.
- Conformance test file for the new assertion path.
## Out of scope
- `BindContext.analytics` field (Story 06).
- ESLint rule (Story 07).
- Any template feature wiring analytics (out of scope per PRD).
## Tasks
- [ ] Add `Analyzed` brand check to `assertFeatureConformance` when `manifest.useCases[name].analyticsEvents.length > 0` + synthetic conformance test asserting `ConformanceError` with message naming `Analyzed` for unwrapped bindings — all gates pass on this commit.

View File

@@ -0,0 +1,42 @@
---
id: 06-analytics-protocol-bind-context
epic: product-analytics-channel
title: Add AnalyticsProtocol and BindContext.analytics
type: technical-story
status: todo
feature: core-shared
depends-on: [05-assert-feature-conformance-analyzed]
blocks: [09-documentation]
created: 2026-05-18T12:05:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Add `AnalyticsProtocol` to `core-shared/di/bind-protocols.ts` and an optional `analytics?: AnalyticsProtocol` field to `BindContext`. This is the DI seam that lets feature binders receive an analytics implementation at bind time without importing the concrete class.
## Why
Feature binders that need analytics accept `ctx.analytics` from the aggregator's `BindContext`. Without the protocol type and context field, there's no typed path for passing analytics through the DI layer. Mirrors how `IEventBus`, `IAuditLog`, and `IJobQueue` are threaded through `BindContext`.
## Done when
- `AnalyticsProtocol` structural type exists in `packages/core-shared/src/di/bind-protocols.ts` and is exported.
- `BindContext` (and `BindProductionContext`) gain `analytics?: AnalyticsProtocol`.
- Existing binder call-sites compile without changes (field is optional).
- All gates pass.
## In scope
- `packages/core-shared/src/di/bind-protocols.ts``AnalyticsProtocol` type.
- `packages/core-shared/src/di/bind-context.ts``analytics?:` field on both context shapes.
- Exports from `@repo/core-shared/di` barrel.
## Out of scope
- Wiring analytics in any actual feature binder (no template feature adopts analytics per PRD).
- `IAnalytics` class methods — `AnalyticsProtocol` is the structural type used for DI; it mirrors the `IAnalytics` shape but lives in `core-shared` to avoid a circular dependency.
## Tasks
- [ ] Add `AnalyticsProtocol` structural type to `packages/core-shared/src/di/bind-protocols.ts` and `analytics?: AnalyticsProtocol` to `BindContext` + `BindProductionContext`; export from the `@repo/core-shared/di` barrel — all gates pass on this commit.

View File

@@ -0,0 +1,43 @@
---
id: 07-eslint-rule-no-undeclared-analytics-event
epic: product-analytics-channel
title: Add no-undeclared-analytics-event ESLint rule to @repo/core-eslint
type: technical-story
status: todo
feature: core-eslint
depends-on: [05-assert-feature-conformance-analyzed]
blocks: [09-documentation]
created: 2026-05-18T12:06:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Add the `conformance/no-undeclared-analytics-event` rule to `@repo/core-eslint` at warn severity. The rule finds `analytics.track("X", ...)` string-literal calls in `*.use-case.ts` files and warns when `"X"` is not declared in the file's feature manifest under `analyticsEvents`. Mirrors `no-undeclared-audit` and `no-undeclared-event-publish`.
## Why
Boot-time conformance (Story 05) catches missing brands at process start, but it can't catch event-slug typos or new undeclared slugs introduced during development before the app is ever booted. The ESLint rule provides sub-second feedback in the editor and in CI, closing the latency gap.
## Done when
- `packages/core-eslint/rules/no-undeclared-analytics-event.js` exists and passes RuleTester fixtures.
- Fixtures cover: declared slug → no warning, undeclared slug → warn, non-use-case file → no-op, manifest with no use cases → no-op.
- `_manifest-ast.js` parses `analyticsEvents` arrays (extends existing AST helper).
- Rule registered in `plugin.js` and `base.js` at `"warn"` severity.
- `pnpm lint` runs the rule; all gates pass.
## In scope
- `packages/core-eslint/rules/no-undeclared-analytics-event.js` + RuleTester test file.
- `packages/core-eslint/rules/_manifest-ast.js``analyticsEvents` parsing addition.
- `packages/core-eslint/plugin.js` + `packages/core-eslint/base.js` — rule registration at warn.
## Out of scope
- Migrating existing use cases (no template feature declares analytics events per PRD).
- Auto-fix — warn only, no `--fix` path in this slice.
## Tasks
- [ ] Add `no-undeclared-analytics-event.js` rule to `@repo/core-eslint` — rule implementation cross-checking `analytics.track("X", ...)` literal slug against manifest `analyticsEvents`, extend `_manifest-ast.js` to parse the field, RuleTester fixtures (declared passes, undeclared warns, non-use-case no-op, no-manifest no-op), register in `plugin.js` + `base.js` at `"warn"` — all gates pass on this commit.

View File

@@ -0,0 +1,43 @@
---
id: 08-react-provider
epic: product-analytics-channel
title: Add React provider to @repo/core-analytics
type: technical-story
status: todo
feature: core-analytics
depends-on: [02-recording-analytics]
blocks: [09-documentation]
created: 2026-05-18T12:07:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Add a `./react` subpath export to `@repo/core-analytics` containing `<AnalyticsProvider value={IAnalytics}>` and `useAnalytics(): IAnalytics`. `useAnalytics()` throws a clear error when called outside a provider. No auto-wired router events.
## Why
Server-side use cases receive analytics through `BindContext.analytics`. Client-side components need an equivalent contract surface so `analytics.track(...)` reads the same whether called from a use case or a React component. The provider bridges `IAnalytics` from DI land to React's context tree without coupling consumers to a specific vendor.
## Done when
- `@repo/core-analytics/react` subpath exports `AnalyticsProvider` and `useAnalytics`.
- `useAnalytics()` throws a named error (`AnalyticsContextError` or similar) when called outside `<AnalyticsProvider>`.
- React Testing Library test: render a child inside `<AnalyticsProvider value={recordingAnalytics}>`, child calls `useAnalytics().track("test.event")`, assert `recordingAnalytics.tracked` contains the event.
- All gates pass.
## In scope
- `packages/core-analytics/src/react/analytics-provider.tsx` + `useAnalytics.ts` (or colocated).
- `packages/core-analytics/src/react/index.ts` — subpath barrel.
- `package.json` `exports` map: `"./react": "./dist/react/index.js"` (or equivalent for the build config).
- React Testing Library test.
## Out of scope
- Auto-wiring Next App Router or TanStack Router route-change hooks (deferred per PRD).
- SSR hydration concerns — provider is a pure context wrapper; consumers handle hydration.
## Tasks
- [ ] Add `AnalyticsProvider` + `useAnalytics()` to `packages/core-analytics/src/react/`, wire `./react` subpath export in `package.json`, write React Testing Library test using `RecordingAnalytics` asserting `track` flows through context — all gates pass on this commit.

View File

@@ -0,0 +1,51 @@
---
id: 09-documentation
epic: product-analytics-channel
title: Documentation — analytics.md, conformance-quickref, CLAUDE.md, template-tiers
type: technical-story
status: todo
feature: docs
depends-on:
[
06-analytics-protocol-bind-context,
07-eslint-rule-no-undeclared-analytics-event,
08-react-provider,
]
blocks: []
created: 2026-05-18T12:08:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Write `docs/guides/analytics.md` and update `docs/guides/conformance-quickref.md`, `CLAUDE.md`, and `docs/architecture/template-tiers.md` to reflect the completed channel. All docs reference the final shipped shape.
## Why
Docs land last (per PRD sequencing) so they can reference the real API rather than draft shapes. The conformance-quickref and CLAUDE.md rule-count bump are load-bearing for agents that use those files as context pointers.
## Done when
- `docs/guides/analytics.md` exists and documents: server-side wiring path (`BindContext.analytics` → feature binder), client-side wiring path (`<AnalyticsProvider>` + `useAnalytics()`), consumer vendor evaluation step (ADR-022 `/evaluate-library` gate), and PII boundary deferral (ADR-024 §"PII boundary").
- `docs/guides/conformance-quickref.md` rule table shows 7 ESLint rules (adds `no-undeclared-analytics-event`) and drift patterns include analytics-event sprawl.
- `CLAUDE.md` rule count updated from 6 to 7 in the conformance ESLint rules list.
- `docs/architecture/template-tiers.md` lists `core-analytics` in the optional-cores table.
- All gates pass on each commit independently.
## In scope
- `docs/guides/analytics.md` — new guide.
- `docs/guides/conformance-quickref.md` — rule table + drift patterns update.
- `CLAUDE.md` — conformance rule count line update.
- `docs/architecture/template-tiers.md` — optional-cores list update.
## Out of scope
- Glossary update (already done in the ADR-024 commit per PRD).
- ADR-024 itself (already exists).
- Storybook component or demo app (deferred per PRD).
## Tasks
- [ ] Write `docs/guides/analytics.md` covering server-side wiring (`BindContext.analytics`), client-side wiring (`<AnalyticsProvider>` + `useAnalytics()`), vendor evaluation step (ADR-022), and PII boundary note — all gates pass on this commit.
- [ ] Update `docs/guides/conformance-quickref.md` (seventh rule + analytics-event drift pattern), `CLAUDE.md` (6 → 7 conformance ESLint rules), and `docs/architecture/template-tiers.md` (add `core-analytics` to optional-cores list) — all gates pass on this commit.

View File

@@ -0,0 +1,30 @@
---
id: product-analytics-channel
prd: docs/work/prds/product-analytics-channel.prd.md
title: Product analytics as a fourth capture channel (ADR-024 implementation)
type: epic
status: in-progress
features: [core-analytics, core-shared, core-testing, core-eslint]
created: 2026-05-18T12:00:00Z
updated: 2026-05-18T10:46:27.080Z
---
## Goal
Ship `@repo/core-analytics` as an optional core package that codifies product analytics as the fourth capture channel alongside `ITracer`, `ILogger`, and `IAuditLog`. Adds `IAnalytics` interface, `NoopAnalytics` + `RecordingAnalytics` implementations, `Analyzed` brand, `withAnalytics` wrapper, `analyticsEvents` manifest field, `assertFeatureConformance` extension, `AnalyticsProtocol` in `BindContext`, `no-undeclared-analytics-event` ESLint rule, and a React provider scaffold — all mirroring the audit channel shape with ADR-024's three deliberate divergences.
## Why
Consumers routinely bolt analytics SDKs on at the React component layer, bypassing manifests, brands, and the five-gate conformance system. Codifying analytics as a channel with the same structural shape as `core-audit` means the conformance gates extend to a fourth signal at zero new gate count.
## Stories
- [ ] [01 — Scaffold @repo/core-analytics package](01-scaffold-core-analytics-package/_story.md)
- [ ] [02 — Add RecordingAnalytics to @repo/core-testing](02-recording-analytics/_story.md)
- [ ] [03 — Add Analyzed brand and withAnalytics wrapper](03-analyzed-brand-and-with-analytics-wrapper/_story.md)
- [ ] [04 — Extend manifest schema and wireUseCase with analyticsEvents](04-manifest-schema-and-wire-use-case/_story.md)
- [ ] [05 — Extend assertFeatureConformance to check Analyzed brand](05-assert-feature-conformance-analyzed/_story.md)
- [ ] [06 — Add AnalyticsProtocol and BindContext.analytics](06-analytics-protocol-bind-context/_story.md)
- [ ] [07 — Add no-undeclared-analytics-event ESLint rule](07-eslint-rule-no-undeclared-analytics-event/_story.md)
- [ ] [08 — Add React provider to @repo/core-analytics](08-react-provider/_story.md)
- [ ] [09 — Documentation](09-documentation/_story.md)