* fix(ci): move the EPUB build out of pre-commit into CI
The build-epub hooks called scripts/build_epub.py, which raises when the
mmdc binary is missing, while check_mermaid.py skips with a warning in the
same situation. On arm64 — where @mermaid-js/mermaid-cli has no working
build — that made 'pre-commit run --all-files' impossible to satisfy for a
docs-only change without --no-verify.
Remove the build-epub, vietnamese-build-epub and japanese-build-epub hooks
and widen the CI build-epub job to a language matrix so PR-time coverage is
not reduced: it previously built en only, and now builds en, vi, zh and ja.
zh was covered by neither the hooks nor this job, so a broken zh diagram
could only surface at release time.
Closes#156
* docs: replace the stale Kroki narrative with local mmdc rendering
Commit e76bbe4 swapped the Kroki HTTP dependency for local mmdc rendering
but left the docs describing the old design. The claim appeared in seven
live files, not the two originally reported: CLAUDE.md, scripts/README.md
and their ja/uk/vi copies.
Also corrects what the same staleness dragged along:
- scripts/README.md documented --timeout and --max-concurrent, which no
longer exist, and omitted --mmdc-path, --lang and --puppeteer-config
- 'Async concurrent fetching' described a render_all() that is now a plain
sequential loop with a dedupe cache
- the network-error/rate-limiting troubleshooting entries are replaced with
the failures that actually occur now (missing mmdc, Chromium sandbox)
CHANGELOG entries recording the Kroki-to-mmdc switch are left alone; they
describe the past accurately.
Closes#157
* fix(scripts): make the ruff config match files that exist
Ruff resolves include/per-file-ignores patterns relative to the directory
holding pyproject.toml. Since the config lives in scripts/, the pattern
"scripts/**/*.py" meant scripts/scripts/**/*.py, which matches nothing —
'ruff check scripts/' printed 'warning: No Python files found under the
given path(s)' and then 'All checks passed!'. per-file-ignores had the same
mistake. Both are now relative to scripts/, so ruff sees all 14 files.
This silently disabled the pre-commit ruff hooks too, not just the
documented command, and two test files had drifted out of format as a
result. Reformatting them exposed a second problem: the pre-commit hook
pinned ruff v0.8.2 while the CI lint job installs unpinned latest and this
venv has 0.15.10. The versions disagree on formatting, so each reverted the
other's output, and CI would have started failing on a file the local hook
kept rewriting. Bump the hook pin and the requirements-dev floor to 0.15.10
so all three agree.
Verified: 'pre-commit run --all-files' is stable across consecutive runs,
'ruff format --check scripts/' and 'ruff check scripts/' both pass, 92
tests pass.
Closes#160
* docs: point localized module links at their own trees
Files at the root of a language directory used ../NN-module/, which from
uk/CATALOG.md resolves to the English 05-mcp/ at the repo root rather than
uk/05-mcp/. Readers following a count stated about the localized tree
landed in the English one. The link checker never caught it because the
English target does exist.
Files one level deeper (uk/04-subagents/README.md and friends) were already
correct — ../06-hooks/ from there resolves to uk/06-hooks/ — so this is
scoped to the 7 depth-1 files that actually escape: CATALOG.md in uk, vi,
ja and zh, LEARNING-ROADMAP.md in uk and vi, and ja/claude_concepts_guide.md.
119 links in total.
The ../NN- occurrences left in STYLE_GUIDE.md and TRANSLATION_NOTES.md are
inside fenced examples showing what a module page should contain, where the
../ form is the correct convention. All 119 rewritten targets were verified
to exist.
Closes#158
* docs(hooks): stop conflating hook types with hook event categories
'**Hook Types** (5 types, 31 events)' sat above four bullets, so the count
read as wrong. It was not — the two numbers describe different axes that
had been merged into one label.
06-hooks/README.md:126 documents five hook *types*: command, http, prompt,
mcp_tool and agent. Those describe how a hook runs. The four bullets are
event *categories* — Tool, Session, Task, Lifecycle — holding 31 events
(6+7+6+12), and describe when it runs.
Split the label so each number belongs to the axis it counts, in all nine
affected files: README.md and INDEX.md plus the ja, uk, vi and zh copies.
QUICK_REFERENCE.md and LEARNING-ROADMAP.md already stated '5 types' with
the handler names attached and needed no change.
Closes#159
* chore(scripts): drop the unused httpx dependency
httpx was the HTTP client for Kroki rendering. Nothing has imported it
since e76bbe4 moved diagram rendering to a local mmdc subprocess —
check_links.py, the only other network caller, uses stdlib urllib.request.
The PEP 723 blocks in build_epub.py and build_website.py had already
dropped it; only the manifests and docs still declared it.
Removes it from requirements.txt, pyproject.toml dependencies, the
dependency tables and uv --with lines in scripts/README.md and its ja/uk
copies, and the .cspell.json word list. Also drops the B113 bandit
suppression, which existed solely for an httpx timeout false positive —
bandit reports no issues without it.
Also corrects two Requirements lines missed in the previous pass: the ja
and uk script READMEs still listed an internet connection rather than mmdc.
Note: tenacity is now dead for the same reason and is left in place.
* chore(scripts): drop the unused tenacity dependency
Nothing imports tenacity — the retry logic it was added for went away
with the Kroki HTTP fetching in e76bbe4. It was still declared in
requirements.txt, pyproject.toml, both `uv run --with` lines and all
three dependency tables.
* docs(i18n): sync the localized pre-commit check lists
The ja/uk/vi CLAUDE.md files still listed build-epub as pre-commit
check #5, stale since 6da8184 moved the EPUB build to CI. They also
omitted markdown-rendering, and the ja/uk stated counts disagreed with
their own lists. Now matches the English CLAUDE.md.
* fix(scripts): silence PLR0917 now stable in ruff 0.16
CI installs the latest ruff (uv pip install ruff, unpinned), and 0.16.1
promoted too-many-positional-arguments from preview to stable, hard-failing
the Code Quality check on build_epub.py's long-signature draw/helper
functions. These sit in the same family as PLR0913, which is already
ignored. Add PLR0917 so the local 0.15.10 pin and unpinned CI lint agree
again.
astral-sh/setup-uv enable-cache defaults to keying off uv.lock, which
this repo intentionally gitignores, so every push to main since
2026-06-17 failed at the cache step before the build ever ran.
Optimize the lesson -> GitHub Pages conversion in build_website.py:
- BS4 single parse: thread one BeautifulSoup tree through heading-id
normalisation, TOC extraction, and link rewriting instead of
reparsing the rendered HTML 3x per page. Build drops 2.97s -> 1.77s
(~40% faster) on the ~222-page site; html.parser reparsing was the
dominant cost.
- Nav O(n^2) -> O(n): build the sidebar section grouping once
(build_nav_skeleton) and resolve per-page relative URLs in localize_nav,
removing ~49k redundant relative_link/grouping calls.
- Read each .md once: cache source text on PageInfo.content and reuse it
across collect_pages, render_pages, and copy_assets (was 2-3 reads/page).
- CI: enable uv cache and cache scripts/.vendor-cache so deploys skip cold
dep resolution and vendor (Tailwind/Mermaid/fonts) downloads.
Public string APIs (derive_page_title, render_markdown, extract_toc,
rewrite_links, normalise_heading_ids) preserved as thin wrappers over the
new soup-based cores. Full suite green (92 tests), ruff/mypy clean.
Output is semantically identical to the prior build; the only diff is raw
hand-written <img/> badges serializing as <img></img> (valid HTML5,
renders identically; nothing validates generated site HTML).
* feat(scripts): add static website generator from markdown sources (#85)
Generate an elegant, mobile-friendly static site from the existing
tutorial markdown files. The markdown remains the single source of
truth — `scripts/build_website.py` reads from the same `.md` files the
EPUB builder uses, rewrites cross-references to site URLs, and rewrites
references to non-markdown repo files (`.json`, `.sh`, `.py`) to
GitHub blob URLs so users can jump to the source on github.com.
Highlights:
- Reuses the chapter ordering convention from `build_epub.py`
- Anchor algorithm mirrors `check_cross_references.heading_to_anchor`
for parity with the validator
- Mermaid renders client-side via `mermaid.js` (no pre-render step)
- Tailwind CSS via CDN; light/dark theme toggle; sidebar nav; in-page
TOC; prev/next page navigation; mobile responsive
- 27 unit + smoke tests covering anchors, link rewriting (including
`<source srcset>` inside `<picture>`), Mermaid handling, and a full
end-to-end build
- GitHub Pages deploy workflow at `.github/workflows/pages.yml`
Closes#85
* fix(website): use relative URLs in sidebar nav and avoid INDEX.html collision
Two bugs found by local browser dogfooding:
1. **Sidebar nav broke from deep pages.** `build_navigation` emitted raw
`output_url` values (site-root-relative) which made every sidebar link
404 from any page below the root. Moved the call inside the per-page
render loop so each page gets nav links computed relative to its own
URL — `01-slash-commands/index.html` from the root, `../01-slash-commands/...`
from a depth-1 page, `../../01-slash-commands/...` from depth-2.
2. **`INDEX.md` overwrote `index.html`.** On case-insensitive filesystems
(macOS/Windows), `INDEX.html` and `index.html` are the same file, so
`INDEX.md` clobbered the rendered `README.md`. Added `_disambiguate_url`
that detects case-insensitive collisions and suffixes the colliding
page with its source stem (`INDEX-index.html`).
Added 2 tests; full suite stays at 83 passed.
* fix(scripts): skip URLs with port in localhost/127.0.0.1 skip list
`check_links.is_skipped()` did an exact-match comparison against the
host, so `http://localhost:8080` (used in scripts/README.md as a preview
example) was not skipped and CI's link check tried to fetch it, which
fails on the GitHub runner. Strip the port before comparing.
* chore(scripts): drop vestigial mypy ignore_errors for build_website
The override silenced all mypy errors for build_website, making the
"mypy: clean" claim technically vacuous. Removing it shows mypy is
actually clean — 0 issues on build_website after type annotations
were added during PR review.
* feat(website): self-host Tailwind, Mermaid, and Inter fonts
Drop all third-party CDN dependencies from rendered pages. The site
previously loaded Tailwind from cdn.tailwindcss.com (Play CDN — JIT
compile in browser, marked not-for-production), Mermaid from
cdn.jsdelivr.net, and Inter/JetBrains Mono from fonts.googleapis.com.
Replace with a vendored toolchain:
- scripts/vendor_assets.py downloads the Tailwind standalone CLI
(Go binary, no Node toolchain), Mermaid's UMD bundle, and Google
Fonts CSS + WOFF2 files. Cached under scripts/.vendor-cache/
(gitignored), refetched only when missing.
- Tailwind compiles a per-build site/assets/tailwind.css with only
the utility classes actually used by the rendered HTML.
- Mermaid and font files land in site/assets/vendor/ and load via
relative URLs.
- Tailwind config + entry CSS live in scripts/website_templates/
alongside the Jinja template.
- build_website grows a skip_vendor flag so the smoke test runs
offline.
- pre-commit mypy hook gets types-Markdown so it can resolve the
same imports as the project venv.
Verification: 86/86 pytest pass, ruff/mypy/bandit clean, full
build produces a working site with zero external requests (verified
in a headless browser — no console errors, no failed network calls,
Mermaid diagrams render).
* fix(website): use tree URLs for repo directory links (#85)
* fix(website): include additional top-level docs (#85)
Extend the release workflow to build separate EPUB artifacts for en, vi,
and zh in parallel via a matrix job, then publish all built files under
a single GitHub Release. Using fail-fast: false so a failure in one
language does not block releasing the others.
Also refactor build_epub.py to drive language-specific paths, filenames,
and titles from a mapping, and add zh support (title/subtitle + choice).
* ci: shift-left quality gates — add mypy to pre-commit, fix CI failures
- Add mypy pre-commit hook (mirrors-mypy v1.13.0) so type checks run locally
- Add [tool.mypy] config to scripts/pyproject.toml with overrides for untyped libs (ebooklib, sync_translations)
- Add mypy>=1.8.0 to requirements-dev.txt
- Fix CI test.yml: remove continue-on-error: true from lint/security/type-check jobs (was silently swallowing failures)
- Fix CI bandit -c path: pyproject.toml → scripts/pyproject.toml
- Fix CI mypy command: use --config-file scripts/pyproject.toml
- Fix CI build-epub: add type-check to needs, fix if: success() → !failure() && !cancelled()
- Fix ruff errors in sync_translations.py (RUF013 implicit Optional, SIM102 nested if)
- Fix mypy errors: add list[str] annotations to errors vars in check_cross_references.py and check_links.py
* fix(ci): install mmdc in build-epub job and correct return type annotation
- Add npm install step for @mermaid-js/mermaid-cli before Build EPUB
to fix CI failure (mmdc not found error)
- Fix check_translation_status() return type from list[dict] to
tuple[list[dict], list[dict]] to match the actual return value
* fix(ci): pass --no-sandbox to Puppeteer in build-epub CI job
mmdc (Mermaid CLI) uses Puppeteer/Chromium which requires --no-sandbox
in the GitHub Actions sandboxed environment. Add --puppeteer-config flag
to build_epub.py that passes a Puppeteer JSON config file to mmdc via -p,
and use it in the CI workflow to inject the no-sandbox args.
* refactor(epub): replace Kroki HTTP dependency with local mmdc rendering (#10)
Remove httpx/tenacity dependencies and Kroki.io API calls from the EPUB
build pipeline. MermaidRenderer now invokes mmdc (mermaid-cli) as a local
subprocess, eliminating intermittent CI failures caused by network
unavailability. CI workflow installs @mermaid-js/mermaid-cli before build.
* fix(epub): add subprocess timeout and fix deduplication log count
- Add 60s timeout to mmdc subprocess.run to prevent hanging builds
when Chromium/Puppeteer stalls in headless CI environments
- Fix misleading log that printed unique/total as equal counts;
now logs "N unique diagrams (M total blocks)" explicitly
- Add test_render_all_timeout and strengthen deduplication assertion
* refactor(ci): shift quality checks to pre-commit, CI as 2nd pass
- Remove ci.yml (lint, security, pytest were only for EPUB scripts)
- Move EPUB build to pre-commit local hook (runs on .md changes)
- Add check_cross_references.py, check_mermaid.py, check_links.py scripts
- Add markdown-lint, cross-references, mermaid-syntax, link-check as
pre-commit hooks — mirrors all 4 CI doc-check jobs locally
- Remove spell check job from docs-check.yml (breaks on translations)
- Refactor docs-check.yml to reuse scripts/ instead of inline Python
- Add .markdownlint.json config shared by pre-commit and CI
- Update CONTRIBUTING.md with required dependencies and hook table
* fix(ci): resolve all CI check failures in docs-check workflow
- fix(check_cross_references): skip code blocks and inline code spans
to avoid false positives from documentation examples; fix emoji
heading anchor generation (rstrip not strip); add blog-posts,
openspec, prompts, .agents to IGNORE_DIRS; ignore README.backup.md
- fix(check_links): strip trailing Markdown punctuation from captured
URLs; add wikipedia, api.github.com to SKIP_DOMAINS; add placeholder
URL patterns to SKIP_URL_PATTERNS; add .agents/.claude to IGNORE_DIRS
- fix(check_mermaid): add --no-sandbox puppeteer config support via
MERMAID_PUPPETEER_NO_SANDBOX env var for GitHub Actions Linux runners
- fix(docs-check.yml): pass MERMAID_PUPPETEER_NO_SANDBOX=true to mermaid job
- fix(content): repair broken anchors in README.md, 09-advanced-features;
fix #plugins -> #claude-code-plugins in claude_concepts_guide.md;
remove non-existent ./docs/performance.md placeholder links; fix
dependabot alerts URL in SECURITY_REPORTING.md; update auto-mode URL
in resources.md; use placeholder pattern for 07-plugins example URL
- remove README.backup.md (stale file)
* fix(check-scripts): fix strip_code_blocks regex and URL fragment handling
- fix regex in strip_code_blocks to avoid conflicting MULTILINE+DOTALL
flags that could fail to strip indented code fences; use DOTALL only
- strip URL fragments (#section) before dispatching link checks to avoid
false-positive 404s on valid URLs with anchor fragments
* fix(check-scripts): fix anchor stripping, cross-ref enforcement, and mermaid temp file cleanup
- heading_to_anchor: use .strip("-") instead of .rstrip("-") to also strip leading hyphens
produced by emoji-prefixed headings, preventing false-positive anchor errors
- check_cross_references: always exit with main()'s return code — filesystem checks
should block pre-commit unconditionally, not silently pass on errors
- check_mermaid: wrap file-processing loop in try/finally so the puppeteer config
temp file is cleaned up even if an unexpected exception (e.g. UnicodeDecodeError) occurs
- docs-check.yml: remove now-unused CROSS_REF_STRICT env var
* fix(scripts): fix anchor stripping and mermaid output path
- Replace .strip('-') with .rstrip('-') in heading_to_anchor() so leading
hyphens from emoji-prefixed headings are preserved, matching GitHub's
anchor generation behaviour.
- Use Path.with_suffix('.svg') in check_mermaid.py instead of
str.replace('.mmd', '.svg') to avoid replacing all occurrences of .mmd
in the full temp path.
Add .cspell.json and markdown-link-check-config.json to the
docs-check workflow trigger paths so spelling and link check
config changes are properly validated.
- Introduced a new script to build an EPUB from markdown files, enhancing documentation accessibility.
- Added a GitHub Actions workflow for automated EPUB builds on version tag pushes.
- Created the initial EPUB file 'claude-howto-guide.epub' for distribution.
This update streamlines the process of creating and releasing the Claude How-To guide in EPUB format.