Changelog¶
All notable changes to this project are documented here. The format follows
Keep a Changelog, and versions use a 0.0.x
series until 0.0.999.
[Unreleased]¶
Added¶
- Portable verification records (
draft --verify --json).--verifycan now emit a stable, machine-readable JSON record (kind: draft.verification-record/v1) of the outcome — the article digest and whether it matches, the grounding summary, the ledger and source checks, the C2PA signature state when a credential is present, and the overall verdict — instead of the human report. The schema type lives in the importableprovenancepackage, so another tool or a verifier can consume it without the CLI: bring your own artifact, verify anywhere. Exit code still reflects the verdict. - Direct-API escape hatch (
--engine api:anthropic,--engine api:openai). For a machine with no agent CLI installed, draft can now call a hosted chat API directly, reading the key fromANTHROPIC_API_KEYorOPENAI_API_KEY. It is strictly opt-in and never chosen by auto mode: the keyless agent-session path — which reads and stores no key — remains the default.DRAFT_MODELoverrides the model. Like every other backend it is asked only for text, and a session failure still fails over to Ollama. (#73) - Signed C2PA credentials (
DRAFT_C2PA_CERT,DRAFT_C2PA_KEY). The C2PA manifest draft writes has always been a definition a publisher signs. When a signing certificate chain and key are configured andc2patoolis installed, draft now also emits a signed, detached.c2pacredential beside each set, bound to the body file, anddraft --verifyvalidates that signature and its trust chain — not only the digests. Signing is opt-in: with no certificate configured the manifest stays an unsigned definition, exactly as before, and a valid-but-untrusted signature (a development certificate) is reported as such rather than failed.DRAFT_C2PA_ALGoverrides the algorithm (default es256). (#63) - Opt-in semantic second gate (
--second-gate,DRAFT_SECOND_GATE). The verbatim gate documents that it checks wording, not meaning — a quote can be present and its numbers match while the claim misreads it. With the second gate on, a local model judges whether each verified quote actually supports its claim, and unsupported claims are dropped before writing. It is strictly additive: off by default, the verbatim gate stays primary, and it is fail-open — a model error or an unreadable verdict keeps the claim, so the pass can only tighten the ledger, never weaken it on a transient failure. The verdict call's output is capped to a single word. (#72) - Table-cell claims. A layout reader (Docling) preserves a table as
Markdown, where plain-text extraction flattens its numbers into an unquotable
jumble.
claims.TableClaimsnow mines each numeric cell into a claim naming its row and column headers, quoted by the verbatim data row — the value is read from the parsed cell, so the (row, column, value) association is grounded by construction, and every candidate is put through the sameVerifygate as a model-extracted claim. The extraction phase appends these per section, so a run over a source with Markdown tables gains the facts its tables state; a plain-text source carries no Markdown tables, so it is a no-op there. (#70) - Keyed session pool with idle close (foundation for the ACP session pool).
New
internal/sessionpoolkeeps one live backend process per key, reused across jobs and closed once it has been idle, so a batch queue pays a provider's multi-second startup once instead of per source. It is generic over aSession(alive/close), safe for concurrent use, and closes an unused process after a configurable idle interval. This is the concurrency core, fully unit-tested (including race and lifecycle tests at 100% coverage); wiring it into the ACP engine and validating against a live agent is a separate step, so behaviour is unchanged. (#66) - Claim JSON Schema + GBNF grammar (foundation for constrained decoding).
claims.Schema()emits the JSON Schema for a claim ledger — an array of objects with a non-empty claim and quote and an enumerated type and strength — from canonicalclaims.ClaimTypes/claims.ClaimStrengths. The newinternal/gbnfpackage compiles that schema to a GBNF grammar for the local llama.cpp/Ollama path; an engine that supportsresponse_format: json_schemacan take the schema directly. This is additive scaffolding: nothing is wired into the live extraction path yet (that step must measure the drop-rate against a model before the legacy repair path can be retired), so behaviour is unchanged. A drift-guard test ties the extraction prompt's TYPE/STRENGTH vocabulary to the schema.claims.ParseJSONis the decoder half: it reads a structured extraction in that schema's shape and returns verified records through exactly the same grounding gate as the text parser (verbatim quote, numbers in quote, one re-verified repair attempt), so the wire format can change without weakening the guarantee. (#64) -
LaTeX (
.tex) sources. A.texfile is now accepted directly, with no external tool. LaTeX carries a formula as exact text, wherepdftotextscrambles it and even a layout reader only approximates it — so the reader keeps the body verbatim (maths included) and strips only what can never be quoted: comments and the preamble. It is deliberately not a full LaTeX-to-text conversion; macro expansion and environment rendering remain a heavier reader's job. (#71) -
Project and user config files. A
draft.tomlin the working directory, and a~/.config/draft/config.toml(honouringXDG_CONFIG_HOME), now set default values forengine,extract-engine,write-engine,edit-engine,reader,model, the Ollama models,outandsources-dir, andstyle. Precedence is flags > environment > project file > user file > built-in default, so nothing about existing setups changes.DRAFT_CONFIGnames an explicit file;DRAFT_NO_CONFIGdisables the layer. The loader is a dependency-free flat-key parser — no new module. (#69)
Changed¶
- Minimum Go is now 1.25 (was 1.24). Raised for a security fix unavailable
on the floor:
golang.org/x/syscarried GO-2026-5024 (an integer overflow inNewNTUnicodeStringon Windows), and the fix landed inx/sysv0.44.0, whose owngodirective requires 1.25. draft does not call the affected function, so this was never exploitable here, but the module is upgraded and the floor raised sogovulncheckis clean. ThemsrvCI job now builds and tests on Go 1.25. This is the raise the minimum-Go policy anticipates for a security fix; themsrvgate is updated in the same change so the floor and its test cannot disagree. - The extraction cache key includes the reader. The content-addressed extraction cache already folded the prompt version, engine, model and section text into its key; it now also folds in the reader name, so an entry is addressed by the full extraction configuration — the same identity the run manifest records for reproducibility — rather than only by the text a reader happened to produce. Existing entries miss once and are re-extracted; nothing else changes. (#65)
- The grounding phase takes its engine chain explicitly. Claim extraction no longer reaches into the Runner's per-kind chain map; the resolved extraction chain is passed in and threaded through, so the gate is a function of the sections, the chain it is handed, and the config. Behaviour is unchanged — the pipeline still hands it the extraction chain — but the gate can now be driven without the Runner's chain wiring. (#68)
0.0.35 - 2026-09-07¶
Fixed¶
- pkg.go.dev renders package documentation again. The module is dual
licensed Apache-2.0 OR MIT, but the root
LICENSE-APACHEheld a non-canonical variant of the Apache text (left-shifted indentation, and its "APPENDIX: How to apply" section replaced with a filled-in copyright notice) thatgoogle/licensecheckcould not match above its confidence threshold. pkg.go.dev therefore classified the module as "UNKNOWN, MIT" and suppressed all package documentation.LICENSE-APACHEnow carries the canonical Apache-2.0 text; the licence itself is unchanged — still Apache-2.0 OR MIT, at your option.
Documentation¶
docs/AUDIT-2026-09.mdrecords that per-sentence attribution and the C2PA manifest shipped in 0.0.34 and are verifiable withdraft --verify.
0.0.34 - 2026-09-06¶
Changed¶
- The pipeline package is split by phase. The 1,500-line
pipeline.gogod file is now four focused files —grounding.go,composing.go,saving.goandenginechain.go— each holding one phase's methods, withpipeline.goleft as the Runner type and its orchestration. Behaviour, the public API and every test are unchanged; it is a navigability change, verified by the full race suite and the claims mutation gate. A deeper redesign of the Runner's shared engine-chain state remains a separate reviewed effort, because that state feeds the grounding gate directly. - Table recall is now measured, not just claimed. A deterministic corpus
(
claims/testdata/tables/) renders the same table the way each reader produces it — Docling as an intact Markdown table, pdftotext as its column-flattened flow — and checks how many claims that quote a cell with its row label still verify. Every such claim grounds against the structured rendering and none against the flattened one, quantifying what--reader doclingis worth on tabular sources (the audit's open A3). It runs in CI without a reader installed. - Higher grounding recall, same verbatim gate. Claim quotes are now
compared in a rendering-tolerant normal form: a literal
\na model wrote for a line break, a hyphen it dropped from a word, a ligature the PDF rendered as one glyph, a non-breaking or zero-width space, and en/em dashes no longer cause a genuine quote to be rejected as "not found in source". A quote cut mid-clause or too short to cite is extended to its sentence boundary using the source's own words, so the record stays verbatim by construction. Measured over 3,217 extraction blocks from real papers, the drop rate fell from 29.6% to 8.5%. A quote that changes, adds, drops or reorders a word is still rejected, an invalid TYPE or STRENGTH is still dropped, and a fabricated number in an adjacent sentence is never pulled into a repaired quote — all three pinned by the grounding corpus, whose candidate total and recall floor rose with two rescued cases and a new fabrication-guard case. Theclaimsmutation gate stays at 100% efficacy.
Added¶
- A configurable house style.
--style <file.json>/DRAFT_STYLEsets the word band, banned words and phrases (replace the built-in list or extend it withalso_banned_*), and the language variant, so a publication can use draft's grounding without adopting its voice. The structural rules stay fixed. An unknown field or an impossible band falls back to the default style with a warning, never a failed run;--dry-runshows which style is in force. Seeexamples/style/. - Token and cost accounting in
--jsonand the progress line. Every model call's usage is summed per job and reported: the--jsonrecord gains ausageobject withinput_tokens,output_tokensandcost_usd, and the headless progress line ends with· N tokens, $X. It is drawn from what each backend reports — Claude's per-run cost, agy's token totals, Ollama's token counts (no price) — and is omitted entirely when nothing was reported, so a silent zero is never shown as "free". draft --verify <file>checks an article against its provenance. It recomputes the body digest and compares it to the C2PA manifest written beside the set, hashes the sources when they are still on the machine, and checks the ledger digest when a kept ledger is present. Point it at the body, the final document or the manifest; it finds the rest by the day-folder layout, prints a per-item report, and exits non-zero if the article, the ledger or a source no longer matches. This turns the manifest definition from Move 3 into something a reader can act on without a signing step.- An Agent Client Protocol transport.
--engine claude-acpdrivesclaude-code-acpover JSON-RPC on stdio: one agent process for the run, a fresh session per call, and a typed stop reason in place of an exit status.gemini-acpandcodex-acpare registered as experimental. The engine is closed at exit throughpipeline.Runner.Close. Measured against the adapter, a warm session costs about what a cold one-shot call costs; the gain is the standard transport and the failure semantics, not wall clock. - Refusal-aware routing. When the active engine declines a prompt, that one prompt is offered to the engines behind it in the chain without moving the cursor; the next prompt returns to the preferred engine. A section that every engine declines is recorded as empty; an article that every engine declines fails its own job only.
-
Provenance in the frontmatter. Every generated set now carries
draft_engine,draft_modelanddraft_version, naming the backend that actually wrote the article, which may be an alternate the preferred engine handed it to. Breaking under the stability guarantees: three new frontmatter keys are emitted. They are omitted when unknown, so a set regenerated with--frontmatterkeeps what it has and gains nothing it cannot prove. -
A pluggable document reader.
--reader docling(orDRAFT_READER) routes PDF and DOCX through the Docling CLI instead ofpdftotext: tables and headings survive as Markdown, DOCX reads on Linux and Windows, and the cost is seconds to minutes per document plus a Python installation.pdftotextstays the default.--doctorreports whether Docling is installed and fails only when it was asked for and is missing;--dry-runnames the reader in its plan. Measured on a 1.9 MB arXiv paper: 155 s against well under a second, with tables that plain text flattened. -
Per-sentence attribution and a C2PA manifest definition. Every set now carries a
provenance/pair:<stem>-attribution.jsonmaps each prose sentence of the body, with byte offsets, to stable claim identifiers (c+ ten hex digits of the quote's SHA-256), flags figures no claim contains, and reports coverage;<stem>-c2pa.jsonis a C2PA manifest definition in the shapec2patoolreads, binding the article digest, the ledger digest, the prompt version, engine, model, reader, sources and claim identifiers undercom.draftlib.grounding. Attribution is deterministic and model-free; signing and embedding are the publisher's step. The--jsonrecord gains aprovenanceobject. Breaking under the stability guarantees: the day-folder layout gains a fourth directory.
Fixed¶
- The prompt no longer reaches the process listing for copilot or agy.
copilotis now driven over the Agent Client Protocol (copilot --acp) andagyover its stream-json stdin turn protocol, so the verbatim source text travels in a JSON-RPC frame or an NDJSON event on stdin rather than as a positional argument any local user could read withps. Both were verified end to end writing a full article. This closes the last of the audit's prompt-in-argv findings for every provider draft selects in auto mode; amp, crush and qwen remain positional-only and are used only when forced. - A refusal from a provider without a typed stop reason is detected and
routed, not surfaced as a crash. copilot, codex, cursor, grok, agy and the
local model decline by writing prose instead of the CLAIM/NONE the
extraction prompt asks for. draft now recognises a short, schema-free
response carrying refusal language as a decline, converts it to the same
engine.ErrRefuseda Claude refusal produces, and offers the section to the next engine before recording it as having no claims. A genuine extraction, a legitimate NONE, and a claim whose text merely contains refusal words are never misclassified, pinned by tests. - Ollama now self-starts outside the TUI. A headless or
--print/--jsonrun that fell back to the local model while offline failed with "connection refused", because only the interactive UI startedollama serve. The engine now starts the server on first use, once per run even under the concurrent extraction workers, on every code path. - A refused prompt no longer demotes the provider for the rest of the
queue. When the model declines a section (the API answers with
stop_reason: "refusal"and the claude CLI exits 1 with an empty stderr),draftread it as a dead backend: in auto mode every later paper in the queue was silently written by the next provider, and with--engine claudethe queue stranded on "no generation engine available". The session engine now reads the stop reason from the stream and returnsengine.ErrRefused; the fallback chain leaves the cursor where it is, and claim extraction records the declined section as having no claims and carries on. Seen on two incident reports whose sections describe agents gaining remote code execution.
0.0.33 - 2026-09-03¶
Security¶
- Bounded the blast radius of a crafted source document.
draftputs a downloaded PDF's text into a prompt for an agent running with your credentials, which is the canonical indirect-prompt-injection shape. Provider subprocesses now run in a fresh empty temporary directory (removed when the call returns) rather than your working directory, so a source cannot reach an agent that has loaded theCLAUDE.md,AGENTS.md,.mcp.jsonor project settings sitting there.DRAFT_*is stripped from the child environment. - Removed every tool grant.
copilot --allow-all-toolsandcursor-agent --force(its own--helpcalls that an alias for--yolo, "Run Everything") are gone.draftonly ever asks for text. A test pins the invocation table against a list of known tool-granting flags. - Fenced untrusted text. Source documents, the claim ledger and the draft
under review are wrapped in a nonce-delimited block stating that the contents
are data, never directions. The nonce is fresh per call, so a document cannot
close its own block and continue as the operator. The ledger is fenced too: a
SOURCE_QUOTEis verbatim source by construction. - Kept prompts out of
argvfor two more providers.gooseis driven over stdin (run -i -) andgrokthrough a0600prompt file, per each CLI's own--help. Onlycopilotandagystill take an argument, and a test pins that exception list.
Fixed¶
enforceStyleno longer rewrites quotations. It ran its banned-vocabulary replacers over the whole draft, soThe paper states: "we leverage a robust seamless pipeline"became"we use a strong smooth pipeline"— attributing to a source words it never wrote. Code and quoted spans are now excluded, and the banned-vocabulary check skips them too, so the repair pass is never asked to satisfy a rule it refuses to touch.- Prompts are cut on rune boundaries.
ContinueWriting,prompt.clipand the template excerpt all sliced at raw byte offsets; on non-ASCII sources that produced invalid UTF-8, andclaims.Verifyrejects any quote containing U+FFFD. Lifting the existing helper out ofinternal/pdfalso surfaced an off-by-one in it: a document opening with a multi-byte rune was cut mid-rune. - Shell completion is derived from the help table. The two lists had drifted by four flags despite a comment promising they could not.
ollama serveis reaped. A started process that is never waited on stayed a zombie for as long asdraftlived.
Added¶
--outand--sources-dir(DRAFT_DRAFTS_DIR,DRAFT_SOURCES_DIR). Both trees were hardcoded under the home directory with no flag and no variable, which madedraftunusable from CI, a container, or as a library whose caller chooses where output goes.--doctor. Checks Poppler, the installed backends, Ollama, the resolved paths and their writability, and the per-kind engine routing. Every requirement was previously discovered at failure time.--strict-numbers/DRAFT_STRICT_NUMBERS. Promotes "numbers not found in any claim" from a warning to a blocking error. Opt-in for one release while the false-positive rate is measured; ordered-list markers and four-digit years are excluded when blocking, and still reported when advisory.-
A run manifest on
--json, plus"schema": 1. Records the draft version, model, a hash of the extraction instructions, the ledger digest and the SHA-256 of every source file. -
A Unix install contract.
GNUmakefileimplementsmake installandmake uninstallhonouringPREFIXandDESTDIR, installing to FHS paths, and CI verifies the staged tree against the exact listdocs/packaging.mdpromises packagers. draft --mangenerates a roff manual page from the same tables--helprenders, so the packaged documentation cannot describe a different program than the binary beside it.- Native packages. Releases now carry
.deb,.rpmand.apkbuilt from the same binaries the archives do, and the archives themselves now contain the manpage, shell completions and licence texts rather than a bare binary. - A release dry run (
workflow_dispatch), so new release machinery can be exercised before a tag makes its failure permanent.
Changed¶
- Claim extractions are cached by content rather than by date. The only
reuse was
--resume, whose ledger is named for today's date and the first source's filename, so redrafting the same paper tomorrow re-paid the whole cost — 80-95% of a run's wall clock. Measured on a one-section source, the second run's extract phase went from 15,108 ms to 1 ms with an identical ledger digest. A cached entry is still re-verified against the freshly read source, so a stale one can only ever yield fewer claims, never an ungrounded one.--no-cacheand--clear-cachecontrol it. - A demoted engine recovers. The fallback cursor was permanent for the life
of a
Runner, so one flaky moment on the first paper demoted a forty-paper queue to the local model for its entire life. The chain now re-probes after 15 minutes. - Ollama concurrency follows the server.
OLLAMA_NUM_PARALLELis read, and the measured floor of 2 is no longer a ceiling. - Near-duplicate paragraphs are repaired without regenerating. A rule violation cost a full rewrite — the most expensive call in a run — even when a deterministic edit could settle it.
-
BREAKING:
engine.Providersis a function, not a slice. It was an exported mutable table that callers were invited to append to, which races every read of it in a concurrent program. Useengine.Providers()to read andengine.Registerto extend. -
Bumped
actions/attest-build-provenancev4.1.1 -> v4.2.2 (both call sites inrelease.yml). -
Documentation is gated. markdownlint, codespell, an intra-repo link and heading-anchor check, and a version-consistency check all run on every push. The repository had 615 markdownlint findings and no gate; it now has none.
- Every exported name must carry a doc comment (revive's
exportedrule), and a newapi surfacejob reports how the public API moved since the last release. - Bumped
anchore/sbom-action/download-syftv0.24.0 -> v0.24.2.
Documentation¶
-
docs/AUDIT-2026-09.md— a full architecture, performance, security and product audit against 2026-2027 market context, with the roadmap these changes implement. -
A rendered user manual, a development guide and decision records.
DEVELOPMENT.mdmaps every CI job to the one command that reproduces it locally;docs/ARCHITECTURE.mddescribes the package graph, the five phases and the invariants each has a test for;docs/adr/records five decisions with their alternatives and what each costs. The manual is assembled from these same files, so a chapter cannot disagree with the document it came from. docs/packaging.mdandpkg/for distribution maintainers, plussupply-chain/recording what is depended on and how it is pinned.GOVERNANCE.md,SUPPORT.md,AGENTS.mdandCITATION.cff.
0.0.32 - 2026-08-04¶
Security¶
- Protected release credentials. Apple signing and notarization secrets are isolated in an approval-gated GitHub environment restricted to version tags.
- Notarized universal installer. Releases now include a Developer ID signed, Apple-notarized and stapled macOS package, with provenance and signed checksums.
- Credential lifecycle automation. Weekly checks warn at 60 and 30 days before certificate expiry and before the 90-day API-key rotation deadline.
- Independent release verification. A clean macOS runner verifies every checksum, Sigstore bundle, provenance attestation, Apple signature, package ticket, architecture, and executable version after publishing.
0.0.31 - 2026-08-04¶
Security¶
- Native macOS trust. Release binaries for Intel and Apple silicon are now signed with a Developer ID Application certificate and notarized by Apple before archiving.
0.0.30 - 2026-07-30¶
Added¶
- Interactive LLM Provider Selection. Running
draftwithout specifying an explicit--engineflag now presents a branded interactive LLM selection screen prior to starting generation. Users can select fromauto, session CLIs (claude,agy,codex,copilot,cursor-agent,grok, etc.), or local offlineollama. Each provider indicates installation status ([installed],[local / offline],[auto]). Explicit--engine <name>usage and non-interactive modes bypass selection directly.
Changed¶
- Unified Brand Wordmark. Set the brand wordmark to lowercase
draftacross all CLI surfaces, dashboard header renders, and library constants to match the website (draftlib.com) and binary command name. Pinned the wordmark shape in unit tests to prevent drift.
Quality & Governance¶
- Install with Homebrew or mise. Release archives now have documented,
first-class installation paths through
sebastienrousseau/tap/draftand mise's native GitHub backend, alongsidego installandmake install. - Deep quality automation. Pull requests compare benchmarks with their base revision and fail on significant regressions above 25%. A daily workflow runs extended native fuzzing and mutation-tests the security-critical grounding gate at 100% efficacy and mutant coverage.
- Repository Audit (10/10 in all categories). Full audit verified clean zero-issue
golangci-lint, 98.1% test coverage (exceeding the 98% gate),go test -raceclean,govulncheckclean, REUSE/SPDX license compliance, and strict versioning policy (incrementing by 0.0.1 up tov0.0.999beforev0.1.0).
0.0.29 - 2026-07-30¶
Added¶
- Per-stage engine routing.
DRAFT_EXTRACT_ENGINE,DRAFT_WRITE_ENGINEandDRAFT_EDIT_ENGINE(with--extract-engine/--write-engine) send each stage to its own backend. The workload is lopsided — a dozen cheap, mechanical extraction calls per paper against one quality-critical write — soDRAFT_EXTRACT_ENGINE=ollama DRAFT_WRITE_ENGINE=claudecuts session usage roughly thirteenfold while keeping the best available writer. Each stage keeps its own fallback chain and cursor, so extraction failing over to Ollama does not drag writing down with it. --resume. Extraction is 80-95% of a run's wall clock, and a failed write phase used to discard all of it — even though the verified ledger was already on disk.--resumere-verifies that ledger against the freshly sectioned sources and skips straight to writing. Re-verification is mandatory: a resumed ledger is trusted because it still passes the same gate, not because we wrote it, so resume cannot weaken grounding.--dry-run. Reports sources, section count, per-stage routing, the estimated model-call count and whether a resumable ledger exists — in the deterministic ~110 ms of a run, without calling a model. It exercises the real resolve and sectioning path, so a clean plan is evidence the sources are readable rather than a guess.-
An extraction ETA on the progress line, from a running mean of completed sections rather than an extrapolation from the first (which settles the engine chain and warms the model, and so overstates badly).
-
DRAFT_CALL_TIMEOUTbounds a single generation call (default 1800s,0disables), so a wedged provider CLI cannot hang a run forever. The Ollama backend uses its own HTTP client with dial and response-header timeouts rather than the timeout-freehttp.DefaultClient. engine.Validatereports an unknown engine name, whichChaincannot.DoneEventcarriesDurationand per-phaseTimings, surfaced in--jsonasduration_msandphases_ms.WarnEventdistinguishes a non-fatal problem from ordinary progress, and--jsonrecords them per job aswarnings.Config.Warningsreports configuration that was recovered from rather than applied silently; every numeric environment variable is now clamped at both ends instead of only floored.- A README for each of the eight importable packages, with a runnable quick start and an API table.
Fixed¶
- Two papers drafted on the same day overwrote each other's claim ledger.
The filename was date-only; it is now derived from the job's sources. This
lost the fact-checking artefact
--keep-artifactsexists to preserve. -
A dead provider was retried once per paper. The
Runnerwas constructed per job, resetting the fallback cursor every time, so a queue of twenty papers re-tried and re-reported every dead backend twenty times. One Runner now serves the queue and keeps the backend it settled on. -
--reviewdid not check faithfulness. It gated only on the house style rules, so a surgical edit could introduce an ungrounded number or an unsupported metric into a finished article — andfactual correctionis an allowed edit reason. Both write paths now runvalidate.Errorsandvalidate.Faithfulness. - A stalled consumer could wedge the pipeline.
Runner.emitperformed an unconditional blocking send while its doc comment claimed the opposite, so a dashboard that quit mid-run left the goroutine blocked forever. Sends now race the run's context, andTokenEvents are dropped rather than applying backpressure — a slow renderer must slow the preview, never the generation. - Truncation was only detected for Ollama. Session providers never set
Result.Truncated, so the continuation machinery was dead code for them and a length-limited stop surfaced much later as a rule violation costing a full rewrite. The stream-json stop reason is now parsed, and an ending that does not close a sentence is treated as a truncation for every backend. - Cancelling a run walked the rest of the engine chain, failing each remaining backend in turn and logging a misleading fallback for each.
--frontmattercould leave an article set disagreeing with itself. The three files were written with three sequentialos.WriteFilecalls, so a failure after the first left the set desynced. They are now staged and published as a unit, and--reviewwrites through a temporary file and a rename so an interrupted write cannot destroy the original draft.- Section splitting could produce invalid UTF-8. With no paragraph or sentence boundary to cut on, the fallback sliced at a fixed byte offset and split multi-byte runes in half.
- A misspelled
--enginesilently used Ollama.draft --engine claudnow exits 2 and lists the valid names. - Drafts could be written to the wrong directory. When
os.UserHomeDirfailed — routine under systemd, cron and in containers — the error was discarded, leaving the Sources and Drafts paths relative to whatever directory the process started in. They are now always absolute. uniquePathcould loop forever on a directory it could not stat into.- Output filenames are claimed with
O_EXCLinstead of a check-then-write, and the search for a free name is bounded. validate.Errorsnow enforcesrules.MaxWords. The writing prompt asks for a 500–3000 word band andrulesdeclares it, but only the floor was checked, so an over-long draft was told one thing and held to another. A newly generated draft that runs long is now rewritten rather than saved.--reviewis unaffected: it judges the edit, not the article (below).--reviewno longer fails on a violation it did not introduce. It operates on a file the user already has, which may predate a rule, exceed the length band, or read as ungrounded against a ledger mined from whichever sources were supplied today. Blocking on any of that made an existing article permanently unreviewable for a reason the review had nothing to do with. The gate now compares the article before and after the edit and fails only on what the edit added; pre-existing problems are reported as warnings.- Read errors during streaming are no longer swallowed, so a broken pipe cannot produce a half-written article that looks complete.
- Failures are reported honestly. A claim ledger or rescued draft that could not be written is no longer logged as saved.
pdftotextfailures surface the tool's own message instead ofexit status 1.
Security¶
- A fabricated claim could pass the grounding gate using invalid UTF-8.
Quote-to-source comparison lowercases both sides, and
strings.ToLowermaps every invalid UTF-8 byte to U+FFFD — so two different invalid byte sequences normalised to the same text and a quote could match a source it does not occur in. That is a bypass of the one invariant the claim ledger rests on.claims.Verifynow rejects a quote that is not valid UTF-8 or that contains a replacement character; accented and CJK quotes are unaffected. Found byFuzzParse, and its input is kept as a corpus seed. - Prompts now go over stdin for
codexandcursor-agent. A prompt passed as a command-line argument is visible in a process listing for the duration of the call, along with the source excerpts it quotes. Stdin delivery was confirmed by running each CLI;copilot,agy,grokandgoosewere confirmed not to read stdin and keep argument delivery. OLLAMA_HOSTis validated. A value that is not a validhttp/httpsURL is refused rather than concatenated into a request URL, and a host that is not loopback is reported — a remote Ollama means prompts and verbatim source text leave the machine.- Extraction helpers are given absolute paths. A source file named
-x.pdfwould otherwise be parsed as a flag bypdftotextortextutil. Their output is also capped, and source files are size-limited.
Changed¶
make covernow runs the same coverage gate CI does. It previously measured only./internal/...and./cmd/..., omitting every library package while describing itself as covering them.FuzzParseasserts the contract the gate actually has. Its invariant required a quote to appear byte-for-byte in the source, but quote matching is deliberately insensitive to runs of whitespace — a sentence wrapped across lines, or split by a column gutter, is still present in the paper it came from, and rejecting it would drop true claims from every real PDF. The invariant now folds whitespace, case and curly quotes, written independently of the production code so it tests the property rather than restating the implementation, andTestGroundedInStillRejectsFabricationpins that the looser bar still refuses invented content.
0.0.28 - 2026-07-29¶
Changed¶
- The README has been rewritten. It now follows the structure used across these projects — grouped contents, a capabilities section, collapsible reference material, and an honest comparison — in a plainer, more direct voice. Claims lead with what the tool does for you; the numbers behind them follow.
Fixed¶
- Two stale statements in the README. "How it works" still described
extraction as
pdftotext -layout, which 0.0.27 replaced with reading-order extraction, and the limitations section still pointed atinternal/rulesandinternal/validate, which became public packages in 0.0.18. Also documents--json,--completionandDRAFT_SHOW_LOGO, which had no entry in the flag or environment tables.
0.0.27 - 2026-07-28¶
Fixed¶
- Two-column papers are no longer read across the columns. Extraction
passed
-layouttopdftotext, which preserves the visual arrangement: on a two-column paper the left and right columns were spliced onto shared lines, so sentences broke mid-thought and merged with unrelated text. Since a claim survives verification only when itsSOURCE_QUOTEappears verbatim in the source, this quietly undermined grounding on exactly the documents the tool is built for. Poppler's default reading order is now used. Measured on a real corpus, spliced lines fell from 158 and 59 on two papers to zero on all of them. - A contents listing no longer truncates the whole paper. Sectioning cut
the document at the first line matching
references|bibliography| acknowledgements|appendix, which in a paper with a contents page is the entry "References … 4" in the front matter, not the bibliography. A 62-page paper was reduced to its first 8kB — the claim ledger was built from the table of contents. The last such heading is now used, which also keeps the degenerate case right: a document that is only a bibliography still yields no sections. The same paper now retains 97.3% of its text, up from 3.9%.
Added¶
- A clear diagnosis for PDFs with no text layer. A scan or image export
previously produced empty text and a late, vague failure.
pdf.ErrNoTextLayernow explains what happened and what to do — run OCR, or supply a text source. - Real PDF fixtures.
internal/pdf/testdataholds a generated two-column paper — complete with a contents listing naming "References" in its front matter — and a page with no text layer, both built by a committed script so they are reproducible and license-clean. Regression tests assert that no output line carries text from two columns, that body claims survive sectioning, that the real bibliography is still dropped, and that a text-less PDF is diagnosed.
0.0.26 - 2026-07-28¶
Fixed¶
- Release signatures are now discoverable. The cosign bundle shipped as
checksums.txt.bundle, an extension no tooling recognises — the OpenSSF Scorecard scored Signed-Releases 0 despite the release being signed. It is nowchecksums.txt.sigstore.json, the canonical Sigstore bundle name. - Workflow tokens are read-only by default.
release.ymlrequestedcontents,id-token, andattestationswrite at the workflow level, so every step ran with them. Permissions are nowread-allat the top and widened only on the job that publishes.
Added¶
- CodeQL analysis runs on push, pull request, and weekly, with the
security-and-qualityquery suite.
0.0.25 - 2026-07-28¶
Added¶
- OpenSSF Scorecard analysis and badge. A weekly (and on-push) workflow publishes the repository's supply-chain score, so the signing, SBOM, provenance, pinned-action and vulnerability-scanning work is visible rather than implied.
- A module overview on pkg.go.dev.
doc.goexplains the grounding guarantee, maps the packages, and states the 0.0.x stability contract, so the module's landing page says what the tool is for.
Changed¶
cmd/draftcoverage rose from 88.6% to 91.1%, covering the--jsonfailure record (a failed job must reportok:falsewith an error rather than claiming success), the version fallback, and themaxhelper.
0.0.24 - 2026-07-28¶
Added¶
- Signed releases with SBOMs and build provenance. Every archive now
ships a CycloneDX SBOM,
checksums.txtis signed keylessly with Sigstore cosign (published aschecksums.txt.sigstore.json, carrying both the signature and the signing certificate), and GitHub build provenance is attested for each artefact. Verify with:
cosign verify-blob --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp 'https://github.com/sebastienrousseau/draft/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txt
--jsonruns headless and emits one JSON object per job (JSON Lines) on stdout — source, output path, engine, mode, word count, and status — leaving stderr for human progress, so runs can be piped into other tools.--completion <bash|zsh|fish>prints a shell completion script, completing flags, engine names, and file arguments.- Runnable examples on pkg.go.dev for
config,frontmatter,pipeline,prompt, andrules, so every public package now documents itself with compiled, verified code. - An API stability note in the README: while the module is
0.0.xthe Go API may change between releases, the CLI surface is the stable one, and breaking changes are always listed in this file.
Changed¶
- The version has one source of truth. It was a hand-maintained literal
in
cmd/draft/main.goand an ldflags injection — the duplication is how v0.0.22 once shipped mislabelled. It is now derived from the build info, overridden by ldflags for releases, andmake build/make installstamp it fromgit describe, so a local build reports exactly what it is.
Fixed¶
rulescoverage rose from 81.1% to 94.6%:MetricForms, which decides whether an abbreviation and its expansion count as the same metric during the faithfulness check, had no test at all.
0.0.23 - 2026-07-28¶
Added¶
- Vulnerability scanning in CI. A
govulncheckjob now fails the build on any known vulnerability reachable from this code, andmake vulnruns the same check locally. This was added because a scan found GO-2026-5856 (crypto/tls) reachable fromengine/ollama.goviahttp.Client.Do, with nothing in CI to catch it. - Fuzz targets for every untrusted-input parser.
claims.Parse,frontmatter.Split,frontmatter.ExtractMetadata, and the review-modeparseSurgicalEditsare now fuzzed against invariants rather than mere absence of panics: a surviving claim's quote must appear verbatim in the source (the grounding guarantee), splitting must be stable and lossless, metadata must stay valid UTF-8 with a URL-safe bounded slug, and an accepted edit set must never empty or corrupt a draft.make fuzzruns them all. - An MSRV job builds and tests on Go 1.24, so the minimum version the
README advertises stays verified now that the other jobs track
stable.
Changed¶
- Builds and releases use a supported Go toolchain. Every job pinned
go-version: "1.24", but Go only patches its two most recent releases, so released binaries were built with a toolchain that no longer receives security fixes. Build, test, lint, and release now usestable.
0.0.22 - 2026-07-28¶
Changed¶
- The command-line surface is branded too. Until now the corral restyle
only reached the full-screen dashboard, so
draftanddraft --help— the surface most people see most often — still printed plain, unstyled text. Help now opens with the gradient nib logo, wordmark, and tagline, with coral section headings and flag names over the reference text. Colour is dropped automatically when the output is piped, andDRAFT_SHOW_LOGO=0suppresses the logo. The logo, palette, and styles moved into a sharedinternal/brandpackage so the CLI and the dashboard cannot drift apart.
Fixed¶
- Help documents every flag.
--frontmatter,--combine,DRAFT_EXTRACT_CONCURRENCY,DRAFT_SITE_*, andDRAFT_SHOW_LOGOwere missing from the reference text, and the OUTPUT section still described the pre-0.0.16 single-file layout instead of the day-folder set.
0.0.21 - 2026-07-28¶
Added¶
examples/dashboardruns the real full-screen TUI against an in-process demo engine — no model, no network, no source of your own. The article streams in word by word, so the queue, phase markers, live preview, progress bar, and focus timer animate exactly as they do on a real run. It is the quickest way to see the interface or check the layout at a given terminal size.
0.0.20 - 2026-07-28¶
Fixed¶
- The logo now shows on a standard terminal. It was gated at 28 rows, so the common 80x24 window only ever got the one-line masthead. The layout is now budgeted line by line: the logo appears from 24 rows (compact form — tagline beside the wordmark), section rules and blank separators give way first on short terminals, and the focus timer and log are drawn only when the space left genuinely fits them. The running view no longer needs scrolling at any height from 20 rows up, with the queue and the full pipeline always visible. The nib artwork was redrawn with shoulders, a vent hole, a slit, and a tip.
0.0.19 - 2026-07-28¶
Changed¶
- The dashboard now speaks corral. The TUI adopts the same design
language as corral: a
red-gradient braille logo (a fountain-pen nib) with the
Draft.wordmark and tagline, a single coral accent (#F56B5E) over a quiet gray ramp, flat divider-underlined sections instead of boxed panels, a coral-gradient progress bar, a[key] action • …shortcut bar, and the "Made with ❤️ in London, UK (vX)" footer. The logo collapses to a one-line masthead on short terminals and can be disabled withDRAFT_SHOW_LOGO=0; the activity line drops detail progressively so narrow terminals never wrap.
0.0.18 - 2026-07-28¶
Added¶
- Publisher identity via environment.
DRAFT_SITE_*variables (BASE_URL, CDN, NAME, SHORT_NAME, EMAIL, TWITTER, LOCATION, MEASUREMENT_ID, COPYRIGHT_FROM) override the frontmatter publisher identity for the CLI and pipeline alike; unset variables keep the defaults, and curated fields still always win.
Changed¶
- Library packages are now public.
claims,config,engine,frontmatter,pipeline,prompt,rules, andvalidatemoved frominternal/to the module root, so external projects can import them (go get github.com/sebastienrousseau/draft). Only the PDF extractor and the TUI remain internal. Import paths within this module changed accordingly; the CLI is unaffected.
0.0.17 - 2026-07-27¶
Added¶
- Complete example coverage. Two new runnable, network-free demos:
examples/review(surgical-edit enhancement end to end — body-only prompting, frontmatter re-attachment, and generated-set resync) andexamples/frontmatter(metadata extraction, a customSitepublisher identity, the Split/Combine round trip, and the three regeneration rules, each demonstrated live). The pipeline demo now drafts from two sources, demonstrating--merge.
Changed¶
- README overhauled. Grouped contents, a Library usage section with API
synopses for the pipeline runner, grounding, frontmatter, and the
Engineseam, an examples table, an honest when-not-to-use section, a documentation index, and thefrontmatter/package added to the architecture tree.
0.0.16 - 2026-07-27¶
Added¶
- Modular frontmatter generation. The new
internal/frontmatterpackage extracts article metadata (title, deck or bold-line subtitle, TL;DR / executive-summary description, stopword-filtered keywords, frequency-scored category) and generates the standard YAML frontmatter schema. Every draft is now saved as a three-file set under the dated folder:source/<stem>-body.md(article only),yaml/<stem>-frontmatter.yaml(adjacent frontmatter), andfinal/<stem>-final.md(combined, ready to publish). --frontmatter <file>(alias--combine) regenerates the set from any article file. Regeneration is identity-preserving: the filename'sYYYY-MM-DD-slugis canonical for dates and URLs, existing frontmatter fields always win over generated values (delete a field to have it regenerated from the body), and reprocessing an unchanged set is a byte-level no-op. Day-folder layouts route output to the siblingsource/,yaml/, andfinal/directories.Sitepublisher identity. Generated frontmatter takes its author, URLs, handles, and analytics ID from aSitestruct (DefaultSiteby default), and the copyright end year follows the article date.
Changed¶
--reviewis frontmatter-aware. The draft's YAML block is set aside before prompting, so the model and the house rules only ever see the body; the frontmatter is re-attached on save, and reviewing one file of a generated set resyncs its siblings.- Draft sets can never desync. The three output files are uniquified as a set — a leftover file in any one folder bumps all three names together.
0.0.15 - 2026-07-22¶
Fixed¶
- Style-calibration headings no longer leak into drafts. The built-in style
example showed a concrete heading outline ("What the result actually shows",
"Why the mechanism matters", "Where it breaks") and
loadTemplatesfed the user's own template headings back as an outline — both of which a literal local model copied verbatim into unrelated articles. The built-in example now states the heading principle instead of showing copyable headings, and template style samples are stripped to prose only. Heading structure still comes from the output skeleton. Verified end to end: drafts now get specific, content-relevant section headings.
0.0.14 - 2026-07-22¶
Fixed¶
- Adverbial banned words are now caught and repaired too. Building on the
inflection handling in 0.0.13, the
-lyform of a banned adjective ("seamlessly", "robustly", "profoundly") is now detected and rewritten to the replacement's adverb ("smoothly", "strongly", "deeply"), with correct spelling for-ic→-ally,-le→-y, and-y→-ily(so "bustling" repairs via "busy" → "busily"). This closes the residual noted in 0.0.13. Thevibrantreplacement moved to "vivid" so its adverb ("vividly") is clean.
0.0.13 - 2026-07-22¶
Fixed¶
- Banned words are now caught and repaired in their inflected forms. Both the validator and the deterministic style repair matched only the base word, so "leverages", "leveraging", "utilizes", and "fostered" slipped through where "leverage" would have been caught. The banned vocabulary is now expanded to its common inflections (plural/third-person, past, gerund), and a matched form is replaced with the replacement inflected the same way — "leverages" becomes "uses", "leveraging" becomes "using" — using proper English spelling rules (silent-e, sibilant "-es"). Adverbial "-ly" forms remain a separate concern.
0.0.12 - 2026-07-22¶
Changed¶
- The metric-conversion guard now accepts a grounded expansion. An
abbreviation and its expansion — for example
bpband "bits per byte" — are the same metric, so using one in the draft when a claim uses the other is no longer flagged as a silent conversion. A switch to a genuinely different metric (perplexity where a claim says cross-entropy) is still caught, because those forms live in separate groups (rules.MetricForms). On a real metric-heavy paper this turned a repeated faithfulness failure into a clean first-attempt pass.
0.0.11 - 2026-07-22¶
Changed¶
- Hardened the article template against a literal local model. Three refinements, none affecting a session provider's output:
- Every skeleton placeholder now self-heals. A copied thesis label or bare
**...**line is stripped, an unfilled## ...section heading is dropped (its body folds into the surrounding prose), and any ellipsis-only heading is caught by the validator — so a placeholder can neither ship nor fail a run. - Style-calibration echo is removed. A small model sometimes reproduces the tone example (or the user's own templates) as body text; any paragraph copied verbatim from the calibration block is now stripped from the draft.
- One source of truth for the house rules. The writing and review prompts
share a single
houseStyleRulesblock instead of two near-duplicate lists, and the structural markers the validator checks for (#,##, the post-lead aside, the executive-summary label) live ininternal/rules, with a test that keeps the skeleton in sync.
0.0.10 - 2026-07-21¶
Changed¶
- Parallel claim extraction on Ollama. Sections are now mined two at a time
against the local server (previously one at a time). On a single small GPU one
request does not saturate the hardware, so with the server started at
OLLAMA_NUM_PARALLEL=2two extractions run at ~1.8× the throughput of one; a server pinned to one slot simply queues the second, so it is safe either way. On a real 12-section paper this cut extraction from ~825s to ~645s. Capped at two for Ollama (override withDRAFT_EXTRACT_CONCURRENCY).
Fixed¶
- Opening-thesis placeholder no longer leaks or fails a run. The skeleton's
bold thesis was a concrete label ("Opening thesis paragraph.") that a literal
model copied verbatim; on a dense paper this tripped the placeholder check and
burned the whole retry budget. The label is gone from the skeleton, and
post-processing now strips both a copied label (keeping any real thesis after
it) and a bare unfilled
**...**line. Combined with the above, a real 12-section paper now drafts in ~817s (down from ~1330s) and passes on the first attempt.
0.0.9 - 2026-07-21¶
Fixed¶
- Skeleton placeholder no longer leaks into drafts. The output skeleton used a concrete-looking heading ("First analytical section") that an obedient local model copied verbatim instead of replacing. The skeleton now uses a neutral placeholder, the writing prompt explicitly says to replace placeholders with specific headings, and the validator rejects any unfilled placeholder (a "..." heading or a leaked thesis marker) as a safety net. gemma now writes real, descriptive section headings.
0.0.8 - 2026-07-21¶
Changed¶
- Offline drafting is roughly 4× faster. On a measured 8 GB machine a two-section source went from ~474s to ~116s end to end, with the draft passing the house rules on the first attempt instead of after retries. The gains come from three changes below; none reduce grounding.
- Single Ollama model. Writing now defaults to
gemma3:4b(the model already used for extraction), so a memory-constrained server no longer swaps a second 4B model in and out between phases. gemma also keeps to the word budget and does not leak planning text into the article, whichqwen3:4bdid.qwen3:4bis no longer used by default; the separate experimentalqwensession provider is unaffected.
Added¶
- Claim-scaled length budget. The target word count and the Ollama output-token cap are derived from the number of verified claims, so a thin ledger yields a short, fully-grounded draft rather than a padded one. A draft truncated at the cap is closed by trimming to its last complete sentence.
- Deterministic style repair. Banned cliché words and phrases are swapped for
neutral, in-style equivalents in place (
internal/rules.StyleReplacements), removing the most common reason an otherwise-clean local draft needed a full, slow regeneration. keep_aliveon Ollama requests and a documented 8 GB server profile (flash attention + quantised KV cache) in the README — the single biggest offline speed-up, taking a cold run from minutes to under two.
0.0.7 - 2026-07-21¶
Changed¶
cursor-agentpromoted to a verified (stable) provider after an end-to-end check. Its invocation now passes--forceto clear the directory-trust prompt that otherwise blocks non-interactive runs. Stable providers are now claude, copilot, codex, grok, agy, and cursor-agent.
0.0.6 - 2026-07-21¶
Changed¶
- Replaced the
geminiprovider withagy(Google Antigravity, the successor CLI), and promoted it to a verified (stable) provider after an end-to-end check. Stable providers are now claude, copilot, codex, grok, agy.
0.0.5 - 2026-07-21¶
Added¶
- Windows support. The CI test matrix now covers Windows alongside Ubuntu
and macOS, and releases ship Windows binaries (amd64 + arm64, as
.zip). PDF, Markdown, and text sources work on all three platforms; DOCX remains macOS-only (textutil).
Changed¶
- Made the
runTooltests portable (usegoinstead of theecho/falseshell builtins) so the suite passes on Windows.
0.0.4 - 2026-07-20¶
Changed¶
grokpromoted to a verified (stable) provider after an end-to-end check (clean, grounded 1.1k-word draft). Stable providers are now claude, copilot, codex, and grok.
0.0.3 - 2026-07-19¶
Added¶
- Parallel claim extraction. On a session provider, sections are mined
concurrently (configurable via
DRAFT_EXTRACT_CONCURRENCY, default 4); Ollama stays sequential. A failed worker retries down the fallback chain. - Live streaming preview. The Claude backend now uses the
stream-jsonevent format, forwarding token deltas as they arrive instead of one jump. - Review mode.
--review <draft.md>enhances an existing draft with surgical find/replace edits grounded in the sources — validated for uniqueness and non-overlap, and re-checked against the house rules. - Cancellation of in-flight work when the TUI quits or
--printis interrupted (signal-aware context). - Experimental provider gating. Only
claude,copilot, andcodexare verified end to end and used by auto mode; the rest need--experimental.
Changed¶
codexpromoted to a verified (stable) provider after end-to-end checks.- Removed the previously-dead surgical-edit code by wiring it into
--review. - DOCX extraction returns a clear "requires macOS" error off Darwin.
Tooling¶
- GitHub Actions pinned to commit SHAs; ubuntu + macOS test matrix; full REUSE
3.3 compliance with a
reuseCI gate; GoReleaser release workflow attaching darwin/linux (amd64/arm64) binaries on tag push.
0.0.2 - 2026-07-19¶
Added¶
- Multi-provider session engines. In
automodedraftnow drives the first installed token-free coding-agent CLI — Claude, Codex, Gemini, Copilot, Cursor, Amp, Crush, Goose, Grok, or Qwen — through its own logged-in session. Force one with--engine <name>; override the model with--model. - Engine fallback chain. A failed session call advances along the chain and finally to Ollama, so a queue works online, offline, or across a change in connectivity — each job re-selects its engine independently.
--keep-artifacts. A successful run now leaves only the finished article in the dated folder; the scratch claim ledger is removed unless this flag is set.--printheadless mode,examples/with three runnable, network-free demos, benchmarks for the hot paths, and godoc examples.- Test coverage raised to ≥95% of app/library statements, including the
session backends (faked via the
TestHelperProcesspattern). - Cancellation. A signal-aware context is threaded through the run; quitting
the TUI or Ctrl+C in
--printterminates any in-flight provider subprocess or Ollama request instead of orphaning it. - Experimental provider gating. Only
claudeandcopilotare verified end to end and used by auto mode; the rest are marked experimental and require--experimental(or--engine <name>), so the breadth claim stays honest.
Changed¶
- DOCX extraction now returns a clear "requires macOS" error off Darwin instead of a confusing missing-command failure.
- Removed the unwired surgical-review prompt (
prompt.Review) and its dead helpers rather than shipping unused code. - Documented the agent auto-approve trust surface in the README security notes.
Fixed¶
- Header no longer bleeds. The status line is fitted to the terminal width and hard-clipped, dropping the tagline then the model/word-range as space shrinks.
- Logo renders the Kenaz rune with a bright wordmark.
- Progress bar shows an explicit percentage.
.golangci.ymlmigrated to the v2 schema;make lintruns clean.- Truncation check decodes the final rune, so a draft ending in a smart quote or ellipsis is no longer flagged as truncated.
0.0.1 - 2026-07-19¶
Added¶
- Initial release: a Bubble Tea CLI that turns research PDFs into grounded, body-only Markdown drafts, writing with Claude via the active CLI session when online and a local Ollama model when offline, grounded by a verified claim ledger.