Skip to main content

Bi-directional runtime reconciler and drift-aware state graph for full-stack systems

Project description

swop

Bi-directional runtime reconciler and drift-aware state graph for full-stack systems.

Version Python License

AI Cost Tracking

PyPI Version Python License AI Cost Human Time Model

  • ๐Ÿค– LLM usage: $3.4500 (23 commits)
  • ๐Ÿ‘ค Human dev: ~$877 (8.8h @ $100/h, 30min dedup)

Generated on 2026-04-26 using openrouter/qwen/qwen3-coder-next


Swop is a Python toolkit for inspecting, reconciling, and maintaining the architecture of full-stack CQRS projects. It scans Python source for commands, queries, events, and handlers; generates deterministic manifests; detects schema drift; and exports the runtime state graph to multiple formats.


Table of Contents


Installation

pip install swop

Development install:

pip install -e ".[dev]"

Requires Python 3.8+ and PyYAML.


Quick Start

1. Initialise a project

swop init

Scaffolds swop.yaml and the .swop/ state directory in the current folder.

2. Annotate your domain code

from dataclasses import dataclass
from swop import command, handler

@command("billing")
@dataclass
class IssueInvoice:
    customer_id: int
    amount: float

@handler(IssueInvoice)
class IssueInvoiceHandler:
    def handle(self, cmd: IssueInvoice) -> int:
        return cmd.customer_id

3. Scan and generate manifests

swop scan --format json
swop gen manifests

4. Watch for changes

swop watch

Re-runs the scan and regenerates manifests automatically when any .py file changes.


CQRS Decorators

swop provides lightweight, no-op decorators that register decorated classes in a module-global registry. They do not change runtime behaviour, so existing code continues to work unchanged.

Decorator Purpose Example
@command(context) Register a command @command("billing") @dataclass class IssueInvoice: ...
@query(context) Register a query @query("catalog") @dataclass class ListProducts: ...
@event(context, emits=[...]) Register a domain event @event("billing", emits=["InvoiceIssued"]) class PaymentReceived: ...
@handler(Target) Register a command/query handler @handler(IssueInvoice) class IssueInvoiceHandler: ...

All decorators expose a __swop_cqrs__ attribute on the decorated class with metadata including kind, context, source_file, and source_line.


CLI Reference

