Repository navigation
Releases: spencerbraun/backdraft
Release list
backdraft 0.16.0
A week on saying what the registry already knows: which drafts a new snapshot touches, where cited words went when the chunker re-cut them, and when a draft has changed since its record judged it.
locatefinds cited words the chunker changed a hash around without changing a word: a short heading merged into the cited chunk, or a paragraph grown past the cut so the cut runs through the words. Themovedline sayswhole inside a chunk that now holds moreoracross two adjoining chunks, and a span proposes both tokens;-separated, which--applywrites as is.bindrecordsmoved_tofor the first case; a span writes none, since the key is one token in the artifact format.ingestthat makes a new generation names each draft in the project whose last bind cited that source, read from the records under.backdraft/records/, with the exactbackdraft locateto run for it;showon a drifted token closes with the same commands instead of a<doc.md>placeholder. Unreadable records are counted, so a short list is never mistaken for a complete one.- A draft kept outside its project is refused with the directory that was searched and, when there is one, the registry that does not cover it and the two ways to connect them (move the draft, or set
BACKDRAFT_HOME), instead of advice tobackdraft initthat would make an empty registry beside it. renderrefuses a draft edited since its bind — a claim added, reworded, re-cited, or moved by an edit above it — at exit 1 with nothing written and the first such claim named, and the re-bind it names carries the record's--session. Prose edits that move no claim still render.
1730 tests.
backdraft 0.15.0
A week on drift: the record and the artifact now know when cited words only moved, locate can make the swap, and a dry run says what an ingest will cost before it runs.
bindwritesmoved_toon adriftedcitation whose cited words stand unchanged at exactly one place in the source's current snapshot, and the artifact's card says where they stand instead of word-diffing them against the unrelated paragraph that took over their address. The status staysdrifted; the key is optional underartifact-v1, so every record without one is byte-identical apart from the legend. The footnotes projection and the artifact skill follow.backdraft locate <doc.md> --applywrites eachmovedcitation's new token over its old one inside that claim's link — whole;-separated pieces only, through a temporary file and a rename — and touches nothing else: not anambiguousorgonecitation, not prose, not the registry or the ledger. It refuses at exit 1, writing nothing, if the document changed while it was reading. The plain run's closing line now names--applyrather than telling you to edit tokens by hand.backdraft session show --in <slug>lists, per page, the chunks this session was shown and the ones it was not (p1 shown c1-c25 not shown c26-c57) and closes with the read that covers the first gap — the question to ask after losing context mid-document, instead of re-reading from the top. A sheet reports whether its page was shown and a count of cells. Mints nothing.backdraft ingest --dry-runends each line with the extractor that would run, chosen the way the ingest chooses it, so a PDF says before the batch whether it goes tovlm— a vision-model call per page — or to the free text layer; an unknown--extractoror a--configkey the chosen one never reads fails the dry run with the ingest's own reason. A source already ingested says what it was extracted with and whether this run would re-extract it.
1686 tests.
backdraft 0.14.0
A week on getting from where a command points to the passage it means: chunk reads, search excerpts that show the match, and reports that say what to do next.
backdraft read <slug> p1.c9andp1.c9-c14read the chunks a table of contents lists, by the locator it prints. What a chunk read prints is minted exactly as a page read's is; a range clips to the chunks that exist, runs past the budget with the same continuation line, and a backwards or empty range exits 1 naming the page's chunk span. A sheet refuses the form and says how it is read and cited.searchexcerpts now open on the words that matched. A hit whose match the first 160 characters already show prints exactly what it did; otherwise the excerpt moves to the densest run of matches, at a word, elided at whichever end it was cut. Display only: no token, receipt or record moves.bindandverifyreports close with onenext:line per kind of failure they printed, naming the exact command to run with the document and session filled in —showfornot_shown,searchforunresolved,locatefordrifted, a rebuild for an edited artifact. A clean report is byte-identical to before;--jsonis unchanged by it.verifynow re-checksnot_shownagainst the ledger session the record names, when that session is in the registry it checks against — the one check that says the author read what they cited. Aledger:line says when it could not be asked (the session is not in this registry, or the bind had none).verify --jsongainssources.ledger.- A spreadsheet or CSV is no longer marked
little textfor being short: a sheet is marked only when none of its cells holds a value, so a three-row rate table is cited like any other source. - A registry locked by another writer, or otherwise unopenable, is no longer called damaged with advice to restore it; the message says nothing changed and to run again when the other writer finishes.
1645 tests.
backdraft 0.13.0
A week on saying the thing a caller would otherwise learn the hard way: what this install can do, what was withdrawn, which session a bind judged against, and when a flag was ignored.
backdraft doctorreports every optional capability at once — the registry, page images (poppler), the vision model, legacy.xls, the entail check and math rendering — eachreadyormissing. A missing one gets acostsline saying what the verbs do without it, and its reason is the exact string the verb prints when it hits the gap, so the two cannot disagree. Keys are shown assetorabsent, never their values. Read-only, and always exit 0. Run it first in an unfamiliar environment.backdraft ls --withdrawnlists whatforgettook out, each with the date it went and thebackdraft ingestthat brings it back. Plainlsis unchanged, except that a registry whose every source is withdrawn now says so instead of claiming to be empty.bindnames the session it judgednot_shownagainst on its first line (against session <id>), and closes with a warning when that is the shareddefaultsession. The artifact carries the same meaning: the legend'snot_shownentry says what adefaultsession costs, andbackdraft verifyprints asession: defaultline from the file alone.ingest --slugon a source already ingested now says--slug not appliedand names the slug the document keeps. Nothing renames a document — not--slug, and notforgetfollowed by a re-ingest, which brings it back under the same slug. The dry run says the same.- A damaged or foreign
.backdraft/registry.dbis now one exit-1 line naming the file, instead of a traceback on every command; nothing is repaired or replaced, anddoctorreports it as the registry's gap.
1578 tests.
backdraft 0.12.0
A week on what happens after the draft: output an agent can act on, a check on a file somebody sent you, and citations that survive a source changing under them.
backdraft bind --jsonandbackdraft verify --jsonprint one JSON object instead of the report, with the same exit codes.bind --jsonis the record itself, byte for byte what bind writes.verify --jsonis a newbackdraft/verify-v1object whosefindingseach carry akind:receiptmeans the file was edited after it was written,sourcemeans the sources no longer stand behind a citation. Both used to be the same exit 2, and now a hook or an agent can tell them apart without reading a sentence.backdraft verify <artifact> --against <project>checks a file you were sent against a project you have, without copying it into the checkout. The flag is you saying where the file came from, andverifystill never infers that. A path with no registry in it is refused rather than quietly checking the file alone.- A claim can straddle two chunks, and the gate now says so. A search hit whose text may run on into the next chunk carries that chunk's token too, indented under its excerpt. That covers a paragraph the chunker cut in two and every page break. A page read that reaches the end of its page closes with the next page's first chunk. Both tokens are minted, so a sentence that runs on is cited by both halves.
backdraft locate <doc.md>finds where a re-ingest moved the text a document cites. A paragraph inserted above a citation shifts its chunk number, so it comes backdriftedeven when its words never changed. For each drifted citation,locatefinds the exact cited text in the new snapshot:movedgives the token that holds it now,ambiguousnames the places when there are several (or when the citation is a cell), andgoneproposes nothing. It is read-only and rewrites nothing.backdraft showon a drifted token now points atlocate. What stands at the old address after an edit can be a different paragraph.bind,locateandrenderrefuse a document that is not UTF-8, naming it, wherelocateandrenderused to stop with a traceback.rendersays where it looked for a missing record, including.backdraft/records/, which is wherebindwrites one.bind --helpnow states what it writes and its exit codes.- Ingesting an
.xlswithout the[xls]extra says to install it, instead of saying the format is unsupported.
1534 tests.
backdraft 0.11.0
A week on sizing up a source before spending on it — what it will be called, how much of it a read costs, and whether there is anything in it.
backdraft ingest <source> --dry-runsays what each source would be called before that is permanent: the slug and media type it would take, whether it is already ingested (under which slug, and whether withdrawn), and the numbered slug a taken name would land on. Nothing is fetched, written or minted. A slug is fixed once a token carries it, so this is how to see the default before--slughas to overrule it.- A page read has a budget. One
backdraft readshows 12,000 characters — 200 rows of a sheet — unless--limitsays otherwise, so a scraped article no longer spends a context window in a single call with nothing to say so. A read that stops short closes with the total and the exact command that continues it. The cut never lands inside a chunk, so every token still names text you were shown whole, and ordinary pages print exactly what they did. - A web page's table of contents is its chunks:
backdraft read <slug>on a one-page source listsp1.c1,p1.c2, … with what each opens with, under the name the page gave itself in its<title>— the map for finding where the article starts past the site's navigation. The artifact names a fetched page by that title too, wherever the record carries one. - A source that came back a shell says so wherever it is listed.
backdraft read's document list, that source's table of contents andbackdraft lsall mark itlittle text: N chars, so a registry ingested in another session still tells the writer which sources are login walls and scans. - Every hint the gate prints —
search's,show's, the table of contents',session show's and a short read's continuation — now carries a--sessionyou typed, so following one no longer reads into a different ledger from the one you bind against. ingest --dry-runrefuses a malformed--configthe wayingestdoes, rather than predicting a run that cannot start.
1440 tests.
backdraft 0.10.0
A week on saying what happened in a form an agent can act on — and one on taking something back.
backdraft forget <slug>withdraws a source that ingest should not have taken: out ofread,searchandls, with nothing removed, so a token already written into somebody's draft or artifact still shows its receipt andbindreports itunresolvednaming the withdrawal rather than passing it silently. Ingesting the source again is the undo, as the same document under the same slug.backdraft session showreads the ledger back: a total and one line per document, so "have I read enough to write this yet?" is answerable before the draft exists rather than by binding it and countingnot_shown. It also says out loud, at exit 0, what the shared default session costs.- An unreadable source now says what to do about it instead of relaying an errno — a directory wants the files inside it, a missing path wants its spelling checked, an unreadable file wants its permissions fixed — and a source with no bytes in it is a failure rather than a document with nothing to cite. Same rule for the URL half, including a server that sends an empty body.
- The markdown projection
bind --boundwrites now calls a fetched source by its page, the wayingest,ls, the gate's list and the artifact already did. That is the form that travels into a pull request or an email, where the artifact cannot follow. - An unknown slug is told about the sources a withdrawal hid, rather than being told the registry is empty.
1340 tests.
backdraft 0.9.0
A week on the reader's side: the recipient gets a command instead of a procedure, search stops hiding what it cut, and a formula renders as a formula.
backdraft verify— the check the recipient can make. Hand it the.backdraft.htmlor the.backdraft.jsonand it runs the whole of spec/artifact.md § Checking an artifact: every snippet rehashed against the sha256 recorded beside it, every token checked against the anchor it names,summaryrecounted fromclaims. That tier needs nothing but the file, which is the point — it is the check someone who was emailed the artifact can still make. Run it inside the project the document was bound in and it also re-resolves every citation against the registry, so a source that moved since shows up rather than going unnoticed. Read-only: no session, no minting, because an audit must not make its subject citable. It found a real bug on the way in — the spec and every artifact's embedded legend said the token's hash must prefix the anchor's, which is false for everydriftedcitation by construction, and implemented as written would have called every drifted artifact forged. Both statements corrected.searchreports the size of the answer, not the size of the page.--limitdefaults to 20 and a capped run said20 resultswhether twenty matched or two hundred did. It now says20 of 56 resultsand closes with the exact widening command, shell-quoted. An uncapped run prints what it always printed, byte for byte.- Math renders. LaTeX written the ordinary way —
$...$,$$...$$,\(...\),\[...\]— becomes MathML in the artifact behind the new[math]extra: static markup the browser lays out itself, so the artifact stays one file with no script and no font to fetch. Currency ($250 per unit,$1.2M) is never mistaken for a formula, and neither are shell variables in code. Without the extra a formula renders verbatim rather than wrong, andrendernow says so and names the install, so an artifact of raw TeX cannot pass for one of typeset math. - A verifier's
skipsays why it skipped.overlap: pass 13, skip 4named four unchecked citations without saying whether that was benign; the reasons now print under the method, grouped. That matters more, not less, for checks that never gate. - The registry export is a specified format.
backdraft exporthas writtenbackdraft/registry-v1since v0 — a version string promising a specification that did not exist. spec/registry.md joins tokens, chunking and artifact as normative prose, pinned by a test that fails in both directions, so an undocumented key and a documented-but-absent one are equally a failure. - Fixes.
snake_case_nameis no longer rewritten tosnake<em>case</em>nameby the renderer — intraword_is not a delimiter, per CommonMark.verify's help no longer prints markdown asterisks at the terminal, and its exit code no longer decides by reading the wording of its own report.
1274 tests.
backdraft 0.8.0
A week on web sources: a fetched page is named, slugged and shown as the page it came from, and ingest finally says what it did.
- The demo cites a web page. backdraft.dev/demo.html now carries three sources, one of them a Wikipedia article ingested at its permanent link (
?oldid=), so a citation into the web is visible in the artifact anybody actually looks at — and staysresolvedrather than reportingdriftedthe next time somebody edits the page. - Every surface names a fetched source by its URL.
ingest,ls,backdraft read's document list and its table-of-contents headline all print the page rather than the filename the fetch invented to stage the bytes in.lsloses its fifth column: one true name replaces two names of which one was fictional. - A URL's slug names its site when its path names nothing. Two sites'
index.htmlpages used to becomeindexandindex-2; they are now named for their hosts. Directory indexes, generic handlers and bare numeric ids earn the host in front of them; a path that already names its page (/reports/q4-2025) is untouched. Truncated slugs no longer keep a dangling hyphen. ingestsays which of its three outcomes happened, and how much text came out. Each line carries the extracted character count, plusunchangedfor a no-op ornew generationwhen the bytes moved — the moment older citations can start reportingdrifted, now named alongside the command that checks. A source that came back thin — a scan with no text layer, a login wall, a deck of images — gets a note naming the likely cause and what to do, at exit 0.- Fixes. The artifact-reading skill described the JSON island's escaping wrongly, which would send an auditing agent reaching for an HTML unescape that does nothing;
backdraft ls --helpno longer points a user at a module path.
1190 tests.
backdraft 0.7.0
Tokens run backwards, and a source list runs to the end.
backdraft show <token>...— the inverse of minting. Hand it a token from an artifact, a draft, or a bind report and it prints that token's bind status, its locator, and the verbatim snippet.driftedprints both the snippet that was cited and the one standing there now;unresolvedsays whether the slug or the locator is the wrong half;malformednames the grammar. It is the gate, so what it shows is minted and citable — a token copied out of somebody else's artifact becomes yours to cite by showing it. Exit 1 if any token named nothing.ingestfinishes its list. A source that cannot be read no longer ends the run: everything else still lands, and the command exits 1 printingN of M sources ingestedwith one line per failure and its reason. Re-running the same list after a fix re-ingests nothing that already landed unchanged.--configkeys are declared and checked. Each extractor declares the keys it reads, and the parsed config is validated against the extractor that was actually chosen — so--config dpy=300fails and names the keys that would have worked, instead of being silently ignored. The keys are listed inbackdraft ingest --help, pinned to the declarations by a test.- Bind report line items name the claim.
! unresolved: <token> — <the claim's own words> @<offset>— exit 2 now says which sentence to go edit, and the offset is what tells two line items carrying the same token apart. - Maintenance. The drift lookup that had landed in both the gate and bind is now one registry-owned function; DESIGN.md's decision table is un-split and pinned by a test;
showno longer prints the same next step twice;EXIT_USAGEdocuments what it actually covers.
1146 tests.