Skip to content

Tech debt

There are zero TODO, FIXME, HACK, and XXX markers in this repository. Three scans confirm it: over packages/*/src, over packages/ plus scripts/, and over every file git ls-files names. That means the marker channel carries no signal here. It does not mean the codebase is debt-free, and this page exists because the debt is real and lives elsewhere.

So the register below is assembled from five sources rather than from comments. Every citation on this page carries its full path, so no reference resolves through a nearby antecedent.

  1. The suppression config, read as the debt register someone already wrote. select = ["ALL"] with exactly 12 ignores (pyproject.toml:153-184), four pyright rules switched off (pyproject.toml:668-679), ty at all = "error" with no rule disabled (pyproject.toml:370-371), and per-site # noqa / # pyright: ignore counts taken by grep. Almost every config entry carries its own measured count and its cost-of-removal analysis inline, which is a better debt record than a comment marker and is quoted here rather than re-derived.
  2. Version ceilings and the reason attached to each, read at the pin in the member manifests, not inferred from the lockfile.
  3. Install weight and platform coverage, computed independently from uv.lock by walking the atif-sql dependency graph twice — once whole, once with harbor blocked — following extras as well as direct dependencies.
  4. Asymmetric enforcement between sibling surfaces: a coverage floor enforced in CI but not in the declared definition of done, so a local green run does not measure it.
  5. Coverage and test-shape gaps, per module. Coverage figures are measured from the generated report, which is gitignored, so every figure is cited to the source module it describes.

Category and cost vocabularies are closed. Categories: marker, wrong abstraction, error handling, dead code adjacent, deprecated pattern, version pin, duplicated logic, missing tests. Cost: S, M, L. No row carries the marker category, for the reason in the first paragraph. Rank is cost-to-fix × consequence-of-leaving, so a cheap fix for a harmless problem sinks and an expensive fix for a structural problem rises.

Rank Debt item Category Cost to fix Citation
1 The entire Claude Code → ATIF conversion path runs through one PRIVATE upstream method, ClaudeCode._convert_events_to_trajectory, verified against harbor 0.22.0 only — so the manifest carries a minor-version ceiling and the unit tests are the drift alarm. Nothing upstream promises the method exists in 0.23. version pin L packages/atif-converter/pyproject.toml:19-23
2 Seven upstream conversion losses are catalogued as FidelityGap enum members rather than fixed. Gap 3 flattens the parentUuid tree by timestamp sort, losing branch and rewind structure; gap 7 drops the event uuid, making step-to-raw-record identity unrecoverable from the trajectory — recovered out of band through a parallel edges.jsonl sidecar the corpus layout mandates. wrong abstraction L packages/atif-converter/src/atif_converter/domain/fidelity.py:44-79, docs/CONTRACT.md:26-28
3 63 of the 113 runtime distributions reach this project only through harborfastapi, uvicorn, starlette, the whole supabase client stack, litellm, openai, tiktoken, tokenizers, huggingface-hub, cryptography, aiohttp — for exactly one private method call. A CLI that converts JSONL ships a web server and a database client. 57% of the roster, 155 MiB. version pin L RELEASING.md:215-222
4 415 pyright findings sit behind three disabled Unknown-propagation rules, and the config names the fix it has not built: a TypedDict model of the ~/.claude JSONL record and the ATIF trajectory would close 144 of them. Until that model exists, every JSON-shaped value in the hottest modules is dict[str, Any] narrowed by isinstance, with the type checker’s opinion switched off. The two heaviest concentrations are the converter’s enrichment module at 59 findings and the analytics corpus reader at 33. wrong abstraction L pyproject.toml:641-671
5 15 SQL statements are built by string interpolation through a hand-rolled quote-doubling escape, because DuckDB rejects prepared parameters as table-function arguments. ruff’s # noqa: S608 silences ruff only — bandit reports the same 15 as B608 and those findings upload to code scanning, so the suppression is not portable across the two scanners that both implement the rule. deprecated pattern L pyproject.toml:737-739, packages/atif-duck/src/atif_duck/infrastructure/registry.py:172, packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:108
6 No test reads a real ~/.claude corpus. The whole integration tier is two tests over two synthetic 4-event sessions, and parity oracles against the predecessor implementation are explicitly not part of this repo. The seven fidelity gaps are policy about real-world JSONL shapes that nothing real-world exercises. missing tests L packages/atif-cli/tests/test_integration.py:3-9, packages/atif-cli/tests/test_integration.py:24-34, AGENTS.md:92-94
7 hdbscan 0.8.44 has never published an aarch64 wheel, so a first install on Graviton, an ARM CI runner, or a linux/arm64 container compiles five Cython extensions and needs a C toolchain. musl is not merely slow but impossible: lancedb 0.37.1 publishes no sdist at all, so there is nothing to build from. version pin L RELEASING.md:203-213
8 The four size ratchets and the complexity ratchet are set at the measured worst function in the tree — max-statements = 119 for _friction_async, max-complexity = 38 for enrich_trajectory, max-args = 19 for the search command. The ratchet stops growth and permanently blesses the current outliers; nothing in the config plans their reduction. wrong abstraction L pyproject.toml:258-281, packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:240, packages/atif-converter/src/atif_converter/domain/enrichment.py:198
9 TC001/TC002/TC003 are ignored on the strength of a manual probe: moving 58 imports under TYPE_CHECKING broke typing.get_type_hints on 159 first-party functions and classes. The 182-site PLC0415 ignore beside it has a regression test proving its property in a fresh interpreter; the get_type_hints property has none, so the argument that justifies the ignore is not locked. missing tests M pyproject.toml:170-183, packages/atif-cli/tests/test_lean_import.py:35
10 Every one of the nine security scanners has its findings exit code swallowed at eight sites, by a stated contract: findings do not fail the job, only a scanner that produced no usable report does. Gating is delegated entirely to GitHub code-scanning alerts and branch protection, so nothing inside the repo fails on a new vulnerability. error handling M mise.toml:217-223, mise.toml:366, mise.toml:482
11 The provider/dimension guard exists as two deliberate 83-line twins because the independence contract forbids either package importing the other. The drift pin reads the atif-duck twin as SOURCE TEXT, parses it with ast, and asserts substrings — including a raw source.index("raise EmbeddingProviderMismatch") that silently changes meaning if the raise site is renamed or a second raise appears. The two docstrings have already diverged; the pin covers the recovery string and the interpolation, not the rule. duplicated logic M packages/atif-embed/tests/test_guard_twin_pin.py:3-13, packages/atif-embed/tests/test_guard_twin_pin.py:93-95, packages/atif-duck/src/atif_duck/domain/embedding_guard.py:5-16
12 The clustering path is the least-tested code in the tree and it is also the least portable: the structural clustering module at 22% (23 of 30 lines unexecuted), the Lance reader at 31%, the cluster use case at 51% — against an 89.98% whole-tree combined figure. This is exactly the UMAP/HDBSCAN code behind the aarch64 wheel gap. missing tests M packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py:88, packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:38-49, packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py:50
13 Both binding contract documents disclaim describing the code. CONTRACT-V2 states outright that when it disagrees with the code, the code wins and the file is design intent “not a description of current behavior”; CONTRACT v1 calls its own scope section historical. A binding document that pre-emptively surrenders authority cannot be used to detect drift, which is the only thing a contract document is for. judgment-call — the disclaimers are honest, and honesty about staleness is still staleness. dead code adjacent M docs/CONTRACT-V2.md:3-8, docs/CONTRACT.md:5-8
14 One version constraint is unexplained where every other pin in the repo justifies itself: lancedb>=0.30,<0.38 is declared twice, in two members, with no comment on either side. A single distribution declares one constraint per package, so test_distribution.py now fails on two members that disagree — but it cannot ask why a constraint exists, which is what a comment is for. version pin S packages/atif-analytics/pyproject.toml:27, packages/atif-embed/pyproject.toml:20-22
15 FakeConverter — a scriptable test double — ships inside the installable atif-corpus wheel under infrastructure/, and it is the converter the end-to-end materialize suite runs against. A fake that both mutates and reads corpus state stands in for the real harbor adapter in exactly the tests that would catch a state-transition bug. wrong abstraction S packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:3-9, packages/atif-corpus/tests/test_materialize.py:25
16 Three independently declared DomainError(Exception) bases with the same name and no shared ancestor, so a caller composing two members cannot write one except clause and any symbol-name search cross-attributes all three. duplicated logic S packages/atif-converter/src/atif_converter/domain/errors.py:14, packages/atif-embed/src/atif_embed/domain/errors.py:14, packages/atif-models/src/atif_models/domain/ports.py:39
17 The 89% coverage floor is not part of the declared definition of done. mise run check depends on nine gates and test:cov is not among them, so a local green run can drop coverage below the floor and only the CI job notices. missing tests S mise.toml:197-212, pyproject.toml:560, .github/workflows/check.yml:78

None. This section is empty because the repository contains no comment markers, not because the search was narrow.

The absence is enforced, not incidental. select = ["ALL"] at pyproject.toml:146 leaves flake8-fixme and flake8-todos on, and neither appears among the 12 ignores at pyproject.toml:153-184. Probed 2026-08-28 by dropping one # TODO: ... comment into packages/atif-duck/src/: ruff check exits 2 with TD002 (missing author), TD003 (missing issue link), and FIX002 (line contains TODO). A marker cannot reach main past mise run lint, so the debt lives in the five channels the register draws on instead of in markers. A sixth channel exists and contributes no row: a comment explaining the code by reference to an implementation no installer of this package can obtain. grep -riE 'claude-sql|predecessor' packages/*/src returns 0 lines. No lint can police that one, so it is empty by review rather than by gate, which is why it is worth naming here.

