80 lines
5.0 KiB
Markdown
80 lines
5.0 KiB
Markdown
# Critical Rules
|
|
|
|
Always-loaded core. Five rules that gate every internal SwiftPM target decision
|
|
under ExtremePackaging.
|
|
|
|
Follow the "ExtremePackaging" architecture pattern: one active Swift package with
|
|
maximum granular modularization into distinct targets. Each target represents a
|
|
single cohesive responsibility with explicit dependencies. This enables isolated
|
|
compilation, parallel builds, clear dependency graphs, and superior testability.
|
|
|
|
### Rule 1: Single Responsibility per Target
|
|
|
|
Create targets with one clear purpose:
|
|
- A single, well-defined responsibility per target
|
|
- Don't mix concerns (UI + networking, models + API client)
|
|
- Name communicates the purpose
|
|
- Independently buildable and testable
|
|
|
|
### Rule 2: Explicit Dependency Declaration
|
|
|
|
Declare dependencies explicitly in `Package.swift`:
|
|
- List all dependencies in the package manifest
|
|
- No implicit/transitive dependencies
|
|
- Minimise cross-target dependencies
|
|
- Prefer unidirectional dependency flow
|
|
|
|
### Rule 3: Target Granularity
|
|
|
|
Prefer smaller, focused targets over larger ones:
|
|
- Single-file targets are acceptable when the unit is genuinely standalone (typography, color foundation, a single protocol, a single transport).
|
|
- Separate by role, not by topical bundling: foundation primitives, infrastructure, protocols, middleware, services, per-feature or per-CLI-verb operation targets, front-door binaries.
|
|
- Each cohesive responsibility (a feature, a CLI verb, a middleware, a transport, a service) gets its own target.
|
|
|
|
### Rule 4: Naming Conventions
|
|
|
|
Follow a consistent naming scheme. Patterns that apply across project shapes:
|
|
|
|
- **Shared foundation:** `Shared*` for cross-target value-types, models, utilities, configuration (e.g. `SharedModels`, `SharedUtils`, `SharedConfiguration`).
|
|
- **Core infrastructure:** `Core*` or descriptive single-purpose names for the foundation domain (protocols, parsers, transports, indexers).
|
|
- **Per-feature or per-verb operation targets:** one target per user-facing flow (UI app) or one per CLI/MCP verb (CLI/MCP app). Name after the responsibility, not after a role suffix (`Distribution`, `Indexer`, `Ingest` in a CLI; the feature's own name in a UI app).
|
|
- **Service targets:** `*Service` or a `Services` aggregator for cross-layer read/write services consumed by multiple front-doors.
|
|
- **Aggregators:** umbrella targets only when the umbrella adds real value (preview hosts, all-features aggregators for app composition); don't add one by default.
|
|
|
|
Project-specific conventions (UI component packages, API client/server splits, design-system packages, hot-reload infrastructure) live in the project's own `AGENTS.md` or rule files, not in this always-loaded universal rule.
|
|
|
|
### Rule 5: Layer Architecture
|
|
|
|
Organize targets into clear architectural layers ordered bottom to top. Imports
|
|
and manifest dependencies point from higher-level consumers to lower-level
|
|
providers. The number and naming of layers varies per project shape; the constants
|
|
are:
|
|
|
|
- **Foundation** (bottom): shared value-types, models, primitive utilities, logging.
|
|
- **Infrastructure**: protocols, persistence, networking, file I/O, parsers, transports.
|
|
- **Domain**: services, indexers, business logic, per-feature or per-CLI-verb operation packages.
|
|
- **Presentation** (UI projects only): component packages, design-system packages, screens.
|
|
- **Front-door** (top): binary targets, apps, CLIs, MCP servers, preview hosts, test harnesses.
|
|
|
|
A lower layer never imports or declares a dependency on a higher layer. Build order
|
|
and dependent propagation travel upward, but the declared `depends on` relation
|
|
points downward. For the active project's actual layer instantiation, read the
|
|
active manifest: root `Package.swift` in a package repo,
|
|
`Packages/Package.swift` in a project monorepo.
|
|
|
|
## Acceptance check
|
|
|
|
The five rules are conforming when each is shown to hold for the active target graph:
|
|
|
|
- **The graph is acyclic and resolves.** SPM rejects a dependency cycle at resolution, so a clean resolve proves Rule 2's "no circular dependencies" floor:
|
|
|
|
```bash
|
|
swift package resolve && swift build
|
|
```
|
|
|
|
Both MUST exit 0. A cycle makes resolution fail.
|
|
|
|
- **Dependencies are declared, not transitive.** Spot-check that every module a target's sources `import` appears in that target's `dependencies:` list (Rule 2). No target relies on a symbol reaching it only through another target's import.
|
|
|
|
- **Observable checklist** for the rules SPM does not enforce, each a clear pass/fail: every target has one cohesive responsibility, not a grab-bag of unrelated concerns (Rule 1); a target is split rather than grown when a second responsibility appears, and single-file targets are allowed when genuinely standalone (Rule 3); target names follow the scheme (`Shared*` foundation, `Core*` or descriptive infrastructure, per-feature/per-verb operation targets, `*Service` services, umbrella aggregators only when they add value) (Rule 4); and every import or manifest dependency goes from a higher-level consumer to a lower-level provider, never the reverse (Rule 5). A back-edge passes `swift build` but fails this checklist.
|