madelogdocs

Getting started

From an installed binary to a published page, on a repository you already have. Every step shows the output the current binary prints.

This walk-through uses a small repository called ledger that already had six commits in it when madelog arrived, and one Claude Code session behind the seventh. The output shown is what madelog 0.1.2 prints; only the home directory was shortened to /Users/ana. Timestamps are UTC, because that is what the log stores.

1. Check the machine

madelog onboard explains what is recorded, checks for the tools madelog works with, and offers to set up the repository you are in. It is safe to run again at any time.

$ cd ~/projects/ledger
$ madelog onboard
  +---+
  | +-+-+   madelog - Proof of Process
  +-+-+ |   Records how your project came together.
    +---+

madelog records three things: the commits you make, the AI sessions that produced them, and the one-sentence decisions you write with `madelog note`.
All of it stays on this machine, in .madelog/log.db, until you run `madelog publish`.
No keystrokes, no screenshots, no files git ignores. `madelog export` hands you everything back as JSON.

git      found    /opt/homebrew/bin/git
ollama   found    /usr/local/bin/ollama

AI tools madelog can read:
  claude-code: transcripts found in /Users/ana/.claude/projects, 1 session touches this repo
  codex: no rollouts in ~/.codex, so nothing to read
  cursor: Cursor keeps conversation bodies encrypted; madelog records that a session happened, 1 session touches this repo
  copilot: not installed
  AI sessions: not recording (no madelog log here — run `madelog init`)

Record every repository you commit in? [y/N] n
Only the repositories you run `madelog init` in, then.

Set up madelog in this repo now? [Y/n] n
Nothing changed. Run `madelog init` when you are ready.

The four AI tools are the ones madelog knows how to read. It reads their own local logs; there is nothing to install and nothing to connect. What each one can and cannot give is in AI sessions.

Answering y to "Record every repository you commit in?" installs three hooks in ~/.config/madelog/hooks/ and points git's global core.hooksPath at them, so madelog gets a chance to record a commit in a project you have not set up yet. Hooks you already had are chained, never replaced. Two more questions follow: whether a new repository should be set up automatically, and whether its AI sessions should be recorded. Both answers go in ~/.config/madelog/config.toml, and you are never asked again.

Answer y and it runs madelog init for you, questions included. Outside a git repository it prints git init and madelog init as the two things to do next and stops. In a repository that already has .madelog/log.db it says so and lists madelog log and madelog verify.

Before madelog init, every command that needs the log stops with the same sentence:

$ madelog log
madelog: no madelog log in this repository yet — run `madelog init` first (/Users/ana/projects/ledger/.madelog/log.db is missing)

2. Set up the repository

madelog init asks three questions and one yes/no, then starts recording — and records the history the repository already had, without being asked twice.

$ madelog init
What are you building? a tool that reconciles a bank export against outstanding invoices
Who is it for? a small accounting studio hiring a junior
Which AI tools do you use? (comma separated) claude-code, cursor
Record the AI sessions behind your commits? [Y/n] y
madelog is recording in /Users/ana/projects/ledger/
  .madelog/intent.md      what you are building, in your own words
  .madelog/log.db         the event log, hash chained
  .madelog/config.toml    settings, including privacy.redact
Your commits are recorded from now on. Nothing leaves this machine.

6 commits in this repository are not recorded yet.

recorded 6 commits, oldest first
  from ff8a9fd  to cd2619d
  no AI sessions were attached: the logs of a session that ran months ago are long gone, and guessing at one would be worse than leaving it out

note: backfilled commits carry no secs_since_prev signal. They were all recorded just now, so the gaps between records say nothing about the gaps between commits; the page uses committed_at for the timeline instead.

Next: `madelog note "why you did it that way"` after a decision.

What happened, in order:

  1. .madelog/ was created with intent.md (your answers as Markdown), config.toml (defaults plus project.name = "ledger", the directory name) and log.db.
  2. The first event, intent, was appended, anchored to the commit HEAD points at.
  3. .madelog/ was added to .git/info/exclude, so it never shows up in git status and never enters a commit.
  4. Two lines were appended to each of .git/hooks/post-commit, .git/hooks/post-merge and .git/hooks/post-rewrite. An existing hook is kept and madelog's lines are added once, however many times you run init:
