Merge branch 'worktree-agent-workflow-docs': agent-workflow-docs v1 — surface conformance system
This commit is contained in:
16
AGENTS.md
16
AGENTS.md
@@ -366,6 +366,22 @@ Each feature binder signature is `(ctx: BindProductionContext): void` for produc
|
||||
|
||||
---
|
||||
|
||||
### Conformance contract (every feature)
|
||||
|
||||
Every feature package MUST declare a `src/feature.manifest.ts` using `defineFeature` from `@repo/core-shared/conformance`. The manifest declares the use cases, what they audit/publish/consume, and which optional cores they require.
|
||||
|
||||
The feature's `src/di/bind-production.ts` MUST call `assertFeatureConformance(container, manifest, symbols, ctx)` at the tail of `bindProduction<Name>` so `pnpm dev` refuses to boot if a binding loses its brand.
|
||||
|
||||
Re-export the manifest from `src/index.ts`:
|
||||
|
||||
```ts
|
||||
export { fooManifest, type FooManifest } from "./feature.manifest";
|
||||
```
|
||||
|
||||
See `docs/guides/conformance-quickref.md` for the canonical pattern; the generator (`pnpm turbo gen feature <name>`) emits all of this correctly by default.
|
||||
|
||||
---
|
||||
|
||||
### Cross-feature events and background jobs (Plan 10, ADR-015)
|
||||
|
||||
Three rules:
|
||||
|
||||
18
CLAUDE.md
18
CLAUDE.md
@@ -44,6 +44,21 @@ Turborepo + pnpm monorepo organized by vertical features. Each feature (`auth`,
|
||||
- `docs/guides/audit-and-compliance.md` — DPA-compliant audit logging cookbook (*requires `gen core-package audit`*)
|
||||
- `docs/architecture/template-tiers.md` — must-have vs optional packages and how to scaffold the optionals
|
||||
|
||||
## Conformance system
|
||||
|
||||
Every feature has a `src/feature.manifest.ts` declaring its use cases, audits, publishes, consumes, and required cores. Drift is caught at four latencies:
|
||||
|
||||
| Layer | Latency | Catches |
|
||||
|---|---|---|
|
||||
| **TypeScript brands** | 0s | forgotten `withSpan` / `withCapture` / `withAudit` at bind time |
|
||||
| **ESLint rules** | <1s | manifest ↔ code drift; undeclared `bus.publish` / `auditLog.record`; missing manifest; missing sibling test |
|
||||
| **Boot assertion** (`pnpm dev`) | ~3s | binding without required brand at runtime; manifest edited without rebinder |
|
||||
| **CI drift gate** (`pnpm conformance`) | ~120s | orphan event consumers across features |
|
||||
|
||||
The five conformance ESLint rules: `feature-must-have-manifest` (error), `usecase-must-have-test-file` (error), `required-cores-installed` (error), `no-undeclared-event-publish` (warn), `no-undeclared-audit` (warn).
|
||||
|
||||
See `docs/architecture/agent-first-workflow-and-conformance.md` for the full design and `docs/guides/conformance-quickref.md` for the day-to-day reference.
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- **Relative imports in `src/`** — Source files use relative paths (`../repositories/...`), not `@/` alias
|
||||
@@ -74,6 +89,9 @@ Turborepo + pnpm monorepo organized by vertical features. Each feature (`auth`,
|
||||
- **Realtime is for state delivery, not for replacing tRPC (R0)** — Persistent request/response operations belong on tRPC procedures. Use realtime when the server needs to push without a request or the data is too high-frequency for HTTP
|
||||
- **Realtime channel descriptors are exported; handlers are private (R1)** — A feature's `realtime/<name>.channel.ts` is re-exported from the root barrel; `realtime/handlers/*.handler.ts` is wired only in bind-* files and never re-exported (ESLint-enforced via `no-realtime-handler-reexport`)
|
||||
- **`socket.io` lives in `@repo/core-realtime` only (R2)** — Feature packages MUST NOT import `socket.io` or `socket.io-client`. ESLint rule `no-direct-socket-io` enforces this; allowlist covers `core-realtime/src/socket-io-*.ts` and `apps/*/server.ts`
|
||||
- **Manifest-first ordering** — for any new use case, the workflow is **(1) manifest entry** → **(2) contracts** (`xInputSchema`, `xOutputSchema`, `IXUseCase`) → **(3) tests (red)** → **(4) implementation (green)**. The generator emits the manifest + a self-asserting `bind-production.ts` so new features are conformance-compliant by default
|
||||
- **Self-asserting `bindProductionX(ctx)`** — every feature's bind-production calls `assertFeatureConformance(container, manifest, symbols, ctx)` at its tail. `pnpm dev` refuses to boot on drift
|
||||
- **`pnpm conformance`** — cross-feature event-closure check; fails CI on orphan consumers
|
||||
|
||||
## MCP Servers
|
||||
|
||||
|
||||
@@ -25,6 +25,21 @@ per-use-case patterns below.
|
||||
|
||||
---
|
||||
|
||||
## Workflow ordering
|
||||
|
||||
For any new use case, follow these four steps in order:
|
||||
|
||||
1. **Manifest entry** — declare the use case in `src/feature.manifest.ts` with its `mutates` flag and (initially empty) `audits` / `publishes` / `consumes` arrays.
|
||||
2. **Contracts** — export `xInputSchema`, `xOutputSchema`, and the `IXUseCase` type alias from the use-case file. Factory body starts as `throw new Error("not implemented")`.
|
||||
3. **Tests (red)** — write the failing test that exercises the contract via the factory + a mock repository.
|
||||
4. **Implementation (green)** — fill the factory body until the tests pass.
|
||||
|
||||
The `feature-must-have-manifest` ESLint rule will catch step 1 omissions; `usecase-must-have-test-file` catches step 3. The boot assertion (`assertFeatureConformance` at the tail of `bindProductionX`) catches forgotten wrappers at startup.
|
||||
|
||||
For the fast path, run `pnpm turbo gen feature <name>` — the generator emits the manifest + contracts + bind-production with the assertion already wired in.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Every feature package owns:
|
||||
|
||||
124
docs/guides/conformance-quickref.md
Normal file
124
docs/guides/conformance-quickref.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Conformance system — quick reference
|
||||
|
||||
Day-to-day reference for the manifest-first workflow. For design rationale see `docs/architecture/agent-first-workflow-and-conformance.md` and the interactive `feature-conformance-explainer.html`.
|
||||
|
||||
---
|
||||
|
||||
## The manifest
|
||||
|
||||
Every feature has one at `src/feature.manifest.ts`:
|
||||
|
||||
```ts
|
||||
import { defineFeature } from "@repo/core-shared/conformance";
|
||||
|
||||
export const fooManifest = defineFeature({
|
||||
name: "foo",
|
||||
requiredCores: [],
|
||||
useCases: {
|
||||
getThing: { mutates: false, audits: [], publishes: [], consumes: [] },
|
||||
createThing: {
|
||||
mutates: true,
|
||||
audits: ["thing.created"],
|
||||
publishes: ["foo.thing-created"],
|
||||
consumes: [],
|
||||
},
|
||||
},
|
||||
realtimeChannels: [],
|
||||
jobs: [],
|
||||
} as const);
|
||||
|
||||
export type FooManifest = typeof fooManifest;
|
||||
```
|
||||
|
||||
Field reference:
|
||||
|
||||
| Field | Type | Meaning |
|
||||
|---|---|---|
|
||||
| `name` | string literal | Feature name (kebab-case, matches package name) |
|
||||
| `requiredCores` | string[] | Optional cores this feature requires (e.g. `["audit", "events"]`) |
|
||||
| `useCases.<name>.mutates` | boolean | True for create/update/delete; drives whether `__audited` brand is required |
|
||||
| `useCases.<name>.audits` | string[] | Audit event types this use case emits via `auditLog.record({ type: "X" })` |
|
||||
| `useCases.<name>.publishes` | string[] | Cross-feature events this use case publishes via `bus.publish("X")` |
|
||||
| `useCases.<name>.consumes` | string[] | Cross-feature events this use case consumes (via an event handler) |
|
||||
| `realtimeChannels` | string[] | Realtime channels this feature owns |
|
||||
| `jobs` | string[] | Job slugs this feature enqueues |
|
||||
|
||||
Re-export from `src/index.ts`:
|
||||
|
||||
```ts
|
||||
export { fooManifest, type FooManifest } from "./feature.manifest";
|
||||
```
|
||||
|
||||
## bindProductionX self-assertion
|
||||
|
||||
Every feature's `bind-production.ts` calls the assertion at the tail:
|
||||
|
||||
```ts
|
||||
import { assertFeatureConformance } from "@repo/core-shared/conformance";
|
||||
import { fooManifest } from "../feature.manifest";
|
||||
|
||||
export function bindProductionFoo(ctx: BindProductionContext): void {
|
||||
// ... bind use cases, wrapped with withSpan + withCapture + (if mutating + audits) withAudit ...
|
||||
|
||||
assertFeatureConformance(
|
||||
fooContainer,
|
||||
fooManifest,
|
||||
{
|
||||
getThing: FOO_SYMBOLS.IGetThingUseCase,
|
||||
createThing: FOO_SYMBOLS.ICreateThingUseCase,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The symbol map declares which container symbol each manifest use-case key resolves to.
|
||||
|
||||
## The four gates
|
||||
|
||||
| Gate | When it fires | What it catches | Severity |
|
||||
|---|---|---|---|
|
||||
| `tsc` | on save | forgotten wrappers; manifest-derived slot type rejects unwrapped factory | error |
|
||||
| `eslint` | on save / `pnpm lint` | manifest ↔ code drift; missing sibling test; missing manifest | error or warn |
|
||||
| `pnpm dev` | at boot | binding lost its runtime brand; manifest declares more than wired | throws synchronously |
|
||||
| `pnpm conformance` | CI | orphan event consumers across features | exits non-zero |
|
||||
|
||||
## ESLint rules
|
||||
|
||||
| Rule | Severity | What it does |
|
||||
|---|---|---|
|
||||
| `conformance/feature-must-have-manifest` | error | Use-case files require a sibling manifest |
|
||||
| `conformance/usecase-must-have-test-file` | error | Every `*.use-case.ts` has a sibling `*.use-case.test.ts` |
|
||||
| `conformance/required-cores-installed` | error | Manifest's `requiredCores` must exist as `core-<name>` packages in pnpm-workspace.yaml |
|
||||
| `conformance/no-undeclared-event-publish` | warn | `bus.publish("X")` literal must match the manifest's `publishes` for the use case |
|
||||
| `conformance/no-undeclared-audit` | warn | `auditLog.record({ type: "X" })` literal must match the manifest's `audits` |
|
||||
|
||||
## Workflow ordering for new use cases
|
||||
|
||||
1. **Manifest** — add the use case to `feature.manifest.ts` with empty `audits` / `publishes` / `consumes`
|
||||
2. **Contracts** — export `xInputSchema`, `xOutputSchema`, `IXUseCase` from the use-case file (factory body throws "not implemented")
|
||||
3. **Tests (red)** — write the test importing the contracts; verify it fails
|
||||
4. **Implementation (green)** — fill the factory body until tests pass
|
||||
|
||||
For the fast path: `pnpm turbo gen feature <name>` scaffolds steps 1 + 2 in a single command.
|
||||
|
||||
## Common drift patterns and the gate that catches them
|
||||
|
||||
- **Forgot `withSpan` at bind time** → tsc TS2322 + boot assertion
|
||||
- **Manifest declares `audits: ["X"]` but factory doesn't call `auditLog.record({type:"X"})`** → no automatic catch yet; future story
|
||||
- **Factory calls `bus.publish("Y")` but manifest doesn't declare it** → `conformance/no-undeclared-event-publish` (warn)
|
||||
- **Feature has use cases but no manifest** → `conformance/feature-must-have-manifest` (error)
|
||||
- **Manifest references `requiredCores: ["X"]` but no `core-X` package exists** → `conformance/required-cores-installed` (error)
|
||||
- **One feature consumes `Y` but no feature publishes `Y`** → `pnpm conformance` orphan check (CI gate)
|
||||
|
||||
## Pinning down a drift
|
||||
|
||||
When a gate fires, the error message tells you what to run. For example:
|
||||
|
||||
> `Feature blog has use cases but no feature.manifest.ts. Run 'pnpm turbo gen feature blog' or scaffold the manifest manually at packages/blog/src/feature.manifest.ts.`
|
||||
|
||||
That's the "fix" line — follow it.
|
||||
|
||||
---
|
||||
|
||||
For the deeper design rationale see `docs/architecture/agent-first-workflow-and-conformance.md` and the interactive `feature-conformance-explainer.html`.
|
||||
@@ -32,6 +32,17 @@ pnpm install # link the new workspace package
|
||||
pnpm --filter @repo/widgets lint typecheck test
|
||||
```
|
||||
|
||||
## Conformance-ready by default
|
||||
|
||||
Since milestone v of the conformance system, `pnpm turbo gen feature <name>` emits two conformance artefacts:
|
||||
|
||||
- **`src/feature.manifest.ts`** declaring the scaffolded `getX` use case
|
||||
- **`src/di/bind-production.ts`** with `assertFeatureConformance(...)` called at the tail
|
||||
|
||||
Run `pnpm conformance` after generating a feature — it should pass cleanly. If you add `bus.publish("X")` calls in a factory body, you'll also need to add `"X"` to the manifest's `publishes[]` array for that use case, or the `no-undeclared-event-publish` ESLint rule will warn.
|
||||
|
||||
See `docs/guides/conformance-quickref.md` for the manifest field reference.
|
||||
|
||||
## What it generates
|
||||
|
||||
- Package files: `package.json`, `tsconfig.json`, `vitest.config.ts`,
|
||||
|
||||
@@ -704,3 +704,16 @@ expect(logger.captures[0]).toMatchObject({
|
||||
For an end-to-end example (controller → use case → repo, all wrapped, asserting no double-capture across layers), see `packages/blog/tests/r44-no-double-capture.test.ts`.
|
||||
|
||||
**Default mocks** (when you don't need assertions): construct `new MockArticlesRepository()` with no args — the constructor defaults bind `NoopTracer` + `NoopLogger`.
|
||||
|
||||
---
|
||||
|
||||
## Conformance gates (post-TDD)
|
||||
|
||||
After your tests are green and the impl is committed, four gates check that the new code stays consistent with the feature's manifest:
|
||||
|
||||
1. **TypeScript brands** — the `ProductionUseCase<I, O, M>` slot in `bind-production.ts` only accepts factories wrapped through `withSpan` + `withCapture` + (if mutating with audits) `withAudit`.
|
||||
2. **ESLint rules** — five `conformance/*` rules check manifest ↔ code drift; see `docs/guides/conformance-quickref.md`.
|
||||
3. **Boot assertion** — `assertFeatureConformance` runs at the tail of every `bindProductionX(ctx)`; `pnpm dev` refuses to start on drift.
|
||||
4. **CI drift gate** — `pnpm conformance` runs after `pnpm lint` in CI; fails on orphan event consumers across features.
|
||||
|
||||
The TDD red-green cycle covers behavioural correctness; the conformance gates cover architectural correctness.
|
||||
|
||||
42
docs/work/agent-workflow-docs-v1/01-docs-rewrite/_story.md
Normal file
42
docs/work/agent-workflow-docs-v1/01-docs-rewrite/_story.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
id: 01-docs-rewrite
|
||||
epic: agent-workflow-docs-v1
|
||||
title: Surface conformance system across top-level docs
|
||||
type: technical-story
|
||||
status: done
|
||||
feature: docs
|
||||
depends-on: []
|
||||
blocks: []
|
||||
---
|
||||
|
||||
## Goal
|
||||
Update CLAUDE.md, AGENTS.md, and four guides so agents picking up this
|
||||
repo discover and follow the manifest-first workflow by default.
|
||||
|
||||
## Done when
|
||||
- CLAUDE.md has a "Conformance system" section + manifest-first ordering
|
||||
added to Key Conventions
|
||||
- AGENTS.md's per-feature conventions mention the manifest contract
|
||||
- adding-a-feature.md updated to the 4-step ordering
|
||||
- scaffolding-a-feature.md notes the manifest emission + boot assertion
|
||||
- tdd-workflow.md links to the conformance enforcement
|
||||
- New conformance-quickref.md exists as a single-page agent reference
|
||||
|
||||
## In scope
|
||||
Mechanical doc updates pointing at existing artifacts (the design doc, the
|
||||
feature-conformance-explainer.html, the actual rules / wrappers / script
|
||||
already merged).
|
||||
|
||||
## Out of scope
|
||||
- Rewriting the existing architecture docs (already in place)
|
||||
- Updating per-package AGENTS.md inside packages/*
|
||||
|
||||
## Tasks
|
||||
- [x] Epic + story scaffold
|
||||
- [x] CLAUDE.md update
|
||||
- [x] AGENTS.md update
|
||||
- [x] adding-a-feature.md update
|
||||
- [x] scaffolding-a-feature.md update
|
||||
- [x] tdd-workflow.md update
|
||||
- [x] conformance-quickref.md (new)
|
||||
- [x] Final verification + closeout
|
||||
36
docs/work/agent-workflow-docs-v1/_epic.md
Normal file
36
docs/work/agent-workflow-docs-v1/_epic.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
id: agent-workflow-docs-v1
|
||||
prd: null
|
||||
title: Agent-workflow docs rollout
|
||||
type: epic
|
||||
status: done
|
||||
features: [docs]
|
||||
created: 2026-05-13
|
||||
---
|
||||
|
||||
## Goal
|
||||
Surface the conformance-system-v1 mechanics across CLAUDE.md, AGENTS.md,
|
||||
and the feature-development guides so agents picking up this codebase
|
||||
follow the manifest-first workflow by default.
|
||||
|
||||
## Why
|
||||
The conformance system is the substrate, but agents read the top-level
|
||||
docs first. Until the docs say "feature.manifest.ts exists, here's what
|
||||
it does, here's how the gates fire," the system stays invisible to
|
||||
agents iterating on this repo for the first time.
|
||||
|
||||
## In scope
|
||||
- CLAUDE.md: add conformance system section + manifest-first ordering
|
||||
- AGENTS.md: add manifest convention to per-package conventions
|
||||
- docs/guides/adding-a-feature.md: update workflow to manifest → contracts → tests → code
|
||||
- docs/guides/scaffolding-a-feature.md: note that generator now emits manifest + boot assertion
|
||||
- docs/guides/tdd-workflow.md: link conformance system as the enforcement layer
|
||||
- New docs/guides/conformance-quickref.md: ~80-line agent-facing reference
|
||||
|
||||
## Out of scope
|
||||
- Frontend-conformance docs (next epic)
|
||||
- Work-system-v1 docs (those land with the work-system epic)
|
||||
- Editing the original conformance design docs (already in place)
|
||||
|
||||
## Stories
|
||||
- [x] [01 — Surface conformance system across CLAUDE.md / AGENTS.md / guides](01-docs-rewrite/_story.md)
|
||||
Reference in New Issue
Block a user