Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ updates:
- 'wry'
- 'tao'

# Cargo / Rust (renderer-neutral Core, independent workspace — see docs/native/CORE-MIGRATION-LEDGER.md)
- package-ecosystem: cargo
directory: /crates
schedule:
interval: weekly
day: monday
cooldown:
default-days: 7
open-pull-requests-limit: 5

# GitHub Actions
- package-ecosystem: github-actions
directory: /
Expand Down
60 changes: 52 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,14 @@ jobs:
- name: Vendor fork invariant guard
run: pnpm run verify:vendor

# QNBS-v3: osv-scanner.toml (in src-tauri/) suppresses accepted RUSTSEC advisories for Cargo.lock;
# scan both lockfiles so npm + Rust advisories are caught on every push
- name: OSV vulnerability scan
uses: google/osv-scanner-action/osv-scanner-action@8deb546fdb875b9996d27d4950be7312dac076a1 # v2.5.0
with:
scan-args: |-
--config=src-tauri/osv-scanner.toml
--lockfile=pnpm-lock.yaml
--lockfile=src-tauri/Cargo.lock
--lockfile=crates/Cargo.lock

# QNBS-v3: gitleaks scans git history for secrets; GITHUB_TOKEN is enough for PR annotations.
- name: Scan for leaked secrets (gitleaks)
Expand All @@ -97,13 +96,14 @@ jobs:
timeout-minutes: 5
outputs:
tauri: ${{ steps.filter.outputs.tauri }}
crates: ${{ steps.filter.outputs.crates }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Detect src-tauri changes
- name: Detect src-tauri / crates changes
id: filter
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
Expand All @@ -112,15 +112,22 @@ jobs:
BASE="${{ github.event.before }}"
fi
if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] || ! git cat-file -e "$BASE" 2>/dev/null; then
echo "::notice::No usable base SHA to diff against — defaulting to tauri=true (fail open)"
echo "::notice::No usable base SHA to diff against — defaulting to tauri=true, crates=true (fail open)"
echo "tauri=true" >> "$GITHUB_OUTPUT"
echo "crates=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --name-only "$BASE" "${{ github.sha }}" | grep -qE '^(src-tauri/|\.github/workflows/ci\.yml$)'; then
CHANGED=$(git diff --name-only "$BASE" "${{ github.sha }}")
if grep -qE '^(src-tauri/|\.github/workflows/ci\.yml$)' <<< "$CHANGED"; then
echo "tauri=true" >> "$GITHUB_OUTPUT"
else
echo "tauri=false" >> "$GITHUB_OUTPUT"
fi
if grep -qE '^(crates/|tests/fixtures/project-golden-masters/|\.github/workflows/ci\.yml$)' <<< "$CHANGED"; then
echo "crates=true" >> "$GITHUB_OUTPUT"
else
echo "crates=false" >> "$GITHUB_OUTPUT"
fi

# ----------------------------------------------------------
# 1. QUALITY GATE: Lint + Typecheck + Tests (parallel matrix)
Expand Down Expand Up @@ -267,6 +274,39 @@ jobs:
working-directory: src-tauri
run: cargo test --locked

# ----------------------------------------------------------
# 1c. CORE-RUST: worldscript-project crate Gate (fmt/check/clippy/test), path-scoped via `changes`
# ----------------------------------------------------------
core-rust:
Comment thread
qnbs marked this conversation as resolved.
name: 🧩 Core Rust Gate
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [security, changes]
if: needs.changes.outputs.crates == 'true'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
components: rustfmt, clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: crates -> target
- name: Rust format check
working-directory: crates
run: cargo fmt --check
- name: Rust compile check
working-directory: crates
run: cargo check --locked
- name: Rust clippy
working-directory: crates
run: cargo clippy --locked --all-targets -- -D warnings
- name: Rust tests
working-directory: crates
run: cargo test --locked

# ----------------------------------------------------------
# 2. BUILD: Production build + Pages artifact upload
# QNBS-v3: Stryker mutation removed from the PR/CI pipeline (2026-06-02) — it added noise as a
Expand Down Expand Up @@ -368,16 +408,16 @@ jobs:
path: ./dist

