Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
f79feb4
Add MacOS Finder Badge
manuelgruber Apr 6, 2026
9db859f
Bump softprops/action-gh-release to v3 for Node 24 runtime
manuelgruber Apr 19, 2026
566a52e
Fix TUI sync hang by timing out gh calls and offloading auth to block…
manuelgruber Apr 19, 2026
ab05538
Add timeouts to SSH probe and pagination
manuelgruber Apr 19, 2026
465e44c
Extract RepoScanService to separate backend API from frontend consumers
manuelgruber Apr 19, 2026
5b20203
Harden daemon shutdown and scaffold Xcode project for Finder badge te…
manuelgruber Apr 19, 2026
d4fc443
Harden FinderSync extension sandbox and drawing for macOS 26 testing
manuelgruber Apr 19, 2026
7403ae7
Switch macOS Xcode project to in-repo source of truth, drop XcodeGen …
manuelgruber Apr 19, 2026
e51dcd6
Trim FinderSync debug logging to lifecycle events
manuelgruber Apr 19, 2026
3ab89f8
Group Finder context menu under one Git-Same submenu to reduce clutter
manuelgruber Apr 19, 2026
e78d693
Enrich Git-Same context menu with org aggregates and last-scan info
manuelgruber Apr 19, 2026
f4b7c2b
Group Git-Same menu data into Organization, Repositories, and details
manuelgruber Apr 20, 2026
e8618de
Classify owner folders as user/org via GitHub API for distinct badges
manuelgruber Apr 20, 2026
ddabff8
Add gray ambient badge case to Swift and ambient-repo tests
manuelgruber Apr 20, 2026
50b4a78
Skip grey R placeholder for O and U badges to avoid letter swap on paint
manuelgruber Apr 20, 2026
663fa90
Split menu into Organization+Workspace and adopt sentence-style Repos…
manuelgruber Apr 20, 2026
4445f0f
Prefill Finder badge cache from status.json, drop grey to color timer
manuelgruber Apr 20, 2026
f0247bc
Fix grey R badge painted on every folder under monitored root
manuelgruber Apr 20, 2026
6335f52
Update run.sh cheat-sheet with daemon, scan, and TUI commands
manuelgruber Apr 20, 2026
b7f7b1a
Nudge Finder daemon after sync so new clones get their repo badge imm…
manuelgruber Apr 22, 2026
b6c1e55
Seed StatusReader on first successful open so badges show without a s…
manuelgruber Apr 22, 2026
515b243
Nudge daemon after reset and add gisa refresh for manual badge resync
manuelgruber Apr 22, 2026
cbb5398
Add nonconcurrent Conductor run mode to prevent install races
manuelgruber May 6, 2026
7fb0c82
Bump version to 3.1.0 for next release
manuelgruber May 7, 2026
e33fc5d
Surface unreadable repos in status and stop calling [gone] branches s…
manuelgruber May 7, 2026
6364c19
Extract RepoEntry and SyncHistoryEntry to types/ to break core to TUI…
manuelgruber May 7, 2026
3afa34e
Rewrite intra-TUI imports to source RepoEntry and SyncHistoryEntry fr…
manuelgruber May 7, 2026
5ef18ff
Drop lib-level cfg gate from pub mod setup
manuelgruber May 7, 2026
2607e6e
Apply cargo fmt to reorder imports introduced in B0.2
manuelgruber May 7, 2026
ac8bebd
Split into Cargo workspace: git-same-core (engine) + git-same-cli (bi…
manuelgruber May 7, 2026
fbcd478
Update CI workflows and toolkit scripts for the workspace layout
manuelgruber May 7, 2026
f35cdf4
Update docs and CLAUDE.md to reflect the workspace split
manuelgruber May 7, 2026
86257ae
Rename CLI crate's package back to git-same to preserve cargo install…
manuelgruber May 7, 2026
4c13543
Move macOS IPC to app-group container and rename badges extension
manuelgruber May 7, 2026
6d24db3
Rename SwiftUI host target from GitSameBadge to GitSameSwiftApp
manuelgruber May 7, 2026
7fd2ae0
Move macOS IPC to app-group container and rename badges extension
manuelgruber May 7, 2026
e1a9eae
Add Tauri macOS app pipeline to ship GUI and Finder badges
manuelgruber May 7, 2026
88a7539
Document Tauri app dev setup and ignore Tauri build artifacts
manuelgruber May 7, 2026
9665be8
Boot Tauri app from Conductor setup/run scripts to cover full stack
manuelgruber May 7, 2026
9aae4f2
Split Tauri app UI into routes and shared store for maintainability
manuelgruber May 8, 2026
7d3727a
Fix Tauri dev launch by cd-ing into app dir instead of using cargo-on…
manuelgruber May 8, 2026
89d8c52
Wire D-App + D-Finder to ship together in 3.1.0 with entitlement pari…
manuelgruber May 8, 2026
994e499
Capitalize Git-Same brand string in Tauri app for consistent product …
manuelgruber May 8, 2026
bf85744
Add Tauri extension status command and first-launch banners for Finde…
manuelgruber May 8, 2026
4f0097c
Ignore non-macOS Tauri icon CLI output to keep working tree clean
manuelgruber May 8, 2026
01a6ea0
Regenerate Tauri app icons in 8-bit RGBA so the native window can launch
manuelgruber May 8, 2026
139fac4
Grant core:event capability to fix blocked listen() in Tauri app
manuelgruber May 8, 2026
e168a25
Rename daemon to monitor and lift run-loop into core
manuelgruber May 8, 2026
15e9ec6
Capitalize Git-Same in display strings, set cask desc, document monitor
manuelgruber May 8, 2026
6e61f88
Rename FinderSync principal class to Principal
manuelgruber May 8, 2026
91ef312
Rename macOS app bundle to Git-Same.app for release consistency
manuelgruber May 8, 2026
89ed58f
Fix Tauri status fallback so dashboard works without monitor
manuelgruber May 8, 2026
fabb5ce
Add shared progress and setup core for GUI reuse
manuelgruber May 8, 2026
804b641
Expand Tauri UI to manage workspaces, badges, and config
manuelgruber May 8, 2026
f92f177
Fix Tauri dev server port collision to prevent wrong UI loading
manuelgruber May 8, 2026
0d3ca6b
Fix Conductor Tauri launch to use workspace dev port
manuelgruber May 8, 2026
29bea06
Add monitor LaunchAgent repair actions to restore Finder badges
manuelgruber May 8, 2026
6196a7f
Simplify brand logo to gradient wordmark in Tauri app UI
manuelgruber May 8, 2026
c6a3289
Pin sidebar so only main content scrolls in Tauri app
manuelgruber May 8, 2026
3c87728
Pair GitHub and local repos in one table to align matching rows
manuelgruber May 8, 2026
2187360
Speed up dev loop by switching run.sh to incremental debug build
manuelgruber May 8, 2026
6c7ca26
Add monitor fullscan interval setting to Tauri Settings UI
manuelgruber May 8, 2026
0218a90
Stop Tauri app from triggering protected-folder TCC prompts
manuelgruber May 8, 2026
ceb37ec
Disable show_ambient by default to fix Finder badges on default install
manuelgruber May 10, 2026
6e9d19a
Bump pnpm to 11.0.9 in Tauri UI to track upstream patch release
manuelgruber May 11, 2026
d6a6386
Remove unused default_true helper to silence dead_code warning
manuelgruber May 11, 2026
4792e24
Watch boot-volume alias paths so Finder requests badges through them
manuelgruber May 11, 2026
c2bf378
Update Tauri UI deps to latest in-range versions
manuelgruber May 11, 2026
cd120d9
Render FinderSync badges via SF Symbols to bypass macOS 26.4 bug
manuelgruber May 11, 2026
e045fec
Tune Finder badge visuals: white-on-color square, blue O/U, 20% darker
manuelgruber May 11, 2026
ea51878
Replace app icon with twin-tiles design and add regen tooling
manuelgruber May 11, 2026
d69cc00
Restyle app icon as Liquid Glass with Finder Badge palette
manuelgruber May 11, 2026
4806524
Shorten BrandLogo subhead so it fits below the sidebar banner
manuelgruber May 11, 2026
51e6703
Reword BrandLogo subhead and center it below the banner
manuelgruber May 11, 2026
d04b17b
Paint workspace roots with Git-Same folder icon for Finder branding
manuelgruber May 11, 2026
171311a
Wire workspace folder icon hooks into Tauri commands and add tests
manuelgruber May 11, 2026
55fde59
Sign monitor helper with app-group entitlement to stop TCC popups
manuelgruber May 13, 2026
49b12d3
Update Cargo
manuelgruber May 14, 2026
20442a5
Update app dependencies and isolate refresh socket tests to keep chec…
manuelgruber May 14, 2026
880cffa
Make Finder badge extension container-only to stop TCC popups
manuelgruber May 31, 2026
dabe7f0
Set package authors to Manuel Gruber for crate metadata
manuelgruber May 31, 2026
7bc010f
Fix bash 3.2 empty-array crash that skipped app signing
manuelgruber May 31, 2026
17b522e
Fix stale Workspace source label and polish paired-table rows
manuelgruber May 31, 2026
f42618b
Update
manuelgruber May 31, 2026
c8f0cdf
Add filter, match-status gutter, and zebra rows to workspace repo table
manuelgruber Jun 11, 2026
fe7ddd7
Bump codecov-action from v6 to v7 to stay on the supported release line
manuelgruber Jun 12, 2026
6a51a85
Update dependencies to latest, migrating notify 8 and objc2 0.6 APIs
manuelgruber Jun 18, 2026
30e1e24
Update remaining dependency patches to keep lockfiles current
manuelgruber Jun 20, 2026
d9cdd2d
Resolve PR #16 review comments: mutex poisoning, socket framing, GHE …
manuelgruber Jun 21, 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
90 changes: 66 additions & 24 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,45 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Build & Test Commands

```bash
cargo build # Debug build
cargo build --release # Optimized release build (LTO, stripped)
cargo test --all-features -- --test-threads=1 # Run CI-equivalent tests
cargo test <test_name> # Run a single test by name
cargo test --test integration_test # Run only integration tests
cargo fmt --all -- --check # Check formatting
cargo clippy --all-targets --all-features -- -D warnings # Lint (zero warnings enforced)
cargo build --workspace # Debug build
cargo build --release --workspace # Optimized release (LTO, stripped)
cargo test --workspace # Run all tests
cargo test -p git-same-core # Tests for the engine crate only
cargo test -p git-same # Tests for the CLI crate only
cargo test --workspace <test_name> # Run a single test by name
cargo test -p git-same --test integration_test # Run only integration tests
cargo fmt --all -- --check # Check formatting
cargo clippy --workspace --all-targets --all-features -- -D warnings # Lint
```

Logging is controlled via `GISA_LOG` env var (e.g., `GISA_LOG=debug cargo run -- sync`).
Logging is controlled via `GISA_LOG` env var (e.g., `GISA_LOG=debug cargo run -p git-same -- sync`).

## Architecture

Git-Same is a Rust CLI + TUI tool that discovers GitHub org/repo structures and mirrors them locally with parallel cloning and syncing.

**Binary aliases:** Cargo defines the primary `git-same` binary at `src/main.rs`. Installers create `gitsame`, `gitsa`, and `gisa` symlinks from `toolkit/packaging/binary-aliases.txt`.
**Workspace layout:** the project is a Cargo workspace with two member crates:

- `git-same-core` (`crates/git-same-core/`) — the engine library. No UI dependencies (no clap, ratatui, crossterm). Holds discovery, clone/sync, IPC, status scanning, and shared types.
- `git-same` (lives at `crates/git-same-cli/` on disk; the directory name and package name intentionally diverge so `cargo install git-same` keeps working as it has since pre-3.x) — the CLI binary + TUI. Depends on `git-same-core`. Owns clap parsing, the TUI screens, the setup wizard, and command handlers. The produced binary is named `git-same` (per `[[bin]]` name) so installer aliases (`gisa`, `gitsa`, `gitsame`) and `target/release/git-same` are unchanged from the pre-split layout.

**Binary aliases:** `git-same`, `gitsame`, `gitsa`, `gisa` — all resolve to the binary built from `crates/git-same-cli/src/main.rs`.

**Dual mode:** Running with a subcommand (`gisa sync`) uses the CLI path. Running without a subcommand (`gisa`) launches the interactive TUI.

**CLI flow:** CLI parsing (`src/cli.rs`) → `main.rs` routes to command handler → handler orchestrates modules.
**macOS host strategy:** Two macOS host apps coexist. The Tauri host (`crates/git-same-app/`, Svelte + TypeScript + Vite) is the primary GUI shipped via the cask. The SwiftUI host (`macos/GitSameSwiftApp/`) is intentionally kept as a fallback per the Phase C plan's "perfect macOS feel" escape hatch (`.context/plans/phase-c-tauri-app.md` §2). **Do not delete `macos/GitSameSwiftApp/` without explicit approval** — this overrides the migration plan's earlier "delete in Phase C" instruction.

**Commands:** `init`, `setup`, `sync`, `status`, `scan`, `workspace {list,default}`, `reset`.
**CLI flow:** CLI parsing (`crates/git-same-cli/src/cli.rs`) → `main.rs` routes to command handler → handler orchestrates engine modules from `git-same-core`.

### Core modules
**Commands:** `init`, `setup`, `sync`, `status`, `scan`, `workspace {list,default}`, `reset`, `monitor` (alias: `daemon`), `refresh`.

**Why `monitor` is a CLI subcommand and not solely a Tauri-host responsibility:** the LaunchAgent invokes `gisa monitor --foreground`, non-cask installs (`cargo install`, the homebrew formula) ship only the binary, `--status` / `--stop` are the supported debugging surface, and a future Linux file-manager extension would talk to the same `gisa monitor` over the same Unix socket. The CLI handler is a thin shim (~140 lines); the loop itself lives in `git-same-core::monitor`.

### Engine modules (`crates/git-same-core/src/`)

- **`app/`** — Top-level entry points: `app/cli/` runs the CLI subcommand path, `app/tui/` boots the interactive TUI. `main.rs` dispatches to one or the other based on whether a subcommand was given
- **`commands/`** — Per-subcommand handlers (`init`, `setup`, `sync_cmd`, `status`, `scan`, `reset`, `workspace`) plus shared `support/` helpers
- **`workflows/`** — Cross-cutting orchestration shared by CLI and TUI: `sync_workspace` (discover + clone + fetch/pull) and `status_scan` (walk local repos, collect git status)
- **`auth/`** — `gh_cli.rs` obtains GitHub API tokens via `gh auth token`. `ssh.rs` exposes low-level SSH probing primitives (`SshProbeResult`, `parse_ssh_probe_output`) used by clone-time diagnostics
- **`workflows/`** — Cross-cutting orchestration: `sync_workspace` (discover + clone + fetch/pull) and `status_scan` (walk local repos, collect git status)
- **`monitor/`**: Long-running monitor loop (periodic scan + Unix-socket server) used by `gisa monitor` and reusable by host apps like the Tauri GUI
- **`config/`** — TOML config parser. Default: `~/.config/git-same/config.toml`. Top-level keys: `workspaces`, `default_workspace`, plus `[clone]` and `[filters]` sections
- **`discovery.rs`** — `DiscoveryOrchestrator` coordinates repo discovery via providers, applies filters, builds `ActionPlan` (what to clone vs sync)
- **`operations/clone.rs`** — `CloneManager` handles concurrent cloning (configurable 1–32, default 4)
Expand All @@ -45,14 +55,24 @@ Git-Same is a Rust CLI + TUI tool that discovers GitHub org/repo structures and
- **`cache/`** — `discovery.rs` provides `DiscoveryCache` (TTL-based validity, persisted at `<workspace-root>/.git-same/cache.json`); `sync_history.rs` records sync runs at `<workspace-root>/.git-same/sync-history.json`
- **`domain/`** — Domain primitives, currently `repo_path_template.rs` for resolving `{org}/{repo}` style structures
- **`infra/storage/`** — Storage abstractions for workspace-local persistence
- **`setup/`** — Setup wizard state machine, shared between the CLI `setup` command and the TUI workspace-setup screen
- **`ipc/`** — Monitor ↔ Finder-extension interface (`status_file.rs`, `unix_socket.rs`)
- **`api/`** — Higher-level service helpers built on top of git/provider/config (e.g. `RepoScanService`)
- **`errors/`** — Custom error hierarchy: `AppError`, `GitError`, `ProviderError` with `suggested_action()` methods
- **`output/`** — `printer.rs` for verbosity-aware text output; `progress/` holds the `indicatif` progress bars (`CloneProgressBar`, `SyncProgressBar`, `DiscoveryProgressBar`)
- **`types/repo.rs`** — Core data types: `Repo`, `Org`, `ActionPlan`, `OpResult`, `OpSummary`
- **`types/`** — Core data types: `Repo`, `Org`, `ActionPlan`, `OpResult`, `OpSummary`, plus `RepoEntry`/`SyncHistoryEntry` (lifted out of the TUI in B0.1)
- **`checks.rs`** — System/runtime checks (presence of `git`, `gh`, auth status, SSH access via `check_ssh_github_access`)

### CLI / TUI modules (`crates/git-same-cli/src/`)

- **`app/`** — Top-level entry points: `app/cli/` runs the CLI subcommand path, `app/tui/` boots the interactive TUI. `main.rs` dispatches to one or the other based on whether a subcommand was given
- **`commands/`** — Per-subcommand handlers (`init`, `setup`, `sync_cmd`, `status`, `scan`, `reset`, `workspace`, `monitor`, `refresh`) plus shared `support/` helpers
- **`setup/`** — Setup wizard state machine + ratatui rendering, shared between the CLI `setup` command and the TUI workspace-setup screen (gated by the `tui` feature)
- **`tui/`** — Ratatui-based TUI (gated by the `tui` feature)
- **`cli.rs`** — clap derive types
- **`banner.rs`** — CLI banner rendering
- **`bin/gen_completions.rs`, `bin/gen_manpage.rs`** — Release-only helpers gated by the `release-tools` feature

### TUI module (`src/tui/`, feature-gated behind `tui`)
### TUI module (`crates/git-same-cli/src/tui/`, feature-gated behind `tui`)

Elm architecture: `app.rs` = Model, `screens/` = View, `handler.rs` = Update.

Expand All @@ -71,6 +91,26 @@ Elm architecture: `app.rs` = Model, `screens/` = View, `handler.rs` = Update.
- **Channel-based TUI updates:** Backend operations send `BackendMessage` through `mpsc::UnboundedSender<AppEvent>`, processed by the TUI event loop
- **Arrow-only navigation:** All directional movement uses arrow keys only (`←` `↑` `↓` `→`). No vim-style `j`/`k`/`h`/`l` letter navigation. Display hints use `[←] [↑] [↓] [→] Move`.

## FinderSync extension gotchas (macOS)

Three non-obvious traps in `macos/GitSameBadges/`. Each one silently breaks badges with no error log — the extension self-check still shows green.

1. **Boot-volume alias paths.** macOS auto-creates `/Volumes/<boot-volume-name>` as a symlink to `/`. Finder presents home-folder URLs with that prefix (`/Volumes/Manuel-SSD-4TB/Users/m/...`) and gates `requestBadgeIdentifier` on the URL matching an entry in `directoryURLs`. `Principal.updateMonitoredDirectories()` must register both the canonical and the alias-prefixed form of every watched root, otherwise the callback fires for nothing.

2. **macOS 26.4 sandbox rendering regression.** Both `NSImage.lockFocus()` and `NSImage(size:flipped:drawingHandler:)` produce empty/invalid pixel data when called inside a sandboxed FinderSync extension on 26.4. Symptom: Finder reserves the badge slot (folder icons shift) but no glyph renders. Workaround: build badges from SF Symbols (`NSImage(systemSymbolName:)` with palette `SymbolConfiguration`). SF Symbols are pre-rendered by macOS, no per-process drawing context required. Apple's own `r.circle.fill`/`o.circle.fill`/`u.circle.fill` are what `BadgeManager.symbolBadge` uses.

3. **Google Drive's FinderSync poisons the badge-rendering pipeline.** When `com.google.drivefs.finderhelper.findersync` is enabled, peer FinderSync extensions render no badge image even after Finder calls `setBadgeIdentifier`. Confirmed in this environment: badges only began appearing after the user disabled Google Drive in System Settings → Login Items & Extensions. Other peers (Keka, Synology, Dropbox) coexist fine. There is no code fix; document the workaround and surface it in the in-app self-check if you can.

`scan_roots` and `show_ambient`: defaults are `["~"]` / `false`. Never re-enable `show_ambient = true` with `~` in `scan_roots` — Finder refuses to call `requestBadgeIdentifier` on extensions whose `directoryURLs` contain the home folder (separate issue from the three above).

## Workspace folder branding (macOS)

The host paints a custom icon onto every workspace root via `NSWorkspace.setIcon` (wrapped in `crates/git-same-core/src/macos/folder_icon.rs`) so Finder shows it in the sidebar, column, list, icon, and Get Info views. A FinderSync extension can never replicate this — it only exposes corner badges. The icon is `crates/git-same-core/assets/workspace-folder.icns`, embedded via `include_bytes!` and regenerable via `bash toolkit/icons/build-workspace-folder-icns.sh`.

Lifecycle: painted by `core::setup::save_workspace` and `app::commands::save_workspace`, reapplied by the monitor (`monitor::run::reapply_workspace_folder_icons`) on every full scan if the `Icon\r` is missing, and stripped by `cli::commands::reset` and `app::commands::delete_workspace`. Opt out globally with `[ui] custom_folder_icon = false`.

**Finder Sidebar snapshot caveat.** `LSSharedFileList` captures a per-item icon bitmap into `~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.FavoriteItems.sfl3` at the moment the user drags a folder into Favorites. That snapshot is frozen — repainting the folder's `Icon\r` does **not** update the sidebar. The only refresh path is manual: right-click the stale sidebar item → Remove from Sidebar, then drag the folder back from a Finder window into Favorites. Don't waste time looking for a programmatic refresh API; the framework doesn't expose one, and the recommended workaround used by Synology / Dropbox is the same drag-and-drop.

## Formatting

`rustfmt.toml`: `max_width = 100`, `tab_spaces = 4`, edition 2021.
Expand All @@ -90,20 +130,22 @@ The test file contains `use super::*;` and all `#[test]` / `#[tokio::test]` func

**Do not** write inline `#[cfg(test)] mod tests { ... }` blocks — always use separate `_tests.rs` files.

**Integration tests** remain in `tests/integration_test.rs`.
**Integration tests** live in `crates/git-same-cli/tests/integration_test.rs`. They spawn the binary via `env!("CARGO_BIN_EXE_git-same")` (compile-time path), so they always run against the freshly built CLI binary at the workspace `target/`.

**Cross-crate test helpers:** `Repo::test()` in `git-same-core` is gated on `cfg(any(test, feature = "test-utils"))`. The CLI crate enables the `test-utils` feature in its `[dev-dependencies]` so its tests can call the helper without exposing it in production builds.

## CI/CD Workflows

All workflows are `workflow_dispatch` (manual trigger) in `.github/workflows/`:

| Workflow | Purpose | Trigger |
|----------|---------|---------|
| `S1-Test-CI.yml` | fmt, clippy, tests, release build dry-run, coverage, alias drift, workflow secret-safety, audit | Manual dispatch |
| `S2-Release-GitHub.yml` | Gated GitHub release assets for targets in `toolkit/packaging/targets.txt` (currently 4 targets) | Manual dispatch (select tag) |
| `S3-Publish-Homebrew.yml` | Publish Homebrew cask + formula-cli | Manual dispatch (select tag) |
| `S4-Publish-Crates.yml` | Publish crates.io package | Manual dispatch (select tag) |
| `S1-Test-CI.yml` | fmt, clippy, test, build dry-run, coverage, audit | Manual dispatch |
| `S2-Release-GitHub.yml` | Full CI + cross-compile 4 targets (per `toolkit/packaging/targets.txt`) + GitHub Release | Manual dispatch (select tag) |
| `S3-Publish-Homebrew.yml` | Download release tarballs and render `git-same-cli` formula + `git-same` cask templates into `zaai-com/homebrew-tap` | Manual dispatch (select tag) |
| `S4-Publish-Crates.yml` | Two-stage publish to crates.io: `git-same-core` → poll until indexed → `git-same` | Manual dispatch (select tag) |

S2 gates release asset builds on tests, coverage, alias drift, audit, and workflow secret-safety checks.
S2 runs all S1 jobs (test, coverage, audit) as gates before building release artifacts.

## Specs & Docs

Expand Down
46 changes: 41 additions & 5 deletions .github/workflows/S1-Test-CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ jobs:
run: cargo +${{ matrix.rust }} fmt --all -- --check

- name: Clippy
run: cargo +${{ matrix.rust }} clippy --all-targets --all-features -- -D warnings
run: cargo +${{ matrix.rust }} clippy --workspace --all-targets --all-features -- -D warnings

- name: Run tests
run: cargo +${{ matrix.rust }} test --all-features -- --test-threads=1
run: cargo +${{ matrix.rust }} test --workspace --all-features -- --test-threads=1

build:
name: Build (${{ matrix.target }})
Expand Down Expand Up @@ -81,7 +81,43 @@ jobs:
prefix-key: v1-rust-no-bin

- name: Build release
run: cargo +stable build --release --target ${{ matrix.target }}
run: cargo +stable build --release -p git-same --target ${{ matrix.target }}

tauri-debug-build:
name: Tauri App Debug Build
needs: [test]
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: Install Node
uses: actions/setup-node@v6
with:
node-version: 24

- name: Enable pnpm
run: corepack enable pnpm

- name: Verify entitlements parity
run: bash toolkit/packaging/macos/check-entitlements-parity.sh

- name: Install frontend dependencies
run: pnpm --dir crates/git-same-app/ui install --frozen-lockfile

- name: Build frontend
run: pnpm --dir crates/git-same-app/ui build

- name: Build Tauri app binary
run: |
cd crates/git-same-app
ui/node_modules/.bin/tauri build --debug --no-bundle

coverage:
name: Code Coverage
Expand Down Expand Up @@ -114,7 +150,7 @@ jobs:
run: cargo +stable tarpaulin --all-features --workspace --timeout 120 --out xml --engine llvm

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
use_oidc: true
fail_ci_if_error: false
Expand Down Expand Up @@ -161,7 +197,7 @@ jobs:
}
in_bin && /^[[:space:]]*required-features[[:space:]]*=/ { has_req=1 }
END { flush(); print default_count, default_name }
' Cargo.toml)"
' crates/git-same-cli/Cargo.toml)"
if [ "${DEFAULT_COUNT:-0}" -ne 1 ]; then
echo "ERROR: Cargo.toml has ${DEFAULT_COUNT:-0} default [[bin]] entries, expected 1"; exit 1
fi
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/S2-Release-GitHub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ jobs:
run: cargo +${{ matrix.rust }} fmt --all -- --check

