Readme
Patchwork
Your RSS feed tells readers what you published. Patchwork tells them what you changed.
Patchwork keeps the version history of the pages you revise and publishes each revision you choose to announce as a feed entry. An entry carries your note, the kind of change, how grown the page is, and a readable diff of the passages that changed. It’s meant for digital gardens, living notes and documentation: pages that keep growing after their first publication.
Status: early development. Folder mode, the feeds and the HTTP API work; git mode and a CI action are next. The v1 JSON contract may still change before the first release.
How it works
Patchwork watches a folder of Markdown files. When a file changes, it stores a snapshot in SQLite. A snapshot becomes a public revision only when you say so, by adding a note to the frontmatter:
---
title: Remote work
revision: "Two years in, I changed my mind about the hybrid model."
revision_kind: position # correction | expansion | position | reorganization
maturity: sprout # seed | sprout | bloom
---
The rules, in short:
- The first time Patchwork sees a file, it records version 1. That version is never announced.
- Edits without a new note are kept as history and never reach the feed, so fixing a typo makes no noise.
- A new note publishes a revision, diffed against the last one you published. Several quiet edits followed by one with a note show up as a single revision.
- Changing only the note rewrites the last revision’s note instead of adding an entry.
- An upload with the same note within 20 minutes of publishing is folded into that revision (the typo you spot right after uploading).
- A page leaves the feeds when its file is missing on two scans in a row.
Mark a block that changes on its own, like a “last checked” line, so it never counts as a change:
<!-- patchwork:ignore -->
Last checked: 2026-10-20
<!-- /patchwork:ignore -->
Install
go install git.pablomurad.com/pablo/patchwork/cmd/patchwork@latest
Patchwork is one binary with no cgo. SQLite is compiled in.
Quick start
patchwork init # writes a commented patchwork.yaml
patchwork doctor # checks the config, the content and the database
patchwork scan # records version 1 of every page
patchwork serve # watches the folder and serves the API and feeds
Keep the database on a local disk. SQLite’s locking isn’t safe on network shares, which is why the default path is in your user cache directory.
Commands
| Command | What it does |
|---|---|
init |
Write a commented patchwork.yaml. |
scan [-dry-run] |
Scan the folder once and apply the publish rules. -dry-run shows what would happen. |
watch |
Keep scanning as files change (file events, plus polling). |
serve |
watch, and serve the API and feeds over HTTP. |
export -out DIR |
Write the API and feeds as static files. |
log <page> |
A page’s public history. |
diff <page> [n \| a b] |
What changed in a revision, in color. |
retract [-undo] <page> <n> |
Hide a published revision from the feeds and the API. |
amend -note TEXT <page> <n> |
Rewrite a published revision’s note or kind. |
snapshot -path P -file F -at T |
Import an older copy of a page, oldest first, to build history from before Patchwork. |
rebuild-diffs [-force] |
Recompute cached diffs. |
backup -to FILE |
Copy the database. |
doctor |
Check everything without changing anything. |
health |
Exit 0 if a running serve answers; for container health checks. |
<page> is a slug or a path inside the content folder.
What it publishes
serve and export publish the same files:
| Path | Content |
|---|---|
/api/v1/pages |
Every page, with its maturity and revision count. |
/api/v1/pages/{slug} |
One page and its public history. |
/api/v1/pages/{slug}/versions/{n} |
One version: its Markdown body and its full diff against the previous version. |
/api/v1/revisions |
Recent revisions across the site. |
/feeds/revisions.atom, /feeds/revisions.json |
Atom and JSON Feed of revisions. |
/feeds/pages/{slug}.atom, .json |
The same, for one page. |
/text/revisions, /text/pages/{slug}/history, /text/pages/{slug}/versions/{n} |
Plain text for terminals, colored by default; ?plain and ?w=100 adjust it. |
API paths also answer with a .json suffix. Responses carry an ETag and answer conditional requests with 304. The JSON shapes are described by the JSON Schema files in schema/v1.
Diffs come as structured segments (eq, del, ins) for your site to render in its own style. Feed entries use <del> and <ins>, which keep their meaning in feed readers that strip CSS.
Configure site.base_url and the public feed paths in patchwork.yaml, so feeds link to your site and not to Patchwork’s own address. Pick site.tag_authority once: it’s part of every feed entry id, and changing it later makes readers see every entry as new.
First site
moonflowersociety.org, a nocturnal garden of ideas.
License
MIT. See LICENSE.