#!/bin/sh
echo "hello from an older hook"

# madelog-cli:post-commit - records this commit in .madelog/log.db
madelog hook post-commit || true

post-commit records the commit you just made. post-merge records the one a merge or a git pull leaves at HEAD. post-rewrite records the commits a rebase or a git commit --amend produces, so a rewritten branch does not leave a hole in the record.

  1. A device key was created at ~/.config/madelog/device.key (65 bytes, mode 0600) if there was none. It signs every publish.

All four answers can be passed as flags for scripts: --what, --for-whom, --tools, and --record-ai-sessions or --no-record-ai-sessions. --yes skips every question and takes the flags given and empty answers for the rest. --no-backfill leaves the existing history alone. Running init a second time appends another intent event and rewrites intent.md; the page reads the newest event, so pass all four answers again when you do.

$ madelog log
   1  2026-09-18T12:36:14.646Z  intent     cd2619d  a tool that reconciles a bank export against outstanding invoices
   2  2026-09-18T12:36:14.647Z  commit     ff8a9fd  chore: readme and gitignore
   3  2026-09-18T12:36:14.648Z  commit     8b2a013  feat: an empty binary that runs
   4  2026-09-18T12:36:14.648Z  commit     6d9f7e2  feat: read a bank export as csv
   5  2026-09-18T12:36:14.649Z  commit     0c4a8b1  fix: importer dropped the last row without a trailing newline
   6  2026-09-18T12:36:14.649Z  commit     a71e33c  feat: load outstanding invoices
   7  2026-09-18T12:36:14.650Z  commit     cd2619d  refactor: one Money type instead of f64 everywhere

3. What that backfill did

madelog init walked first parents back from HEAD and recorded every commit the log was missing, oldest first, with the same file statistics and diff excerpts the hook writes. A page that starts on the day you installed madelog would say the project took a day, which is the one thing it must not say.

Each backfilled event carries two times: ts, the moment madelog recorded the commit, which is now, and committed_at, the moment git says the commit was made. The page draws its timeline, its date range and its day count from committed_at, so a project that madelog joined in September still shows the August it really had. The verification badge keeps using ts, because what it certifies is the chain. Both are in madelog status:

last commit  97c43c9  fix: equal amounts with a different reference were counted as a match
             recorded 2026-09-18T12:36:15.920Z   committed 2026-09-18T12:36:15Z

madelog backfill is the same walk, by hand, for a branch you merged later or a repository you initialised with --no-backfill. Run it twice and nothing happens — nothing to record: all 8 commits on this branch are already in the log. Use --since 2026-08-01 or --since <sha> to start from a point rather than the beginning, and --yes to skip the question.

No AI sessions are attached to backfilled commits. A commit from March did not come out of a session that ran this afternoon, and the logs of the session that did produce it are long gone; inventing one would be worse than leaving it out.

4. Commit, and write down a decision

From here everything is automatic. The hook records the commit, reads the AI sessions that ran since the last one, looks for patterns, keeps the weekly summaries current and re-renders the page — silently, and without you typing anything:

$ git commit -m "feat: match bank rows to invoices by amount and reference"
hello from an older hook
[main 9a1246f] feat: match bank rows to invoices by amount and reference
 2 files changed, 9 insertions(+)
 create mode 100644 src/reconcile.rs

madelog printed nothing. A hook that chatters at every commit is a hook people delete.

When you choose something, say why. One sentence, no editor, anchored to the commit you are on:

$ madelog note "matched on reference as well as amount because two invoices can share a total"
noted at 97c43c9
$ madelog note --private "the client sends CSVs with a BOM; strip it before parsing"
noted at 97c43c9 (private, never published)

A private note stays in the chain and never reaches the page, the preview or the summaries. An empty note is refused: madelog: a note needs a sentence: madelog note "why you did it that way".

5. The AI session behind that commit

