-
.loom/scripts/gh-since.sh— read-only "new merged PRs / new closed issues since watermark N" helper (#1060). The Auditor's Guard-Decision Telemetry Review standing policy and ad-hoc WORK_LOG/changelog cross-referencing both need this exact query shape; composed by hand as a multi-linegh-cached+jq+ shell-loop script, it always fell to the guard's slow structural path and was denied outright at the catastrophicworktree-write-confinementtier despite being 100% read-only (10 logged incidents 2026-08-06..2026-08-13). The new script consolidates every numeric comparison and jq filter inside the file itself (never on the Bash-tool command line), so its own invocation (.loom/scripts/gh-since.sh <last-pr> <last-issue>) has no guard-sensitive shell metacharacter and is registered underguards.readOnlyFastPathExtrain.loom/config.json— admitted at the guard's read-only fast path instead of ever reaching the buggy slow-path masking logic..loom/scripts/tests/test-gh-since.shcovers both the script's own argument validation (no silent default watermark, rejects non-numeric/injection-shaped arguments) and a direct.loom/hooks/guard-destructive-generic.shreplay confirming the typical invocation is fast-path admitted while a deliberately dangerous chained variant still denies.CLAUDE.mddocuments the workaround (the vendored.loom/roles/auditor.mdand.loom/docs/guard-hooks.mdwere left untouched — both are resynced wholesale and any local edit would not survive the nextresync-installed.shrun). -
anvil:paperships regression fixtures for the underclaiming vs. bold-synthesis scoring split (#1048, Phase 2 of the #1046 decomposition). A newanvil/skills/paper/examples/directory (paper's first) carries two hand-authored projects that report the same synthetic study, with the same evidence and the same bibliography, framed two ways:underclaiming-buried-lede/is rigorous, fully sourced, and heavily qualified with its organizing idea demoted to the last paragraph of the Discussion — recorded review 33/44,advance: false, with a namedunderclaiming_buried_ledefinding atblockerseverity and full weight on rigor, evidence sufficiency, reproducibility, and citation hygiene;bold-synthesis-labeled/stakes the synthesis claim in the title and first sentence with each contribution labeled demonstrated / derived / synthesis / conjecture — recorded review 43/44,advance: true, with no overclaiming deduction. The two bodies share a byte-identical Method-through-Experiments span, a byte-identicalrefs.bib, and a byte-identical## Strongest claimbrief section, so all ten points of separation sit in the framing dims (3, 4, 7, 9) that #1047 made symmetric.tests/test_paper_underclaiming_fixtures.pypins the recorded verdicts, the scorecard arithmetic, the evidence-dim parity, and the quoted-evidence verbatimness (viaanvil/lib/evidence_check.py), so a future rubric edit that makes caution cheap or ambition costly fails a test instead of passing silently. Every organization, trace, measurement, and bibliography entry in the fixtures is synthetic — do not cite them. -
anvil:papernames underclaiming/buried-contribution as an explicit failure mode (#1047, Phase 1 of the #1046 decomposition — guidance-layer only).rubric.mddims 3 (Clarity of contribution) and 9 (Rhetorical economy) gain a new §"Dim 3 / Dim 9 — overclaiming and underclaiming are symmetric failure modes": a technically correct, fully-sourced, heavily-qualified paper that buries its central idea after background and qualification now has a named failure mode symmetric to overclaiming, plus an "Ambition is not novelty inflation" carve-out so a labeled bold synthesis/framing/research-program claim ("we propose/organize/build/ argue/conjecture/test") is not penalized as overclaiming.paper-draft.mdrequires the brief to answer six strongest-claim questions (a new## Strongest claimsection,# TODO(operator)-marked when skipped) and requires the drafted opening to state that claim before the defensive literature apparatus.paper-review.mdadds a named underclaiming/buried-lede finding evaluated via an inline cold-reader check modeled on (not delegated to)memo-comprehension.md's blind-read pattern — no new critic infrastructure.paper-litsearch.mdgains "Component-novelty calibration" guidance so positioning against prior art calibrates ingredient-level novelty rather than collapsing a broader synthesis claim to its least-novel component.assets/example-brief.mdgains a fully worked## Strongest claimexample. Prose/guidance-only; the sibling regression-fixture issue exercises these criteria against real content. -
Plain-language-first writing framework, lib foundation (#1043, Phase 1 of 3 for epic #1042). New
anvil/lib/snippets/plain_language.mdcodifies the 7-point framework ("the purpose of writing is to help a reader learn or decide, not to display expertise") — explain in ordinary language before naming a technical term, introduce jargon only when it earns its place, keep the plain explanation even after naming the term, allow more words when they teach, and use a cold-reader restatement test in review (worked example:anvil/skills/memo/commands/memo-comprehension.md). States legal/patent/spec terminology exceptions explicitly (claim language, normative keywords, venue-required vocabulary are exempt from the "prefer plain words" rule, not from the framework as a whole).anvil/lib/snippets/rubric.mdgains a new §"Dim 9 — teaching-oriented length is not padding" clarifying that rhetorical economy penalizes padding and repetition, never patient explanation. New regression fixturetests/lib/fixtures/jargon_dense_unexplained.mdplustest_zero_findings_on_jargon_dense_unexplained_fixtureintests/lib/test_rhetoric_lint.pydocument the boundary this snippet fills: the deterministic lint returns zero findings on polished, jargon-dense prose that nonetheless fails the plain-language test — that judgment is semantic, not something a regex scanner can make. Purely additive lib-level work; no skill's draft/review commands are wired to the new snippet yet (that rollout is #1044 and #1045). -
Per-PR changelog discipline, enforced in the Builder → Judge cycle (#1037). The v0.11.0 cut found
[Unreleased]covering ~28 items while the cycle had merged ~50 morefeat/fix/securityPRs with no entry — two whole new skills among them — all reconstructed by hand at release time.CLAUDE.md§ "Changelog discipline" now states the contract: afeat/fix/securityPR adds its own[Unreleased]entry and carries aCHANGELOG:line in the PR body (every other conventional-commit type is exempt), and the Judge verifies aCHANGELOG: yesclaim against the diff — a falseyesis a blocking finding, the same contradiction tier as a falseTDD: yes. Newscripts/check-changelog-entry.shis the cheap deterministic pre-flight (exit 0 = entry or exemption, 1 = missing, 2 = could not check), usable live against a PR number or offline from a title + body + changed-files list./repo:release's merged-work coverage check is explicitly retained as the backstop, not the mechanism.
anvil:report'sreport-promotestep 6 now invokesack.py's CLI shim instead of re-deriving the ack-file validation algorithm in prose (#1098).anvil/skills/report/lib/ack.py(the nineAckError.mode-value executable specification of the structured--ack-fileacknowledgment contract) shipped fully tested but was never wired intoreport-promote.md— the command doc hand-described the identical YAML/hash checks for an agent to re-implement inline on every promotion, leaving two independent copies of a security-relevant release-gate contract free to drift.ack.pygains apython -m anvil.skills.report.lib.ackCLI entrypoint (the sameuv run --project .anvilshim precedentanvil.lib.sidecaralready uses in this same doc); step 6 now shells out to it as the primary path, with the nine failure modes preserved verbatim as a documented last-resort manual fallback for a session with nopython/uv. No change to the acknowledgment contract itself (schema, validation rules, and operator-facing messages are unchanged) — only to which code path enforces it.
-
anvil:memoirtest suite:test_revise_never_fabricates_mappingno longer breaks on prose reflow (#1054). PR #1033 reworded the fabrication prohibition inmemoir-revise.md; the wording survived intact but a hard line-wrap split the asserted phrase across two lines, so the test's raw-textassertInfailed even though the documented behavior was unchanged. The test now reads the doc through the file's existing whitespace-collapsing_flat()helper (already used at six other call sites against the same file) instead of raw_read(), matching the convention used elsewhere in this test module for phrase assertions against hard-wrapped command docs. No change tomemoir-revise.mditself. -
anvil/skills/deck/commands/deck-imagegen.mdfrontmatter now parses as valid YAML (#1084). The unquoteddescription:value contained a second:sequence (`imagery_policy: generative-eligible`), whichyaml.safe_loadmisparsed as a nested mapping and raisedScannerError— the same class of bug fixed incidentally inip-uspto-provisional-vision.mdby PR #1085. Newtests/lib/test_skill_doc_frontmatter_parses.pysweeps everyanvil/skills/*/commands/*.mdandanvil/skills/*/SKILL.mdfrontmatter block throughyaml.safe_load, asserting each parses to adict, so this class of bug can't ship silently again. -
.loom/scripts/random-file.shnow actually excludes.loom/worktrees/and.git/whenfdis not installed (#1112). Thefind-fallback path had two independent regex-anchoring bugs that silently defeated both its default excludes and its.gitignorehandling:apply_exclusions()misrouted any dotted directory pattern (.git,.loom/worktrees) into the file-extension branch, producing a regex that could never match afind -type fresult, andgitignore_to_regex()'s trailing-slash branch required a leading/that relativefindoutput never has. Verified reproduction: a live worktree file surfaced as a "random" pick. The ~150-line hand-rolled glob-to-regex machinery (apply_exclusions,glob_to_regex,filter_by_gitignore,gitignore_to_regex, andfind_with_glob'seval-based matching) is replaced withgit ls-files --cached --others --exclude-standard(native.gitignorehandling, zero regex conversion) plus a small bash-native glob matcher forDEFAULT_EXCLUDES/--exclude/--include; thefdpath now letsfdhonor.gitignorenatively too (dropped--no-ignore-vcs) instead of re-implementing it via the same buggy post-filter.
-
Corpus-provenance scope-widening check (#1033, Phase 1 of #888). A claim's cited
provenance.mdrow must have matching-or-wider grammatical scope wherever the claim carries a universal quantifier, a superlative, or a negated existential; a narrower row is now a finding even when the underlying fact would otherwise classify VERIFIED/PARAPHRASE_OK. Adds thescope_overreachcritical-flag type (§6) with calibration examples from #888's field evidence, and syncs memoir's rubric/SKILL/command restatements of the flag-type list (five flags → six). No schema change. -
Structural-importance-announcement AI-tell class (#1026). Adds "load-bearing" as a new AI-tell word class alongside the existing self-flattering-adjective class in
STYLE_GUIDE.template.md§3, wires it intoanvil:paper's dim 7/dim 9 default AI-tell check, and rewords the literal "Is every paragraph load-bearing?" dim-9 boilerplate across datasheet, essay, installation, memo, paper, proposal, report, and slides rubrics so drafting agents no longer read the tell word immediately before writing. -
Sidecar
copy— a sanctioned binary/bulk-asset channel for driverless sessions (#1017). A consumer hook that blocks every Bash-channel write into the checkout (redirection,tee,cp,mv,sed -i— a worktree-isolation guard) left a manual/agent session with no sanctioned way to place bytes it did not itself compose: an agent's editing tool is text-only, so a prior version's unchangedfigures/*.pdfcarried forward between versions, a compiledmain.pdf, or the raw stdout/stderr of apdflatexcompile pass destined forcompile-log.txthad no code-enforced path — only ad hoc escape hatches like ashutil.copyfileone-off or a hand-typed "summary" standing in for real compiler bytes. Addsanvil.lib.sidecar.copy_bytes()(+ CLI shimpython -m anvil.lib.sidecar copy <src> <dst> [--force] [--no-verify]), the byte-safe analog of the existingstage/committext-sidecar shim (issue #645): copies a single file or a whole directory tree, staged into a same-parent leading-dot sibling and landed with one atomicPath.rename(the same stage-then-rename shapestaged_sidecaruses for critic directories), with post-copy(size, sha256)byte-identity verification by default (SidecarCopyVerificationError, non-zero exit, staged copy left unrenamed for forensics — never lands an unverified copy). Refuses to overwrite an existing destination unless--force/overwrite=Trueis given, matching every other write primitive in the module.commands/paper-revise.mdstep 8 andcommands/paper-audit.mdstep 4 now name this path explicitly in their non-Python-driver fallback guidance for figure carry-forward and compile-log capture, respectively. (Investigated and ruled out of scope: the deterministic detectors'--write-reviewsidecar writes —numeric_consistency.py,hyperlink_resolver.py,figure_content.py— already write their_review.jsonvia their own Pythonwrite_review_dir(), which itself callsstaged_sidecar(); that is already a code-enforced write, not an agent-typed one, so it needed no change here.) -
Claim provenance — stable anchor identity for drifting corpus citations (#868).
provenance.mdrows (issue #597) cited their supporting corpus passage by a bareSource file+Line range, which is not a stable address: a mid-file edit of the corpus (an insertion, a reflow, an appended correction) silently shifts every row citing text below the edit point, and the citation still resolves — just to the wrong text. Only an exhaustive corpus audit that re-opens every range catches the drift, and only if it re-runs after the corpus changed; a spot-sampling reviewer reading plausible text at the stale range passes it. Canary-hit three times onwalters-family-tree(amemoirproject): a six-line insertion silently invalidated three rows of a terminal-AUDITEDchapter, and the only fix that existed was line-count-neutral hand-editing discipline the framework neither enforced nor knew about. Addsanvil/lib/provenance_anchor.py: eachprovenance.mdrow may now carry anAnchorcolumn — a short verbatim quoted snippet from the cited passage — which is the row's real, content-addressed identity;Line rangeis demoted to a hint.provenance_anchor.py checksearches the WHOLE cited file (not just the hinted range) and classifies each rowNO_ANCHOR/FILE_NOT_FOUND/NOT_FOUND/RESOLVED/DRIFTED—DRIFTEDmeans the anchor text is verbatim-present elsewhere in the file, a distinct signal from a contentMISMATCH/NOT_FOUND.provenance_anchor.py repointmechanically rewrites only theLine rangecell ofDRIFTEDrows, leavingClaim/Source file/Anchor/Notesand every non-drifted row untouched — explicitly not the "fabricating a source-line mapping" failure the drafter/reviser contract prohibits, since the anchor text itself already proves the citation is genuine. Both subcommands are purely advisory/mechanical (never gate, never score, exit0always), the same posture asevidence_drift.py(#857) andprobe_freshness.py(#863) — seeanvil/lib/snippets/provenance.md§"Relationship to #863" for the boundary between this (internal: the evidence never changed, its address did) and #863 (external: the evidence itself rotted). Documented framework-wide inanvil/lib/snippets/provenance.md(new §Section 4a "anchor-drift detection" + §Section 4b "mechanical repoint" + §Section 9 "corpus-editing expectations for consumers") since the contract is shared verbatim bymemoir,essay, andpaper; wired intomemoir-audit's exhaustive corpus-audit sweep andmemoir-revise's provenance carry-forward,paper-audit's corpus-audit sidecar andpaper-revise's carry-forward, andessay-review's back-check (essay ships no audit command, so this is the only exhaustive drift check its provenance tier gets) andessay-revise's carry-forward. Backward compatible: aprovenance.mdrow with noAnchorvalue (every pre-#868 row) reportsNO_ANCHOR— never an error, never a false drift signal, never coerced or bulk-migrated; drift protection simply activates the next time a revise pass touches that row. -
Audit contract — claim perishability (#863). The
kind: tool_evidencecontract conflated two materially different verifications: durable ones (BOM arithmetic, internal consistency — true forever unless the document changes) and perishable ones (a live URL, an HTTP status, a version pin, the SHA of a served artifact — true until the world moves). Both landed on disk as an indistinguishableVERIFIED, so a perishable claim's implicit expiry went unrecorded. The censusapi canary hit this one day apart on a partner-facing proposal: a clean 44-row audit on 2026-08-01 had two of its 41 verified claims false by 2026-08-02, and nothing on disk told the next pass to look. Three additions, all additive: aProbemodel inanvil/lib/review_schema.py(target/method/observed/checked_at, plus optionalclaim,recheck_command, and a per-probemax_age_days) whose existence is the perishability marker — durable verifications record nothing, so there is nodurable: truecounterpart to retrofit; two carriers for it —Review.probes[]for schema-era critics and a standaloneprobes.json(ProbeLog) for the prose-era auditors that have not migrated offfindings.md, unioned by the reader so the contract does not wait on the per-skill_review.jsonmigration; andanvil/lib/probe_freshness.py, which walks every critic sibling across every version of a thread and returns a bounded re-probe checklist —STALE(older than its freshness budget) andNOT-REPROBED(verified against an earlier version and carried forward since). Documented framework-wide inanvil/lib/snippets/audit.md§"Perishable vs durable verifications";proposal-audit(step 9b) now writesprobes.jsonand re-probes carried-forward targets, andproposal-revise(step 6e) consumes the checklist under the issue-#749 "findings are leads, not evidence" discipline extended across time. Purely advisory, mirroringevidence_drift(#857): never gates, never scores, never a critical flag, CLI always exits0. Backward compatible — a sibling with no probe record is reported asunknown_freshness(never a defect, never conflated with an explicit emptyprobes: [], which is the auditor declaring the artifact durable-only). Scoped toproposalfor this initial landing, matching the #857 core-then-adoption rollout shape. -
anvil:paper/anvil:report— BRIEF/refs evidence-drift advisory (#857). New framework primitiveanvil/lib/evidence_drift.pydetects when a thread'sBRIEF.mdorrefs/**change AFTER the latest version was drafted/revised — the gap that let a censusapi thread reachAUDITEDon refs later found contaminated, and aBRIEF.mdretraction go unnoticed after a v1 draft, with no framework signal either time.paper-draft/paper-reviseandreport-draft/report-revisenow record aBRIEF.md/refs/**mtime baseline intometadata.evidence_snapshotin the version dir's_progress.jsonat completion;paper-review/report-reviewcompare the CURRENT thread-root mtimes against that baseline and, on drift, add a non-blocking "Evidence drift" note toverdict.md. Purely advisory, mirroring theNEVER-VISION-CHECKEDposture: it never changesadvance, dimension scores, or the terminal-state gate, and — unlike the pending-marker gate — is never routed throughanvil/lib/convergence.py'sCriticalFlagmachinery, since there is nothing to resolve, only something to re-weigh. A thread with no recorded baseline (pre-#857, or no draft/revise pass since adoption) reports clean, never a false positive. Scoped topaperandreportfor this initial landing; adoption in the other ~11 skill orchestrators is deferred to follow-up issues, mirroring the #847 → #851 core-then- adoption rollout shape. -
anvil:paper— pending-measurement placeholder gate (#842; Phase 1 of parent tracking issue #841). New framework primitiveanvil/lib/pending_marker.py(fifth member of the deterministic-checks family alongsidenumeric_consistency.py,render_gate.py,marp_lint.py,revise_consistency.py) detects well-formed[PENDING <source>]/[PENDING: <source>]placeholders — a first-class convention for a load-bearing number that genuinely doesn't exist yet (a training run still running, a benchmark queued, a vendor quote not returned), documented inanvil/lib/snippets/pending_marker.md, with<!-- anvil-lint-disable: pending_marker -->suppression. A well-formed marker is scored as a known-incomplete disclosure, NOT a defect (no dimension penalty). It surfaces as a distinct, specially-resolvedpending_dependencyCriticalFlag— an additive schema type (no version bump) modeled on theno_goprecedent, with its own priority tier inanvil/lib/convergence.py/anvil/lib/critics.py: visible in the aggregate as an outstanding dependency but never forcingVerdict.BLOCKand never deducting a dimension score. TheREADY/AUDITEDterminal-state gate is enforced separately (viaconvergence.has_pending_dependency_flag/ the CLI exit code), decoupled from the score/verdict path — so a reviser is never instructed to "resolve" an honest marker by fabricating the number (the fabrication failure mode #841 exists to close). Optional<thread>/BRIEF.mdfrontmatterpending_sources:(bare labels or{source, expected_by}mappings, parsed byanvil/lib/project_brief.py::resolve_pending_sources) lets a thread declare which sources it expects to resolve, surfaced asoutstanding_sources/resolved_sourcesfor reporting.anvil/lib/render_gate.pyCheck 6 carves out well-formed markers so the generic placeholder scan never double-flags them. Phase 1 wires this intoanvil:paper(paper-reviewstep 4g at review time with the terminal-state gate in step 7,paper-auditstep 6b as the terminal gate, apaper-reviseno-fabrication carve-out, plus arubric.md"Outstanding dependencies (not critical flags)" section and scoring-guidance note);proposal/memo/reportadoption is deferred to the Phase 2–5 sub-issues (#843–#846). -
anvil:proposal/anvil:memo/anvil:report— pending-measurement placeholder gate adoption (#841, closing out the parent tracking issue's remaining phases). Wires theanvil/lib/pending_marker.pyprimitive (shipped foranvil:paperper the entry above) into the three remaining named skills, following the "adopting the convention in a skill" recipe inanvil/lib/snippets/pending_marker.md:proposal-reviewstep 4l /proposal-auditstep 11b run the gate againstproposal.tex(via--body, since the module's<slug>.md/main.texauto-detect doesn't match this skill's fixed filename);proposal-revisegets the no-fabrication carve-out; theREADY/AUDITEDterminal transition holds on an active marker.memo-reviewstep 4n runs the gate against<thread>.md(auto- detected);memo-revisegets the carve-out; sincememohas no separate audit phase, theREADYterminal transition is held by a distinctreadycomputation (separate fromadvance) documented inmemo-reviewstep 7 andmemo.md's state table.report-reviewstep 4f /report-auditstep 10b run the gate againstreport.md(via--body);report-revisegets the carve-out;report-promoteadditionally re-checks the gate beforeCUSTOMER-READYas defense-in-depth alongside the machine-checkableAUDITEDprecondition. Each skill'srubric.mdgains the "Pending-measurement markers do not incur a dimension penalty" scoring-guidance note and an "Outstanding dependencies (not critical flags)" section mirroringpaper/rubric.md. No lib or schema changes — this is skill-wiring only, reusing the distinctpending_dependencyCriticalFlagtype and terminal-gate contract shipped in the paper Phase 1.
-
memo-review: version-drift check (#746). New step 4m invokes
anvil/skills/memo/lib/version_drift.py(pure-stdlib, deterministic) to compare<thread>.{N}/against<thread>.{N-1}/(and, when available,<thread>.{N-2}/) on seven mechanical metrics — word count, bold span count, bold-word share, hedge-marker count, meta-commentary count, mean sentence length, exhibit count. A finding fires only when a densification metric (bold-word share, hedge-marker count, meta-commentary count) increased in BOTH of the last two version transitions — a single-transition increase stays observational. Every finding is fixed atseverity: major,scope: reduceand is echoed directly intocomments.md(the load-bearing path, sincememo-revise's default--scope importantreadscomments.md, not_summary.md); the check never setscritical_flag. Closes the sensor gap the anvil#241 dim-9 countervailing-pressure fix could not close on its own — no phase in the lifecycle could previously see a cross-version trend, so a memo that densifies a few points every revision looks, at every single review, like a static style choice. -
New skill:
anvil:memoir(#740). Chaptered narrative nonfiction reconstructed from a private evidentiary corpus (family memoirs, oral histories, biography-from-archive, interview-grounded long-form profiles) — the first artifact-class skill to compose dual-corpus claim provenance (#597) and dual voice tiers (#598) active at once in one document, chapter-thread-native (one thread per chapter, assembled viaanvil:project-book), audit-mandatory withAUDITEDas the terminal state (no shortcut toREADY). Ships the skeleton:SKILL.md,rubric.md(9-dim /44,anvil-memoir-v1, sourcing fidelity as the dominant dim 1 at weight 7, ≥39 audit-grade advance threshold), six lifecycle commands (memoir,memoir-draft,memoir-review,memoir-audit,memoir-revise,memoir-figures), templates (BRIEF.md.example+ a LaTeX chapter template with\famphoto/\fullphoto/\marginphotophoto-placement macro stubs foranvil:project-photos),ArtifactType.MEMOIRregistration, 5 regenerated lifecycle agents, and a minimal synthetic worked example (grani-memoir-mini).memoir-auditis the first command to implement the exhaustivekind: tool_evidencecorpus-audit critic (anvil/lib/snippets/provenance.md§Section 4) as a<thread>.{N}.corpus-audit/sibling, coexisting with a general.audit/sibling. Deferred to follow-up issues: cross-chapter consistency checking, a structured facts-register companion input, and a fullnitas-mamadogfood worked example. -
anvil:proposal—cost_basisknob for non-hardware proposals (#840). The proposal template, rubric, and audit assumed a priced, vendor-sourced hardware BOM unconditionally (the Gossamer LAN worked example's shape), leaving a partnership/integration proposal two bad options: fabricate hardware-shaped line items, or omit mandated sections and take a structural hit — the first being outright dangerous in an outward-facing document. Adds acost_basis: quoted | estimated | noneBRIEF frontmatter knob (defaultquoted, byte-identical to prior behavior), mirroring therecommendation_targetprecedent:load_cost_basis()in the skill'slib/project_brief.py(lenient, closed-set, never raises);templates/proposal.tex.j2section 7 branches on the resolved value (estimatedkeeps the three priced tables but estimate-labels every caption;nonedrops the BOM requirement for a short "Cost Basis" section deferring commercial terms to Open Decisions);rubric.mdgains a "Dim 6 —cost_basiscalibration" section so an unsourced estimate in anestimatedthread isn't scored as a sourceability defect; andproposal-auditstep 7 skips the vendor-quote back-check undernone, substituting an estimate-basis check underestimated. -
anvil:proposal—--polishoperator-directed revision flag (#862).proposal-revise's step-4 combined-advance pre-check correctly refused to revise an already-passing version, but its parenthetical recommended manual state surgery as the workaround — deleting a critic verdict sibling (destroying immutable evidence) or hand-editing_progress.json(forging state the framework derives). Adopts the--polish "<reason>"contract fromanvil/lib/snippets/directed_revision.md(the memo/primer precedent): it bypasses the step-4 pre-check only, requires a non-empty reason, recordsmetadata.revision_mode/metadata.revise_force_reasonas an audit trail, composes with the existing--scopeseverity filter, and grants no inherited credit on the next critic pass.proposalmoves from "pending" to "adopted" in the shared snippet. -
anvil:project-book—chapter_filenameresolves as a per-thread{slug}template (#864).build.chapter_filenamewas one fixed, project-wide bare filename joined verbatim onto every thread's resolved version dir, which cannot expressanvil:memoir's slug-echo body contract (<slug>.tex, "neverchapter.tex" per #295) — so every memoir thread in a book build failed the chapter-source check, staged a placeholder, and warned "resolved version has nochapter.tex" (canary: the 15-threadwalters-family-treeproject, which had shipped a consumer-side staging script as the workaround). Two composed changes:collect.py::resolve_chapter_filenamesubstitutes a literal{slug}token with the thread's own slug before locating the source (a token.replace(), deliberately notstr.format— a filename carrying unbalanced braces must never raise mid-build, and a template with no token passes through unchanged, so every pre-existing config is byte-identical); andconfig.py::BookConfig.chapter_filename_for()picks the effective per-document template, an explicit BRIEF value winning project-wide, else a slug-echo artifact type (SLUG_ECHO_ARTIFACT_TYPES, today justmemoir) defaulting to{slug}.tex. -
anvil:memoir— at-cap iteration semantics and the BRIEF override in the BLOCKED notice (#869).memoir-revise's iteration-cap check was a two-line gesture ("at cap → report BLOCKED") that never defined "at cap", never mentioned the paired BRIEF override exists, and carried the sibling skills' off-by-one parenthetical. A canary chapter landedAUDITEDat iteration 4/4 — healthy, terminal, and indistinguishable in the reports from a thread one blocked pass from refusal. Step 3 now documents the full cap resolution order (BRIEF paired override →_progress.jsonfallback →DEFAULT_MAX_ITERATIONS), the explicitN + 1 > effective_max_iterationspredicate, and a refuse/warn/proceed table at the ceiling — the combined-verdict pre-check runs first, so a clean terminus atiteration == capis a normalAUDITEDterminus, never "capped". Adds the BLOCKED-notice contract ported frommemo-revise(state line, trajectory, override pointer, prior rationale echoed verbatim when an elevated cap is already active) plus an explicit "never raises the cap itself" non-goal, and mirrors the resolvedmax_iterations+iteration_cap_rationaleinto every version dir's_progress.json. -
anvil:ip-search— prior-art search skill (#969; opt-in pre-search step code-enforced in #981; wired into the ip skills' positioning critic in #975). Derives queries from a thread'sBRIEF.md§3 inventive-feature inventory, queries PatentsView / USPTO Open Data via stdliburllib, and writes one<thread>/prior-art/<slug>.mdper reference in the frontmatter shape the ip skills already parse. A drafting aid, never an attorney clearance search. -
anvil:diff— local side-by-side prose diff viewer (#925, PR #931). A stdlib-only (http.server+difflib), word-level, read-only HTML diff between two anvil version dirs, a deslop origin/cleaned.txtpair, or two arbitrary files, with optional.review/+ rhetoric-lint overlays. Binds127.0.0.1only; never writes to any input path. -
Codex CLI parity for the installer (#1010, #1014; cross-runtime parity + upgrade/uninstall ownership tests in #1023). The installer now emits Codex CLI skill registration alongside the Claude shim, generates a consumer
AGENTS.mdentry point, and extendsanvil:help's degraded-mode fallback to cover Codex consumers. -
Rhetoric-lint / deslop growth: 18 AI-humanizer-corpus default rules (bucket a of #920, PR #927), a
sentence_variancerhythm-uniformity rule kind (#928), a deterministic no-fabrication diff gate in the deslop revise loop (#929), a long-sentence-density default rule (#771), and emphasis-density / no-meta-commentary / no-warning-emoji default rules (#760). -
anvil:memoadditions: cold-reader comprehension critic (#753, PR #770), resolution modes + net-delta reporting formemo-revise(#748, PR #766), an evidence-grade rendering rule keeping grade tags out of reader prose (#751, PR #773), and a self-derived calibration mode formemo-redteamwhen no strongman exists (#915). -
anvil:paper— deterministiccandidates.bibsyntax lint in litsearch (#1001). -
anvil:deckadditions: pdftotext-based text-layer-completeness gate (#983, PR #988);paritynow reframes BRIEF-quarantined figures instead of promoting them toonly_in_memo_economic(#917). -
anvil:project-shareadditions:export.cover/cover_asdurable cover-note knob (#776), a report-only dangling-citation lint (#758, PR #775), and a runnablelib/cli.pyargparse entry point (#768).
-
anvil:memoir— the final revision undermax_iterationscan now actually be validated (#933).memoir-revise's #869 cap predicate (N + 1 > effective_max_iterations) counted the free initial draft the same as every framework-mandated repair, so the LAST version a thread could afford could be written (e.g. a fabrication fix) but never scored — the write that fixed the defect was itself the write that exhausted the budget, and the thread BLOCKED with an unvalidated, possibly still-defective final version and no visibility into why. Canary-hit twice on the samewalters-family-treesession (05-quebecas a near-miss, an operator-raised cap on03-garrardreasoning "leave room for one further pass" — i.e. manually reserving a slot the lifecycle itself did not).max_iterationsnow counts revisions, not version dirs: the predicate becomesN > effective_max_iterations(the draft,<thread>.1/, is never chargeable), so a chapter gets its one free draft plusmax_iterationsrevision opportunities, and the revision that fixes the last defect always has a slot to be critiqued. Step 3 also prints an explicit pre-write budget notice before writing the version that would consume the final slot — the budget consequence is stated before the exhausting write, not only after it in the BLOCKED notice — and the BLOCKED notice's state line now names what happened (<thread>.{N} is a final version written and unvalidatable under the current cap) rather than a bare "hit the cap", so it reads distinctly from the healthy "converged, slot(s) unspent"AUDITEDterminus, which never reaches this notice at all.memoir.md'sItercolumn and SKILL.md's override contract are updated to match (revisions consumed, not the raw version-dir count). Map-only repairs still consume a revision slot exactly as before (#869) — only whether the draft itself counts changed. The same version-vs-revision off-by-one is duplicated skill-locally acrossmemo/paper/report/deck/slides/ip-uspto/ip-uspto-provisional/installation/proposal/datasheet; this fix is scoped tomemoironly (issue #933's canary), left as a known follow-up for the other skills. -
Deck theme: invisible ask-slide pagination + zebra-striped tables (#906).
deck-visionrasterized a 13-slide deck at 96 DPI and found two systematicanvil-deck.cssdefects. First, the pagination glyph (section::after) is styledvar(--anvil-muted)(#6b6b6b) with no override for dark-background_classslides — measured contrast against the#1f4e7aaccent background was ~1.79:1, well under WCAG's 3:1 floor for incidental text and visually absent in the render, on the ask slide (the one slide most likely to be referenced by number in a follow-up email) and the section-divider slide. Second, tables rendered with alternating-row zebra shading inherited from the imported Marpdefaulttheme, contradicting the stylesheet's own comment ("Tables — clean, no shading on alternating rows (chartjunk)");section.askalready defeated this locally for ask-slide tables, but ordinary (light-background) slides had no equivalent reset. Addssection.ask::afterandsection.section::afteroverrides (rgba(255, 255, 255, 0.75), ~5.7:1 against the accent background) and a theme-widesection table tbody tr:nth-child(even)transparent reset. -
slide-content-overflowno longer charges standalone-image alt text as body prose (#905). The lint's standalone-image branch was gated onlen(raw_line.strip()) < 250(anvil/lib/marp_lint.py), so a long, accessiblestring could push the line past the threshold and fall through to the body-paragraph branch — where the alt characters, never rendered on the slide, were double-charged as rendered prose (per-70-char text rate) on top of the image's own cost, penalizing decks for writing better accessibility text. Rekeys the branch on line structure — reusing the existing_STANDALONE_FIGURE_REanchored whole-line image pattern (already load-bearing forfigure-italic-supporting-line-too-long) instead of a length heuristic — so a standalone image line is recognized as an image regardless of alt-text length. Adds a regression test asserting identical estimated cost for slides differing only in alt-text length (40 vs. 400 chars). -
essay-reviewno longer calibrates voice fidelity against the artifact under review (#890). Avoice.corpusglob naturally points at the consumer's published archive — correct when drafting a new thread, circular when reviewing a revision of an already-published one, because the thread's own prior published form sits inside its own calibration base for dim 2 (Voice fidelity, weight 7 — the rubric's heaviest dimension). Flagged independently by four consecutive reviews in one session, each working around it by hand with the same unrecorded caveat — and on a two-note corpus, hand-excluding the self-match left a single exemplar with no framework signal that the base had thinned.anvil/lib/project_brief.py::resolve_voice_docsgains an optionalexclude_self_slugkwarg: when the reviewing command passes the thread's own slug, its published form is dropped from the resolvedcorpusvia two unioned sources — automatic inference (a matched filename stem, after stripping one optional leadingYYYY-MM-DD-date prefix, exactly equals the slug) and an optional declaredvoice_corpus_excludeon the thread'sdocuments:entry (the escape hatch for a publish-path shape the automatic rule cannot infer, e.g. a title-cased or nested filename — same scalar-or-list shape asspec_ref/code_ref).essay-reviewstep 4 now passesexclude_self_slug=<thread>unconditionally and records the exclusion in_summary.md.voice_grounding.corpus_excluded(path + reason per dropped path), plus acorpus_thin: truenote when fewer than 2 exemplars remain after exclusion — auditable instead of silent, per the four reviewers' hand-written caveat.None(the default) is a complete no-op for every otherresolve_voice_docscaller (memo, report, memoir, essay-draft). -
Shared loader for hyphenated skill
lib/packages, replacing seven copy-pastedimportlibincantations (#879). Four utility skills'commands/*.md(project-scout,project-book,project-photos,help) told the operator to "Load the skill lib" and then showed a bareorchestrate.run(...)call with no import shown — silently assuming a working import already happened. That undersold two real footguns: the lib modules use relative imports among themselves (orchestrate.pydoesfrom .cluster import ...), so the directory must be loaded as a package, not just added tosys.path; and skill directory names likeproject-scoutare not valid Python identifiers, soanvil.skills.project-scout.lib.orchestrateis a syntax error even thoughanvilitself is an importable package. Every vendored test suite's_<skill>_skill_lib.pyhelper (project-scout,project-migrate,project-book,project-photos,project-share,help, and rubric-rebackport's_skill_lib.py) independently re-derived the sameModuleSpec(is_package=True)+ manualsys.modulesregistration trick, each maintaining its own hand-written "dependency-safe load order" list. Addsanvil/lib/skill_lib_loader.py(load_skill_lib_package/import_skill_lib_module/load_skill_lib): registers a skill'slib/directory under a synthetic, collision-safe package name (project-scout->project_scout_lib) and then lets ordinary import machinery resolve any requested submodule's relative imports — no dependency-order list to keep in sync, and no risk of the stub-then-exec-out-of-order failure mode that breaks graphs likeproject-migrate'sadopt_family.pydoingfrom .adopt_vn import _FOREIGN_TAG_SUFFIX_RE. The four affectedcommands/*.mdnow show the actual working load incantation; all seven vendored test helpers delegate to the shared helper instead of duplicating it. Companion to #877; the original repro (a consumer-install path that didn't exist) was already fixed by PR #763 — this closes the remaining invocation-ergonomics gap the curator's re-verification againstmainsurfaced. -
numeric_consistency's percent check no longer pairs unrelated numbers sharing a paragraph (#854). TheX% ofproportion check validated a percent claim against the NEAREST fraction shape in its same-paragraph (±1) window regardless of subject, soanvil/lib/numeric_consistency.py::_check_proportion_claimproduced a recurringpercent_mismatchfalse positive at the censusapi consumer: "An ink gate resolves 27.3% of readable cells" sharing a paragraph with "18/18 pages of the test enumeration district registered correctly" computed18/18 = 100% != 27.3%, even though the two numbers describe different subjects (cells vs. pages). A percent claim now only pairs with a fraction that shares a lexical anchor — a content word present in both the claim's sentence and the fraction's sentence, via new_anchor_tokens/_sentence_spanhelpers — UNLESS some fraction in the window already numerically satisfies the claim within tolerance (that "prefer a matching pair over a mismatching one" behavior is unchanged and still checked first, regardless of subject). A window with only unrelated-subject fractions now silently skips instead of flagging, matching the module's documented FP-averse/conservative contract; a genuine same-subject mismatch across sentences still fires. -
vocab_reminder's shipped-default word list now reachable in installed consumer repos (#800).anvil/lib/vocab_reminder.py'sDEFAULT_WORD_LIST_PATHresolves relative to__file__as<lib>/../templates/voice/vocab.words.txt— correct in the dev tree, but from the installed module's location (.anvil/anvil/lib/vocab_reminder.py) it resolved to.anvil/anvil/templates/voice/vocab.words.txt, a pathinstall-anvil.shnever populated (Stage 5 copied onlyanvil/lib; Stage 7.9 scaffolds the same source file to a different, consumer-owned destination,.anvil/voice/VOCABULARY.words.txt, gated onessay/memobeing selected). The documented "works out of the box" zero-config fallback was dead on arrival for every installed consumer, returningno words available to sample(exit 1) instead of a sample. Stage 5 now ships one narrow, unconditional copy —anvil/templates/voice/vocab.words.txt->.anvil/anvil/templates/voice/vocab.words.txt— mirroring the "importable mirror, always refreshed" discipline already used foranvil/lib -> .anvil/anvil/lib, rather than fixing the path-resolution logic itself (the constant was correct once the file it expects actually exists at that path). -
sidecar's.bakreplace surface hardened (#885). Three narrow residuals surfaced during Judge review of PR #884, all inanvil/lib/sidecar.py'sstage_replace/commit_replace/abort_replace/recover_interrupted_replacefamily. (1)stage_replace()unconditionallyrmtreed an existing backup before its move-aside rename — including one thatrecover_interrupted_replacehad deliberately preserved under its "never deletes content it cannot prove is preserved" invariant, which the consuming doc's very next prescribed action would then destroy silently. It now refuses withFileExistsError(naming the unpreserved files) when the backup holds content not proven present infinal_dir. (2) The redundancy predicate_unpreserved_backup_entries()is content-aware rather than name-only: it recurses into subdirectories and compares each file's size + content hash against itsfinal_dircounterpart, and bothrecover_interrupted_replace()andstage_replace()share the one predicate. (3)staged_sidecar()gains anallow_orphaned_backup=Falsedefault, applying the orphaned-backup guardstage_enter/commit_stagedalready carried;project-migrate/lib/adopt_review.py's_convert_one/_rescore_one, which legitimately manage their own same-named.bakmove-aside around the call, opt in explicitly. -
anvil:project-migraterenames foreign.mdbodies and accepts--artifact-typeon bare-shape migration (#878).anvil:essaynamesproject-migrateas the supported path for migrating a consumer'spost.mdcorpus, but bare-shape synthesis defaulted every thread toartifact_type: investment-memoand could never rename a body filename outside anvil's own historical fixed-name set — so a foreign pipeline that had independently converged on the anvil grammar (<slug>.<n>/post.md+<slug>.<n>.review/) planned the directory nesting correctly and zero body-file renames, the circular gap being that the declaration needed for the rename would have to come from the very BRIEF the run was synthesizing.build_plan()/orchestrate.run()now accept--artifact-type(validated against the two-tier #394 registry,PlanErrorpre-mutation on an unregistered value), applied to every synthesizeddocuments:entry on a bare thread with no TODO marker. Independently, the planner detects a single consistent non-canonical.mdbody filename per bare thread and plans its rename to<slug>.md— reviewable in the dry-run plan, TODO-marked — leaving multiple distinct candidates unrenamed with an operator-facing ambiguity note. -
render_gateglyph verification no longer counts glyphs inside LaTeX comments (#856)._verify_source_glyphs's source sweep counted every non-ASCII codepoint in.texfiles, including characters used purely in comment-only section rules (%% ── 4. The Item Pool ──, box-drawing U+2500). Those glyphs never reach the PDF, so the censusapi consumer saw 541 source occurrences vs 0 rendered reported as a glyph failure — shape-identical to a real fontspec fallback silently dropping a character, and costing a manual adjudication pass._strip_nonrendered_regionsnow takes alatexkwarg and strips unescaped%-to-end-of-line before the codepoint sweep, mirroringnumeric_consistency's existing flavor-gated LaTeX comment mask (a bare%in markdown is a percent sign, not a comment opener);_verify_source_glyphsgates it onpath.suffix == ".tex". Two regression fixtures prove comment-stripping doesn't swallow a real glyph drop. -
render_gateplaceholder patterns cover qualified[TBD …]and add[FIXME …](#855).DEFAULT_PLACEHOLDER_PATTERNSandDEFAULT_MEMO_PLACEHOLDER_PATTERNSmatched[TBD]only exactly, so qualified forms like[TBD: vendor quote]slipped past the placeholder gate silently, and there was no[FIXME …]bracket pattern at all (only the memo-side<!-- FIXME … -->comment form). Both TBD patterns now accept an optional trailing qualifier and both tuples gain the FIXME analog, with a doc comment at each tuple recording that[PENDING …]markers are deliberately excluded — handled by the dedicatedanvil/lib/pending_marker.pygate (#842) — so a future contributor doesn't reintroduce the redundant pattern. -
numeric_consistencyno longer parses season/fiscal-year labels as fractions (#836). Labels like2018/19and2018/2019were matched by the bare slash-fraction pattern in_FRACTION_RESand fed to_extract_shapesas fraction evidence (a=2018, b=19), producing a boguspercent_mismatchwhenever an unrelated "X% of" claim shared a paragraph — canary: the studioall-dogs-go-to-heavenmemo thread, where a "2018/19 baseline" citation tripped the advisory on bothmemo.1andmemo.2, the standing workaround being a per-line lint-disable comment in the body. Adds_is_season_fiscal_year_label(), reusing the existing_YEAR_MIN/_YEAR_MAXcalendar-year heuristic, scoped by identity check to_SLASH_FRACTION_REonly — the "of"/"out of" fraction pattern and_PAIR_RESare untouched, so47 of 94/47/94and non-year fractions like12/45keep working unchanged. -
install-anvil.shno longer copies dev-machine__pycache__/*.pyc/.DS_Storecruft into consumer installs (#818). All fourcp -R-based tree-copy helpers (copy_tree,replace_tree,copy_lib_preserving_overrides,copy_skill_body_excluding_lib) physically shipped whatever bytecode caches a dev checkout had accumulated from a normalpytestorpip install -e .run, making installs non-deterministic and risking bytecode compiled under the wrong Python version — the same bug class already fixed forproject-shareexports in #756. Adds a sharedstrip_pycache_artifactshelper (portablefind -execacross BSD/GNU find) called at the end of all four copy sites, and excludes the same patterns from the override-detection hash/diff helpers (dir_hash,dir_hash_body_only,dirs_identical,dirs_identical_body_only) — otherwise a source tree carrying stray build artifacts would no longer hash identically to its (always cruft-free, post-strip) installed copy, and every untouched-since-install skill would spuriously read as consumer-modified on the very next run. -
anvil:deck—deck-design's additive-ness gate resolvedimagery_policyagainst the wrongBRIEF.md(#984). The deck project-org model nests two distinct files sharing the literal nameBRIEF.md: a project-levelBRIEF.md(frontmatterdocuments:list) and a thread-levelBRIEF.md(frontmatterimagery_policy:).deck-design.mdstep 7b named its resolution source as bare "BRIEF.md" — the one call site, among four documenting the same resolution order, that omitted the<thread>/qualifier — so a design-critic pass reasoning at the project root could read the project-level file, find noimagery_policykey, and silently fall through to the built-indeterministic-onlydefault even when the thread's own BRIEF declaredgenerative-eligible. Extracts the inline resolution block previously duplicated only insideimagegen.py::run_imagegeninto a standalone, reusableresolve_effective_imagery_policy(thread_dir, portfolio_path, config_path=None) -> (policy, policy_source)inanvil/skills/deck/lib/imagegen.py;run_imagegennow delegates to it (behavior-preserving), anddeck-design.mdstep 7b + thedeck-audit.md"Generative-imagery audit" section are rewritten to name it as the single source of truth for the<thread>/BRIEF.md∪.anvil/config.jsondeck.imagegen.default_policy∪ built-in-default resolution order (issue #547).imagegen_additive.py::gate_should_run's docstring cross-reference is updated to point at the new function.test_additive_gate_docs.pygains a regression assertion thatdeck-design.mdnames<thread>/BRIEF.mdexplicitly, andtest_imagegen.pygains direct unit coverage for the extracted resolver, including a case that plants a sibling project-levelBRIEF.mdalongside the thread-level one to prove the resolver reads the correct file. -
Installer fixes: version now reads from a root
VERSIONfile, with machine-local install fields split into a gitignored sidecar (#896);*.egg-info/appended to.anvil/.gitignore, coveringuv sync's editable install (#880);__pycache__/*.pyc/.DS_Storecruft stripped from every tree-copy site (#818, PR #821); thedeck_imagegenextra mirrored into the generated consumerpyproject(#953). -
anvil:deckimagegen/lint fixes: speaker-notes imagery-prompt extraction stops at the first blank line (#955); the imagegen field-block terminator accepts multi-word bold markers (#966); figure-legibility glyph height scales by actual render DPI (#908);marp_lintstandalone-image detection keys on line structure, not length (#907). -
anvil:ip-uspto-provisional— the figurer no longer conflates numeral-drift with visual verification (#987). -
Lib fixes:
render_gateattributes overfull boxes to their\input'd source file (#967);provenance_anchorparses every claim table, not just the first (#934, PR #936); hyperlink/rhetoric-lint scans cover the whole body, not per line (#892); memo scoring tables tolerate markdown emphasis and error on zero-row scorecards (#916); the evidence-drift advisory's mtime comparison is tolerance-aware and its hand-recording fallback is dropped (#1021); the four vision critics gitignore their regenerable per-page PNGs (#738, PR #739).
-
Pillow floor bumped to
>=12.3.0, resolving 13 Dependabot alerts (#765). -
Identifier scrub: real client/project identifiers in example files replaced with synthetic ones; the token-filename example now uses a placeholder address; operator home paths in spec example files genericized (#942, PR #943).
Two anvil:paper correctness fixes surfaced by the geode-fem canary: the
reviser now applies the same author-voice guidance the drafter does and the
reviewer ships a default self-flattering-AI-tell prose check, and the
orchestrator now surfaces the previously-silent paper-vision critic so
figures no longer reach terminal AUDITED without a visual-review
recommendation.
anvil:paperorchestrator now recommendspaper-vision(#731). Thepaper.mdnext-command table splits itsREADY/AUDITEDrows by figure +.vision/-sibling presence: a thread with figures and no vision sibling is routed topaper-vision, and aNEVER-VISION-CHECKEDanomaly is flagged — closing the gap where a figure-bearing paper could reach terminalAUDITEDwith its figures never visually inspected. Text-only and already-checked threads route byte-identically; theAUDITEDdefinition and rubric contract are unchanged.
voice.mdnow loads symmetrically inpaper-revise(#732). The reviser — which rewrites prose across every version — now applies the same consumer.anvil/skills/paper/voice.mdauthor-voice guidance thatpaper-draftalready honored. Additionally, the reviewer's D7/D9 prose dimensions ship a default self-flattering / virtue-signaling AI-tell check (the "honest/candid/frank/uncomfortable X" adjective class), firing for every consumer without a hand-authoredvoice.md, with the semantic-work exception preserved. No rubric total/weight/threshold change (/44, D7=4, D9=4, ≥35).
Completes the anvil:spec artifact class (epic #697, all four phases) and
adds anvil:help, an onboarding/orientation skill — bringing the catalog
to 20 skills. Also lands a wave of consumer-install robustness fixes
surfaced by the botho canary running anvil:spec against a real
implementation (which found 3 real spec↔code divergences along the way).
- New utility skill
anvil:help(#725). A strictly read-only orientation tool for consumers: it introspects the installed skill set (from.anvil/install-metadata.json, falling back to a.claude/skills/anvil-*/scan) and prints a two-tier view —anvil:helpoverview (installed skills grouped artifact-vs-utility, the common lifecycle with its per-skill variations, a "start here" pointer) andanvil:help <skill>deep-dive (real command set, rubric total/threshold, thread layout). Describes only what is installed; writes nothing. anvil:helpinstalls unconditionally regardless of--skills=filtering (#728). A Stage 4 always-on allowlist ininstall-anvil.shunionshelpinto the selected set after the--skills=filter (dedup-safe, no-op on full installs, typos still error, and the carve-out does not widen to other utility skills) — so orientation is present precisely in the filtered installs where it is most needed.anvil:specworked example (#709), completing epic #697. A vendored, trimmed snapshot of botho's terminal-AUDITEDbotho-bridge-specthread underanvil/skills/spec/examples/— exercising the realcode_refthree-way audit, the implementation-status register, and live% anvil-const:markers — plus anexpected-thread.N/README.mdstructural-contract doc and a parse test. Theanvil:specclass is now complete end-to-end: skeleton (#706), three-way verdict + register (#707), constant gate (#708), worked example (#709).code_ref/spec_refaccept a list of paths/globs (#719). Multi-crate / non-contiguous implementation roots can be declared as a list, not only a single scalar glob.
- Atomic-sidecar CLI invocations are consumer-reachable (#724). The skill
command docs now invoke the sidecar shim via the
uv run --project .anvilwrapper (dual-form Primary-tier headers), fixing the unreachable bare-python -m anvil.lib.sidecargap in.anvil/-vendored consumer installs; the manual-mv manifest check is now count/ls-based (robust under restricted-statsandboxes); andspec-auditgains adisposition_countsworked example. anvil:specaudit & adoption guidance (#709). Documents the claim-driven large-tree sweep strategy (extract normative claims, then grep the resolvedcode_refpaths per-claim — the 405-file friction), thecheck_constant_consistency_multidict[label→text]signature, and the_progress.jsonscaffolding prerequisite for adopted specs.
- Malformed
code_ref/spec_refsurfaces as a major finding instead of silently degrading to tier-inactive (#718): a bad path resolves tomissing=True, never a silentNone. - Full installs prune upstream-removed / renamed skills (Stage 7.6, #720): stale skill directories from a prior install are cleaned up.
- Audit commands cross-reference the
findings.mdharness-guard fallback (#717) across six audit command docs.
- Skill rename:
anvil:pub→anvil:paper(#694). The research-paper skill was the least self-describing name in the catalog (pubreads as publish / public / pint); every sibling skill names its artifact. Hard rename in one PR, no deprecation alias: the skill directory (anvil/skills/pub/→anvil/skills/paper/), all command names (pub-draft→paper-draft,pub-review→paper-review,pub-litsearch→paper-litsearch, …), the SKILL.md frontmatter/name (anvil:paper), the generated per-phase agent shims (anvil-pub-*→anvil-paper-*), the installer's skill enumeration, and the README / CLAUDE.md skill tables all move topaper. Migration: switch--skills=pub→--skills=paperon your next install (invoking--skills=pubnow prints a clear redirect error). Existing thread directories are slug-named, not skill-named, so they are untouched. Two input-alias compatibility shims keep existing consumer data working: (1) aBRIEF.mddocuments:entry carrying the legacyartifact_type: pubstill parses — it normalizes to the canonicalArtifactType.PAPER(input-only; nothing emitspub); (2) therubric-rebackport/project-migratecatalogs recognize legacypub.mdbodies andartifact_type: pubBRIEF entries, mapping them to the currentpaperskill. The rubric_id literals stay frozen asanvil-pub-v1/anvil-pub-v2/anvil-pub-<venue>-v1/anvil-pub-vision-v1: a rubric_id is a versioned identity already stamped onto existing consumer reviews, decoupled from the skill directory name — a rename does not bump the rubric version.
- New artifact class
anvil:spec(Phase 1 skeleton) (#697/#706): normative technical specifications — protocol whitepapers, wire-format specs, consensus rules, API contracts — maintained truthfully against their implementation. ShipsSKILL.md, a 9-dim /44 normative-correctness-dominant rubric (anvil-spec-v1; dim 1 Normative correctness at weight 7, ≥39 audit-grade advance threshold), six lifecycle commands (spec,spec-draft,spec-review,spec-audit,spec-revise,spec-figures), BRIEF + LaTeX-body templates, a README, and skeleton tests. Borrowsanvil:report's /anvil:primer's lifecycle shape (parallel review+audit, source-of-truth- optional PDF) as a NEW skill — not a parameterization (per "skill identity = artifact identity"). Phase 1 of the #697 epic; the three-way audit verdict + implementation-status register (Phase 2 / #707), the deterministic cross-table constant-consistency gate (Phase 3 / #708), and the botho worked example (Phase 4 / #709) are deferred.
- Optional
code_refcompanion input for spec threads: aBRIEF.mddocuments:entry may declarecode_ref(a path/glob naming the implementation the spec normatively describes — commonly a glob over a multi-file source tree). The mirror image of primer'sspec_ref, resolved project-root-first then consumer-root by the newanvil/lib/project_brief.py::resolve_code_ref/ResolvedCodeRef(shipped as a standalone mirror ofresolve_spec_ref, not a generalized resolver — see the design note inproject_brief.py). Activation follows the #428/#449 posture: declared+resolves → the spec↔implementation consistency tier is active; absent → silent-off +majorfinding; declared-but-missing →majorfinding, graceful (never a crash, never a false critical flag).ArtifactType.SPECis registered as a skill-identity artifact type (18 registered / 11 skill-identity types). anvil:specthree-way audit verdict + implementation-status register (Phase 2) (#697/#707):spec-auditnow performs the full spec↔code_refconsistency sweep (every constant / struct / formula / validity-predicate claim) and, on a contradiction, emits ONE critical flag —implementation_contradicts_spec— carrying a mandatory three-wayDisposition: (a)spec-wrongroutes to the normalspec-revisepath (fix the spec to match the code); (b)code-wrongemits a copy-pasteable operator-escalation block (quoted spec + code + a suggested consumer-repo issue) and blocks advance until the code is fixed or the operator overrides viaspec-revise --override-code-wrong "<reason>"(non-empty rationale, modeled onmemo's NO-GO--override-no-go) —spec-reviseNEVER silently rewrites the spec toward a vestigial code path; (c)intentional-gapis register-suppressed when a matching## Implementation statusrow exists, or flaggedunregisteredotherwise. The discrimination lives in command-doc +findings.md/verdict.mdconventions (aDispositioncolumn +_summary.md.spec_consistency.disposition_counts) — NOreview_schema.pychange (single free-form flag type, deliberately not three types). Auditor discipline: never default an uncertain contradiction tospec-wrong; when uncertain, default tocode-wrong(escalating a true spec-wrong costs one confirmation; silently spec-editing a true code-wrong recreates the botho near-miss). Adds the first-class## Implementation statusregister (a live/target/status/tracking table inspec.template.tex+ a SKILL.md contract — operator/drafter-authored, not auditor-generated);spec-reviewraises amajorfinding for an unregistered target-state claim (the prose-side twin of the audit's mechanical code-side check, with the division of labor documented in both command docs). Includes a four-fixture disposition-discrimination test (test_audit_disposition.py) covering spec-wrong / code-wrong / intentional-gap-registered / intentional-gap-UNREGISTERED (the near-miss shape), asserting acode-wrongcase NEVER routes to a spec edit. No consumer-repo issue-filing automation — escalation is a human-actionable note.anvil:specdeterministic constant-consistency gate (Phase 3) (#697/#708): a new skill-localanvil/skills/spec/lib/constant_consistency.pyextracts marker-declared named constants (% anvil-const: name=… value=… [unit=…]— standalone or inline table-row-comment) and\newcommanddefinitions across the spec's multi-file LaTeX and flags same-name / different-value occurrences before the expensive content review, feeding the internal-consistency rubric dimension. Exact string-normalized comparison (a normative constant matches or is a defect — no numeric tolerance);unit-mismatchandmalformed-declarationare separate lower-severity finding kinds; marker-driven only (no prose extraction in v1). Wired intospec-reviewas a step-3b pre-flight gate; the marker convention is documented for drafters inSKILL.md,spec-draft, and the template. Skill-local per "wait for the second consumer" — not promoted toanvil/lib/. (Only the Phase 4 botho worked example / #709 remains deferred, blocked on the upstream dogfood run.)- First-class operator-directed revision (
--polish) (#691): the*-revisecombined-verdict pre-check conflated "passed the advance threshold" with "nothing worth fixing" — once a thread advanced (and, for audit-bearing skills, the audit was clean) the command reported terminal and exited without writing, leaving operators to prompt-hack around the command to spend one more iteration on critic-enumerated minor improvements.memo's already-shipped--polish "<reason>"contract is now generalized into a shared snippet (anvil/lib/snippets/directed_revision.md) and adopted byanvil:primeras the second consumer: a required non-empty reason bypasses ONLY the pre-check early-exit (iteration cap + critic-completeness still apply), records an audit-trail-onlyrevision_mode/revise_force_reason, grants no inherited credit (a fresh critic pair must re-score), and keeps the default no-flag behavior byte-identical. The remaining ten skills are an enumerated follow-up rollout. - Four deterministic PDF-render gates from the primer canary (#692):
a
\pandocboundedoverride capping images at0.85\textheightso a tall figure plus a multi-line caption no longer overflows the page (shipped in a primer-localpandoc-defaults.yaml, co-located with theFigure N —/\captionsetup{labelformat=empty}caption convention); a source-driven glyph-verification check inrender_gate.py(sweeps ALL non-ASCII codepoints — excludingZswhitespace and non-rendered URL-target / HTML- comment regions — and asserts each survives inpdftotextoutput, catching silent font glyph drops by construction); an embedded-image assertion (![…]()ref count vspdfimages -list); andcheck_mmdc_launchable()inrender.py(a two-stage PATH-plus-probe that catches anmmdcwhose puppeteer Chrome is absent, distinct from the presence-onlycheck_mmdc_available()). anvil:primerdraft-time figure placement (#690): the drafter now emitsreferences and records afigure_planin_progress.json;primer-figuresis un-gated from the terminalAUDITEDstate and renders to exactly those paths, so the review/audit critics finally score figure captions and placement (rubric dims 3/7). Closes the structural gap where the figures phase could never satisfy its own "reference the image" guidance and PDFs shipped image-free.anvil:primerBotho worked example (#693): a trimmed, provenance- stamped snapshot of the first real consumer run (botho#881 → PR #900) is vendored underanvil/skills/primer/examples/botho/with a BRIEF-parse regression test, matching the shape six sibling skills already ship.- DataCite fallback in
anvil/lib/cite.py(#696): on a Crossref 404/406 for a syntactically valid DOI,resolve()now falls back toapi.datacite.orgbefore failing, so Zenodo / software / dataset DOIs resolve to first-classBibRecords instead of being demoted to unverified leads. Stdlib-only, cache-first, cassette-tested; the paper-litsearch verified-or-dropped invariant is preserved (a DOI unknown to both registries still raises).
anvil:reportpandoc-defaults.yamlfailed to parse under pandoc 3.x (#701): the page-number CSS used theinclude-in-header: - text:form, which a pandoc defaults file rejects (it expects file paths there). The content now threads throughvariables: header-includes, with a parse-contract test (mirroring the primer asset from #692).- Committed
anvil/lib/*_schema.jsonhad drifted from their pydantic sources (#714): regeneratedreview_schema.json(missing theNO_GOverdict from #559) andrubric_schema.json(stalepubrubric path from the #694 rename), and added a drift-guard test asserting the committed files match a freshexport_schemaregeneration. - Internal test-pin hygiene:
memo'sTestSkillIdentityArtifactTypespins were stale against the registered artifact-type set (missingArtifactType.PRIMER) and failing on a clean tree (#702); a pre-existingrubric-rebackportcatalog-drift guard failing on the unregisteredprimerrubric was backfilled alongside thespecregistration (#706).
One new artifact class plus the upgrade-path reconciliation pass the
0.8.0 tractatus canary asked for: anvil:primer joins the catalog for
long-form pedagogical explainers, and the installer now cleans up after
what pre-0.8.0 installs left behind — stale unscoped agent files are
pruned on a --skills= narrowing, and .anvil/ artifacts committed
before the shipped .gitignore existed are detected with a remediation
hint (opt-in --fix-tracked to untrack, never auto-committing).
- New artifact class
anvil:primer(#686): long-form pedagogical explainers — a teach-from-intuition companion to a formal spec (the "Mechanics of MobileCoin" genre). ShipsSKILL.md, a 9-dim /44 pedagogy-dominant rubric (anvil-primer-v1; dim 1 Pedagogical scaffolding / learnability at weight 7, general ≥35 advance threshold), six lifecycle commands (primer,primer-draft,primer-review,primer-audit,primer-revise,primer-figures), BRIEF + body templates, and skeleton tests. Borrowsanvil:report's lifecycle shape (parallel review+audit, markdown source-of-truth + optional PDF via the sharedanvil/lib/render.pypandoc-first path) as a NEW skill — not areportparameterization (per "skill identity = artifact identity"). - Optional
spec_refcompanion input for primer threads: aBRIEF.mddocuments:key naming the formal sibling artifact a primer teaches alongside. Resolved project-root-first then consumer-root by the newanvil/lib/project_brief.py::resolve_spec_ref/ResolvedSpecRef. When active,primer-audituses it as a spec-consistency oracle (the "Contradicts cited spec" critical flag) andprimer-reviewruns the duplication check ("Duplicates formal spec section"); absent → the tier is silent/off and both critics record amajorfinding; declared-but- missing → the tier activates but degrades gracefully (amajorfinding, never a crash, never a false critical flag) — the standard #428/#449 activation contract. primerregistered as a skill-identityartifact_type(REGISTERED_ARTIFACT_TYPES+SKILL_IDENTITY_ARTIFACT_TYPES+ArtifactType.PRIMER); fiveanvil-primer-*lifecycle agents added to the generated agent registry (source registry 54 → 59). README skill table and CLAUDE.md skill counts bumped (12 artifact classes).- Tracked-artifact detection +
--fix-trackedinstaller flag (#684): after writing.anvil/.gitignore(Stage 8.6), the installer now detects files already git-tracked under.anvil/that the ignore patterns would suppress (the pre-0.8.0 committed-__pycache__upgrade gap) and prints a warn + exactgit rm -r --cachedremediation hint. An explicit--fix-trackedperforms the untracking itself — index-only, never a commit on the consumer's behalf, working-tree files untouched. Patterns are derived from the written.gitignoreviagit check-ignore(no second hard-coded list to drift); graceful no-op on non-git targets;--dry-runhonest throughout.
- Stale agent files pruned on a
--skills=narrowing (#685): a pre-0.8.0 unscoped install wrote all agent shims to.claude/agents/; the 0.8.0 scoped copy (#662/#675) never removed the now-unselected ones, so they kept registering in the consumer's agent picker. Stage 7.5 now prunes anvil-owned agent files whoseagent_skill_for()resolution is a known skill outside the live selection — per-file removal only, non-anvil (loom-*) and unknown-prefix files untouched, no-op for full installs,--dry-runhonored.
- The Botho "Botho from the Basics" worked example (
examples/, dogfooded via Botho #881 once the shape lands), voice-grounding wiring, a consumer-pluggable figure-adapter registry, and the LaTeX/TikZ figure path — documented in the primer SKILL.md §Deferred.
The tractatus canary wave: anvil's first external install into a
machine-checked Lean 4 papers repo (rjwalters/tractatus, --skills=pub)
surfaced eleven friction reports in one day, and this release closes all of
them. The arc is three-fold — (1) run the artifact, not just the PDF:
pub-review gains a consumer-extensible external-artifact verification gate
(build the companion proof repo, check quoted theorems against source) and
multi-file \input/\include threads become first-class in review; (2)
compile-contract truth: the pub compile cycle becomes a latexmk-style
convergence loop instead of a fixed pass count, the render-gate input is
named by its real entry-point-derived name (main.pdf), overfull boxes
dedupe across multi-pass logs, and pub-audit's git-sync scope now stages the
PDF it produces; (3) install ergonomics for strict-subset consumers: the
agents copy is scoped by --skills=, .anvil/.gitignore ships in the box,
the sidecar CLI runs warning-free, and adopted-in-place legacy threads get a
--body override in both content detectors.
- External-artifact verification gate for pub-review (#663/#665): opt-in
artifact_verifyblock in.anvil.jsonruns consumer-declared commands (e.g.lake build) before scoring; failures surface as fabrication-class critical flags through the unmodifiedVerdict.BLOCKmachinery; broken declarations fail open as major findings. Byte-identical when undeclared; discovery mirrors the venue-overlay contract. - Multi-file LaTeX threads first-class in pub review (#654):
\input/\includeresolution via thetex_includesresolver wired through review content-reads. - Sidecar CLI for driver-less staging (#652):
python -m anvil.lib.sidecar stage|commit|cleanup— crash-safe critic-sibling writes without a Python driver. --bodypath override for adopted-in-place threads (#670/#679):numeric_consistencyandevidence_checkaccept an explicit body path (legacy entry points likepaper.tex); default discovery byte-identical; the numeric sidecar records the override portfolio-relative.numericclass option for anvil-paper.cls (#671/#680): swaps natbib authoryear →[numbers,sort&compress]for content-identical migration of numeric-cite papers (authoryear stays the default; compile-verified withplainnatacross both modes); SKILL.md gains a "Migrating an existing paper" section elevating keep-original-class to a first-class path.- Installer ships
.anvil/.gitignore(#674/#683): new Stage 8.6 writes a self-contained ignore file (__pycache__/,*.py[cod],.venv/) — skip-if-exists, dry-run honest; README documents per-worktreeuv sync --project .anvilpre-sync andUV_LINK_MODE=copy.
- Pub compile contract: convergence loop, not fixed count (#669/#678):
pdflatex → bibtex → pdflatex ×2floor, then rerun while LaTeX emits "Label(s) may have changed. Rerun" (max 5 passes, latexmk semantics); non-convergence at the cap is a critical flag; optional.aux-diff cross-check documented. pub-vision/pub-review/SKILL.md defer to pub-audit step 4 as the single source of truth. - pub-audit git-sync scope stages its compiled PDF (#672/#681): the
staging target now explicitly includes
<thread>.{N}/main.pdfalongside the audit sidecar (never the wholesale version dir);git_sync.mdsnippet documents the dual-role command shape for future skills.
- Installer agent copy scoped by
--skills=(#662/#666): Stage 7.5 no longer copies all 54 agent files on a single-skill install (5 for--skills=pub); longest-prefix matching keepsip-uspto/ip-uspto-provisionalsiblings separate; unknown-prefix agents still copy unconditionally. - Sidecar runpy RuntimeWarning silenced (#673/#682):
anvil/lib/__init__.pyre-exports sidecar primitives lazily (PEP 562), so everypython -m anvil.lib.sidecarinvocation is warning-free; public re-export contract preserved; zero doc changes. - Overfull-box over-count on multi-pass logs (#668/#677):
render_gatededupes by(line, amount, kind)— 18-for-6 canary inflation gone; line-less hits never collapse; verdicts unaffected. - Render-gate input named by its real artifact (#667/#676): pub-review
step 4b and SKILL.md say
main.pdf(the entry-point-derived name pub-audit actually produces), ending the spurious fail-open/fail-closed ambiguity. - README status line under version.sh management (#661/#675): README.md
joins the managed version-file set (drift now caught by
version.sh check); skill count dropped from prose; Skills table filled to all 17 (addsproject-photos,project-book). - version.sh
--tagstages CHANGELOG.md (#638/#641). - Consumer-path migration finish + doc-pin guard widening (#642); critic-doc consistency batch: sidecar fallback rollout (#658), findings.md scope (#664), stale counts/cross-refs (#659), orchestrator output-guard collisions (#651), fail-open uv-gated fallback (#650), rubric/manifest reconciliation (#649), tex_includes wiring for installation/proposal (#657).
- Per-skill install staleness tracking (#633/#635): the install manifest gains a
skill_versionsblock parallel toskill_hashes; skipped-override warnings now report "last installed: vX, current: vY" and the baseline survives across runs. - Cross-file rubric-threshold consistency lint (#618/#631): framework test asserting rubric totals/thresholds agree across each skill's rubric, commands, and SKILL.md.
- Blog-parity: figure-carried economic false-promotion suppression (#626):
parity checks consult a
figures/srcCSV corpus so numbers carried by figures don't false-promote as ungrounded prose claims.
- Voice-grounding scaffold moved under
.anvil/voice/(#617/#632): Stage 7.9 no longer writes STYLE_GUIDE.md / VOCABULARY.md / VOCABULARY.words.txt / VALUES.local.md to the consumer repo root; pre-existing root-level docs are preserved and suppress the new-location scaffold so upgrades never duplicate.
- Consumer-path resolution to
.anvil/anvil/lib/(#625, #634/#636): command specs, snippets, and marp/figures asset docs all reference the canonical post-#230 consumer path;render.pymarp-config and mermaid-theme defaults are now__file__-relative (correct from both a source checkout and a consumer repo);figures/mermaid-theme.jsongains installer override protection. - Marp stdin hang (#629): render helpers pass
--no-stdin/ DEVNULL so marp never blocks in non-TTY contexts. - Overflow lint accuracy (#627): the marp overflow charge is aspect-ratio- and CSS-flex-aware.
- Mermaid v11 edge-label legibility (#628): the pinned theme forces dark edge-label text.
- Deck prompt-journal tolerance (#630): the reader ignores unknown per-entry fields instead of failing.
Two arcs since v0.6.0: (1) the deck business-model wave — deck gains dim 10 Business-model & unit-economics credibility (/44 → /49, threshold ≥43), a dim-10-owning deck-economics adversarial critic, a fifth critical flag, an outline gate, a figure-legibility gate, and a unified overflow/fit gate; (2) the nitas-mama family-memoir enablement wave — two new utility skills (anvil:project-photos, anvil:project-book, catalog 15 → 17) plus two lib-level grounding contracts (subject voice tier, local-corpus claim provenance) that point anvil's voice and evidence machinery at a real person's spoken corpus and an on-disk ground-truth archive — and their skill-level adoption: essay and pub write and back-check per-version provenance.md claim maps, pub-audit runs the five-way corpus verification, and pub/report adopt the subject voice tier beyond the essay pilot. A blog-parity batch (opening-line em-dash lint scope, VOCABULARY.words.txt install scaffold, VALUES template schema completion) and assorted memo/ip/install hardening round it out.
-
Essay adoption of the claim-provenance contract (#611/#614):
essay-draftwrites theprovenance.mdclaim→source map before prose whencorpus:is declared;essay-reviewback-checks 5–10 rows per pass (provenance_back_check_summary.mdblock) and gains conditional fabrication-class critical flags 9–13;essay-revisecopies the map forward. Byte-identical when no corpus is declared. -
Pub adoption of the claim-provenance contract — the audit pilot (#612/#615):
pub-draftwrites the map,pub-reviewback-checks,pub-revisecopies forward, andpub-auditruns the exhaustive five-way verification against the on-disk corpus — a separatestaged_sidecarfor<thread>.{N}.corpus-audit/withkind: tool_evidencefindings, fabrication-class critical flags through the existingVerdict.BLOCKmachinery, and a six-counterprovenance_summary. -
Pub + report adoption of the subject voice tier (#613/#616): drafters record per-subject
metadata.subject_voice_exemplars; reviewers run the per-subject voice-fidelity pass with the quote-the-transcript deduction discipline (pub folds into dim 7, report into its existing dim 8 sub-step) with the conditional Misattribution critical flag (≥2 subjects);report-revisepreserves subject one-liners. Rubric stamps unchanged. -
NEW SKILL
anvil:project-book(#596/#606): multi-thread book assembly — stages the.latest-resolved version of every chapter thread into a consumer-owned master LaTeX document (skill-localbuild:BRIEF block), two-pass compiles viacompile_and_gate, and writes a per-threadBOOK_REPORT.md(state, score, audit, next command). Build-does-not-block-on-quality: EMPTY/missing threads get placeholder chapters, below-READY threads warn; marker-guarded blow-away rebuild;--dry-run. -
NEW SKILL
anvil:project-photos(#599/#603): read-only scanned-photo provenance manifest — human-authored numbering doc → deterministicmanifest.json(original capture → stable name + archive item IDs + rotation hint +multi_itemflag +missing_captures); byte-identical re-runs; image manipulation deliberately stays consumer-native. -
Subject voice tier for the voice-grounding contract (#598/#604):
voice.subjects[](per-subject spokencorpusglob + optionalvoice_doc), independently activated from the author tier;resolve_subject_voice_docsmirror resolver; essay pilot consumer with conditional Misattribution critical flag 8 (voice-identity failures; substance verification belongs to the corpus-provenance contract). -
Local-corpus claim provenance contract (#597/#605): top-level
corpus: [paths]BRIEF field +resolve_corpus_dirs(directory-gated, project-first/consumer-fallback, missing-as-structured-entry) +anvil/lib/snippets/provenance.md— per-version claim→source map, five-way audit classification (VERIFIED / PARAPHRASE_OK / MISMATCH / NOT_FOUND / FABRICATED), five fabrication-class critical flags,<thread>.{N}.corpus-audit/sibling naming. Skill adoption (pub/essay) tracked as follow-ons. -
Deck business-model dimension (#550/#556, #557, #565, #566): dim 10 Business-model & unit-economics credibility (weight 5) — deck rubric /44 → /49, threshold ≥43; perspective substrate extended;
deck-economicsadversarial critic owns the dim; fifth critical flag for incoherent/absent business model. -
Deck gates + imagery (#547/#585, #562/#574, #563/#571, #564/#570, #567, #568): proactive generative imagery with
default_policyoverride + additive-ness gate; unified sizing-aware overflow/fit gate; figure legibility-at-display-size gate; JPEG/WebP → PNG transcode via optional[deck_imagegen]extra; deck-outline gate + reviser restructure authority; mmdc--scale 2fix. -
Memo hardening (#559/#583, #560/#573):
NO-GOterminal state for honest thesis-failure; independent red-team adversarial critic sibling. -
Rhetoric lint positional scope (#601/#608):
scope: "first-line"rule attribute +no-opening-emdashdefault rule (28 rules); consumer density-tightening recipe (5/1000 id-collision replacement) documented. -
Install scaffold:
VOCABULARY.words.txt(#602/#607): Stage 7.9 ships the starter word list as the fourth voice-grounding pair (sibling-resolution convention); hint block documentspython -m anvil.lib.vocab_reminder. -
VALUES template schema completion (#600/#609): Substrate / Forming positions / Voice modes sections + Standing reference-scaffolding prompt — the concepts essay rubric dims 2/5 already score against.
-
version.shrelease interface (#590/#591):list+bump <level> --tagsubcommands per upstream Loom v0.10.4release.md. -
project_brief.py:audience: {primary, secondary}dict shape (#546/#588). -
ip-uspto: audit/finalize render-gate backstop + 2pt ip-skill threshold adjustment (#572/#587).
- All four xelatex preambles load amsmath/amssymb — pandoc-emitted
\text{}no longer fails render (#592/#594). - Installer auto-proceeds on non-TTY stdin (CI/agent installs) (#569).
- Per-critic entry steps guarded against portfolio-wide staging sweeps (test armor, #593/#595).
v0.6.0 is the adoption-readiness release: the entire backlog surfaced by the v0.5.0 multi-repo adoption survey is closed (62 PRs, issues #424–#543). The arc is three-fold — (1) sphere parity: every gap that blocked the studio-sibling install (consumer git-sync hooks, report figure adapters + data-contract audit + customer-context store + audience-class switches, pub web-litsearch + interview bootstrap, deck imagegen backend, beamer→Marp theme porting, project-migrate foreign-grammar adoption); (2) house-engine absorption: anvil becomes the artifact engine for the whole repo family, adding anvil:essay (short-form voice-grounded essays/blog posts — the 11th artifact class) plus the reusable voice/persona grounding contract, deterministic numeric-consistency + rhetoric-lint pre-flight gates, and a quoted-evidence requirement for critic scores; (3) ip-uspto maturation: the new anvil:ip-uspto-provisional sibling grows a full lifecycle (pre-flight, claims-seed, figures, drawings-vision, inventorship-lite, audit, COUNSEL-READY finalize), provisional→non-provisional conversion linkage (§119(e) text, 12-month deadline, §112(a) coverage), and evidence-mined inventorship (git-mining → interview packets → determination synthesis) on the non-provisional side.
Catalog: 13 → 15 skills (anvil:essay, anvil:ip-uspto-provisional). Test count: 3956 → 5956 (+2000 across the release, 0 regressions). Three new skills (datasheet, ip-uspto-provisional, essay) now vendor worked examples.
-
NEW SKILL
anvil:ip-uspto-provisional(issues #433/#444 Phase 1; #502/#515/#516/#480 Phase 2). USPTO provisional applications — claims-optional, enablement-depth-first (anvil-ip-provisional-v1, 9-dim /45 ≥39, dim 1 §112(a) enablement depth weight 8, dim 9 Conversion readiness). Full lifecycle: draft/review/§112/prior-art/revise loop, a deterministic pre-flight gate (claims-optional shape, advisory §112 stub scan), an opt-in claim-seed critic (absence is never a finding), provisional-shaped figures + a drawings-vision critic (informal drawings valid; scores rubric dim 4, no 37 CFR 1.84 formality nits), an advisory inventorship-lite list check, an audit command, and a COUNSEL-READY finalize assembling the<thread>.counsel/filing package. The conversion seed foranvil:ip-uspto. -
anvil:ip-usptoprovisional conversion linkage (issues #501/#519, #517/#523). Aconverts_provisionalBRIEF block threads §119(e) priority-claim text into the spec/ADS at draft/finalize, a producer-side_filing.jsoncaptures the filing date, and a pure-stdlibconversion_deadline.pysurfaces the 12-month bar (end-of-month-clamped, fail-loud). Theip-uspto-112critic gains aconverts_provisional-gated §112(a) disclosure-coverage check (re-runs the claim-support sweep against the provisional spec; advisory for counsel, critical-flag-eligible for an unsupported converted independent-claim limitation, dormant when absent). -
anvil:ip-usptoevidence-mined inventorship (v1 + v2) (issues #445/#492, #493/#512, #511/#525).--evidencemines repo git history intoinventorship_map.json+evidence.jsonlwith Notes-only matrix citations;--interviewgenerates per-inventor packets;--synthesizerolls completed packets into a determination FOR COUNSEL. Legal invariants held throughout — advisory attorney work product, never touches the●attestation matrix, never adjudicates.inventorship_evidence.pypromoted toanvil/lib/(provisional is the second consumer). -
anvil:ip-usptoopt-in findings-only critics (issues #434/#447, #446/#498). An adversarial critic (ip-uspto-adversary, all dims null) attacks the application via §103 combinations over supplied art + AAPA, design-arounds, and §112(a) enablement holes; an FTO triage critic (ip-uspto-fto, report-only, never flags) produces a triage-for-counsel relevance assessment (0–4 scale, NOT-AN-FTO-OPINION boilerplate, no clearance verdict). -
Consumer-integration tier for
anvil:report(issues #427/#443, #428/#449, #429/#452, #450/#455). Pluggable block-figure adapters (.anvil/config.jsonreport.figure_adapters; shell-free subprocess contract); data-contract numerical audit (refs/data/manifest.json-activated, four-valued VERIFIED/UNVERIFIED/CONTRADICTED/NOT-IN-REFS + orthogonal STALE); cross-project customer-context store (customers/<slug>/context.yaml+ append-onlydisclosures.jsonl, topics-to-avoid critical flag); audience-class house-style switches (commercial/defense/internal, defense DRAFT watermark, no anvil-shipped legal text). -
Consumer entry paths for
anvil:pub(issues #424/#437, #425/#451). Opt-inweb_searchBRIEF knob (live searches allowed, but every citation must resolve throughanvil/lib/cite.pybefore enteringrefs.bib— unresolvable hits become "Web leads", never citations); interview-driven BRIEF bootstrap when<thread>/BRIEF.mdis absent (--no-interviewopt-out, TODO(operator) markers never fabricated). -
anvil:deckimagegen reference backend + onboarding (issue #430/#441) and beamer→Marp brand-theme porting path (issue #431/#448): a stdlib deterministic placeholder backend exercising the real config→dispatch→journal path, an onboarding guide, andanvil/lib/marp/brand-theme-starter.csswith a.sty→CSS mapping recipe. -
anvil:project-migrateforeign-grammar adoption (issues #432/#439, #440/#457, #454/#506, #507/#513, #503/#520).--adopt-vn(vN report dirs),--adopt-family({Project}.{Letter}.{N}letter families + declarative--tag-map),--adopt-review(honest unscored stub conversion of foreignreview.mdsidecars) with an operator-driven--rescore, and filename-driven enrollment of nativeprovisional.texthreads. Scan-time refusal for leading-zero version-slot collisions (#458/#459). -
NEW SKILL
anvil:essay(issue #460). Eleventh artifact-class skill: short-form voice-grounded essays / blog posts (markdown-only<slug>.mdbody, 500–1500 words typical), grounded in the rjwalters.info adoption survey — its pre-anvil blog skill is strictly upgraded (monolithic review.md → critic sidecars; 6-dim /30 → 9-dim /44anvil-essay-v1with full #346 stamping; ≥35 general tier ≈ the consumer's 80% bar). Deliberately small command set (essaystatus orchestrator +essay-draft/essay-review/essay-revise; no figures, no audit, no PDF); the state machine ends atREADYwith a documented publish handoff contract (the report CUSTOMER-READY precedent — TSX conversion / registry / Cloudflare deploy stay consumer-native). Voice fidelity is the OWNED dim 2 at weight 7 (first heavy consumer of the #461 voice/persona grounding contract; corpus-quoted deductions mandatory; a missingvoice:block is amajorfinding every pass, not a crash); dim 9 Rhetorical economy is load-bearing (absorbs the consumer's length-discipline dim, fed by the #463 rhetoric lint as advisory evidence). Gates:numeric_consistency --blocking(the #462 hook built for this skill — its first blocking consumer) and broken-link resolution are convergence-BLOCKING; rhetoric lint stays advisory per its #463 contract; example coherence ships as the blog-review step-2.5 LLM critical-flag prose check (detector deferred per #462 gate 1). Seven critical flags ported from the consumer's blog-review (anti-stance violation, out-of-standing claim, generic AI cadence, factual error, unattributed borrowing, example-coherence failure, numeric-consistency failure).essayregistered as a skill-identityartifact_type;anvil-essay-{drafter,reviewer,reviser}agents generated. -
Voice/persona grounding-docs contract (issue #461/#466, #468/#476). An optional top-level BRIEF
voice:block (style_guide/vocabulary/values/corpusglob +rhetoric_rules), thevoice_grounding.mdsnippet (drafter exemplar-quoting + reviewer corpus-quoted deductions), and a memo dim-8 calibration consumer. Absent = byte-identical. -
Deterministic authoring pre-flight gates (issues #462/#465, #463/#467, #464/#474). A numeric-consistency gate (
anvil/lib/numeric_consistency.py— claim-vs-claim spread/percent/multiplier validation with currency K/M/B/bn/mn/tn scale handling, advisory for memo/pub, blocking hook for essay); a rhetoric lint (anvil/lib/rhetoric_lint.py— 27 conservative AI-tell/anti-trope rules + consumer JSON rules, advisorymemo_rhetoric_lintgate dimension); a quoted-evidence requirement (anvil/lib/evidence_check.py— every critic dimension justification must quote the reviewed body verbatim or mark by-absence; fabricated quote = MAJOR), piloted on memo and rolled out to all 10 main reviewers + 9 scored specialist critics. -
anvil:ip-usptoevidence-mined inventorship v2 — interview packets (--interview) (issue #493, follow-up to #445). New opt-in--interviewmode generates one structured interview packet per candidate inventor from the v1-mined artifacts (inventorship_map.json+evidence.jsonl) — the deterministic-templating half of the native consumer's proven/ip-inventorshipv2 (the judgment-laden--synthesizerollup is a deferred follow-up, filed atloom:triagenow that the packet markdown shape is frozen). New skill-localanvil/skills/ip-uspto/lib/inventorship_interview.py(mirrors v1's placement — noanvil/lib/promotion; #480 is the trigger) ports the native verbatim statutory constants (STATUTORY_INTROwith the Burroughs Wellcome conception standard / MPEP §2138.04 / plain-English §115/§116/§256, the Q1–Q7 question block,CONFIDENTIAL_FOOTER,VENDORED_CODE_PROMPT), the 5-step bot-author resolution chain (operator-configurable bot pattern; the bot is never a §115 inventor), composite-label collapse (a label like1(b)(iv-v)yields exactly ONE Q1–Q7 block), candidate-matching by email OR display-name, and a three-level sensitivity taxonomy — reusing v1'sis_vendored_path/vendored_prefixeshelpers rather than reimplementing. Legal invariants held byte-identical: packets are advisory-only attorney work product; they never touch the●matrix, inventor columns, or TBD markers; evidence anchors are labelled memory-aids-only; there is no auto-adjudication. Packets land at<thread>/inventorship-evidence/interviews/{slug}.md;--interviewconsumes v1 outputs only (emits a "run--evidencefirst" notice when they are absent, writing nothing). Command prose extended with the I1–I6 mode section; Git-sync paragraph stagesinterviews/. New fixture pair + 30+ tests (packet structure, composite-label collapse, vendored/bot blocks, candidate matching, advisory-only invariants, graceful degradation, command-file structure). -
NEW SKILL
anvil:essay(issue #460). Eleventh artifact-class skill: short-form voice-grounded essays / blog posts (markdown-only<slug>.mdbody, 500–1500 words typical), grounded in the rjwalters.info adoption survey — its pre-anvil blog skill is strictly upgraded (monolithic review.md → critic sidecars; 6-dim /30 → 9-dim /44anvil-essay-v1with full #346 stamping; ≥35 general tier ≈ the consumer's 80% bar). Deliberately small command set (essaystatus orchestrator +essay-draft/essay-review/essay-revise; no figures, no audit, no PDF); the state machine ends atREADYwith a documented publish handoff contract (the report CUSTOMER-READY precedent — TSX conversion / registry / Cloudflare deploy stay consumer-native). Voice fidelity is the OWNED dim 2 at weight 7 (first heavy consumer of the #461 voice/persona grounding contract; corpus-quoted deductions mandatory; a missingvoice:block is amajorfinding every pass, not a crash); dim 9 Rhetorical economy is load-bearing (absorbs the consumer's length-discipline dim, fed by the #463 rhetoric lint as advisory evidence). Gates:numeric_consistency --blocking(the #462 hook built for this skill — its first blocking consumer) and broken-link resolution are convergence-BLOCKING; rhetoric lint stays advisory per its #463 contract; example coherence ships as the blog-review step-2.5 LLM critical-flag prose check (detector deferred per #462 gate 1). Seven critical flags ported from the consumer's blog-review (anti-stance violation, out-of-standing claim, generic AI cadence, factual error, unattributed borrowing, example-coherence failure, numeric-consistency failure).essayregistered as a skill-identityartifact_type;anvil-essay-{drafter,reviewer,reviser}agents generated. Deferred: worked example, rjwalters.infodrafts/migration,voice.rhetoric_ruleswiring (#468), PDF path, example-coherence detector.
- Lib promotion (issue #460, second-consumer trigger):
hyperlink_resolver.pymoves fromanvil/skills/memo/lib/toanvil/lib/— the essay skill's blocking link gate is the second consumer of the #335 deterministic link-validation critic, firing the CLAUDE.md "wait for the second consumer before generalizing" rule (the #382/#393 promotion pattern). The memo path remains a back-compat re-export shim (both import paths and bothpython -minvocations keep working); tests move totests/lib/test_hyperlink_resolver.pywith shim-identity coverage.citation_coverage.pydeliberately stays memo-local (assessed in its docstring: memo detects unlinked load-bearing claims; essay's coverage concern is unlinked named entities — judgment-side, carried as essay-review prose). - Opt-in per-phase git commit/sync hook (issues #426/#438, #436/#456, #528/#532, #537). Repo-level
.anvil/config.json{"git":{"commit_per_phase","push"}}knob (default off) wired into all ~88 write-bearing commands; the verbose inline prose was later trimmed to the canonicalgit_sync.mdshort pointer while keeping each command's per-phase staging target inline. anvil:memoframework-maintained.latestsymlinks + runnable render-phase CLI (issues #472/#495, #473/#500). New skill-local CLIs make two previously-prose lifecycle steps actually runnable from agent-driven lifecycles (the PDF render and the.latestpointer maintenance), the latter preserving operator pins.[deck.imagegen]config migrated.anvil/config.toml→.anvil/config.json(issue #442/#485), consolidating on the single runtime config file introduced for the git-sync knob.- Installer hardening (issues #471/#489, #490/#514): scaffolds a consumer-owned starter theme so fresh memo renders aren't unstyled, and extends the hash-tracked skip-with-warning discipline to Stage 5 lib override assets (framework code always upgrades; only documented override assets are preserved).
anvil:rubric-rebackportKNOWN_RUBRICS catalog refresh + drift guard (issues #482/#484, #486/#509) for datasheet / ip-uspto-provisional / essay;datasheetregistered inREGISTERED_ARTIFACT_TYPES.
- Memo
--csssilently dropped (issue #470/#483): restored the$for(css)$loop intemplate.html(a #331 regression) so consumer theme overrides actually load. - Stale /40-era rubric prose aligned with the shipped /44 and /45 rubrics across ~44 files, including six behavior-affecting gate lines (issue #435/#453).
evidence_checkmachine-summary scorecards were vacuous (issue #536/#538): the JSON-only parser never matched the markdown-table scorecards the ip skills actually emit, so the quoted-evidence self-check silently checked nothing in real ip reviews — now falls back to the table parser. Plus ellipsis-elided quote acceptance + dash folding (#478/#494).numeric_consistencydiagnostics show the selected-ratio arithmetic and honor currency K/M/B + bn/mn/tn scale suffixes (issues #469/#488, #491/#508).project-migrateleading-zero version-slot collisions now refuse at scan time in both adoption planners instead of silently collapsing or failing mid-apply (issue #458/#459).
v0.5.0 grows the catalog from 10 to 13 skills: anvil:datasheet (the ninth artifact class — customer-facing IC/component datasheets, LaTeX → PDF, 9-dim /44 rubric with mechanical pin-map/bus-width checkers), anvil:project-scout (read-only repo-wide discovery of anvil-adoptable document clusters), and anvil:project-share (per-project SHARE/ export of latest sources + refs + research). Alongside: the #295/#296 project-org nesting rollout to deck/slides/proposal, a per-skill-phase subagent registry, deck-side rubric_overrides + dimension waivers, the render-gate image-dimension lint, project-migrate enrollment growth (--enroll, starter-BRIEF synthesis, surgical BRIEF merge), three memo BRIEF knobs, and hardening fixes across the LaTeX classes, sidecar sweep, and rubric-rebackport.
Test count: 2962 → 3956 (+994 across the release, 0 regressions).
-
NEW SKILL
anvil:datasheet(issue #418, PR #421). Ninth artifact-class skill: customer-facing IC/component datasheets (LaTeX → PDF via xelatex). Distilled from hand-authoring two real preliminary datasheets at the studio canary (dual-SKU edge-AI part family). 9-dim /44 rubric (anvil-datasheet-v1, ≥39 customer-facing threshold, five critical flags) with dims for source-traceability, internal consistency, and measured-vs-projected provenance. Six commands;anvil-datasheet.clstemplate (two-column first page, provenance macros\est{}/\simval{}/\meas{},\preliminarynotice); deterministic skill-local checkerspinmap_check.py(every pin assigned exactly once) andbuswidth_check.py(N-bit field must cover its claimed range) with the canary failure cases pinned as unit tests. Audit-owned refs back-check (spec bundle inrefs/outranks the brief for numbers) and a revision-history READY-gate. 61 new tests incl. two real XeLaTeX compiles. -
NEW SKILL
anvil:project-scout(issue #407, PR #417). Strictly read-only repo-wide survey: walks a tree, classifies anvil-adoptable document clusters into an adoption taxonomy (foreign-grammar guard runs BEFORE anydetect_shapedelegation), reports the recommended next command per cluster. -
NEW SKILL
anvil:project-share(issue #396, PR #399). Collects each thread's.latest-resolved source + PDF + assets + per-thread refs and the sharedresearch/pool into one shareable, provenance-stampedSHARE/folder. Marker-guarded blow-away rebuild;--dry-run/--zipflags. -
anvil:project-migratesingle-file enrollment--enroll(issue #406, PR #414) and starter-BRIEF synthesis for bare version-dir threads (issue #408, PR #411). -
anvil:memochallenge/strategy genres + consumer artifact-type overlay tier (issue #394, PR #401), deterministic scorecard arithmetic validation (issue #392, PR #400), and consumer pandoc-template passthrough via BRIEF (issue #391, PR #398). -
Per-skill-phase subagent registry (issue #377, PR #384). Generated
.claude/agentsregistrations per skill phase viascripts/generate-anvil-agents.py; installer copies them into consumers. -
ArtifactTypeextended with deck/slides/proposal + silentartifact_typedefault fixed (issue #386, PR #388). -
Parallel-safe per-critic staging sweep
cleanup_one_staging(issue #376, PR #381). -
Monorepo coexistence contract test for the installer (issue #409, PR #410).
-
Render-gate image-dimension/aspect sanity check (issue #395). New advisory
memo_image_dimensionsdimension (check 5 of the now-six-dimension memo gate inanvil/lib/render_gate.py), catching runaway figure canvases before they ship — canary:technical-vision'ssilicon-ladder.pngrendered at 16,622×5,652 px through two full versions (matplotlibbbox_inches="tight"inflated ~9× by a rogue artist on a transparent canvas; the framework's ownanvil.mplstylepins exactly thatsavefig.bbox: tight+savefig.transparent: Truepair, so this is a safety net for a framework-shipped hazard). For every body-referenced image plus every PNG/JPEG underexhibits/(union enumeration; URL/absolute refs skipped): (1) pixel ceiling — width or height >image_max_px(default 6000; per-thread overridable via the newgate(kind="memo", image_max_px=...)kwarg with thewords_per_pagecoerce-or-silently-fallback validation, effective ceiling recorded in the message); (1b) extreme aspect — > 6:1 either orientation; (2) declared-vs-actual — actual dims diverging > 1.5× from a siblingsrc/<stem>.py's parseablefigsize/dpi(or the PNGpHYsdensity) — silent skip when nothing declarative is parseable; (3) content-bbox vs canvas — content occupying < 25% of the canvas (the tight-bbox rogue-artist signature), behind a new[image_lint]optional extra (same Pillow+numpy set as[auto_shrink]; the bbox algorithm adapts the deck auto-shrink corner-sampling precedent into RGBA space so transparent canvases read as background) with graceful degradation viacheck_image_lint_deps_available()+IMAGE_LINT_REMEDIATIONinanvil/lib/render.py, plus a per-image decode skip for over-ceiling canvases (decoding a 90-megapixel image is the hazard, not the cure). Checks 1/1b/2 are pure stdlib (struct PNG-IHDR / JPEG-SOFn header parsing — zero new base deps; the few-hundred-byte huge-IHDR fixture trick fromtest_imagegen.py::_make_tiny_pngproves the path). All findings are warning severity —passunaffected, noCriticalFlag, thememo_overfull_checkadvisory model; findings flow to_progress.json.render_gate.findingsthrough the existing wiring. Suppression via<!-- anvil-lint-disable: memo_image_dimensions -->(suppressed hits surface as info findings). 44 new tests attests/lib/test_render_gate_image_dims.py. -
Deck-side per-thread
rubric_overrides+ dimension waivers (issue #393). The deck skill becomes the second consumer of the per-docrubric_overrides:BRIEF contract (memo precedent #233/#265/#296), and the shared schema gains an explicit waiver shape for operator-directed content exclusions (canary: the acme-semi series-a-deck carried a no-team-content directive that structurally capped dim 6). Two halves: (1) calibrations —dim_N_calibrationalready parsed artifact-type-agnostically post-#382/#386;deck-review.mdnow loads them (new step 5e mirroring memo-review 4h) and attaches verbatim suffixes to owned-dimscoring.mdjustifications via the promoted helper. (2) waivers — net-newdim_N_waiverkey family inanvil/lib/project_brief.py(rationale-as-value: the YAML value IS the mandatory rationale; unjustified waivers and waiver+calibration conflicts on the same dim are rejected at parse time with errors naming the keys). At verdict time a waived dim is removed from BOTH numerator and denominator:advance iff total_over_remaining >= 39 × (44 − waived_weight) / 44, compared as an exact fraction (dim 6 weight 4 waived →390/11 ≈ 35.45/40).verdict.mdstates the normalized judgment explicitly and quotes each waiver rationale verbatim;_summary.mdgains a top-level observationalrubric_overridesblock (ran/calibrations_applied/waivers/waived_weight). Boundaries: critical flags are NOT waivable (a dim-6 waiver does not suppressFabricated team credentials);_meta.jsonstamping stays nominal per the #346 contract; memo-side waiver consumption is out of scope (the shared parser accepts waivers on any entry, but only deck-review consumes them in v0); specialist critics defer per the PR #363 split-init precedent — only the aggregator loads/applies overrides. -
#295/#296 project-org nesting for
anvil:deck/anvil:slides/anvil:proposal+ project-migrate deck handling (issue #382). The three remaining rich-command-set artifact skills adopt the nested<project>/<slug>/<slug>.N/artifact contract (SKILL.md trees rewritten; project-levelBRIEF.mddocumented as the config locus per #296). The slug-echo body rename is deliberately scoped out for these skills —deck.md(Marp CLI) andproposal.tex(xelatex +anvil-proposal.cls) are consumed by external tooling; each SKILL.md carries an explicit body-filename note and the migration is directory nesting only (matching the studio hand-fix2cf3f37). Proposal SKILL.md's four.anvil.jsonreferences are retired per #296 (iteration cap repointed at the project-BRIEF paired override; render-gatepage_capflagged TBD-on-BRIEF-grow mirroring memo'swords_per_pageprose).anvil:project-migratenow recognizes the nested-but-flat shape (thread root with BRIEF/refs/assets/.anvil.json as a sibling of flat version dirs), plans the<slug>.N→<slug>/<slug>.Nnesting move (critic siblings included), merges the deck pairedmax_iterations+iteration_cap_rationaleoverride into the project BRIEF (only when it would survive the strict parser), dispatches mixed-grammar projects per thread, and stays idempotent on migrated mixed trees (the FULLY_MIGRATED body check now keys on skill-fixed bodies rather than slug-echo). New fixturesbuild_beacon_shaped_deck+build_mixed_memo_deck_proposaland four new mixed-shape test files.
-
Lib promotion (issue #393, second-consumer trigger):
rubric_overrides_suffix.pymoves fromanvil/skills/memo/lib/toanvil/lib/— deck is the second consumer of the calibration-suffix primitive, firing the CLAUDE.md "wait for the second consumer before generalizing" rule (same shape as the #382 promotion ofproject_brief.py). The memo path remains a back-compat re-export shim; the memo behavioral corpus keeps running against the canonical implementation through it. The promoted module additionally grows the waiver-normalization helpers (normalized_advance_threshold/meets_normalized_threshold/waived_weight_for, exact-Fractionmath). -
Lib promotion (factoring A of issue #382):
latest_resolution.py,project_discovery.py,project_brief.py, andcross_thread_refs.pymove fromanvil/skills/memo/lib/toanvil/lib/— deck/slides/proposal are the 2nd–4th consumers of the project-org primitives, firing the CLAUDE.md "wait for the second consumer before generalizing" rule. The memo paths remain as back-compat re-export shims (bothanvil.skills.memo.lib.Xandanvil.lib.Ximport paths work;rubric_overlays.pystays memo-local — overlay JSONs are memo-artifact-type-specific).anvil/lib/render_gate.py's theme-discovery tier drops its memo-lib sys.path injection in favor of plainanvil.libsibling imports. New canonical-path test files undertests/lib/pin shim identity + representative behavior for all four modules. -
Docs requalification sweep: deck/slides/proposal command docs requalified to nested thread paths (issue #387, PR #389); deck filesystem-path references rewritten to Python-import form (issue #375, PR #380); memo-review step 4i aligned with post-#386 skill-identity artifact types (issue #390, PR #397).
- LaTeX classes: prefix-sensitive
\ifxempty guards replaced with etoolbox\ifdefempty(issue #422, PR #423). Hardening acrossanvil-doc.cls,anvil-proposal.cls,anvil-installation.cls,anvil-datasheet.cls— the reported\long-prefix root cause does not reproduce on TeX Live 2026, but the\ifxidiom is fragile under older kernels/refactors; text-level regression tests added in all four consumers. anvil:project-migratesurgical BRIEF merge for migrate-mode--apply(PR #416).anvil:memoload_overlayhardened against path-separatorartifact_typevalues (issue #403, PR #404) and memo/lib sibling imports qualified for the package-import contract (issue #379, PR #383).anvil:rubric-rebackport--skillflag acts as force-set, not just filter (issue #374, PR #378).- Test scoping: check-2 silent-skip assertion moved past the check-3 deps breadcrumb (issue #412, PR #413).
- Dead
require_mmdc()convenience wrapper (issue #419, PR #420). Zero production callers since PR #72;check_mmdc_available()andMMDC_REMEDIATIONuntouched; theMMDC_REMEDIATIONtest coverage preserved.
v0.4.0 lands three structural primitives and a new bridge-tool skill, all driven by canary friction from the Studio's 47-thread iteration sweep. (1) Sidecar atomicity — new anvil/lib/sidecar.py primitive (staged_sidecar context manager + cleanup_stale_staging sweep) prevents partial critic-sibling writes from looking like complete sidecars to discover_critics. Piloted in memo-review (#350), then migrated across 38 critic-writing commands in 8 skills (#359). Closes the 13-partials-in-Studio canary surface. (2) Per-review rubric version stamping + /44 (and /45) migration — rubrics now write rubric_id / rubric_total / advance_threshold to _meta.json plus a top-level rubric block in _summary.md plus per-row rubric_id on _progress.json.metadata.score_history[] plus a "Rubric version transition" subsection in findings.md (#346 for memo + proposal, #357 for the remaining six skills). (3) NEW skill anvil:rubric-rebackport — operator-facing bridge tool for legacy unstamped reviews: --stamp-only infers rubric_id from a KNOWN_RUBRICS catalog; --rescore dispatches per-skill --rescore-mode <id> into a .review.rescore-<id>/ sidecar (#358, #366, #368). Skill count: 9 → 10.
Plus: memo BRIEF schema gains three new optional keys (latex_header_includes #347, recommendation_target #348, max_iterations+iteration_cap_rationale paired override #349); proposal mirrors recommendation_target: undecided on dim 8 Open decisions (#356); tier:maintenance cleanups (#360, #367, #369).
Test count: 2390 → 2962 (+572 across the release, 0 regressions).
-
Sidecar directory atomic writes (issue #350). New framework primitive
anvil/lib/sidecar.pyshipsstaged_sidecar(context manager) +cleanup_stale_staging(startup sweep) +staging_path_for(pure-function path helper) +SidecarIncompleteError(manifest-violation exception) +STAGING_SUFFIXconstant. The Studio canary surfaced 13 critic-sibling directories in partial state after mid-cycle interrupts: some of the expected files (verdict.md,scoring.md,comments.md,_summary.md,_meta.json,_progress.json) made it to disk, others did not — and the existingdiscover_criticscontract inanvil/lib/critics.pytreats any sibling dir with a canonical_review.jsonOR a complete legacy file triple as a valid critic, so partial-write shapes slipped through silently and were aggregated as under-specified sidecars. The directory-level analog of the existing file-leveltmp + os.replaceprecedent (seeanvil/lib/cite.py::_cache_write,anvil/skills/proposal/lib/synthesizer.py,anvil/skills/project-migrate/lib/apply.py,anvil/skills/deck/lib/imagegen.py) gives correctness at the directory boundary: the writer stages files into a leading-dot sibling.<slug>.<N>.<tag>.tmp/, then on clean completion the manifest is verified and the staging dir is atomically renamed to the final<slug>.<N>.<tag>/name. On exception or missing-required-file, the staging dir is left in place for forensic inspection; the nextcleanup_stale_stagingsweep removes it. The leading-dot +.tmpshape is invisible todiscover_criticsby the existingcritics.py:122-129rejection paths (the leading-dot prefix fails the<slug>.<...>shape check; the inner tagreview.tmpcarries a dot which fails the no-dot-in-tag check — belt-and-suspenders rejection). API surface re-exported fromanvil/lib/__init__.pyso callers canfrom anvil.lib import staged_sidecar, cleanup_stale_staging. Pure-stdlib implementation (no newpyproject.tomldeps — uses onlyos,pathlib,shutil,contextlib,logging). The crash-recovery contract inanvil/lib/snippets/progress.mdnow documents two shapes distinctly: version dir (single-canonical-output check —memo.md/deck.md/spec.texpresent and parses) vs critic sidecar dir (staged-rename — final-named dir exists iff complete). Pilot consumer isanvil/skills/memo/commands/memo-review.md: step 1 invokescleanup_stale_stagingon the portfolio root before the resume check; step 3 opens thestaged_sidecarcontext manager with the six-file memo-review manifest; step 11 closes the context after the final_progress.jsonwrite triggers the manifest verification + atomic rename. The other seven artifact skills (pub,deck,slides,report,ip-uspto,installation,proposal) plusmemo-reviseare a follow-on issue per CLAUDE.md §"Skill-local first, lib promotion later" — pilot on memo (11 of 13 studio partials were memo reviews) per the issue body's four-step migration plan; expand consumers in a separate PR once the primitive's behavior is canary-validated. Tests attests/lib/test_sidecar.py(19 new tests) cover the happy path (clean rename), missing-required-file branch (no rename + staging preserved +SidecarIncompleteError), exception-in-body branch (no rename + staging preserved + exception propagates), pre-existing final dir refusal (FileExistsErroron entry), pre-existing staging dir wipe-and-retry (forward-progress contract),cleanup_stale_stagingdiscrimination (removes leading-dot*.tmp/only; leaves final-named dirs,.git, plain files, and bare.tmpalone), idempotency, safe-on-nonexistent-parent / safe-on-file-parent paths, INFO-level logging shape, discovery isolation (a staging dir carrying a valid_review.jsonis NOT discovered), and a canary-replay test that synthesizes all 62 non-empty proper subsets of the six-file manifest, plants them as leading-dot staging dirs, and verifies (a)discover_criticsfinds zero of them, (b)cleanup_stale_stagingremoves all of them. Eleven additional doc-coverage guards attests/skills/memo/test_memo_review_sidecar_atomicity_doc.pypin the lib API surface (module exists, exports the documented names, re-exported fromanvil/lib/__init__.py) and the memo-review.md + progress.md prose (the wiring referencesstaged_sidecar+cleanup_stale_staging, the six-file manifest appears verbatim, the atomic-rename contract is described, the issue number is cited for traceability, the snippet distinguishes version-dir from critic-sidecar shapes). -
Sidecar primitive migration across 8 skills (issue #359). The
staged_sidecarprimitive shipped in #350 is now consumed by 38 critic-writing commands: memo siblings (6:memo-perspective,memo-figure-content,memo-hyperlinks,memo-citations,memo-image-accessibility,memo-revise --plan); pub (3); deck (7: review, audit, narrative, market, design, perspective, vision); slides (4); report (6 includingreport-promote's receipt-as-marker contract andreport-claim-figure-grounding); ip-uspto (9, includingip-uspto-finalize's terminal.final/package atomicity); installation (1); proposal (4 includingproposal-synthesize'sgaps.jsonhalf-write race closure). Each command adopts the same four-touch pattern: stale-staging sweep at the entry step,staged_sidecar(staging_path, expected_files=[...])at the initialize step, close at the_progress.jsonupdate / commit step, Outputs-section atomicity note. Subdirectory outputs under vision sidecars (slides/,pages/,drawings/) stage inside the staging dir but are not validated by the flat required-files manifest. Opt-in--write-reviewcommands (memo-figure-content / memo-hyperlinks / memo-citations / memo-image-accessibility / report-figure-content / report-claim-figure-grounding) gate both the sweep AND the staged_sidecar wrap on the flag. 185 new doc-coverage tests pin the migration (5 guards per command × 37 commands + ip-uspto-finalize). Backward-compatible with already-complete sidecars from prior versions. -
NEW SKILL
anvil:rubric-rebackport(issue #358). One-shot bridge tool — modeled onanvil:project-migrate(#297) — for stamping or rescoring legacy /40 reviews under the new per-review version stamping contract from #346. Skill count: 9 → 10.- CLI shape:
anvil:rubric-rebackport <project-tree> [--legacy-rubric=<id>] [--stamp-only | --rescore] [--apply]. Dry-run is the default; mutation requires--apply. --stamp-onlymode: walks the project tree, finds reviews lackingrubric_id, infers the stamp from aKNOWN_RUBRICScatalog (skill, total) → id mapping, or from an operator-supplied--legacy-rubric=<id>flag. Writes the stamp into_meta.json+_progress.jsonscore-history rows +_summary.mdrubric block.--rescoremode: dispatches the per-skill<skill>-review --rescore-mode <id>hook (see #368), which re-runs the reviewer into a.review.rescore-<id>/sidecar instead of.review/, withrescore_state: "completed"+rescore_id: "<id>"in_meta.json.- Detect / plan / stamp / rescore / apply / verify / orchestrate split mirroring
anvil:project-migrate's lib layout. Atomic per-review apply with snapshot/rollback; idempotent re-run. Dry-run-by-default invariant SHA-256-tested across all fixtures. - 80 unit + 16 end-to-end tests at
anvil/skills/rubric-rebackport/tests/. Tests use animportlib-based unique package-name pattern intests/_skill_lib.pyto dodge cross-skilllib/pytest cache collisions.
- CLI shape:
-
Per-review rubric version stamping for
anvil:memo+anvil:proposal(issue #346). Reviewers now writerubric_id+rubric_total+advance_thresholdinto_meta.json; a top-levelrubricblock into_summary.md; per-rowrubric_idonto_progress.json.metadata.score_history[]; and a## Rubric version transitionsubsection intofindings.mdwhenever the prior version was scored against a different rubric. The contract is additive — legacy reviews without stamps still pass verdict logic byte-identically;anvil/lib/critics.pyandanvil/lib/convergence.pyare unchanged. Snippet prose inanvil/lib/snippets/rubric.md,scorecard_kind.md, andprogress.mdis now genericized from hardcoded "/40" to per-skilltotal. New rubric IDs:anvil-memo-v2(was implicit /40),anvil-proposal-v2. 25 doc-coverage tests attests/skills/memo/test_memo_rubric_version_transition_doc.py+ six JSON fixtures undertests/skills/memo/fixtures/rubric_version_transition/+ two newtests/lib/test_rubric.pycases pinning the /44 schema-validation contract. -
/44 (+/45) rubric migration for the remaining 6 skills (issue #357). Skills
pub,installation,slides,report,deckmigrate from /40 to /44 via the addition of dim 9 Rhetorical economy (weight 4, paralleling the memo + proposal precedent).ip-usptomigrates to /45 via a skill-appropriate dim 9 Claim-spec correspondence (weight 5, preserving the flat-weight design). Customer-facing thresholds (report,deck) move to ≥39; general thresholds (pub,installation,slides) move to ≥35;ip-usptoto ≥39. Each skill's<skill>-review.mdnow writes the four stamping artifacts from #346 contract. New rubric IDs:anvil-pub-v2,anvil-deck-v2,anvil-slides-v2,anvil-report-v2,anvil-installation-v2,anvil-ip-uspto-v2.ip-uspto'sscorecard_kind: machine-summaryis preserved (distinct from the other 5 skills'human-verdict). 94 new doc-coverage tests across the 6 skills + 6 fixture directories + a/45-weight-validation lib test (test_rubric_total_45_weights_sum_to_45_validates). -
KNOWN_RUBRICScatalog entries for /44 (+/45) skills (issue #366). Following the migration in #357, theanvil:rubric-rebackportplanner'sKNOWN_RUBRICScatalog gains 6 new entries pairing each skill to its post-migration(rubric_id, total, threshold)tuple.CURRENT_RUBRIC_BY_SKILLis repointed at the /44+ entries; legacy /40 rows are retained (guarded bytest_legacy_40_rows_retained_for_stamp_only_inference). End-to-end auto-inference testTestPub44AutoInferenceexercises the full inventory → plan → resolve path: drop an unstamped /44-era pub review, run--stamp-only --applywithout--legacy-rubric, verify the stamp resolves toanvil-pub-v2. SKILL.md heuristic-inference table updated with the new rows + an explanatory paragraph on /40 + /44 coexistence. -
--rescore-mode <id>reviewer hook across all 8 skills (issue #368). Each<skill>-review.mdcommand now accepts--rescore-mode <id>at the entry step. When set, the staged_sidecar staging path is re-derived from.review.tmp/to.review.rescore-<id>.tmp/(renamed to.review.rescore-<id>/),_meta.jsonaddsrescore_state: "completed"+rescore_id: "<id>", and prior-review lookup re-targets version N (not N-1; rescore re-scores the version that's already final). Default-mode behavior is byte-identical to pre-#368 review. Theanvil:rubric-rebackport --rescore --applyend-to-end flow is now functional (was arescore_state: "scheduled"no-op placeholder until this PR). 9 commits, one per skill + test commit; 40 new doc-coverage guards (5 per skill) + a 16-subtest end-to-end test inanvil/skills/rubric-rebackport/tests/test_rubric_rebackport_rescore_end_to_end.pythat walks inventory → plan → dispatch → assertrescore_state: "completed"lands across all 8 skills + legacy byte-identity assertions for the default-mode path. Deck's split-step variant (sidecar opens at step 3,_meta.jsonwrites at step 4) handled with a back-reference to step 3's staging path. ip-uspto'sscorecard_kind: machine-summarypreservation verified by a dedicated test. -
anvil:memoper-doclatex_header_includesBRIEF knob (issue #347).BriefDocumentgains an optionallatex_header_includes: |string field. When set on a doc whoserender_engine: xelatex, the value is threaded through_progress.json.metadata.latex_header_includes_resolvedintorender_gate.gate(...)and on into_render_memo_sourcevia pandoc's--include-in-header=<tempfile>flag, with tempfile cleanup in afinallyblock (covers both pandoc success and pandoc failure paths). Silent-with-record skip on non-xelatex engines mirrors the PR #326 precedent. The shipped memotemplate.texalready had the$for(header-includes)$slot at L131-133; no template change needed. Memo-only scope;proposal/installationcompile.texdirectly without pandoc,reportinherits memo's chain for free. 6 new schema tests + 5 new render-gate tests (including the safety-critical tempfile-cleanup test). -
anvil:memoBRIEFrecommendation_target: undecidedpropagation to dim 1 (issue #348). Newload_recommendation_targethelper inanvil/skills/memo/lib/project_brief.pyresolves the structured key from the thread BRIEF (closed set:invest,pass,conditional,undecided; lenient contract — never raises, returnsNoneon every absence path).memo-reviewstep 4j loads the value, step 5 applies a new dim-1 calibration suffix when set toundecided(scoring decision-framework clarity instead of recommendation clarity), and step 9 writes the resolved value into a top-level_summary.md.recommendation_target_resolvedaudit-trail block.rubric.mdgains a §"Dim 1 —recommendation_target: undecidedcalibration" section with a 5-point ladder (5/5 → 0/5). Calibration suffix order: base → overlay →recommendation_target: undecided→ per-docdim_1_calibration. Byte-identical verdict when the key is absent (additive contract). 25 unit tests + 14 doc-coverage tests. -
anvil:memoiteration-cap unlock via BRIEF paired override (issue #349).BriefDocumentgains pairedmax_iterations+iteration_cap_rationalefields (both required when override is present — the rationale is the audit trail). Cross-field validator enforces pairing; ≥4 floor; boolean rejection; whitespace-rationale normalization. Override is mirrored into_progress.json.metadataper version and into a new §"Per-document override contract" section inmemo/SKILL.md. New BLOCKED notice subsection inmemo-revise.mdcarries the override pointer (when none set) + verbatim rationale (when already set). Memo-only scope; deck retains its existing.anvil.jsonpaired-override mechanism (already unblocking thebeacon.4studio canary). 11 schema tests + 15 doc-coverage tests + 1 worked-example regression test inBRIEF.rubric-overrides.md.example. -
anvil:proposalBRIEFrecommendation_target: undecidedpropagation to dim 8 (issue #356). Mirrors #348 into proposal — but calibrates dim 8 Open decisions instead of dim 1, because proposal dim 1 is "Intent / requirements clarity" (different semantic from memo dim 1's "Recommendation clarity"). New proposal-localanvil/skills/proposal/lib/project_brief.pywith byte-identicalload_recommendation_targethelper (closed-set tuple matches memo's so a future lib promotion is mechanical when a third consumer adopts the pattern).recommendation_targetrecognized inproposal-draftstep 3 and propagated intoproposal-reviewstep 4j load + step 5 dim-8 calibration sub-step + step 9b audit-trail block. Helper stays skill-local per "wait for the second consumer to promote" rule (guarded bytest_project_brief_stays_skill_local).templates/BRIEF.md.exampleupdated to includerecommendation_target: undecidedas the documented default for new proposals. 25 unit + 17 doc-coverage tests.
anvil:project-migratetest infrastructure adopts the unique package-name pattern (issue #367). The pytest cross-skilllib/import-cache collision thatanvil:rubric-rebackportworks around viatests/_skill_lib.pyis now also mitigated foranvil:project-migrate: newtests/_project_migrate_skill_lib.pyloads each lib module under the unique package nameproject_migrate_lib.<mod>viaimportlib.util. Filename intentionally differs from rubric-rebackport's_skill_lib.pyto avoid a secondarysys.modules['_skill_lib']collision in combined-run pytest invocations. All 7 project-migrate test files updated to import from_project_migrate_skill_libinstead offrom lib.<x> import <y>.conftest.pydropped the redundant_SKILL_ROOTsys.path insert. Combined-run AC verified in both orderings (123 tests each direction). Promotion of the unique-package-import helper toanvil/lib/deferred until the third bridge tool arrives.
-
anvil:memo# Body-filename helper (issue #295)banner relocated (issue #360). The banner had been visually stranded after #348 inserted theload_recommendation_targethelper between it and its actualbody_filename_fortarget. Moved to immediately abovebody_filename_forat L1949; the new #348 banner aboveload_recommendation_targetis untouched. Five-line move; no behavior change; all existing tests pass byte-identically. -
Dead
_PROGRESS_HISTORY_REregex removed fromanvil:rubric-rebackport(issue #369). Defined atanvil/skills/rubric-rebackport/lib/plan.py:282-284but never referenced anywhere in the codebase. Removed along with the now-orphanimport reat L43 (no other consumer ofreinplan.py). Six lines deleted; no behavior change.
Epic #328 complete + render-stack hardening + lib promotions. v0.3.2 closes the specialized-critics epic with six new critics spanning judgment enrichment, tool-evidence detection, and VLM scoring: figure + hyperlink enrichment guidance (rubric, Phase 1), hyperlink-resolver + citation-coverage detectors (Phase 2–3), and the deferred-phase trilogy figure-content (VLM, anvil/lib/), image-accessibility (memo), claim-figure-grounding (report) (Phases 4–6). Coordination conventions settled across all three Track B / Deferred critics: opt-in --write-review CLI, exit codes 0/1/2, session-lifetime content-hash VLM cache, critical-flag → BLOCK via aggregate + compute_verdict round-trip. Alongside: per-company theme primitive (#327), per-doc render_engine knob (#326), shared XeLaTeX render primitive (#312), parity_lint + marp_lint promoted to anvil/lib/, render-stack fixes (Unicode font fallback, weasyprint runtime check, lower memo words-per-page default), addressed (judgment-held) as a third convictions ledger Resolution type (#228), strongman files standardized as recognized source-of-truth materials (#330), vendored worked examples for installation + proposal (#325).
(Full bullet-by-bullet detail in the original [Unreleased] section below; moved verbatim into [0.3.2] at release time.)
anvil:memo+anvil:reportfigure-content VLM critic (Epic #328 Phase 4, issue #340). New direct-lib moduleanvil/lib/figure_content.pyships a vision-language-model pass over every figure in a memo or report version directory, scoring three axes per figure: (1) on-brand palette match againstanvil/lib/figures/palette.py(navy / muted grey / navy tint / rule grey — VLM is told the canonical hex list explicitly so it scores against the documented palette, not its prior on "what looks corporate"); (2) caption-grounding — does the caption accurately describe what the figure depicts?; (3) adjacency-grounding — does the figure support the surrounding prose claim? Direct-lib placement (NOT skill-local) per CLAUDE.md's "wait for the second consumer" rule — bothmemoandreportconsume from day one, so the two-consumer threshold is satisfied at ship. Two figure-discovery paths: (a) PDF page extraction viapdftoppm(subprocess; graceful-degrades when the tool is missing — top-levelreason+ zero findings, mirroring thecheck_*_available()family inanvil/lib/render.py); (b)<version_dir>/figures/walk for direct PNG / JPG / WebP sources (SVG sources recorded as unverified via top-level reason — VLM consumes raster bytes). The critic composes the existingVisionCritic/VisionRubricsubstrate fromanvil/lib/vision.py— no reimplementation. Per-figure VLM cost cap (default 1 call per figure per run, configurable viavlm_budget_per_figure=N). Session-lifetime in-process content-hash cache (sha256(figure_bytes) → VLM payload) so a 30-page customer report with a repeated header logo costs 1 VLM call total, not 30 — load-bearing for the report consumer specifically. Cache shipped inline infigure_content.py; promotion toanvil/lib/vision_cache.pyis deferred until Phase 5 (image-accessibility, #341) reaches for the same shape (per the issue body coordination note). Critical-flagcritical_figure_misrepresents_claimfires on VLM-detected caption-vs-figure contradiction; aggregator'scompute_verdictshort-circuits toVerdict.BLOCK. Per-figure scores are clamped to rubric range defensively; sub-threshold dims (score ≤ max/2) auto-emit aFindingeven when the VLM omits a narrative entry (safety net for "low score, no narrative entry"). Per-figure scores roll up to the three rubric rows via per-dimension mean (rounded to nearest int via round-half-to-even) so the_review.jsonreads as a single 3-row scorecard, total /15. Two command docs land:anvil/skills/memo/commands/memo-figure-content.mdandanvil/skills/report/commands/report-figure-content.md— both document the CLI shape (python -m anvil.lib.figure_content <version_dir> [--write-review], exit codes 0 clean / 1 findings / 2 invocation error), the three scoring axes, the severity ladder, the auto-discovery wiring, the failure-mode table, and the cross-skill consumer parity rationale (reports are figure-heavy; the cache amortizes the cost for them specifically). Auto-discovery via the existing<version_dir>.<tag>/contract —.figure-content/sibling is picked up byanvil/lib/critics.py::discover_criticswith no aggregator changes (third consumer of the convention, after Phase 2's.hyperlinks/and Phase 3's.citations/). Tests attests/lib/test_figure_content.py(43 new tests) cover the full #340 AC: three-axis rubric shape (each /5, max_total=15), prompt construction (rubric dim names + critical-flag taxonomy + caption + adjacency + brand palette hex list), figure discovery (empty version dir, figures/ dir PNGs, SVG-skip, pdftoppm graceful-degrade), the three scoring axes (on-brand / caption / adjacency clean + finding-emitting cases), VLM cache deduplication (identical content → no second VLM call; distinct content → distinct calls; external cache lives across calls), critical-flag firing on contradiction + Verdict.BLOCK short-circuit, Review schema compliance (kind=vision + rendered_artifact required + round-trip through model_validate), auto-discovery + aggregate round-trip, per-figure score roll-up to rubric mean, CLI smoke (exit codes + --write-review sibling creation), top-level reasons (empty version dir + JSON shape), pdftoppm preflight, command-doc lock-in (both memo and report docs ship with the expected CLI / sibling-name / critical-flag invariants), and VLM budget cap. All VLM calls in tests are MOCKED via thecallback=injection point onVisionCritic; no network access required for the test suite. The Phase 4 settle holds: zero schema changes inanvil/lib/review_schema.py, zero changes to existing skill rubrics, zero changes to existing command step structures. TheFindingmodel lock-in test asserts the deferred experimental fields (action,target_anchor,proposed_content) have NOT crept in.anvil:memoimage-accessibility critic (Epic #328 Phase 5, issue #341). New tool-evidence + VLM-hybrid critic atanvil/skills/memo/lib/image_accessibility.pythat scans the body markdown of a memo version dir for three classes of accessibility defect: (a) missing alt text (markdownwith empty alt, HTML<img>with noalt=attribute oralt=""), (b) inadequate alt text (literal placeholdersimage/figure/chart/img/picture/graphic/diagram; single-word generic prefixes without further subject —screenshot/photo/illustration/drawing/icon; sub-10-character non-descriptive alt), and (c) broken paths (image file does not exist at the resolved path; reusesmemo_image_refs.lint_sourcefor the determination — no duplicate filesystem walking per the issue body AC). Missing/inadequate-alt findings carry a VLM-generated candidate inFinding.suggested_fixwhen a callback is wired (viaanvil/lib/vision.py); the CLI default is OFFLINE-safe and uses a deterministic template instead. Broken-path findings carry a closest-match suggestion viadifflib.get_close_matches(mirrors the citation-coverage pattern from #337) when a similarly-named file exists nearby (propose_edit); otherwise emit a propose_removal template. VLM cost discipline: content-hash cache (in-process dict, session-lifetime) ensures identical image bytes hash to one VLM call; the cache shape mirrors the coordination point with Phase 4 (figure-content, #340) so a futureanvil/lib/vision_cache.pypromotion is a one-line import swap. No critical flags — a11y is advisory in v0 per the issue body settle; severity ladder ismajor(missing alt, broken path) /minor(inadequate alt). Kind decision (single sibling, single Kind): ships as ONE<version_dir>.image-accessibility/sibling withkind=Kind.TOOL_EVIDENCEfor the entire Review. Broken-path findings carrytool_calls=[](no tool invocation); missing/inadequate-alt findings carry oneToolCallentry per finding describing the VLM invocation. The rejected alternative (two siblings — one TOOL_EVIDENCE, one VISION) was incompatible with the schema validator:Kind.VISIONrequiresrendered_artifactset on the Review (one rendered artifact per Review), but the critic spans N images per memo (one per ref). The N-images-per-Review shape is a clean fit for TOOL_EVIDENCE and a structural mismatch for VISION. Suppression directive<!-- anvil-lint-disable: memo_image_accessibility_{missing_alt,inadequate_alt,broken_path} -->supported per-line, mirroring the existingmemo_image_refs_existdiscipline. New command docanvil/skills/memo/commands/memo-image-accessibility.mddocuments the CLI entry-point (python -m anvil.skills.memo.lib.image_accessibility <version_dir> [--write-review]), the three classes with severity table, the kind-decision rationale, the VLM cost discipline, the auto-discovery contract, and the failure modes / re-run patterns. CLI shape mirrors the Phase 2 (hyperlink_resolver, #338) / Phase 3 (citation_coverage, #337) sibling contracts — opt-in write via--write-review, exit0clean /1on findings /2on invocation error. Skill-local first per CLAUDE.md — promotion toanvil/lib/is deferred untilpub/report/proposalreach for the same primitive. Zeroanvil/lib/changes, zero schema changes inanvil/lib/review_schema.py, zero changes toanvil/skills/memo/rubric.md(Phase 1 from #333 just touched dim 3 weighting), zero changes tomemo-review.mdstep structure (4f/4g/4h/4i load-bearing from PR #332/#334). New tests attests/skills/memo/test_image_accessibility.py(46 tests; VLM calls MOCKED) cover all eight acceptance criteria: valid alt passes, missing alt (markdown empty + HTML no-attribute + HTML empty-string) emits with VLM candidate, placeholder alts (image,figure,chart,screenshotalone vs with subject, case-insensitive) emit inadequate, sub-10-char alt emits inadequate, broken path no-nearby emits propose_removal, broken path with nearby (cp -r footgun shape + typo) emits propose_edit with closest-match, VLM cache deduplicates identical bytes (one callback invocation for two identical PNGs), the.image-accessibility/sibling is auto-discovered bydiscover_critics+ round-trips throughload_review+aggregatewithout raising, the schema validator accepts every emitted Finding (all carrytool_calls), suppression directive (same-line + line-above), URL/absolute path skipping, graceful-degrade on VLM-callback-raises and missing body file, and CLI exit codes. Full test suite green — 1319 passed, 2 skipped (network smoke tests undertests/lib/).anvil:reportclaim-figure-grounding critic (Epic #328 Track B Phase 6, issue #342). New deterministictool_evidence-class critic atanvil/skills/report/lib/claim_figure_grounding.pythat scans the body markdown of a report version dir for prose references to figures / tables / charts whose label is not present in the version directory's known-label roster. The critic recognizes three prose-detection shapes (prepositional:see Figure 3,as shown in Chart B,per Table 2,in Figure 3.1; subject-verb:Figure 3 illustrates …,Table 2 reports …,Chart 1 shows …; parenthetical:(Figure 3),(Table 2)) over three label-id forms (integer3, dotted3.1, single uppercase letterA). The label-validation step walks three ground-truth sources inside the version dir: LaTeX\label{<prefix>:<id>}macros in any.md/.tex/.latexfile (recursive); markdown pandoc-style anchors{#<prefix>:<id>}on headings or images; and filenames infigures/andexhibits/subdirectories whose stem matches<prefix>[-_.]<id>(e.g.figure-3.png,fig_a.svg,table-2.md,chart-1-2.pdf). Missing references emit amajor-severityFindingwith a closest-match suggestion (integer distance ≤ 2 for numeric ids;difflib.get_close_matcheswith the 0.6 cutoff for alphabetic ids — mirroring the citation-coverage precedent from PR #337; class-restricted so suggestions never crossFigure/Table/Chart) or the "add or remove" fallback when no close candidate exists. Per the issue body's "Dedupe by(label_class, label_id)" requirement, multiple references to the same missing label produce one finding with the first reference's line + verbatim text anchoring the evidence span and anadditional_references: Ncount in the rationale. Acritical_promised_figure_missingflag fires on any missing reference (the issue body's "Critical flag on any non-existent reference" contract), short-circuiting the aggregator's verdict toVerdict.BLOCK. False-positive disciplines mirror citation-coverage: quoted material (blockquotes, fenced code, inline backticks) is silently skipped; same-line same-(class, id)duplicates collapse at detection time. New command docanvil/skills/report/commands/report-claim-figure-grounding.mddocuments the CLI entry-point (python -m anvil.skills.report.lib.claim_figure_grounding <version_dir> [--write-review] [--body-filename <name>]), the three detection classes with examples, the three ground-truth roster sources, the closest-match suggestion algorithm, the dedupe contract, the auto-discovery wiring (<version_dir>.claim-figure-grounding/sibling matches the<version_dir>.<tag>/pattern thatanvil/lib/critics.py::discover_criticsrecognizes without code changes), and the failure-mode / re-run patterns. The CLI shape is byte-faithful to the Phase 2 / 3 precedents (PRs #338 / #337):--write-reviewis opt-in; exit codes are0clean,1findings,2invocation error. Skill-local first per CLAUDE.md §"Skill-local first, lib promotion later" — promotion toanvil/lib/is deferred until the sibling Phase 5 critic (#340figure-content) lands and afigure_discoverypromotion candidate emerges. Zeroanvil/lib/changes, zero schema changes inanvil/lib/review_schema.py, zero changes to other skills. New tests attests/skills/report/test_claim_figure_grounding.py(59 tests) cover the module surface guards; positive cases for all three prose-detection shapes (prepositionalsee Figure N/as shown in Chart B/per Table 2/in Figure 3.1; subject-verbFigure 3 illustrates/Table 2 reports/Chart 1 shows; parenthetical(Figure 3)/(Table 2)); positive cases for the abbreviationFig. 3form; grounded paths for all three ground-truth sources (LaTeX\label{fig:3}in body markdown; markdown{#fig:3}anchor on a heading; filenamefigure-3.pnginfigures/; filenamefig-3.svginexhibits/; dotted-id filenamefigure-3-1.pngforFigure 3.1; filenamechart-a.pngforChart A; filenametable-2.mdforTable 2); the closest-match suggestion algorithm (numeric distance 1 / 2 / 3, class-restricted, dotted-id); the dedupe contract (two references one finding; distinct labels separate findings; first-reference evidence-span anchoring); the false-positive disciplines (blockquoted / fenced-code / inline-backtick / same-line dupes); the label-roster discovery from each of the three sources (LaTeX macros; markdown anchors;figures/filenames;exhibits/filenames; dotted filename normalization; ignoring unrelated LaTeX prefixes\label{sec:intro}; ignoring non-label files likeREADME.mdinfigures/); the critical-flag heuristic (fires on any missing; does not fire on clean; justification lists first three missing labels with+N more); the filesystem integration viascan_version_dir(with body filename override); the auto-discovery contract (verifyaggregatemerges findings from a.claim-figure-grounding/sibling and the verdict short-circuits toBLOCK); the CLI entry-point shape (write-review opt-in; clean-scan-exits-zero; missing-dir-exits-two; grounded body exits zero); and a doc-coverage suite over the new command file. Full test suite green at 1332 passed, 2 skipped (network smoke tests undertests/lib/).- CLI entry-point convention for deferred-phase critics (Epic #328 Phase 4 + #341 + #342):
python -m anvil.lib.<module> <version_dir> [--write-review]. Documented as the agreed coordination point with the parallel deferred-phase critics #341 (image-accessibility) and #342 (claim-figure-grounding) so all three share an invocation shape. Phase 4 is the first deferred-phase critic to ship and serves as the reference. anvil:memoconvictions ledger extended withaddressed (judgment-held)Resolution type (#228). The changelog-embedded convictions ledger (the carry-forward record that lets the reviser remember why a prior reviewer's finding was declined or judgment-held across versions) gains a third Resolution type alongside the existingdeclined:addressed (judgment-held) — <judgment>. This covers the #226 structural defect inherited from the killed_convictions.mdapproach (#229) — the prior write trigger only captureddeclinedrows, missing load-bearing convictions from canary threads where the reviser addressed a finding but the addressing judgment was structural (e.g., the $450B-as-anchor-not-TAM reframe, the 6-risks-is-enough scope limit, the bear/base/bull sufficiency call). The new type lives inchangelog.md(always written regardless ofverdict.advance), inheriting the #225 fix for free — no dead-letter risk. Single-file scope:anvil/skills/memo/commands/memo-revise.md— CLI invariants bullet now names bothdeclinedandaddressed (judgment-held)as ledger types the scope filter must not drop; step 7 always-include carries forward both types to the next reviser; step 9 changelog example adds rows for both with a three-type vocabulary definition; the Notes / Convictions ledger section explains both write triggers and the "load-bearing framing decisions only" usage guidance. Falsifiability contract documented in commit history (3-week observation window; kill-switch A — disuse — if grep returns empty; kill-switch B — ineffective — if marker used but judgment re-raised without reviser acknowledgment).anvil:memocitation-coverage critic CLI aligned withhyperlink_resolver(#338) sibling (PR #337 judge follow-up). Refactored_cli_maininanvil/skills/memo/lib/citation_coverage.pyso the two Track B detectors feel interchangeable from a consumer / CI perspective: writes to<version_dir>.citations/are opt-in via a new--write-reviewflag (default invocation prints theCoverageResult.to_json()payload to stdout with no filesystem side effects), and the exit code is non-zero when findings exist (0clean,1on any unhooked claim or broken citation,2on invocation error). Matches the byte-faithful contract documented in PR #338's_cli_main. Doc update toanvil/skills/memo/commands/memo-citations.mdreflects the new shape; new tests pin (a)--write-reviewopt-in semantics, (b) no-write default, (c) clean-scan-exits-zero, (d) missing-dir-exits-two, (e) the multi-claim-one-cite line-level-hook behavior (a line carrying two claims with only one\cite{}suppresses BOTH — documenting the line-level hook granularity design note), and (f) lock-in cases forTable 2 reports the breakdownandsee page 12 for detailsas structural self-references that must not fire as numeric claims.anvil:memocitation-coverage critic (Epic #328 Track B Phase 3, issue #336). New deterministictool_evidence-class critic atanvil/skills/memo/lib/citation_coverage.pythat scans the body markdown of a memo version dir for two failure modes: (a) load-bearing claims that are unhooked — numeric ($2.3B,42 %,12 ms,Q3 2024), named-author (Smith (2023),Karpathy's 2024 talk), quantitative summary (we found that…,the median was…), and date-pinned events (On March 5, 2025,); and (b) broken\cite{key}/[@key]markers wherekeyis not present in any discovered refs source (per-threadrefs/+ portfolioresearch/viarefs_resolver.resolve_refs_dirs, plus the version dir's ownrefs.bib). Broken keys carry a closest-match suggestion viadifflib.get_close_matches(stdlib alternative;cite.pydoes not ship a Levenshtein helper). The critic emits a typedReview(kind=tool_evidence) into the<version_dir>.citations/sibling, auto-discovered byanvil/lib/critics.py::aggregatewith no aggregator change. False-positive discipline is deliberately conservative — version numbers in technical context (Python 3.12,Node.js 22.0.0), self-referencing structural markers (see Figure 3,Section 4), hedged claims (roughly 30 customers,an estimated $1B market), and quoted material (blockquotes, fenced code, inline backticks) all default to NOT-emit to preserve dim 3 reviewer headroom. Acritical_unsourced_load_bearing_claimflag fires when more than 5 unhooked claims surface OR any single named-author claim is unhooked (the named-author short-circuit reflects that class as the highest-confidence positive shape — an unhooked one is a fabrication risk). New command docanvil/skills/memo/commands/memo-citations.mddocuments the CLI entry-point (python -m anvil.skills.memo.lib.citation_coverage <version_dir>), the detection contract, the severity ladder, and the auto-discovery contract. The CLI shape is the documented default until Phase 2 (hyperlink_resolver, #335) lands and a sibling convention is coordinated. Skill-local first per CLAUDE.md — promotion toanvil/lib/is deferred untilpub/report/proposalreach for the same primitive. Zeroanvil/lib/changes, zero schema changes inanvil/lib/review_schema.py, zero changes toanvil/lib/cite.py. New tests attests/skills/memo/test_citation_coverage.py(46 tests) cover all four detector classes with both positive AND false-positive cases, both citation styles (\cite{}and[@]) with both broken-key and valid-key paths, the closest-match suggestion path (\cite{karpathy204}with refs containingkarpathy2024suggests the correction), the critical-flag heuristic at both threshold and named-author short-circuits, the filesystem integration viascan_version_dir+collect_refs_keys, the auto-discovery contract (verifyaggregatemerges findings from a.citations/sibling without aggregator changes), and the CLI entry-point smoke. Full test suite (1239 passed, 2 skipped — network smoke tests).anvil:memohyperlink-resolver critic (Epic #328 Track B Phase 2, issue #335). New skill-local lib moduleanvil/skills/memo/lib/hyperlink_resolver.pyships a deterministic link-validation pass over a memo version directory and emits a canonical_review.json(kind=tool_evidence,critic_id=hyperlinks) for broken cross-thread refs, broken markdown internal paths, broken wiki-links, and (behind the off-by-default--check-externalflag) failing external HTTP/HTTPS links. Four link classes: (1) cross-thread refs[[../slug/slug.N]]validated viacross_thread_refs.resolve_cross_thread_ref(no duplicate parsing — delegates entirely to the existing typed resolver), severityblockeron failure AND raises the load-bearingcritical_broken_cross_thread_anchorflag so the aggregator's verdict short-circuits toBLOCK; (2) markdown internal links[text](path/to/file)validated against the version dir (trailing#anchor/?querystripped — anchor validity is out of scope), severitymajoron missing file; (3) markdown external links[text](https://...)recorded by default but NOT probed (offline-safe / CI-reproducible), probed via subprocesscurl -Iwith a 5-second timeout when--check-externalis set, severitymajoron 4xx / 5xx / timeout; (4) wiki-links[[document-name]]validated against the enclosing project'sBRIEF.mddocuments:list (discovered viaproject_discovery.discover_thread_root+project_brief.load_project_brief), severitymajoron unknown slug or missing BRIEF. New command docanvil/skills/memo/commands/memo-hyperlinks.mddocuments the invocation, the four link classes with severity table, the auto-discovery wiring (<version_dir>.hyperlinks/sibling matches the<version_dir>.<tag>/pattern thatanvil/lib/critics.py::discover_criticsrecognizes without code changes), and the seven failure-mode entries with operator actions. Implementation reuses existing primitives:cross_thread_refs.pyfor the internal-ref half (~60% of the link surface),project_brief.py+project_discovery.pyfor the wiki-link half,review_schema.pyfor the canonicalReview/Finding/CriticalFlagshape (no schema delta — settled at Epic #328 kickoff). Tests attests/skills/memo/test_hyperlink_resolver.py(28 new tests) cover every issue #335 acceptance criterion plus schema-compliance regression guards (asserting theFindingmodel has NOT grownaction/target_anchor/proposed_contentfields the Phase 2 settle deferred), the auto-discovery round-trip throughaggregate, the CLI entry-point exit codes (0 clean / 1 findings / 2 invocation error), and a doc-coverage suite over the new command file.- CLI entry-point convention for Epic #328 Track B mechanical detectors:
python -m anvil.skills.memo.lib.<module> <version_dir> [--check-external] [--write-review]. Documented as the agreed coordination point with the parallel Phase 3 citation-coverage critic (#336) so both detectors share an invocation shape. - Output-dir naming convention for Epic #328 Track B siblings:
<version_dir>.<critic-tag>/(.hyperlinks/here,.citations/for #336). Both forms are picked up byanvil/lib/critics.py::discover_criticsvia the<version_dir>.<tag>/pattern without code changes — the contract was already shipped; this just exercises the second consumer of the convention. anvil:memofigure + hyperlink enrichment guidance for the standard reviewer (Epic #328 Track A, issue #333, documentation-only). Extendsanvil/skills/memo/rubric.md§"Refs back-check (dim 3)" with a new §"Figure + hyperlink enrichment (dim 3, advisoryscope: expand)" subsection that instructs the standardmemo-reviewreviewer to emitscope: expandenrichment findings incomments.mdfor four classes of gap: (1) missing or off-brand figures (load-bearing claims where a chart / table / diagram would clarify; figures that visibly diverge from the brand palette inanvil/lib/figures/palette.py), (2) missing or inadequate alt text on markdownand HTML<img>references (empty alt, literalimage/figure/chartplaceholders, sub-10-character non-descriptive alt — the existingmemo_image_refs_existlint covers existence only), (3) prose references to figures that do not exist in the version directory (see Figure N,as shown in Chart M,Table K reportspatterns whose referenced anchor is missing), and (4) suggested-but-missing hyperlinks on load-bearing claims that could anchor to a known external source (advisory; lower priority than 1–3). Severity ladder reuses the existingmajor/minor/nitcomments.mdvocabulary; scope tag isscope: expand(participates in the existing §"Expand trim-candidate rule" so anymajorenrichment proposing ≥1 paragraph names what could be trimmed to fund the addition). Enrichment iscomments.md-side only and does NOT alter the dim 3 /6 score — the existing dim 3 sub-rules (citation hooks, refs back-check, strongman back-check, cross-thread back-check) own the per-instance deduction surface; this layer is enrichment that proposes additions for the next revision to consider.- Authoring-surface precedent codified: enrichment guidance lives in
rubric.md(per-skill, durable, ships with the skill) rather than inrubric_overridesonBRIEF.md(per-thread, ephemeral). The decision is load-bearing for future enrichment scopes (image accessibility, claim grounding, etc.): cross-thread always-on guidance ships inrubric.md; per-thread tuning lives asrubric_overrides.dim_N_calibrationcalibration suffixes that compose with the rubric via the existing Reader-dispatch-order contract. A per-thread override that scopes out figure enrichment (e.g., a synthesis brief where figures are intentionally minimal) attaches as a calibration suffix on dim 3's justification and the reviewer respects it inline. - Phase A discipline preserved: zero
anvil/lib/changes, zero schema changes inanvil/lib/review_schema.py, zero new Python detector, zero new critic-sibling type, zero new command. The change is documentation inanvil/skills/memo/rubric.mdonly.commands/memo-review.mdstep structure is unchanged (steps 4f / 4g / 4h / 4i renumbered by the just-merged PR #332 are left alone — the new enrichment guidance attaches to existing step 5's dim 3 scoring loop via thecomments.mdwrite at step 8, no new step required). The contract follows the same shape as the strongman back-check (#330 / PR #332) and the prior back-check Phase A precedents (PR #144 / PR #250 / #236). - Backwards compatibility: this sub-rule is always active (no on-disk substrate trigger required — unlike the strongman back-check). A memo with no figures, no image refs, and no figure-reference prose produces zero enrichment findings of classes 1–3 (and likely zero of class 4 if the citation-hook contract is fully satisfied). A reviewer running on a memo authored before this sub-rule shipped behaves byte-identically — there are no missing-figure findings to emit when no claim load-bearingly anticipates a figure.
- Validation step (deferred to follow-on): the canary author's
.enrich/smart-actuators fixtures (offered in Epic #328's comment thread, not yet attached to #333) serve as the future Phase B regression anchor in the same waytests/fixtures/summary_detail_consistency/northwind_gen_attribution/anchors the summary-detail Phase B detector. The rubric guidance ships first; fixture validation lands when the fixtures are attached. A fixture-request comment can be filed on #333 without blocking landing. - Track A canary validation for Epic #328: this is the first active phase of the reframed Epic #328 (#328 was reshaped 2026-06-05 after the studio canary demonstrated that judgment enrichment via rubric guidance captures ~80% of the value of the original five-critic plan at ~30% of the implementation surface). Phases 2 (
hyperlink-resolver) and 3 (citation-coverage) are Track B mechanical detectors — they are informed by Phase 1's outcome (specifically: whether the canary's "judgment enrichment is enough" hypothesis holds against the smart-actuators fixtures, and whether loading the existing reviewer with figure/hyperlink enrichment scope dilutes its judgment quality on existing dimensions) but do not block on it. anvil:memostrongman files standardized as recognized source-of-truth materials (#330, documentation-only formalization). The studio canary's strongman practice (paired adversarial briefs scoped per research question, stored alongside research inputs that feed the memo draft) is formalized asstrongman-for.md/strongman-against.mdrecognized filenames in the resolved refs-dir list. Three studio threads actively use the pattern in production (meridian-licensing with 5 vertical pairs, smart-actuators/investment-memo with 7 vertical pairs, smart-actuators/broadcom-thesis with one pair on the Broadcom analogy); the second-consumer threshold for the pattern shape was reached months ago. The strongman files live in the research/input layer (refs/orresearch/<topic>-analysis/), NOT as a critic sibling of a versioned memo dir — they are author-supplied substrate, not critique output.anvil/skills/memo/SKILL.md— extended §"Source-of-truth materials" withstrongman-for.md(dim 2 Thesis coherence calibration substrate) andstrongman-against.md(dim 3 Evidence quality back-check substrate) as recognized filenames, plus a §"Strongman scoping convention" paragraph documenting the per-thesis scoping (one named thesis or research question, not the memo as a whole; multiple pairs may exist per thread root organized intorefs/<topic>/orresearch/<topic>-analysis/).anvil/skills/memo/rubric.md— extended §"Refs back-check (dim 3)" with a §"Strongman back-check (dim 3)" sub-paragraph naming the three-valued classifier (ADDRESSED/PARTIALLY_ADDRESSED/NOT_ADDRESSED) and the load-bearing-aware severity ladder (load-bearingNOT_ADDRESSED→critical+ critical-flag candidate + -2 dim 3; non-load-bearingNOT_ADDRESSED→important+ -1;PARTIALLY_ADDRESSED→important+ -1).strongman-for.mdcalibrates dim 2 (Thesis coherence) — the reviewer's dim 2 justification SHOULD note whether the memo's thesis aligns with the strongest version of its own argument whenstrongman-for.mdis present.anvil/skills/memo/commands/memo-review.md— added new sub-step 4g (strongman back-check, reviewer-prose-only Phase A) implementing the per-objection classifier; renumbered the prior 4g (rubric_overrides loader) to 4h and the prior 4h (artifact-type overlay) to 4i. The new step mirrors the structural shape of step 4f (cross-thread cite back-check) with a 3-valued vocabulary instead of 4-valued. Added thestrongman_back_checktop-level block to the_summary.mdschema (sibling tosummary_detail_consistency,cross_thread_cite_consistency,scope_distribution,rubric_overrides) carryingran,strongman_against_files_scanned,strongman_for_files_scanned,objections_enumerated,findings_count,findings_by_severity,findings,critical_flag_candidate. Added the## Strongman back-check findingssubsection tofindings.md(always emitted; three shapes — findings present / skipped / clean). Extended step 7 verdict aggregation with the strongman critical flag, step 10 verdict-write with theStrongman: NOT_ADDRESSED (load-bearing)critical-flag entry and the strongman-driven top-3 revision priority.anvil/skills/memo/commands/memo-draft.md— extended step 3 Read inputs with the §"Strongman drafter contract" sub-paragraph documenting that when strongman files are present the drafter reads them as authoritative load-bearing substrate and either directly addresses or explicitly scopes out the named counter-arguments insidestrongman-against.md(reviewer will check at step 4g);strongman-for.mdis read as load-bearing thesis-statement context for dim 2 alignment. Extended step 6 Evidence §"Source-of-truth refs as authoritative hooks" to list strongman files among the canonical source-of-truth shapes.- Phase A discipline preserved: zero
anvil/lib/changes, zero schema changes toanvil/lib/review_schema.py, zero new Python detector, zero new critic-sibling type, zero new command. The entire change is documentation in four files insideanvil/skills/memo/(SKILL.md, rubric.md, commands/memo-review.md, commands/memo-draft.md). The contract follows the existing back-check Phase A precedent: ship as reviewer-prose discipline first; promote to detector if canary signal warrants. - Backwards compatibility: threads with no strongman files of either kind are unaffected — the strongman back-check is inactive (the reviewer emits
ran: falsewith a reason; the drafter proceeds normally). Threads where the operator wrotestrongman-for.mdbut nostrongman-against.mdget dim 2 calibration substrate but no dim 3 findings; threads withstrongman-against.mdbut nostrongman-for.mdget the full dim 3 back-check but no dim 2 calibration note. The canary's three active threads (which already write both files in production) get the full contract.
anvil:memorender: removed redundant<link rel="stylesheet">fromtemplate.html(#319). The bare relativehref="styles.css"was resolved by weasyprint against pandoc's CWD (the consumer repo root on canary installs), producing a stderrERROR: Failed to load stylesheet: file:///…/styles.csswhile the absolute--csspath passed byanvil/lib/render_gate.pyseparately loaded the correct file. The render succeeded and the styling landed because--cssdid the work, but the failed-fetch warning sailed through and would be promoted to a hard error by the--fail-if-warningsinvariant already active inrender_gate.py:972. One-line delete ofanvil/lib/memo/template.html:36; stylesheet delivery is now solely via pandoc--css(the existing--cssinvariant inrender_gate.pyis untouched and remains correct). Consumer-visible effect: no more spurious weasyprint stderr line onanvil:memo-renderruns against the canary'soutput/clear-signal/memo/memo.1/. New regression guardtest_lib_memo_template_html_avoids_bare_stylesheet_linkintests/lib/test_memo_render_detection.pyasserts the<link rel="stylesheet">shape stays absent (modeled ontest_memo_styles_page_size_doc.py::test_styles_css_page_block_avoids_bare_letter_keyword, the same inverse-shape "this must NOT appear" pattern). The xelatex fallback path (template.tex) is unaffected.
parity_lintpromoted toanvil/lib/parity.py(#317, PR #205 follow-on #2 of 4). Per CLAUDE.md §"Skill-local first, lib promotion later", the second-consumer trigger fired on merge of PR #215 (memo-side mirror); this is the canonical one-line import-path swap. The shared module ships the byte-identical core (extractors, normalization, escape-hatch,Finding,LintResult) plus a unifiedlint_parity(primary_path, sibling_path, primary_kind, sibling_kind)wrapper. Both skill-local modules (anvil/skills/deck/lib/parity_lint.py,anvil/skills/memo/lib/parity_lint.py) shrink to thin re-exports preserving the public API byte-compatibly — the deck-review step 5d / memo-review step 4d invocations and the two*-review.mddoc-coverage tests remain unchanged.
Project-organization model lock + canary-driven completion of the multi-thread named-document layout. v0.3.0 closes out the #283 epic (project-as-thread-root layout discovery, project-level BRIEF.md parser, cross-thread reference validation, .latest symlink resolution, rubric overlay selection from BRIEF, shared research/) and locks the model with the three-part contract change: body filename echoes doc slug (#295), BRIEF.md absorbs all per-project anvil config (#296), and a new anvil:project-migrate skill bridges existing projects (#297). Skill count: 8 → 9.
The full project-as-thread-root layout shipped across six sub-deliverables, matching the canary's smart-actuators model where one project (e.g. foundry, northwind-pitch-strategy) holds multiple named documents (investment-memo, team-thesis, execution-plan, latency-wall, technical-vision) as siblings inside <project>/<slug>/<slug>.<N>/.
- Sub-deliverable 1 — dual-layout thread-root discovery (#284 / PR #290).
anvil/skills/memo/lib/project_discovery.pywalks upward from any path and recognizes the enclosing thread root.discover_thread_root()returns a typedDiscoveryResultcarryingthread_root,layout,project_root, andslug. (The classic-layout dispatch ships here for v0.2.0 back-compat, then is removed in #295 — see Removed below.) - Sub-deliverable 2 — typed project BRIEF parser (#285 / PR #292).
anvil/skills/memo/lib/project_brief.pydefines pydantic models for the project BRIEF:ProjectBrief(project/audience/hard_rules/documents) andBriefDocument(slug/artifact_type/target_length). Closed-endedArtifactTypeenum with five seed values (investment-memo,position-paper,tactical-plan,vision-document,descriptive-thesis) — adding a new type is a documented anvil feature request. Lenient + strict loaders; optionalvalidate_dirsflag for slug-vs-directory divergence checking (listed-but-missing → warn, on-disk-but-unlisted → hard error). - Sub-deliverable 3 — rubric overlay selection from BRIEF (#286 / PR #294, absorbs closed #278).
anvil/skills/memo/lib/rubric_overlays.pyplus per-artifact-type overlay JSONs underanvil/skills/memo/rubric_overlays/. The reviewer resolves the active overlay by reading the project BRIEF'sartifact_typefor the current thread's slug and applies the matching overlay on top of the base /44 rubric. Five overlays ship at v0.3.0 matching the five registered artifact types. Closes #278 (per-thread rubric overlays for non-memo artifact types — folded into this issue's BRIEF-driven dispatch). - Sub-deliverable 4 — cross-thread reference validation (#287 / PR #291).
anvil/skills/memo/lib/cross_thread_refs.pywalks the project's siblings during reviewer back-check (dim 3) and flags brokensee <slug>.<N> §<section>references: cite-target slug doesn't exist, version doesn't exist, or the cited section anchor is missing. New_review.json.cross_thread_refsblock carries the audit trail. - Sub-deliverable 5 — canonical
.latestsymlink resolution (#288 / PR #293).anvil/skills/memo/lib/latest_resolution.pyresolves<slug>.latest/to the highest-numbered concrete version dir, with optional pinned-symlink override. SKILL.md documents the convention; the framework consistently respects whichever version.latestpoints at. - Project-level shared
research/(#280 / PR #281).<project>/research/is a first-class peer of the document folders — cross-thread evidence pool that any sibling's reviewer can cite. Composes with the existing per-thread<slug>/refs/.
NEW skill anvil/skills/project-migrate/ — one-shot bridge tool that migrates existing studio projects to the post-#295 / post-#296 canonical model (project root + BRIEF.md absorbing all anvil config + <slug>.md body filename + <project>/<slug>/<slug>.<N>/ shape). Closes the third leg of the three-part model lock (#295 + #296 + #297). Skill count: 8 → 9.
- Commands:
/anvil:project-migrate <project-dir>(dry-run, NO mutations),/anvil:project-migrate <project-dir> --apply(execute),/anvil:project-migrate <project-dir> --report(markdown report only). - Three recognized current shapes: pre-#283 classic (
<stem>.N/sibling version dirs, no project BRIEF), post-#283 with.anvil.json(project BRIEF + per-thread.anvil.json), fully-migrated (target shape — no-op). - Per-project steps: detect → plan → (optional) apply → verify. Each
DocumentPlanis independently applyable; rollback is per-doc via a<project>/.anvil-migrate-rollback/<slug>/snapshot. - Cross-thread reference rewriting: planner walks each body markdown for old-stem tokens (e.g.,
memo.7) and emits aContentRewritethat updates them to the new slug-shaped reference (<slug>.7) after the directory renames land. - Git integration:
git mvis preferred when the project is under git so history follows; falls back to plainshutil.moveotherwise. - Opinionated: no back-compat flags. The skill converges existing projects onto one shape; it does not preserve the legacy shape under any option.
- Idempotent: re-running
--applyon a fully-migrated project is byte-identical zero-diff. - rubric.md OMITTED: migration output is mechanical; no /40 dimension to score.
anvil:memo-migratecarve-out: the LaTeX bootstrap path continues to write a legacy.anvil.json;project-migrateruns as the documented post-step that consolidates it into the project BRIEF.- Per-doc rollback fault-injection test (#301 / PR #302) exercises the
_restore_docbranch via_renamemonkeypatch — verifies failing doc rolls back to pre-migration shape while successfully-migrated prior docs stay migrated. - Touched: NEW
anvil/skills/project-migrate/SKILL.md, NEWanvil/skills/project-migrate/commands/project-migrate.md, NEWanvil/skills/project-migrate/lib/(detect.py,plan.py,apply.py,verify.py,orchestrate.py), NEWanvil/skills/project-migrate/tests/(seventest_project_migrate_*.pyfiles +_fixtures.py+conftest.py), MODIFIEDanvil/skills/README.mdand top-levelREADME.md(skill index), MODIFIEDCLAUDE.md(skill count 8 → 9).
The structural primitives shipped in #283 (sub-deliverables 1–5) get the contracts on top of them tightened to one shape:
- Body filename echoes doc slug (#295 / PR #298). The default body is
<slug>.md(e.g.investment-memo.md,team-thesis.md), not skill-fixedmemo.md. Anvil discovery is doc-name-agnostic; macOS Spotlight, "Open Recent", and shell output all carry the document identity.body_filename_for(slug)is the single resolution helper (originally added inanvil_config.pyhere, then moved toproject_brief.pyin #296 whenanvil_config.pywas deleted). The override mechanism in the prior.anvil.jsonshape is removed because the new echo default makes it redundant. - BRIEF.md absorbs all per-project anvil config (#296 / PR #299).
<project>/BRIEF.mdis the single config locus; per-thread.anvil.jsonfiles are no longer read or written by the live pipeline.BriefDocumentgrows optionaltarget_length_overrides(per-version override map) andrubric_overrides(preserves PR #265's calibration-suffix shape verbatim) under eachdocuments:entry.anvil/skills/memo/lib/anvil_config.pyis deleted entirely (711 lines); itsload_rubric_overrides(thread_dir)API is replaced byproject_brief.load_rubric_overrides_for_slug(project_dir, slug)preserving the empty-on-absence contract.anvil/skills/memo/templates/BRIEF.rubric-overrides.md.exampleships as the new worked example (replaces the deleted.anvil.json.synthesis-brief.exampleand.anvil.json.feedback-memo.example).
anvil/skills/memo/lib/render_gate.py and the xelatex template gain a pandoc 3.x emission compat block. Recent pandoc emits LaTeX requiring packages the template didn't load; the fix backstops the canary's render-gate against the regression without pinning pandoc.
anvil/skills/memo/lib/project_discovery.py no longer returns LAYOUT_CLASSIC. The per-thread-BRIEF layout (with no project root) is gone — every memo thread now lives inside a project root with a project-level BRIEF.md. The LAYOUT_CLASSIC constant and its branches in _resolve_layout / discover_thread_root are deleted. Stray threads without an acknowledging project BRIEF return None from discovery. The classic-layout test cases were either deleted or converted to the project-brief shape; the classic-portfolio fixture under tests/fixtures/project_brief/ was removed.
anvil/skills/memo/lib/anvil_config.py deleted in full (711 lines), plus its test surface (test_anvil_config.py, test_anvil_json_examples_roundtrip.py) and the .anvil.json.*.example templates. All anvil_config consumers now read from BRIEF.md. The one intentional carve-out: anvil/skills/memo/lib/migrate.py (the LaTeX bootstrap path) still writes a legacy .anvil.json as transient output that anvil:project-migrate then consolidates — documented in both commands/memo-migrate.md §".anvil.json legacy note" and project-migrate/SKILL.md §"Relationship to memo-migrate".
- Post-#297 polish bundle (#301 / PR #302). Top-level
README.mdskill count 8 → 9 and project-migrate skill table row,project-migrate/SKILL.mdline 144 test-filename drift fix, per-doc rollback fault-injection test (test_project_migrate_rollback.py, 4 assertions exercising the_restore_docbranch), andapply.py_rewrite_filecount math rewritten withtext.count(old_string)for clarity. - Convictions ledger residuals cleanup (#303 / PR #304). Removed three dead references to the killed-by-#229
_convictions.mdartifact:memo-revise.md"Convictions section when_convictions.mdexists (legacy)" bullet, theplan.md.templateconvictions block, and thetest_template_documents_convictions_sectiontest. The lightweightResolution: declined — see prior conviction at <anchor>mechanism in the regularchangelog.mdis preserved untouched atmemo-revise.mdlines 49, 265, 366.
- #228 — cross-version conviction carry-forward remains open as a
loom:architectplaceholder per the curator's 2026-06-03 defer-ratification verdict. The unsolved problem (firm convictions silently re-litigated in subsequent revise passes) is real; the #142_convictions.mdprimitive was the wrong shape (write-only on advance per #225; wrong trigger per #226); no concrete redesign has emerged and none of the three re-opening triggers from #228's body has fired. v0.3.0 leaves it untouched.
Canary-driven iteration since 0.1.0. Seventy-eight PRs landed in five days as the framework absorbed friction from the 2AM Logic Studio canary running multi-thread investment memos and proposals against rolling deadlines. The shape of this release: a new dim 9 Rhetorical economy rubric dimension (rubrics → /44, threshold ≥35), an anvil:proposal synthesis-sibling pipeline that consolidates cross-critic findings before revise, an anvil:memo --plan / --apply two-phase reviser, a rubric_overrides mechanism for non-investment-memo shapes (synthesis-brief, feedback-memo), a bulk memo-migrate LaTeX→markdown migration tool with 9 detector clusters, a framework-wide <thread>.{N}.perspective/ sibling role, and an installer pivoted to uv-runnable consumer layouts. See WORK_LOG.md for the chronological merge record.
- Critic-side
scope: preserve | expand | reducetagging on every<thread>.{N}.review/comments.mdentry, mechanically tied to dim 9 Rhetorical economy (#244 / PR #254). Phase A reviewer-prose-only (noanvil/lib/schema changes); composes with reviser-side severity filtering (#241). anvil/skills/memo/rubric.md— extended §"Dim 9 — rhetorical economy" with a "Surfacing tocomments.md" subsection codifying the dim 9 →scope: reduceecho rule; added a §"Scope tagging (comments.md)" top-level subsection defining the three-valued vocabulary, the dim 9 echo rule, thescope: expandtrim-candidate rule (major→minor downgrade when no trim candidate named), theverdict.mdscope: reducefirst-priority rule (when dim 9 < 4/4), the_summary.md.scope_distributionblock, and the backwards-compat fallback for legacy reviews.anvil/skills/memo/commands/memo-review.md— extended step 5 (per-dim scoring) with the dim 9scope: reduceecho sub-step; extended step 8 (line-level comments) with thescopelabel requirement, the dim 9 echo, and thescope: expandtrim-candidate downgrade rule; extended step 9 (_summary.mdwrite) with the top-levelscope_distributionblock (sibling tolintandrender_gate); extended step 10 (verdict.mdwrite) with thescope: reducefirst-priority rule when dim 9 < 4/4.anvil/skills/memo/SKILL.md— added a short "Critics → reviser: scope tagging oncomments.md" framing subsection pointing at the rubric and command-spec contract.tests/skills/memo/test_memo_review_scope_tagging_doc.py— new doc-AC test asserting the rubric and command spec carry the scope-tag contract surface (mirrors the existingtest_memo_review_render_gate_wiring_doc.pydoc-AC pattern). Distinct filename per the #58 packaging convention.
The asymmetry the canary diagnosed (critics propose adding content, never trimming): dim 9 (#244) closed the scoring-side hole; this issue closes the comments-stream-side hole. Without the echo, the reviser sees the dim 9 deduction in scoring.md but has no comments.md entry to act on. With it, every dim 9 anti-pattern instance mechanically becomes a scope: reduce comment, every scope: expand comment proposing a paragraph or subsection names a trim candidate, and _summary.md.scope_distribution carries the operator-visible signal that the critic is surfacing both directions. Memo-only this round; proposal-side mirror deferred per the precedent that #245's deck-side mirror followed (ship the rubric-side primitive on the canary-surface skill first; mirror to siblings after one consumption cycle).
--plan/--applyCLI flags onmemo-revise— opt-in two-phase invocation that materializes aplan.mdchange-set preview between scope choice and edit application. The shape mirrorsterraform plan/terraform apply(orgit rebase -i):memo-revise <thread> --planwrites a per-item planned-edit table at<thread>.{N+1}.plan/plan.md(a critic-sibling-shaped artifact, NOT a version dir) and exits without producing<thread>.{N+1}/memo.md; operators editplan.mdin place to decline items (three accepted shapes: same-line<!-- declined: <reason> -->, row deletion, orPriority: declined+ bracketed[declined: <reason>]);memo-revise <thread> --applythen reads the (optionally edited) plan, validates freshness, and produces<thread>.{N+1}/memo.md+changelog.mdper the existing reviser contract.- Plan validity contract.
--applyrefuses stale plans across five cases: no matching plan exists, source reviewverdict.mdmtime is newer thanplan.md(re-reviewed since plan written), a new critic sibling was added since plan time, plan is older thanplan_max_age_days(default 7, configurable via<thread>/.anvil.json), or<thread>.{N+1}/already exists. Each rejection points at remediation (typically: re-run--planto refresh). Plan-sibling shape:_meta.jsondeclaresscorecard_kind: "planner";_progress.json.metadata.critic_siblings_at_plan_timesnapshots the critic set so apply-side detection of new siblings is exact. - Composition with
--polish.memo-revise <thread> --polish "<reason>" --planwrites a polish-pass plan; the verbatim operator reason flows from the plan header through to the produced version dir's_progress.json.metadata.revise_force_reason(operator does NOT re-pass--polishon--apply— the plan IS the audit trail). New composedrevision_modevalues:"plan_then_apply"and"polish_plan_then_apply". Both are additive and audit-trail-only — not scored, not gating, no state-machine impact.--planand--applyare mutually exclusive. - State-machine impact: none. Plan siblings (
<thread>.{N+1}.plan/) do NOT advance the thread toREVISED. The state-machine derivation inSKILL.mdcontinues to use<thread>.{N+1}/memo.mdpresence as theREVISEDevidence; plan siblings are invisible to it. This preserves the immutability contract (a half-built version dir without amemo.mdis neverREVISED). - Default no-flag path unchanged (load-bearing regression contract).
memo-revise <thread>with no flags continues to produce<thread>.{N+1}/directly per the existing 11-step procedure. The new dispatch steps 0a (--plan) and 0b (--apply) fire FIRST in the Procedure block; absence of either falls through to the unchanged 11-step procedure. Every existing consumer (the canary today, the 8 shipped skills' integration tests, the install-script regression tests) continues to work without modification. - Phase A scope: reviewer-prose-only — no Python detector module. The plan-parsing logic is small enough to live inline in the command spec; extraction to
anvil/skills/memo/lib/plan.pyis a follow-on once a second consumer adopts the two-phase pattern (per CLAUDE.md "skill-local first, lib promotion later"). The shipped change touchesanvil/skills/memo/commands/memo-revise.md(primary spec — new §"Plan-then-apply mode" + dispatch steps 0a/0b),anvil/skills/memo/SKILL.md(new §"Operator-confirmable change-set preview" sibling to §"Operator-initiated polish passes" + plan-sibling row in the artifact-contract diagram + state-machine non-gating note + command-dispatch-table flag update),anvil/skills/memo/templates/plan.md.template(canonical plan-artifact shape),anvil/skills/memo/tests/test_memo_revise_plan.py(44 tests across 8 test classes — doc-coverage + fixture-shape + AC10 inventory), and six fixtures underanvil/skills/memo/tests/fixtures/memo_revise_plan/(clean_plan_apply,stale_verdict_rejected,declined_items_decay,target_length_exceeded,polish_plan_compose,no_flag_regression).
This addresses the studio canary friction documented in issue #243 (northwind-pitch-strategy thread, 2026-06-02): memo.3 → memo.4 produced a defensible higher-scoring version that the operator deleted and reverted on read because each addition was defensible in isolation but the aggregate drifted away from "clean and forceful presentation." A plan.md preview at the --plan step would have surfaced the per-item summaries and allowed line-level rejection before any edit was committed.
anvil/skills/proposal/lib/synthesis_schema.py— NEW pydantic models for the<thread>.{N}.synthesis/gaps.jsoncontract.GapListcarriesschema_version: "1",for_version, optionalthread, and the two cross-sibling consolidation primitives:Gap(clustered findings — at least oneContributingFindingper gap, plusroot_concern/recommended_response/ severity ∈ {critical,blocker,should-fix,nice-to-have} / optionalrubric_dimensions) andSingleton(findings that did NOT cluster). Mirrorsanvil/lib/review_schema.pyshape and discipline (extra="forbid", pinned schema version, optional fields default to safe empties). Skill-local first per CLAUDE.md "Skill-local first, lib promotion later" — lives under the proposal skill until a second skill adopts synthesis.anvil/skills/proposal/lib/synthesis_schema.json— companion JSON Schema document (Draft 2020-12) auto-generated from the pydantic model so non-Python callers validategaps.jsonagainst the same contract.anvil/skills/proposal/commands/proposal-synthesize.md— NEW command spec for the synthesizer lifecycle role inserted between parallel critics and the single reviser. Documents the on-disk shape (<thread>.{N}.synthesis/withverdict.md,synthesis.md,gaps.json,_meta.json,_progress.json), the resume / crash-recovery contract, the clustering procedure (deterministic pre-filter + LLM cluster step + conservative "leave as singleton when uncertain" fallback), the severity ladder (max-across-contributors), the aggregator-skip rule (role: "synthesizer"in_meta.json— no per-dimension scores means the existing aggregator's null-fall-through handles it without code change), the state-machine integration (newSYNTHESIZEDtransient state betweenREVIEWED+AUDITEDandREVISED), and the reviser-side backward-compatibility fallback (whengaps.jsonis absent,proposal-revisereads per-sibling findings directly — preserved as the rollout safety net).anvil/skills/proposal/tests/test_synthesis_schema.py— schema-only tests: round-trip on the 12LP+ canary fixture from the issue body, default-fields safety, schema-rejects-invalid coverage (empty contributing-findings → rejected; unknown severity → rejected; missing required fields → rejected; extra fields → rejected;for_version < 1→ rejected), full severity vocabulary acceptance, JSON Schema document presence + drift detection between model and on-disk JSON, command frontmatter parse. Distinct filename per the #58 packaging convention.
This is sub-issue 1 of 4 from the curator's decomposition of issue #246. It establishes the load-bearing contract on which sub-issues 2 (reviser-side consumption), 3 (orchestrator + state-machine integration), and 4 (Studio reproducer integration test) depend. Reviser code, orchestrator wiring, and SKILL.md state-machine updates are deliberately out of scope for this PR — sub-issues 2 and 3 will land in parallel once the contract is fixed; sub-issue 4 lands last.
- New dim 9 Rhetorical economy (weight 4) added to both
anvil:memoandanvil:proposalrubrics. Both rubrics now score against 9 weighted dimensions summing to 44 (was 8 dims / 40). The advance threshold rises to ≥35/44 (was ≥32/40) — the ~80% bar is preserved (35/44 = 79.5%; 32/40 = 80%). Dim 9 polices whether every paragraph is load-bearing — could the same argument land in fewer words? Are the most important claims surfaced early? Is hedging proportional to genuine uncertainty? Could a busy reader extract the recommendation in 90 seconds? It is the countervailing pressure against the bloat failure mode the existing 8 dims structurally rewarded (every other dim rewards adding more). Six named anti-patterns (multi-paragraph hedges, oversized footnotes, redundant subsections, restating tables, reformulated open-decisions entries, restating bullet lists) make the dim actionable for the reviser. The justification MUST cite specific instances — same anchoring discipline as the existing dim 3 citation-hooks rule. Closes #244 (canary surface: 2AM Logic Studio'snorthwind-pitch-strategyv1→v2 produced a "less compelling" v2 despite a higher /40 score; dim 9 is the missing countervailing pressure). - Cross-skill divergence: the other six anvil-shipped skills (
anvil:pub,anvil:report,anvil:deck,anvil:slides,anvil:ip-uspto,anvil:installation) continue on the 8-dim /40 rubric. Dim 9 ships first on the two skills where canary friction surfaced it; broader propagation is a separate decision driven by per-skill calibration evidence. The framework no longer has a single "8-dim /40" default — per-skill rubric shape is now the explicit reality. - Backward compatibility: existing on-disk
<thread>.{N}.review/verdict.mdwritten against the old /40 rubric remains a legal historical record and will not be retroactively re-scored. The first revise pass after upgrade produces a v{N+1} whose subsequent review scores against the new /44 rubric. Noanvil/lib/schema changes; critic siblings continue to emit thehuman-verdictscorecard kind via the existingLEGACY_MEMO_FILESadapter inanvil/lib/critics.py. - Deferred — Option C (genre-flag knob): the long-term shape is a
genre: buildable-system | strategicfrontmatter knob that activates dim 9 only for strategic-positioning artifacts. Option C depends on the per-genre rubric-override mechanism in #233 and ships as a companion issue once #233 lands. The dim 9 prose shipping here is reusable across both shapes.
- Sub-issue 2 — reviser consumes
gaps.json(#270).proposal-revise.mdsteps 6/7/9 updated to prefer<thread>.{N}.synthesis/gaps.jsonas the revision-plan source when present (validated against the pinnedGapListpydantic model), with the per-sibling reading path preserved verbatim as the rollout-safety fallback. Step 7 walksgaps+singletonswithcritical → blocker → should-fix → nice-to-haveordering, planning one coordinated response per gap. Step 9 introduces the canonicalsynthesis <gap-id> (<sibling>.<ref>, ...)row format while preserving the<thread>.<N>.<sibling> (<severity>)shape on the fallback. 26 new structural tests pin the contract. - Sub-issue 3 — orchestrator + state-machine integration (#271).
commands/proposal.mdstate inference (step 3) recognizesSYNTHESIZEDas a transient state when<thread>.{N}.synthesis/verdict.md+gaps.jsonexist. Dispatch table (step 4) gains rows forREVIEWED+AUDITED → proposal-synthesizeandSYNTHESIZED → proposal-revise, plus the parallel at-cap → BLOCKED rows. Anomaly detection extended for stalled-no-synthesis, crashed-synthesis, and orphan-synthesis cases.SKILL.mdstate-machine ASCII diagram now includesSYNTHESIZEDbetweenREVIEWED+AUDITEDandREVISED; evidence table and command-dispatch table updated to match. 32 new tests across 9 classes pin the contract. - Sub-issue 4 — Studio reproducer integration test (#272). Fixture-and-clustering regression test for the 12LP+ FinFET mask cost canary (three siblings → one gap with three contributing findings). Ships an
anvil/skills/proposal/lib/synthesizer.pyclustering primitive with callback-injection seam (mirrorsanvil/lib/vision.py::VisionCritic): default path raisesNotImplementedError; consumers pass a callback for the LLM clustering step. Skill-local per CLAUDE.md "skill-local first, lib promotion later." 25 new tests pin clustering shape + post-processing pipeline (severity ladder defensive-override, dim-list union, schema validation). - Tolerant findings filename + alias contract (#255 /
proposal-synthesize.md). The synthesizer's input contract documents that critic siblings may emitfindings.md,Findings.md, orfindings.json; the synthesizer reads whichever exists. Matches the existing audit-side tolerance pattern.
Sub-issue 1 of #246 (schema + command spec, #253) is documented above under "Added — anvil:proposal synthesis-sibling schema + command spec". With all four sub-issues landed, the EMPTY → DRAFTED → REVIEWED+AUDITED → SYNTHESIZED → REVISED → … → READY → AUDITED proposal lifecycle is fully wired end-to-end.
- Sub-issue 1 — typed loader (#267 /
anvil/skills/memo/lib/anvil_config.py). Pydantic loader for therubric_overridesblock in<thread>/.anvil.json. Supports per-dimension calibration strings (dim_N_calibration), optionaltarget_lengthinside the override, and amemo_subtypediscriminator (synthesis-brief,feedback-memo, etc.).extra="forbid"on the inner block; unknown keys surface asunknown_keysfor forward-compat visibility without hard-failing the load. - Sub-issue 2 — reviewer integration (#273 /
anvil/skills/memo/lib/rubric_overrides_suffix.py).memo-reviewreadsrubric_overridesvia the typed loader and appends\"calibration applied: <override text>\"as a verbatim suffix to each scored dimension's justification in_review.json+scoring.md. New top-level §"Reader dispatch order:.anvil.jsonvsBRIEF.md'Critical reviewer guidance'" documents the precedence (structured config wins; BRIEF.md is documented Option-A fallback). Zero-impact whenrubric_overridesis absent; documented in 27 tests covering suffix attached / suffix absent / per-dim dispatch / zero-impact / verbatim contract / loader-integration pipeline. - Sub-issue 3 — docs + worked-example templates (#274).
SKILL.mdgains a "Rubric overrides and non-investment-memo shapes" section with worked examples for both canary subtypes and theBRIEF.mdOption-A fallback.rubric.mdcarries one-sentence pointers near the most-commonly-recalibrated dims (1, 5, 6, 7). Two.anvil.jsonexample templates ship underanvil/skills/memo/templates/:.anvil.json.synthesis-brief.example(meridian-synthesis canary, [9000, 13000] words, calibrates dims 1/5/6/7) and.anvil.json.feedback-memo.example(northwind-pitch-strategy canary, [4000, 6000] words, calibrates dims 1/4/5/6/7). 19 round-trip tests pin loader compatibility + cross-template consistency. Deferred:memo-draft/memo-reviseconsumption ofrubric_overrides.target_length(the reviewer surfacestarget_length_presentfor audit visibility but doesn't act on the value; drafter/reviser wiring is a follow-on).
A new anvil:memo-migrate command and supporting library for converting a portfolio of legacy LaTeX memo threads to the markdown convention in bulk. Ships across nine PRs:
- Command + base migration (#207).
anvil:memo-migrate <source.tex> <portfolio>runspandocover the source, lays down the version-dir layout, and emits a structured_progress.jsonreport. - Refs/ seeding from BRIEF.md Sources (#208).
anvil:memo-migrate-refsextracts Sources references from BRIEF.md and seedsrefs/for later reviewer back-check consumption. - Detector clusters (#217–#222). Five detector clusters in
anvil/skills/memo/lib/migrate.pyflag layouts the canary corpus surfaces but pandoc loses: orphan figures in sourcefigures/(#217), packed single-cell tabularx layouts (#218), 4-column key/value metricbox tables (#219), emptyfigures/directories (#221), andfigure_policyclassification for zero-figures intent (#222). Each emits a structured finding into the migration report. - Source brief ingestion (#220). Earliest-brief-wins rule: when multiple version directories carry a
brief.md, the migration ingests the root-level (or oldest) brief and records provenance in_progress.json.metadata.source_brief_path. - Detector cluster reference (#223).
commands/memo-migrate.mddocuments the detector cluster catalog with one-paragraph framing per cluster, anchoring the reviewer-side surface to the source-side detectors. - Memo parity lint mirror (#224). Memo-side
<thread>.{N}.review/lint.jsonmirror of the existinganvil:deckparity lint shape (warning-only, Phase A). Surfaces missing-from-BRIEF and missing-from-memo discrepancies without gating advance.
A new perspective-aware critic sibling that surfaces strong-form alternatives to the artifact's central claim before the reviewer scores it. Lands across five PRs as a framework-level addition rather than a per-skill one.
- Snippet convention + perspective discipline (#154).
anvil/lib/snippets/perspective.mdintroduces the perspective sibling convention to the snippet-library substrate every skill reads. anvil:memoperspective sibling (#183).memo-perspectivecommand surfaces alternative candidate threads from the portfolio; output lands at<thread>.{N}.perspective/candidates.mdfor reviewer consumption.anvil:proposalperspective sibling (#184).proposal-perspectivemirrors the memo shape against the proposal lifecycle; new audit-wiring lines surface perspective evidence in the audit'sfindings.md.- Deck market perspective cross-check (#156, #157).
deck-perspectivecommand + market-side cross-check. - Perspective-aware dimension calibration (#194).
anvil/lib/rubric.pyextension that lets venue-pinned rubric overlays opt into perspective-aware calibration prose per scored dimension.
memo-revise --polishoperator-initiated polish-pass entry point (#206). Operators can force a polish-pass revision with a verbatim free-text reason that flows into_progress.json.metadata.revise_force_reasonfor audit. Composes with--scopeand--plan(see below).--scope severity filteron revisers (#257, memo + proposal). Defaultimportant; operators may pass--scope allto fold innice-to-have-severity findings or--scope blockerto limit to blocker-severity only. Filter is applied to per-sibling findings (or to gap severity under synthesis).- Per-revision directive convention (#260). Formalizes the operator-supplied
<thread>.{N+1}.directives.mdshape as the pre-revise directive surface — separate from BRIEF.md's per-thread directives. - Summary-detail consistency back-check (#245, PR #250). Phase A reviewer-prose-only back-check that surfaces verdict-summary vs per-dim-detail mismatches in
<thread>.{N}.review/comments.md. - Cross-thread citation back-check (#236, PR #262). Phase A reviewer-prose-only back-check that flags citations referenced in a thread's body but absent from its
refs.bib, walking sibling threads in the same portfolio.
anvil:memo-rendercommand + state-machine integration (Epic #158 / PR #193). New explicit render lifecycle phase, replacing the implicit rendering that previously hid insidememo-revise. State-machine snippets updated to document the new phase.anvil:memolib substrate + renderer detection (Epic #158 Phase 1 / PR #172). Newanvil/skills/memo/lib/with renderer-detection helpers (weasyprint,pandoc, fallbacks).- Memo PDF render-gate (kind="memo") (#185).
anvil/lib/render_gate.pygains akind="memo"mode for the markdown→PDF pipeline; matches the LaTeX-side gate's shape (page-fit, overfull boxes, compile success, placeholder scan). - Render-gate findings wired into reviewer + word-count-primacy rubric prose (Epic #158 Phase 4 / PR #198). Render-gate output flows into reviewer prose for the dim 7 length-pinning calculation.
@pagesize US-Letter pinning for weasyprint (#232, PR #263). Fixes a regression where weasyprint defaulted to A4 because the@pagesize was unspecified.- Per-thread
words_per_pageoverride formemo_page_fit(#264).<thread>/.anvil.json: words_per_pagelets operators tune the page-fit gate's word/page coefficient when the canary corpus drifts from the default. - Booktabs-class CSS for markdown tables (#238, PR #259). Brings the markdown table render closer to the LaTeX booktabs aesthetic; opt-in via
class="booktabs". orientation: landscapefrontmatter knob for table-dense proposals (#248). Per-version landscape rendering for proposals whose table widths exceed portrait.- Pre-flight image-reference lint (
memo_image_refs_exist) (#160). Detects markdown image links that resolve to missing files infigures/. - Per-version
target_lengthoverrides + provenance (#161). Operators may pintarget_lengthon a per-version basis via<thread>.{N}/_progress.json.metadata.target_lengthfor dim 7 anchoring. refs/source-of-truth materials + reviewer back-check (#162). Reviewers verify every body-of-memo citation against the materials inrefs/; mismatches surface as dim 3 findings.- Configurable
target_lengthin.anvil.json(#122). Top-leveltarget_lengthin.anvil.jsondefines the resolved range that flows into dim 7 scoring. (Carried forward to the rubric_overrides mechanism above.) - Per-skill installer content-hash detection (#163). Installer records a per-skill content hash for modified-vs-pristine detection at upgrade time.
- Surface new-skill availability on upgrade (#239, PR #261). Installer prints which skills are NEW at upgrade time, with copy-and-paste invocation lines.
anvil:deck-imagegenorchestration runtime + command spec (Epic #130 Phase 2 / PRs #169, #170, #171, #182, #186, #191, #192, #197). Brings generative-imagery into deck via a backend-agnostic preset library, animagery_policyBRIEF.md frontmatter field, animagegenorchestration runtime with prompt-journal schema and read/write primitive, three new audit findings (fabrication-attribution, generative-imagery findings gated onimagery_policy), and a consolidatedimagegen_phrases.pyfor stock phrasing.deck↔memoparity pre-flight lint (#205). Warning-only Phase A check that flags structural mismatches between a deck and its companion memo (used by the canary's deck-from-memo pipeline).- Deck
.row+.splitstock layout classes (#174). Reusable layout-only utility classes inanvil-deck.css. - Slides
marp_lintre-export (#164, PR #173).anvil:slidespins re-export of the deck-sideinline-display-style-droppedrule; the underlying primitive lives inmarp_lint. - Inline
display:grid/display:flexlint (#134). Source-side warning for inline display styles that Marp's foreignObject SVG render silently drops. - Deck iteration-cap rationale paired with
max_iterationsoverride (#141). When a deck override raisesmax_iterations, aniteration_cap_rationaleline is mandatory and the orchestrator surfaces a BLOCKED notice for missing-rationale cases.
palette.jsonsibling for bare-python3 consumption (#126). Anvil's brand palette is now also available as a JSON sibling alongsidepalette.pyfor tooling that can't import the Python module.- Optional
.latestsymlink convention (#120, PR #123). Documented convention for the optional<thread>.latestsymlink that resolves to the highest version dir. - Filetype-first vs project-first portfolio placement (#127). Snippet-level documentation of the two portfolio-layout shapes.
- Drafter-side citation-hook contract (#140). Documented contract for the drafter-emitted citation-hook lines that the reviewer back-check consumes.
anvil:memoBRIEF.md fresh + migration templates (#139). Two new BRIEF.md templates: a fresh-thread template and a migration-target template.- Optional pdftotext PDF refs back-check (#175). Path A: subprocess
pdftotextextracts the rendered PDF's text and back-checks refs against the source markdown. - Refs back-check rolled out to deck + proposal (#176). Same back-check primitive consumed by both sibling skills.
revise_consistencystale-token sweep + deck-revise wiring (#114). Newanvil/lib/revise_consistency.pyprimitive sweeps for stale tokens between drafter output and reviser input; wired intodeck-revise.
pyyaml>=6.0declared as a base[project]dep (#231, PR #268).anvil/lib/rubric.pydoes a top-levelimport yaml;anvil/lib/__init__.pyre-exportsRubric/load_rubric/discover_venue_rubric, so anyfrom anvil.lib import ...(and downstreamanvil.lib.render_gate) transitively requires yaml at import time. This matches the same load-bearing-for-import-chain shape as the existingpydanticbase-dep exception;pyproject.toml's header comment is updated to document the second exception. Without this, a freshuv syncproduced a build that failed on first import.- Installer pivots to uv-runnable consumer layout (#230, PR #269). The shipped install layout is now a directly-
uv sync-runnable shape; consumers no longer need a separatepip installinvocation. Drift-detection note added to the installer to flag layouts that predate the new shape.
_convictions.mdadvisory contract (PR #229, retiring PR #155). The Epic #142 Phase A_convictions.mdadvisory primitive shipped in #155 was removed after Phase B verdict surfaced no canary signal to justify it. Kill-switch removes the snippet, command-doc references, and tests. The same surface area is being re-explored under a different (in-progress) design.anvil:memoper-skill--polishBRIEF.md scaffolding (rolled into the new--polishflag landed in #206 above; pre-#206 partial scaffolding removed in the same PR).
memo-migrateRenderErrorinline for consumer-install layout (#199, PR #204). InlinedRenderErrorinrefs_pdf.pyso consumer installs don't carry a dangling import.- Installer Claude shim depth-1 placement (#138). Shims must land at depth 1 of
.claude/commands/so/anvil-*:*commands actually register; the installer was previously placing them too deep. anvil:deckauto-shrink tests gated on[auto_shrink]extra (#115, PR #116). Without the gate, the auto-shrink tests fail when the optional[auto_shrink]extra isn't installed.matplotlib parse_mathanti-pattern guidance (#125). Docs note steering away fromparse_math=Truein figure-side matplotlib code.- CLAUDE.md refresh post-0.1.0 (#112). Refresh of stale sections after the first installable release.
First installable release. Anvil moves from skeleton to a working framework with 8 shipped skills, a maturing anvil/lib/ substrate, and active use by the 2AM Logic Studio canary. 30+ PRs landed in two days of canary-driven development. See WORK_LOG.md for the chronological merge record.
anvil:memo— investment memos, internal documents (Markdown).anvil:pub— research papers with venue-pinned rubrics (NeurIPS / Nature / arXiv overlays).anvil:report— customer-facing technical reports (Markdown / LaTeX → PDF) with mandatory audit +CUSTOMER-READYpromotion gate.anvil:deck— investor pitch decks (Marp Markdown → PDF).anvil:slides— talk / conference slides with speaker notes (Marp Markdown → PDF + handouts).anvil:ip-uspto— USPTO non-provisional utility patent applications (LaTeX → PDF) with 9-check pre-flight including render-gate.anvil:installation— experiential / installation artwork concept proposals (LaTeX → PDF).anvil:proposal— buildable-system proposals (pre-contract bookend toanvil:report); collapses the "internal build spec" case viacustomer_kind: internal.
Each skill ships a complete draft → review → revise → (audit) → figures lifecycle, an 8-dimension /40 rubric, opinionated templates, a worked example thread, and tests.
snippets/(10 markdown files) — pure-markdown conventions every skill reads (progress, timestamp, version_layout, thread_state, state_machine, rubric, critics, scorecard_kind, audit, cite).review_schema.py+.json— typed_review.jsoncontract withkind ∈ {judgment, tool_evidence, vision}discriminator; JSON Schema export.critics.py— sibling-critic discovery, aggregation, verdict computation; legacy-shape adapters for the memo prose triple and ip-uspto hybrid.convergence.py—check_stable+decide_termination;STALLEDverdict for plateaued threads.cite.py— DOI + arXiv resolver, BibTeX writer, idempotentrefs.bib; stdlib only.rubric.py+rubric_schema.json— pydantic models + venue-pinned overlay discovery (<thread>/.anvil.json: venue→ optional advisory rubric).render.py— Marp → PDF, PDF → PNGs, pandoc → PDF, matplotlib figure walker;check_*_available()preflight helpers formmdc/pdfjam/ auto-shrink dep set.vision.py—VisionCritic+VisionRubric; injectable callback for offline/CI use.render_gate.py— deterministic gate over compiled PDFs (page-fit + overfull boxes + compile success + placeholder scan); LaTeX-skill analog ofmarp_lint.figures/palette.py+anvil.mplstyle+mermaid-theme.json— shared brand-palette substrate with 6 named tokens (navy / ink / muted / rule / bg-section / bg) + 4 semantic mermaid classDefs (anvil-accent/anvil-muted/anvil-warning/anvil-success) + per-glyph Unicode fallback for matplotlib.marp/config.yml— pinned Marp config (MathJax + html;mmdc → PNGis the documented working diagram path).
deck-vision,slides-vision,pub-vision,report-vision,ip-uspto-vision— VLM critique of rendered PDFs / drawings. Each composes a skill-appropriateVisionRubric(e.g.ip-uspto-visioncovers USPTO drawing requirements: reference numeral legibility, line weight / contrast, label placement, figure-number visibility, cross-reference accuracy).
marp_lint(deck + slides) with 5 named rules: source-side overflow detection (slide-content-overflow), figure-bullet stack detection, ask-slide H1+H2 detection, italic-supporting-line word-budget (figure-italic-supporting-line-too-long), and the suppression directive<!-- anvil-lint-disable: <rule> -->.
scripts/install-anvil.shwith--skills=filter,--dry-run,--check-deps(coversmarp,mmdc,pdfjam,pdftoppm,xelatex,pandoc); quoted-path safety (removed all 7sh -cindirection sites); honest dry-run output.pyproject.toml— Anvil's first declared Python dep file, uv-shaped. Base dep:pydantic>=2.0(load-bearing for the schema layer). Optional extras:[auto_shrink](Pillow + numpy for the Marp auto-shrink detector). Documented dep philosophy: subprocess-only by default; Python deps for genuinely-better-than-subprocess detection only.
AGENTS.md— Loom agent-archetype reference for ongoing development.ROADMAP.md— mission, design philosophy, current state, near-term themes.WORK_LOG.md— chronological record of merged PRs and closed issues.WORK_PLAN.md— prioritized backlog generated from current label state..claude/commands/loom/release.mdadapted from Loom for anvil's actual structure (2 version-bearing files, no build workflow).
tests/lib/— full coverage for schema, critics, convergence, cite, rubric, render, vision, render_gate, figures.tests/scripts/— install-script regression armor (quoted-path safety, dry-run honesty,--skills=validation, version drift,version.sh setround-trip).- Per-skill tests under
anvil/skills/<skill>/tests/— skill-local lint, vision-critic, and template-correctness tests. - CSS-drift guard test enforces palette tokens stay in sync between
palette.pyandanvil-deck.css :root. - Version-drift guard test enforces
CLAUDE.mdandpyproject.tomlstay in sync.
- Mermaid diagram silently rendered as raw code in Marp PDFs (regression introduced by inline-mermaid-as-default design); switched to
mmdc → PNGas the documented working path with proactive preflight. - White-on-white table rendering on
_class: askslides (Marp default-theme cell-bg leak). - Deck draft and narrative critic recommended conflicting slide orders.
- Install script broke on target paths containing
'(single-quote injection viash -c). - Install
--dry-runemitted misleadingok: ...confirmations of actions that didn't happen. - Bare
--skills=silently fell through to install-all-skills (argument-validation gap). - Phantom
completedphase state introduced by a malformed Markdown table inlib/snippets/progress.md. - Report ack-file substring matching was too lenient (now requires a structured YAML token + sha256 verification).
- Report auditor allowed unreachable external citations to pass (now a
critical_flag). - Report reviewer didn't check whether
report.pdfexisted despite the figurer claiming Dim 7 scored its existence. - IP-USPTO critic phase names in
_progress.jsonsnippets used generic"review"instead of the critic's own tag (s101 / s112 / claims / priorart). - Cross-skill pytest filename collision (deck vs slides
test_marp_*.py) — completed__init__.pypackage chain across all skill test directories. - Slide-archetypes "ONE italic supporting line" guidance was line-counting; replaced with explicit word/character budget (≤18 words / ≤108 chars) + lint detection of overlong supporting lines under figure refs.
release.mdskill adapted for anvil's actual version-bearing files (was inherited verbatim from Loom with 5 wrong file paths).scripts/version.shnow tracks bothCLAUDE.mdandpyproject.tomlwithcheckreporting drift.- README rewritten from "Alpha. Skeleton only" to a real installation + repository-layout + working-with-anvil guide.
- Deck
_class: askslide template no longer uses H1+H2 stack (overflowed 16:9); single H2 + inline use-of-funds paragraph. - Deck figure-bullets idiom replaced with figure + one supporting line (Market / Traction / Financials templates updated; lint enforces word budget).
- Marp config pinned at framework level: MathJax not KaTeX;
html: truefor inline content;--config-file anvil/lib/marp/config.yml.
v0.1.0 is the first installable release. Anvil works on a single laptop with no GitHub account; renderer dependencies (marp / mmdc / pdfjam / pdftoppm / xelatex / pandoc) are checked by install-anvil.sh --check-deps. Active development continues against canary friction — see WORK_PLAN.md and the open-issues backlog for what's next.
- Per-skill audit-command migrations to emit typed
_review.json(pre-date the #29 codification). - Markdown-appropriate length-proxy gate for
anvil:memoif canary friction emerges (memo is markdown-first; no PDF page-fit contract). - Per-skill
lib/extraction toanvil/lib/once duplication patterns are observed across skills (e.g.marp_lint,auto_shrink_detector, report'spdf_freshness).
- Initial repository skeleton.
- Vision, design principles, and planned v0 skill catalog in README.
- MIT license.
- Project-level
CLAUDE.mdfor AI session context. - Directory structure for
anvil/{skills,lib,templates,roles}andscripts/. - Minimal
scripts/version.sh(managesCLAUDE.mdversion string only; will grow as more version-bearing files appear).
- Alpha. No installable functionality. No skills yet implemented.
- Implement v0 skills per the catalog in README.
- Extract framework
lib/from observed duplication after the first few skill implementations land. - Implement
scripts/install-anvil.sh.