matiaszanolli

Audit → GitHub Issues Publisher

"Convert an audit report's findings into GitHub issues with completeness checks"

matiaszanolli 28 3 Actualitzat fa 1 setm.
GitHub

Instal·la

npx skillscat add matiaszanolli/byroredux/claude-commands-audit-publish

Instal·la des del registre de SkillsCat.

SKILL.md

Audit → GitHub Issues Publisher

Turn a finished audit report (docs/audits/AUDIT_<TYPE>_<DATE>.md) into one GitHub
issue per actionable finding, with dedup, label reconciliation, and a completeness
gate so nothing silently slips through.

Shared protocol (read, do not restate): .claude/commands/_audit-common.md —
the Base Per-Finding Format (the exact field set this skill parses) and the
Deduplication (MANDATORY) flow. Severity scale: .claude/commands/_audit-severity.md.

This skill is the only place issues are created. Audit skills stop at writing the
report; they never call gh issue create.

Process

1. Load + parse the report

Read $ARGUMENTS (e.g. docs/audits/AUDIT_RENDERER_2026-04-04.md). Each finding block
follows _audit-common's Base Per-Finding Format: ### <ID>: <Title> then Severity,
Dimension, Location, Status, Description, Evidence, Impact, Related,
Suggested Fix. Extract those fields per finding; ID + Severity + Location + Status
are required, the rest carry into the issue body.

2. Path-validation gate (run first, before judging any finding)

.claude/commands/_audit-validate.sh        # exit 1 on any STALE backticked path

This is the #1114 / TD7-050 gate. It fails fast when a report was written against
pre-split paths — a Location: pointing at a file that is now a directory. Common
splits to expect (old single file → current dir; the old paths are
deliberately un-backticked since they no longer exist): archive.rs →
crates/bsa/src/archive/, render.rs → byroredux/src/render/,
import/walk.rs → crates/nif/src/import/walk/, blocks/collision.rs →
crates/nif/src/blocks/collision/, blocks/tri_shape.rs →
crates/nif/src/blocks/tri_shape/.
byroredux/src/systems.rs and byroredux/src/cell_loader.rs survive as thin re-export
shims beside their systems/ and cell_loader/ dirs — re-point a Location there
to the owning submodule (e.g. byroredux/src/systems/particle.rs).

3. Filter by status

Process only findings with status NEW. Existing: #NNN and Regression of #NNN
are already tracked upstream — record them in the summary, do not re-file.

4. Validate each NEW finding against current code

  • Read the referenced file at the symbol (not the line — line numbers drift; trust
    grep -rn <fn/struct>).
  • Re-map before judging. If the Location: file no longer exists but the code does
    (a split, not a fix), resolve to the current submodule and update the finding's path
    before filing. Do NOT mark a path move as STALE. Examples: material logic lives at
    byroredux/src/material_translate.rs (translate_material) +
    crates/core/src/ecs/components/material.rs (Material::resolve_pbr); the per-frame
    particle system is byroredux/src/systems/particle.rs (apply_emitter_params), fed by
    crates/nif/src/import/walk/emitter.rs (extract_emitter_params / extract_emitter_rate).
  • Classify: CONFIRMED (bug still present) → file it; STALE (already fixed) → skip,
    record in summary; UNVERIFIABLE (cannot confirm against code) → skip, record in summary.

5. Deduplicate against open issues

Follow _audit-common's Deduplication flow:

mkdir -p /tmp/audit
gh issue list --repo matiaszanolli/ByroRedux --limit 400 --json number,title,state,labels \
  > /tmp/audit/issues.json

Match each CONFIRMED finding's keywords against existing open issue titles.
On a match, skip and record Existing #NNN in the summary. The list above is open-only
(gh issue list defaults to --state open), so check closed issues per finding with
gh issue list --repo matiaszanolli/ByroRedux --state closed --search "<keywords>" --json number,title;
if a closed issue matches and the bug is back, file it but title/note it as a regression of #NNN.

6. Reconcile labels against the live repo (do this once, before any create)

The set of labels that exist in the repo is authoritative — gh issue create rejects an
unknown label. Pull the live set first:

gh label list --repo matiaszanolli/ByroRedux --limit 200 --json name --jq '.[].name' \
  > /tmp/audit/labels.txt

