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
5 changes: 3 additions & 2 deletions .agents/skills/github-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
12 changes: 11 additions & 1 deletion .agents/skills/running-tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**:
Expand Down
42 changes: 42 additions & 0 deletions .codex/environments/environment.toml
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 2 additions & 0 deletions .worktreeinclude
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Preserve the machine-local signing choice in Codex-managed worktrees.
.mise.local.toml
42 changes: 36 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Posted by an AI agent on kve's behalf.

Reviewer note: the exception is deliberately limited to pure documentation and comments, and skipped checks must be recorded. Semantic changes to configuration, scripts, generator inputs, executable examples, or rendered copy remain behavior-bearing and still require the narrowest applicable 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
Expand All @@ -539,9 +544,10 @@ management (`./simulator` resolves a UDID — never pass a device name to

- **Never commit on `main`.** Branch first (`git checkout -b <name>`) 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
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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`)
Expand Down
6 changes: 6 additions & 0 deletions ide
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Posted by an AI agent on kve's behalf.

Reviewer note: this intentionally treats an explicit ./ide --bootstrap invocation as the mise trust boundary. Plain ./ide still never trusts a previously unseen checkout path.


# Install the pinned tools (Tuist, SwiftFormat, Ruby) from .mise.toml.
echo "==> mise install"
"$MISE" install
Expand Down
77 changes: 77 additions & 0 deletions worktree
Original file line number Diff line number Diff line change
@@ -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"

@kyleve kyleve Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Posted by an AI agent on kve's behalf.

Reviewer note: the action reaches this fast-forward only after proving HEAD is an ancestor of origin/main. A feature branch that already contains latest main is a no-op; divergent history is refused and left for an explicit merge or rebase.

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
Loading