The toolchain config is the real debt register, and it is load-bearing

Section titled “The toolchain config is the real debt register, and it is load-bearing”

Every suppression in pyproject.toml carries its measured count and its cost-of-removal analysis, which makes the config the most honest debt document in the repo — and also means a large amount of unresolved work is encoded as a rule that is off rather than as an issue anyone tracks. The three disabled Unknown-propagation rules cover 415 findings and the config names the two things that would let them back on, neither of which exists. Three TC* rules are off because a probe found that fixing them breaks typing.get_type_hints on 159 first-party functions. Nine per-site # noqa: N818 mark error classes deliberately not named *Error. Five size limits are pinned at the current worst function. Reading the config top to bottom is a more complete account of what this codebase owes than reading its comments, and none of it appears in any tracker.

Shows up in:

  • pyproject.toml:153-184 — the 12 ignores, each with its site count.
  • pyproject.toml:641-680 — 415 findings traced to origin, plus the 23-warning stub gap.
  • pyproject.toml:258-281 — the five ratchets set at the measured worst.
  • pyproject.toml:370-371[tool.ty.rules] all = "error" with nothing disabled beneath it, which is what the pattern looks like when it holds: a suppression here would need the same measured argument every entry above carries.

Cost: L — the analysis is done and written; the work it defers is a TypedDict model of the JSONL record, four stub distributions, and three refactors of named functions.