Every label this skill applies MUST appear in that file. The four axes are defined in
_audit-common's Issue Labels section — do not restate them; the summary below is the
publish-time mapping only.

Severity (always exactly one): critical · high · medium · low · info.

Type (always exactly one): bug · enhancement · documentation. There is no
maintenance label — tech-debt findings use the tech-debt domain label plus bug;
doc-rot findings use documentation + doc-rot.

Domain (one or more) — as of 2026-08-21 nearly every audited subsystem has its own
label. Map the finding's subsystem directly:

Finding subsystem Domain label(s)
NIF parser / block dispatch nif-parser (primary) + nif (format tag)
NIFAL canonical translation nifal + the subsystem it lands in (nif-parser / renderer)
ESM / CELL / WRLD / plugin loading esm-plugin
BSA / BA2 / CSG archive readers import-pipeline (no bsa label — flag the gap)
Vulkan renderer / RT / denoiser renderer (+ vulkan / pipeline / memory)
GLSL / SPIR-V sources, shader contract shaders (+ renderer)
Water — WATAL translation, buoyancy, waterline water
Terrain / LOD / sky / weather / ground cover / worldspace (EXAL, SKYAL) terrain-exterior (+ renderer / shaders for the GPU half)
Inventory / containers / loot / consumables / combat / AI packages / locomotion gameplay + the narrower tag (inventory / combat / ai / quests / dialogue)
BGSM / CDB / HKX / FaceGen / MenuXml / game-detect readers import-pipeline (no own label — flag the gap)
SDK, debug server / protocol / byro-dbg, launcher, settings-io tech-debt (no own label — flag the gap)
Physics — Havok→Rapier, colliders, ragdoll (PHYSAL) physics
SpeedTree (.spt) speedtree
Character rulesets — ActorValues, perks, leveling (CHARAL) character
Audio (M44) audio
Scaleform / SWF UI (R4 + M48) ui
Save/load (M45) save-load
Papyrus / ObScript / scripting runtime scripting
ECS storage / queries / scheduler ecs
Animation runtime / KF import / clip sampling animation (+ ecs / nif-parser for the side it lands in)
Cargo dependency drift, pins, MSRV dependencies (+ tech-debt)
GPU sync (semaphores, fences, barriers) sync
CPU lock ordering / races / access declarations concurrency
Platform / windowing, audit infrastructure tech-debt (no own label — flag the gap)

Game (zero or more) — apply whenever the finding is specific to a title, in addition
to the domain label. A per-game audit report labels every finding with its game; a
cross-cutting audit labels only the findings that name a specific title's data:
game:fnv · game:fo3 · game:fo4 · game:fo76 · game:skyrim · game:oblivion ·
game:starfield.

Cross-cutting kind tags (zero or more, alongside the domain):

  • doc-rot — the defect is documentation drifted from code (stale ROADMAP row, SKILL doc
    naming a deleted symbol, a comment describing removed behaviour). Pair with
    documentation as the type.
  • test-gap — the defect is missing/vacuous/non-asserting coverage. Pair with the domain
    of the code left untested.
  • tech-debt — dead code, duplication, stale markers, magic numbers, oversized files.

If a finding genuinely has no reasonable existing label, file it with severity + bug
only and flag the missing-label gap in the summary. Do not gh label create ad hoc —
new labels are a deliberate repo decision, not a per-publish side effect.

Report-family defaults — the AUDIT_<TYPE>_<DATE>.md filename selects a default
domain/type; a per-finding Dimension/domain always overrides it:

