Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
f70c2e3
fix(ci): replace inline python interpolation in version workflows (#123)
SashaRX Aug 6, 2026
8aac825
fix(ci): stop auto-committing built native binaries (#125)
SashaRX Aug 6, 2026
ab237f5
fix(tools): harden gen.bat argument forwarding (#135)
SashaRX Aug 6, 2026
9d2f97f
fix(docs): shell-escape skills-overhaul parameter file (#136)
SashaRX Aug 6, 2026
254e131
fix(packaging): mirror .gitignore exclusions into .npmignore (#164)
SashaRX Aug 6, 2026
7e05c11
fix(native): vendor xatlas instead of fetching a moving branch (#127)
claude Aug 6, 2026
ed32493
fix(repack): prevent atlas oversample size overflow (#129)
SashaRX Aug 6, 2026
a6667c5
fix(benchmark): escape CSV formula prefixes in sweep summaries (#157)
SashaRX Aug 6, 2026
0e50071
fix(fbx): escape CSV formula prefixes in metrics export (#159)
SashaRX Aug 6, 2026
e157498
fix(benchmark): escape CSV formula prefixes in benchmark records (#161)
SashaRX Aug 6, 2026
d183260
fix(tools): escape model name in gallery nav href (#158)
SashaRX Aug 6, 2026
8892f9a
refactor(editor): share one CsvUtil.Escape across the CSV writers (#1…
claude Aug 6, 2026
8fc1373
fix(collision): validate sidecar collision entries before mesh rebuil…
SashaRX Aug 6, 2026
3bfda6e
fix(repack): sanitize sidecar-supplied xatlas settings and save path …
SashaRX Aug 6, 2026
d333790
fix(replay): preserve optimized vertex colors during sidecar replay (…
SashaRX Aug 6, 2026
b8e7c8b
fix(replay): validate sidecar UV replay channels (#139)
SashaRX Aug 6, 2026
e6ef72b
fix(replay): validate sidecar submesh triangle counts (#126)
SashaRX Aug 6, 2026
95eef91
fix(replay): abort incomplete stale remap rebuilds (#188)
SashaRX Aug 6, 2026
dcfeddd
fix(replay): bound sidecar vertex remapping work (#185)
SashaRX Aug 6, 2026
5ade5c8
fix(arap): reject ARAP UVs outside float range (#134)
SashaRX Aug 6, 2026
b80baff
fix(benchmark): bound benchmark UV PNG generation (#137)
SashaRX Aug 6, 2026
b4e9f78
fix(transfer): drop partial UV2 output on cancellation (#156)
SashaRX Aug 6, 2026
c021b9e
fix(repack): prevent concurrent xatlas native sessions (#128)
SashaRX Aug 6, 2026
11474a5
fix(benchmark): validate sweep parameters before running (#138)
SashaRX Aug 6, 2026
0b1d245
fix(repack): cache mesh area preview instead of scanning per repaint …
claude Aug 6, 2026
49e1c87
fix(preview): guard shell color hash against int.MinValue (#168)
SashaRX Aug 6, 2026
7dbedc9
fix(transfer): bound grouped transfer overlap scan (#186)
SashaRX Aug 6, 2026
79decd4
fix(partition): bound spatial overlap detection work (#190)
SashaRX Aug 6, 2026
390f7cb
fix(scene): bound SceneView hover raycasts (#187)
SashaRX Aug 6, 2026
494b049
fix(lod): bound LOD indices parsed from object names (#142)
claude Aug 6, 2026
9d11d56
fix(lod): normalize LOD0 transition before generating levels (#179)
claude Aug 6, 2026
d57c3c6
fix(transfer): isolate cross-LOD hints per mesh group (#181)
SashaRX Aug 6, 2026
dab2c98
fix(collision): bound V-HACD recursion depth (#122)
SashaRX Aug 6, 2026
e24de00
fix(collision): validate V-HACD triangle indices (#152)
SashaRX Aug 6, 2026
504f84d
fix(fbx): avoid overflow in FBX backup path hashes (#162)
SashaRX Aug 6, 2026
fbd797d
fix(transfer): expose Apply UV2 after source-only repack (#170)
SashaRX Aug 6, 2026
13d50b6
fix(repack): keep post-pack density correction inside packed charts (…
SashaRX Aug 6, 2026
fe5c210
fix(repack): keep UV2 clamp toggle independently editable (#172)
SashaRX Aug 6, 2026
135f7c4
fix(repack): forward RepackUv rotate flag to xatlas (#173)
SashaRX Aug 6, 2026
0e7dd50
fix(ao): defer GPU AO cleanup until queued work completes (#140)
SashaRX Aug 6, 2026
8b5b283
fix(ao): guard AO blur against incomplete mesh attributes (#163)
SashaRX Aug 6, 2026
ddbadcc
fix(ao): bound Vertex AO seam matching work (#143)
claude Aug 6, 2026
c1eeb19
fix(ao): bound 3D AO blur work on dense meshes (#144)
claude Aug 6, 2026
42feec8
fix(ao): make atomic AO accumulation NaN-safe (#147)
SashaRX Aug 6, 2026
454d491
fix(ao): bound face-area AO correction on degenerate meshes (#148)
claude Aug 6, 2026
4a6ba2e
fix(ao): clear stale AO results before loading mesh data (#177)
SashaRX Aug 6, 2026
362bd23
fix(ao): prevent duplicate Vertex AO preview backups (#145)
SashaRX Aug 6, 2026
a2e2be8
fix(ao): guard vertex AO bake workload (#149)
claude Aug 6, 2026
3d898b1
fix(canvas): render Inverted triangles in validation overlay (#174)
claude Aug 6, 2026
40f0036
fix(export): avoid cumulative transform baking on shared FBX meshes (…
claude Aug 6, 2026
d0a6ba9
fix(hub): restore preview meshes before undo/redo refresh (#176)
claude Aug 6, 2026
543795e
fix(collision): actually strip extra vertex channels (#178)
claude Aug 6, 2026
24bb542
fix(repack): normalize UV0 shell winding at the repack boundary (#180)
claude Aug 6, 2026
e0d85e4
fix(transfer): reject incomplete source UV channels (#183)
claude Aug 6, 2026
e1218ad
fix(meshopt): guard buffer sizes before the unsafe native call (#184)
claude Aug 6, 2026
3e82c9d
docs(changelog): record consolidated security and bugfix sweep
claude Aug 6, 2026
78a6e7a
fix(export): destroy temporary mesh clones after FBX export (#175 fol…
claude Aug 6, 2026
2d84f15
fix(ci): harden checkout credentials and package.json validation (PR …
claude Aug 6, 2026
a3262de
fix(npm): exclude agent and workspace config from the published packa…
claude Aug 6, 2026
1424e67
fix(csv): flatten line breaks so one CSV record stays one physical li…
claude Aug 6, 2026
8bd14b2
fix(partition): dedupe vertex-pair keys of degenerate triangles (PR #…
claude Aug 6, 2026
d1c41bf
fix(lod): share LOD index parsing and apply the level cap where it bi…
claude Aug 6, 2026
cdaeae1
fix(postprocess): keep sidecar colors authoritative and abort partial…
claude Aug 6, 2026
d964ed2
fix(png): reject index lists that are not whole triangles (PR #191 re…
claude Aug 6, 2026
902566b
fix(ao): bound seam scanning by candidates examined and drop cancelle…
claude Aug 6, 2026
39565fc
fix(repack): report session contention as a result error, free the te…
claude Aug 6, 2026
62ac795
fix(benchmark): decouple atlas utilization from the PNG snapshot budg…
claude Aug 6, 2026
ed73bcc
fix(transfer): gate Apply UV2 on a repack that actually produced mesh…
claude Aug 6, 2026
043b0d2
fix(native): validate the remaining collision ABI parameters (PR #191…
claude Aug 6, 2026
6f98c59
fix(native): record the vendored xatlas upstream snapshot (PR #191 re…
claude Aug 6, 2026
388dcbd
fix(tools): validate the values gen.bat forwards to python (PR #191 r…
claude Aug 6, 2026
5eb9e3a
fix(preview): share the non-negative colour key helper (PR #191 review)
claude Aug 6, 2026
2eb1a5e
fix(native): rename xatlas VERSION file shadowing the C++ <version> h…
claude Aug 6, 2026
bf9013c
fix(export): destroy remaining temporary export meshes (leak cleanup)
claude Aug 6, 2026
b579d2f
fix(export): destroy sidecar-built collision meshes after FBX export …
claude Aug 6, 2026
9625a69
fix(transfer): create analysis material folders only when debug is en…
claude Aug 6, 2026
84e37e2
ci(native): restore automatic publishing of built binaries
claude Aug 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions .claude/skills/skills-overhaul-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -522,19 +522,15 @@ if [ -n "$NAMESPACE" ] && ! echo "$NAMESPACE" | grep -qE '^SashaRX\.'; then
echo "WARN: namespace '$NAMESPACE' does not match SashaRX.<Package> — migration required in Phase 2."
fi

# 0.7 Write the parameter file used by later phases
cat > /tmp/skills-overhaul.env <<EOF
REPO_NAME=$REPO_NAME
PACKAGE_ID=$PACKAGE_ID
DISPLAY_NAME=$DISPLAY_NAME
NAMESPACE=$NAMESPACE
UNITY_MIN=$UNITY_MIN
DEFAULT_BRANCH=$DEFAULT_BRANCH
EOF
# 0.7 Write shell-escaped parameters used by later phases.
# `declare -p` produces Bash syntax that cannot reinterpret repository values
# (for example, command substitutions in package.json) when the file is sourced.
declare -p REPO_NAME PACKAGE_ID DISPLAY_NAME NAMESPACE UNITY_MIN DEFAULT_BRANCH \
> /tmp/skills-overhaul.env
cat /tmp/skills-overhaul.env
```

**Verification:** `/tmp/skills-overhaul.env` is non-empty; working tree either clean or known-dirty; git tag created.
**Verification:** `/tmp/skills-overhaul.env` is non-empty and contains only `declare -- NAME=...` records; working tree either clean or known-dirty; git tag created.

**Rollback:** `git reset --hard <pre-overhaul-sha>` (SHA saved in `/tmp/pre-overhaul-sha.txt`), and `git tag -d pre-skills-overhaul-<date>`.

Expand Down Expand Up @@ -895,4 +891,4 @@ The overhaul is successful when, in each of the three repos:

1. **Run Phase 0 in each of the three repos and paste back the `/tmp/skills-overhaul.env` output for each.** This resolves the repo-identity question (are `UnityLodUvLightmapTransfer` / `lightmap-uv-tool` the same as `UnityMeshLab`?) and gives the plan the parameter values it needs. Estimated time: 3 minutes per repo.
2. **Decide the two policy questions in §5.2**: (a) namespace Stance A vs B and (b) whether to include directive "ALWAYS invoke" phrasing on the three most-critical skills. These decisions change only a handful of lines in `_shared/naming-conventions.md` and in three frontmatter descriptions.
3. **Execute Phase 1 (scaffold) in one repo as a pilot**, then Phase 2.1 (`_shared/` and `_checklists/` files) — these are repo-agnostic and can be copied identically to the other two repos once validated. Commit per the sub-commit plan. Only after that pilot runs cleanly through Phase 4 validation do you replicate to the remaining repos.
3. **Execute Phase 1 (scaffold) in one repo as a pilot**, then Phase 2.1 (`_shared/` and `_checklists/` files) — these are repo-agnostic and can be copied identically to the other two repos once validated. Commit per the sub-commit plan. Only after that pilot runs cleanly through Phase 4 validation do you replicate to the remaining repos.
21 changes: 17 additions & 4 deletions .github/workflows/build-native.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: Build Native Libraries

# Default for every job: read-only. The `commit` job below opts into
# `contents: write` for itself, so only the step that publishes the built
# binaries carries a writable token — the compile jobs never see one.
Comment on lines +3 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file='.github/workflows/build-native.yml'
sed -n '3,5p;59,105p' "$file"

printf '\nКлючевые настройки:\n'
rg -n -C 4 'contents: write|actions/checkout|persist-credentials|git push' "$file"

Repository: SashaRX/UnityMeshLab

Length of output: 3285


🌐 Web query:

site:github.com/actions/checkout v4.2.2 persist-credentials default git push http.extraheader README

💡 Result:

The persist-credentials input is a configuration option in the GitHub actions/checkout action (used to check out repositories in GitHub Actions) that determines whether the authentication token used for the checkout is persisted in the local Git configuration [1][2]. When set to its default value of true, the action automatically configures the local Git environment—historically by adding an http.<url>.extraheader to the .git/config file—to allow subsequent Git commands (like git fetch or git push) within the same job to use the same authentication credentials [1][2][3]. Setting persist-credentials: false allows you to opt out of this behavior, preventing the action from automatically adding these headers [1][3]. Regarding the technical implementation: - Historical Context: Older versions of actions/checkout relied heavily on setting http.extraheader to inject the AUTHORIZATION header [3][4]. This approach can sometimes cause conflicts with other Git tools, such as Git LFS, or lead to issues when multiple headers are present [4]. - Version v4.2.2 and Updates: GitHub actions/checkout v4.2.2 is a release version (published October 2024) [5]. Recent improvements in the action have transitioned away from direct extraheader usage toward using Git credential helpers (e.g., credential.helper), which is considered a more robust and secure method for handling credentials in Git [6][2]. - Troubleshooting: If you encounter issues such as HTTP 400 errors or authentication conflicts, it is often due to malformed or persistent http.extraheader configurations in your runner's global or local Git config [7][8]. Ensuring that persist-credentials is managed correctly or manually clearing problematic headers from your Git config can resolve these issues [7][4]. In summary, persist-credentials manages whether the GitHub Action makes your job's authentication token available to Git commands via your local Git config, and the underlying mechanism for this has evolved from extraheader to more modern credential-handling approaches in recent versions [6][2][3].

Citations:


🌐 Web query:

site:docs.github.com GitHub Actions permissions job step GITHUB_TOKEN persist credentials checkout

💡 Result:

In GitHub Actions, the actions/checkout action uses the GITHUB_TOKEN to authenticate git operations by default [1][2]. The persist-credentials input for this action determines whether these credentials remain configured in the local git environment after the checkout step completes [1]. Key details regarding persist-credentials and GITHUB_TOKEN: 1. Behavior: When persist-credentials is set to true (which is the default), the actions/checkout action configures the local git environment to use the provided token, allowing subsequent git commands in the same job to authenticate automatically using those same credentials [1][2]. If you set persist-credentials to false, the action will not persist the token in the git configuration, preventing subsequent git commands from using it for authentication [1]. 2. Security: As a security best practice, you should grant the GITHUB_TOKEN only the minimum permissions required for your workflow [3][4][5]. You can configure these permissions at the workflow or job level using the permissions key [3][6][7]. For example, if you only need to clone a repository, you might set the contents permission to read [8][9]. 3. Usage Example: permissions: contents: read jobs: example-job: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: persist-credentials: false # Disables token persistence for git - name: Git commands run: | # Git commands here will not use the GITHUB_TOKEN automatically

Citations:


Ограничьте доступ к contents: write и отключите автоматическое сохранение credentials.

permissions: contents: write задан на уровне job и действует для всех шагов commit. actions/checkout по умолчанию сохраняет credentials для последующих Git-команд, поэтому текущий git push использует write-токен автоматически. Добавьте persist-credentials: false и передайте токен только команде git push через временный http.extraheader. GitHub Actions не поддерживает permissions на уровне шага; для изоляции вынесите публикацию в отдельный job.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-native.yml around lines 3 - 5, Ограничьте `contents:
write` отдельным job публикации вместо всего `commit` job: оставьте compile jobs
и checkout с read-only доступом, настройте `actions/checkout` с отключённым
сохранением credentials, а в шаге `git push` передайте write-токен только через
временный `http.extraheader`.

Source: Linters/SAST tools

permissions:
contents: read

on:
push:
paths:
Expand Down Expand Up @@ -33,7 +39,9 @@ jobs:
name: Build (${{ matrix.os }})

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Configure CMake
run: cmake -S 'Native~' -B build ${{ matrix.cmake_args }} -DCMAKE_BUILD_TYPE=Release
Expand All @@ -42,12 +50,17 @@ jobs:
run: cmake --build build --config Release

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: native-${{ matrix.os }}
path: ${{ matrix.plugin_dir }}/${{ matrix.artifact }}
if-no-files-found: error

# Publishes the freshly built binaries back into Plugins/ so a native
# source change lands in the package without anyone copying files by hand.
# Only runs for pushes to this repository (never for pull_request, so a
# fork can never reach a writable token), and the commit carries [skip ci]
# so it cannot retrigger this workflow.
commit:
needs: build
runs-on: ubuntu-latest
Expand All @@ -56,12 +69,12 @@ jobs:
contents: write

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.ref }}

- name: Download all artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: artifacts

Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/meta-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,15 @@ jobs:
- uses: actions/checkout@v4

- name: Check package.json is valid JSON
run: python3 -c "import json; json.load(open('package.json'))"
# `jq empty` succeeds on a stream of several root values ("{...}{...}"),
# which every JSON consumer but jq rejects. Slurping and asserting a
# single object root is what "valid package.json" actually means.
run: jq -e -s 'length == 1 and (.[0] | type == "object")' package.json

- name: Check version format (semver)
run: |
VERSION=$(python3 -c "import json; print(json.load(open('package.json'))['version'])")
if echo "$VERSION" | grep -qP '^\d+\.\d+\.\d+$'; then
VERSION=$(jq -er '.version | select(type == "string")' package.json)
if [[ "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
Comment on lines 92 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Синхронизируйте ограничение PATCH с workflow обновления версии.

Эта проверка принимает третью компоненту версии любой длины. В .github/workflows/version-bump.yml (Lines 30–40) значение PATCH длиной более 9 цифр отклоняется до арифметической операции. Поэтому версия, например 1.2.1000000000, проходит этот job, но затем ломает обновление версии.

Добавьте здесь такое же ограничение или вынесите правило в общий валидатор.

Предлагаемое исправление
          if [[ "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
+           PATCH="${VERSION##*.}"
+           if [ "${`#PATCH`}" -gt 9 ]; then
+             echo "::error::Patch component out of range: $PATCH (expected < 1000000000)"
+             exit 1
+           fi
            echo "Version: $VERSION"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Check version format (semver)
run: |
VERSION=$(python3 -c "import json; print(json.load(open('package.json'))['version'])")
if echo "$VERSION" | grep -qP '^\d+\.\d+\.\d+$'; then
VERSION=$(jq -er '.version | select(type == "string")' package.json)
if [[ "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
- name: Check version format (semver)
run: |
VERSION=$(jq -er '.version | select(type == "string")' package.json)
if [[ "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
PATCH="${VERSION##*.}"
if [ "${`#PATCH`}" -gt 9 ]; then
echo "::error::Patch component out of range: $PATCH (expected < 1000000000)"
exit 1
fi
echo "Version: $VERSION"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/meta-check.yml around lines 92 - 95, Update the “Check
version format (semver)” regex to enforce the same maximum nine-digit limit for
the PATCH component as the version-bump workflow, while preserving the existing
validation of MAJOR and MINOR and the overall semantic-version format.

echo "Version: $VERSION"
else
echo "::error::Invalid version format: $VERSION (expected semver X.Y.Z)"
Expand Down
45 changes: 31 additions & 14 deletions .github/workflows/version-bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,53 @@ jobs:
- uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: false

- name: Bump patch version in package.json
id: bump
run: |
CURRENT=$(python3 -c "import json; print(json.load(open('package.json'))['version'])")
# A file with several root values ("{...}{...}") passes `jq empty` but
# makes `jq -er .version` emit one line per document — validate the
# single-object root before anything reads or rewrites the manifest.
if ! jq -e -s 'length == 1 and (.[0] | type == "object")' package.json > /dev/null; then
echo "::error::package.json is not a single JSON object"
exit 1
fi
CURRENT=$(jq -er '.version | select(type == "string")' package.json)
if [[ ! "$CURRENT" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "::error::Invalid version format: $CURRENT (expected semver X.Y.Z)"
exit 1
fi
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
# $((PATCH + 1)) wraps silently at the 64-bit boundary (it would turn
# 9223372036854775807 into a negative patch), so bound the input
# first. Compare digit count, not value: a numeric [ -ge ] test on a
# 20-digit patch is itself an "integer expression expected" error.
if [ "${#PATCH}" -gt 9 ]; then
echo "::error::Patch component out of range: $PATCH (expected < 1000000000)"
exit 1
fi
NEW_PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
python3 -c "
import json
with open('package.json', 'r') as f:
data = json.load(f)
data['version'] = '${NEW_VERSION}'
with open('package.json', 'w') as f:
json.dump(data, f, indent=2)
f.write('\n')
"
TEMP_FILE=$(mktemp)
jq --arg version "$NEW_VERSION" '.version = $version' package.json > "$TEMP_FILE"
mv "$TEMP_FILE" package.json
echo "old=$CURRENT" >> "$GITHUB_OUTPUT"
echo "new=$NEW_VERSION" >> "$GITHUB_OUTPUT"

- name: Commit version bump
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OLD_VERSION: ${{ steps.bump.outputs.old }}
NEW_VERSION: ${{ steps.bump.outputs.new }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add package.json
if git diff --cached --quiet; then
echo "No version change"
else
git commit -m "Bump version to ${{ steps.bump.outputs.new }} [skip ci]"
git push
echo "Bumped: ${{ steps.bump.outputs.old }} → ${{ steps.bump.outputs.new }}"
git commit -m "Bump version to ${NEW_VERSION} [skip ci]"
git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:main
echo "Bumped: ${OLD_VERSION} → ${NEW_VERSION}"
fi
42 changes: 42 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ CLAUDE.md
CLAUDE.md.meta
REVIEW.md
REVIEW.md.meta
.claude/
.codex/

# Editor/workspace config — git only
.vscode/
.prettierignore

# CI/CD & GitHub
.github/
Expand All @@ -18,3 +24,39 @@ CONFLICT_CHECK_LAST_5_PRS.md
CONFLICT_CHECK_LAST_5_PRS.md.meta
.gitignore
*.bat

# Local Unity-generated files (mirrors .gitignore because npm stops using it
# whenever this file exists)
[Ll]ibrary/
[Tt]emp/
[Oo]bj/
[Bb]uild/
[Bb]uilds/
[Ll]ogs/
[Uu]ser[Ss]ettings/
*.csproj
*.sln
*.suo
*.user
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db

# Local OS and IDE files
.DS_Store
Thumbs.db
.idea/
.vs/
*.swp

# Build intermediates and temporary files
*.obj
*.lib
*.exp
test.txt
.commitmsg
commitmsg.txt
Comment thread
coderabbitai[bot] marked this conversation as resolved.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
- Repack tab: the `Advanced (debug)` section (manual texels-per-UV-unit, post-pack density correction, SymSplit threshold mode).
- Unity top menu: `Mesh Lab ▸ Export FBX Metrics (Selected Assets)` and `(Scene LODGroup)`.
- Assets ▸ Create context menu: `Mesh Lab ▸ Sweep Test Suite` (was always-visible `Lightmap UV Tool/Test Suite` via `[CreateAssetMenu]`).
- On-disk transfer-analysis artefacts: `BenchmarkRecorder.NewRun` returns a no-op scope when the toggle is off, so Run Full Pipeline / Repack / Transfer All no longer create `<projectRoot>/BenchmarkReports/` and its per-mesh `*_png/` subfolder on a production run. Existing report folders on disk are left untouched.
- **In-flight gate + `FireAndForget` helper** for fire-and-forget async UI actions. The "Run Full Pipeline" / "Run Repack only" / "Run Transfer only" / "Repack All" / "Transfer All Targets" buttons now sit inside an `EditorGUI.DisabledScope` on `_pipelineInFlight` so a second click can't launch an interleaving run; `FireAndForget` attaches `ContinueWith` on the Unity sync context to log Task faults through `UvtLog.Error`, release the gate, and call `UvProgress.Fail` so a thrown exception can't leave the strip stuck on a stale phase.
- **Cancel-aware benchmark recording** in `ExecTransferAllImpl` — mirrors the `completedSuccessfully` guard `ExecFullPipelineImpl` already uses. Cancelled transfers no longer emit stale `shellTransferResult` / validation rows that taint sweep aggregates.

Expand All @@ -37,6 +38,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
- `[CreateAssetMenu(menuName = "Lightmap UV Tool/Test Suite")]` from `TestSuiteAsset` — replaced with a gated `[MenuItem("Assets/Create/Mesh Lab/Sweep Test Suite")]`.
- Transfer tab decluttered: removed the misplaced `Generate LODs` section (full LOD-Gen UI lives in the dedicated LOD Gen tab), the `FBX Export` block (Export as New / Overwrite Source — both duplicates of the sidebar footer's `Export New FBX` / `Overwrite FBX`), and the `Save FBX from main (_main)` button (duplicate of the footer's `Backup from main`). Transfer tab now reads: per-LOD status list → `Transfer All Targets` → `Quality Report` → `Validation Overlay` → `Apply UV2` actions.

### Security
Consolidated hardening sweep — incorporates PRs #122–#123, #125–#129, #131, #134–#140, #142–#145, #147–#149, #151–#152, #156–#159, #161–#164, #168 and #170–#190.

- **CI and build tooling** — release/version workflows no longer interpolate untrusted values straight into inline Python; the native build workflow pins every action to a verified SHA and defaults to a read-only token, with `contents: write` scoped to the publish job alone, which still commits the freshly built binaries into `Plugins/` automatically on push; `gen.bat` and the skills-overhaul parameter file quote/escape their arguments; `.npmignore` mirrors `.gitignore` so ignored artifacts can't leak into a published tarball (#123, #125, #135, #136, #164).
- **Vendored xatlas** — `Native~/CMakeLists.txt` builds a pinned, vendored xatlas source tree instead of `FetchContent`-ing a moving upstream branch (#127).
- **Sidecar replay validation** — `Uv2DataAsset` payloads are now treated as untrusted input: UV channel dimensions and lengths, submesh triangle counts, collision entries, vertex-color arrays, xatlas settings and the sidecar-supplied save path are validated before use, stale/incomplete remap rebuilds abort instead of half-applying, and remap work is bounded (#126, #139, #151, #182, #185, #188, #189).
- **Resource limits** — bounded or overflow-checked work budgets across Vertex/GPU AO (bake workload, seam matching, 3D and 2D blur, face-area correction, NaN-safe atomic accumulation, deferred GPU cleanup), repack (atlas oversample sizing, pack cost, exclusive native xatlas sessions, cached area preview), grouped transfer and spatial overlap scans, SceneView hover raycasts, benchmark sweep parameters and UV PNG generation, ARAP UV ranges, FBX backup path hashing, V-HACD recursion depth and triangle indices, and the meshoptimizer packing buffers feeding the unsafe/native path (#122, #128–#129, #131, #134, #137–#138, #140, #143–#144, #147–#149, #152, #162–#163, #184, #186–#187, #190).
- **Output escaping** — a shared `CsvUtil.Escape` neutralises formula prefixes in every CSV writer (sweep summaries, FBX metrics, benchmark records) and the gallery generator escapes model names before embedding them in HTML hrefs (#157–#159, #161).

### Fixed
- **UV transfer / repack correctness** — Repack normalizes UV0 shell winding at its own API boundary instead of assuming the optional Weld stage ran; source meshes with incomplete (non-empty but short) UV0/UV2 channels are rejected before indexing; cancelled transfers drop their partial UV2 output; post-pack density correction stays inside each packed chart; `RepackUv` forwards its rotate flag to xatlas; the UV2 clamp toggle is independently editable; `Apply UV2` is reachable after a source-only repack; cross-LOD hints are isolated per mesh group (#128, #156, #170–#173, #180–#181, #183).
- **Undo and preview handling** — undo/redo restores preview-swapped meshes before rebuilding `MeshEntries` so a preview mesh can't become the cached baseline; Vertex AO no longer takes duplicate preview backups and clears stale results before loading new mesh data; the shell colour hash survives `int.MinValue`; Inverted triangles are rendered in the validation overlay instead of drawing clean or vanishing under the filter (#145, #168, #174, #176–#177).
- **LOD parsing and generation** — LOD indices parsed from object names are bounded, and LOD0's transition height is normalized before generating the remaining levels (#142, #179).
- **FBX export** — `NormalizeExportHierarchy` bakes each node's transform into a per-node mesh copy, so a shared FBX sub-asset is no longer mutated cumulatively (#175).
- **Collision meshes** — `StripCollisionMesh` calls `mesh.Clear(false)`; with the default `keepVertexLayout=true` the extra tangent/color/UV channels were retained and the strip was a no-op (#178).

## [1.0.5] - 2026-05-13

### Added
Expand Down
Loading