- name: Clippy
run: cargo +${{ matrix.rust }} clippy --all-targets --all-features -- -D warnings
run: cargo +${{ matrix.rust }} clippy --workspace --all-targets --all-features -- -D warnings

- name: Run tests
run: cargo +${{ matrix.rust }} test --all-features -- --test-threads=1
run: cargo +${{ matrix.rust }} test --workspace --all-features -- --test-threads=1

coverage:
name: Code Coverage
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
run: cargo +stable tarpaulin --all-features --workspace --timeout 120 --out xml --engine llvm

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
use_oidc: true
fail_ci_if_error: false
Expand Down Expand Up @@ -115,7 +115,7 @@ jobs:
}
in_bin && /^[[:space:]]*required-features[[:space:]]*=/ { has_req=1 }
END { flush(); print default_count, default_name }
' Cargo.toml)"
' crates/git-same-cli/Cargo.toml)"
if [ "${DEFAULT_COUNT:-0}" -ne 1 ]; then
echo "ERROR: Cargo.toml has ${DEFAULT_COUNT:-0} default [[bin]] entries, expected 1"; exit 1
fi
Expand Down Expand Up @@ -267,7 +267,7 @@ jobs:
prefix-key: v1-rust-no-bin

- name: Build
run: cargo +stable build --release --target ${{ matrix.target }}
run: cargo +stable build --release -p git-same --target ${{ matrix.target }}

