Setup
Each part of this page says what you do and what you see afterwards. Every single setting (Einstellung) is listed with its effect and default under Settings.
Installation
What you do. Change into your project folder, download the installer and start it:
curl -O https://docs.mwolff.org/install.mjs
node install.mjsWhat your computer needs for this is listed in the getting started guide.
The installer asks in this order:
- Language of the kit,
deoren. - Global or project-local. Global:
~/.claude/skills/, applies in all projects. Project-local:./.claude/skills/, the way for a team. - Code host:
github,gitlaborlocal. - Issue tracker:
github,gitlaborlocal; defaultgithub. - Main branch (target of
/push-main) and production branch (target of/merge-production). - Review scope:
diffshows the reviewer only the changed lines,fullall files. - Review model or review command, exactly one of the two. A model runs as a subagent; a command starts a foreign tool that receives the prompt via stdin.
- Hook in the commit gate, project-local only and only if no other
core.hooksPathis set. - Areas and, for each existing check, its assignment, project-local only (see Areas); on first setup with the suggestion from
areas suggest; empty leaves everything as it is. - Vault path, global only. With a path the installer writes
~/.claude/kontext.config.json.
With github and a project-local installation, three questions on protecting the main branch follow the code host question: whether the project has GitHub Actions, whether the check before the push should move there and the main and release branches be protected, and which check branch applies (default kit-pruefung). With yes the installer pushes to the check branch as a probe, waits for the build service and reports scharf (rulesets set), anleitung (probe passed, the output names the steps at GitHub), offen (no run of the build service) or nicht moeglich.
An existing project switches protection on later with node install.mjs --protection; the call changes only pushCheck. node .claude/kit/board.mjs code protection status names the state at any time.
What you see afterwards. Restart Claude Code. The seventeen skills appear under /help, and /session-start reports the state of the project. The installer reports uncommitted files last with WARNING; commit or discard them before the runner, otherwise it stops.
What the installer writes
- the skills under
.claude/skills/, .claude/workflow.config.jsonwith your answers,CLAUDE-workflow.mdwith the process description and the gate registersCLAUDE-Fachplan.mdandCLAUDE-Plan.md,- the tools under
.claude/kit/: board adapter (Board-Adapter)board.mjs, check run (Prüflauf)checks.mjs, runner (Runner)night.mjsand their parts, - with a project-local installation, the block in
.gitignore, the hooks in.claude/settings.jsonand the commit gate under.githooks/.
Everything under .claude/ except the config is generated: the next installer run writes it anew. It therefore does not belong in the repo. The block in .gitignore begins like this:
.claude/*
!.claude/workflow.config.json
.claude/workflow.config.local.jsonThe first line must read .claude/*, not .claude/: git does not descend into an excluded directory, and the exception in the second line would have no effect. The further lines of the block name the tools' state files. The installer does not overwrite the config itself, it merges: defaults, above them the existing file, on top your answers. Fields not asked for, such as buildChecks, stay. The config is a team setting and belongs under version control; you commit it once yourself.
Hooks. The installer enters three entries in the hooks block in .claude/settings.json. Under SessionEnd and Stop Claude Code calls node .claude/kit/board.mjs session report; the session reporter (Sitzungs-Melder) sends the session's consumption to the board's control centre (Leitstand), finally at the end of the session and throttled per turn. It reports only with the tracker toolbox and a project-bound token and stays silent otherwise. Under PreToolUse with matcher Bash stands the hook bash-check. It rejects a Bash line that joins an exception from sandbox.excludedCommands with a pipe, a redirect or a follow-up command, because the call would then run in the sandbox after all. It also rejects a call with run_in_background as soon as KIT_AGENT_MODEL is set: without supervision a session has no follow-up turn, and the result would be lost. Interactively, background work stays allowed. Your own entries stay.
Switching off. Delete the entry from the hooks block. The file is not under version control, so the decision applies to your machine. The next installer run enters it again; you silence the reporter permanently by taking away its token.
The known gap: worktrees. A freshly created git worktree carries only versioned files, so neither settings.json nor .claude/kit/. A session in it reports nothing and has no hook. Remedy: copy both over from the main working tree. The worktrees of the chain (Kette) are reported by the runner itself.
Protected files
.claude/settings.json, .claude/settings.local.json and .claude/hooks/ may only be written by a human; Claude Code rejects every write access to them. A work package (Arbeitspaket) that has to change one of them stops. The label kit:protected, like review:done, has to be created once per board; unlike kit:clarify it shows not an open question but a waiting action, and only the human may remove it. The halt comment begins with ## Geschuetzte Datei and names every file together with the line from the package.
Already when a package is written, issue check-form checks the form gates I7 to I9: the task names at least one file (I7), none of them is protected (I8), and it names the source instead of the installed copy under .claude/kit/ (I9). Which protected files a package names and what the halt comment says is supplied by issue check-protected <id>; with --path <pfad> a path rejected while writing is added. In the run report (Laufbericht) such a halt appears with the kind geschuetzt, separate from the open questions.
What you do then. Make the change to the file yourself and remove kit:protected. At the next attempt the package counts as released.
The commit gate
The commit gate rejects every commit that was not preceded by a green node .claude/kit/checks.mjs run on the same state. It applies to every commit in the repo: from the skills, by hand, from a GUI client. A tool without node in the PATH is rejected.
Two versioned files carry it: .githooks/pre-commit calls .githooks/gate.mjs. It becomes active through the local git config core.hooksPath .githooks, which the installer sets. If core.hooksPath is already taken, for example by Husky, it changes nothing; then you add node .githooks/gate.mjs pre-commit to your own hook.
What it checks. Every check run writes .claude/checks-summary.json, with a hash per changed file, formed before the first command. The gate compares these hashes with the index, that is, with what is really committed. The commit is green only if the run was green and every staged file is in it with a matching hash.
What it does not do.
--no-verifybypasses the hook. The git workflow forbids that; mechanically nothing prevents it.- A fresh clone has the gate only after an installer run or
git config core.hooksPath .githooks. - With its own hook manager the project hooks in the call itself.
- The assurance applies per committed file, not to the state as a whole. Whoever commits
Aand leaves the matchingBlying around creates a state nobody has checked.
Unattended, the runner additionally evaluates the evidence after every round; if it is missing or red, the round counts as a failure. Interactively nobody steps in; there the gate remains the safeguard.
Which version is my installation?
node .claude/kit/board.mjs --version
node .claude/kit/night.mjs --versionBoth name their kit version; node install.mjs --version names the current one. If your copy lags behind, you run the installer again. If the two files drift apart, the runner warns at start.
What is the vault?
The vault is a personal memory store outside the repo: profile, working rules, decisions, daily logs. /session-start loads it, /document writes into it at the end of the session. Without a vault both skills run in degraded mode.
What you do. Create a directory and enter its path in ~/.claude/kontext.config.json; with a global installation the installer does that. The fields are listed under kontext.config.json. This structure is expected:
/pfad/zum/vault/
Index.md (always-Datei)
Profil.md (always-Datei)
Projekte/{repo-name}/{repo-name}.md
Log/YYYY-MM-DD.mdYou create Projekte/ and Log/ once by hand.
What you see afterwards. /session-start names at the beginning what it loaded from the vault, and /document writes the daily log and the project note.
The config file
.claude/workflow.config.json holds everything project-specific. The skills read only from it:
{
"codeHost": "github",
"issueTracker": "github",
"buildChecks": ["<build-kommando>", "<test-kommando>"],
"mutationCommand": "",
"mainBranch": "main",
"productionBranch": "production",
"reviewScope": "diff",
"reviewModel": "claude-opus-4-8",
"triggers": { "go": "GO", "push": "push main", "merge": "merge production" },
"github": { "projectNumber": 11 }
}buildChecks contains the check commands that node .claude/kit/checks.mjs run executes before every commit and /push-main before the push: the affected ones, selected by the areas the package touches. Every one executed must be green. mutationCommand runs once in checks.mjs run --stage push, after the buildChecks; its result is only reported and stops nothing. Empty switches it off. triggers holds the phrases of the three stop points (Stop-Punkte). columns renames the board's columns.
Instead of editing the file by hand, you can use the settings interface (Einstellungs-Oberfläche). It checks before saving.
Area-based checks: checkAreas
An entry in buildChecks can say which areas (Bereiche) it is responsible for:
{
"buildChecks": [
"npx eslint .",
{ "cmd": "npm --prefix frontend run build", "areas": ["frontend"] },
{ "cmd": "mvn verify", "always": true }
],
"checkAreas": {
"frontend": ["frontend/**"],
"backend": ["src/main/**", "pom.xml"]
}
}The bare command string always runs. { "cmd", "areas" } runs when one of the areas is touched. { "cmd", "always": true } always runs.
String form and always behave the same and say different things: the string may be forgotten, always is decided.
areas and always exclude each other, and areas must not be empty.
checkAreas assigns path patterns to every area. * applies within a path segment, ** across segment boundaries; / is the separator every path is normalised to. The kit brings the evaluation along, without a runtime dependency.
The implement skills check before the commit against HEAD: the diff is exactly this work package. /push-main checks before the push against git merge-base HEAD origin/<mainBranch> and measures everything since the last push.
Swapped, both would go wrong. merge-base before the commit would collect foreign packages, and a red result would hit the wrong issue.
HEAD before the push would let the summary attest the wrong state: its hashes would describe the last package instead of the batch going out, and exactly this evidence is what the commit gate reads.
When in doubt, everything runs, not switchable off. The full scope is pulled in by a file that is assigned to no area, by an unassigned check and by an empty or unresolvable anchor.
If changes often end up in the full scope, the assignment should be improved, not the rule softened.
Files with nothing to check go into noCheck, each with a pattern and a mandatory reason. They trigger no check and appear in the report with their reason. If a file also matches a pattern from checkAreas, the area wins. A JavaScript file without a pattern inherits the areas of the files that import it.
Without checkAreas all checks run unchanged; nobody silently loses a check.
mutationCommand is not part of the selection and always runs at the push stage.
node .claude/kit/checks.mjs plan shows the selection without executing anything.
Staged checks: stage
An entry can say when its turn comes (stage, Stufe):
{ "cmd": "mvn verify -Pintegration", "stage": "push" }| Stage | Point in time | Skill |
|---|---|---|
paket | before the local commit | /implement-next, /implement-ready, /implement-done |
push | before publishing to main | /push-main |
merge | before the release to production | /merge-production |
The stages are cumulative, and every check runs once before the release: push main runs the package and push stages in full, merge production only what push main did not check. No check is dropped; a later one appears before as an omission with its stage as the reason, at merge production for example as Stufe push, geprueft beim push main. If the field is missing, paket applies, and existing configurations stay unchanged.
If a run of the package stage is red and formatFixCommand is set, checks.mjs run runs the fix once and a second pass; that is not a repetition in the sense of --retry.
Short release. Put long checks on the stage push instead of merge; the price: every push main takes longer by them.
At least one check belongs on the package stage (Paketstufe), otherwise a work package has no gate, and the runner does not start. stage is neither one of the reviewStages nor the task level (Aufgabenstufe) of the chain.
Completion and publishing: notAtCompletion
A check of stage paket can be left out when a single card (Karte) is completed and run only before publishing:
{
"buildChecks": [
{ "cmd": "mvn -q test", "areas": ["backend"] },
{ "cmd": "mvn -q verify -DskipUnitTests", "areas": ["backend"], "notAtCompletion": "zusammenspiel" },
{ "cmd": "mvn -q verify jacoco:check", "areas": ["backend"], "notAtCompletion": "volleTestmenge" }
]
}The unit tests (surefire) run for every card. The integration tests (failsafe) check the interplay, the coverage (jacoco) needs the full set of tests; both run before publishing in /push-main. Only the completion run with --finish <kartennummer> may leave them out. With completionAreas the check runs anyway when the package touches one of these areas directly. A missing field leaves everything as it is.
Who was it? The culprit search
If a run with --stage push turns red, it names the cards whose changes touch the red check. It reads the number from the commit message, (Issue #<n>) in the subject or Refs #<n> in the body. A commit without a recognisable number appears as Commit ohne Karte <sha>. The repair is a new card; the named one stays in In review.
What the reduction brings: the average check time per card
node .claude/kit/effectiveness.mjs evaluate writes .claude/wirksamkeit.md. One block names the average check time per card across its completion runs, next to the comparison value without omissions. It is information and not a gate.
Quality measurement and mark: quality
At most one entry may be a quality measurement: it measures how many deliberately inserted faults the tests notice. The block quality carries a pattern, a regular expression with exactly one group for the percentage, and a marker from 0 to 100:
{ "cmd": "mvn -q org.pitest:pitest-maven:mutationCoverage", "stage": "push", "quality": { "pattern": "Killed \\d+ \\((\\d+)%\\)", "marker": 80 } }| Tool | Output | Pattern |
|---|---|---|
| PIT | Killed 42 (84%) | Killed \\d+ \\((\\d+)%\\) |
| Stryker | Mutation score: 84.21 | Mutation score: ([\\d.]+) |
A value below the mark stops publishing like a red mandatory check, not a stop point of its own; so does a missing value. Without the designation there is neither measurement nor mark nor stop. mutationCommand is not a quality measurement: it carries no mark and stops nothing.
Language of the kit
The kit speaks German or English per project. The setting is called language, values de and en, default German. A new project chooses with the installer's first question.
What you do to switch.
node install.mjs --language enThe installer backs up the config as .claude/workflow.config.json.vor-umstellung, sets the language and switches the values of fixed enumerations in both config files to the new language, for example stage from paket to package. The settings themselves carry their English name in every language; the switch does not rename them. Values you chose yourself, such as your own label names, stay. The kit also reads every setting under its old German name; if both are there, the English one applies.
What you see afterwards. Under Umbenannt every switched value with both spellings. Under Offen what the switch does not take care of, for example cards with a title prefix of the other language or labels still missing on the board. The last line names the way back, node install.mjs --language de.
Messages, reports, comments on the cards and the interfaces are in the project language from the next call on. What is already on the board stays. Lines the kit reads back later, such as ## Laufstand or Autor-Modell:, it recognises in both languages; node .claude/kit/board.mjs names outputs which version it writes. JSON field names, file names under .claude/ and the form words of findings stay the same in both languages.
Labels and title prefixes
Labels and title prefixes carry the English name in every project.
| Thing | Name |
|---|---|
| question waits for the human | kit:clarify |
| protected file waits for the human | kit:protected |
| variant B of the chain | kit:carry-through |
| check run | kit:check |
| run is running, aborted, waiting | run:running, run:aborted, run:waiting |
| trace of the review | review:done |
| questions to the PO settled | kit:clarified |
| requirement waits for an event | kit:on-hold |
| goal of the chain | goal:plan, goal:packages, goal:implementation, goal:push-prepared |
| prefixes | [Requirement], [Idea], [Plan], [Human], [Task] |
In addition there are kit:night, kit:nightrun, planreview:1 and planreview:2. The kit also recognises the German names on open cards; the mapping is under Names for those moving up from version 4. issue create --art requirement|plan|idea|human|task sets the matching prefix itself.
Team config and personal deviations
The config consists of two files:
| File | Location | Content |
|---|---|---|
.claude/workflow.config.json | in the repository | everything that applies to the team |
.claude/workflow.config.local.json | local, gitignored | personal deviations |
From the local file only these fields apply:
| Field | Why personal |
|---|---|
reviewModel | choice of model is taste and budget |
reviewCommand | the foreign CLI is installed locally |
reviewReadLimit | the read-only switch of this CLI |
reviewScope | some prefer reading the full source |
triggers | typing habit for the stop phrases |
toolbox.tokenFile | points to a token in your own file system |
The kit ignores everything else and reports it on stderr. The whole team must see buildChecks and columns the same: whoever could configure their gate away locally would have none, and deviating columns produce an empty issue list instead of an error.
reviewModel and reviewCommand give way as a pair. If the local file sets one of them, the other drops out of the shared one, and reviewReadLimit goes with reviewCommand.
What you do. Write personal values into .claude/workflow.config.local.json. What you see. git status stays clean; a change to the shared file, by contrast, shows up there.
One file, one writer
Every file in the vault that a skill writes to by itself belongs to exactly one repo. If several projects share a vault, they would otherwise write into the same daily log, and a synchronised store would produce conflict copies.
- Daily log per project. As soon as two projects use the same vault,
logPathshould be set, for exampleLog/{date}-{project}.md. The defaultLog/{date}.mdis one file per day. The fields are under kontext.config.json. - The shared umbrella note is never written by
/documentby itself. With an effect beyond one project, the skill proposes an entry and writes only after your consent.
Issue tracker and code host
Two independent settings: codeHost for repo and pull requests, issueTracker for issues and board. All skills talk only to the board adapter .claude/kit/board.mjs; when the values change, they follow at the next call.
The board needs five columns (Spalten): Backlog, Ready, In progress, In review, Done. issue list --status ready returns the cards in the column's order, top first; with drag and drop you decide what gets implemented next. Only the tracker local sorts by number.
You create the kit's labels once per board, at least kit:clarify, kit:protected and review:done, and for the chain its labels from the table above as well.
Origin on the board: --derived-from
issue create --derived-from <nummer> sends along the project-wide card number of the nearest ancestor. That way the board knows the chain from business requirement (fachliche Anforderung) via plan to work package.
| Skill | Reference |
|---|---|
/requirement | never, the business requirement is the root |
/task | never, a [Task] has no ancestor |
/techplan | to the business requirement the plan arises from |
/issues | to the plan document, otherwise to the business requirement |
Only the tracker toolbox evaluates the field; GitHub, GitLab and local accept the option and do not transmit it. A server that does not know the field swallows it silently: exit 0, the card is created without origin. Reading back does not safeguard this, because it fails on the pool idea, which cannot be read.
A project move deletes the origin on the board, for the moved card and for all that point to it. The lines Plan: Issue #M and Fachliche Quelle: Issue #N in the body are the permanent form and therefore stay.
GitHub
What you type.
gh auth loginThen create a GitHub Project with the five columns and enter its number as github.projectNumber. If it is missing, the adapter takes the owner's only project and says so on stderr.
What you see. On the first call .claude/board-meta-cache.json is created with the project's IDs. If you delete it, the adapter rebuilds it.
GitLab
glab auth loginThe five columns are labels, ~Backlog to ~Done. The installer creates them if you answer the question with yes; you create the board view once in the GitLab interface (Issues → Boards → Add list). /merge-production creates a merge request instead of a pull request.
Local
With issueTracker: local nothing is needed. Every card is a file under issues/, its status is in the front matter:
---
id: 1
status: backlog
title: Beispiel-Issue
---Toolbox
A personal Kanban board with its own server; the installer does not ask for it.
{
"issueTracker": "toolbox",
"toolbox": { "host": "https://toolbox.example.org", "tokenFile": ".claude/tbx.token" }
}The token comes from the first of these sources: the environment variable TBX_TOKEN, the file from toolbox.tokenFile or the global login tbx auth login. The adapter rejects a plain-text token in the config. The token file belongs in .gitignore:
echo "<token>" > .claude/tbx.token
echo ".claude/tbx.token" >> .gitignoreThe column names are fixed. New cards land with a number in the backlog. If a call behind a proxy ends with „fetch failed“, NODE_USE_ENV_PROXY=1 is missing; board.mjs sets it itself, your own Node calls need it too.
Switching trackers
If a project changes its tracker, tools/migrate-issues.mjs transfers the open issues from GitHub to the Toolbox:
node tools/migrate-issues.mjs export
node tools/migrate-issues.mjs import --file <exportdatei> --dry-run --out-dir <verzeichnis>
node tools/migrate-issues.mjs import --file <exportdatei> --yes
node tools/migrate-issues.mjs verify --in <exportdatei>Before the first --yes the tool demands a complete --dry-run. Every card keeps its number and its comments with author and date, carries externalKey: github#<N> and begins with two origin lines:
> Quelle: https://github.com/<owner>/<repo>/issues/<N>
> Ursprüngliche Spalte: <Spaltenname oder keine>The import is idempotent via externalKey. --from <N> --to <N> fetches a single issue, for example a bug report that arrives on GitHub later.
Then switch issueTracker. Do not switch off the old tracker: it is the archive of the closed issues, and commit messages refer to its numbers. Set the new tracker's number counter above the highest number assigned, so that no number occurs twice.
Updating and multiple projects
What you do. Run the installer in the project again. It overwrites skills and tools and merges the config: your fields stay, only the ones asked for take your new answers.
What you see afterwards. node .claude/kit/board.mjs --version names the new version. If a name has changed, the installer rewrites the calls it created itself and names every other place with the old and the new name: in .claude/settings.json, .claude/settings.local.json, the scripts of package.json and crontab -l.
A new project gets the kit with the same installer, or you copy another project's workflow.config.json and adjust the branch names. If several projects share a Toolbox server, each gets its own project-bound token.
Troubleshooting
The skills are missing under /help. Restart Claude Code after the installation. Are the skills in ~/.claude/skills/ or .claude/skills/?
/implement-ready reports „Ready ist leer“ (Ready is empty). The skill processes only Ready and pulls nothing from the backlog. Move a card to Ready.
/kit-review produces thin findings. Set reviewScope to full. In very large repos full overflows the context window; then stay with diff and name files in the prompt.
/push-main does not happen. The skill starts only on the exact phrase, default push main. An earlier approval does not apply to new commits.
/session-start or /document reports an error. Is kontext.config.json in ~/.claude/ or .claude/? Is gh or glab logged in?
The commit gate rejects. Run node .claude/kit/checks.mjs run on the state to be committed, and change no file afterwards.