diff --git a/.agents/skills/github-workflow/SKILL.md b/.agents/skills/github-workflow/SKILL.md index 97cf8144..28f9da4b 100644 --- a/.agents/skills/github-workflow/SKILL.md +++ b/.agents/skills/github-workflow/SKILL.md @@ -10,8 +10,9 @@ always-on commit and test invariants — this skill assumes those. - Use the `gh` CLI for all GitHub interaction — PRs, issues, checks, releases, review comments. -- **`./swiftformat --lint` and `./test` are part of "done".** Never push a red - tree. +- Validate in proportion to risk. Pure documentation or comment-only changes + may skip checks that cannot exercise them; record skipped checks in the PR. + Never push a known-red tree. - **Never commit on `main`.** Branch first and keep every commit for one piece of work on that one branch. diff --git a/.agents/skills/running-tests/SKILL.md b/.agents/skills/running-tests/SKILL.md index 3d36366e..1842c4b3 100644 --- a/.agents/skills/running-tests/SKILL.md +++ b/.agents/skills/running-tests/SKILL.md @@ -5,10 +5,20 @@ description: Runs the test suite via ./test, picks the right tier, and manages t How to run tests in this repo. Read root [`AGENTS.md`](../../../AGENTS.md) for always-on rules: **use [`./test`](../../../test)** — never hand-roll `tuist test` -or `xcodebuild`; **`./swiftformat --lint` and `./test` are part of "done".** +or `xcodebuild`; validate in proportion to the change. Canonical flag list: `./test --help`. Rationale for `./test` over alternatives: header comment in [`test`](../../../test). +## Documentation-only changes + +Pure documentation or comment-only changes may skip `./test`. Skip +`./swiftformat --lint` too when the changed files are outside the formatter's +scope. Record skipped checks and the reason in the commit or PR validation. + +Do not classify a semantic change to configuration, scripts, generator inputs, +executable examples, or app-rendered copy as documentation-only. Run the +narrowest applicable checks below instead. + ## Pick a tier Pick the **narrowest tier that covers the change**: diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml new file mode 100644 index 00000000..e300c02c --- /dev/null +++ b/.codex/environments/environment.toml @@ -0,0 +1,42 @@ +version = 1 +name = "Stuff" + +[setup] +script = ''' +echo "error: Stuff worktree setup is supported on macOS and Linux only" >&2 +exit 1 +''' + +[setup.darwin] +script = "./worktree --check-main && ./ide --bootstrap --no-open" + +[setup.linux] +script = "./worktree --check-main && ./.cursor/install.sh" + +[cleanup] +script = "" + +[cleanup.darwin] +script = "./simulator --delete" + +[[actions]] +name = "Update to latest main" +icon = "run" +command = "./worktree --update-main" + +[[actions]] +name = "Regenerate project" +icon = "tool" +command = "./ide --no-open" +platform = "darwin" + +[[actions]] +name = "Test affected" +icon = "test" +command = "./test" +platform = "darwin" + +[[actions]] +name = "Lint formatting" +icon = "test" +command = "./swiftformat --lint" diff --git a/.worktreeinclude b/.worktreeinclude new file mode 100644 index 00000000..bd9a4f14 --- /dev/null +++ b/.worktreeinclude @@ -0,0 +1,2 @@ +# Preserve the machine-local signing choice in Codex-managed worktrees. +.mise.local.toml diff --git a/AGENTS.md b/AGENTS.md index fa5c4f8e..cadc7049 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,7 +51,7 @@ generating; plain `./ide` fails fast pointing at it. The executables in the repo root are the dev scripts — `ide`, `test`, `swiftformat`, `sync-agents`, `profile`, `icons`, `flaky`, `simulator`, -`xcstrings`, `attribution` — and each takes `--help`. Reach for one rather than +`worktree`, `xcstrings`, `attribution` — and each takes `--help`. Reach for one rather than hand-rolling its job: `test` is the only way tests should be run (see [Running tests](#running-tests)), and `icons`, `attribution`, and `simulator` in particular own state that is easy to corrupt by hand — `./simulator` owns a per-checkout device (see the @@ -527,8 +527,13 @@ flag is needed there. ## Running tests **Use [`./test`](test)** — the only way to run tests. Never hand-roll `tuist -test` or `xcodebuild`. **`./swiftformat --lint` and `./test` are part of -"done".** +test` or `xcodebuild`. **Validate in proportion to risk:** run +`./swiftformat --lint` when the changed files are in its scope, and run the +narrowest applicable `./test` tier for code, build, tooling, or behavior +changes. Pure documentation or comment-only changes may skip checks that +cannot exercise them; record skipped checks in the commit or PR validation. +Semantic changes to configuration, scripts, generator inputs, executable +examples, or app-rendered copy are not documentation-only. Load the [`running-tests`](../.agents/skills/running-tests/SKILL.md) skill for test tiers, snapshot opt-in, why not `tuist test`, and per-checkout simulator @@ -539,9 +544,10 @@ management (`./simulator` resolves a UDID — never pass a device name to - **Never commit on `main`.** Branch first (`git checkout -b `) and keep every commit for one piece of work on that one branch. -- **`./swiftformat --lint` and `./test` are part of "done".** Never commit a red - tree. Load the [`running-tests`](../.agents/skills/running-tests/SKILL.md) - skill for which tier to run. +- **Validate in proportion to risk.** Follow [Running tests](#running-tests), + never commit a known-red tree, and load the + [`running-tests`](../.agents/skills/running-tests/SKILL.md) skill to choose + the applicable checks. - **Multi-step work lands one commit per step**, so history stays bisectable and can land piecewise — including pure-groundwork steps, which say so in the body. - **Commit when asked, or when working through a plan.** If it's unclear whether @@ -553,6 +559,30 @@ Load the [`github-workflow`](../.agents/skills/github-workflow/SKILL.md) skill for PRs, pushes, review feedback, CI, and posting as the user. Always-on: use `gh`; open PRs ready-for-review; mark AI-posted comments. +## Codex worktree specific instructions + +[`.codex/environments/environment.toml`](.codex/environments/environment.toml) +owns setup, cleanup, and toolbar actions for Codex-managed worktrees. Keep it +idempotent and regenerate it through the ChatGPT desktop app's local environment +editor when changing its schema. + +- macOS setup runs `./ide --bootstrap --no-open`; bootstrap trusts the new + checkout's `.mise.toml`, installs pinned tools, syncs agent files, and + generates without opening Xcode. +- Linux setup delegates to [`.cursor/install.sh`](.cursor/install.sh), with the + same platform limits documented below. +- Setup first runs `./worktree --check-main`, which refreshes `origin/main` and + warns without moving `HEAD` when the selected checkout does not contain it; + an unavailable remote warns without blocking setup. +- The **Update to latest main** action runs `./worktree --update-main`; it only + fast-forwards a checkout directly behind `origin/main` and refuses divergent + history. +- [`.worktreeinclude`](.worktreeinclude) copies only ignored machine-local files + required by a new managed worktree. `AGENTS.override.md` is copied by Codex + automatically and must not be listed there. +- Cleanup uses `./simulator --delete`, which deletes only that checkout's + device and is safe when no device was created. + ## Cursor Cloud specific instructions Cloud agent VMs run **Linux**, not macOS. This repo targets **iOS 26** with diff --git a/README.md b/README.md index 5f1e8f3e..45d6fa58 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,17 @@ same simulator. `./simulator --list` shows them with their owning checkouts and `./simulator --prune` (`--dry-run` to preview) cleans up after a checkout you deleted; see `./simulator --help`. +Codex-managed worktrees use the checked-in local environment at +`.codex/environments/environment.toml`. Setup fetches `origin/main` and warns +without changing the checkout when its `HEAD` does not contain the latest main. +The **Update to latest main** toolbar action safely fast-forwards a checkout +directly behind main and refuses divergent feature history. On macOS the +environment also runs `./ide --bootstrap --no-open`, offers project generation, +affected tests, and format lint actions, and removes only that checkout's +simulator on cleanup. `.worktreeinclude` copies the gitignored +`.mise.local.toml` signing override from the source checkout into each new +managed worktree. + Where's production architecture is checked with Bumper Bowling through the root Swift package: @@ -126,10 +137,13 @@ Tuist.swift Tuist configuration .mise.toml Pins the Tuist, SwiftFormat, and Ruby versions .mise.local.toml Local mise overrides, gitignored (e.g. TUIST_DEVELOPMENT_TEAM) .swiftformat SwiftFormat rules +.codex/ Codex managed-worktree setup, cleanup, and actions +.worktreeinclude Ignored local files copied into Codex-managed worktrees ide Dev script – bootstrap (mise + tools), hooks, sync-agents, tuist generate swiftformat Run SwiftFormat via mise (default: format `.`) sync-agents Sync AGENTS.md → CLAUDE.md and .claude/skills/ simulator Resolve/create this checkout's simulator, boot it, print its UDID +worktree Check or safely fast-forward a checkout against origin/main profile Report build/test hot spots (see `./profile --help`) flaky Detect flaky tests, update FLAKY_TESTS.md (see `./flaky --help`) FLAKY_TESTS.md Flaky tests and their flake counts (generated by `./flaky`) diff --git a/ide b/ide index c416eeab..ad40eb26 100755 --- a/ide +++ b/ide @@ -141,6 +141,12 @@ EOF exit 1 fi + # Every clone and worktree has a distinct config path, and mise refuses to + # read an untrusted .mise.toml. Running this explicit bootstrap is the trust + # boundary, so make fresh checkouts usable without a separate manual step. + echo "==> mise trust" + "$MISE" trust + # Install the pinned tools (Tuist, SwiftFormat, Ruby) from .mise.toml. echo "==> mise install" "$MISE" install diff --git a/worktree b/worktree new file mode 100755 index 00000000..19e84eff --- /dev/null +++ b/worktree @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: ./worktree --check-main | --update-main + +Checks whether this checkout contains the latest origin/main, or safely +fast-forwards a checkout that is directly behind it. + +Options: + --check-main Fetch origin/main and warn when HEAD does not contain it. + Never changes HEAD or the working tree; a fetch failure warns + without blocking worktree setup. + --update-main Fetch origin/main and fast-forward only when HEAD is its + ancestor. Refuses divergent history. + -h, --help Show this help. +EOF +} + +if [ "$#" -ne 1 ]; then + usage >&2 + exit 1 +fi + +mode="$1" +main_ref="refs/remotes/origin/main" + +fetch_main() { + git fetch --quiet origin '+refs/heads/main:refs/remotes/origin/main' +} + +short_commit() { + git rev-parse --short=8 "$1" +} + +case "$mode" in + --check-main) + if ! fetch_main; then + echo "warning: could not fetch origin/main; main freshness is unknown" >&2 + exit 0 + fi + + if git merge-base --is-ancestor "$main_ref" HEAD; then + echo "==> Checkout contains latest origin/main ($(short_commit "$main_ref"))" + else + echo "warning: checkout $(short_commit HEAD) does not contain latest origin/main ($(short_commit "$main_ref"))" >&2 + echo "warning: run the 'Update to latest main' action if this checkout should be a current main checkout" >&2 + fi + ;; + --update-main) + echo "==> Fetching origin/main" + fetch_main + + if git merge-base --is-ancestor "$main_ref" HEAD; then + echo "==> Checkout already contains latest origin/main ($(short_commit "$main_ref"))" + exit 0 + fi + + if ! git merge-base --is-ancestor HEAD "$main_ref"; then + echo "error: checkout $(short_commit HEAD) has diverged from origin/main ($(short_commit "$main_ref")); refusing to rewrite or merge feature history" >&2 + echo "error: merge or rebase origin/main explicitly instead" >&2 + exit 1 + fi + + git merge --ff-only "$main_ref" + echo "==> Fast-forwarded checkout to latest origin/main ($(short_commit HEAD))" + ;; + -h|--help) + usage + ;; + *) + echo "error: unknown option '$mode'" >&2 + usage >&2 + exit 1 + ;; +esac