backdraft 0.4.0__tar.gz → 0.4.1__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.4.1/.claude-plugin/marketplace.json +16 -0
- backdraft-0.4.1/.claude-plugin/plugin.json +14 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/PKG-INFO +47 -9
- {backdraft-0.4.0 → backdraft-0.4.1}/README.md +46 -8
- {backdraft-0.4.0 → backdraft-0.4.1}/ROADMAP.md +6 -2
- {backdraft-0.4.0 → backdraft-0.4.1}/pyproject.toml +1 -1
- {backdraft-0.4.0 → backdraft-0.4.1}/site/docs.html +33 -6
- {backdraft-0.4.0 → backdraft-0.4.1}/site/llms.txt +9 -2
- {backdraft-0.4.0 → backdraft-0.4.1}/skills/backdraft/SKILL.md +7 -1
- {backdraft-0.4.0 → backdraft-0.4.1}/skills/backdraft-artifact/SKILL.md +1 -1
- {backdraft-0.4.0 → backdraft-0.4.1}/skills/backdraft-backfill/SKILL.md +7 -1
- backdraft-0.4.1/src/backdraft/__main__.py +6 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/cli.py +33 -15
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_cli.py +89 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/uv.lock +1 -1
- {backdraft-0.4.0 → backdraft-0.4.1}/.github/workflows/ci.yml +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/.github/workflows/release.yml +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/.github/workflows/site.yml +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/.gitignore +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/.python-version +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/DESIGN.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/LICENSE +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/SPEC.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/assets/backdraft-mark.svg +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/demo/generate_sources.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/demo/memo.backdraft.html +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/demo/memo.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/demo/sources/t12-summary.pdf +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/demo/sources/underwriting-model.xlsx +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/demo/walkthrough.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/site/demo.html +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/site/index.html +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/spec/artifact.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/spec/chunking.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/spec/tokens.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/binder.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/evidence.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/base.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/entail.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/overlap.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/recompute.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/value_trace.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/cli_context.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/credentials.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/base.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/csv.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/docx.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/image.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/pdf_text.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/pptx.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/sheet.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/text.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/vlm.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/vlm_settings.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/xls.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/xlsx.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/gate/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/gate/cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/gate/reader.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/gate/searcher.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/artifact.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/chunking.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/claims.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/errors.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/hashing.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/model.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/tokens.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/registry/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/registry/ledger.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/registry/schema.sql +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/registry/store.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/_text.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/footnotes.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/__init__.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/assets.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/components.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/fmt.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/fmt_vectors.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/page.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/text.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/markdown.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/placement.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/sidecar.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/conftest.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/conftest_registry.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/conftest_render.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/fakes.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/conftest.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/fake_registry.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_gate_cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_gate_ledger.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_gate_reader.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_gate_searcher.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_reader_cells.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/bind/backfill.bound.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/bind/frontwalk.bound.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/long_tail.json +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/long_tail.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/many_small.json +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/many_small.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/mixed.json +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/mixed.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/one_giant.json +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/one_giant.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/short_page.json +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/short_page.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/unsplittable.json +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/unsplittable.txt +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/render/backfill.footnotes.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/render/backfill.sidecar.json +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/render/demo.footnotes.md +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/render/demo.sidecar.json +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden_util.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_bind_binder.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_bind_cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_bind_evidence.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_bind_verify.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_chunking.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_claims.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_credentials.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_extract.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_fmt_parity.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_footnotes.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_gate_integration.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_hashing.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_html.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_invariants.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_model.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_placement.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_records_location.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_registry.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_reingest.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_render_cli.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_render_markdown.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_sidecar.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_snapshot_settings.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_tokens.py +0 -0
- {backdraft-0.4.0 → backdraft-0.4.1}/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.4.1",
|
|
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
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backdraft
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
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
|
|
@@ -232,13 +232,7 @@ uv run pytest
|
|
|
232
232
|
|
|
233
233
|
## Skills
|
|
234
234
|
|
|
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
|
-
```
|
|
235
|
+
Three agent skills ship inside the package and in this repo:
|
|
242
236
|
|
|
243
237
|
| Skill | For |
|
|
244
238
|
|---|---|
|
|
@@ -247,7 +241,51 @@ backdraft skill install --project # into this repo's .claude/skills/
|
|
|
247
241
|
| [`backdraft-artifact`](skills/backdraft-artifact/SKILL.md) | reading someone else's artifact cold |
|
|
248
242
|
|
|
249
243
|
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.
|
|
244
|
+
the skills is required to use backdraft by hand. How the skills reach your agent
|
|
245
|
+
depends on the harness:
|
|
246
|
+
|
|
247
|
+
**Claude Code.** The repo is its own plugin marketplace, so the plugin route
|
|
248
|
+
tracks releases:
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
/plugin marketplace add spencerbraun/backdraft
|
|
252
|
+
/plugin install backdraft@backdraft
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Or have the CLI copy the skills into your skills directory:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
backdraft skill install # the writing skill, into ~/.claude/skills/
|
|
259
|
+
backdraft skill install --all # plus backfill and artifact-reading
|
|
260
|
+
backdraft skill install --project # into this repo's .claude/skills/
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Claude Cowork.** Once the plugin is listed in Anthropic's community
|
|
264
|
+
directory, it installs from Cowork's built-in skills directory. Until then,
|
|
265
|
+
zip a skill folder from `skills/` and upload it under Customize > Skills.
|
|
266
|
+
Inside a Cowork session the CLI itself runs per-command as
|
|
267
|
+
`uvx backdraft ...`, since installs do not persist between sessions.
|
|
268
|
+
|
|
269
|
+
**Codex, Cursor, Copilot.** Agents in this family read skills from
|
|
270
|
+
`~/.agents/skills`:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
backdraft skill install --agent codex # into ~/.agents/skills/
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
or commit the skill folders to your repo under `.agents/skills/` so every
|
|
277
|
+
checkout carries them.
|
|
278
|
+
|
|
279
|
+
**Standing context, any harness.** Paste this into your repo's AGENTS.md:
|
|
280
|
+
|
|
281
|
+
```markdown
|
|
282
|
+
## backdraft (cited writing)
|
|
283
|
+
When a document must cite its sources, write it through the backdraft CLI:
|
|
284
|
+
it shows source text with a citation token over every span, and only shown
|
|
285
|
+
spans are citable. In a sandboxed session run every command as
|
|
286
|
+
`uvx backdraft ...` (no install, no PATH edits). Start from
|
|
287
|
+
`uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.
|
|
288
|
+
```
|
|
251
289
|
|
|
252
290
|
## Documentation
|
|
253
291
|
|
|
@@ -207,13 +207,7 @@ uv run pytest
|
|
|
207
207
|
|
|
208
208
|
## Skills
|
|
209
209
|
|
|
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
|
-
```
|
|
210
|
+
Three agent skills ship inside the package and in this repo:
|
|
217
211
|
|
|
218
212
|
| Skill | For |
|
|
219
213
|
|---|---|
|
|
@@ -222,7 +216,51 @@ backdraft skill install --project # into this repo's .claude/skills/
|
|
|
222
216
|
| [`backdraft-artifact`](skills/backdraft-artifact/SKILL.md) | reading someone else's artifact cold |
|
|
223
217
|
|
|
224
218
|
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.
|
|
219
|
+
the skills is required to use backdraft by hand. How the skills reach your agent
|
|
220
|
+
depends on the harness:
|
|
221
|
+
|
|
222
|
+
**Claude Code.** The repo is its own plugin marketplace, so the plugin route
|
|
223
|
+
tracks releases:
|
|
224
|
+
|
|
225
|
+
```text
|
|
226
|
+
/plugin marketplace add spencerbraun/backdraft
|
|
227
|
+
/plugin install backdraft@backdraft
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Or have the CLI copy the skills into your skills directory:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
backdraft skill install # the writing skill, into ~/.claude/skills/
|
|
234
|
+
backdraft skill install --all # plus backfill and artifact-reading
|
|
235
|
+
backdraft skill install --project # into this repo's .claude/skills/
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Claude Cowork.** Once the plugin is listed in Anthropic's community
|
|
239
|
+
directory, it installs from Cowork's built-in skills directory. Until then,
|
|
240
|
+
zip a skill folder from `skills/` and upload it under Customize > Skills.
|
|
241
|
+
Inside a Cowork session the CLI itself runs per-command as
|
|
242
|
+
`uvx backdraft ...`, since installs do not persist between sessions.
|
|
243
|
+
|
|
244
|
+
**Codex, Cursor, Copilot.** Agents in this family read skills from
|
|
245
|
+
`~/.agents/skills`:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
backdraft skill install --agent codex # into ~/.agents/skills/
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
or commit the skill folders to your repo under `.agents/skills/` so every
|
|
252
|
+
checkout carries them.
|
|
253
|
+
|
|
254
|
+
**Standing context, any harness.** Paste this into your repo's AGENTS.md:
|
|
255
|
+
|
|
256
|
+
```markdown
|
|
257
|
+
## backdraft (cited writing)
|
|
258
|
+
When a document must cite its sources, write it through the backdraft CLI:
|
|
259
|
+
it shows source text with a citation token over every span, and only shown
|
|
260
|
+
spans are citable. In a sandboxed session run every command as
|
|
261
|
+
`uvx backdraft ...` (no install, no PATH edits). Start from
|
|
262
|
+
`uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.
|
|
263
|
+
```
|
|
226
264
|
|
|
227
265
|
## Documentation
|
|
228
266
|
|
|
@@ -46,8 +46,12 @@ the objection rather than rediscovering it.
|
|
|
46
46
|
- **HTML pages and email (.eml)** — both arrive in diligence folders, both
|
|
47
47
|
have messy identity questions (what is "the source" of a web page?) that
|
|
48
48
|
deserve a decision row before code.
|
|
49
|
-
- **Distribution** — published on PyPI;
|
|
50
|
-
|
|
49
|
+
- **Distribution** — published on PyPI; the repo now ships
|
|
50
|
+
`.claude-plugin/plugin.json` and `marketplace.json`, so it installs as a
|
|
51
|
+
Claude Code plugin and self-hosts its marketplace, and the AGENTS.md
|
|
52
|
+
snippet is published in the README and site docs. Still queued: the
|
|
53
|
+
community-directory listing (submission form is a maintainer action,
|
|
54
|
+
pending), which is also what surfaces the skills in Cowork's directory.
|
|
51
55
|
|
|
52
56
|
## Someday
|
|
53
57
|
|
|
@@ -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>
|
|
@@ -277,14 +277,41 @@ of a gigabyte corpus is a ~2 MB file. Recipients need the one
|
|
|
277
277
|
</section>
|
|
278
278
|
|
|
279
279
|
<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>
|
|
280
|
+
<h2>Agents and harnesses</h2>
|
|
284
281
|
<p>The skill's core instruction is a substitution: for source documents, use
|
|
285
282
|
<code>backdraft read</code>/<code>search</code>, never raw file reads. The
|
|
286
283
|
token-efficient reference for any agent is <a href="llms.txt">llms.txt</a>
|
|
287
|
-
(~800 tokens).</p>
|
|
284
|
+
(~800 tokens). How the skill reaches your agent depends on the harness.</p>
|
|
285
|
+
<h3>Claude Code</h3>
|
|
286
|
+
<p>The repo is its own plugin marketplace, so the plugin route tracks
|
|
287
|
+
releases:</p>
|
|
288
|
+
<pre>/plugin marketplace add spencerbraun/backdraft
|
|
289
|
+
/plugin install backdraft@backdraft</pre>
|
|
290
|
+
<p>Or have the CLI copy the skills into your skills directory:</p>
|
|
291
|
+
<pre>backdraft skill install <span class="dim"># the writing skill, into ~/.claude/skills/</span>
|
|
292
|
+
backdraft skill install --all <span class="dim"># plus backfill and artifact-reading</span>
|
|
293
|
+
backdraft skill install --project<span class="dim"> # into this repo's .claude/skills/</span></pre>
|
|
294
|
+
<h3>Claude Cowork</h3>
|
|
295
|
+
<p>Once the plugin is listed in Anthropic's community directory, it installs
|
|
296
|
+
from Cowork's built-in skills directory; until then, zip a skill folder from
|
|
297
|
+
the repo's <code>skills/</code> and upload it under Customize > Skills.
|
|
298
|
+
Inside a session, run the CLI per-command as <code>uvx backdraft ...</code>,
|
|
299
|
+
since installs do not persist between sessions.</p>
|
|
300
|
+
<h3>Codex, Cursor, Copilot</h3>
|
|
301
|
+
<p>Agents in this family read skills from <code>~/.agents/skills</code>:</p>
|
|
302
|
+
<pre>backdraft skill install --agent codex <span class="dim"># into ~/.agents/skills/</span></pre>
|
|
303
|
+
<p>Or commit the skill folders to your repo under <code>.agents/skills/</code>
|
|
304
|
+
so every checkout carries them. For Codex cloud environments, add
|
|
305
|
+
<code>pip install backdraft</code> to the setup script, which runs while the
|
|
306
|
+
network is still on.</p>
|
|
307
|
+
<h3>Standing context</h3>
|
|
308
|
+
<p>For any harness that reads AGENTS.md, paste this into your repo's file:</p>
|
|
309
|
+
<pre>## backdraft (cited writing)
|
|
310
|
+
When a document must cite its sources, write it through the backdraft CLI:
|
|
311
|
+
it shows source text with a citation token over every span, and only shown
|
|
312
|
+
spans are citable. In a sandboxed session run every command as
|
|
313
|
+
`uvx backdraft ...` (no install, no PATH edits). Start from
|
|
314
|
+
`uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.</pre>
|
|
288
315
|
</section>
|
|
289
316
|
|
|
290
317
|
<section id="security">
|
|
@@ -10,8 +10,15 @@ https://github.com/spencerbraun/backdraft/blob/main/skills/backdraft/SKILL.md
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# Sandboxed session (Cowork, Codex cloud, CI): do NOT install. Run every
|
|
14
|
+
# command through uvx instead: `uvx backdraft init`, `uvx backdraft read` …
|
|
15
|
+
# No PATH edits, never run `backdraft skill install`, never modify agent
|
|
16
|
+
# config from inside a session.
|
|
17
|
+
# Owned machine:
|
|
18
|
+
uv tool install backdraft # pip install backdraft where uv is absent
|
|
19
|
+
# Human-run setup step (not for sessions): `backdraft skill install` copies
|
|
20
|
+
# the writing skill into ~/.claude/skills/ (Claude Code); add
|
|
21
|
+
# `--agent codex` to target ~/.agents/skills for the Codex family.
|
|
15
22
|
# Vision-model extraction for PDFs and images ships by default (needs poppler
|
|
16
23
|
# for PDFs + BACKDRAFT_VLM_API_KEY). Ambient provider keys (OPENAI_API_KEY
|
|
17
24
|
# etc.) are NEVER read; only BACKDRAFT_*.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backdraft
|
|
3
|
-
description: Write
|
|
3
|
+
description: Write documents whose factual claims each carry a checkable citation into source files. Use when producing a memo, report, or analysis from PDFs, spreadsheets, or docs with citations or provenance.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# backdraft — writing with citations that resolve
|
|
@@ -13,6 +13,12 @@ fact you obtained any other way has no receipt and cannot be cited.
|
|
|
13
13
|
|
|
14
14
|
## Workflow
|
|
15
15
|
|
|
16
|
+
If `backdraft` is not on PATH (sandboxed sessions start fresh), run every
|
|
17
|
+
command through uvx instead: `uvx backdraft init`, `uvx backdraft read`, and so
|
|
18
|
+
on. Where uv is absent, `pip install backdraft` once per session, then use
|
|
19
|
+
`backdraft` or `python -m backdraft`. Never modify PATH, shell rc files, or
|
|
20
|
+
agent config directories; installs from PyPI need no special permissions.
|
|
21
|
+
|
|
16
22
|
```bash
|
|
17
23
|
backdraft init # once per project
|
|
18
24
|
backdraft ingest report.pdf model.xlsx notes.md # every source, up front
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backdraft-artifact
|
|
3
|
-
description: Read
|
|
3
|
+
description: Read and check a backdraft artifact (*.backdraft.json or *.backdraft.html), verifying receipts and summarizing claim statuses. Use when handed such a file or asked to audit its citations.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# backdraft-artifact — reading a bound document cold
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backdraft-backfill
|
|
3
|
-
description:
|
|
3
|
+
description: Attach citations to an existing document after the fact, tracing each factual claim to source files and listing unsupported claims. Use for add-citations or fact-check-against-these-files requests.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# backdraft-backfill — attributing a document that already exists
|
|
@@ -14,6 +14,12 @@ what the claim says — a plausible-looking hit is not evidence.**
|
|
|
14
14
|
|
|
15
15
|
## 1. Ingest the sources
|
|
16
16
|
|
|
17
|
+
If `backdraft` is not on PATH (sandboxed sessions start fresh), run every
|
|
18
|
+
command through uvx instead: `uvx backdraft init`, `uvx backdraft read`, and so
|
|
19
|
+
on. Where uv is absent, `pip install backdraft` once per session, then use
|
|
20
|
+
`backdraft` or `python -m backdraft`. Never modify PATH, shell rc files, or
|
|
21
|
+
agent config directories; installs from PyPI need no special permissions.
|
|
22
|
+
|
|
17
23
|
```bash
|
|
18
24
|
backdraft init
|
|
19
25
|
backdraft ingest sources/*.pdf sources/*.xlsx
|
|
@@ -154,6 +154,11 @@ def ingest(
|
|
|
154
154
|
|
|
155
155
|
SKILLS = ("backdraft", "backdraft-backfill", "backdraft-artifact")
|
|
156
156
|
|
|
157
|
+
# Skills directories per agent family: Claude Code reads `.claude/skills/`;
|
|
158
|
+
# `.agents/skills/` is the Agent Skills standard path read by OpenAI Codex,
|
|
159
|
+
# Cursor, Copilot and others.
|
|
160
|
+
AGENT_DIRS = {"claude": ".claude", "codex": ".agents"}
|
|
161
|
+
|
|
157
162
|
|
|
158
163
|
def _skills_source() -> Path:
|
|
159
164
|
"""Where the bundled skills live: package data in a wheel, repo in a checkout."""
|
|
@@ -171,37 +176,50 @@ def skill(
|
|
|
171
176
|
action: Annotated[str, typer.Argument(help="`install` is the only action.")] = "install",
|
|
172
177
|
project: Annotated[
|
|
173
178
|
bool,
|
|
174
|
-
typer.Option("--project", help="Install into
|
|
179
|
+
typer.Option("--project", help="Install into the project's skills directory instead of the home one."),
|
|
175
180
|
] = False,
|
|
176
181
|
all_: Annotated[
|
|
177
182
|
bool,
|
|
178
183
|
typer.Option("--all", help="Also install the backfill and artifact-reading skills."),
|
|
179
184
|
] = False,
|
|
185
|
+
agent: Annotated[
|
|
186
|
+
str,
|
|
187
|
+
typer.Option("--agent", help="Target agent layout: `claude`, `codex`, or `all`."),
|
|
188
|
+
] = "claude",
|
|
180
189
|
) -> None:
|
|
181
190
|
"""Install the agent skill: `backdraft skill install`, then ask for cited work.
|
|
182
191
|
|
|
183
|
-
Copies the writing skill into your agent's skills directory
|
|
184
|
-
|
|
185
|
-
|
|
192
|
+
Copies the writing skill into your agent's skills directory. `--agent claude`
|
|
193
|
+
(the default) targets Claude Code's `~/.claude/skills/`; `--agent codex`
|
|
194
|
+
targets `~/.agents/skills/`, the Agent Skills standard path read by OpenAI
|
|
195
|
+
Codex, Cursor, Copilot and others; `--agent all` targets both. `--project`
|
|
196
|
+
installs under the current directory (`.claude/skills/`, `.agents/skills/`)
|
|
197
|
+
instead of the home directory. `--all` adds the backfill and
|
|
198
|
+
artifact-reading skills.
|
|
186
199
|
"""
|
|
187
200
|
import shutil
|
|
188
201
|
|
|
189
202
|
with guard():
|
|
190
203
|
if action != "install":
|
|
191
204
|
raise UsageError(f"unknown action {action!r}; try: backdraft skill install")
|
|
205
|
+
if agent not in (*AGENT_DIRS, "all"):
|
|
206
|
+
raise UsageError(f"unknown agent {agent!r}; try: claude, codex, or all")
|
|
192
207
|
source = _skills_source()
|
|
193
|
-
|
|
208
|
+
agents = tuple(AGENT_DIRS) if agent == "all" else (agent,)
|
|
209
|
+
base = Path.cwd() if project else Path.home()
|
|
194
210
|
names = SKILLS if all_ else SKILLS[:1]
|
|
195
|
-
for
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
211
|
+
for agent_name in agents:
|
|
212
|
+
target_root = base / AGENT_DIRS[agent_name] / "skills"
|
|
213
|
+
for name in names:
|
|
214
|
+
src = source / name
|
|
215
|
+
if not src.is_dir():
|
|
216
|
+
raise UsageError(f"bundled skill missing: {name}")
|
|
217
|
+
dst = target_root / name
|
|
218
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
219
|
+
for item in src.iterdir():
|
|
220
|
+
if item.is_file():
|
|
221
|
+
shutil.copy2(item, dst / item.name)
|
|
222
|
+
typer.echo(f"installed {name} -> {dst}")
|
|
205
223
|
typer.echo('next: ask your agent for cited work, e.g. "Write me a memo from ./docs, with citations."')
|
|
206
224
|
|
|
207
225
|
|
|
@@ -217,3 +217,92 @@ def test_skill_install_copies_the_bundled_skill(tmp_path, monkeypatch) -> None:
|
|
|
217
217
|
result = CliRunner().invoke(app, ["skill", "install", "--all"])
|
|
218
218
|
assert result.exit_code == 0, result.output
|
|
219
219
|
assert (tmp_path / ".claude" / "skills" / "backdraft-backfill" / "SKILL.md").is_file()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_skill_install_default_agent_touches_only_claude(tmp_path, monkeypatch) -> None:
|
|
223
|
+
from typer.testing import CliRunner
|
|
224
|
+
|
|
225
|
+
from backdraft.cli import app
|
|
226
|
+
|
|
227
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
|
228
|
+
result = CliRunner().invoke(app, ["skill", "install"])
|
|
229
|
+
assert result.exit_code == 0, result.output
|
|
230
|
+
assert (tmp_path / ".claude" / "skills" / "backdraft" / "SKILL.md").is_file()
|
|
231
|
+
assert not (tmp_path / ".agents").exists()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_skill_install_agent_codex_uses_the_standard_path(tmp_path, monkeypatch) -> None:
|
|
235
|
+
from typer.testing import CliRunner
|
|
236
|
+
|
|
237
|
+
from backdraft.cli import app
|
|
238
|
+
|
|
239
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
|
240
|
+
result = CliRunner().invoke(app, ["skill", "install", "--agent", "codex"])
|
|
241
|
+
assert result.exit_code == 0, result.output
|
|
242
|
+
assert (tmp_path / ".agents" / "skills" / "backdraft" / "SKILL.md").is_file()
|
|
243
|
+
assert not (tmp_path / ".claude").exists()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def test_skill_install_agent_all_lands_in_both_layouts(tmp_path, monkeypatch) -> None:
|
|
247
|
+
from typer.testing import CliRunner
|
|
248
|
+
|
|
249
|
+
from backdraft.cli import app
|
|
250
|
+
|
|
251
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
|
252
|
+
result = CliRunner().invoke(app, ["skill", "install", "--agent", "all", "--all"])
|
|
253
|
+
assert result.exit_code == 0, result.output
|
|
254
|
+
for root in (".claude", ".agents"):
|
|
255
|
+
for name in ("backdraft", "backdraft-backfill", "backdraft-artifact"):
|
|
256
|
+
assert (tmp_path / root / "skills" / name / "SKILL.md").is_file()
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_skill_install_agent_codex_project_lands_in_cwd(tmp_path, monkeypatch) -> None:
|
|
260
|
+
from typer.testing import CliRunner
|
|
261
|
+
|
|
262
|
+
from backdraft.cli import app
|
|
263
|
+
|
|
264
|
+
monkeypatch.chdir(tmp_path)
|
|
265
|
+
result = CliRunner().invoke(app, ["skill", "install", "--agent", "codex", "--project"])
|
|
266
|
+
assert result.exit_code == 0, result.output
|
|
267
|
+
assert (tmp_path / ".agents" / "skills" / "backdraft" / "SKILL.md").is_file()
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def test_skill_install_rejects_an_unknown_agent(tmp_path, monkeypatch) -> None:
|
|
271
|
+
from typer.testing import CliRunner
|
|
272
|
+
|
|
273
|
+
from backdraft.cli import app
|
|
274
|
+
|
|
275
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
|
276
|
+
result = CliRunner().invoke(app, ["skill", "install", "--agent", "cursor"])
|
|
277
|
+
assert result.exit_code == 1
|
|
278
|
+
assert "unknown agent" in result.output
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def test_python_dash_m_backdraft_runs_the_cli() -> None:
|
|
282
|
+
import subprocess
|
|
283
|
+
import sys
|
|
284
|
+
|
|
285
|
+
result = subprocess.run(
|
|
286
|
+
[sys.executable, "-m", "backdraft", "--help"],
|
|
287
|
+
capture_output=True,
|
|
288
|
+
text=True,
|
|
289
|
+
)
|
|
290
|
+
assert result.returncode == 0, result.stderr
|
|
291
|
+
assert "ingest" in result.stdout
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def test_skill_descriptions_fit_the_upload_cap() -> None:
|
|
295
|
+
"""claude.ai's skill upload rejects descriptions over 200 characters."""
|
|
296
|
+
import re
|
|
297
|
+
|
|
298
|
+
from backdraft.cli import SKILLS, _skills_source
|
|
299
|
+
|
|
300
|
+
for name in SKILLS:
|
|
301
|
+
text = (_skills_source() / name / "SKILL.md").read_text(encoding="utf-8")
|
|
302
|
+
match = re.search(r"^description: (.+)$", text, flags=re.MULTILINE)
|
|
303
|
+
assert match, name
|
|
304
|
+
assert len(match.group(1)) <= 200, (name, len(match.group(1)))
|
|
305
|
+
# An unquoted YAML scalar dies on ": " — keep descriptions parseable.
|
|
306
|
+
assert ": " not in match.group(1), name
|
|
307
|
+
named = re.search(r"^name: (.+)$", text, flags=re.MULTILINE)
|
|
308
|
+
assert named and named.group(1) == name
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|