book-to-skill Agent Skills & AI Rules

GitHub

Turn any technical book PDF into a Claude Code skill β€” ready to study, reference, and use while you work.

20,893 stars Python Python 1 file Β· ~5,297 tokens
RAW Rules
2 Discovered Rules β€’ ~14,487 Estimated Tokens
Quick:
Loading skill content...

docs - System Architecture & Module Boundaries

docs/ARCHITECTURE.md project-spec ~2,010 tokens
Raw
Description: How book-to-skill is built: a deterministic Python extractor plus a spec-driven agent generator. Pipeline, component map, and the design tradeoffs behind both.
---
description: "How book-to-skill is built: a deterministic Python extractor plus a spec-driven agent generator. Pipeline, component map, and the design tradeoffs behind both."
seo_title: "Architecture - How book-to-skill Extracts and Generates"
---

# Architecture

book-to-skill has two halves: a **deterministic extractor** (Python) and a
**spec-driven generator** (the agent following `SKILL.md`). The extractor turns any
document into clean text + metadata; the agent turns that into a structured skill.

```
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ EXTRACTOR (Python, deterministic) ──┐
 documents  β”‚  scripts/extract.py (shim)  β†’  book_to_skill/                   β”‚
 (pdf/epub/ β”‚    β”œβ”€ cli.py Β· utils.py   CLI parse Β· multi-source Β· runner     β”‚
  docx/...) β”‚    β”œβ”€ config.py           supported extensions Β· paths Β· deps   β”‚
     β”‚      β”‚    β”œβ”€ dependencies.py     optional-dep probing Β· --check report β”‚
     β–Ό      β”‚    β”œβ”€ sanitize.py         strip invisible/zero-width Unicode    β”‚
 ───────────│    └─ parsers/            pdf Β· epub Β· docx Β· html Β· rtf Β·      β”‚
            β”‚                             calibre Β· text (best tool, fallback)β”‚
            β”‚  output β†’ <tempdir>/book_skill_work/                            β”‚
            β”‚    full_text.txt   (all sources merged, source-marked)          β”‚
            β”‚    metadata.json   (pages, words, tokens, chapters, ToC)        β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   β”‚
                                   β–Ό
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ GENERATOR (agent, follows SKILL.md) ┐
            β”‚  Step 1.5  ask content type β†’ BOOK_TYPE (technical | text)      β”‚
            β”‚  Step 2/2.5 extract Β· cost estimate Β· confirm                   β”‚
            β”‚  Step 2.6  REPL-style probing for large books (grep/sed, no     β”‚
            β”‚            full re-reads)                                        β”‚
            β”‚  Step 3    analyze structure (title, author, chapters, ToC)     β”‚
            β”‚  Step 4    purpose β†’ DEPTH (reference | study)                   β”‚
            β”‚  Step 7    per-chapter summaries (budget = BOOK_TYPE Γ— DEPTH)    β”‚
            β”‚  Step 8    glossary Β· patterns Β· cheatsheet (decision layer)    β”‚
            β”‚  Step 9/9.5 SKILL.md core + indexes                             β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   β”‚
                                   β–Ό
                 <SKILLS_HOME>/<slug>/  ← chosen per host + scope:
                  ~/.copilot/skills/   GitHub Copilot CLI (personal)
                  ~/.agents/skills/    Copilot CLI or Amp (cross-agent, personal)
                  ~/.claude/skills/    Claude Code (personal)
                  ~/.openclaw/skills/  OpenClaw (personal, default state only)
                  $HERMES_HOME/skills/<category>/  Hermes Agent (personal)
                  .github/skills/ | .claude/skills/ | .agents/skills/  project-local (any host)
                  .hermes/skills/<category>/                         Hermes project-local
                  skills/                                                   OpenClaw workspace
                  ~/.config/opencode/skills/                         OpenCode (own root; on request)
                  .opencode/skills/                                  OpenCode project-local
                  scope: personal vs project-local (Step 5, BOOK_TO_SKILL_SCOPE)
                  SKILL.md         core frameworks + chapter & topic index (~4K)
                  chapters/*.md    on-demand, loaded only when asked
                  glossary.md      terms
                  patterns.md      techniques
                  cheatsheet.md    decision rules / trees / trade-offs / tells
```

OpenClaw's shared `~/.agents/skills` compatibility root is discoverable only
when `OPENCLAW_STATE_DIR` is unset or uses the default `~/.openclaw`. A
non-default state uses its active `<state-dir>/skills` root instead; the
converter does not infer arbitrary `skills.load.extraDirs`.

