diff --git a/README.md b/README.md index d5546f9..0026998 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- Architecture Governance & Architecture-as-Code + Architecture Insights, Governance & Versioned Design

@@ -22,7 +22,7 @@ > **Warning:** Experimental. Expect breaking changes until release 0.1.0 -Pacta is an architecture governance tool that helps teams define architectural intent, gain insights through metrics and historical trends, detect architectural drift, and evolve codebases safely without blocking delivery. +Pacta turns software architecture into versioned, queryable data — so teams can see, compare, and reason about architectural change over time, not just block violations. ```bash pip install pacta @@ -45,86 +45,23 @@ Codebases rot. Architecture degrades through small changes no one tracks. Pacta ## What it does -- **Static analysis** — parses Python AST, builds a system graph -- **Layer enforcement** — domain can't import from infra, etc. -- **Snapshots** — version your architecture like code -- **Baseline mode** — fail only on *new* violations, not legacy debt -- **History tracking** — view architecture evolution over time -- **Trend analysis** — track violations, nodes, edges over time with charts - -## Quick example - -> This is a minimal example. See the docs for advanced rules, baselines, and history. - -Define your layers in `architecture.yml`: - -```yaml -version: 1 -system: - id: myapp - name: My App - -containers: - backend: - code: - roots: [src] - layers: - domain: - patterns: [src/domain/**] - infra: - patterns: [src/infra/**] -``` +- **Architecture snapshots** — version your architecture like code +- **History & trends** — track how dependencies, coupling, and violations evolve over time +- **Diffs** — compare architectural states like Git commits +- **Metrics & insights** — nodes, edges, layers, instability, drift +- **Rules & governance** — express architectural intent and enforce it incrementally +- **Baseline mode** — govern change without being blocked by legacy debt -Add rules in `rules.pacta.yml`: - -```yaml -rule: - id: no_domain_to_infra - name: Domain cannot depend on Infrastructure - severity: error - target: dependency - when: - all: - - from.layer == domain - - to.layer == infra - action: forbid - message: Domain layer must not import from Infrastructure -``` +## Think of Pacta like Git for architecture -Run it: +| Git | Pacta | +|-----|-------| +| `git commit` | `pacta scan` — capture an architectural snapshot | +| `git log` | `pacta history` — timeline and trends of architectural states | +| `git diff` | `pacta diff` — compare two snapshots | +| branch protection | `rules.pacta.yml` — governance that prevents drift | -```bash -$ pacta scan . --model architecture.yml --rules rules.pacta.yml - -✗ 2 violations (2 error) [2 new] - ✗ ERROR [no_domain_to_infra] Domain cannot depend on Infrastructure @ src/domain/user.py:3:1 - status: new - Domain layer must not import from Infrastructure -``` - -### Baseline workflow - -Got legacy violations? Save a baseline and only fail on new ones: - -```bash -# Save current state -pacta scan . --model architecture.yml --rules rules.pacta.yml --save-ref baseline - -# Later, in CI — fail only on new violations -pacta scan . --model architecture.yml --rules rules.pacta.yml --baseline baseline -``` - -### History tracking - -Every scan creates a content-addressed snapshot. Inspect how your architecture evolves over time: - -```bash -# View timeline -$ pacta history show --last 5 - -# View trends over time (violations, nodes, edges, coupling) -$ pacta history trends . -``` +See the [Getting Started](https://akhundmurad.github.io/pacta/getting-started/) guide for a full walkthrough. ## Docs @@ -139,7 +76,7 @@ $ pacta history trends . - [x] Trend analysis with chart export - [ ] Architecture visualization (Mermaid, D2) - [ ] Health metrics (drift score, instability) -- [ ] Optional hosted service (future): +- [ ] Future: Architecture Intelligence Layer: - Cross-repository insights - Historical trend analysis - Team-level governance and reporting diff --git a/docs/getting-started.md b/docs/getting-started.md index d17e42e..145fafb 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,35 +1,20 @@ # Getting Started -This guide walks you through setting up Pacta in your Python project to enforce architectural rules. +You're six months into a project. The codebase that started clean now has database calls in the domain layer, circular dependencies nobody remembers adding, and a "quick fix" that coupled two services together. You know the architecture drifted — but when? How much? Is it getting worse? -## Prerequisites +Pacta answers these questions. It versions your architecture like Git versions your code, so you can see exactly how your system evolves over time. -- Python 3.10 or later -- A Python project with importable modules +--- -## Installation +## Your First Snapshot -Install Pacta from PyPI: +Install Pacta: ```bash pip install pacta ``` -For chart image export (PNG/SVG), install with visualization support: - -```bash -pip install pacta[viz] -``` - -Verify the installation: - -```bash -pacta --help -``` - -## Step 1: Define Your Architecture - -Create an `architecture.yml` file in your project root. This file defines your system structure and architectural layers. +Create `architecture.yml` to describe your system's layers: ```yaml version: 1 @@ -39,44 +24,91 @@ system: containers: backend: - name: Backend Service code: - roots: - - src + roots: [src] layers: domain: - name: Domain Layer - description: Core business logic - patterns: - - src/domain/** + patterns: [src/domain/**] application: - name: Application Layer - description: Use cases and orchestration - patterns: - - src/application/** + patterns: [src/application/**] infra: - name: Infrastructure Layer - description: External services, databases, APIs - patterns: - - src/infra/** + patterns: [src/infra/**] +``` + +Now capture a snapshot: + +```bash +pacta snapshot . --model architecture.yml +``` + +Pacta just analyzed every module and dependency in your codebase and stored a content-addressed snapshot in `.pacta/`. This snapshot is immutable — a permanent record of your architecture at this moment. + +--- + +## Watching Architecture Change + +A week passes. Your team ships features, fixes bugs, refactors code. Run another snapshot: + +```bash +pacta snapshot . --model architecture.yml +``` + +Now you have two points in time. See what changed: + +```bash +pacta history show --last 5 +``` + +``` +TIMESTAMP SNAPSHOT NODES EDGES VIOLATIONS +2024-01-22 14:30:00 f7a3c2... 48 82 0 +2024-01-15 10:00:00 abc123... 45 78 0 +``` + +Three new modules, four new dependencies. But are things getting better or worse? View the trend: + +```bash +pacta history trends . --metric edges +``` + +``` +Edge Count Trend (5 entries) +============================ + + 82 │ ● + │ ●-------------- + 79 │ ●---------- + │ + 76 ├●--- + └──────────────────────────────── + Jan 15 Jan 22 + +Trend: ↑ Increasing (+6 over period) +First: 76 edges (Jan 15) +Last: 82 edges (Jan 22) + +Average: 79 edges +Min: 76, Max: 82 +``` + +Coupling is climbing. You caught drift early — before it became a problem someone complains about in a retrospective. + +Need to share this with the team? Export as an image: + +```bash +pip install pacta[viz] # one-time install for chart export +pacta history trends . --metric edges --output coupling-trend.png ``` -**Key concepts:** +This generates a publication-ready chart with trend annotations — drop it into a PR, a Slack thread, or your architecture docs. -| Concept | Description | -|---------|-------------| -| `system` | Top-level identifier for your project | -| `containers` | Deployable units (services, apps) | -| `roots` | Directories to scan for code | -| `layers` | Architectural boundaries within a container | -| `patterns` | Glob patterns mapping code to layers | +--- -## Step 2: Define Your Rules +## Adding Guardrails -Create a `rules.pacta.yml` file to define architectural constraints: +You want to protect what you've built. Create `rules.pacta.yml`: ```yaml -# Domain layer must not depend on Infrastructure rule: id: no_domain_to_infra name: Domain cannot depend on Infrastructure @@ -88,129 +120,104 @@ rule: - to.layer == infra action: forbid message: Domain layer must not import from Infrastructure - suggestion: Use dependency injection and define interfaces in the domain layer - -# Domain layer must not depend on Application -rule: - id: no_domain_to_application - name: Domain cannot depend on Application - severity: error - target: dependency - when: - all: - - from.layer == domain - - to.layer == application - action: forbid - message: Domain layer must be independent of use cases ``` -**Rule structure:** - -| Field | Description | -|-------|-------------| -| `id` | Unique identifier for the rule | -| `name` | Human-readable rule name | -| `severity` | `error` (fails build), `warning`, or `info` | -| `target` | What to evaluate (`dependency` or `node`) | -| `when` | Conditions that trigger the rule | -| `action` | `forbid`, `allow`, or `require` | -| `message` | Explanation shown on violation | -| `suggestion` | How to fix the violation | - -## Step 3: Run Your First Scan - -Scan your project: +Run a scan with rules: ```bash pacta scan . --model architecture.yml --rules rules.pacta.yml ``` -**Example output with violations:** - ``` -✗ 2 violations (2 error) +✗ 2 violations (2 error) [2 new] - ✗ ERROR [no_domain_to_infra] Domain cannot depend on Infrastructure @ src/domain/user.py:3:1 + ✗ ERROR [no_domain_to_infra] @ src/domain/user.py:3:1 status: new - "myapp.domain.UserService" in domain layer imports "myapp.infra.Database" in infra layer - - ✗ ERROR [no_domain_to_application] Domain cannot depend on Application @ src/domain/order.py:5:1 - status: new - "myapp.domain.OrderEntity" in domain layer imports "myapp.application.OrderService" in application layer + Domain layer must not import from Infrastructure ``` -**Example output with no violations:** +Two violations. But wait — this is a legacy codebase. You can't fix everything today. -``` -✓ 0 violations -``` +--- -## Step 4: Set Up Baseline (Optional) +## Living with Legacy -If you have existing violations you can't fix immediately, create a baseline. Future scans will only fail on *new* violations: +Save the current state as a baseline: ```bash -# Save current state as baseline pacta scan . --model architecture.yml --rules rules.pacta.yml --save-ref baseline +``` -# Later scans compare against baseline +Now future scans compare against this baseline: + +```bash pacta scan . --model architecture.yml --rules rules.pacta.yml --baseline baseline ``` -With a baseline, output shows violation status: +New violations fail CI. Existing ones are tracked but tolerated. You can pay down debt at your own pace while preventing new debt from accumulating. -``` -✗ 3 violations (2 error, 1 warning) [1 new, 2 existing] +A month later, you check progress: - ✗ ERROR [no_domain_to_infra] Domain cannot depend on Infrastructure @ src/domain/new_feature.py:3:1 - status: new <-- This is new, will fail CI +```bash +pacta history trends . --metric violations +``` - ✗ ERROR [no_domain_to_infra] Domain cannot depend on Infrastructure @ src/domain/legacy.py:10:1 - status: existing <-- Known issue, won't fail CI ``` +Violations Trend (8 entries) +============================ -## Step 5: Track History + 12 ├● + │ ●---● + 8 │ ●---● + │ ●---● + 4 │ ● + └──────────────────────────────── + Feb 01 Feb 28 -Every scan creates a content-addressed snapshot. View your architecture evolution: +Trend: ↓ Improving (-8 over period) +First: 12 violations (Feb 01) +Last: 4 violations (Feb 28) -```bash -# View timeline of snapshots -pacta history show . --last 10 +Average: 7 violations +Min: 4, Max: 12 +``` -# View violation trends over time -pacta history trends . --metric violations +You're winning. Export it for the next retro: -# Export chart as image -pacta history trends . --output violations.png +```bash +pacta history trends . --metric violations --output debt-burndown.png ``` -## Project Structure Example +--- -Here's a typical project structure with Pacta configuration: +## The Bigger Picture + +Traditional architecture tools give you a pass/fail at a point in time. Pacta gives you something different: a versioned history of your architecture that you can query, compare, and trend. + +When someone asks "when did our architecture start degrading?" — you have the answer. When a refactor claims to improve coupling — you can measure it. When leadership wants proof that technical debt is being addressed — you have the chart. + +Architecture stops being something that "just happens" and becomes something you observe, understand, and control. + +--- + +## Reference + +**Project structure:** ``` myproject/ -├── architecture.yml # Architecture definition -├── rules.pacta.yml # Architectural rules +├── architecture.yml # Layer definitions +├── rules.pacta.yml # Governance rules ├── src/ -│ ├── domain/ # Business logic (no external dependencies) -│ │ ├── __init__.py -│ │ ├── entities.py -│ │ └── services.py -│ ├── application/ # Use cases (orchestrates domain + infra) -│ │ ├── __init__.py -│ │ └── use_cases.py -│ └── infra/ # External services (implements domain interfaces) -│ ├── __init__.py -│ ├── database.py -│ └── api_client.py -└── .pacta/ # Pacta data directory (auto-created) - └── snapshots/ # Content-addressed snapshot storage -``` - -## Next Steps - -- [CLI Reference](cli.md) - All commands and options -- [Architecture Model](architecture.md) - Full configuration schema -- [Rules DSL](rules.md) - Advanced rule conditions -- [CI Integration](ci-integration.md) - GitHub Actions and GitLab CI setup +│ ├── domain/ +│ ├── application/ +│ └── infra/ +└── .pacta/ # Snapshot storage +``` + +**Next steps:** + +- [CLI Reference](cli.md) — Commands and options +- [Architecture Model](architecture.md) — Configuration schema +- [Rules DSL](rules.md) — Rule conditions +- [CI Integration](ci-integration.md) — Automate in your pipeline