Skip to main content

Continuous quality gate daemon for agent-driven Python development.

Project description

pulci

Continuous quality gate daemon for agent-driven Python development.

CI License Python 3.10+ Rust

v0.0.5 — Apache-2.0 — docs/AGENTS.md

Why

When AI coding agents (Claude Code, Cursor, Codex) iterate on a Python project, they invoke ruff check, ty check, pytest over and over. Pre-commit hooks run at commit time. CI runs even later. Nothing in the existing tooling stack was designed for the loop an agent actually runs: edit → check → edit, dozens to hundreds of times per hour.

pulci is a small daemon — Rust core, Python CLI — that runs your configured quality gates continuously as files change, and exposes the aggregated state as structured JSON. Agents stop re-invoking tools; they query state.

Comparison

Tool When it runs Output format Built for
pre-commit Commit time Human terminal Humans
prek Commit time (fast) Human terminal Humans
MegaLinter CI time Reports CI/CD
pytest-watch File change Human terminal Humans
pulci Iteration time Compiler-style + JSON Agents

pulci does not replace any of these. It fills the empty quadrant.

Demo

pulci watching a project for changes

MCP server

pulci ships an MCP server for Claude Desktop, Cursor, and any MCP-compatible host.

Auto-install (recommended) — writes the entry into the host's config file directly, preserving any other MCP servers you have:

pulci mcp install claude-desktop      # ~/Library/Application Support/Claude/...
pulci mcp install cursor              # .cursor/mcp.json (project-local)
pulci mcp install cursor --global     # ~/.cursor/mcp.json (all projects)
pulci mcp install cursor --dry-run    # preview without writing

For Claude Code, use its native claude mcp add pulci $(which pulci) mcp instead.

Manual setuppulci mcp info prints the JSON block you can paste yourself:

{
  "mcpServers": {
    "pulci": {
      "command": "/path/to/.venv/bin/pulci",
      "args": ["mcp"]
    }
  }
}

Once configured, the host exposes the pulci_status tool — agents call it instead of invoking ruff/ty/pytest directly.

Real example: what the agent loop looks like

I built pulci because I wanted my own Claude Code sessions to stop wasting tokens re-invoking ruff after every edit. Here is the actual loop:

Before pulci — each edit pays the cold start of every tool, the agent re-parses the output, and the diagnostic stream grows linearly with violation count:

agent: edit src/api.py
agent: $ ruff check src/api.py
        ... 2391 tokens of JSON ...
agent: $ ty check src/api.py
        ... more tokens ...
agent: parse, decide next action

With pulci — the daemon already ran the checks in the background. The agent reads state_version before the edit, waits for it to advance after, then reads a single structured response:

# 1. Capture the version before editing
v = (await pulci_status())["state_version"]

# 2. Make the edit (write_file, replace_in_file, whatever)
edit_file("src/api.py", "...")

# 3. Wait for the daemon to process it — no polling, no fixed sleeps
result = await pulci_status(since_version=v)

# 4. Branch on the structured response
if result.get("tool_errors"):
    # A hook timed out or crashed — retry or surface
    ...
elif result["summary"]["errors"] > 0:
    # Real diagnostics; iterate
    for d in result["diagnostics"]:
        ...
else:
    # Clean. Move on.

In the benchmark fixture (28 files, all hooks enabled), pulci's per-iteration cost is 339 tokens vs 2391 for the manual invocation flow — a 7× reduction, freeing the agent's context window for actual reasoning. The latency is also lower (329 ms vs 466 ms) because the daemon stays warm between checks.

The MCP tool docstring documents the exact response shapes for not_running, running_no_checks_yet, timeout, error, and tool_errors so an agent can branch cleanly on each — see docs/AGENTS.md for the full table.

Diagnose your setup: pulci doctor

When something looks wrong, pulci doctor tells you which layer is broken without running the daemon:

$ pulci doctor
Configuration
   project root       /home/me/my-project
   pulci.toml         valid; enabled hooks: ruff, ty, pytest

Tool resolution
   ruff               0.7.4     via local-venv  .venv/bin/ruff
   ty                 0.0.3     via local-venv  .venv/bin/ty
   pytest             8.3.0     via local-venv  .venv/bin/pytest

Filesystem
   .pulci/ writable   .pulci

Daemon
   running            not running (run `pulci start` to launch)

State
   state.json         not present (daemon never ran)

All checks passed.

