Files
agentic-dev/docs/architecture/dependency-flow.md
Danijel Martinek ef2b8e300e docs(plan-9): doc-pass slice 1 — CLAUDE.md, core-shared AGENTS, architecture, plan-8 annotations
First slice of the combined Plan 8 + Plan 9 doc-update pass:
- CLAUDE.md Key Conventions: append schema-in-use-case, presenter,
  controller unknown input, feature-scoped tRPC error mapping, public
  surface split (./ui)
- packages/core-shared/AGENTS.md: document defineErrorMiddleware export
  + t re-export from trpc/init
- docs/superpowers/plans/2026-05-05-plan-8-*.md and matching spec:
  one-line note that some controller/router patterns shifted in Plan 9;
  link to the Plan 9 refactor log
- docs/architecture/overview.md: data-flow box now shows xProcedure +
  xInputSchema + xOutputSchema.parse + presenter + middleware lanes;
  three explanatory paragraphs added (schemas, presenter, error mapping)
- docs/architecture/dependency-flow.md: app-side ./ui subpath note,
  allowed/disallowed examples updated for Plan 9 paths

Remaining doc-pass items (root AGENTS.md, per-feature AGENTS.md ×5,
core-testing AGENTS.md, adding-a-feature.md, tdd-workflow.md,
testing-strategy.md, vertical-feature-spec.md) follow in subsequent
commits — to be dispatched in parallel.
2026-05-06 16:43:13 +02:00

4.6 KiB

Dependency Flow

                    +-------------+   +-----------------+   +-----------+
                    | apps/web-   |   | apps/web-       |   | apps/cms  |
                    | next        |   | tanstack        |   |           |
                    +------+------+   +--------+--------+   +-----+-----+
                           |                   |                  |
        +------------------+--------------+    |                  |
        |                  |              |    |                  |
   +----v-----+      +-----v------+ +-----v----v---+      +-------v------+
   | core-api |      | core-trpc  | | feature      |      | core-cms     |
   |          |      |            | | packages     |      |              |
   +-----+----+      +-----+------+ +------+-------+      +-------+------+
         |                 |               |                      |
         |                 |               |                      |
         +--+-------+------+---------------+----+   +-------------+
            |       |                           |   |
       +----v---+ +-v---------+         +-------v---v---+
       | core-  | | core-ui   |         | core-shared    |
       | shared | |           |         |                |
       +--------+ +-----------+         +----------------+

  Boundary rules (enforced by ESLint + Turborepo boundaries):
    app             → app, core, core-composition, feature, tooling
    feature         → core, tooling
    core            → core, core-composition, tooling
    core-composition → core, core-composition, feature, tooling
    tooling         → tooling
    
    Composition exceptions:
      core-api  → @repo/<feature>/api (subpath only)
      core-cms  → @repo/<feature>/cms (subpath only)

  App-side feature subpaths (Plan 9):
    @repo/<feature>     — contracts (types, errors, schemas, IUseCase aliases, router type, constants)
    @repo/<feature>/ui  — UI artifacts (query builders, components)

Concrete examples

Allowed:

// in apps/web-next
import { appRouter } from "@repo/core-api";
import { NextTrpcProvider } from "@repo/core-trpc/next";
import { bindProductionBlog } from "@repo/blog/di/bind-production";
import { signInInputSchema, type SignInInput } from "@repo/auth";   // contracts (Plan 9)
import { articleBySlugQuery } from "@repo/blog/ui";                  // queries (Plan 9)

// in packages/blog
import { slugifyIfMissing } from "@repo/core-shared/payload";

// in packages/core-api
import { blogRouter } from "@repo/blog/api";          // composition exception
import { router } from "@repo/core-shared/trpc/init"; // core → core fine

// in packages/core-cms
import { articles } from "@repo/blog/cms";             // composition exception

Disallowed:

// in packages/blog (cross-feature)
import { Article } from "@repo/marketing-pages";     // ❌ feature → feature

// in packages/blog (deep import past public exports)
import { articles } from "@repo/blog/src/integrations/cms/collections/articles"; // ❌ no-private

// in packages/core-shared
import { blogRouter } from "@repo/blog/api";         // ❌ core → feature
import { ArticleNotFoundError } from "@repo/blog";   // ❌ core → feature
                                                     // (defineErrorMiddleware takes Error
                                                     // constructors as args from features —
                                                     // core-shared never imports them)

// in packages/core-trpc
import { someBlogThing } from "@repo/blog";          // ❌ core → feature (only core-api/core-cms have exception)

// in apps (using the wrong subpath)
import { articleBySlugQuery } from "@repo/blog";     // ❌ queries live on ./ui
import { Article } from "@repo/blog/ui";             // ❌ types live on the root subpath

Enforcement strategy

Three layers work in tandem:

  1. package.json dependencies — if you didn't declare it, you can't import it
  2. exports map — blocks deep imports; only public subpaths are accessible
  3. Two parallel automated checks (both enforcing the same five-tag model):
    • ESLint eslint-plugin-boundaries runs at lint time, catching direct-import violations
    • Turborepo boundaries runs at build time, validating the entire workspace graph including transitive dependencies

The two enforcement layers are independent but complementary. ESLint is stricter on per-import context (e.g., file-specific exemptions via // @boundaries-ignore), while Turborepo catches transitive issues that lint-time checking misses. Run pnpm lint and pnpm turbo boundaries in CI to catch all violations.