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:
@@ -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": []
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
30
docs/work/epics/product-analytics-channel/_epic.md
Normal file
30
docs/work/epics/product-analytics-channel/_epic.md
Normal 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)
|
||||
Reference in New Issue
Block a user