# ----------------------------------------------------------
# 3. CI SUCCESS: single required-status aggregator (security + quality + changes + rust-tauri + build + e2e + vrt)
# 3. CI SUCCESS: single required-status aggregator
# (security + quality + changes + rust-tauri + core-rust + build + e2e + vrt)
# ----------------------------------------------------------
ci-success:
name: ✅ CI Success
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [security, quality, changes, rust-tauri, build, e2e, vrt]
needs: [security, quality, changes, rust-tauri, core-rust, build, e2e, vrt]
if: always()
steps:
# QNBS-v3: rust-tauri may legitimately be 'skipped' (changes.outputs.tauri == 'false') — that's a pass, not a failure
- name: Verify all required jobs succeeded
run: |
FAIL=0
Expand All @@ -387,6 +427,9 @@ jobs:
if [ "${{ needs.rust-tauri.result }}" != "success" ] && [ "${{ needs.rust-tauri.result }}" != "skipped" ]; then
FAIL=1
fi
if [ "${{ needs.core-rust.result }}" != "success" ] && [ "${{ needs.core-rust.result }}" != "skipped" ]; then
FAIL=1
fi
[ "${{ needs.build.result }}" = "success" ] || FAIL=1
[ "${{ needs.e2e.result }}" = "success" ] || FAIL=1
[ "${{ needs.vrt.result }}" = "success" ] || FAIL=1
Expand All @@ -396,6 +439,7 @@ jobs:
echo " quality: ${{ needs.quality.result }}"
echo " changes: ${{ needs.changes.result }}"
echo " rust-tauri: ${{ needs.rust-tauri.result }} (skipped = OK, src-tauri untouched)"
echo " core-rust: ${{ needs.core-rust.result }} (skipped = OK, crates/ untouched)"
echo " build: ${{ needs.build.result }}"
echo " e2e: ${{ needs.e2e.result }}"
echo " vrt: ${{ needs.vrt.result }}"
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ tests/bench/baseline/

# Tauri build output
src-tauri/target/

# crates/ (Wave 2 Rust Core workspace) build output
crates/target/
.env

test-results/
Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,9 @@ On any non-trivial code change add a single-line comment explaining **why**, not
| CMake (`CMakeLists.txt`) | `# QNBS-v3: <reason / impact>` |
| Pure config (JSON, YAML, TOML — e.g. `package.json`, workflow `.yml`, `Cargo.toml`) | No inline comment — explain in the commit message |

**Hard rule — one physical line, never wrapped, in every syntax above:** a `QNBS-v3: …` comment MUST fit on a single physical line, however long, regardless of which comment syntax (`//`, `#`, `/* */`) the language uses. Never split it across two lines (`// QNBS-v3: foo\n// bar`) — CodeRabbit and Qodo both flag this as a nitpick on every PR that does it, and it has recurred across TS/JS, YAML, and C++ files in this repo's history. If the reason doesn't fit on one line, shorten it; don't wrap it. This applies the first time a new language/file type is touched too — don't wait for a review bot to point out that the language wasn't in the table yet before applying the same one-line discipline.
**Hard rule — one physical line, never wrapped, in every syntax above:** a `QNBS-v3: …` comment MUST fit on a single physical line, however long, regardless of which comment syntax (`//`, `#`, `/* */`) the language uses. Never split it across two lines (`// QNBS-v3: foo\n// bar`) — CodeRabbit, Qodo, and chatgpt-codex-connector all flag this as a nitpick on every PR that does it, and it has recurred across TS/JS, YAML, Rust, and C++ files in this repo's history — including cases where the agent wrote the violation itself in the same PR that documents the rule. If the reason doesn't fit on one line, shorten it; don't wrap it. This applies the first time a new language/file type is touched too — don't wait for a review bot to point out that the language wasn't in the table yet before applying the same one-line discipline.

**Mandatory self-check before every commit that adds or edits a `QNBS-v3` comment:** run `git diff --cached -- '*.ts' '*.tsx' '*.js' '*.mjs' '*.css' '*.rs' '*.cpp' '*.yml' '*.yaml' | grep -A1 "QNBS-v3"` (or equivalent for the touched paths) and confirm every matched `QNBS-v3:` line is followed by a line that does NOT start with the same comment token continuing the sentence (i.e., the next line is blank, unrelated code, or a new comment). Do this even when the comment "looks short enough" — the violations in this repo's history were all cases the author believed fit, not deliberate multi-line comments. Treat a caught violation here as a required fix before committing, not an optional cleanup.

Skip for pure formatting, lockfile updates, or generated artefacts.

Expand Down
2 changes: 2 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"!!**/.storybook",
"!!**/backend",
"!!**/src-tauri",
"!!**/crates",
"!!**/tests/fixtures/project-golden-masters",
"!!**/public/sw.js",
"!!**/coverage",
"!!**/playwright-report",
Expand Down
107 changes: 107 additions & 0 deletions crates/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[workspace]
Comment thread
qnbs marked this conversation as resolved.
resolver = "2"
members = ["worldscript-project"]
18 changes: 18 additions & 0 deletions crates/worldscript-project/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "worldscript-project"
version = "0.1.0"
edition = "2021"
rust-version = "1.77.2"
description = "Renderer-neutral WorldScript Studio project schema, validation, migration, and I/O (Wave 2)"
publish = false

[lib]
name = "worldscript_project"

[[bin]]
name = "wsproj"
path = "src/bin/wsproj.rs"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Loading
Loading