Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
[0.4.0] - 2026-07-16¶
Added¶
- Severity emoji (🎉 🦠 🚧 🚨 🔥 💀) on every value, ranking it against a
guide on a logarithmic ladder. With no guide set, one is derived from
the size of the codebase (one unit per 100 lines), so debt is read as a
density. Pin one with
[display] guide, or per metric withguide. [display] loc_rangenames the range those lines are counted over (default: the default range).- Group headers show the sum of their metrics, ranked against the summed guides. In the TUI a group summing to zero starts folded, unless it holds an errored metric.
- The summary tables now read as an outline — a group name heads its indented
metrics, blocks ruled apart — replacing the
Groupcolumn, where a totalled heading row could read as a nameless metric. Every value's emoji is aligned into one column, the numbers space-padded beneath it. ignore_linesonregex_countandsymbol_uses: regexes matched against the line a hit sits on, excusing hits that are not debt —ANYin an assertion counts,"form": ANYdoes not.over_linesonfile_count: counts only files longer than the gate. In a diff, a file growing past the gate is new debt, one refactored back under it is debt paid off.descriptionon any metric, shown intingle report, the JSON, and under the metric in the TUI.tingle add --descriptionwrites one.tingle checkprints a line when it passes, instead of exiting silently.- In the TUI, arrow onto an occurrence and press Space or Enter to open it in VS Code — the file at its line, in the window you are already in. Works from VS Code's integrated terminal; elsewhere the key says there is no editor.
Fixed¶
- TUI: clicking empty space no longer moves focus off the metric rows, leaving the arrow keys scrolling instead of navigating.
[0.1.0] - 2026-07-12¶
Initial release.
Added¶
tingle stat— the compact summary table (--json,--diff,--base).tingle report— the full occurrence listing (--json,--diff,--base), plus--cobertura: Cobertura XML for CI consumers (GitLab, Jenkins, diff-cover) marking each occurrence line as uncovered.tingle check— the CI gate: measures the branch likestat --diff, then exits 1 if it worsened the metrics, printing only the lines the branch added under the metrics that grew (no removed lines, no summary table, nothing unchanged). Configured by the[check]section:policy = "sum"(default — fails when the metrics grow in total, so paying off one metric can offset another) orpolicy = "any"(fails when any single metric grows), plusignore = [...]for metrics that are expected to grow and should never fail the build.--policyoverrides the config for one run;--base,--config, and--metricwork as onstat.--diff [--base REF]onstatandreport— measure the impact of the current branch against the merge-base with a base branch (diff-cover style). Line-scoped metrics (regex_count,symbol_uses,line_count,file_count) count occurrences on added vs removed lines; value metrics (toml_list_length,ini_list_length) report the delta between the merge-base and now. Colored table (added red, removed green) with a Total column; JSON output includes the resolved base ref and merge-base sha. Base branch:--baseflag >[diff] baseconfig key >main, withorigin/<base>fallback. Uncommitted changes count; untracked files count as fully added.- Interactive mode: bare
tingleon a terminal opens a three-level accordion (textual) of group → metric → file results, navigated with the arrow keys (↑/↓between headers,→unfold,←fold, Space to toggle;hjklalias the same moves); each group and metric folds independently and unfolding a metric shows its occurrences.ffolds or unfolds every group at once, leaving the file results alone.popens the command palette,qquits. Non-TTY invocations print the static summary instead. - Occurrences: every metric reports where its hits are — file and line
for
regex_count/symbol_uses, file names forfile_count, and the actual list entries fortoml_list_length/ini_list_length. Diff results carry signed added/removed occurrences (list metrics show which entries changed).report --jsonincludes them and the per-filedetails;stat --jsonstays values-only, like its table. - Metric groups: an optional
group = "<name>"(ortingle add --group) on any metric. Grouped metrics are collected under a heading in the report listing, aGroupcolumn in the summary tables, their own foldable section in the TUI, and an additivegroupkey in JSON. Presentation only — values, occurrences, and warnings are unchanged. - TOML configuration:
tingle.toml, with fallback to[tool.tingle]inpyproject.toml(tingle.tomlwins),--config PATHoverride. - Named ranges: include/exclude glob file sets; a metric may target one
range (
range) or several (ranges, union of files); one range may be markeddefault = true; built-in excludes for.git,.venv,__pycache__,node_modules,dist,.tox,.mise. - Built-in metric types:
regex_count— regex matches across a range's filessymbol_uses— AST-based references to a bare or dotted Python symboltoml_list_length— length of the list at a dotted key in a TOML filetoml_table_array— entries of a TOML array of tables (e.g.[[tool.mypy.overrides]]), labelling each occurrence by a configurable field;explode = truefans a list-valued label out into one count per elementini_list_length— entries of a comma/newline separated INI optionfile_count/line_count— files and lines in a range- Config authoring commands:
tingle add TYPE [VALUE](validate-before-write, auto-generated names, repeatable--range,--param key=value,--group),tingle init(starter config),tingle list/tingle list --types. - Rich table and JSON output; reports on stdout, warnings and per-metric
errors on stderr; exit codes: 0 ran, 1 metric failure, 2 config/usage
error — metric values never affect the exit code (
checkis the one command whose exit code reflects the measurements). - Per-metric failure isolation: one broken metric does not stop the run.
- Python 3.11 through 3.14; the test matrix runs on all four.
- GLIMPSE architecture (pacts/specs/mills/links/gates/inits) enforced with import-linter; strict mypy.