backdraft 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backdraft-0.5.0/.claude-plugin/marketplace.json +16 -0
- backdraft-0.5.0/.claude-plugin/plugin.json +14 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/DESIGN.md +1 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/PKG-INFO +52 -12
- {backdraft-0.4.0 → backdraft-0.5.0}/README.md +51 -11
- backdraft-0.5.0/ROADMAP.md +177 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/SPEC.md +5 -3
- {backdraft-0.4.0 → backdraft-0.5.0}/demo/walkthrough.md +20 -21
- {backdraft-0.4.0 → backdraft-0.5.0}/pyproject.toml +1 -1
- {backdraft-0.4.0 → backdraft-0.5.0}/site/docs.html +53 -13
- {backdraft-0.4.0 → backdraft-0.5.0}/site/llms.txt +16 -2
- backdraft-0.5.0/site/privacy.html +126 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/skills/backdraft/SKILL.md +19 -6
- {backdraft-0.4.0 → backdraft-0.5.0}/skills/backdraft-artifact/SKILL.md +1 -1
- {backdraft-0.4.0 → backdraft-0.5.0}/skills/backdraft-backfill/SKILL.md +7 -1
- backdraft-0.5.0/src/backdraft/__main__.py +6 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/cli.py +25 -3
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/cli.py +111 -81
- backdraft-0.5.0/src/backdraft/extract/snapshots.py +170 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/vlm.py +20 -18
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/gate/reader.py +23 -3
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/gate/searcher.py +2 -2
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/gate/test_gate_cli.py +13 -3
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/gate/test_gate_reader.py +17 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_bind_cli.py +26 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_cli.py +113 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_extract.py +20 -24
- backdraft-0.5.0/tests/test_snapshots.py +509 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/uv.lock +1 -1
- backdraft-0.4.0/ROADMAP.md +0 -59
- {backdraft-0.4.0 → backdraft-0.5.0}/.github/workflows/ci.yml +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/.github/workflows/release.yml +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/.github/workflows/site.yml +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/.gitignore +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/.python-version +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/LICENSE +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/assets/backdraft-mark.svg +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/demo/generate_sources.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/demo/memo.backdraft.html +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/demo/memo.md +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/demo/sources/t12-summary.pdf +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/demo/sources/underwriting-model.xlsx +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/site/demo.html +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/site/index.html +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/spec/artifact.md +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/spec/chunking.md +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/spec/tokens.md +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/binder.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/evidence.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/verify/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/verify/base.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/verify/entail.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/verify/overlap.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/verify/recompute.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/bind/verify/value_trace.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/cli_context.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/credentials.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/base.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/csv.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/docx.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/image.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/pdf_text.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/pptx.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/sheet.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/text.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/vlm_settings.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/xls.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/extract/xlsx.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/gate/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/gate/cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/kernel/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/kernel/artifact.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/kernel/chunking.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/kernel/claims.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/kernel/errors.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/kernel/hashing.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/kernel/model.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/kernel/tokens.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/registry/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/registry/ledger.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/registry/schema.sql +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/registry/store.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/_text.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/footnotes.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/html/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/html/assets.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/html/components.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/html/fmt.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/html/fmt_vectors.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/html/page.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/html/text.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/markdown.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/placement.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/src/backdraft/render/sidecar.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/conftest.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/conftest_registry.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/conftest_render.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/fakes.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/gate/conftest.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/gate/fake_registry.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/gate/test_gate_ledger.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/gate/test_gate_searcher.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/gate/test_reader_cells.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/bind/backfill.bound.md +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/bind/frontwalk.bound.md +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/long_tail.json +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/long_tail.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/many_small.json +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/many_small.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/mixed.json +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/mixed.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/one_giant.json +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/one_giant.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/short_page.json +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/short_page.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/unsplittable.json +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/chunking/unsplittable.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/render/backfill.footnotes.md +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/render/backfill.sidecar.json +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/render/demo.footnotes.md +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden/render/demo.sidecar.json +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/golden_util.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_bind_binder.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_bind_evidence.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_bind_verify.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_chunking.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_claims.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_credentials.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_fmt_parity.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_footnotes.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_gate_integration.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_hashing.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_html.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_invariants.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_model.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_placement.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_records_location.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_registry.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_reingest.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_render_cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_render_markdown.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_sidecar.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_snapshot_settings.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_tokens.py +0 -0
- {backdraft-0.4.0 → backdraft-0.5.0}/tests/test_value_trace.py +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backdraft",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Spencer Braun",
|
|
5
|
+
"url": "https://github.com/spencerbraun"
|
|
6
|
+
},
|
|
7
|
+
"description": "Self-hosting marketplace for the backdraft plugin: agent skills for writing documents whose factual claims carry verifiable citations.",
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "backdraft",
|
|
11
|
+
"source": "./",
|
|
12
|
+
"description": "Drop-in provenance for factual claims: one click from any claim to the evidence behind it. Ships the backdraft writing, backfill, and artifact-reading skills; the CLI installs separately (uv tool install backdraft).",
|
|
13
|
+
"category": "productivity"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backdraft",
|
|
3
|
+
"displayName": "Backdraft",
|
|
4
|
+
"version": "0.5.0",
|
|
5
|
+
"description": "Drop-in provenance for factual claims: one click from any claim to the evidence behind it. Ships the backdraft writing, backfill, and artifact-reading skills; the CLI installs separately (uv tool install backdraft).",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Spencer Braun",
|
|
8
|
+
"url": "https://github.com/spencerbraun"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://backdraft.dev",
|
|
11
|
+
"repository": "https://github.com/spencerbraun/backdraft",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": ["citations", "provenance", "sources", "evidence", "documents", "pdf", "xlsx", "writing"]
|
|
14
|
+
}
|
|
@@ -134,6 +134,7 @@ Self-contained HTML (plus the machine-readable sidecar): **quotes + receipts + e
|
|
|
134
134
|
| 2026-07-29 | **Office formats extract natively, never through conversion.** DOCX and PPTX are read straight from their XML (python-docx / python-pptx, core deps). Converting to PDF was rejected on principle: conversion is rendering, so it needs a layout engine (LibreOffice, MS Office, or a cloud API — a 700MB dependency, a platform lock, or documents leaving the machine), and converter-dependent pagination would break content-addressed locator stability. DOCX pages are heading sections: the split level is the smallest outline level present among {Heading 1, Heading 2}; preamble is section 1; no headings → one section — locators stay `pN.cM`, no grammar change. PPTX pages are slides (slide N is slide N in every renderer). The text floor deliberately misses slide visuals; the ingest note says so and names the path that captures them — the user exporting the deck to PDF with the real renderer they already have, ingested through the vision extractor — so a calling agent can relay it. |
|
|
135
135
|
| 2026-07-29 | **The vision extractor ships by default (supersedes "[vlm] as extra").** `openai` + `pdf2image` moved into core dependencies; `[vlm]` remains as an empty compat alias. Rationale: the VLM path is the recommended path for real PDFs, and an extra made the recommended path opt-in. The consent boundary does not move — installing deps was never consent; the backdraft-scoped `BACKDRAFT_VLM_API_KEY` is, and ambient provider keys stay unread. There is no slim install: pip extras can only add, never subtract; a separate core distribution is possible later if anyone asks. poppler stays a runtime capability check. |
|
|
136
136
|
| 2026-07-29 | **Renderer decomposed; Python is the formatting authority.** `render/html.py` (1,300 lines) became the `render/html/` package (fmt, text, components, assets, page), byte-identical at the seam. The deliberate `fmt_cell` (Python) / `fmtCell` (JS) duplication is now pinned by one shared vector table driving both tests, with the JS run under node when available. The JS was found diverging on exact rounding ties (`toFixed` rounds half-up; Python rounds half-to-even on the double's value) and was rewritten to round exactly — Python defines correct, JS matches it. The artifact emits minified CSS/JS and unindented markup; the `$legend` and sidecar JSON stay readable in view-source (spec constraint). Snapshot budget knobs `BACKDRAFT_SNAPSHOT_QUALITY`/`BACKDRAFT_SNAPSHOT_MAX_HEIGHT` tune page-image weight at ingest — display only, tokens never derive from pixels. |
|
|
137
|
+
| 2026-07-30 | **Every PDF ingest captures page images, not just the VLM path's.** The text-layer route stored none, so the keyless path produced visually poorer artifacts for no principled reason — the pages render locally through poppler, no model calls, and the capability already existed as the `snapshot-pages` backfill. The capture is a step *after* extraction, owned by `cli.py` over `extract/snapshots.py`, deliberately **not** inside `pdf-text`: an extractor whose output varied with whether poppler happened to be installed would not be `deterministic = True`, and the same bytes must extract identically on every machine. Because snapshots only ever write `page_images` — a table no token, chunk, or receipt path reads — capture is safe to make best-effort: no poppler means ingest still exits 0, with a one-line note naming `snapshot-pages` as the backfill, rather than failing over a display concern. Tradeoff accepted: PDF ingest is now slower by a local render per page (one poppler call per page rather than one per document, to bound peak memory on long PDFs), and a library caller using `Registry.ingest` directly gets no snapshots — the policy lives at the CLI, which is the substrate that has a user to notify. |
|
|
137
138
|
|
|
138
139
|
## Open
|
|
139
140
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backdraft
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Drop-in provenance for factual claims: one click from any claim to the evidence behind it.
|
|
5
5
|
Project-URL: Homepage, https://backdraft.dev
|
|
6
6
|
Project-URL: Documentation, https://backdraft.dev/docs.html
|
|
@@ -61,6 +61,7 @@ Markdown.
|
|
|
61
61
|
$ backdraft init
|
|
62
62
|
registry: /…/demo/.backdraft
|
|
63
63
|
documents: 0
|
|
64
|
+
… plus where settings live and what to do next
|
|
64
65
|
|
|
65
66
|
$ backdraft ingest sources/t12-summary.pdf sources/underwriting-model.xlsx
|
|
66
67
|
t12-summary t12-summary.pdf pdf 3 pages
|
|
@@ -130,7 +131,7 @@ $ backdraft bind memo.md --session s-bridgeview --check value-trace,overlap
|
|
|
130
131
|
bound 15 claim(s), 16 citation(s) [frontwalk]
|
|
131
132
|
resolved: 15
|
|
132
133
|
unresolved: 1
|
|
133
|
-
overlap:
|
|
134
|
+
overlap: pass 11, skip 4
|
|
134
135
|
value-trace: pass 15
|
|
135
136
|
! unresolved: bd:t12-summary:p4.c1:1a2b
|
|
136
137
|
wrote .backdraft/records/memo.backdraft.json
|
|
@@ -209,8 +210,9 @@ explicit, backdraft-scoped consent: `BACKDRAFT_VLM_API_KEY` (env or
|
|
|
209
210
|
key in the environment is not consent to send documents to its provider. The
|
|
210
211
|
default provider is OpenRouter running Gemini 3.1 Flash Lite; direct OpenAI is
|
|
211
212
|
base_url + model, set explicitly. Without a scoped key, `auto` falls back to
|
|
212
|
-
the keyless text layer and says so
|
|
213
|
-
installed separately
|
|
213
|
+
the keyless text layer and says so — still storing each page's image, which it
|
|
214
|
+
renders locally. PDF page rendering needs poppler, installed separately; without
|
|
215
|
+
it ingest succeeds without the images and names `snapshot-pages` as the backfill.
|
|
214
216
|
|
|
215
217
|
Two extras:
|
|
216
218
|
|
|
@@ -232,13 +234,7 @@ uv run pytest
|
|
|
232
234
|
|
|
233
235
|
## Skills
|
|
234
236
|
|
|
235
|
-
Three agent skills ship inside the package
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
backdraft skill install # the writing skill, into ~/.claude/skills/
|
|
239
|
-
backdraft skill install --all # plus backfill and artifact-reading
|
|
240
|
-
backdraft skill install --project # into this repo's .claude/skills/
|
|
241
|
-
```
|
|
237
|
+
Three agent skills ship inside the package and in this repo:
|
|
242
238
|
|
|
243
239
|
| Skill | For |
|
|
244
240
|
|---|---|
|
|
@@ -247,7 +243,51 @@ backdraft skill install --project # into this repo's .claude/skills/
|
|
|
247
243
|
| [`backdraft-artifact`](skills/backdraft-artifact/SKILL.md) | reading someone else's artifact cold |
|
|
248
244
|
|
|
249
245
|
The CLI is the system; a skill is one page telling an agent to use it. Nothing in
|
|
250
|
-
the skills is required to use backdraft by hand.
|
|
246
|
+
the skills is required to use backdraft by hand. How the skills reach your agent
|
|
247
|
+
depends on the harness:
|
|
248
|
+
|
|
249
|
+
**Claude Code.** The repo is its own plugin marketplace, so the plugin route
|
|
250
|
+
tracks releases:
|
|
251
|
+
|
|
252
|
+
```text
|
|
253
|
+
/plugin marketplace add spencerbraun/backdraft
|
|
254
|
+
/plugin install backdraft@backdraft
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Or have the CLI copy the skills into your skills directory:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
backdraft skill install # the writing skill, into ~/.claude/skills/
|
|
261
|
+
backdraft skill install --all # plus backfill and artifact-reading
|
|
262
|
+
backdraft skill install --project # into this repo's .claude/skills/
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Claude Cowork.** Once the plugin is listed in Anthropic's community
|
|
266
|
+
directory, it installs from Cowork's built-in skills directory. Until then,
|
|
267
|
+
zip a skill folder from `skills/` and upload it under Customize > Skills.
|
|
268
|
+
Inside a Cowork session the CLI itself runs per-command as
|
|
269
|
+
`uvx backdraft ...`, since installs do not persist between sessions.
|
|
270
|
+
|
|
271
|
+
**Codex, Cursor, Copilot.** Agents in this family read skills from
|
|
272
|
+
`~/.agents/skills`:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
backdraft skill install --agent codex # into ~/.agents/skills/
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
or commit the skill folders to your repo under `.agents/skills/` so every
|
|
279
|
+
checkout carries them.
|
|
280
|
+
|
|
281
|
+
**Standing context, any harness.** Paste this into your repo's AGENTS.md:
|
|
282
|
+
|
|
283
|
+
```markdown
|
|
284
|
+
## backdraft (cited writing)
|
|
285
|
+
When a document must cite its sources, write it through the backdraft CLI:
|
|
286
|
+
it shows source text with a citation token over every span, and only shown
|
|
287
|
+
spans are citable. In a sandboxed session run every command as
|
|
288
|
+
`uvx backdraft ...` (no install, no PATH edits). Start from
|
|
289
|
+
`uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.
|
|
290
|
+
```
|
|
251
291
|
|
|
252
292
|
## Documentation
|
|
253
293
|
|
|
@@ -36,6 +36,7 @@ Markdown.
|
|
|
36
36
|
$ backdraft init
|
|
37
37
|
registry: /…/demo/.backdraft
|
|
38
38
|
documents: 0
|
|
39
|
+
… plus where settings live and what to do next
|
|
39
40
|
|
|
40
41
|
$ backdraft ingest sources/t12-summary.pdf sources/underwriting-model.xlsx
|
|
41
42
|
t12-summary t12-summary.pdf pdf 3 pages
|
|
@@ -105,7 +106,7 @@ $ backdraft bind memo.md --session s-bridgeview --check value-trace,overlap
|
|
|
105
106
|
bound 15 claim(s), 16 citation(s) [frontwalk]
|
|
106
107
|
resolved: 15
|
|
107
108
|
unresolved: 1
|
|
108
|
-
overlap:
|
|
109
|
+
overlap: pass 11, skip 4
|
|
109
110
|
value-trace: pass 15
|
|
110
111
|
! unresolved: bd:t12-summary:p4.c1:1a2b
|
|
111
112
|
wrote .backdraft/records/memo.backdraft.json
|
|
@@ -184,8 +185,9 @@ explicit, backdraft-scoped consent: `BACKDRAFT_VLM_API_KEY` (env or
|
|
|
184
185
|
key in the environment is not consent to send documents to its provider. The
|
|
185
186
|
default provider is OpenRouter running Gemini 3.1 Flash Lite; direct OpenAI is
|
|
186
187
|
base_url + model, set explicitly. Without a scoped key, `auto` falls back to
|
|
187
|
-
the keyless text layer and says so
|
|
188
|
-
installed separately
|
|
188
|
+
the keyless text layer and says so — still storing each page's image, which it
|
|
189
|
+
renders locally. PDF page rendering needs poppler, installed separately; without
|
|
190
|
+
it ingest succeeds without the images and names `snapshot-pages` as the backfill.
|
|
189
191
|
|
|
190
192
|
Two extras:
|
|
191
193
|
|
|
@@ -207,13 +209,7 @@ uv run pytest
|
|
|
207
209
|
|
|
208
210
|
## Skills
|
|
209
211
|
|
|
210
|
-
Three agent skills ship inside the package
|
|
211
|
-
|
|
212
|
-
```bash
|
|
213
|
-
backdraft skill install # the writing skill, into ~/.claude/skills/
|
|
214
|
-
backdraft skill install --all # plus backfill and artifact-reading
|
|
215
|
-
backdraft skill install --project # into this repo's .claude/skills/
|
|
216
|
-
```
|
|
212
|
+
Three agent skills ship inside the package and in this repo:
|
|
217
213
|
|
|
218
214
|
| Skill | For |
|
|
219
215
|
|---|---|
|
|
@@ -222,7 +218,51 @@ backdraft skill install --project # into this repo's .claude/skills/
|
|
|
222
218
|
| [`backdraft-artifact`](skills/backdraft-artifact/SKILL.md) | reading someone else's artifact cold |
|
|
223
219
|
|
|
224
220
|
The CLI is the system; a skill is one page telling an agent to use it. Nothing in
|
|
225
|
-
the skills is required to use backdraft by hand.
|
|
221
|
+
the skills is required to use backdraft by hand. How the skills reach your agent
|
|
222
|
+
depends on the harness:
|
|
223
|
+
|
|
224
|
+
**Claude Code.** The repo is its own plugin marketplace, so the plugin route
|
|
225
|
+
tracks releases:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
/plugin marketplace add spencerbraun/backdraft
|
|
229
|
+
/plugin install backdraft@backdraft
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Or have the CLI copy the skills into your skills directory:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
backdraft skill install # the writing skill, into ~/.claude/skills/
|
|
236
|
+
backdraft skill install --all # plus backfill and artifact-reading
|
|
237
|
+
backdraft skill install --project # into this repo's .claude/skills/
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Claude Cowork.** Once the plugin is listed in Anthropic's community
|
|
241
|
+
directory, it installs from Cowork's built-in skills directory. Until then,
|
|
242
|
+
zip a skill folder from `skills/` and upload it under Customize > Skills.
|
|
243
|
+
Inside a Cowork session the CLI itself runs per-command as
|
|
244
|
+
`uvx backdraft ...`, since installs do not persist between sessions.
|
|
245
|
+
|
|
246
|
+
**Codex, Cursor, Copilot.** Agents in this family read skills from
|
|
247
|
+
`~/.agents/skills`:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
backdraft skill install --agent codex # into ~/.agents/skills/
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
or commit the skill folders to your repo under `.agents/skills/` so every
|
|
254
|
+
checkout carries them.
|
|
255
|
+
|
|
256
|
+
**Standing context, any harness.** Paste this into your repo's AGENTS.md:
|
|
257
|
+
|
|
258
|
+
```markdown
|
|
259
|
+
## backdraft (cited writing)
|
|
260
|
+
When a document must cite its sources, write it through the backdraft CLI:
|
|
261
|
+
it shows source text with a citation token over every span, and only shown
|
|
262
|
+
spans are citable. In a sandboxed session run every command as
|
|
263
|
+
`uvx backdraft ...` (no install, no PATH edits). Start from
|
|
264
|
+
`uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.
|
|
265
|
+
```
|
|
226
266
|
|
|
227
267
|
## Documentation
|
|
228
268
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
Sequenced intent. DESIGN.md records what was *decided*; this file records what
|
|
4
|
+
is *queued*, so tabling something is an act of writing it down here rather than
|
|
5
|
+
losing it. Items graduate by getting built and deleted.
|
|
6
|
+
|
|
7
|
+
## How the queue runs
|
|
8
|
+
|
|
9
|
+
The Now section is an ordered queue written for an implementing agent with no
|
|
10
|
+
session context: each item carries its intent, its shape, and its acceptance
|
|
11
|
+
test. The working agreement is one item at a time, top first, shipped complete
|
|
12
|
+
— code, tests, docs, and a DESIGN.md decision row where the item calls for one
|
|
13
|
+
— then deleted from this file in the same commit. Releases cut Friday, so an
|
|
14
|
+
item that lands mid-week waits on PyPI until the release; nothing ships
|
|
15
|
+
half-done to make a release. An item that turns out bigger than its sizing
|
|
16
|
+
gets split here, not stretched silently.
|
|
17
|
+
|
|
18
|
+
The cadence is daily and the runner is an agent: one item each morning Monday
|
|
19
|
+
through Thursday, landing as a direct commit to `main` — this repo does not
|
|
20
|
+
use pull requests, so a week of commits is the only review surface there is.
|
|
21
|
+
|
|
22
|
+
Friday builds nothing new. It reads that week's diff instead — duplication,
|
|
23
|
+
drift from the repo's own idioms, branches that landed untested, help text and
|
|
24
|
+
error messages that confuse, commands the docs describe wrongly — and lands
|
|
25
|
+
the fixes. Then the release, and only if something landed since the last tag:
|
|
26
|
+
the version moves in both `pyproject.toml` and `.claude-plugin/plugin.json`,
|
|
27
|
+
and publishing the GitHub release is what ships to PyPI. Then this section is
|
|
28
|
+
refilled with five items for the week ahead, drawn against the core — the four
|
|
29
|
+
verbs, the registry, the grammar, the artifact — and against where an agent
|
|
30
|
+
using backdraft actually stumbles. What the maintenance pass just found is the
|
|
31
|
+
best evidence available for what those five should be.
|
|
32
|
+
|
|
33
|
+
## Now
|
|
34
|
+
|
|
35
|
+
### 1. Source card: contents resize with the card
|
|
36
|
+
|
|
37
|
+
**Intent.** The artifact's source card is user-resizable (`resize:vertical`,
|
|
38
|
+
`max-height:82vh` — assets.py) but its inner blocks hold their own fixed caps
|
|
39
|
+
— the quote scroller is pinned at `15rem`, and the sheet grid and page image
|
|
40
|
+
size themselves — so dragging the card taller yields whitespace, not more
|
|
41
|
+
evidence. Resizing should mean seeing more.
|
|
42
|
+
|
|
43
|
+
**Shape.** Make the card a flex column whose scrollable evidence region
|
|
44
|
+
(quote / drift / sheet / page image) flexes to fill the card's height, with the
|
|
45
|
+
header, source selector, and tabs as fixed rows. Audit every fixed
|
|
46
|
+
`max-height` inside `.card` and convert to flex-driven sizing. The mobile
|
|
47
|
+
card keeps `resize:none` and its current behavior. Python is the formatting
|
|
48
|
+
authority and this is CSS/JS only, so no fmt-parity surface is touched; the
|
|
49
|
+
minified asset budget should move only trivially.
|
|
50
|
+
|
|
51
|
+
**Acceptance.** Dragging the card taller visibly grows the quote, sheet, and
|
|
52
|
+
page-image viewports; dragging it short reintroduces inner scrolling rather
|
|
53
|
+
than clipping the header or tabs. Verified against a real artifact (the demo)
|
|
54
|
+
across the three evidence kinds, plus the mobile breakpoint. Node-side DOM
|
|
55
|
+
test if cheap; otherwise the existing render tests plus a manual screenshot
|
|
56
|
+
pass recorded in the PR.
|
|
57
|
+
|
|
58
|
+
**Size.** One day.
|
|
59
|
+
|
|
60
|
+
### 2. Theming: sticky user preferences, drop-in themes
|
|
61
|
+
|
|
62
|
+
**Intent.** The artifact's look is currently one hardcoded design. Let a user
|
|
63
|
+
set a theme once and have every artifact they render honor it, and let a theme
|
|
64
|
+
be a small file someone can drop in — fonts, colors, heading treatment at
|
|
65
|
+
minimum.
|
|
66
|
+
|
|
67
|
+
**Shape.** Two parts, in order:
|
|
68
|
+
|
|
69
|
+
- *Decision row first.* A theme is a named set of overrides for the CSS custom
|
|
70
|
+
properties the stylesheet already exposes (`--paper`, `--ink`, `--sel`, the
|
|
71
|
+
font stacks) plus a bounded set of typographic choices (heading family/case,
|
|
72
|
+
maybe rule weight). A theme may not change layout, structure, or any
|
|
73
|
+
verification affordance — display only, tokens and records untouched.
|
|
74
|
+
Settle the file format (a flat TOML of variable names, or a raw CSS-variables
|
|
75
|
+
block) and write the DESIGN.md row before code.
|
|
76
|
+
- *Then the loader.* Precedence: `render --theme <file-or-name>` >
|
|
77
|
+
project `.backdraft/theme.*` > user-wide config (XDG,
|
|
78
|
+
`~/.config/backdraft/theme.*`) > built-in default. The user-wide file is
|
|
79
|
+
what makes preferences stick across projects. Bundled themes (the default
|
|
80
|
+
plus one or two alternates) prove the format; the resolved theme is baked
|
|
81
|
+
into the artifact at render time so the file stays self-contained.
|
|
82
|
+
|
|
83
|
+
**Acceptance.** Rendering with no config is byte-stable against today's
|
|
84
|
+
output. A theme file in the XDG location changes fonts/colors/headers on every
|
|
85
|
+
subsequent render in any project without flags; `--theme` overrides it; a
|
|
86
|
+
malformed theme fails with a clear message, never a half-styled artifact.
|
|
87
|
+
Docs page gains a short theming section with a sample theme file.
|
|
88
|
+
|
|
89
|
+
**Size.** Two days — decision row and variable audit, then loader, bundled
|
|
90
|
+
themes, docs.
|
|
91
|
+
|
|
92
|
+
### 3. URL sources: capture and link back
|
|
93
|
+
|
|
94
|
+
**Intent.** Diligence folders contain links, not just files. `backdraft ingest
|
|
95
|
+
<url>` should fetch a page, snapshot it into the registry like any other
|
|
96
|
+
source, and — the other half — artifacts should show a source's origin URL and
|
|
97
|
+
link back to it, so a claim cited to a web page carries both the frozen
|
|
98
|
+
receipt and the live pointer.
|
|
99
|
+
|
|
100
|
+
**Shape.** This is the graduation of the "HTML pages" question from Later, and
|
|
101
|
+
it needs its decision row: *the source's identity is the sha256 of the fetched
|
|
102
|
+
snapshot at ingest time*, same as a file — the URL is provenance metadata, not
|
|
103
|
+
identity. A page that changes later is a new generation; the existing drift
|
|
104
|
+
machinery already describes that. Capture: fetch (stdlib or the lightest
|
|
105
|
+
dependency that handles redirects and encodings), extract readable text into
|
|
106
|
+
the normal page/chunk structure, store the origin URL and fetch timestamp in
|
|
107
|
+
source meta. Render: sources carrying an origin URL show it in the resting
|
|
108
|
+
source list and the card, as a real link. Out of scope for this item:
|
|
109
|
+
JavaScript-rendered pages (note the limitation), authentication, and `.eml`
|
|
110
|
+
(stays in Later).
|
|
111
|
+
|
|
112
|
+
**Acceptance.** `ingest https://…` on a static page mints citable tokens;
|
|
113
|
+
`read`/`search`/`cell`-equivalents behave as for text sources; bind embeds the
|
|
114
|
+
receipt; the artifact's source entry links to the URL and the record JSON
|
|
115
|
+
carries url + fetched-at. Re-ingesting a changed page produces a new
|
|
116
|
+
generation and `drifted` behaves correctly. A network-free test suite (fixture
|
|
117
|
+
HTML served locally or loaded from disk); DESIGN.md row written; docs updated.
|
|
118
|
+
|
|
119
|
+
**Size.** Two to three days — decision row and capture first, render linkage
|
|
120
|
+
second.
|
|
121
|
+
|
|
122
|
+
## Parked
|
|
123
|
+
|
|
124
|
+
Deliberately not queued, each with the reason, so picking one up starts from
|
|
125
|
+
the objection rather than rediscovering it.
|
|
126
|
+
|
|
127
|
+
- **Exhibits view** — the evidence-first inversion. Risk: it grows the
|
|
128
|
+
artifact and pollutes a design whose strength is restraint. Needs a
|
|
129
|
+
deliberate design pass and a real audience before any code; not a
|
|
130
|
+
weekend-feature shape.
|
|
131
|
+
- **Excel region maps and range reads** — the research notes exist, but no
|
|
132
|
+
confidence the deterministic detection cascade lands well on the first
|
|
133
|
+
try. Revisit when large-sheet navigation hurts in a real corpus, with
|
|
134
|
+
test workbooks in hand.
|
|
135
|
+
- **`bd:calc` derivations** — reserved in the grammar and parsed as
|
|
136
|
+
malformed-on-purpose, which is enough for now. Full support complicates
|
|
137
|
+
binding and verification for one field-trial case.
|
|
138
|
+
- **Merged-cell and frozen-pane rendering** — ingest captures `merged` and
|
|
139
|
+
`frozen` in sheet meta and keeps them (cheap, and capture is the part you
|
|
140
|
+
cannot backfill). Frozen-pane rendering is cut; merged-cell rendering
|
|
141
|
+
waits for a real sheet that renders wrong without it.
|
|
142
|
+
|
|
143
|
+
## Later
|
|
144
|
+
|
|
145
|
+
- **Living documents** — re-ingest, re-bind, and a drift-first report: "these
|
|
146
|
+
claims cite figures that changed; cited-then vs. now." The primitives
|
|
147
|
+
(generations, `drifted`, the word-diff) all exist; the missing piece is a
|
|
148
|
+
diff-shaped report and a demo.
|
|
149
|
+
- **Substrates beyond the CLI** — SDK middleware for pipelines (the exit-code
|
|
150
|
+
and `--to json` contracts are already shaped for it), and a client-side
|
|
151
|
+
drag-drop viewer page (no upload — the file never leaves the reader's
|
|
152
|
+
machine).
|
|
153
|
+
- **Email (.eml)** — arrives in diligence folders alongside everything else;
|
|
154
|
+
identity questions (headers vs. body, attachments as child sources) deserve
|
|
155
|
+
a decision row before code. The web-page half of this item graduated to the
|
|
156
|
+
Now queue as URL sources.
|
|
157
|
+
- **Anthropic-API provider for the VLM extractor** — Cowork's sandbox
|
|
158
|
+
egress allowlist includes api.anthropic.com but not the OpenAI-compatible
|
|
159
|
+
providers, so an Anthropic-native option would let sandboxed sessions
|
|
160
|
+
ingest at full fidelity. Needs a client abstraction the vlm module
|
|
161
|
+
currently doesn't have; the documented workaround (ingest locally, the
|
|
162
|
+
registry travels with the folder) covers it meanwhile.
|
|
163
|
+
- **Distribution** — published on PyPI; the repo ships
|
|
164
|
+
`.claude-plugin/plugin.json` and `marketplace.json`, so it installs as a
|
|
165
|
+
Claude Code plugin and self-hosts its marketplace, and the AGENTS.md
|
|
166
|
+
snippet is published in the README and site docs. The community-directory
|
|
167
|
+
submission went in 2026-07-29 and is awaiting review — approval is what
|
|
168
|
+
surfaces the skills in Cowork's directory.
|
|
169
|
+
|
|
170
|
+
## Someday
|
|
171
|
+
|
|
172
|
+
- **Hosted team features** — upload, access control, shared registries.
|
|
173
|
+
Deliberately parked: the product's trust story is the self-contained file,
|
|
174
|
+
and a hosted viewer inverts it. Revisit only as a distinct teams product.
|
|
175
|
+
- **Entail at scale** — the model-judge verifier is wired (`[entail]` extra)
|
|
176
|
+
but has never been field-calibrated; needs a corpus and a rubric before it
|
|
177
|
+
is recommended anywhere.
|
|
@@ -352,8 +352,10 @@ class Registry:
|
|
|
352
352
|
# page snapshots (v0.2: evidence for the artifact)
|
|
353
353
|
def page_image(self, slug: str, number: int) -> PageImage | None: ...
|
|
354
354
|
# The stored visual snapshot of a current-extraction page: what the VLM
|
|
355
|
-
# extractor was shown (WebP q85),
|
|
356
|
-
#
|
|
355
|
+
# extractor was shown (WebP q85), the local poppler render the CLI
|
|
356
|
+
# captures after a text-layer PDF ingest, or a `snapshot-pages`
|
|
357
|
+
# backfill. None for non-PDFs, and for PDFs ingested where poppler was
|
|
358
|
+
# unavailable.
|
|
357
359
|
def save_page_image(self, extraction_id: int, number: int, *, data: bytes,
|
|
358
360
|
format: str, width: int, height: int) -> None: ...
|
|
359
361
|
def current_extraction_id(self, slug: str) -> int | None: ... # for backfill tools
|
|
@@ -370,4 +372,4 @@ class Registry:
|
|
|
370
372
|
|
|
371
373
|
## Addendum B — CLI assembly
|
|
372
374
|
|
|
373
|
-
Top-level `cli.py` (W1) owns: typer app, registry discovery (nearest `.backdraft/` walking up from cwd; `BACKDRAFT_HOME` override), session resolution (`--session` flag > `BACKDRAFT_SESSION` env > default session), and the `init` / `ingest` / `ls` / `export` commands. It mounts sub-apps `gate/cli.py` (W2: `read`, `search`, `session`), `bind/cli.py` (W3: `bind`), `render/cli.py` (W4: `render`) — each exposes `app = typer.Typer()`; the top level mounts each inside a try/except ImportError so partial merges still run. The `backdraft` console script is declared by W1.
|
|
375
|
+
Top-level `cli.py` (W1) owns: typer app, registry discovery (nearest `.backdraft/` walking up from cwd; `BACKDRAFT_HOME` override), session resolution (`--session` flag > `BACKDRAFT_SESSION` env > default session), and the `init` / `ingest` / `ls` / `export` / `snapshot-pages` commands. After each ingest, `cli.py` captures page snapshots for any PDF whose extraction carries none — the text-layer path, since `vlm` stores its own — via `extract/snapshots.py` (poppler through pdf2image, `snapshot-pages`' internals). It sits outside the extractor so `pdf-text` stays deterministic regardless of whether poppler is installed, and it is best-effort: a `SnapshotError` leaves ingest at exit 0 with a one-line note naming `snapshot-pages` as the backfill. It mounts sub-apps `gate/cli.py` (W2: `read`, `search`, `session`), `bind/cli.py` (W3: `bind`), `render/cli.py` (W4: `render`) — each exposes `app = typer.Typer()`; the top level mounts each inside a try/except ImportError so partial merges still run. The `backdraft` console script is declared by W1.
|
|
@@ -43,17 +43,15 @@ underwriting-model underwriting-model.xlsx xlsx 2 pages
|
|
|
43
43
|
note: extracted with pdf-text (the embedded text layer). Glossy or scanned PDFs extract better through a vision model: set BACKDRAFT_VLM_API_KEY in .backdraft/env.
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
The keyless text layer is fine for these clean generated sources.
|
|
47
|
-
artifact to show the cited pages themselves, store their snapshots — local
|
|
48
|
-
rendering, no model calls:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
backdraft snapshot-pages t12-summary
|
|
52
|
-
```
|
|
46
|
+
The keyless text layer is fine for these clean generated sources.
|
|
53
47
|
|
|
54
48
|
Ingest picked an extractor per file (`pdf-text` for the PDF, `xlsx` for the
|
|
55
49
|
workbook), snapshotted the text, and minted every anchor: chunks for the PDF
|
|
56
|
-
pages, cells for the sheets. Nothing is citable that was not ingested.
|
|
50
|
+
pages, cells for the sheets. Nothing is citable that was not ingested. It also
|
|
51
|
+
rendered each PDF page to an image and stored it, so the artifact at the end
|
|
52
|
+
can show the cited pages themselves — local rendering through poppler, no model
|
|
53
|
+
calls. On a machine without poppler ingest prints a note instead and
|
|
54
|
+
`backdraft snapshot-pages t12-summary` backfills them whenever you like.
|
|
57
55
|
|
|
58
56
|
The note is real: this walkthrough deliberately uses the keyless text-layer
|
|
59
57
|
path so it reproduces anywhere, and our source PDF is born-digital, where that
|
|
@@ -362,12 +360,15 @@ claim occurs at the cited location, across unit, scale and format differences. I
|
|
|
362
360
|
is what makes `[a purchase price of $24.85 million](…assumptions!B10:964a)` pass
|
|
363
361
|
against a cell whose snippet is the eight characters `24850000`.
|
|
364
362
|
|
|
365
|
-
**`overlap:
|
|
363
|
+
**`overlap: pass 11, skip 4`.** Word-overlap between claim and snippet,
|
|
366
364
|
report-only by construction — it never returns `fail`, because a faithful summary
|
|
367
|
-
can share almost no vocabulary with its source. The four `
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
can share almost no vocabulary with its source. The four `skip`s are the overlap
|
|
366
|
+
check declining to measure wording against single-cell sources: those claims cite
|
|
367
|
+
Excel cells, and a bare number is not a sentence, so the record says it declined
|
|
368
|
+
instead of emitting a low score as though it meant something. Where it does
|
|
369
|
+
measure and finds little it says `partial` — a signal, not a fault. The quoted
|
|
370
|
+
claim in the memo is checked differently: a quotation is an exact-substring
|
|
371
|
+
assertion, and it passes.
|
|
371
372
|
|
|
372
373
|
Both checks are off by default. Out of the box, bind is pure provenance.
|
|
373
374
|
|
|
@@ -379,10 +380,6 @@ directory; the memo and the artifact are the only files you see. (A readable
|
|
|
379
380
|
markdown projection with a `## References` section is available with
|
|
380
381
|
`bind --bound` when you want one.)
|
|
381
382
|
|
|
382
|
-
The four `skip` verdicts are the overlap check declining to measure wording
|
|
383
|
-
against single-cell sources — a number is not a sentence, and the record says
|
|
384
|
-
so instead of emitting noise.
|
|
385
|
-
|
|
386
383
|
Here is what one claim looks like in the sidecar:
|
|
387
384
|
|
|
388
385
|
```json
|
|
@@ -409,8 +406,8 @@ Here is what one claim looks like in the sidecar:
|
|
|
409
406
|
},
|
|
410
407
|
{
|
|
411
408
|
"method": "overlap",
|
|
412
|
-
"status": "
|
|
413
|
-
"detail": "
|
|
409
|
+
"status": "skip",
|
|
410
|
+
"detail": "wording overlap does not apply to a single cell"
|
|
414
411
|
}
|
|
415
412
|
]
|
|
416
413
|
}
|
|
@@ -452,8 +449,10 @@ backdraft render memo.md --to html
|
|
|
452
449
|
memo.backdraft.html
|
|
453
450
|
```
|
|
454
451
|
|
|
455
|
-
[`memo.backdraft.html`](memo.backdraft.html) is one file,
|
|
456
|
-
request of any kind
|
|
452
|
+
[`memo.backdraft.html`](memo.backdraft.html) is one file, 522 KB — most of it the
|
|
453
|
+
embedded page images — with no external request of any kind: no font, no
|
|
454
|
+
stylesheet, no script that executes. `bind --lean` skips the images and takes it
|
|
455
|
+
to 126 KB, at the cost of the cited page being shown. Open it
|
|
457
456
|
from `file://` with the network off. Hover or focus a claim to reveal its
|
|
458
457
|
receipt: the verbatim snippet, the source slug and locator, the token, the full
|
|
459
458
|
snippet hash, and a badge per verdict. Below the document body an **Unresolved**
|
|
@@ -109,7 +109,7 @@ footer{text-align:center;font-family:var(--sans);font-size:.74rem;color:var(--mu
|
|
|
109
109
|
<a href="#writing">Writing rules</a>
|
|
110
110
|
<a href="#verification">Verification</a>
|
|
111
111
|
<a href="#files">Files & the artifact</a>
|
|
112
|
-
<a href="#agents">Agents</a>
|
|
112
|
+
<a href="#agents">Agents & harnesses</a>
|
|
113
113
|
<a href="#security">Security & privacy</a>
|
|
114
114
|
<a href="#faq">FAQ</a>
|
|
115
115
|
</aside>
|
|
@@ -131,9 +131,11 @@ repo</a>, and agents should start from <a href="llms.txt">llms.txt</a>.</p>
|
|
|
131
131
|
<p>Python 3.13+. The vision-model extractor ships by default, the recommended
|
|
132
132
|
path for real PDFs (glossy layouts, info boxes, scans) and the only path for
|
|
133
133
|
scanned images; it activates only when <code>BACKDRAFT_VLM_API_KEY</code> is
|
|
134
|
-
set,
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
set. Without a key, PDFs fall back to the keyless embedded-text layer, and
|
|
135
|
+
ingest says so. Either way PDFs want <code>poppler</code> on your PATH
|
|
136
|
+
(<code>brew install poppler</code>) — it renders the pages, which is what puts
|
|
137
|
+
the cited page into the artifact; without it ingest still works and says what
|
|
138
|
+
is missing.
|
|
137
139
|
Spreadsheets (xlsx, xlsm), CSV, Word (docx), PowerPoint (pptx), text and
|
|
138
140
|
Markdown are keyless and built in, and sheet evidence carries the workbook's
|
|
139
141
|
own styling: bold, fills, number formats, column widths. Legacy xls
|
|
@@ -209,7 +211,7 @@ skill</a> imposes the gate, and you receive the artifact.</div>
|
|
|
209
211
|
<tr><th>Command</th><th>Does</th></tr>
|
|
210
212
|
<tr><td><code>init</code></td><td>Create <code>.backdraft/</code> (registry, credentials template) in the current directory.</td></tr>
|
|
211
213
|
<tr><td><code>ingest <files></code></td><td>Snapshot sources into the registry, minting every anchor. Formats: PDF, XLSX/XLSM, XLS, CSV/TSV, DOCX, PPTX, images (png, jpeg, tiff), text and Markdown. <code>--extractor auto|vlm|image|pdf-text|xlsx|xls|csv|docx|pptx|text</code>, <code>--slug</code>, <code>--config k=v</code>.</td></tr>
|
|
212
|
-
<tr><td><code>snapshot-pages <slug></code></td><td>Backfill page images for an already-ingested PDF, locally, no model calls (needs <code>poppler</code> for rendering).</td></tr>
|
|
214
|
+
<tr><td><code>snapshot-pages <slug></code></td><td>Backfill page images for an already-ingested PDF, locally, no model calls (needs <code>poppler</code> for rendering). Ingest stores them already — this is for a registry built before it did, or on a machine that had no poppler then.</td></tr>
|
|
213
215
|
<tr><td><code>ls</code></td><td>List ingested documents.</td></tr>
|
|
214
216
|
<tr><td><code>read [slug] [selector]</code></td><td>The gate: document list, table of contents, or a token-marked page/range/sheet read. Mints what it shows.</td></tr>
|
|
215
217
|
<tr><td><code>search <query></code></td><td>Full-text search over every anchor; hits are citable. <code>--in slug</code>, <code>--limit</code>.</td></tr>
|
|
@@ -277,14 +279,48 @@ of a gigabyte corpus is a ~2 MB file. Recipients need the one
|
|
|
277
279
|
</section>
|
|
278
280
|
|
|
279
281
|
<section id="agents">
|
|
280
|
-
<h2>Agents</h2>
|
|
281
|
-
<pre>backdraft skill install <span class="dim"># the writing skill, into ~/.claude/skills/</span>
|
|
282
|
-
backdraft skill install --all <span class="dim"># plus backfill and artifact-reading</span>
|
|
283
|
-
backdraft skill install --project<span class="dim"> # into this repo's .claude/skills/</span></pre>
|
|
282
|
+
<h2>Agents and harnesses</h2>
|
|
284
283
|
<p>The skill's core instruction is a substitution: for source documents, use
|
|
285
284
|
<code>backdraft read</code>/<code>search</code>, never raw file reads. The
|
|
286
285
|
token-efficient reference for any agent is <a href="llms.txt">llms.txt</a>
|
|
287
|
-
(~800 tokens).</p>
|
|
286
|
+
(~800 tokens). How the skill reaches your agent depends on the harness.</p>
|
|
287
|
+
<h3>Claude Code</h3>
|
|
288
|
+
<p>The repo is its own plugin marketplace, so the plugin route tracks
|
|
289
|
+
releases:</p>
|
|
290
|
+
<pre>/plugin marketplace add spencerbraun/backdraft
|
|
291
|
+
/plugin install backdraft@backdraft</pre>
|
|
292
|
+
<p>Or have the CLI copy the skills into your skills directory:</p>
|
|
293
|
+
<pre>backdraft skill install <span class="dim"># the writing skill, into ~/.claude/skills/</span>
|
|
294
|
+
backdraft skill install --all <span class="dim"># plus backfill and artifact-reading</span>
|
|
295
|
+
backdraft skill install --project<span class="dim"> # into this repo's .claude/skills/</span></pre>
|
|
296
|
+
<h3>Claude Cowork</h3>
|
|
297
|
+
<p>Once the plugin is listed in Anthropic's community directory, it installs
|
|
298
|
+
from Cowork's built-in skills directory; until then, zip a skill folder from
|
|
299
|
+
the repo's <code>skills/</code> and upload it under Customize > Skills.
|
|
300
|
+
Inside a session, run the CLI per-command as <code>uvx backdraft ...</code>,
|
|
301
|
+
since installs do not persist between sessions.</p>
|
|
302
|
+
<p>Sandboxes and credentials: a sandbox usually cannot reach model providers,
|
|
303
|
+
so ingest inside one falls back to the keyless text layer and says so. For
|
|
304
|
+
full fidelity, ingest once on your own machine with the vision extractor.
|
|
305
|
+
The registry lives in <code>.backdraft/</code> inside the project folder, so
|
|
306
|
+
it travels with the folder into any session, and binding and rendering never
|
|
307
|
+
need a key. Put <code>BACKDRAFT_VLM_API_KEY</code> in
|
|
308
|
+
<code>.backdraft/env</code> yourself; never paste keys into a chat.</p>
|
|
309
|
+
<h3>Codex, Cursor, Copilot</h3>
|
|
310
|
+
<p>Agents in this family read skills from <code>~/.agents/skills</code>:</p>
|
|
311
|
+
<pre>backdraft skill install --agent codex <span class="dim"># into ~/.agents/skills/</span></pre>
|
|
312
|
+
<p>Or commit the skill folders to your repo under <code>.agents/skills/</code>
|
|
313
|
+
so every checkout carries them. For Codex cloud environments, add
|
|
314
|
+
<code>pip install backdraft</code> to the setup script, which runs while the
|
|
315
|
+
network is still on.</p>
|
|
316
|
+
<h3>Standing context</h3>
|
|
317
|
+
<p>For any harness that reads AGENTS.md, paste this into your repo's file:</p>
|
|
318
|
+
<pre>## backdraft (cited writing)
|
|
319
|
+
When a document must cite its sources, write it through the backdraft CLI:
|
|
320
|
+
it shows source text with a citation token over every span, and only shown
|
|
321
|
+
spans are citable. In a sandboxed session run every command as
|
|
322
|
+
`uvx backdraft ...` (no install, no PATH edits). Start from
|
|
323
|
+
`uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.</pre>
|
|
288
324
|
</section>
|
|
289
325
|
|
|
290
326
|
<section id="security">
|
|
@@ -312,9 +348,13 @@ registry contains the full text of everything ingested.</li>
|
|
|
312
348
|
Text-layer receipts are fine for clean digital PDFs; glossy or scanned ones
|
|
313
349
|
deserve the vision model.</p>
|
|
314
350
|
<h3>My artifact has no page images.</h3>
|
|
315
|
-
<p>
|
|
316
|
-
|
|
317
|
-
|
|
351
|
+
<p>Ingest stores them for every PDF, through both the vision and the text-layer
|
|
352
|
+
path — the latter renders the pages locally, which needs <code>poppler</code>
|
|
353
|
+
on the machine. If ingest printed a note that it could not capture them, install
|
|
354
|
+
poppler; then, for that registry or any built before ingest did this, run
|
|
355
|
+
<code>backdraft snapshot-pages <slug></code> (local, free) and re-bind.
|
|
356
|
+
Also check you did not bind with <code>--lean</code>, which skips them
|
|
357
|
+
deliberately.</p>
|
|
318
358
|
<h3>What does <code>not_shown</code> actually catch?</h3>
|
|
319
359
|
<p>A real token, valid in the registry, that the writing session was never
|
|
320
360
|
shown, i.e. the writer cited something it didn't read. Only a session ledger
|