Skip to content

ci: project-health gates and release repackage#112

Merged
danko-nobre merged 7 commits into
mainfrom
feat/spec-035-project-health
Jun 12, 2026
Merged

ci: project-health gates and release repackage#112
danko-nobre merged 7 commits into
mainfrom
feat/spec-035-project-health

Conversation

@danko-nobre

@danko-nobre danko-nobre commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Spec 035 (project health and shipping), first PR: the five cheap, high-value toolchain and shipping items. The sixth now-item, the end-to-end mixed-feature fixture, is split into a focused follow-up PR.

What

  • Release repackage (release.yml): a photoshop-v* tag failed deterministically - the job copied the retired proscenio_export.jsx. It now builds the UXP plugin (dist/ is gitignored) and zips its contents with manifest.json at the archive root. Adds a workflow_dispatch dry-run that packages without uploading a release.
  • ESLint gate: the strictTypeChecked + stylisticTypeChecked config was decorative (CI ran only tsc + vitest). Wires pnpm run lint into lint-photoshop plus a pre-commit hook - the no-unsafe-* family now guards the untyped UXP host boundary. The src tree already passes clean.
  • models/codegen mypy gate: the schema source of truth and the binding emitter were the only ungated Python. Strict-strict [tool.mypy] blocks + CI steps + pre-commit hooks. disallow_any_explicit is dropped from this profile (pydantic's plugin-synthesized methods carry explicit Any on every model); the rest of the strict profile stays. The gate caught one real bug - a set literal joining to the wrong per-module _Strict base in the union dispatcher resolver.
  • Saved-scene assert (test_importer.gd): packs each built character, saves, reloads, and asserts every node's get_script() == null - the shipped .scn must carry no editor-only addon script refs. Runs for all four fixtures in the existing headless pass.
  • Fixture relpath: slot_cycle and simple_psd were the only image-loading fixtures whose build scripts lacked the explicit bpy.path.relpath rewrite (with the Windows cross-drive guard). The committed .blends already store relative // paths (save_as_mainfile remaps by default), so no re-bake was committed - the change makes the guarantee explicit for the next regeneration.

Verification

  • All pre-commit gates green per commit; the three pre-existing dirty working-tree files kept out of every commit.
  • Release workflow_dispatch dry-run exercised for all three components: photoshop zip carries manifest.json at the root, blender the addon tree, godot addons/proscenio/.
  • Local run_tests.py re-export stays green after the fixture script change; test_importer.gd passes 65 assertions (the saved-scene guard was verified to fail on an injected script, then reverted).

Follow-up

The mixed-feature fixture (skinned body + sprite_frame + slot + atlas + drive-from-bone + animation, baked Blender-to-Godot golden) lands in its own PR. Its precondition is met: the export-correctness writer fixes (picker-first find_armature, MeshElement.polygons) are already in code.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Enhanced automated validation for Godot scene exports to ensure saved scenes contain no bundled addon scripts.
  • Chores

    • Improved CI/CD release flow with a tagless dry-run path, built+zipped Photoshop artifacts, and gated upload on tag pushes.
    • Added stricter type-checking and local CI hooks, plus an ESLint gate for the Photoshop code.
    • Made fixture generation more portable by normalizing image paths and re-saving blends.
  • Documentation

    • Updated project health TODO to reflect completed items above.

danko-nobre and others added 6 commits June 11, 2026 20:45
A `photoshop-v*` tag failed deterministically: the job copied
`apps/photoshop/proscenio_export.jsx`, removed in the UXP migration.
Build the plugin (`dist/` is gitignored) and zip its contents so
manifest.json sits at the archive root the host loads from.

Add a workflow_dispatch dry-run that packages without uploading a
release - the stale line rotted unnoticed because the job only ran
on tags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lint-photoshop job ran only tsc plus vitest, so the no-unsafe-*
family - the any-flow guard at the untyped UXP host boundary that tsc
cannot see - was decorative. Wire `pnpm run lint` into CI and mirror it
as a pre-commit hook. The src tree already passes clean, so the gate
lands green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
proscenio-models is the schema source of truth and proscenio-codegen
emits every downstream binding from it, yet both sat outside the type
gate. Add strict-strict [tool.mypy] blocks (3.11 floor, the package's
real runtime) plus CI steps and pre-commit hooks.

disallow_any_explicit is dropped from this profile: pydantic's plugin
synthesizes model methods that carry explicit Any, so the flag fires on
the model definitions themselves, not on author looseness. The gate
caught one real bug - a set literal joining to the wrong per-module
_Strict base in the union dispatcher resolver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The plugin is editor-only; a consumer runs the imported character with it
disabled. If a builder ever attaches an addon script to an output node,
the packed .scn bakes that script path and the scene breaks at load.

Mirror importer.gd's owner-set + pack + ResourceSaver.save, reload from
disk, and walk every node asserting get_script() == null - across all
four fixture documents in the existing headless pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
slot_cycle and simple_psd were the only image-loading fixtures whose
build scripts lacked the explicit bpy.path.relpath rewrite that
blink_eyes / automesh / slot_swap already carry. Add it (with the
Windows cross-drive ValueError guard) so a regeneration cannot bake a
machine-absolute path into the committed .blend.

The committed .blends already store relative // paths: save_as_mainfile
remaps relative by default, verified via image.filepath_raw, and the
full run_tests.py re-export stays green. So no rebake was needed - the
script change makes the guarantee explicit for the next regeneration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Five of the six now-items landed this PR: the release UXP repackage,
the eslint gate, the models/codegen mypy gate, the saved-scene assert,
and the fixture relpath hardening. The mixed-feature fixture is carried
to a focused follow-up PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 115da076-39e6-4240-8320-592052cf5b02

📥 Commits

Reviewing files that changed from the base of the PR and between b0ca1fa and 5594c4a.

📒 Files selected for processing (1)
  • .github/workflows/release.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/release.yml

📝 Walkthrough

Walkthrough

PR implements six interconnected improvements for Spec 035 project health: strict mypy type checking for Python packages with CI and pre-commit integration, ESLint linting gate for Photoshop, enhanced release workflow supporting manual dry-run dispatch and improved Photoshop packaging, Godot scene save validation to guard against baked addon scripts, Blender fixture image path rewriting for cross-platform portability, and documentation updates.

Changes

Spec 035 Project Health Checkpoints

Layer / File(s) Summary
Python strict type checking configuration and CI integration
packages/models/pyproject.toml, packages/codegen/pyproject.toml, packages/codegen/src/proscenio_codegen/godot_emit.py, .github/workflows/ci.yml, .pre-commit-config.yaml
packages/models and packages/codegen now have strict mypy configurations targeting Python 3.11 with the pydantic plugin and strict warning flags. godot_emit.py is updated with explicit type annotations (set[type[BaseModel]]) to pass strict checks. CI workflow and pre-commit hooks are wired to run these mypy validations on models and codegen packages independently.
Photoshop ESLint linting gate wiring
.github/workflows/ci.yml, .pre-commit-config.yaml
CI workflow lint-photoshop job adds an explicit pnpm run lint step. A new local pre-commit hook eslint-photoshop runs the Photoshop app's linting on TypeScript/TSX files under apps/photoshop/src without passing individual filenames.
Release workflow dry-run and packaging improvements
.github/workflows/release.yml
Release workflow gains a workflow_dispatch trigger with component and version inputs, enabling manual tagless dry-run execution. Metadata extraction now branches between dispatch inputs and tag-based GITHUB_REF parsing. Photoshop packaging is reworked to archive the entire built apps/photoshop/dist folder into a single versioned zip file, with a verification step listing archive contents. GitHub release upload is gated to tag pushes only (not manual dispatch).
Godot scene save validation helpers and fixture integration
apps/godot/tests/test_importer.gd
New helpers _assert_saved_scene_has_no_scripts, _collect_scripted_nodes, and _set_owner_recursive validate that saved .scn files contain no baked addon script references. The validation is integrated into all four fixture test flows (dummy, effect, skinned, slots) by calling the assertion after existing checks, ensuring importer output is clean of addon scripts.
Blender fixture image path rewriting for portability
packages/fixtures/simple_psd/build_blend.py, packages/fixtures/slot_cycle/build_blend.py
Both fixture build scripts now include a _rewrite_images_to_relpath() helper that converts absolute image filepaths to //-relative paths using bpy.path.relpath. Cross-platform drive mismatches are handled by keeping the original absolute path and logging to stderr. The rewrite is called after the initial save, followed by a second save to persist the relative paths in the committed .blend file.
Spec 035 project health documentation update
specs/035-project-health/TODO.md
Six deliverable items in the "Now" section are marked as completed: Photoshop release job rework with tagless dry-run support and artifact verification; ESLint CI gate and pre-commit hook; strict mypy configuration for models/codegen with CI and pre-commit integration; Godot saved-scene validation; fixture image path portability; and deferral of the end-to-end mixed-feature fixture to a follow-up PR.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • firebound/proscenio#45: Introduces the lint-photoshop CI job; this PR extends that job with an explicit linting step.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the two primary changes: CI gates (linting, mypy checks) and release workflow repackaging of the Photoshop artifact.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/spec-035-project-health
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/spec-035-project-health

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
.github/workflows/release.yml (1)

54-54: ⚖️ Poor tradeoff

Consider pinning actions to commit SHAs for supply chain security.

Using version tags (@v4, @v2) instead of commit SHAs makes the workflow vulnerable to tag-moving attacks. This is a broader pattern across the workflow file (lines 34, 54, 59, 98).

Pinning to SHAs with version comments improves supply chain security and follows GitHub's recommended practices.

Example SHA-pinned format
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2

Also applies to: 59-59, 98-98

🤖 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/release.yml at line 54, The workflow pins third-party
actions by mutable tags (e.g., "uses: actions/setup-node@v4") which is
susceptible to tag-moving attacks; replace each occurrence of a tag-pinned
action with the corresponding commit SHA (for example "uses:
actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8") and add a trailing
comment with the human-friendly tag (e.g., "# v4.0.2"); apply this change to all
"uses:" entries currently using tags (including the shown actions/setup-node@v4
and the other tag-pinned actions referenced in the file) so every action is
pinned to a specific commit SHA and annotated with the version comment.

Source: Linters/SAST tools

packages/fixtures/simple_psd/build_blend.py (1)

103-123: ⚡ Quick win

Extract _rewrite_images_to_relpath() to a shared fixture utility module.

The helper in packages/fixtures/simple_psd/build_blend.py and packages/fixtures/slot_cycle/build_blend.py duplicates identical logic already present in packages/fixtures/automesh/build_blend.py and packages/fixtures/blink_eyes/build_blend.py. The only variation is the log prefix string. Consolidating into a shared module (e.g., packages/fixtures/_shared/blend_utils.py) would establish a single source of truth for the relpath rewrite pattern and simplify future changes to error handling or logging.

🤖 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 `@packages/fixtures/simple_psd/build_blend.py` around lines 103 - 123,
Duplicate helper _rewrite_images_to_relpath is defined in multiple fixture
build_blend files; extract it into a shared fixture utility module (e.g.,
_shared/blend_utils.py) as a single function (e.g., rewrite_images_to_relpath)
that accepts a log_prefix or logger argument to preserve per-fixture prefix
differences, move the implementation (including the bpy.path.relpath try/except
and stderr print) into that module, and update the existing callers (functions
named _rewrite_images_to_relpath in the various build_blend.py files) to import
and call the shared rewrite_images_to_relpath with their specific prefix so
behavior and logging remain identical but centralized.
🤖 Prompt for all review comments with 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.

Inline comments:
In @.github/workflows/release.yml:
- Around line 39-48: The current shell directly expands inputs.component and
inputs.version into the script which allows template/shell injection; instead
read the raw inputs into safe intermediate variables and emit them to
GITHUB_OUTPUT using a safe, non-evaluating write (e.g., assign to local vars
like component_raw/version_raw and use printf '%s' or GitHub safe write
semantics to append to GITHUB_OUTPUT), and when parsing tags keep the
tag/GITHUB_REF handling unchanged but operate on a copied variable (tag_raw) to
avoid accidental evaluation; update references to component and version to use
these sanitized intermediate variables before echoing to GITHUB_OUTPUT.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Line 54: The workflow pins third-party actions by mutable tags (e.g., "uses:
actions/setup-node@v4") which is susceptible to tag-moving attacks; replace each
occurrence of a tag-pinned action with the corresponding commit SHA (for example
"uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8") and add a
trailing comment with the human-friendly tag (e.g., "# v4.0.2"); apply this
change to all "uses:" entries currently using tags (including the shown
actions/setup-node@v4 and the other tag-pinned actions referenced in the file)
so every action is pinned to a specific commit SHA and annotated with the
version comment.

In `@packages/fixtures/simple_psd/build_blend.py`:
- Around line 103-123: Duplicate helper _rewrite_images_to_relpath is defined in
multiple fixture build_blend files; extract it into a shared fixture utility
module (e.g., _shared/blend_utils.py) as a single function (e.g.,
rewrite_images_to_relpath) that accepts a log_prefix or logger argument to
preserve per-fixture prefix differences, move the implementation (including the
bpy.path.relpath try/except and stderr print) into that module, and update the
existing callers (functions named _rewrite_images_to_relpath in the various
build_blend.py files) to import and call the shared rewrite_images_to_relpath
with their specific prefix so behavior and logging remain identical but
centralized.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6aacf9f6-ff68-4d54-a899-7dd29d2c6df3

📥 Commits

Reviewing files that changed from the base of the PR and between 917f80d and b0ca1fa.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .pre-commit-config.yaml
  • apps/godot/tests/test_importer.gd
  • packages/codegen/pyproject.toml
  • packages/codegen/src/proscenio_codegen/godot_emit.py
  • packages/fixtures/simple_psd/build_blend.py
  • packages/fixtures/slot_cycle/build_blend.py
  • packages/models/pyproject.toml
  • specs/035-project-health/TODO.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
apps/godot/**/*.{gd,gdc,gdns,gdst,tscn,scn}

📄 CodeRabbit inference engine (AGENTS.md)

The Godot plugin runs only at editor import time. Generated scenes must use only built-in nodes (Skeleton2D, Bone2D, Polygon2D, Sprite2D, Node2D, AnimationPlayer, AnimationLibrary). No GDExtension or native runtime

Files:

  • apps/godot/tests/test_importer.gd
🪛 LanguageTool
specs/035-project-health/TODO.md

[uncategorized] ~9-~9: The official name of this software platform is spelled with a capital “H”.
Context: ...ce the stale .jsx copy in release.yml with a UXP build...

(GITHUB)


[uncategorized] ~16-~16: The official name of this software platform is spelled with a capital “H”.
Context: ...p to the lint-photoshop job in ci.yml. - [x] Mirror it as a...

(GITHUB)

🪛 zizmor (1.25.2)
.github/workflows/release.yml

[error] 41-41: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 54-54: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 59-59: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 54-54: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)


[info] 83-83: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 86-86: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 98-98: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[info] 98-98: action functionality is already included by the runner (superfluous-actions): use gh release in a script step

(superfluous-actions)

🔇 Additional comments (25)
specs/035-project-health/TODO.md (6)

9-11: LGTM!


15-17: LGTM!


21-23: LGTM!


27-28: LGTM!


32-33: LGTM!


37-37: LGTM!

packages/models/pyproject.toml (1)

18-43: LGTM!

packages/codegen/pyproject.toml (1)

22-44: LGTM!

packages/codegen/src/proscenio_codegen/godot_emit.py (1)

124-133: LGTM!

.github/workflows/ci.yml (1)

31-34: LGTM!

Also applies to: 54-56

.pre-commit-config.yaml (1)

52-79: LGTM!

.github/workflows/release.yml (5)

9-25: LGTM!


52-67: LGTM!


80-83: Packaging logic correctly implements the manifest-at-root requirement.

The workflow archives apps/photoshop/dist contents (where webpack places manifest.json after build) and preserves the flat structure required by UDT's plugin loader.


91-96: LGTM!


98-99: LGTM!

packages/fixtures/simple_psd/build_blend.py (1)

48-49: LGTM!

packages/fixtures/slot_cycle/build_blend.py (1)

57-58: LGTM!

apps/godot/tests/test_importer.gd (7)

80-80: LGTM!


128-129: LGTM!


170-171: LGTM!


246-247: LGTM!


330-362: LGTM!


364-371: LGTM!


373-378: LGTM!

Comment thread .github/workflows/release.yml
CodeRabbit/zizmor flagged a template-injection vector: the free-text
`inputs.version` expanded straight into the meta step's shell body.
Route the dispatch inputs and the derived component/version through env
vars in the meta and build steps so no attacker-influenceable value
reaches the script as `${{ }}` text.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danko-nobre danko-nobre merged commit 34aec4a into main Jun 12, 2026
10 checks passed
@danko-nobre danko-nobre deleted the feat/spec-035-project-health branch June 12, 2026 00:42
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.

1 participant