Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,55 @@
</p>

<p align="center">
<a href="https://github.com/QuackbackIO/quackback/stargazers"><img src="https://img.shields.io/github/stars/QuackbackIO/quackback?style=flat&color=f5a623" alt="GitHub stars" /></a>
<a href="https://github.com/QuackbackIO/quackback/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-blue" alt="License" /></a>
<a href="https://github.com/QuackbackIO/quackback/actions"><img src="https://img.shields.io/github/actions/workflow/status/QuackbackIO/quackback/ci.yml?label=CI" alt="CI" /></a>
<a href="https://github.com/QuackbackIO/quackback/issues"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome" /></a>
<a href="https://github.com/theRealBithive/quackback/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/theRealBithive/quackback/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="CLAUDE.md#measuring-coverage"><img src="https://img.shields.io/badge/diff%20coverage-enforced%20per%20PR-blue" alt="Diff coverage enforced per pull request" /></a>
<a href="CLAUDE.md#what-done-means"><img src="https://img.shields.io/badge/mutation%20gate-no%20unjustified%20survivors-blue" alt="Mutation gate: no unjustified survivors" /></a>
<a href="https://github.com/QuackbackIO/quackback"><img src="https://img.shields.io/badge/fork%20of-QuackbackIO%2Fquackback-lightgrey" alt="Fork of QuackbackIO/quackback" /></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-blue" alt="License" /></a>
</p>

> **The two blue badges state a rule, not a measurement.** There is no
> repository-wide coverage or mutation percentage here, deliberately: the gates
> grade **what a change touches**, not the total, because a total is a number
> nobody has to defend and it improves when you measure less. `diff-coverage`
> and `mutation` are jobs in the workflow the CI badge reports, and per PR is
> where they bite: on `main` the comparison base is `main`, so the diff is empty
> and there is nothing for them to grade. What the two rules actually require is
> in [CLAUDE.md](CLAUDE.md).

## This is a fork

Upstream is [QuackbackIO/quackback](https://github.com/QuackbackIO/quackback);
everything below this section is upstream's own description of the product and
is left as it stands, so a sync stays a sync. This section is the fork-local
part.

Exkulpa runs this codebase as a network service for its own products. Under
AGPL-3.0 §13 that carries obligations towards the people using that instance —
what they are, what is already satisfied, and what is still open is recorded in
[LEGAL.md](LEGAL.md). Read it before building anything proprietary in here.

Four things differ from upstream and will bite whoever ignores them:

- **Everything written into the repo is English** — code, comments, commits,
docs. The conversation around it is German; the file is not. A patch has to be
able to go upstream without a translation pass.
- **The contract comes before the tests, and the tests before the code.** For
anything with branches, money, dates, permissions or parsing, the guarantees
are written as a numbered list in plain domain language and confirmed _before_
a property is written. A property read off the implementation cannot fail.
- **Two CI gates grade the diff, not the repo** — see the badges above.
- **Never deploy a moving tag.** A push to `main` publishes
`ghcr.io/therealbithive/quackback:main`, which is fine to try things with and
wrong to run: a pod rescheduled overnight silently picks up a different build,
and afterwards nobody can say which source was running. Every merge gets a
`vX.Y.Z-exkulpa.N` tag instead.

The working rules are in [CLAUDE.md](CLAUDE.md), and the concrete gotchas that
have already cost someone an afternoon are collected in
[SELF-IMPROVE.md](SELF-IMPROVE.md). Both are fork-local and absent upstream, so
neither ever conflicts on a sync.

<p align="center">
<img src=".github/screenshot.png" alt="Quackback feedback portal" width="800" />
</p>
Expand Down Expand Up @@ -73,7 +116,7 @@ Slack, Linear, Jira, GitHub, GitLab, Asana, ClickUp, Monday, Trello, Notion, Sho
### Docker

```bash
git clone https://github.com/QuackbackIO/quackback.git
git clone https://github.com/theRealBithive/quackback.git
cd quackback
cp .env.example .env # Edit with your configuration
docker build -t quackback -f apps/web/Dockerfile .
Expand All @@ -97,7 +140,7 @@ See the [Contributing Guide](CONTRIBUTING.md) to get started.
Prerequisites: [Bun](https://bun.sh/) v1.4.0+ and [Docker](https://docker.com/)

```bash
git clone https://github.com/QuackbackIO/quackback.git
git clone https://github.com/theRealBithive/quackback.git
cd quackback
bun run setup # Install deps, start Docker, run migrations
bun run db:seed # Optional: seed demo data
Expand Down
34 changes: 34 additions & 0 deletions SELF-IMPROVE.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,40 @@ database up in one command.

# Resolved

## 1x — Declaring a file in the mutation manifest is a claim, and it can cost an afternoon to find out it is false

Adding an entry to `scripts/mutation-manifest.json` asserts that the named
suites, on their own, pin that file's behaviour. The word "asserts" is doing
real work: on a file that was already in the repository, the claim is usually
**not** true, and there is no cheap way to find out except running the gate.

Measured while touching two GitLab files with one field each.
`gitlab/server/inbound.ts` came back with 21 survivors — all but one in guards
nobody had ever pinned (an issue hook naming no issue, a note whose text is not
text, an absent author, both 401 bodies). Closing them was worth doing and took
about a dozen small tests, because the file is pure parsing. Then
`gitlab/server/hook.ts` came back with **60**: 15 survivors and 45 never
executed, across outbound error handling the change had not touched. That one
was backed out — the entry would have been a false claim, and the gate's
designed answer for an unpinned file is to name it as ungraded.

So: the size of the claim has nothing to do with the size of the diff. Before
declaring a pre-existing file, run the gate with the entry added and read the
number before committing to it. A pure module is usually a dozen tests away
from honest; a module that does I/O in its branches is a work item of its own.

Two smaller traps found on the way:

- `NoCoverage` mutants mean the entry names too few suites, not that the code is
untested. `inbound.ts` had 18 of them purely because `verifySignature` lives
in that file and is tested from `signature-matrix.test.ts` two directories
away. The fix is a second entry in `suites`, not more tests.
- Editing the manifest with a blind string replace puts the new record in
`equivalents` as well as `graded`, because the same `"file":` line matches in
both arrays. The gate refuses to run at all and says an equivalence record
needs a `why` — which reads like a missing reason rather than a record that
should not exist. Edit that file as JSON, not as text.

## 1x — A Stryker run leaves two things behind that nothing else guards

Both surfaced within an hour of the mutation gate going green, and neither is
Expand Down
Loading