Harness
The harness is the set of guardrails that lets humans and coding agents change DrinkIt without breaking master. Each layer catches a mistake as early as it can, and no layer relies on the previous one having run.
A person can skip the local git hooks with --no-verify, a Claude Code session cannot skip pre-push. The GitHub layers cannot be skipped, owner included.
Coding agent
/implement-issue <number> takes an open issue to a pull request, whatever its board status: a worktree of its own, the code test first, the lanes CI would run, a fresh judge, then the pull request and the board moves. The maintainer merges. Run again once the pull request is merged, it removes the worktree and the branch, unless work is left in them, pulls master, and moves the issue to Done once it is closed.
| Part | What it does | Where | Since |
|---|---|---|---|
| Conventions | Commands, structure, code patterns and traps a coding agent reads when a session starts. Claude Code, Codex, Copilot and Cursor all read the file | AGENTS.md | 2026-05-01 as CLAUDE.md, AGENTS.md since 2026-10-03 |
Agent issue-judge | A fresh subagent that sees the issue and the branch, not the author's account of it. It reruns the tests it relies on and returns two verdicts: Spec, each acceptance criterion met, and Design, the refactor done and the guidelines followed. Two rounds at most, then the maintainer decides | .claude/agents/issue-judge.md | 2026-10-03 |
Agent security-reviewer | A fresh, read-only subagent that reviews a branch for security: authorization of each endpoint, CSRF, CORS, headers, input validation, secrets, new dependencies, the frontend's handling of user input. implement-issue starts it when the diff touches one of those | .claude/agents/security-reviewer.md | 2026-10-03 |
| Lint hook | Once Claude Code writes a Kotlin source or a Gradle script, runs lint-kotlin from its worktree, and shows Claude the findings when detekt fails | .claude/hooks/detekt-after-edit | 2026-09-13, Kotlin only and findings shown since 2026-10-03 |
| Guard hook | Refuses, before it runs, a command that writes to a ruleset, to branch protection or to a push protection bypass, or that merges a pull request, through gh, gh api, GraphQL or curl. Also refuses any way around pre-push: --no-verify, an overridden or unset core.hooksPath, a push from a clone without the git hooks. Reads pass, and so does text inside quotes or a heredoc that no shell reads. It reads the command as text, so one built indirectly, through a variable or a script, passes. permissions.deny in the same settings refuses gh pr merge and git push --no-verify a second time | .claude/hooks/guard-github-protections | 2026-10-02, merges and pre-push since 2026-10-03 |
| cmux status hook | In the cmux terminal, names the session's tab after its issue and step, #382 implement, #382 judge, #382 PR #450, and shows the judge's verdict and the pull request's CI gate as sidebar pills, read from gh pr checks whether it passes or fails. Does nothing elsewhere | .claude/hooks/cmux-status | 2026-10-03 |
| Generated files hook | Refuses a hand edit of a file a tool writes: jOOQ classes, the generated API client, Gradle's and npm's lock files, the generated documentation pages, and .env files. Its message names the command that changes the file instead. A shell command that writes the file passes | .claude/hooks/protect-generated-files | 2026-10-03 |
| Compile hook | At the end of a turn that changed Kotlin sources, compiles them with their tests. When frontend sources changed, runs the typecheck script of each app that has one and has its dependencies installed. The errors go back to Claude, which keeps working until they are fixed. A state already checked is not checked again, so it cannot loop | .claude/hooks/compile-at-stop | 2026-10-03 |
| Session context hook | At start, on resume and after a compaction, tells Claude its branch, its issue, how far it is ahead of master, its uncommitted work, and the worktrees whose branch is merged into the last fetched master, left for /implement-issue to clean up | .claude/hooks/session-context | 2026-10-03 |
Each Claude Code hook has a test next to it, <hook>.test, and so do pre-commit and pre-push, run by hand for now. Claude Code also loads the personal configuration of whoever runs it, from ~/.claude, on top of these parts, and a project cannot turn it off.
Skills
A skill holds how to carry out one kind of change. Claude loads it when the files it names are touched, or when it is asked for, and other agents are told to read it by AGENTS.md.
| Skill | When it applies | Started by |
|---|---|---|
implement-issue | Taking an open issue to a pull request, and cleaning up once it is merged | The maintainer, /implement-issue <number> |
new-issue | Turning an idea, a bug or discovered work into an issue that follows its form, created at once and Ready unless blocked | On request, or by implement-issue |
review-pr | Reviewing a pull request and posting inline comments ranked by severity | The maintainer, /review-pr <number> |
test-driven-development | Adding or changing behavior, backend or frontend: a test list, red-green-tidy cycles, then a technical and functional refactor | Code under drinkit/ or a backend starter |
test-existing-code | Covering code that works but has no tests, each test proven able to fail | On request |
hexagonal-backend | Where each piece of a backend feature goes, from use case to controller and security rule | Backend production code |
api-contract-change | Changing the OpenAPI contract, then both generated sides | The contract and the client generator settings |
database-change | Changing the schema in place, idempotently, then the jOOQ classes and their tests | The changelogs and the generated jOOQ code |
frontend-feature | Shaping a feature of the Nuxt app around the generated client | Frontend sources |
new-backend-tech-starter | Creating a backend tech starter, or aligning one with the conventions | On request |
new-frontend-tech-starter | Creating the first frontend tech starter, a Nuxt layer, once its layout is agreed | On request |
pentest | Scanning the running application locally with OWASP ZAP, then triaging the findings | The maintainer, /pentest |
threat-model | A STRIDE threat model of one feature or flow | The maintainer, /threat-model |
ci-workflow-change | Changing a workflow, an action reference or a lane: pinned actions, mapped paths, actionlint and zizmor | The workflows and the in-repo actions |
harness-change | Changing a hook, its test, the settings, a skill or an agent: portable sh, tests next to each hook, globs that match | The hooks, the settings, the skills and the agents |
dependency-change | Adding or bumping a dependency: one version in the catalogs and the BOM, then the verification metadata and the lock | The platform, the build scripts and the package.json files |
build-convention-change | Changing a convention plugin: its id from its file name, the archetypes, the configuration cache | build-logic |
caveman | Terse replies to the maintainer, on by default through AGENTS.md, with plain prose where clarity needs it | Every session, /caveman lite, full or ultra |
Git hooks
Enabled once per clone with git config core.hooksPath .githooks.
| Part | What it does | Where | Since |
|---|---|---|---|
lint-kotlin | Formats Kotlin sources and Gradle scripts, fails when detekt does. Shared by pre-commit and Claude Code | .githooks/lint-kotlin | 2026-09-13 |
pre-commit | Refuses a staged file over 5 MB, and a dependency change in a package.json without its package-lock.json. When Kotlin files are staged, runs lint-kotlin and re-stages what it reformatted | .githooks/pre-commit | 2026-09-13, size and lock file checks since 2026-10-03 |
pre-push | Refuses any push, force push or deletion of master before anything is sent | .githooks/pre-push | 2026-09-27 |
Gradle
| Part | What it does | Where | Since |
|---|---|---|---|
| detekt convention plugin | Applies detekt with type resolution to every module, skips generated code, makes check run detektAll | com.drinkit.code-analysis-convention | 2024-03-17, fails the build since 2026-09-13 |
| detekt configuration | This project's deviations from detekt's defaults, formatting rules included | code-analysis/detekt/detekt.yml | 2024-03-17 |
| detekt baseline | Findings older than the switch to failing builds. They no longer block, new ones do | code-analysis/detekt/baseline.xml | 2024-03-17 |
| Wrapper checksum | The wrapper refuses a Gradle distribution whose SHA-256 differs from the committed one. Renovate updates it with each Gradle version | gradle-wrapper.properties | 2026-09-30 |
| Dependency verification | The build refuses a dependency or plugin whose SHA-256 differs from the committed one, or has none. Renovate regenerates the file in its own pull requests. Sources, javadoc and two artifacts that plugins resolve on their own are trusted, each with its reason | verification-metadata.xml | 2026-10-02 |
| Dependency locking | drinkit-backend resolves exactly the versions its gradle.lockfile lists, transitive and test dependencies included. The libraries are not locked, so a new release inside a version range they ask for, cucumber's for instance, fails verification until the files are regenerated | com.drinkit.api-convention | 2026-10-03 |
Continuous integration
One workflow, ci.yml, runs the lanes a change touches, each from a file of its own. CI/CD explains the lanes and the choices behind them.
| Part | What it does | Where | Since |
|---|---|---|---|
CI gate | The only required check. Fails when a lane failed or was cancelled, passes when a lane was not needed | ci.yml | 2026-10-01 |
Decision | Maps the changed files to the workflows, backend, frontend, docs, ops and dependencies lanes. A file no list knows runs every lane | ci-lanes.yml | 2026-10-01 |
CI files | actionlint checks that the workflows are valid and zizmor audits their security, whenever they change. A finding blocks the merge | ci-workflows.yml | 2026-10-02 |
Backend | Compiles and tests the backend and runs detekt in one Gradle run, findings in code scanning | ci-backend.yml | 2024-03-03 as build, detekt in the same run since 2026-10-01 |
CodeQL | Looks for security flaws in the Kotlin and Java code, results in code scanning. Not required | ci-codeql.yml | 2024-03-25, off from 2026-09-13 to 2026-09-26 |
Frontend | Generates the API client and builds the Nuxt app | ci-frontend.yml | 2026-10-01 |
Ops | Validates the local compose file | ci-ops.yml | 2026-10-01 |
Docs and Deploy docs | Generate the living documentation and build the site on pull requests, deploy that build from master | ci-docs.yml, cd-docs.yml | deployed since 2025-07-03, built on pull requests since 2026-10-01 |
Dependencies: graph submission | Sends the Gradle dependency graph to GitHub for every master commit and for pull requests that change dependencies | ci-dependencies.yml | 2024-03-25, nothing sent from 2025-01-25 to 2026-09-27 |
Dependencies: review | Fails a pull request that adds a dependency with a known vulnerability | ci-dependencies.yml | 2026-10-01 |
| Weekly full run | Runs every lane on master each Monday, CodeQL on all the code included | weekly.yml | 2026-10-01 |
| Gradle wrapper validation | Checks that the Gradle wrapper is an official release, in every Gradle setup | setup-gradle-jdk | 2024-03-25, in the shared setup since 2026-10-01 |
GitHub configuration
| Part | What it does | Where | Since |
|---|---|---|---|
| master ruleset | No bypass, owner and agents included. Pull request required, CI gate green on a branch up to date with master, conversations resolved, no force push, no deletion | .github/rulesets/master.json | 2026-09-27 |
| Merge settings | Rebase is the only merge method, "Update branch" is offered, merged branches are deleted | Repository settings | 2026-09-27 |
| Workflow token | Read-only by default, each workflow asks for what it needs | Repository settings | Not recorded |
| Fork pull requests | Workflows of every external contributor wait for approval | Repository settings | 2026-10-02, first-time contributors only before |
| Pinned actions | A workflow that references an action by tag or branch does not start: every action is pinned by commit SHA, in-repo ones with $/ | Repository settings | 2026-10-02 |
| Renovate | Opens dependency update pull requests a week after a release, on Monday mornings, and pins GitHub Actions and compose images by digest. Security fixes and undated releases (JDK, large Docker Hub images) skip the wait. Majors and lock file refreshes wait for a checkbox on the Dependency Dashboard. A pull request is rebased only on conflict | .github/renovate.json | 2024-04-11, delayed since 2026-09-30, rebased on conflict only since 2026-10-01 |
| Dependabot alerts | Flag dependencies with a known vulnerability, which Renovate turns into security updates. Dependabot opens no pull request of its own | Repository settings | Not recorded, its security updates off since 2026-10-01 |
| Secret scanning and push protection | GitHub refuses a push that contains a known secret format, from git, the web interface or the API, and scans the whole history for secrets already pushed | Repository settings | 2026-10-02 |
| Private vulnerability reporting | A vulnerability can be reported from the Security tab, without a public issue. SECURITY.md points there | Repository settings, SECURITY.md | 2026-10-02, policy since 2026-10-03 |
| Issue forms | "New issue" offers a work item, an epic and a bug form. Only collaborators can still open a blank issue. Forms only apply on github.com, so AGENTS.md tells agents to reuse their headings | .github/ISSUE_TEMPLATE | 2026-10-03 |
| Pull request template | Prefills a new pull request: what changes and why, Closes #, choices, verification | pull_request_template.md | 2026-10-03 |
| Contributing guide | How to write issues, pull requests and comments for a human reader, by people and agents alike. GitHub links it when an issue or a pull request is opened | CONTRIBUTING.md | 2026-10-03 |
| Gradle configuration cache key | The GRADLE_ENCRYPTION_KEY secret lets Backend keep Gradle's configuration cache between runs | Repository secrets | 2026-10-01 |
Not covered yet
- An agent session holds the owner's admin token: only the guard hook keeps it from editing the ruleset
- Container images and a deployment for the backend and frontend: #421
- Detekt and coverage reports on every pull request: #404
- Documentation generation as part of the build: #395
- Frontend upgrade, then its linting and tests in the frontend lane: #388, then #400
- The judge is required by
/implement-issue, not by a hook: the pull request shows its verdict line, which the maintainer checks before merging - The hook tests run by hand, not in CI: #434