Skip to content

Architecture Checks in CI

Migrated from root technical docs.

This document explains what architecture checks run in CI, how to run them locally, and what to do when they fail.


Terminal window
# Run import-boundary lint on all boundary-critical paths
pnpm lint:arch
# Run structural hygiene checks (tracked artifacts, required docs, etc.)
pnpm check:arch
# Detect workspace package dependency cycles
pnpm check:cycles
# Run all three (what CI runs in the architecture-checks job)
pnpm ci:arch

Both commands are included in the local pnpm validate sequence, so running pnpm validate before pushing is sufficient for full coverage.

If you are not sure where new code should go before running checks, start with 16-contributing-and-templates.md.


Architecture checks run in a dedicated architecture-checks CI job in .github/workflows/turbo-ci.yml. The job is separate from the main validate job so failures are easy to identify and act on.

Step 1: pnpm lint:arch — import-boundary lint

Section titled “Step 1: pnpm lint:arch — import-boundary lint”

Runs Oxlint (same config as pnpm lint) scoped specifically to the paths where architectural boundary rules are enforced:

PathBoundary rules enforced
packages/feature-*/srcMust not import apps, @/, server fns, DB directly
packages/platform-*/srcMust not import feature packages or app code
packages/domain-*/srcMust not import React, TanStack UI, or CF runtime
packages/db/srcMust not import React, TanStack UI, or app code
packages/utils/srcMust not import React or feature packages
packages/types/srcMust not import app code or aliases
packages/schemas/srcMust not import app code or aliases
packages/api-contract/srcMust not import app code or aliases
packages/integration-core/srcMust not import app code or aliases
apps/dashboard/src/featuresMust not statically import server fns; use api.ts
apps/dashboard/src/componentsMust not import feature modules
apps/dashboard/src/sharedMust not import CF runtime or server-only modules
apps/dashboard/src/libMust not import feature modules
apps/dashboard/src/appMust not import server fns or feature internals
apps/dashboard/src/routesMust not import feature internal subfolders

The full rule set is in .oxlintrc.json. Canonical dependency direction rules: docs/architecture/03-dependency-rules.md.

When it fails: Check the oxlint error message. It will name the violated rule and include a message describing the correct pattern (e.g. “use api.ts wrapper with dynamic import() + call()”).

Barrel rule enforcement for cross-feature internal imports is included. Routes and app/ must consume feature modules through their public boundary (@/features/{name}) and may not deep-import into internal subfolders (components/, hooks/, utils/, lib/, store/, mutations/, model/). See 14-barrel-rules.md — Rule 1 and Rule 3.

Step 2: pnpm check:arch — structural hygiene checks

Section titled “Step 2: pnpm check:arch — structural hygiene checks”

Runs scripts/check-arch.mjs. Each check is fast, git-based, or file-system based. No build required.

CheckEnforcementNotes
No artifact directories tracked in gitBlockingCatches coverage/, dist/, logs/, .wrangler/, test-results/ accidentally committed. See 12-generated-artifacts.md.
No stray root-level src/ directoryBlockingGuards against code added at repo root. All code belongs in apps/ or packages/.
Required architecture docs presentBlockingKey architecture docs (00–03, 12, 13) must not be deleted.
Intentionally committed generated artifacts presentBlockingAllowlisted files (routeTree.gen.ts, events/emitters.ts, event-catalog.md) must be regenerated if missing.
Deprecated workers/my-api/srcStaged (non-blocking)Prints a warning if present; does not fail CI. Follow-up: monitor for growth; full removal tracked in 06-current-gaps.md.
Split ingestion topology presentBlockingRequires workers/ingestion-orchestrator, workers/ingestion-process, and packages/platform-ingestion; fails if the retired legacy ingestion package returns.
No retired legacy-ingestion refs in active docsBlockingPrevents current scaffolding, repo map, and onboarding docs from drifting back to the removed legacy ingestion package.

CheckLevelWhy
Import-boundary lint (lint:arch)BlockingRules are mature; boundary violations are clear regressions.
Tracked-artifact hygieneBlockingSimple and stable; git ls-files is reliable.
Required architecture docsBlockingDocs must not vanish silently.
Allowlisted generated artifactsBlockingMissing files indicate a broken generation workflow.
worker-my-api deprecationStaged (warn only)Still present for reference; not yet safe to enforce removal.
Split ingestion topology hygieneBlockingThe split worker + shared runtime layout is now canonical.
Retired legacy-ingestion referencesBlockingActive guidance must keep pointing at workers/ingestion-* and @platform/ingestion.
Cycle detection (check:cycles)BlockingPure-Node DFS over workspace package.json dep graph; zero external deps.
Workspace dependency direction (graph)Not yet addeddepcruise config needed. See §Future checks below.

Read the Oxlint message carefully. Each boundary rule includes a human-readable message pointing to the correct pattern:

Feature packages must not statically import server function modules.
Use dynamic import() + call() from api.ts
(see docs/architecture/03-dependency-rules.md)

Fix the import using the pattern described. Common cases:

  • Feature importing server function → add a wrapper in the feature’s api.ts using dynamic import() + call()
  • Shared component importing feature → move shared logic to a utility or keep it inside the feature
  • Package importing app code → use a package entrypoint or @host/* shim

Each check in check:arch prints a hint with the remediation step:

❌ 2 artifact file(s) tracked in git
→ Run: git rm --cached <file>
See: docs/architecture/12-generated-artifacts.md

Follow the hint. If you believe the file should be committed, check 12-generated-artifacts.md for the allowlist policy and add an explicit exception there if justified.

pnpm check:cycles reads all workspace package.json files, builds a dependency graph, and reports any cycles:

✗ 1 workspace dependency cycle(s) found:
@legaciti/feature-foo → @legaciti/feature-bar → @legaciti/feature-foo
Fix by removing one of the cyclic dependencies. See docs/architecture/03-dependency-rules.md

Remove the dep that closes the cycle. If the shared code needs to be available to both packages, extract it into a third package (e.g. a platform-* or domain-* package) with no feature dependencies.


Terminal window
# Full local validation (same as CI validate + architecture-checks jobs)
pnpm validate
# Architecture checks only (lint + hygiene + cycles)
pnpm ci:arch
# Just import-boundary lint
pnpm lint:arch
# Just structural hygiene
pnpm check:arch
# Just cycle detection
pnpm check:cycles

The following checks are desirable but not yet ready to block CI:

CheckReason not yet enforcedRecommended next step
Workspace dependency direction graphdepcruise config neededEvaluate after package extraction stabilizes
In-feature barrel indirectionSame-feature absolute @/features/own/ paths allowedEnforce relative-only once all same-feature violations fixed

These are tracked in 06-current-gaps.md.