Report (AUDIT_<TYPE>_*.md) Default domain Type Extra
AUDIT_RENDERER_* renderer bug —
AUDIT_ECS_* ecs bug —
AUDIT_CONCURRENCY_* concurrency bug sync on GPU-side findings
AUDIT_NIF_* nif-parser bug nif
AUDIT_NIFAL_* nifal bug + the landing subsystem
AUDIT_ESM_* esm-plugin bug —
AUDIT_PHYSICS_* physics bug water on the WATAL buoyancy sink
AUDIT_CHARACTER_* character bug —
AUDIT_UI_* ui bug —
AUDIT_SAVE_* save-load bug —
AUDIT_AUDIO_* audio bug —
AUDIT_SPEEDTREE_* speedtree bug terrain-exterior
AUDIT_SCRIPTING_* scripting bug —
AUDIT_PAPYRUS_* scripting bug —
AUDIT_PERFORMANCE_* performance bug + the landing subsystem
AUDIT_SAFETY_* safety bug + the landing subsystem
AUDIT_RUNTIME_* (per finding) bug performance on fps/draw-call drift
AUDIT_EXTERIOR_* terrain-exterior bug water / shaders / renderer per finding
AUDIT_GAMEPLAY_* gameplay bug inventory / combat / ai / quests / dialogue per finding
AUDIT_PARSERS_* import-pipeline bug game:* where title-specific
AUDIT_TOOLING_* tech-debt bug —
AUDIT_FNV_* / AUDIT_FO3_* / AUDIT_FO4_* / AUDIT_SKYRIM_* / AUDIT_OBLIVION_* / AUDIT_STARFIELD_* (per finding) bug the matching game:* on every finding + legacy-compat
AUDIT_LEGACY_COMPAT_* legacy-compat bug game:* where title-specific
AUDIT_TECH_DEBT_* (per finding) bug tech-debt
AUDIT_INCREMENTAL_* / AUDIT_REGRESSION_* (per finding) bug —

For AUDIT_TECH_DEBT_* the final set is <severity>,<domain?>,tech-debt,bug. Doc-rot
findings — in any report family — swap bug → documentation and add doc-rot;
coverage findings add test-gap. For everything else: <severity>,<domain>,bug,
plus game:<title> whenever the finding is specific to one game's data.

7. Build the completeness checklist (per CONFIRMED finding)

Append to each issue body. Drop rows that can't apply (e.g. omit FFI if the fix is
NIF-only):

## Completeness Checks
- [ ] **UNSAFE**: If the fix adds `unsafe`, a safety comment states the upheld invariant
- [ ] **SIBLING**: Same pattern checked in related files (other shader types, other block parsers)
- [ ] **DROP**: If Vulkan objects change, the Drop impl is still reverse-order correct
- [ ] **LOCK_ORDER**: If a RwLock scope changes, TypeId-sorted acquisition is preserved
- [ ] **FFI**: If the cxx bridge is touched, pointer lifetimes across the boundary are sound
- [ ] **CANONICAL-BOUNDARY**: If the fix touches `byroredux/src/material_translate.rs` (`translate_material`), `Material::resolve_pbr` (`crates/core/src/ecs/components/material.rs`), or the emitter params in `crates/nif/src/import/walk/emitter.rs` (`extract_emitter_params` / `extract_emitter_rate`), per-game logic stays at the NIFAL parser→`Material` boundary — never pushed into shaders/renderer, never re-derived at render time. See `/audit-nifal`.
- [ ] **TESTS**: A regression test pins this specific fix

8. Create the issue

gh issue create --repo matiaszanolli/ByroRedux \
  --title "<ID>: <title>" \
  --body  "<description + evidence + impact + suggested fix + completeness checks>" \
  --label "<severity>,<domain>,<type>"

--label takes a comma-separated list. Every token must be in /tmp/audit/labels.txt
(step 6). If gh rejects a label, the reconciliation missed one — fix the mapping, do not
drop the finding silently.

9. Snapshot to local tracking

mkdir -p .claude/issues/<NUMBER>

Write .claude/issues/<NUMBER>/ISSUE.md with the finding details.

Immutable-snapshot convention (TD10-001 / #1156): this file is the issue as filed,
not a live mirror. GitHub is authoritative for current state — query
gh issue view <N> --json state when live state is needed. Do not write a
State:/Status: field. The convention applies symmetrically to INVESTIGATION.md and
any sibling created by /fix-issue.

10. Completeness summary (the gate)

Every NEW finding must reach a terminal action — created, skipped-as-duplicate, or
skipped-with-reason. Print the table and assert the count matches: NEW findings parsed ==
(Created + Skipped). A NEW finding that is neither created nor consciously skipped is a
publish bug.

Finding Action Reason
REN-001 Created #42 NEW, CONFIRMED
REN-002 Skipped Existing #38
REN-003 Skipped STALE (fixed in composite.rs)
BSA-004 Created #43 NEW, CONFIRMED — labeled import-pipeline (no bsa label)

Flag any subsystem-without-a-label mappings (step 6) here so the gap is visible.

11. Suggest next step

For each created issue:

Fix with: /fix-issue <number>