It catches typos in pulci.toml (like clipy = true inside [hooks]), missing tools, permission issues, and corrupted state files. Exits 0 when clean, 1 if anything's broken. Add --json for structured output.

For AI agents

AI coding agents (Claude Code, Cursor, Codex) should start at docs/AGENTS.md.

The short version: run pulci start once, then call pulci status --json after each edit instead of invoking ruff/ty/pytest directly.

Install

pip install pulci
pulci --version

From source (to modify the Rust core — requires Rust stable and uv):

git clone https://github.com/grego-casparri/pulci
cd pulci
uv sync
uv run maturin develop --release
uv run pulci --version

Usage

Start the daemon (runs in foreground; press Ctrl-C to stop):

pulci start                   # watches current directory, human output
pulci start /path/to/project  # explicit root
pulci start --agent           # suppress startup messages; structured exit events

Output — compiler-style diagnostics, one per line (same in both modes):

src/foo.py:12:1: error[ruff/F401] 'os' imported but unused
1 error, 0 warnings (3 files checked, 0.4s)

Query current state (reads .pulci/state.json):

pulci status                        # human-readable table
pulci status --json                 # full JSON for agents
pulci status /path/to/project --json

Sample pulci status --json output:

{
  "schema_version": 1,
  "state_version": 7,
  "timestamp": "2026-05-16T12:00:00Z",
  "summary": { "errors": 2, "warnings": 1, "checks_run": 3, "stale": false },
  "tools": [
    {"name": "ruff", "version": "0.7.4", "source": "local-venv", "path": ".venv/bin/ruff"},
    {"name": "ty",   "version": "0.0.3", "source": "uvx-latest", "path": null}
  ],
  "diagnostics": [
    {
      "tool": "ruff",
      "file": "src/foo.py",
      "line": 12,
      "col": 1,
      "severity": "error",
      "code": "F401",
      "message": "'os' imported but unused"
    }
  ]
}

Configuration

Create pulci.toml in the project root (all fields optional):

[hooks]
ruff         = true    # ruff check on changed .py files (default: true)
ruff_format  = false   # ruff format --check; format violations as diagnostics (default: false)
ty           = true    # ty check on changed .py files (default: true)
pytest       = false   # pytest on matching test files (default: false)
timeout_secs = 120     # per-hook subprocess timeout in seconds (default: 120)

# How pytest maps source → tests. Each template substitutes {stem} for the
# source's file stem. Empty (default) keeps `tests/test_{stem}.py` only.
pytest_test_patterns = [
    "tests/test_{stem}.py",
    "test/test_{stem}.py",
]

[tools]            # optional — pin exact versions for reproducibility
ruff   = "0.7.4"   # uses uvx ruff@0.7.4 instead of auto-resolving
ty     = "0.0.3"

[watch]            # optional — paths to skip from initial scan and events
exclude = ["vendor", "fixtures"]

If pulci.toml is absent, defaults apply (ruff=true, ty=true, pytest=false, ruff_format=false, timeout 120 s). Unknown keys fail at startup (typos like clipy are rejected loudly rather than silently ignored). If [tools] is absent, pulci resolves each tool automatically: .venv/bin/<tool>$PATHuvx <tool>. Pinned versions are probed at daemon startup, so a typo or unpublished version fails fast.

pulci watches .py files only (.rs is included for internal Rust dogfooding, not a user-facing feature). Non-Python files are ignored without notice.

Benchmark

A benchmark script is included to compare pulci against manual tool invocation and prek across N iterations:

uv run python benchmarks/bench_modes.py --iterations 50

Metrics: mean/p50/p95 latency per iteration, total wall time, estimated output tokens per iteration. pulci's compact state.json is a fixed-schema file; manual tool output grows linearly with the number of violations.

State file contract

.pulci/state.json is the primary contract between pulci and consumers. Schema version is 1 and will be bumped on breaking changes.

Scope of diagnostics. Each write of state.json reflects the last check pass, not the cumulative state of the whole project. The initial sweep at daemon startup populates state with diagnostics across every watched source file; every subsequent file change replaces that with the diagnostics from the files in that change. A consumer that asks "is the project clean?" after editing one file gets back "the files just checked are clean", not "no problems anywhere". This is intentional for the iteration loop (the relevant question is "did my edit introduce errors?"), but the difference matters for agents tracking project-wide health — they should consult the post-startup sweep snapshot, or trigger a rescan, when they want the full view.

stale: true appears when pulci detects that the tool binaries changed between daemon runs (e.g. ruff was upgraded in the venv). It goes back to false after the next check pass. Treat a stale: true state as a fresh full re-check, not stale data.

summary.checks_run is the count of hook adapters that ran in the last check pass (typically 2-4 depending on which hooks are enabled), not a cumulative counter of total check passes since daemon start.

Contributing

See CONTRIBUTING.md.

License

Apache-2.0

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

pulci-0.0.5.tar.gz (68.0 kB view details)

Uploaded Source

Built Distributions

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

pulci-0.0.5-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (1.3 MB view details)

Uploaded CPython 3.14macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

pulci-0.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (761.3 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

pulci-0.0.5-cp312-cp312-win_amd64.whl (689.3 kB view details)

Uploaded CPython 3.12Windows x86-64

pulci-0.0.5-cp312-cp312-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

pulci-0.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (761.9 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

pulci-0.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (761.9 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

pulci-0.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (761.8 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

File details

Details for the file pulci-0.0.5.tar.gz.

File metadata

  • Download URL: pulci-0.0.5.tar.gz
  • Upload date:
  • Size: 68.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pulci-0.0.5.tar.gz
Algorithm Hash digest
SHA256 1afeb583a508702f4e469ea7cef74b0052f38ec49851fbfc80e3a77ad325a7d8
MD5 ae20f05853e7f3c43c85b3f7256fa539
BLAKE2b-256 9a1db25b0a2257f72386f07f177243b889114b565aae9da4d9abf8dfbc7ddbd1

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.5.tar.gz:

Publisher: release.yml on grego-casparri/pulci

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulci-0.0.5-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for pulci-0.0.5-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 0699985be95338e48c333cf001df72ea46657bf94b32af0d9a8b85841fdda9b5
MD5 de86969e2b0f012258f1b16f1a60cce3
BLAKE2b-256 bea73f9485e3ffeca100e52fd750716af63cfe626743ae902def0a2adebe09aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.5-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: release.yml on grego-casparri/pulci

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulci-0.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 c2a43a62da3120e7c21f292f911f021acb7fcad50f1c117ad9e0ed946de6dfd2
MD5 ebca31a0da9d29632da4bb3af5a4123e
BLAKE2b-256 0e74def555c399faa3bd0eb1847053f8936b115101f6995eb2ecc5241e7e1fed

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on grego-casparri/pulci

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulci-0.0.5-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: pulci-0.0.5-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 689.3 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pulci-0.0.5-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 b11cc86582820e2d5a9107e2cee328eb56f2587d8145459c60ced93e67f6405e
MD5 9b571877d69a238dc3e16d02a5d33cc0
BLAKE2b-256 deaa440c168b382d5418e681a6add812e27aa1a234de50be8eaf11f5a02a0160

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.5-cp312-cp312-win_amd64.whl:

Publisher: release.yml on grego-casparri/pulci

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulci-0.0.5-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.5-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 f314f63896b19773b453a7b031cf5b702f05306494c752e8e707a436b2a217f2
MD5 38e1d095601cb5ded630fcf7c228c8af
BLAKE2b-256 776d93a6a72d827e73430437ceec59341d891d0f8ce216357f537ed84bc9c018

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.5-cp312-cp312-musllinux_1_2_x86_64.whl:

Publisher: release.yml on grego-casparri/pulci

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulci-0.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 618f98fb06a20e71cc2df57fbaa1b90aa2846aedf19ddab4332ebb068ef034d1
MD5 4208a9e6e000143f0b481d746c16845a
BLAKE2b-256 b3af6a86348e095667de88bacec6ca0f4604d631399ea5b14995f3dbb8a22342

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on grego-casparri/pulci

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulci-0.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 8ebd3d30e6bdcfb547d28eb8f586440ff939016102092cebecef674020d12726
MD5 da279a809ed9b99c4ab2e97a9731be44
BLAKE2b-256 42eff99f64799b0e62620ea1d0fd62e388df64391d4f27171ddbebd3dd5d39ae

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on grego-casparri/pulci

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulci-0.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a71d6cbf2f461e298a727f8ddbc73486dac43ce8bfe53c3b0421725542111451
MD5 d15066ecc264e83d00973f33c556c1c3
BLAKE2b-256 93b740ea95cc99fd3d30b32cbdaced9ec169e6046c9af71c7288e4aaac948af9

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on grego-casparri/pulci

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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