Audit de dette technique

VérifiéSûr

Auditez la dette technique accumulée : marqueurs obsolètes, code mort, duplication, nombres magiques, implémentations stub, documentation obsolète, fichiers trop volumineux.

Spar Skills Guide Bot
DeveloppementAvancé
0023/07/2026
Claude Code
#tech-debt-audit#code-quality#maintenance#rust

Recommandé pour

Notre avis

Audite une base de code Rust pour la dette technique comme le code mort, les nombres magiques, les logiques dupliquées, la documentation obsolète et les fichiers surdimensionnés, en se concentrant sur neuf dimensions avec une profondeur configurable.

Points forts

  • Couvre plusieurs dimensions de la dette technique (9 dimensions) de manière systématique.
  • Fournit des recettes concrètes pour découvrir les instances actuelles, pas des listes statiques.
  • Inclut des règles de promotion de sévérité pour prioriser les problèmes critiques.
  • Génère un instantané de référence pour suivre l'évolution de la dette.

Limites

  • Fonctionne uniquement pour les bases de code Rust avec la structure spécifique (crates/).
  • Nécessite une configuration préalable des commandes .claude/ et de l'infrastructure d'audit.
  • Le mode profond peut être long pour les grandes bases de code.
  • Peut ne pas détecter toutes les formes de dette technique (par exemple, la dette de conception).
Quand l'utiliser

Utilisez cette compétence lorsque vous avez besoin d'un audit de dette technique systématique et reproductible avant une refonte majeure ou une livraison.

Quand l'éviter

Ne l'utilisez pas pour identifier des bugs de correction ou pour une revue de code rapide ponctuelle sans maintenance continue.

Analyse de sécurité

Sûr
Score qualité92/100

The skill uses only read-only file inspection and GitHub issue listing commands (find, grep, gh) to gather technical debt metrics, with no destructive or data-exfiltration instructions. All file output is to /tmp.

Aucun point d'attention détecté

Exemples

Quick shallow tech debt scan
Run a tech debt audit with depth=shallow and focus on dimensions 1,3,5 (stale markers, dead code, magic numbers).
Full deep tech debt audit
Perform a comprehensive tech debt audit across all dimensions with depth=deep.
Focused doc rot audit
Audit technical debt specifically for stale documentation (dimension 7) with shallow depth.

description: "Audit accumulated technical debt — stale markers, dead code, duplication, magic numbers, stub impls, doc rot, oversized files" argument-hint: "--focus <dimensions> --depth shallow|deep"

Tech-Debt Audit

Audit ByroRedux for accumulated technical debt: code that compiles, passes tests, and ships, but quietly raises the cost of every future change. The goal is not correctness bugs (other audits own that) — it is decay that crept in since the last cleanup pass.

Every dimension below is a DISCOVERY RECIPE, not a finding list. Instances churn between audits (markers get deleted, files get split, line numbers drift). So each dimension hands you a command to enumerate current instances, then a triage rule. Do not trust any hardcoded instance list — there are none here on purpose. Re-run the recipe; report what it surfaces today.

Architecture: Orchestrator. Each dimension runs as a Task agent (max 3 concurrent).

See .claude/commands/_audit-common.md for project layout, the 21-crate roster, methodology, deduplication, context rules, severity, and finding format. Do not duplicate any of that here. The newest crates — crates/pex/ (M47.2 compiled- Papyrus .pex decompiler), crates/save/ (M45 full-ECS snapshot save/load), and the expanded crates/scripting/ (M47.1/M47.2 recognizer chain) — are young code that has not yet seen a debt sweep; the dimensions below should reach them.

Parameters (from $ARGUMENTS)

  • --focus <dimensions>: Comma-separated dimension numbers (e.g., 1,3,5). Default: all 9.
  • --depth shallow|deep: shallow = surface counts + worst offenders; deep = per-instance triage with a concrete fix proposal. Default: deep.