## Design principles

1. **Extract structure, not summaries** β€” named frameworks, decision rules,
   anti-patterns; never raw passages.
2. **Compile-time over runtime** β€” pay navigation/structuring once; at query time
   load only the relevant chapter. See [Performance & Cost](performance.md).
3. **On-demand chapters** β€” `SKILL.md` stays small; chapter files cost tokens only
   when read.
4. **Front-loaded `SKILL.md`** β€” most important content first (compaction truncates
   from the end).
5. **Graceful degradation** β€” every format has a stdlib fallback; one bad source is
   skipped, not fatal.

## Key components

| Path | Responsibility |
|------|----------------|
| `scripts/extract.py` | thin entrypoint shim β†’ `book_to_skill.cli` (kept so old invocations keep working) |
| `book_to_skill/cli.py`, `utils.py` | CLI parsing, multi-source resolution, chapter/ToC detection, runner |
| `book_to_skill/parsers/` | one module per format (`pdf`, `epub`, `docx`, `html`, `rtf`, `calibre`, `text`) |
| `book_to_skill/config.py` | supported extensions, output paths, dependency map |
| `book_to_skill/dependencies.py` | optional-dependency probing + `--check` |
| `book_to_skill/sanitize.py` | strips zero-width / Unicode-tag-block characters from extracted text, and folds Kangxi radicals to their ideographs (see Security) |
| `tools/discovery_tax.py` | measures token cost vs context-dump / discovery loop |
| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\|copilot\|amp\|hermes\|openclaw`) |
| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\|copilot\|amp\|hermes\|opencode`) |
| `tools/scan_generated_skill.py` | advisory prompt-injection scan of a generated skill (see Security) |
| `SKILL.md` | the generator spec (Steps 0–10 + fold-in workflow) |

## Security

Untrusted documents flow into an agent's context and then into a generated skill
that later loads into other agents — a document→context supply chain. The hardening
is layered:

- **Extraction sanitization** (`book_to_skill/sanitize.py`) β€” strips zero-width
  (`U+200B/200C/200D/2060/FEFF`) and the Unicode tag block (`U+E0000–E007F`) from
  every parser's output before metrics or `full_text.txt`, so invisible
  document-borne instructions never reach the agent. Reports the removal count;
  rejects a source with no visible content left.
- **CJK radical folding** (`book_to_skill/sanitize.py`) β€” PDFs whose embedded
  font subset carries no correct Unicode mapping emit a Kangxi radical
  (`U+2F00–U+2FDF`) where a character belongs, so a Chinese book extracts
  "εˆ€ζ–­βΌ’" for "εˆ€ζ–­εŠ›". Each radical in that block has a Unicode compatibility
  decomposition, so the fold is NFKC applied to those code points only: the rest
  of the text is passed through byte for byte. Nothing is removed, so it does
  not count toward the security removal total. CJK Radicals Supplement
  (`U+2E80–U+2EF3`) is deliberately not folded β€” Unicode defines no
  decomposition for it, and some entries are ordinary kanji.
- **DOCX XXE / Billion-Laughs guard** (`parsers/docx.py`) β€” rejects any XML part
  declaring a DTD or entities before parsing.
- **Subprocess argument-injection** β€” file paths are absolutised before reaching
  `pdftotext` / `pdfinfo` / `ebook-convert`, so a `-`-leading filename can't be read
  as a flag.
- **Generated-skill scan** (`tools/scan_generated_skill.py`) β€” an advisory step in
  the generator (Step 9.5) that flags instruction-override phrases, model-control
  tags, residual invisible Unicode, authority-widening frontmatter, and
  exfiltration-shaped content across the generated `SKILL.md`, `chapters/*.md`,
  `glossary.md`, `patterns.md`, and `cheatsheet.md`. Findings name only the rule and
  file location β€” never the matched text.
- **CI** β€” CodeQL, Bandit (gate on HIGH), Zizmor, and dependency CVE review on PRs.

## Extending

- **New format** β†’ add `book_to_skill/parsers/<fmt>.py`, register its extension in
  `config.py`, wire dependency probing in `dependencies.py`, branch in
  `utils.extract_single_file`.
- **New generation behavior** β†’ edit the relevant Step in `SKILL.md`; keep it lean
  and back the change with evidence (see CONTRIBUTING.md).