Files
Maven/docs/workflow.md
T

5.0 KiB

Session workflow: the five stores and the guards

Last verified: 2026-08-25 @ 5cae33a

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: <date> @ <sha> 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 may start without one: filing is not a gate on work (owner's call, 2026-08-25).

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.

task start <vikunja-id>   # 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

One hook in .githooks/, tracked, wired with core.hooksPath. A fresh clone needs git config core.hooksPath .githooks. commit-msg and its (V-<id>) requirement were deleted on 2026-08-25: a subject ref that names a task nobody filed is a wrong link, not a record.

  • pre-commit refuses master, and refuses more than 300 changed lines in non-markdown files. Markdown is exempt and may land as one batch.

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.

Static gates

Three analyzers, one target each, and make analyze for all three. The 2026-08-10 audit asked for them because none was installed on the box and make audit is a git-grep inventory, not analysis. Do not read make audit as a gate.

  • make vuln, govulncheck over ./... (V-682).
  • make lint, staticcheck over ./... (V-694).
  • make deadcode, deadcode with -test over ./... (V-694).

None of the three joins make test. All three install over the network, and test has to pass on a box with no route out. vuln reads the advisory database at run time as well. Run make analyze before a dependency or toolchain bump lands, and before calling a symbol unreachable.

Each tool is pinned in the Makefile beside GO_VERSION. A gate that moves on its own is not a gate. Each installs into deps/bin, because a tool is not a dependency of the module.

staticcheck and deadcode pass against a baseline, not against zero. The accepted findings live in scripts/analyzers/*.baseline, one line each. A key holds file, check id and message, never a line number. A line number goes stale on the next edit above it. The output then reports moved findings as new ones, and the reader learns to skip it.

scripts/analyzer-gate.sh gives the verdict. A finding absent from the baseline fails. So does a baseline entry whose finding is gone, which is what stops the accepted set from outliving the repo. Deleting the entry is part of each fix.

deadcode runs with -test because a test is a caller. Without the flag the report is 172 lines, most of internal/router/eval, none of it a mistake.

A baseline entry carries the reason it stays. Three reasons appear. Another task owns the finding. The check cannot see through a false positive. A cosmetic finding waits for a sweep.