swop [--mode {STRICT,SOFT,OBSERVE,AUTO_HEAL}] <command>
Command Description
swop init Scaffold swop.yaml and .swop/ state dir
swop scan [--format {text,json,html}] [--json-out FILE] [--html-out FILE] [--strict-heuristics] [--strict-errors] Walk source roots and classify CQRS artifacts
swop gen manifests Generate per-context YAML manifests
swop gen proto [--out PATH] Generate .proto from manifests
swop gen grpc-python Compile Python gRPC bindings
swop gen grpc-ts Compile TypeScript gRPC bindings
swop gen services [--bus TYPE] [--base-image IMG] [--grpc-port N] Generate service stubs + docker-compose.cqrs.yml from manifests
swop watch [--once] Watch source files and rebuild on change
swop sync Run one reconciliation pass
swop diff Compute drift and exit non-zero if drift exists
swop state Dump current runtime state as YAML
swop inspect backend|frontend Introspect actual runtime state
swop resolve Diff current scan against stored manifests
swop gen registry [--contracts DIR] [--check] Generate registry.json + REGISTRY.md from contracts/*.json files
swop generate --from-markpact FILE.md [--sync] [--sync-files] [--check-files] [--output-yaml PATH] [--output-docker PATH] Build a ProjectGraph from a Markpact manifest
swop refactor --frontend PATH [--backend PATH] [--db PATH] [--route /path] [--strategy {seeded,louvain}] --out <dir> Extract modules into a new directory
swop doctor [--deep] Verify the local swop environment
swop hook install|uninstall|status Manage the git pre-commit hook

Reconciliation modes

Mode Behaviour
STRICT Fail on any drift
SOFT Log drift, continue (default)
OBSERVE Read-only, never modify
AUTO_HEAL Apply detected fixes automatically

Python API

Scan a project

from swop import scan_project, load_config

cfg = load_config("swop.yaml")
report = scan_project(cfg)

for det in report.detections:
    print(f"{det.kind:8} {det.name:20} ({det.confidence:.1f} via {det.via})")

Generate manifests

from swop import generate_manifests

manifests = generate_manifests(report, cfg)
for mf in manifests.files:
    print(mf.path)

Watch programmatically

from swop import WatchEngine, load_config

cfg = load_config("swop.yaml")
engine = WatchEngine(config=cfg, interval=0.5, debounce=0.3)

# Single-shot rebuild
from swop import rebuild_once
result = rebuild_once(cfg, incremental=True)
print(result.format())

Runtime graph

from swop import SwopRuntime

rt = SwopRuntime(mode="SOFT")
rt.add_model("Pressure", ["temp", "pressure_low", "pressure_high"])
rt.add_service("api", ["/pressure", "/status"])
rt.add_ui_binding("#sensor-temp", "temp")

drift = rt.run_sync()
print(rt.state_yaml())

Configuration

swop.yaml describes the project structure:

version: 1
project: my-service
source_roots: [src]
exclude: ["tests/*", "__pycache__/*"]
bounded_contexts:
  - name: billing
    source: src/billing
  - name: catalog
    source: src/catalog
    external: false
bus:
  type: rabbitmq
  url: amqp://localhost
read_models:
  engine: postgresql
  url: postgresql://localhost/mydb
state_dir: .swop
Key Description
source_roots Directories to scan (relative to project root)
bounded_contexts Named contexts with source paths
exclude Glob patterns to skip
bus Message-bus configuration
read_models Read-model store configuration
state_dir Local state / cache directory

Manifest Generation

For each bounded context swop generates three manifest files under .swop/manifests/<context>/:

  • commands.yml โ€” all detected commands with fields
  • queries.yml โ€” all detected queries with fields
  • events.yml โ€” all detected events with fields

Example output (billing/commands.yml):

version: 1
context: billing
commands:
  IssueInvoice:
    module: billing.ops
    fields:
      - name: customer_id
        type: int
        required: true
      - name: amount
        type: float
        required: true

These manifests are the single source of truth for downstream generators (proto, gRPC, service stubs).


Watch Mode

The watcher uses stdlib-only mtime polling โ€” no extra dependencies.

# Continuous watch
swop watch

# One-shot (CI friendly)
swop watch --once --no-incremental

The watcher automatically:

  • Skips the state directory (.swop/) so regenerated manifests do not re-trigger a rebuild.
  • Debounces bursts of changes into a single rebuild pass.
  • Tracks file creation, modification, and deletion.

Drift Detection & Resolution

Swop compares the expected state (from manifests) with the actual state (introspected from running backend/frontend) and reports drift:

swop diff
swop resolve [--json] [--apply] [--strict] [--no-incremental]

Drift categories:

  • schema โ€” field additions, removals, type changes
  • missing โ€” expected artifacts not found in runtime
  • unexpected โ€” runtime artifacts not in manifests

Use swop sync --mode AUTO_HEAL to apply fixes automatically.


Refactoring

Extract modules from a full-stack project into a clean output directory:

swop refactor --out ./refactored

The refactor pipeline clusters related code, builds a composed module graph, and generates new file layouts while preserving behaviour.


Registry Generation

Generate a registry.json and REGISTRY.md from JSON contract files in a contracts/ directory:

swop gen registry [--contracts DIR] [--check]
Flag Description
--contracts DIR Contracts directory (default: <root>/contracts)
--check Validate only; do not write output files

Markpact Generation

Build a SwopRuntime graph directly from a Markpact manifest (.md file with markpact:* blocks):

swop generate --from-markpact manifest.md \
  [--strict] [--sync] [--sync-files] [--sync-files-dry-run] \
  [--check-files] [--from-disk] [--from-disk-dry-run] \
  [--output-yaml PATH] [--output-docker PATH]
Flag Description
--from-markpact FILE Path to Markpact manifest (required)
--strict Fail fast on any DOQL parse error
--sync Run sync engine after building the graph
--sync-files Materialise markpact:file blocks to their declared paths
--sync-files-dry-run Report which files would be written without writing
--check-files Report drift between markpact:file blocks and filesystem
--from-disk Reverse sync: rewrite blocks with disk content
--from-disk-dry-run Report which blocks would be updated without writing
--output-yaml PATH Write runtime state YAML to this path
--output-docker PATH Write docker-compose YAML to this path

Development

Run tests

pytest

160 tests, all passing.

Project structure

swop/
โ”œโ”€โ”€ cli.py              # CLI entry point
โ”œโ”€โ”€ commands.py         # Command implementations
โ”œโ”€โ”€ config.py           # swop.yaml loader
โ”œโ”€โ”€ core.py             # SwopRuntime orchestrator
โ”œโ”€โ”€ cqrs/               # @command, @query, @event, @handler decorators
โ”œโ”€โ”€ graph.py            # ProjectGraph, DataModel, Service
โ”œโ”€โ”€ introspect/         # Backend & frontend state introspection
โ”œโ”€โ”€ manifests/          # YAML manifest generator
โ”œโ”€โ”€ markpact/           # Manifest parsing and sync engine
โ”œโ”€โ”€ proto/              # Protobuf generation & compilation
โ”œโ”€โ”€ reconcile.py        # Drift detection & resync
โ”œโ”€โ”€ refactor/           # Code clustering & module extraction
โ”œโ”€โ”€ resolve.py          # Schema-evolution resolution
โ”œโ”€โ”€ scan/               # AST scanner for CQRS artifacts
โ”œโ”€โ”€ services/           # Service stub generator
โ”œโ”€โ”€ sync.py             # Sync engine
โ”œโ”€โ”€ tools/              # Project init, doctor, git hooks
โ”œโ”€โ”€ versioning.py       # Graph versioning
โ””โ”€โ”€ watch/              # mtime-polling file watcher

License

Licensed under Apache-2.0.

Status

Last updated by taskill at 2026-04-25 13:39 UTC

Metric Value
HEAD f4f020e
Coverage โ€”
Failing tests โ€”
Commits in last cycle 25

Added registry validation features (directional subset checks and enum/Literal cross-checks), plus many documentation updates (markdown output, changelog generation) and test/configuration improvements for the test harness and CLI.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

swop-0.2.16.tar.gz (132.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

swop-0.2.16-py3-none-any.whl (126.7 kB view details)

Uploaded Python 3

File details

Details for the file swop-0.2.16.tar.gz.

File metadata

  • Download URL: swop-0.2.16.tar.gz
  • Upload date:
  • Size: 132.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for swop-0.2.16.tar.gz
Algorithm Hash digest
SHA256 f49a357eb2f9b3661f6890991c09ced2eeeb88f485678741fb9432261568f05d
MD5 e74c589f203edcfa70b6ff74b0b0bf7c
BLAKE2b-256 247a89fe2c9060e0ce6daca7167f9f7d3bec4614f24474bf6a8c5407862e86bb

See more details on using hashes here.

File details

Details for the file swop-0.2.16-py3-none-any.whl.

File metadata

  • Download URL: swop-0.2.16-py3-none-any.whl
  • Upload date:
  • Size: 126.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for swop-0.2.16-py3-none-any.whl
Algorithm Hash digest
SHA256 861b6308c26ff08d53680609ca6d9129e53447ec64d424eb93e5ed256944a4a9
MD5 093bd8f5e93a82377b191ceaaa2ea6e6
BLAKE2b-256 8801ff94dbf8617339e94a14383efc303260959e11aed5c5e578f210efb669f7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page