One private upstream method carries the whole product

Section titled “One private upstream method carries the whole product”

atif-converter exists to call ClaudeCode._convert_events_to_trajectory, a private harbor method. Four consequences compound. The manifest carries a minor-version ceiling that only a manual re-audit can lift. Seven data losses are documented as enum members because the private method’s behavior cannot be changed, and two of them — flattened parent chains, dropped event uuids — are worked around by writing a parallel edges.jsonl sidecar for every session, so the corpus layout itself is shaped by an upstream defect. The dependency arrives with 63 other distributions including a web framework, an ASGI server, and a hosted-database client, none of which this code imports.

And it forecloses a supply-chain control the rest of the toolchain would take. semgrep’s uv-missing-dependency-cooldown asks for exclude-newer under [tool.uv], so a freshly published version waits before it can be resolved — the standard mitigation for a compromised release. Probed 2026-08-28 with uv lock --dry-run --exclude-newer "7 days": the workspace becomes UNSATISFIABLE, because no distribution inside harbor>=0.22.0,<0.23 is older than the cutoff. The control is declined for that measured reason rather than overlooked, and uv.lock plus uv sync --locked remain what actually pins the resolution.

Shows up in:

Cost: L — either vendor the conversion the method performs and drop harbor entirely, or get a public entry point upstreamed. Both are real projects.

The independence contract is paid for in copies, and the copies are pinned by text

Section titled “The independence contract is paid for in copies, and the copies are pinned by text”

Five packages may never import each other, so shared logic is duplicated instead of extracted. Three byte-identical SQL-literal escapers exist, one per package that inlines SQL, and the same pure provider/dimension guard exists as two 83-line twins. The escapers are the contract’s price paid honestly: each package owns one copy, in domain/, and no import would be legal. The guard is the price paid dangerously. The mechanism keeping the twins honest is a test that reads the other package’s file as text, parses it with ast, and asserts substrings — including one raw str.index on a raise keyword. That test is fragile in a way an import never is: it passes when the twins agree on a string and disagree on their logic, and it breaks on a rename that changes nothing.

Shows up in:

Cost: M — collapsing the guard twins needs a decision about a shared-kernel package the contract forbids. The three escapers need no decision: a shared kernel would absorb them, and short of one, one copy per package is the contract working rather than failing.

Nine security scanners run, and every one has its findings exit code swallowed — eight sites use a trailing || true, the rest an equivalent flag — under an explicit contract: a finding does not fail the task, a missing report does. The reasoning is sound, because a non-zero exit kills the CI step before the SARIF upload. The consequence is that no gate inside this repository fails on a new vulnerability, a new secret, or a new SAST finding, and enforcement lives entirely in GitHub code-scanning alerts and branch protection. The same asymmetry appears in the coverage floor: fail_under = 89 exists, and the task that reads it is not among the nine mise run check depends on, so the declared definition of done does not measure coverage. The suppression ledger this tier generates is currently empty, which reads identically to “nothing needed suppressing” and to “nobody has looked”.

Shows up in:

Cost: M — splitting each scanner into “run and upload” plus “assert the finding delta against a baseline” is mechanical; agreeing on the baseline is the work.

The code the type checkers guard hardest is the code the tests reach least

Section titled “The code the type checkers guard hardest is the code the tests reach least”

Combined line-and-branch coverage is 89.98% and the misses are not spread evenly — they concentrate in one path, the numeric clustering one. packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py:88 is at 22%, and it is the module carrying a pyright: ignore on the UMAP call and depending on the one package with no aarch64 wheel. packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:38-49 is at 31%, so the store-shape branches deciding whether the structural stages run at all are unexecuted. Above all of it, the end-to-end materialize suite runs against a fake converter that ships in the wheel, so the state transitions that matter most are exercised against a double.

Shows up in:

Cost: M — a corpus fixture built from real, scrubbed ~/.claude records is the whole of it, and it is what would exercise both the clustering path and the fidelity gaps nothing real-world currently touches.