Skip to content

ci: add semantic release and nexus upload pipeline#2130

Merged
alandtse merged 4 commits into
community-shaders:devfrom
alandtse:semantic-release
Apr 19, 2026
Merged

ci: add semantic release and nexus upload pipeline#2130
alandtse merged 4 commits into
community-shaders:devfrom
alandtse:semantic-release

Conversation

@alandtse
Copy link
Copy Markdown
Collaborator

@alandtse alandtse commented Apr 16, 2026

Standardize feature metadata via INI files and add automated release:

  • Add semantic-release configuration for dev branch with RC prerelease support
  • Create release.yaml workflow for version bumping and changelog generation
  • Create upload-nexus.yaml for parallel feature uploads with dry-run and recovery
  • Extend feature_version_audit.py with fuzzy directory matching and AutoUpload support
  • Standardize all 34 feature INI files to [Info] + [Nexus] format
    • Core features: empty [Nexus] section (AutoUpload implicit false)
    • Independent mods: [Nexus] with NexusModID, NexusFileName, AutoUpload=true
  • Support dry-run mode and graceful failure handling (core strict, features optional)

Summary by CodeRabbit

  • Chores

    • Standardized feature metadata across many shaders (normalized version key, trailing newlines) and added Nexus upload metadata where applicable.
    • Added CI release workflow (manual trigger with release type) and semantic-release configuration to automate releases.
    • Added an automated Nexus upload workflow with artifact resolution, matrix-driven uploads, dry-run support and upload summaries.
  • New Features

    • Enhanced feature-audit tooling: Nexus metadata ingestion/normalization, new-code detection and author stats, and a CLI option to export Nexus upload matrices.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds two GitHub Actions workflows for semantic releases and Nexus uploads, normalizes many feature INI metadata (rename Versionversion, append [Nexus]), and significantly extends tools/feature_version_audit.py to parse INIs/Nexus metadata, build/export an upload matrix, and emit new outputs.

Changes

