All notable changes to pyclawd are documented here. The format follows Keep a Changelog, and pyclawd aims to follow Semantic Versioning once it reaches 1.0.
While pyclawd is pre-1.0, a 0.x minor bump may include breaking changes to
the config model (Project and the nested *Configs). A project records the
pyclawd it was built on via Project.pyclawd_version; pyclawd doctor WARNs when
the running pyclawd has a different major.minor, pointing here for what changed.
pyclawd test changed— impact-scoped test selection. Reverse-maps the working diff to the tests whose coverage intersects the changed lines and runs only those, turning a whole-suite gate into a handful of targeted tests. Build the per-test context map once with the newpyclawd coverage --contextflag (--cov-context=test). Changed files with no covering test (new/untested code) are reported loudly, never silently skipped.--against REFdiffs against a ref;--listprints the impacted node ids without running.pyclawd benchmark— a performance-regression oracle (sibling ofgolden). Tag a test@pytest.mark.benchmark; its body is timed (warm-up + best-of-N) and gated against a baseline at a relative tolerance — only a slow-down fails. Baselines are hardware-specific, so they are never committed: they live in the gitignoredwork_dirandbenchmarkcompares against your last blessed run on this machine. Subcommandsupdate/status/prune; standalone pytest plugin (benchmark_plugin) auto-registers via apytest11entry point. NewBenchmarkConfig.pyclawd api— a public-API surface oracle. Statically extracts (viaast, no import) each package's public surface —__all__or non-underscore functions/classes/methods/constants with signatures — and diffs it against a committed text baseline. Removals and signature changes fail (breaking); pure additions pass unlessstrict.updateblesses,statusshows drift. NewApiConfig; the diff reads like an API changelog.
pyclawd docs validate+ an automatic guardrail inpyclawd docs build— fail the build when a notebook that executed with output renders to HTML without it (the empty-page failure mode, wherenbsphinx_execute='never'renders whatever output state is on disk and a render that races ahead of hydration ships blank cells). Compares each executed.ipynbunderdocs.source_diragainst its rendered page underdocs.build_html; false- positive resistant (output-free pages assert nothing) and skips pages not rendered in the run (--changed).docs validateruns the same check against an already-built tree, for use as a standalone pre-deploy gate.pyclawd web— an optional live, multi-project diff & review dashboard (pip install 'pyclawd[web]', thenpyclawd web serve). Watch changes across all your repos while agents work, compare any two refs (working tree ↔ branch/tag/SHA) in inline/split/full views, stage line comments and send them straight into a runningclaudetmux pane. The core install staystyper+rich: the web stack (FastAPI/uvicorn/watchfiles) is an extra, and the React frontend is prebuilt into the wheel so end users never need Node. Live updates use SSE backed by a filesystem watch (no polling), with a content-aware change token that reacts even to repeated edits of one already-modified file. Manage the project set withpyclawd web add/list/remove.PYCLAWD_DISCOVERY— anos.pathsep-separated search path of config directories for walk-up discovery (default:.pyclawd). Setting".local/.pyclawd:.pyclawd"lets a project keep its config uncommitted at<repo>/.local/.pyclawd/config.py(gitignore.local/) whileProject.rootstill resolves to the repo. Because the entries are relative, one global value is safe across many repos and concurrent projects — resolution stays per-cwd (unlike pointingPYCLAWD_CONFIGat a fixed absolute path).pyclawd configshows the effective search path.- Agent-driven upgrade flow for when pyclawd itself is updated:
pyclawd versionnow also reports the version a project's config was authored against (Project.pyclawd_version) and whether it matches the running pyclawd — with a--jsonform for agents.pyclawd changelog [--since VERSION] [--full]prints what changed (defaulting to "since this config'spyclawd_version"). The CHANGELOG ships inside the wheel, so it works from any pip-installed pyclawd.- A bundled
pyclawd-upgradeskill drives the migration: read the changelog, update.pyclawd/config.py, re-stamp the version, refresh skills, verify green. - The
pyclawd doctorcompat WARN now points atpyclawd changelog+ the skill.
pyclawd skills installnow auto-refreshes drifted skills: an installed user-scope skill whose content differs from the running pyclawd's bundled version is re-copied without needing--force(identical ones are still skipped), so a pyclawd upgrade propagates to~/.claude/skills. It reportsinstalled / refreshed / skippedcounts.pyclawd doctoradds a skills row that WARNs when an installed skill has gone stale (and stays silent when none are installed).Project.work_dir(+$PYCLAWD_WORK_DIR) — a configurable per-project working directory for pyclawd's transient files; run logs live under<work_dir>/logs/<category>/instead of a hardcoded/tmp/pyclawd.pyclawd doctorshows the resolved work dir row. A docs example lives indocs/.Project.pyclawd_version— the pyclawd a config was authored against (stamped bypyclawd new);pyclawd doctorreports a pyclawd compat row and WARNs on amajor.minormismatch so migrations are visible, not silent.Project.python_cmd+ thePYCLAWD_PYTHONenv var — a configurable interpreter (venv /conda run/uv run/sys.executable);pyclawd doctorshows the resolved python exec row.pyclawd doctornow reports a pyclawd row (version + install location, with an(editable)marker) so you can tell which pyclawd is driving a project.
- Build fields grouped into a new
BuildConfig(breaking config change). The five loose top-level build knobs —compile_cmd,dist_cmd,clean_targets,clean_ext_dir,clean_ext_globs— have moved offProjectinto a new frozenBuildConfigdataclass, reached viaProject.build(aBuildConfig | None, defaultNone). MigrateProject(compile_cmd=…, clean_targets=…, …)toProject(build=BuildConfig(compile_cmd=…, clean_targets=…, …));BuildConfigis exported frompyclawdalongside the other*Configs. WhenProject.buildisNonethecompile/dist/cleancommands self-report and exit 2 (the same 0/2 contract as before). This is a clean break — there is no compatibility shim. - Scaffolded
check_sequencenow includes thedescriptionsstep —pyclawd newwrites["format-check", "lint", "typecheck", "descriptions", "test"], so a freshly scaffolded project enforces the file-description code-map doctrine out of the box, matching pyclawd's own dogfood config. - Test taxonomy aligned to two marker axes — speed (
slow) and scope (integration) — with a canonical tier ladder (fast = not slow and not integration,default = not slow,all =everything). Thelongmarker is dropped from the default (documented as an opt-in extra tier). All surfaces now agree: dogfoodtests/pytest.ini(now--strict-markers/--strict-config), the scaffold,AGENTS.md,README.md, and the skills.TestConfig.integration_filesis clarified as a lastfailed-cache helper, not a second "integration" concept. - Docstring convention made honest — the inert
DOC(pydoclint) ruff selection is dropped (it only runs under unstablepreview); the rule set is nowE F I B UP SIM C4 RUF PGH D, consistent between dogfood and scaffold. The core modules' docstrings were converted from NumPy to Google style, and the docs no longer claim NumPy "fails lint" (the Google convention is upheld byDrules + review). pyclawd compile/distnow exit2(not0) when unconfigured, matching the 0/2 contract used by every other command group.pyclawd doctorvalidatesroot_markersexist at the detected root (previously declared but unused), and config-load errors at every CLI boundary surface as a clean exit2instead of a possible traceback.- The version is now single-sourced from
src/pyclawd/__init__.py::__version__(hatchlingdynamic = ["version"]), removing the duplicatedpyprojectstring.
- Initial generic core: config model + discovery, and the
test/quality(check) /build/docs/doctor/new/skills/lscommands.