Jedes Engineering-Team trägt unsichtbare Schulden, die gängige Metriken übersehen.
Coverage sagt dir, wie viel Code getestet ist. Sie sagt nichts darüber, welche Dateien sich jede Woche ändern und ständig kaputtgehen. Linting erzwingt Stil. Es kann dir nicht sagen, dass zwei vermeintlich unabhängige Services in 85 % der Fälle gemeinsam geändert werden. Code Review fängt Bugs. Es sagt dir nicht, dass die Datei, die du gerade reviewst, in zwei Jahren 300 Mal geändert wurde und die mit Abstand gefährlichste Datei im Repository ist.
git-xray-tools liest die einzige Quelle der Wahrheit, die jedes Team
generiert, aber kaum jemand systematisch analysiert: die Commit-Historie.
Vierzehn CLI-Werkzeuge. Jedes beantwortet eine Frage mit einer belastbaren Zahl, die in einem Sprint-Planning-Meeting Bestand hat. Kein LLM im Loop. Kein Server. Keine Daten verlassen die Maschine. Jedes Tool hat einen Wizard, einen Scripted-Modus, JSON-Output für Pipelines und einen self-contained HTML-Report, den du dem Team per E-Mail schicken kannst.
Verfügbar auf npm als git-xray-tools und auf GitHub unter
cloudhobbit/git-xray.
Warum bash, nicht Python oder Go
Die Sache, an der jedes Static-Analysis-Tool leise scheitert, ist
installiert zu werden. Wählst du Python, kämpfst du am ersten Tag mit
pyenv. Wählst du Go, lieferst du ein 12-MB-Binary pro Architektur aus.
Wählst du Node, hast du einen Dependency-Tree geerbt, den das Team
irgendwann auditieren muss.
Ich habe bash + jq gewählt. Beides ist auf jeder Entwicklungsmaschine
vorhanden, die git selbst hat. Die Install-Story ist
npm install -g git-xray-tools, und die einzigen Runtime-Checks sind
git und jq; das postinstall-Script gibt klare Anweisungen aus,
wenn eines fehlt.
Der Trade-off ist real. Bash ist nicht angenehm, eine 14-Tool-Suite darin zu schreiben. Aber die Runtime ist portabel, die Tools verketten sich natürlich über Pipes, und es gibt keinen Versions-Konflikt zwischen einer vom Nutzer installierten Python- oder Node-Version und der vom Tool erwarteten.
Die vierzehn Tools, nach Frage, die sie beantworten
| Tool | Frage |
|---|---|
git-churn | Welche Dateien ändern sich am häufigsten? |
git-cochange | Welche Dateien ändern sich immer zusammen (verstecktes Coupling)? |
git-orphans | Welche Dateien wurden seit N Tagen nicht mehr angefasst? |
git-hotspots | Welche Dateien sind groß UND ändern sich häufig? |
git-age | Wie viele Tage seit dem letzten Commit pro Datei? |
git-authors | Welche Dateien haben Bus-Faktor 1? |
git-fixratio | Welche Dateien ziehen die meisten Bugfix-Commits an? |
git-velocity | Hat sich das Commit-Tempo beschleunigt oder verlangsamt? |
git-blame-summary | Wem gehören die Codezeilen, die heute existieren? |
git-commits | Wie diszipliniert ist die Commit-Hygiene? |
git-test-ratio | Welche Source-Dateien haben keinen Test-Coverage-Proxy in der Historie? |
git-branches | Welche Branches sind Ghost-WIP oder überlasten einzelne Autoren? |
git-intel | Alle der obigen, fusioniert in einem HTML-Report |
git-healthscore | Ein einzelner 0–100-Score + die drei wichtigsten Aktionen |
Die gleiche Form gilt für jedes Tool: Wizard für erste Exploration,
--yes für scripted CI, --format json zum Piping, --format html
für ein Artefakt, das das Team lesen kann.
Wie „Team-Verhalten" in git tatsächlich aussieht
Ein einzelnes Beispiel. git-cochange liest die geänderten Dateien
jedes Commits, erfasst, welche Datei-Paare gemeinsam auftraten, und
berechnet ein Coupling-Verhältnis: wie oft sind Datei A und Datei B im
selben Commit aufgetaucht, gemessen an allen Commits, die eine der
beiden berührt haben?
Was das in realen Codebasen aufdeckt, gegen die ich es laufen ließ:
auth/session.tsundpayments/checkout.tsändern sich in 85 % der Fälle gemeinsam. Das Architektur-Diagramm sagt, das sind getrennte Services. Git sagt, das ist ein Service, aufgeteilt auf zwei Ordner.i18n/dictionaries/en.tsundi18n/dictionaries/de.tsändern sich in 100 % der Fälle gemeinsam, by design. Mit--exclude '(^|/)locales/'wird das Signal sauber.- Zwei Dateien in
apps/admin/ändern sich in 70 % der Fälle mit drei Dateien inservices/inventory/. Übersetzung: „Admin" ist downstream von „Inventory" auf eine Weise, die kein Import-Graph abbildet, und jede Inventory-Schemaänderung erzwingt still eine Admin-Änderung.
Der Punkt ist nicht, dass git die ultimative Wahrheit über Architektur ist. Der Punkt ist, dass git aufzeichnet, was tatsächlich passiert ist, nicht das, was hätte passieren sollen.
Ein Tool im Detail: git-intel
git-intel ist der unifizierte Report — der eine Befehl für eine
Codebasis, die du noch nie gesehen hast. Er führt zehn Analysen
parallel aus und kombiniert sie in einer interaktiven HTML-Datei mit
Scatter-Plots, Ranking-Tabellen und Cluster-Karten.
Was er aufdeckt:
- Blast Radius — Hotspot-Score, verstärkt durch Coupling-Gewicht. Die Dateien, deren Änderung am gefährlichsten ist, weil sie auch noch mit der halben Codebasis gekoppelt sind.
- Frozen Hotspots — Dateien, die einst stark churned, dann komplett verstummten. Wahrscheinliche angstbedingte Aufgabe: „niemand will den Auth-Flow mehr anfassen."
- Coupling Clusters — Connected-Component-Analyse des Co-Change-Graphen. Bringt echte Modul-Gruppierungen zum Vorschein, die das Architektur-Diagramm nicht zeigt.
- Knowledge Risk — Bus-Faktor pro Datei, gewichtet nach Churn.
- Bug Magnets — Dateien, in deren Historie Fix-Commits dominieren, mit einer Delta-Metrik, die die schlechter werdenden flaggt.
- Orphan Candidates — Dateien, die über
--orphan-dayshinaus ruhen (Default 365). - Test Coverage Gaps — High-Churn-Source-Dateien ohne Test-Commits.
- Team Velocity — monatliches Histogramm mit Beschleunigungs- / Stabilitäts- / Verlangsamungs-Verdikt.
- Line Ownership — opt-in via
--include-blame; Ownership der überlebenden Zeilen pro Autor.
Der HTML-Report ist self-contained: kein CDN, keine extern geladenen Fonts, kein externes CSS oder JS. Das Team kann ihn per E-Mail verschicken, an ein JIRA-Ticket hängen oder als Build-Artefakt archivieren.
Der Bug, dessen Fix mich am meisten stolz macht
Das erste HTML-Template war ein 1,3-Kilo-Zeilen bash-Heredoc.
bash-Heredocs sind syntaktisch grauenhaft, das Escaping ist fragil, und
das Template wuchs. Irgendwann musste ich Listen mit Hunderten Zeilen
pro Sektion rendern, und der naheliegende Ansatz — innerHTML += in
einer for-Schleife — stellte sich als O(N²) heraus, weil jede Zuweisung
den gesamten DOM-String neu parst.
Auf einem realen Repo mit 700 Orphan-Kandidaten und 400 Test-Gaps brauchte der HTML-Report 18 Sekunden zum Rendern im Browser. Unbrauchbar als teilbares Artefakt.
Der Fix waren zwei Änderungen, keine davon glamourös:
- Das Template aus dem bash-Heredoc extrahieren in eine eigene Datei, damit der bash-Code lesbar bleibt und HTML als HTML editierbar ist.
innerHTML +=durcharray.push()ersetzen, dann ein einzigesel.innerHTML = arr.join('')am Ende. Die Laufzeit ging von 18 Sekunden auf 280 Millisekunden. Die Commit-Message des git-intel-Commits lautet: „perf(intel-html): replace O(N²) innerHTML += in OR/TG loops with array.join".
Das ist die Art Bug, die in keinem Code Review auftaucht, bis jemand den Report auf einer echten Produktions-Codebasis laufen lässt. Es ist die Art Bug, die ein Co-Change-Tool vorhersagen würde, wenn man es auf das bash-Script anwendet, das ihn produziert hat.
Was 14 Tools gemeinsam brauchten, und wie die Suite kombinierbar bleibt
Jedes Tool gliederte sich in dieselben vier Phasen:
- Argument-Parsing — Repo-Pfad,
--since,--branch,--author,--exclude, Output-Format, Output-Pfad. Lebt inlib/args-common.sh, geteilt von jedem Tool. - Git-Log-Mining — dieselben paar
git log-Aufrufe, parametrisiert durch Filter-Flags. Lebt inlib/git-helpers.sh. - Analyse — die tool-spezifische Logik. Das kürzeste ist
git-agemit ~80 Zeilen; das längste istgit-intelmit ~700. - Rendering — JSON, Tabelle, CSV, HTML. Geteilte
Rendering-Helper in
lib/render-*.shfür jedes Format.
Ergebnis: ein fünfzehntes Tool hinzuzufügen ist hauptsächlich die
Analyse-Phase zu schreiben. Die Form jedes Tools — Wizard, --yes,
vier Output-Formate, JSON-Schema — wurde einmal entworfen und kopiert.
Outcomes
| Outcome | Notiz |
|---|---|
| 14 Tools ausgeliefert | npm git-xray-tools v0.1.0, MIT-Lizenz |
| Tage vom Scaffold zur Release-Reife | 8 (2. → 10. Mai 2026), 51 Commits |
| Modell-Calls zur Laufzeit | 0. Nur deterministische Analyse |
| Output-Formate pro Tool | 4: JSON, Tabelle, CSV, HTML |
| HTML-Report-Rendering (700-Orphan-Repo) | 18 s → 280 ms nach dem O(N²)-Fix |
| Aus dem größten Heredoc extrahierte bash-Zeilen | ~1.300, in ein eigenständiges Template |
| Konfigurierbare Filter | --since, --branch, --path, --author, --exclude |
| CI-Integration | --yes liest .git-<tool>.json, Exit 1 bei Threshold |
Was ich anders machen würde
Ich würde das JSON-Schema zuerst schreiben. Drei Tools waren
ausgeliefert, bevor mir auffiel, dass die JSON-Output-Shapes
auseinanderdrifteten. git-churn hatte files: [...],
git-velocity hatte buckets: [...], git-cochange hatte
pairs: [...]. Pro Tool vernünftig, aber die User-Oberfläche ist
„pipe ein Tool ins nächste via jq" — und ein stabiles, dokumentiertes
Schema hätte Schritt null sein müssen, kein nachträglicher Retrofit.
Ich würde einen TypeScript-on-Node-Rewrite-Pfad früher einplanen. bash funktioniert. bash wird weiter funktionieren. Aber jeder Beitragende zu dieser Codebasis hat dieselbe erste Reaktion („das ist bash?"), und der Test-Runner, den ich geschrieben habe, ist eine kleinere Version jedes Test-Frameworks. Die richtige Entscheidung könnte ein sauberer Rewrite in TypeScript sein, sobald die API-Oberfläche in v1.0 stabilisiert ist — die npm-Distributionsstory bleibt intakt.
Ich würde den HTML-Report ab Woche eins ausliefern. Ich habe JSON und Tabelle zuerst ausgeliefert, in der Annahme, dass „echte Nutzer" scripted Output wollen. Das mit Abstand meistgeliebte Feature war in Gesprächen, nachdem ich die Suite open-sourced habe, der HTML-Report. Menschen wollen Artefakte, die sie per E-Mail verschicken können. Lektion: das Artefakt zählt mehr als das Format.