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.6 — 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.6.tar.gz (71.3 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.6-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.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (773.8 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

pulci-0.0.6-cp312-cp312-win_amd64.whl (698.5 kB view details)

Uploaded CPython 3.12Windows x86-64

pulci-0.0.6-cp312-cp312-musllinux_1_2_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

pulci-0.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (774.3 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

pulci-0.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (774.4 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

pulci-0.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (774.4 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

File details

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

File metadata

  • Download URL: pulci-0.0.6.tar.gz
  • Upload date:
  • Size: 71.3 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.6.tar.gz
Algorithm Hash digest
SHA256 3758e9c0991412d6cb1373eb3f583dbc9873490a51e35c48aa1fa992e37f2361
MD5 bcced17bb04abbd47fe619230af8401f
BLAKE2b-256 17a062e86828bd164a879c935ccc1c5df32f2a33c8377e5e577f67d7b394f8bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.6.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.6-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.6-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 1c64bfe338826492ebead8c32483d0ac192345779e7eee1d3e259cc8e13822fd
MD5 f7fde77b94d41f964cff5dc321ff3aa7
BLAKE2b-256 84977767b1907dc2073653fb82ada67ff14d539c573d7d05174c6f8e376acc1d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.6-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.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9aae43863ec5a480a032ff6d9990f815d338d3cac503f84d398200b97265ee92
MD5 f80c2757ecb4eb507a86202b3b9bdf8c
BLAKE2b-256 eac31131d348490845551cd6169b492c2d2921f65c8600317a5ae7547f24272e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.6-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.6-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: pulci-0.0.6-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 698.5 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.6-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 7b23ce67fc56d9e4d121e19a2d752e2666ad1621fd769ca639460faa06e2eea3
MD5 29cf7314c7f5c4c270ff9c269d6f069b
BLAKE2b-256 4b4bd04527f1a1a0b857fd0824feac568ddd11cf483126735a3b9fc6704e2a85

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.6-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.6-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.6-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 1d9cfe1f093a1c3dcdd74aacd9db5eb63867dd3956fe54887e8b756e3b1114a5
MD5 e48fc698343d53cdeb15bb880e0990d1
BLAKE2b-256 b9a8ebb217a388da0552651844220ea068f20033ce18691030f5537f29f2d10d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.6-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.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 8a84e9b1c1f5485b6b991add8524d9335a6ea0d79256dd1b0674f085382467e7
MD5 d6656d870ec99e7e3130bb462dd8d383
BLAKE2b-256 e2e499e7f98d35059ccf649122b43f263903ef4da961a9db7b34ffd87f0e8670

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.6-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.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d0dc1aa9bd05d7acc43365f317614e8a6a7e5c95562856746abab5a8d3da6ce8
MD5 d34ae68118c84a7fef4ca4ba8f6800cf
BLAKE2b-256 7340b21fdf1088a183eef225097572e8c1d944da0c8d77f5f273c71ec603b9da

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.6-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.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulci-0.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 7c542062f753a46678b0226a61b7437ed61c59e935e46b501669b9d18e840d46
MD5 a80d8601f3f2fc45d2ce2e2a409d5bc8
BLAKE2b-256 611152a541c6969d205934b4d4c1d5f66bc288c352716acc3529d79ef196eb1d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulci-0.0.6-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