Bartosz Bartosik

Curriculum Vitae

SME IT Support Expert

Databases / Monitoring / IAM / SQL

Docker / Kubernetes / Python / AI / ML

Project Management

ITIL / IT Service Management

Knowledge Management

Compliance & Risk Management

Blog Post

How I Keep a 2000-Commit Project Under Control: a Constitution, a Pipeline and 1600+ Tests

2026-09-22 Bez kategorii
How I Keep a 2000-Commit Project Under Control: a Constitution, a Pipeline and 1600+ Tests

Live at: https://sdr.gut.red


HexSDR is a big project for one person. Over 120 decoders, a map, a transmit canvas, roles and permissions. Such a thing can fall apart very fast, especially when I also work with AI coding agents. So from the beginning I wrote down rules, and I made a machine to check them. This post is about these rules, not about radio.

Stack

  • Git, Conventional Commits
  • GitLab CI/CD
  • pytest (backend), Vitest (frontend)
  • Docker
  • Small scripts in Bash and Python for checking rules

1. A constitution which every agent must read

In the repo I have a file with the high laws, and a second one with sticky rules for agents and contributors. Every session must start with reading them. They say, for example: documentation must describe the real code, not wishes. Infrastructure changes must be mirrored in both Docker Compose files. Blocking I/O in signal loops is forbidden. Fake data in production code is forbidden. If I ask for something against the constitution, the agent must warn me first.

I also have a safe word. When the agent hears it, it stops everything. I will not tell you what it is. 🙂

2. The backlog is the same as the issue tracker

Every task in the plans folder has a stable ID like [CORE-070]. A script reads those IDs and keeps GitLab issues in sync one to one. When I close a task in the markdown, the issue closes too. IDs are never renumbered, because this would break the match, and one decoder always has its own ID. I also have a rule that the backlog row must be updated in the same session when the work is done, not later.

3. Commits are checked, and the changelog writes itself

All commit messages follow Conventional Commits, and a hook rejects the wrong ones. The changelog is generated from the git history by a script, and the version number is calculated from it too. The same number appears on the README badge and on the boot screen of the app, so I always know which build I am looking at.

There is one more check, which is maybe unusual. A pipeline job scans new commits for co-author trailers added by agents and tools, and fails if it finds one. It looks only at new commits, so old history is not rewritten. I want authorship under my control.

4. A pipeline with real stages

On every push, the pipeline goes through secret scanning, static security analysis, dependency audit, linters, unit tests, image build, smoke and regression tests, push to the registry, and then deploy. After deploy, the wiki and the issues are synchronized. The default is to skip nothing, because a green pipeline is the finish line. But for documentation-only commits or for emergencies I have registered tags in the commit message to skip chosen jobs. Only registered tags work, so nobody can invent a new one by accident.

5. The status table tells the truth

This is my favourite rule. Decoders have four statuses: confirmed, operational, untested and broken. The status changes only when I confirm it with a real signal. Real radio tests are not a part of the sprint backlog at all, they are a separate phase, for the target hardware. Because of this, nobody (including me) can mark something as “done” just because the code compiles.

6. Tests, and a problem I did not hide

The backend has 1662 unit tests, and the full run takes about six and a half minutes on my machine. In August the full run was eating around 26 GB of memory and was killed by the system, two times. I did not hide it. I wrote it into the backlog as its own task, with the observations, the suspects (for example Numba cache growing across hundreds of modules) and the risk for the CI runner. A known problem on a list is much better than a surprise in the pipeline.

Is it too much process for a solo project? Maybe. But when I come back to a part of code after two months, or when an agent starts a new session without any memory, these rules are the memory.

👉 Check it live: https://sdr.gut.red

Tech stack in details

For the code quality I use linters for Python and TypeScript, a TypeScript build as a check on the frontend, pytest with over 1600 tests on the backend and Vitest on the frontend. The security scans (secrets, static analysis, dependency audit) run on every pipeline, and the check of commit trailers cannot be skipped by a normal skip tag, only by skipping the whole pipeline.

Commit rules are enforced locally by small shell scripts: one validates the message format, and a helper commits and cleans forbidden trailers. The changelog and the version are generated by one Python script from the git history. A second script synchronizes the backlog markdown with GitLab issues, and a third one synchronizes the documentation with the wiki.

The pipeline builds Docker images with layer caching in mind, because build speed is one of my laws. After a green pipeline on the main branch, a webhook updates the production automatically. Both Compose files, for local and for registry deployment, are kept in sync by a rule, not by memory.

Tags: