"Convert an audit report's findings into GitHub issues with completeness checks"
Instal·la
npx skillscat add matiaszanolli/byroredux/claude-commands-audit-publish Instal·la des del registre de SkillsCat.
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 pathThis 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 atbyroredux/src/material_translate.rs(translate_material) +crates/core/src/ecs/components/material.rs(Material::resolve_pbr); the per-frame
particle system isbyroredux/src/systems/particle.rs(apply_emitter_params), fed bycrates/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.jsonMatch 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 withgh 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.txtEvery 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 nomaintenance 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 withdocumentationas 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 fix8. 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 — querygh issue view <N> --json state when live state is needed. Do not write aState:/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>