fakoli/anvil
Local-first, runtime-neutral project state for humans and AI coding agents — turn PRDs into lockable, evidence-backed work packets. Hermes portable-plugin manifest (Agent Plugins v1).
Changelog
All notable changes to anvil are documented here. This project adheres to Keep a Changelog and Semantic Versioning.
[Unreleased]
[0.6.13] - 2026-09-21
Changed
- Enabled, export-authorized Jev calls can resolve only
TYPESAFE_API_KEYfrom the selected project's.envor~/.envafter a non-empty process environment value; malformed sources fail closed and custom key names remain process-environment only.
Added
- Preview-first local recovery of one audited historical event omission, with replay-equivalence checks, retained recovery copies, in-place publication, and explicit resumption after interruption. Pending recovery blocks normal state access; existing strict provider-read checks remain intact.
[0.6.12] - 2026-09-20
Added
- Default-off, explicitly attributed TypeSafe Jev advice for PRD criteria, evidence scope, proof-contract preparation, skill suggestions, context relevance, incident triage, and finalized voice intent. Per-capability and per-operation cloud-export controls preserve existing proof and approval gates.
- Bounded advisory CLI, selected-item audits, a stateless local consumer bridge, four integration PRDs, and a reproducible synthetic qualification corpus. No new runtime dependency, provider switch, or automatic action is introduced.
[0.6.11] - 2026-09-19
Added
- CLI-only root-set evidence submission and read-only recovery status preserve immutable per-root baselines, verification commands, and file identities for one coordinated claim. The owner keeps terminal reservations overheld until explicit stopped-runner reconciliation; independent review still decides acceptance.
Fixed
- Root-set evidence now validates at the State append boundary, so direct backend callers cannot substitute per-root facts, summaries, or owner manifest digests.
[0.6.10] - 2026-09-19
Added
anvil roots request-digestderives a lost root-set claim response's immutable lookup identity from its original task, actor, State checkout, and request file without reading or mutating the owner registry or canonical project State.
[0.6.9] - 2026-09-19
Added
- Owner-local, POSIX-locked root-set enrollment and CLI claim coordination
(
anvil roots enroll|claim|status|reconcile). A canonical task claim retains immutable facts for every enrolled Git root, while ordinary and bundle claims refuse enrolled repositories. - API 15 and schema 22 provider-read contracts, including regenerated snapshot schemas and canonical fixtures for the expanded owner CLI surface.
Fixed
- Consistent event-log reads accept a complete final JSON record without its terminal newline while continuing to reject malformed or torn JSON records.
[0.6.8] - 2026-09-12
Added
- Pi's native
anvil_submittool and/anvil:submitcommand can forward bounded, claim-bound command-proof artifact paths to Anvil State for its existing validation. The extension never creates proofs or approves work.
Fixed
- The dedicated Pi wrapper now applies a bounded argument-count limit that
accommodates the documented command-proof inputs without weakening the
generic
anvil_runboundary.
[0.6.7] - 2026-09-10
Added
anvil repair projectfor historical workspaces whose immutable PRD events agree on one owner but predate aproject.createdevent. It dry-runs by default, retains an online backup, and verifies replay before and after an explicit repair.
[0.6.6] - 2026-09-10
Added
- GPT-6 Astra planning through Codex's ChatGPT subscription, explicit reasoning effort, and an optional native OpenAI Responses provider.
- Current-session execution guidance for subscription and local-model harnesses,
with
llm_provider: harnesspreventing nested model calls. - Opt-in Codex/Claude behavioral execution checks and Astra automation templates that stop at evidence submission and preserve human acceptance.
anvil repair projection --yesfor verified local reconstruction of a diverged projection from immutable event history, retaining an online backup.
Fixed
- Bundle-member hook proofs now validate active coordinator ownership and both leases instead of rejecting all typed command evidence for bundles.
Changed
- Direct OpenAI, Anthropic, Bedrock, and custom API providers now require
llm_allow_api: true. Existing API users must explicitly enable that setting. Subscription children ignore API credentials and ambient provider overrides.
[0.6.5] - 2026-08-24
v0.6.4 remained an untagged release candidate and was never published. v0.6.5 contains the complete user-visible delta from v0.6.3.
Highlights
- Revision-safe PRD lifecycle — review, approval, planning, and new claims now bind the exact current source revision (#180, #192).
- Transactional Git claims — task and bundle claims either establish their recorded branch/worktree ownership completely or roll back cleanly (#180).
- Bounded provider reads — providers can consume versioned project snapshots and PRD content without scraping internal SQLite state (#178).
- Scoped, truthful task routing — explicit PRD selectors, 1-5 risk ceilings, and actor-specific review governance now agree across CLI and MCP (#56, #109, #180).
- Faster Windows feedback without UAC — the maintained parallel suite and focused contract slice avoid junction-dependent collection and unnecessary serial repetition (#118).
Added
-
Revision-bound PRD approval and canonical source lineage (#180, #192). Parsed and revised PRDs now persist a domain-separated canonical material digest and exact content-event identity. Review and approval bind that revision, source, material, and content event; live append, Git replay, CLI, and MCP refuse stale or mismatched lineage. Title-only revisions may retain lifecycle state, while any other material change returns the PRD to
draft. New task and bundle claims require an exact current approval; active claims are unaffected. Historical unbound review/approval remains in the audit log but migrates conservatively todraft, requiring explicit parse/review/approve. This adds schema version 21; the command and envelope surface remains API version 14. -
Transactional task and bundle claim Git wiring (#180). Claims now freeze and persist the selected local base, exact claim-start ref and commit, branch, canonical repository root, and shared or isolated target before Git mutation. The engine revalidates those observations under one cross-process claim lock; branch/worktree failures release state and remove invocation-owned artifacts, including after normal ref packing, reftable compaction, or reflog expiry. External replacements are preserved while Git retains any distinct ref, reflog, or filesystem-identity witness. A same-SHA delete/recreate followed by destruction of every continuity witness is intentionally outside this local coordination guarantee because Git no longer exposes which generation owns the indistinguishable ref. Historical claims keep nullable Git metadata. This adds schema version 20 and advances the public API contract to version 11.
-
PRD-scoped decision discovery and resolution (#180). CLI decision reads and writes now resolve an explicit
--prd,ANVIL_PRD, or the unambiguous default/single partition;--fileselects content without changing that state scope. MCP discovery accepts the same PRD selector, and CLI/MCP JSON, human guidance, continuation argv, and audit events report the effective partition. This advances the public API contract to version 12. -
PRD-scoped task review (#180). CLI and MCP task-review passes now resolve one PRD by default and require an explicit
--all-prds/all_prds=trueopt-in for project-wide mutation. Drafted-to-reviewed, reviewed-to-ready, and durable risk-score confirmation all use the same reported partition scope. This advances the public API contract to version 13. -
PRD-scoped scoring and advisories (#180). CLI and MCP scoring now resolve one PRD by default and require explicit
--all-prds/all_prds=truefor a project-wide run. Scored and skipped counts, task-owner checks, recursive expansion queues, persisted confirmation state, and human/JSON/MCP output all use the same reported scope while global conflict groups remain unchanged. This advances the public API contract to version 14. -
Truthful task-rejection provenance and governor recovery (#180). Rejected review attempts now persist engine-derived quality, evidence-resubmission, or process provenance with exact claim/evidence identity, and new accepted reviews bind the exact evidence attempt under live and replay validation; historical accepted reviews remain explicitly unbound. Only finalized quality attempts count against accept rate, while legacy rejections migrate conservatively as quality. CLI and MCP rejection/offer responses now report the exact numerator, denominator, rate, floor, inclusive window, calculation time, queue depth, withhold reason, and recovery paths. MCP task offers now enforce the same governor as CLI. This adds schema version 19 and advances the public API contract to version 10.
-
Explicit lifecycle actor continuity (#180). Claim, renewal, release, progress, submission, bundle, hook, work-packet, and MCP surfaces now carry one canonical actor identity with shell-safe continuation data. New actor identifiers are NFC-normalized and bounded, exact legacy owners remain addressable, normalized aliases cannot collide, and the local actor field is clearly identified as audit attribution rather than authentication.
-
Claim-bound external progress attestations (#180). Standalone Git-backed claims now persist a generation and immutable repository/task/PRD context. CLI and MCP callers can submit bounded canonical commit or file attestations; the next renewal consumes one accepted attestation atomically and reports its trust mode. Free-text progress remains audit-only, duplicate or stale evidence cannot extend a lease, and legacy/non-Git claims retain their prior renewal behavior. This introduced public API contract version 6 for the new CLI flag and MCP request/response fields.
-
Claim-bound typed command-proof import (#180). CLI and MCP evidence submission can now import bounded canonical command artifacts that bind exact command and output bytes to the active actor, claim generation, repository, working directory, task, and PRD revision. Mixed or stale batches fail before the single atomic evidence transition, legacy hook proofs remain supported, and descriptive
--output-filecontent can no longer be mistaken for a typed proof. The public API contract advances to version 7. -
Atomic named-PRD dependency edits (#180). CLI and MCP dependency batches now select an explicit PRD, validate source ownership and the complete final graph, and persist every edit in one replay-safe event and transaction. The public API contract advances to version 8.
-
Revision-bound PRD source provenance (#180). Parse and revision events now retain exact bounded UTF-8 source bytes, digest, size, encoding, and revision across CLI, MCP, migration, and events-only replay. Legacy rows remain explicitly unavailable rather than receiving fabricated provenance; schema version is 18.
-
Versioned provider reads (#178, #192). JSON-only
anvil project snapshotandanvil prd showexpose bounded, non-mutating version-1 read contracts with packaged draft-2020-12 schemas, canonical fixtures, digest vectors, and a fail-closed Workbench hierarchy mapping.anvil describenow publishes the operation catalog and advances the public API contract to version 9. The supported Typer range is now explicit (>=0.13,<0.28) and installed-wheel qualification runs at both dependency floors.
Fixed
-
Lease diagnostics and guidance match runtime defaults.
claim/renewhelp,doctorfallback calculations, generated work packets, shipped skills, and active hook docs now consistently use the 240-minute default and, when bundled PostToolUse hooks are active, the progress-gated automatic heartbeat contract. -
Claim-isolated hook command capture (#180). Hook-observed command proofs now require an explicit claim pin and retain digest-covered actor, generation, task, PRD, and repository attribution. Unscoped, stale, wrong-owner, or wrong-session captures stay in the orphan buffer instead of being inferred from the sole active claim or an actor-only match.
-
Exact sample PRD source binding on Windows (#180).
init --with-samplenow publishes the embedded PRD as canonical UTF-8 bytes, verifies the exact on-disk source before lifecycle approval, and keeps the first task claim from being refused solely because Windows translated LF newlines to CRLF. -
Transactional claim and decision recovery (#180). Task and bundle claims now release state on cancellation as well as ordinary Git errors, absorbed submodules resolve their real worktree root, and the supported local
.anvillayout is excluded from claim cleanliness checks without hiding unrelated changes. Git observation output is bounded. PRD decision resolution now uses a locked compare-and-swap source replacement and restores exact source bytes when the matching audit append is refused. -
Total, mutation-atomic planning contracts (#180). CLI and MCP PRD revisions keep requirement identifiers structurally typed through diff construction, and scoring validates all six bounded dimensions for the entire request before appending any
task.scoredevent. Incomplete mixed batches now fail with a boundedscore_incompleterefusal and leave scores and event history intact; the strict planning/scoring mypy boundary is enforced in CI. -
Atomic canonical planning graphs (#180). CLI, MCP, sample, and scan planning now persist one validated canonical graph event and one SQLite transaction, so a later operation failure cannot leave a partial or transient raw graph in the audit log or projection. Graph batches bind the exact PRD revision, source digest, and Git causal content head they planned; stale local writers and complete graph lineages descended from losing Git branches fail closed.
-
Crash-safe scan recovery (#180). Scan sessions now serialize across threads and processes, reject competing recovery journals, and durably mark artifacts whose corresponding state commit succeeded before retiring recovery data. A retry therefore preserves authoritative PRD/scan artifacts after a crash or retirement failure instead of rolling them back beneath committed state. Recovery markers and directory enumeration are bounded before materialization.
-
Bounded Windows test collection (#118). The maintained contract slice now collects only the intended tests, while the full suite is qualified in parallel with a private physical temp directory and no UAC.
-
Strict task-offer filters (#56, #109). CLI and MCP task offers validate inclusive 1-5 blast/review-risk ceilings and consistently honor the selected PRD before applying actor-specific governance.
-
Named-PRD title and dependency ownership (#177, #181). Empty legacy titles migrate conservatively, and dependency mutations preserve the owning PRD.
[0.6.3] - 2026-08-08
Fixed
- Release checksums now select exactly the wheel and source distribution.
The qualifier ignores build-tool sidecars such as
bin/dist/.gitignoreand uploads only the two validated artifacts. The v0.6.2 GitHub release failed closed before bundle upload or PyPI publication and is superseded by v0.6.3.
[0.6.2] - 2026-08-08
Added
- Safe version-skew recovery and upgrade verification (#180).
anvil status --path-onlyresolves the state directory without opening or creating its database, so operators can back up schema-v0, future-schema, or damaged state before any normal backend initialization. The upgrade runbook now verifies the active CLI, schema, harness integration, and live MCP identity in Bash and PowerShell, with explicit and automatic migration paths.
Fixed
- Schema incompatibility now fails closed across every runtime boundary (#180). SQLite preflight, CLI commands and JSON envelopes, SessionStart hooks, in-process MCP calls, and real stdio startup all return bounded, redacted, direction-aware diagnostics without mutating future state, leaking raw paths, recursing through cleanup, or hanging the host process.
- MCP initialize metadata preserves build provenance. Source checkouts now
report the same commit-qualified display identity as
anvil-mcp --versioninstead of collapsing back to the plain package version. - Release/skill compatibility is now fail-closed (#196). Release checks
build and install the candidate wheel, validate every command and exact flag
cited by shipped skills, and compare the independently installed artifact to
a frozen per-version contract before PyPI publication.
anvil --versionandanvil describedistinguish source checkouts from installed artifacts, and named-PRD skills stop with actionable upgrade instructions whenprd source-nameis unavailable instead of guessing or mutating state. - Skill/CLI contract checks no longer ignore phantom commands globally.
Captured output and negative prose use exact source-scoped exceptions, while
real citations of nonexistent
decision,start-prd, orforcommands now fail release qualification. - Named-PRD dependency recovery and bounded diagnostics (#181). Legacy
dependency upserts that omitted
prd_idnow recover during forward catch-up and full replay without renumbering events. Dependency validation and ownership-recovery failures use bounded, redacted messages across CLI, MCP, server logs, and audit output. Dependency batches use linear membership, reject more than 10,000 edges before state access, and check deep graphs without recursion failures. - Canonical PRD titles are concurrency-, replay-, and terminal-safe. Current
first parses use a transactional create-if-absent precondition; non-material
title revisions cannot regress a concurrently approved PRD during git-union
replay, while legacy events retain their historical meaning and material
revisions still demote. Review and approval events bind both content revision
and lifecycle status so a same-revision race cannot demote approved state.
Seeded PRDs created by
init --with-sample,init --from-repo, orscanpersist the same canonical title as the normal parse path. Humanprd listoutput escapes terminal-active controls already present in legacy state without changing JSON or replay. Malformed UTF-8 sources, hostile stale-create partition IDs, and oversized retired-requirement IDs now return bounded typed CLI (--jsonincluded) and MCP errors without mutation.
[0.6.0] - 2026-07-19
Added
- Behavior-first PRD readiness and bounded autonomous assumptions. PRDs can
now carry typed
## Assumptionsrecords with stable IDs, rationales, and optional requirement references. The deterministic, read-onlyanvil prd assesscommand and planning-gatedassess_prdMCP tool report explainable, location-aware gaps in user context, outcomes, observable behavior, boundaries, acceptance scenarios, and task verification without changing lifecycle gates. Opt-in challenge mode asks one high-value question at a time; explicitly autonomous runs may document only bounded, reversible defaults and continue. Relevant assumptions propagate into planning context and work packets, with replay-safe schema v16 persistence for old event logs. The additive CLI/MCP surface advances the capability contract to API v4. - Coordinator-first execution bundles (issue #171) — ordered, same-PRD task bundles with one coordinator lease, atomic member authorizations, aggregate packets and status, evidence-bound completion, bounded independent review rounds, delivery checkpoints, idempotent reconciliation, and history-preserving supersession. The complete lifecycle is exposed through deterministic CLI and typed MCP contracts (API v4; 24 execution tools by default, 36 with planning enabled). Existing task graphs adopt bundles additively; task IDs, dependencies, claims, evidence, and replay history remain canonical. A new coordinator workflow guide covers bounded delegation, stalled-worker recovery, replan, migration, and delivery semantics. Replanned bundles can create a same-member replacement generation; supersession preserves old evidence while reopening shared members for fresh authorizations. The model-neutral benchmark fixture records time to accepted commit, coordinator/delegate tokens, accepted-task efficiency, review findings, re-reviews, waits, and human interventions without ranking models or vendors.
anvil list --openandanvil list --summary— answering "what's left to do?" no longer requires composing per-status filters or raw SQL.--openhides terminal tasks per the new canonicalTERMINAL_TASK_STATUSES(done/accepted—rejectedawaits rework, so it stays open; sync reconciliation now imports the same set instead of its own copy).--summaryrolls tasks up per PRD via the sharedcompute_prd_rolluphelper (open count, true total, status breakdown), open work sorted first; combined with--openit hides fully-terminal PRDs without shrinking totals. Both compose with the existing--status/--feature/--type/--prdfilters and--jsonenvelope.- Worktree isolation policy (
worktree_isolation: off|advisory|require, default advisory) — retro-corpus concurrency theme.advisorywarns when a new claim would share the working tree with another active claim;requiremakesanvil claimisolate into a worktree by default, with an explicit--shared-treeopt-out for read-only/docs work — fail-closed: if the worktree cannot be created the claim is released and refused (--forcekeeps it). The MCPclaim_tasktool honors the same policy (refuses underrequireunlessshared_tree=true; returns the advisory warning in a new responsewarningslist). The claim and execute skills document the policy and instruct agents to work inside the claim's worktree. - Distinct-actor fail-fast (schema v10:
claims.session_id) — a claim's loop session (ANVIL_SESSION_ID / CLAUDE_CODE_SESSION_ID) is recorded independently of the actor string, so two loops sharing a pinned ANVIL_ACTOR are detected: a claim arriving from a different LIVE session under the same actor is refused (--force overrides; lease-expired claims from a crashed loop do not count), and the heartbeat hook renews only its own session's claims — closing the cross-loop lease-renewal vector at its source. A cross-session direct renew logs a loud warning (not a refusal: persistent MCP servers and hook subprocesses legitimately resolve different sessions for the same owner). Session ids compare at FULL length — never truncated — because prefix-sharing pinned ids would silently defeat the guard. Claims with no session identity skip the check (local-first, never guess). Additive v9→v10 migration; NULL backfill.
Fixed
- Terminal bundle reconciliation now releases coordinator ownership (#173).
Reconciling a bundle as merged atomically closes its coordinator and member claims,
so status views no longer report a terminal bundle with an active nested claim or an
already_claimedrefusal. Retrying reconciliation also repairs affected pre-fix terminal state once, without changing the terminal delivery result. scripts/release.pyprinted a✎glyph that crashes cp1252 Windows consoles (the encoding-gotcha class the repo purged from host output); now ASCII.
[0.5.0] - 2026-07-08
Added
- Retro-mined product-opportunity wave (retro-opps PRD — 5 features, 14
tasks, PRs #139–#152). The five highest-leverage opportunities from the
post-session-findings retro corpus, dogfooded end-to-end through anvil's
own claim → execute → review → apply loop:
- Review Tier Planner — a derive-only
review_tier(light/standard/max) computed from the six-dimension score plus the B45 risk-confirmation flags (planning.scoring.review_tier; knobsreview_tier_max_min/review_tier_light_risk_max). Rendered in work packets,anvil next/show, and the MCPget_task/get_next_taskresponses; the finish skill dispatches critic depth by tier. No schema change. (B15 folded in.) - Merge Safety Check —
git_ops/freshness.py(base resolution with structural offline degradation +git merge-treeconflict probe), theanvil merge-check [--run-checks]command (verification commands run against the WOULD-BE merge result in a crash-safe throwaway worktree), and amerge_check: off|advisory|strictgate onanvil apply(strict refuses verifiably-stale/conflicted branches with codebase_stale; apply-before-merge ordering caveat documented). - Concurrency Sentinel — pre-expiry
[anvil:lease]warnings in the PostToolUse heartbeat hook (marker-file debounced;lease_warning_minutesknob, 0 disables), anexpiring_sooncount innotify-digest, and advisoryconflict_warningsonanvil next/ MCPget_next_task. Advisory only — hooks still always exit 0. (Extends B46.) - Workflow Heartbeat Bus — optional
phase/detailon theprogress.notedevent (omit-when-None keeps old-reader byte-compat), theanvil progress TASK_ID PHASECLI twin of MCPsubmit_progress, and per-claimphase / elapsed / lease-expires-inread-back inanvil status(additiveclaimsJSON list; claim-scoped phase attribution). (Aligns with roadmap SL-4.) - Preflight Validator —
anvil doctor --preflight [--prd]: PRD-parse, unresolved-decision, and git tree-state probes on the doctor finding chassis with a finalPREFLIGHT: GO/NO-GOline anddata.preflight/data.goJSON keys; plaindoctorbyte-compatible.
- Review Tier Planner — a derive-only
- Evidence contracts (evidence-contracts PRD — issue #153, 7 features, 11
tasks, PRs #155–#165). Completion evidence can now be bound to named
claims and machine-checked against artifact content, so "done" is a
verified transition rather than a self-report — closing the voice-benchmark
incident where a task claimed it benchmarked a candidate while the evidence
showed only the baseline was measured. Schema v9 (additive:
tasks.claims,evidence.category).- Claims + artifact assertions — task blocks author
**Claims:**(named claims with a kind) and**Artifact assertions:**(a fencedyamlblock of predicates over a JSON artifact: 12 operators, a single-level[*]wildcard, and phase predicatesmust_reach/must_not_fail_beforeover a declaredstage_order). Parsed into typed models with claim bindings; loud on every malformation and on an assertion referencing an undeclared claim. - Predicate engine (
review/assertions.py) — a pure, total evaluator: missing artifact, malformed JSON, and unresolvable paths are failed assertions with reasons, never tracebacks; paths escaping the project root are refused; unknown recorded stages fail loudly. - Per-claim gate (
review/gates.py:evaluate_claims) — typed proofs and artifact assertions grouped by claim intopassed/failed/incomplete/blocked/diagnostic_onlyverdicts (overall = the worst); a named claim binding no contract isincomplete, not a vacuous pass. - Auto-strict apply — a task declaring an evidence contract is held to
it independent of
strict_evidence:anvil apply --approve(CLI and MCP) re-evaluates the artifacts at approval time and refuses with codeclaim_unprovenwhile any enforceable claim is unproven; the task stays inneeds_review.--rejectis never gated. - Evidence categories —
anvil submit --category/ MCPcategory(completiondefault ·diagnostic·blocked·advisory·promotion_quality): diagnostic/advisory evidence can never satisfy a completion claim, and a blocked/diagnostic submission refuses apply even on a contract-less task. - Advisory intent linter —
applyflags task intents (candidate, benchmark, migration, deploy, live, security, …) that no declared claim or assertion covers (intent_warnings); word-boundary matched, never blocking. - Proof attribution fix — the capture-evidence hook now attaches CommandProofs by active claim rather than requiring a matching session actor (the retro's own incident, where the SL-3 proof gate was silently inert), with a submit-time mismatch warning.
- Sentinel gains an evidence-critic verdict mode; the voice incident is pinned by an end-to-end regression fixture. B22 folded in.
- Claims + artifact assertions — task blocks author
Fixed
resolve_prd_idhardened against non-string sentinels (invisible AttributeError class from the v0.4.2 SessionStart fix); MCP task tools now derive review tiers through the same merged global+project config loader as the CLI, so the two surfaces cannot disagree.
[0.4.2] - 2026-07-07
Fixed
- SessionStart hook no longer degrades to "status check unavailable" for
initialized projects.
hooks._status_hook_linecalled thestatuscommand as a bare function without passingprd, leaking the TyperOptionInfosentinel intoresolve_prd_id(), which raised on.strip()and was swallowed as exit 1 — so every initialized project's SessionStart context showed the degraded line instead of its real task counts. The call now passesprd=None, andresolve_prd_id()gained anisinstanceguard so any future bare caller degrades to default resolution instead of an invisible crash. Covered by a regression test that drives the programmatic_dispatch_detect_stateseam (whichCliRunnertests could not reach).
[0.4.1] - 2026-07-07
Documented
- Full docs audit and information-architecture restructure. A
maintainer-only audit (
docs/plans/2026-07-07-docs-assessment.md) scored every doc against the v0.4.0 code and found drift/audience-mixing as the systemic issues; wave 1-2 accuracy fixes landed in #133 (storage/backup answers, phantom flags, broken harness commands, evidence-buffer lifecycle, skills/agents roster drift, CLI defaults, migrations history, personal-path scrub) alongside new roster-sync tests (tests/test_docs_rosters.py) that pin skills-reference, agents-reference, mcp.md, and hooks-reference to their live sources so this class of drift fails CI going forward. - LLM provider docs merged.
llm-providers.mdis retired intollm.md's canonical provider matrix, precedence rule, and cost table — one page instead of three overlapping ones. - Site nav restructured to separate user docs from contributor docs. The
public site now covers Home / Getting started / How-to guides / Reference /
Architecture & design only; a new Development section holds
sync-providers, the live backlogs, the production-readiness plan, specs,
decisions, and the quality/evidence docs (including the two runbooks moved
out of How-to: bake-off, packet-quality). Internal competitive-research
notes are dropped from the nav (kept in-repo). Superseded plans/backlogs/
audits move to
docs/archive/. - Added a glossary (
docs/glossary.md) defining packet, claim, lease, loop, gate, PRD, and workspace in one place.
[0.4.0] - 2026-07-06
Release-scoped PRD and cross-harness hardening release. This finishes the v0.4.0 work packet set: OpenClaw is version-locked, Codex and Windows launch paths are shell-free, PRD-scoped task discovery is explicit, and risk ceilings are live across CLI/MCP.
Added
- OpenClaw is now release-locked with the other first-class harnesses. The
native OpenClaw plugin manifest and package are pinned to
anvil.__version__and covered bytests/test_install_manifests.py, matching the Codex/Gemini release-drift guard. - OpenClaw runner controls for low-capability agents. The native plugin
exposes
maxBlast,maxReviewRisk, andactivePrdconfig knobs, exports them to Anvil's environment, and ships separate verbose guidance for weaker agents without changing Claude/Codex instructions.
Changed
- Risk ceilings are live on both CLI and MCP paths.
next_claimableand MCPget_next_tasknow share the same ceiling helper, andanvil review tasksconfirms blast-radius/review-risk scores durably so low-ceiling runners receive eligible confirmed tasks instead of an inert empty queue. - Task discovery is PRD-scoped.
anvil next --prd,anvil list --prd, andanvil status --prdrestrict output to the intended PRD.anvil claimwarns before cross-PRD drift by default and can refuse it viacrossPrdGuard: refuse, with--forceas the explicit override.
Fixed
- Codex and Windows launch paths no longer rely on shell assumptions. Codex
MCP/hook manifests use shell-free
uv ... python -m ...dispatch, the Stop hook remains opt-in, Windows HOME/global-config expansion is normalized, and install/run-workflow/signing/sync-provider tests avoid POSIX-only assumptions. - Claim races are serialized within one Python process. SQLite-backed claim operations use a re-entrant process lock around the full logical claim flow, preventing same-process contenders from sharing an otherwise exclusive task.
- Named PRD paths display portably on Windows.
anvil prd parse --prd <id>and missing-source diagnostics renderprds/<id>.mdwith forward slashes, matching the cross-platform CLI contract. - Windows test suite passes natively — the failure half of #118. Three
clusters of Windows-only test failures (green on Linux CI, so previously
invisible) are fixed: (1) reconciliation's
missing_expected_filediagnostic paths now render forward slashes viaPath.as_posix()instead of OS-native backslashes — a real (if low-impact) production fix that also un-masks two"/bin/src/" not in …guard assertions that silently passed on Windows; (2) the hook tests (test_hook_escaping,test_layout_assumptions) resolve Git Bash explicitly instead of the System32 WSL launcher that a bare-namesubprocesspicks up on Windows (viaCreateProcesssearch order), which couldn't run a hook referenced by a Windows-filesystem path; (3) the--cwdforwarding tests compare paths separator-agnostically. The ~10× suite slowness on Windows (git-subprocess spawn cost) remains tracked in #118.
Documented
- Live OpenClaw weak-runner validation captured. The v0.4.0 docs include
the Fakoli mini gateway run proving
activePrdpropagation, risk-ceiling withholding, weak-agent guidance injection, and claim/evidence/submit flow. - User-facing version references were refreshed for
0.4.0across the README, getting-started guide, CLI reference, architecture notes, and OpenClaw README.
[0.3.2] - 2026-07-05
Bug-fix and hardening release resolving six open issues surfaced by real multi-agent runs, especially on Windows: task-ids that corrupted filesystem paths and git refs, an over-literal evidence gate, and gaps in the claim/lease coordination primitive. Dogfooded end-to-end through anvil's own PRD → plan → execute loop.
Fixed
- Namespaced task ids no longer corrupt files or git refs (#105, #108). A
task id like
advise-and-defer:T005was interpolated verbatim into packet / acceptance-proof filenames (the:opens an NTFS alternate data stream on Windows, so the file was unreadable by its logical name), branch names (an illegal refname), and worktree directories. A sharedanvil.naming.safe_path_componentnow sanitizes the id at every path/ref boundary; bare ids likeT001are unchanged. Sync reconciliation matches the sanitized names too, so it no longer mis-flags them as orphans. anvil claim --worktreeactually creates the worktree (#104). It created the branch by checking it out in the MAIN repo, sogit worktree addthen failed "already used by worktree" — the flag silently did nothing. Claim now creates the branch without moving the main checkout, so the new worktree gets it and main stays on its original branch; fixed for both the auto-generated and--branchpaths.- The evidence gate accepts placeholder proofs (#108). A generated
required-evidence line like
captured in `docs/findings/<date>-foo.md`was matched by exact string, so concrete evidence (2026-07-04-foo.md) never satisfied it.<...>placeholders are now matched as single-token wildcards; a requirement that is entirely a placeholder still needs a literal anchor (no vacuous acceptance). - A heartbeating agent that makes no progress loses its claim (#55).
renew()extended the lease on every heartbeat with no proof of forward progress, so a wedged-but-busy agent held its task for the whole max-claim-age window. A renew with nofile_changedon an expected file since the last heartbeat is now a no-op, so the lease expires and the stale reaper reclaims it. The explicitrenewCLI and MCP tool reportrenewed: falsefor the no-op instead of announcing a fresh lease. - Concurrent agent loops no longer share one identity (#103). Two loops on
the same machine/user derived the same default actor, so a second
claimrenewed the first's lease instead of conflicting — lease mutual-exclusion was a no-op between siblings. The derived default actor now carries a per-loop session discriminator ($ANVIL_SESSION_ID, falling back to$CLAUDE_CODE_SESSION_ID); explicit--actor/$ANVIL_ACTORare unchanged.
Documented
--cwdvs workspace selection (#108). Clarified that state is selected by project via--cwd(there is no--workspaceflag) in the CLI reference and the state-ops skill.
Internal
- Regression tests pin the #104 branch-creation and #106 cp1252-arrow fixes
(both shipped in 0.3.1); fixed a Windows-only test-isolation bug — the
workspace-layout test set
$HOME, butPath.home()reads$USERPROFILEon Windows.
[0.3.1] - 2026-07-02
Onboarding truth + silent-failure fixes, driven by six real-session retrospectives (post-session findings) and a five-path hands-on reproduction of the published 0.3.0 onboarding; plus the strategic backlog and a production-readiness plan.
Fixed
- Duplicate or suffixed PRD ids can no longer brick a workspace. A PRD
with a duplicated requirement/feature/task id passed validation, appended
the
prd.parsedline toevents.jsonl, then aborted the DB write on the UNIQUE constraint — leaving a poisoned event log that failed every subsequent command untilanvil init --force(data loss). The parser now refuses duplicate ids and non-canonical suffixed ids (R003a) with clearParseErrors before anything is written; regression tests pin both the refusal and the clean recovery. anvil claimnow creates the promisedagent/<task>-<slug>branch in the default HOME-workspace layout. Git branch/worktree ops resolved their working dir through the state base dir (~/.anvil/workspaces/<key>, never a git repo), so every workspace-layout claim warned "git branch not created". New_resolve_project_dir(explicit--cwd>ANVIL_ROOT> cwd) drives git ops; regression test pins it.anvil installdry-runs no longer claim "(wrote)". The default dry-run printed the same verbs as--write, so new users believed the install had completed. Dry-runs now print "would write"/"would merge" and end with "# dry-run — nothing was written. Re-run with --write to apply."- Windows cp1252 crash (#106).
submit/applycrashed withUnicodeEncodeErrorprinting→/↳on cp1252 consoles. The CLI now reconfigures stdout/stderr to UTF-8 (losslessbackslashreplacefallback); no morePYTHONUTF8=1workaround. anvil statusno longer hides claimed/needs_review/done tasks. Task rollups (human + JSON, per-PRD and total) now report all six lifecycle buckets; previously a claimed task appeared in no bucket and a finished project read "0 of N".- Shipped
AGENTS.mdpointed harnesses atbin/anvil/bin/anvil-mcp— repo-relative paths that don't exist for installed users. Now bareanvil/anvil-mcp, with the 14-default/24-gated MCP surface noted (all three shipped copies synced). - Docs corrected against shipped code (~25 stale claims). A verification
pass found docs describing shipped features as unbuilt and vice versa; now
corrected: B46 max-claim-age, B47 unified
ANVIL_ACTORresolver, B48 typed- signed
AcceptanceProofs,anvil replay/backup/restore, four LLM providers, hook count (5), CLI entry count (39), schema version (8), MCP 24-registered/14-on-wire split, and — most load-bearing — the evidence gate documented as advisory-by-default everywhere (README's headline included) instead of claiming an unconditional hard gate. Roadmap SL-1 (replay-equivalence in CI) marked SHIPPED. README comparison table gains a measured always-on context-cost row (~2.4k tokens, frombenchmarks/CONTEXT_AUDIT.md).
- signed
Changed
- Default claim lease raised 60 → 240 minutes. Real >15-minute agent
workflows silently lost their lease mid-task and reverted to
ready; the standing field workaround was--lease 240. Max-claim-age (4×) still caps a wedged agent at 16 h. - Onboarding docs rewritten around the real default state layout
(
~/.anvil/workspaces/<key>/.anvil, not./.anvil): README Quick Start,docs/how-to/getting-started.md(example outputs regenerated from real runs),docs/prd-template.md(plus a strictRNNNrequirement-ID pre-flight callout),docs/mcp.md(tool-surface gating section),docs/live-tests.md,benchmarks/README.md(committed-results status).
Added
-
Documentation site (MkDocs Material + Read the Docs).
mkdocs.ymlwith a comprehensive curated nav over the wholedocs/tree (60+ pages: how-tos, references, specs, plans, research;_positioning.mdand PRD data files stay out of the nav by design), a docs homepage (docs/index.md),.readthedocs.yamlfor readthedocs.io hosting, pinneddocs/requirements.txt, and adocs build --strictCI job. All ~160 site link warnings were cleaned: links into the source tree became absolute GitHub URLs (valid on GitHub and on the site), broken anchors were re-slugged,attr_listwas enabled for{ #id }heading anchors, and three genuinely dead links were unlinked. The site auto-deploys to GitHub Pages (https://fakoli.github.io/anvil/) on every docs push tomainvia.github/workflows/docs-pages.yml. Local preview:uvx --with-requirements docs/requirements.txt mkdocs serve. -
docs/plans/2026-07-02-production-readiness.md— strengths/weaknesses triangulated from real-session retrospectives + onboarding reproduction + in-repo evidence, with a sequenced plan (silent-failure elimination, CLI-satisfiable evidence gate, review-cost tiering from the six-dimension scores, production-ops guide). -
README "Proven in real sessions" summary — measured field numbers (32 tasks/21 PRs/23.7 h autonomous run; 18/18-task PRD by two concurrent loops; cross-session claim deconfliction).
-
docs/backlog/strategic-backlog.md— moat-execution overlay (S1–S13) from a full product/PMF/moat review: sequences the closed verification loop (re-execution + countersigned proofs, strict-gate default, gaming/critic measurement) ahead of breadth work, and cross-references the existing roadmap/E-B backlogs.
[0.3.0] - 2026-06-23
Multi-PRD, revisable, partitioned state: a project holds several release-scoped
PRDs in one state.db, gated per-PRD and coordinated globally, with
event-sourced (revisable) PRDs and schema migration v6 -> v7 -> v8.
Added
- Multi-PRD projects (schema v7). A project can now hold several
release-scoped PRDs in one
state.db, each a separately-gated revisable plan carrying a target version/tag, partitioned byprd_id. The v6 -> v7 migration backfills a singledefaultPRD that owns every existing row, so single-PRD projects upgrade with zero data loss and unchanged behavior. anvil prd list. Lists every PRD (id, status, revision, target; the default marked*;--json) so you can pick which one to work on.--prd <id>/$ANVIL_PRDscoping acrossprd parse,prd review,plan,sync provider|github, and the MCP tools, resolved through a sharedresolve_prd_id(theprdsentinel collapses to thedefaultpartition). Omitting it preserves single-PRD behavior.- Per-PRD claim gate. A ready task is claimable only when its OWNING PRD
(
task.prd_id) is reviewed/approved; a sibling draft PRD's tasks are refused. File-overlap and conflict-group checks still span ALL PRDs (the cross-PRD moat). - Per-PRD status rollup (
anvil statusshows one block per PRD) and per-PRD sync push scoping withprd_id-stamped mappings + reconciliation discrepancies attributed to the owning PRD. - Revisable PRDs. Re-running
anvil prd parseon an existing PRD emits a non-destructiveprd.revised(supersedes changed requirements, never deletes lineage) instead of overwriting; the audit log stays fully replayable. - Schema v8 — per-PRD
revisioncounter (v0.3 T023).prdsgains arevision INTEGER NOT NULL DEFAULT 1column, bumped byprd.revised. This is a separate schema version from v7 (the multi-PRD foundation) so a DB already stamped at v7 re-enters the migration ladder and grows the column; the v7->v8 ALTER backfills every existing PRD to revision 1. Auto-upgrade is purely additive.
Fixed
prd.revisedis now fully non-destructive and amend-aware (v0.3 T023).- Re-adding a previously-superseded (or live) requirement id is rejected up
front instead of silently destroying the existing lineage row — the added
requirements now use a plain
INSERTrather thanINSERT OR REPLACEagainst the single-column requirements PK. - Status demotion (approved -> draft) now keys off a validated, real change: superseding an id that does not exist / is already retired is rejected, so a malformed diff can no longer demote a PRD on a 0-row update.
requirements_unchangededits are now applied to the carried-forward rows instead of being silently dropped.serialize_state(the replay-equivalence oracle /doctorsnapshot) now captures the full requirement lineage (live + superseded) with therevision_introduced/revision_supersededstamps, so superseded-row divergence between a live and a replayed DB is caught.
- Re-adding a previously-superseded (or live) requirement id is rejected up
front instead of silently destroying the existing lineage row — the added
requirements now use a plain
[0.1.2] — 2026-06-22
Added
- Claude Agent SDK LLM provider — now the default for
--use-llm. A newClaudeAgentSDKProviderdrives the bundledclaudeCLI viaclaude_agent_sdk.query()over the logged-in Claude subscription instead of a per-tokenANTHROPIC_API_KEY. This matches anvil's economics — it is capacity-bound (rides flat-rate plans), not per-token-cost bound — so the subscription path is the default rather than metered API spend. The provider scrubsANTHROPIC_API_KEY/CLAUDE_API_KEYfor the duration of the call (so a quota-capped key cannot hijack the run) and runs single-shot with no tools.claude-agent-sdkis now a core dependency; it needs theclaudeCLI on PATH at call time (a missing CLI/SDK surfaces as a cleanLLMProviderErrornaming the fix). The previous providers (anthropic,bedrock,custom) are all still available — pin one viallm_provider:in.anvil/config.yaml. llm_fallbackconfig knob (defaultfalse). With no explicitllm_provider, anvil now defaults toagent-sdkand does NOT consultANTHROPIC_API_KEY/AWS_REGION/CUSTOM_LLM_BASE_URL. Setllm_fallback: trueto restore the legacy env auto-detect chain (anthropic → bedrock → custom) before falling through toagent-sdk. Becauseagent-sdkis the guaranteed final default, provider resolution no longer fails with "no provider configured".--modelflag onplan/score/expand. Overrides the LLM model for one run (wins overllm_model/llm_tier); threads through to both the--use-llmaugmentation andplan's no-tasks backstop. Foragent-sdkit accepts a CLI model name (sonnet/opus) or a full id; foranthropic/bedrock, a model id; forcustom, the route name the endpoint serves.
Changed
ClaudeAgentSDKProvideris now safe to call from inside a running event loop.generate()is synchronous but drives the SDK viaanyio.run(), which raises if a loop is already running. It now detects a running loop and offloads to a worker thread (the sync CLI and the MCP server's sync tools are unaffected — they take the direct path). Keeps the provider usable from async callers without the previous footgun.scripts/install.shnow installs from PyPI viauv toolinstead of cloning a checkout. The one-liner runsuv tool install anvil-state(which putsanvilanvil-mcpon PATH) thenanvil install <harness>. Dropped the~/.anvil-srcclone/cache, thegit reset --hardupdater, and the--pathsymlink flag —uv toolhandles install, upgrade, and PATH placement. README, getting-started, and the cross-harness guide now lead withuv tool install anvil-state.
Fixed
anvil drift/sync/doctormis-resolved checkout-relative paths against the SHARED state directory under the default HOME-workspace layout. Root cause:state_dirwas overloaded as both the state location (~/.anvil/workspaces/<key>/.anvil) and the git checkout root, which the HOME-workspace migration split apart. Three symptoms, all fixed:- False
missing_expected_filedrift — plan-declaredlikely_files(e.g.bin/src/widget.py) were probed under the workspace base instead of the checkout, so every expected-file check false-failed. - False
doctorverification-path WARNINGs —doctorresolved ready-task verification-command paths against the workspace base too. - Silently dead
orphan_branch/orphan_worktreedrift — the git scans ran in the state dir, which is not a git repo under this layout, so_is_git_repo()returned False and those two drift kinds never fired. The reconciliation engine now resolves a singleproject_root(the checkout, threaded from each CLI command's--cwd/cwd) and uses it for both the expected-file checks and the git scans; packets stay understate_dir(they are anvil-managed state). Library/test callers that pass the project root asstate_dirkeep the legacy strip-.anvilderivation. Regression guards: an engine-level workspace-split case (tests/test_reconciliation.py) and a CLI-level one running the real HOME-workspace layout (tests/test_cli_drift.py) that pins all four call sites.
- False
[0.1.1] — 2026-06-21
Added
- anvil is now installable as a standard Python tool (
uv tool install anvil-state/pipx install anvil-state/pip install anvil-state). The package now ships ananvil-mcpconsole script alongsideanvil, and the MCP config thatanvil install <harness>/anvil mcp-configemit adapts to the install method: a source checkout / plugin bundle still points at that tree'sbin/anvil-mcpwrapper, while an installed package emits theanvil-mcpconsole script (on PATH). Previously a wheel install produced a working CLI but a dead MCP integration (every config pointed at a non-existentbin/anvil-mcp).
Fixed
scripts/install.shadvertised harnesses the engine rejected. Its usage list namedclaude-codeandvscode, both of whichanvil installfailed with "unknown harness" (exit 2).claude-codenow redirects to the/plugin marketplace add fakoli/anvilflow (its real install path),vscodeis an accepted alias for the copilot config, and a new test pins the script's list to the harness registry so it can't drift again.anvil installfrom a wheel silently skipped the AGENTS.md splice and Codex automations. Both were read by walking 4 parents up fromanvil.__file__(correct only in a checkout; in a wheel it resolved outside the package). They now ship as package data underanvil/_data/and are read viaimportlib.resources, so the instruction splice and--automationswork for any install method.uv buildfailed (readme = "../README.md"escaped thebin/build root, breaking the sdist→wheel step). The package now uses a build-root-localbin/README.md, so the full sdist+wheel build succeeds.
[0.1.0] — 2026-06-21
Epic E13 — Agent Fleet: Capacity-Coordination Pull MVP. Signed evidence proofs, accept-rate governance, risk-axis routing, packet-quality measurement, and the two-week bake-off harness, hardened by two rounds of blind adversarial review.
Fixed
- E13 hardening from a blind adversarial review. (1) Honesty: the typed
evidence proof's "observed, not asserted / non-gameable" docstrings overstated
the guarantee — corrected to a TRUST BOUNDARY note (the gate closes the
free-text-in-the-same-field hole, but a
CommandProof's authenticity rests on a trusted hook writer;output_sha256is recorded, not re-verified). Real hardening tracked as E13-1. (2) B49 rework misattribution: accept-rate now attributes each review decision to the runner whose submission it actually reviewed (latest at/before the decision), not the task's latest submitter — so a reworked task's rejection stays with the runner who earned it. (3) B45 inertness is now flagged[EXPERIMENTAL]in--help(ceilingednextreturns nothing until a risk-confirmation source ships). (4) Governor observability:anvil nextnow surfaces awithheld_reason(review_queue_saturated/actor_below_floor) so a governed withhold is distinguishable from an empty queue. (5) Strict no-evidence branch now fails closed onrequired_proofstoo (was checking only legacyrequired_evidence). (6)verify_acceptanceenforces the declaredalgorithm; (7) proof-emission failures are logged, not silently swallowed.
Added
-
Two-week bake-off runbook + metrics snapshot (B50). The gate for everything bigger: a documented protocol (
docs/how-to/bake-off.md) to measure — on the real repo across the flat-rate pools + the local box — whether the capacity-coordination premise actually holds before any capacity-pool machinery is built. Captures per-pool throttle frequency, spillover frequency, local false-pass/rework (the SL-2 corpus run with local models vs a Sonnet baseline), review-minutes,needs_reviewdepth, and cloud-tokens before/after, with an explicit kill/pivot trigger. Newbenchmarks/bakeoff_snapshot.pylogs the live in-engine half daily (review debt + per-runner accept-rate (B49) + packet right-sizing savings (B51)); a results-note stub awaits the run indocs/research/. Capacity-pools-as-a-first-class concept stays DEFERRED pending this bake-off's data. -
Packet-quality measurement harness (B51). Packet quality becomes a tracked, measured workstream:
anvil.context.packet_metricsquantifies the token reduction of right-sized (lightweight) vs full work packets across a backlog — per task and in aggregate (as_routed_savings_pct) — runnable from Python or standalone (python -m anvil.context.packet_metrics <state_dir>). The renderer already routes by score and the packet already carries intent / acceptance / scope / non-goals / verification; this adds the measurement so right-sizing is tracked, not asserted. Token reduction is the half measurable without a live model; the local-model success-rate lift is measured in the B50 bake-off. Seedocs/how-to/packet-quality.md. -
Accept-rate governor + review-debt cap (B49). Human review is the binding constraint on an unattended fleet, so the pull seam (
anvil next) now refuses new work when (a) the review queue is saturated —needs_reviewdepth >=needs_review_cap(default 10) — or (b) the requesting runner's recent accept-rate (overaccept_rate_window_days, default 7) is belowaccept_rate_floor(default 0.80). A task rejected >= 3 times escalates: it requires a near-perfect (0.95) accept-rate, so it goes to a proven runner or a human instead of recirculating to the same weak executor. The accept-rate is per runner (the task'sevidence.submitted_by, not the reviewer), computed live from review outcomes joined to the work actor; a runner with no track record gets the benefit of the doubt for base work but cannot take escalated tasks. NewAcceptRateMetrics(claims/metrics.py) +list_task_review_ decisionsaccessor. (Governs the CLI pull seam; unifying the MCPget_next_taskadvisory path onto the same gate is a tracked follow-up.) -
Risk-axis eligibility on
anvil next— the pull-fleet MVP (B45).anvil next --max-blast N --max-review-risk Mreturns only ready tasks scoring at or below those ceilings, composing with the existing dependency/priority sort and preserving the-qexit codes (0 = a task printed, 3 = none eligible). It is safe-by-construction from the first commit: the blast/review-risk scores ride on an untrusted filename regex, so a task is eligible for a ceilinged (low-risk, e.g. local) runner ONLY if that dimension is confirmed (Score.blast_radius_confirmed/review_risk_confirmed, new) and within the ceiling — an unscored, unconfirmed, or over-ceiling task is treated as frontier-only (ineligible), so the filter fails safe, never routing weakly-scored risk to a weak executor. Defaults make every task unconfirmed until a trusted risk-label source ships (follow-up), so ceilingednextis conservative by design; unrestrictedanvil nextis unchanged. -
Unified actor identity — one
resolve_actor()across every claim surface (B47). Before this,anvil claimdefaulted to$USER, the bundled heartbeat hook passed the Claudesession_id, gate-check/claim-guard used$USER, and the hook verbs used$ANVIL_GATE_ACTOR— four different identities. A claim made under one and heartbeated/gated under another renewed zero leases (the lease silently expired mid-work) and the finish-gate, seeing no matching claim, failed OPEN. Now claim / release / renew / next / heartbeat / stop-gate / gate-check / claim-guard / submit all resolve identity through oneresolve_actor()with precedence explicit--actor>$ANVIL_ACTOR>$ANVIL_GATE_ACTOR(legacy) >$USER> stable per-runner signing-key fingerprint >agent. The bundledheartbeat.shno longer passes a per-session id — it lets the CLI resolve the same identity the claim used. The stable fingerprint (from B48's keypair) means two headless runners no longer collide onagent. Set$ANVIL_ACTORto pin a fleet runner's identity. (MCP tools still require an explicit actor by design — an audit-integrity guard against blank attribution.) -
Max-claim-age cutoff — wedged agents can't hold a lease forever (B46, part 1).
renew()now refuses once a claim is older thandefault_lease_minutes × max_claim_age_multiplier(new config, default 4×), even if the agent keeps heartbeating — so the lease then expires and the stale reaper reclaims the task (and frees its conflict group). Protection applies even without aconfig.yaml(ClaimManager defaults to 4× the base lease).anvil doctorgains amax_claim_ageprobe that surfaces over-age active claims as a warning. This is the robust half of B46; the progress-gated heartbeat (extend only on observedfile_changed/evidence) is deferred untilfile_changedemission under the HOME layout (B44-2) and unified actor identity (B47) make it correct rather than fragile (file_changedis file-keyed + actor-tagged). -
Portable signed AcceptanceProof + strict-by-default for loops (B48, part 2 of 2). On acceptance the engine now emits a typed, signed
AcceptanceProof— a portable receipt binding the task id, claim/lease id, actor, the observedCommandProofs, and the event-log range it covers, with a detached Ed25519 signature over a canonical serialization. It is written to<state>/proofs/<task>-<event>.jsonand verifies off-host with only the signer's public key (anvil proof verify <file> --trust <list>): the three checks — valid signature, genuinesigner_idfingerprint, and key in the trust list — make it verifiable without trusting the producer (asymmetric, not HMAC). Keys are a per-runner Ed25519 keypair under~/.anvil/keys/(overrideANVIL_KEYS_DIR); the public-key fingerprint is the signer id. Emission is best-effort and file-only (never blocks acceptance, never enters replayable state, so replay-equivalence stays deterministic).ANVIL_STRICT_EVIDENCEjoins the strict-evidence precedence (--strictflag > env > config > off) so an autonomous loop/fleet can enforce the evidence gate everywhere without per-project config.cryptographyis now an explicit dependency. Builds on part 1's typedCommandProofs. -
Typed, observed evidence proofs (SL-3 / B48, part 1 of 2). The review gate no longer has to trust strings. A new typed
ProofArtifactunion (CommandProof/DiffProof/LinkProof/AssertionProof) is carried onEvidence.proofs, and tasks can declare typedVerification.required_proofs. Acommandrequirement is satisfiable only by aCommandProofwhose realexit_codeis in the passing set — so a free-text claim can't fake it, and a recorded command that failed (exit != 0) no longer passes the gate. Thecapture-evidencePostToolUse hook now records each command's real exit code and anoutput_sha256over its full output, andanvil submit(CLI and MCP) reconciles that per-claim buffer intoEvidence.proofs— proofs are observed by the engine, not asserted by the agent. The planner emits a typed command requirement per verification command, so the gate is active for real tasks. Additive and non-breaking: the legacy free-textrequired_evidencepath is untouched (both surfaces are enforced), so old event logs replay unchanged. Schema v5 -> v6 (auto-upgrade adds theevidence.proofscolumn, DEFAULT'[]'). The portable signed ProofArtifact + strict-by-default for autonomous loops land in part 2. -
HOME-workspace follow-ups (B44). (1)
anvil migrate-workspace— one-time, safe-first migration of legacy in-repo<repo>/.anvil/(or<repo>/bin/.anvil/) into the home workspace: dry-run by default (--yesto apply), never clobbers an existing home workspace, copies (never moves) so the legacy dir survives, and uses an atomic temp-then-rename so an interrupted copy can't half-populate. (2) Basename-collision fix (dual-key): new workspaces are keyed by basename + a path hash (app-1a2b3c4d) so two projects sharing a basename no longer collide; a pre-existing bare-name workspace is still honored (resolves unchanged — no orphaning of existing state). (3) Plugin-root init guard now fires only under the legacylocallayout (it was dead in workspace layout — it checked the resolved HOME base, never a plugin root); initializing at the plugin root in workspace layout is harmless and correct for anvil-on-anvil dogfooding. (4) The SessionStart hook (detect-state.sh) is now workspace-aware — it no longer gates on a local./.anvil(which the home layout doesn't create) and nudges toanvil migrate-workspacewhen it detects un-migrated legacy in-repo state. -
Codex/cross-harness hook verbs (B41). Two new
anvil hooksubcommands, both reusing existing engine logic:anvil hook heartbeat(PostToolUse lease renew — renews the actor's active claim lease(s) on tool activity; non-blocking, wired into the bundledhooks.json, cross-harness Claude + Codex) andanvil hook stop-gate(the Codex/Claude analogue of the OpenClaw finish-gate — onStop, emits{"decision":"block","reason":…}+ exit 2 when a claimed task lacks submitted evidence, reusinggate-check'sdecide_from_rows). The Stop-gate is OPT-IN, not auto-wired — anvil's bundled hooks are non-blocking by design and the Codex Stop-block mechanism is unverified on codex-cli 0.130.0; enable it via a documented/hooks-trusted recipe after verifying.gate_check.pyrefactored to sharedecide_from_rows. Verb decisions pinned bytests/test_codex_hooks.py(incl. a gate-check↔stop-gate parity test);heartbeat.shsmoke-tested intests/test_hooks.sh. -
OpenClaw native finish-gate (B42 Phase 2). anvil's first native OpenClaw
definePluginEntryplugin (packaging/openclaw/plugin/): abefore_agent_finalizehook that blocks an agent from finalizing a turn while its claimed anvil task lacks submitted verification evidence — a blocking gate, stronger than anvil's non-blocking Claude-Code hooks. It shells out to a new read-only verb,anvil gate-check --json, which reportsblock/continue(exit 0 continue / 2 block / 1 error) for the actor's active claim using anvil's own accept-path predicate (review.gates.evidence_complete). The gate is default-open (no project / no claim / anvil missing ⇒ finalize normally) and bounded (revise capped at 3 attempts per run, keyed task+runId). Opt-in install recipe viaanvil install openclaw --finish-gate(anvil registers nothing — honoring the OpenClaw no-files contract). Requiresplugins.entries.anvil-finish-gate.hooks.allowConversationAccess=trueandanvilon the Gateway PATH; hook dispatch verified against OpenClaw 2026.6.6. -
OpenClaw
after_tool_callevidence auto-capture (B42 Phase 2). The same native plugin now registers anafter_tool_callhook: when the OpenClawexectool runs a verification command (pytest / ruff check / mypy / npm test / cargo test / bun test), it forwards the command + exit code + combined output to the existinganvil hook capture-evidenceverb, appending to the active claim's.anvil/.evidence-buffer/. The OpenClaw-native equivalent of anvil's Claude-Codecapture-evidence.shPostToolUse hook — a pure fire-and-forget observer that never blocks or fails a tool call. Extractor verified against OpenClaw 2026.6.6's exec result shape (event.result.details.{exitCode,aggregated,cwd}; stdout+stderr are combined). -
OpenClaw
before_prompt_buildguidance injection (B42 Phase 2). The native plugin injects a short, cacheable anvil-usage note into the system prompt for anvil-tracked projects (how toclaim/submit+ a heads-up that the finish-gate blocks finalizing un-evidenced work). UsesprependSystemContext(provider prompt-cached) and is gated on a single memoizedanvil statusprobe per workspace — no per-turn shell-out. Returns nothing for non-anvil projects; disable viaplugins.entries.anvil-finish-gate.hooks.allowPromptInjection=false. (session_startcan't inject — it's observation-only — sobefore_prompt_buildis the injection seam.) -
OpenClaw
before_tool_callclaim-guard (B42 Phase 2 — completes Phase 2). The native plugin's fourth hook: when a mutating tool (write/edit/apply_patch) runs while the actor holds no active anvil claim, it shells out to a new read-only verbanvil claim-guardand acts per mode. Defaultwarn(log-only, never blocks — chosen by a 3-model vote as the only false-block-safe non-inert default); opt-inblock(hard veto) /require_approval(interactive only — hard-blocks in headless) /off. Editing outside a claim's declared scope only warns (advisory). Default-OPEN on every uncertain path.before_tool_callcarries no cwd, so the guard reuses theworkspaceDircached bybefore_prompt_build(session-keyed), with an absolute-path fallback.claim-guarddecision pinned by stub + real-SqliteBackendtests. The plugin now hosts all four Phase-2 hooks.
[0.0.9] — 2026-06-19
Changed
- Harness install is now tiered: three supported, the rest MCP-only. Only the
harnesses anvil supports end-to-end — claude-code, codex, openclaw —
get the full native integration; every other harness is MCP-only best-effort:
anvil install <harness>now merges only the anvil MCP server into its config. TheAGENTS.mdinstruction splice is kept only for codex (openclaw's plugin ships it). This shrinks the blast radius behind the earlier config-corruption incident — anvil no longer writes instruction files into a dozen third-party harnesses. To give an MCP-only agent anvil's usage guidance, point it at the repo'sAGENTS.md. Docs reframed to the two-tier model — supported vs MCP-only (docs/how-to/using-anvil-on-any-harness.md).
Removed
- The neutral
.agents/skills/drop. No harness used it anymore (supported harnesses ship anvil's skills via their plugin), so install no longer copiesSKILL.mddirs into.agents/skills/, the--jsoninstall envelope no longer carries askillskey, and the deadHarness.reads_agents_skillsflag is gone.
Fixed
-
State is no longer stranded inside individual git worktrees. anvil kept
state.dbat<cwd>/.anvil/(orbin/.anvil/), so every git worktree got its own separate, gitignored database — work tracked in one worktree was invisible in another, and the canonical data ended up trapped in whichever worktree last touched it. The default state dir is now a per-project home workspace,~/.anvil/workspaces/<repo>/.anvil/, keyed by the canonical git repo (git rev-parse --git-common-dir) so all worktrees + the main checkout share onestate.db.ANVIL_ROOTis unchanged (a literal<ANVIL_ROOT>/.anviloverride);ANVIL_STATE_LAYOUT=localkeeps the legacy in-repo layout. CLI and MCP resolve identically. Seedocs/how-to/state-location.md. -
The one-line installer no longer runs stale, config-corrupting anvil.
scripts/install.shcached a shallow checkout at~/.anvil-srcand updated it withgit pull --ff-only || warn— a fail-soft update. When the shallow pull failed it kept running whatever stale version was cached; a checkout from before the native-install fix still had the old TOML-manglinginstall codex, so re-running the installer could re-corrupt~/.codex/config.toml. The updater now force-syncs the cache to the latestmain(git fetch --depth 1+reset --hard FETCH_HEAD) and exits loudly if that fails, rather than silently running an old, harmful version. -
anvil installno longer corrupts a user's config or clobbers their files. The Codex path used to hand-edit~/.codex/config.tomlby parsing and re-emitting the whole file; that mangled inline tables and special-character keys ([projects]paths,foo@barplugin keys) into invalid TOML — Codex reported "Failed to load plugins". Two layers of fix:- Codex installs natively now.
anvil install codexdrives Codex's own CLI (codex plugin marketplace add+codex mcp add) and lets Codex write its own config — anvil never text-editsconfig.toml. This deletes the fragile TOML splicer entirely (including a latent silent-corruption bug where a[mcp_servers.anvil]line inside a triple-quoted string dropped the rest of the file).--rollbackruns Codex's own removers. anvil'splugin.jsongains a Codexinterfaceblock +skillspointer so it appears in the Plugins panel and ships its skills natively. - Instruction files are spliced, not overwritten.
AGENTS.md/copilot-instructions.mdetc. get anvil's content inside a marked, removable<!-- BEGIN ANVIL -->…<!-- END ANVIL -->block appended after the user's text; re-running replaces only that block. Ambiguous pre-existing markers make install refuse rather than risk corruption.
- Codex installs natively now.
Added
-
anvil notify-digest+ OpenClaw cron recipes (B42 Phase 1). Newanvil notify-digestverb — a one-lineneeds_review+ blockers summary that prints nothing on a clean queue, so a recurring OpenClawcron … --announcestays silent instead of pinging a channel every interval; it always exits 0 (a notifier must never fail a cron run) and--jsonemits the counts.anvil install openclawnow surfaces the sandbox-allowlist prerequisite (add anvil's tools tosandbox.tools.allow, or the 24 MCP tools vanish in sandboxed turns), and a new--cron-recipesflag prints opt-in Gateway cron recipes (queue probe, nightly reconcile, lease watchdog, finish-gate nudge) — anvil registers nothing, honoring the OpenClaw no-files contract. -
install.sh --path— putanvilon your PATH. After the one-line install theanvillauncher lives inside the checkout, so you can't run it globally. Pass--path(e.g.… | sh -s -- codex --path) and the installer symlinksanvilinto~/.local/bin(override withANVIL_BIN_DIR) — opt-in, idempotent, and it never clobbers a pre-existinganvilyou put there yourself; it also prints a hint if that dir isn't on yourPATHyet. -
Codex skill metadata — anvil's 8 skills now show up named in Codex. Each skill ships a minimal
agents/openai.yaml(interface:withdisplay_name,short_description,default_prompt, and a#60a5fabrand_color), so the/skillspicker and Plugins panel renderanvil:claim…anvil:state-opswith human labels and a consistent badge instead of blank/auto-titled entries. Claude Code ignores theagents/dir, so the skill tree stays dual-harness clean. Validated against the Codex skill schema (theinterfacekeys the loader recognizes;short_descriptionwithin OpenAI's 25–64-char scaffolder guideline). The "bundle hooks into the Codex plugin" headline (backlog B41) needed no packaging change: Codex installs anvil from the repo root, sohooks/hooks.json(using${CLAUDE_PLUGIN_ROOT}, the same variable real Codex plugins use) already ships and runs on Codex. -
Codex feature-surface reference —
docs/reference/codex.md. A living map of the Codex CLI surface (plugins, skills, hooks, MCP, config/profiles, sandbox/approval/trust, automations, headlessexec/review, sessions/fork, cloud, app-server, memory, notify, instructions) with, for each, where it's authoritatively documented (on-disk under~/.codex/and the official URL) and how it relates to anvil — so future work picks Codex surfaces without re-discovering them. Verified againstcodex-cli 0.130.0. -
Native OpenClaw install. OpenClaw is its own agent platform (not a Claude
.mcp.jsonbundle, as the old harness row wrongly assumed) — it manages MCP, skills, and plugins through theopenclawCLI.anvil install openclawnow drives that CLI:openclaw mcp add anvil --no-probe --command bash --arg <wrapper>plusopenclaw plugins install anvil --marketplace fakoli/anvil --force(skills + commands via anvil's Claude-compatible marketplace).--no-probeavoids a cold-startuv syncoverrunning OpenClaw's 30s connect probe (which would half-install);--forcerefreshes the plugin on re-install. It touches none of the user's files — no.mcp.json, noAGENTS.md, no skills drop — and--rollbackundoes viaopenclaw mcp unset/plugins uninstall, only removing the global registration when no other project still references it. OpenClaw joins Claude Code and Codex as a truly-supported harness. -
Backups +
anvil install <harness> --rollback. Every file an install modifies is copied to<file>.anvil-bakfirst and logged to~/.anvil/install-log.json;--rollbackundoes the install. Rollback of an instruction file strips only anvil's block, preserving everything the user wrote around it (before or after install) — it never deletes a file the user has adopted. The log is keyed per project+harness and refcounts shared resources (a projectAGENTS.md/.agents/skills/used by two harnesses is only undone when the last one rolls back); for Codex, the global MCP/marketplace removers only fire when no other project still references them. The record is persisted before any write (crash-safe), directory restores are atomic, broken-symlink destinations are refused, and the manifest tracks each path's kind so rollback picks the right reversal. Non-Codex harnesses also get anvil's skills dropped into the neutral.agents/skills/anvil-*location read by most harnesses. -
Codex automations (
anvil install codex --automations). Installs anvil's scheduled-automation templates (anvil-work-queue,anvil-sync-reconcile) into Codex's native~/.codex/automations/— recurring agent runs that work the task queue / reconcile state on a cron schedule. They are installedstatus = "PAUSED"with the project path filled in (anvil never auto-activates a background run on your subscription — you turn them on in the Codex app), namespaced per project, and tracked by the same backup/manifest so--rollbackremoves them. Templates live underpackaging/codex/automations/. -
Surface prior unresolved review findings on file overlap (T017). When a reviewer rejects a task or requests changes at the finish gate, that verdict is recorded as a
reject/needs_changesreview whose notes carry the finding. A later task that intends to touch one of those same files no longer starts blind: its work packet now surfaces the outstanding finding so the agent can address it or explicitly carry it forward.- Two pure helpers in
review/gates.py. A new frozenDeferredFindingdataclass plusdeferred_findings(reviews, tasks, evidence)anddeferred_findings_for_files(..., expected_files). The first derives one finding per task-targetedreject/needs_changesreview — its touched files being the union of the reviewed task'slikely_filesand thefiles_changedof any evidence submitted on it (approvals and non-task-targeted reviews are ignored). The second filters those to the ones overlapping an incoming claim's files and annotates each with the exact overlapping subset. Both are I/O-free and deterministic; they are re-exported fromreview/__init__.py. - Wired into both surfaces.
anvil packet(CLI) and thegenerate_work_packetMCP tool now compute overlap against the active claim'sexpected_files(falling back to the task's planner-populatedlikely_filesbefore a claim exists) and pass the findings to the renderer. - Rendered in both packet formats.
render_packet/fast_lane_packetgain adeferred_findingsparameter (defaults to none — fully back-compatible). When non-empty the markdown packet grows a "Prior unresolved review findings (overlapping files)" section and the JSON packet grows adeferred_findingsarray; when empty, neither is rendered. - Import-cycle fix.
state/transitions.pynow importsevidence_completelazily at call time soreview.gatescan be the import entry point (e.g. runningtests/test_review.pystandalone) without re-entering a partially-initialized module. - Covered by
tests/test_review.py.
- Two pure helpers in
[0.0.8] — 2026-06-17
Changed
- Renamed fakoli-state -> Anvil (the system of record for agent teams). BREAKING: CLI command is now
anvil(was fakoli-state); state dir is.anvil/(was .fakoli-state/); env var isANVIL_ROOT(was FAKOLI_STATE_ROOT); import package isanvil(PyPI distanvil-state).- Anvil (formerly fakoli-state, extracted from the fakoli-plugins monorepo).
- The Python import package moved from
fakoli_statetoanvil; the distribution is published asanvil-statebecause the bareanvilname is taken on PyPI. The console script and MCP launcher are nowanvilandanvil-mcp. The companion env vars (ANVIL_GLOBAL_CONFIG,ANVIL_TEST_GH_TOKEN,ANVIL_TEST_REPO,ANVIL_HOOK_DEBUG) follow the same prefix change.
[1.39.0] — 2026-06-17
Added
- Fast-lane work packets — right-size process by score (T020). A task
whose
complexityANDblast_radiusscores are both at or below configurable ceilings now routes to a minimal, single-step work packet: the trimmed update-protocol prose introduced in T015 plus a required-evidence checklist trimmed to the single essential field. A trivial change no longer drags an agent through a multi-item evidence ceremony, while anything above either ceiling (e.g. a one-line change that touches a 5/5-blast schema/config/public-API surface) — or any unscored task — keeps the full packet, the safe default.- Configurable ceilings. Two new
config.yamlkeys,fast_lane_complexity_max(default 2) andfast_lane_blast_radius_max(default 2), let a project widen or narrow the fast-lane without touching code. Both are validated to the 1-5 score scale (booleans rejected); absent keys fall back to the renderer's built-in conservative defaults, so every pre-T020 config keeps its exact prior packet routing. - New config-aware seam. A new public
context.packets.fast_lane_packethelper reads those two ceilings and drivesis_lightweight/render_packet, giving CLI and MCP callers one obvious entry point instead of re-deriving thelightweightdecision at every call site.render_packetgains afast_lane_required_evidence_maxparameter (default 1; negative disables the evidence trim) and now exportsFAST_LANE_REQUIRED_EVIDENCE_MAX. - Wired into both surfaces.
anvil packet(CLI) and thegenerate_work_packetMCP tool now route the fast-lane from the project's config when one is loadable, and fall back torender_packetwith built-in defaults when there is no/brokenconfig.yaml— a broken config never blocks packet generation. - Packet-shape only — never the evidence ledger. The trim is purely a
rendering concern: the task's stored
Verification.required_evidenceis never mutated, the JSON packet still carries the full declared list for audit (alongside the right-sizedrequired_evidencethe agent is shown), completion still records the same immutable evidence transition, and the review gate still reads the full stored list. The fast-lane is advisory right-sizing, not a back-door that weakens the evidence record. - Covered by
tests/test_config.pyandtests/test_context.py.
- Configurable ceilings. Two new
[1.38.0] — 2026-06-17
Added
- Structured acceptance grammar (EARS / Gherkin) in the PRD parser
(T028/F007). Acceptance criteria can now carry machine-readable intent: a
new public
parse_acceptance_grammarhelper inplanning/template.pydecomposes each criterion into a structuredAcceptanceClause(text,kind,clauses) when an EARS or Gherkin grammar is present, and falls back to afreeformclause otherwise — so the same loop that already reads a PRD's bullet criteria can now act on why/when/then structure, not just opaque strings.- EARS recognition (single-line). Handles the conditional forms
WHEN <trigger> THEN <response>,WHEN <trigger>, THE SYSTEM SHALL <response>, plusWHILE/WHERE/IFpreconditions (including a nestedWHILE x, WHEN y, the system shall z), and the ubiquitousTHE SYSTEM SHALL <response>form. A conditional clause is only classified as EARS when it actually yields a response, so an ordinary sentence merely starting with "When" is not misclassified — it falls through to freeform. - Gherkin recognition (single- and multi-line). Parses
Given <context> When <event> Then <outcome>whether written inline or as separate Given/When/Then bullets in the same criteria block;And/Butcontinuations fold into the nearest preceding keyword, and structural noise lines (Scenario:,Feature:,Background:) are skipped. A minimal viable scenario requires at least aWhenand aThen. - Purely additive — never raises, never does I/O. The canonical
Task.acceptance_criterialist of raw strings is unchanged; callers opt in to the structured view, and a freeform PRD parses exactly as before. The raw criterion text is always preserved verbatim on the clause (multi-line Gherkin blocks collapse several input strings into one clause, joined by newlines).AcceptanceClauseandparse_acceptance_grammarare exported fromplanning.template. - Covered by
tests/test_parser.py(-k acceptance_grammar, 20 cases) spanning EARS when/then/shall/while/where/if, inline and multi-line Gherkin, freeform fallback, and the round-trip that a PRD with structured criteria parses into clauses while a freeform PRD still parses unchanged.
- EARS recognition (single-line). Handles the conditional forms
[1.37.0] — 2026-06-17
Added
- Batch dependency-edit primitive — atomic, cycle-detecting (T022/F007).
A new way to rewire task dependencies in bulk, exposed both as the
anvil depsCLI command and theedit_dependenciesMCP tool, so a human or an agent can add and remove many dependency edges in a single transaction instead of mutating tasks one at a time.- Edge model. Each edge is
SOURCE:TARGET(CLI) /[source, target](MCP), meaning source depends on target.--add/--remove(CLI) andadd/remove(MCP) are both repeatable and may be mixed in one call; the CLI also accepts the arrow formSOURCE->TARGET. - Atomic, all-or-nothing. The entire batch is planned and validated up
front. Any unknown task, self-dependency, or edit that would introduce a
dependency cycle rejects the WHOLE batch with zero events appended — there
is never a partial application. On success exactly one
task.createdupsert is emitted per task whose dependency set actually changed (no-op edges are skipped), and each task's status is preserved. - Structured results.
--json(CLI) and the tool response returnchanged(tasks mutated) plus theadded/removededges that took effect. Rejections carry a machine-readablecode(cycle,unknown_task,self_loop,bad_request); the CLI exits 1 on a rejected batch and 2 on a malformed request. - Implemented via new
planning/_plan_helpers.pyprimitives (parse_dep_edge,plan_batch_dep_edits,emit_batch_dep_events,DepEdge,BatchDepError); the MCP server now exposes 24 agent-facing tools. Covered by new tests intests/test_cli_plan.pyandtests/test_mcp.py.
- Edge model. Each edge is
[1.36.0] — 2026-06-17
Added
- Docker MCP catalog packaging for the FastMCP stdio server (T021/F006).
anvil's MCP surface can now be published to (and run from) the Docker
MCP catalog as a self-contained stdio server image, so a host without a local
Python/uv toolchain can launch the full PRD → plan → review → claim → apply
tool set by pulling one image and bind-mounting a project directory.
- Repo-root
Dockerfilebuilds the engine on the distroless-friendlyghcr.io/astral-sh/uvbase, installs dependencies frombin/uv.lockin a cached layer (frozen, no dev/provider extras), runs as a non-root user, and setsENTRYPOINT ["python", "-m", "anvil.mcp_server"]. The image is stateless — project state lives under.anvil/in a bind-mounted host directory resolved viaANVIL_ROOT(defaulting to/project, declared as aVOLUME), never baked into the image. server.yamlDocker MCP catalog manifest (mirroringdocker/mcp-registryconventions): server metadata,productivitycategory, tags, the{{anvil.project_path}}:/projectvolume mount, theANVIL_ROOTenv wiring, and aproject_pathparameter — plus inline submission steps for opening the registry PR.--help/--versionentry point on the MCP server — a newmain(argv)short-circuits beforemcp.run()so a container smoke test (docker run --rm anvil-mcp --help) prints and exits 0 without blocking on stdio. The help page is self-contained (no backend/project access, so it works in a bare image), introspects the live FastMCP tool list (it can never drift from the registered surface), documentsANVIL_ROOT, and rejects unknown flags with exit 2 rather than silently starting the server. The no-argument path (start the blocking stdio server) is byte-for-byte unchanged..dockerignorekeeps the build context minimal and deterministic (onlybin/{pyproject.toml,uv.lock,src}andREADME.mdreach the image).- Publishing walkthrough documented in
docs/mcp.md(build, smoke test, bind-mount run, and the catalog submission flow). - Covered by
tests/test_docker_mcp_catalog.py: in-process entry-point checks (--help/--versionprint and exit 0, unknown flags exit 2, the help page lists every registered tool) run always in CI; the realdocker build/docker runsmoke test is guarded behind a Docker-availability skip.
- Repo-root
[1.35.0] — 2026-06-17
Added
graph --format mermaiddependency/state diagram (T019/F008). A new read-onlyanvil graphcommand renders the persisted task graph as a copy-pasteable diagram.--format mermaidemits a deterministic Mermaidgraph LRflowchart whose nodes are tasks (labelled with ID, title, and status, and coloured per status viaclassDef) and whose edges are dependencies orienteddep --> task— the same node/edge/ready-to-claim view the MCPget_dependency_graphtool exposes, so the two surfaces never drift.- Three output formats.
--format text(default) prints a short human-readable summary (node count, edge count, by-status breakdown, ready-to-claim list);--format mermaidprints the flowchart;--json(or--format json) emits the v1.24 envelope underdatawithnodes,edges,ready_to_claim, and — when--format mermaidis requested — the rendereddiagramstring alongside the structured graph. - Scoping.
--scope all(default) renders the whole project,--scope feature --target F001narrows to one feature, and--scope task --target T007renders a task plus its transitive dependencies — mirroring the MCP tool's scope semantics exactly. - Deterministic, pipe-safe rendering. Nodes are emitted in task-ID order
and edges in
(from, to)order so identical state always produces byte-identical output. Subtask IDs likeT001.1are sanitised to Mermaid-safe node IDs, labels are HTML-escaped, and an empty scope still yields a valid (renderable) diagram rather than a broken block. Invalid--scope/--formatvalues and a badANVIL_ROOTstill return a parseable error envelope under--json.
- Three output formats.
[1.34.0] — 2026-06-17
Added
- Non-feature task types (bugfix / refactor / modify) through the
PRD-to-claims loop (T015/F005). Tasks are no longer implicitly "features":
a new
TaskTypeenum (feature/bugfix/refactor/modify) flows end to end —plan→score→claim→ work-packet → evidence — so a brownfield or maintenance PRD can describe repairs and restructuring as first-class work instead of dressing every item up as net-new capability.**Type:** feature|bugfix|refactor|modifyin the PRD task block. The template parser reads an optional**Type:**field (documented indocs/prd-template.md); an unknown value falls back tofeaturewith a non-fatal parse warning rather than aborting the parse — the same forgiving policy**Priority:**already uses.--typefilter onlistandnext.anvil list --type bugfixandanvil next --type refactorscope to a single type, pushed down to SQL via a newtask_typeparameter threaded throughBackend.list_tasks/SqliteBackend.list_tasks,ClaimManager.next_claimable, and the MCPlist_taskstool. Thelisttable and--jsonfiltersobject both surface the type; omitting the flag keeps the pre-T015 behaviour (all types eligible).- Score-routed lightweight work-packet variant. A new pure
is_lightweight(task)predicate (context/packets.py) routes a task whose six-dimension score puts both complexity and blast_radius at/below conservative ceilings (LIGHTWEIGHT_COMPLEXITY_MAX= 2,LIGHTWEIGHT_BLAST_RADIUS_MAX= 2) to a trimmed packet — the update-protocol section collapses to the single submit line, dropping the heartbeat reminder and status-flow walkthrough. Every load-bearing section (goal, acceptance criteria, scope, verification, claim) is byte-for-byte identical in both variants; an unscored or high-blast task always gets the full packet. Routing is by score, never by type, so a high-blastrefactoris treated exactly like a high-blastfeature.WorkPacketgains avariant("lightweight"/"full") field, echoed intojson_data["variant"]for MCP consumers; the thresholds are parameters so T020 can later drive them from config. - Schema v4 → v5 (auto-upgrade, purely additive).
tasksgainstask_type TEXT NOT NULL DEFAULT 'feature'; theDEFAULTbackfills every pre-v5 row tofeature— exactly its original meaning — so no data migration is required. The on-open upgrade path retrofits the column idempotently across every legacy version (0/1 → 5,2 → 5,3 → 5,4 → 5) via a new_ensure_task_type_columnALTER wrapped in duplicate-column tolerance.TaskCreatedPayload.task_typedefaults tofeatureso a payload predating the enum (or any minimal hand-rolled caller) deserialises unchanged, preserving the replay byte-equality invariant. - A bugfix PRD item now produces a typed task that claims, executes, and
submits evidence end-to-end. Covered by new cases in
tests/test_models.py(-k task_type),tests/test_template.py,tests/test_context.py,tests/test_sqlite.py,tests/test_mcp.py,tests/test_schema_version.py, and a newtests/test_task_type_e2e.py.docs/cli-reference.mdanddocs/migrations.mddocument the--typefilter and the v5 migration.
[1.33.1] — 2026-06-17
Changed
- Standalone-first onboarding docs (T011/F002). The Getting Started
walkthrough and the README now lead with a crew/flow-free path — every step
(
init→prd→plan→claim→execute→finish) is driven entirely through theanvilCLI (or the equivalent MCP tools), so a new user can run the full loop withoutfakoli-floworfakoli-crewinstalled and without Claude Code.docs/how-to/getting-started.mdgains an explicit "Optional: fakoli-flow / fakoli-crew integration" section, clearly marked as purely additive — the sibling plugins are an opt-in orchestration upgrade over the same state engine, never a prerequisite, and nothing in the standalone walkthrough degrades without them.README.mdreframes the integration section as "Optional:" and splits Requirements into Required (Python 3.11+ withuv) vs Optional (Claude Code, fakoli-flow, fakoli-crew — each additive), correcting the prior "recommended" framing that implied the siblings were needed.
Added
- Docs self-sufficiency gate. New
tests/test_standalone_docs.pyenforces the standalone-first contract so the onboarding story cannot silently rot back into "you also need flow/crew": it mirrors the backlog verification (grep -L -e 'flow:' -e 'crew:' docs/how-to/getting-started.md) — the standalone walkthrough references noflow:/crew:command token — and adds structural checks that the standalone path leads and that an explicitly optional, additive integration section exists.
[1.33.0] — 2026-06-17
Added
anvil claim --branch NAME— caller-supplied / existing-branch claims (T027). A claim can now attach to a branch the user names directly instead of always generating the defaultagent/<task>-<slug>name, so the engine meets an agent or human where their own git workflow already is rather than forcing every claim onto a synthesized branch.- New
--branchflag onclaim. When supplied, the branch is resolved before the claim is recorded: an existing local branch is checked out (git checkout NAME), and a non-existent one is created (git checkout -b NAME). The name is used verbatim — no slugging — so the caller controls the exact ref; git itself rejects invalid ref names, which surfaces as a non-fatal branch warning rather than a failed claim. - The resolved branch is recorded on the claim. Unlike the default path
(where the auto-generated branch is reported but not stored), a
--branchname is persisted on theClaimand the claims row, so project state reflects the branch the work actually lives on.ClaimManager.claim()gains an optionalbranchparameter threaded into the constructedClaim; theClaimResultcarries it back. - Non-git working trees are tolerated. If git is unavailable or the
directory is not a repo, the claim still succeeds — the requested branch
name is recorded on the claim to preserve intent, and a branch warning is
surfaced exactly like the default path (same no-fail contract as
create_branch_for_task). - Default path is byte-for-byte unchanged. Without
--branch, the auto-generatedagent/<task>-<slug>branch is created after the claim and reported (not stored) as before. The CLI now prefers the branch recorded on the claim when emitting both the--jsonenvelope and human output, falling back to the checkout result for the default path. - New
use_named_branch()ingit_ops/branch.py(checkout-or-create against a verbatim name, optionalbaseref). Covered by new cases intests/test_cli.py.
- New
[1.32.0] — 2026-06-17
Added
anvil prd resolve-decision— decision back-propagation into the PRD (T018). Closes the loop opened byprd find-decisions: an answered decision can now be written back intoprd.mditself, not just tracked in a reviewer's head, so the PRD stays the single source of truth instead of drifting from the conversation that resolved its open questions.- Resolves a decision by its detector id. Takes a
DECISION_IDexactly asprd find-decisionsreports it (ND-001/OQ001/MF-T012-AC) plus a--resolutionanswer, locates the referenced PRD span via the samefind_unresolved_decisionsdetector (no second, drift-prone parser), and edits only that span. - Kind-aware back-propagation, never a blind overwrite. A
[NEEDS DECISION]marker is rewritten inline on its linked requirement; an open question is moved into a## Decisionssection; a missing acceptance/ verification field is added under its task block. Unrelated PRD content is left byte-for-byte intact — the newapply_decision_to_markdownengine inplanning/decisions.pyrewrites the targeted span and nothing else, and raisesResolutionErrorrather than guessing when the anchor no longer matches. - Recorded as an additive, replayable audit fact. A new
prd.decision_resolvedevent (payloadPrdDecisionResolvedPayload, transitionprd_decision_resolved) is appended to the log capturing the decision id, kind, PRD ref, resolution, resolver, and the exactbefore/afterspan that changed. It is audit-only — it mutates no SQLite table and replays as a no-op — so the immutable event log records that a decision was answered while the PRD source itself remains the canonical text (refreshstate.dbwith a laterprd parse). The transition refuses to resolve a decision on arejectedPRD (wrong_status) and rejects blank ids/refs/resolutions (invalid_input). - Honors the v1.24
--jsonenvelope and--by/--file/--cwdresolution. Covered by new cases intests/test_decisions.py,tests/test_transitions.py, andtests/test_cli.py.
- Resolves a decision by its detector id. Takes a
[1.31.0] — 2026-06-17
Added
- Global-config layer
~/.config/anvil/config.yamlwith project override (T016/F006). A user-wide config now supplies defaults that any individual project can override, so settings likedefault_lease_minutes,llm_tier, orauto_expand_thresholdcan be set once per machine instead of re-declared in every project's.anvil/config.yaml.- Strict precedence, lowest → highest: built-in dataclass default <
global config (
~/.config/anvil/config.yaml) < project config (.anvil/config.yaml) < explicit CLI flag. A key set in the project config wins over the same key in the global config; a global-only key supplies the value; a key in neither falls through to the dataclass default. - New
load_merged_config()andglobal_config_path()inconfig.py. The merge is a shallow project-over-global overlay validated against the MERGED mapping, so a global config MAY supply a defaultproject_name/project_idthat a project overrides — but it is never required to carry them, and a project missing them still raises the sameValueErrorwhen the global layer does not supply them either. Per-project paths (db_path/events_path) always resolve relative to the PROJECT config's directory, never~/.config.load_config()stays a single-file load (no merge) for callers that want it; the single-file YAML parse and Config construction are factored into shared_read_yaml_mapping/_build_confighelpers so both paths validate identically. - Global-path resolution honours XDG.
global_config_path()resolvesANVIL_GLOBAL_CONFIG(explicit file override, for tests / unusual installs) >$XDG_CONFIG_HOME/anvil/config.yaml> the documented~/.config/anvil/config.yamldefault. A missing or empty global config means "no global defaults" rather than an error; a broken global config the user explicitly wrote raises the same loudValueError/YAMLErroras a broken project config. - CLI consumers now load the merged config.
claim,renew, andplanread throughload_merged_config(), so global planning/lease defaults apply everywhere the project config used to be the only source. - New
--leaseflag onclaimandrenew. An explicit--lease N(minutes) overrides the merged project>globaldefault_lease_minutes, and works even in a project with noconfig.yamlat all — closing the top of the lease precedence chain (flag > project > global > built-in 60). docs/cli-reference.mddocuments the global layer, the precedence order, and the--leaseflag. Covered by new cases intests/test_config.py(TestGlobalConfigPath,TestLoadMergedGlobalConfig,TestGlobalConfigLeasePrecedence), including the acceptance scenario where a global default lease of 45 is overridden to 30 by a project config and to 15 by the CLI flag.
- Strict precedence, lowest → highest: built-in dataclass default <
global config (
[1.30.0] — 2026-06-17
Added
next_readyfield in finish/submit responses (CLI + MCP) (T014). The surfaces an agent hits when it finishes a task now hand back the next safe task to pick up, so a multi-agent loop can chain finish → claim without a separatenextround-trip. Both response paths carry anext_readyobject ({id, title, priority}) ornullwhen the ready queue is empty.- CLI
apply--jsongainsnext_readyin both review-only and decision modes, and the submit path emits it too — a stable key set script authors can rely on regardless of which apply mode ran. - MCP
apply_review_decisionandsubmit_completion_evidencereturn a typedNextReadyTask | Nonefield with the same shape, so the agent-facing tools and the CLI stay in lockstep. - Suggestion is file-conflict-aware, not just the raw queue head. Both
surfaces compute the field through the new
ClaimManager.next_ready_excluding_active_files()helper, which starts fromnext_claimable(priority desc, complexity asc, created_at asc; honoring status, dependencies, active claims, and conflict groups) and adds ONE stricter exclusion: a candidate is skipped when itslikely_filesoverlap theexpected_filesof any active claim held by another actor. The named "next ready" task can never be one whose files collide with work another agent is already holding — and re-suggesting work the same actor already holds is not treated as a conflict. docs/mcp.mddocuments the field on both tools. Covered by new cases intests/test_claims.py,tests/test_json_output.py, andtests/test_mcp.py.
- CLI
[1.29.0] — 2026-06-17
Added
anvil describeself-describing command surface +--versionschema reporting (T012/F003). The engine now exposes a machine-readable manifest of its own public surface so a non-Claude host (Codex/Cursor/CI/another agent) can discover what it can do without parsing--helptext or hard-coding a tool list.anvil --versionnow reports engine AND schema version —anvil 1.29.0 (schema 4). The first token staysanvil {__version__}for backward compatibility with existing parsers; the(schema N)suffix identifies the on-disk state format the engine speaks, which a host pinning behaviour needs alongside the build id.- New
describecommand emits the standard{"ok": true, "command": "describe", "data": {...}}envelope by default (it is inherently machine-readable), carryingapi_version,engine_version,schema_version,envelope, the sorted list of every CLI subcommand (grouped commands rendered as their full invocation path, e.g.prd parse), and the sorted list of every MCP tool name — each with acount.--humanprints a readable summary;--jsonis an accepted no-op for flag symmetry. Needs no project: it never opens a backend and works from any directory, even beforeinit. - Stable
api_versionto pin against.__version__bumps on every release (often metadata-only), but the shape of the command surface changes far less often.api_version(currently"1") only changes when the surface changes in a way consumers must react to — a CLI command or MCP tool added, renamed, or removed, or the--jsonenvelope shape changing — so a host can pin the contract without re-pinning on every patch release. - No drift, no hand-maintained list. The manifest is introspected live from
the registered Typer app (via
typer.main.get_command) and the FastMCP instance (viamcp.list_tools()) at call time, so the described surface is generated from the exact objects the CLI and MCP server actually expose — includingdescribe/describe_surfacethemselves. - New MCP tool
describe_surface(tool 23) is the agent-facing counterpart: it returns the identical manifest dict by reusing the CLI'sbuild_manifest(single source of truth), so an MCP-only host gets the same self-description without shelling out to the CLI. - Covered by
tests/test_cli.py::TestDescribe(7 cases), which asserts the described CLI commands match the registered Typer commands and the described MCP tools match the live FastMCP registry — so CI fails if a command or tool is added or renamed without the surface staying coherent.
[1.28.0] — 2026-06-17
Added
anvil doctorone-shot health diagnosis (T010/F002). A read-only triage command that answers "is this project's state healthy?" in a single pass, emitting a list of severity-tagged findings (ok/info/warning/error) and exiting non-zero when ANY finding is ERROR-level — a real health gate, unlikedrift's always-0 report. Five probes:- state.db reachability + schema version — reads the TRUE on-disk
PRAGMA user_versionbefore open (so a re-stamp on open can't mask drift) and reports it against the engine-targetedSCHEMA_VERSION. An un-migratable / unknown schema is the canonical "your install needs migration" signal — an ERROR. A migrated-on-open db is healthy with aninfonote. - config parse status + effective lease/heartbeat — does
config.yamlparse, and what lease/heartbeat would a claim actually use? A broken config is a WARNING (the CLI falls back to the built-in 60/5-minute defaults), not a hard error; a missing config isinfo. - active / stale claim counts — counts active claims and how many have an expired lease (work silently wedged until a mutating command reaps it). Read-only: it compares leases against the clock WITHOUT reaping. A stale claim is an ERROR.
- replay integrity — rebuilds canonical state from
events.jsonlinto a scratch db via the samereplay_from_emptyenginereplayuses, then byte-compares it against the livestate.dbthrough theserialize_statesnapshot the SL-1 replay-equivalence test uses. A mismatch (the event log no longer reproduces the projection) is an ERROR. - reconciliation drift summary — the local INTENT/STATE/FS/GIT drift
driftreports (orphan branches/worktrees/packets, missing expected files), reusingReconciliationEngine.scan()with no providers. A non-empty drift set is a WARNING (a report, not a gate); stale claims are not re-escalated here since the claims probe already counts them as an ERROR. - Degrades gracefully. Every probe is wrapped so one failed check (corrupt
config, unreadable db) produces a finding rather than a traceback, and the
rest of the report still runs. When the backend can't open (schema mismatch),
replay/reconciliation are reported as skipped
infofindings — but the stale-claim probe falls back to a narrow read-only SELECT against the version-stableclaimscolumns, so a project with BOTH an injected stale claim and a schema mismatch lists BOTH findings, not just the schema one. - Honors the v1.24
--jsonenvelope ({"ok": true, "command": "doctor", "data": {"healthy", "worst_severity", "findings": [...]}}) and theANVIL_ROOT/--cwdresolution precedence. Covered bytests/test_cli.py(11 cases, incl. the healthy-clean-exit and stale-claim-plus-schema-mismatch acceptance scenarios).
- state.db reachability + schema version — reads the TRUE on-disk
[1.27.0] — 2026-06-17
Added
anvil scanbrownfield ingest +init --from-repo(T008). A new command takes an existing repo from nothing to a ready-to-claim task graph with no PRD authoring, no LLM, and no API key.scanwalks the working tree, persists a re-scannable codebase model in its own SQLite db (.anvil/scan.db), and — on the first scan of a project with no PRD yet — synthesises a draftprd.mdplus an initial feature/task graph by driving the same offline parse → plan → score → review pipelineinit --with-sampleuses.init --from-repois the convenience entry point: it scaffolds.anvil/like a bareinit, then immediately runs the scan.- Separate
scan.dbkeeps the replay/audit guarantee intact. The codebase model lives in its own database, never in the event-sourcedstate.db, so the canonical event log and its byte-equality replay invariant are untouched. - Re-scan reports a delta, never clobbers. A second
scanreconciles the refreshed working tree against the persisted model and reports added / removed / changed files instead of overwriting the seeded graph. An authored or already-parsedprd.mdis left alone unless--forceis passed — only a project with no PRD yet (the common brownfield first run) gets seeded. - Fully offline / no API key. Seeding reuses the generalised
cli._sample.seed_pipeline_from_prdengine (extracted from the sample path) so the brownfield seed drives the exact parse → plan → score → review modules the per-command CLI bodies use and cannot drift from the hand-run sequence. The generated draft PRD carries## Features/## Taskssections with non-empty acceptance-criteria + verification blocks, so the run ends with at least one task inreadyandanvil nextreturns a first task. --from-repois mutually exclusive with--with-sample(both ownprd.mdand seed the graph; the combination is refused up front) and honours the v1.24 conventions —ANVIL_ROOTresolution via the shared helpers and the single--jsonenvelope. Covered bytests/test_scan.py.
- Separate
[1.26.0] — 2026-06-17
Added
anvil init --with-sampleone-command quickstart (T004). A single command now takes a brand-new user from nothing to a ready-to-claim task with no PRD authoring, no LLM, and no API key. Passing--with-sampletoinitscaffolds the.anvil/directory as before, then writes a self-contained sampleprd.md(a fictional "Markdown Link Checker" CLI) and runs the full deterministic pipeline offline — parse → review → approve → plan → score → review tasks — leaving at least one task inreadysoanvil nextreturns a first task immediately.- Fully offline / no API key. The sample PRD already contains a
## Taskssection, soplannever reaches its LLM task-generation backstop. Seeding drives the same engine modules the per-command CLI bodies use (planning.template.parse_prd,planning.inference.infer_all,planning.scoring.score_task,state.transitions) so the seeded path cannot drift from the hand-run command path. - Every sample task carries non-empty
**Acceptance criteria:**and**Verification:**blocks — exactly the gatereview tasksenforces for thedrafted → reviewed → readypromotion — so the run actually ends with ready tasks rather than an empty queue. - Atomic with
init. The seed runs inside the same backend session thatinitopens, so the sample pipeline is applied in one shot. The sample PRD is embedded as a module constant (_sample.SAMPLE_PRD) rather than a data file, since the wheel only packagessrc/anvil. - Opt-in and back-compatible. Without the flag,
initbehaviour is unchanged. The confirmation output reports the seeded feature/task/ready counts and points straight atanvil next.
- Fully offline / no API key. The sample PRD already contains a
[1.25.1] — 2026-06-17
Added
- Token-footprint CI budget gate (T013/F003). A self-audit that enforces an
explicit token budget on the plugin's skill surface, so the always-on context
tax cannot creep up unnoticed.
tests/test_token_budget.pymeasures, against a documented baseline-plus-headroom budget, (1) the combined always-loaded skill frontmatter — the command surface Claude Code injects into the system prompt on every turn whether or not a skill fires — capped atALWAYS_LOADED_FRONTMATTER_BUDGET(1000 tok, baseline ~688); (2) any singleSKILL.mdbody viaPER_SKILL_FULL_CEILING(6000 tok, largestplan~5097); and (3) all bodies combined viaTOTAL_FULL_BUDGET(40000 tok, baseline ~33200).- No tokenizer dependency — counts use the deterministic
ceil(chars / 4)heuristic rather than pulling intiktoken, so the gate is stable across machines and Python versions and needs no network fetch. It catches relative growth (a skill quietly doubling) without flapping on ordinary edits. - Reports every offender, not just the first — a single failing CI run prints per-skill token counts so all the trimming work is named at once.
- Doc and gate can't silently drift —
docs/context-budget.mdis the human-readable contract (budgets, baseline table, "changing a budget" guidance), andtest_budget_is_documentedasserts the doc exists and names every budget knob. Raising a budget requires updating both in the same change. docs/architecture.mdcross-references the budget and the gate from the skills section.
- No tokenizer dependency — counts use the deterministic
[1.25.0] — 2026-06-17
Added
anvil migrate statecommand (T009/F006). Promotes the schema migration that previously only ran silently insideinitialize()— on the first command after an engine upgrade — to an explicit, backed-up, dry-run-by-default operation. It does not introduce a new migration framework: it runs the exact ordered, idempotent forward branches (0/1→4,2→4,3→4) that already live inSqliteBackend._check_schema_version.- Detects the TRUE on-disk version via the
read_db_schema_versionaccessor (read-only — detection never migrates as a side effect). - Dry-run by default — reports
from → toand exits without touching the db;--yesapplies. - Backs up
state.db(and any-wal/-shmsidecars) tostate.db.pre-schema-migration.bakbefore mutating, and refuses to clobber a leftover backup from a prior attempt. - Refuses while any claim is active — same guard as
migrate-events; listing active claims on the dry-run path uses a scratch copy so the live db is never migrated before--yes. A db version newer than the engine (schema_too_new) is surfaced cleanly instead of failing mid-apply. - Idempotent — once at the current
SCHEMA_VERSION, re-running is a reported no-op that mutates nothing. --jsonemits the standard envelope ({"ok": true, "command": "migrate state", "data": {"from_version", "to_version", "applied", "migrated", "backup"}}).migrate-eventsstays a top-level command;migrateis now a sub-app groupingmigrate state.
- Detects the TRUE on-disk version via the
docs/migrations.md: documents the explicit-migration flow, behaviour, and its relationship to thereplayescape hatch (in-place row-preserving vs. rebuild from the audit log). Covered by new cases intests/test_snapshot.py.
[1.24.0] — 2026-06-17
Changed
- Trimmed agent descriptions. Moved the
<example>blocks out of every agent's frontmatterdescriptionand into the agent body, shrinking the always-on context footprint from 7,248 → 5,414 tokens (−25%). The triggering examples still live in each agent file — just below the frontmatter rather than inside it — so dispatch guidance is preserved without paying the token cost on every session load.
Removed
- The monorepo-only
marketplace-scribeagent. Its duties — keeping the rootREADME.mdplugins table,.claude-plugin/marketplace.json, andregistry/*.jsonin sync — only exist inside the fakoli-plugins monorepo, not in a standalone anvil checkout.docs-scribeandstate-keeperare refocused for the standalone surface, and marketplace-level regeneration defers tofakoli-crew:keeperwhen that plugin is present. anvil now ships 5 plugin-owned agents:critic,docs-scribe,planner,sentinel,state-keeper.
[1.23.8] — 2026-06-17
Added
anvil driftcommand (read-only): surfaces three-source divergence — INTENT (PRD/tasks) vs STATE (SQLite) vs FILESYSTEM/GIT — by reusing the reconciliation engine (no duplicated logic). Reportsmissing_expected_file(a done/accepted task whoselikely_filesare absent on disk — a new engine check),orphan_branch,orphan_worktree,orphan_packet, andstale_claim. Works without a configured sync provider (local drift only), emits the--jsonenvelope, exits 0 (a report, not a gate), and honorsANVIL_ROOT. This is the "is the code still what the spec said?" view competitors structurally lack.
Fixed
- Reconciliation: orphan-packet scan never fired.
_scan_orphan_packetsused a doubled.anvil/.anvil/packetspath under the real CLI/MCP calling convention, so orphan packets were undetectable via bothdriftandsync(only unit tests that pass the project root masked it). Now resolves correctly. - Expected-file path normalization corrupted dotfiles.
str.lstrip("./")stripped a character set, mangling.env→env,.github/workflows/ci.yml→github/..., and../x→x. Replaced with a proper leading-"./"prefix strip + in-tree..clamping, so dot-rootedlikely_filesare checked at the correct path.
[1.23.7] — 2026-06-17
Added
ANVIL_ROOTenvironment override for project resolution across the CLI and the MCP server. When set, it points anvil at the project root (the directory containing.anvil) regardless of the current working directory — making the engine correct inside containers and non-cwd MCP hosts. Precedence: explicit--cwd>ANVIL_ROOT> cwd. An invalid value fails loudly (no silent cwd fallback), andinit/init_projecthonor it too so create-then-read target the same directory. Centralized incli/_helpers._resolve_base_dir.schema_versionexposed to tooling.status(human +--json) now reportsschema_version(the code-targetedSCHEMA_VERSION) anddb_schema_version(the true on-diskPRAGMA user_version, read before migration so pre-migration drift is observable). An un-migratable schema is reported as a clean{"error":{"code":"schema_mismatch"}}+ exit 1 (no traceback), andstatus --hook-formatstays exit-0-safe. Accessors:schema.get_schema_version(),SqliteBackend.get_schema_version(),sqlite.read_db_schema_version().
Known follow-up
- An un-migratable DB is silently re-stamped to the current
SCHEMA_VERSIONby_apply_ddlbefore the mismatch is raised, soschema_mismatchis reported only on the first open (masked afterward). Pre-existing; a clean fix requires reordering coreinitialize/_apply_ddl. Tracked.
[1.23.6] — 2026-06-17
Added
- Context-footprint self-audit (
benchmarks/context_audit.py+benchmarks/CONTEXT_AUDIT.md): a reproducible measurement (tiktoken, chars/4 fallback) of the always-on context cost anvil imposes when installed — ~7,248 tokens (MCP tool schemas 54%, agent descriptions 38%, skill descriptions 8%, hooks 0.3%), with the ~42.7K of skill/agent bodies excluded by progressive disclosure. MCP schemas are measured live from the real FastMCP serialization. Verdict: modest (~3.6% of a 200K window) but mixed — ~68% of agent-description tokens are verbose<example>blocks and two repo-maintenance agents (~20%) a standalone user never needs. Identified trims (strip agent-description examples into bodies, opt-in the maintenance agents, tighten MCP docstrings) could cut the baseline ~48% with no capability loss. Earns the right to the "context-frugal" claim by measuring it honestly.
[1.23.5] — 2026-06-17
Added
- Enforceable completion-evidence gate (strict mode). The evidence gate was
advisory — it flagged missing required evidence but never blocked, so "agents lie
about done" (the single most-felt pain in the competitor-issue analysis) was
unenforceable. Now, when strict mode is active, accepting a task with unsatisfied
required_evidenceis refused (the task staysneeds_review) on both surfaces:- CLI
apply --approve(a--strict / --no-strictflag), and - the MCP
apply_review_decisiontool (astrictparameter) — the surface agents actually use. Strict is resolved as: explicit flag/param > project configstrict_evidence>False(default advisory, fully back-compatible). It reusesreview.gates.evidence_complete(no reimplementation). Under CLI--json, a refusal is{"ok": false, "command": "apply", "error": {"code": "evidence_incomplete", "missing": [...]}}with exit 1; the MCP tool raisesToolErrorcarrying the sameevidence_incompletecode + missing items.--rejectis never gated; a complete gate or emptyrequired_evidenceis a no-op.submitstays advisory (it transitions toneeds_review, notdone— the gate belongs at the accept transition). Configstrict_evidence: falseis added to theinitscaffold. Covered bytests/test_strict_evidence.py(CLI) andtests/test_mcp.py::TestApplyReviewDecisionStrictEvidence(MCP).
- CLI
[1.23.4] — 2026-06-17
Added
- Machine-readable
--jsonoutput across the query and mutation CLI commands (list,show,status,next,prd find-decisions,plan,score,review tasks,claim,renew,release,submit,apply) — the standalone / non-Claude-host product surface. One consistent envelope, emitted as a single compact JSON line on stdout: success{"ok": true, "command": "<name>", "data": {...}}; error{"ok": false, "command": "<name>", "error": {"code": "<stable-token>", "message": "..."}}with a non-zero exit. Domain objects serialize via Pydanticmodel_dump(mode="json")(ISO-8601 datetimes, enum values), so the wire form stays in lockstep with the models. Non-fatal warnings fold intodata.warnings; stderr stays empty in--jsonmode so stdout is always pipeable. Helpers live incli/_json.py; covered bytests/test_json_output.py. - The
commandfield is the space-joined CLI invocation path (e.g."review tasks","prd find-decisions"), andapply --jsonreturns an identical key set in both review-only and decision modes — a predictable, stable contract for script authors.
Not yet covered (by design, this pass)
expand(--format text|prdalready),packet(--format jsonalready),sync(provider/network surface),init, andprd parse/prd reviewremain human-output only — tracked for a follow-up.score --jsonand the MCPscore_taskstool expose the same operation with different shapes; alignment is a tracked follow-up.
[1.23.3] — 2026-06-17
Fixed
- TOCTOU race in the file-overlap claim check (the known issue from 1.23.2). The
cross-task file-overlap and conflict-group re-checks now run inside the same
BEGIN IMMEDIATEtransaction that inserts the claim (instate/sqlite.py_check_claim_created, via a shortBEGIN IMMEDIATE … COMMITso reads see the latest committed snapshot rather than a stale WAL read mark). Aforceflag is threaded throughClaimCreatedPayloadsoclaim --forcestill overrides the cross-task guards; the same-task double-claim guard is never overridable. Two concurrent claims on file-overlapping or same-conflict-group tasks now yield exactly one winner. Benchmarkoverlapping_filescollisions drop from a ~8%-dual-claim residual to 0 across all trials. - CLI
claim/renewignoreddefault_lease_minutes(the known issue from 1.23.2). Both now load the project config and threaddefault_lease_minutes/default_heartbeat_minutesintoClaimManager.config.pystores these asfloatand coerces viafloat(str(...)), so fractional (sub-minute) leases are honored instead of truncated/rejected.
Added
- Concurrency regression suite (
tests/test_claim_concurrency.py): N=8 threads × 300 iterations assert exactly one winner for same-task, file-overlap, and conflict-group claims (fails 4/5 pre-fix), plus a force-override test and a CLI test asserting adefault_lease_minutes: 0.5config yields a ~30s lease.
Known follow-ups (from the fix review)
- Overlap guards do hot-path scans of active claims; add an index / filter by
lease_expires_atif it matters at scale. MCPclaim_taskstill derives its lease from its own parameter, not project config.renewlease wiring is covered by the CLI but not a dedicated unit test. Tracked in the backlog.
[1.23.2] — 2026-06-17
Added
- Coordination benchmark (
benchmarks/). A reproducible harness that runs the same multi-actor workload through two coordinators — naive shared-markdown vs. the real anvil CLI — across four scenarios (overlapping files, dependency ordering, crash/stale-lease recovery, evidence gaming). Drives the live engine; no third-party deps.python3 benchmarks/run_benchmark.py.
Known issues (surfaced by the benchmark, not yet fixed)
- TOCTOU race in the file-overlap claim check — two file-overlapping tasks can
both be claimed concurrently (~8% of head-to-head attempts) because the overlap
read happens outside the
BEGIN IMMEDIATEclaim transaction. - CLI
claimignoresdefault_lease_minutes—cli/claim.pybuildsClaimManagerwithout the configured lease, so the CLI always uses the 60-minute default (the MCP path wires it correctly). The config loader also rejects fractional minutes viaint(str(...)).
[1.23.1] — 2026-06-12
Fixed
- Recursive expansion queue now reaches CLI and MCP.
scoreandscore_tasksuse the depth-capped recursive frontier, so over-depth leaves are no longer surfaced for automatic expansion after repeated splits. - Git-backed event IDs include full event identity. Hash IDs now include
action, target kind, and target id in addition to parent, payload, actor, and
timestamp. Fields are joined with the ASCII unit separator
\x1f, preventing same-payload branch events from colliding and being deduped away during git-mode replay.
[1.23.0] — 2026-06-10
Added
- Score recursion + parent roll-up — closes the four weaknesses claude-task-master users documented around its complexity scoring.
- Parent roll-up (TM #250): an expanded task (one with children) is now a container, not an actionable unit. Its stored complexity score is preserved (the event log is immutable audit history — the score still records how big the unsplit unit was), but
build_expansion_queueexcludes it from the actionable EXPANSION QUEUE, so an already-split parent is never re-queued. Newis_expanded(task, all_tasks)predicate. - Recursive expand-to-threshold: new
build_recursive_expansion_queue()returns the leaf-only expansion frontier annotated with treedepth. Recursion happens across re-scores (expand a leaf → re-score → its over-threshold children re-enter the queue), bounded byDEFAULT_RECURSION_DEPTH_CAP(3) and a cycle guard in_depth_ofso the walk always terminates — a lineage deeper than the cap is dropped from the auto-queue as a signal that the PRD block needs human restructuring. - Partial-score merge (TM #1644): re-scoring a subset of tasks no longer wipes the others. This already held by construction — scores persist as per-task
task.scoredevents, so a single-task re-score is an append that leaves every other task's projected score intact — and is now proven by a regression test (the event-sourced answer to task-master's overwrite-on-partial bug).
- Parent roll-up (TM #250): an expanded task (one with children) is now a container, not an actionable unit. Its stored complexity score is preserved (the event log is immutable audit history — the score still records how big the unsplit unit was), but
skills/plan/SKILL.mddocuments the roll-up + across-re-score recursion model and its depth/cycle safety rails.
[1.22.0] — 2026-06-10
Added
- Git-backed events, Phase A (opt-in). First slice of the 2026-06-10 git-backed-events spec:
events.jsonlcan now be a repo-scoped, merge-friendly source of truth instead of a machine-scoped one (idea adopted from steveyegge/beads):- New config knob
events_storage: local | git(defaultlocal; validated at load time like every other literal field).localkeeps pre-1.22.0 behaviour byte-for-byte — sequence ids, strict sequential replay, byte-equality guarantee untouched - In git mode, event ids are hash-chained —
"E-" + sha256(parent_event_id ‖ canonical_json(payload) ‖ actor ‖ ts)[:12]— so two branches/machines append concurrently with zero collision risk; the envelope gainsparent_event_id(chain link) andlamport(writer sets max-seen + 1). Local mode omits both keys from serialized lines - Order-tolerant replay in git mode: load all lines (torn trailing line tolerated exactly as before), dedupe by event id (a line duplicated by a
merge=unionunion applies once), order by(lamport, ts, event_id). Competing cross-branchclaim.createdevents on one task resolve deterministically — earliest(lamport, ts, id)wins the task transition (claim.supersededmaterialization is Phase B) - New CLI command
anvil migrate-events --to git [--yes]: rewrites the local log with hash ids preserving order, emitsid_mapping.json(old → new), writes.anvil/.gitattributes(events.jsonl merge=union), prints.gitignoreguidance, flipsevents_storage: gitin config.yaml, and rebuilds the projection. Dry-run by default; refuses while claims are active; backs up the original log - Schema v4 (auto-upgrade from v0–v3):
events.idCHECK widened to accept hash ids; new nullableevents.seqcolumn — the replay-assigned display order in git mode (derived state, never written to the log; NULL in local mode where the monotonic id is the order) - New pure module
state/hashing.py(hash_event_id/canonical_payload_json) shared by the write path, the migration command, and tests - 33 new tests: hash chain linkage, dedupe replay, lamport tie ordering, a divergent-merge simulation (common prefix + two branch suffixes concatenated both ways converge to identical state, with the competing-claims winner deterministic), migration round-trip (pre-state equals post-state modulo the id mapping), config knob validation, v3→v4 schema auto-upgrade
- New config knob
[1.21.0] — 2026-06-10
Added
- Complexity score → auto-expansion loop. After scoring, every task whose
complexityis at/above the configurable threshold is queued for decomposition instead of just sitting in a report (idea adopted from claude-task-master):- New config knobs:
auto_expand(defaulttrue) andauto_expand_threshold(default 4, validated 1–5; booleans explicitly rejected sotruecannot silently become threshold 1) - CLI
scorenow prints an EXPANSION QUEUE section — task id, complexity, deterministic suggested sub-task count (complexity - 1, clamped to the expand engine's 2–5 envelope), and the exactanvil expand TXXX --use-llmfollow-up per task - MCP
score_tasksresponse gains anexpansion_queuefield with the same data (queueing stays deterministic; LLM-side decomposition still only runs viaexpand --use-llmor the planner agent) expandthreshold no longer hardcoded —planning.inference.expand_taskaccepts the config thresholdskills/plan/SKILL.md: after scoring, the orchestrator auto-dispatches planner expansion for queued tasks (one summary checkpoint, no per-task Q&A) unlessauto_expand: false- New pure helpers in
planning/scoring.py:build_expansion_queue()/suggested_subtask_count()/ExpansionCandidate - 18 new tests (queue filtering/ordering/edge cases, config knob validation, template coverage)
- New config knobs:
[1.20.2] — 2026-06-10
Fixed
- Flock contention deadline measured on the monotonic clock (Greptile P1 on #80). 1.20.1 computed the
_append_lock5 s contention deadline from wall-clockClock.now(), so an NTP step mid-contention could silently stretch the timeout (backward step) or fireStateLockedearly (forward step). The deadline is now measured on an injectable monotonic source — new keyword-onlySqliteBackend(monotonic_fn=...)parameter, defaulting totime.monotonic— mirroringsleep_fn. The contention unit test drives a fake monotonic counter instead of advancingFrozenClock, keeping the timeout path fully deterministic.
[1.20.1] — 2026-06-10
Fixed
- Jittered exponential backoff for event-log flock contention.
_append_lockretried a contendedflockonevents.jsonlwith a fixed-interval sleep, which woke every waiter on the same tick. Under a coordinated multi-agent wave (10+ concurrent claimants) early winners re-acquired the lock in lockstep and late arrivals exhausted the 5 s budget without ever winning a release, surfacing as spuriousStateLocked. Retries now follow an exponential schedule — 10 ms initial, doubling to a 500 ms cap, ±10% jitter — under the same 5 s overall timeout, so wake-ups are de-synchronized and each lock release is contested by waiters at staggered offsets. _append_locknow follows the Clock-protocol convention. The contention deadline is computed via the injectedClock(previously a raw inlinetime.monotonic()), and the retry sleep is injectable via a new keyword-onlySqliteBackend(sleep_fn=...)constructor parameter (defaults totime.sleep). The backoff schedule and timeout path are covered by deterministic unit tests usingFrozenClock— no real sleeping, no monkey-patching.
[1.20.0] — 2026-06-01
Added / Changed — Event-Sourced Write Path (SL1-RR-1 closed)
Breaking internal API (additive at the feature level; no CLI surface change):
append(EventDraft) -> Event | Nonereplacesapply_eventas the sole production write entry point. Returns a committedEventon success,Noneon idempotent no-op; raisesEventRejectedon validation failure. The oldapply_event,next_event_id, andPENDING_EVENT_IDsentinel are removed.- Decide/apply split (
_check_*/_write_*). Each of the ~17 action handlers is split into a read-only_check_<action>(raisesEventRejectedon any illegal transition; no side effects) and an infallible_write_<action>(mutates given a passing check). Dispatch table mapsaction -> ActionSpec(payload_model, check, write). - Log-as-id-authority via
flock. Event ids are assigned insideappend()by scanning the tail ofevents.jsonlunder an exclusiveflock, not fromSELECT MAX(id). This closes the PR #41 Critic-3 cross-process id-collision race: two concurrent processes can no longer observe the sameMAX(id)and collide on insert. - Append-only
events.jsonl+ siblingaudit.jsonl.events.jsonlrecords only committed, successfully applied events. Validation rejections and idempotent no-ops are written exclusively toaudit.jsonl, making a poison canonical line structurally impossible. - Strict no-skip-list replay.
replay_from_emptyapplies every line via_write_*only — no validation, no logging, no skip-list. Any well-formed log replays infallibly. - Forward-catch-up self-heal. On open, the backend scans the tail of
events.jsonlfor lines not yet reflected in the SQLite projection and replays them forward, closing the post-COMMIT audit gap (crash between COMMIT and JSONL write no longer leaves the projection ahead of the log). - Relaxed-default durability with strict opt-in.
append()accepts adurabilityparameter; the default relaxesfsyncoverhead for the common single-writer case;strictmode forces a full sync for multi-writer / crash-recovery scenarios.
Closed
- SL1-RR-1 (tech-debt-backlog): a rejected non-PENDING event can no longer persist a poison canonical line in
events.jsonl— validation now happens entirely before any log write. - Inverse post-COMMIT audit gap: a crash between SQLite COMMIT and JSONL append is recovered on next open via forward-catch-up.
- PR #41 Critic-3 cross-process id-collision race: closed by log-as-id-authority +
flock.
Removed
apply_event(replaced byappend)next_event_id(id assignment is now internal toappend)PENDING_EVENT_IDsentinel (no longer needed; all callers useappend(EventDraft))
[1.19.0] — 2026-06-01
Added
- Replay proven in CI (SL-1). New
serialize_state(backend)produces a deterministic, total snapshot of canonical state;test_replay_equivalenceassertsserialize_state(normal apply path) == serialize_state(replay_from_empty) == a committed golden, plus cross-db and same-handle idempotence. A new.github/workflows/anvil.ymlruns the full suite (incl. this check) on every PR — the replay guarantee is now an enforced invariant, not an assertion. anvil replay --from-events <jsonl> --into <db>CLI wrapping the existingreplay_from_emptyengine; guards the livestate.dband missing-file inputs.- Backend read methods
list_claims/list_reviews/list_evidence/list_requirementsreturning full canonical collections (not active-only), deterministically ordered.
Fixed
- Review-decision read mapping: a rejected
task.appliedreturns the task todraftedfor rework, solist_reviews()now reportsneeds_changes(not a terminalreject). - Pre-existing
test_claim_type_valuesfailure on Python 3.11 (str in EnumTyperaisedTypeError). - Version drift across
pyproject.toml/__init__.py/plugin.json, now all1.19.0.
Known issues
- Latent replay-robustness gap: a rejected non-PENDING event leaves a poison canonical line in
events.jsonlthatreplay_from_emptyre-applies and re-fails on, aborting a full replay. Tracked intech-debt-backlog.md(SL1-RR-1) and the fakoli-style P4open_work.
[1.18.2] — 2026-06-01
Added
- Roadmap: deferral note for semantic indexing (
sqlite-vec) and a knowledge-graph view. Useful after Wave 1 (SL-5 contract conflicts, SL-6 spec assumptions) but kept outside the replay boundary as rebuildable derived indexes — never canonical state inevents.jsonl. Cross-references fakoli-style principle P11. Docs-only.
[1.18.1] — 2026-06-01
Added
- Operating-model up-links:
_evidence_complete(the unified evidence gate) and the integrity-first roadmap track now back-reference their fakoli-style principles (P1 advisory and enforcing share one code path; P5 sequence by credibility risk). Documentation-only; no behavior change.
[1.18.0] — 2026-05-31
Added
docs/roadmap.md— integrity-first 90-day priority track. A committed direction that sits above the version buckets, sequenced by credibility risk: Wave 1 (days 1-30) makes existing claims true (prove replay in CI, measure the critic false-pass rate, plus the already-shipped evidence-gate unification), Wave 2 (days 31-60) makes governance non-gameable (typedProofArtifactevidence, scoring spec assumptions), Wave 3 (days 61-90) earns the "state layer underneath a dynamic-workflow runtime" reframe (promote status-file coordination to canonical state, contract-level conflict detection, a workflow-adapter spike). Documents the three integration postures (beside / governed step / projection) and the two new types (ProofArtifact,OutputContract) the track introduces.
[1.17.1] — 2026-05-31
Fixed
- Evidence gate divergence (correctness). The
needs_review → acceptedtransition enforced a different evidence check than the oneanvil applypreviews to the reviewer. The transition used a raw, case-sensitive substring match against a flattened corpus of everyEvidencefield, whileapply(cli/packet_apply.py) used the intent-basedreview.gates.evidence_complete. The two could disagree in both directions: a task could preview as INCOMPLETE yet be accepted, or preview as complete yet be rejected. The substring gate was also trivially gameable — writing a required literal (e.g."test output") into any field passed it, andpytest --collect-onlysatisfied a "tests pass" requirement.transitions._evidence_completenow delegates toreview.gates.evidence_complete, making it the single source of truth. A parametrized agreement test locks the enforcing gate to the preview gate so they can never diverge again.
[1.17.0] — 2026-05-26
Major capability release: multi-provider LLM access (direct Anthropic API, Amazon Bedrock, OpenAI-compatible custom endpoints) plus tier-aware default model selection that drops typical session cost by ~60% versus the prior "everything routes through Opus" pattern. The five plugin-surface critics extract to a new dedicated fakoli-plugin-critic plugin (fakoli-crew 2.3.0+ no longer ships them).
Added
planning/llm.BedrockProvider— Anthropic-on-Bedrock viaanthropic.AnthropicBedrock. Boto3 credential chain (env vars / profile / IAM role) just works. Optional dep:pip install 'anvil[bedrock]'.planning/llm.CustomEndpointProvider— any OpenAI-compatible/v1/chat/completionsendpoint via theopenaiSDK withbase_url=. Targets vLLM, LiteLLM proxy, OpenRouter, Together, Groq, Azure OpenAI, local llama.cpp. Optional dep:pip install 'anvil[custom]'.planning/llm.MODEL_TIERS,BEDROCK_MODEL_TIERS,DEFAULT_TIER,resolve_model_for_tier()— tier vocabulary (opus/sonnet/haiku) and the helper that maps a logical tier to the right model id for each provider's namespace.Configfields:llm_provider(anthropic/bedrock/custom),llm_tier(opus/sonnet/haiku),bedrock_region,bedrock_profile,custom_base_url,custom_api_key_env. All optional; env auto-detect kicks in when blank.docs/llm-providers.md— provider setup guide with worked examples for direct API, Bedrock (env / profile / IAM), and three custom-endpoint shapes (vLLM, OpenRouter, LiteLLM proxy).docs/model-strategy.md— tier rationale, per-agent assignments, override precedence, May 2026 cost figures, and the rationale for not shipping a dynamic complexity router by default.[bedrock]/[custom]/[all-providers]optional extras inpyproject.tomlso the default install stays lean (no boto3, no openai) for users on the Anthropic-API-only path.- Test coverage:
TestResolvePlannerProvider(8 new tests covering env auto-detect precedence, config overrides, tier threading);TestResolveModelForTier(3 tests for direct-API + Bedrock tier tables + error on unknown tier);TestCustomEndpointProvider(2 tests for required-model and required-base_url validation).
Changed
planning/llm_planner.resolve_planner_provider()— gained an optionalconfig: Config | Noneparameter. New precedence: explicitconfig.llm_provider> env auto-detect (ANTHROPIC_API_KEY>AWS_REGION+bedrock-extras >CUSTOM_LLM_BASE_URL) > fail loudly. Single provider per process; no silent fallback across providers.generate_tasks_markdown()— gained an optionalconfig:parameter, threaded through to the resolver so projects' explicit provider+tier+credential knobs apply.cli/plan._resolve_llm_provider()— delegates toresolve_planner_provider(config)so--use-llmaugmentation honors the same multi-provider precedence as the no-tasks LLM backstop. Single source of truth for provider selection across the CLI.cli/plan._load_config_optional()— new helper that soft-loads.anvil/config.yamland emits a stderr warning naming the exception class on load failure. Mirrorscli/claim.py's existing pattern.AnthropicProvider— gainedtier=kwarg (Opus / Sonnet / Haiku), resolves viaMODEL_TIERS. Existingmodel=arg still wins when both are passed; backward compatible for every existing caller.- Default model tier across the codebase shifts from "Sonnet (hardcoded constant)" to
DEFAULT_TIER = "sonnet"(community consensus per anthropics/claude-code#27665). Functional behaviour unchanged on the default path; the change is documentation + config plumbing. - Agent frontmatter —
model:set explicitly across all 6 anvil agents (wasopusuniformly; nowopusfor reasoning/synthesis,sonnetfor structured generation,haikufor mechanical/read-only):planner→ opus |critic→ opus |docs-scribe→ sonnet |marketplace-scribe→ sonnet |sentinel→ haiku |state-keeper→ haiku
config.yamltemplate — gains commentedllm_*,bedrock_*,custom_*blocks with tier-mapping reference so new projects see the multi-provider shape at init time.
Fixed
cli/plan._resolve_llm_provider()previously hardcoded anANTHROPIC_API_KEYenv check, diverging from the resolver's own logic and forcing users on Bedrock or custom endpoints to skip--use-llmentirely even when their non-Anthropic provider was correctly configured. Now both paths share the resolver.
Fixed
- greptile MUST FIX #1.
_choose_provider_familywas usinghasattr(anthropic, "AnthropicBedrock")to detect whether the Bedrock extras were installed. TheAnthropicBedrockclass ships with the baseanthropicinstall — onlyboto3(the transitive dep added by the[bedrock]extra) actually gates it. Switched totry: import boto3so AWS_REGION-set boxes without the extras correctly fall through to "no provider available" instead of picking Bedrock and crashing at construction. - greptile MUST FIX #2 + critic MUST FIX #1. When the operator pinned
llm_provider: bedrock(orcustom) in config without installing the extras, the underlyingLLMProviderErrorpropagated past the resolver'sPlannerProviderUnavailablecontract — users saw a raw traceback where curated help text was promised. The resolver now wraps every per-family_build_*call'sLLMProviderErrorintoPlannerProviderUnavailablewith an install-command suggestion. - critic MUST FIX #2.
_build_customsilently defaulted toclaude-sonnet-4-6when the operator hadllm_provider: custombut nollm_model/llm_tier. On a local vLLM serving Mistral-7B (or any non-Anthropic OpenRouter route) this produced a confusing "model not found" failure that looked like a network issue. The resolver now refuses to invent a model and raisesPlannerProviderUnavailablewith an actionable message naming the config keys. - structure-critic MUST FIX.
bin/src/anvil/__init__.py__version__was stale at1.16.0— every other source of truth had bumped to1.17.0. Now in sync, plus a newtests/test_version_sync.pyregression that assertspyproject.toml,__init__.py, andplugin.jsonagree at the start of every test run.
Changed (post-review polish)
mcp_server.pyplan_taskssoft-load — narrowed the broadexcept Exceptionto(FileNotFoundError, OSError, ValueError)first, then a labeled last-resort guard foryaml.YAMLErrorand friends. Mirrorscli/plan.py:_load_config_optional's pattern (mcp-critic SHOULD FIX).cli/plan.py:_load_config_optional— caughtyaml.YAMLErrorexplicitly; dropped the misleading "yaml.YAMLError is a subclass of yaml.YAMLError" comment (critic SHOULD FIX #3).cli/plan.py— removed unusedosandreimports left over from the refactor (critic SHOULD FIX #6).mcp_server.PlanTasksResponse.llm_providerfield comment andplan_tasksdocstring — updated to document the v1.17.0 multi-provider story (anthropic/bedrock/customrather than the stale "anthropic today, claude-agent-sdk reserved for v1.16+"); added a note that the MCP server inherits env from the host process (mcp-critic SHOULD FIX).bin/pyproject.tomlkeywords— synced withplugin.jsonkeywords (was drifted: 10 keys differ pre-PR). Cosmetic alignment for marketplace search (structure-critic SHOULD FIX).fakoli-plugin-criticagent files — sed sweep of "fakoli-crew critic severity rubric" → "fakoli-plugin-critic severity rubric" and similar namespace-stale prose left over from the extraction. Agent system prompts now read as part offakoli-plugin-critic, notfakoli-crew(structure-critic SHOULD FIX #4).fakoli-plugin-critic/README.md— added standard shields.io badges (license, version, marketplace) to match sibling plugins; install snippet shows themarketplace addprerequisite (structure-critic SHOULD FIX #5, #10).fakoli-plugin-critic/CHANGELOG.md— added an_No unreleased changes._placeholder under[Unreleased](structure-critic CONSIDER #7).fakoli-plugin-critic/docs/— removed (was an empty directory).
Tests
1103 passing (was 1083 baseline). Diff: +20 net (8 new TestResolvePlannerProvider + 3 TestResolveModelForTier + 2 TestCustomEndpointProvider + 4 TestResolvePlannerProviderGreptileFixes (greptile + critic regression) + 4 TestBedrockProvider (closes the BedrockProvider test gap critic SHOULD FIX #8 flagged) + 2 test_version_sync.py (structure-critic regression); 4 existing tests updated for the new resolver signature and the MUST FIX #2 contract change.
[1.16.0] — 2026-05-26
Single-bug release driven by in-the-wild testing: the planner missed an
obvious task→task dependency. T002 (chaos tests in 2-process mode)
clearly depended on T001 (HttpTransport implementation) — without T001
the 2-process mode the tests need doesn't exist — but the generated
task graph showed dependencies=[] for T002, and the user only caught
it by reading the PRD acceptance criteria during claim.
Root cause was a three-layer gap:
- Parser (
planning/template.py) didn't recognise a**Dependencies:** T001, T002field in task blocks. Even if the PRD author wrote it explicitly, the parser silently dropped it. - LLM planner prompt (
planning/llm_planner.py) didn't instruct the model to identify dependencies from acceptance criteria text. The model had no example to emit and no rule telling it to look. - Only existing dep inference (
planning/inference.pyinfer_dependencies) was a file-subset heuristic — purely file-based. It would NEVER catch "T002 needs T001 because the criteria say 'in 2-process mode'" if the tests lived intests/chaos/while the implementation lived inpackages/transport/(no file overlap).
v1.16.0 closes all three.
Added
- Parser support for
**Dependencies:**field in task blocks (planning/template.py). Comma-separated TaskIDs, normalised to upper-case (t001, T002→["T001", "T002"]). Post-parse validation surfaces aParseErrorwarning when a dependency references a task ID that doesn't exist in the same## Taskssection — the dep is kept on the task regardless so downstream tooling can see the author's intent. - LLM planner prompt instructions for dependency emission
(
planning/llm_planner.py). The system prompt now includes**Dependencies:**in the example task block AND a "Dependencies (CRITICAL — read carefully)" rule block explaining the two trigger conditions:- Infrastructure dependency — Task A creates infrastructure (API, service, transport, schema, CLI command) that Task B needs.
- Phrasal dependency in acceptance criteria — "in X mode",
"using Y", "after Z is complete", "given W from ".
Plus an explicit cycle-avoidance rule and an instruction to OMIT
the field entirely when no deps exist (no empty
**Dependencies:**lines).
anvil claimwarns on undone dependencies (soft gate). Before acquiring the lease, claim fetchestask.dependenciesand checks each one's status. If any are not yetdone, emits a stderr warning naming each dep + status, then proceeds with the claim.--forcesilences the warning. The soft-gate design preserves legitimate stacked-PR workflows (claim T002 while T001 is still in_progress and merge them together) while ensuring the user knows what they're doing.- 10 new regression tests:
- 4 in
tests/test_template.py::TestTaskParsing: explicit deps field parses, multi-value normalises uppercase, unknown ID warns, omitted field defaults to empty. - 4 in
tests/test_llm_planner.py::TestSystemPromptInstructsDependencyEmission: prompt shows the field, prompt names the two triggers, prompt says omit-when-empty, prompt warns against cycles. - 2 in
tests/test_cli.py::TestClaimCommand: claim warns on undone deps;--forcesilences the warning.
- 4 in
Changed
- README badges updated for v1.16.0: tests 1071 → 1081 (+10); version 1.15.0 → 1.16.0.
docs/prd-template.mdtask-field reference table gains a**Dependencies:**row, the canonical-example T003 demonstrates using it (**Dependencies:** T001, T002), and a paragraph explains when to emit the field vs leave it to file-overlap conflict groups.
Migration
No breaking changes. Schema unchanged. The Task.dependencies field
already existed in the Pydantic model since pre-v1.0 — v1.16.0 just
wires it through the parser, the planner prompt, and the claim
gate. PRDs without **Dependencies:** fields continue to work
unchanged (the field is optional, defaults to empty list). The
existing file-subset infer_dependencies() still runs after parse
— v1.16.0 layers explicit semantic deps on top of, not in place of,
the file-based inference.
The claim warning is soft (proceeds with the claim) so existing CI /
scripts that call claim won't suddenly start failing. Users who
prefer stricter behaviour can wrap the warning in their own
project's git-pre-push hook or check anvil show TASK_ID
before claiming.
Fixed (post-greptile review)
- Dependency ParseError now points at the offending
### Txxx:block, not at the## Taskssection header. The parser tracks atask_id → block_linemap during the parse loop and consults it during post-loop validation. Before the fix, a user with a bad**Dependencies:** T099on T002 would be pointed at line 1 of the section instead of T002's heading. - Self-dependency now stripped + warned instead of passing
silently. A task with
**Dependencies:** T001on T001 would otherwise trigger a perpetual claim-time warning (T001 can never bedonebefore it is claimed). The parser strips the self-ref AND emits a clear "remove yourself from your own dependencies" warning naming the offending task. Note this differs from the unknown-ID handling, which KEEPS the bad ID so downstream tooling can see the author's intent — self-refs are unambiguously wrong. --forcehelp text updated to mention that the flag silences both file-conflict warnings AND dependency warnings. Previously only the file-conflict half was documented, so users wouldn't know--forcecleared the dep-warning noise too.- Suite is 1083 passing (+2 regression tests for self-dep stripping and per-block line attribution).
[1.15.0] — 2026-05-26
Five-bug release driven by in-the-wild testing on a real project. Every bug shares the same anti-pattern at progressively deeper layers: the agent silently failed or asked the user to make a routing/typing decision the agent had the context to make. v1.15.0 closes all five.
What changed in this release:
anvil planGUARANTEES task generation — calls the LLM automatically when the PRD has features + requirements but no## Taskssection. The user no longer has to remember to dispatch theanvil:plannersubagent as a workaround.anvil planPRUNES ORPHAN tasks/features on re-parse — the docs always claimed "Re-parse replaces, not merges" but the implementation only upserted by ID. Newtask.deleted/feature.deletedevent types with safety guards land the re-parse contract.anvil expand --use-llmtolerates fenced JSON + prose — previously every call failed withExpecting value: line 1 column 1 (char 0)because the parser couldn't handle the markdown fences Claude routinely wraps JSON in.branch_prefixis host-project-configurable — host projects withfeature//fix/conventions no longer get silently- incompatibleagent/branches.executeskill auto-dispatches to fakoli-crew — instead of asking the user "how would you like to proceed?" the skill encodes a routing heuristic that picks the right specialist from the task's signals (likely_files, verb, criteria).
Suite is 1071 passing (was 1024 in v1.14.0; +47 new tests across
the LLM planner module, orphan-cleanup handlers, fenced-JSON parser,
branch_prefix validation, and CLI/MCP integration). Schema
unchanged. No breaking changes for existing callers, with one
behaviour-change callout in Migration below.
Added
- New
planning/llm_planner.pymodule withgenerate_tasks_markdown()andresolve_planner_provider(). Pure module; emits## Tasksmarkdown that the existingplanning.template.parse_prdconsumes via round-trip. Tier-chain provider resolver (see "Provider tier-chain design" below). 15 unit tests covering tier resolution, prompt assembly, output validation, and end-to-end with a recorded LLM provider. anvil planLLM backstop (CLI). When the deterministic parse yields 0 tasks but ≥1 features, the CLI callsgenerate_tasks_markdown(), idempotently appends the## Tasksblock toprd.md(re-runs are no-ops once the section exists), re-reads + re-parses, emitstask.createdevents. The output line explicitly tells the user the file was modified.- New
--no-llmflag onplanCLI. Opts out of auto-generation (e.g. on CI without API keys). When passed and 0 tasks are parsed, CLI exits 1 with a clear "author them manually" message — never silently returns 0. - MCP
plan_tasksmirrors the CLI. Newuse_llm: bool = Trueandprune_force: bool = Falseparameters. Response model gainsllm_generated,llm_provider,pruned_task_ids,pruned_feature_idsfields (all with defaults so old clients see no breaking change).PlannerProviderUnavailableandTaskGenerationErrorraiseToolErrorwith safe summaries — no silent 0-task responses, no LLM-output leakage in error messages. - New
task.deletedandfeature.deletedevent types with handlers instate/sqlite.py. The schema'stasks.parent_task_id ON DELETE SET NULLandsync_mappings.task_id ON DELETE CASCADEwere designed for deletion from the start (seestate/schema.py:40comment); v1.15.0 finally wires the events. Safety:task.deletedrefuses non-safe statuses (claimed, in_progress, needs_review, etc.) unlessforce=True, AND refuses unconditionally when claims or evidence rows still reference the task (FK-protected audit history that not even--prune-forceoverrides).feature.deletedrefuses if tasks still reference the feature (FK RESTRICT pre-check). anvil planauto-prunes orphans on re-parse. Computes the diff betweenstate.dband the new parse, emits the deletion events. Safe-status orphans prune silently; unsafe-status orphans cause exit 1 with a blocked-IDs list and the--prune-forceescape hatch. Output line surfaces what was pruned.Backend.list_features()Protocol method + SQLite impl — orphan detection needs the full feature set for the diff.- New
branch_prefixfield in.anvil/config.yaml(default"agent", preserving pre-v1.15.0 behaviour). The CLI'sclaimcommand reads this and creates branches as<branch_prefix>/<task-id>-<slug>— setbranch_prefix: featureandclaimproducesfeature/t012-...instead of the silently-incompatibleagent/t012-.... Nested prefixes (feature/agent) preserved verbatim. Empty string ("") is explicit no-prefix mode. Validation at config-load time: leading/trailing slashes, whitespace, non-string →ValueErrorwith a clear message. create_branch_for_taskbranch_prefix=keyword arg (default"agent"for backwards compat). Pre-v1.15.0 callers see no change.config.yamltemplate (write_default_config) now emits thebranch_prefix:line with inline guidance so fresh projects see the choice at init time.- Shared helpers module
planning/_plan_helpers.pyconsolidates the_has_tasks_sectionregex,SAFE_DELETE_STATUSESconstant,classify_orphans(), andemit_prune_events(). Both CLI and MCP import from here instead of carrying twin copies (post-review consolidation — see Fixed below). - 47 new regression tests across:
tests/test_llm_planner.py(NEW, 15 tests): tier resolution, prompt assembly, fence-stripping, round-trip parse contracttests/test_llm_integration.py::TestExpandTaskHandlesLlmQuirks(5 tests): fenced JSON, prose preamble, empty response, garbagetests/test_cli.py(7 tests):TestPlanLlmBackstop+TestPlanOrphanPrune(CLI integration)tests/test_mcp.py(6 tests): MCP integration mirrorstests/test_config.py+tests/test_git_ops.py(13 tests):branch_prefixvalidation and end-to-end- +1 sanity test added during post-review CHANGELOG consolidation
Changed
planskill Step 1 rewritten. The pre-v1.15.0 "if 0 tasks, dispatch the planner subagent" workaround is gone — the CLI now guarantees tasks, so the skill just runsplanand surfaces the result. Agent MUST surface(N generated via LLM ...)output to the user so they knowprd.mdwas modified.planskill new Step 1.5 — structured Q&A for post-plan decisions. Scope overruns, structural concerns, expansion candidates each become a one-turnAskUserQuestion(in Claude Code) or numbered prompt elsewhere. One decision per turn — do NOT batch. Bolded as the leading sentence per skill-critic review.executeskill Step 3 rewritten — auto-routes to fakoli-crew specialist. First-match-wins routing table maps task signals to crew members (smith/guido/welder/scout/herald/keeper/sentinel/ flow-execute). Tie-break rule disambiguates when two rows match (per skill-critic review). Step split into Step 3 (routing) + Step 3a (implementation discipline). Anti-pattern callout names the "How would you like to proceed?" failure mode.finishskill new "Decision-presentation discipline" subsection. Generalizes the v1.13.0 disposition-gate pattern (accept / reject / hold / discard) from a one-off to a rule: any 2+ option decision usesAskUserQuestionor explicit numbered prompts, never prose-with-bullets.claimskill anti-pattern subsection extended with the same Q&A discipline rule for claim-time decisions.prdskill## Iteratingsection clarified — the destructive-re-parse contract now reads as two distinct bullets (prd parsereplaces requirements;planprunes orphan features/tasks) so readers cannot conflate which command does which prune (per skill-critic review).- README badges + "What ships today" + Architecture table updated for v1.15.0: version 1.14.0 → 1.15.0; test count 1024 → 1071; CLI commands 23 (was previously claimed as 24, but disk shows 23 — corrected per structure-critic review); MCP tools 22 (the pre-v1.13.0 "13 tools" prose was stale in three places).
- README "Highlights from v1.10.0" section replaced with v1.15.0 highlights — the previous block was 5 releases stale.
Fixed
expand --use-llmwas failing for every task becauseplanning.inference._parse_subtask_responsecalledjson.loadswith no tolerance for markdown fences. Modern Claude models routinely wrap JSON in```json … ```despite the prompt. The parser saw the leading backtick instead of[and emittedExpecting value: line 1 column 1 (char 0). Three-layer fix: strip fences (```json,```jsonl, plain```) → fall back to string-aware bracket-matching extractor forHere are 3 sub-tasks: [...]shapes → warning now includes a 300-char sample of the response so debugging doesn't require extra verbosity. System prompt strengthened in parallel.- CLI
planorphan-deletion loops now catchTransactionAborted(post-greptile fix). Previously the MCP path caught and re-raised asToolError, but the CLI emitted the raw Python traceback — most accessible trigger was "user removes a feature heading from prd.md while keeping its referencing tasks." Now CLI surfaces the handler's clear message viatyper.echo+Exit(1). - CLI/MCP duplication consolidated (post-critic fix). The
_has_tasks_sectionhelper,SAFE_DELETE_STATUSESconstant (previously triplicated acrosscli/plan.py,mcp_server.py, andstate/sqlite.py), and the orphan-prune emit loops (~90 duplicated lines) now live inplanning/_plan_helpers.pyand both layers import from there. Future changes to safe-statuses no longer require three synchronized edits. cli/claim.pyno longer silently swallows config-load errors (post-critic fix — "exactly the bug class this PR is supposed to fix"). A YAML typo inbranch_prefix: featurenow emits a stderr warning naming the failure before falling back to the default, instead of silently producing anagent/...branch the user thought they had configured away.planning/llm_planner.pyprompt-injection defense (post-critic fix). User PRD text (summary, goals, requirements, features) now wraps in a<prd>...</prd>XML fence and the system prompt instructs the model to treat anything inside as data, not instructions. PRDs are author-controlled so practical risk is low; this is defense-in-depth.mcp_server.pyTaskGenerationErrorsafe summary (post-mcp-critic fix). The exception's message can include up to 500 chars of raw LLM output; re-raising viaToolErrorleaked that to MCP clients. Full exception logged to stderr; client sees a safe, actionable summary.state/sqlite.py_handle_task_deletedconflict_groups cleanup (post-critic fix). Malformedtask_idsJSON in a conflict_group row was silentlycontinue'd, leaving the deleted task ID reachable from subsequent queries. Now logs the corruption to stderr AND resets the malformed row to"[]"so state.db ends consistent. Tuple-unpack of cursor rows replaced with explicitrow["id"]/row["task_ids"]access via thesqlite3.Rowrow factory.
Migration
No breaking changes for existing callers. Schema unchanged. All
23 CLI commands and 22 MCP tools continue to work; the new fields
on PlanTasksResponse have defaults so old clients see no surprises.
New CLI flags (--no-llm, --prune-force) and MCP parameters
(use_llm, prune_force) are opt-in.
One behaviour callout for MCP and CLI callers (post-mcp-critic
review). Pre-1.15.0 callers of plan_tasks / anvil plan
on a PRD with features + requirements but no ## Tasks section
previously got task_count=0 and unchanged prd.md. As of v1.15.0
the default behaviour is to call the LLM and rewrite prd.md —
the file gets a fresh ## Tasks section appended. Pass
use_llm=False (MCP) or --no-llm (CLI) to preserve the
pre-1.15.0 "task_count=0, file untouched" behaviour. The CLI
output line and the new MCP response fields explicitly surface
when the file was modified.
The anvil:planner agent file (agents/planner.md) is
unchanged. It remains useful as a reference and for explicit-
dispatch use cases that need the subagent's structured-output
discipline (PRD critique, expansion proposals, incremental
planning across PRD revisions).
Provider tier-chain design
resolve_planner_provider() walks an ordered chain:
- Tier 1 — claude-agent-sdk (RESERVED for v1.16+). Currently
falls through silently. Hook is in place so a future PR can land
the wrapper without touching callers. Deferred because the SDK
is async, requires Node.js + Claude Code CLI installed system-
wide, and Claude Code's environment already exposes
ANTHROPIC_API_KEY— so Tier 2 covers the same use case at zero extra setup cost today. - Tier 2 — anthropic SDK with
ANTHROPIC_API_KEY(CURRENT). Direct Anthropic API call via the existingAnthropicProvider. Used in both standalone and Claude Code contexts. - Tier 3 — fail loudly with a multi-line message naming both the env var path and the future SDK path. Never returns 0 tasks silently.
[1.14.0] — 2026-05-26
Generalizes v1.13.0's "drive interactively" principle one layer deeper:
when the PRD has [NEEDS DECISION] markers, unresolved ## Open Questions,
or task-level missing fields (empty acceptance criteria, missing
verification), the agent now drives each one as a one-question
conversational turn with proposed options — instead of telling the
user "open the editor and fix these first." An LLM agent's strength
over a CLI is exactly this: turning blocked on a decision into
let me ask you the right question.
Added
- New
planning/decisions.pymodule withfind_unresolved_decisionsfunction. Scans both raw markdown (for inline[NEEDS DECISION]markers — case-sensitive bracket-enclosed; HTML comments stripped to avoid false positives on draft notes) and parsed PRD/Tasks (for## Open Questionsitems and missing acceptance criteria / verification commands). Returns a flat ordered list ofUnresolvedDecisionrecords. Pure module — no I/O, no backend. 18 unit tests intests/test_decisions.py. - New CLI subcommand
anvil prd find-decisionsthat prints a structured per-kind summary with id, location, text, surrounding context paragraph, and suggested resolution field. Exits 0 regardless of finding count (it's a read-only inspection command). 4 new tests intests/test_cli.py. - New MCP tool
find_decisions(cwd)mirroring the CLI with a typedFindDecisionsResponsemodel. Total MCP surface 21 → 22. 6 new tests intests/test_mcp.py. - New
resolve-decisionsskill (skills/resolve-decisions/SKILL.md). Drives each unresolved item as one Q&A turn with proposed options when the surrounding context allows. Applies answers toprd.mdinline:[NEEDS DECISION]markers get rewritten in place; resolved Open Questions get moved to a new## Decisionssection that preserves the audit trail (what was unclear at draft time + what was decided + when); missing-field decisions edit the relevant### TXXX:block to add the chosen acceptance criteria or verification commands. Re-parses on completion. Total skills 7 → 8. - Soft gates wired into
prdskill (Step 2 — after parse) andplanskill (new Step 0 — before plan_tasks). Whenfind_decisionsreturns non-empty, the agent surfaces the summary and asks "resolve now via the resolve-decisions skill, or proceed without resolving?" The gate is soft by design: Open Questions are informational and don't block review/approval; the agent surfaces the cost of proceeding and lets the user pick the cadence.
Changed
- README badges and "What ships today" table updated for v1.14.0: version 1.13.0 → 1.14.0; tests 994 → 1022 (+28 new across decisions/CLI/MCP); CLI commands 23 → 24; MCP tools 21 → 22; skills 7 → 8.
Migration
No breaking changes. Schema unchanged. The 21 existing MCP tools and
23 existing CLI commands are unchanged. The skill rewrites only add
soft-gate prose; they do not change the user-visible behavior of
prd review or plan for clean PRDs (find_decisions returns empty,
the gate skips). Existing PRDs that have always-clean (no markers,
no Open Questions, no missing fields) see no change at all.
Detection scope details
[NEEDS DECISION]marker: case-sensitive, bracket-enclosed, optional: <question>payload (e.g.[NEEDS DECISION: which encoding?]). Markers inside HTML comments (<!-- [NEEDS DECISION: ...] -->) are intentionally ignored so drafts can carry TODO-style notes without triggering the resolver. Fuzzy prose like "needs decision on the auth flow" inside a paragraph does NOT trigger detection — the marker is the explicit contract.## Open Questions: explicit "none identified" / "none" / "n/a" / "tbd" bullets are recognized as placeholders and skipped. This preserves the v1.10.0 convention of declaring "no open questions" with an explicit bullet instead of an empty section.- Missing fields: only
task.acceptance_criteriaandtask.verification.commandsare checked. Empty requirements text and empty feature descriptions are reserved for v1.15+ (the detection module acceptsrequirements=andfeatures=parameters now to avoid a signature break later).
Fixed (post-greptile review)
- OQ IDs are now contiguous after placeholder skipping. Previously
the counter advanced for every Open Questions item including
"none identified"/"n/a"/"tbd"placeholders, so a PRD with[placeholder, real, placeholder, real]producedOQ002+OQ004instead ofOQ001+OQ002. Non-contiguous IDs would confuse the resolver skill (which iterates decisions sequentially). The contiguous-ID counter only advances for items that survive the placeholder filter; thelocationfield still carries the source position so users can find the item in the file. - MCP
find_decisionsnow matches the CLI on parse failures. Previously the MCP tool silently proceeded whenparse_prdreturned errors, yielding a deceptive0 open_questionscount even though the PRD was malformed. Now it raisesToolErrorwith the first few errors summarised in the message, matching the CLI's exit-1 behaviour so MCP clients see the parse failure before drawing conclusions from the decision list. - Test suite is 1024 passing (was 1022 — +1 regression test for the OQ contiguous-ID fix, +1 for the MCP parse-failure fix).
[1.13.0] — 2026-05-26
Two-axis release driven by a single user observation: agents using
anvil were ending every workflow with a CLI to-do list ("1. Run
prd parse, 2. Run prd review, 3. Run prd review --approve,
4. Run plan, …") instead of driving the workflow inline. The
handoff pattern only makes sense when work is leaving the session
entirely; with one agent and one user in the same conversation, the
agent should drive each command, surface its output, and present the
next decision. The fix is at the skill layer (so every future session
inherits it) AND at the MCP layer (so non-shell MCP clients can drive
the full workflow without dropping to a Bash tool they may not have).
Changed
- Skills now drive the workflow interactively.
start-prd,prd,plan,claim, andfinishrewrote their interactive sections to encode "agent runs each state-engine command itself; surfaces the output inline; asks the user the next decision; runs the next command on the user's word." The closing-with-a-CLI-to-do-list pattern is explicitly named as an anti-pattern in each of these skills. The two approval gates (prd review --approveandapply --approve) are the only hard handoffs, and even at those the agent asks "ready? yes/no" and runs the command on confirmation — it does not paste the command for the user to type. An explicit escape hatch is preserved: when the user says "just give me the commands," or when the runtime has no execution tool, the CLI list output is correct. - README test-count badge 975 → 993; version badges 1.12.1 → 1.13.0.
Added
- 8 new MCP tools so the full PRD → plan → review → approve →
claim → submit → apply workflow is drivable from any MCP client
without a Bash tool:
init_project,get_project_status,parse_prd,review_prd,plan_tasks,score_tasks,review_tasks,apply_review_decision. Total MCP surface is now 21 tools (was 13). All new tools call the same shared modules the CLI calls — no logic duplication. Structured response models return well-shaped data; operational failures (missing file, uninitialized project) raiseToolErrorwith a clear message, parse-level errors are returned as data so callers can inspect- and-retry without exception handling. - 19 new MCP regression tests in
tests/test_mcp.pycovering happy and error paths for each new tool, including a regression test for theplan_tasksordering guard (must runparse_prdfirst or fail loudly). Suite is 994 passing (was 975 in v1.12.1).
Fixed (post-greptile review)
plan_tasksno longer mutates state when called out of order. Previously, callingplan_tasksbeforeparse_prdwould emitfeature.createdandtask.createdevents into a backend with no PRD row — leavingreview_prdandapply_review_decisionto fail later with "No PRD found in state" after the state was already partially mutated. The tool now verifiesbackend.get_prd()returns a row before emitting any events and raisesToolErrorwith a clear "call parse_prd first" message if not.init_projectresource cleanup hardened.backend.initialize()was being called outside thetry/finallyblock, so a failure during schema bootstrap would leak the backend connection. Moved inside the try sobackend.close()always runs.score_tasksdocstring clarified to match the CLI's intentional behavior: an explicittask_idalways re-scores (whether or not scores are complete); omittingtask_idonly scores tasks whose Score is incomplete. The asymmetry is deliberate and matchesanvil score [TASK_ID].- New "Workflow tools (v1.13.0)" section in
docs/mcp.md, organized by lifecycle phase (Bootstrap → PRD → Planning → Review) with parameters, returns, examples, and CLI equivalents for each new tool.
Migration
No breaking changes. The 13 existing MCP tools, all 23 CLI commands,
and the on-disk state schema are unchanged. The skill rewrites
change the agent's interactive behavior only — any CLI script,
hook, or pre-existing workflow that invokes anvil commands
directly continues to work identically. Plugins that wrap anvil
(fakoli-flow, fakoli-crew) are unaffected.
Notes on init_project and helper extraction
The MCP init_project tool inlines a small amount of project-seeding
logic that today lives privately inside cli/init_status.py
(_apply_init_event). A future cleanup could lift that helper into
state/init.py so MCP and CLI share a single source. Flagged in the
codebase; not blocking for v1.13.0. Similarly _PRD_FILENAME is
duplicated between cli/_helpers.py and mcp_server.py to avoid
pulling typer into the MCP import graph — worth promoting to a
shared constants module someday.
[1.12.1] — 2026-05-26
Bug-fix release for a silent-drop in the PRD parser. Reported by a user
running anvil in another project: an agent authored a PRD with
## Features written as bullets (instead of ### F001: H3 blocks) and
anvil prd parse reported "0 features, 0 tasks" before exiting
0. The agent's work was invisibly discarded.
Fixed
_parse_featuresand_parse_tasksinbin/src/anvil/planning/template.pynow emit aParseErrorwhen their section body has non-empty / non-comment content but produces zero### Fxxx:/### Txxx:H3 blocks. The error message names the canonical format and points todocs/prd-template.md. This matches the parser's own documented contract ("Silent fallback is explicitly rejected") — previously both functions returned[]silently when the format was wrong, in violation of that contract. CLI behavior unchanged: anyParseErrorcausesprd parseto exit non-zero before writing tostate.db, so no malformed data ever reaches state._parse_featuresand_parse_tasksnow also warn when an H3 heading looks like an attempted custom ID (e.g.### F-DURABILITY: foo,### T-1: foo) but does not match the requiredFxxx/Txxxformat (letter + 3+ digits). Previously the parser silently auto- assigned a default ID and the user had no way to know their custom ID had been discarded. Conservative detection (only F/T + a separator like-,_,.) so legitimate auto-ID fallbacks for English headings like### My Featurekeep working unchanged.- An empty
## Featuresor## Taskssection header (no body at all) still parses cleanly with no error, preserving the existing escape hatch for "section declared but no items yet".
Added
- Eight regression tests in
tests/test_template.py: fourfeaturesvariants (bullets_only,prose_only,empty_section,malformed_id_prefix) and four paralleltasksvariants. Suite now 975 passing (was 967).
Changed
- README test-count badge 967 → 975; version badges 1.12.0 → 1.12.1.
- The malformed-ID warning messages in
_parse_featuresand_parse_taskswere reworded to be unambiguous: the previous "an ID was auto-assigned" phrasing described an internal fallback the user never sees (because the ParseError blocks persistence), which read as if the parse had succeeded. The new wording names the format violation and tells the user to rename the heading to### F001:/### T001:and re-run.
[1.12.0] — 2026-05-26
Skill rename to resolve the brainstorm namespace collision with
fakoli-flow:brainstorm. The anvil skill that drafts a PRD from
a rough idea is now start-prd — slug, slash command, and every
cross-reference updated. The skill router can now route "let's
brainstorm" cleanly to fakoli-flow and "start a PRD" cleanly to
anvil. Markdown-choreography only; no Python source, schema, or
test changes (967 tests still passing).
Changed
- BREAKING:
/anvil:brainstormskill renamed to/anvil:start-prd. Directory (skills/brainstorm/→skills/start-prd/), frontmattername:, slash-command form, and every cross-reference in README,architecture.md,skills-reference.md,how-to/integrating-with-fakoli-flow-and-crew.md,skills/prd/SKILL.md,roadmap.md,phase-11-backlog.md, and the v0 spec updated. Skill description rewritten to remove "brainstorm" as a trigger word and add concrete trigger phrases ("start a PRD", "draft requirements", "author a PRD", "spec out a project"). Bridge to/fakoli-flow:brainstorm(the OTHER plugin's skill, unrelated to this rename) is unchanged and still fires when fakoli-flow is installed. - Marketing copy updated to drop "brainstorms" in favor of "rough
ideas" — README hero,
_positioning.mdcanonical (Q) sentence,bin/pyproject.tomldescription, CLI--helpdescription, and the v0 spec. The vocabulary now matches the renamed skill instead of pulling the skill router back toward the colliding term. - README badges refreshed: version 1.10.0 → 1.12.0; test count 965 → 967 (catches up the v1.11.0 additions that did not update the badges).
Migration
If your scripts, hooks, CI, agents, or muscle memory invoke
/anvil:brainstorm, change to /anvil:start-prd. No
state schema migration is required (still v3, unchanged since v1.8.0);
no CLI command was renamed (no anvil brainstorm CLI command
ever shipped — only the skill); the rename is purely at the skill-
router layer.
Notes on historical artifacts
The docs/tech-debt-backlog.md entry P9-5 and the
docs/specs/2026-05-26-plugin-audit-and-critics.md audit doc still
reference skills/brainstorm/SKILL.md — these are intentionally
unchanged. P9-5 documents a Phase 9 fix to the file at the path it had
AT THAT TIME; the audit doc is a frozen v1.9.0 snapshot. Rewriting
either would obscure audit traceability. Status files under
docs/plans/agent-*-status.md are similarly frozen.
Versioning note
By strict SemVer this is a breaking change and would warrant 2.0.0.
The project reserves 2.0.0 for the inflection-point release planned
on docs/roadmap.md (sync v2, replay tooling, multi-backend sync
abstraction). This rename is shipped as 1.12.0 with an explicit
BREAKING: marker to keep the v2.0 slot intact.
[1.11.0] — 2026-05-25
Comprehensive documentation overhaul + 3 silent-failure bug fixes surfaced
during research (missing fakoli-flow detection in claim/execute skills;
submit CLI hardcoding screenshots=[]; broken grep -q "^fakoli-..."
anchor pattern at 5 sites). No breaking changes; the submit --screenshots
flag is the only behavior change and is purely additive (optional).
Added (docs)
- Positioning + foundation:
docs/_positioning.md(internal reference), rewrittenREADME.mdwith hero block / 5-wedge comparison / working v1.10.0 Quick Start,docs/architecture.md(579 lines, 3 mermaid diagrams, 25-entity data model, replay guarantee),docs/design.md(301 lines, design rationale- Deferred decisions). [PR #54]
- User-facing how-to (5 files):
docs/how-to/getting-started.md,authoring-a-prd.md,claiming-and-shipping-a-task.md,syncing-with-github.md,integrating-with-fakoli-flow-and-crew.md. [PR #55] - Reference (5 files):
docs/cli-reference.md(all 23 commands),skills-reference.md(7 skills + dependency graph),agents-reference.md(6 agents + defer-to-crew mapping),hooks-reference.md(4 hooks + non-blocking contract),faq.md(15 Q&A). [PR #55] - Visual brand:
assets/logo-{64,256,1024}.pnggenerated via nano-banana-pro;assets/diagrams/{component,lifecycle,trinity}.mmdfor re-render. [PR #54] - CLI flag:
submit --screenshots PATH1,PATH2for tasks whoseverification.required_evidenceincludes "screenshots". 2 new tests. [PR #55]
Fixed (silent-failure bugs)
skills/claim/SKILL.md+skills/execute/SKILL.mdhad no fakoli-flow detection snippet — both used prose-only "when fakoli-flow is installed" framing with no shell check, so the bridge to/fakoli-flow:executenever fired even when the plugin was installed. Added Step 0 detection blocks mirroringskills/brainstorm/SKILL.md. [PR #55]submitCLI hardcodedscreenshots=[]— Evidence model field, evidence_complete gate, and gate tests all existed; only the CLI surface was missing. Tasks requiring "screenshots" evidence were unsatisfiable. SeeAddedabove. [PR #55]grep -q "^fakoli-..."detection pattern was broken at 5 sites — realclaude plugin listoutput starts with❯indent +@sourcesuffix;^anchor never matched. Trinity composition silently never activated even when fakoli-flow + fakoli-crew were installed. Fixed inskills/brainstorm/SKILL.md,skills/finish/SKILL.md, and 3 PR-B docs. Updated 5 roadmap entries (P11-SK-S1/S2/S3/S6) so future work uses the corrected pattern. [PR #55]- PR A doc drifts caught during PR B research:
docs/architecture.mddefault lease 15 → 60 min;docs/mcp.mdget_next_taskranking corrected (priority desc → complexity asc → created_at asc, not agent_suitability);agents/sentinel.mdfrontmatterallowed-tools:→tools:(Phase 10 audit leftover);docs/design.md+docs/faq.mdconfig file pointersettings.json→config.yaml. [PR #55]
Changed
.claude-plugin/plugin.jsondescription tightened from 192 → 131 chars; 10 high-signal keywords (addedlocal-first,runtime-neutral,terraform-for-work,llm-work-packets; dropped generic terms). [PR #54]- Repo root
README.md: added anvil to "The Fakoli Ecosystem" trinity narrative; refreshed "Available Plugins" row (removed stale "Scaffolded; phases 2-8 in progress"). [This PR]
Tests
- 967 passed (up from 965 in v1.10.0; +2 for the new
--screenshotsflag). - 0 regressions.
- Manual scratch-dir verification of the getting-started.md Quick Start end-to-end (init → PRD → parse → review → approve → plan → score → claim → packet → submit → apply → done).
[1.10.0] — 2026-05-26
Phase 10: first plugin-dev best-practices audit + 8 MUST FIX items closed
inline. fakoli-crew v2.2.0 ships 5 new cross-plugin specialist critic agents
(agent-critic, skill-critic, hook-critic, mcp-critic, structure-critic — see
plugins/fakoli-crew/CHANGELOG.md § 2.2.0); this release applies their first
audit pass against anvil v1.9.0's surface area and closes every MUST
FIX they surfaced. 57 SHOULD FIX / CONSIDER / NIT items are deferred to
docs/phase-11-backlog.md with per-critic provenance preserved.
Audit doc: docs/audits/2026-05-26-plugin-audit.md.
Phase plan: docs/plans/2026-05-26-phase-10-plugin-audit.md.
Ships v1.10.0.
Fixed — MUST FIX items closed in this phase (8)
agents/critic.md:26— renamedallowed-tools:→tools:(theallowed-tools:key is the command frontmatter key; on agent files it is silently ignored and the agent loads with full unrestricted tool access, defeating the Iron Rule's least-privilege intent for the read-only reviewer). Found by agent-critic.agents/docs-scribe.md:67— sameallowed-tools:→tools:rename. Found by agent-critic.agents/marketplace-scribe.md:67— same rename. Found by agent-critic.agents/planner.md:44— same rename (highest-stakes occurrence: the Iron Rule explicitly forbids planner writes to.anvil/, but the frontmatter that was supposed to enforce least-privilege was silently ignored). Found by agent-critic.agents/state-keeper.md:45— same rename (highest blast-radius occurrence given agent proximity to git and state files). Found by agent-critic.skills/finish/SKILL.md:249-252— removed dangling/anvil:sentinelslash-command reference (sentinel is an agent surface, not a skill — there is no slash-command for it; the broken reference would 404 on invocation). Replaced with explicitclaude plugin list 2>/dev/null | grep -q "^fakoli-crew"shell gate plus prose explaining the agent-dispatch contract so the next Claude session cannot re-introduce the bug by pattern-matching. As a bonus closure (welder Fix #6), this also resolves what would have been P11-SK-S5 in the Phase 11 backlog ("Fuzzy detection forfakoli-crew:sentinel— no shell check") — reducing the deferred SHOULD FIX count from 25 to 24. Found by skill-critic.skills/prd/SKILL.md:57-59— added overwrite-confirmation gate beforeprd parsemutatesstate.dbrows. Mirrors the brainstorm/SKILL.md:162-176 exists-check + summary +yes/no/save-as-backupprompt; applied at two mutation points (the editor open in Step 1 and the re-parse during iteration) since the prd flow has two destructive entry points. Found by skill-critic.README.md:103— Skills row claimed averifyskill that does NOT exist on disk (overpromise); rewrote the row to list the 7 real skills (brainstorm, prd, plan, claim, execute, finish, state-ops) and document that verification is delegated tofakoli-flow:verifyandfakoli-crew:sentinel, so the reader understands whyverifyis absent. Found by structure-critic.
Added — 5 new fakoli-crew critic agents (audit infrastructure)
fakoli-crew v2.2.0 (see its CHANGELOG) ships 5 new cross-plugin specialist critic agents that this release was the first subject of:
- agent-critic (magenta) — reviews
<plugin>/agents/*.mdfrontmatter (name/description/color/model/tools), color-collision detection,<example>count discipline, theallowed-tools:vstools:antipattern, defer-to validity, and file-length proportionality. - skill-critic (teal) — reviews
<plugin>/skills/*/SKILL.mdfor frontmatter validity, one-question-at-a-time discipline, hard-gate presence on irreversible actions, decision-flow clarity, lazy-loading discipline, and the no-fuzzy-detection rule. - hook-critic (gray) — reviews
<plugin>/hooks/*.sh+hooks.jsonfor shebang portability,${CLAUDE_PLUGIN_ROOT}usage, stdin handling, hot-path performance, idempotency, matcher specificity, and — critically — whether the script's error-handling style matches the plugin's declared hook contract (e.g., anvil's non-blocking contract forbidsset -e). - mcp-critic (white) — reviews
.mcp.json+ MCP server source for schema validity,@mcp.tool()decoration discipline, typed parameter annotations, structured error returns, secret-leak risks, transport choice rationale, and actor-identification on mutating tools. - structure-critic (brown) — reviews cross-plugin structure:
plugin.jsonrequired fields, version sync acrossplugin.json/pyproject.toml/__init__.py/marketplace.json/registry/index.json, README surface tables vs actual filesystem counts, CHANGELOG Keep-a-Changelog discipline, and[Unreleased]hygiene after a tag.
Each critic ships with a known-bad fixture at plugins/fakoli-crew/tests/fixtures/audit-targets/ and a manual-verification recipe at plugins/fakoli-crew/tests/RECIPES.md. Together they form the cross-plugin critic surface — anvil was the first subject; future plugins (fakoli-flow, fakoli-speak, etc.) can run the same five-critic audit.
Deferred — Phase 11 backlog
57 SHOULD FIX / CONSIDER / NIT items deferred (25 SHOULD FIX, 21 CONSIDER, 11 NIT) — full per-critic detail with file:line provenance, recommended actions, and cross-cutting themes (no-fuzzy-detection across skills, non-empty actor validation across MCP tools, hot-path perf budget on hook scripts, hook-contract documentation gap, phase-status table drift, composition duplication across doc/state agents, install-messaging drift in README + CHANGELOG) recorded in docs/phase-11-backlog.md. The Phase 10 welder Fix #6 closed one SHOULD FIX item (P11-SK-S5, fuzzy detection for fakoli-crew:sentinel) as a bonus during the dangling-slash-command fix — phase-11-backlog.md marks that line [CLOSED in Phase 10 Fix #6]. Net deferred SHOULD FIX count is 24 (down from 25 at audit time).
Documentation
docs/audits/2026-05-26-plugin-audit.md(NEW) — consolidated audit with severity-sorted findings table, per-critic detail sections, and "Items applied this phase" annotations linking each MUST FIX to the welder fix that closed it.docs/phase-11-backlog.md(NEW) — Phase 11 work-tracking doc mirroring the format ofdocs/phase-9-backlog.md. Includes 7 cross-cutting themes and explicit Phase 11 planner notes.docs/plans/2026-05-26-phase-10-plugin-audit.md(NEW) — full 16-task / 9-wave execution plan for the audit + fix cycle.docs/specs/2026-05-26-plugin-audit-and-critics.md(NEW) — source spec for the 5 new critic agents.
Tests
- 965 passing (unchanged from v1.9.0 — all 8 MUST FIX items were markdown/frontmatter edits; no Python code touched, no behaviour drift).
[1.9.0] — 2026-05-25
Phase 9: audit honesty + multi-provider config + Phase 7 cleanup + two
new plugin-owned doc agents. The sync engine's audit stream is now
truthful — six dishonest sync.pull.completed emissions on
conflict-resolution branches that did not actually mutate local state
now correctly emit sync.pull.deferred. The local_moved-only pull
path bug-collapse (mapping was set to in_sync despite local being
ahead) is fixed to set sync_state="local_ahead" and emit a
sync.push.deferred hint. The SyncAuditPayload model is now a Pydantic
v2 discriminated union with extra="forbid" per action — field-vs-action
mismatches surface as ValidationError instead of being silently
accepted. A new opt-in sync.providers config key lets projects narrow
or fully opt out of provider iteration.
Phase 7 leftovers closed: RecordedLLMProvider.record_key now folds
max_tokens and temperature into the canonical hash (two recordings
under different tuning args no longer collide); brainstorm-skill
fakoli-flow detection uses an explicit claude plugin list check rather
than fuzzy prose; anvil expand --use-llm --format prd emits
paste-ready markdown blocks matching docs/prd-template.md (with
**Feature:** and **Priority:** populated from the parent task).
Ships v1.9.0.
Added — Audit honesty (T5)
bin/src/anvil/cli/sync.py:- Six deferred conflict-resolution branches (
local_wins_deferred,remote_wins_deferred,prompt_defaulted_to_local,prompt_chose_local,prompt_chose_remote,prompt_skipped) now emitsync.pull.deferredinstead of the priorsync.pull.completed. The JSONL is safe to grep for "did this task actually update?". local_moved-only pull path (local Task ahead oflast_synced_at, no remote movement) now setssync_state="local_ahead"(wasin_sync— wrong) and emitssync.push.deferredwithresolution="local_moved_no_push". Operators grepevents.jsonlfor the token to find tasks awaiting a follow-up--push._resolve_conflictsignature:-> bool→-> tuple[bool, bool, str]=(resolved, applied, resolution). Internal private function; no external caller impact.
- Six deferred conflict-resolution branches (
bin/src/anvil/state/payloads.py:- 13 new per-action Pydantic v2 subclasses replacing the v1.8.0 single all-optional
SyncAuditPayloadmodel:SyncBatchStartedPayload,SyncBatchCompletedPayload,SyncPushStartedPayload,SyncPushCompletedPayload,SyncPushDeferredPayload,SyncPushFailedPayload,SyncPullStartedPayload,SyncPullCompletedPayload,SyncPullDeferredPayload,SyncPullFailedPayload,SyncConflictDetectedPayload,SyncReconciliationStartedPayload,SyncReconciliationCompletedPayload. Each hasextra="forbid"; field-vs-action mismatches now fail at validate time. SyncAuditPayloadpreserved as a backwards-compat module-level type-form (Annotated[Union[...], Field(discriminator="action")]) — existing imports resolve; callers that usedSyncAuditPayload.model_validate(d)directly migrate toTypeAdapter(SyncAuditPayload).validate_python(d)or look up the concrete subclass viaACTION_TO_PAYLOAD[action].ACTION_TO_PAYLOAD: dict[str, type[BaseModel]]exported for direct dispatcher lookup.
- 13 new per-action Pydantic v2 subclasses replacing the v1.8.0 single all-optional
bin/src/anvil/state/sqlite.py: dispatcher uses the newACTION_TO_PAYLOADregistry; the prior 13 explicit(SyncAuditPayload, handler)entries collapse into a single dict comprehension. When a new sync action is added the dispatcher auto-picks it up.
Added — Multi-provider config (T5)
bin/src/anvil/config.py: new optional top-levelsync.providersconfig key parsed intoConfig.sync_providers: tuple[str, ...] | None. Three-way semantics:- Key absent →
None→ fall back tosorted(PROVIDER_REGISTRY)(v1.8.0 default). sync.providers: [a, b]→("a", "b")→ use the explicit list.sync.providers: []→()(NOTNone) → opt out of every provider; sync is a no-op.
- Key absent →
bin/src/anvil/cli/sync.py::_resolve_configured_providersis the single lookup seam; wrapped intry/except (ValueError, OSError)so a malformed config falls back to the registry rather than breakinganvil sync. Loud config errors are the job ofinit/doctor.- Documented in
docs/sync-providers.md§ "Per-provider configuration (v1.9.0)" — full schema, three-way table, fallback semantics, reconciliation interaction.
Added — Phase 7 cleanup (T6)
- C2 —
RecordedLLMProvider.record_key: signature extended torecord_key(system, user, *, max_tokens=4096, temperature=0.0); canonical hash foldsstr(int(max_tokens))andrepr(float(temperature))as length-prefixed chunks 3 and 4.repr(float(...))normalises0,0.0, and0.00to the same key. Default values mirrorLLMProvider.generatedefaults so back-compat calls with no kwargs still work. The v1.7.0 footgun where two recordings under different tuning args silently collided is closed; tests that pre-compute keys MUST pass the matching values the engine uses at lookup time (_SCORE_EXPLAIN_MAX_TOKENS=300,_DESCRIPTION_ENRICH_MAX_TOKENS=400,_EXPAND_MAX_TOKENS=2000). Collateral updates to 8 call sites intests/test_llm_integration.py+ 1 intests/test_cli.py. - C3 — Brainstorm-flow detection:
skills/brainstorm/SKILL.mdreplaces the fuzzy "if fakoli-flow seems available" prose with an explicitclaude plugin list 2>/dev/null | grep -q "^fakoli-flow"shell check plus exit-code-driven branching. Slash-command name corrected from/flow:brainstorm(typo) to the fully-qualified/fakoli-flow:brainstorm— the typo would have broken the bridge invocation when fakoli-flow IS installed. Detection is OPTIONAL: exit non-zero (or missingclaudebinary) falls through to the local interview. - C4 —
expand --format prd: new--format {text,prd}Typer option onanvil expand.--format prdemits ready-to-paste markdown blocks matchingdocs/prd-template.md's## Tasksschema (H3 heading,**Feature:**,**Priority:**,**Likely files:**, description paragraph,**Acceptance criteria:**bullets,**Verification:**withTODOplaceholder).**Feature:**and**Priority:**are populated from the parent task's metadata (Phase 9 critic CONSIDER fix — eliminates the manual-edit step in the paste-into-prd.mdworkflow). Default--format textkeeps the v1.7.0 human-readable per-subtask block output unchanged. The new mode round-trips cleanly throughparse_prd— seetests/test_cli_plan.py::test_prd_format_output_round_trips_to_prd_parserfor the canonical proof.
Added — Two new plugin-owned doc agents (T4)
plugins/anvil/agents/marketplace-scribe.md— cyan, opus. Owns.claude-plugin/marketplace.json, the rootREADME.mdplugins table, andregistry/*.jsonindex files. Fires after any version bump, agent add/remove, or skill add/remove inside anvil. Defers tofakoli-crew:keeperwhen not in an anvil context. IncludesBashinallowed-toolsso it can runscripts/generate-index.shand validate regenerated JSON viapython -m json.tool/jq ..plugins/anvil/agents/docs-scribe.md— purple, opus. Owns the plugin'sdocs/folder,CHANGELOG.md, and thedescriptionfield of.claude-plugin/plugin.json. Audits cross-references between docs — broken[[wikilinks]], mismatched section anchors, danglingsee alsopointers, references to moved/archived files. Fires after any schema change, new CLI command, new agent, or completed phase. Defers tofakoli-crew:heraldfor general README work. NoBash(pure docs work).- Color collisions checked vs the existing four agents (planner=white, critic=magenta, sentinel=gray, state-keeper=teal). Cyan and purple are unused by every existing anvil agent and by every fakoli-crew agent.
Added — Documentation
docs/phase-9-backlog.md(NEW) — forward-looking v2.x roadmap. Carries the items consciously deferred from Phase 9 (LinearIssuesProvider, MondayBoardsProvider, JiraIssuesProvider, GitHubProjectsProvider, webhook-based sync spec, immediate-apply*_appliedresolution variants,anvil snapshotCLI, MCP sync tools surface, per-provider config nesting), plus the carry-forwardCL-N/TQ-N/PS-Nitems still open indocs/tech-debt-backlog.md.docs/github-sync.md— new "Audit honesty" section explainingsync.pull.completedvssync.pull.deferredsemantics, thelocal_aheadmapping state, and the full controlled vocabulary ofresolutiontokens (including the newlocal_moved_no_push). Audit events table grows by one (sync.push.deferred).docs/sync-providers.md— new "Per-provider configuration (v1.9.0)" section documenting the optionalsync.providersconfig key with the three-way absent/explicit/empty semantics.docs/llm.md— correctedRecordedLLMProvider.record_keysignature and example (tuning args participate in the key per Phase 9 C2); newexpand --format prdworked example with paste-ready output and round-trip note.docs/tech-debt-backlog.md— new "Phase 8 / Phase 9 closures" section at the top covering P9-1..P9-8 (audit honesty fixes, discriminated payloads, multi-provider config, record_key fix, brainstorm detection, --format prd, two new doc agents). Status legend growsMOVED-P9-BACKLOGfor items forward-carried tophase-9-backlog.md.README.md— version badge bumped to 1.9.0; phase table marks phases 1–9 Done with per-release version pointers; new "Plugin-owned agents" section listing all six agents (planner, critic, sentinel, state-keeper, marketplace-scribe, docs-scribe) with color + ownership + crew defer-to target.
Changed
bin/src/anvil/state/payloads.py::SyncPullCompletedPayloaddocstring — enumerates the four honest emission conditions (clean pull, tombstone, in_sync no-divergence, local-moved-only with pairedsync.push.deferredhint). The local-moved-only branch is the one most likely to surprise readers who expect "completed" to imply "mutated"; the docstring is explicit that the pull terminal is honest because the pull itself succeeded — only the follow-up push is deferred.bin/src/anvil/cli/sync.py::_emit_auditdocstring — updated to reflect that the discriminated union has REQUIRED fields per action now; the None-strip is still load-bearing for OPTIONAL fields withNonedefaults (JSONL would otherwise carry"audit_note": nullrows that clutter forensic queries and breakjq 'has("audit_note")'filters).bin/src/anvil/planning/llm.pymodule docstring —RecordedLLMProviderkey shape description corrected from the stalesha256(system + "---" + user)to the length-prefixedsha256over(system, user, max_tokens, temperature).bin/src/anvil/state/payloads.py:393-399—TypeAliasterminology corrected to "module-level type-form (Annotated[Union[...], Field(discriminator="action")])".SyncAuditPayloadis NOT atyping.TypeAlias(no: TypeAlias =annotation); it is a Pydantic discriminated-union type form.bin/src/anvil/cli/plan.py::_render_subtask_proposals_as_prd— added optional kw-onlyparent_feature_idandparent_priorityparameters. The CLI caller (expand --format prd) now threadstask.feature_idandstr(task.priority)through — eliminates the prior empty**Feature:**line and**Priority:** mediumdefault that the user had to manually edit beforeprd parse. Default behaviour (when the helper is called without parent context) preserved for backwards compat.tests/test_llm.py::test_separator_prevents_collisiondocstring — updated from the stale"\n---\n"separator description to "Length-prefixed encoding prevents concat-collisions across any byte boundary." Assertion unchanged.
Tests
- 935 → 964 passing, 3 skipped (Δ +29 net):
- +14 new tests authored across T5 / T6: 5 in
test_cli_sync.py(deferred-branch emission, local_moved_no_push), 7 intest_config.py(sync_providers three-way semantics), 2 intest_sqlite.py(dispatcher uses concrete subclasses). - +4 new tests in
test_llm.py::TestRecordedLLMProviderKeyfor the C2 tuning-args participation. - +11 new tests in
test_cli_plan.py(greenfield) covering--format textbaseline,--format prdblocks + round-trip + bullets + headings + suppression of legacy delimiter, validation precedence, and--helpdocumentation. - 1 inverted-name test:
test_ignores_max_tokens_and_temperature→test_distinguishes_max_tokens_and_temperature(assertions inverted to lock the new contract). - 15 pre-existing tests that were failing on T3's payload changes are now passing again (dispatcher fix in
state/sqlite.py).
- +14 new tests authored across T5 / T6: 5 in
- One existing test (
test_prd_format_includes_template_fields) updated to assert the new**Feature:** F001and**Priority:** highshape (inherited from the parent T001) rather than the prior empty-Feature / default-medium values.
Migration notes
- Schema version unchanged (still v3 from v1.8.0). No DB migration required.
- The audit-event action change is forward-compat for log readers: a tool that filtered
sync.pull.completedon v1.8.0 will now see fewer rows under v1.9.0, but the rows it does see are honest. To recover the union, filter(.action == "sync.pull.completed" or .action == "sync.pull.deferred"). - The
SyncAuditPayloadrename is backwards-compatible at the import path; callers that usedSyncAuditPayload.model_validate(d)directly must migrate toTypeAdapter(SyncAuditPayload).validate_python(d)or look up the concrete subclass viaACTION_TO_PAYLOAD[action]. RecordedLLMProvider.record_keynow requires matchingmax_tokens/temperatureat lookup time when the engine overrides the defaults — tests that pre-computed keys with the no-kwargs form will seeLLMProviderError("no recording for prompt hash ...")if the engine passes non-default tuning args. The collateral updates intests/test_llm_integration.py(this release) are the template.- The new
agents/marketplace-scribe.mdandagents/docs-scribe.mdare created in v1.9.0 but only become invocable as subagent types in NEW sessions (Claude Code discovers agents at session start). Existing sessions need to restart to pick them up.
[1.8.0] — 2026-05-25
Phase 8: bidirectional sync. Adds a multi-provider SyncProvider Protocol
abstraction with GitHub Issues as the first concrete implementation, wires
opt-in anvil sync CLI surface with bidirectional push/pull, four
conflict-resolution strategies, watch-loop polling, and full reconciliation
between SQLite state / filesystem / git. The Protocol is registry-driven
so Monday, Linear, Jira, and custom providers can plug in without engine
changes — see docs/sync-providers.md for the contributor guide.
The schema gains a sync_mappings table (SCHEMA_VERSION 2 → 3) with an auto-upgrade path for existing v1.7.x databases; the diff is purely additive. See docs/migrations.md.
Added — Sync abstraction layer
bin/src/anvil/sync/package —SyncProviderProtocol (push_task,fetch_task,list_tasks,delete_task,health_check),ExternalRef+ExternalTask+ProviderHealthPydantic models,SyncProviderErrorhierarchy (AuthenticationFailed,RateLimitExceeded,ProviderUnavailable,SyncConflict),RecordedSyncProvidertest double (sha256 length-prefixed keyed),PROVIDER_REGISTRY+register_sync_provider/get_sync_provider/list_sync_providers. snake_caseprovider_iddiscipline.bin/src/anvil/sync/providers/github_issues.py—GitHubIssuesProviderconcrete impl. Auto-registers as"github_issues"on package import. Dual transport:ghCLI primary,httpx+GITHUB_TOKENfallback. Status mapping: 11 TaskStatus values →status:*labels; onlydonecloses the issue. Body footer convention (---\n_synced from anvil task {task_id}_) is round-trippable via_strip_footer. Label preservation across pushes (HTTP transport reads existing labels first, preserves non-status:*).bin/src/anvil/sync/clients/gh_cli.py— subprocess wrapper forgh issue create/edit/view/list/close. Stderr-scan error classification (auth/rate-limit/network).bin/src/anvil/sync/clients/github_http.py— httpx wrapper with Link-header pagination + 1000-page safety cap +responses-style HTTP mocking via respx in tests.bin/src/anvil/sync/reconciliation.py—ReconciliationEngine.scan() / fix(dry_run=False)covering 6 discrepancy kinds: orphan_branch, orphan_packet, orphan_worktree, stale_claim, missing_sync_mapping, drift_sync_state. The first 4 have full fix paths; the latter 2 emit operator-facing CLI commands (anvil sync provider <id> --pull --task T001) for Phase 9 immediate-apply.
Added — State schema (SCHEMA_VERSION 3)
sync_mappingstable: composite PK(task_id, external_system)+UNIQUE(external_system, external_id)(prevents cross-task collisions) +FK ON DELETE CASCADEto tasks +external_url+provider_metadata_json. Auto-upgrade from v1/v2 dbs in_check_schema_version; purely additive.- New Pydantic models:
SyncMapping,SyncState,ConflictResolutionStrategy(enums),ExternalSystem(snake_case enum). - New payload models in
state/payloads.py:SyncMappingUpsertedPayload,SyncMappingDeletedPayload,SyncAuditPayload. Allextra="forbid". - New event handlers in
state/sqlite.py:_handle_sync_mapping_upserted,_handle_sync_mapping_deleted,_handle_sync_audit(no-op like file_changed / progress.noted). - New Backend Protocol methods:
get_sync_mapping(task_id, *, external_system=None),list_sync_mappings(external_system=None),apply_sync_mapping(mapping, *, actor='system')(uses PENDING_EVENT_ID for race-free assignment). - Nine new
sync.*audit-event actions:sync.batch.started/completed,sync.push.started/completed/failed,sync.pull.started/completed/failed,sync.conflict_detected.
Added — CLI surface
anvil sync— runs reconciliation only (scan + print report).anvil sync --fix --yes— reconciliation + apply remediations (--yesrequired for non-interactive; refuses without--yeson cron/CI).anvil sync <provider>— push+pull all tasks via the named provider.anvil sync github— backwards-compat alias forsync github_issues.anvil sync provider <id>— generic provider invocation.- Flags:
--push(push-only),--pull(pull-only),--task T001(single-task),--watch --interval N(long-running poll loop with per-iteration error recovery),--health(provider auth probe, works pre-init),--fix(forces remote_wins conflict strategy). - Conflict resolution: per-task
SyncMapping.conflict_resolution_strategy∈ {local_wins,remote_wins,prompt,manual_merge}. Resolution events emit*_deferredaudit strings (truthful — actual mutations happen on the next pass; Phase 9 will wire immediate apply).manual_mergewrites.anvil/.sync-conflicts/<task_id>.md; batch exits 2 if any task needed operator input.
Added — Plugin-owned agent
plugins/anvil/agents/state-keeper.md(colorteal, modelopus) — specialized agent for sync drift detection + reconciliation triage. Defers tofakoli-crew:keeperwhen crew is installed.
Added — Documentation
docs/github-sync.md(245 lines, 12 sections) — user-facing GitHub Issues sync reference.docs/sync-providers.md(280 lines, 11 sections) — contributor-facing Protocol reference with a step-by-step Linear-provider walkthrough.docs/live-tests.md— operator runbook for the nightly live-GitHub CI.docs/migrations.md— already shipped in 1.7.1; documents the v1/v2 → v3 auto-upgrade.
Added — Nightly CI
.github/workflows/anvil-live-github.yml— daily cron at 06:00 UTC. Gated onsecrets.ANVIL_TEST_GH_TOKEN(job exits 0 with a notice if secret missing). Runspytest -m live_github -vagainst a real test repo.tests/test_github_issues_live.py— 3 live tests (lifecycle, label preservation, rate-limit handling). All decorated@pytest.mark.live_github; excluded from defaultpytest -qviaaddopts = "-m 'not live_github'"in pyproject.toml. Cleanup contract: every test closes its own issues + leaves a[fakoli-test]UUID prefix for orphan sweeping.
Changed
bin/pyproject.toml— dropped unusedresponses>=0.25dev dep; addedhttpx>=0.27runtime; addedrespx>=0.21dev (for httpx-side HTTP mocking); registeredlive_githubpytest marker.cli/__init__.py— wires the newsync_appTyper sub-app into the main CLI.
Tests
- 750 → 917 baseline tests (+167) plus 3 live-github tests (excluded from default).
- Across waves: 58 sync_provider tests, 23 sync_mapping tests, 82 github_issues_provider tests, 37 reconciliation tests, 42+ cli_sync tests, 4 follow-up + Wave 3 fix-cycle additions.
- Ruff clean. Migration auto-upgrade path tested for v0/v1/v2 → v3.
Migration notes
- Schema bumps 2 → 3. Existing v1.7.x databases auto-upgrade on first
anvilinvocation under 1.8.0. The diff is purely additive (new table, no shape changes to existing tables). No manual action required. - The
responsesdev dep has been dropped; if you have a custom test that imported it, switch torespxfor httpx mocking. anvil syncis a NEW command. Existing CLI commands are unchanged.
[1.7.1] — 2026-05-25
Backlog cleanup. Closes 14 items from the deferred review backlog
(docs/tech-debt-backlog.md) — 6 correctness fixes (welder), 5 doc/config
cleanups, and the leftover deferrals from the PR #47 critic review. No
behavior changes visible to existing CLI / MCP callers.
Fixed (correctness — welder backlog wave)
- CL-1:
hooks/check-claim.shnow invokes thehook check-claim --file --actorCLI subcommand (Phase 5) instead of parsingstatus --hook-formatoutput (Phase 4 leftover that fired on any claim regardless of file scope). - CL-3:
_reap_stale_claimsno longer swallowsSchemaMismatch; narrowed catch to(StateLocked, TransactionAborted)so DDL drift surfaces loudly. - CL-8:
_handle_evidence_submittedrejects double-submit with a differentevidence_idfor the same claim; emits the establishedwarn.idempotent_no_opJSONL tombstone instead of inserting a duplicate row. - CL-11:
planning.template.parse_prdaccepts an optionalclock: Clock;_parse_tasksnow requires a clock injection instead of callingdatetime.now()directly. - CL-13:
SqliteBackend.next_event_idnow raisesRuntimeErrorvia_require_conn()instead of returning the hardcoded"E000001"when the connection is closed — eliminates the silent collision-on-reopen footgun. - PS-1:
ClaimManager._check_group_conflictscollapses 1+N round-trips into 2 via a single bulklist_tasks()+ in-memorydict[task_id, Task]lookup.
Fixed (small cleanups)
- CL-7:
agents/critic.mdandagents/sentinel.mdcolor collisions with fakoli-crew — state/critic purple → magenta, state/sentinel cyan → gray. - CL-9:
review.gates._contains_test_keywordno longer matchespytest --collect-only/--co(zero-test runs were satisfying the "tests pass" evidence gate). - CL-14:
skills/finish/SKILL.mdtext updated — the apply flow emits a singletask.appliedevent, not the nonexistentreview.created+task.status_changedpair. - PS-2:
initno longer pre-creates.anvil/snapshots/; the directory will be created on first use whenanvil snapshotships.
Fixed (PR #47 critic deferrals)
- S2 / Greptile-G1 (already in 1.7.0): noted closed.
- S5:
template.DESCRIPTION_SHORT_THRESHOLDis now public; CLIplan --use-llmhelp text references the constant rather than the literal "50". - N1: comment in
parse_prdclarifies that HTML-comment stripping runs before the LLM augmentation pass. - N2:
parse_prd's reservedprd_idparameter now uses# noqa: ARG001instead of the_ = prd_iddiscard idiom. - N3:
planning.llm._DEFAULT_MODELcarries a "Last verified" date comment so future maintainers know when to refresh. - N5: removed the unused
responses>=0.25dev dependency (the test suite mocks the anthropic SDK at theunittest.mocklevel sinceanthropicuseshttpx, notrequests).
Documentation
docs/evidence-buffer.md(NEW) — format, lifecycle, orphan.json policy, sentinel interaction, cleanup. Closes CL-15.docs/tech-debt-backlog.mdstatus markers updated: P6-1..P6-5 marked DONE (closed in PR #44); CL-7/CL-9/CL-14/CL-15/PS-2 DONE (this PR); TQ-5 DONE (PR #42 fixup).
Tests
- 639 → 653 pytest tests (+14): 6 for CL-9 collection-only exclusion, 3 for CL-3 SchemaMismatch propagation, 1 for CL-8 double-submit guard + strengthened existing CL-8 test, 2 for CL-11 clock injection, 1 for CL-13 require-conn guard, 1 for PS-1 N+1 → 2 query collapse.
- 18 → 21 bash hook tests (+3 for CL-1 invocation surface).
[1.7.0] — 2026-05-25
Phase 7: LLM augmentation. Adds an LLMProvider Protocol with an Anthropic-backed implementation (ephemeral prompt caching on the system block) and a RecordedLLMProvider test double, wires opt-in --use-llm flags into plan, score, and expand, and ships a brainstorm skill that bridges to fakoli-flow:brainstorm. The deterministic planning engine is unchanged — LLM enrichment is strictly additive and falls back cleanly on missing key, missing recording, or mid-operation failure.
Added
bin/src/anvil/planning/llm.py—LLMProviderProtocol +AnthropicProvider(with ephemeral prompt-caching on the system block per the claude-api skill guidance) +RecordedLLMProviderfor deterministic tests +LLMResponsePydantic model +LLMProviderError. Default model:claude-sonnet-4-6; API key sourced fromANTHROPIC_API_KEYenv var.--use-llmflag onanvil plan,score, andexpand. Off by default — opt-in augmentation that enriches deterministic output (score explanations, short task descriptions, sub-task proposals for complex tasks).bin/src/anvil/planning/inference.py::expand_task— new function returninglist[SubtaskProposal]. Deterministic path returns[]; with provider +complexity >= 4, calls LLM to propose 2-5 sub-tasks. JSON-parse-tolerant; malformed responses fall back to[]with a warning.plugins/anvil/skills/brainstorm/SKILL.md— interview-style PRD authoring skill. Bridges tofakoli-flow:brainstormwhen installed; standalone otherwise.docs/llm.md— provider config, prompt-caching usage,RecordedLLMProvidertest pattern, failure modes.- 46 new tests: 29 in
tests/test_llm.py(provider unit tests), 17 intests/test_llm_integration.py(engine integration viaRecordedLLMProvider), plus 10 new CLI flag tests intests/test_cli.py.
Changed
planning.scoring.score_task/score_all— new kw-onlyprovider: LLMProvider | None = None. Default behavior unchanged.planning.template.parse_prd— new kw-onlyprovider: LLMProvider | None = None. Default behavior unchanged.- LLM failures during augmentation print a warning to stderr; the engine returns the deterministic-only result. LLM augmentation never aborts a planning operation.
Technical notes
- One ephemeral cache breakpoint on the system block per Anthropic call. Repeated
score --use-llmruns against the same task batch hit the cache and pay only for new user tokens. RecordedLLMProviderkeys are a length-prefixedsha256over(system, user)— tests pre-compute viaRecordedLLMProvider.record_key(...). (v1.9.0 extended the key to includemax_tokensandtemperature— see the v1.9.0 entry above.)
Tests: 613 → 640 + Wave 3a additions (Wave 3a may add a few more — total to be confirmed at sentinel time).
[1.6.0] — 2026-05-25
Phase 6: MCP server. Exposes 13 agent-facing tools via FastMCP (stdio), wires them into Claude Code via .mcp.json, adds the progress.noted audit event, and ships 50 MCP integration tests. Any agent in a project with anvil installed now has direct programmatic access to the full state engine without shelling out to the CLI.
Added
bin/src/anvil/mcp_server.py— FastMCP (stdio) server with 13 agent-facing tools. Read-only tools:get_project_summary,list_tasks,get_task,get_next_task,generate_work_packet,check_conflicts,get_dependency_graph. Mutating tools:claim_task,release_task,renew_claim,submit_progress,submit_completion_evidence,update_task_status. Stale-claim reaping runs at the top ofget_project_summaryand all six mutating tools. The server opens a freshSqliteBackendper tool call (Path.cwd() / .anvil) — agents in different cwds see their own state, no leakage.plugins/anvil/.mcp.json— wiresanvil-mcpas a stdio MCP server via${CLAUDE_PLUGIN_ROOT}/bin/anvil-mcp. Claude Code agents in any project with this plugin installed automatically see the 13 tools.progress.notedevent action — audit-only, structurally parallel tofile_changed. NewProgressNotedPayloadinstate/payloads.pyand a no-op handler insqlite.py. Emitted bysubmit_progress.docs/mcp.md— 645-line full tool reference covering each tool's signature, return shape, error cases, integration notes for fakoli-flow / fakoli-crew, and the documented error envelope contract.- 50 new MCP integration tests in
tests/test_mcp.pyvia the FastMCP in-process Client. 2 additionalprogress.notedpayload tests in the existing payload test suite.
Changed
bin/anvil-mcp— wrapper now executespython -m anvil.mcp_serverviauv run(fully functional). The Phase-6 "not yet implemented" guard block is removed.
Technical notes
- Error envelope: tools raise
fastmcp.exceptions.ToolError(message)with a human-readable string. The spec's structured{code, message, target_id, payload}envelope is deferred — the documented contract lives indocs/mcp.md. - The process-per-request connection pattern keeps the server a thin shim. No shared in-process state, no connection pooling concerns across concurrent agent calls.
Tests: 530 → 580 (+50 MCP integration tests, +2 payload tests). Ruff clean.
[1.5.0] — 2026-05-25
Phase 6 prep: backend / state-engine refactors that unblock the MCP server
(landing next in Phase 6 proper) by closing the five must-fix items from the
PR #41 critic and Greptile reviews tracked as P6-1..P6-5 in
docs/tech-debt-backlog.md.
Added
bin/src/anvil/cli/— new package replacing the 2,499-linecli.pymonolith. Per-command modules:init_status,prd,plan,claim,packet_apply,hooks, plus_helpersfor shared utilities and__init__.pyas the Typer-app assembler. Public import path (from anvil.cli import app) is unchanged. (P6-4)bin/src/anvil/state/payloads.py— 17 per-action Pydantic v2 payload models (ProjectCreatedPayload,PrdParsedPayload,EvidenceSubmittedPayload, etc.) all usingConfigDict(extra="forbid").SqliteBackend._apply_mutationnow validatesevent.payload_jsonagainst the model forevent.actiononce before dispatch, replacing the 17-elif chain with adict[str, (PayloadModel, handler)]table. Handler signatures normalize to(conn, payload: TypedPayload, event: Event)— handlers read fields via attribute access rather thanpayload.get(...). (P6-5)BackendProtocol gains three methods previously only on the SqliteBackend reach-through:get_feature(feature_id),list_events(target_id, target_kind, limit),get_latest_evidence(task_id). The CLI no longer touchesbackend._conndirectly; the three call sites incli/_helpers.py(_fetch_recent_events,_fetch_latest_evidence) collapse into Protocol calls. (P6-1)PENDING_EVENT_ID = "PENDING"sentinel onstate.backend. Callers construct events asEvent(id=PENDING_EVENT_ID, ...)and the backend assigns the realE000001-format ID insideapply_event's BEGIN IMMEDIATE transaction, closing the read-before-lock race that allowed event drops under concurrent claim/release. (P6-2)- 37 new test cases in
tests/test_sqlite.py::TestPayloadValidationcovering each payload model's happy path andextra="forbid"rejection plus dispatch-levelValidationErrorpropagation. - 7 new test cases in
tests/test_sqlite.py::TestBackendProtocolExtensionscovering the three new Protocol methods.
Changed
- All CLI commands and
claims.stale.detect_and_release_stale()now emit events viaPENDING_EVENT_IDinstead of pre-allocating IDs throughbackend.next_event_id().next_event_id()remains for backward compatibility but is documented as the legacy path. SqliteBackend.apply_eventrewritesevent.idin place when the sentinel is passed and returns the updated event so callers can recover the assigned ID without re-querying.
Removed
bin/src/anvil/cli.py— replaced by the package above. Imports resolve identically.TaskStatus.stalefromstate.modelsand the correspondingtask_to_stale/task_stale_to_readytransitions. The state was structurally unreachable — only claims can be stale, and the task returns directly toreadywhen the claim is reaped. Task lifecycle ASCII diagram indocs/specs/2026-05-24-anvil-v0.mdupdated. CL-16 (claim.stale task transition skips the intermediatestalestate) is resolved as a side-effect. (P6-3)cli/_helpers.py::_fetch_recent_eventsand_fetch_latest_evidence— callers now go through the Protocol methods.
Migration notes
- External code calling
apply_eventshould switch to passingEvent(id=PENDING_EVENT_ID, ...)to get race-free ID assignment. Pre-built events with concrete IDs still work (the replay path requires this) but the pre-allocation path is racy under concurrency. - Subclasses of
Backendmust implement the three new methods or accept theNotImplementedErrorfrom the Protocol default. - The CLI external surface (
anvil <subcommand>) is unchanged.
[1.4.1] — 2026-05-25
Docs-only patch release. Stages the deferred items from the PR #41 critic + Greptile reviews into a single backlog document so Phase 6 work picks them up explicitly without re-reading chat transcripts.
Added
docs/tech-debt-backlog.md— 31 open items + 11 already-closed (for reference), grouped into: Phase 6 must-close (5), Cleanup (16), Test quality (8), Performance (2). Each entry cites its source (Greptile, Critic-1/2/3/4) and includes a concrete fix sketch.
[1.4.0] — 2026-05-25
Phase 5: Context engine. Delivers the context engine, review apply gate, three new CLI commands, one new hook subcommand, two new skills, two new plugin-owned agents, a new PostToolUse hook, state engine extensions, and a comprehensive test suite. The plugin now supports the complete claim → packet → work → submit → apply lifecycle.
Added
- Context engine (
context/packets.py) —render_packet()produces both markdown (for.anvil/packets/T001.md) and JSON (for MCPget_work_packetin Phase 6). Pure function; no I/O. - Review engine apply gate (
review/gates.py) —evidence_complete(task, evidence)validates that submitted Evidence satisfies the task'srequired_evidencelist; surfaces specific missing items. - Three new CLI commands:
packet TASK_ID [--format md|json],submit TASK_ID --commands ... --files-changed ... [--output-file --pr-url --commit-sha --known-limitations --actor],apply TASK_ID [--approve | --reject] [--reason --reviewer]. - One new hook subcommand:
anvil hook capture-evidence --command --exit-code --stdout-file --stderr-file --actor— used by the new PostToolUse Bash hook. - Two new skills:
skills/execute/SKILL.md(full claim → packet → work → submit loop; coordinates withfakoli-flow:executewhen installed) andskills/finish/SKILL.md(apply + ship decision: merge/PR/keep/discard). - Two new plugin-owned agents:
agents/critic.md(code reviewer; defers tofakoli-crew:critic) andagents/sentinel.md(evidence validator; defers tofakoli-crew:sentinel). Bothallowed-toolsexclude Edit/Write (Iron Rule at tool-permission level). - New PostToolUse hook:
hooks/capture-evidence.sh(Bash matcher) — captures stdout/stderr/exit-code of verification commands (pytest,ruff,mypy,npm test,cargo test,bun test) into.anvil/.evidence-buffer/per-claim JSON files for later attachment to Evidence. - State engine: 2 new event handlers (
evidence.submitted,task.applied) both routed via_apply_mutation.evidence.submittedatomically inserts Evidence + transitions task toneeds_review+ auto-releases the active claim.task.appliedcombinesneeds_review→accepted→donein one transaction whendecision='accepted'. - 81 new tests (403 → 484):
test_context.py(24 tests),test_review.py(20),test_sqlite.pyextensions (16 new Phase 5 handler tests + the audit replay test forevidence+applied),test_cli.pyextensions (17 new),test_hooks.shextensions (5 new capture-evidence smoke tests). - Coverage: context 93%, review 97%, state 95.70%, claims 99%, overall 91.16%.
- Audit guarantee extended:
TestReplayIncludesPhase5Eventsbyte-comparessqlite3 .dumpafter replaying the full lifecycle includingevidence.submittedandtask.applied(both accepted and rejected branches).
Fixed
- Dead-code unreachable branch in
_handle_evidence_submitted—if commands_run is Nonewas never reachable because the field defaulted to[]. Fixed toif not commands_runwhich catches both None and empty (submitting evidence with no verification commands is meaningless).
[1.3.0] — 2026-05-24
Phase 4: Claims manager. Delivers atomic claim/release/renew/next semantics with lease and heartbeat enforcement, git branch auto-creation, two new bash hooks, a claim skill, and a comprehensive test suite. The plugin now supports the complete claim-based coordination workflow for AI agents working in parallel.
Added
- Claims manager (
claims/manager.py— atomic claim/release/renew with lease and heartbeat semantics; Clock-injected for deterministic tests). - Stale claim detector (
claims/stale.py— runs on every CLI invocation; returns expired claims back to the ready pool with audit trail). - Four new CLI commands:
claim TASK_ID [--worktree] [--force] [--actor],release CLAIM_ID [--force] [--reason],renew CLAIM_ID,next [--actor]. - Hook sub-app:
anvil hook check-claimandanvil hook record-file-change(used by the new bash hooks). - Git ops module:
git_ops/branch.pyauto-createsagent/<task>-<slug>branches on claim (with name-collision suffix, graceful no-op when git absent);git_ops/worktree.pyfor optional--worktreeparallel-checkout. - Two new hooks:
check-claim.sh(PreToolUse on Edit|Write|NotebookEdit; warns when active claims exist) andrecord-file-change.sh(PostToolUse; appends file_changed events to the audit log). - New skill:
skills/claim/SKILL.md— workflow choreography for the claim → work → renew → release loop. - State engine: 4 new event handlers (
claim.created,claim.released,claim.renewed,claim.stale) all routed through_apply_mutationdispatch. - 98 new tests (300 → 398):
test_claims.py(concurrency-critical,claims/coverage 99%),test_git_ops.py(real git per test),test_hooks.sh(11 bash smoke tests), extendedtest_sqlite.pyandtest_cli.py. - Audit guarantee extended:
TestReplayIncludesPhase4ClaimActionsbyte-comparessqlite3 .dumpafter replayingclaim.created→claim.renewed→claim.released; companiontest_replay_includes_claim_stalecovers the stale path.
Fixed
claims/stale.pyevent payload was missing the requiredreasonfield expected by_handle_claim_stale(caught by Wave 3 tests)._handle_claim_releasedwas incorrectly requiringrelease_reason— payload field is optional and the ClaimManager legitimately passes None when no reason is given.
Notes
- Stale claim reaping is automatic on every mutating CLI command (
claim,release,renew,next); users don't need to think about it. - Claims survive without git: when git is absent or cwd is not a git repo, the claim succeeds without a branch and prints a warning (record-only mode).
[1.2.0] — 2026-05-24
Phase 3: Planning engine. Delivers the full planning runtime — deterministic PRD parser, six-dimension scoring engine, dependency and conflict-group inference, eight new CLI subcommands, two new skills, a new agent, and a PRD template doc. The plugin now supports the complete PRD-to-ready-tasks workflow without LLM augmentation.
Added
- Planning engine: deterministic template parser (
planning/template.py— turns structured markdown into Pydantic Requirements/Features/Tasks; full quick-start example documented atdocs/prd-template.md). - Six-dimension scoring engine (
planning/scoring.py— rule-based heuristics for complexity, parallelizability, context_load, blast_radius, review_risk, agent_suitability; explanation string per task). - Dependency and conflict-group inference (
planning/inference.py— subset-overlap heuristic for dependencies, partial-overlap detection for conflict groups). - Eight new CLI subcommands:
prd parse,prd review [--approve],plan,score [TASK_ID],expand TASK_ID(Phase 7 scaffold),review tasks,list [--status STATUS --feature F],show TASK_ID. - Two new skills:
skills/prd/(PRD authoring/review workflow) andskills/plan/(PRD → ready tasks workflow), both following the state-ops imperative-voice and scannable-description conventions. - New agent:
agents/planner.md(PRD-to-tasks specialist; defers tofakoli-crew:guidowhen fakoli-crew is installed; allowed-tools excludes Edit/Write to enforce the "propose, don't mutate" Iron Rule at the tool-permission level). - PRD template doc (
docs/prd-template.md— ~2,500 words; quick-start JSON-to-YAML converter example demonstrates every documented field). - SQLite event router extended with 8 new actions:
prd.parsed,prd.reviewed,prd.approved,feature.created,task.created,task.scored,task.expanded,task.status_changed; all routed via_apply_mutationdispatch; replay-from-empty handles all 8.
Fixed
_insert_task_rowswitched fromINSERT OR REPLACEtoINSERT ... ON CONFLICT DO UPDATEto preserve task row identity acrossplanre-runs.INSERT OR REPLACEis DELETE+INSERT, which tripsON DELETE RESTRICTonclaims.task_idandevidence.task_idonce work has begun. Regression test:test_plan_is_idempotent.
Tests
- 122 new tests (174 → 296).
state/coverage 95.05% (audit-critical),planning/~93%,cli~88%, overall 92.72%. - Audit guarantee extended:
test_replay_includes_new_event_actionsbyte-comparessqlite3 .dumpbefore/after replaying a mixed sequence of all 8 new event actions.
[1.1.0] — 2026-05-24
Phase 2: State engine. Delivers the full runtime core — data models, state machine, SQLite backend, event log, CLI, skill, hook, and test suite. The plugin is now operationally useful for tracking project state.
Added
- State engine: Pydantic v2 models (14 entities) in
state/models.py—Project,Requirement,Feature,Task,Claim,Evidence,FileChange,Snapshot,TaskScore,SnapshotEntry,Config, and supporting enums (TaskStatus,ClaimStatus,EvidenceKind). - Pure state machine transitions in
state/transitions.py— 17 transition functions plusTransitionErrorand gate helper predicates; no I/O, fully deterministic. - Backend Protocol + concrete
SqliteBackendinstate/sqlite.py— WAL journal mode, JSONL event log (events.jsonl) written atomically on every mutation, full replay guarantee. - DDL schema generator (
state/schema.py) — foreign keys, composite indexes, schema versioning table; generates idempotentCREATE TABLE IF NOT EXISTSSQL. - Clock Protocol with
SystemClockandFrozenClockfor deterministic tests — injected viaSqliteBackend(clock=...). - Config loader (
config.py) — readsconfig.yamlfrom the.anvil/directory; Pydantic-validated; falls back to sensible defaults. - PEP 561
py.typedmarker —anvilis now a typed package. - CLI subcommand
init— scaffolds.anvil/directory in the caller's project:config.yaml,state.db,events.jsonl,prd.md,packets/, andsnapshots/. Fixed a wrapper bug (--project "$BIN_DIR"→ wrapper now passes--projectto preserve the caller's working directory soinitscaffolds in the correct location). - CLI subcommand
status— human-readable summary of project state;--hook-formatflag emits compact key=value pairs for hook consumption. - First skill:
state-ops— covers common state inspection and manipulation workflows from within Claude Code. SessionStarthookdetect-state.sh— detects.anvil/state.dbin the project root on session start and surfaces a brief status banner to the agent.- 173 tests covering
state/models.py,state/transitions.py,state/sqlite.py, CLI (init,status,--version),config.py, and thedetect-state.shhook; 94% overall coverage, 95% onstate/. - Audit-guarantee test
test_replay_from_empty_reconstructs_state_exactly— replaysevents.jsonlfrom scratch against an empty database and asserts byte-for-byte equality with the livestate.db.
[1.0.0] — 2026-05-24
Phase 1: Plugin scaffold. No executable state operations ship in this release — this entry records the structural foundation that all subsequent phases build on. Version 1.0.0 follows the fakoli-plugins repository convention that new plugins ship at 1.0.0 regardless of feature completeness (per CLAUDE.md § New Plugin Checklist).
Added
.claude-plugin/plugin.json— plugin manifest declaring name, version (1.0.0), description, author, repository, license, and marketplace keywords.README.md— positions anvil against CCPM and issue-tracker-as-state patterns; documents the "5 must-do-better" list; install instructions (git clone until marketplace publication); Quick Start teaser for the intendedanvil initflow; architecture overview; 8-phase build status table; integration notes for fakoli-flow and fakoli-crew.CHANGELOG.md— this file; Keep a Changelog format.LICENSE— MIT license, copyright 2026 (author attribution kept neutral for privacy).docs/specs/2026-05-24-anvil-v0.md— canonical build specification: data model, CLI command set, MCP tool surface, hook event mappings, phasing plan, and integration contracts.bin/anvil— bash wrapper that invokesuv run python -m anvil.cli;--versionstub returns1.0.0.bin/anvil-mcp— bash wrapper that invokesuv run python -m anvil.mcp_server; stubbed pending Phase 6 with a clean error message instead of a raw Python traceback.bin/pyproject.toml— uv-managed Python project (Hatchling build backend); declares dependencies: Typer, Pydantic v2, FastMCP, and test tooling (pytest, ruff, mypy, responses).bin/uv.lock— locked dependency tree for reproducible installs.bin/src/anvil/__init__.py— package init; exports__version__ = "1.0.0".bin/src/anvil/cli.py— Typer application; single--versionflag functional; all other subcommands stubbed withtyper.echo("Not yet implemented").- Skeleton directories establishing the plugin layout:
skills/,agents/,hooks/,tests/,docs/.