{"owner":"virgiliojr94","repo":"book-to-skill","hasSkills":true,"totalSkillsCount":2,"totalTokensCount":14487,"categories":["anthropic-skill","project-spec"],"hasMcp":false,"mcpConfig":null,"found":["SKILL.md","docs/ARCHITECTURE.md"],"skills":{"SKILL.md":"---\nname: book-to-skill\ndescription: \"Converts books and documents (PDF, EPUB, DOCX, HTML, Markdown, plain text, RTF, MOBI/AZW with Calibre) into structured agent skills, extracting frameworks, mental models, principles, techniques, and anti-patterns. Use when the user wants to study a document through GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, or OpenClaw, apply an author's frameworks while working, or build a reusable knowledge base from a file.\"\n---\n\n<!--\nCross-agent notes (informational; ignored by host agents):\n  - Compatible skill roots: GitHub Copilot CLI (~/.copilot/skills, ~/.agents/skills,\n    .github/skills, .claude/skills, .agents/skills), Amp (.agents/skills,\n    ~/.config/agents/skills, ~/.config/amp/skills), Claude Code (~/.claude/skills),\n    Hermes Agent ($HERMES_HOME/skills, .hermes/skills, .agents/skills),\n    OpenClaw (${OPENCLAW_STATE_DIR:-~/.openclaw}/skills, .agents/skills, skills/;\n    ~/.agents/skills only with the default state).\n    OpenCode (~/.config/opencode/skills, ~/.claude/skills, .opencode/skills,\n    ~/.agents/skills, .agents/skills).\n  - `allowed-tools` is intentionally omitted to stay agent-neutral: Copilot CLI uses\n    `shell`/MCP-server names, Claude uses `Bash`/`Read`/`Write`/`Glob`/`Grep`, Amp\n    adds `shell_command`. The skill needs shell (to run extract.py) and file\n    read/write — each host will prompt for those on first use.\n  - Argument hint: <path-to-document-folder-or-glob>... [skill-name-slug]\n-->\n\n# Book-to-Skill Converter\n\nTransform written knowledge into actionable agent skills by extracting structure — not producing summaries.\n\n## Philosophy\n\nBooks contain crystallized expertise: frameworks, principles, and techniques that took years to develop. This skill extracts that knowledge into a format GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, OpenClaw, or another compatible agent can leverage repeatedly.\n**Extract structure, not summaries.** A skill isn't a book report. It's a toolkit of:\n- Named frameworks (mental models with clear application)\n- Actionable principles (rules that guide decisions)\n- Techniques (step-by-step methods)\n- Anti-patterns (what to avoid and why)\n- Voice calibration (how the author thinks and communicates)\n\n**Preserve the author's precision.** Frameworks often have specific names for reasons. \"The 5 Whys\" isn't interchangeable with \"ask why multiple times.\" Capture the exact formulation.\n\n**Layer depth appropriately.** Simple books → simple skills. Complex books with 10+ frameworks → skills with reference files and on-demand chapters.\n\n---\n\n## Modes of Operation\n\nFour paths available. Route based on what the user asks:\n\n### 1. Full Conversion (Default)\n**Trigger:** User provides one or more document/directory/glob paths without special instructions\n**Action:** Run all steps below (Steps 0–9)\n**Output:** Complete skill with SKILL.md, chapters/, glossary, patterns, cheatsheet\n\n### 2. Analyze Only\n**Trigger:** User says \"analyze\", \"just extract\", or \"I want to review before generating\"\n**Action:** Run Steps 0–3, then produce a structured extraction report (frameworks, principles, techniques found). Stop — do NOT generate skill files.\n**Output:** Analysis report for user review\n\n### 3. Generate from Prior Analysis\n**Trigger:** User has existing analysis notes or previously ran analyze-only\n**Action:** Skip Steps 0–3, use the provided analysis as input, run Steps 4–9\n**Output:** Skill files from the provided analysis\n\n### 4. Update / Fold-in (Existing Skill)\n**Trigger:** User provides one or more new source paths and indicates they want to update an existing skill (either by pointing to the existing skill folder, providing a skill slug that already exists in `SKILLS_HOME`, or explicitly requesting an update).\n**Action:** Run Step 0 (out-of-scope check), Step 1 (validate inputs), Step 1.5 (identify book type), and Step 2 (extract new files). Then skip to Step 5 (identify/detect existing skill path) and run the **Update / Fold-in Workflow** to merge the new content into the existing skill files.\n**Output:** Updated existing skill with new/revised chapter summaries and merged indexes/glossaries.\n\n---\n\n## Skill Locations\n\nThis converter can run from multiple skill systems. When looking for this converter's helper script or writing the generated book skill, prefer these locations in order:\n\n1. GitHub Copilot CLI personal skills: `~/.copilot/skills/`\n2. Cross-agent personal skills (Copilot, Amp, Codex; OpenClaw with its default state): `~/.agents/skills/`\n3. Claude Code personal skills: `~/.claude/skills/`\n4. Project-local Copilot skills: `.github/skills/`\n5. Project-local Claude skills: `.claude/skills/`\n6. Project-local Amp / Copilot / OpenClaw skills: `.agents/skills/`\n7. Amp global skills: `~/.config/agents/skills/`\n8. Amp legacy global skills: `~/.config/amp/skills/`\n9. Hermes Agent personal skills: `$HERMES_HOME/skills/` (defaults to `~/.hermes/skills/`)\n10. Hermes Agent project skills: `.hermes/skills/` or `.agents/skills/`\n11. OpenClaw personal skills: `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/` (active state; `~/.agents/skills/` is shared only with the default state)\n12. OpenClaw project skills: `.agents/skills/` or `skills/`\n11. OpenCode personal skills: `~/.config/opencode/skills/` or `~/.agents/skills/`\n12. OpenCode project skills: `.opencode/skills/` or `.agents/skills/`\n\nFor **generated** book skills, prefer the user-level cross-agent root `~/.agents/skills/` — one physical copy serves the cross-agent hosts and OpenClaw when it uses its default state. Copilot CLI and Amp discover it natively; Claude Code needs a symlink from `~/.claude/skills/<skill_name>` (created in Step 10, see Step 5 for the rules). Pick a host-private or project-local root only when the user explicitly asks for one. `BOOK_TO_SKILL_SCOPE=project` or `personal` can make that choice explicit for automation; do not ask a mandatory scope question merely because both scopes are available.\n\n---\n\n## Step 0 — Out-of-scope check\n\nIf no arguments are provided, stop and respond:\n> \"book-to-skill requires a supported document path, folder, or glob pattern. Usage: `book-to-skill <path-to-document-folder-or-glob>... [skill-name-slug]`\"\n\nThroughout the workflow:\n- **Resolve the destination root first.** Before any existing-skill lookup, determine where this run would write: the personal or project-local root for the user's host, per the Step 5 table and selection rules — a project-local request uses its project-local root, never the personal root. Below, \"the resolved root\" means that destination.\n- Identify the input paths and the optional skill slug.\n- If the last argument is not a file, folder, or glob that exists or matches any files, and it looks like a skill slug (e.g. lowercase hyphens, alphanumeric), treat it as `SKILL_NAME`.\n- Treat all other arguments as the list of `INPUT_PATHS`.\n- If any input path is an existing skill directory (contains `SKILL.md` and a `chapters/` sub-folder), or if `SKILL_NAME` matches an existing skill slug in the resolved root, flag this run as an **Update/Fold-in** operation (Mode 4).\n- **Re-run guard.** Before starting extraction for a Full Conversion, derive the prospective skill slug under Step 5's naming options — the author-concept form, the by-title form, and any `SKILL_NAME` given — and check the resolved root for an existing match. On a match, STOP and ask the user: \"`<skill-name>` already exists. Choose: (1) Update/Fold-in (Mode 4), (2) verify the existing skill is complete and stop, or (3) force full regeneration.\" Do not re-extract until the user chooses. On context recovery after an interrupted run (network cut-off, replayed or continued conversation), reuse the extraction work directory that run reported (`Workdir ->` in its output, or the path you set in `BOOK_SKILL_WORKDIR`) **only if it is still intact and still matches the current inputs and options** — the directory exists, its `metadata.json` is readable, the `sources` it lists match the files being converted now one-to-one on filename **and content fingerprint** (the extractor records a per-source `sha256` in `metadata.json`; recompute the fingerprint from each file as it exists now and require an exact match — `reuse_is_safe()` in `book_to_skill/utils.py` is the executable form of this check), and its recorded `extraction_mode` is the mode this run is using (Step 2's \"confirm the extraction is the document you asked for\" rule). If it is missing (temp cleanup), its `metadata.json` is gone, the sources no longer match (a different filename, or a changed content fingerprint), the recorded metadata has no `sha256` for a source (recorded before fingerprints existed, so freshness cannot be established), or the mode differs, say so and start a fresh extraction instead of resuming. When in doubt, ask the user before discarding or resuming.\n\n---\n\n## Step 1 — Validate input\n\nVerify that there is at least one supported file, directory, or glob pattern among the `INPUT_PATHS`.\nFor directories and globs, expand them to find matching supported files (`.pdf`, `.epub`, `.docx`, `.txt`, `.md`, `.markdown`, `.rst`, `.adoc`, `.html`, `.htm`, `.rtf`, `.mobi`, `.azw`, `.azw3`).\n\nIf no supported files are found, stop with a clear error message.\n\n---\n\n## Step 1.5 — Identify content type\n\nBefore extracting, ask the user:\n\n> \"What kind of content do these sources have? This helps me choose the best extraction method.\n>\n> 1. **Technical** — has code blocks, tables, formulas, diagrams (e.g. programming books, academic papers, architecture guides)\n> 2. **Text-heavy** — mostly prose, few or no tables/code (e.g. management, productivity, narrative non-fiction)\n> 3. **Not sure** — I'll use the fast method and warn you if quality seems limited\"\n\nStore the answer as `BOOK_TYPE`:\n- Option 1 → `BOOK_TYPE=technical`\n- Option 2 → `BOOK_TYPE=text`\n- Option 3 → `BOOK_TYPE=text`\n\n**If `BOOK_TYPE=technical`**, inform the user before proceeding:\n> \"📐 Technical mode selected — using Docling for structure-aware extraction (tables, code blocks, formulas preserved as markdown). This takes ~1.5s per page, so expect a few minutes for longer sources. Starting now…\"\n\n**If `BOOK_TYPE=text`**, inform:\n> \"📄 Text mode selected — using the fastest suitable extractor for each file type. Plain text/Markdown/HTML are usually ready in seconds; PDFs use pdftotext when available.\"\n\n---\n\n## Step 2 — Extract text from the source documents\n\nRun the extraction script, passing the input paths:\n\n```bash\nSCRIPT_PATH=\"\"\nHERMES_HOME_RESOLVED=\"${HERMES_HOME:-$HOME/.hermes}\"\nOPENCLAW_STATE_DIR_RESOLVED=\"${OPENCLAW_STATE_DIR:-$HOME/.openclaw}\"\nPROJECT_ROOT=\"$(git rev-parse --show-toplevel 2>/dev/null || true)\"\nHERMES_PROJECT_TRUSTED=false\nif [ -n \"$PROJECT_ROOT\" ] && [ \"${HERMES_AGENT:-}\" = true ] && \\\n  command -v hermes >/dev/null 2>&1 && \\\n  command -v python3 >/dev/null 2>&1 && \\\n  hermes config get skills.trusted_project_dirs --json 2>/dev/null | PROJECT_ROOT=\"$PROJECT_ROOT\" python3 -c 'import json, os, pathlib, sys; root=pathlib.Path(os.environ[\"PROJECT_ROOT\"]).resolve(); sys.exit(not any(pathlib.Path(p).expanduser().resolve() == root for p in json.load(sys.stdin)))' 2>/dev/null\nthen\n  HERMES_PROJECT_TRUSTED=true\nfi\n\nCANDIDATES=(\n  \"$HOME/.copilot/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.agents/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.claude/skills/book-to-skill/scripts/extract.py\"\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills/book-to-skill/scripts/extract.py\"\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n  \"$HOME/.config/opencode/skills/book-to-skill/scripts/extract.py\"\n  \"$HERMES_HOME_RESOLVED/skills/book-to-skill/scripts/extract.py\"\n  \"$HERMES_HOME_RESOLVED\"/skills/*/book-to-skill/scripts/extract.py\n)\nif [ \"${HERMES_AGENT:-}\" != true ]; then\n  # Project-local roots are resolved against the git worktree, not the current\n  # directory: an agent can be invoked from anywhere inside the project (e.g.\n  # `src/nested`) and these roots still have to be found — that is how OpenCode\n  # and the other hosts discover project skills. The CWD-relative forms are kept\n  # so the probe also works outside a git repository.\n  if [ -n \"$PROJECT_ROOT\" ]; then\n    CANDIDATES+=(\n      \"$PROJECT_ROOT/.github/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.claude/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.opencode/skills/book-to-skill/scripts/extract.py\"\n    )\n  fi\n  CANDIDATES+=(\n    \".github/skills/book-to-skill/scripts/extract.py\"\n    \".claude/skills/book-to-skill/scripts/extract.py\"\n    \".agents/skills/book-to-skill/scripts/extract.py\"\n    \"skills/book-to-skill/scripts/extract.py\"\n    \"skills\"/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n    \".opencode/skills/book-to-skill/scripts/extract.py\"\n  )\n  if [ -n \"$PROJECT_ROOT\" ]; then\n    CANDIDATES+=(\n      \"$PROJECT_ROOT/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/skills\"/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n    )\n  fi\nfi\nCANDIDATES+=(\n  \"$HOME/.config/agents/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.config/amp/skills/book-to-skill/scripts/extract.py\"\n)\nif [ \"$HERMES_PROJECT_TRUSTED\" = true ]; then\n  CANDIDATES=(\n    \"$PROJECT_ROOT/.hermes/skills/book-to-skill/scripts/extract.py\"\n    \"$PROJECT_ROOT/.hermes/skills\"/*/book-to-skill/scripts/extract.py\n    \"$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py\"\n    \"$PROJECT_ROOT/.agents/skills\"/*/book-to-skill/scripts/extract.py\n    \"${CANDIDATES[@]}\"\n  )\nfi\nfor candidate in \"${CANDIDATES[@]}\"\ndo\n  if [ -f \"$candidate\" ]; then\n    SCRIPT_PATH=\"$candidate\"\n    break\n  fi\ndone\n\nif [ -z \"$SCRIPT_PATH\" ]; then\n  echo \"Could not find scripts/extract.py for book-to-skill\" >&2\n  exit 1\nfi\n\nPYTHON_BIN=\"${PYTHON_BIN:-python3}\"\nif ! command -v \"$PYTHON_BIN\" >/dev/null 2>&1; then\n  PYTHON_BIN=\"python\"\nfi\n\n\"$PYTHON_BIN\" \"$SCRIPT_PATH\" $INPUT_PATHS --mode <BOOK_TYPE> --install-missing ask\n```\n\nBefore extraction, the script checks optional Python packages needed for the detected format. If a better extractor is missing, it prompts the user with the available fallback. Non-interactive sessions default to fallback unless install mode is explicitly `yes`.\n\n**Tip — preflight the environment:** run `\"$PYTHON_BIN\" \"$SCRIPT_PATH\" --check` to print a per-format report of which extractors are installed and the exact command to install whatever is missing, without processing any file. Useful when a user reports a setup or quality problem.\n\nThis creates a **per-run** work directory — `<tempdir>/book_skill_work-<pid>/` by default, or exactly the path you set in `BOOK_SKILL_WORKDIR` — containing:\n- `full_text.txt` — combined extracted text of all sources with clear visually demarcated boundaries.\n- `metadata.json` — overall combined size, words, pages, token counts, dropped EPUB image counts, the resolved `workdir`, and a detailed list of individual processed `sources`.\n\nThe run prints all three paths on completion (`Workdir ->`, `Text ->`, `Meta ->`). **Take the paths from that output (or from `metadata.json`'s own `workdir` field) rather than assuming a fixed location** — the directory name differs per run so that concurrent extractions on one machine cannot overwrite each other's results.\n\nRead that run's `metadata.json` to inspect the results.\n\n**Always confirm the extraction is the document you asked for** before generating anything: check `filename` / `source_file` in `metadata.json`, or the `SOURCE:` header on the first line of `full_text.txt`. If you are waiting on a background run, wait on *its* specific workdir — polling a shared path can surface a different run's output.\n\n---\n\n## Step 2.5 — Pre-flight cost estimate\n\nRead this run's `metadata.json` (the `Meta ->` path from the extraction output) and present the user with an estimate **before doing any generation**:\n\n```\n📖 Sources detected: <total_sources> source(s)\n<list each source filename and format from the sources metadata list>\n<if images_dropped > 5: warn that N source images were not read>\n📄 Combined Pages/Sections: ~<N> | Words: ~<N> | Total tokens: ~<N>K\n\n💰 Estimated token cost (Full Conversion / Update):\n   Input  (reading + prompts): ~<N>K tokens\n   Output (skill files generated/updated):  ~<N>K tokens\n   Total:                           ~<N>K tokens\n\n   Cost: multiply the token counts above by your model's current\n   input/output per-1M-token rates (prices and model names change often —\n   do not hardcode them; quote today's rate and label it as an estimate).\n\n   ⏱  Estimated time: ~<N> minutes\n\n📁 Files to be generated/updated:\n   SKILL.md + chapter files + glossary + patterns + cheatsheet\n\n➡  Proceed with Full Conversion / Update? (or type \"analyze only\" to preview first)\n```\n\n**How to estimate:**\n- Input tokens ≈ `estimated_tokens` from metadata × 1.3 (prompts overhead per chapter pass)\n- Output tokens ≈ chapters × per-chapter budget + 4,000 (SKILL.md) + 4,500 (glossary + patterns + cheatsheet)\n  - Per-chapter budget midpoint by `BOOK_TYPE` (DEPTH is decided later in Step 4 and can raise it): `text` ≈ 1,000, `technical` ≈ 1,800. If the user has already indicated reference-only vs deep study, use the matching row of the Step 7 matrix.\n- Cost: report the token counts and multiply by the user's current per-1M-token input/output rates. Do NOT hardcode dollar figures — model names and prices change; if you show one, label it an estimate and date it.\n\nWait for the user to confirm before proceeding. If they say \"analyze only\", switch to Mode 2.\n\n---\n\n## Step 2.6 — REPL-style access for large books (> 50k tokens)\n\nInspired by the Recursive Language Model (RLM) paradigm: treat `full_text.txt` as a queryable corpus, not a single read. Loading the whole file into context burns budget you will need later for generation.\n\nFor books over ~50k tokens, prefer programmatic probes over `Read(full_text.txt)` without bounds:\n\n```bash\n# Size check before any Read\nwc -w \"$FULL_TEXT_PATH\"\n\n# Find chapter offsets without loading the whole file\ngrep -n -E \"^\\s*(Chapter|CHAPTER)\\s+[0-9]+\" \"$FULL_TEXT_PATH\" | head -40\n\n# Pull only the chapter you need (lines start..end inclusive)\nsed -n '<start>,<end>p' \"$FULL_TEXT_PATH\"\n\n# Verify a framework is actually mentioned before claiming it in SKILL.md\ngrep -c -i \"westrum\\|dora\" \"$FULL_TEXT_PATH\"\n\n# Targeted Read with offset/limit avoids dumping the full file\n# Read(file_path=full_text.txt, offset=<line>, limit=<lines>)\n```\n\nUse this approach for Step 3 (structure analysis), Step 7 (per-chapter summaries), and Step 8 (glossary / patterns extraction). On books under 50k tokens, a single `Read` is fine.\n\nWhy this matters: a 200-page book is ~75k tokens. Re-reading it once per chapter (28 passes) costs ~2M input tokens; using grep + sed to pull only relevant slices keeps generation cost proportional to the output, not the source.\n\n---\n\n## Step 3 — Analyze book structure\n\nRead the first 8,000 characters of the extracted `full_text.txt` to identify:\n- Book **title** and **author(s)**\n- **Chapter structure** (look for \"Chapter N\", \"PART I\", numbered headings, table of contents)\n- **Core themes** and subject domain\n- Approximate number of chapters\n\nThen read the Table of Contents section if present to map all chapters.\n\n**If mode is \"Analyze Only\":** produce the extraction report now and stop. Structure:\n```\n## Extraction Report — <Title>\n\n### Author's Core Frameworks\n- **<Framework Name>**: <what it is and when to apply>\n\n### Key Principles\n- <Principle>: <actionable rule>\n\n### Techniques & Methods\n- <Technique>: <step-by-step or how-to>\n\n### Anti-patterns\n- <What to avoid>: <why>\n\n### Suggested Skill Name\n`{author-lastname}-{core-concept}` — e.g. `cialdini-influence`\n\n### Chapters Detected\n| # | Title | Main Frameworks |\n```\n\n---\n\n## Step 4 — Ask purpose (Full Conversion only)\n\nBefore generating, ask the user:\n\n> \"What should this skill help you do? (Pick one or more)\n> 1. Apply the author's frameworks while working\n> 2. Think with the author's mental models\n> 3. Reference specific chapters and concepts\n> 4. All of the above\"\n\nUse the answer to weight what gets highlighted in the SKILL.md Core section.\n\n**Derive `DEPTH` from the answer (no extra prompt):**\n- Answer is **only** option 3 (reference) → `DEPTH=reference` — lean, fast-lookup chapters.\n- Answer includes option 1, 2, or 4 → `DEPTH=study` — deeper chapters with more worked detail, examples, and reasoning.\n\n`DEPTH` and `BOOK_TYPE` together set the per-chapter token budget in Step 7. Do **not** ask a separate \"study vs reference\" question — it is inferred here. (In Modes 2/3, where Step 4 is skipped, default `DEPTH=study`.)\n\n---\n\n## Step 5 — Determine skill name\n\nIf `SKILL_NAME` was provided, use it as the skill slug.\nOtherwise, propose two options and let the user choose:\n- **By author-concept**: `{author-lastname}-{core-concept}` (e.g. `cialdini-influence`, `meadows-systems`)\n- **By title**: lowercase hyphens from book title (e.g. `designing-data-intensive-apps`)\n\nDefault to author-concept format if the book has a strong methodological identity.\n\nChoose the destination skill root (`SKILLS_HOME`). First resolve **scope** from an explicit user request or `BOOK_TO_SKILL_SCOPE`, then probe **host**. A request for project-local/project output selects the project-local row; a request for personal/global output selects the personal row. If neither scope is requested, preserve the established personal default (`~/.agents/skills` for non-Hermes hosts). Do not ask a mandatory scope question solely because project-local roots exist. The selected root may still require host approval before writing.\n\n| Host agent | Personal skill root | Project-local root |\n|---|---|---|\n| **GitHub Copilot CLI** | `~/.agents/skills` (discovered natively) | `.github/skills` → `.claude/skills` → `.agents/skills` |\n| **Amp** | `~/.agents/skills` (discovered natively) | `.agents/skills` |\n| **OpenAI Codex** | `~/.agents/skills` (discovered natively; follows symlinks) | `.agents/skills` |\n| **Hermes Agent** | `$HERMES_HOME/skills/<category>` (defaults to `~/.hermes/skills/<category>`) | `.hermes/skills/<category>` → `.agents/skills` |\n| **Claude Code** | `~/.agents/skills` + symlink from `~/.claude/skills/<skill_name>` | `.claude/skills` |\n| **OpenClaw** | `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills` (active state; `~/.agents/skills` only with default state) | `.agents/skills` → `skills/` |\n\nHermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's `HERMES_HOME` and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run `hermes skills trust <project-root>` after generation and verify discovery with `hermes skills list`; project skills remain unavailable until the project is trusted.\n\nFor OpenClaw, use the active state directory's `skills/` root: `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/`. The shared `~/.agents/skills` compatibility root is discoverable only when `OPENCLAW_STATE_DIR` is unset or the default `~/.openclaw`; with a non-default state, do not claim that OpenClaw will see a shared-root install. Verify discovery with `openclaw skills list` after generation.\n| **OpenCode** | `~/.agents/skills` (discovered natively; `~/.config/opencode/skills` and `~/.claude/skills` are read too) | `.opencode/skills` → `.agents/skills` → `.claude/skills` |\n\nHermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's `HERMES_HOME` and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run `hermes skills trust <project-root>` after generation and verify discovery with `hermes skills list`; project skills remain unavailable until the project is trusted.\n\nOpenCode scans the cross-agent `~/.agents/skills` root natively, so the generated-skill default above already serves it — no symlink and no trust step. It also reads its own managed `~/.config/opencode/skills` root and the Claude compatibility root `~/.claude/skills`; use the OpenCode-managed root only when the user asks for it. Project skills are discovered by walking up from the working directory to the git worktree, so start a new session after generation if the skill does not appear. (`~/.cache/opencode/skills` is the remote-skill download cache from `skills.urls`, not an authoring root — never write a generated skill there.)\n\nSelection rules:\n1. Personal install: set `SKILLS_HOME` to `~/.agents/skills` (create the directory if missing). One exception, so the default does not invent a convention in someone else's house: if `~/.agents/skills` does not exist **and** the host's private root already contains skills, use the private root instead and say why in the report.\n2. **Claude Code does not scan `~/.agents/skills`** — after generation completes, Step 10 links the skill in with `ln -sfn \"$HOME/.agents/skills/<skill_name>\" \"$HOME/.claude/skills/<skill_name>\"`.\n3. **Hermes Agent personal installs use the Hermes row above**, not the cross-agent root, and take no symlink.\n4. If the user explicitly asks for a host-private root (`~/.copilot/skills`, `~/.claude/skills`, `~/.config/agents/skills`, `~/.config/amp/skills`), honor it and skip the symlink.\n5. If the user explicitly asked for project-local output, use the project-local row for their host.\n6. If the choice requires knowing the host (project-local output, the Hermes personal root, the OpenClaw state root, or the Claude Code symlink) and you cannot identify it, ask: \"Which agent are you running in — OpenCode, OpenClaw, Hermes Agent, GitHub Copilot CLI, Amp, Codex, or Claude Code?\"\n7. For OpenClaw personal output, use `${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/skills`. The shared `~/.agents/skills` root is a valid OpenClaw destination only when `OPENCLAW_STATE_DIR` is unset or equals the default `$HOME/.openclaw`; otherwise use the active state root or a project/extra directory.\n8. If the user explicitly asks for an OpenClaw-managed personal root, use the active state root and verify discovery with `openclaw skills list`.\n\nSet `SKILLS_HOME` to the selected root and check if `$SKILLS_HOME/<skill_name>/` already exists. On Claude Code, also check whether `~/.claude/skills/<skill_name>` exists as a **real directory** (not a symlink) — a previous install may live there; if so, offer to migrate it (move the directory into `~/.agents/skills/` and replace the original path with the symlink) before continuing.\nIf the skill already exists, prompt the user to choose:\n1. **Update / Fold-in** (Mode 4) — integrate new files/content into the existing skill components.\n2. **Overwrite** — delete and regenerate the skill from scratch.\n3. **Rename** — append `-2` or use a different custom slug.\n\nIf the user selects **Update / Fold-in**, proceed immediately to the **Update / Fold-in Workflow** section after Step 2.5 (skipping Steps 3, 4, 6, 7, 8, 9).\n\n---\n\n## Step 6 — Create skill directory structure\n\n```bash\nmkdir -p \"$SKILLS_HOME/<skill_name>/chapters\"\n```\n\n---\n\n## Step 7 — Generate chapter summaries\n\n**TOKEN BUDGET RULE — CRITICAL (adaptive):**\n\nThe per-chapter budget scales with `BOOK_TYPE` and `DEPTH`. Technical chapters need room for code and tables; study depth needs room for worked reasoning. Pick the budget from this matrix:\n\n| | `DEPTH=reference` | `DEPTH=study` |\n|---|---|---|\n| `BOOK_TYPE=text` | 800–1,200 tokens | 1,000–1,800 tokens |\n| `BOOK_TYPE=technical` | 1,200–1,800 tokens | 2,000–3,000 tokens |\n\n- These are per-file targets, not hard caps — a dense chapter may run over, a thin one under. Density still beats length (Quality Rule #3): never pad to hit a number.\n- Files are loaded on-demand, so a larger chapter only costs tokens when that chapter is actually read.\n- When in doubt between two cells (e.g. mixed-content book), use the lower budget and let depth come from precision, not volume.\n\n**`DEPTH=study` is earned with content, not a bigger number.** The standard section template (Core Idea → Connects To) naturally lands a dense prose chapter around 700–900 tokens. To reach the study budget *honestly* — not by padding — a study-depth chapter must add concrete material:\n- **Reproduce one worked example or artifact** from the chapter (e.g. the example press release, a sample dialogue, a filled-in template, a decision the author walks through) under a `## Worked Example` section. This is the single biggest lever and the main thing a learner returns for.\n- **Expand the \"How\" of each framework** into explicit steps or criteria, not a one-liner.\n- **Add a short \"Why it works / failure mode\" note** to the top 1–2 frameworks.\n\nIf a chapter genuinely has no worked example and resists expansion, let it land below the study floor rather than padding — and note that the chapter is thin in its Core Idea. A `reference`-depth chapter, by contrast, deliberately omits worked examples and keeps only the decision-ready essentials.\n\nFor EACH chapter/major section identified in Step 3:\n\nRead the corresponding section of the extracted `full_text.txt` (use character offsets or grep for chapter headings).\n\nCreate `$SKILLS_HOME/<skill_name>/chapters/ch<NN>-<slug>.md` using the structure below.\n\n**Adapt emphasis based on `BOOK_TYPE`:**\n- `technical` → prioritize \"Code Examples\", \"Reference Tables\", and \"Commands & APIs\" sections; preserve exact syntax\n- `text` → prioritize \"Frameworks Introduced\", \"Mental Models\", and \"Key Takeaways\"; skip empty technical sections\n\n```markdown\n# Chapter N: <Full Title>\n\n## Core Idea\n<1–2 sentences: the single most important thing this chapter teaches>\n\n## Frameworks Introduced\n- **<Framework Name>**: <exact formulation — preserve the author's naming>\n  - When to use: <specific situation>\n  - How: <steps or criteria>\n\n## Key Concepts\n- **<Term>**: <precise definition in 1 sentence>\n(5–10 most important terms from this chapter)\n\n## Mental Models\n<2–4 frameworks or thinking tools. Write as \"Use X when Y\" or \"Think of X as Y\">\n\n## Anti-patterns\n- **<What to avoid>**: <why it fails>\n\n## Code Examples *(technical books only — omit if BOOK_TYPE=text)*\n<!-- Copy the most instructive snippet from the chapter. Preserve indentation exactly. -->\n```<language>\n<key code example from this chapter>\n```\n- **What it demonstrates**: <one line>\n\n## Reference Tables *(technical books only — omit if BOOK_TYPE=text)*\n<!-- Reproduce any comparison matrix, parameter table, or decision table from the chapter in markdown. -->\n\n## Worked Example *(DEPTH=study only — omit for DEPTH=reference)*\n<!-- Reproduce or reconstruct one concrete example the author works through: a\n     sample document, a dialogue, a filled-in template, a before/after, or a\n     decision walked end-to-end. This is what makes a study chapter worth its\n     budget. Keep it faithful to the source; never copy long raw passages —\n     reconstruct the example compactly. -->\n\n## Key Takeaways\n1. <Actionable insight>\n2. <Actionable insight>\n3. <Actionable insight>\n(3–7 takeaways a practitioner must remember)\n\n## Connects To\n- **Ch N**: <why this chapter relates>\n- **<Concept>**: <external concept or standard it connects with>\n```\n\n---\n\n## Step 8 — Generate supporting files\n\n### glossary.md\nCreate `$SKILLS_HOME/<skill_name>/glossary.md`:\n- Every significant term from the book, alphabetically sorted\n- Format: `**Term** — definition (Ch N)`\n- Max 1,500 tokens\n\n### patterns.md\nCreate `$SKILLS_HOME/<skill_name>/patterns.md`:\n- All concrete techniques, design patterns, algorithms from the book\n- Format: `## Pattern Name\\n**When to use**: ...\\n**How**: ...\\n**Trade-offs**: ...`\n- Max 2,000 tokens\n\n### cheatsheet.md\nCreate `$SKILLS_HOME/<skill_name>/cheatsheet.md`:\n\n**This is the most differentiated layer of the skill — treat it as a reasoning aid, not a keyword list.** Anyone can grep the glossary for a term. The cheatsheet captures the author's *judgment*: the decisions they'd make and why. It's the file that turns \"I know the words\" into \"I'd act the way the author would\".\n\nPrioritize, in order:\n1. **Decision rules** — \"When X, do Y, because Z.\" The if/then logic the author applies, stated so the reader can apply it without re-reading the book.\n2. **Decision trees / flowcharts** (as nested bullets or a small table) — for choices with more than two branches.\n3. **Trade-off matrices** — competing options scored on the dimensions the author cares about, so the reader can pick under their own constraints.\n4. **Thresholds & defaults** — the specific numbers, ratios, or rules of thumb the author commits to (e.g. \"keep functions under ~20 lines\", \"alert when error budget < 10%\").\n5. **Tells & smells** — fast heuristics for recognizing a situation (\"if you see X, you're probably in trouble Y\").\n\nAvoid: bare term→definition rows (that's the glossary), and prose paragraphs (that's the chapters). Every line should help the reader *decide* something.\n\n- Format mostly as compact tables and decision rules; the content you'd want on a single printed page kept beside you while working.\n- Max 1,200 tokens.\n\n---\n\n## Step 9 — Generate the master SKILL.md\n\n**CRITICAL TOKEN BUDGET: Keep SKILL.md body under 4,000 tokens.**\nCompaction truncates from the END — put the most important content FIRST.\n\nCreate `$SKILLS_HOME/<skill_name>/SKILL.md`:\n\n```markdown\n---\nname: <skill_name>\ndescription: \"Knowledge base from \\\"<Full Title>\\\" by <Author(s)>. Use when applying <author>'s frameworks for <key topics, 3–6 terms>, studying the book, or referencing its concepts.\"\n---\n\n<!-- argument-hint: [topic, framework name, or chapter number] -->\n\n# <Full Title>\n**Author**: <Author(s)> | **Pages**: ~<N> | **Chapters**: <N> | **Generated**: <YYYY-MM-DD>\n\n## How to Use This Skill\n\n- **Without arguments** — load core frameworks for reference\n- **With a topic** — ask about `replication`, `pricing`, or another indexed topic; I find and read the relevant chapter\n- **With chapter** — ask for `ch05`; I load that specific chapter\n- **Browse** — ask \"what chapters do you have?\" to see the full index\n\nWhen you ask about a topic not covered in Core Frameworks below, I will read\nthe relevant chapter file before answering.\n\n---\n\n## Core Frameworks & Mental Models\n<!-- ~2,000 tokens: the author's most important named frameworks and principles.\n     Preserve exact names. Write as \"Use X when Y\", \"Prefer X over Y because Z\".\n     This is a toolkit, not a summary. -->\n\n<generate 2,000 tokens of the most critical frameworks and insights here>\n\n---\n\n## Chapter Index\n\n| # | Title | Key Frameworks |\n|---|-------|----------------|\n| [ch01](chapters/ch01-<slug>.md) | <Title> | <framework1>, <framework2> |\n| [ch02](chapters/ch02-<slug>.md) | <Title> | <framework1>, <framework2> |\n...\n\n## Topic Index\n\n<!-- Alphabetical. Major terms/frameworks → chapter(s) that cover them. -->\n- **<Term>** → ch<N>[, ch<N>]\n- **<Term>** → ch<N>\n\n## Supporting Files\n\n- [glossary.md](glossary.md) — all key terms with definitions\n- [patterns.md](patterns.md) — all techniques and design patterns\n- [cheatsheet.md](cheatsheet.md) — quick reference tables and decision guides\n\n---\n\n## Scope & Limits\n\nThis skill covers the book content only. For hands-on implementation in your codebase,\ncombine with project-specific tools. For topics beyond this book, check related skills\nor ask the agent directly.\n<if images_dropped > 5: state that N source images were not read>\n```\n\n---\n\n## Step 9.5 — Scan the generated skill\n\nBefore reporting success, loading the skill in another session, or publishing it, run the advisory security scan:\n\n```bash\nSKILL_CONVERTER_ROOT=\"$(cd \"$(dirname \"$SCRIPT_PATH\")/..\" && pwd)\"\n\"$PYTHON_BIN\" \"$SKILL_CONVERTER_ROOT/tools/scan_generated_skill.py\" \"$SKILLS_HOME/<skill_name>\"\n```\n\nIf the scanner exits non-zero, stop and ask a human to review its file/line findings. Do not silently rewrite the generated files, and do not load or publish the skill until the findings are resolved or explicitly accepted.\n\n---\n\n## Step 10 — Cleanup and report\n\nIf the host is Claude Code and `SKILLS_HOME` is `~/.agents/skills` (the default personal install), expose the skill to Claude Code with a symlink — Claude Code only scans `~/.claude/skills`:\n\n```bash\nmkdir -p \"$HOME/.claude/skills\"\nLINK=\"$HOME/.claude/skills/<skill_name>\"\nTARGET=\"$HOME/.agents/skills/<skill_name>\"\nif [ -d \"$LINK\" ] && [ ! -L \"$LINK\" ]; then\n  CLAUDE_STATUS=\"skipped-realdir\"                 # Step 5 migration declined; leave the old dir\nelse\n  ln -sfn \"$TARGET\" \"$LINK\" 2>/dev/null || true\n  # Read the link back — do NOT trust that `ln` did what was asked. On Windows/MSYS\n  # `ln -s` may COPY instead of link (or need Developer Mode / an elevated shell), and\n  # PowerShell/cmd have no `ln` at all. The report must reflect what is on disk, not the\n  # fact that the command ran.\n  if [ -L \"$LINK\" ] && [ \"$(readlink \"$LINK\")\" = \"$TARGET\" ]; then\n    CLAUDE_STATUS=\"linked\"\n  elif [ -e \"$LINK\" ]; then\n    CLAUDE_STATUS=\"copy\"                           # a real file/dir landed instead of a link\n  else\n    CLAUDE_STATUS=\"absent\"                         # ln unavailable or refused\n  fi\nfi\n```\n\nThe real-directory guard is required: `ln -sfn` into an existing real directory would nest the link *inside* it (`~/.claude/skills/<skill_name>/<skill_name>`), leaving Claude Code loading the stale copy. If the user declined the Step 5 migration, skip the symlink and say so in the report — Claude Code keeps using the old directory until it is migrated.\n\n**Read the link back before you report anything about it.** The symlink is a claim, not a fact: fill the \"Discoverable by\" line from `CLAUDE_STATUS` (what is actually on disk), never from \"the command was issued\". **Do not hard-fail when the link is missing or is a copy** — the skill exists at the hub and every other host still finds it; the honest report is \"written to `~/.agents/skills/<skill_name>`; Claude Code will not see it until the link is created\", not an abort. (Windows lead, unverified: a directory junction — `mklink /J` in an elevated `cmd`, or `New-Item -ItemType Junction` in PowerShell — needs neither Developer Mode nor a symlink privilege; if you attempt it, it does not change the read-back-then-report rule.)\n\nSkip this when the user chose a host-private or project-local root (Step 5, rules 3-4).\n\nThen clean up the extraction workdir:\n\n```bash\nPYTHON_BIN=\"${PYTHON_BIN:-python3}\"\nif ! command -v \"$PYTHON_BIN\" >/dev/null 2>&1; then\n  PYTHON_BIN=\"python\"\nfi\n\nRemove **the work directory this run actually used** — the `Workdir ->` path from the\nextraction output, which is also stored as `workdir` in `metadata.json`. Never delete a\ndirectory you did not create: another extraction may be running beside yours.\n\n```bash\n# WORKDIR is the path this run reported; quote it in case of spaces.\nrm -rf \"$WORKDIR\"\n```\n\nEquivalently, if you still have the metadata file:\n\n```bash\n\"$PYTHON_BIN\" - \"$WORKDIR_METADATA_JSON\" <<'PY'\nimport json\nimport shutil\nimport sys\nfrom pathlib import Path\n\nmeta_path = Path(sys.argv[1])\nworkdir = json.loads(meta_path.read_text(encoding=\"utf-8\")).get(\"workdir\")\nif workdir:\n    shutil.rmtree(workdir, ignore_errors=True)\nPY\n```\n\nOlder copies of this file removed a single fixed `book_skill_work` directory. That path is\nno longer used, so such a cleanup is now a harmless no-op rather than something that could\ndelete a concurrent run's output.\n\nThen report to the user:\n\n```\n✅ Skill created: $SKILLS_HOME/<skill_name>/\n\n📚 Book: <Full Title> — <Author>\n📄 Pages: ~<N> | Chapters: <N>\n\nFiles generated:\n  SKILL.md         — core frameworks + index   (~X tokens)\n  chapters/        — <N> chapter summaries     (~X tokens each, ~X total)\n  glossary.md      — key terms                 (~X tokens)\n  patterns.md      — techniques & patterns     (~X tokens)\n  cheatsheet.md    — quick reference           (~X tokens)\n  ─────────────────────────────────────────────────────\n  Total skill size: ~X tokens (loaded on-demand, not all at once)\n\n💡 Tip: check your agent's session cost/usage command to see actual token usage.\n\nUsage:\n  Ask for <skill_name>                  → load core frameworks\n  Ask <skill_name> about <topic>        → find and explain a topic\n  Ask <skill_name> for ch<N>            → dive into a specific chapter\n\nDiscoverable by: <only what is true for the chosen destination — see below>\n\nSomewhere else?  mv ~/.agents/skills/<skill_name> <dest_root>/<skill_name> \\\n                   && ln -sfn <dest_root>/<skill_name> ~/.claude/skills/<skill_name>\n\nPrompted for permission on every file? That is your host gating writes outside the\nworking directory. Say \"save it in this project\" and re-run to write inside it.\n\nReload (if your agent doesn't auto-detect new skills):\n  GitHub Copilot CLI:  /skills reload\n  Claude Code:         restart the session\n  Amp:                 restart the session\n  Hermes Agent:         start a new session\n  OpenClaw:             openclaw skills list (new session if watcher disabled)\n  OpenCode:             start a new session\n\nShare this skill (optional):\n  GitHub repo, installable on any host (Step 11):  say \"publish\"\n  Copilot ecosystem:  gh skill publish $SKILLS_HOME/<skill_name>\n```\n\nFill the \"Discoverable by\" line from `CLAUDE_STATUS` (the read-back result), never from the fact that `ln` ran — for `~/.agents/skills` installs:\n- `linked` → \"Copilot CLI, Amp, Codex (natively); Claude Code via symlink ~/.claude/skills/<skill_name>\"\n- `skipped-realdir` → \"Copilot CLI, Amp, Codex (natively); **NOT** Claude Code — migrate the real directory at ~/.claude/skills/<skill_name> first\"\n- `copy` or `absent` → \"Copilot CLI, Amp, Codex (natively); **NOT** Claude Code — the host could not create the symlink (a plain copy drifts on the next Update/Fold-in). Enable Developer Mode / create the link manually, or run the skill from ~/.agents/skills\"\n- Hermes Agent personal root → \"Hermes Agent (from `$HERMES_HOME/skills/<category>`)\"; no symlink claim, and no cross-agent claim, because the other hosts do not scan the Hermes root\n- other host-private or project-local root → name only the host(s) that scan that root; no symlink claim\n\nThe \"Somewhere else?\" relocation line must be correct for the path actually taken, so it never breaks the symlink the run just created. **`mv` always targets the final skill directory, `<dest_root>/<skill_name>`, never `<dest_root>` itself.** `mv ~/.agents/skills/mybook ~/.copilot/skills && ln -sfn ~/.copilot/skills ~/.claude/skills/mybook` reads as valid and is not: the skill lands at `~/.copilot/skills/mybook` while the link points one level up at the root, so Claude Code resolves to a directory with no `SKILL.md`, which is the exact breakage this line exists to avoid. Substitute the destination the user actually named, so the printed command carries real paths and there is nothing left to interpret:\n- `~/.agents/skills` + symlink → `mv ~/.agents/skills/<skill_name> <dest_root>/<skill_name> && ln -sfn <dest_root>/<skill_name> ~/.claude/skills/<skill_name>`\n- host-private root, Hermes Agent included → `mv <src_root>/<skill_name> <dest_root>/<skill_name>`\n- project-local root → `mv <project_root>/<skill_name> <dest_root>/<skill_name>`\n\nThe \"Prompted for permission on every file?\" line is the answer to a host that gates writes outside the working directory (any personal-scope root is out-of-cwd): the destination was announced above, and the one-line fix — re-run asking for the project-local root — sits next to it. Keep it only for personal-scope installs; drop it when the user already chose project-local.\n\n---\n\n## Step 11 — Publish the generated skill to GitHub (optional)\n\nAfter the Step 10 report, offer once — and only if the Step 9.5 scan passed:\n\n> \"Want me to publish this skill to GitHub so any Agent Skills host can install it with `npx skills add`? (yes / skip)\"\n\nIf the user declines, stop here. Requirements: the `gh` CLI, authenticated (check `gh auth status`). If `gh` is missing or unauthenticated, offer to set it up (`brew install gh` or https://cli.github.com, then `gh auth login`) — or use the no-`gh` path: the user creates an empty repo of the chosen visibility in the GitHub web UI, then you run the `git init`/`add`/`commit` commands below followed by `git remote add origin <repo-url> && git push -u origin main`. The visibility rule below applies to the web-created repo exactly the same.\n\n**Visibility is a separate closed question — never inferred, never read out of an earlier answer.** Once the user accepts, ask it on its own and require a one-word reply:\n\n> \"Private or public repository? Reply with one word: `private` or `public`.\"\n\n**The reply must *be* `public`, not merely contain it — a hard rule, not a suggestion.** Run `gh repo create` with `--private` in every case except one: the answer to the visibility question is the bare word `public`. Substring matching is forbidden, because a sentence about **the source's licence is not a visibility answer** — \"it's public domain\", \"the book is public domain\", \"it's publicly available\" all describe the material, not the repository, and all resolve to `--private`. A paraphrase, a sentence, an ambiguous answer, silence, or your own inference is NOT consent: re-ask once, and if the reply is still not the bare word, use `--private` and say so in the report. A private repo can be flipped public later; a public push of book-derived content cannot be un-published.\n\n**Copyright gate — always apply before creating the repo:** chapter files are synthesized summaries, not raw text, but they still derive from the source material. Per the README's Copyright & fair use policy, skills generated from **third-party copyrighted books must stay private**; offer public only when the source is the user's own writing, openly licensed content, or material the user explicitly confirms they are authorized to redistribute publicly — and state which case applies. Having access to internal company material is not permission to disclose it: skills from internal docs stay **private** unless the user states they hold publication rights.\n\nIf accepted:\n\n1. Add a repo `README.md` inside `$SKILLS_HOME/<skill_name>/` (never overwrite an existing file) — the skill title, a one-paragraph description (\"Agent skill generated from *<Title>* by <Author> with [book-to-skill](https://github.com/virgiliojr94/book-to-skill)\"), the install command from step 3 below, the file inventory, and a note that the content is synthesized summaries, not the book text.\n2. Initialize the skill folder as a git repository and create the remote (default repo name `<skill_name>`; let the user override — some prefer a `<skill_name>-skill` suffix). **Nested-repo guard:** first check whether the skill folder already sits inside a git repository (`git -C \"$SKILLS_HOME/<skill_name>\" rev-parse --show-toplevel` — always the case for project-local roots like `.claude/skills/`). If it does, do NOT `git init` in place: the outer repository would record the folder as an embedded repo (gitlink, mode 160000) without `.gitmodules`, and fresh clones of the outer project would silently omit the skill. Instead, copy the skill folder to a scratch directory, run the commands below from the copy, and tell the user the published repo — not the project-local folder — is the remote's working copy.\n\n```bash\ncd \"$SKILLS_HOME/<skill_name>\"\ngit init -b main\ngit add -A\ngit commit -m \"Add <skill_name> skill\"\ngh repo create <repo_name> --private --source . --push\n# --private is the default; substitute --public ONLY under the visibility rule above\n# (the visibility answer WAS the bare word \"public\" AND the copyright gate allows it)\n```\n\n3. Report the repo URL and the cross-host install command:\n\n```\n✅ Published: https://github.com/<owner>/<repo_name> (<private|public>)\n\nInstall on any Agent Skills host:\n  npx skills add https://github.com/<owner>/<repo_name> --skill <skill_name>\n```\n\n   When the nested-repo guard fired and the repo was published from a scratch copy, add one line — that local folder never gains a remote, so the Update/Fold-in push offer will never appear for it:\n\n```\n⚠️  Published from a copy: <skill folder> sits inside another git repository, so it has\n    no remote of its own. To publish a later update, re-run Step 11, or clone\n    https://github.com/<owner>/<repo_name> and fold new material into the clone.\n```\n\nThe root-level `SKILL.md` layout is exactly what the `skills` CLI detects, so the repo is installable as-is — no restructuring needed. Outside the nested-repo case the local folder stays the live install for this machine and is the remote's working copy, so later Update/Fold-in runs can commit and push their changes to the same remote.\n\n---\n\n## Update / Fold-in Workflow\n\nWh","docs/ARCHITECTURE.md":"---\ndescription: \"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.\"\nseo_title: \"Architecture - How book-to-skill Extracts and Generates\"\n---\n\n# Architecture\n\nbook-to-skill has two halves: a **deterministic extractor** (Python) and a\n**spec-driven generator** (the agent following `SKILL.md`). The extractor turns any\ndocument into clean text + metadata; the agent turns that into a structured skill.\n\n```\n            ┌─────────────────────────── EXTRACTOR (Python, deterministic) ──┐\n documents  │  scripts/extract.py (shim)  →  book_to_skill/                   │\n (pdf/epub/ │    ├─ cli.py · utils.py   CLI parse · multi-source · runner     │\n  docx/...) │    ├─ config.py           supported extensions · paths · deps   │\n     │      │    ├─ dependencies.py     optional-dep probing · --check report │\n     ▼      │    ├─ sanitize.py         strip invisible/zero-width Unicode    │\n ───────────│    └─ parsers/            pdf · epub · docx · html · rtf ·      │\n            │                             calibre · text (best tool, fallback)│\n            │  output → <tempdir>/book_skill_work/                            │\n            │    full_text.txt   (all sources merged, source-marked)          │\n            │    metadata.json   (pages, words, tokens, chapters, ToC)        │\n            └────────────────────────────────────────────────────────────────┘\n                                   │\n                                   ▼\n            ┌─────────────────────────── GENERATOR (agent, follows SKILL.md) ┐\n            │  Step 1.5  ask content type → BOOK_TYPE (technical | text)      │\n            │  Step 2/2.5 extract · cost estimate · confirm                   │\n            │  Step 2.6  REPL-style probing for large books (grep/sed, no     │\n            │            full re-reads)                                        │\n            │  Step 3    analyze structure (title, author, chapters, ToC)     │\n            │  Step 4    purpose → DEPTH (reference | study)                   │\n            │  Step 7    per-chapter summaries (budget = BOOK_TYPE × DEPTH)    │\n            │  Step 8    glossary · patterns · cheatsheet (decision layer)    │\n            │  Step 9/9.5 SKILL.md core + indexes                             │\n            └────────────────────────────────────────────────────────────────┘\n                                   │\n                                   ▼\n                 <SKILLS_HOME>/<slug>/  ← chosen per host + scope:\n                  ~/.copilot/skills/   GitHub Copilot CLI (personal)\n                  ~/.agents/skills/    Copilot CLI or Amp (cross-agent, personal)\n                  ~/.claude/skills/    Claude Code (personal)\n                  ~/.openclaw/skills/  OpenClaw (personal, default state only)\n                  $HERMES_HOME/skills/<category>/  Hermes Agent (personal)\n                  .github/skills/ | .claude/skills/ | .agents/skills/  project-local (any host)\n                  .hermes/skills/<category>/                         Hermes project-local\n                  skills/                                                   OpenClaw workspace\n                  ~/.config/opencode/skills/                         OpenCode (own root; on request)\n                  .opencode/skills/                                  OpenCode project-local\n                  scope: personal vs project-local (Step 5, BOOK_TO_SKILL_SCOPE)\n                  SKILL.md         core frameworks + chapter & topic index (~4K)\n                  chapters/*.md    on-demand, loaded only when asked\n                  glossary.md      terms\n                  patterns.md      techniques\n                  cheatsheet.md    decision rules / trees / trade-offs / tells\n```\n\nOpenClaw's shared `~/.agents/skills` compatibility root is discoverable only\nwhen `OPENCLAW_STATE_DIR` is unset or uses the default `~/.openclaw`. A\nnon-default state uses its active `<state-dir>/skills` root instead; the\nconverter does not infer arbitrary `skills.load.extraDirs`.\n\n## Design principles\n\n1. **Extract structure, not summaries** — named frameworks, decision rules,\n   anti-patterns; never raw passages.\n2. **Compile-time over runtime** — pay navigation/structuring once; at query time\n   load only the relevant chapter. See [Performance & Cost](performance.md).\n3. **On-demand chapters** — `SKILL.md` stays small; chapter files cost tokens only\n   when read.\n4. **Front-loaded `SKILL.md`** — most important content first (compaction truncates\n   from the end).\n5. **Graceful degradation** — every format has a stdlib fallback; one bad source is\n   skipped, not fatal.\n\n## Key components\n\n| Path | Responsibility |\n|------|----------------|\n| `scripts/extract.py` | thin entrypoint shim → `book_to_skill.cli` (kept so old invocations keep working) |\n| `book_to_skill/cli.py`, `utils.py` | CLI parsing, multi-source resolution, chapter/ToC detection, runner |\n| `book_to_skill/parsers/` | one module per format (`pdf`, `epub`, `docx`, `html`, `rtf`, `calibre`, `text`) |\n| `book_to_skill/config.py` | supported extensions, output paths, dependency map |\n| `book_to_skill/dependencies.py` | optional-dependency probing + `--check` |\n| `book_to_skill/sanitize.py` | strips zero-width / Unicode-tag-block characters from extracted text, and folds Kangxi radicals to their ideographs (see Security) |\n| `tools/discovery_tax.py` | measures token cost vs context-dump / discovery loop |\n| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\\|copilot\\|amp\\|hermes\\|openclaw`) |\n| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\\|copilot\\|amp\\|hermes\\|opencode`) |\n| `tools/scan_generated_skill.py` | advisory prompt-injection scan of a generated skill (see Security) |\n| `SKILL.md` | the generator spec (Steps 0–10 + fold-in workflow) |\n\n## Security\n\nUntrusted documents flow into an agent's context and then into a generated skill\nthat later loads into other agents — a document→context supply chain. The hardening\nis layered:\n\n- **Extraction sanitization** (`book_to_skill/sanitize.py`) — strips zero-width\n  (`U+200B/200C/200D/2060/FEFF`) and the Unicode tag block (`U+E0000–E007F`) from\n  every parser's output before metrics or `full_text.txt`, so invisible\n  document-borne instructions never reach the agent. Reports the removal count;\n  rejects a source with no visible content left.\n- **CJK radical folding** (`book_to_skill/sanitize.py`) — PDFs whose embedded\n  font subset carries no correct Unicode mapping emit a Kangxi radical\n  (`U+2F00–U+2FDF`) where a character belongs, so a Chinese book extracts\n  \"判断⼒\" for \"判断力\". Each radical in that block has a Unicode compatibility\n  decomposition, so the fold is NFKC applied to those code points only: the rest\n  of the text is passed through byte for byte. Nothing is removed, so it does\n  not count toward the security removal total. CJK Radicals Supplement\n  (`U+2E80–U+2EF3`) is deliberately not folded — Unicode defines no\n  decomposition for it, and some entries are ordinary kanji.\n- **DOCX XXE / Billion-Laughs guard** (`parsers/docx.py`) — rejects any XML part\n  declaring a DTD or entities before parsing.\n- **Subprocess argument-injection** — file paths are absolutised before reaching\n  `pdftotext` / `pdfinfo` / `ebook-convert`, so a `-`-leading filename can't be read\n  as a flag.\n- **Generated-skill scan** (`tools/scan_generated_skill.py`) — an advisory step in\n  the generator (Step 9.5) that flags instruction-override phrases, model-control\n  tags, residual invisible Unicode, authority-widening frontmatter, and\n  exfiltration-shaped content across the generated `SKILL.md`, `chapters/*.md`,\n  `glossary.md`, `patterns.md`, and `cheatsheet.md`. Findings name only the rule and\n  file location — never the matched text.\n- **CI** — CodeQL, Bandit (gate on HIGH), Zizmor, and dependency CVE review on PRs.\n\n## Extending\n\n- **New format** → add `book_to_skill/parsers/<fmt>.py`, register its extension in\n  `config.py`, wire dependency probing in `dependencies.py`, branch in\n  `utils.extract_single_file`.\n- **New generation behavior** → edit the relevant Step in `SKILL.md`; keep it lean\n  and back the change with evidence (see CONTRIBUTING.md).\n"},"files":{"SKILL.md":"---\nname: book-to-skill\ndescription: \"Converts books and documents (PDF, EPUB, DOCX, HTML, Markdown, plain text, RTF, MOBI/AZW with Calibre) into structured agent skills, extracting frameworks, mental models, principles, techniques, and anti-patterns. Use when the user wants to study a document through GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, or OpenClaw, apply an author's frameworks while working, or build a reusable knowledge base from a file.\"\n---\n\n<!--\nCross-agent notes (informational; ignored by host agents):\n  - Compatible skill roots: GitHub Copilot CLI (~/.copilot/skills, ~/.agents/skills,\n    .github/skills, .claude/skills, .agents/skills), Amp (.agents/skills,\n    ~/.config/agents/skills, ~/.config/amp/skills), Claude Code (~/.claude/skills),\n    Hermes Agent ($HERMES_HOME/skills, .hermes/skills, .agents/skills),\n    OpenClaw (${OPENCLAW_STATE_DIR:-~/.openclaw}/skills, .agents/skills, skills/;\n    ~/.agents/skills only with the default state).\n    OpenCode (~/.config/opencode/skills, ~/.claude/skills, .opencode/skills,\n    ~/.agents/skills, .agents/skills).\n  - `allowed-tools` is intentionally omitted to stay agent-neutral: Copilot CLI uses\n    `shell`/MCP-server names, Claude uses `Bash`/`Read`/`Write`/`Glob`/`Grep`, Amp\n    adds `shell_command`. The skill needs shell (to run extract.py) and file\n    read/write — each host will prompt for those on first use.\n  - Argument hint: <path-to-document-folder-or-glob>... [skill-name-slug]\n-->\n\n# Book-to-Skill Converter\n\nTransform written knowledge into actionable agent skills by extracting structure — not producing summaries.\n\n## Philosophy\n\nBooks contain crystallized expertise: frameworks, principles, and techniques that took years to develop. This skill extracts that knowledge into a format GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, OpenClaw, or another compatible agent can leverage repeatedly.\n**Extract structure, not summaries.** A skill isn't a book report. It's a toolkit of:\n- Named frameworks (mental models with clear application)\n- Actionable principles (rules that guide decisions)\n- Techniques (step-by-step methods)\n- Anti-patterns (what to avoid and why)\n- Voice calibration (how the author thinks and communicates)\n\n**Preserve the author's precision.** Frameworks often have specific names for reasons. \"The 5 Whys\" isn't interchangeable with \"ask why multiple times.\" Capture the exact formulation.\n\n**Layer depth appropriately.** Simple books → simple skills. Complex books with 10+ frameworks → skills with reference files and on-demand chapters.\n\n---\n\n## Modes of Operation\n\nFour paths available. Route based on what the user asks:\n\n### 1. Full Conversion (Default)\n**Trigger:** User provides one or more document/directory/glob paths without special instructions\n**Action:** Run all steps below (Steps 0–9)\n**Output:** Complete skill with SKILL.md, chapters/, glossary, patterns, cheatsheet\n\n### 2. Analyze Only\n**Trigger:** User says \"analyze\", \"just extract\", or \"I want to review before generating\"\n**Action:** Run Steps 0–3, then produce a structured extraction report (frameworks, principles, techniques found). Stop — do NOT generate skill files.\n**Output:** Analysis report for user review\n\n### 3. Generate from Prior Analysis\n**Trigger:** User has existing analysis notes or previously ran analyze-only\n**Action:** Skip Steps 0–3, use the provided analysis as input, run Steps 4–9\n**Output:** Skill files from the provided analysis\n\n### 4. Update / Fold-in (Existing Skill)\n**Trigger:** User provides one or more new source paths and indicates they want to update an existing skill (either by pointing to the existing skill folder, providing a skill slug that already exists in `SKILLS_HOME`, or explicitly requesting an update).\n**Action:** Run Step 0 (out-of-scope check), Step 1 (validate inputs), Step 1.5 (identify book type), and Step 2 (extract new files). Then skip to Step 5 (identify/detect existing skill path) and run the **Update / Fold-in Workflow** to merge the new content into the existing skill files.\n**Output:** Updated existing skill with new/revised chapter summaries and merged indexes/glossaries.\n\n---\n\n## Skill Locations\n\nThis converter can run from multiple skill systems. When looking for this converter's helper script or writing the generated book skill, prefer these locations in order:\n\n1. GitHub Copilot CLI personal skills: `~/.copilot/skills/`\n2. Cross-agent personal skills (Copilot, Amp, Codex; OpenClaw with its default state): `~/.agents/skills/`\n3. Claude Code personal skills: `~/.claude/skills/`\n4. Project-local Copilot skills: `.github/skills/`\n5. Project-local Claude skills: `.claude/skills/`\n6. Project-local Amp / Copilot / OpenClaw skills: `.agents/skills/`\n7. Amp global skills: `~/.config/agents/skills/`\n8. Amp legacy global skills: `~/.config/amp/skills/`\n9. Hermes Agent personal skills: `$HERMES_HOME/skills/` (defaults to `~/.hermes/skills/`)\n10. Hermes Agent project skills: `.hermes/skills/` or `.agents/skills/`\n11. OpenClaw personal skills: `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/` (active state; `~/.agents/skills/` is shared only with the default state)\n12. OpenClaw project skills: `.agents/skills/` or `skills/`\n11. OpenCode personal skills: `~/.config/opencode/skills/` or `~/.agents/skills/`\n12. OpenCode project skills: `.opencode/skills/` or `.agents/skills/`\n\nFor **generated** book skills, prefer the user-level cross-agent root `~/.agents/skills/` — one physical copy serves the cross-agent hosts and OpenClaw when it uses its default state. Copilot CLI and Amp discover it natively; Claude Code needs a symlink from `~/.claude/skills/<skill_name>` (created in Step 10, see Step 5 for the rules). Pick a host-private or project-local root only when the user explicitly asks for one. `BOOK_TO_SKILL_SCOPE=project` or `personal` can make that choice explicit for automation; do not ask a mandatory scope question merely because both scopes are available.\n\n---\n\n## Step 0 — Out-of-scope check\n\nIf no arguments are provided, stop and respond:\n> \"book-to-skill requires a supported document path, folder, or glob pattern. Usage: `book-to-skill <path-to-document-folder-or-glob>... [skill-name-slug]`\"\n\nThroughout the workflow:\n- **Resolve the destination root first.** Before any existing-skill lookup, determine where this run would write: the personal or project-local root for the user's host, per the Step 5 table and selection rules — a project-local request uses its project-local root, never the personal root. Below, \"the resolved root\" means that destination.\n- Identify the input paths and the optional skill slug.\n- If the last argument is not a file, folder, or glob that exists or matches any files, and it looks like a skill slug (e.g. lowercase hyphens, alphanumeric), treat it as `SKILL_NAME`.\n- Treat all other arguments as the list of `INPUT_PATHS`.\n- If any input path is an existing skill directory (contains `SKILL.md` and a `chapters/` sub-folder), or if `SKILL_NAME` matches an existing skill slug in the resolved root, flag this run as an **Update/Fold-in** operation (Mode 4).\n- **Re-run guard.** Before starting extraction for a Full Conversion, derive the prospective skill slug under Step 5's naming options — the author-concept form, the by-title form, and any `SKILL_NAME` given — and check the resolved root for an existing match. On a match, STOP and ask the user: \"`<skill-name>` already exists. Choose: (1) Update/Fold-in (Mode 4), (2) verify the existing skill is complete and stop, or (3) force full regeneration.\" Do not re-extract until the user chooses. On context recovery after an interrupted run (network cut-off, replayed or continued conversation), reuse the extraction work directory that run reported (`Workdir ->` in its output, or the path you set in `BOOK_SKILL_WORKDIR`) **only if it is still intact and still matches the current inputs and options** — the directory exists, its `metadata.json` is readable, the `sources` it lists match the files being converted now one-to-one on filename **and content fingerprint** (the extractor records a per-source `sha256` in `metadata.json`; recompute the fingerprint from each file as it exists now and require an exact match — `reuse_is_safe()` in `book_to_skill/utils.py` is the executable form of this check), and its recorded `extraction_mode` is the mode this run is using (Step 2's \"confirm the extraction is the document you asked for\" rule). If it is missing (temp cleanup), its `metadata.json` is gone, the sources no longer match (a different filename, or a changed content fingerprint), the recorded metadata has no `sha256` for a source (recorded before fingerprints existed, so freshness cannot be established), or the mode differs, say so and start a fresh extraction instead of resuming. When in doubt, ask the user before discarding or resuming.\n\n---\n\n## Step 1 — Validate input\n\nVerify that there is at least one supported file, directory, or glob pattern among the `INPUT_PATHS`.\nFor directories and globs, expand them to find matching supported files (`.pdf`, `.epub`, `.docx`, `.txt`, `.md`, `.markdown`, `.rst`, `.adoc`, `.html`, `.htm`, `.rtf`, `.mobi`, `.azw`, `.azw3`).\n\nIf no supported files are found, stop with a clear error message.\n\n---\n\n## Step 1.5 — Identify content type\n\nBefore extracting, ask the user:\n\n> \"What kind of content do these sources have? This helps me choose the best extraction method.\n>\n> 1. **Technical** — has code blocks, tables, formulas, diagrams (e.g. programming books, academic papers, architecture guides)\n> 2. **Text-heavy** — mostly prose, few or no tables/code (e.g. management, productivity, narrative non-fiction)\n> 3. **Not sure** — I'll use the fast method and warn you if quality seems limited\"\n\nStore the answer as `BOOK_TYPE`:\n- Option 1 → `BOOK_TYPE=technical`\n- Option 2 → `BOOK_TYPE=text`\n- Option 3 → `BOOK_TYPE=text`\n\n**If `BOOK_TYPE=technical`**, inform the user before proceeding:\n> \"📐 Technical mode selected — using Docling for structure-aware extraction (tables, code blocks, formulas preserved as markdown). This takes ~1.5s per page, so expect a few minutes for longer sources. Starting now…\"\n\n**If `BOOK_TYPE=text`**, inform:\n> \"📄 Text mode selected — using the fastest suitable extractor for each file type. Plain text/Markdown/HTML are usually ready in seconds; PDFs use pdftotext when available.\"\n\n---\n\n## Step 2 — Extract text from the source documents\n\nRun the extraction script, passing the input paths:\n\n```bash\nSCRIPT_PATH=\"\"\nHERMES_HOME_RESOLVED=\"${HERMES_HOME:-$HOME/.hermes}\"\nOPENCLAW_STATE_DIR_RESOLVED=\"${OPENCLAW_STATE_DIR:-$HOME/.openclaw}\"\nPROJECT_ROOT=\"$(git rev-parse --show-toplevel 2>/dev/null || true)\"\nHERMES_PROJECT_TRUSTED=false\nif [ -n \"$PROJECT_ROOT\" ] && [ \"${HERMES_AGENT:-}\" = true ] && \\\n  command -v hermes >/dev/null 2>&1 && \\\n  command -v python3 >/dev/null 2>&1 && \\\n  hermes config get skills.trusted_project_dirs --json 2>/dev/null | PROJECT_ROOT=\"$PROJECT_ROOT\" python3 -c 'import json, os, pathlib, sys; root=pathlib.Path(os.environ[\"PROJECT_ROOT\"]).resolve(); sys.exit(not any(pathlib.Path(p).expanduser().resolve() == root for p in json.load(sys.stdin)))' 2>/dev/null\nthen\n  HERMES_PROJECT_TRUSTED=true\nfi\n\nCANDIDATES=(\n  \"$HOME/.copilot/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.agents/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.claude/skills/book-to-skill/scripts/extract.py\"\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills/book-to-skill/scripts/extract.py\"\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n  \"$HOME/.config/opencode/skills/book-to-skill/scripts/extract.py\"\n  \"$HERMES_HOME_RESOLVED/skills/book-to-skill/scripts/extract.py\"\n  \"$HERMES_HOME_RESOLVED\"/skills/*/book-to-skill/scripts/extract.py\n)\nif [ \"${HERMES_AGENT:-}\" != true ]; then\n  # Project-local roots are resolved against the git worktree, not the current\n  # directory: an agent can be invoked from anywhere inside the project (e.g.\n  # `src/nested`) and these roots still have to be found — that is how OpenCode\n  # and the other hosts discover project skills. The CWD-relative forms are kept\n  # so the probe also works outside a git repository.\n  if [ -n \"$PROJECT_ROOT\" ]; then\n    CANDIDATES+=(\n      \"$PROJECT_ROOT/.github/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.claude/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.opencode/skills/book-to-skill/scripts/extract.py\"\n    )\n  fi\n  CANDIDATES+=(\n    \".github/skills/book-to-skill/scripts/extract.py\"\n    \".claude/skills/book-to-skill/scripts/extract.py\"\n    \".agents/skills/book-to-skill/scripts/extract.py\"\n    \"skills/book-to-skill/scripts/extract.py\"\n    \"skills\"/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n    \".opencode/skills/book-to-skill/scripts/extract.py\"\n  )\n  if [ -n \"$PROJECT_ROOT\" ]; then\n    CANDIDATES+=(\n      \"$PROJECT_ROOT/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/skills\"/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n    )\n  fi\nfi\nCANDIDATES+=(\n  \"$HOME/.config/agents/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.config/amp/skills/book-to-skill/scripts/extract.py\"\n)\nif [ \"$HERMES_PROJECT_TRUSTED\" = true ]; then\n  CANDIDATES=(\n    \"$PROJECT_ROOT/.hermes/skills/book-to-skill/scripts/extract.py\"\n    \"$PROJECT_ROOT/.hermes/skills\"/*/book-to-skill/scripts/extract.py\n    \"$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py\"\n    \"$PROJECT_ROOT/.agents/skills\"/*/book-to-skill/scripts/extract.py\n    \"${CANDIDATES[@]}\"\n  )\nfi\nfor candidate in \"${CANDIDATES[@]}\"\ndo\n  if [ -f \"$candidate\" ]; then\n    SCRIPT_PATH=\"$candidate\"\n    break\n  fi\ndone\n\nif [ -z \"$SCRIPT_PATH\" ]; then\n  echo \"Could not find scripts/extract.py for book-to-skill\" >&2\n  exit 1\nfi\n\nPYTHON_BIN=\"${PYTHON_BIN:-python3}\"\nif ! command -v \"$PYTHON_BIN\" >/dev/null 2>&1; then\n  PYTHON_BIN=\"python\"\nfi\n\n\"$PYTHON_BIN\" \"$SCRIPT_PATH\" $INPUT_PATHS --mode <BOOK_TYPE> --install-missing ask\n```\n\nBefore extraction, the script checks optional Python packages needed for the detected format. If a better extractor is missing, it prompts the user with the available fallback. Non-interactive sessions default to fallback unless install mode is explicitly `yes`.\n\n**Tip — preflight the environment:** run `\"$PYTHON_BIN\" \"$SCRIPT_PATH\" --check` to print a per-format report of which extractors are installed and the exact command to install whatever is missing, without processing any file. Useful when a user reports a setup or quality problem.\n\nThis creates a **per-run** work directory — `<tempdir>/book_skill_work-<pid>/` by default, or exactly the path you set in `BOOK_SKILL_WORKDIR` — containing:\n- `full_text.txt` — combined extracted text of all sources with clear visually demarcated boundaries.\n- `metadata.json` — overall combined size, words, pages, token counts, dropped EPUB image counts, the resolved `workdir`, and a detailed list of individual processed `sources`.\n\nThe run prints all three paths on completion (`Workdir ->`, `Text ->`, `Meta ->`). **Take the paths from that output (or from `metadata.json`'s own `workdir` field) rather than assuming a fixed location** — the directory name differs per run so that concurrent extractions on one machine cannot overwrite each other's results.\n\nRead that run's `metadata.json` to inspect the results.\n\n**Always confirm the extraction is the document you asked for** before generating anything: check `filename` / `source_file` in `metadata.json`, or the `SOURCE:` header on the first line of `full_text.txt`. If you are waiting on a background run, wait on *its* specific workdir — polling a shared path can surface a different run's output.\n\n---\n\n## Step 2.5 — Pre-flight cost estimate\n\nRead this run's `metadata.json` (the `Meta ->` path from the extraction output) and present the user with an estimate **before doing any generation**:\n\n```\n📖 Sources detected: <total_sources> source(s)\n<list each source filename and format from the sources metadata list>\n<if images_dropped > 5: warn that N source images were not read>\n📄 Combined Pages/Sections: ~<N> | Words: ~<N> | Total tokens: ~<N>K\n\n💰 Estimated token cost (Full Conversion / Update):\n   Input  (reading + prompts): ~<N>K tokens\n   Output (skill files generated/updated):  ~<N>K tokens\n   Total:                           ~<N>K tokens\n\n   Cost: multiply the token counts above by your model's current\n   input/output per-1M-token rates (prices and model names change often —\n   do not hardcode them; quote today's rate and label it as an estimate).\n\n   ⏱  Estimated time: ~<N> minutes\n\n📁 Files to be generated/updated:\n   SKILL.md + chapter files + glossary + patterns + cheatsheet\n\n➡  Proceed with Full Conversion / Update? (or type \"analyze only\" to preview first)\n```\n\n**How to estimate:**\n- Input tokens ≈ `estimated_tokens` from metadata × 1.3 (prompts overhead per chapter pass)\n- Output tokens ≈ chapters × per-chapter budget + 4,000 (SKILL.md) + 4,500 (glossary + patterns + cheatsheet)\n  - Per-chapter budget midpoint by `BOOK_TYPE` (DEPTH is decided later in Step 4 and can raise it): `text` ≈ 1,000, `technical` ≈ 1,800. If the user has already indicated reference-only vs deep study, use the matching row of the Step 7 matrix.\n- Cost: report the token counts and multiply by the user's current per-1M-token input/output rates. Do NOT hardcode dollar figures — model names and prices change; if you show one, label it an estimate and date it.\n\nWait for the user to confirm before proceeding. If they say \"analyze only\", switch to Mode 2.\n\n---\n\n## Step 2.6 — REPL-style access for large books (> 50k tokens)\n\nInspired by the Recursive Language Model (RLM) paradigm: treat `full_text.txt` as a queryable corpus, not a single read. Loading the whole file into context burns budget you will need later for generation.\n\nFor books over ~50k tokens, prefer programmatic probes over `Read(full_text.txt)` without bounds:\n\n```bash\n# Size check before any Read\nwc -w \"$FULL_TEXT_PATH\"\n\n# Find chapter offsets without loading the whole file\ngrep -n -E \"^\\s*(Chapter|CHAPTER)\\s+[0-9]+\" \"$FULL_TEXT_PATH\" | head -40\n\n# Pull only the chapter you need (lines start..end inclusive)\nsed -n '<start>,<end>p' \"$FULL_TEXT_PATH\"\n\n# Verify a framework is actually mentioned before claiming it in SKILL.md\ngrep -c -i \"westrum\\|dora\" \"$FULL_TEXT_PATH\"\n\n# Targeted Read with offset/limit avoids dumping the full file\n# Read(file_path=full_text.txt, offset=<line>, limit=<lines>)\n```\n\nUse this approach for Step 3 (structure analysis), Step 7 (per-chapter summaries), and Step 8 (glossary / patterns extraction). On books under 50k tokens, a single `Read` is fine.\n\nWhy this matters: a 200-page book is ~75k tokens. Re-reading it once per chapter (28 passes) costs ~2M input tokens; using grep + sed to pull only relevant slices keeps generation cost proportional to the output, not the source.\n\n---\n\n## Step 3 — Analyze book structure\n\nRead the first 8,000 characters of the extracted `full_text.txt` to identify:\n- Book **title** and **author(s)**\n- **Chapter structure** (look for \"Chapter N\", \"PART I\", numbered headings, table of contents)\n- **Core themes** and subject domain\n- Approximate number of chapters\n\nThen read the Table of Contents section if present to map all chapters.\n\n**If mode is \"Analyze Only\":** produce the extraction report now and stop. Structure:\n```\n## Extraction Report — <Title>\n\n### Author's Core Frameworks\n- **<Framework Name>**: <what it is and when to apply>\n\n### Key Principles\n- <Principle>: <actionable rule>\n\n### Techniques & Methods\n- <Technique>: <step-by-step or how-to>\n\n### Anti-patterns\n- <What to avoid>: <why>\n\n### Suggested Skill Name\n`{author-lastname}-{core-concept}` — e.g. `cialdini-influence`\n\n### Chapters Detected\n| # | Title | Main Frameworks |\n```\n\n---\n\n## Step 4 — Ask purpose (Full Conversion only)\n\nBefore generating, ask the user:\n\n> \"What should this skill help you do? (Pick one or more)\n> 1. Apply the author's frameworks while working\n> 2. Think with the author's mental models\n> 3. Reference specific chapters and concepts\n> 4. All of the above\"\n\nUse the answer to weight what gets highlighted in the SKILL.md Core section.\n\n**Derive `DEPTH` from the answer (no extra prompt):**\n- Answer is **only** option 3 (reference) → `DEPTH=reference` — lean, fast-lookup chapters.\n- Answer includes option 1, 2, or 4 → `DEPTH=study` — deeper chapters with more worked detail, examples, and reasoning.\n\n`DEPTH` and `BOOK_TYPE` together set the per-chapter token budget in Step 7. Do **not** ask a separate \"study vs reference\" question — it is inferred here. (In Modes 2/3, where Step 4 is skipped, default `DEPTH=study`.)\n\n---\n\n## Step 5 — Determine skill name\n\nIf `SKILL_NAME` was provided, use it as the skill slug.\nOtherwise, propose two options and let the user choose:\n- **By author-concept**: `{author-lastname}-{core-concept}` (e.g. `cialdini-influence`, `meadows-systems`)\n- **By title**: lowercase hyphens from book title (e.g. `designing-data-intensive-apps`)\n\nDefault to author-concept format if the book has a strong methodological identity.\n\nChoose the destination skill root (`SKILLS_HOME`). First resolve **scope** from an explicit user request or `BOOK_TO_SKILL_SCOPE`, then probe **host**. A request for project-local/project output selects the project-local row; a request for personal/global output selects the personal row. If neither scope is requested, preserve the established personal default (`~/.agents/skills` for non-Hermes hosts). Do not ask a mandatory scope question solely because project-local roots exist. The selected root may still require host approval before writing.\n\n| Host agent | Personal skill root | Project-local root |\n|---|---|---|\n| **GitHub Copilot CLI** | `~/.agents/skills` (discovered natively) | `.github/skills` → `.claude/skills` → `.agents/skills` |\n| **Amp** | `~/.agents/skills` (discovered natively) | `.agents/skills` |\n| **OpenAI Codex** | `~/.agents/skills` (discovered natively; follows symlinks) | `.agents/skills` |\n| **Hermes Agent** | `$HERMES_HOME/skills/<category>` (defaults to `~/.hermes/skills/<category>`) | `.hermes/skills/<category>` → `.agents/skills` |\n| **Claude Code** | `~/.agents/skills` + symlink from `~/.claude/skills/<skill_name>` | `.claude/skills` |\n| **OpenClaw** | `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills` (active state; `~/.agents/skills` only with default state) | `.agents/skills` → `skills/` |\n\nHermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's `HERMES_HOME` and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run `hermes skills trust <project-root>` after generation and verify discovery with `hermes skills list`; project skills remain unavailable until the project is trusted.\n\nFor OpenClaw, use the active state directory's `skills/` root: `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/`. The shared `~/.agents/skills` compatibility root is discoverable only when `OPENCLAW_STATE_DIR` is unset or the default `~/.openclaw`; with a non-default state, do not claim that OpenClaw will see a shared-root install. Verify discovery with `openclaw skills list` after generation.\n| **OpenCode** | `~/.agents/skills` (discovered natively; `~/.config/opencode/skills` and `~/.claude/skills` are read too) | `.opencode/skills` → `.agents/skills` → `.claude/skills` |\n\nHermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's `HERMES_HOME` and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run `hermes skills trust <project-root>` after generation and verify discovery with `hermes skills list`; project skills remain unavailable until the project is trusted.\n\nOpenCode scans the cross-agent `~/.agents/skills` root natively, so the generated-skill default above already serves it — no symlink and no trust step. It also reads its own managed `~/.config/opencode/skills` root and the Claude compatibility root `~/.claude/skills`; use the OpenCode-managed root only when the user asks for it. Project skills are discovered by walking up from the working directory to the git worktree, so start a new session after generation if the skill does not appear. (`~/.cache/opencode/skills` is the remote-skill download cache from `skills.urls`, not an authoring root — never write a generated skill there.)\n\nSelection rules:\n1. Personal install: set `SKILLS_HOME` to `~/.agents/skills` (create the directory if missing). One exception, so the default does not invent a convention in someone else's house: if `~/.agents/skills` does not exist **and** the host's private root already contains skills, use the private root instead and say why in the report.\n2. **Claude Code does not scan `~/.agents/skills`** — after generation completes, Step 10 links the skill in with `ln -sfn \"$HOME/.agents/skills/<skill_name>\" \"$HOME/.claude/skills/<skill_name>\"`.\n3. **Hermes Agent personal installs use the Hermes row above**, not the cross-agent root, and take no symlink.\n4. If the user explicitly asks for a host-private root (`~/.copilot/skills`, `~/.claude/skills`, `~/.config/agents/skills`, `~/.config/amp/skills`), honor it and skip the symlink.\n5. If the user explicitly asked for project-local output, use the project-local row for their host.\n6. If the choice requires knowing the host (project-local output, the Hermes personal root, the OpenClaw state root, or the Claude Code symlink) and you cannot identify it, ask: \"Which agent are you running in — OpenCode, OpenClaw, Hermes Agent, GitHub Copilot CLI, Amp, Codex, or Claude Code?\"\n7. For OpenClaw personal output, use `${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/skills`. The shared `~/.agents/skills` root is a valid OpenClaw destination only when `OPENCLAW_STATE_DIR` is unset or equals the default `$HOME/.openclaw`; otherwise use the active state root or a project/extra directory.\n8. If the user explicitly asks for an OpenClaw-managed personal root, use the active state root and verify discovery with `openclaw skills list`.\n\nSet `SKILLS_HOME` to the selected root and check if `$SKILLS_HOME/<skill_name>/` already exists. On Claude Code, also check whether `~/.claude/skills/<skill_name>` exists as a **real directory** (not a symlink) — a previous install may live there; if so, offer to migrate it (move the directory into `~/.agents/skills/` and replace the original path with the symlink) before continuing.\nIf the skill already exists, prompt the user to choose:\n1. **Update / Fold-in** (Mode 4) — integrate new files/content into the existing skill components.\n2. **Overwrite** — delete and regenerate the skill from scratch.\n3. **Rename** — append `-2` or use a different custom slug.\n\nIf the user selects **Update / Fold-in**, proceed immediately to the **Update / Fold-in Workflow** section after Step 2.5 (skipping Steps 3, 4, 6, 7, 8, 9).\n\n---\n\n## Step 6 — Create skill directory structure\n\n```bash\nmkdir -p \"$SKILLS_HOME/<skill_name>/chapters\"\n```\n\n---\n\n## Step 7 — Generate chapter summaries\n\n**TOKEN BUDGET RULE — CRITICAL (adaptive):**\n\nThe per-chapter budget scales with `BOOK_TYPE` and `DEPTH`. Technical chapters need room for code and tables; study depth needs room for worked reasoning. Pick the budget from this matrix:\n\n| | `DEPTH=reference` | `DEPTH=study` |\n|---|---|---|\n| `BOOK_TYPE=text` | 800–1,200 tokens | 1,000–1,800 tokens |\n| `BOOK_TYPE=technical` | 1,200–1,800 tokens | 2,000–3,000 tokens |\n\n- These are per-file targets, not hard caps — a dense chapter may run over, a thin one under. Density still beats length (Quality Rule #3): never pad to hit a number.\n- Files are loaded on-demand, so a larger chapter only costs tokens when that chapter is actually read.\n- When in doubt between two cells (e.g. mixed-content book), use the lower budget and let depth come from precision, not volume.\n\n**`DEPTH=study` is earned with content, not a bigger number.** The standard section template (Core Idea → Connects To) naturally lands a dense prose chapter around 700–900 tokens. To reach the study budget *honestly* — not by padding — a study-depth chapter must add concrete material:\n- **Reproduce one worked example or artifact** from the chapter (e.g. the example press release, a sample dialogue, a filled-in template, a decision the author walks through) under a `## Worked Example` section. This is the single biggest lever and the main thing a learner returns for.\n- **Expand the \"How\" of each framework** into explicit steps or criteria, not a one-liner.\n- **Add a short \"Why it works / failure mode\" note** to the top 1–2 frameworks.\n\nIf a chapter genuinely has no worked example and resists expansion, let it land below the study floor rather than padding — and note that the chapter is thin in its Core Idea. A `reference`-depth chapter, by contrast, deliberately omits worked examples and keeps only the decision-ready essentials.\n\nFor EACH chapter/major section identified in Step 3:\n\nRead the corresponding section of the extracted `full_text.txt` (use character offsets or grep for chapter headings).\n\nCreate `$SKILLS_HOME/<skill_name>/chapters/ch<NN>-<slug>.md` using the structure below.\n\n**Adapt emphasis based on `BOOK_TYPE`:**\n- `technical` → prioritize \"Code Examples\", \"Reference Tables\", and \"Commands & APIs\" sections; preserve exact syntax\n- `text` → prioritize \"Frameworks Introduced\", \"Mental Models\", and \"Key Takeaways\"; skip empty technical sections\n\n```markdown\n# Chapter N: <Full Title>\n\n## Core Idea\n<1–2 sentences: the single most important thing this chapter teaches>\n\n## Frameworks Introduced\n- **<Framework Name>**: <exact formulation — preserve the author's naming>\n  - When to use: <specific situation>\n  - How: <steps or criteria>\n\n## Key Concepts\n- **<Term>**: <precise definition in 1 sentence>\n(5–10 most important terms from this chapter)\n\n## Mental Models\n<2–4 frameworks or thinking tools. Write as \"Use X when Y\" or \"Think of X as Y\">\n\n## Anti-patterns\n- **<What to avoid>**: <why it fails>\n\n## Code Examples *(technical books only — omit if BOOK_TYPE=text)*\n<!-- Copy the most instructive snippet from the chapter. Preserve indentation exactly. -->\n```<language>\n<key code example from this chapter>\n```\n- **What it demonstrates**: <one line>\n\n## Reference Tables *(technical books only — omit if BOOK_TYPE=text)*\n<!-- Reproduce any comparison matrix, parameter table, or decision table from the chapter in markdown. -->\n\n## Worked Example *(DEPTH=study only — omit for DEPTH=reference)*\n<!-- Reproduce or reconstruct one concrete example the author works through: a\n     sample document, a dialogue, a filled-in template, a before/after, or a\n     decision walked end-to-end. This is what makes a study chapter worth its\n     budget. Keep it faithful to the source; never copy long raw passages —\n     reconstruct the example compactly. -->\n\n## Key Takeaways\n1. <Actionable insight>\n2. <Actionable insight>\n3. <Actionable insight>\n(3–7 takeaways a practitioner must remember)\n\n## Connects To\n- **Ch N**: <why this chapter relates>\n- **<Concept>**: <external concept or standard it connects with>\n```\n\n---\n\n## Step 8 — Generate supporting files\n\n### glossary.md\nCreate `$SKILLS_HOME/<skill_name>/glossary.md`:\n- Every significant term from the book, alphabetically sorted\n- Format: `**Term** — definition (Ch N)`\n- Max 1,500 tokens\n\n### patterns.md\nCreate `$SKILLS_HOME/<skill_name>/patterns.md`:\n- All concrete techniques, design patterns, algorithms from the book\n- Format: `## Pattern Name\\n**When to use**: ...\\n**How**: ...\\n**Trade-offs**: ...`\n- Max 2,000 tokens\n\n### cheatsheet.md\nCreate `$SKILLS_HOME/<skill_name>/cheatsheet.md`:\n\n**This is the most differentiated layer of the skill — treat it as a reasoning aid, not a keyword list.** Anyone can grep the glossary for a term. The cheatsheet captures the author's *judgment*: the decisions they'd make and why. It's the file that turns \"I know the words\" into \"I'd act the way the author would\".\n\nPrioritize, in order:\n1. **Decision rules** — \"When X, do Y, because Z.\" The if/then logic the author applies, stated so the reader can apply it without re-reading the book.\n2. **Decision trees / flowcharts** (as nested bullets or a small table) — for choices with more than two branches.\n3. **Trade-off matrices** — competing options scored on the dimensions the author cares about, so the reader can pick under their own constraints.\n4. **Thresholds & defaults** — the specific numbers, ratios, or rules of thumb the author commits to (e.g. \"keep functions under ~20 lines\", \"alert when error budget < 10%\").\n5. **Tells & smells** — fast heuristics for recognizing a situation (\"if you see X, you're probably in trouble Y\").\n\nAvoid: bare term→definition rows (that's the glossary), and prose paragraphs (that's the chapters). Every line should help the reader *decide* something.\n\n- Format mostly as compact tables and decision rules; the content you'd want on a single printed page kept beside you while working.\n- Max 1,200 tokens.\n\n---\n\n## Step 9 — Generate the master SKILL.md\n\n**CRITICAL TOKEN BUDGET: Keep SKILL.md body under 4,000 tokens.**\nCompaction truncates from the END — put the most important content FIRST.\n\nCreate `$SKILLS_HOME/<skill_name>/SKILL.md`:\n\n```markdown\n---\nname: <skill_name>\ndescription: \"Knowledge base from \\\"<Full Title>\\\" by <Author(s)>. Use when applying <author>'s frameworks for <key topics, 3–6 terms>, studying the book, or referencing its concepts.\"\n---\n\n<!-- argument-hint: [topic, framework name, or chapter number] -->\n\n# <Full Title>\n**Author**: <Author(s)> | **Pages**: ~<N> | **Chapters**: <N> | **Generated**: <YYYY-MM-DD>\n\n## How to Use This Skill\n\n- **Without arguments** — load core frameworks for reference\n- **With a topic** — ask about `replication`, `pricing`, or another indexed topic; I find and read the relevant chapter\n- **With chapter** — ask for `ch05`; I load that specific chapter\n- **Browse** — ask \"what chapters do you have?\" to see the full index\n\nWhen you ask about a topic not covered in Core Frameworks below, I will read\nthe relevant chapter file before answering.\n\n---\n\n## Core Frameworks & Mental Models\n<!-- ~2,000 tokens: the author's most important named frameworks and principles.\n     Preserve exact names. Write as \"Use X when Y\", \"Prefer X over Y because Z\".\n     This is a toolkit, not a summary. -->\n\n<generate 2,000 tokens of the most critical frameworks and insights here>\n\n---\n\n## Chapter Index\n\n| # | Title | Key Frameworks |\n|---|-------|----------------|\n| [ch01](chapters/ch01-<slug>.md) | <Title> | <framework1>, <framework2> |\n| [ch02](chapters/ch02-<slug>.md) | <Title> | <framework1>, <framework2> |\n...\n\n## Topic Index\n\n<!-- Alphabetical. Major terms/frameworks → chapter(s) that cover them. -->\n- **<Term>** → ch<N>[, ch<N>]\n- **<Term>** → ch<N>\n\n## Supporting Files\n\n- [glossary.md](glossary.md) — all key terms with definitions\n- [patterns.md](patterns.md) — all techniques and design patterns\n- [cheatsheet.md](cheatsheet.md) — quick reference tables and decision guides\n\n---\n\n## Scope & Limits\n\nThis skill covers the book content only. For hands-on implementation in your codebase,\ncombine with project-specific tools. For topics beyond this book, check related skills\nor ask the agent directly.\n<if images_dropped > 5: state that N source images were not read>\n```\n\n---\n\n## Step 9.5 — Scan the generated skill\n\nBefore reporting success, loading the skill in another session, or publishing it, run the advisory security scan:\n\n```bash\nSKILL_CONVERTER_ROOT=\"$(cd \"$(dirname \"$SCRIPT_PATH\")/..\" && pwd)\"\n\"$PYTHON_BIN\" \"$SKILL_CONVERTER_ROOT/tools/scan_generated_skill.py\" \"$SKILLS_HOME/<skill_name>\"\n```\n\nIf the scanner exits non-zero, stop and ask a human to review its file/line findings. Do not silently rewrite the generated files, and do not load or publish the skill until the findings are resolved or explicitly accepted.\n\n---\n\n## Step 10 — Cleanup and report\n\nIf the host is Claude Code and `SKILLS_HOME` is `~/.agents/skills` (the default personal install), expose the skill to Claude Code with a symlink — Claude Code only scans `~/.claude/skills`:\n\n```bash\nmkdir -p \"$HOME/.claude/skills\"\nLINK=\"$HOME/.claude/skills/<skill_name>\"\nTARGET=\"$HOME/.agents/skills/<skill_name>\"\nif [ -d \"$LINK\" ] && [ ! -L \"$LINK\" ]; then\n  CLAUDE_STATUS=\"skipped-realdir\"                 # Step 5 migration declined; leave the old dir\nelse\n  ln -sfn \"$TARGET\" \"$LINK\" 2>/dev/null || true\n  # Read the link back — do NOT trust that `ln` did what was asked. On Windows/MSYS\n  # `ln -s` may COPY instead of link (or need Developer Mode / an elevated shell), and\n  # PowerShell/cmd have no `ln` at all. The report must reflect what is on disk, not the\n  # fact that the command ran.\n  if [ -L \"$LINK\" ] && [ \"$(readlink \"$LINK\")\" = \"$TARGET\" ]; then\n    CLAUDE_STATUS=\"linked\"\n  elif [ -e \"$LINK\" ]; then\n    CLAUDE_STATUS=\"copy\"                           # a real file/dir landed instead of a link\n  else\n    CLAUDE_STATUS=\"absent\"                         # ln unavailable or refused\n  fi\nfi\n```\n\nThe real-directory guard is required: `ln -sfn` into an existing real directory would nest the link *inside* it (`~/.claude/skills/<skill_name>/<skill_name>`), leaving Claude Code loading the stale copy. If the user declined the Step 5 migration, skip the symlink and say so in the report — Claude Code keeps using the old directory until it is migrated.\n\n**Read the link back before you report anything about it.** The symlink is a claim, not a fact: fill the \"Discoverable by\" line from `CLAUDE_STATUS` (what is actually on disk), never from \"the command was issued\". **Do not hard-fail when the link is missing or is a copy** — the skill exists at the hub and every other host still finds it; the honest report is \"written to `~/.agents/skills/<skill_name>`; Claude Code will not see it until the link is created\", not an abort. (Windows lead, unverified: a directory junction — `mklink /J` in an elevated `cmd`, or `New-Item -ItemType Junction` in PowerShell — needs neither Developer Mode nor a symlink privilege; if you attempt it, it does not change the read-back-then-report rule.)\n\nSkip this when the user chose a host-private or project-local root (Step 5, rules 3-4).\n\nThen clean up the extraction workdir:\n\n```bash\nPYTHON_BIN=\"${PYTHON_BIN:-python3}\"\nif ! command -v \"$PYTHON_BIN\" >/dev/null 2>&1; then\n  PYTHON_BIN=\"python\"\nfi\n\nRemove **the work directory this run actually used** — the `Workdir ->` path from the\nextraction output, which is also stored as `workdir` in `metadata.json`. Never delete a\ndirectory you did not create: another extraction may be running beside yours.\n\n```bash\n# WORKDIR is the path this run reported; quote it in case of spaces.\nrm -rf \"$WORKDIR\"\n```\n\nEquivalently, if you still have the metadata file:\n\n```bash\n\"$PYTHON_BIN\" - \"$WORKDIR_METADATA_JSON\" <<'PY'\nimport json\nimport shutil\nimport sys\nfrom pathlib import Path\n\nmeta_path = Path(sys.argv[1])\nworkdir = json.loads(meta_path.read_text(encoding=\"utf-8\")).get(\"workdir\")\nif workdir:\n    shutil.rmtree(workdir, ignore_errors=True)\nPY\n```\n\nOlder copies of this file removed a single fixed `book_skill_work` directory. That path is\nno longer used, so such a cleanup is now a harmless no-op rather than something that could\ndelete a concurrent run's output.\n\nThen report to the user:\n\n```\n✅ Skill created: $SKILLS_HOME/<skill_name>/\n\n📚 Book: <Full Title> — <Author>\n📄 Pages: ~<N> | Chapters: <N>\n\nFiles generated:\n  SKILL.md         — core frameworks + index   (~X tokens)\n  chapters/        — <N> chapter summaries     (~X tokens each, ~X total)\n  glossary.md      — key terms                 (~X tokens)\n  patterns.md      — techniques & patterns     (~X tokens)\n  cheatsheet.md    — quick reference           (~X tokens)\n  ─────────────────────────────────────────────────────\n  Total skill size: ~X tokens (loaded on-demand, not all at once)\n\n💡 Tip: check your agent's session cost/usage command to see actual token usage.\n\nUsage:\n  Ask for <skill_name>                  → load core frameworks\n  Ask <skill_name> about <topic>        → find and explain a topic\n  Ask <skill_name> for ch<N>            → dive into a specific chapter\n\nDiscoverable by: <only what is true for the chosen destination — see below>\n\nSomewhere else?  mv ~/.agents/skills/<skill_name> <dest_root>/<skill_name> \\\n                   && ln -sfn <dest_root>/<skill_name> ~/.claude/skills/<skill_name>\n\nPrompted for permission on every file? That is your host gating writes outside the\nworking directory. Say \"save it in this project\" and re-run to write inside it.\n\nReload (if your agent doesn't auto-detect new skills):\n  GitHub Copilot CLI:  /skills reload\n  Claude Code:         restart the session\n  Amp:                 restart the session\n  Hermes Agent:         start a new session\n  OpenClaw:             openclaw skills list (new session if watcher disabled)\n  OpenCode:             start a new session\n\nShare this skill (optional):\n  GitHub repo, installable on any host (Step 11):  say \"publish\"\n  Copilot ecosystem:  gh skill publish $SKILLS_HOME/<skill_name>\n```\n\nFill the \"Discoverable by\" line from `CLAUDE_STATUS` (the read-back result), never from the fact that `ln` ran — for `~/.agents/skills` installs:\n- `linked` → \"Copilot CLI, Amp, Codex (natively); Claude Code via symlink ~/.claude/skills/<skill_name>\"\n- `skipped-realdir` → \"Copilot CLI, Amp, Codex (natively); **NOT** Claude Code — migrate the real directory at ~/.claude/skills/<skill_name> first\"\n- `copy` or `absent` → \"Copilot CLI, Amp, Codex (natively); **NOT** Claude Code — the host could not create the symlink (a plain copy drifts on the next Update/Fold-in). Enable Developer Mode / create the link manually, or run the skill from ~/.agents/skills\"\n- Hermes Agent personal root → \"Hermes Agent (from `$HERMES_HOME/skills/<category>`)\"; no symlink claim, and no cross-agent claim, because the other hosts do not scan the Hermes root\n- other host-private or project-local root → name only the host(s) that scan that root; no symlink claim\n\nThe \"Somewhere else?\" relocation line must be correct for the path actually taken, so it never breaks the symlink the run just created. **`mv` always targets the final skill directory, `<dest_root>/<skill_name>`, never `<dest_root>` itself.** `mv ~/.agents/skills/mybook ~/.copilot/skills && ln -sfn ~/.copilot/skills ~/.claude/skills/mybook` reads as valid and is not: the skill lands at `~/.copilot/skills/mybook` while the link points one level up at the root, so Claude Code resolves to a directory with no `SKILL.md`, which is the exact breakage this line exists to avoid. Substitute the destination the user actually named, so the printed command carries real paths and there is nothing left to interpret:\n- `~/.agents/skills` + symlink → `mv ~/.agents/skills/<skill_name> <dest_root>/<skill_name> && ln -sfn <dest_root>/<skill_name> ~/.claude/skills/<skill_name>`\n- host-private root, Hermes Agent included → `mv <src_root>/<skill_name> <dest_root>/<skill_name>`\n- project-local root → `mv <project_root>/<skill_name> <dest_root>/<skill_name>`\n\nThe \"Prompted for permission on every file?\" line is the answer to a host that gates writes outside the working directory (any personal-scope root is out-of-cwd): the destination was announced above, and the one-line fix — re-run asking for the project-local root — sits next to it. Keep it only for personal-scope installs; drop it when the user already chose project-local.\n\n---\n\n## Step 11 — Publish the generated skill to GitHub (optional)\n\nAfter the Step 10 report, offer once — and only if the Step 9.5 scan passed:\n\n> \"Want me to publish this skill to GitHub so any Agent Skills host can install it with `npx skills add`? (yes / skip)\"\n\nIf the user declines, stop here. Requirements: the `gh` CLI, authenticated (check `gh auth status`). If `gh` is missing or unauthenticated, offer to set it up (`brew install gh` or https://cli.github.com, then `gh auth login`) — or use the no-`gh` path: the user creates an empty repo of the chosen visibility in the GitHub web UI, then you run the `git init`/`add`/`commit` commands below followed by `git remote add origin <repo-url> && git push -u origin main`. The visibility rule below applies to the web-created repo exactly the same.\n\n**Visibility is a separate closed question — never inferred, never read out of an earlier answer.** Once the user accepts, ask it on its own and require a one-word reply:\n\n> \"Private or public repository? Reply with one word: `private` or `public`.\"\n\n**The reply must *be* `public`, not merely contain it — a hard rule, not a suggestion.** Run `gh repo create` with `--private` in every case except one: the answer to the visibility question is the bare word `public`. Substring matching is forbidden, because a sentence about **the source's licence is not a visibility answer** — \"it's public domain\", \"the book is public domain\", \"it's publicly available\" all describe the material, not the repository, and all resolve to `--private`. A paraphrase, a sentence, an ambiguous answer, silence, or your own inference is NOT consent: re-ask once, and if the reply is still not the bare word, use `--private` and say so in the report. A private repo can be flipped public later; a public push of book-derived content cannot be un-published.\n\n**Copyright gate — always apply before creating the repo:** chapter files are synthesized summaries, not raw text, but they still derive from the source material. Per the README's Copyright & fair use policy, skills generated from **third-party copyrighted books must stay private**; offer public only when the source is the user's own writing, openly licensed content, or material the user explicitly confirms they are authorized to redistribute publicly — and state which case applies. Having access to internal company material is not permission to disclose it: skills from internal docs stay **private** unless the user states they hold publication rights.\n\nIf accepted:\n\n1. Add a repo `README.md` inside `$SKILLS_HOME/<skill_name>/` (never overwrite an existing file) — the skill title, a one-paragraph description (\"Agent skill generated from *<Title>* by <Author> with [book-to-skill](https://github.com/virgiliojr94/book-to-skill)\"), the install command from step 3 below, the file inventory, and a note that the content is synthesized summaries, not the book text.\n2. Initialize the skill folder as a git repository and create the remote (default repo name `<skill_name>`; let the user override — some prefer a `<skill_name>-skill` suffix). **Nested-repo guard:** first check whether the skill folder already sits inside a git repository (`git -C \"$SKILLS_HOME/<skill_name>\" rev-parse --show-toplevel` — always the case for project-local roots like `.claude/skills/`). If it does, do NOT `git init` in place: the outer repository would record the folder as an embedded repo (gitlink, mode 160000) without `.gitmodules`, and fresh clones of the outer project would silently omit the skill. Instead, copy the skill folder to a scratch directory, run the commands below from the copy, and tell the user the published repo — not the project-local folder — is the remote's working copy.\n\n```bash\ncd \"$SKILLS_HOME/<skill_name>\"\ngit init -b main\ngit add -A\ngit commit -m \"Add <skill_name> skill\"\ngh repo create <repo_name> --private --source . --push\n# --private is the default; substitute --public ONLY under the visibility rule above\n# (the visibility answer WAS the bare word \"public\" AND the copyright gate allows it)\n```\n\n3. Report the repo URL and the cross-host install command:\n\n```\n✅ Published: https://github.com/<owner>/<repo_name> (<private|public>)\n\nInstall on any Agent Skills host:\n  npx skills add https://github.com/<owner>/<repo_name> --skill <skill_name>\n```\n\n   When the nested-repo guard fired and the repo was published from a scratch copy, add one line — that local folder never gains a remote, so the Update/Fold-in push offer will never appear for it:\n\n```\n⚠️  Published from a copy: <skill folder> sits inside another git repository, so it has\n    no remote of its own. To publish a later update, re-run Step 11, or clone\n    https://github.com/<owner>/<repo_name> and fold new material into the clone.\n```\n\nThe root-level `SKILL.md` layout is exactly what the `skills` CLI detects, so the repo is installable as-is — no restructuring needed. Outside the nested-repo case the local folder stays the live install for this machine and is the remote's working copy, so later Update/Fold-in runs can commit and push their changes to the same remote.\n\n---\n\n## Update / Fold-in Workflow\n\nWh","docs/ARCHITECTURE.md":"---\ndescription: \"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.\"\nseo_title: \"Architecture - How book-to-skill Extracts and Generates\"\n---\n\n# Architecture\n\nbook-to-skill has two halves: a **deterministic extractor** (Python) and a\n**spec-driven generator** (the agent following `SKILL.md`). The extractor turns any\ndocument into clean text + metadata; the agent turns that into a structured skill.\n\n```\n            ┌─────────────────────────── EXTRACTOR (Python, deterministic) ──┐\n documents  │  scripts/extract.py (shim)  →  book_to_skill/                   │\n (pdf/epub/ │    ├─ cli.py · utils.py   CLI parse · multi-source · runner     │\n  docx/...) │    ├─ config.py           supported extensions · paths · deps   │\n     │      │    ├─ dependencies.py     optional-dep probing · --check report │\n     ▼      │    ├─ sanitize.py         strip invisible/zero-width Unicode    │\n ───────────│    └─ parsers/            pdf · epub · docx · html · rtf ·      │\n            │                             calibre · text (best tool, fallback)│\n            │  output → <tempdir>/book_skill_work/                            │\n            │    full_text.txt   (all sources merged, source-marked)          │\n            │    metadata.json   (pages, words, tokens, chapters, ToC)        │\n            └────────────────────────────────────────────────────────────────┘\n                                   │\n                                   ▼\n            ┌─────────────────────────── GENERATOR (agent, follows SKILL.md) ┐\n            │  Step 1.5  ask content type → BOOK_TYPE (technical | text)      │\n            │  Step 2/2.5 extract · cost estimate · confirm                   │\n            │  Step 2.6  REPL-style probing for large books (grep/sed, no     │\n            │            full re-reads)                                        │\n            │  Step 3    analyze structure (title, author, chapters, ToC)     │\n            │  Step 4    purpose → DEPTH (reference | study)                   │\n            │  Step 7    per-chapter summaries (budget = BOOK_TYPE × DEPTH)    │\n            │  Step 8    glossary · patterns · cheatsheet (decision layer)    │\n            │  Step 9/9.5 SKILL.md core + indexes                             │\n            └────────────────────────────────────────────────────────────────┘\n                                   │\n                                   ▼\n                 <SKILLS_HOME>/<slug>/  ← chosen per host + scope:\n                  ~/.copilot/skills/   GitHub Copilot CLI (personal)\n                  ~/.agents/skills/    Copilot CLI or Amp (cross-agent, personal)\n                  ~/.claude/skills/    Claude Code (personal)\n                  ~/.openclaw/skills/  OpenClaw (personal, default state only)\n                  $HERMES_HOME/skills/<category>/  Hermes Agent (personal)\n                  .github/skills/ | .claude/skills/ | .agents/skills/  project-local (any host)\n                  .hermes/skills/<category>/                         Hermes project-local\n                  skills/                                                   OpenClaw workspace\n                  ~/.config/opencode/skills/                         OpenCode (own root; on request)\n                  .opencode/skills/                                  OpenCode project-local\n                  scope: personal vs project-local (Step 5, BOOK_TO_SKILL_SCOPE)\n                  SKILL.md         core frameworks + chapter & topic index (~4K)\n                  chapters/*.md    on-demand, loaded only when asked\n                  glossary.md      terms\n                  patterns.md      techniques\n                  cheatsheet.md    decision rules / trees / trade-offs / tells\n```\n\nOpenClaw's shared `~/.agents/skills` compatibility root is discoverable only\nwhen `OPENCLAW_STATE_DIR` is unset or uses the default `~/.openclaw`. A\nnon-default state uses its active `<state-dir>/skills` root instead; the\nconverter does not infer arbitrary `skills.load.extraDirs`.\n\n## Design principles\n\n1. **Extract structure, not summaries** — named frameworks, decision rules,\n   anti-patterns; never raw passages.\n2. **Compile-time over runtime** — pay navigation/structuring once; at query time\n   load only the relevant chapter. See [Performance & Cost](performance.md).\n3. **On-demand chapters** — `SKILL.md` stays small; chapter files cost tokens only\n   when read.\n4. **Front-loaded `SKILL.md`** — most important content first (compaction truncates\n   from the end).\n5. **Graceful degradation** — every format has a stdlib fallback; one bad source is\n   skipped, not fatal.\n\n## Key components\n\n| Path | Responsibility |\n|------|----------------|\n| `scripts/extract.py` | thin entrypoint shim → `book_to_skill.cli` (kept so old invocations keep working) |\n| `book_to_skill/cli.py`, `utils.py` | CLI parsing, multi-source resolution, chapter/ToC detection, runner |\n| `book_to_skill/parsers/` | one module per format (`pdf`, `epub`, `docx`, `html`, `rtf`, `calibre`, `text`) |\n| `book_to_skill/config.py` | supported extensions, output paths, dependency map |\n| `book_to_skill/dependencies.py` | optional-dependency probing + `--check` |\n| `book_to_skill/sanitize.py` | strips zero-width / Unicode-tag-block characters from extracted text, and folds Kangxi radicals to their ideographs (see Security) |\n| `tools/discovery_tax.py` | measures token cost vs context-dump / discovery loop |\n| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\\|copilot\\|amp\\|hermes\\|openclaw`) |\n| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\\|copilot\\|amp\\|hermes\\|opencode`) |\n| `tools/scan_generated_skill.py` | advisory prompt-injection scan of a generated skill (see Security) |\n| `SKILL.md` | the generator spec (Steps 0–10 + fold-in workflow) |\n\n## Security\n\nUntrusted documents flow into an agent's context and then into a generated skill\nthat later loads into other agents — a document→context supply chain. The hardening\nis layered:\n\n- **Extraction sanitization** (`book_to_skill/sanitize.py`) — strips zero-width\n  (`U+200B/200C/200D/2060/FEFF`) and the Unicode tag block (`U+E0000–E007F`) from\n  every parser's output before metrics or `full_text.txt`, so invisible\n  document-borne instructions never reach the agent. Reports the removal count;\n  rejects a source with no visible content left.\n- **CJK radical folding** (`book_to_skill/sanitize.py`) — PDFs whose embedded\n  font subset carries no correct Unicode mapping emit a Kangxi radical\n  (`U+2F00–U+2FDF`) where a character belongs, so a Chinese book extracts\n  \"判断⼒\" for \"判断力\". Each radical in that block has a Unicode compatibility\n  decomposition, so the fold is NFKC applied to those code points only: the rest\n  of the text is passed through byte for byte. Nothing is removed, so it does\n  not count toward the security removal total. CJK Radicals Supplement\n  (`U+2E80–U+2EF3`) is deliberately not folded — Unicode defines no\n  decomposition for it, and some entries are ordinary kanji.\n- **DOCX XXE / Billion-Laughs guard** (`parsers/docx.py`) — rejects any XML part\n  declaring a DTD or entities before parsing.\n- **Subprocess argument-injection** — file paths are absolutised before reaching\n  `pdftotext` / `pdfinfo` / `ebook-convert`, so a `-`-leading filename can't be read\n  as a flag.\n- **Generated-skill scan** (`tools/scan_generated_skill.py`) — an advisory step in\n  the generator (Step 9.5) that flags instruction-override phrases, model-control\n  tags, residual invisible Unicode, authority-widening frontmatter, and\n  exfiltration-shaped content across the generated `SKILL.md`, `chapters/*.md`,\n  `glossary.md`, `patterns.md`, and `cheatsheet.md`. Findings name only the rule and\n  file location — never the matched text.\n- **CI** — CodeQL, Bandit (gate on HIGH), Zizmor, and dependency CVE review on PRs.\n\n## Extending\n\n- **New format** → add `book_to_skill/parsers/<fmt>.py`, register its extension in\n  `config.py`, wire dependency probing in `dependencies.py`, branch in\n  `utils.extract_single_file`.\n- **New generation behavior** → edit the relevant Step in `SKILL.md`; keep it lean\n  and back the change with evidence (see CONTRIBUTING.md).\n"},"items":[{"name":"ARCHITECTURE.md","path":"docs/ARCHITECTURE.md","rawUrl":"https://raw.githubusercontent.com/virgiliojr94/book-to-skill/HEAD/docs/ARCHITECTURE.md","title":"docs - System Architecture & Module Boundaries","category":"project-spec","format":"markdown","content":"---\ndescription: \"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.\"\nseo_title: \"Architecture - How book-to-skill Extracts and Generates\"\n---\n\n# Architecture\n\nbook-to-skill has two halves: a **deterministic extractor** (Python) and a\n**spec-driven generator** (the agent following `SKILL.md`). The extractor turns any\ndocument into clean text + metadata; the agent turns that into a structured skill.\n\n```\n            ┌─────────────────────────── EXTRACTOR (Python, deterministic) ──┐\n documents  │  scripts/extract.py (shim)  →  book_to_skill/                   │\n (pdf/epub/ │    ├─ cli.py · utils.py   CLI parse · multi-source · runner     │\n  docx/...) │    ├─ config.py           supported extensions · paths · deps   │\n     │      │    ├─ dependencies.py     optional-dep probing · --check report │\n     ▼      │    ├─ sanitize.py         strip invisible/zero-width Unicode    │\n ───────────│    └─ parsers/            pdf · epub · docx · html · rtf ·      │\n            │                             calibre · text (best tool, fallback)│\n            │  output → <tempdir>/book_skill_work/                            │\n            │    full_text.txt   (all sources merged, source-marked)          │\n            │    metadata.json   (pages, words, tokens, chapters, ToC)        │\n            └────────────────────────────────────────────────────────────────┘\n                                   │\n                                   ▼\n            ┌─────────────────────────── GENERATOR (agent, follows SKILL.md) ┐\n            │  Step 1.5  ask content type → BOOK_TYPE (technical | text)      │\n            │  Step 2/2.5 extract · cost estimate · confirm                   │\n            │  Step 2.6  REPL-style probing for large books (grep/sed, no     │\n            │            full re-reads)                                        │\n            │  Step 3    analyze structure (title, author, chapters, ToC)     │\n            │  Step 4    purpose → DEPTH (reference | study)                   │\n            │  Step 7    per-chapter summaries (budget = BOOK_TYPE × DEPTH)    │\n            │  Step 8    glossary · patterns · cheatsheet (decision layer)    │\n            │  Step 9/9.5 SKILL.md core + indexes                             │\n            └────────────────────────────────────────────────────────────────┘\n                                   │\n                                   ▼\n                 <SKILLS_HOME>/<slug>/  ← chosen per host + scope:\n                  ~/.copilot/skills/   GitHub Copilot CLI (personal)\n                  ~/.agents/skills/    Copilot CLI or Amp (cross-agent, personal)\n                  ~/.claude/skills/    Claude Code (personal)\n                  ~/.openclaw/skills/  OpenClaw (personal, default state only)\n                  $HERMES_HOME/skills/<category>/  Hermes Agent (personal)\n                  .github/skills/ | .claude/skills/ | .agents/skills/  project-local (any host)\n                  .hermes/skills/<category>/                         Hermes project-local\n                  skills/                                                   OpenClaw workspace\n                  ~/.config/opencode/skills/                         OpenCode (own root; on request)\n                  .opencode/skills/                                  OpenCode project-local\n                  scope: personal vs project-local (Step 5, BOOK_TO_SKILL_SCOPE)\n                  SKILL.md         core frameworks + chapter & topic index (~4K)\n                  chapters/*.md    on-demand, loaded only when asked\n                  glossary.md      terms\n                  patterns.md      techniques\n                  cheatsheet.md    decision rules / trees / trade-offs / tells\n```\n\nOpenClaw's shared `~/.agents/skills` compatibility root is discoverable only\nwhen `OPENCLAW_STATE_DIR` is unset or uses the default `~/.openclaw`. A\nnon-default state uses its active `<state-dir>/skills` root instead; the\nconverter does not infer arbitrary `skills.load.extraDirs`.\n\n## Design principles\n\n1. **Extract structure, not summaries** — named frameworks, decision rules,\n   anti-patterns; never raw passages.\n2. **Compile-time over runtime** — pay navigation/structuring once; at query time\n   load only the relevant chapter. See [Performance & Cost](performance.md).\n3. **On-demand chapters** — `SKILL.md` stays small; chapter files cost tokens only\n   when read.\n4. **Front-loaded `SKILL.md`** — most important content first (compaction truncates\n   from the end).\n5. **Graceful degradation** — every format has a stdlib fallback; one bad source is\n   skipped, not fatal.\n\n## Key components\n\n| Path | Responsibility |\n|------|----------------|\n| `scripts/extract.py` | thin entrypoint shim → `book_to_skill.cli` (kept so old invocations keep working) |\n| `book_to_skill/cli.py`, `utils.py` | CLI parsing, multi-source resolution, chapter/ToC detection, runner |\n| `book_to_skill/parsers/` | one module per format (`pdf`, `epub`, `docx`, `html`, `rtf`, `calibre`, `text`) |\n| `book_to_skill/config.py` | supported extensions, output paths, dependency map |\n| `book_to_skill/dependencies.py` | optional-dependency probing + `--check` |\n| `book_to_skill/sanitize.py` | strips zero-width / Unicode-tag-block characters from extracted text, and folds Kangxi radicals to their ideographs (see Security) |\n| `tools/discovery_tax.py` | measures token cost vs context-dump / discovery loop |\n| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\\|copilot\\|amp\\|hermes\\|openclaw`) |\n| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\\|copilot\\|amp\\|hermes\\|opencode`) |\n| `tools/scan_generated_skill.py` | advisory prompt-injection scan of a generated skill (see Security) |\n| `SKILL.md` | the generator spec (Steps 0–10 + fold-in workflow) |\n\n## Security\n\nUntrusted documents flow into an agent's context and then into a generated skill\nthat later loads into other agents — a document→context supply chain. The hardening\nis layered:\n\n- **Extraction sanitization** (`book_to_skill/sanitize.py`) — strips zero-width\n  (`U+200B/200C/200D/2060/FEFF`) and the Unicode tag block (`U+E0000–E007F`) from\n  every parser's output before metrics or `full_text.txt`, so invisible\n  document-borne instructions never reach the agent. Reports the removal count;\n  rejects a source with no visible content left.\n- **CJK radical folding** (`book_to_skill/sanitize.py`) — PDFs whose embedded\n  font subset carries no correct Unicode mapping emit a Kangxi radical\n  (`U+2F00–U+2FDF`) where a character belongs, so a Chinese book extracts\n  \"判断⼒\" for \"判断力\". Each radical in that block has a Unicode compatibility\n  decomposition, so the fold is NFKC applied to those code points only: the rest\n  of the text is passed through byte for byte. Nothing is removed, so it does\n  not count toward the security removal total. CJK Radicals Supplement\n  (`U+2E80–U+2EF3`) is deliberately not folded — Unicode defines no\n  decomposition for it, and some entries are ordinary kanji.\n- **DOCX XXE / Billion-Laughs guard** (`parsers/docx.py`) — rejects any XML part\n  declaring a DTD or entities before parsing.\n- **Subprocess argument-injection** — file paths are absolutised before reaching\n  `pdftotext` / `pdfinfo` / `ebook-convert`, so a `-`-leading filename can't be read\n  as a flag.\n- **Generated-skill scan** (`tools/scan_generated_skill.py`) — an advisory step in\n  the generator (Step 9.5) that flags instruction-override phrases, model-control\n  tags, residual invisible Unicode, authority-widening frontmatter, and\n  exfiltration-shaped content across the generated `SKILL.md`, `chapters/*.md`,\n  `glossary.md`, `patterns.md`, and `cheatsheet.md`. Findings name only the rule and\n  file location — never the matched text.\n- **CI** — CodeQL, Bandit (gate on HIGH), Zizmor, and dependency CVE review on PRs.\n\n## Extending\n\n- **New format** → add `book_to_skill/parsers/<fmt>.py`, register its extension in\n  `config.py`, wire dependency probing in `dependencies.py`, branch in\n  `utils.extract_single_file`.\n- **New generation behavior** → edit the relevant Step in `SKILL.md`; keep it lean\n  and back the change with evidence (see CONTRIBUTING.md).\n","frontmatter":{"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"},"isInternal":false,"tokens":2010,"sizeBytes":8923},{"name":"SKILL.md","path":"SKILL.md","rawUrl":"https://raw.githubusercontent.com/virgiliojr94/book-to-skill/HEAD/SKILL.md","title":"book-to-skill","category":"anthropic-skill","format":"markdown","content":"---\nname: book-to-skill\ndescription: \"Converts books and documents (PDF, EPUB, DOCX, HTML, Markdown, plain text, RTF, MOBI/AZW with Calibre) into structured agent skills, extracting frameworks, mental models, principles, techniques, and anti-patterns. Use when the user wants to study a document through GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, or OpenClaw, apply an author's frameworks while working, or build a reusable knowledge base from a file.\"\n---\n\n<!--\nCross-agent notes (informational; ignored by host agents):\n  - Compatible skill roots: GitHub Copilot CLI (~/.copilot/skills, ~/.agents/skills,\n    .github/skills, .claude/skills, .agents/skills), Amp (.agents/skills,\n    ~/.config/agents/skills, ~/.config/amp/skills), Claude Code (~/.claude/skills),\n    Hermes Agent ($HERMES_HOME/skills, .hermes/skills, .agents/skills),\n    OpenClaw (${OPENCLAW_STATE_DIR:-~/.openclaw}/skills, .agents/skills, skills/;\n    ~/.agents/skills only with the default state).\n    OpenCode (~/.config/opencode/skills, ~/.claude/skills, .opencode/skills,\n    ~/.agents/skills, .agents/skills).\n  - `allowed-tools` is intentionally omitted to stay agent-neutral: Copilot CLI uses\n    `shell`/MCP-server names, Claude uses `Bash`/`Read`/`Write`/`Glob`/`Grep`, Amp\n    adds `shell_command`. The skill needs shell (to run extract.py) and file\n    read/write — each host will prompt for those on first use.\n  - Argument hint: <path-to-document-folder-or-glob>... [skill-name-slug]\n-->\n\n# Book-to-Skill Converter\n\nTransform written knowledge into actionable agent skills by extracting structure — not producing summaries.\n\n## Philosophy\n\nBooks contain crystallized expertise: frameworks, principles, and techniques that took years to develop. This skill extracts that knowledge into a format GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, OpenClaw, or another compatible agent can leverage repeatedly.\n**Extract structure, not summaries.** A skill isn't a book report. It's a toolkit of:\n- Named frameworks (mental models with clear application)\n- Actionable principles (rules that guide decisions)\n- Techniques (step-by-step methods)\n- Anti-patterns (what to avoid and why)\n- Voice calibration (how the author thinks and communicates)\n\n**Preserve the author's precision.** Frameworks often have specific names for reasons. \"The 5 Whys\" isn't interchangeable with \"ask why multiple times.\" Capture the exact formulation.\n\n**Layer depth appropriately.** Simple books → simple skills. Complex books with 10+ frameworks → skills with reference files and on-demand chapters.\n\n---\n\n## Modes of Operation\n\nFour paths available. Route based on what the user asks:\n\n### 1. Full Conversion (Default)\n**Trigger:** User provides one or more document/directory/glob paths without special instructions\n**Action:** Run all steps below (Steps 0–9)\n**Output:** Complete skill with SKILL.md, chapters/, glossary, patterns, cheatsheet\n\n### 2. Analyze Only\n**Trigger:** User says \"analyze\", \"just extract\", or \"I want to review before generating\"\n**Action:** Run Steps 0–3, then produce a structured extraction report (frameworks, principles, techniques found). Stop — do NOT generate skill files.\n**Output:** Analysis report for user review\n\n### 3. Generate from Prior Analysis\n**Trigger:** User has existing analysis notes or previously ran analyze-only\n**Action:** Skip Steps 0–3, use the provided analysis as input, run Steps 4–9\n**Output:** Skill files from the provided analysis\n\n### 4. Update / Fold-in (Existing Skill)\n**Trigger:** User provides one or more new source paths and indicates they want to update an existing skill (either by pointing to the existing skill folder, providing a skill slug that already exists in `SKILLS_HOME`, or explicitly requesting an update).\n**Action:** Run Step 0 (out-of-scope check), Step 1 (validate inputs), Step 1.5 (identify book type), and Step 2 (extract new files). Then skip to Step 5 (identify/detect existing skill path) and run the **Update / Fold-in Workflow** to merge the new content into the existing skill files.\n**Output:** Updated existing skill with new/revised chapter summaries and merged indexes/glossaries.\n\n---\n\n## Skill Locations\n\nThis converter can run from multiple skill systems. When looking for this converter's helper script or writing the generated book skill, prefer these locations in order:\n\n1. GitHub Copilot CLI personal skills: `~/.copilot/skills/`\n2. Cross-agent personal skills (Copilot, Amp, Codex; OpenClaw with its default state): `~/.agents/skills/`\n3. Claude Code personal skills: `~/.claude/skills/`\n4. Project-local Copilot skills: `.github/skills/`\n5. Project-local Claude skills: `.claude/skills/`\n6. Project-local Amp / Copilot / OpenClaw skills: `.agents/skills/`\n7. Amp global skills: `~/.config/agents/skills/`\n8. Amp legacy global skills: `~/.config/amp/skills/`\n9. Hermes Agent personal skills: `$HERMES_HOME/skills/` (defaults to `~/.hermes/skills/`)\n10. Hermes Agent project skills: `.hermes/skills/` or `.agents/skills/`\n11. OpenClaw personal skills: `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/` (active state; `~/.agents/skills/` is shared only with the default state)\n12. OpenClaw project skills: `.agents/skills/` or `skills/`\n11. OpenCode personal skills: `~/.config/opencode/skills/` or `~/.agents/skills/`\n12. OpenCode project skills: `.opencode/skills/` or `.agents/skills/`\n\nFor **generated** book skills, prefer the user-level cross-agent root `~/.agents/skills/` — one physical copy serves the cross-agent hosts and OpenClaw when it uses its default state. Copilot CLI and Amp discover it natively; Claude Code needs a symlink from `~/.claude/skills/<skill_name>` (created in Step 10, see Step 5 for the rules). Pick a host-private or project-local root only when the user explicitly asks for one. `BOOK_TO_SKILL_SCOPE=project` or `personal` can make that choice explicit for automation; do not ask a mandatory scope question merely because both scopes are available.\n\n---\n\n## Step 0 — Out-of-scope check\n\nIf no arguments are provided, stop and respond:\n> \"book-to-skill requires a supported document path, folder, or glob pattern. Usage: `book-to-skill <path-to-document-folder-or-glob>... [skill-name-slug]`\"\n\nThroughout the workflow:\n- **Resolve the destination root first.** Before any existing-skill lookup, determine where this run would write: the personal or project-local root for the user's host, per the Step 5 table and selection rules — a project-local request uses its project-local root, never the personal root. Below, \"the resolved root\" means that destination.\n- Identify the input paths and the optional skill slug.\n- If the last argument is not a file, folder, or glob that exists or matches any files, and it looks like a skill slug (e.g. lowercase hyphens, alphanumeric), treat it as `SKILL_NAME`.\n- Treat all other arguments as the list of `INPUT_PATHS`.\n- If any input path is an existing skill directory (contains `SKILL.md` and a `chapters/` sub-folder), or if `SKILL_NAME` matches an existing skill slug in the resolved root, flag this run as an **Update/Fold-in** operation (Mode 4).\n- **Re-run guard.** Before starting extraction for a Full Conversion, derive the prospective skill slug under Step 5's naming options — the author-concept form, the by-title form, and any `SKILL_NAME` given — and check the resolved root for an existing match. On a match, STOP and ask the user: \"`<skill-name>` already exists. Choose: (1) Update/Fold-in (Mode 4), (2) verify the existing skill is complete and stop, or (3) force full regeneration.\" Do not re-extract until the user chooses. On context recovery after an interrupted run (network cut-off, replayed or continued conversation), reuse the extraction work directory that run reported (`Workdir ->` in its output, or the path you set in `BOOK_SKILL_WORKDIR`) **only if it is still intact and still matches the current inputs and options** — the directory exists, its `metadata.json` is readable, the `sources` it lists match the files being converted now one-to-one on filename **and content fingerprint** (the extractor records a per-source `sha256` in `metadata.json`; recompute the fingerprint from each file as it exists now and require an exact match — `reuse_is_safe()` in `book_to_skill/utils.py` is the executable form of this check), and its recorded `extraction_mode` is the mode this run is using (Step 2's \"confirm the extraction is the document you asked for\" rule). If it is missing (temp cleanup), its `metadata.json` is gone, the sources no longer match (a different filename, or a changed content fingerprint), the recorded metadata has no `sha256` for a source (recorded before fingerprints existed, so freshness cannot be established), or the mode differs, say so and start a fresh extraction instead of resuming. When in doubt, ask the user before discarding or resuming.\n\n---\n\n## Step 1 — Validate input\n\nVerify that there is at least one supported file, directory, or glob pattern among the `INPUT_PATHS`.\nFor directories and globs, expand them to find matching supported files (`.pdf`, `.epub`, `.docx`, `.txt`, `.md`, `.markdown`, `.rst`, `.adoc`, `.html`, `.htm`, `.rtf`, `.mobi`, `.azw`, `.azw3`).\n\nIf no supported files are found, stop with a clear error message.\n\n---\n\n## Step 1.5 — Identify content type\n\nBefore extracting, ask the user:\n\n> \"What kind of content do these sources have? This helps me choose the best extraction method.\n>\n> 1. **Technical** — has code blocks, tables, formulas, diagrams (e.g. programming books, academic papers, architecture guides)\n> 2. **Text-heavy** — mostly prose, few or no tables/code (e.g. management, productivity, narrative non-fiction)\n> 3. **Not sure** — I'll use the fast method and warn you if quality seems limited\"\n\nStore the answer as `BOOK_TYPE`:\n- Option 1 → `BOOK_TYPE=technical`\n- Option 2 → `BOOK_TYPE=text`\n- Option 3 → `BOOK_TYPE=text`\n\n**If `BOOK_TYPE=technical`**, inform the user before proceeding:\n> \"📐 Technical mode selected — using Docling for structure-aware extraction (tables, code blocks, formulas preserved as markdown). This takes ~1.5s per page, so expect a few minutes for longer sources. Starting now…\"\n\n**If `BOOK_TYPE=text`**, inform:\n> \"📄 Text mode selected — using the fastest suitable extractor for each file type. Plain text/Markdown/HTML are usually ready in seconds; PDFs use pdftotext when available.\"\n\n---\n\n## Step 2 — Extract text from the source documents\n\nRun the extraction script, passing the input paths:\n\n```bash\nSCRIPT_PATH=\"\"\nHERMES_HOME_RESOLVED=\"${HERMES_HOME:-$HOME/.hermes}\"\nOPENCLAW_STATE_DIR_RESOLVED=\"${OPENCLAW_STATE_DIR:-$HOME/.openclaw}\"\nPROJECT_ROOT=\"$(git rev-parse --show-toplevel 2>/dev/null || true)\"\nHERMES_PROJECT_TRUSTED=false\nif [ -n \"$PROJECT_ROOT\" ] && [ \"${HERMES_AGENT:-}\" = true ] && \\\n  command -v hermes >/dev/null 2>&1 && \\\n  command -v python3 >/dev/null 2>&1 && \\\n  hermes config get skills.trusted_project_dirs --json 2>/dev/null | PROJECT_ROOT=\"$PROJECT_ROOT\" python3 -c 'import json, os, pathlib, sys; root=pathlib.Path(os.environ[\"PROJECT_ROOT\"]).resolve(); sys.exit(not any(pathlib.Path(p).expanduser().resolve() == root for p in json.load(sys.stdin)))' 2>/dev/null\nthen\n  HERMES_PROJECT_TRUSTED=true\nfi\n\nCANDIDATES=(\n  \"$HOME/.copilot/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.agents/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.claude/skills/book-to-skill/scripts/extract.py\"\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills/book-to-skill/scripts/extract.py\"\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n  \"$HOME/.config/opencode/skills/book-to-skill/scripts/extract.py\"\n  \"$HERMES_HOME_RESOLVED/skills/book-to-skill/scripts/extract.py\"\n  \"$HERMES_HOME_RESOLVED\"/skills/*/book-to-skill/scripts/extract.py\n)\nif [ \"${HERMES_AGENT:-}\" != true ]; then\n  # Project-local roots are resolved against the git worktree, not the current\n  # directory: an agent can be invoked from anywhere inside the project (e.g.\n  # `src/nested`) and these roots still have to be found — that is how OpenCode\n  # and the other hosts discover project skills. The CWD-relative forms are kept\n  # so the probe also works outside a git repository.\n  if [ -n \"$PROJECT_ROOT\" ]; then\n    CANDIDATES+=(\n      \"$PROJECT_ROOT/.github/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.claude/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.opencode/skills/book-to-skill/scripts/extract.py\"\n    )\n  fi\n  CANDIDATES+=(\n    \".github/skills/book-to-skill/scripts/extract.py\"\n    \".claude/skills/book-to-skill/scripts/extract.py\"\n    \".agents/skills/book-to-skill/scripts/extract.py\"\n    \"skills/book-to-skill/scripts/extract.py\"\n    \"skills\"/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n    \".opencode/skills/book-to-skill/scripts/extract.py\"\n  )\n  if [ -n \"$PROJECT_ROOT\" ]; then\n    CANDIDATES+=(\n      \"$PROJECT_ROOT/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/skills\"/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n    )\n  fi\nfi\nCANDIDATES+=(\n  \"$HOME/.config/agents/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.config/amp/skills/book-to-skill/scripts/extract.py\"\n)\nif [ \"$HERMES_PROJECT_TRUSTED\" = true ]; then\n  CANDIDATES=(\n    \"$PROJECT_ROOT/.hermes/skills/book-to-skill/scripts/extract.py\"\n    \"$PROJECT_ROOT/.hermes/skills\"/*/book-to-skill/scripts/extract.py\n    \"$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py\"\n    \"$PROJECT_ROOT/.agents/skills\"/*/book-to-skill/scripts/extract.py\n    \"${CANDIDATES[@]}\"\n  )\nfi\nfor candidate in \"${CANDIDATES[@]}\"\ndo\n  if [ -f \"$candidate\" ]; then\n    SCRIPT_PATH=\"$candidate\"\n    break\n  fi\ndone\n\nif [ -z \"$SCRIPT_PATH\" ]; then\n  echo \"Could not find scripts/extract.py for book-to-skill\" >&2\n  exit 1\nfi\n\nPYTHON_BIN=\"${PYTHON_BIN:-python3}\"\nif ! command -v \"$PYTHON_BIN\" >/dev/null 2>&1; then\n  PYTHON_BIN=\"python\"\nfi\n\n\"$PYTHON_BIN\" \"$SCRIPT_PATH\" $INPUT_PATHS --mode <BOOK_TYPE> --install-missing ask\n```\n\nBefore extraction, the script checks optional Python packages needed for the detected format. If a better extractor is missing, it prompts the user with the available fallback. Non-interactive sessions default to fallback unless install mode is explicitly `yes`.\n\n**Tip — preflight the environment:** run `\"$PYTHON_BIN\" \"$SCRIPT_PATH\" --check` to print a per-format report of which extractors are installed and the exact command to install whatever is missing, without processing any file. Useful when a user reports a setup or quality problem.\n\nThis creates a **per-run** work directory — `<tempdir>/book_skill_work-<pid>/` by default, or exactly the path you set in `BOOK_SKILL_WORKDIR` — containing:\n- `full_text.txt` — combined extracted text of all sources with clear visually demarcated boundaries.\n- `metadata.json` — overall combined size, words, pages, token counts, dropped EPUB image counts, the resolved `workdir`, and a detailed list of individual processed `sources`.\n\nThe run prints all three paths on completion (`Workdir ->`, `Text ->`, `Meta ->`). **Take the paths from that output (or from `metadata.json`'s own `workdir` field) rather than assuming a fixed location** — the directory name differs per run so that concurrent extractions on one machine cannot overwrite each other's results.\n\nRead that run's `metadata.json` to inspect the results.\n\n**Always confirm the extraction is the document you asked for** before generating anything: check `filename` / `source_file` in `metadata.json`, or the `SOURCE:` header on the first line of `full_text.txt`. If you are waiting on a background run, wait on *its* specific workdir — polling a shared path can surface a different run's output.\n\n---\n\n## Step 2.5 — Pre-flight cost estimate\n\nRead this run's `metadata.json` (the `Meta ->` path from the extraction output) and present the user with an estimate **before doing any generation**:\n\n```\n📖 Sources detected: <total_sources> source(s)\n<list each source filename and format from the sources metadata list>\n<if images_dropped > 5: warn that N source images were not read>\n📄 Combined Pages/Sections: ~<N> | Words: ~<N> | Total tokens: ~<N>K\n\n💰 Estimated token cost (Full Conversion / Update):\n   Input  (reading + prompts): ~<N>K tokens\n   Output (skill files generated/updated):  ~<N>K tokens\n   Total:                           ~<N>K tokens\n\n   Cost: multiply the token counts above by your model's current\n   input/output per-1M-token rates (prices and model names change often —\n   do not hardcode them; quote today's rate and label it as an estimate).\n\n   ⏱  Estimated time: ~<N> minutes\n\n📁 Files to be generated/updated:\n   SKILL.md + chapter files + glossary + patterns + cheatsheet\n\n➡  Proceed with Full Conversion / Update? (or type \"analyze only\" to preview first)\n```\n\n**How to estimate:**\n- Input tokens ≈ `estimated_tokens` from metadata × 1.3 (prompts overhead per chapter pass)\n- Output tokens ≈ chapters × per-chapter budget + 4,000 (SKILL.md) + 4,500 (glossary + patterns + cheatsheet)\n  - Per-chapter budget midpoint by `BOOK_TYPE` (DEPTH is decided later in Step 4 and can raise it): `text` ≈ 1,000, `technical` ≈ 1,800. If the user has already indicated reference-only vs deep study, use the matching row of the Step 7 matrix.\n- Cost: report the token counts and multiply by the user's current per-1M-token input/output rates. Do NOT hardcode dollar figures — model names and prices change; if you show one, label it an estimate and date it.\n\nWait for the user to confirm before proceeding. If they say \"analyze only\", switch to Mode 2.\n\n---\n\n## Step 2.6 — REPL-style access for large books (> 50k tokens)\n\nInspired by the Recursive Language Model (RLM) paradigm: treat `full_text.txt` as a queryable corpus, not a single read. Loading the whole file into context burns budget you will need later for generation.\n\nFor books over ~50k tokens, prefer programmatic probes over `Read(full_text.txt)` without bounds:\n\n```bash\n# Size check before any Read\nwc -w \"$FULL_TEXT_PATH\"\n\n# Find chapter offsets without loading the whole file\ngrep -n -E \"^\\s*(Chapter|CHAPTER)\\s+[0-9]+\" \"$FULL_TEXT_PATH\" | head -40\n\n# Pull only the chapter you need (lines start..end inclusive)\nsed -n '<start>,<end>p' \"$FULL_TEXT_PATH\"\n\n# Verify a framework is actually mentioned before claiming it in SKILL.md\ngrep -c -i \"westrum\\|dora\" \"$FULL_TEXT_PATH\"\n\n# Targeted Read with offset/limit avoids dumping the full file\n# Read(file_path=full_text.txt, offset=<line>, limit=<lines>)\n```\n\nUse this approach for Step 3 (structure analysis), Step 7 (per-chapter summaries), and Step 8 (glossary / patterns extraction). On books under 50k tokens, a single `Read` is fine.\n\nWhy this matters: a 200-page book is ~75k tokens. Re-reading it once per chapter (28 passes) costs ~2M input tokens; using grep + sed to pull only relevant slices keeps generation cost proportional to the output, not the source.\n\n---\n\n## Step 3 — Analyze book structure\n\nRead the first 8,000 characters of the extracted `full_text.txt` to identify:\n- Book **title** and **author(s)**\n- **Chapter structure** (look for \"Chapter N\", \"PART I\", numbered headings, table of contents)\n- **Core themes** and subject domain\n- Approximate number of chapters\n\nThen read the Table of Contents section if present to map all chapters.\n\n**If mode is \"Analyze Only\":** produce the extraction report now and stop. Structure:\n```\n## Extraction Report — <Title>\n\n### Author's Core Frameworks\n- **<Framework Name>**: <what it is and when to apply>\n\n### Key Principles\n- <Principle>: <actionable rule>\n\n### Techniques & Methods\n- <Technique>: <step-by-step or how-to>\n\n### Anti-patterns\n- <What to avoid>: <why>\n\n### Suggested Skill Name\n`{author-lastname}-{core-concept}` — e.g. `cialdini-influence`\n\n### Chapters Detected\n| # | Title | Main Frameworks |\n```\n\n---\n\n## Step 4 — Ask purpose (Full Conversion only)\n\nBefore generating, ask the user:\n\n> \"What should this skill help you do? (Pick one or more)\n> 1. Apply the author's frameworks while working\n> 2. Think with the author's mental models\n> 3. Reference specific chapters and concepts\n> 4. All of the above\"\n\nUse the answer to weight what gets highlighted in the SKILL.md Core section.\n\n**Derive `DEPTH` from the answer (no extra prompt):**\n- Answer is **only** option 3 (reference) → `DEPTH=reference` — lean, fast-lookup chapters.\n- Answer includes option 1, 2, or 4 → `DEPTH=study` — deeper chapters with more worked detail, examples, and reasoning.\n\n`DEPTH` and `BOOK_TYPE` together set the per-chapter token budget in Step 7. Do **not** ask a separate \"study vs reference\" question — it is inferred here. (In Modes 2/3, where Step 4 is skipped, default `DEPTH=study`.)\n\n---\n\n## Step 5 — Determine skill name\n\nIf `SKILL_NAME` was provided, use it as the skill slug.\nOtherwise, propose two options and let the user choose:\n- **By author-concept**: `{author-lastname}-{core-concept}` (e.g. `cialdini-influence`, `meadows-systems`)\n- **By title**: lowercase hyphens from book title (e.g. `designing-data-intensive-apps`)\n\nDefault to author-concept format if the book has a strong methodological identity.\n\nChoose the destination skill root (`SKILLS_HOME`). First resolve **scope** from an explicit user request or `BOOK_TO_SKILL_SCOPE`, then probe **host**. A request for project-local/project output selects the project-local row; a request for personal/global output selects the personal row. If neither scope is requested, preserve the established personal default (`~/.agents/skills` for non-Hermes hosts). Do not ask a mandatory scope question solely because project-local roots exist. The selected root may still require host approval before writing.\n\n| Host agent | Personal skill root | Project-local root |\n|---|---|---|\n| **GitHub Copilot CLI** | `~/.agents/skills` (discovered natively) | `.github/skills` → `.claude/skills` → `.agents/skills` |\n| **Amp** | `~/.agents/skills` (discovered natively) | `.agents/skills` |\n| **OpenAI Codex** | `~/.agents/skills` (discovered natively; follows symlinks) | `.agents/skills` |\n| **Hermes Agent** | `$HERMES_HOME/skills/<category>` (defaults to `~/.hermes/skills/<category>`) | `.hermes/skills/<category>` → `.agents/skills` |\n| **Claude Code** | `~/.agents/skills` + symlink from `~/.claude/skills/<skill_name>` | `.claude/skills` |\n| **OpenClaw** | `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills` (active state; `~/.agents/skills` only with default state) | `.agents/skills` → `skills/` |\n\nHermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's `HERMES_HOME` and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run `hermes skills trust <project-root>` after generation and verify discovery with `hermes skills list`; project skills remain unavailable until the project is trusted.\n\nFor OpenClaw, use the active state directory's `skills/` root: `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/`. The shared `~/.agents/skills` compatibility root is discoverable only when `OPENCLAW_STATE_DIR` is unset or the default `~/.openclaw`; with a non-default state, do not claim that OpenClaw will see a shared-root install. Verify discovery with `openclaw skills list` after generation.\n| **OpenCode** | `~/.agents/skills` (discovered natively; `~/.config/opencode/skills` and `~/.claude/skills` are read too) | `.opencode/skills` → `.agents/skills` → `.claude/skills` |\n\nHermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's `HERMES_HOME` and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run `hermes skills trust <project-root>` after generation and verify discovery with `hermes skills list`; project skills remain unavailable until the project is trusted.\n\nOpenCode scans the cross-agent `~/.agents/skills` root natively, so the generated-skill default above already serves it — no symlink and no trust step. It also reads its own managed `~/.config/opencode/skills` root and the Claude compatibility root `~/.claude/skills`; use the OpenCode-managed root only when the user asks for it. Project skills are discovered by walking up from the working directory to the git worktree, so start a new session after generation if the skill does not appear. (`~/.cache/opencode/skills` is the remote-skill download cache from `skills.urls`, not an authoring root — never write a generated skill there.)\n\nSelection rules:\n1. Personal install: set `SKILLS_HOME` to `~/.agents/skills` (create the directory if missing). One exception, so the default does not invent a convention in someone else's house: if `~/.agents/skills` does not exist **and** the host's private root already contains skills, use the private root instead and say why in the report.\n2. **Claude Code does not scan `~/.agents/skills`** — after generation completes, Step 10 links the skill in with `ln -sfn \"$HOME/.agents/skills/<skill_name>\" \"$HOME/.claude/skills/<skill_name>\"`.\n3. **Hermes Agent personal installs use the Hermes row above**, not the cross-agent root, and take no symlink.\n4. If the user explicitly asks for a host-private root (`~/.copilot/skills`, `~/.claude/skills`, `~/.config/agents/skills`, `~/.config/amp/skills`), honor it and skip the symlink.\n5. If the user explicitly asked for project-local output, use the project-local row for their host.\n6. If the choice requires knowing the host (project-local output, the Hermes personal root, the OpenClaw state root, or the Claude Code symlink) and you cannot identify it, ask: \"Which agent are you running in — OpenCode, OpenClaw, Hermes Agent, GitHub Copilot CLI, Amp, Codex, or Claude Code?\"\n7. For OpenClaw personal output, use `${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/skills`. The shared `~/.agents/skills` root is a valid OpenClaw destination only when `OPENCLAW_STATE_DIR` is unset or equals the default `$HOME/.openclaw`; otherwise use the active state root or a project/extra directory.\n8. If the user explicitly asks for an OpenClaw-managed personal root, use the active state root and verify discovery with `openclaw skills list`.\n\nSet `SKILLS_HOME` to the selected root and check if `$SKILLS_HOME/<skill_name>/` already exists. On Claude Code, also check whether `~/.claude/skills/<skill_name>` exists as a **real directory** (not a symlink) — a previous install may live there; if so, offer to migrate it (move the directory into `~/.agents/skills/` and replace the original path with the symlink) before continuing.\nIf the skill already exists, prompt the user to choose:\n1. **Update / Fold-in** (Mode 4) — integrate new files/content into the existing skill components.\n2. **Overwrite** — delete and regenerate the skill from scratch.\n3. **Rename** — append `-2` or use a different custom slug.\n\nIf the user selects **Update / Fold-in**, proceed immediately to the **Update / Fold-in Workflow** section after Step 2.5 (skipping Steps 3, 4, 6, 7, 8, 9).\n\n---\n\n## Step 6 — Create skill directory structure\n\n```bash\nmkdir -p \"$SKILLS_HOME/<skill_name>/chapters\"\n```\n\n---\n\n## Step 7 — Generate chapter summaries\n\n**TOKEN BUDGET RULE — CRITICAL (adaptive):**\n\nThe per-chapter budget scales with `BOOK_TYPE` and `DEPTH`. Technical chapters need room for code and tables; study depth needs room for worked reasoning. Pick the budget from this matrix:\n\n| | `DEPTH=reference` | `DEPTH=study` |\n|---|---|---|\n| `BOOK_TYPE=text` | 800–1,200 tokens | 1,000–1,800 tokens |\n| `BOOK_TYPE=technical` | 1,200–1,800 tokens | 2,000–3,000 tokens |\n\n- These are per-file targets, not hard caps — a dense chapter may run over, a thin one under. Density still beats length (Quality Rule #3): never pad to hit a number.\n- Files are loaded on-demand, so a larger chapter only costs tokens when that chapter is actually read.\n- When in doubt between two cells (e.g. mixed-content book), use the lower budget and let depth come from precision, not volume.\n\n**`DEPTH=study` is earned with content, not a bigger number.** The standard section template (Core Idea → Connects To) naturally lands a dense prose chapter around 700–900 tokens. To reach the study budget *honestly* — not by padding — a study-depth chapter must add concrete material:\n- **Reproduce one worked example or artifact** from the chapter (e.g. the example press release, a sample dialogue, a filled-in template, a decision the author walks through) under a `## Worked Example` section. This is the single biggest lever and the main thing a learner returns for.\n- **Expand the \"How\" of each framework** into explicit steps or criteria, not a one-liner.\n- **Add a short \"Why it works / failure mode\" note** to the top 1–2 frameworks.\n\nIf a chapter genuinely has no worked example and resists expansion, let it land below the study floor rather than padding — and note that the chapter is thin in its Core Idea. A `reference`-depth chapter, by contrast, deliberately omits worked examples and keeps only the decision-ready essentials.\n\nFor EACH chapter/major section identified in Step 3:\n\nRead the corresponding section of the extracted `full_text.txt` (use character offsets or grep for chapter headings).\n\nCreate `$SKILLS_HOME/<skill_name>/chapters/ch<NN>-<slug>.md` using the structure below.\n\n**Adapt emphasis based on `BOOK_TYPE`:**\n- `technical` → prioritize \"Code Examples\", \"Reference Tables\", and \"Commands & APIs\" sections; preserve exact syntax\n- `text` → prioritize \"Frameworks Introduced\", \"Mental Models\", and \"Key Takeaways\"; skip empty technical sections\n\n```markdown\n# Chapter N: <Full Title>\n\n## Core Idea\n<1–2 sentences: the single most important thing this chapter teaches>\n\n## Frameworks Introduced\n- **<Framework Name>**: <exact formulation — preserve the author's naming>\n  - When to use: <specific situation>\n  - How: <steps or criteria>\n\n## Key Concepts\n- **<Term>**: <precise definition in 1 sentence>\n(5–10 most important terms from this chapter)\n\n## Mental Models\n<2–4 frameworks or thinking tools. Write as \"Use X when Y\" or \"Think of X as Y\">\n\n## Anti-patterns\n- **<What to avoid>**: <why it fails>\n\n## Code Examples *(technical books only — omit if BOOK_TYPE=text)*\n<!-- Copy the most instructive snippet from the chapter. Preserve indentation exactly. -->\n```<language>\n<key code example from this chapter>\n```\n- **What it demonstrates**: <one line>\n\n## Reference Tables *(technical books only — omit if BOOK_TYPE=text)*\n<!-- Reproduce any comparison matrix, parameter table, or decision table from the chapter in markdown. -->\n\n## Worked Example *(DEPTH=study only — omit for DEPTH=reference)*\n<!-- Reproduce or reconstruct one concrete example the author works through: a\n     sample document, a dialogue, a filled-in template, a before/after, or a\n     decision walked end-to-end. This is what makes a study chapter worth its\n     budget. Keep it faithful to the source; never copy long raw passages —\n     reconstruct the example compactly. -->\n\n## Key Takeaways\n1. <Actionable insight>\n2. <Actionable insight>\n3. <Actionable insight>\n(3–7 takeaways a practitioner must remember)\n\n## Connects To\n- **Ch N**: <why this chapter relates>\n- **<Concept>**: <external concept or standard it connects with>\n```\n\n---\n\n## Step 8 — Generate supporting files\n\n### glossary.md\nCreate `$SKILLS_HOME/<skill_name>/glossary.md`:\n- Every significant term from the book, alphabetically sorted\n- Format: `**Term** — definition (Ch N)`\n- Max 1,500 tokens\n\n### patterns.md\nCreate `$SKILLS_HOME/<skill_name>/patterns.md`:\n- All concrete techniques, design patterns, algorithms from the book\n- Format: `## Pattern Name\\n**When to use**: ...\\n**How**: ...\\n**Trade-offs**: ...`\n- Max 2,000 tokens\n\n### cheatsheet.md\nCreate `$SKILLS_HOME/<skill_name>/cheatsheet.md`:\n\n**This is the most differentiated layer of the skill — treat it as a reasoning aid, not a keyword list.** Anyone can grep the glossary for a term. The cheatsheet captures the author's *judgment*: the decisions they'd make and why. It's the file that turns \"I know the words\" into \"I'd act the way the author would\".\n\nPrioritize, in order:\n1. **Decision rules** — \"When X, do Y, because Z.\" The if/then logic the author applies, stated so the reader can apply it without re-reading the book.\n2. **Decision trees / flowcharts** (as nested bullets or a small table) — for choices with more than two branches.\n3. **Trade-off matrices** — competing options scored on the dimensions the author cares about, so the reader can pick under their own constraints.\n4. **Thresholds & defaults** — the specific numbers, ratios, or rules of thumb the author commits to (e.g. \"keep functions under ~20 lines\", \"alert when error budget < 10%\").\n5. **Tells & smells** — fast heuristics for recognizing a situation (\"if you see X, you're probably in trouble Y\").\n\nAvoid: bare term→definition rows (that's the glossary), and prose paragraphs (that's the chapters). Every line should help the reader *decide* something.\n\n- Format mostly as compact tables and decision rules; the content you'd want on a single printed page kept beside you while working.\n- Max 1,200 tokens.\n\n---\n\n## Step 9 — Generate the master SKILL.md\n\n**CRITICAL TOKEN BUDGET: Keep SKILL.md body under 4,000 tokens.**\nCompaction truncates from the END — put the most important content FIRST.\n\nCreate `$SKILLS_HOME/<skill_name>/SKILL.md`:\n\n```markdown\n---\nname: <skill_name>\ndescription: \"Knowledge base from \\\"<Full Title>\\\" by <Author(s)>. Use when applying <author>'s frameworks for <key topics, 3–6 terms>, studying the book, or referencing its concepts.\"\n---\n\n<!-- argument-hint: [topic, framework name, or chapter number] -->\n\n# <Full Title>\n**Author**: <Author(s)> | **Pages**: ~<N> | **Chapters**: <N> | **Generated**: <YYYY-MM-DD>\n\n## How to Use This Skill\n\n- **Without arguments** — load core frameworks for reference\n- **With a topic** — ask about `replication`, `pricing`, or another indexed topic; I find and read the relevant chapter\n- **With chapter** — ask for `ch05`; I load that specific chapter\n- **Browse** — ask \"what chapters do you have?\" to see the full index\n\nWhen you ask about a topic not covered in Core Frameworks below, I will read\nthe relevant chapter file before answering.\n\n---\n\n## Core Frameworks & Mental Models\n<!-- ~2,000 tokens: the author's most important named frameworks and principles.\n     Preserve exact names. Write as \"Use X when Y\", \"Prefer X over Y because Z\".\n     This is a toolkit, not a summary. -->\n\n<generate 2,000 tokens of the most critical frameworks and insights here>\n\n---\n\n## Chapter Index\n\n| # | Title | Key Frameworks |\n|---|-------|----------------|\n| [ch01](chapters/ch01-<slug>.md) | <Title> | <framework1>, <framework2> |\n| [ch02](chapters/ch02-<slug>.md) | <Title> | <framework1>, <framework2> |\n...\n\n## Topic Index\n\n<!-- Alphabetical. Major terms/frameworks → chapter(s) that cover them. -->\n- **<Term>** → ch<N>[, ch<N>]\n- **<Term>** → ch<N>\n\n## Supporting Files\n\n- [glossary.md](glossary.md) — all key terms with definitions\n- [patterns.md](patterns.md) — all techniques and design patterns\n- [cheatsheet.md](cheatsheet.md) — quick reference tables and decision guides\n\n---\n\n## Scope & Limits\n\nThis skill covers the book content only. For hands-on implementation in your codebase,\ncombine with project-specific tools. For topics beyond this book, check related skills\nor ask the agent directly.\n<if images_dropped > 5: state that N source images were not read>\n```\n\n---\n\n## Step 9.5 — Scan the generated skill\n\nBefore reporting success, loading the skill in another session, or publishing it, run the advisory security scan:\n\n```bash\nSKILL_CONVERTER_ROOT=\"$(cd \"$(dirname \"$SCRIPT_PATH\")/..\" && pwd)\"\n\"$PYTHON_BIN\" \"$SKILL_CONVERTER_ROOT/tools/scan_generated_skill.py\" \"$SKILLS_HOME/<skill_name>\"\n```\n\nIf the scanner exits non-zero, stop and ask a human to review its file/line findings. Do not silently rewrite the generated files, and do not load or publish the skill until the findings are resolved or explicitly accepted.\n\n---\n\n## Step 10 — Cleanup and report\n\nIf the host is Claude Code and `SKILLS_HOME` is `~/.agents/skills` (the default personal install), expose the skill to Claude Code with a symlink — Claude Code only scans `~/.claude/skills`:\n\n```bash\nmkdir -p \"$HOME/.claude/skills\"\nLINK=\"$HOME/.claude/skills/<skill_name>\"\nTARGET=\"$HOME/.agents/skills/<skill_name>\"\nif [ -d \"$LINK\" ] && [ ! -L \"$LINK\" ]; then\n  CLAUDE_STATUS=\"skipped-realdir\"                 # Step 5 migration declined; leave the old dir\nelse\n  ln -sfn \"$TARGET\" \"$LINK\" 2>/dev/null || true\n  # Read the link back — do NOT trust that `ln` did what was asked. On Windows/MSYS\n  # `ln -s` may COPY instead of link (or need Developer Mode / an elevated shell), and\n  # PowerShell/cmd have no `ln` at all. The report must reflect what is on disk, not the\n  # fact that the command ran.\n  if [ -L \"$LINK\" ] && [ \"$(readlink \"$LINK\")\" = \"$TARGET\" ]; then\n    CLAUDE_STATUS=\"linked\"\n  elif [ -e \"$LINK\" ]; then\n    CLAUDE_STATUS=\"copy\"                           # a real file/dir landed instead of a link\n  else\n    CLAUDE_STATUS=\"absent\"                         # ln unavailable or refused\n  fi\nfi\n```\n\nThe real-directory guard is required: `ln -sfn` into an existing real directory would nest the link *inside* it (`~/.claude/skills/<skill_name>/<skill_name>`), leaving Claude Code loading the stale copy. If the user declined the Step 5 migration, skip the symlink and say so in the report — Claude Code keeps using the old directory until it is migrated.\n\n**Read the link back before you report anything about it.** The symlink is a claim, not a fact: fill the \"Discoverable by\" line from `CLAUDE_STATUS` (what is actually on disk), never from \"the command was issued\". **Do not hard-fail when the link is missing or is a copy** — the skill exists at the hub and every other host still finds it; the honest report is \"written to `~/.agents/skills/<skill_name>`; Claude Code will not see it until the link is created\", not an abort. (Windows lead, unverified: a directory junction — `mklink /J` in an elevated `cmd`, or `New-Item -ItemType Junction` in PowerShell — needs neither Developer Mode nor a symlink privilege; if you attempt it, it does not change the read-back-then-report rule.)\n\nSkip this when the user chose a host-private or project-local root (Step 5, rules 3-4).\n\nThen clean up the extraction workdir:\n\n```bash\nPYTHON_BIN=\"${PYTHON_BIN:-python3}\"\nif ! command -v \"$PYTHON_BIN\" >/dev/null 2>&1; then\n  PYTHON_BIN=\"python\"\nfi\n\nRemove **the work directory this run actually used** — the `Workdir ->` path from the\nextraction output, which is also stored as `workdir` in `metadata.json`. Never delete a\ndirectory you did not create: another extraction may be running beside yours.\n\n```bash\n# WORKDIR is the path this run reported; quote it in case of spaces.\nrm -rf \"$WORKDIR\"\n```\n\nEquivalently, if you still have the metadata file:\n\n```bash\n\"$PYTHON_BIN\" - \"$WORKDIR_METADATA_JSON\" <<'PY'\nimport json\nimport shutil\nimport sys\nfrom pathlib import Path\n\nmeta_path = Path(sys.argv[1])\nworkdir = json.loads(meta_path.read_text(encoding=\"utf-8\")).get(\"workdir\")\nif workdir:\n    shutil.rmtree(workdir, ignore_errors=True)\nPY\n```\n\nOlder copies of this file removed a single fixed `book_skill_work` directory. That path is\nno longer used, so such a cleanup is now a harmless no-op rather than something that could\ndelete a concurrent run's output.\n\nThen report to the user:\n\n```\n✅ Skill created: $SKILLS_HOME/<skill_name>/\n\n📚 Book: <Full Title> — <Author>\n📄 Pages: ~<N> | Chapters: <N>\n\nFiles generated:\n  SKILL.md         — core frameworks + index   (~X tokens)\n  chapters/        — <N> chapter summaries     (~X tokens each, ~X total)\n  glossary.md      — key terms                 (~X tokens)\n  patterns.md      — techniques & patterns     (~X tokens)\n  cheatsheet.md    — quick reference           (~X tokens)\n  ─────────────────────────────────────────────────────\n  Total skill size: ~X tokens (loaded on-demand, not all at once)\n\n💡 Tip: check your agent's session cost/usage command to see actual token usage.\n\nUsage:\n  Ask for <skill_name>                  → load core frameworks\n  Ask <skill_name> about <topic>        → find and explain a topic\n  Ask <skill_name> for ch<N>            → dive into a specific chapter\n\nDiscoverable by: <only what is true for the chosen destination — see below>\n\nSomewhere else?  mv ~/.agents/skills/<skill_name> <dest_root>/<skill_name> \\\n                   && ln -sfn <dest_root>/<skill_name> ~/.claude/skills/<skill_name>\n\nPrompted for permission on every file? That is your host gating writes outside the\nworking directory. Say \"save it in this project\" and re-run to write inside it.\n\nReload (if your agent doesn't auto-detect new skills):\n  GitHub Copilot CLI:  /skills reload\n  Claude Code:         restart the session\n  Amp:                 restart the session\n  Hermes Agent:         start a new session\n  OpenClaw:             openclaw skills list (new session if watcher disabled)\n  OpenCode:             start a new session\n\nShare this skill (optional):\n  GitHub repo, installable on any host (Step 11):  say \"publish\"\n  Copilot ecosystem:  gh skill publish $SKILLS_HOME/<skill_name>\n```\n\nFill the \"Discoverable by\" line from `CLAUDE_STATUS` (the read-back result), never from the fact that `ln` ran — for `~/.agents/skills` installs:\n- `linked` → \"Copilot CLI, Amp, Codex (natively); Claude Code via symlink ~/.claude/skills/<skill_name>\"\n- `skipped-realdir` → \"Copilot CLI, Amp, Codex (natively); **NOT** Claude Code — migrate the real directory at ~/.claude/skills/<skill_name> first\"\n- `copy` or `absent` → \"Copilot CLI, Amp, Codex (natively); **NOT** Claude Code — the host could not create the symlink (a plain copy drifts on the next Update/Fold-in). Enable Developer Mode / create the link manually, or run the skill from ~/.agents/skills\"\n- Hermes Agent personal root → \"Hermes Agent (from `$HERMES_HOME/skills/<category>`)\"; no symlink claim, and no cross-agent claim, because the other hosts do not scan the Hermes root\n- other host-private or project-local root → name only the host(s) that scan that root; no symlink claim\n\nThe \"Somewhere else?\" relocation line must be correct for the path actually taken, so it never breaks the symlink the run just created. **`mv` always targets the final skill directory, `<dest_root>/<skill_name>`, never `<dest_root>` itself.** `mv ~/.agents/skills/mybook ~/.copilot/skills && ln -sfn ~/.copilot/skills ~/.claude/skills/mybook` reads as valid and is not: the skill lands at `~/.copilot/skills/mybook` while the link points one level up at the root, so Claude Code resolves to a directory with no `SKILL.md`, which is the exact breakage this line exists to avoid. Substitute the destination the user actually named, so the printed command carries real paths and there is nothing left to interpret:\n- `~/.agents/skills` + symlink → `mv ~/.agents/skills/<skill_name> <dest_root>/<skill_name> && ln -sfn <dest_root>/<skill_name> ~/.claude/skills/<skill_name>`\n- host-private root, Hermes Agent included → `mv <src_root>/<skill_name> <dest_root>/<skill_name>`\n- project-local root → `mv <project_root>/<skill_name> <dest_root>/<skill_name>`\n\nThe \"Prompted for permission on every file?\" line is the answer to a host that gates writes outside the working directory (any personal-scope root is out-of-cwd): the destination was announced above, and the one-line fix — re-run asking for the project-local root — sits next to it. Keep it only for personal-scope installs; drop it when the user already chose project-local.\n\n---\n\n## Step 11 — Publish the generated skill to GitHub (optional)\n\nAfter the Step 10 report, offer once — and only if the Step 9.5 scan passed:\n\n> \"Want me to publish this skill to GitHub so any Agent Skills host can install it with `npx skills add`? (yes / skip)\"\n\nIf the user declines, stop here. Requirements: the `gh` CLI, authenticated (check `gh auth status`). If `gh` is missing or unauthenticated, offer to set it up (`brew install gh` or https://cli.github.com, then `gh auth login`) — or use the no-`gh` path: the user creates an empty repo of the chosen visibility in the GitHub web UI, then you run the `git init`/`add`/`commit` commands below followed by `git remote add origin <repo-url> && git push -u origin main`. The visibility rule below applies to the web-created repo exactly the same.\n\n**Visibility is a separate closed question — never inferred, never read out of an earlier answer.** Once the user accepts, ask it on its own and require a one-word reply:\n\n> \"Private or public repository? Reply with one word: `private` or `public`.\"\n\n**The reply must *be* `public`, not merely contain it — a hard rule, not a suggestion.** Run `gh repo create` with `--private` in every case except one: the answer to the visibility question is the bare word `public`. Substring matching is forbidden, because a sentence about **the source's licence is not a visibility answer** — \"it's public domain\", \"the book is public domain\", \"it's publicly available\" all describe the material, not the repository, and all resolve to `--private`. A paraphrase, a sentence, an ambiguous answer, silence, or your own inference is NOT consent: re-ask once, and if the reply is still not the bare word, use `--private` and say so in the report. A private repo can be flipped public later; a public push of book-derived content cannot be un-published.\n\n**Copyright gate — always apply before creating the repo:** chapter files are synthesized summaries, not raw text, but they still derive from the source material. Per the README's Copyright & fair use policy, skills generated from **third-party copyrighted books must stay private**; offer public only when the source is the user's own writing, openly licensed content, or material the user explicitly confirms they are authorized to redistribute publicly — and state which case applies. Having access to internal company material is not permission to disclose it: skills from internal docs stay **private** unless the user states they hold publication rights.\n\nIf accepted:\n\n1. Add a repo `README.md` inside `$SKILLS_HOME/<skill_name>/` (never overwrite an existing file) — the skill title, a one-paragraph description (\"Agent skill generated from *<Title>* by <Author> with [book-to-skill](https://github.com/virgiliojr94/book-to-skill)\"), the install command from step 3 below, the file inventory, and a note that the content is synthesized summaries, not the book text.\n2. Initialize the skill folder as a git repository and create the remote (default repo name `<skill_name>`; let the user override — some prefer a `<skill_name>-skill` suffix). **Nested-repo guard:** first check whether the skill folder already sits inside a git repository (`git -C \"$SKILLS_HOME/<skill_name>\" rev-parse --show-toplevel` — always the case for project-local roots like `.claude/skills/`). If it does, do NOT `git init` in place: the outer repository would record the folder as an embedded repo (gitlink, mode 160000) without `.gitmodules`, and fresh clones of the outer project would silently omit the skill. Instead, copy the skill folder to a scratch directory, run the commands below from the copy, and tell the user the published repo — not the project-local folder — is the remote's working copy.\n\n```bash\ncd \"$SKILLS_HOME/<skill_name>\"\ngit init -b main\ngit add -A\ngit commit -m \"Add <skill_name> skill\"\ngh repo create <repo_name> --private --source . --push\n# --private is the default; substitute --public ONLY under the visibility rule above\n# (the visibility answer WAS the bare word \"public\" AND the copyright gate allows it)\n```\n\n3. Report the repo URL and the cross-host install command:\n\n```\n✅ Published: https://github.com/<owner>/<repo_name> (<private|public>)\n\nInstall on any Agent Skills host:\n  npx skills add https://github.com/<owner>/<repo_name> --skill <skill_name>\n```\n\n   When the nested-repo guard fired and the repo was published from a scratch copy, add one line — that local folder never gains a remote, so the Update/Fold-in push offer will never appear for it:\n\n```\n⚠️  Published from a copy: <skill folder> sits inside another git repository, so it has\n    no remote of its own. To publish a later update, re-run Step 11, or clone\n    https://github.com/<owner>/<repo_name> and fold new material into the clone.\n```\n\nThe root-level `SKILL.md` layout is exactly what the `skills` CLI detects, so the repo is installable as-is — no restructuring needed. Outside the nested-repo case the local folder stays the live install for this machine and is the remote's working copy, so later Update/Fold-in runs can commit and push their changes to the same remote.\n\n---\n\n## Update / Fold-in Workflow\n\nWh","frontmatter":{"name":"book-to-skill","description":"Converts books and documents (PDF, EPUB, DOCX, HTML, Markdown, plain text, RTF, MOBI/AZW with Calibre) into structured agent skills, extracting frameworks, mental models, principles, techniques, and anti-patterns. Use when the user wants to study a document through GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, or OpenClaw, apply an author's frameworks while working, or build a reusable knowledge base from a file."},"isInternal":false,"tokens":12477,"sizeBytes":50462}],"systemPromptSnippet":"<yakaai_skills repo=\"virgiliojr94/book-to-skill\">\n<!-- File: docs/ARCHITECTURE.md (Tokens: ~2010 | Category: project-spec) -->\n---\ndescription: \"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.\"\nseo_title: \"Architecture - How book-to-skill Extracts and Generates\"\n---\n\n# Architecture\n\nbook-to-skill has two halves: a **deterministic extractor** (Python) and a\n**spec-driven generator** (the agent following `SKILL.md`). The extractor turns any\ndocument into clean text + metadata; the agent turns that into a structured skill.\n\n```\n            ┌─────────────────────────── EXTRACTOR (Python, deterministic) ──┐\n documents  │  scripts/extract.py (shim)  →  book_to_skill/                   │\n (pdf/epub/ │    ├─ cli.py · utils.py   CLI parse · multi-source · runner     │\n  docx/...) │    ├─ config.py           supported extensions · paths · deps   │\n     │      │    ├─ dependencies.py     optional-dep probing · --check report │\n     ▼      │    ├─ sanitize.py         strip invisible/zero-width Unicode    │\n ───────────│    └─ parsers/            pdf · epub · docx · html · rtf ·      │\n            │                             calibre · text (best tool, fallback)│\n            │  output → <tempdir>/book_skill_work/                            │\n            │    full_text.txt   (all sources merged, source-marked)          │\n            │    metadata.json   (pages, words, tokens, chapters, ToC)        │\n            └────────────────────────────────────────────────────────────────┘\n                                   │\n                                   ▼\n            ┌─────────────────────────── GENERATOR (agent, follows SKILL.md) ┐\n            │  Step 1.5  ask content type → BOOK_TYPE (technical | text)      │\n            │  Step 2/2.5 extract · cost estimate · confirm                   │\n            │  Step 2.6  REPL-style probing for large books (grep/sed, no     │\n            │            full re-reads)                                        │\n            │  Step 3    analyze structure (title, author, chapters, ToC)     │\n            │  Step 4    purpose → DEPTH (reference | study)                   │\n            │  Step 7    per-chapter summaries (budget = BOOK_TYPE × DEPTH)    │\n            │  Step 8    glossary · patterns · cheatsheet (decision layer)    │\n            │  Step 9/9.5 SKILL.md core + indexes                             │\n            └────────────────────────────────────────────────────────────────┘\n                                   │\n                                   ▼\n                 <SKILLS_HOME>/<slug>/  ← chosen per host + scope:\n                  ~/.copilot/skills/   GitHub Copilot CLI (personal)\n                  ~/.agents/skills/    Copilot CLI or Amp (cross-agent, personal)\n                  ~/.claude/skills/    Claude Code (personal)\n                  ~/.openclaw/skills/  OpenClaw (personal, default state only)\n                  $HERMES_HOME/skills/<category>/  Hermes Agent (personal)\n                  .github/skills/ | .claude/skills/ | .agents/skills/  project-local (any host)\n                  .hermes/skills/<category>/                         Hermes project-local\n                  skills/                                                   OpenClaw workspace\n                  ~/.config/opencode/skills/                         OpenCode (own root; on request)\n                  .opencode/skills/                                  OpenCode project-local\n                  scope: personal vs project-local (Step 5, BOOK_TO_SKILL_SCOPE)\n                  SKILL.md         core frameworks + chapter & topic index (~4K)\n                  chapters/*.md    on-demand, loaded only when asked\n                  glossary.md      terms\n                  patterns.md      techniques\n                  cheatsheet.md    decision rules / trees / trade-offs / tells\n```\n\nOpenClaw's shared `~/.agents/skills` compatibility root is discoverable only\nwhen `OPENCLAW_STATE_DIR` is unset or uses the default `~/.openclaw`. A\nnon-default state uses its active `<state-dir>/skills` root instead; the\nconverter does not infer arbitrary `skills.load.extraDirs`.\n\n## Design principles\n\n1. **Extract structure, not summaries** — named frameworks, decision rules,\n   anti-patterns; never raw passages.\n2. **Compile-time over runtime** — pay navigation/structuring once; at query time\n   load only the relevant chapter. See [Performance & Cost](performance.md).\n3. **On-demand chapters** — `SKILL.md` stays small; chapter files cost tokens only\n   when read.\n4. **Front-loaded `SKILL.md`** — most important content first (compaction truncates\n   from the end).\n5. **Graceful degradation** — every format has a stdlib fallback; one bad source is\n   skipped, not fatal.\n\n## Key components\n\n| Path | Responsibility |\n|------|----------------|\n| `scripts/extract.py` | thin entrypoint shim → `book_to_skill.cli` (kept so old invocations keep working) |\n| `book_to_skill/cli.py`, `utils.py` | CLI parsing, multi-source resolution, chapter/ToC detection, runner |\n| `book_to_skill/parsers/` | one module per format (`pdf`, `epub`, `docx`, `html`, `rtf`, `calibre`, `text`) |\n| `book_to_skill/config.py` | supported extensions, output paths, dependency map |\n| `book_to_skill/dependencies.py` | optional-dependency probing + `--check` |\n| `book_to_skill/sanitize.py` | strips zero-width / Unicode-tag-block characters from extracted text, and folds Kangxi radicals to their ideographs (see Security) |\n| `tools/discovery_tax.py` | measures token cost vs context-dump / discovery loop |\n| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\\|copilot\\|amp\\|hermes\\|openclaw`) |\n| `tools/validate_skill.py` | checks a generated SKILL.md against host rules (`--lens claude\\|copilot\\|amp\\|hermes\\|opencode`) |\n| `tools/scan_generated_skill.py` | advisory prompt-injection scan of a generated skill (see Security) |\n| `SKILL.md` | the generator spec (Steps 0–10 + fold-in workflow) |\n\n## Security\n\nUntrusted documents flow into an agent's context and then into a generated skill\nthat later loads into other agents — a document→context supply chain. The hardening\nis layered:\n\n- **Extraction sanitization** (`book_to_skill/sanitize.py`) — strips zero-width\n  (`U+200B/200C/200D/2060/FEFF`) and the Unicode tag block (`U+E0000–E007F`) from\n  every parser's output before metrics or `full_text.txt`, so invisible\n  document-borne instructions never reach the agent. Reports the removal count;\n  rejects a source with no visible content left.\n- **CJK radical folding** (`book_to_skill/sanitize.py`) — PDFs whose embedded\n  font subset carries no correct Unicode mapping emit a Kangxi radical\n  (`U+2F00–U+2FDF`) where a character belongs, so a Chinese book extracts\n  \"判断⼒\" for \"判断力\". Each radical in that block has a Unicode compatibility\n  decomposition, so the fold is NFKC applied to those code points only: the rest\n  of the text is passed through byte for byte. Nothing is removed, so it does\n  not count toward the security removal total. CJK Radicals Supplement\n  (`U+2E80–U+2EF3`) is deliberately not folded — Unicode defines no\n  decomposition for it, and some entries are ordinary kanji.\n- **DOCX XXE / Billion-Laughs guard** (`parsers/docx.py`) — rejects any XML part\n  declaring a DTD or entities before parsing.\n- **Subprocess argument-injection** — file paths are absolutised before reaching\n  `pdftotext` / `pdfinfo` / `ebook-convert`, so a `-`-leading filename can't be read\n  as a flag.\n- **Generated-skill scan** (`tools/scan_generated_skill.py`) — an advisory step in\n  the generator (Step 9.5) that flags instruction-override phrases, model-control\n  tags, residual invisible Unicode, authority-widening frontmatter, and\n  exfiltration-shaped content across the generated `SKILL.md`, `chapters/*.md`,\n  `glossary.md`, `patterns.md`, and `cheatsheet.md`. Findings name only the rule and\n  file location — never the matched text.\n- **CI** — CodeQL, Bandit (gate on HIGH), Zizmor, and dependency CVE review on PRs.\n\n## Extending\n\n- **New format** → add `book_to_skill/parsers/<fmt>.py`, register its extension in\n  `config.py`, wire dependency probing in `dependencies.py`, branch in\n  `utils.extract_single_file`.\n- **New generation behavior** → edit the relevant Step in `SKILL.md`; keep it lean\n  and back the change with evidence (see CONTRIBUTING.md).\n\n<!-- File: SKILL.md (Tokens: ~12477 | Category: anthropic-skill) -->\n---\nname: book-to-skill\ndescription: \"Converts books and documents (PDF, EPUB, DOCX, HTML, Markdown, plain text, RTF, MOBI/AZW with Calibre) into structured agent skills, extracting frameworks, mental models, principles, techniques, and anti-patterns. Use when the user wants to study a document through GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, or OpenClaw, apply an author's frameworks while working, or build a reusable knowledge base from a file.\"\n---\n\n<!--\nCross-agent notes (informational; ignored by host agents):\n  - Compatible skill roots: GitHub Copilot CLI (~/.copilot/skills, ~/.agents/skills,\n    .github/skills, .claude/skills, .agents/skills), Amp (.agents/skills,\n    ~/.config/agents/skills, ~/.config/amp/skills), Claude Code (~/.claude/skills),\n    Hermes Agent ($HERMES_HOME/skills, .hermes/skills, .agents/skills),\n    OpenClaw (${OPENCLAW_STATE_DIR:-~/.openclaw}/skills, .agents/skills, skills/;\n    ~/.agents/skills only with the default state).\n    OpenCode (~/.config/opencode/skills, ~/.claude/skills, .opencode/skills,\n    ~/.agents/skills, .agents/skills).\n  - `allowed-tools` is intentionally omitted to stay agent-neutral: Copilot CLI uses\n    `shell`/MCP-server names, Claude uses `Bash`/`Read`/`Write`/`Glob`/`Grep`, Amp\n    adds `shell_command`. The skill needs shell (to run extract.py) and file\n    read/write — each host will prompt for those on first use.\n  - Argument hint: <path-to-document-folder-or-glob>... [skill-name-slug]\n-->\n\n# Book-to-Skill Converter\n\nTransform written knowledge into actionable agent skills by extracting structure — not producing summaries.\n\n## Philosophy\n\nBooks contain crystallized expertise: frameworks, principles, and techniques that took years to develop. This skill extracts that knowledge into a format GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, OpenClaw, or another compatible agent can leverage repeatedly.\n**Extract structure, not summaries.** A skill isn't a book report. It's a toolkit of:\n- Named frameworks (mental models with clear application)\n- Actionable principles (rules that guide decisions)\n- Techniques (step-by-step methods)\n- Anti-patterns (what to avoid and why)\n- Voice calibration (how the author thinks and communicates)\n\n**Preserve the author's precision.** Frameworks often have specific names for reasons. \"The 5 Whys\" isn't interchangeable with \"ask why multiple times.\" Capture the exact formulation.\n\n**Layer depth appropriately.** Simple books → simple skills. Complex books with 10+ frameworks → skills with reference files and on-demand chapters.\n\n---\n\n## Modes of Operation\n\nFour paths available. Route based on what the user asks:\n\n### 1. Full Conversion (Default)\n**Trigger:** User provides one or more document/directory/glob paths without special instructions\n**Action:** Run all steps below (Steps 0–9)\n**Output:** Complete skill with SKILL.md, chapters/, glossary, patterns, cheatsheet\n\n### 2. Analyze Only\n**Trigger:** User says \"analyze\", \"just extract\", or \"I want to review before generating\"\n**Action:** Run Steps 0–3, then produce a structured extraction report (frameworks, principles, techniques found). Stop — do NOT generate skill files.\n**Output:** Analysis report for user review\n\n### 3. Generate from Prior Analysis\n**Trigger:** User has existing analysis notes or previously ran analyze-only\n**Action:** Skip Steps 0–3, use the provided analysis as input, run Steps 4–9\n**Output:** Skill files from the provided analysis\n\n### 4. Update / Fold-in (Existing Skill)\n**Trigger:** User provides one or more new source paths and indicates they want to update an existing skill (either by pointing to the existing skill folder, providing a skill slug that already exists in `SKILLS_HOME`, or explicitly requesting an update).\n**Action:** Run Step 0 (out-of-scope check), Step 1 (validate inputs), Step 1.5 (identify book type), and Step 2 (extract new files). Then skip to Step 5 (identify/detect existing skill path) and run the **Update / Fold-in Workflow** to merge the new content into the existing skill files.\n**Output:** Updated existing skill with new/revised chapter summaries and merged indexes/glossaries.\n\n---\n\n## Skill Locations\n\nThis converter can run from multiple skill systems. When looking for this converter's helper script or writing the generated book skill, prefer these locations in order:\n\n1. GitHub Copilot CLI personal skills: `~/.copilot/skills/`\n2. Cross-agent personal skills (Copilot, Amp, Codex; OpenClaw with its default state): `~/.agents/skills/`\n3. Claude Code personal skills: `~/.claude/skills/`\n4. Project-local Copilot skills: `.github/skills/`\n5. Project-local Claude skills: `.claude/skills/`\n6. Project-local Amp / Copilot / OpenClaw skills: `.agents/skills/`\n7. Amp global skills: `~/.config/agents/skills/`\n8. Amp legacy global skills: `~/.config/amp/skills/`\n9. Hermes Agent personal skills: `$HERMES_HOME/skills/` (defaults to `~/.hermes/skills/`)\n10. Hermes Agent project skills: `.hermes/skills/` or `.agents/skills/`\n11. OpenClaw personal skills: `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/` (active state; `~/.agents/skills/` is shared only with the default state)\n12. OpenClaw project skills: `.agents/skills/` or `skills/`\n11. OpenCode personal skills: `~/.config/opencode/skills/` or `~/.agents/skills/`\n12. OpenCode project skills: `.opencode/skills/` or `.agents/skills/`\n\nFor **generated** book skills, prefer the user-level cross-agent root `~/.agents/skills/` — one physical copy serves the cross-agent hosts and OpenClaw when it uses its default state. Copilot CLI and Amp discover it natively; Claude Code needs a symlink from `~/.claude/skills/<skill_name>` (created in Step 10, see Step 5 for the rules). Pick a host-private or project-local root only when the user explicitly asks for one. `BOOK_TO_SKILL_SCOPE=project` or `personal` can make that choice explicit for automation; do not ask a mandatory scope question merely because both scopes are available.\n\n---\n\n## Step 0 — Out-of-scope check\n\nIf no arguments are provided, stop and respond:\n> \"book-to-skill requires a supported document path, folder, or glob pattern. Usage: `book-to-skill <path-to-document-folder-or-glob>... [skill-name-slug]`\"\n\nThroughout the workflow:\n- **Resolve the destination root first.** Before any existing-skill lookup, determine where this run would write: the personal or project-local root for the user's host, per the Step 5 table and selection rules — a project-local request uses its project-local root, never the personal root. Below, \"the resolved root\" means that destination.\n- Identify the input paths and the optional skill slug.\n- If the last argument is not a file, folder, or glob that exists or matches any files, and it looks like a skill slug (e.g. lowercase hyphens, alphanumeric), treat it as `SKILL_NAME`.\n- Treat all other arguments as the list of `INPUT_PATHS`.\n- If any input path is an existing skill directory (contains `SKILL.md` and a `chapters/` sub-folder), or if `SKILL_NAME` matches an existing skill slug in the resolved root, flag this run as an **Update/Fold-in** operation (Mode 4).\n- **Re-run guard.** Before starting extraction for a Full Conversion, derive the prospective skill slug under Step 5's naming options — the author-concept form, the by-title form, and any `SKILL_NAME` given — and check the resolved root for an existing match. On a match, STOP and ask the user: \"`<skill-name>` already exists. Choose: (1) Update/Fold-in (Mode 4), (2) verify the existing skill is complete and stop, or (3) force full regeneration.\" Do not re-extract until the user chooses. On context recovery after an interrupted run (network cut-off, replayed or continued conversation), reuse the extraction work directory that run reported (`Workdir ->` in its output, or the path you set in `BOOK_SKILL_WORKDIR`) **only if it is still intact and still matches the current inputs and options** — the directory exists, its `metadata.json` is readable, the `sources` it lists match the files being converted now one-to-one on filename **and content fingerprint** (the extractor records a per-source `sha256` in `metadata.json`; recompute the fingerprint from each file as it exists now and require an exact match — `reuse_is_safe()` in `book_to_skill/utils.py` is the executable form of this check), and its recorded `extraction_mode` is the mode this run is using (Step 2's \"confirm the extraction is the document you asked for\" rule). If it is missing (temp cleanup), its `metadata.json` is gone, the sources no longer match (a different filename, or a changed content fingerprint), the recorded metadata has no `sha256` for a source (recorded before fingerprints existed, so freshness cannot be established), or the mode differs, say so and start a fresh extraction instead of resuming. When in doubt, ask the user before discarding or resuming.\n\n---\n\n## Step 1 — Validate input\n\nVerify that there is at least one supported file, directory, or glob pattern among the `INPUT_PATHS`.\nFor directories and globs, expand them to find matching supported files (`.pdf`, `.epub`, `.docx`, `.txt`, `.md`, `.markdown`, `.rst`, `.adoc`, `.html`, `.htm`, `.rtf`, `.mobi`, `.azw`, `.azw3`).\n\nIf no supported files are found, stop with a clear error message.\n\n---\n\n## Step 1.5 — Identify content type\n\nBefore extracting, ask the user:\n\n> \"What kind of content do these sources have? This helps me choose the best extraction method.\n>\n> 1. **Technical** — has code blocks, tables, formulas, diagrams (e.g. programming books, academic papers, architecture guides)\n> 2. **Text-heavy** — mostly prose, few or no tables/code (e.g. management, productivity, narrative non-fiction)\n> 3. **Not sure** — I'll use the fast method and warn you if quality seems limited\"\n\nStore the answer as `BOOK_TYPE`:\n- Option 1 → `BOOK_TYPE=technical`\n- Option 2 → `BOOK_TYPE=text`\n- Option 3 → `BOOK_TYPE=text`\n\n**If `BOOK_TYPE=technical`**, inform the user before proceeding:\n> \"📐 Technical mode selected — using Docling for structure-aware extraction (tables, code blocks, formulas preserved as markdown). This takes ~1.5s per page, so expect a few minutes for longer sources. Starting now…\"\n\n**If `BOOK_TYPE=text`**, inform:\n> \"📄 Text mode selected — using the fastest suitable extractor for each file type. Plain text/Markdown/HTML are usually ready in seconds; PDFs use pdftotext when available.\"\n\n---\n\n## Step 2 — Extract text from the source documents\n\nRun the extraction script, passing the input paths:\n\n```bash\nSCRIPT_PATH=\"\"\nHERMES_HOME_RESOLVED=\"${HERMES_HOME:-$HOME/.hermes}\"\nOPENCLAW_STATE_DIR_RESOLVED=\"${OPENCLAW_STATE_DIR:-$HOME/.openclaw}\"\nPROJECT_ROOT=\"$(git rev-parse --show-toplevel 2>/dev/null || true)\"\nHERMES_PROJECT_TRUSTED=false\nif [ -n \"$PROJECT_ROOT\" ] && [ \"${HERMES_AGENT:-}\" = true ] && \\\n  command -v hermes >/dev/null 2>&1 && \\\n  command -v python3 >/dev/null 2>&1 && \\\n  hermes config get skills.trusted_project_dirs --json 2>/dev/null | PROJECT_ROOT=\"$PROJECT_ROOT\" python3 -c 'import json, os, pathlib, sys; root=pathlib.Path(os.environ[\"PROJECT_ROOT\"]).resolve(); sys.exit(not any(pathlib.Path(p).expanduser().resolve() == root for p in json.load(sys.stdin)))' 2>/dev/null\nthen\n  HERMES_PROJECT_TRUSTED=true\nfi\n\nCANDIDATES=(\n  \"$HOME/.copilot/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.agents/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.claude/skills/book-to-skill/scripts/extract.py\"\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills/book-to-skill/scripts/extract.py\"\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n  \"${OPENCLAW_STATE_DIR_RESOLVED}/skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n  \"$HOME/.config/opencode/skills/book-to-skill/scripts/extract.py\"\n  \"$HERMES_HOME_RESOLVED/skills/book-to-skill/scripts/extract.py\"\n  \"$HERMES_HOME_RESOLVED\"/skills/*/book-to-skill/scripts/extract.py\n)\nif [ \"${HERMES_AGENT:-}\" != true ]; then\n  # Project-local roots are resolved against the git worktree, not the current\n  # directory: an agent can be invoked from anywhere inside the project (e.g.\n  # `src/nested`) and these roots still have to be found — that is how OpenCode\n  # and the other hosts discover project skills. The CWD-relative forms are kept\n  # so the probe also works outside a git repository.\n  if [ -n \"$PROJECT_ROOT\" ]; then\n    CANDIDATES+=(\n      \"$PROJECT_ROOT/.github/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.claude/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/.opencode/skills/book-to-skill/scripts/extract.py\"\n    )\n  fi\n  CANDIDATES+=(\n    \".github/skills/book-to-skill/scripts/extract.py\"\n    \".claude/skills/book-to-skill/scripts/extract.py\"\n    \".agents/skills/book-to-skill/scripts/extract.py\"\n    \"skills/book-to-skill/scripts/extract.py\"\n    \"skills\"/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n    \"skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n    \".opencode/skills/book-to-skill/scripts/extract.py\"\n  )\n  if [ -n \"$PROJECT_ROOT\" ]; then\n    CANDIDATES+=(\n      \"$PROJECT_ROOT/skills/book-to-skill/scripts/extract.py\"\n      \"$PROJECT_ROOT/skills\"/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/*/book-to-skill/scripts/extract.py\n      \"$PROJECT_ROOT/skills\"/*/*/*/*/*/*/book-to-skill/scripts/extract.py\n    )\n  fi\nfi\nCANDIDATES+=(\n  \"$HOME/.config/agents/skills/book-to-skill/scripts/extract.py\"\n  \"$HOME/.config/amp/skills/book-to-skill/scripts/extract.py\"\n)\nif [ \"$HERMES_PROJECT_TRUSTED\" = true ]; then\n  CANDIDATES=(\n    \"$PROJECT_ROOT/.hermes/skills/book-to-skill/scripts/extract.py\"\n    \"$PROJECT_ROOT/.hermes/skills\"/*/book-to-skill/scripts/extract.py\n    \"$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py\"\n    \"$PROJECT_ROOT/.agents/skills\"/*/book-to-skill/scripts/extract.py\n    \"${CANDIDATES[@]}\"\n  )\nfi\nfor candidate in \"${CANDIDATES[@]}\"\ndo\n  if [ -f \"$candidate\" ]; then\n    SCRIPT_PATH=\"$candidate\"\n    break\n  fi\ndone\n\nif [ -z \"$SCRIPT_PATH\" ]; then\n  echo \"Could not find scripts/extract.py for book-to-skill\" >&2\n  exit 1\nfi\n\nPYTHON_BIN=\"${PYTHON_BIN:-python3}\"\nif ! command -v \"$PYTHON_BIN\" >/dev/null 2>&1; then\n  PYTHON_BIN=\"python\"\nfi\n\n\"$PYTHON_BIN\" \"$SCRIPT_PATH\" $INPUT_PATHS --mode <BOOK_TYPE> --install-missing ask\n```\n\nBefore extraction, the script checks optional Python packages needed for the detected format. If a better extractor is missing, it prompts the user with the available fallback. Non-interactive sessions default to fallback unless install mode is explicitly `yes`.\n\n**Tip — preflight the environment:** run `\"$PYTHON_BIN\" \"$SCRIPT_PATH\" --check` to print a per-format report of which extractors are installed and the exact command to install whatever is missing, without processing any file. Useful when a user reports a setup or quality problem.\n\nThis creates a **per-run** work directory — `<tempdir>/book_skill_work-<pid>/` by default, or exactly the path you set in `BOOK_SKILL_WORKDIR` — containing:\n- `full_text.txt` — combined extracted text of all sources with clear visually demarcated boundaries.\n- `metadata.json` — overall combined size, words, pages, token counts, dropped EPUB image counts, the resolved `workdir`, and a detailed list of individual processed `sources`.\n\nThe run prints all three paths on completion (`Workdir ->`, `Text ->`, `Meta ->`). **Take the paths from that output (or from `metadata.json`'s own `workdir` field) rather than assuming a fixed location** — the directory name differs per run so that concurrent extractions on one machine cannot overwrite each other's results.\n\nRead that run's `metadata.json` to inspect the results.\n\n**Always confirm the extraction is the document you asked for** before generating anything: check `filename` / `source_file` in `metadata.json`, or the `SOURCE:` header on the first line of `full_text.txt`. If you are waiting on a background run, wait on *its* specific workdir — polling a shared path can surface a different run's output.\n\n---\n\n## Step 2.5 — Pre-flight cost estimate\n\nRead this run's `metadata.json` (the `Meta ->` path from the extraction output) and present the user with an estimate **before doing any generation**:\n\n```\n📖 Sources detected: <total_sources> source(s)\n<list each source filename and format from the sources metadata list>\n<if images_dropped > 5: warn that N source images were not read>\n📄 Combined Pages/Sections: ~<N> | Words: ~<N> | Total tokens: ~<N>K\n\n💰 Estimated token cost (Full Conversion / Update):\n   Input  (reading + prompts): ~<N>K tokens\n   Output (skill files generated/updated):  ~<N>K tokens\n   Total:                           ~<N>K tokens\n\n   Cost: multiply the token counts above by your model's current\n   input/output per-1M-token rates (prices and model names change often —\n   do not hardcode them; quote today's rate and label it as an estimate).\n\n   ⏱  Estimated time: ~<N> minutes\n\n📁 Files to be generated/updated:\n   SKILL.md + chapter files + glossary + patterns + cheatsheet\n\n➡  Proceed with Full Conversion / Update? (or type \"analyze only\" to preview first)\n```\n\n**How to estimate:**\n- Input tokens ≈ `estimated_tokens` from metadata × 1.3 (prompts overhead per chapter pass)\n- Output tokens ≈ chapters × per-chapter budget + 4,000 (SKILL.md) + 4,500 (glossary + patterns + cheatsheet)\n  - Per-chapter budget midpoint by `BOOK_TYPE` (DEPTH is decided later in Step 4 and can raise it): `text` ≈ 1,000, `technical` ≈ 1,800. If the user has already indicated reference-only vs deep study, use the matching row of the Step 7 matrix.\n- Cost: report the token counts and multiply by the user's current per-1M-token input/output rates. Do NOT hardcode dollar figures — model names and prices change; if you show one, label it an estimate and date it.\n\nWait for the user to confirm before proceeding. If they say \"analyze only\", switch to Mode 2.\n\n---\n\n## Step 2.6 — REPL-style access for large books (> 50k tokens)\n\nInspired by the Recursive Language Model (RLM) paradigm: treat `full_text.txt` as a queryable corpus, not a single read. Loading the whole file into context burns budget you will need later for generation.\n\nFor books over ~50k tokens, prefer programmatic probes over `Read(full_text.txt)` without bounds:\n\n```bash\n# Size check before any Read\nwc -w \"$FULL_TEXT_PATH\"\n\n# Find chapter offsets without loading the whole file\ngrep -n -E \"^\\s*(Chapter|CHAPTER)\\s+[0-9]+\" \"$FULL_TEXT_PATH\" | head -40\n\n# Pull only the chapter you need (lines start..end inclusive)\nsed -n '<start>,<end>p' \"$FULL_TEXT_PATH\"\n\n# Verify a framework is actually mentioned before claiming it in SKILL.md\ngrep -c -i \"westrum\\|dora\" \"$FULL_TEXT_PATH\"\n\n# Targeted Read with offset/limit avoids dumping the full file\n# Read(file_path=full_text.txt, offset=<line>, limit=<lines>)\n```\n\nUse this approach for Step 3 (structure analysis), Step 7 (per-chapter summaries), and Step 8 (glossary / patterns extraction). On books under 50k tokens, a single `Read` is fine.\n\nWhy this matters: a 200-page book is ~75k tokens. Re-reading it once per chapter (28 passes) costs ~2M input tokens; using grep + sed to pull only relevant slices keeps generation cost proportional to the output, not the source.\n\n---\n\n## Step 3 — Analyze book structure\n\nRead the first 8,000 characters of the extracted `full_text.txt` to identify:\n- Book **title** and **author(s)**\n- **Chapter structure** (look for \"Chapter N\", \"PART I\", numbered headings, table of contents)\n- **Core themes** and subject domain\n- Approximate number of chapters\n\nThen read the Table of Contents section if present to map all chapters.\n\n**If mode is \"Analyze Only\":** produce the extraction report now and stop. Structure:\n```\n## Extraction Report — <Title>\n\n### Author's Core Frameworks\n- **<Framework Name>**: <what it is and when to apply>\n\n### Key Principles\n- <Principle>: <actionable rule>\n\n### Techniques & Methods\n- <Technique>: <step-by-step or how-to>\n\n### Anti-patterns\n- <What to avoid>: <why>\n\n### Suggested Skill Name\n`{author-lastname}-{core-concept}` — e.g. `cialdini-influence`\n\n### Chapters Detected\n| # | Title | Main Frameworks |\n```\n\n---\n\n## Step 4 — Ask purpose (Full Conversion only)\n\nBefore generating, ask the user:\n\n> \"What should this skill help you do? (Pick one or more)\n> 1. Apply the author's frameworks while working\n> 2. Think with the author's mental models\n> 3. Reference specific chapters and concepts\n> 4. All of the above\"\n\nUse the answer to weight what gets highlighted in the SKILL.md Core section.\n\n**Derive `DEPTH` from the answer (no extra prompt):**\n- Answer is **only** option 3 (reference) → `DEPTH=reference` — lean, fast-lookup chapters.\n- Answer includes option 1, 2, or 4 → `DEPTH=study` — deeper chapters with more worked detail, examples, and reasoning.\n\n`DEPTH` and `BOOK_TYPE` together set the per-chapter token budget in Step 7. Do **not** ask a separate \"study vs reference\" question — it is inferred here. (In Modes 2/3, where Step 4 is skipped, default `DEPTH=study`.)\n\n---\n\n## Step 5 — Determine skill name\n\nIf `SKILL_NAME` was provided, use it as the skill slug.\nOtherwise, propose two options and let the user choose:\n- **By author-concept**: `{author-lastname}-{core-concept}` (e.g. `cialdini-influence`, `meadows-systems`)\n- **By title**: lowercase hyphens from book title (e.g. `designing-data-intensive-apps`)\n\nDefault to author-concept format if the book has a strong methodological identity.\n\nChoose the destination skill root (`SKILLS_HOME`). First resolve **scope** from an explicit user request or `BOOK_TO_SKILL_SCOPE`, then probe **host**. A request for project-local/project output selects the project-local row; a request for personal/global output selects the personal row. If neither scope is requested, preserve the established personal default (`~/.agents/skills` for non-Hermes hosts). Do not ask a mandatory scope question solely because project-local roots exist. The selected root may still require host approval before writing.\n\n| Host agent | Personal skill root | Project-local root |\n|---|---|---|\n| **GitHub Copilot CLI** | `~/.agents/skills` (discovered natively) | `.github/skills` → `.claude/skills` → `.agents/skills` |\n| **Amp** | `~/.agents/skills` (discovered natively) | `.agents/skills` |\n| **OpenAI Codex** | `~/.agents/skills` (discovered natively; follows symlinks) | `.agents/skills` |\n| **Hermes Agent** | `$HERMES_HOME/skills/<category>` (defaults to `~/.hermes/skills/<category>`) | `.hermes/skills/<category>` → `.agents/skills` |\n| **Claude Code** | `~/.agents/skills` + symlink from `~/.claude/skills/<skill_name>` | `.claude/skills` |\n| **OpenClaw** | `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills` (active state; `~/.agents/skills` only with default state) | `.agents/skills` → `skills/` |\n\nHermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's `HERMES_HOME` and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run `hermes skills trust <project-root>` after generation and verify discovery with `hermes skills list`; project skills remain unavailable until the project is trusted.\n\nFor OpenClaw, use the active state directory's `skills/` root: `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/`. The shared `~/.agents/skills` compatibility root is discoverable only when `OPENCLAW_STATE_DIR` is unset or the default `~/.openclaw`; with a non-default state, do not claim that OpenClaw will see a shared-root install. Verify discovery with `openclaw skills list` after generation.\n| **OpenCode** | `~/.agents/skills` (discovered natively; `~/.config/opencode/skills` and `~/.claude/skills` are read too) | `.opencode/skills` → `.agents/skills` → `.claude/skills` |\n\nHermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's `HERMES_HOME` and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run `hermes skills trust <project-root>` after generation and verify discovery with `hermes skills list`; project skills remain unavailable until the project is trusted.\n\nOpenCode scans the cross-agent `~/.agents/skills` root natively, so the generated-skill default above already serves it — no symlink and no trust step. It also reads its own managed `~/.config/opencode/skills` root and the Claude compatibility root `~/.claude/skills`; use the OpenCode-managed root only when the user asks for it. Project skills are discovered by walking up from the working directory to the git worktree, so start a new session after generation if the skill does not appear. (`~/.cache/opencode/skills` is the remote-skill download cache from `skills.urls`, not an authoring root — never write a generated skill there.)\n\nSelection rules:\n1. Personal install: set `SKILLS_HOME` to `~/.agents/skills` (create the directory if missing). One exception, so the default does not invent a convention in someone else's house: if `~/.agents/skills` does not exist **and** the host's private root already contains skills, use the private root instead and say why in the report.\n2. **Claude Code does not scan `~/.agents/skills`** — after generation completes, Step 10 links the skill in with `ln -sfn \"$HOME/.agents/skills/<skill_name>\" \"$HOME/.claude/skills/<skill_name>\"`.\n3. **Hermes Agent personal installs use the Hermes row above**, not the cross-agent root, and take no symlink.\n4. If the user explicitly asks for a host-private root (`~/.copilot/skills`, `~/.claude/skills`, `~/.config/agents/skills`, `~/.config/amp/skills`), honor it and skip the symlink.\n5. If the user explicitly asked for project-local output, use the project-local row for their host.\n6. If the choice requires knowing the host (project-local output, the Hermes personal root, the OpenClaw state root, or the Claude Code symlink) and you cannot identify it, ask: \"Which agent are you running in — OpenCode, OpenClaw, Hermes Agent, GitHub Copilot CLI, Amp, Codex, or Claude Code?\"\n7. For OpenClaw personal output, use `${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/skills`. The shared `~/.agents/skills` root is a valid OpenClaw destination only when `OPENCLAW_STATE_DIR` is unset or equals the default `$HOME/.openclaw`; otherwise use the active state root or a project/extra directory.\n8. If the user explicitly asks for an OpenClaw-managed personal root, use the active state root and verify discovery with `openclaw skills list`.\n\nSet `SKILLS_HOME` to the selected root and check if `$SKILLS_HOME/<skill_name>/` already exists. On Claude Code, also check whether `~/.claude/skills/<skill_name>` exists as a **real directory** (not a symlink) — a previous install may live there; if so, offer to migrate it (move the directory into `~/.agents/skills/` and replace the original path with the symlink) before continuing.\nIf the skill already exists, prompt the user to choose:\n1. **Update / Fold-in** (Mode 4) — integrate new files/content into the existing skill components.\n2. **Overwrite** — delete and regenerate the skill from scratch.\n3. **Rename** — append `-2` or use a different custom slug.\n\nIf the user selects **Update / Fold-in**, proceed immediately to the **Update / Fold-in Workflow** section after Step 2.5 (skipping Steps 3, 4, 6, 7, 8, 9).\n\n---\n\n## Step 6 — Create skill directory structure\n\n```bash\nmkdir -p \"$SKILLS_HOME/<skill_name>/chapters\"\n```\n\n---\n\n## Step 7 — Generate chapter summaries\n\n**TOKEN BUDGET RULE — CRITICAL (adaptive):**\n\nThe per-chapter budget scales with `BOOK_TYPE` and `DEPTH`. Technical chapters need room for code and tables; study depth needs room for worked reasoning. Pick the budget from this matrix:\n\n| | `DEPTH=reference` | `DEPTH=study` |\n|---|---|---|\n| `BOOK_TYPE=text` | 800–1,200 tokens | 1,000–1,800 tokens |\n| `BOOK_TYPE=technical` | 1,200–1,800 tokens | 2,000–3,000 tokens |\n\n- These are per-file targets, not hard caps — a dense chapter may run over, a thin one under. Density still beats length (Quality Rule #3): never pad to hit a number.\n- Files are loaded on-demand, so a larger chapter only costs tokens when that chapter is actually read.\n- When in doubt between two cells (e.g. mixed-content book), use the lower budget and let depth come from precision, not volume.\n\n**`DEPTH=study` is earned with content, not a bigger number.** The standard section template (Core Idea → Connects To) naturally lands a dense prose chapter around 700–900 tokens. To reach the study budget *honestly* — not by padding — a study-depth chapter must add concrete material:\n- **Reproduce one worked example or artifact** from the chapter (e.g. the example press release, a sample dialogue, a filled-in template, a decision the author walks through) under a `## Worked Example` section. This is the single biggest lever and the main thing a learner returns for.\n- **Expand the \"How\" of each framework** into explicit steps or criteria, not a one-liner.\n- **Add a short \"Why it works / failure mode\" note** to the top 1–2 frameworks.\n\nIf a chapter genuinely has no worked example and resists expansion, let it land below the study floor rather than padding — and note that the chapter is thin in its Core Idea. A `reference`-depth chapter, by contrast, deliberately omits worked examples and keeps only the decision-ready essentials.\n\nFor EACH chapter/major section identified in Step 3:\n\nRead the corresponding section of the extracted `full_text.txt` (use character offsets or grep for chapter headings).\n\nCreate `$SKILLS_HOME/<skill_name>/chapters/ch<NN>-<slug>.md` using the structure below.\n\n**Adapt emphasis based on `BOOK_TYPE`:**\n- `technical` → prioritize \"Code Examples\", \"Reference Tables\", and \"Commands & APIs\" sections; preserve exact syntax\n- `text` → prioritize \"Frameworks Introduced\", \"Mental Models\", and \"Key Takeaways\"; skip empty technical sections\n\n```markdown\n# Chapter N: <Full Title>\n\n## Core Idea\n<1–2 sentences: the single most important thing this chapter teaches>\n\n## Frameworks Introduced\n- **<Framework Name>**: <exact formulation — preserve the author's naming>\n  - When to use: <specific situation>\n  - How: <steps or criteria>\n\n## Key Concepts\n- **<Term>**: <precise definition in 1 sentence>\n(5–10 most important terms from this chapter)\n\n## Mental Models\n<2–4 frameworks or thinking tools. Write as \"Use X when Y\" or \"Think of X as Y\">\n\n## Anti-patterns\n- **<What to avoid>**: <why it fails>\n\n## Code Examples *(technical books only — omit if BOOK_TYPE=text)*\n<!-- Copy the most instructive snippet from the chapter. Preserve indentation exactly. -->\n```<language>\n<key code example from this chapter>\n```\n- **What it demonstrates**: <one line>\n\n## Reference Tables *(technical books only — omit if BOOK_TYPE=text)*\n<!-- Reproduce any comparison matrix, parameter table, or decision table from the chapter in markdown. -->\n\n## Worked Example *(DEPTH=study only — omit for DEPTH=reference)*\n<!-- Reproduce or reconstruct one concrete example the author works through: a\n     sample document, a dialogue, a filled-in template, a before/after, or a\n     decision walked end-to-end. This is what makes a study chapter worth its\n     budget. Keep it faithful to the source; never copy long raw passages —\n     reconstruct the example compactly. -->\n\n## Key Takeaways\n1. <Actionable insight>\n2. <Actionable insight>\n3. <Actionable insight>\n(3–7 takeaways a practitioner must remember)\n\n## Connects To\n- **Ch N**: <why this chapter relates>\n- **<Concept>**: <external concept or standard it connects with>\n```\n\n---\n\n## Step 8 — Generate supporting files\n\n### glossary.md\nCreate `$SKILLS_HOME/<skill_name>/glossary.md`:\n- Every significant term from the book, alphabetically sorted\n- Format: `**Term** — definition (Ch N)`\n- Max 1,500 tokens\n\n### patterns.md\nCreate `$SKILLS_HOME/<skill_name>/patterns.md`:\n- All concrete techniques, design patterns, algorithms from the book\n- Format: `## Pattern Name\\n**When to use**: ...\\n**How**: ...\\n**Trade-offs**: ...`\n- Max 2,000 tokens\n\n### cheatsheet.md\nCreate `$SKILLS_HOME/<skill_name>/cheatsheet.md`:\n\n**This is the most differentiated layer of the skill — treat it as a reasoning aid, not a keyword list.** Anyone can grep the glossary for a term. The cheatsheet captures the author's *judgment*: the decisions they'd make and why. It's the file that turns \"I know the words\" into \"I'd act the way the author would\".\n\nPrioritize, in order:\n1. **Decision rules** — \"When X, do Y, because Z.\" The if/then logic the author applies, stated so the reader can apply it without re-reading the book.\n2. **Decision trees / flowcharts** (as nested bullets or a small table) — for choices with more than two branches.\n3. **Trade-off matrices** — competing options scored on the dimensions the author cares about, so the reader can pick under their own constraints.\n4. **Thresholds & defaults** — the specific numbers, ratios, or rules of thumb the author commits to (e.g. \"keep functions under ~20 lines\", \"alert when error budget < 10%\").\n5. **Tells & smells** — fast heuristics for recognizing a situation (\"if you see X, you're probably in trouble Y\").\n\nAvoid: bare term→definition rows (that's the glossary), and prose paragraphs (that's the chapters). Every line should help the reader *decide* something.\n\n- Format mostly as compact tables and decision rules; the content you'd want on a single printed page kept beside you while working.\n- Max 1,200 tokens.\n\n---\n\n## Step 9 — Generate the master SKILL.md\n\n**CRITICAL TOKEN BUDGET: Keep SKILL.md body under 4,000 tokens.**\nCompaction truncates from the END — put the most important content FIRST.\n\nCreate `$SKILLS_HOME/<skill_name>/SKILL.md`:\n\n```markdown\n---\nname: <skill_name>\ndescription: \"Knowledge base from \\\"<Full Title>\\\" by <Author(s)>. Use when applying <author>'s frameworks for <key topics, 3–6 terms>, studying the book, or referencing its concepts.\"\n---\n\n<!-- argument-hint: [topic, framework name, or chapter number] -->\n\n# <Full Title>\n**Author**: <Author(s)> | **Pages**: ~<N> | **Chapters**: <N> | **Generated**: <YYYY-MM-DD>\n\n## How to Use This Skill\n\n- **Without arguments** — load core frameworks for reference\n- **With a topic** — ask about `replication`, `pricing`, or another indexed topic; I find and read the relevant chapter\n- **With chapter** — ask for `ch05`; I load that specific chapter\n- **Browse** — ask \"what chapters do you have?\" to see the full index\n\nWhen you ask about a topic not covered in Core Frameworks below, I will read\nthe relevant chapter file before answering.\n\n---\n\n## Core Frameworks & Mental Models\n<!-- ~2,000 tokens: the author's most important named frameworks and principles.\n     Preserve exact names. Write as \"Use X when Y\", \"Prefer X over Y because Z\".\n     This is a toolkit, not a summary. -->\n\n<generate 2,000 tokens of the most critical frameworks and insights here>\n\n---\n\n## Chapter Index\n\n| # | Title | Key Frameworks |\n|---|-------|----------------|\n| [ch01](chapters/ch01-<slug>.md) | <Title> | <framework1>, <framework2> |\n| [ch02](chapters/ch02-<slug>.md) | <Title> | <framework1>, <framework2> |\n...\n\n## Topic Index\n\n<!-- Alphabetical. Major terms/frameworks → chapter(s) that cover them. -->\n- **<Term>** → ch<N>[, ch<N>]\n- **<Term>** → ch<N>\n\n## Supporting Files\n\n- [glossary.md](glossary.md) — all key terms with definitions\n- [patterns.md](patterns.md) — all techniques and design patterns\n- [cheatsheet.md](cheatsheet.md) — quick reference tables and decision guides\n\n---\n\n## Scope & Limits\n\nThis skill covers the book content only. For hands-on implementation in your codebase,\ncombine with project-specific tools. For topics beyond this book, check related skills\nor ask the agent directly.\n<if images_dropped > 5: state that N source images were not read>\n```\n\n---\n\n## Step 9.5 — Scan the generated skill\n\nBefore reporting success, loading the skill in another session, or publishing it, run the advisory security scan:\n\n```bash\nSKILL_CONVERTER_ROOT=\"$(cd \"$(dirname \"$SCRIPT_PATH\")/..\" && pwd)\"\n\"$PYTHON_BIN\" \"$SKILL_CONVERTER_ROOT/tools/scan_generated_skill.py\" \"$SKILLS_HOME/<skill_name>\"\n```\n\nIf the scanner exits non-zero, stop and ask a human to review its file/line findings. Do not silently rewrite the generated files, and do not load or publish the skill until the findings are resolved or explicitly accepted.\n\n---\n\n## Step 10 — Cleanup and report\n\nIf the host is Claude Code and `SKILLS_HOME` is `~/.agents/skills` (the default personal install), expose the skill to Claude Code with a symlink — Claude Code only scans `~/.claude/skills`:\n\n```bash\nmkdir -p \"$HOME/.claude/skills\"\nLINK=\"$HOME/.claude/skills/<skill_name>\"\nTARGET=\"$HOME/.agents/skills/<skill_name>\"\nif [ -d \"$LINK\" ] && [ ! -L \"$LINK\" ]; then\n  CLAUDE_STATUS=\"skipped-realdir\"                 # Step 5 migration declined; leave the old dir\nelse\n  ln -sfn \"$TARGET\" \"$LINK\" 2>/dev/null || true\n  # Read the link back — do NOT trust that `ln` did what was asked. On Windows/MSYS\n  # `ln -s` may COPY instead of link (or need Developer Mode / an elevated shell), and\n  # PowerShell/cmd have no `ln` at all. The report must reflect what is on disk, not the\n  # fact that the command ran.\n  if [ -L \"$LINK\" ] && [ \"$(readlink \"$LINK\")\" = \"$TARGET\" ]; then\n    CLAUDE_STATUS=\"linked\"\n  elif [ -e \"$LINK\" ]; then\n    CLAUDE_STATUS=\"copy\"                           # a real file/dir landed instead of a link\n  else\n    CLAUDE_STATUS=\"absent\"                         # ln unavailable or refused\n  fi\nfi\n```\n\nThe real-directory guard is required: `ln -sfn` into an existing real directory would nest the link *inside* it (`~/.claude/skills/<skill_name>/<skill_name>`), leaving Claude Code loading the stale copy. If the user declined the Step 5 migration, skip the symlink and say so in the report — Claude Code keeps using the old directory until it is migrated.\n\n**Read the link back before you report anything about it.** The symlink is a claim, not a fact: fill the \"Discoverable by\" line from `CLAUDE_STATUS` (what is actually on disk), never from \"the command was issued\". **Do not hard-fail when the link is missing or is a copy** — the skill exists at the hub and every other host still finds it; the honest report is \"written to `~/.agents/skills/<skill_name>`; Claude Code will not see it until the link is created\", not an abort. (Windows lead, unverified: a directory junction — `mklink /J` in an elevated `cmd`, or `New-Item -ItemType Junction` in PowerShell — needs neither Developer Mode nor a symlink privilege; if you attempt it, it does not change the read-back-then-report rule.)\n\nSkip this when the user chose a host-private or project-local root (Step 5, rules 3-4).\n\nThen clean up the extraction workdir:\n\n```bash\nPYTHON_BIN=\"${PYTHON_BIN:-python3}\"\nif ! command -v \"$PYTHON_BIN\" >/dev/null 2>&1; then\n  PYTHON_BIN=\"python\"\nfi\n\nRemove **the work directory this run actually used** — the `Workdir ->` path from the\nextraction output, which is also stored as `workdir` in `metadata.json`. Never delete a\ndirectory you did not create: another extraction may be running beside yours.\n\n```bash\n# WORKDIR is the path this run reported; quote it in case of spaces.\nrm -rf \"$WORKDIR\"\n```\n\nEquivalently, if you still have the metadata file:\n\n```bash\n\"$PYTHON_BIN\" - \"$WORKDIR_METADATA_JSON\" <<'PY'\nimport json\nimport shutil\nimport sys\nfrom pathlib import Path\n\nmeta_path = Path(sys.argv[1])\nworkdir = json.loads(meta_path.read_text(encoding=\"utf-8\")).get(\"workdir\")\nif workdir:\n    shutil.rmtree(workdir, ignore_errors=True)\nPY\n```\n\nOlder copies of this file removed a single fixed `book_skill_work` directory. That path is\nno longer used, so such a cleanup is now a harmless no-op rather than something that could\ndelete a concurrent run's output.\n\nThen report to the user:\n\n```\n✅ Skill created: $SKILLS_HOME/<skill_name>/\n\n📚 Book: <Full Title> — <Author>\n📄 Pages: ~<N> | Chapters: <N>\n\nFiles generated:\n  SKILL.md         — core frameworks + index   (~X tokens)\n  chapters/        — <N> chapter summaries     (~X tokens each, ~X total)\n  glossary.md      — key terms                 (~X tokens)\n  patterns.md      — techniques & patterns     (~X tokens)\n  cheatsheet.md    — quick reference           (~X tokens)\n  ─────────────────────────────────────────────────────\n  Total skill size: ~X tokens (loaded on-demand, not all at once)\n\n💡 Tip: check your agent's session cost/usage command to see actual token usage.\n\nUsage:\n  Ask for <skill_name>                  → load core frameworks\n  Ask <skill_name> about <topic>        → find and explain a topic\n  Ask <skill_name> for ch<N>            → dive into a specific chapter\n\nDiscoverable by: <only what is true for the chosen destination — see below>\n\nSomewhere else?  mv ~/.agents/skills/<skill_name> <dest_root>/<skill_name> \\\n                   && ln -sfn <dest_root>/<skill_name> ~/.claude/skills/<skill_name>\n\nPrompted for permission on every file? That is your host gating writes outside the\nworking directory. Say \"save it in this project\" and re-run to write inside it.\n\nReload (if your agent doesn't auto-detect new skills):\n  GitHub Copilot CLI:  /skills reload\n  Claude Code:         restart the session\n  Amp:                 restart the session\n  Hermes Agent:         start a new session\n  OpenClaw:             openclaw skills list (new session if watcher disabled)\n  OpenCode:             start a new session\n\nShare this skill (optional):\n  GitHub repo, installable on any host (Step 11):  say \"publish\"\n  Copilot ecosystem:  gh skill publish $SKILLS_HOME/<skill_name>\n```\n\nFill the \"Discoverable by\" line from `CLAUDE_STATUS` (the read-back result), never from the fact that `ln` ran — for `~/.agents/skills` installs:\n- `linked` → \"Copilot CLI, Amp, Codex (natively); Claude Code via symlink ~/.claude/skills/<skill_name>\"\n- `skipped-realdir` → \"Copilot CLI, Amp, Codex (natively); **NOT** Claude Code — migrate the real directory at ~/.claude/skills/<skill_name> first\"\n- `copy` or `absent` → \"Copilot CLI, Amp, Codex (natively); **NOT** Claude Code — the host could not create the symlink (a plain copy drifts on the next Update/Fold-in). Enable Developer Mode / create the link manually, or run the skill from ~/.agents/skills\"\n- Hermes Agent personal root → \"Hermes Agent (from `$HERMES_HOME/skills/<category>`)\"; no symlink claim, and no cross-agent claim, because the other hosts do not scan the Hermes root\n- other host-private or project-local root → name only the host(s) that scan that root; no symlink claim\n\nThe \"Somewhere else?\" relocation line must be correct for the path actually taken, so it never breaks the symlink the run just created. **`mv` always targets the final skill directory, `<dest_root>/<skill_name>`, never `<dest_root>` itself.** `mv ~/.agents/skills/mybook ~/.copilot/skills && ln -sfn ~/.copilot/skills ~/.claude/skills/mybook` reads as valid and is not: the skill lands at `~/.copilot/skills/mybook` while the link points one level up at the root, so Claude Code resolves to a directory with no `SKILL.md`, which is the exact breakage this line exists to avoid. Substitute the destination the user actually named, so the printed command carries real paths and there is nothing left to interpret:\n- `~/.agents/skills` + symlink → `mv ~/.agents/skills/<skill_name> <dest_root>/<skill_name> && ln -sfn <dest_root>/<skill_name> ~/.claude/skills/<skill_name>`\n- host-private root, Hermes Agent included → `mv <src_root>/<skill_name> <dest_root>/<skill_name>`\n- project-local root → `mv <project_root>/<skill_name> <dest_root>/<skill_name>`\n\nThe \"Prompted for permission on every file?\" line is the answer to a host that gates writes outside the working directory (any personal-scope root is out-of-cwd): the destination was announced above, and the one-line fix — re-run asking for the project-local root — sits next to it. Keep it only for personal-scope installs; drop it when the user already chose project-local.\n\n---\n\n## Step 11 — Publish the generated skill to GitHub (optional)\n\nAfter the Step 10 report, offer once — and only if the Step 9.5 scan passed:\n\n> \"Want me to publish this skill to GitHub so any Agent Skills host can install it with `npx skills add`? (yes / skip)\"\n\nIf the user declines, stop here. Requirements: the `gh` CLI, authenticated (check `gh auth status`). If `gh` is missing or unauthenticated, offer to set it up (`brew install gh` or https://cli.github.com, then `gh auth login`) — or use the no-`gh` path: the user creates an empty repo of the chosen visibility in the GitHub web UI, then you run the `git init`/`add`/`commit` commands below followed by `git remote add origin <repo-url> && git push -u origin main`. The visibility rule below applies to the web-created repo exactly the same.\n\n**Visibility is a separate closed question — never inferred, never read out of an earlier answer.** Once the user accepts, ask it on its own and require a one-word reply:\n\n> \"Private or public repository? Reply with one word: `private` or `public`.\"\n\n**The reply must *be* `public`, not merely contain it — a hard rule, not a suggestion.** Run `gh repo create` with `--private` in every case except one: the answer to the visibility question is the bare word `public`. Substring matching is forbidden, because a sentence about **the source's licence is not a visibility answer** — \"it's public domain\", \"the book is public domain\", \"it's publicly available\" all describe the material, not the repository, and all resolve to `--private`. A paraphrase, a sentence, an ambiguous answer, silence, or your own inference is NOT consent: re-ask once, and if the reply is still not the bare word, use `--private` and say so in the report. A private repo can be flipped public later; a public push of book-derived content cannot be un-published.\n\n**Copyright gate — always apply before creating the repo:** chapter files are synthesized summaries, not raw text, but they still derive from the source material. Per the README's Copyright & fair use policy, skills generated from **third-party copyrighted books must stay private**; offer public only when the source is the user's own writing, openly licensed content, or material the user explicitly confirms they are authorized to redistribute publicly — and state which case applies. Having access to internal company material is not permission to disclose it: skills from internal docs stay **private** unless the user states they hold publication rights.\n\nIf accepted:\n\n1. Add a repo `README.md` inside `$SKILLS_HOME/<skill_name>/` (never overwrite an existing file) — the skill title, a one-paragraph description (\"Agent skill generated from *<Title>* by <Author> with [book-to-skill](https://github.com/virgiliojr94/book-to-skill)\"), the install command from step 3 below, the file inventory, and a note that the content is synthesized summaries, not the book text.\n2. Initialize the skill folder as a git repository and create the remote (default repo name `<skill_name>`; let the user override — some prefer a `<skill_name>-skill` suffix). **Nested-repo guard:** first check whether the skill folder already sits inside a git repository (`git -C \"$SKILLS_HOME/<skill_name>\" rev-parse --show-toplevel` — always the case for project-local roots like `.claude/skills/`). If it does, do NOT `git init` in place: the outer repository would record the folder as an embedded repo (gitlink, mode 160000) without `.gitmodules`, and fresh clones of the outer project would silently omit the skill. Instead, copy the skill folder to a scratch directory, run the commands below from the copy, and tell the user the published repo — not the project-local folder — is the remote's working copy.\n\n```bash\ncd \"$SKILLS_HOME/<skill_name>\"\ngit init -b main\ngit add -A\ngit commit -m \"Add <skill_name> skill\"\ngh repo create <repo_name> --private --source . --push\n# --private is the default; substitute --public ONLY under the visibility rule above\n# (the visibility answer WAS the bare word \"public\" AND the copyright gate allows it)\n```\n\n3. Report the repo URL and the cross-host install command:\n\n```\n✅ Published: https://github.com/<owner>/<repo_name> (<private|public>)\n\nInstall on any Agent Skills host:\n  npx skills add https://github.com/<owner>/<repo_name> --skill <skill_name>\n```\n\n   When the nested-repo guard fired and the repo was published from a scratch copy, add one line — that local folder never gains a remote, so the Update/Fold-in push offer will never appear for it:\n\n```\n⚠️  Published from a copy: <skill folder> sits inside another git repository, so it has\n    no remote of its own. To publish a later update, re-run Step 11, or clone\n    https://github.com/<owner>/<repo_name> and fold new material into the clone.\n```\n\nThe root-level `SKILL.md` layout is exactly what the `skills` CLI detects, so the repo is installable as-is — no restructuring needed. Outside the nested-repo case the local folder stays the live install for this machine and is the remote's working copy, so later Update/Fold-in runs can commit and push their changes to the same remote.\n\n---\n\n## Update / Fold-in Workflow\n\nWh\n</yakaai_skills>"}