[project] name = "claude-writ" version = "1.7.0" description = "Guardrails for Claude Code: the rules that fit the task delivered per prompt, and no risky writes before you approve a plan and tests. Hybrid retrieval (BM25 + vector + graph traversal + weighted ranking, with an abstention gate) over a Neo4j knowledge graph, wired in through hook scripts and a session state machine." readme = "README.md" requires-python = ">=3.11" license = "MIT" license-files = ["LICENSE"] authors = [ { name = "Lucio Saldivar" }, ] keywords = [ "claude-code", "rag", "rules", "enforcement", "neo4j", "fastapi", "hooks", "workflow", "code-quality", "ai-tooling", "governance", "guardrails", "knowledge-graph", "tdd", ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Operating System :: POSIX :: Linux", "Operating System :: MacOS", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ # Production runtime only. Anything imported by writ/ at request # time goes here. Build tooling (optimum, pytest, mypy) belongs in # [project.optional-dependencies] dev. The SentenceTransformer # fallback library belongs in [project.optional-dependencies] # fallback because the runtime imports it only when the operator # explicitly opts in via WRIT_ALLOW_EMBEDDING_FALLBACK=1. # Upper-bounded to the validated family (2026-08-01): fastapi 0.136+ pulls # starlette 1.x, a major bump that changes app.routes composition (router # scans break while routing still works) and is unvalidated at runtime. # Raise both bounds together, deliberately, with the suite green on the # new pair first. "fastapi>=0.115,<0.136", "starlette<1", "uvicorn>=0.32,<1", "neo4j>=5.0,<6", "tantivy>=0.22,<1", "hnswlib>=0.8,<1", "httpx>=0.27,<1", "pydantic>=2.9,<3", "typer>=0.13,<1", "rich>=13,<14", # Required at runtime by writ/retrieval/embeddings.py for the ONNX # path. Production code imports onnxruntime to serve predictions # from the exported model. Without this declaration, fresh # installs of writ silently lacked the package, and build_pipeline # silently fell back to SentenceTransformer. The fallback path is # now gated behind WRIT_ALLOW_EMBEDDING_FALLBACK=1 (see # writ/retrieval/pipeline.py), so this dep must be present for # the daemon to start without an override. "onnxruntime>=1.20,<2", ] [project.optional-dependencies] # Three-group partitioning rule, established 2026-05-14: # - [dependencies]: production runtime only. Imported by writ/ at # request time. # - dev: build + test + maintenance tooling. Imported by scripts/ # and tests/, never by the production code path. Includes # optimum (ONNX export tooling), pytest, mypy, ruff. # - fallback: the SentenceTransformer library, imported by the # WRIT_ALLOW_EMBEDDING_FALLBACK=1 branch in # writ/retrieval/pipeline.py and by maintainer-only # code paths (writ compress; integrity-check redundancy # detection). Opt-in: production daemons running on # ONNX never need it. Pulls ~5GB of torch + CUDA # transitives that are dead weight in lean installs. # When adding a new dep, ask: is it imported by writ/ at request time # (-> core), by scripts/tests (-> dev), or by the explicit fallback # path (-> fallback)? Keep production deps lean. dev = [ "pytest>=8,<9", "pytest-benchmark>=4,<5", "pytest-asyncio>=0.23,<1", "mypy>=1.11,<2", "ruff>=0.6,<1", # Build-time only: scripts/export_onnx.py uses optimum to convert # sentence-transformers/all-MiniLM-L6-v2 to the optimized ONNX # graph that the runtime path consumes via onnxruntime. The # runtime (writ/) never imports optimum. Kept out of core deps # to avoid bloating production installs with build-time tooling. "optimum[onnxruntime]>=2.0,<3", ] fallback = [ # Opt-in only. Required by: # - writ/retrieval/pipeline.py when WRIT_ALLOW_EMBEDDING_FALLBACK=1 # and the operator has chosen to run the daemon on the # SentenceTransformer path instead of the production ONNX # path. # - writ/cli.py `writ compress` command (maintainer-only: regenerates # abstraction nodes). # - writ/graph/integrity.py redundancy-similarity check (subset # of `writ validate`). # Production daemons running on ONNX do not need this group. The # transitive cascade includes torch + nvidia CUDA libraries (~5GB). "sentence-transformers>=3.3,<4", # writ/compression/clusters.py imports sklearn directly (HDBSCAN, # KMeans, silhouette_score); declare it rather than relying on it # arriving as a sentence-transformers transitive. "scikit-learn>=1.5,<2", ] benchmark = [ "pytest>=8,<9", "pytest-benchmark>=4,<5", "memory-profiler>=0.61,<1", ] [project.scripts] writ = "writ.cli:app" [tool.setuptools.packages.find] include = ["writ*"] [tool.pytest.ini_options] # Timing gates are DESELECTED by default and run via `make perf`. They measure p95 # wall clock, which is deliberately sensitive to a few slow samples, and inside the # loaded suite (Neo4j, the daemon, thousands of subprocesses) they measure the machine # rather than the hook: measured 2026-08-07 on identical code, p95 moved ~30ms between # runs an hour apart while the code moved zero. The marker existed and was applied # since Item 4, but nothing ever read it, so the gates were running in exactly the # environment that makes them unreliable. Run them alone: `make perf`. addopts = "-m 'not perf'" markers = [ "perf: hook latency p95 regression floor tests (Item 4). Deselected by default; run with `make perf`", "integration: tests that require a live Claude Code install or external service", "no_friction_isolation: opt out of the autouse WRIT_FRICTION_LOG redirect (tests that exercise friction-log path resolution itself)", ] [build-system] requires = ["setuptools>=68"] build-backend = "setuptools.build_meta"