A companion authoring, navigation, indexing and synchronisation layer for GitHub.com Wikis that gives them a CherryTree/Zim-style hierarchical notebook experience — while keeping the content as plain, GitHub-compatible Markdown in ordinary Git storage.
Core principle: the wiki must stay fully usable when BerryWiki is not. You can always clone the
.wiki.gitrepo, read/edit the Markdown in any editor, commit, push, and view it through GitHub's normal Wiki UI. BerryWiki enhances the files; it never makes them depend on the app.
A local wiki folder can be browsed and edited end to end:
berrywiki serve ./my-wiki # three-pane explorer + editor at :23779- Hierarchical notebook over flat files. Tree, sibling ordering, backlinks
and a generated
_Sidebar.mdthat GitHub renders natively — all driven by a hidden metadata block that is invisible in the rendered wiki, never by filenames. - Zero-JavaScript editor. Source editing with preview, page create and
delete, explicit Save and Save-draft. Drafts live outside the clone,
survive a killed process, and are visible as a banner, a badge and a marker in
the tree. No
<script>is served anywhere — a test asserts it on every route. - Writes that refuse rather than clobber. If a page changed on disk, or changed since your editor was opened, Save is refused with a 409 — and your text is kept, both in the form and as a draft. Nothing you typed is discarded.
- Tags with index pages. A
tags:list in the metadata block gives/tags(every tag with a page count),/tags/<tag>(the pages carrying it) and/search?tag=(filter alone or beside a query). The tag field is part of the editor and the new-page form. GitHub ignores the key, so a tagged wiki is still an ordinary wiki. - Consistency diagnostics. Broken links, missing parents, cycles and
duplicate ids, surfaced in the UI and via
berrywiki check. - Commit-on-save (
berrywiki-sync, wired intoserve): when the folder is a git working tree, every save, create and delete is one atomic commit with the sidebar in the same commit. Changes made outside BerryWiki are checkpointed as their own commit first, never clobbered./changeslists unpublished commits and offers fetch + fast-forward + push; if the branch has diverged,/conflictshands off with the exactgitsteps. Once you have started the merge yourself,/conflictsclassifies each clash from the git index, shows base, ours and theirs, and can conclude a merge whose only clash is the generated_Sidebar.mdby regenerating it. Never force-pushes, never starts a merge, never merges two authored sides, never discards local work.serve --no-commitserves the folder without touching git. - Move a page or a whole subtree from its page (
Move…): pick a new parent and position, Preview the exact list of files renamed and links rewritten without changing anything, then Move to apply it as one commit. Retitling is done in the editor; the filename follows on the next move. - Attachments, and history per page. Files attach to a page and are stored in the wiki repo like any other content; each page has a history view reading real git history, so you can see what changed and when without leaving the reader.
- Accessibility as a gate, not an aspiration.
berrywiki-a11yis a dev-only crate of fourteen structural rules that runs over every rendered route in the test suite: heading order, landmarks, a skip link, accessible names, and text alternatives for anything that signals with colour or shape alone. Contrast ratios are recorded by hand in ADR-0012. Writing the gate found an unswept route that two green "every route" tests had both missed. - Backup and restore, and CherryTree import.
berrywiki backupwrites a git bundle of committed history plus drafts and the operation journal;berrywiki restorerebuilds from it.berrywiki importreads a CherryTree.ctdnotebook and reports what it would become before writing anything.
Being explicit, because the difference matters:
- Merging two authored sides. BerryWiki never starts a merge and never merges page bodies or metadata. It classifies a merge you started, shows all three sides, and can conclude one whose only clash is the generated sidebar; anything else is refused and left for you to settle in git.
- GitHub serving is read-only.
serve --githubmirrors a wiki and renders no edit affordances. - The accessibility walkthrough has not been run. The structural half is
a gate and is enforced on every route (above). The manual half — driving
the reader and the editor with a screen reader — is written up in
docs/execution/a11y-walkthrough.adocand has never been executed. It needs a human at a browser with NVDA. No screen-reader claim is made until it has been. - Live GitHub behaviour is unverified. Every GitHub Wiki behaviour BerryWiki
relies on is recorded in
docs/compatibility/github-wiki.adocand, as of today, none has been tested against a real wiki — those spikes are credential-gated. Treat the compatibility report as a hypothesis list. - CherryTree import works; Zim import, packaging and proofs do not yet —
berrywiki importreads CherryTree XML notebooks, and what it cannot carry across is listed construct by construct in ADR-0014. Zim import and Guix packaging are the rest of Phase 5. The invariants a proof would cover are written down indocs/proofs/invariants.adoc, each with the tests that witness it and a CI gate that keeps the list honest. They are tested, not proved.
Current position: Phases 0–4 are built — all five Phase 4 packages
(tags, history, attachments, accessibility, backup/restore) are done, with the
accessibility walkthrough the one explicit exception above. Phase 5 is one
of four: CherryTree import is done; Zim import, Guix packaging and the SPARK
proof work are open. The suite is 429 tests across 42 binaries, green
(measured 2026-09-15). See
docs/execution/work-packages.adoc for the
package-by-package state and docs/execution/debt-register.adoc
for known debt.
Requires Rust 1.89 or newer. No other runtime.
cargo build --release
./target/release/berrywiki --helpberrywiki check ./my-wiki # tree + diagnostics; exit 1 on any error
berrywiki sidebar ./my-wiki --write # regenerate _Sidebar.md
berrywiki serve ./my-wiki # browse and edit at http://127.0.0.1:23779
berrywiki serve ./my-wiki --no-commit # same, without commit-on-save
berrywiki serve ./my-wiki --author "Ada <ada@example.org>" # commit identity
berrywiki serve --github owner/repo # mirror a GitHub wiki (read-only)
berrywiki backup ./my-wiki ./backup-2026-09-03 # bundle + drafts + journal
berrywiki restore ./backup-2026-09-03 ./restored-wiki
berrywiki import notes.ctd ./my-wiki # dry run: what it would bring across
berrywiki import notes.ctd ./my-wiki --apply # write the pages, in one commitFor a private wiki, supply a token via BERRYWIKI_GITHUB_TOKEN — never as a
command-line argument, so it stays out of shell history and process listings.
fixtures/test-wiki/ is a small notebook you can point any of these at.
Ordinary Markdown, preceded by a comment GitHub does not render:
<!-- berrywiki
id: 0195f6ec-36a2-7a42-b519-5f558842e256
parent: 0195f6d0-b787-7c3a-a48f-c1a04fb2ea84
position: 30
kind: page
tags:
- assessment
-->
# Assessment Plan
Ordinary Markdown from here on.Delete the comment and the page is still a perfectly good wiki page — it simply stops being part of the tree. That is the point.
berrywiki check validates a wiki tree — broken links, missing parents, cycles,
duplicate ids — and exits non-zero on error-level diagnostics, so it works as
a CI gate over a course wiki. You do not have to install anything: this
repository ships a composite Action at its root.
# .github/workflows/wiki.yml in your own course repository
name: wiki
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
# Pin this to a commit SHA rather than @main. The Action builds the CLI
# from whatever revision you pinned it at and caches the binary on that
# commit, so a pinned workflow restores a cached binary while @main
# rebuilds every time this repository moves.
- uses: metadatastician/berrywiki@main
with:
path: . # folder holding the wiki pages
strict: 'false' # 'true' also fails the build on warningsThe Action writes a summary to the job page and exposes errors, warnings and
pages as step outputs. Warnings do not fail the build by default —
a missing parent or a broken link is a warning, and a duplicate page id is an
error — which is usually what you want on a wiki that is still being written.
Set strict: 'true' once your tree is clean and you want it to stay that way.
The point, for a module wiki, is finding out that Week 7 links to a page you deleted before a student does.
The Action currently builds
berrywikifrom source and caches the binary by commit, because there is no tagged release yet. That is a deliberate hold, not an oversight — see the note inaction.yml.
Thirteen crates, layered so the parts that must be provably correct have no I/O
to be wrong about — see ARCHITECTURE.md.
- Engine: Rust. No hand-written JavaScript or TypeScript (ADR-0003); the UI is server-rendered and script-free by test. ADR-0007 was ruled on 2026-09-03: generated script may ship, but only from a named toolchain, reproducibly, and listed in a provenance manifest. None exists, so nothing is served.
berrywiki-servehas no third-party dependencies at all — a hand-rolledstd::netserver, no async runtime, no web framework.- Docs: AsciiDoc (
.adoc) for technical docs and ADRs; Markdown (.md) for wiki content and community-health files.
crates/berrywiki-core/ deterministic engine (no I/O)
crates/berrywiki-store/ WikiStore trait + LocalFolderStore (atomic writes)
crates/berrywiki-serve/ zero-JS three-pane explorer and editor
crates/berrywiki-git/ closed-set git wrapper · -sync/-github/-git-compat
crates/berrywiki-appstate/ out-of-clone app state · -draft for drafts
crates/berrywiki-cli/ the `berrywiki` command
fixtures/test-wiki/ fixture notebook (Markdown)
docs/architecture/ plan + overview
docs/compatibility/ GitHub Wiki compatibility findings (unverified)
docs/decisions/ architecture decision records
docs/execution/ work packages + debt register
docs/proofs/ invariants ledger INV-1..6 (tested, proof scheduled)
scripts/ CI gates (invariants-ledger check, AsciiDoc render)
cargo test --workspace # includes the no-<script> and no-data-loss harnesses
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warningscargo test is the safety gate: it carries the script-free SSR assertions and
the git conflict / no-data-loss harness.
See CONTRIBUTING.md. Security reports:
SECURITY.md.
Code is licensed under MPL-2.0; documentation under CC-BY-SA-4.0.
Full texts in LICENSES/; machine-readable mapping in
REUSE.toml.
The root LICENSE file is the verbatim, unmodified MPL-2.0 text, and must
stay that way. It carried a short dual-licence preamble until 2026-09-15, which
read correctly to a human but dropped GitHub's licensee below its match
threshold, so the repository reported its licence as "other" and showed no
licence in the sidebar. The dual-licence statement lives here and in
REUSE.toml instead, which are the normative record; LICENSE is only the
copy GitHub reads. Adding anything above the MPL text — even a comment — breaks
detection again.