a926383827
The 2026-08-10 audit asked for three analyzers. V-682 wired the first as `make vuln`. The other two were still absent: neither was installed on the box and no target ran them, so every reachability claim in the audit stood unchecked. `make lint` runs staticcheck v0.7.0 and `make deadcode` runs deadcode v0.48.0. Both are pinned in the Makefile beside GO_VERSION and installed into deps/bin the way govulncheck is, because a tool is not a dependency of the module. Both carry the CGO env `test` carries, or the four CGO daemons fail to load and the analyzer reports a build error instead of a finding. `make analyze` runs all three. None joins `make test`: they install over the network and `test` has to pass on a box with no route out. Neither reports zero, so neither fails on its own output. staticcheck finds 20 and deadcode finds 13, and the audit asked for an allowlist by name, because three of deadcode's eleven production symbols are deliberate and an unannotated list invites deleting them. The accepted set lives in scripts/analyzers/*.baseline, one line per finding with the reason it stays, and scripts/analyzer-gate.sh gives the verdict. A key holds file, check id and message, never a line number: a line number goes stale on the next edit above it, and a gate that reports moved findings as new ones teaches the reader to skip it. An entry whose finding is gone also fails, so a fix that leaves its line behind does not pass. deadcode runs with -test, because a test is a caller. Without the flag the report is 172 lines, most of internal/router/eval, and none of it is a mistake. With it, the 11 symbols the audit listed come back exactly, plus two test helpers it did not count. Three staticcheck findings were checked and are false positives, recorded as such: the iCal determinism test must call RenderICal twice, the morning hedge loop breaks after the first rune on purpose, and the SA9009 line is prose about //go:embed with the real directive below it. One is V-687 already. The remaining 17 are V-701 with the judgement on each. The analyzers caveat is deleted rather than edited. What replaces it is the limit that is now true: the gates are green against a baseline, not against zero.
118 lines
5.0 KiB
Markdown
118 lines
5.0 KiB
Markdown
# Session workflow: the five stores and the guards
|
|
|
|
*Last verified: 2026-08-11 @ 557f5a3*
|
|
|
|
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
|
|
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 <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
|
|
|
|
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-<id>)`. `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.
|
|
|
|
## 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.
|