Extra Per-Finding Fields

  • Dimension: one of the 9 below.
  • Age (when relevant): commit hash + date the debt landed (git log -L / git blame).
  • Effort: trivial (≤30 min) | small (≤2 h) | medium (≤1 day) | large (>1 day, decompose first).
  • ID convention: TD<dim>-NNN (e.g. TD7-050 = Dim 7 Doc Rot, finding 50). The path-validation gate (_audit-validate.sh, #1114) was itself a TD7-* finding — recurring stale-path findings are what motivated the gate.

Severity for Tech Debt

Tech-debt findings default to LOW (see _audit-severity.md). Promote only on amplification:

| Promotion Trigger | Floor | |-------------------|-------| | Duplicated logic with divergent bug-fix history (one branch fixed, the other regressed) | MEDIUM | | unimplemented!() / todo!() / panic!("not …") reachable from a shipped CLI flag or smoke test | MEDIUM | | #[ignore]d test that guards a fix from a closed CRITICAL/HIGH issue | MEDIUM | | Stale doc/audit baseline that misled an audit in the last 90 days | MEDIUM | | Magic number that would silently over/underflow under documented use | HIGH | | Stale GpuCamera/GpuInstance/GpuMaterial size in a doc comment (lockstep-drift bait) | MEDIUM |

Phase 1: Setup

  1. Parse $ARGUMENTS for --focus, --depth.
  2. mkdir -p /tmp/audit/tech-debt.
  3. Dedup baseline:
    gh issue list --repo matiaszanolli/ByroRedux --limit 500 --state all --label tech-debt --json number,title,state > /tmp/audit/tech-debt/issues_all.json
    
  4. Scan docs/audits/ for prior AUDIT_TECH_DEBT_*.md (diff direction, not re-litigation).
  5. Snapshot totals so the next audit can diff:
    {
      echo "TODO/FIXME/HACK/XXX:   $(grep -RInE '(TODO|FIXME|HACK|XXX)\b' crates byroredux | wc -l)"
      echo "allow(dead_code):      $(grep -RInE 'allow\(dead_code\)' crates byroredux | wc -l)"
      echo "unimplemented!/todo!(): $(grep -RInE 'unimplemented!|todo!\(\)' crates byroredux | wc -l)"
      echo "#[ignore] tests:        $(grep -RIn '#\[ignore\]' . | wc -l)"
      echo "files >2000 LOC:        $(find crates byroredux -name '*.rs' -exec wc -l {} + | awk '$1>2000 && $2!="total"' | wc -l)"
    } > /tmp/audit/tech-debt/baseline.txt
    
    Orientation only (will drift — re-run, never quote): the marker total runs ~20, unimplemented!/todo!() is currently 0 (the engine prefers explicit fallbacks over panics — a fresh todo!() is therefore notable), #[ignore] runs in the low-hundreds (mostly Vulkan/smoke gating, not debt), and the

    2000-LOC set is ~7 files (Dim 1).

Phase 2: Dimension Agents

Ordered by debt impact: complexity and duplication compound across every future edit; doc/audit rot misdirects the next audit; markers and dead code are cheap. Each agent writes /tmp/audit/tech-debt/dim_<N>.md.

Dimension 1: File / Function / Module Complexity

The highest-leverage debt: an oversized file taxes every edit, review, and merge.

Discovery:

find crates byroredux -name '*.rs' -exec wc -l {} + | awk '$1>2000 && $2!="total"' | sort -rn

Session 34/35/36 (2026-05) split the original oversized set (acceleration.rs, dispatch_tests.rs, cell/tests.rs, draw.rs, scene_buffer.rs, context/mod.rs, import/mesh.rs, blocks/collision.rs, nif/anim.rs) into submodules — all of those are closed; do not re-file them. Membership has since turned over: the two big Vulkan-context files grew after re-split, and several byroredux/ files crossed 2000. Re-run the command; the threshold is 2000 LOC (the Session-34 split target). Whatever it lists today is the live set — including any file the skill once cited as a success (a previously-split module can grow back over threshold).

Per oversized file, propose a split AXIS by responsibility (not by line count):

  • A Vulkan context/ file → per-pass recording groups (geometry / RT / denoise / composite / overlay) or struct+new() vs Drop vs accessors. Vulkan-recording splits are render-pass-adjacent — see feedback_speculative_vulkan_fixes.md before proposing barrier/order changes.
  • byroredux/src/asset_provider/ → BSA/BA2 resolution vs TextureProvider vs mesh extraction.
  • byroredux/src/main.rs → App/ApplicationHandler event loop vs system registration vs boot/config.
  • byroredux/src/commands/ → console-command groups, already split per-domain (world_info / assets / view / scene / shared) under #1323; check the submodules stay cohesive, not re-bloated.
  • crates/nif/src/blocks/particle.rs → typed emitter/ctlr structs vs the opaque NiPSysBlock fallback vs grow/fade modifiers.
  • crates/nif/src/import/collision/mod.rs → split per bhk shape family (primitive/compound/mesh/compressed), mirroring crates/nif/src/blocks/collision/.
  • crates/core/src/ecs/resources/mod.rs → partially split already (SkinSlotPool extracted to skin_slot_pool.rs under #1869; mod.rs now 1210 LOC, under threshold). Split further per resource domain (rendering/world/audio/scripting) only if it re-bloats.
  • crates/plugin/src/esm/records/actor.rs → split per NPC_ data-group (13 groups).

Also flag: functions >200 LOC (propose extraction); match arms >50 cases (want a lookup table); nesting depth >5 (state-machine extraction); a mod.rs / lib.rs with >20 pub use (doing two jobs). cargo +nightly clippy --all-targets -- -W clippy::cognitive_complexity if available, else inspect the worst offenders.

Dimension 2: Logic Duplication

CLAUDE.md global policy is explicit: improve existing code, never duplicate logic. Every finding must name a concrete consolidation site.

Discovery: target subsystems with N>1 sibling files, then read for repeated scaffolding:

ls crates/nif/src/blocks/*.rs crates/plugin/src/esm/records/**/*.rs crates/renderer/src/vulkan/*.rs byroredux/src/cell_loader/*.rs

Look for:

  • Block-parser scaffolding repeated across crates/nif/src/blocks/ (header read → field read → fixup) that should funnel through a shared helper/macro.
  • Texture-upload chains (BC1/BC3/BC5/RGBA) duplicated in crates/renderer/src/vulkan/.
  • The same image-layout barrier sequence repeated per render pass.
  • vk::WriteDescriptorSet builder boilerplate.
  • ESM sub-record parse loops repeated across crates/plugin/src/esm/records/.
  • Z-up → Y-up coordinate flips reimplemented outside the canonical homes (crates/nif/src/import/coord.rs, crates/nif/src/anim/coord.rs) — any other call site is a leak.

Dimension 3: Stale Documentation & Comments

Doc rot is high-impact debt because it misleads the next reader and the next audit. Run the path gate first (it is also Dim 9's input):

.claude/commands/_audit-validate.sh

Any STALE refs it prints are auto-eligible findings (effort: trivial). Then sweep for content rot the gate cannot see:

  • Numeric claims in doc comments that drift from a pinned test. The canonical trap: GpuCamera / GpuInstance / GpuMaterial byte sizes and Vertex::SIZE. Do NOT trust prose — cross-check against the layout test, whose value is authoritative and whose name may itself be stale:
    grep -rn "fn gpu_camera_is\|fn gpu_instance_is\|assert_eq.*size_of" crates/renderer/src/vulkan/scene_buffer/gpu_instance_layout_tests.rs
    
  • Doc comments naming renamed/deleted symbols. The recurring one: the deleted render-time Material::classify_pbr (PBR resolution moved to the parse-time NIFAL boundary). Several doc-comments in crates/core/src/ecs/components/material.rs still name it — each must frame it as deleted/historical, never as a live entry point. Enumerate and read each:
    grep -n "classify_pbr" crates/core/src/ecs/components/material.rs
    
    The surviving symbols are the free function classify_pbr_keyword and the method Material::resolve_pbr; metalness/roughness are plain resolved f32. (This overlaps Dim 8 — report material doc rot under Dim 3.)
  • ROADMAP.md milestones marked "in progress" whose issues are all closed (or vice versa) — cross-check git log / gh issue.
  • docs/feature-matrix.md — re-check each milestone row against shipped code (git log --grep M45, --grep M47.2, …). The known M45/M47.2 lag was fixed on 2026-06-21 (the Save/load row was removed, the M47.2 row now reads "✓ .pex recognizer slice … full transpiler deferred"), so those are clean — but the matrix is a status floor, not a record of what exists, so any future milestone can drift the same way. Flag any row whose status contradicts the crate that implements it.
  • HISTORY.md entries referencing later-reverted work.
  • README.md command examples whose flags/paths changed.
  • docs/legacy/ references to Gamebryo source paths that moved.
  • crates/renderer/shaders/triangle.frag doc comments quoting outdated GPU struct byte sizes — cross-check the layout test, not the prose.

Path convention (post-#1114): a backticked .ext path in any audit-*.md or this file asserts "exists right now". Forward-looking (not-yet-created) or backwards-looking (deleted) refs must NOT use backticks. The gate fails on any backticked path that does not resolve. The gate now globs both the shared .claude/commands/_audit-*.md files AND every .claude/commands/audit-*/SKILL.md subdir (so paths in this very file ARE gate-covered) — run it before committing.

Dimension 4: Audit-Finding Rot

The audit infrastructure decays like any other code, and stale baselines actively misdirect future audits. Discovery:

.claude/commands/_audit-validate.sh            # structural path gate (#1114)
ls .claude/commands/audit-*.md docs/audits/
  • STALE refs from the gate that live in other audit skills → Dim 4 findings (trivial).
  • Symbol-anchor refs the gate cannot verify (e.g. crates/audio/src/lib.rs::drain_pending_oneshots) — spot-check the symbol still exists.
  • "Existing: #NNN" callouts in skills where the issue is now CLOSED — reframe as a closed-state baseline.
  • Skill files quoting a dimension count ("all N dimensions") that no longer matches the live list.
  • docs/audits/ reports >90 days old whose CRITICAL/HIGH findings are not all triaged on GitHub.
  • Do NOT flag .claude/issues/<N>/ISSUE.md "Status: Open" drift — dropped per TD10-001 / #1156: local issue files are immutable snapshots; GitHub is authoritative. Query gh issue view <N> --json state for live state.

Dimension 5: Stale Markers (TODO / FIXME / HACK / XXX)

Discovery:

grep -RInE '(TODO|FIXME|HACK|XXX)\b' crates byroredux
grep -RInE '(TODO|HACK)' crates/renderer/shaders/

Triage each (skip markers <30 days old unless they name a closed issue):

  • git blame for age — anything >6 months gets reported.
  • Does it name an issue number? Is that issue still open? Closed issue + live marker → "marker outlived its driver" (delete or reopen).
  • Does it name a milestone (M21, M29, …) now complete per ROADMAP.md?
  • // TODO: implement on a path now reachable from a shipped CLI flag → promote (see severity table).
  • False positives to exclude: XXXX is the ESM extended-size sub-record tag (crates/plugin/src/esm/reader.rs, records/misc/magic.rs) — protocol, not a marker. // FIXME note referencing a reference implementation's FIXME (e.g. crates/bgsm/src/bgem.rs) is documentation of upstream, not our debt.
  • Must-not-delete: the third-party attribution block atop crates/renderer/shaders/triangle.frag (GLSL-PathTracer MIT notice + Burley 2012 citation, ~first 30 lines). Flag any edit that strips/truncates it — MIT requires the notice travel with the code.

Dimension 6: Stub & Placeholder Implementations

Discovery:

grep -RInE 'unimplemented!|todo!\(\)|panic!\("not ' crates byroredux
grep -RInE '// *(stub|TODO: real|placeholder|not yet)' crates byroredux

The first command currently returns nothing — the codebase prefers explicit fallbacks to panics, so any hit is genuinely notable. For each:

  • Reachable from a shipped CLI flag or smoke test? → promote to MEDIUM.
  • Functions returning None / Vec::new() / Default::default() with a "// stub"/"// TODO: real impl" comment.
  • Trait impls with empty bodies that the trait docs say should do work.
  • Per-game ESM record coverage in crates/plugin/src/esm/records/ — fully wired vs stubbed per game; cross-check ROADMAP.md per-game compat matrix. (The legacy per-game stubs in crates/plugin/src/legacy/ were removed under #390 — coverage now lives in the unified records tree; do not re-file the removed stubs.)
  • Console commands in byroredux/src/commands/ that exist but no-op / print "TODO".

Dimension 7: Magic Numbers & Hardcoded Constants

Discovery: read the version-gate and budget sites; do not regex blindly (most literals are legitimate).

  • Bare numeric literals in crates/nif/src/blocks/ compared against version codes → should be a NifVersion constant.
  • Vulkan MAX_*/MIN_* hardcoded inline → reference vk::PhysicalDeviceLimits or a named constant.
  • Shader #define provenance: every shader define is generated from one Rust source — crates/renderer/src/shader_constants_data.rs is include!d by both crates/renderer/src/shader_constants.rs and crates/renderer/build.rs (which emits shaders/include/shader_constants.glsl). The generated-header infra exists; the check is "every shader #define is sourced from shader_constants_data.rs; flag any literal that bypasses it" (lockstep risk HIGH — feedback_shader_struct_sync.md).
  • GPU #[repr(C)] size literals: GpuCamera, GpuInstance, GpuMaterial sizes are pinned by gpu_instance_layout_tests.rs. Flag any inline size literal that should reference those tests, and any doc comment quoting an outdated size (overlaps Dim 3). Get the live values from the test, not from memory:
    grep -rn "fn gpu_camera_is\|fn gpu_instance_is\|size_of::<Gpu" crates/renderer/src/vulkan/scene_buffer/gpu_instance_layout_tests.rs
    
  • Frame/ray/cache budgets (GLASS_RAY_BUDGET, MAX_TOTAL_BONES, MAX_MATERIALS, …) scattered vs in one tunable module.
  • ESM sub-record sizes hardcoded (if data.len() == 24) → named constant from the record struct.
  • Do NOT flag protocol-defined magic: FourCC tags, BSA/NIF/BA2 magic, Vulkan format enums.

Dimension 8: Dead Code & Backwards-Compat Cruft

Discovery:

grep -RInE 'allow\(dead_code\)' crates byroredux
grep -RInE '#\[deprecated\]|// *removed:|_unused|fn .*_unused' crates byroredux
cargo machete 2>/dev/null || echo "cargo machete not installed — scan Cargo.toml deps vs use stmts"
  • Each #[allow(dead_code)] — actually called now, or still dead? Delete if dead.
  • pub fn in a private module no one imports (cargo +nightly rustc -- -W unused).
  • mod.rs/lib.rs re-exports with no downstream consumer.
  • _-prefixed params that survived a refactor (CLAUDE.md: delete, don't rename to _var).
  • // removed: … breadcrumbs (CLAUDE.md: delete completely, no breadcrumbs).
  • Re-exports of deleted types kept "for compatibility" — ByroRedux has no external consumers yet, so these are pure rot.
  • Cargo.toml feature flags with only one branch (always-on/always-off) → remove the flag.
  • #[deprecated] items with no consumers → delete, don't deprecate.
  • Do NOT flag: cfg(test)/cfg(debug_assertions)-gated code, FFI boundary functions, or public API of a workspace-internal crate a future binary will consume (note such cases rather than deleting).

Dimension 9: Test Hygiene

Discovery:

grep -RIn '#\[ignore\]' . | grep -v target/

Most #[ignore]s gate Vulkan/smoke tests that need a GPU or on-disk game data — those are not debt. Triage the rest:

  • Each #[ignore] test: referenced issue still open? If it guards a closed CRITICAL/HIGH fix → MEDIUM (severity table).
  • Tests with only smoke assertions (assert!(result.is_ok()) and nothing else) — should assert on values.
  • Commented-out assertions inside otherwise-passing tests (// assert_eq!(…)).
  • #[cfg(feature = "…")]-gated tests where the feature is never enabled in CI.
  • Tests that println! without a follow-up assert.
  • byroredux/tests/golden_frames.rs (opts into --ignored) — still runnable, golden image current.
  • Cross-reference "must not regress" lines in other audit skills (e.g. audit-performance) — each named regression test still present and not #[ignore]d.

Cross-Dimension Dedup

A TODO inside a dead function reports under Dim 8 (Dead Code), not also Dim 5. Material doc rot reports under Dim 3, not also Dim 8. A stale GPU-size doc comment reports under Dim 3; a stale GPU-size code literal under Dim 7. NIFAL/material translation correctness is out of scope here — that is /audit-nifal. This audit only owns the debt around that tier (dead code, stale doc, leftover breadcrumbs).

Phase 3: Merge

  1. Read all /tmp/audit/tech-debt/dim_*.md.
  2. Combine into docs/audits/AUDIT_TECH_DEBT_<TODAY>.md:
    • Executive Summary — findings by severity + delta vs baseline.txt.
    • Baseline Snapshot — the Phase-1 counts, so the next audit can diff.
    • Top 10 Quick Wins — trivial/small effort, immediate readability or compile-time payoff.
    • Top 5 Medium Investments — file/function splits, duplication consolidations.
    • Findings — by severity (HIGH → MEDIUM → LOW), then by dimension.
    • Deferred — findings gated on an in-progress milestone; name the gating milestone.
  3. Remove cross-dimension duplicates per the rules above.

Phase 4: Cleanup

  1. rm -rf /tmp/audit/tech-debt.
  2. Tell the user the report is ready.
  3. Suggest: /audit-publish docs/audits/AUDIT_TECH_DEBT_<TODAY>.md.

GitHub Label

Findings publish under the tech-debt label (plus the standard <severity> and <domain> labels). It is registered in the repo — /audit-publish applies it automatically when a finding's audit type is TECH_DEBT.

Skills similaires