The commit above came out of a Claude Code session. madelog read that session's own transcript, compared the lines the model wrote with the lines the commit added, and recorded what matched. A Cursor session that ran in the same window is recorded too — Cursor keeps its conversation bodies encrypted, so that one says it happened and claims nothing more.

$ madelog log
   1  2026-09-18T12:36:14.646Z  intent     cd2619d  a tool that reconciles a bank export against outstanding invoices
   2  2026-09-18T12:36:14.647Z  commit     ff8a9fd  chore: readme and gitignore
   3  2026-09-18T12:36:14.648Z  commit     8b2a013  feat: an empty binary that runs
   4  2026-09-18T12:36:14.648Z  commit     6d9f7e2  feat: read a bank export as csv
   5  2026-09-18T12:36:14.649Z  commit     0c4a8b1  fix: importer dropped the last row without a trailing newline
   6  2026-09-18T12:36:14.649Z  commit     a71e33c  feat: load outstanding invoices
   7  2026-09-18T12:36:14.650Z  commit     cd2619d  refactor: one Money type instead of f64 everywhere
   8  2026-09-18T12:36:14.819Z  commit     9a1246f  feat: match bank rows to invoices by amount and reference
   9  2026-09-18T12:36:14.820Z  ai_session 9a1246f  cursor: (prompt not recorded)
  10  2026-09-18T12:36:14.820Z  ai_session 9a1246f  claude-code: match bank rows to invoices by amount and reference
  11  2026-09-18T12:36:14.821Z  summary     week of 2026-08-31
  12  2026-09-18T12:36:14.821Z  summary     week of 2026-09-07
  13  2026-09-18T12:36:14.821Z  summary     week of 2026-09-14
  14  2026-09-18T12:36:15.920Z  commit     97c43c9  fix: equal amounts with a different reference were counted as a match
  15  2026-09-18T12:36:15.921Z  pattern    97c43c9  fix_after_ai: 97c43c9 reworked AI-written code in src/reconcile.rs less than a minute after 9a1246f.
  16  2026-09-18T12:36:15.921Z  summary     week of 2026-09-14
  17  2026-09-18T12:36:15.931Z  decision   97c43c9  matched on reference as well as amount because two invoices can share a total

The columns are the sequence number, the timestamp, the kind, the short sha the event is anchored to, and a one-line title. Events 11 to 13, 15 and 16 were written by the hook: the summaries and the pattern arrive with the commits rather than waiting for you to ask.

madelog log --json prints the same events with every field, including each event's id, which madelog hide and madelog summary edit take. Event 10 in full:

{
  "seq": 10,
  "ts": "2026-09-18T12:36:14.820Z",
  "kind": "ai_session",
  "commit_sha": "9a1246f17ed3b776cfae898d8ebc1ce2b3e874d2",
  "payload": {
    "accepted": true,
    "files": [
      { "ai_lines": 1, "ai_lines_modified_by_human": 0, "path": "src/main.rs" },
      { "ai_lines": 7, "ai_lines_modified_by_human": 0, "path": "src/reconcile.rs" }
    ],
    "model": "claude-sonnet-4-5-20250929",
    "prompt": "match bank rows to invoices by amount and reference",
    "response_excerpt": null,
    "session_id": "5f2c8a41-9d0e-47b6-bb3a-1c4e7a90d2f8",
    "tool": "claude-code"
  }
}

Seven of the eight lines in src/reconcile.rs are there word for word, so they count as the model's. Had you rewritten one before committing, it would have moved to ai_lines_modified_by_human. AI sessions has the whole rule, the per-tool table and the list of things that are never captured.

6. Check the chain

$ madelog verify
chain ok
17 events, root be71da247ec0e1eb3f54a8e1ab2cd0d1ed5f3fcd8e1b0e1c5b46b6dd8e8c6a21

The root is the hash of the last event. It changes with every event you add, and it is what the server stores when you publish. Concepts shows what a broken chain looks like.

7. Look for patterns

The hook already ran the four rules after each commit, so madelog analyze mostly confirms what is in the log. It prints the moments the page would show and records anything the hook has not:

