description: "Audit accumulated tech debt — duplication, dead code, stale docs, stubs, magic numbers" argument-hint: "[--focus <dims>] [--path <dir>]"
Tech-Debt Audit
Find maintainability debt across the Python codebase — the slow-accumulating kind that no single feature audit owns. Not a correctness audit (that's the subsystem skills); this is about code that works but costs more to change than it should.
Shared protocol: .claude/commands/_audit-common.md (layout, dedup, finding format).
Severity: .claude/commands/_audit-severity.md — tech-debt findings are usually LOW,
escalating to MEDIUM when the debt actively hides bugs (a swallowed exception, a stub on a
live path) or contradicts a hardware doc.
Parameters (from $ARGUMENTS)
--focus <dims>— comma-separated dimension numbers (e.g.--focus 2,3). Default: all.--path <dir>— restrict to a subtree (e.g.--path exporter). Default: whole repo.
Extra Per-Finding Field
- Dimension: one of the 8 below.
Dimensions
Dimension 1: Logic Duplication
Repeated logic that should be shared (the user's standing rule: improve existing code, never
duplicate). Hot spots: the four exporters (exporter/) re-implementing register/byte
serialization; per-channel handling copy-pasted across nes/; the two pattern detectors
(tracker/pattern_detector.py vs tracker/pattern_detector_parallel.py). grep for
near-identical blocks; report the canonical home.
#346 (TD-26) is CLOSED: the "two parsers drifting" hot spot — tracker/parser.py, a
full parser reachable only by three tests and on no production pipeline path — is gone.
tracker/parser_fast.py is now the sole MIDI front-end; its three former callers
(tests/test_midi_parser_integration.py, tests/test_integration.py,
tests/test_pattern_integration.py) were retargeted to it. Verify-the-fix: confirm no new
second parser implementation reappears.
Two prior instances of exactly this pattern are now fixed — verify they haven't regressed before hunting for new ones:
- The copy-pasted
_find_pattern_matchesbetweentracker/pattern_detector.pyandtracker/pattern_detector_parallel.py(TD-03/#131) is gone: the parallel module now has its own O(n) grouping helper,_collect_length_candidates, shared between its parallel and serial (_detect_patterns_serial) code paths, and both detectors sharescore_patternfromtracker/pattern_detector.py(#100/#103/#104). The sequentialEnhancedPatternDetector._find_pattern_matches(tracker/pattern_detector.py:291) still exists on its own — it does variation/transposition-aware matching the O(n) grouping can't, so this is now a deliberate algorithmic split, not copy-paste drift. (_find_pattern_matchesis attracker/pattern_detector.py:320.) - The duplicate MIDI-note→note-name converter (TD-07/#134,
midi_note_to_ftin the old exporter/exporter.py) was removed entirely as dead code in commit2bcb780.exporter/exporter_famistudio.py:midi_note_to_famistudio(line 164) is now the sole implementation of that conversion — confirm no second copy has reappeared (e.g. inexporter/exporter_ca65.py, which only has the unrelatedmidi_note_to_timer_valueat line 42) before reporting a new instance of TD-07.
Dimension 2: Dead Code & Cruft
Unused functions/imports/modules, unreachable branches, root-level scratch files, or
*.s/*.nes/*.log artifacts checked into the tree. Confirm no caller via grep -rn
before flagging. Distinguish "dead" from "only called by tests".
Five such root scripts (implementation_examples.py, show_greeting.py,
batch_test.py, nes_devflow.py, generate_test_midi.py) plus a duplicate,
unrelated-implementation check_rom.py at the repo root were removed in commit
535b7ae (TD-04/#132, TD-05/#133 — closed). The repo root now holds only
main.py, constants.py, and validate_rom.py at the top level. Treat this as
fixed and reframe the check as: confirm no new stray root-level script, duplicate
check_rom.py/validate_rom.py, or other dead module has been reintroduced since.
Non-Python files count too. Verify fix (#461/TD-41, closed): nes/linker_mmc3.cfg was
checked in but grep -rn linker_mmc3 found no reference anywhere in the tree — every
mapper emits its nes.cfg programmatically from generate_linker_config(), so nothing
read it. It also failed the "deliberately-kept reference copy" test: its header described a
128KB PRG layout while mappers/mmc3.py generates 512KB/60 swap banks, so as a reference it
was actively misleading. Deleted (git history preserves it). Confirm no new orphaned
non-Python config/data file (a *.cfg, *.s, or similar checked-in artifact nothing reads)
has been reintroduced since — the same "checked in but grep -rn finds nothing" test applies
to any future candidate; only judge a hit as a deliberately-kept reference copy (comment,
not deletion) if its content still matches what the live generator produces.
Retired placeholders to watch for a reappearance: prepare_multi_song_project and
add_song_bank in nes/project_builder.py were removed once song build gave the song
bank a real ROM route (#30/F-13). Either name coming back is dead code, not a feature.
Dimension 3: Stale Documentation & Comments
A docs/*.md, docstring, or comment that contradicts the code. Highest-value targets:
CLAUDE.md (it already notes the MMC1→MMC3 prepare drift — check for more), docs/ROADMAP.md,
docs/WORK_PLAN_1.0.0.md, README.md, and the APU reference docs vs the actual
nes/pitch_table.py / nes/envelope_processor.py constants. Doc-rot that misstates
hardware behavior is MEDIUM.
Dimension 4: Stale Markers (TODO / FIXME / HACK / XXX)
grep -rnE 'TODO|FIXME|HACK|XXX' --include='*.py' .
Report markers that describe real unfinished work (not just notes). Group by subsystem.
TD-08/#137 is CLOSED: the DPCM .incbin TODO in the macro-bytecode export path
(exporter/exporter_ca65.py, .segment "DPCM" block) was itself stale rather than
describing real unfinished work, and has been replaced with an accurate comment
(exporter/exporter_ca65.py:1090-1094) explaining the segment is deliberately left
empty — the actual .incbin lines and lookup tables are produced by
dpcm_sampler/dpcm_packer.py's generate_assembly and appended to music.asm via the
shared pack_dpcm_into_asm helper (main.py:126-215, called from both run_export and
run_full_pipeline — see /audit-dpcm Dimension 2 / /audit-safety Dimension 1 for
that consolidation, #380/TD-28), not into this fixed $C000/R6-window segment. Confirmed
at the time of the fix this was the only TODO/FIXME/HACK/XXX in non-test source — re-run
the grep before reporting new ones.
Dimension 5: Stub & Placeholder Implementations
Functions that return None/pass/raise NotImplementedError, or hardcode a value where
real logic is implied — especially on a live pipeline path (a stubbed exporter branch, a
no-op validation). A stub on a path the default main.py input.mid out.nes run hits is MEDIUM.
Dimension 6: Magic Numbers & Hardcoded Constants
Bare numeric literals that should be named or sourced from a doc — APU register addresses
($4000–$4017), 11-bit timer maxima, the 60Hz frame rate, NTSC 1.789773 MHz, the MMC1/MMC3
bank sizes, the LARGE_FILE_THRESHOLD/MIN_ROM_SIZE constants. Where a docs/APU_*.md
or docs/MAPPER_*.md defines the value, cite it. (LOW unless the magic number is wrong.)
Dimension 7: Error-Handling Debt
Bare except: / except Exception: pass, broad catches that hide the real error,
print-and-continue where the pipeline should stop. Overlaps /audit-safety — here, focus
on the pattern prevalence and a shared remedy, not each individual site.
Verify fix (TD-10/#135, closed): utils/profiling.py had a bare except: clause
that also swallowed KeyboardInterrupt/SystemExit. No bare except: remains anywhere
in the file — only narrow except (psutil.NoSuchProcess, psutil.AccessDenied): and
except Exception: (with a comment explaining KeyboardInterrupt/SystemExit are
deliberately let through); the module docstring refers to the old bare except in the past
tense. Confirm no new bare except: (or except Exception: pass) has crept back into this
file or elsewhere in profiling/benchmark tooling.
Dimension 8: Module / Function Size & Structure
Oversized modules or functions doing too much. TD-11/#136 covered two monoliths; one half is now closed, the other explicitly deferred:
exporter/exporter_ca65.py'sexport_direct_frameshalf is CLOSED (#136): extracted 8 per-channel emitter methods —_emit_pulse_or_triangle_table(:213),_emit_noise_table(:303),_emit_dpcm_table(:326) for frame-data tables, and_emit_pulse1_proc(:341),_emit_pulse2_proc(:399),_emit_triangle_proc(:453),_emit_noise_proc(:507),_emit_dpcm_proc(:545) for playback subroutines — cuttingexport_direct_frames(:603-1027) from ~750 lines to ~425. Verified byte-for-byte identical emitted output via a golden-file diff across 24 configs at the time of the fix.exporter_ca65.pyis now ~1766 lines total (grown well past the ~1445 an earlier pass of this file claimed -- not a re-inlining regression but the jukebox feature,_build_song_bytecode/export_song_bank_bytecode, added after that count was taken; TD-43/#463). Treat any line count here as a snapshot, not a target --wc -lbefore citing a fresh one. Verify-the-fix: confirm the 8 emitters stay focused (one channel's table or proc each) and a future edit doesn't re-inline them back intoexport_direct_frames.main.py'srun_full_pipelinehalf is now CLOSED (#406): three of its stages were extracted into independently-testable module-level functions, all defined just aboverun_full_pipeline(main.py:1060-1294) —detect_patterns_or_direct_export(:1060, Step 4: parallel/sequential pattern detection with fallback + sampling, or the direct-export stats stub),export_frames_and_resolve_mapper(:1181, Steps 5-5.5: CA65 export, DPCM packing, and--mapperresolution — unifying the two resolution-timing paths that used to be split across two different points inrun_full_pipeline), andbuild_and_validate_rom(:1261, Steps 6-8: capacity pre-flight, project prep, compile, validate).run_full_pipelineitself (main.py:1295) dropped from ~335 to ~137 lines.main.pyis now ~1878 lines total (grew further from the helper extraction's own boilerplate and docstrings — the same pattern the exporter half's extraction showed, see #136 above — but the one oversized function is gone). Each extracted helper raises instead of callingsys.exititself;run_full_pipeline's single try/except/finally (still gating backup/restore-on-failure, #26) is the only place that decides how to report a failure. Steps 1-3 (parse → map/arrange → frames) deliberately remain inline inrun_full_pipeline— thedel midi_data/del mappedcalls trimming peak memory (#371/PERF-A-01, see/audit-performance) only free anything if they execute in the frame holding the last reference, so extracting that code into a callee would silently break the memory contract. Verify-the-fix: confirm the three extracted helpers stay focused on their named steps and aren't re-inlined back intorun_full_pipeline; confirm a future stage addition also raises rather than callingsys.exitinline, keeping the single-recovery-point contract intact.
Report the split that would help, not just the line count — and flag if either has grown further since the numbers above.
Cross-Dimension Dedup
A single root cause can surface in several dimensions (a duplicated block that is also a stub that also has a stale comment). Report it once, in the most actionable dimension, and cross-reference.
Output
Write to: docs/audits/AUDIT_TECH_DEBT_<TODAY>.md (YYYY-MM-DD). Structure:
- Summary — counts per dimension, the 3 highest-leverage cleanups.
- Findings — base format +
Dimension.
Then suggest:
/audit-publish docs/audits/AUDIT_TECH_DEBT_<TODAY>.md
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.