# Session workflow: the five stores and the guards *Last verified: 2026-08-09 @ a9b480a* How a session starts, where each kind of writing belongs, and what the hooks refuse. `CLAUDE.md` carries the commands. This file carries the reasoning. ## Five stores Each owns something the others must not hold. | Store | Holds | Lifetime | |---|---|---| | Vikunja task | goal, constraints, assumption ledger, status | durable | | `CLAUDE.md`, `AGENTS.md` | what an agent must know before touching code | durable | | `docs/` | design, measurements, decisions | durable | | `TASK.md` | the brief for this branch, written by `task start`, immutable | one branch | | `HANDOFF.md` | only what the next agent needs to resume | one session | `TASK.md` and `.task/` are excluded through `.git/info/exclude`. `HANDOFF.md` is gitignored and injected at session start. If a line in the handoff would still matter next week, it is in the wrong file. ## Doc tiers Tiered by path, so staleness is visible from the filename. - Files directly under `docs/` are living. They carry a `Last verified: @ ` line and are corrected in place. - Files under `docs/evals/` are dated measurements and are never edited after the day. A newer number is a new file, not an edit. - Files under `docs/archive/` are dead and read by nobody by default. ## Vikunja This repo is project **Maven** (ID 2). MCP at `http://localhost:9100/mcp`, or `http://192.168.1.104:9100/mcp` from workpc. Feature, bug and deploy tasks go there. A task holds the goal, the constraints and the assumption ledger. A session without a task id cannot be resumed by anyone, so a session with none asks for one first. **Close a finished task with `done: true` and nothing else** (owner's call, 2026-08-07). Do not write a completion summary into the description on the way out. It is lost anyway, and the durable record is the commit messages and the merged PR. `update_task` carrying a `description` resets `done` to false, which is why a write-up ever took two calls. ## The branch tool `~/.local/bin/task` owns the branch, the commit identity and the PR. One task, one session, one PR. ```sh task start # branch off origin/master, write TASK.md, fetch review comments task pr # push, open or refresh the PR, label Vikunja, notify task comments # re-pull this branch's review comments into .task/ ``` `/pickup` opens a session and `/wrap` closes it. Wrap at roughly half context rather than letting the session compact. ## Guards Two hooks in `.githooks/`, tracked, wired with `core.hooksPath`. A fresh clone needs `git config core.hooksPath .githooks`. - `pre-commit` refuses master, and refuses more than 300 changed lines in non-markdown files. Markdown is exempt and may land as one batch. - `commit-msg` requires the subject to end with `(V-)`. `V-` and not `#`, because Gitea autolinks `#123` to a Gitea issue, which is the wrong tracker. Two more guards live outside the repo, in `~/.claude/hooks/`. `diff-budget.sh` blocks further edits past 600 changed lines on a `task/` branch. `prose_lint_hook.py` checks prose on every write. Both measure against `origin/master`, so a local master that is ahead of the remote makes the diff budget read high. `--no-verify` exists. Using it means saying why in the commit body.