{
  "markdown": "<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/brand/lockup-dark.svg\">\n    <img src=\"docs/assets/brand/lockup.svg\" alt=\"katra\" width=\"408\" height=\"91\">\n  </picture>\n</p>\n\n# katra\n\n[![CI](https://github.com/craigjmidwinter/katra/actions/workflows/ci.yml/badge.svg)](https://github.com/craigjmidwinter/katra/actions/workflows/ci.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/craigjmidwinter/katra.svg)](https://pkg.go.dev/github.com/craigjmidwinter/katra)\n[![Go version](https://img.shields.io/github/go-mod/go-version/craigjmidwinter/katra)](go.mod)\n[![Release](https://img.shields.io/github/v/release/craigjmidwinter/katra?color=blue)](https://github.com/craigjmidwinter/katra/releases/latest)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nA committed, rich-component **dev log you write as you build** — for a\ndeveloper working solo or alongside a coding agent — and the memory that makes\n**spec-driven agentic development** work. Markdown entries with embedded\ninteractive components, stamped automatically with the commit and diffstat\nthey describe, served as a live, auto-reloading page.\n\nIt exists to make one workflow reliable: *chronicle the work as it happens —\nthe why, the dead ends, the screenshots and animations — and never lose a draft\nto a \"promote\" step that gets skipped.*\n\nThe same store is what keeps an agent effective across sessions: designs land\nas committed specs, tasks point at them, and a session starting cold reads the\nspec instead of re-deriving intent from a conversation that no longer exists.\nThat loop is [the agent workflow](#the-agent-workflow) below.\n\n```bash\nkatra init --install-hook        # store + portable Git auto-stamp\nkatra new \"Reworked the swing\"   # start a draft (a markdown file)\nkatra capture shot.png           # drop a screenshot into it\nkatra compare before.png after.png\nkatra serve                      # live page on the LAN, reloads as you write\ngit commit -m \"…\"                # the draft is stamped with this commit\n```\n\n## The one idea\n\n**A draft is an entry with no commit hash.** That is the entire state machine.\n\nIt appears in the *In Progress* panel the moment you create it. There is no\nscratch file, no separate document, no \"publish\" toggle. Stamping it — adding\nthe hash and the diffstat — is what drops it into the log.\n\nEverything else follows from that. Nothing can get stranded in a buffer you\nforgot to promote, because there is no buffer. The log ends up in the order you\nactually worked, including the parts that did not pan out, which is the half a\nsquashed history always loses.\n\n## The viewer\n\n![The Overview page: a Future → Now → Past spine down the middle, the current draft sitting at Now awaiting a stamp, and load-bearing choices and threads pulled into the right rail](docs/assets/screenshots/viewer-overview.png)\n\n`katra serve` renders the store as a live page — no build step, no static\nsite to regenerate while you write. The spine down the middle is the log in\nthe order it happened: future epics, the entry in flight right now, then the\npast. The right rail surfaces what a flat file list buries — decisions that\nare still load-bearing, and which tags a stretch of work threads through.\n\n<details>\n<summary>More screenshots</summary>\n\nAn entry, open — this one shipped mid-draft with a before/after slider\ncomparing two icon-mark directions, dragged in with `katra compare`:\n\n![An in-progress entry with a before/after slider comparing two versions of the page-fold icon mark, and a table of contents generated from the entry's own headings](docs/assets/screenshots/viewer-entry.png)\n\nThe board, grouped by status — `specced` is its own column between `todo` and\n`doing`, for the tasks that have a committed design and nobody building them\nyet:\n\n![The Board view with tasks grouped into todo, specced, doing and done columns, one task sitting in doing with a link to its committed spec](docs/assets/screenshots/viewer-board.png)\n\nThe hub, one page across every registered project on the machine — this is\nthe maintainer's own, 14 projects and 25 things in flight at once, none of\nthem re-typed anywhere:\n\n![The hub's cross-project view: a grid of in-flight tasks and draft entries pulled from 14 separate repositories, each card labelled with its own project](docs/assets/screenshots/hub-projects.png)\n\n</details>\n\n## The problem\n\nYou finish a hard week. The commits say `fix streaming`, `wip`, `actually fix\nstreaming`. Six months later you need to know *why* the spawn budget is\nnearest-first, and the answer is not in the diff — it was in your head, and in a\nscreenshot you no longer have.\n\nThe usual answers are all bad in the same way: they are a **second job**.\n\n- A `CHANGELOG.md` records what shipped, not what you learned, and it is written\n  at release time from the diff you are trying to explain.\n- A wiki or a Notion page is not in the repo, so it drifts from the code\n  immediately and is not there when someone clones.\n- A blog post is written afterwards, from memory — which is exactly when the\n  detail you needed is gone.\n- Conventional commits give you a machine-readable *what* and no room for a\n  *why*, a picture, or an alternative you rejected.\n\nThe common failure is that all of them are a step *after* the work, and any step\nafter the work gets skipped when the work runs long — precisely when the log\nwould have been worth most.\n\nkatra's bet is that the log has to be a **side effect of working** rather than a\ntask that follows it. So the draft is created when you start, it accumulates\nscreenshots and reasoning while you go, and the commit you were going to make\nanyway is what publishes it.\n\n## The agent workflow\n\nThis is what katra is built around, and the reason its shape is odd compared to\na static-site generator: it exists to keep two things from being lost between\nagent sessions — the design an agent is meant to build from, and the record of\nhow the build actually went.\n\nAn agent that logs at the end writes a summary of a diff — the one thing the\ndiff already tells you. What is lost is everything before the final state: the\napproach that failed, the measurement that changed the plan, the picture of the\nbug. So katra pushes the log *into* the work. The common contract is the CLI\nsequence in [The Katra workflow](docs/workflow.md): declare the epic/task,\nattach and read a committed spec when one is warranted, open the draft before\nimplementation, record decisions and evidence as you go, then stamp and close.\nIt works from Codex, Claude Code, MCP, or a plain shell.\n\n```bash\nkatra init --install-hook\n```\n\nThat creates the Katra and installs the harness-neutral Git `post-commit`\nauto-stamp. Claude Code users can additionally run `katra setup`, which\ninstalls its skill and seven session hooks. With that optional adapter:\n\n1. **`SessionStart`** reports the active draft, unresolved memory, or in-flight\n   changes that need reconciling.\n2. **`PostToolUse`** records every file the agent edits.\n3. **`Stop`** blocks the turn from ending if authored code changed and nothing\n   declared what it was for.\n4. **`PreToolUse`** blocks a `git commit` whose staged code has no\n   reconciliation receipt.\n5. Your commit fires the **post-commit hook**, which stamps the draft.\n\nThe gate is the part people react to, so it is worth being precise about when it\nfires. It blocks only when the turn authored code that is still present in the\nworking tree, outside the katra directory, and nothing declared its purpose. A\nconversational turn never blocks. An edit-then-revert nets to nothing and never\nblocks. Someone else's pre-existing dirt is not your work and never blocks. A\nblocked turn never blocks twice for the same unchanged work.\n\nTo satisfy it — `<task>` is a slug from `katra task list` (or create one with\n`katra task new`):\n\n```bash\nkatra reconcile --advance <task>   # this moves a task forward\nkatra reconcile --close   <task>   # this finishes one\nkatra reconcile --no-task --reason \"…\"\nkatra reconcile status             # what does the gate want right now?\n```\n\n**If you are trying katra out, use `katra setup --no-gate`.** You get the\nnudges and the auto-stamp without anything blocking a commit, and you can turn\nthe gate on later by re-running `katra setup`. A blocking hook in every\nrepository is a real change to how committing feels, and it should be a choice\nmade on purpose.\n\nThere is also an MCP server (`katra-mcp`) for clients that would rather call a\ntool than shell out. Its fifteen tools cover entries plus tasks, task specs,\nepics, decisions, and articles. Full detail: [The Katra workflow](docs/workflow.md)\nand [Agents](docs/agents.md).\n\nWhen the first release ships, the official MCP Registry will be able to\ndiscover the same stdio server through a narrow OCI package. That image\ncontains only `katra-mcp` and `git`; it exists because the registry needs an\ninstallable package, not because a container is a better way to use Katra. A\nregistry client still has to expose the repository working tree to it. For\ndirect use, install the native binaries above.\n\n### Spec-driven, not spec-derived\n\n> **Current-source feature:** the installed v0.1.0 CLI does not yet have\n> `task spec`, `task new --spec`, or the `specced` list-filter help value.\n> Build current source with `make all` for this phase until the next release;\n> the release checklist now asserts all three against the packaged binary.\n\nA task can carry `spec:` — a node slug in the katra (a decision, an article, an\nentry) or a path relative to the repository root, resolved the same way as a\n`[[wikilink]]`. `katra task spec <slug> <ref>` attaches it, and moves the task\nfrom `todo` (or empty) to a new status, `specced`: *a design exists, committed,\nand nobody has started building it.*\n\n```bash\nkatra task new \"Cache swap\"                           # todo, slug: cache-swap\nkatra decide \"Cache invalidation: TTL, not events\"   # write the design\nkatra task spec cache-swap cache-invalidation-ttl-not-events   # by node slug\nkatra task start cache-swap                          # -> doing, implement from it\n```\n\nThe benefit is implementation efficiency and it compounds across sessions: an\nagent starting cold — new context window, different day — reads the spec\ninstead of re-deriving intent from a conversation that no longer exists.\n`katra task list --status specced` is the worklist. Nothing requires a spec;\n`todo → doing` is still legal, and most tasks don't warrant one. `specced` is\njust a place to stand between \"we should do this\" and \"someone is doing it,\"\nfor the tasks where a design is worth writing down first — and the entries you\nwrite while implementing become the other half of the record: what the spec\nproposed against what actually happened, dead ends included.\n\n### Optional Claude Code memory ingest\n\nClaude Code keeps native per-project memory. katra can ingest it into a private\nledger, so the log gets the play-by-play without anyone re-typing it. Only\n`metadata.type: project` memories are admitted by default — not `user` (who you\nare), not `feedback` (how you like to be worked with), because neither belongs\nin a committed log. Anything matching a secret detector, or a term you list in\n`sensitiveTerms`, is quarantined rather than offered.\n\nThe ledger itself lives at `katra/.state/memory-ledger.json` and is\n**deliberately local and gitignored** — raw agent memory is unreviewed text\nabout you and your machine, and it should never reach a shared branch by\ndefault; only the prose you choose to write from it does. So \"review\" means the\n`katra memory scan | status | resolve | ignore` queue *on the machine that\nproduced it*: a teammate cloning the repo gets your entries, not your ledger.\n\n## Alternatives\n\nRead this before adopting. Several tools do part of this, and some of them are a\nbetter fit than this one.\n\n| Tool | Use it instead when |\n| --- | --- |\n| [Keep a Changelog](https://keepachangelog.com/) + [release-please](https://github.com/googleapis/release-please) | You want a release-facing record of *what shipped*, for users. That is a different document from a working record of *why*, and most projects should have both. |\n| [adr-tools](https://github.com/npryce/adr-tools) / [Log4brains](https://github.com/thomvaill/log4brains) | Architecture decisions are the only thing you want to record. ADRs are a tighter, more disciplined format; katra's `decide` is a deliberately lighter-weight cousin. |\n| Obsidian, Logseq, a wiki | The notes are personal and span projects, and being in the repo is not the point. katra is per-repository and committed on purpose. |\n| Hugo, Jekyll, Astro | You are writing a public blog. They have themes, feeds, taxonomies and an audience; katra has none of those and does not want them. |\n| Notion, Linear, Jira | You need assignees, permissions and a shared team workflow. katra's tasks exist to be linked from entries, not to run a team. |\n| `git log` and good commit messages | Honestly, quite often. If your commits already carry the reasoning and you never need a picture, you do not need this. |\n\nWhat none of them do, and what this tool exists for: keep the log **inside the\nrepo**, **written while the work happens** rather than after it, with\n**screenshots and interactive components inline**, published by **the commit you\nwere making anyway**. If you do not need all four, one of the above will serve\nyou better and has years more mileage.\n\n## Install\n\nBinaries ship for macOS and Linux, amd64 and arm64. There is no Windows\nbuild; Windows is untested and unsupported.\n\n### `go install`\n\nThe primary channel, and the only one that works before the first release\nships (see Homebrew and Download a binary, below). The right path if you\nalready have Go 1.25 or newer:\n\n```bash\ngo install github.com/craigjmidwinter/katra/cmd/katra@latest\ngo install github.com/craigjmidwinter/katra/cmd/katra-mcp@latest\n```\n\nInstall both. `katra-mcp` is not optional extra tooling — the skill and every\nMCP client wiring assume it sits beside `katra` on your `PATH`.\n\nA `go install` build reports the version you installed —\n`go install …@v0.1.0` reports `v0.1.0`, and installing from a working tree\nreports a pseudo-version naming the commit. Release binaries and `make build`\ncarry a link-time stamp instead, which additionally marks a dirty tree.\n\n### Build from source\n\n```bash\ngit clone https://github.com/craigjmidwinter/katra\ncd katra\nmake install        # both binaries into GOBIN, version stamped\n```\n\n`make build` writes into `./bin/` rather than the repo root, because katra\ndogfoods itself and `./katra` is the directory its own log lives in.\n\n`make snapshot` builds the full set of release archives locally (requires\n[goreleaser](https://goreleaser.com)) if you want to check what a release would\ncontain.\n\n### Homebrew\n\nNo release has shipped yet, so this tap has nothing to install until v0.1.0\ndoes — cutting one is what [RELEASING.md](RELEASING.md) walks through, and once\nit happens this becomes accurate:\n\n```bash\nbrew install craigjmidwinter/tap/katra\n```\n\nThat taps [craigjmidwinter/homebrew-tap](https://github.com/craigjmidwinter/homebrew-tap)\nand installs prebuilt binaries. `brew upgrade katra` tracks new releases.\n\n### Download a binary\n\nSame trigger as Homebrew — these archives are produced by the release\nworkflow, so they exist starting with v0.1.0, not yet. Every\n[release](https://github.com/craigjmidwinter/katra/releases/latest), once one\nexists, ships archives for macOS and Linux on both amd64 and arm64, each\ncontaining **both** `katra` and `katra-mcp`, plus a `checksums.txt` and a\nsignature over it.\n\nThe snippet below is for a macOS/Linux shell (bash/zsh) — there is no Windows\nbuild, and the `uname` mapping it relies on would just produce a garbage URL\non one.\n\n```bash\n# Latest release, without the leading v. Set this by hand to pin a version.\nVERSION=$(curl -fsSL https://api.github.com/repos/craigjmidwinter/katra/releases/latest \\\n  | sed -n 's/.*\"tag_name\": *\"v\\{0,1\\}\\([^\"]*\\)\".*/\\1/p')\n\nOS=$(uname -s | tr '[:upper:]' '[:lower:]')     # darwin | linux\nARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')\n\ncurl -fsSLO \"https://github.com/craigjmidwinter/katra/releases/download/v${VERSION}/katra_${VERSION}_${OS}_${ARCH}.tar.gz\"\ntar xzf \"katra_${VERSION}_${OS}_${ARCH}.tar.gz\" katra katra-mcp\nsudo install -m 0755 katra katra-mcp /usr/local/bin/\n```\n\nOn macOS, a binary you downloaded yourself is quarantined by Gatekeeper. Clear\nit with `xattr -d com.apple.quarantine /usr/local/bin/katra`, or use the\nHomebrew install above, which does this for you.\n\n#### Verify what you downloaded\n\n```bash\ncurl -fsSLO \"https://github.com/craigjmidwinter/katra/releases/download/v${VERSION}/checksums.txt\"\n\n# Linux\nsha256sum --check --ignore-missing checksums.txt\n# macOS\nshasum -a 256 --check --ignore-missing checksums.txt\n```\n\nThen the signature over `checksums.txt`. Releases are signed keylessly with\n[cosign](https://docs.sigstore.dev/cosign/system_config/installation/) — there\nis no public key to fetch and no private key anyone has to guard. The signing\ncertificate is issued to the release workflow's own GitHub OIDC identity and\nrecorded in the public Rekor transparency log, so what you are checking is\n\"this was built by `release.yml` in this repo, from a tag\":\n\n```bash\ncurl -fsSLO \"https://github.com/craigjmidwinter/katra/releases/download/v${VERSION}/checksums.txt.sig\"\ncurl -fsSLO \"https://github.com/craigjmidwinter/katra/releases/download/v${VERSION}/checksums.txt.pem\"\n\ncosign verify-blob \\\n  --certificate checksums.txt.pem \\\n  --signature checksums.txt.sig \\\n  --certificate-identity-regexp '^https://github\\.com/craigjmidwinter/katra/\\.github/workflows/release\\.yml@refs/tags/' \\\n  --certificate-oidc-issuer https://token.actions.githubusercontent.com \\\n  checksums.txt\n```\n\n### Upgrading\n\nRe-running the `go install ...@latest` commands above replaces the binaries in\nplace — verified: the second run relinks both, same as the first. Once a\nrelease exists, `brew upgrade katra` does the equivalent for the Homebrew\nchannel.\n\n### Uninstalling\n\n`katra setup` creates five things; each has its own removal.\n\n1. **The git post-commit hook.** `katra hook uninstall`.\n2. **The hub launchd agent**, if you ran `katra hub install`. `katra hub\n   uninstall` reverses it — `launchctl unload` plus deleting the plist.\n3. **The Claude Code skill.** `rm -rf .claude/skills/katra/` (it holds one\n   file, `SKILL.md`).\n4. **The hooks block in `.claude/settings.json`.** Remove the `katra\n   agent-hook` entries under the `SessionStart`, `UserPromptSubmit`,\n   `PostToolUse`, `Stop`, `PreCompact`, `SessionEnd` keys, and `PreToolUse` too\n   if you installed the commit gate — then drop any of those keys left empty.\n   `katra setup` only ever added entries under those keys; anything else there\n   is yours.\n5. **The registry entry**, in `~/.config/katra/registry.yml`. There is no\n   explicit deregister command, and none of the above removes it — it lives as\n   long as `katra/config.yml` does, which is usually what you want, since\n   uninstalling the automation is not the same as abandoning the log. If you\n   do delete `katra/`, the entry self-prunes the next time `katra hub list` or\n   `katra hub serve` reads the registry — including a hub daemon already\n   running.\n\nNothing above touches `katra/` itself — the `katra/` directory is your data,\nand it stays.\n\n## Quickstart\n\nAbout five minutes, from inside a git repository.\n\n1. **Set it up without tying the repository to a coding harness.**\n\n   ```bash\n   katra init --install-hook\n   ```\n\n   ```\n   ✓ katra created at katra\n     entries/   — one markdown file per post\n     media/     — images, gifs, video, html embeds\n     config.yml — title, accent, hook behaviour\n     registered with the global katra registry (…)\n   ✓ post-commit hook installed at .git/hooks/post-commit\n   ```\n\n   Creates `katra/`, a welcome draft, the portable Git auto-stamp hook, and a\n   hub registration. Claude Code users can layer on `katra setup --no-gate`\n   for session nudges or `katra setup` for nudges plus its commit gate.\n\n2. **Add to the welcome draft, then drop in a screenshot.**\n\n   ```bash\n   katra append --entry hello-katra \"The first reason this project needs a chronicle.\"\n   ```\n\n   ```\n   ✓ appended to hello-katra\n   ```\n\n   Optional, and worth doing for real once you have something to show: capture\n   any image on your machine (`screencapture -x shot.png` grabs one on macOS\n   if you don't).\n\n   ```bash\n   katra capture ~/Desktop/swing.png --entry hello-katra --caption \"first visible proof\"   # swap in your own path\n   ```\n\n   ```\n   ✓ imported media/swing.png\n   ✓ added to hello-katra\n   ```\n\n3. **Serve it.**\n\n   ```bash\n   katra serve\n   ```\n\n   ```\n     local:   http://localhost:8080/\n     network: http://192.168.1.23:8080/   (one line per LAN interface)\n     watching katra — open tabs reload on change. Ctrl-C to stop.\n   ```\n\n   Leave that running; open tabs reload as you write.\n\n4. **Commit as usual.**\n\n   ```bash\n   git commit -m \"Reworked the swing arc\"\n   ```\n\n   The post-commit hook stamps the draft with that commit's hash and\n   diffstat. Without the hook: `katra stamp` (HEAD), or `katra stamp --hash\n   a1b2c3,d4e5f6` for a chapter of several commits.\n\n5. **Confirm it landed.**\n\n   ```bash\n   katra list\n   ```\n\n   ```\n   2026-08-20 16:06:54  Reworked the swing arc   [12acf46]  (+261 −0)\n   ```\n\n   A stamped entry, off the commit you already made — the guaranteed visible\n   success this quickstart ends on.\n\nTo publish a static copy:\n\n```bash\nkatra build --out ./site\n```\n\nA self-contained directory — `index.html`, `data.json`, media — with no external\nrequests. Host it anywhere, or open it from a USB stick.\n\n## Rich components\n\nA component is a fenced code block whose language names it. The source stays\nreadable and diffable; the page shows the widget.\n\n````markdown\n```compare\nbefore: media/bunker_before.png\nafter:  media/bunker_after.png\ncaption: Bunker reshape\n```\n\n```gallery\n- src: media/a.png\n  cap: tier one\n- src: media/b.png\n  cap: tier two\n```\n\n```video\nsrc: media/horde.mp4\nloop: true\n```\n\n```embed\nsrc: media/frame-times.html\nheight: 480\ncaption: Frame time vs district count\n```\n\n```note\nA callout. **Markdown** works inside it.\n```\n\n```warning\nSame, with a warning style.\n```\n````\n\nPlain `![caption](media/x.png)` images get a lightbox for free.\n\n**An unregistered language renders as an ordinary code block.** That is the\ncompatibility rule for the format, not a fallback: an entry written against a\nnewer katra still renders in an older one, just less prettily.\n\nThere is no chart component, deliberately. You author a self-contained HTML\nfigure and capture it — `katra capture` recognises `.html` and emits an `embed`\nblock — so any chart you can draw is available rather than a fixed set of types.\n[examples/media/frame-times.html](examples/media/frame-times.html) is a worked\none, and [examples/entry.md](examples/entry.md) exercises every component in a\nsingle entry (CI renders it on every push, so it cannot rot).\n\nAdding a component is one `ComponentFunc` in `internal/core/render.go` — that is\nthe whole extension surface. Full reference:\n[docs/components.md](docs/components.md).\n\n## On-disk layout\n\n**The files katra writes are its public API.** They are markdown in your\nrepository, readable without the tool, and they outlive it.\n\n```\nkatra/\n  config.yml        title, accent, hook behaviour, memory settings\n  entries/          one .md per post\n  tasks/  epics/  decisions/  articles/\n  media/            images, gifs, video, html embeds\n  .state/           machine state — ledger, receipts (gitignored)\n```\n\n```yaml\n---\ntitle: Reworked the swing arc\ndate: \"2026-08-03\"\ntags: [physics, gameplay]\nhash: 5ddc0f5          # or  hashes: [a, b]  for a chapter of commits\nstat: {f: 12, a: 340, d: 50}\nsummary: tuned the magnus model      # optional, for the index\nfeatured: true                       # optional → \"Deep Dives\" zone\ncover: media/hero.png                # optional banner image\n---\n\nMarkdown body. Write the *why*, not a paraphrased diff.\n```\n\nA directory is a katra if and only if it holds `config.yml`. The conventional\nname is `katra/`; `devlog/` is the pre-rename name and is still discovered, so\nolder repositories keep working without migration.\n\nThe compatibility rules a consumer can rely on: an unknown frontmatter key is\nignored, an unknown fence degrades to a code block, and an absent `type` means\n`entry`. Full contract: [docs/format.md](docs/format.md).\n\n## Commands\n\n| Command | What it does |\n|---|---|\n| `katra setup [--no-gate]` | Add the Claude Code skill/hooks plus portable Git auto-stamp and hub registration. Idempotent. |\n| `katra init [--title T] [--install-hook]` | Scaffold a harness-neutral Katra, with optional Git auto-stamp |\n| `katra new \"Title\" [--tags a,b] [--featured]` | Start a draft entry |\n| `katra append [text] [--entry slug] [--file -]` | Append markdown to a draft |\n| `katra capture <file> [--caption C]` | Import media into the active draft |\n| `katra compare <before> <after>` | Add a before/after slider |\n| `katra stamp [--hash H…] [--closes task]` | Stamp the draft with commit + diffstat |\n| `katra list [--drafts] [--json]` | List entries |\n| `katra serve [--port N]` | Live, auto-reloading page on the LAN |\n| `katra build [--out dir] [--all]` | Build a static site |\n| `katra hook install \\| uninstall` | Manage the auto-stamp git hook |\n| `katra doctor` | Find dangling media, parse errors, entries with no visual, stale epics |\n| `katra task \\| epic \\| decide \\| article` | The node model |\n| `katra task spec <slug> <ref>` | Point a task at its committed spec (`todo`/empty → `specced`) |\n| `katra reconcile …` | Declare what the current work is for |\n| `katra memory scan \\| status` | Claude Code memory ingest |\n| `katra hub serve \\| list \\| scan \\| install` | Across every registered katra |\n\nFull reference: [docs/cli.md](docs/cli.md).\n\n## Git integration\n\n`katra stamp` reads `git` to resolve the hash and compute the diffstat\n(`--numstat`). The optional **post-commit hook** removes the \"forgot to stamp\"\nfailure mode entirely:\n\n```bash\nkatra hook install\n```\n\nAfter each commit it stamps the active draft with that commit, skipping its own\nbookkeeping commits and commits that only touch the katra. By default the stamp\nis left as a working-tree change for you to commit; set `autoCommit: true` in\n`config.yml` to have the hook commit it itself.\n\nThe hook honours `core.hooksPath`. Under husky it installs to the tracked\n`.husky/post-commit`, **not** the generated `.husky/_/`, which husky rewrites on\nevery `npm install` — and whose shim exits before anything appended to it would\nrun.\n\n## The hub\n\nA katra is per-repository, which is right for the log and wrong for \"what have I\nbeen doing?\".\n\n```bash\nkatra hub serve          # http://localhost:4200 — every registered katra\nkatra hub install        # run it at login (macOS launchd)\n```\n\nOne project index, a cross-project board and roadmap, a merged chronological\nlog, and every project's own viewer under `/p/<id>/`. The registry lives at\n`~/.config/katra/registry.yml` and is pruned as a side effect of reading it, so\na project you deleted stops appearing without a cleanup step.\n\n`katra build --all` produces the same thing as a static directory. There is no\nhosted service, no account, and no sync. [docs/hub.md](docs/hub.md);\n[`contrib/`](contrib/) has the launchd agent and a systemd unit.\n\n## Contributing\n\nPRs are welcome — [CONTRIBUTING.md](CONTRIBUTING.md) has the setup, the test\nand lint commands, and the invariants a change should hold.\n[RELEASING.md](RELEASING.md) covers how a release actually ships, for anyone\ncutting one.\n\n## Status and scope\n\nPre-1.0, and written by one person for their own projects. The concrete\nversion of that, since \"battle-tested\" is easy to say and easy to check:\n**11 repositories, 237 entries, over four months.** Usage is\nlumpy rather than daily — one heavy month (24 of July's 31 days had an entry\nwritten) against three sparse ones before it.\n\nNobody else has used it yet, so every rough edge you hit is probably one nobody\nhas hit.\n\nTwo things that argue for it and one that argues against, all checkable:\n\n- The on-disk format is the stable part. It is markdown, and the compatibility\n  rules above are held to deliberately.\n- The tool is used to build itself, so `katra/` in this repo is a real sample of\n  its output rather than a demo.\n- **Keeping it wired across that many repos turned out to be a real problem.**\n  An audit in August found 40 unstamped drafts, three projects that had never\n  stamped an entry at all, and the agent hooks broken or missing in 9 of the 11.\n  Most of that was katra's fault — the hook installer wrote into a directory\n  husky regenerates, and the hub snapshotted its registry at startup — and both\n  are fixed. But the honest read is that the automation was quieter about\n  failing than it should have been, and `katra doctor` is worth running.\n\nCommand names and flags may still move.\n\n### Deliberately out of scope\n\n- **A hosted service.** Your log is markdown in your repo. There is nothing to\n  sign into and nothing to migrate off, and adding a backend would trade that\n  away for convenience.\n- **A general-purpose container install.** katra operates on your working tree,\n  your git history and your hooks. Containerising the whole tool means mounting\n  all three, at which point you have a worse local install. The release workflow\n  does build a minimal `katra-mcp` OCI wrapper solely so the official MCP\n  Registry has a package to index; it is not an alternative to the install\n  paths above.\n- **A theme system.** The viewer is one design, with `accent` as the knob. The\n  static build is a directory you can restyle yourself if you must.\n- **Feeds, comments, analytics.** It is a log for the people in the repo.\n- **Diff summarization.** katra will not write your entry from the diff. The\n  diff is the thing it *cannot* tell you, and an auto-generated entry would be\n  exactly the paraphrase this tool exists to avoid.\n\n### Known limitations\n\n- **The hub's launchd agent is macOS-only** as a first-class install.\n  [`contrib/systemd/`](contrib/systemd/) covers Linux, but there is no\n  `katra hub install` for it.\n- **`katra serve` and `katra hub serve` bind all interfaces**, so a headset or a phone on the LAN can\n  reach it. There is no auth. Do not run it on a network you do not trust.\n- **A slug change breaks inbound `[[wikilinks]]`.** The file path is the\n  identity; `katra doctor` reports the resulting missing links, but nothing\n  rewrites them for you.\n- **Media is committed.** A log full of gifs is a repository full of gifs. Git\n  LFS works, and nothing in katra knows or cares.\n- **The `stat` diffstat counts the whole commit**, including files unrelated to\n  the entry, and for a chapter it is the sum across commits. It is a sense of\n  scale, not an accounting.\n- **`katra build` overwrites what it generates, and only that.** `index.html`,\n  `app.js`, `styles.css` and `data.json` are rewritten on every build; files you\n  add alongside them are left alone. That combination is the trap. A generator\n  that clobbered the whole directory would teach you to be careful with it; this\n  one respects the file you added, which invites the reasonable conclusion that\n  it respects your edits generally — and then discards the one edit that had to\n  live *inside* a generated file.\n\n  So anything hand-edited into that page — an analytics snippet, social tags, a\n  favicon link — is silently gone on the next build, and the page still renders\n  perfectly while whatever it powered has stopped. Keep such insertions in a\n  step that runs **after** the build. Verify by checking that a fresh build plus\n  your step reproduces the file you committed: that the page looks right proves\n  it worked once, not that it is still working.\n- **A built site has one URL, so entries have no individual ones.** The viewer\n  routes on the fragment (`#/node/<slug>`), which the browser never sends to a\n  server. Two consequences, both worth knowing before you publish a katra\n  somewhere that matters:\n  - **Every entry shares one social card.** A link to any entry unfurls as the\n    site, not as that entry, because there is no per-entry URL for a scraper to\n    fetch. Setting tags on the one page that exists cannot change this.\n  - **Analytics count one pageview per visit, not per entry read.** Moving\n    between entries is a fragment change, which emits no navigation. A reader\n    who works through twenty entries registers as one pageview, so per-entry\n    readership is not merely inaccurate — it is absent.\n\n  These are one limitation, not two, and neither half can be fixed on its own:\n  both need a URL per entry. Injecting head tags into the built page addresses\n  neither — it cannot vary a card the server never routes, and it cannot emit a\n  navigation that never happens.\n\n  Per-entry pages the server can see are planned, and every published\n  `#/node/<slug>` link will keep working when they land. Until then, a katra is\n  a chronicle you can publish and link *to*, not one whose individual entries\n  can be linked, previewed, or counted.\n\n## Documentation\n\nBrowsable at <https://midwinter.io/katra/>, or in this repo:\n\n- [docs/quickstart.md](docs/quickstart.md) — install to first stamped entry.\n- [docs/workflow.md](docs/workflow.md) — the complete harness-neutral\n  spec-to-stamp workflow.\n- [docs/components.md](docs/components.md) — every component, the exact keys\n  each takes, and the recipe for charts and diagrams.\n- [docs/cli.md](docs/cli.md) — every command and flag.\n- [docs/configuration.md](docs/configuration.md) — every `config.yml` key, its\n  default and its failure mode.\n- [docs/format.md](docs/format.md) — the on-disk contract. Read it before\n  writing a tool that consumes a katra.\n- [docs/agents.md](docs/agents.md) — CLI, fifteen MCP tools, and the optional\n  Claude skill, hooks, commit gate, and memory ingest.\n- [docs/hub.md](docs/hub.md) — the registry and the cross-project views.\n- [docs/architecture.md](docs/architecture.md) — the seams, and where your\n  change belongs.\n- [CONTRIBUTING.md](CONTRIBUTING.md) — build, test, and the conventions the\n  codebase holds itself to.\n\nRunnable examples: [`examples/entry.md`](examples/entry.md) (every component in\none entry, rendered by CI), [`examples/config/`](examples/config/) (a minimal\nconfig and a fully-commented one), and\n[`examples/media/frame-times.html`](examples/media/frame-times.html) (a\nself-contained chart).\n\n## Acknowledgements\n\nThe wordmark is set in [Fraunces](https://github.com/undercasetype/Fraunces)\nby Undercase Type (Phaedra Charles and Flavia Zimbardi), used under the SIL\nOpen Font License 1.1. Brand assets, the palette and its measured contrast\nratios are in [branding/BRAND.md](branding/BRAND.md).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n",
  "bytes": 34976,
  "sha": "323582785627852497aa67810f1ab794e646270073ba368aafd284e2dc056766",
  "repo_slug": "craigjmidwinter/katra",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_craigjmidwinter_katra_fbe7f179/readme"
}