$ madelog analyze
1 pattern in this log:
  fix_after_ai  9a1246f 97c43c9          97c43c9 reworked AI-written code in src/reconcile.rs less than a minute after 9a1246f.
nothing new to record

1 moment for the page:
  1. Fixing AI-written code in src/reconcile.rs (fix_after_ai)
     9a1246f ("feat: match bank rows to invoices by amount and reference") carried AI-written lines in src/reconcile.rs. Less than a minute later, 97c43c9 went back to the same code: "fix: equal amounts with a different reference were counted a...". 8 lines in 9a1246f were written by the model.

nothing new to record is the normal answer in a repository where the hook has been running. On a history that arrived through madelog backfill, where no hook ever saw the commits, the same command says recorded 1 new pattern event.

A log with nothing to find prints no patterns yet — keep committing and no moments yet — a moment needs a fix after AI, a decision that changed the shape of the project, or a long gap. The four rules and their thresholds are in Concepts.

8. Write the summaries

The hook already wrote a summary for every week the log covers, and rewrites the newest one whenever a commit lands in it, so there is usually nothing to do here. madelog summarize exists for the two cases the hook will not handle: a week you want rewritten from scratch (--force), and a model the hook refused to wait for.

Weeks are Monday to Sunday from the first commit. With Ollama running and the configured model already pulled, the model writes them; otherwise a template does, and the command says why. The hook only ever uses a model that is already on the machine, and never waits more than three seconds for it — madelog summarize is where you spend the minute.

$ madelog summarize --week 3 --force
week 3  2026-09-14 to 2026-09-20  (generated)  heuristic  01a0b487-9446-76be-be98-c64a96643bf0
In the week of 2026-09-14 you made 3 commits over 2 days. Most of the changes landed in src/reconcile.rs, src/invoice.rs and src/main.rs.

You wrote down 1 decision: matched on reference as well as amount because two invoices can share a total.

The analysis found 1 pattern that week: a fix after an AI session (97c43c9 reworked AI-written code in src/reconcile.rs less than a minute after 9a1246f).

edit any of them with `madelog summary edit <id>`

The last value on the week 3 line is the summary event's id. Editing opens the text in your editor and appends a new summary event in your words:

$ madelog summary edit 01a0b487-9446-76be-be98-c64a96643bf0
week of 2026-09-14 to 2026-09-20  (edited by you)  01a0b487-95c1-7b0e-8a44-6d2f0c9e7b13
This week I built the matcher and fixed the equal-amount bug the same afternoon.

Two invoices for the same client can come to the same total, so amount alone was never going to be enough.

A summary you edited is never written over: the hook leaves the newest week alone once edited is true. See Summaries for the provider order and the marker the page shows.

9. Render the page locally

madelog publish --local does everything publish does except talk to a server: it refreshes patterns and summaries, opens the preview, and writes .madelog/site/index.html. With --yes there is no preview.

$ madelog publish --local --yes
1 pattern in this log:
  fix_after_ai  9a1246f 97c43c9          97c43c9 reworked AI-written code in src/reconcile.rs less than a minute after 9a1246f.
nothing new to record

1 moment for the page:
  1. Fixing AI-written code in src/reconcile.rs (fix_after_ai)
     9a1246f ("feat: match bank rows to invoices by amount and reference") carried AI-written lines in src/reconcile.rs. Less than a minute later, 97c43c9 went back to the same code: "fix: equal amounts with a different reference were counted a...". 8 lines in 9a1246f were written by the model.

every week already has a summary — run `madelog summarize --force` to write them again

page   /Users/ana/projects/ledger/.madelog/site/index.html
not published. Run `madelog publish` without --local to put it online.

Without --yes, and with a terminal, the preview opens first. It is one screen with three panes: the timeline on the left, the selected item on the right, the summaries at the bottom. Keys: j/k or the arrows move, h hides the AI session under the cursor, e opens the selected summary in your editor, Tab/n and Shift-Tab/p step through summaries, PageUp/PageDown scroll the detail pane, Enter confirms, q or Esc aborts with exit code 1 and the message publish aborted. Without a terminal, publish proceeds without the preview.

The hook already rendered the page after your last commit, so madelog publish --local mostly confirms it. madelog open opens it in your browser, rendering it again first when the log has moved on since it was written; madelog open --print prints the path instead. madelog status says page older than log — madelog open renders it again when that is the case. The badge at the bottom reads "Not yet published". With fewer than 15 events the command prints a warning first and goes on:

warning: this project has 13 events so far, and a page built from fewer than 15 events says very little. It will still publish. Keep committing and run `madelog publish` again later.

10. Publish

The same command without --local signs the chain root with your device key and sends the page to the server.

$ madelog publish --yes
...
page   /Users/ana/projects/ledger/.madelog/site/index.html
page   /Users/ana/projects/ledger/.madelog/site/index.html
verify https://madelog-server-production.up.railway.app/v1/verify/north-fable-4xt3

published  https://madelog-server-production.up.railway.app/p/north-fable-4xt3
Send that link. `madelog unpublish` takes it down again.

page is printed twice because the page is rendered twice: once with "Not yet published" in the badge, uploaded to get a slug, then once more with the server's confirmation and uploaded again to the same slug. What was sent, both times: the rendered HTML, the root, the first and last event timestamps, the publish timestamp, and an ed25519 signature over root, timestamp and slug. The server answers with a slug, which madelog stores in .madelog/config.toml as server.slug, next to the server.device_id it gave your public key:

[server]
url = "https://madelog-server-production.up.railway.app"
device_id = "5ea56900-b0d3-4683-aa32-fe1e3eca821c"
slug = "north-fable-4xt3"

The next madelog publish replaces the same page instead of minting a second link. Anyone can compare the badge with the server's record:

$ curl -sS https://madelog-server-production.up.railway.app/v1/verify/north-fable-4xt3
{"slug":"north-fable-4xt3","root":"6cc1406d82499fc6f69483a603f1f5efe04ac8767edd31674a4f1afaf3b18535","first_ts":"2026-09-18T07:53:48.617Z","last_ts":"2026-09-18T07:53:49.507Z","received_at":"2026-09-18T07:53:50.471573Z","device_id":"5ea56900-b0d3-4683-aa32-fe1e3eca821c"}

The hosted server answers at madelog-server-production.up.railway.app today and moves to a madelog.app address once that domain is attached; the pages published before keep working at the address they were given.

11. Take it down

madelog unpublish deletes the page from the server. The chain on your machine is untouched, and madelog publish can put the page back at the same address.

$ madelog unpublish
north-fable-4xt3 is gone from https://madelog-server-production.up.railway.app. The chain is untouched, and `madelog publish` can put it back.
$ curl -sS https://madelog-server-production.up.railway.app/v1/verify/north-fable-4xt3
{"error":"not_found","message":"no such page"}

When something looks wrong

madelog doctor checks the setup and, with --fix, repairs what is safe to repair — the three hooks, the .git/info/exclude entry and the mode of your device key. It never touches the log.

$ madelog doctor
ok    log            17 events, chain verifies, root be71da247ec0
ok    post-commit    calls madelog
ok    post-merge     calls madelog
ok    post-rewrite   calls madelog
ok    git exclude    .madelog/ is excluded
ok    device key     /Users/ana/.config/madelog/device.key is there, mode 0600
ok    claude-code    transcripts found in /Users/ana/.claude/projects, 1 session touches this repo
warn  codex          no rollouts in ~/.codex, so nothing to read
ok    cursor         Cursor keeps conversation bodies encrypted; madelog records that a session happened, 1 session touches this repo
warn  copilot        not installed
ok    ai sessions    recording
ok    ollama         off by configuration (llm.provider = none)
ok    server         https://madelog-server-production.up.railway.app answers

2 things madelog can live without are missing; nothing is broken

warn is for things madelog can live without, and it does not change the exit code; only a FAIL does, and only then does the command exit 1.

From here: Workflow follows a project over three weeks, and Commands has every flag, exit code and edge case.