- name: Resolve version from tag
if: startsWith(github.ref, 'refs/tags/')
Expand Down Expand Up @@ -388,7 +388,7 @@ jobs:
find artifacts -type f -exec cp {} release-assets/ \;

- name: Create/update release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v2.6.2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Resolve the immutable commit behind tag v2.6.2
git ls-remote https://github.com/softprops/action-gh-release refs/tags/v2.6.2^{}

# Confirm current workflow reference(s)
rg -n 'softprops/action-gh-release@' .github/workflows/S2-Release-GitHub.yml

Repository: ZAAI-com/git-same

Length of output: 114


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Resolve the commit SHA for tag v2.6.2 with explicit error handling
echo "=== Resolving commit SHA for v2.6.2 ==="
COMMIT_SHA=$(git ls-remote --heads --tags https://github.com/softprops/action-gh-release refs/tags/v2.6.2^{} | awk '{print $1}')

if [ -z "$COMMIT_SHA" ]; then
  echo "Tag resolution failed, trying alternative method..."
  # Try without the ^{} syntax (which dereferences annotated tags)
  COMMIT_SHA=$(git ls-remote https://github.com/softprops/action-gh-release refs/tags/v2.6.2 | awk '{print $1}')
fi

echo "Resolved commit SHA: $COMMIT_SHA"

# Show current workflow reference
echo -e "\n=== Current workflow reference ==="
rg -n 'softprops/action-gh-release@' .github/workflows/S2-Release-GitHub.yml

# Check if there are any other unpinned GitHub actions in the workflow
echo -e "\n=== All GitHub actions in this workflow ==="
rg -n 'uses:.*@' .github/workflows/S2-Release-GitHub.yml | head -20

Repository: ZAAI-com/git-same

Length of output: 1147


🌐 Web query:

GitHub Actions security best practices pinning commit SHA versus tags mutable references

💡 Result:

Pinning GitHub Actions to a full-length commit SHA is considered the industry-standard security best practice [1][2][3]. Unlike tags or branch references, which are mutable and can be force-updated or redirected by anyone with write access to the action's repository, a full-length commit SHA provides an immutable, cryptographically verifiable reference [1][4][5][6]. Security Risks of Mutable References Using tags (e.g., @v1) or branch names (e.g., @main) introduces significant supply chain risks [7][8][3]. Because these references can be moved, an attacker who compromises an action's repository can point a widely used tag to a malicious commit [7][5][6]. This allows them to execute arbitrary code within the workflows of every repository consuming that action, often with the high privileges typically granted to CI runners [7][8][3]. This vulnerability has been exploited in real-world supply chain attacks, where workflows silently began pulling malicious code without any changes to the user's workflow files [7][8]. Best Practices for Implementation 1. Pin to Full-Length SHA: Always use the 40-character full commit SHA [4][6]. Shortened SHAs are insecure as they are susceptible to collision attacks [9]. 2. Maintain Readability: To keep workflows maintainable, include the original version tag as a comment alongside the pinned SHA (e.g., uses: actions/checkout@b4ffde6 # v4.1.1) [10][5][6]. 3. Automate Updates: Use tools like Dependabot or Renovate to manage updates [10][6]. These tools are capable of monitoring new releases and automatically opening pull requests that bump the pinned SHA, allowing you to review changes before merging [10][5]. 4. Enforce via Policy: If you manage an organization or enterprise, use GitHub's built-in policy features to mandate SHA pinning [1][2][11]. This prevents the accidental use of mutable references by failing any workflow that does not adhere to the requirement [2][7][12]. 5. Trust Considerations: While SHA pinning is the primary defense against malicious updates, you should still only use actions from reputable, verified sources [1][9][13]. Additionally, be aware that even a pinned action may rely on other mutable dependencies at runtime (such as external Docker images or network-fetched scripts), so defense-in-depth remains essential [7].

Citations:


Pin softprops/action-gh-release to a commit SHA instead of a tag.

@v2.6.2 is a mutable reference. Tags can be moved or force-updated by repository maintainers (intentionally or through compromise), allowing malicious code to be injected into workflows without changing the workflow file. Pinning to the immutable commit SHA is the industry-standard security practice endorsed by GitHub.

-        uses: softprops/action-gh-release@v2.6.2
+        uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
🧰 Tools
🪛 zizmor (1.25.2)

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

(unpinned-uses)


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

(superfluous-actions)

🤖 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/S2-Release-GitHub.yml at line 391, The
`softprops/action-gh-release` action reference at line 391 uses a mutable tag
version `@v2.6.2` instead of an immutable commit SHA, which poses a security
risk. Replace the tag reference `@v2.6.2` with the full commit SHA that
corresponds to that release version (find this by checking the
softprops/action-gh-release repository's releases or tags page), and update the
uses statement to pin to that specific commit hash instead of the version tag.

Source: Linters/SAST tools

with:
files: release-assets/*
env:
Expand Down
Loading