Cohort / File(s) Summary
Workflows
​.github/workflows/release.yaml, ​.github/workflows/upload-nexus.yaml
Add a manually-triggerable Semantic Release workflow that runs the feature audit and emits release outputs; add an Upload Nexus workflow that resolves tags (manual or workflow_run), builds/filters a Nexus upload matrix, downloads release assets, and invokes a reusable Nexus upload workflow (supports dry-run and per-feature skips).
Semantic Release config
​.releaserc.js
New semantic-release configuration reading RELEASE_TYPE to enable rc prereleases on dev, runs plugins (analyze, generate notes, replace VERSION in CMakeLists.txt), and commits updated files via @semantic-release/git.
Feature INI files
features/*/Shaders/Features/*.ini (many files, e.g., features/Cloud Shadows/.../CloudShadows.ini, features/Grass Collision/.../GrassCollision.ini, features/Screen Space GI/.../ScreenSpaceGI.ini, etc.)
Standardized metadata across many INI files: renamed [Info] Versionversion, ensured trailing newlines, and appended [Nexus] sections (some include nexusmodid, nexusfilename, autoupload).
Feature audit tool
tools/feature_version_audit.py
Large extension: make version parsing case‑insensitive and preserve key casing when bumping; add robust quoted-string extraction; add JSON and INI Nexus metadata ingestion and normalization; fuzzy INI/dir lookup; include auto_upload, mod_id, mod_filename, artifact_pattern; add new_code_features, author attribution, VR special-case handling; add --export-nexus-matrix CLI and emit upload matrix; update reporting and outputs.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant GH as GitHub Actions
    participant Repo as Repository
    participant Py as feature_version_audit.py
    participant SR as semantic-release-action

    User->>GH: workflow_dispatch release (release_type)
    GH->>Repo: checkout (fetch-depth: 0)
    GH->>Py: run tools/feature_version_audit.py (CI mode / emit matrix)
    Py-->>GH: audit results + nexus matrix
    GH->>SR: run semantic-release-action (v${version})
    SR->>Repo: analyze commits, create tag, publish release
    SR-->>GH: outputs new_release_published, new_release_version

    alt new_release_published == 'true'
        GH->>GH: trigger upload-nexus workflow_run / notify-upload
        GH->>Repo: download release artifacts for tag
    end

    GH->>User: append step summary (release tag, upload status/link)
Loading
sequenceDiagram
    participant GH as GitHub Actions
    participant Repo as Repository
    participant Py as feature_version_audit.py
    participant CLI as gh CLI
    participant Nexus as Nexus Upload Workflow

    GH->>Repo: checkout resolved tag or head ref
    GH->>Py: prepare-nexus-matrix (resolve tag, run audit)
    Py-->>GH: full matrix + filtered upload_matrix
    loop per-feature
        GH->>CLI: gh release download (assets *.7z)
        CLI-->>GH: list of assets
        GH->>GH: select artifact by artifact_pattern or mark skipped
        alt auto_upload && not dry_run
            GH->>Nexus: call reusable upload-nexus workflow with creds
            Nexus-->>GH: per-feature upload results
        else
            GH-->>GH: record dry-run / skipped
        end
    end
    GH->>GH: write upload-summary to $GITHUB_STEP_SUMMARY
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • davo0411
  • doodlum

"🐰
I hopped through INIs, neat and spry,
made Version lowercase, gave Nexus a try,
stitched audits, matrices, a release parade—
tags and uploads now dance in the glade! 🥕"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'ci: add semantic release and nexus upload pipeline' clearly and concisely describes the main changes: adding CI/CD workflows for semantic release and Nexus upload functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

No actionable suggestions for changed features.

@alandtse alandtse mentioned this pull request Apr 16, 2026
2 tasks
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/feature_version_audit.py (1)

320-325: ⚠️ Potential issue | 🟡 Minor

Keep the standardized lowercase version key when auto-bumping.

This rewrite always emits Version = ..., so the release workflow will revert bumped INIs back to the old casing right after this PR standardizes them to lowercase version.

Suggested change
-        new_content = RE_VERSION.sub(f"Version = {proposed_ver_str}", content, count=1)
+        new_content = RE_VERSION.sub(f"version = {proposed_ver_str}", content, count=1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/feature_version_audit.py` around lines 320 - 325, The replacement
currently hardcodes "Version = ..." which forces uppercase and loses the
original key casing; update the RE_VERSION.sub call to preserve the matched
key's exact text (e.g., using a replacement function/lambda that returns
f"{match.group(1)} = {proposed_ver_str}" or equivalent) so the original casing
(like "version") is kept when writing new_content before opening ini_path for
write.
🧹 Nitpick comments (1)
features/Skylighting/Shaders/Features/Skylighting.ini (1)

1-7: PR metadata follow-up: use conventional commit title and add issue linkage.

Consider renaming the PR title to something like ci(release): add semantic release and nexus upload, and add issue keywords (e.g., Implements #<id> / Addresses #<id>) in the PR description if applicable.

As per coding guidelines: "Conventional Commit Titles ... Format: type(scope): description" and "Issue References ... Suggest adding appropriate GitHub keywords".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/Skylighting/Shaders/Features/Skylighting.ini` around lines 1 - 7,
The PR metadata uses a free-form title and lacks issue linkage; update the pull
request title to follow Conventional Commits (e.g., "ci(release): add semantic
release and nexus upload") and add an issue reference keyword in the PR
description such as "Implements #<id>" or "Addresses #<id>" to link the work to
the relevant issue; ensure the title follows the type(scope): description
pattern and the description includes the chosen GitHub keyword and issue number.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yaml:
- Around line 11-49: The workflow is missing explicit permissions required for
semantic-release to create tags/commits; add a top-level permissions block with
"contents: write" so the release job (job name "release" and the "Semantic
Release" step using `@semantic-release/git`) can push tags/commits even when repo
defaults are restricted; insert a root-level permissions: contents: write entry
(not inside a job) above the jobs: block so the GITHUB_TOKEN has the needed
write scope for publishing.

In @.github/workflows/upload-nexus.yaml:
- Around line 195-210: The reusable workflow is incorrectly invoked as a step
(the step with id nexus_upload and name "Upload to Nexus"); move this invocation
to a job-level reusable-workflow call by creating a new job (e.g.,
nexus_upload_job) that uses:
alandtse/nexus-workflows/.github/workflows/upload-nexus.yml@main and pass the
same with: inputs (nexus_game_id, nexus_mod_id, artifact_name, tag_name,
mod_version, mod_filename, changelog) and secrets
(UNEX_NEXUSMODS_SESSION_COOKIE, UNEX_APIKEY); replicate the original condition
(if: steps.artifact.outputs.artifact_found == 'true' &&
steps.dryrun.outputs.dry_run != 'true' && matrix.auto_upload != false) as the
job-level if, and preserve continue-on-error behavior by setting
continue-on-error on the new job (or handle failure strategy appropriately),
then remove the original step (id: nexus_upload) from the job's steps.
- Around line 90-94: The nexus-matrix.json is being written with pretty-printed
JSON via json.dump(wrapped, f, indent=2), which creates newlines that break the
echo "matrix=$(cat nexus-matrix.json)" >> $GITHUB_OUTPUT step; change the
json.dump call in this block to use compact serialization (remove the indent
parameter or use separators) so nexus-matrix.json is a single-line JSON string
compatible with $GITHUB_OUTPUT/fromJson(), ensuring the matrix value can be read
by the next job.
- Around line 53-69: The download-artifact step with id "changelogs" is missing
the run-id and token so when triggered by workflow_run it pulls from the wrong
run; add run-id: ${{ github.event.workflow_run.id }} and github-token: ${{
github.token }} to that step to fetch the upstream artifacts, and update the
"generate" step (which invokes tools/feature_version_audit.py) to supply
fallback/core defaults when github.event.inputs.nexus_mod_id,
github.event.inputs.mod_filename or github.event.inputs.artifact_pattern are
empty (e.g., use conditional/env fallbacks or a matrix-default value) so the
core row in the generated Nexus matrix isn’t blank during workflow_run
invocations.
- Around line 32-37: The workflow is passing
github.event.workflow_run.head_branch (a branch name) into the gh release
download step, which expects a release tag; change the argument passed to gh
release download to an actual release tag value instead of
github.event.workflow_run.head_branch—for example, consume a release tag emitted
by the Semantic Release job (e.g., needs.semantic_release.outputs.release_tag or
a workflow input like inputs.release_tag) or use the tag ref (github.ref when
the event is a tag ref like refs/tags/v1.2.3); update the gh release download
invocation to use that release_tag variable (replace
github.event.workflow_run.head_branch) so the command receives a real release
tag.

In `@features/Dynamic` Cubemaps/Shaders/Features/DynamicCubemaps.ini:
- Around line 4-5: The [Nexus] INI section is missing an explicit autoupload
setting which is parsed as true; add the key autoupload = false under the
[Nexus] section (i.e., update the section block containing [Nexus] to include
the parameter autoupload set to false) so the feature is explicitly disabled for
core/non-upload features.

In `@features/Interior` Sun/Shaders/Features/InteriorSun.ini:
- Around line 4-5: The empty [Nexus] section currently leaves autoupload
undefined (which the parser treats as true); explicitly add the autoupload key
to the [Nexus] block and set autoupload = false to ensure the feature is not
treated as upload-enabled (update the Features/Interior
Sun/Shaders/Features/InteriorSun.ini [Nexus] section to include autoupload =
false).

In `@features/Screen` Space GI/Shaders/Features/ScreenSpaceGI.ini:
- Around line 4-6: The Nexus configuration block [Nexus] is missing the
autoupload toggle so the feature won't be auto-uploaded; update the [Nexus]
section (where nexusmodid and nexusfilename are defined) to include autoupload =
true so the feature is included in automated uploads.

In `@features/Sky` Sync/Shaders/Features/SkySync.ini:
- Around line 4-6: The Nexus metadata block ([Nexus] containing nexusmodid and
nexusfilename) is missing the autoupload flag; add the key autoupload = true
under the [Nexus] section so the feature matches the independent-mod schema and
enables automatic uploads for Sky Sync.

In `@features/Wetness` Effects/Shaders/Features/WetnessEffects.ini:
- Around line 4-6: Add an explicit autoupload setting under the existing [Nexus]
block: locate the [Nexus] section containing nexusmodid and nexusfilename and
add the line autoupload = true so the Nexus-enabled feature metadata explicitly
enables automatic uploads instead of relying on implicit defaults.

In `@tools/feature_version_audit.py`:
- Around line 422-456: The fuzzy-match branch finds a matching candidate_dir but
never updates feature_dir, so subsequent logic still treats the feature as
missing; modify the branch in which candidate_dir is found (inside the loop that
calls normalize_feature_key and get_feature_ini_metadata) to set feature_dir =
candidate_dir (and update ini_meta from get_feature_ini_metadata) so the rest of
the function (the mod_id update, nexus lookup, and the final conditional that
checks feature_dir.exists() or ini_meta.get('mod_id')) sees the discovered
directory and its full metadata (short_name, artifact_pattern, mod_filename,
etc.); ensure normalize_feature_key, candidate_dir, get_feature_ini_metadata,
ini_meta and feature_dir are the referenced symbols to change.
- Around line 189-212: The comprehension at the end strips falsy values so an
explicit auto_upload=False is removed; preserve boolean False while still
filtering out None/empty strings by changing the final return to only drop
values that are None or empty strings (e.g., return {k: v for k, v in
metadata.items() if v is not None and v != ''}), so auto_upload set by the
parsing logic for section handling (where auto_upload is computed) remains
present and build_nexus_upload_matrix() can respect an explicit false.

---

Outside diff comments:
In `@tools/feature_version_audit.py`:
- Around line 320-325: The replacement currently hardcodes "Version = ..." which
forces uppercase and loses the original key casing; update the RE_VERSION.sub
call to preserve the matched key's exact text (e.g., using a replacement
function/lambda that returns f"{match.group(1)} = {proposed_ver_str}" or
equivalent) so the original casing (like "version") is kept when writing
new_content before opening ini_path for write.

---

Nitpick comments:
In `@features/Skylighting/Shaders/Features/Skylighting.ini`:
- Around line 1-7: The PR metadata uses a free-form title and lacks issue
linkage; update the pull request title to follow Conventional Commits (e.g.,
"ci(release): add semantic release and nexus upload") and add an issue reference
keyword in the PR description such as "Implements #<id>" or "Addresses #<id>" to
link the work to the relevant issue; ensure the title follows the type(scope):
description pattern and the description includes the chosen GitHub keyword and
issue number.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 28b21031-bbb9-4dba-bebb-2e046040041a

📥 Commits

Reviewing files that changed from the base of the PR and between de34cd6 and c428eae.

📒 Files selected for processing (38)
  • .github/workflows/release.yaml
  • .github/workflows/upload-nexus.yaml
  • .releaserc.js
  • features/Cloud Shadows/Shaders/Features/CloudShadows.ini
  • features/Dynamic Cubemaps/Shaders/Features/DynamicCubemaps.ini
  • features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini
  • features/Extended Materials/Shaders/Features/ExtendedMaterials.ini
  • features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini
  • features/Grass Collision/Shaders/Features/GrassCollision.ini
  • features/Grass Lighting/Shaders/Features/GrassLighting.ini
  • features/HDR Display/Shaders/Features/HDRDisplay.ini
  • features/Hair Specular/Shaders/Features/HairSpecular.ini
  • features/IBL/Shaders/Features/ImageBasedLighting.ini
  • features/Interior Sun/Shaders/Features/InteriorSun.ini
  • features/Inverse Square Lighting/Shaders/Features/InverseSquareLighting.ini
  • features/LOD Blending/Shaders/Features/LODBlending.ini
  • features/Light Limit Fix/Shaders/Features/LightLimitFix.ini
  • features/Linear Lighting/Shaders/Features/LinearLighting.ini
  • features/Performance Overlay/Shaders/Features/PerformanceOverlay.ini
  • features/RenderDoc/Shaders/Features/RenderDoc.ini
  • features/Screen Space GI/Shaders/Features/ScreenSpaceGI.ini
  • features/Screen-Space Shadows/Shaders/Features/ScreenSpaceShadows.ini
  • features/Sky Sync/Shaders/Features/SkySync.ini
  • features/Skylighting/Shaders/Features/Skylighting.ini
  • features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini
  • features/Terrain Blending/Shaders/Features/TerrainBlending.ini
  • features/Terrain Helper/Shaders/Features/TerrainHelper.ini
  • features/Terrain Shadows/Shaders/Features/TerrainShadows.ini
  • features/Terrain Variation/Shaders/Features/TerrainVariation.ini
  • features/Unified Water/Shaders/Features/UnifiedWater.ini
  • features/Upscaling/Shaders/Features/Upscaling.ini
  • features/VR/Shaders/Features/VR.ini
  • features/Volumetric Lighting/Shaders/Features/VolumetricLighting.ini
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
  • features/Water Effects/Shaders/Features/WaterEffects.ini
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • features/Wetness Effects/Shaders/Features/WetnessEffects.ini
  • tools/feature_version_audit.py

Comment thread .github/workflows/release.yaml
Comment thread .github/workflows/upload-nexus.yaml Outdated
Comment thread .github/workflows/upload-nexus.yaml Outdated
Comment thread .github/workflows/upload-nexus.yaml Outdated
Comment thread .github/workflows/upload-nexus.yaml Outdated
Comment thread features/Screen Space GI/Shaders/Features/ScreenSpaceGI.ini
Comment thread features/Sky Sync/Shaders/Features/SkySync.ini
Comment thread features/Wetness Effects/Shaders/Features/WetnessEffects.ini
Comment thread tools/feature_version_audit.py Outdated
Comment thread tools/feature_version_audit.py
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 16, 2026

✅ A pre-release build is available for this PR:
Download

@davo0411
Copy link
Copy Markdown
Collaborator

Address AI/make sure its warnings are valid & fixed, I will approve after that. Hard to review yaml changes so im just going by what it says.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
.github/workflows/upload-nexus.yaml (1)

93-98: ⚠️ Potential issue | 🔴 Critical

github.event.inputs.* are empty on workflow_run, corrupting the core matrix row.

For the workflow_run trigger (the automated path after Semantic Release), github.event.inputs is not populated — it only exists for workflow_dispatch/workflow_call. These expressions expand to empty strings, and argparse then receives --core-mod-id "" --core-filename "" --core-artifact-pattern "", which override the Python defaults (86492, Community Shaders, CommunityShaders-*.7z) rather than falling back to them.

Downstream consequences on automated runs:

  • prepare-artifacts core row can't match any asset (empty glob) → core is skipped.
  • upload-to-nexus core job is invoked with empty nexus_mod_id → reusable workflow fails.

Use the same || fallback pattern already present at Line 205 for nexus_game_id:

🛠️ Suggested fix
                   python tools/feature_version_audit.py \
                     --export-nexus-matrix \
                     --matrix-output nexus-matrix-raw.json \
-                    --core-mod-id "${{ github.event.inputs.nexus_mod_id }}" \
-                    --core-filename "${{ github.event.inputs.mod_filename }}" \
-                    --core-artifact-pattern "${{ github.event.inputs.artifact_pattern }}"
+                    --core-mod-id "${{ github.event.inputs.nexus_mod_id || '86492' }}" \
+                    --core-filename "${{ github.event.inputs.mod_filename || 'Community Shaders' }}" \
+                    --core-artifact-pattern "${{ github.event.inputs.artifact_pattern || 'CommunityShaders-*.7z' }}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/upload-nexus.yaml around lines 93 - 98, The workflow uses
github.event.inputs.* for the python call to tools/feature_version_audit.py
which are empty on workflow_run and override Python defaults; update the
arguments passed to add the same fallback pattern used for nexus_game_id (use
the || fallback) so --core-mod-id, --core-filename and --core-artifact-pattern
expand to defaults when inputs are empty, ensuring the python invocation
receives the intended default values instead of empty strings.
tools/feature_version_audit.py (1)

427-439: ⚠️ Potential issue | 🟠 Major

Fuzzy-matched INI metadata is still mostly discarded.

The fuzzy branch now correctly promotes feature_dir and refreshes ini_meta (addressing the earlier feedback), but only mod_id is subsequently read from the refreshed ini_meta (Lines 438‑439). The merges at Lines 410‑425 for mod_link, description, key_features, short_name, artifact_pattern, and mod_filename already ran against the empty dict from the failed exact match and are never re-applied.

Net effect for any feature whose headers use a different casing/spacing than its features/ directory (the exact scenario fuzzy matching targets): the emitted row falls back to artifact_pattern = f'{name}-*.7z' and mod_filename = name (Lines 881/894), which likely won't match the released .7z asset name and will break the Nexus upload for that feature.

🛠️ Suggested fix: re-apply the ini_meta merge after fuzzy promotion
         # Fallback: if exact match failed, try fuzzy matching
         if not feature_dir.exists():
             # Try to find directory by normalizing names (ScreenSpaceGI -> screen space gi)
             normalized_name = normalize_feature_key(name)
             for candidate_dir in FEATURES_DIR.iterdir():
                 if candidate_dir.is_dir() and normalize_feature_key(candidate_dir.name) == normalized_name:
                     feature_dir = candidate_dir
                     ini_meta = get_feature_ini_metadata(candidate_dir)
+                    if ini_meta.get('mod_link'):
+                        mod_link = ini_meta['mod_link']
+                    if ini_meta.get('description') and not description:
+                        description = ini_meta['description']
+                    if ini_meta.get('key_features') and not key_features:
+                        key_features = ini_meta['key_features']
+                    if ini_meta.get('short_name'):
+                        short_name = ini_meta['short_name']
+                    if ini_meta.get('artifact_pattern'):
+                        artifact_pattern = ini_meta['artifact_pattern']
+                    if ini_meta.get('mod_filename'):
+                        mod_filename = ini_meta['mod_filename']
                     break

Or refactor the merge block into a helper and call it both after the exact-match read and after the fuzzy promotion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/feature_version_audit.py` around lines 427 - 439, The fuzzy-match
branch updates feature_dir and reloads ini_meta with
get_feature_ini_metadata(candidate_dir) but the subsequent merge that populates
mod_link, description, key_features, short_name, artifact_pattern, and
mod_filename (previously run against the empty ini_meta) is not re-applied;
update the code to re-run the same ini_meta merge after the fuzzy promotion
(either by extracting the merge logic into a helper function and calling it both
after the exact-match read and after the fuzzy branch, or by duplicating the
merge statements after the block) so that normalize_feature_key,
get_feature_ini_metadata, ini_meta, and mod_id are all consistently applied for
fuzzy-matched directories.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/upload-nexus.yaml:
- Around line 93-98: The workflow uses github.event.inputs.* for the python call
to tools/feature_version_audit.py which are empty on workflow_run and override
Python defaults; update the arguments passed to add the same fallback pattern
used for nexus_game_id (use the || fallback) so --core-mod-id, --core-filename
and --core-artifact-pattern expand to defaults when inputs are empty, ensuring
the python invocation receives the intended default values instead of empty
strings.

In `@tools/feature_version_audit.py`:
- Around line 427-439: The fuzzy-match branch updates feature_dir and reloads
ini_meta with get_feature_ini_metadata(candidate_dir) but the subsequent merge
that populates mod_link, description, key_features, short_name,
artifact_pattern, and mod_filename (previously run against the empty ini_meta)
is not re-applied; update the code to re-run the same ini_meta merge after the
fuzzy promotion (either by extracting the merge logic into a helper function and
calling it both after the exact-match read and after the fuzzy branch, or by
duplicating the merge statements after the block) so that normalize_feature_key,
get_feature_ini_metadata, ini_meta, and mod_id are all consistently applied for
fuzzy-matched directories.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f2b4f588-b927-4a6a-b27a-a078d6e7c9d0

📥 Commits

Reviewing files that changed from the base of the PR and between c428eae and 889db85.

📒 Files selected for processing (38)
  • .github/workflows/release.yaml
  • .github/workflows/upload-nexus.yaml
  • .releaserc.js
  • features/Cloud Shadows/Shaders/Features/CloudShadows.ini
  • features/Dynamic Cubemaps/Shaders/Features/DynamicCubemaps.ini
  • features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini
  • features/Extended Materials/Shaders/Features/ExtendedMaterials.ini
  • features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini
  • features/Grass Collision/Shaders/Features/GrassCollision.ini
  • features/Grass Lighting/Shaders/Features/GrassLighting.ini
  • features/HDR Display/Shaders/Features/HDRDisplay.ini
  • features/Hair Specular/Shaders/Features/HairSpecular.ini
  • features/IBL/Shaders/Features/ImageBasedLighting.ini
  • features/Interior Sun/Shaders/Features/InteriorSun.ini
  • features/Inverse Square Lighting/Shaders/Features/InverseSquareLighting.ini
  • features/LOD Blending/Shaders/Features/LODBlending.ini
  • features/Light Limit Fix/Shaders/Features/LightLimitFix.ini
  • features/Linear Lighting/Shaders/Features/LinearLighting.ini
  • features/Performance Overlay/Shaders/Features/PerformanceOverlay.ini
  • features/RenderDoc/Shaders/Features/RenderDoc.ini
  • features/Screen Space GI/Shaders/Features/ScreenSpaceGI.ini
  • features/Screen-Space Shadows/Shaders/Features/ScreenSpaceShadows.ini
  • features/Sky Sync/Shaders/Features/SkySync.ini
  • features/Skylighting/Shaders/Features/Skylighting.ini
  • features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini
  • features/Terrain Blending/Shaders/Features/TerrainBlending.ini
  • features/Terrain Helper/Shaders/Features/TerrainHelper.ini
  • features/Terrain Shadows/Shaders/Features/TerrainShadows.ini
  • features/Terrain Variation/Shaders/Features/TerrainVariation.ini
  • features/Unified Water/Shaders/Features/UnifiedWater.ini
  • features/Upscaling/Shaders/Features/Upscaling.ini
  • features/VR/Shaders/Features/VR.ini
  • features/Volumetric Lighting/Shaders/Features/VolumetricLighting.ini
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
  • features/Water Effects/Shaders/Features/WaterEffects.ini
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • features/Wetness Effects/Shaders/Features/WetnessEffects.ini
  • tools/feature_version_audit.py
✅ Files skipped from review due to trivial changes (30)
  • features/Cloud Shadows/Shaders/Features/CloudShadows.ini
  • features/Performance Overlay/Shaders/Features/PerformanceOverlay.ini
  • features/Screen-Space Shadows/Shaders/Features/ScreenSpaceShadows.ini
  • features/IBL/Shaders/Features/ImageBasedLighting.ini
  • features/LOD Blending/Shaders/Features/LODBlending.ini
  • features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
  • features/Light Limit Fix/Shaders/Features/LightLimitFix.ini
  • features/Volumetric Lighting/Shaders/Features/VolumetricLighting.ini
  • features/Terrain Blending/Shaders/Features/TerrainBlending.ini
  • features/Terrain Shadows/Shaders/Features/TerrainShadows.ini
  • features/Sky Sync/Shaders/Features/SkySync.ini
  • features/Water Effects/Shaders/Features/WaterEffects.ini
  • features/Linear Lighting/Shaders/Features/LinearLighting.ini
  • features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini
  • features/Extended Materials/Shaders/Features/ExtendedMaterials.ini
  • features/Unified Water/Shaders/Features/UnifiedWater.ini
  • features/Dynamic Cubemaps/Shaders/Features/DynamicCubemaps.ini
  • features/Upscaling/Shaders/Features/Upscaling.ini
  • features/Terrain Variation/Shaders/Features/TerrainVariation.ini
  • features/Interior Sun/Shaders/Features/InteriorSun.ini
  • features/Skylighting/Shaders/Features/Skylighting.ini
  • features/Inverse Square Lighting/Shaders/Features/InverseSquareLighting.ini
  • features/Grass Collision/Shaders/Features/GrassCollision.ini
  • features/Wetness Effects/Shaders/Features/WetnessEffects.ini
  • features/RenderDoc/Shaders/Features/RenderDoc.ini
  • features/Hair Specular/Shaders/Features/HairSpecular.ini
  • features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini
  • .releaserc.js
  • .github/workflows/release.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • features/HDR Display/Shaders/Features/HDRDisplay.ini
  • features/Terrain Helper/Shaders/Features/TerrainHelper.ini
  • features/Screen Space GI/Shaders/Features/ScreenSpaceGI.ini
  • features/Grass Lighting/Shaders/Features/GrassLighting.ini
  • features/VR/Shaders/Features/VR.ini

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
tools/feature_version_audit.py (1)

405-435: ⚠️ Potential issue | 🟠 Major

Fuzzy-matched directory still drops short_name/artifact_pattern/mod_filename.

The fix promotes feature_dir and re-reads ini_meta (Lines 433-434), but short_name, artifact_pattern, and mod_filename were already frozen from the initial empty ini_meta at Lines 416-425 and are never re-derived from the fuzzy-matched ini_meta. Only mod_id is re-picked up at Line 438. Fuzzy-matched features therefore still lose these fields unless the optional nexus_metadata JSON happens to supply them.

🛠️ Proposed fix
-        feature_dir = FEATURES_DIR / name
-        ini_meta = get_feature_ini_metadata(feature_dir) if feature_dir.exists() else {}
-        if not mod_id:
-            mod_id = ini_meta.get('mod_id')
-
-        if ini_meta.get('mod_link'):
-            mod_link = ini_meta['mod_link']
-        if ini_meta.get('description') and not description:
-            description = ini_meta['description']
-        if ini_meta.get('key_features') and not key_features:
-            key_features = ini_meta['key_features']
-        if ini_meta.get('short_name'):
-            short_name = ini_meta['short_name']
-        if ini_meta.get('artifact_pattern'):
-            artifact_pattern = ini_meta['artifact_pattern']
-        else:
-            artifact_pattern = None
-        if ini_meta.get('mod_filename'):
-            mod_filename = ini_meta['mod_filename']
-        else:
-            mod_filename = None
-
-        # Fallback: if exact match failed, try fuzzy matching
-        if not feature_dir.exists():
-            normalized_name = normalize_feature_key(name)
-            for candidate_dir in FEATURES_DIR.iterdir():
-                if candidate_dir.is_dir() and normalize_feature_key(candidate_dir.name) == normalized_name:
-                    feature_dir = candidate_dir
-                    ini_meta = get_feature_ini_metadata(candidate_dir)
-                    break
-
-        # Update mod_id from INI if still missing
-        if not mod_id:
-            mod_id = ini_meta.get('mod_id')
+        feature_dir = FEATURES_DIR / name
+        ini_meta = get_feature_ini_metadata(feature_dir) if feature_dir.exists() else {}
+
+        # Fallback: fuzzy match before consuming ini_meta
+        if not feature_dir.exists():
+            normalized_name = normalize_feature_key(name)
+            for candidate_dir in FEATURES_DIR.iterdir():
+                if candidate_dir.is_dir() and normalize_feature_key(candidate_dir.name) == normalized_name:
+                    feature_dir = candidate_dir
+                    ini_meta = get_feature_ini_metadata(candidate_dir)
+                    break
+
+        if not mod_id:
+            mod_id = ini_meta.get('mod_id')
+        if ini_meta.get('mod_link'):
+            mod_link = ini_meta['mod_link']
+        if ini_meta.get('description') and not description:
+            description = ini_meta['description']
+        if ini_meta.get('key_features') and not key_features:
+            key_features = ini_meta['key_features']
+        if ini_meta.get('short_name'):
+            short_name = ini_meta['short_name']
+        artifact_pattern = ini_meta.get('artifact_pattern')
+        mod_filename = ini_meta.get('mod_filename')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/feature_version_audit.py` around lines 405 - 435, The fuzzy-match
branch updates feature_dir and re-reads ini_meta (normalize_feature_key,
get_feature_ini_metadata) but doesn’t reassign short_name, artifact_pattern,
mod_filename (nor mod_link/description/key_features) after ini_meta is
refreshed; update the code after the fuzzy-match break to re-extract those
fields the same way as the initial block (assign mod_link, description,
key_features, short_name, artifact_pattern, mod_filename and mod_id as needed
from the new ini_meta) so fuzzy-matched directories populate the same variables
as exact matches.
.github/workflows/upload-nexus.yaml (1)

90-115: ⚠️ Potential issue | 🔴 Critical

Core matrix row is blank on workflow_run triggers.

github.event.inputs.* is only populated for workflow_dispatch/workflow_call; on workflow_run, Lines 96-98 expand to empty strings, which argparse accepts as-is (string defaults are only applied when the flag is absent). The generated matrix then has empty nexus_mod_id/mod_filename/artifact_pattern for the core row, breaking both artifact selection and the Nexus upload.

Guard the values with workflow-level fallbacks so the argparse defaults apply:

-                  python tools/feature_version_audit.py \
-                    --export-nexus-matrix \
-                    --matrix-output nexus-matrix-raw.json \
-                    --core-mod-id "${{ github.event.inputs.nexus_mod_id }}" \
-                    --core-filename "${{ github.event.inputs.mod_filename }}" \
-                    --core-artifact-pattern "${{ github.event.inputs.artifact_pattern }}"
+                  ARGS=( --export-nexus-matrix --matrix-output nexus-matrix-raw.json )
+                  [ -n "${{ github.event.inputs.nexus_mod_id }}" ] && ARGS+=( --core-mod-id "${{ github.event.inputs.nexus_mod_id }}" )
+                  [ -n "${{ github.event.inputs.mod_filename }}" ] && ARGS+=( --core-filename "${{ github.event.inputs.mod_filename }}" )
+                  [ -n "${{ github.event.inputs.artifact_pattern }}" ] && ARGS+=( --core-artifact-pattern "${{ github.event.inputs.artifact_pattern }}" )
+                  python tools/feature_version_audit.py "${ARGS[@]}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/upload-nexus.yaml around lines 90 - 115, The core matrix
flags (--core-mod-id, --core-filename, --core-artifact-pattern) are being passed
with empty strings on workflow_run triggers so argparse sees them as present and
overrides defaults; change the Generate Nexus upload matrix step to only include
those flags when the corresponding github.event.inputs values are non-empty
(i.e., check nexus_mod_id, mod_filename, artifact_pattern before appending each
flag to the python invocation), leaving flags out entirely when empty so
tools/feature_version_audit.py can apply its default values; locate the call to
tools/feature_version_audit.py and the environment variables
nexus_mod_id/mod_filename/artifact_pattern in that block and wrap each flag
addition in a conditional that tests for a non-empty value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/upload-nexus.yaml:
- Around line 104-115: The matrix placeholder row "_empty" causes the reusable
"upload-to-nexus" job to run with invalid inputs; fix by having the prepare step
set a boolean output (e.g., needs.prepare-nexus-matrix.outputs.has_uploads) when
upload_data is non-empty and then gate the upload-to-nexus job with that output,
or alternatively filter out the placeholder in the job-level if:, so update the
script that writes nexus-upload-matrix.json (the upload_data creation) to also
emit an outputs.has_uploads flag (true/false) and change the "upload-to-nexus"
job's if: to require has_uploads == 'true' (or add an if: that excludes rows
where name == '_empty') so the reusable workflow is not invoked with the
placeholder row.

In `@tools/feature_version_audit.py`:
- Around line 895-911: The code currently sets auto_upload =
ini_metadata.get('auto_upload', True) which contradicts
get_feature_ini_metadata's opt-in behavior; change the default to False so
missing or empty ini_metadata does not enable uploads by default (i.e., set
auto_upload = ini_metadata.get('auto_upload', False)) and ensure this update is
applied where the row dict is constructed (referencing variables auto_upload,
ini_metadata, rows, and function get_feature_ini_metadata).

---

Duplicate comments:
In @.github/workflows/upload-nexus.yaml:
- Around line 90-115: The core matrix flags (--core-mod-id, --core-filename,
--core-artifact-pattern) are being passed with empty strings on workflow_run
triggers so argparse sees them as present and overrides defaults; change the
Generate Nexus upload matrix step to only include those flags when the
corresponding github.event.inputs values are non-empty (i.e., check
nexus_mod_id, mod_filename, artifact_pattern before appending each flag to the
python invocation), leaving flags out entirely when empty so
tools/feature_version_audit.py can apply its default values; locate the call to
tools/feature_version_audit.py and the environment variables
nexus_mod_id/mod_filename/artifact_pattern in that block and wrap each flag
addition in a conditional that tests for a non-empty value.

In `@tools/feature_version_audit.py`:
- Around line 405-435: The fuzzy-match branch updates feature_dir and re-reads
ini_meta (normalize_feature_key, get_feature_ini_metadata) but doesn’t reassign
short_name, artifact_pattern, mod_filename (nor
mod_link/description/key_features) after ini_meta is refreshed; update the code
after the fuzzy-match break to re-extract those fields the same way as the
initial block (assign mod_link, description, key_features, short_name,
artifact_pattern, mod_filename and mod_id as needed from the new ini_meta) so
fuzzy-matched directories populate the same variables as exact matches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c98d89e0-cbd9-4121-8658-334a70f95d24

📥 Commits

Reviewing files that changed from the base of the PR and between 889db85 and 2cb3a9c.

📒 Files selected for processing (38)
  • .github/workflows/release.yaml
  • .github/workflows/upload-nexus.yaml
  • .releaserc.js
  • features/Cloud Shadows/Shaders/Features/CloudShadows.ini
  • features/Dynamic Cubemaps/Shaders/Features/DynamicCubemaps.ini
  • features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini
  • features/Extended Materials/Shaders/Features/ExtendedMaterials.ini
  • features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini
  • features/Grass Collision/Shaders/Features/GrassCollision.ini
  • features/Grass Lighting/Shaders/Features/GrassLighting.ini
  • features/HDR Display/Shaders/Features/HDRDisplay.ini
  • features/Hair Specular/Shaders/Features/HairSpecular.ini
  • features/IBL/Shaders/Features/ImageBasedLighting.ini
  • features/Interior Sun/Shaders/Features/InteriorSun.ini
  • features/Inverse Square Lighting/Shaders/Features/InverseSquareLighting.ini
  • features/LOD Blending/Shaders/Features/LODBlending.ini
  • features/Light Limit Fix/Shaders/Features/LightLimitFix.ini
  • features/Linear Lighting/Shaders/Features/LinearLighting.ini
  • features/Performance Overlay/Shaders/Features/PerformanceOverlay.ini
  • features/RenderDoc/Shaders/Features/RenderDoc.ini
  • features/Screen Space GI/Shaders/Features/ScreenSpaceGI.ini
  • features/Screen-Space Shadows/Shaders/Features/ScreenSpaceShadows.ini
  • features/Sky Sync/Shaders/Features/SkySync.ini
  • features/Skylighting/Shaders/Features/Skylighting.ini
  • features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini
  • features/Terrain Blending/Shaders/Features/TerrainBlending.ini
  • features/Terrain Helper/Shaders/Features/TerrainHelper.ini
  • features/Terrain Shadows/Shaders/Features/TerrainShadows.ini
  • features/Terrain Variation/Shaders/Features/TerrainVariation.ini
  • features/Unified Water/Shaders/Features/UnifiedWater.ini
  • features/Upscaling/Shaders/Features/Upscaling.ini
  • features/VR/Shaders/Features/VR.ini
  • features/Volumetric Lighting/Shaders/Features/VolumetricLighting.ini
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
  • features/Water Effects/Shaders/Features/WaterEffects.ini
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • features/Wetness Effects/Shaders/Features/WetnessEffects.ini
  • tools/feature_version_audit.py
✅ Files skipped from review due to trivial changes (23)
  • features/IBL/Shaders/Features/ImageBasedLighting.ini
  • features/Performance Overlay/Shaders/Features/PerformanceOverlay.ini
  • features/Upscaling/Shaders/Features/Upscaling.ini
  • features/LOD Blending/Shaders/Features/LODBlending.ini
  • features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini
  • features/Interior Sun/Shaders/Features/InteriorSun.ini
  • features/Water Effects/Shaders/Features/WaterEffects.ini
  • features/Unified Water/Shaders/Features/UnifiedWater.ini
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
  • features/Cloud Shadows/Shaders/Features/CloudShadows.ini
  • features/Dynamic Cubemaps/Shaders/Features/DynamicCubemaps.ini
  • features/Linear Lighting/Shaders/Features/LinearLighting.ini
  • features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini
  • features/Terrain Blending/Shaders/Features/TerrainBlending.ini
  • features/Skylighting/Shaders/Features/Skylighting.ini
  • features/Terrain Variation/Shaders/Features/TerrainVariation.ini
  • features/Terrain Helper/Shaders/Features/TerrainHelper.ini
  • features/Sky Sync/Shaders/Features/SkySync.ini
  • features/RenderDoc/Shaders/Features/RenderDoc.ini
  • .releaserc.js
  • features/Terrain Shadows/Shaders/Features/TerrainShadows.ini
  • features/Inverse Square Lighting/Shaders/Features/InverseSquareLighting.ini
  • features/Screen-Space Shadows/Shaders/Features/ScreenSpaceShadows.ini
🚧 Files skipped from review as they are similar to previous changes (12)
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini
  • features/HDR Display/Shaders/Features/HDRDisplay.ini
  • features/Volumetric Lighting/Shaders/Features/VolumetricLighting.ini
  • features/Extended Materials/Shaders/Features/ExtendedMaterials.ini
  • features/VR/Shaders/Features/VR.ini
  • features/Grass Lighting/Shaders/Features/GrassLighting.ini
  • features/Screen Space GI/Shaders/Features/ScreenSpaceGI.ini
  • features/Hair Specular/Shaders/Features/HairSpecular.ini
  • features/Grass Collision/Shaders/Features/GrassCollision.ini
  • features/Wetness Effects/Shaders/Features/WetnessEffects.ini
  • features/Light Limit Fix/Shaders/Features/LightLimitFix.ini

Comment thread .github/workflows/upload-nexus.yaml
Comment thread tools/feature_version_audit.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/feature_version_audit.py (1)

690-725: ⚠️ Potential issue | 🟠 Major

Fix existing-feature new-code detection and honor --head.

Line 715 skips any feature whose PR-scoped changes are all additions, which is also the normal case for “new files added to an existing feature.” Line 723 also hardcodes HEAD, so --head comparisons can attribute authors from the checked-out revision instead of HEAD_REF.

Suggested fix
+        feature_all_added = bool(changes) and all(s == "A" for s, _ in changes)
+        brand_new_feature = feature_all_added and (
+            (ini_path and pr_prior_ver is None and new_ver is not None) or not ini_path
+        )
+
         # Detect new feature (all files added, ini present)
-        if changes and all(s == "A" for s, _ in changes):
+        if brand_new_feature:
             if ini_path:
                 note = f"New feature (with ini v{new_ver_str})"
                 new_features.append((feature_dir.name, new_ver_str, bump_commit))
@@
-        if ini_path and pr_prior_ver is None and new_ver is not None:
+        elif ini_path and pr_prior_ver is None and new_ver is not None:
             note = f"New ini added (v{new_ver_str})"
             new_features.append((feature_dir.name, new_ver_str, bump_commit))
             is_attention = True
         # Detect files added but ini missing
-        if not ini_path and any(s == "A" for s, _ in changes):
+        elif not ini_path and any(s == "A" for s, _ in changes):
             note = "Files added, ini missing!"
             new_features.append((feature_dir.name, "-", bump_commit))
             is_attention = True
@@
-        if (not (changes and all(s == "A" for s, _ in changes)) and  # Not a brand new feature
+        if (not brand_new_feature and
             release_changes and any(s == "A" for s, _ in release_changes)):  # Has new files added
@@
-                            ["git", "log", "--follow", "--diff-filter=A", "--format=%H", f"{version_ref}..HEAD", "--", f],
+                            ["git", "log", "--reverse", "--follow", "--diff-filter=A", "--format=%H", f"{version_ref}..{HEAD_REF}", "--", f],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/feature_version_audit.py` around lines 690 - 725, The existing-feature
new-code detection wrongly skips cases when the PR-scoped changes are all
additions and also hardcodes "HEAD" in the git log call; fix by removing the
leading negation that checks (changes and all(s == "A"...)) from the if
condition so the block for detecting added files on existing features triggers
whenever release_changes has additions, and update the git-range used when
computing add_commit to use the configured head reference (replace
f"{version_ref}..HEAD" with f"{version_ref}..{head_ref}" or the variable your
CLI uses for --head) and guard against empty git output before indexing into
split results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/feature_version_audit.py`:
- Line 1035: The --nexus-api-key CLI flag
(parser.add_argument('--nexus-api-key', ...)) is declared but never used; remove
the misleading flag or wire it up to a safe env-first retrieval: either delete
that parser.add_argument call and update the help text to remove the
NEXUS_API_KEY mention, or stop accepting secrets on the command line and instead
read credentials via os.environ.get('NEXUS_API_KEY') at runtime (use that value
where Nexus API calls will occur), ensuring you do not log the secret and do not
store it in persistent CLI-parsed structures; update any help strings
referencing NEXUS_API_KEY accordingly.
- Around line 64-65: normalize_feature_key currently only strips whitespace so
fuzzy matching still fails on hyphenated names; update normalize_feature_key to
remove hyphens (and any whitespace) from the lowercased input before returning
so it matches how find_feature_dir describes fuzzy matching (e.g., transform the
input via replace or translate to drop '-' and then collapse whitespace via
split/join). Target the normalize_feature_key function to implement this change
so hyphenated metadata and directory names are normalized the same way as
space-separated ones.

---

Outside diff comments:
In `@tools/feature_version_audit.py`:
- Around line 690-725: The existing-feature new-code detection wrongly skips
cases when the PR-scoped changes are all additions and also hardcodes "HEAD" in
the git log call; fix by removing the leading negation that checks (changes and
all(s == "A"...)) from the if condition so the block for detecting added files
on existing features triggers whenever release_changes has additions, and update
the git-range used when computing add_commit to use the configured head
reference (replace f"{version_ref}..HEAD" with f"{version_ref}..{head_ref}" or
the variable your CLI uses for --head) and guard against empty git output before
indexing into split results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 19208b0e-3c55-4de2-aaca-3b91cd7e3ef8

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb3a9c and af1d43a.

📒 Files selected for processing (2)
  • .github/workflows/upload-nexus.yaml
  • tools/feature_version_audit.py
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/upload-nexus.yaml

Comment thread tools/feature_version_audit.py Outdated
Comment thread tools/feature_version_audit.py Outdated
Standardize feature metadata via INI files and add automated release:

- Add semantic-release configuration for dev branch with RC prerelease support
- Create release.yaml workflow for version bumping and changelog generation
- Create upload-nexus.yaml for parallel feature uploads with dry-run and recovery
- Extend feature_version_audit.py with fuzzy directory matching and AutoUpload support
- Standardize all 34 feature INI files to [Info] + [Nexus] format
  - Core features: explicit autoupload = false
  - Independent mods: nexusmodid + nexusfilename + autoupload = true (opt-out later)
  - Default parser behavior changed to opt-in (false when unset)
- Support dry-run mode and graceful failure handling (core strict, features optional)
- Fix normalize_feature_key to strip hyphens for hyphenated directory matching
- Fix new-code detection to use release_changes and HEAD_REF instead of HEAD
- Remove unused --nexus-api-key CLI flag; credentials via NEXUS_API_KEY env var only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/upload-nexus.yaml:
- Around line 61-68: The run script interpolates GitHub workflow inputs directly
into shell, causing quoting/command-injection risks; move each input into
environment variables in the job's env section (e.g., TAG, DRY_RUN,
NEXUS_MOD_ID, MOD_FILENAME, ARTIFACT_PATTERN) using expressions like TAG: ${{
github.event.inputs.tag }} and DRY_RUN: ${{ github.event.inputs.dry_run }}, then
update the shell commands to reference the plain shell vars ($TAG, $DRY_RUN,
$NEXUS_MOD_ID, $MOD_FILENAME, $ARTIFACT_PATTERN) instead of inlining
expressions; also ensure the HEAD_SHA value used to compute TAG is assigned to
an env variable (e.g., HEAD_SHA: ${{ github.event.workflow_run.head_sha }}) and
referenced as $HEAD_SHA in the gh api call to avoid direct interpolation in the
run block.
- Around line 91-110: The upload workflow currently runs
tools/feature_version_audit.py without a --base so get_latest_release_tag() ends
up comparing the release tag to itself and misses changed features; update the
"Generate Nexus upload matrix" step to pass an explicit
--base=<previous-release-tag> (compute or retrieve the prior release tag before
calling feature_version_audit.py and append it to ARGS) and also ensure the
repository Checkout step uses fetch-depth: 0 so git tags/history are available
for get_latest_release_tag(); update the ARGS construction and the Checkout
action accordingly (referencing the Generate Nexus upload matrix step and
feature_version_audit.py / get_latest_release_tag()).
- Around line 71-74: The step currently exits with error when TAG is empty;
change it to detect if GITHUB_EVENT_NAME == "workflow_run" and if TAG is empty
then print a skip message and exit 0 (graceful no-op), otherwise retain the
existing error exit 1 for manual/dispatch runs; also update the downstream job
conditions for the prepare-artifacts and upload-to-nexus jobs (or their
job-level if: expressions) to check for the skip condition (empty TAG or a skip
signal output) so they don't run on workflow_run with no tag but still fail when
a manual run supplies no TAG.

In `@tools/feature_version_audit.py`:
- Around line 720-729: The current git log call in the block that sets
add_commit (the subprocess.check_output invocation) takes the first element of
git's output, but git log returns newest-first by default, so the code can
attribute added files to the wrong author; change the git invocation (used where
add_commit is assigned) to request the oldest add commit—either add the
"--reverse" flag to the git log args or select the last element of the decoded
split list—then keep the rest of the flow that calls
get_commit_author(add_commit) and appends to new_code_features unchanged.
- Around line 189-212: The merge currently blindly calls metadata.update(...)
which lets later sections erase earlier non-empty values (and flips auto_upload
based on order); change this to only set each metadata field when the parsed
value is non-empty (i.e., not None and not ""), and for auto_upload only write
metadata['auto_upload'] if the autoupload/auto_upload key exists in
section_items (so unspecified sections don't override an explicit setting or the
default); when handling key_features merge by extending/deduping the existing
metadata.get('key_features', []) list rather than replacing it; adjust the block
around metadata.update, the auto_upload variable, and the key_features handling
to implement these conditional-assign/merge rules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f012de9-a573-4b66-9f16-d6a3257b5b79

📥 Commits

Reviewing files that changed from the base of the PR and between af1d43a and d8aef00.

📒 Files selected for processing (38)
  • .github/workflows/release.yaml
  • .github/workflows/upload-nexus.yaml
  • .releaserc.js
  • features/Cloud Shadows/Shaders/Features/CloudShadows.ini
  • features/Dynamic Cubemaps/Shaders/Features/DynamicCubemaps.ini
  • features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini
  • features/Extended Materials/Shaders/Features/ExtendedMaterials.ini
  • features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini
  • features/Grass Collision/Shaders/Features/GrassCollision.ini
  • features/Grass Lighting/Shaders/Features/GrassLighting.ini
  • features/HDR Display/Shaders/Features/HDRDisplay.ini
  • features/Hair Specular/Shaders/Features/HairSpecular.ini
  • features/IBL/Shaders/Features/ImageBasedLighting.ini
  • features/Interior Sun/Shaders/Features/InteriorSun.ini
  • features/Inverse Square Lighting/Shaders/Features/InverseSquareLighting.ini
  • features/LOD Blending/Shaders/Features/LODBlending.ini
  • features/Light Limit Fix/Shaders/Features/LightLimitFix.ini
  • features/Linear Lighting/Shaders/Features/LinearLighting.ini
  • features/Performance Overlay/Shaders/Features/PerformanceOverlay.ini
  • features/RenderDoc/Shaders/Features/RenderDoc.ini
  • features/Screen Space GI/Shaders/Features/ScreenSpaceGI.ini
  • features/Screen-Space Shadows/Shaders/Features/ScreenSpaceShadows.ini
  • features/Sky Sync/Shaders/Features/SkySync.ini
  • features/Skylighting/Shaders/Features/Skylighting.ini
  • features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini
  • features/Terrain Blending/Shaders/Features/TerrainBlending.ini
  • features/Terrain Helper/Shaders/Features/TerrainHelper.ini
  • features/Terrain Shadows/Shaders/Features/TerrainShadows.ini
  • features/Terrain Variation/Shaders/Features/TerrainVariation.ini
  • features/Unified Water/Shaders/Features/UnifiedWater.ini
  • features/Upscaling/Shaders/Features/Upscaling.ini
  • features/VR/Shaders/Features/VR.ini
  • features/Volumetric Lighting/Shaders/Features/VolumetricLighting.ini
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
  • features/Water Effects/Shaders/Features/WaterEffects.ini
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • features/Wetness Effects/Shaders/Features/WetnessEffects.ini
  • tools/feature_version_audit.py
✅ Files skipped from review due to trivial changes (27)
  • features/Performance Overlay/Shaders/Features/PerformanceOverlay.ini
  • features/Upscaling/Shaders/Features/Upscaling.ini
  • features/Unified Water/Shaders/Features/UnifiedWater.ini
  • features/Interior Sun/Shaders/Features/InteriorSun.ini
  • features/Inverse Square Lighting/Shaders/Features/InverseSquareLighting.ini
  • features/Light Limit Fix/Shaders/Features/LightLimitFix.ini
  • features/Dynamic Cubemaps/Shaders/Features/DynamicCubemaps.ini
  • features/Water Effects/Shaders/Features/WaterEffects.ini
  • features/Skylighting/Shaders/Features/Skylighting.ini
  • features/Linear Lighting/Shaders/Features/LinearLighting.ini
  • features/Sky Sync/Shaders/Features/SkySync.ini
  • features/Weather Editor/Shaders/Features/WeatherEditor.ini
  • features/Terrain Helper/Shaders/Features/TerrainHelper.ini
  • features/RenderDoc/Shaders/Features/RenderDoc.ini
  • features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini
  • features/IBL/Shaders/Features/ImageBasedLighting.ini
  • features/Terrain Variation/Shaders/Features/TerrainVariation.ini
  • features/Screen Space GI/Shaders/Features/ScreenSpaceGI.ini
  • features/Hair Specular/Shaders/Features/HairSpecular.ini
  • features/Terrain Blending/Shaders/Features/TerrainBlending.ini
  • features/LOD Blending/Shaders/Features/LODBlending.ini
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
  • features/Terrain Shadows/Shaders/Features/TerrainShadows.ini
  • .releaserc.js
  • features/Grass Collision/Shaders/Features/GrassCollision.ini
  • features/Screen-Space Shadows/Shaders/Features/ScreenSpaceShadows.ini
  • features/Wetness Effects/Shaders/Features/WetnessEffects.ini
🚧 Files skipped from review as they are similar to previous changes (9)
  • features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini
  • features/HDR Display/Shaders/Features/HDRDisplay.ini
  • features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini
  • features/Extended Materials/Shaders/Features/ExtendedMaterials.ini
  • features/Volumetric Lighting/Shaders/Features/VolumetricLighting.ini
  • features/Cloud Shadows/Shaders/Features/CloudShadows.ini
  • features/Grass Lighting/Shaders/Features/GrassLighting.ini
  • .github/workflows/release.yaml
  • features/VR/Shaders/Features/VR.ini

Comment thread .github/workflows/upload-nexus.yaml Outdated
Comment thread .github/workflows/upload-nexus.yaml
Comment thread .github/workflows/upload-nexus.yaml
Comment thread tools/feature_version_audit.py Outdated
Comment thread tools/feature_version_audit.py
@davo0411
Copy link
Copy Markdown
Collaborator

@alandtse is this good to merge?

@alandtse alandtse merged commit f2fd2a3 into community-shaders:dev Apr 19, 2026
15 checks passed
ParticleTroned pushed a commit to ParticleTroned/skyrim-community-shaders that referenced this pull request May 2, 2026
…#2130)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit f2fd2a3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants