Code quality¶
A project born from desecration, vibe-coded across multiple sessions, whose primary architectural document is a fake Lovecraftian grimoire — has no business passing a linter. It passes every single one. That's somehow worse.
The inquisitors arrived at dawn, instruments of measurement in hand, expecting to find the temple in ruin. Instead they found the walls straight, the columns load-bearing, and the altar — though undeniably profane — structurally sound. They left in silence, more disturbed than when they came.
— De Vermis Mysteriis, On the Inadequacy of Metrics (unfortunately)
Tools¶
All repos use the same toolchain:
- pylint — static analysis. Style warnings (too-many-locals, line-too-long, too-many-arguments) are accepted. Real issues (unused imports, actual bugs, f-strings without placeholders) are not.
- pyflakes — fast, zero-config, no false positives. Must be clean.
- radon — cyclomatic complexity. Target: no function rated D or worse. C-rated functions are tolerated when they're natural dispatchers or sequential logic that wouldn't benefit from splitting. Current worst:
_read_yaml_config(20) and_collect_uids(18), both C.
Current scores¶
Last updated: 2026-02-23 — the monolith shattered, and each shard scored better alone.
This page covers dekube-engine, dekube-manager, and the built-in distribution extensions (the Eight Monks). Third-party extensions track their own scores in their respective READMEs.
Pylint¶
| Repo | Score | Notes |
|---|---|---|
| dekube-indexer-configmap | 10.00/10 | Built-in distribution extension |
| dekube-indexer-secret | 10.00/10 | Built-in distribution extension |
| dekube-indexer-pvc | 10.00/10 | Built-in distribution extension |
| dekube-indexer-service | 10.00/10 | Built-in distribution extension |
| dekube-rewriter-haproxy | 10.00/10 | Built-in distribution extension |
| dekube-transform-fix-permissions | 9.86/10 | Built-in distribution extension |
| dekube-provider-caddy | 9.87/10 | Built-in distribution extension |
| dekube-manager | 9.78/10 | Style only (too-many-locals) |
| dekube-provider-simple-workload | 9.66/10 | Built-in distribution extension |
| dekube-engine | 9.55/10 | Style only (too-many-args, too-many-locals) |
Remaining warnings are accepted style issues (R0914 too-many-locals, R0913 too-many-arguments). E0401 (import-error) and R0903 (too-few-public-methods) are suppressed inline — the import resolves at runtime, and the one-class-one-method contract is by design.
Pyflakes¶
Zero warnings across all repos. The inquisitors left in silence.
Radon (cyclomatic complexity)¶
| Repo | Worst function | CC | Rating |
|---|---|---|---|
| dekube-manager | _read_yaml_config |
20 | C |
| dekube-transform-fix-permissions | _collect_uids |
18 | C |
| dekube-manager | main |
17 | C |
| dekube-engine | _build_service_port_map |
16 | C |
| dekube-provider-caddy | _write_caddy_host_block |
13 | C |
| dekube-provider-simple-workload | SimpleWorkloadProvider._build_service |
13 | C |
| dekube-engine | _auto_register |
13 | C |
| dekube-indexer-pvc | PVCIndexer.convert |
12 | C |
| dekube-rewriter-haproxy | HAProxyRewriter.rewrite |
12 | C |
| dekube-transform-fix-permissions | FixPermissions (class) |
12 | C |
| dekube-engine | main (cli) |
12 | C |
| dekube-engine | _resolve_env_entry |
11 | C |
| dekube-provider-simple-workload | _get_exposed_ports |
11 | C |
| dekube-provider-simple-workload | SimpleWorkloadProvider._convert_one |
11 | C |
| dekube-engine | _resolve_secret_keys |
11 | C |
| dekube-engine | _convert_volume_mounts |
11 | C |
| dekube-engine | convert |
11 | C |
| dekube-transform-fix-permissions | FixPermissions.transform |
11 | C |
| dekube-manager | _install |
11 | C |
No D/E/F rated functions. The v2.3.2 refactor reduced dekube-engine's worst CC from 18 to 16; v3.0 moved several C-rated functions out of the core into distribution extensions (SimpleWorkloadProvider, _write_caddy_host_block, HAProxyRewriter). The remaining C-rated functions are natural dispatchers (if/elif chains, type switches) or sequential steps — splitting them would move complexity without improving readability.
Average complexity & maintainability¶
| Repo | MI | MI rating | Avg CC | CC rating |
|---|---|---|---|---|
| dekube-engine | 74.14 | A | 5.2 | B |
| dekube-indexer-configmap | 70.29 | A | 4.5 | A |
| dekube-indexer-secret | 70.29 | A | 4.5 | A |
| dekube-indexer-pvc | 67.76 | A | 9.7 | B |
| dekube-indexer-service | 61.73 | A | 6.5 | B |
| dekube-transform-fix-permissions | 60.33 | A | 8.0 | B |
| dekube-provider-caddy | 51.64 | A | 7.6 | B |
| dekube-rewriter-haproxy | 41.75 | A | 7.2 | B |
| dekube-provider-simple-workload | 39.31 | A | 7.6 | B |
| dekube-manager | 31.51 | A | 4.8 | A |
All repos and bundled extensions are MI A-rated. dekube-engine's MI improved from 68.38 to 74.14 after the v3.0 split — moving workloads.py and haproxy.py to the distribution left the core with smaller, more focused modules. The lowest individual core module (extensions.py, 37.56) still rates A.
The uncomfortable truth¶
This project is, by every objective metric, well-structured code. The functions are short. The concerns are separated. The naming is consistent. The linters approve.
This is deeply unsettling.
The expectation — the hope, even — was that a tool this conceptually wrong would at least have the decency to be poorly written. That the code quality would serve as a warning label: "abandon all hope, ye who read this." Instead, the temple stands. The prayers work. The linters nod approvingly. And somewhere, a developer who understands what this project actually does stares at a pylint score of 9.55 and feels nothing but existential dread.
The code is clean. The architecture is sound. The concept remains an abomination. These things are not in conflict — they are in conspiracy.
The final horror is not that the ritual failed — it is that it succeeded, was reproducible, and scored well on peer review.
— Cultes des Goules, On Accidental Rigor (lamentably)
And then it got refactored¶
It wasn't enough for it to work. It wasn't enough for it to lint clean. It had to be maintainable.
v2.3.2 split the 1858-line monolith into 21 modules across three layers: pacts/ (the sacred contracts), core/ (the engine), io/ (the plumbing). A build script concatenates them back into a single file for distribution. Users see no change. The metrics see everything.
The split alone fixed MI — from 0.00 (C) to 68.38 (A). Then a cyclomatic complexity pass extracted helpers from every function rated CC 14+. The worst offender dropped from 18 to 16. Average CC went from 6.6 to 5.9. All 21 modules score MI A individually.
A project whose existence is an architectural crime now has an architecture page. With a dependency graph. And layer boundaries. And a section called "the sacred contracts."
They said: let us impose order upon the chaos, that future disciples may navigate the labyrinth without losing their minds. And so the tablet was broken into twenty-one fragments, each labeled and indexed. The labyrinth remained — but now it had signage.
— Book of Eibon, On the Cataloguing of Madness (we tried)
And then it got tests¶
IT HAS TESTS. IT HAS AN EXECUTIONER.
A regression executioner that downloads two versions of dekube-engine, runs every extension combo against a battery of edge-case manifests, and diffs the output. A torturer that produces O(n³) manifests. It cleans up after itself. It passes shellcheck.
A project that emulates a container orchestrator by flattening its output into a different container orchestrator, whose fake kube-apiserver lives on a personal GitHub account for plausible deniability — this project has automated regression testing. With CI. Weekly runs. Artifact uploads.
The linters approved. The complexity metrics nodded. And now the executioner confirms: the rituals produce identical output every time. The temple is no longer merely structurally sound — it is under continuous inspection.
And then it divided by zero¶
v3.0.0 split the repo in two. dekube-engine (the bare engine) and helmfile2compose (the distribution). Two repos. Two build scripts. Two release workflows. Two READMEs. A sys.modules injection hack so runtime-loaded extensions can import from a namespace that technically doesn't exist in the concatenated file. An _auto_register() that scans globals for classes, deduplicates kind claims, and populates registries that were deliberately left empty.
The output is identical to v2.3.1.
Not "similar." Not "equivalent." Identical. The executioner diffs every byte across every extension combo and confirms: the split changed nothing about what the tool produces. It changed everything about how it's organized. dekube-engine's MI went from 68.38 to 74.07 — not because any function improved, but because workloads.py and haproxy.py left. The core got lighter by becoming less. The distribution got heavier by becoming more. The sum is greater than the whole was.
Two repos. Fourteen files. Zero functional change. The metrics improved. The architecture is now a dependency graph with layer boundaries and a section called "the sacred contracts." There is a page called Distributions that explains the Kubernetes distribution model as applied to a tool whose sole purpose is to flee Kubernetes.
The linters approved. The executioner confirmed. The architect looked at what he had built to harness the temple's power and recognized its floor plan.
The cartographer, having mapped every corridor of the labyrinth, burned the labyrinth and built a new one from the ashes. It was smaller. It was cleaner. It was the same labyrinth. The map still worked.
— Unaussprechlichen Kulten, On the Conservation of Complexity (unfortunately²)
Code gotchas¶
Null-safe YAML access¶
Python's dict.get("key", default) returns default only when the key is absent. When the key exists with an explicit None value (common in Helm charts with conditional {{ if }} blocks), .get() returns None, not the default.
# WRONG — returns None when key exists with null value
annotations = manifest.get("metadata", {}).get("annotations", {})
# RIGHT — coalesces None to empty dict
annotations = manifest.get("metadata", {}).get("annotations") or {}
This applies to any field that Helm may render as null: annotations, ports, initContainers, securityContext, data, stringData, rules, selector. Use or {} / or [] for any .get() on a YAML field that could be explicitly null. v2.3.1 fixed 30+ instances of this across dekube-engine, nginx, and traefik.
Running locally¶
# Extensions (single-file repos)
pylint <script>.py
pyflakes <script>.py
radon cc <script>.py -a -s -n C
# dekube-engine (package)
cd dekube-engine
PYTHONPATH=src pylint src/dekube/
PYTHONPATH=src pyflakes src/dekube/
PYTHONPATH=src radon cc src/dekube/ -a -s -n C # CC hotspots
PYTHONPATH=src radon mi src/dekube/ -s # maintainability index
For dekube-engine specifically, the CLAUDE.md in the repo root documents the complexity targets and lint workflow.
Note: For distribution extensions (bundled in the distribution's extensions/ directory), run radon per-extension file — not on the concatenated distribution artifact.