{"owner":"utmapp","repo":"UTM","hasSkills":true,"totalSkillsCount":6,"totalTokensCount":7696,"categories":["root-instruction","command-prompt","anthropic-skill","project-spec"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md",".claude/commands/utm-submit.md",".claude/commands/utm-review.md",".agents/skills/utm-review/SKILL.md","Documentation/Architecture.md",".agents/skills/utm-submit/SKILL.md"],"skills":{"AGENTS.md":"## Building\n\nUTM **cannot** build without prebuilt native dependencies (QEMU, SPICE, …) staged\ninto `sysroot-*` directories at the repo root. Get them from the project's GitHub\nActions `Sysroot-*` artifacts — don't build them manually unless you're modifying a\ndependency (it's slow and fragile). Then build with `./scripts/build_utm.sh` and\nsign/package with `./scripts/package*.sh`. Setup, platform/arch names, schemes, and\nsigning: `Documentation/iOSDevelopment.md`.\n\n## Architecture\n\nLayered: platform-independent backend (`Services/`, `Configuration/`) + SwiftUI\nfrontend (`Platform/`). Full picture: `Documentation/Architecture.md`. Load-bearing\ngotchas:\n\n- **Two VM backends** behind the `UTMVirtualMachine` protocol — `UTMQemuVirtualMachine`\n  (QEMU, via the QEMUKit/CocoaSpice packages) and `UTMAppleVirtualMachine`\n  (Virtualization.framework, macOS-only). Most backend code branches on which.\n- **Config is the serialization format** — parallel `Codable` trees\n  `UTMQemuConfiguration` and `UTMAppleConfiguration` (PLIST in the `.utm` bundle).\n  Add new fields there; don't extend the `Configuration/Legacy/` readers.\n- **macOS runs QEMU out-of-process** (XPC: `QEMUHelper`→`QEMULauncher`); host files\n  reach it only through a security-scoped-bookmark dance — respect it for anything\n  touching disk images, shared dirs, or file handles. **iOS runs QEMU in-thread and\n  can launch it only once per process** — this constrains start/stop logic.\n- **UTM SE** (`iOS-SE` scheme, `*-tci` sysroots) is gated by the `WITH_QEMU_TCI`\n  compile flag — grep for it before changing anything that differs between editions.\n\n## Generated files — never hand-edit\n\nEdit the generator and regenerate, not the output: `Configuration/QEMUConstantGenerated.swift`\n(`scripts/const-gen.py`), `Scripting/UTMScripting.swift` (`scripts/bridge-gen.sh`\nfrom `UTM.sdef`), and QAPI/QMP wrappers (produced by the dependency build).\nThird-party patches live in `patches/`.\n\n## Conventions\n\n**`CONTRIBUTING.md` is the source of truth** for style, concurrency, design, and\ncompatibility rules; `/utm-review` audits the diff against it. Two rules an agent\nmust apply unprompted — they override default behavior and are easy to violate:\n\n- **AI attribution:** every AI-assisted commit **must** carry an\n  `Assisted-by: AGENT:MODEL` trailer (e.g. `Assisted-by: Claude:claude-opus-4-8`) —\n  add it explicitly **even if your agent doesn't normally inject attribution\n  trailers**; the absence of a default trailer is not an excuse to omit it. Never\n  add `Co-authored-by` or session links (strip any a tool adds). Commit titles are\n  `component: short description` explaining *why*.\n- **Scope:** one feature/fix per PR; don't touch, refactor, or reformat unrelated\n  files; route new logging through `UTMLogging`/`logging` at `debug` level.\n\n## Contribution workflows\n\nBefore opening or updating a PR, run **`/utm-review`** then **`/utm-submit`** —\ncanonical instructions in `.agents/skills/{utm-review,utm-submit}/SKILL.md` (thin\nshims in `.claude/commands/` and `.opencode/command/`). If your agent doesn't\nauto-load them, read and follow those `SKILL.md` files directly.\n",".claude/commands/utm-submit.md":"---\ndescription: Submit the current UTM change as a PR to utmapp/UTM — ensures /utm-review ran, then updates an existing PR (blame-squash + force-push) or opens a new one (issue links + human-test attestation).\nargument-hint: [PR number or URL — for updates]\nallowed-tools: Bash, Read, Grep, Glob, Skill, Task, AskUserQuestion\n---\n\nRead and follow the canonical, agent-neutral workflow in\n`.agents/skills/utm-submit/SKILL.md`, executing its steps end to end.\n\nClaude Code specifics:\n- Where the workflow says \"ask the user\" for a fixed set of choices (e.g. the\n  Step 1 review gate), use AskUserQuestion.\n- To run the Step 1 review gate (\"/utm-review now\"), read and follow\n  `.agents/skills/utm-review/SKILL.md`.\n- For the interactive `gh auth login`, suggest the user type `! gh auth login`\n  so its output lands in this session.\n- Argument passed to this command (PR number or URL, if updating): $ARGUMENTS\n",".claude/commands/utm-review.md":"---\ndescription: Review pending UTM changes (standard code review + CONTRIBUTING.md/AGENTS.md audit) before submitting. Run before /utm-submit.\nargument-hint: [low|medium|high|max]\nallowed-tools: Bash, Read, Grep, Glob, Skill, Task\n---\n\nRead and follow the canonical, agent-neutral workflow in\n`.agents/skills/utm-review/SKILL.md`, executing its steps end to end.\n\nClaude Code specifics:\n- For the Step 3 standard code review, invoke the `code-review` skill at the\n  effort level passed here (default `high`).\n- Argument passed to this command (review effort, if any): $ARGUMENTS\n",".agents/skills/utm-review/SKILL.md":"---\nname: utm-review\ndescription: Review pending UTM changes before they are submitted. Runs a standard correctness/quality code review of the current branch's diff AND audits it against UTM's contribution rules in CONTRIBUTING.md and AGENTS.md — scope discipline, commit/PR format, AI attribution, logging hooks, generated files, UI/design philosophy, and platform compatibility. Use this before opening or updating a UTM pull request, whenever the user asks to review UTM changes, or when the user runs /utm-review. The /utm-submit workflow expects this to have been run on the current changes first.\n---\n\n# utm-review\n\nAgent-neutral instructions — follow them with whatever tools your agent provides\n(Claude Code, Codex, OpenCode, Antigravity, …). This is the canonical copy; the\nper-agent entries under `.claude/commands/`, `.opencode/command/`, etc. just\npoint here.\n\nGate a UTM change through two passes before it can be submitted:\n\n1. **A standard code review** for real correctness bugs and worthwhile cleanups.\n2. **A UTM contribution-guideline audit** — the part specific to this project and\n   the reason this workflow exists. UTM has firm rules (especially for\n   AI-assisted contributions) that a generic review won't check.\n\nReport everything you find. This workflow *reviews and reports*; it does not\npush, commit, or rewrite history — that is `/utm-submit`'s job.\n\nIf you were invoked with an argument, treat it as a review effort level\n(`low|medium|high|max`); default to `high`.\n\n## Step 1 — Read the authoritative rules\n\nRead `CONTRIBUTING.md` and `AGENTS.md` at the repo root now — they are the single\nsource of truth for what makes an acceptable UTM contribution, and Step 4 audits\nthe diff against them. (`AGENTS.md` overrides default tool behavior for this repo\n— notably the commit-trailer policy.)\n\n## Step 2 — Establish the review scope\n\nThe change under review is everything that will land in the PR: commits on this\nbranch since it diverged from the base, plus any uncommitted edits.\n\n```sh\nbase=$(git merge-base HEAD origin/main)\ngit log --oneline \"$base\"..HEAD          # the PR's commits\ngit --no-stat diff \"$base\"...HEAD        # committed change\ngit --no-stat diff HEAD                  # uncommitted change (if any)\ngit status --porcelain                   # untracked files\n```\n\nKeep the list of changed files handy — several guideline checks below hinge on\n*which* files were touched.\n\n## Step 3 — Standard code review\n\nRun a standard code review of the pending diff to surface correctness bugs and\nreuse/simplification/efficiency cleanups. Use whatever review capability your\nagent provides — for example a `/code-review` or `/review` command — or perform a\nfocused review pass yourself if none exists. If the change includes uncommitted\nedits the review tool doesn't pick up, review those yourself to the same\nstandard. Collect the findings; you'll merge them into one report.\n\n## Step 4 — UTM contribution-guideline audit\n\nAudit the diff against **every** rule in the `CONTRIBUTING.md` and `AGENTS.md` you\njust read — those files *are* the checklist, so keeping it there (rather than\ncopied here) means it never drifts out of sync. Cite `file:line` and the specific\nrule for each issue you raise. Judge the diff against all of it: style and\nconcurrency, design philosophy, platform compatibility, dependency-upstreaming,\nand the rest — defer to the files' wording instead of restating it.\n\nThese few are high-value, repo-specific, and the easiest to miss, so check them\nexplicitly:\n\n- **AI attribution.** Every AI-assisted commit in `\"$base\"..HEAD` **must** carry an\n  `Assisted-by: AGENT:MODEL` trailer — many agents omit it by default, so a missing\n  trailer is a common, easy-to-miss finding, not an acceptable absence — and **no\n  `Co-authored-by`** (strip it — UTM takes full human responsibility per the\n  Linux-kernel policy). Verify with `git log --format='%H%n%B' \"$base\"..HEAD`.\n- **Scope discipline.** One feature/fix; no edits, refactors, reformatting, or\n  whitespace/header churn in unrelated files; no stray logging.\n- **Generated files not hand-edited** — `Configuration/QEMUConstantGenerated.swift`,\n  `Scripting/UTMScripting.swift`, QAPI/QMP wrappers. Edit the generator instead.\n- **Logging** goes through `UTMLogging`/`logging` at `debug` level (warnings and\n  errors excepted); bring-up-only logging is removed before committing.\n- **UI strings** avoid jargon (\"GPU Acceleration\" not \"Venus\", \"Apple Silicon\"\n  not ARM64); commit titles are `component: short description` explaining *why*.\n\n## Step 5 — Report\n\nProduce one consolidated report:\n\n- A one-line summary of the change and its apparent scope.\n- **Correctness/quality findings** (from Step 3), each with `file:line`.\n- **Guideline findings** (from Step 4), each with `file:line` and the rule cited.\n- A short verdict: is this ready to submit, or what must change first? Call out\n  any blocker that `/utm-submit` will trip on (e.g. a `Co-authored-by` trailer,\n  an unrelated-file edit, or a missing `Assisted-by`).\n\nBe honest about uncertainty and avoid nitpicks a senior reviewer wouldn't raise\n(pre-existing issues, things a compiler/linter would catch, lines the change\ndidn't touch). The goal is a change that sails through human review on the UTM\nrepo, not a wall of pedantry.\n\n## Step 6 — Record that the review ran (handoff to /utm-submit)\n\n`/utm-submit` checks whether the current changes were reviewed. Leave a marker so\nit can confirm even across sessions or a different agent:\n\n```sh\ngitdir=$(git rev-parse --git-dir)\nhash=$( { git rev-parse HEAD; git diff \"$(git merge-base HEAD origin/main)\"...HEAD; git diff HEAD; } | git hash-object --stdin )\nprintf 'branch=%s\\nhead=%s\\ndiff_hash=%s\\nat=%s\\n' \"$(git rev-parse --abbrev-ref HEAD)\" \"$(git rev-parse HEAD)\" \"$hash\" \"$(date -u +%FT%TZ)\" > \"$gitdir/utm-review-marker\"\n```\n\nThen end with a clear, recognizable line, e.g.:\n`✅ utm-review complete — <branch> @ <short-sha>, N findings (M blockers).`\n\nThe `diff_hash` lets `/utm-submit` tell whether the changes it's about to submit\nare exactly the ones you reviewed, or whether they've been edited since.\n","Documentation/Architecture.md":"# Architecture\n\nUTM is built upon several pieces of technology, layered to provide compatibility across various host configurations. Below is a simplified diagram of key pieces of UTM with additional details provided below.\n\n```\n┌────────────────────┬──────────────────────┐\n│   iOS VM Display   │   macOS VM Display   │\n├────────────────────┴──────────────────────┤\n│                  SwiftUI                  │\n├───────────────────────────────────────────┤\n│             UTMVirtualMachine             │\n├────────────────┬──────────────────────────┤\n│   CocoaSpice   │                          │\n├────────────────┤ Virtualization.framework │\n│ QEMU (TCG/HVF) │                          │\n└────────────────┴──────────────────────────┘\n```\n\n## QEMU\n\nThe backbone of UTM is QEMU, which provides the emulation and virtualization engine. We run a custom [fork][1] which includes several features such as:\n\n* Building QEMU as a shared library\n* APRR support for jailbroken iOS\n* ARM64 TCTI from @ktemkin (JIT-less iOS support)\n* SPICE ANGLE backend for hardware GL acceleration\n\nThese features, along with several others (which are undergoing code review and may not have made the latest stable release), allow our fork to be optimized for Darwin.\n\n### Hypervisor.framework\n\nQEMU includes support for the `hvf` accelerator which provides same architecture virtualization (x86 -> x86 or ARM64 -> ARM64) on macOS. This framework is not available on iOS.\n\n### UTMQemu\n\nQEMU is linked as a shared library. On iOS, there is no ability to use `fork`, XPC (directly), or any other way of launching a new process. As a result, we run the QEMU main loop in a pthread which for all intents and purposes work like normal (the key exception being that you cannot spawn multiple instances of QEMU and since QEMU does not properly clean up resources, you cannot re-launch QEMU). On macOS, XPC is used to launch QEMU in a new process. `UTMQemu` manages the pthread or XPC implementation.\n\n#### XPC Helper\n\nOn macOS, spawning new processes is permitted but due to App Sandbox security requirements, we need some additional \"bootstrapping\" code to launch QEMU properly. The added benefit is that we only have to provide a single bundle identifier for the \"launcher\" executable rather than a different identifier for each QEMU executable (required for App Sandbox).\n\n`QEMUHelper` is an XPC helper with its own App Sandbox separate from the UTM main application. This improves security by providing an additional layer of separation. However, due to this extra care has to be taken when passing file handles and other system resources from the main app. For example, the Unix socket file used to communicate with SPICE is stored in a shared App Group directory. For disk images and shared directories that cannot be stored in the App Group, we have to do a complicated sandbox dance to get the right access permissions.\n\n1. The main application opens a `NSOpenPanel`, allowing the user to select a file/directory outside the sandbox. This returns a `NSURL` with the right security scope attached. If we take a regular bookmark of `NSURL` and pass it through XPC, the XPC process should also have access once it reads the bookmark back into a `NSURL`.\n2. However, the access to that file is only valid while the app is open. As soon as you close it, the app loses those permissions and must either prompt the user to select the file again or store a \"security scoped bookmark.\" The issue here is that a security scoped bookmark is only valid for the sandbox that created it. If we take a security scoped bookmark and pass it directly to the XPC process, it cannot get back a `NSURL`.\n3. So the solution is to take a *standard* bookmark, pass it to the XPC process, have the XPC process then create a *security scoped* bookmark and pass it back to the main process. The main process can now store this bookmark and have it work as intended when it passes it to the XPC process next launch.\n\nThe XPC helper spawns `QEMULauncher` with an inherited sandbox. This means it can access files that are accessible to the helper XPC. The launcher process is what runs QEMU. When a new file is opened (for example a new disk image is mounted), the main application will pass a bookmark to the helper XPC where it will call `-startAccessingSecurityScopedResource` which also applies to the child process (`QEMULauncher`). This way, QEMU does not have to have any knowledge of the App Sandbox.\n\n#### iOS Helper\n\nOn iOS 26 and later, tools such as `qemu-img` run out of process in `iOSHelper`, an ExtensionKit extension embedded in the app (`iOSHelper-SE` for UTM SE, as an extension's bundle identifier must start with its host's). It implements the same `QEMUHelperProtocol` as `QEMUHelper`, but loads the tool into its own process like the in-thread QEMU does, so each helper process runs one tool and the app launches a new one for the next. `UTMHelperProcess` launches the helper through `AppExtensionProcess` and hands its connection to `UTMProcess`, which otherwise works as on macOS: the helper has no access to the app's container, so files are passed as bookmarks, whose implicit security scope lets the process that resolves them access the file. QEMU itself still runs in the app process on iOS.\n\n### UTMConfiguration\n\nVM configuration is stored in a PLIST format. This PLIST maps to either a `UTMQemuConfiguration` or `UTMAppleConfiguration` structure which stores the underlying configuration data in a `Codable` interface for easy serialization.\n\n### UTMQemuSystem\n\n`UTMQemuSystem` maps `UTMQemuConfiguration` to command line arguments used to launch QEMU.\n\n### UTMQemuManager\n\nAfter a VM is launched, `UTMQemuManager` provides run-time services though the QMP protocol. These services include stopping/pausing/resuming the VM, taking snapshots, switching between mouse and tablet, mounting removable disk images, and more. The underlying transport for QMP is JSON (over a socket), so `UTMJSONStream` marshals the data to and from `NSDictionary` objects.\n\n#### QAPI\n\nQMP protocol is defined by the QAPI schema which is provided as a set of JSON files in QEMU. QEMU uses these JSON files to generate wrapper C functions for internal usage. UTM includes a modified function generator derived from QEMU's own script (`qapi-gen.py`) along with modified QAPI C visitors (for `NSDictionary`). This allows UTM to use the same commands, structures, and events that QEMU uses in a transparent way.\n\nFor example, when UTM makes a call to some QAPI command such as `qmp_blockdev_change_medium`, the generated C functions will automatically marshal the function arguments into a `NSDictionary` object which `UTMJSONStream` converts into JSON and sends it over the QMP socket. When a response is received, `UTMJSONStream` converts the JSON into a `NSDictionary` object, which goes through the generated C functions to unmarshal into some C structure which is returned from `qmp_blockdev_change_medium`. This is all transparent to the caller as long as they understand that the function call will block until the response is received so it must not be called from the main thread.\n\n## Virtualization.framework\n\nOn Apple Silicon Macs running macOS 12 or later, `Virtualization.framework` is provided by Apple to run macOS 12 guests.\n\n### UTMAppleConfiguration\n\nAs the backend is different, the configuration format UTM uses is also different and is represented in `UTMAppleConfiguration`. As this configuration is handed in Swift, the Codable protocol is used for serialization instead of a `NSDictionary` backing used in `UTMQemuConfiguration`. The Codable backing is more extendible as it allows more complex data to be represented without a lot of boilerplate.\n\n## CocoaSpice\n\nUTM uses the SPICE front-end with QEMU because it has more versatility than VNC to handle things like USB forwarding, multiple displays, and the ability to use a SPICE agent running on the guest to share clipboard and change the resolution. [CocoaSpice][2] is provided as a Swift package and acts as Cocoa/Objective-C bindings for SPICE GTK. CocoaSpice also provides a bridge between the Pixman framebuffer that SPICE uses and Metal textures that is used by MetalKit to render to screen.\n\n### UTMSpiceIO\n\n`UTMSpiceIO` connects `CocoaSpice` to UTM and is used by `UTMQemuVirtualMachine` to control the SPICE client and respond to client events.\n\n## UTMVirtualMachine\n\n`UTMVirtualMachine` provides file I/O operations for creating and saving .utm VM bundles as well as controls for the platform-specific layer above to do high level tasks like starting and stopping the VM. It is highest level of the \"backend\", providing a platform independent view of UTM virtual machines.\n\n### UTMQemuVirtualMachine\n\nThis subclass manages QEMU+CocoaSpice backend VMs.\n\n### UTMAppleVirtualMachine\n\nThis subclass manages Apple Virtualization.framework backend VMs.\n\n## Frontend\n\n### SwiftUI\n\nThe frontend for UTM is designed mostly in SwiftUI 3.0. That means the minimum supported operating system is iOS 15 and macOS 12 and is the main reason there are no plans to back-port UTM to earlier versions. Most views are designed to work on both macOS and iOS.\n\n#### UTMData\n\n`UTMData` is a SwiftUI `ObservableObject` that contains the \"state\" and is the \"single source of truth\" for the home view. It stores the list of VMs and functions to create, modify, move, etc VMs on that list. `UTMConfigurable` provides the state for VM configuration views.\n\n### iOS VM Display\n\nThe VM display uses UIKit as SwiftUI is not mature enough to do everything UTM needs. This includes the custom keyboard accessory view implemented in a NIB for emulating keys that are not available on the standard iOS keyboard.\n\n### macOS VM Display\n\nOn macOS, the VM display uses AppKit for similar reasons above.\n\n[1]: https://github.com/utmapp/qemu\n[2]: https://github.com/utmapp/CocoaSpice\n",".agents/skills/utm-submit/SKILL.md":"---\nname: utm-submit\ndescription: Submit the current UTM change as a pull request to github.com/utmapp/UTM. Ensures /utm-review has run, then either updates an existing PR (squashing each new edit into the commit that owns it and force-pushing) or opens a new PR (gathering issue links and a human-testing attestation). Use this when the user wants to submit, open, update, or push a UTM pull request, or runs /utm-submit.\n---\n\n# utm-submit\n\nAgent-neutral instructions — follow them with whatever tools your agent provides\n(Claude Code, Codex, OpenCode, Antigravity, …). This is the canonical copy; the\nper-agent entries under `.claude/commands/`, `.opencode/command/`, etc. just\npoint here.\n\nTurn the current change into a clean pull request against `utmapp/UTM`. Two\npaths: **update an existing PR** (squash edits into the commits they belong to,\nthen force-push) or **open a new PR** (collect issue links + a human-testing\nattestation, then create it).\n\nRead `CONTRIBUTING.md` and `AGENTS.md` first — they govern commit/PR format and\nthe AI-attribution policy enforced below. If you were invoked with an argument,\ntreat it as a PR number or URL to update.\n\n## Step 1 — Confirm the change was reviewed\n\n`/utm-submit` must never run on un-reviewed changes. Determine whether\n`/utm-review` was run on the *current* changes, in this order:\n\n1. If you already ran `/utm-review` earlier in this session against the current\n   changes, proceed.\n2. Otherwise read `\"$(git rev-parse --git-dir)/utm-review-marker\"` and recompute\n   the pending-diff hash exactly as utm-review did, then compare to `diff_hash`:\n   ```sh\n   { git rev-parse HEAD; git diff \"$(git merge-base HEAD origin/main)\"...HEAD; git diff HEAD; } | git hash-object --stdin\n   ```\n   If it matches the marker's `diff_hash`, the current changes were reviewed\n   (possibly in an earlier session or by another agent) — proceed, and say so. If\n   the marker exists but the hash differs, the code was edited after review —\n   treat as un-reviewed.\n3. Otherwise ask the user: *\"I can't confirm /utm-review ran on the current\n   changes. How do you want to proceed?\"* — offer: **Run /utm-review now**\n   (recommended; run that workflow, then continue), **I've already reviewed it\n   previously** (continue), **Cancel**.\n\nDo not silently skip this gate.\n\n## Step 2 — Locate the repo, branch, and any existing PR\n\n```sh\ngit remote -v                                   # find the remote pointing at utmapp/UTM\nbranch=$(git rev-parse --abbrev-ref HEAD)\n```\n\n- **Never submit from the default branch.** If `branch` is `main` (or otherwise\n  tracks the upstream default), stop and ask the user to move the work onto a\n  feature branch first (`git switch -c component/short-description`) — you cannot\n  open a `main → main` PR.\n- This workflow uses GitHub's `gh` CLI for everything that talks to GitHub. **If\n  `gh` is not installed or not authenticated**, tell the user and offer to set it\n  up: `brew install gh` then `gh auth login` (the login is interactive — have the\n  user run it themselves in their terminal). If they decline, fall back to the\n  manual paths noted in Steps 3a/3b.\n- Detect an existing open PR for this branch (or honor a PR number/URL you were\n  given):\n\n  ```sh\n  gh pr view \"$branch\" --repo utmapp/UTM --json number,url,state,baseRefName,headRefName 2>/dev/null\n  ```\n\n  An open PR → **Step 3a (update)**. No PR → **Step 3b (new)**.\n\n## Step 3a — Update an existing PR (squash, then force-push)\n\nUTM does **not** keep \"address review feedback\" commits. Every edit belongs in\nthe commit that introduced the lines it touches. The change history within a\nsingle PR must stay clean.\n\n1. **Express the update as working-tree changes on top of the PR's published\n   commits.** If you committed the update locally, undo just those commits so\n   their content returns to the working tree, leaving HEAD at the published PR\n   tip:\n   ```sh\n   git reset \"$(git rev-parse --abbrev-ref --symbolic-full-name @{u})\"   # remote tracking ref of this branch\n   ```\n   Now `git diff HEAD` is the full set of edits to fold in, and the commits in\n   `base..HEAD` (base = `git merge-base HEAD origin/main`) are exactly the PR's\n   commits.\n\n2. **Save a recovery point** before rewriting history:\n   `git branch utm-submit-backup/$branch` (or note `git rev-parse HEAD`).\n\n3. **Assign each chunk to the commit that owns it.** Walk the hunks of\n   `git diff HEAD` (use `git diff -U0 HEAD` for tight, one-change-per-hunk\n   output). For each hunk, `git blame` the lines it modifies on HEAD to find the\n   commit that last changed them:\n   ```sh\n   git blame -L <start>,<end> HEAD -- <file>\n   ```\n   - If that commit is one of the PR's commits (in `base..HEAD`) → this chunk gets\n     **squashed into that commit**.\n   - If the lines are owned by a commit *outside* `base..HEAD` (code that shipped\n     before this PR), or the chunk adds a brand-new file/section unrelated to the\n     existing commits → it is a **logically distinct change** and becomes its own\n     **new commit** (the only time a new commit is allowed here). For a pure\n     insertion, blame the adjacent line to decide the owner.\n   - When a hunk's lines are owned by several PR commits, fold it into the newest\n     of them. Precision isn't critical — use judgment; the goal is that each\n     commit ends up self-consistent.\n\n4. **Fold the chunks in.** The reliable, non-interactive mechanism:\n   - Stage the chunks belonging to a target commit and create a fixup:\n     `git commit --fixup=<target-sha>` (stage whole files with `git add <file>`\n     when all of a file's changes map to one commit; for a file whose changes\n     span multiple targets, apply just the relevant hunks to the index with\n     `git apply --cached <patch>` — `git add -p` is interactive and may be\n     unavailable to your agent).\n   - Commit any logically-distinct chunks as normal new commits with a proper\n     `component: short description` message.\n   - Replay so the fixups collapse into their targets, run headless:\n     ```sh\n     GIT_SEQUENCE_EDITOR=true GIT_EDITOR=true git rebase -i --autosquash \"$base\"\n     ```\n     (The no-op editors make `-i --autosquash` run without prompting.)\n\n5. **Verify.** `git log --oneline \"$base\"..HEAD` should show the original commits\n   (plus any genuinely-distinct new ones) with **no `fixup!` lines left** and a\n   clean working tree. Confirm `git range-diff \"@{u}\"...HEAD` reflects only the\n   intended edits.\n\n6. **Force-push (safely).** Show the user the rewritten log and confirm, then:\n   ```sh\n   git push --force-with-lease\n   ```\n   `--force-with-lease` refuses to clobber unexpected remote work, unlike\n   `--force`. The PR updates automatically. **The workflow is done** — do not\n   re-prompt for issues or testing on an update.\n\n   *No `gh`?* The squash is pure git; only the push needs the remote. Push with\n   `--force-with-lease` to the branch's remote as usual.\n\n## Step 3b — Open a new PR\n\n1. **Make sure the change is committed cleanly.** If there are uncommitted edits,\n   commit them following the rules in *Commit & message policy* below — ideally\n   one commit (the PR must be a single feature/fix).\n\n2. **Push the branch** to the head repo (the user's fork, or `utmapp/UTM` if they\n   have push access). Confirm the remote if ambiguous:\n   `git push -u <remote> \"$branch\"`.\n\n3. **Find the issue(s) this resolves.** Search open issues for relevance and\n   present recommendations:\n   ```sh\n   gh issue list --repo utmapp/UTM --state open --search \"<keywords from the change>\" --limit 10\n   ```\n   Derive keywords from the touched component and the change's purpose. Show the\n   top few as `#<number> — <title>` with one-line relevance notes. Then ask the\n   user (free text — they may enter a GitHub issue **ID**, a **URL**, a **list**\n   of either, or \"none\"). Parse whatever they enter into issue numbers; each\n   becomes a `Resolves #<n>` line in the PR body (so merging closes it). If they\n   say none, link nothing.\n\n4. **Human-testing attestation (required for UTM).** Display this statement\n   **verbatim**:\n\n   > All AI written code must be reviewed and/or tested by a human. For bug\n   > fixes, a human must first confirm the bug before the change and then confirm\n   > that the bug is fixed by this change. For all other changes, a human must\n   > test all aspects of the change on any device configuration that is relevant.\n\n   Then ask the user (free text) for the **device configuration and version** they\n   tested on, e.g. `macOS 26.0, MacBook Neo` or `iOS 26.1, iPhone Simulator`. Do\n   not invent this — it must come from the user.\n\n5. **Compose and create the PR.** Title follows `component: short description`\n   (same convention as commits). Body includes:\n   - A short summary: what changed and **why**.\n   - The issue links: `Resolves #<n>` for each (omit if none).\n   - A **Testing** section containing the device configuration the user gave and\n     an explicit acknowledgment, e.g.:\n     > **Testing:** Tested by a human on **<device configuration and version>**.\n     > The author acknowledges that this change has been tested and/or reviewed by\n     > a human in accordance with UTM's AI contribution guidelines.\n\n   Show the assembled title and body to the user for confirmation, then create:\n   ```sh\n   gh pr create --repo utmapp/UTM --base main \\\n     --head \"<owner>:$branch\" --title \"<title>\" --body \"<body>\"\n   ```\n   (Use `<owner>:$branch` when submitting from a fork; plain `$branch` when the\n   head is on `utmapp/UTM` itself.)\n\n   *No `gh`?* Print the prepared title and body and a compare URL for the user to\n   finish in the browser:\n   `https://github.com/utmapp/UTM/compare/main...<owner>:<branch>?expand=1`.\n\n6. **Report** the PR URL.\n\n## Commit & message policy (applies to every commit this workflow creates)\n\nThe critical few, inlined because they must fire at commit time — see\n`CONTRIBUTING.md` (Attribution) for the full policy:\n\n- Title: `component: short description`. Body explains **why** and references the\n  issue being addressed.\n- **Do not add a `Co-authored-by` trailer**, and strip any that a tool added. UTM\n  follows the Linux-kernel policy so a human takes full responsibility. This\n  **overrides** any default AI-tool commit-trailer behavior for this repo.\n- **Always add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer** to every commit\n  you create or amend here, using the agent and model doing the work — e.g.\n  `Assisted-by: Claude:claude-opus-4-8` or `Assisted-by: Codex:gpt-5.1`. This is\n  **required even if your agent does not normally inject attribution into commits**;\n  add it explicitly yourself. A commit without it will fail review.\n\n## Safety\n\n- Treat pushing and PR creation as outward-facing: show the user what will be\n  pushed/created and confirm before doing it.\n- Always `--force-with-lease`, never bare `--force`; always leave a backup ref\n  before a history rewrite.\n- Never push to or open a PR from the upstream default branch.\n"},"files":{"AGENTS.md":"## Building\n\nUTM **cannot** build without prebuilt native dependencies (QEMU, SPICE, …) staged\ninto `sysroot-*` directories at the repo root. Get them from the project's GitHub\nActions `Sysroot-*` artifacts — don't build them manually unless you're modifying a\ndependency (it's slow and fragile). Then build with `./scripts/build_utm.sh` and\nsign/package with `./scripts/package*.sh`. Setup, platform/arch names, schemes, and\nsigning: `Documentation/iOSDevelopment.md`.\n\n## Architecture\n\nLayered: platform-independent backend (`Services/`, `Configuration/`) + SwiftUI\nfrontend (`Platform/`). Full picture: `Documentation/Architecture.md`. Load-bearing\ngotchas:\n\n- **Two VM backends** behind the `UTMVirtualMachine` protocol — `UTMQemuVirtualMachine`\n  (QEMU, via the QEMUKit/CocoaSpice packages) and `UTMAppleVirtualMachine`\n  (Virtualization.framework, macOS-only). Most backend code branches on which.\n- **Config is the serialization format** — parallel `Codable` trees\n  `UTMQemuConfiguration` and `UTMAppleConfiguration` (PLIST in the `.utm` bundle).\n  Add new fields there; don't extend the `Configuration/Legacy/` readers.\n- **macOS runs QEMU out-of-process** (XPC: `QEMUHelper`→`QEMULauncher`); host files\n  reach it only through a security-scoped-bookmark dance — respect it for anything\n  touching disk images, shared dirs, or file handles. **iOS runs QEMU in-thread and\n  can launch it only once per process** — this constrains start/stop logic.\n- **UTM SE** (`iOS-SE` scheme, `*-tci` sysroots) is gated by the `WITH_QEMU_TCI`\n  compile flag — grep for it before changing anything that differs between editions.\n\n## Generated files — never hand-edit\n\nEdit the generator and regenerate, not the output: `Configuration/QEMUConstantGenerated.swift`\n(`scripts/const-gen.py`), `Scripting/UTMScripting.swift` (`scripts/bridge-gen.sh`\nfrom `UTM.sdef`), and QAPI/QMP wrappers (produced by the dependency build).\nThird-party patches live in `patches/`.\n\n## Conventions\n\n**`CONTRIBUTING.md` is the source of truth** for style, concurrency, design, and\ncompatibility rules; `/utm-review` audits the diff against it. Two rules an agent\nmust apply unprompted — they override default behavior and are easy to violate:\n\n- **AI attribution:** every AI-assisted commit **must** carry an\n  `Assisted-by: AGENT:MODEL` trailer (e.g. `Assisted-by: Claude:claude-opus-4-8`) —\n  add it explicitly **even if your agent doesn't normally inject attribution\n  trailers**; the absence of a default trailer is not an excuse to omit it. Never\n  add `Co-authored-by` or session links (strip any a tool adds). Commit titles are\n  `component: short description` explaining *why*.\n- **Scope:** one feature/fix per PR; don't touch, refactor, or reformat unrelated\n  files; route new logging through `UTMLogging`/`logging` at `debug` level.\n\n## Contribution workflows\n\nBefore opening or updating a PR, run **`/utm-review`** then **`/utm-submit`** —\ncanonical instructions in `.agents/skills/{utm-review,utm-submit}/SKILL.md` (thin\nshims in `.claude/commands/` and `.opencode/command/`). If your agent doesn't\nauto-load them, read and follow those `SKILL.md` files directly.\n",".claude/commands/utm-submit.md":"---\ndescription: Submit the current UTM change as a PR to utmapp/UTM — ensures /utm-review ran, then updates an existing PR (blame-squash + force-push) or opens a new one (issue links + human-test attestation).\nargument-hint: [PR number or URL — for updates]\nallowed-tools: Bash, Read, Grep, Glob, Skill, Task, AskUserQuestion\n---\n\nRead and follow the canonical, agent-neutral workflow in\n`.agents/skills/utm-submit/SKILL.md`, executing its steps end to end.\n\nClaude Code specifics:\n- Where the workflow says \"ask the user\" for a fixed set of choices (e.g. the\n  Step 1 review gate), use AskUserQuestion.\n- To run the Step 1 review gate (\"/utm-review now\"), read and follow\n  `.agents/skills/utm-review/SKILL.md`.\n- For the interactive `gh auth login`, suggest the user type `! gh auth login`\n  so its output lands in this session.\n- Argument passed to this command (PR number or URL, if updating): $ARGUMENTS\n",".claude/commands/utm-review.md":"---\ndescription: Review pending UTM changes (standard code review + CONTRIBUTING.md/AGENTS.md audit) before submitting. Run before /utm-submit.\nargument-hint: [low|medium|high|max]\nallowed-tools: Bash, Read, Grep, Glob, Skill, Task\n---\n\nRead and follow the canonical, agent-neutral workflow in\n`.agents/skills/utm-review/SKILL.md`, executing its steps end to end.\n\nClaude Code specifics:\n- For the Step 3 standard code review, invoke the `code-review` skill at the\n  effort level passed here (default `high`).\n- Argument passed to this command (review effort, if any): $ARGUMENTS\n",".agents/skills/utm-review/SKILL.md":"---\nname: utm-review\ndescription: Review pending UTM changes before they are submitted. Runs a standard correctness/quality code review of the current branch's diff AND audits it against UTM's contribution rules in CONTRIBUTING.md and AGENTS.md — scope discipline, commit/PR format, AI attribution, logging hooks, generated files, UI/design philosophy, and platform compatibility. Use this before opening or updating a UTM pull request, whenever the user asks to review UTM changes, or when the user runs /utm-review. The /utm-submit workflow expects this to have been run on the current changes first.\n---\n\n# utm-review\n\nAgent-neutral instructions — follow them with whatever tools your agent provides\n(Claude Code, Codex, OpenCode, Antigravity, …). This is the canonical copy; the\nper-agent entries under `.claude/commands/`, `.opencode/command/`, etc. just\npoint here.\n\nGate a UTM change through two passes before it can be submitted:\n\n1. **A standard code review** for real correctness bugs and worthwhile cleanups.\n2. **A UTM contribution-guideline audit** — the part specific to this project and\n   the reason this workflow exists. UTM has firm rules (especially for\n   AI-assisted contributions) that a generic review won't check.\n\nReport everything you find. This workflow *reviews and reports*; it does not\npush, commit, or rewrite history — that is `/utm-submit`'s job.\n\nIf you were invoked with an argument, treat it as a review effort level\n(`low|medium|high|max`); default to `high`.\n\n## Step 1 — Read the authoritative rules\n\nRead `CONTRIBUTING.md` and `AGENTS.md` at the repo root now — they are the single\nsource of truth for what makes an acceptable UTM contribution, and Step 4 audits\nthe diff against them. (`AGENTS.md` overrides default tool behavior for this repo\n— notably the commit-trailer policy.)\n\n## Step 2 — Establish the review scope\n\nThe change under review is everything that will land in the PR: commits on this\nbranch since it diverged from the base, plus any uncommitted edits.\n\n```sh\nbase=$(git merge-base HEAD origin/main)\ngit log --oneline \"$base\"..HEAD          # the PR's commits\ngit --no-stat diff \"$base\"...HEAD        # committed change\ngit --no-stat diff HEAD                  # uncommitted change (if any)\ngit status --porcelain                   # untracked files\n```\n\nKeep the list of changed files handy — several guideline checks below hinge on\n*which* files were touched.\n\n## Step 3 — Standard code review\n\nRun a standard code review of the pending diff to surface correctness bugs and\nreuse/simplification/efficiency cleanups. Use whatever review capability your\nagent provides — for example a `/code-review` or `/review` command — or perform a\nfocused review pass yourself if none exists. If the change includes uncommitted\nedits the review tool doesn't pick up, review those yourself to the same\nstandard. Collect the findings; you'll merge them into one report.\n\n## Step 4 — UTM contribution-guideline audit\n\nAudit the diff against **every** rule in the `CONTRIBUTING.md` and `AGENTS.md` you\njust read — those files *are* the checklist, so keeping it there (rather than\ncopied here) means it never drifts out of sync. Cite `file:line` and the specific\nrule for each issue you raise. Judge the diff against all of it: style and\nconcurrency, design philosophy, platform compatibility, dependency-upstreaming,\nand the rest — defer to the files' wording instead of restating it.\n\nThese few are high-value, repo-specific, and the easiest to miss, so check them\nexplicitly:\n\n- **AI attribution.** Every AI-assisted commit in `\"$base\"..HEAD` **must** carry an\n  `Assisted-by: AGENT:MODEL` trailer — many agents omit it by default, so a missing\n  trailer is a common, easy-to-miss finding, not an acceptable absence — and **no\n  `Co-authored-by`** (strip it — UTM takes full human responsibility per the\n  Linux-kernel policy). Verify with `git log --format='%H%n%B' \"$base\"..HEAD`.\n- **Scope discipline.** One feature/fix; no edits, refactors, reformatting, or\n  whitespace/header churn in unrelated files; no stray logging.\n- **Generated files not hand-edited** — `Configuration/QEMUConstantGenerated.swift`,\n  `Scripting/UTMScripting.swift`, QAPI/QMP wrappers. Edit the generator instead.\n- **Logging** goes through `UTMLogging`/`logging` at `debug` level (warnings and\n  errors excepted); bring-up-only logging is removed before committing.\n- **UI strings** avoid jargon (\"GPU Acceleration\" not \"Venus\", \"Apple Silicon\"\n  not ARM64); commit titles are `component: short description` explaining *why*.\n\n## Step 5 — Report\n\nProduce one consolidated report:\n\n- A one-line summary of the change and its apparent scope.\n- **Correctness/quality findings** (from Step 3), each with `file:line`.\n- **Guideline findings** (from Step 4), each with `file:line` and the rule cited.\n- A short verdict: is this ready to submit, or what must change first? Call out\n  any blocker that `/utm-submit` will trip on (e.g. a `Co-authored-by` trailer,\n  an unrelated-file edit, or a missing `Assisted-by`).\n\nBe honest about uncertainty and avoid nitpicks a senior reviewer wouldn't raise\n(pre-existing issues, things a compiler/linter would catch, lines the change\ndidn't touch). The goal is a change that sails through human review on the UTM\nrepo, not a wall of pedantry.\n\n## Step 6 — Record that the review ran (handoff to /utm-submit)\n\n`/utm-submit` checks whether the current changes were reviewed. Leave a marker so\nit can confirm even across sessions or a different agent:\n\n```sh\ngitdir=$(git rev-parse --git-dir)\nhash=$( { git rev-parse HEAD; git diff \"$(git merge-base HEAD origin/main)\"...HEAD; git diff HEAD; } | git hash-object --stdin )\nprintf 'branch=%s\\nhead=%s\\ndiff_hash=%s\\nat=%s\\n' \"$(git rev-parse --abbrev-ref HEAD)\" \"$(git rev-parse HEAD)\" \"$hash\" \"$(date -u +%FT%TZ)\" > \"$gitdir/utm-review-marker\"\n```\n\nThen end with a clear, recognizable line, e.g.:\n`✅ utm-review complete — <branch> @ <short-sha>, N findings (M blockers).`\n\nThe `diff_hash` lets `/utm-submit` tell whether the changes it's about to submit\nare exactly the ones you reviewed, or whether they've been edited since.\n","Documentation/Architecture.md":"# Architecture\n\nUTM is built upon several pieces of technology, layered to provide compatibility across various host configurations. Below is a simplified diagram of key pieces of UTM with additional details provided below.\n\n```\n┌────────────────────┬──────────────────────┐\n│   iOS VM Display   │   macOS VM Display   │\n├────────────────────┴──────────────────────┤\n│                  SwiftUI                  │\n├───────────────────────────────────────────┤\n│             UTMVirtualMachine             │\n├────────────────┬──────────────────────────┤\n│   CocoaSpice   │                          │\n├────────────────┤ Virtualization.framework │\n│ QEMU (TCG/HVF) │                          │\n└────────────────┴──────────────────────────┘\n```\n\n## QEMU\n\nThe backbone of UTM is QEMU, which provides the emulation and virtualization engine. We run a custom [fork][1] which includes several features such as:\n\n* Building QEMU as a shared library\n* APRR support for jailbroken iOS\n* ARM64 TCTI from @ktemkin (JIT-less iOS support)\n* SPICE ANGLE backend for hardware GL acceleration\n\nThese features, along with several others (which are undergoing code review and may not have made the latest stable release), allow our fork to be optimized for Darwin.\n\n### Hypervisor.framework\n\nQEMU includes support for the `hvf` accelerator which provides same architecture virtualization (x86 -> x86 or ARM64 -> ARM64) on macOS. This framework is not available on iOS.\n\n### UTMQemu\n\nQEMU is linked as a shared library. On iOS, there is no ability to use `fork`, XPC (directly), or any other way of launching a new process. As a result, we run the QEMU main loop in a pthread which for all intents and purposes work like normal (the key exception being that you cannot spawn multiple instances of QEMU and since QEMU does not properly clean up resources, you cannot re-launch QEMU). On macOS, XPC is used to launch QEMU in a new process. `UTMQemu` manages the pthread or XPC implementation.\n\n#### XPC Helper\n\nOn macOS, spawning new processes is permitted but due to App Sandbox security requirements, we need some additional \"bootstrapping\" code to launch QEMU properly. The added benefit is that we only have to provide a single bundle identifier for the \"launcher\" executable rather than a different identifier for each QEMU executable (required for App Sandbox).\n\n`QEMUHelper` is an XPC helper with its own App Sandbox separate from the UTM main application. This improves security by providing an additional layer of separation. However, due to this extra care has to be taken when passing file handles and other system resources from the main app. For example, the Unix socket file used to communicate with SPICE is stored in a shared App Group directory. For disk images and shared directories that cannot be stored in the App Group, we have to do a complicated sandbox dance to get the right access permissions.\n\n1. The main application opens a `NSOpenPanel`, allowing the user to select a file/directory outside the sandbox. This returns a `NSURL` with the right security scope attached. If we take a regular bookmark of `NSURL` and pass it through XPC, the XPC process should also have access once it reads the bookmark back into a `NSURL`.\n2. However, the access to that file is only valid while the app is open. As soon as you close it, the app loses those permissions and must either prompt the user to select the file again or store a \"security scoped bookmark.\" The issue here is that a security scoped bookmark is only valid for the sandbox that created it. If we take a security scoped bookmark and pass it directly to the XPC process, it cannot get back a `NSURL`.\n3. So the solution is to take a *standard* bookmark, pass it to the XPC process, have the XPC process then create a *security scoped* bookmark and pass it back to the main process. The main process can now store this bookmark and have it work as intended when it passes it to the XPC process next launch.\n\nThe XPC helper spawns `QEMULauncher` with an inherited sandbox. This means it can access files that are accessible to the helper XPC. The launcher process is what runs QEMU. When a new file is opened (for example a new disk image is mounted), the main application will pass a bookmark to the helper XPC where it will call `-startAccessingSecurityScopedResource` which also applies to the child process (`QEMULauncher`). This way, QEMU does not have to have any knowledge of the App Sandbox.\n\n#### iOS Helper\n\nOn iOS 26 and later, tools such as `qemu-img` run out of process in `iOSHelper`, an ExtensionKit extension embedded in the app (`iOSHelper-SE` for UTM SE, as an extension's bundle identifier must start with its host's). It implements the same `QEMUHelperProtocol` as `QEMUHelper`, but loads the tool into its own process like the in-thread QEMU does, so each helper process runs one tool and the app launches a new one for the next. `UTMHelperProcess` launches the helper through `AppExtensionProcess` and hands its connection to `UTMProcess`, which otherwise works as on macOS: the helper has no access to the app's container, so files are passed as bookmarks, whose implicit security scope lets the process that resolves them access the file. QEMU itself still runs in the app process on iOS.\n\n### UTMConfiguration\n\nVM configuration is stored in a PLIST format. This PLIST maps to either a `UTMQemuConfiguration` or `UTMAppleConfiguration` structure which stores the underlying configuration data in a `Codable` interface for easy serialization.\n\n### UTMQemuSystem\n\n`UTMQemuSystem` maps `UTMQemuConfiguration` to command line arguments used to launch QEMU.\n\n### UTMQemuManager\n\nAfter a VM is launched, `UTMQemuManager` provides run-time services though the QMP protocol. These services include stopping/pausing/resuming the VM, taking snapshots, switching between mouse and tablet, mounting removable disk images, and more. The underlying transport for QMP is JSON (over a socket), so `UTMJSONStream` marshals the data to and from `NSDictionary` objects.\n\n#### QAPI\n\nQMP protocol is defined by the QAPI schema which is provided as a set of JSON files in QEMU. QEMU uses these JSON files to generate wrapper C functions for internal usage. UTM includes a modified function generator derived from QEMU's own script (`qapi-gen.py`) along with modified QAPI C visitors (for `NSDictionary`). This allows UTM to use the same commands, structures, and events that QEMU uses in a transparent way.\n\nFor example, when UTM makes a call to some QAPI command such as `qmp_blockdev_change_medium`, the generated C functions will automatically marshal the function arguments into a `NSDictionary` object which `UTMJSONStream` converts into JSON and sends it over the QMP socket. When a response is received, `UTMJSONStream` converts the JSON into a `NSDictionary` object, which goes through the generated C functions to unmarshal into some C structure which is returned from `qmp_blockdev_change_medium`. This is all transparent to the caller as long as they understand that the function call will block until the response is received so it must not be called from the main thread.\n\n## Virtualization.framework\n\nOn Apple Silicon Macs running macOS 12 or later, `Virtualization.framework` is provided by Apple to run macOS 12 guests.\n\n### UTMAppleConfiguration\n\nAs the backend is different, the configuration format UTM uses is also different and is represented in `UTMAppleConfiguration`. As this configuration is handed in Swift, the Codable protocol is used for serialization instead of a `NSDictionary` backing used in `UTMQemuConfiguration`. The Codable backing is more extendible as it allows more complex data to be represented without a lot of boilerplate.\n\n## CocoaSpice\n\nUTM uses the SPICE front-end with QEMU because it has more versatility than VNC to handle things like USB forwarding, multiple displays, and the ability to use a SPICE agent running on the guest to share clipboard and change the resolution. [CocoaSpice][2] is provided as a Swift package and acts as Cocoa/Objective-C bindings for SPICE GTK. CocoaSpice also provides a bridge between the Pixman framebuffer that SPICE uses and Metal textures that is used by MetalKit to render to screen.\n\n### UTMSpiceIO\n\n`UTMSpiceIO` connects `CocoaSpice` to UTM and is used by `UTMQemuVirtualMachine` to control the SPICE client and respond to client events.\n\n## UTMVirtualMachine\n\n`UTMVirtualMachine` provides file I/O operations for creating and saving .utm VM bundles as well as controls for the platform-specific layer above to do high level tasks like starting and stopping the VM. It is highest level of the \"backend\", providing a platform independent view of UTM virtual machines.\n\n### UTMQemuVirtualMachine\n\nThis subclass manages QEMU+CocoaSpice backend VMs.\n\n### UTMAppleVirtualMachine\n\nThis subclass manages Apple Virtualization.framework backend VMs.\n\n## Frontend\n\n### SwiftUI\n\nThe frontend for UTM is designed mostly in SwiftUI 3.0. That means the minimum supported operating system is iOS 15 and macOS 12 and is the main reason there are no plans to back-port UTM to earlier versions. Most views are designed to work on both macOS and iOS.\n\n#### UTMData\n\n`UTMData` is a SwiftUI `ObservableObject` that contains the \"state\" and is the \"single source of truth\" for the home view. It stores the list of VMs and functions to create, modify, move, etc VMs on that list. `UTMConfigurable` provides the state for VM configuration views.\n\n### iOS VM Display\n\nThe VM display uses UIKit as SwiftUI is not mature enough to do everything UTM needs. This includes the custom keyboard accessory view implemented in a NIB for emulating keys that are not available on the standard iOS keyboard.\n\n### macOS VM Display\n\nOn macOS, the VM display uses AppKit for similar reasons above.\n\n[1]: https://github.com/utmapp/qemu\n[2]: https://github.com/utmapp/CocoaSpice\n",".agents/skills/utm-submit/SKILL.md":"---\nname: utm-submit\ndescription: Submit the current UTM change as a pull request to github.com/utmapp/UTM. Ensures /utm-review has run, then either updates an existing PR (squashing each new edit into the commit that owns it and force-pushing) or opens a new PR (gathering issue links and a human-testing attestation). Use this when the user wants to submit, open, update, or push a UTM pull request, or runs /utm-submit.\n---\n\n# utm-submit\n\nAgent-neutral instructions — follow them with whatever tools your agent provides\n(Claude Code, Codex, OpenCode, Antigravity, …). This is the canonical copy; the\nper-agent entries under `.claude/commands/`, `.opencode/command/`, etc. just\npoint here.\n\nTurn the current change into a clean pull request against `utmapp/UTM`. Two\npaths: **update an existing PR** (squash edits into the commits they belong to,\nthen force-push) or **open a new PR** (collect issue links + a human-testing\nattestation, then create it).\n\nRead `CONTRIBUTING.md` and `AGENTS.md` first — they govern commit/PR format and\nthe AI-attribution policy enforced below. If you were invoked with an argument,\ntreat it as a PR number or URL to update.\n\n## Step 1 — Confirm the change was reviewed\n\n`/utm-submit` must never run on un-reviewed changes. Determine whether\n`/utm-review` was run on the *current* changes, in this order:\n\n1. If you already ran `/utm-review` earlier in this session against the current\n   changes, proceed.\n2. Otherwise read `\"$(git rev-parse --git-dir)/utm-review-marker\"` and recompute\n   the pending-diff hash exactly as utm-review did, then compare to `diff_hash`:\n   ```sh\n   { git rev-parse HEAD; git diff \"$(git merge-base HEAD origin/main)\"...HEAD; git diff HEAD; } | git hash-object --stdin\n   ```\n   If it matches the marker's `diff_hash`, the current changes were reviewed\n   (possibly in an earlier session or by another agent) — proceed, and say so. If\n   the marker exists but the hash differs, the code was edited after review —\n   treat as un-reviewed.\n3. Otherwise ask the user: *\"I can't confirm /utm-review ran on the current\n   changes. How do you want to proceed?\"* — offer: **Run /utm-review now**\n   (recommended; run that workflow, then continue), **I've already reviewed it\n   previously** (continue), **Cancel**.\n\nDo not silently skip this gate.\n\n## Step 2 — Locate the repo, branch, and any existing PR\n\n```sh\ngit remote -v                                   # find the remote pointing at utmapp/UTM\nbranch=$(git rev-parse --abbrev-ref HEAD)\n```\n\n- **Never submit from the default branch.** If `branch` is `main` (or otherwise\n  tracks the upstream default), stop and ask the user to move the work onto a\n  feature branch first (`git switch -c component/short-description`) — you cannot\n  open a `main → main` PR.\n- This workflow uses GitHub's `gh` CLI for everything that talks to GitHub. **If\n  `gh` is not installed or not authenticated**, tell the user and offer to set it\n  up: `brew install gh` then `gh auth login` (the login is interactive — have the\n  user run it themselves in their terminal). If they decline, fall back to the\n  manual paths noted in Steps 3a/3b.\n- Detect an existing open PR for this branch (or honor a PR number/URL you were\n  given):\n\n  ```sh\n  gh pr view \"$branch\" --repo utmapp/UTM --json number,url,state,baseRefName,headRefName 2>/dev/null\n  ```\n\n  An open PR → **Step 3a (update)**. No PR → **Step 3b (new)**.\n\n## Step 3a — Update an existing PR (squash, then force-push)\n\nUTM does **not** keep \"address review feedback\" commits. Every edit belongs in\nthe commit that introduced the lines it touches. The change history within a\nsingle PR must stay clean.\n\n1. **Express the update as working-tree changes on top of the PR's published\n   commits.** If you committed the update locally, undo just those commits so\n   their content returns to the working tree, leaving HEAD at the published PR\n   tip:\n   ```sh\n   git reset \"$(git rev-parse --abbrev-ref --symbolic-full-name @{u})\"   # remote tracking ref of this branch\n   ```\n   Now `git diff HEAD` is the full set of edits to fold in, and the commits in\n   `base..HEAD` (base = `git merge-base HEAD origin/main`) are exactly the PR's\n   commits.\n\n2. **Save a recovery point** before rewriting history:\n   `git branch utm-submit-backup/$branch` (or note `git rev-parse HEAD`).\n\n3. **Assign each chunk to the commit that owns it.** Walk the hunks of\n   `git diff HEAD` (use `git diff -U0 HEAD` for tight, one-change-per-hunk\n   output). For each hunk, `git blame` the lines it modifies on HEAD to find the\n   commit that last changed them:\n   ```sh\n   git blame -L <start>,<end> HEAD -- <file>\n   ```\n   - If that commit is one of the PR's commits (in `base..HEAD`) → this chunk gets\n     **squashed into that commit**.\n   - If the lines are owned by a commit *outside* `base..HEAD` (code that shipped\n     before this PR), or the chunk adds a brand-new file/section unrelated to the\n     existing commits → it is a **logically distinct change** and becomes its own\n     **new commit** (the only time a new commit is allowed here). For a pure\n     insertion, blame the adjacent line to decide the owner.\n   - When a hunk's lines are owned by several PR commits, fold it into the newest\n     of them. Precision isn't critical — use judgment; the goal is that each\n     commit ends up self-consistent.\n\n4. **Fold the chunks in.** The reliable, non-interactive mechanism:\n   - Stage the chunks belonging to a target commit and create a fixup:\n     `git commit --fixup=<target-sha>` (stage whole files with `git add <file>`\n     when all of a file's changes map to one commit; for a file whose changes\n     span multiple targets, apply just the relevant hunks to the index with\n     `git apply --cached <patch>` — `git add -p` is interactive and may be\n     unavailable to your agent).\n   - Commit any logically-distinct chunks as normal new commits with a proper\n     `component: short description` message.\n   - Replay so the fixups collapse into their targets, run headless:\n     ```sh\n     GIT_SEQUENCE_EDITOR=true GIT_EDITOR=true git rebase -i --autosquash \"$base\"\n     ```\n     (The no-op editors make `-i --autosquash` run without prompting.)\n\n5. **Verify.** `git log --oneline \"$base\"..HEAD` should show the original commits\n   (plus any genuinely-distinct new ones) with **no `fixup!` lines left** and a\n   clean working tree. Confirm `git range-diff \"@{u}\"...HEAD` reflects only the\n   intended edits.\n\n6. **Force-push (safely).** Show the user the rewritten log and confirm, then:\n   ```sh\n   git push --force-with-lease\n   ```\n   `--force-with-lease` refuses to clobber unexpected remote work, unlike\n   `--force`. The PR updates automatically. **The workflow is done** — do not\n   re-prompt for issues or testing on an update.\n\n   *No `gh`?* The squash is pure git; only the push needs the remote. Push with\n   `--force-with-lease` to the branch's remote as usual.\n\n## Step 3b — Open a new PR\n\n1. **Make sure the change is committed cleanly.** If there are uncommitted edits,\n   commit them following the rules in *Commit & message policy* below — ideally\n   one commit (the PR must be a single feature/fix).\n\n2. **Push the branch** to the head repo (the user's fork, or `utmapp/UTM` if they\n   have push access). Confirm the remote if ambiguous:\n   `git push -u <remote> \"$branch\"`.\n\n3. **Find the issue(s) this resolves.** Search open issues for relevance and\n   present recommendations:\n   ```sh\n   gh issue list --repo utmapp/UTM --state open --search \"<keywords from the change>\" --limit 10\n   ```\n   Derive keywords from the touched component and the change's purpose. Show the\n   top few as `#<number> — <title>` with one-line relevance notes. Then ask the\n   user (free text — they may enter a GitHub issue **ID**, a **URL**, a **list**\n   of either, or \"none\"). Parse whatever they enter into issue numbers; each\n   becomes a `Resolves #<n>` line in the PR body (so merging closes it). If they\n   say none, link nothing.\n\n4. **Human-testing attestation (required for UTM).** Display this statement\n   **verbatim**:\n\n   > All AI written code must be reviewed and/or tested by a human. For bug\n   > fixes, a human must first confirm the bug before the change and then confirm\n   > that the bug is fixed by this change. For all other changes, a human must\n   > test all aspects of the change on any device configuration that is relevant.\n\n   Then ask the user (free text) for the **device configuration and version** they\n   tested on, e.g. `macOS 26.0, MacBook Neo` or `iOS 26.1, iPhone Simulator`. Do\n   not invent this — it must come from the user.\n\n5. **Compose and create the PR.** Title follows `component: short description`\n   (same convention as commits). Body includes:\n   - A short summary: what changed and **why**.\n   - The issue links: `Resolves #<n>` for each (omit if none).\n   - A **Testing** section containing the device configuration the user gave and\n     an explicit acknowledgment, e.g.:\n     > **Testing:** Tested by a human on **<device configuration and version>**.\n     > The author acknowledges that this change has been tested and/or reviewed by\n     > a human in accordance with UTM's AI contribution guidelines.\n\n   Show the assembled title and body to the user for confirmation, then create:\n   ```sh\n   gh pr create --repo utmapp/UTM --base main \\\n     --head \"<owner>:$branch\" --title \"<title>\" --body \"<body>\"\n   ```\n   (Use `<owner>:$branch` when submitting from a fork; plain `$branch` when the\n   head is on `utmapp/UTM` itself.)\n\n   *No `gh`?* Print the prepared title and body and a compare URL for the user to\n   finish in the browser:\n   `https://github.com/utmapp/UTM/compare/main...<owner>:<branch>?expand=1`.\n\n6. **Report** the PR URL.\n\n## Commit & message policy (applies to every commit this workflow creates)\n\nThe critical few, inlined because they must fire at commit time — see\n`CONTRIBUTING.md` (Attribution) for the full policy:\n\n- Title: `component: short description`. Body explains **why** and references the\n  issue being addressed.\n- **Do not add a `Co-authored-by` trailer**, and strip any that a tool added. UTM\n  follows the Linux-kernel policy so a human takes full responsibility. This\n  **overrides** any default AI-tool commit-trailer behavior for this repo.\n- **Always add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer** to every commit\n  you create or amend here, using the agent and model doing the work — e.g.\n  `Assisted-by: Claude:claude-opus-4-8` or `Assisted-by: Codex:gpt-5.1`. This is\n  **required even if your agent does not normally inject attribution into commits**;\n  add it explicitly yourself. A commit without it will fail review.\n\n## Safety\n\n- Treat pushing and PR creation as outward-facing: show the user what will be\n  pushed/created and confirm before doing it.\n- Always `--force-with-lease`, never bare `--force`; always leave a backup ref\n  before a history rewrite.\n- Never push to or open a PR from the upstream default branch.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/utmapp/UTM/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"## Building\n\nUTM **cannot** build without prebuilt native dependencies (QEMU, SPICE, …) staged\ninto `sysroot-*` directories at the repo root. Get them from the project's GitHub\nActions `Sysroot-*` artifacts — don't build them manually unless you're modifying a\ndependency (it's slow and fragile). Then build with `./scripts/build_utm.sh` and\nsign/package with `./scripts/package*.sh`. Setup, platform/arch names, schemes, and\nsigning: `Documentation/iOSDevelopment.md`.\n\n## Architecture\n\nLayered: platform-independent backend (`Services/`, `Configuration/`) + SwiftUI\nfrontend (`Platform/`). Full picture: `Documentation/Architecture.md`. Load-bearing\ngotchas:\n\n- **Two VM backends** behind the `UTMVirtualMachine` protocol — `UTMQemuVirtualMachine`\n  (QEMU, via the QEMUKit/CocoaSpice packages) and `UTMAppleVirtualMachine`\n  (Virtualization.framework, macOS-only). Most backend code branches on which.\n- **Config is the serialization format** — parallel `Codable` trees\n  `UTMQemuConfiguration` and `UTMAppleConfiguration` (PLIST in the `.utm` bundle).\n  Add new fields there; don't extend the `Configuration/Legacy/` readers.\n- **macOS runs QEMU out-of-process** (XPC: `QEMUHelper`→`QEMULauncher`); host files\n  reach it only through a security-scoped-bookmark dance — respect it for anything\n  touching disk images, shared dirs, or file handles. **iOS runs QEMU in-thread and\n  can launch it only once per process** — this constrains start/stop logic.\n- **UTM SE** (`iOS-SE` scheme, `*-tci` sysroots) is gated by the `WITH_QEMU_TCI`\n  compile flag — grep for it before changing anything that differs between editions.\n\n## Generated files — never hand-edit\n\nEdit the generator and regenerate, not the output: `Configuration/QEMUConstantGenerated.swift`\n(`scripts/const-gen.py`), `Scripting/UTMScripting.swift` (`scripts/bridge-gen.sh`\nfrom `UTM.sdef`), and QAPI/QMP wrappers (produced by the dependency build).\nThird-party patches live in `patches/`.\n\n## Conventions\n\n**`CONTRIBUTING.md` is the source of truth** for style, concurrency, design, and\ncompatibility rules; `/utm-review` audits the diff against it. Two rules an agent\nmust apply unprompted — they override default behavior and are easy to violate:\n\n- **AI attribution:** every AI-assisted commit **must** carry an\n  `Assisted-by: AGENT:MODEL` trailer (e.g. `Assisted-by: Claude:claude-opus-4-8`) —\n  add it explicitly **even if your agent doesn't normally inject attribution\n  trailers**; the absence of a default trailer is not an excuse to omit it. Never\n  add `Co-authored-by` or session links (strip any a tool adds). Commit titles are\n  `component: short description` explaining *why*.\n- **Scope:** one feature/fix per PR; don't touch, refactor, or reformat unrelated\n  files; route new logging through `UTMLogging`/`logging` at `debug` level.\n\n## Contribution workflows\n\nBefore opening or updating a PR, run **`/utm-review`** then **`/utm-submit`** —\ncanonical instructions in `.agents/skills/{utm-review,utm-submit}/SKILL.md` (thin\nshims in `.claude/commands/` and `.opencode/command/`). If your agent doesn't\nauto-load them, read and follow those `SKILL.md` files directly.\n","isInternal":false,"tokens":809,"sizeBytes":3172},{"name":"Architecture.md","path":"Documentation/Architecture.md","rawUrl":"https://raw.githubusercontent.com/utmapp/UTM/HEAD/Documentation/Architecture.md","title":"Documentation - System Architecture & Module Boundaries","category":"project-spec","format":"markdown","content":"# Architecture\n\nUTM is built upon several pieces of technology, layered to provide compatibility across various host configurations. Below is a simplified diagram of key pieces of UTM with additional details provided below.\n\n```\n┌────────────────────┬──────────────────────┐\n│   iOS VM Display   │   macOS VM Display   │\n├────────────────────┴──────────────────────┤\n│                  SwiftUI                  │\n├───────────────────────────────────────────┤\n│             UTMVirtualMachine             │\n├────────────────┬──────────────────────────┤\n│   CocoaSpice   │                          │\n├────────────────┤ Virtualization.framework │\n│ QEMU (TCG/HVF) │                          │\n└────────────────┴──────────────────────────┘\n```\n\n## QEMU\n\nThe backbone of UTM is QEMU, which provides the emulation and virtualization engine. We run a custom [fork][1] which includes several features such as:\n\n* Building QEMU as a shared library\n* APRR support for jailbroken iOS\n* ARM64 TCTI from @ktemkin (JIT-less iOS support)\n* SPICE ANGLE backend for hardware GL acceleration\n\nThese features, along with several others (which are undergoing code review and may not have made the latest stable release), allow our fork to be optimized for Darwin.\n\n### Hypervisor.framework\n\nQEMU includes support for the `hvf` accelerator which provides same architecture virtualization (x86 -> x86 or ARM64 -> ARM64) on macOS. This framework is not available on iOS.\n\n### UTMQemu\n\nQEMU is linked as a shared library. On iOS, there is no ability to use `fork`, XPC (directly), or any other way of launching a new process. As a result, we run the QEMU main loop in a pthread which for all intents and purposes work like normal (the key exception being that you cannot spawn multiple instances of QEMU and since QEMU does not properly clean up resources, you cannot re-launch QEMU). On macOS, XPC is used to launch QEMU in a new process. `UTMQemu` manages the pthread or XPC implementation.\n\n#### XPC Helper\n\nOn macOS, spawning new processes is permitted but due to App Sandbox security requirements, we need some additional \"bootstrapping\" code to launch QEMU properly. The added benefit is that we only have to provide a single bundle identifier for the \"launcher\" executable rather than a different identifier for each QEMU executable (required for App Sandbox).\n\n`QEMUHelper` is an XPC helper with its own App Sandbox separate from the UTM main application. This improves security by providing an additional layer of separation. However, due to this extra care has to be taken when passing file handles and other system resources from the main app. For example, the Unix socket file used to communicate with SPICE is stored in a shared App Group directory. For disk images and shared directories that cannot be stored in the App Group, we have to do a complicated sandbox dance to get the right access permissions.\n\n1. The main application opens a `NSOpenPanel`, allowing the user to select a file/directory outside the sandbox. This returns a `NSURL` with the right security scope attached. If we take a regular bookmark of `NSURL` and pass it through XPC, the XPC process should also have access once it reads the bookmark back into a `NSURL`.\n2. However, the access to that file is only valid while the app is open. As soon as you close it, the app loses those permissions and must either prompt the user to select the file again or store a \"security scoped bookmark.\" The issue here is that a security scoped bookmark is only valid for the sandbox that created it. If we take a security scoped bookmark and pass it directly to the XPC process, it cannot get back a `NSURL`.\n3. So the solution is to take a *standard* bookmark, pass it to the XPC process, have the XPC process then create a *security scoped* bookmark and pass it back to the main process. The main process can now store this bookmark and have it work as intended when it passes it to the XPC process next launch.\n\nThe XPC helper spawns `QEMULauncher` with an inherited sandbox. This means it can access files that are accessible to the helper XPC. The launcher process is what runs QEMU. When a new file is opened (for example a new disk image is mounted), the main application will pass a bookmark to the helper XPC where it will call `-startAccessingSecurityScopedResource` which also applies to the child process (`QEMULauncher`). This way, QEMU does not have to have any knowledge of the App Sandbox.\n\n#### iOS Helper\n\nOn iOS 26 and later, tools such as `qemu-img` run out of process in `iOSHelper`, an ExtensionKit extension embedded in the app (`iOSHelper-SE` for UTM SE, as an extension's bundle identifier must start with its host's). It implements the same `QEMUHelperProtocol` as `QEMUHelper`, but loads the tool into its own process like the in-thread QEMU does, so each helper process runs one tool and the app launches a new one for the next. `UTMHelperProcess` launches the helper through `AppExtensionProcess` and hands its connection to `UTMProcess`, which otherwise works as on macOS: the helper has no access to the app's container, so files are passed as bookmarks, whose implicit security scope lets the process that resolves them access the file. QEMU itself still runs in the app process on iOS.\n\n### UTMConfiguration\n\nVM configuration is stored in a PLIST format. This PLIST maps to either a `UTMQemuConfiguration` or `UTMAppleConfiguration` structure which stores the underlying configuration data in a `Codable` interface for easy serialization.\n\n### UTMQemuSystem\n\n`UTMQemuSystem` maps `UTMQemuConfiguration` to command line arguments used to launch QEMU.\n\n### UTMQemuManager\n\nAfter a VM is launched, `UTMQemuManager` provides run-time services though the QMP protocol. These services include stopping/pausing/resuming the VM, taking snapshots, switching between mouse and tablet, mounting removable disk images, and more. The underlying transport for QMP is JSON (over a socket), so `UTMJSONStream` marshals the data to and from `NSDictionary` objects.\n\n#### QAPI\n\nQMP protocol is defined by the QAPI schema which is provided as a set of JSON files in QEMU. QEMU uses these JSON files to generate wrapper C functions for internal usage. UTM includes a modified function generator derived from QEMU's own script (`qapi-gen.py`) along with modified QAPI C visitors (for `NSDictionary`). This allows UTM to use the same commands, structures, and events that QEMU uses in a transparent way.\n\nFor example, when UTM makes a call to some QAPI command such as `qmp_blockdev_change_medium`, the generated C functions will automatically marshal the function arguments into a `NSDictionary` object which `UTMJSONStream` converts into JSON and sends it over the QMP socket. When a response is received, `UTMJSONStream` converts the JSON into a `NSDictionary` object, which goes through the generated C functions to unmarshal into some C structure which is returned from `qmp_blockdev_change_medium`. This is all transparent to the caller as long as they understand that the function call will block until the response is received so it must not be called from the main thread.\n\n## Virtualization.framework\n\nOn Apple Silicon Macs running macOS 12 or later, `Virtualization.framework` is provided by Apple to run macOS 12 guests.\n\n### UTMAppleConfiguration\n\nAs the backend is different, the configuration format UTM uses is also different and is represented in `UTMAppleConfiguration`. As this configuration is handed in Swift, the Codable protocol is used for serialization instead of a `NSDictionary` backing used in `UTMQemuConfiguration`. The Codable backing is more extendible as it allows more complex data to be represented without a lot of boilerplate.\n\n## CocoaSpice\n\nUTM uses the SPICE front-end with QEMU because it has more versatility than VNC to handle things like USB forwarding, multiple displays, and the ability to use a SPICE agent running on the guest to share clipboard and change the resolution. [CocoaSpice][2] is provided as a Swift package and acts as Cocoa/Objective-C bindings for SPICE GTK. CocoaSpice also provides a bridge between the Pixman framebuffer that SPICE uses and Metal textures that is used by MetalKit to render to screen.\n\n### UTMSpiceIO\n\n`UTMSpiceIO` connects `CocoaSpice` to UTM and is used by `UTMQemuVirtualMachine` to control the SPICE client and respond to client events.\n\n## UTMVirtualMachine\n\n`UTMVirtualMachine` provides file I/O operations for creating and saving .utm VM bundles as well as controls for the platform-specific layer above to do high level tasks like starting and stopping the VM. It is highest level of the \"backend\", providing a platform independent view of UTM virtual machines.\n\n### UTMQemuVirtualMachine\n\nThis subclass manages QEMU+CocoaSpice backend VMs.\n\n### UTMAppleVirtualMachine\n\nThis subclass manages Apple Virtualization.framework backend VMs.\n\n## Frontend\n\n### SwiftUI\n\nThe frontend for UTM is designed mostly in SwiftUI 3.0. That means the minimum supported operating system is iOS 15 and macOS 12 and is the main reason there are no plans to back-port UTM to earlier versions. Most views are designed to work on both macOS and iOS.\n\n#### UTMData\n\n`UTMData` is a SwiftUI `ObservableObject` that contains the \"state\" and is the \"single source of truth\" for the home view. It stores the list of VMs and functions to create, modify, move, etc VMs on that list. `UTMConfigurable` provides the state for VM configuration views.\n\n### iOS VM Display\n\nThe VM display uses UIKit as SwiftUI is not mature enough to do everything UTM needs. This includes the custom keyboard accessory view implemented in a NIB for emulating keys that are not available on the standard iOS keyboard.\n\n### macOS VM Display\n\nOn macOS, the VM display uses AppKit for similar reasons above.\n\n[1]: https://github.com/utmapp/qemu\n[2]: https://github.com/utmapp/CocoaSpice\n","isInternal":false,"tokens":2210,"sizeBytes":10398},{"name":"SKILL.md","path":".agents/skills/utm-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/utmapp/UTM/HEAD/.agents/skills/utm-review/SKILL.md","title":"utm-review","category":"anthropic-skill","format":"markdown","content":"---\nname: utm-review\ndescription: Review pending UTM changes before they are submitted. Runs a standard correctness/quality code review of the current branch's diff AND audits it against UTM's contribution rules in CONTRIBUTING.md and AGENTS.md — scope discipline, commit/PR format, AI attribution, logging hooks, generated files, UI/design philosophy, and platform compatibility. Use this before opening or updating a UTM pull request, whenever the user asks to review UTM changes, or when the user runs /utm-review. The /utm-submit workflow expects this to have been run on the current changes first.\n---\n\n# utm-review\n\nAgent-neutral instructions — follow them with whatever tools your agent provides\n(Claude Code, Codex, OpenCode, Antigravity, …). This is the canonical copy; the\nper-agent entries under `.claude/commands/`, `.opencode/command/`, etc. just\npoint here.\n\nGate a UTM change through two passes before it can be submitted:\n\n1. **A standard code review** for real correctness bugs and worthwhile cleanups.\n2. **A UTM contribution-guideline audit** — the part specific to this project and\n   the reason this workflow exists. UTM has firm rules (especially for\n   AI-assisted contributions) that a generic review won't check.\n\nReport everything you find. This workflow *reviews and reports*; it does not\npush, commit, or rewrite history — that is `/utm-submit`'s job.\n\nIf you were invoked with an argument, treat it as a review effort level\n(`low|medium|high|max`); default to `high`.\n\n## Step 1 — Read the authoritative rules\n\nRead `CONTRIBUTING.md` and `AGENTS.md` at the repo root now — they are the single\nsource of truth for what makes an acceptable UTM contribution, and Step 4 audits\nthe diff against them. (`AGENTS.md` overrides default tool behavior for this repo\n— notably the commit-trailer policy.)\n\n## Step 2 — Establish the review scope\n\nThe change under review is everything that will land in the PR: commits on this\nbranch since it diverged from the base, plus any uncommitted edits.\n\n```sh\nbase=$(git merge-base HEAD origin/main)\ngit log --oneline \"$base\"..HEAD          # the PR's commits\ngit --no-stat diff \"$base\"...HEAD        # committed change\ngit --no-stat diff HEAD                  # uncommitted change (if any)\ngit status --porcelain                   # untracked files\n```\n\nKeep the list of changed files handy — several guideline checks below hinge on\n*which* files were touched.\n\n## Step 3 — Standard code review\n\nRun a standard code review of the pending diff to surface correctness bugs and\nreuse/simplification/efficiency cleanups. Use whatever review capability your\nagent provides — for example a `/code-review` or `/review` command — or perform a\nfocused review pass yourself if none exists. If the change includes uncommitted\nedits the review tool doesn't pick up, review those yourself to the same\nstandard. Collect the findings; you'll merge them into one report.\n\n## Step 4 — UTM contribution-guideline audit\n\nAudit the diff against **every** rule in the `CONTRIBUTING.md` and `AGENTS.md` you\njust read — those files *are* the checklist, so keeping it there (rather than\ncopied here) means it never drifts out of sync. Cite `file:line` and the specific\nrule for each issue you raise. Judge the diff against all of it: style and\nconcurrency, design philosophy, platform compatibility, dependency-upstreaming,\nand the rest — defer to the files' wording instead of restating it.\n\nThese few are high-value, repo-specific, and the easiest to miss, so check them\nexplicitly:\n\n- **AI attribution.** Every AI-assisted commit in `\"$base\"..HEAD` **must** carry an\n  `Assisted-by: AGENT:MODEL` trailer — many agents omit it by default, so a missing\n  trailer is a common, easy-to-miss finding, not an acceptable absence — and **no\n  `Co-authored-by`** (strip it — UTM takes full human responsibility per the\n  Linux-kernel policy). Verify with `git log --format='%H%n%B' \"$base\"..HEAD`.\n- **Scope discipline.** One feature/fix; no edits, refactors, reformatting, or\n  whitespace/header churn in unrelated files; no stray logging.\n- **Generated files not hand-edited** — `Configuration/QEMUConstantGenerated.swift`,\n  `Scripting/UTMScripting.swift`, QAPI/QMP wrappers. Edit the generator instead.\n- **Logging** goes through `UTMLogging`/`logging` at `debug` level (warnings and\n  errors excepted); bring-up-only logging is removed before committing.\n- **UI strings** avoid jargon (\"GPU Acceleration\" not \"Venus\", \"Apple Silicon\"\n  not ARM64); commit titles are `component: short description` explaining *why*.\n\n## Step 5 — Report\n\nProduce one consolidated report:\n\n- A one-line summary of the change and its apparent scope.\n- **Correctness/quality findings** (from Step 3), each with `file:line`.\n- **Guideline findings** (from Step 4), each with `file:line` and the rule cited.\n- A short verdict: is this ready to submit, or what must change first? Call out\n  any blocker that `/utm-submit` will trip on (e.g. a `Co-authored-by` trailer,\n  an unrelated-file edit, or a missing `Assisted-by`).\n\nBe honest about uncertainty and avoid nitpicks a senior reviewer wouldn't raise\n(pre-existing issues, things a compiler/linter would catch, lines the change\ndidn't touch). The goal is a change that sails through human review on the UTM\nrepo, not a wall of pedantry.\n\n## Step 6 — Record that the review ran (handoff to /utm-submit)\n\n`/utm-submit` checks whether the current changes were reviewed. Leave a marker so\nit can confirm even across sessions or a different agent:\n\n```sh\ngitdir=$(git rev-parse --git-dir)\nhash=$( { git rev-parse HEAD; git diff \"$(git merge-base HEAD origin/main)\"...HEAD; git diff HEAD; } | git hash-object --stdin )\nprintf 'branch=%s\\nhead=%s\\ndiff_hash=%s\\nat=%s\\n' \"$(git rev-parse --abbrev-ref HEAD)\" \"$(git rev-parse HEAD)\" \"$hash\" \"$(date -u +%FT%TZ)\" > \"$gitdir/utm-review-marker\"\n```\n\nThen end with a clear, recognizable line, e.g.:\n`✅ utm-review complete — <branch> @ <short-sha>, N findings (M blockers).`\n\nThe `diff_hash` lets `/utm-submit` tell whether the changes it's about to submit\nare exactly the ones you reviewed, or whether they've been edited since.\n","frontmatter":{"name":"utm-review","description":"Review pending UTM changes before they are submitted. Runs a standard correctness/quality code review of the current branch's diff AND audits it against UTM's contribution rules in CONTRIBUTING.md and AGENTS.md — scope discipline, commit/PR format, AI attribution, logging hooks, generated files, UI/design philosophy, and platform compatibility. Use this before opening or updating a UTM pull request, whenever the user asks to review UTM changes, or when the user runs /utm-review. The /utm-submit workflow expects this to have been run on the current changes first."},"isInternal":false,"tokens":1486,"sizeBytes":6179},{"name":"SKILL.md","path":".agents/skills/utm-submit/SKILL.md","rawUrl":"https://raw.githubusercontent.com/utmapp/UTM/HEAD/.agents/skills/utm-submit/SKILL.md","title":"utm-submit","category":"anthropic-skill","format":"markdown","content":"---\nname: utm-submit\ndescription: Submit the current UTM change as a pull request to github.com/utmapp/UTM. Ensures /utm-review has run, then either updates an existing PR (squashing each new edit into the commit that owns it and force-pushing) or opens a new PR (gathering issue links and a human-testing attestation). Use this when the user wants to submit, open, update, or push a UTM pull request, or runs /utm-submit.\n---\n\n# utm-submit\n\nAgent-neutral instructions — follow them with whatever tools your agent provides\n(Claude Code, Codex, OpenCode, Antigravity, …). This is the canonical copy; the\nper-agent entries under `.claude/commands/`, `.opencode/command/`, etc. just\npoint here.\n\nTurn the current change into a clean pull request against `utmapp/UTM`. Two\npaths: **update an existing PR** (squash edits into the commits they belong to,\nthen force-push) or **open a new PR** (collect issue links + a human-testing\nattestation, then create it).\n\nRead `CONTRIBUTING.md` and `AGENTS.md` first — they govern commit/PR format and\nthe AI-attribution policy enforced below. If you were invoked with an argument,\ntreat it as a PR number or URL to update.\n\n## Step 1 — Confirm the change was reviewed\n\n`/utm-submit` must never run on un-reviewed changes. Determine whether\n`/utm-review` was run on the *current* changes, in this order:\n\n1. If you already ran `/utm-review` earlier in this session against the current\n   changes, proceed.\n2. Otherwise read `\"$(git rev-parse --git-dir)/utm-review-marker\"` and recompute\n   the pending-diff hash exactly as utm-review did, then compare to `diff_hash`:\n   ```sh\n   { git rev-parse HEAD; git diff \"$(git merge-base HEAD origin/main)\"...HEAD; git diff HEAD; } | git hash-object --stdin\n   ```\n   If it matches the marker's `diff_hash`, the current changes were reviewed\n   (possibly in an earlier session or by another agent) — proceed, and say so. If\n   the marker exists but the hash differs, the code was edited after review —\n   treat as un-reviewed.\n3. Otherwise ask the user: *\"I can't confirm /utm-review ran on the current\n   changes. How do you want to proceed?\"* — offer: **Run /utm-review now**\n   (recommended; run that workflow, then continue), **I've already reviewed it\n   previously** (continue), **Cancel**.\n\nDo not silently skip this gate.\n\n## Step 2 — Locate the repo, branch, and any existing PR\n\n```sh\ngit remote -v                                   # find the remote pointing at utmapp/UTM\nbranch=$(git rev-parse --abbrev-ref HEAD)\n```\n\n- **Never submit from the default branch.** If `branch` is `main` (or otherwise\n  tracks the upstream default), stop and ask the user to move the work onto a\n  feature branch first (`git switch -c component/short-description`) — you cannot\n  open a `main → main` PR.\n- This workflow uses GitHub's `gh` CLI for everything that talks to GitHub. **If\n  `gh` is not installed or not authenticated**, tell the user and offer to set it\n  up: `brew install gh` then `gh auth login` (the login is interactive — have the\n  user run it themselves in their terminal). If they decline, fall back to the\n  manual paths noted in Steps 3a/3b.\n- Detect an existing open PR for this branch (or honor a PR number/URL you were\n  given):\n\n  ```sh\n  gh pr view \"$branch\" --repo utmapp/UTM --json number,url,state,baseRefName,headRefName 2>/dev/null\n  ```\n\n  An open PR → **Step 3a (update)**. No PR → **Step 3b (new)**.\n\n## Step 3a — Update an existing PR (squash, then force-push)\n\nUTM does **not** keep \"address review feedback\" commits. Every edit belongs in\nthe commit that introduced the lines it touches. The change history within a\nsingle PR must stay clean.\n\n1. **Express the update as working-tree changes on top of the PR's published\n   commits.** If you committed the update locally, undo just those commits so\n   their content returns to the working tree, leaving HEAD at the published PR\n   tip:\n   ```sh\n   git reset \"$(git rev-parse --abbrev-ref --symbolic-full-name @{u})\"   # remote tracking ref of this branch\n   ```\n   Now `git diff HEAD` is the full set of edits to fold in, and the commits in\n   `base..HEAD` (base = `git merge-base HEAD origin/main`) are exactly the PR's\n   commits.\n\n2. **Save a recovery point** before rewriting history:\n   `git branch utm-submit-backup/$branch` (or note `git rev-parse HEAD`).\n\n3. **Assign each chunk to the commit that owns it.** Walk the hunks of\n   `git diff HEAD` (use `git diff -U0 HEAD` for tight, one-change-per-hunk\n   output). For each hunk, `git blame` the lines it modifies on HEAD to find the\n   commit that last changed them:\n   ```sh\n   git blame -L <start>,<end> HEAD -- <file>\n   ```\n   - If that commit is one of the PR's commits (in `base..HEAD`) → this chunk gets\n     **squashed into that commit**.\n   - If the lines are owned by a commit *outside* `base..HEAD` (code that shipped\n     before this PR), or the chunk adds a brand-new file/section unrelated to the\n     existing commits → it is a **logically distinct change** and becomes its own\n     **new commit** (the only time a new commit is allowed here). For a pure\n     insertion, blame the adjacent line to decide the owner.\n   - When a hunk's lines are owned by several PR commits, fold it into the newest\n     of them. Precision isn't critical — use judgment; the goal is that each\n     commit ends up self-consistent.\n\n4. **Fold the chunks in.** The reliable, non-interactive mechanism:\n   - Stage the chunks belonging to a target commit and create a fixup:\n     `git commit --fixup=<target-sha>` (stage whole files with `git add <file>`\n     when all of a file's changes map to one commit; for a file whose changes\n     span multiple targets, apply just the relevant hunks to the index with\n     `git apply --cached <patch>` — `git add -p` is interactive and may be\n     unavailable to your agent).\n   - Commit any logically-distinct chunks as normal new commits with a proper\n     `component: short description` message.\n   - Replay so the fixups collapse into their targets, run headless:\n     ```sh\n     GIT_SEQUENCE_EDITOR=true GIT_EDITOR=true git rebase -i --autosquash \"$base\"\n     ```\n     (The no-op editors make `-i --autosquash` run without prompting.)\n\n5. **Verify.** `git log --oneline \"$base\"..HEAD` should show the original commits\n   (plus any genuinely-distinct new ones) with **no `fixup!` lines left** and a\n   clean working tree. Confirm `git range-diff \"@{u}\"...HEAD` reflects only the\n   intended edits.\n\n6. **Force-push (safely).** Show the user the rewritten log and confirm, then:\n   ```sh\n   git push --force-with-lease\n   ```\n   `--force-with-lease` refuses to clobber unexpected remote work, unlike\n   `--force`. The PR updates automatically. **The workflow is done** — do not\n   re-prompt for issues or testing on an update.\n\n   *No `gh`?* The squash is pure git; only the push needs the remote. Push with\n   `--force-with-lease` to the branch's remote as usual.\n\n## Step 3b — Open a new PR\n\n1. **Make sure the change is committed cleanly.** If there are uncommitted edits,\n   commit them following the rules in *Commit & message policy* below — ideally\n   one commit (the PR must be a single feature/fix).\n\n2. **Push the branch** to the head repo (the user's fork, or `utmapp/UTM` if they\n   have push access). Confirm the remote if ambiguous:\n   `git push -u <remote> \"$branch\"`.\n\n3. **Find the issue(s) this resolves.** Search open issues for relevance and\n   present recommendations:\n   ```sh\n   gh issue list --repo utmapp/UTM --state open --search \"<keywords from the change>\" --limit 10\n   ```\n   Derive keywords from the touched component and the change's purpose. Show the\n   top few as `#<number> — <title>` with one-line relevance notes. Then ask the\n   user (free text — they may enter a GitHub issue **ID**, a **URL**, a **list**\n   of either, or \"none\"). Parse whatever they enter into issue numbers; each\n   becomes a `Resolves #<n>` line in the PR body (so merging closes it). If they\n   say none, link nothing.\n\n4. **Human-testing attestation (required for UTM).** Display this statement\n   **verbatim**:\n\n   > All AI written code must be reviewed and/or tested by a human. For bug\n   > fixes, a human must first confirm the bug before the change and then confirm\n   > that the bug is fixed by this change. For all other changes, a human must\n   > test all aspects of the change on any device configuration that is relevant.\n\n   Then ask the user (free text) for the **device configuration and version** they\n   tested on, e.g. `macOS 26.0, MacBook Neo` or `iOS 26.1, iPhone Simulator`. Do\n   not invent this — it must come from the user.\n\n5. **Compose and create the PR.** Title follows `component: short description`\n   (same convention as commits). Body includes:\n   - A short summary: what changed and **why**.\n   - The issue links: `Resolves #<n>` for each (omit if none).\n   - A **Testing** section containing the device configuration the user gave and\n     an explicit acknowledgment, e.g.:\n     > **Testing:** Tested by a human on **<device configuration and version>**.\n     > The author acknowledges that this change has been tested and/or reviewed by\n     > a human in accordance with UTM's AI contribution guidelines.\n\n   Show the assembled title and body to the user for confirmation, then create:\n   ```sh\n   gh pr create --repo utmapp/UTM --base main \\\n     --head \"<owner>:$branch\" --title \"<title>\" --body \"<body>\"\n   ```\n   (Use `<owner>:$branch` when submitting from a fork; plain `$branch` when the\n   head is on `utmapp/UTM` itself.)\n\n   *No `gh`?* Print the prepared title and body and a compare URL for the user to\n   finish in the browser:\n   `https://github.com/utmapp/UTM/compare/main...<owner>:<branch>?expand=1`.\n\n6. **Report** the PR URL.\n\n## Commit & message policy (applies to every commit this workflow creates)\n\nThe critical few, inlined because they must fire at commit time — see\n`CONTRIBUTING.md` (Attribution) for the full policy:\n\n- Title: `component: short description`. Body explains **why** and references the\n  issue being addressed.\n- **Do not add a `Co-authored-by` trailer**, and strip any that a tool added. UTM\n  follows the Linux-kernel policy so a human takes full responsibility. This\n  **overrides** any default AI-tool commit-trailer behavior for this repo.\n- **Always add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer** to every commit\n  you create or amend here, using the agent and model doing the work — e.g.\n  `Assisted-by: Claude:claude-opus-4-8` or `Assisted-by: Codex:gpt-5.1`. This is\n  **required even if your agent does not normally inject attribution into commits**;\n  add it explicitly yourself. A commit without it will fail review.\n\n## Safety\n\n- Treat pushing and PR creation as outward-facing: show the user what will be\n  pushed/created and confirm before doing it.\n- Always `--force-with-lease`, never bare `--force`; always leave a backup ref\n  before a history rewrite.\n- Never push to or open a PR from the upstream default branch.\n","frontmatter":{"name":"utm-submit","description":"Submit the current UTM change as a pull request to github.com/utmapp/UTM. Ensures /utm-review has run, then either updates an existing PR (squashing each new edit into the commit that owns it and force-pushing) or opens a new PR (gathering issue links and a human-testing attestation). Use this when the user wants to submit, open, update, or push a UTM pull request, or runs /utm-submit."},"isInternal":false,"tokens":2810,"sizeBytes":11034},{"name":"utm-review.md","path":".claude/commands/utm-review.md","rawUrl":"https://raw.githubusercontent.com/utmapp/UTM/HEAD/.claude/commands/utm-review.md","title":"Command: /utm-review","category":"command-prompt","format":"markdown","content":"---\ndescription: Review pending UTM changes (standard code review + CONTRIBUTING.md/AGENTS.md audit) before submitting. Run before /utm-submit.\nargument-hint: [low|medium|high|max]\nallowed-tools: Bash, Read, Grep, Glob, Skill, Task\n---\n\nRead and follow the canonical, agent-neutral workflow in\n`.agents/skills/utm-review/SKILL.md`, executing its steps end to end.\n\nClaude Code specifics:\n- For the Step 3 standard code review, invoke the `code-review` skill at the\n  effort level passed here (default `high`).\n- Argument passed to this command (review effort, if any): $ARGUMENTS\n","frontmatter":{"description":"Review pending UTM changes (standard code review + CONTRIBUTING.md/AGENTS.md audit) before submitting. Run before /utm-submit.","argument-hint":["low|medium|high|max"],"allowed-tools":"Bash, Read, Grep, Glob, Skill, Task","allowedTools":["Bash,","Read,","Grep,","Glob,","Skill,","Task"]},"allowedTools":["Bash,","Read,","Grep,","Glob,","Skill,","Task"],"isInternal":false,"tokens":144,"sizeBytes":580},{"name":"utm-submit.md","path":".claude/commands/utm-submit.md","rawUrl":"https://raw.githubusercontent.com/utmapp/UTM/HEAD/.claude/commands/utm-submit.md","title":"Command: /utm-submit","category":"command-prompt","format":"markdown","content":"---\ndescription: Submit the current UTM change as a PR to utmapp/UTM — ensures /utm-review ran, then updates an existing PR (blame-squash + force-push) or opens a new one (issue links + human-test attestation).\nargument-hint: [PR number or URL — for updates]\nallowed-tools: Bash, Read, Grep, Glob, Skill, Task, AskUserQuestion\n---\n\nRead and follow the canonical, agent-neutral workflow in\n`.agents/skills/utm-submit/SKILL.md`, executing its steps end to end.\n\nClaude Code specifics:\n- Where the workflow says \"ask the user\" for a fixed set of choices (e.g. the\n  Step 1 review gate), use AskUserQuestion.\n- To run the Step 1 review gate (\"/utm-review now\"), read and follow\n  `.agents/skills/utm-review/SKILL.md`.\n- For the interactive `gh auth login`, suggest the user type `! gh auth login`\n  so its output lands in this session.\n- Argument passed to this command (PR number or URL, if updating): $ARGUMENTS\n","frontmatter":{"description":"Submit the current UTM change as a PR to utmapp/UTM — ensures /utm-review ran, then updates an existing PR (blame-squash + force-push) or opens a new one (issue links + human-test attestation).","argument-hint":["PR number or URL — for updates"],"allowed-tools":"Bash, Read, Grep, Glob, Skill, Task, AskUserQuestion","allowedTools":["Bash,","Read,","Grep,","Glob,","Skill,","Task,","AskUserQuestion"]},"allowedTools":["Bash,","Read,","Grep,","Glob,","Skill,","Task,","AskUserQuestion"],"isInternal":false,"tokens":237,"sizeBytes":914}],"systemPromptSnippet":"<yakaai_skills repo=\"utmapp/UTM\">\n<!-- File: AGENTS.md (Tokens: ~809 | Category: root-instruction) -->\n## Building\n\nUTM **cannot** build without prebuilt native dependencies (QEMU, SPICE, …) staged\ninto `sysroot-*` directories at the repo root. Get them from the project's GitHub\nActions `Sysroot-*` artifacts — don't build them manually unless you're modifying a\ndependency (it's slow and fragile). Then build with `./scripts/build_utm.sh` and\nsign/package with `./scripts/package*.sh`. Setup, platform/arch names, schemes, and\nsigning: `Documentation/iOSDevelopment.md`.\n\n## Architecture\n\nLayered: platform-independent backend (`Services/`, `Configuration/`) + SwiftUI\nfrontend (`Platform/`). Full picture: `Documentation/Architecture.md`. Load-bearing\ngotchas:\n\n- **Two VM backends** behind the `UTMVirtualMachine` protocol — `UTMQemuVirtualMachine`\n  (QEMU, via the QEMUKit/CocoaSpice packages) and `UTMAppleVirtualMachine`\n  (Virtualization.framework, macOS-only). Most backend code branches on which.\n- **Config is the serialization format** — parallel `Codable` trees\n  `UTMQemuConfiguration` and `UTMAppleConfiguration` (PLIST in the `.utm` bundle).\n  Add new fields there; don't extend the `Configuration/Legacy/` readers.\n- **macOS runs QEMU out-of-process** (XPC: `QEMUHelper`→`QEMULauncher`); host files\n  reach it only through a security-scoped-bookmark dance — respect it for anything\n  touching disk images, shared dirs, or file handles. **iOS runs QEMU in-thread and\n  can launch it only once per process** — this constrains start/stop logic.\n- **UTM SE** (`iOS-SE` scheme, `*-tci` sysroots) is gated by the `WITH_QEMU_TCI`\n  compile flag — grep for it before changing anything that differs between editions.\n\n## Generated files — never hand-edit\n\nEdit the generator and regenerate, not the output: `Configuration/QEMUConstantGenerated.swift`\n(`scripts/const-gen.py`), `Scripting/UTMScripting.swift` (`scripts/bridge-gen.sh`\nfrom `UTM.sdef`), and QAPI/QMP wrappers (produced by the dependency build).\nThird-party patches live in `patches/`.\n\n## Conventions\n\n**`CONTRIBUTING.md` is the source of truth** for style, concurrency, design, and\ncompatibility rules; `/utm-review` audits the diff against it. Two rules an agent\nmust apply unprompted — they override default behavior and are easy to violate:\n\n- **AI attribution:** every AI-assisted commit **must** carry an\n  `Assisted-by: AGENT:MODEL` trailer (e.g. `Assisted-by: Claude:claude-opus-4-8`) —\n  add it explicitly **even if your agent doesn't normally inject attribution\n  trailers**; the absence of a default trailer is not an excuse to omit it. Never\n  add `Co-authored-by` or session links (strip any a tool adds). Commit titles are\n  `component: short description` explaining *why*.\n- **Scope:** one feature/fix per PR; don't touch, refactor, or reformat unrelated\n  files; route new logging through `UTMLogging`/`logging` at `debug` level.\n\n## Contribution workflows\n\nBefore opening or updating a PR, run **`/utm-review`** then **`/utm-submit`** —\ncanonical instructions in `.agents/skills/{utm-review,utm-submit}/SKILL.md` (thin\nshims in `.claude/commands/` and `.opencode/command/`). If your agent doesn't\nauto-load them, read and follow those `SKILL.md` files directly.\n\n<!-- File: Documentation/Architecture.md (Tokens: ~2210 | Category: project-spec) -->\n# Architecture\n\nUTM is built upon several pieces of technology, layered to provide compatibility across various host configurations. Below is a simplified diagram of key pieces of UTM with additional details provided below.\n\n```\n┌────────────────────┬──────────────────────┐\n│   iOS VM Display   │   macOS VM Display   │\n├────────────────────┴──────────────────────┤\n│                  SwiftUI                  │\n├───────────────────────────────────────────┤\n│             UTMVirtualMachine             │\n├────────────────┬──────────────────────────┤\n│   CocoaSpice   │                          │\n├────────────────┤ Virtualization.framework │\n│ QEMU (TCG/HVF) │                          │\n└────────────────┴──────────────────────────┘\n```\n\n## QEMU\n\nThe backbone of UTM is QEMU, which provides the emulation and virtualization engine. We run a custom [fork][1] which includes several features such as:\n\n* Building QEMU as a shared library\n* APRR support for jailbroken iOS\n* ARM64 TCTI from @ktemkin (JIT-less iOS support)\n* SPICE ANGLE backend for hardware GL acceleration\n\nThese features, along with several others (which are undergoing code review and may not have made the latest stable release), allow our fork to be optimized for Darwin.\n\n### Hypervisor.framework\n\nQEMU includes support for the `hvf` accelerator which provides same architecture virtualization (x86 -> x86 or ARM64 -> ARM64) on macOS. This framework is not available on iOS.\n\n### UTMQemu\n\nQEMU is linked as a shared library. On iOS, there is no ability to use `fork`, XPC (directly), or any other way of launching a new process. As a result, we run the QEMU main loop in a pthread which for all intents and purposes work like normal (the key exception being that you cannot spawn multiple instances of QEMU and since QEMU does not properly clean up resources, you cannot re-launch QEMU). On macOS, XPC is used to launch QEMU in a new process. `UTMQemu` manages the pthread or XPC implementation.\n\n#### XPC Helper\n\nOn macOS, spawning new processes is permitted but due to App Sandbox security requirements, we need some additional \"bootstrapping\" code to launch QEMU properly. The added benefit is that we only have to provide a single bundle identifier for the \"launcher\" executable rather than a different identifier for each QEMU executable (required for App Sandbox).\n\n`QEMUHelper` is an XPC helper with its own App Sandbox separate from the UTM main application. This improves security by providing an additional layer of separation. However, due to this extra care has to be taken when passing file handles and other system resources from the main app. For example, the Unix socket file used to communicate with SPICE is stored in a shared App Group directory. For disk images and shared directories that cannot be stored in the App Group, we have to do a complicated sandbox dance to get the right access permissions.\n\n1. The main application opens a `NSOpenPanel`, allowing the user to select a file/directory outside the sandbox. This returns a `NSURL` with the right security scope attached. If we take a regular bookmark of `NSURL` and pass it through XPC, the XPC process should also have access once it reads the bookmark back into a `NSURL`.\n2. However, the access to that file is only valid while the app is open. As soon as you close it, the app loses those permissions and must either prompt the user to select the file again or store a \"security scoped bookmark.\" The issue here is that a security scoped bookmark is only valid for the sandbox that created it. If we take a security scoped bookmark and pass it directly to the XPC process, it cannot get back a `NSURL`.\n3. So the solution is to take a *standard* bookmark, pass it to the XPC process, have the XPC process then create a *security scoped* bookmark and pass it back to the main process. The main process can now store this bookmark and have it work as intended when it passes it to the XPC process next launch.\n\nThe XPC helper spawns `QEMULauncher` with an inherited sandbox. This means it can access files that are accessible to the helper XPC. The launcher process is what runs QEMU. When a new file is opened (for example a new disk image is mounted), the main application will pass a bookmark to the helper XPC where it will call `-startAccessingSecurityScopedResource` which also applies to the child process (`QEMULauncher`). This way, QEMU does not have to have any knowledge of the App Sandbox.\n\n#### iOS Helper\n\nOn iOS 26 and later, tools such as `qemu-img` run out of process in `iOSHelper`, an ExtensionKit extension embedded in the app (`iOSHelper-SE` for UTM SE, as an extension's bundle identifier must start with its host's). It implements the same `QEMUHelperProtocol` as `QEMUHelper`, but loads the tool into its own process like the in-thread QEMU does, so each helper process runs one tool and the app launches a new one for the next. `UTMHelperProcess` launches the helper through `AppExtensionProcess` and hands its connection to `UTMProcess`, which otherwise works as on macOS: the helper has no access to the app's container, so files are passed as bookmarks, whose implicit security scope lets the process that resolves them access the file. QEMU itself still runs in the app process on iOS.\n\n### UTMConfiguration\n\nVM configuration is stored in a PLIST format. This PLIST maps to either a `UTMQemuConfiguration` or `UTMAppleConfiguration` structure which stores the underlying configuration data in a `Codable` interface for easy serialization.\n\n### UTMQemuSystem\n\n`UTMQemuSystem` maps `UTMQemuConfiguration` to command line arguments used to launch QEMU.\n\n### UTMQemuManager\n\nAfter a VM is launched, `UTMQemuManager` provides run-time services though the QMP protocol. These services include stopping/pausing/resuming the VM, taking snapshots, switching between mouse and tablet, mounting removable disk images, and more. The underlying transport for QMP is JSON (over a socket), so `UTMJSONStream` marshals the data to and from `NSDictionary` objects.\n\n#### QAPI\n\nQMP protocol is defined by the QAPI schema which is provided as a set of JSON files in QEMU. QEMU uses these JSON files to generate wrapper C functions for internal usage. UTM includes a modified function generator derived from QEMU's own script (`qapi-gen.py`) along with modified QAPI C visitors (for `NSDictionary`). This allows UTM to use the same commands, structures, and events that QEMU uses in a transparent way.\n\nFor example, when UTM makes a call to some QAPI command such as `qmp_blockdev_change_medium`, the generated C functions will automatically marshal the function arguments into a `NSDictionary` object which `UTMJSONStream` converts into JSON and sends it over the QMP socket. When a response is received, `UTMJSONStream` converts the JSON into a `NSDictionary` object, which goes through the generated C functions to unmarshal into some C structure which is returned from `qmp_blockdev_change_medium`. This is all transparent to the caller as long as they understand that the function call will block until the response is received so it must not be called from the main thread.\n\n## Virtualization.framework\n\nOn Apple Silicon Macs running macOS 12 or later, `Virtualization.framework` is provided by Apple to run macOS 12 guests.\n\n### UTMAppleConfiguration\n\nAs the backend is different, the configuration format UTM uses is also different and is represented in `UTMAppleConfiguration`. As this configuration is handed in Swift, the Codable protocol is used for serialization instead of a `NSDictionary` backing used in `UTMQemuConfiguration`. The Codable backing is more extendible as it allows more complex data to be represented without a lot of boilerplate.\n\n## CocoaSpice\n\nUTM uses the SPICE front-end with QEMU because it has more versatility than VNC to handle things like USB forwarding, multiple displays, and the ability to use a SPICE agent running on the guest to share clipboard and change the resolution. [CocoaSpice][2] is provided as a Swift package and acts as Cocoa/Objective-C bindings for SPICE GTK. CocoaSpice also provides a bridge between the Pixman framebuffer that SPICE uses and Metal textures that is used by MetalKit to render to screen.\n\n### UTMSpiceIO\n\n`UTMSpiceIO` connects `CocoaSpice` to UTM and is used by `UTMQemuVirtualMachine` to control the SPICE client and respond to client events.\n\n## UTMVirtualMachine\n\n`UTMVirtualMachine` provides file I/O operations for creating and saving .utm VM bundles as well as controls for the platform-specific layer above to do high level tasks like starting and stopping the VM. It is highest level of the \"backend\", providing a platform independent view of UTM virtual machines.\n\n### UTMQemuVirtualMachine\n\nThis subclass manages QEMU+CocoaSpice backend VMs.\n\n### UTMAppleVirtualMachine\n\nThis subclass manages Apple Virtualization.framework backend VMs.\n\n## Frontend\n\n### SwiftUI\n\nThe frontend for UTM is designed mostly in SwiftUI 3.0. That means the minimum supported operating system is iOS 15 and macOS 12 and is the main reason there are no plans to back-port UTM to earlier versions. Most views are designed to work on both macOS and iOS.\n\n#### UTMData\n\n`UTMData` is a SwiftUI `ObservableObject` that contains the \"state\" and is the \"single source of truth\" for the home view. It stores the list of VMs and functions to create, modify, move, etc VMs on that list. `UTMConfigurable` provides the state for VM configuration views.\n\n### iOS VM Display\n\nThe VM display uses UIKit as SwiftUI is not mature enough to do everything UTM needs. This includes the custom keyboard accessory view implemented in a NIB for emulating keys that are not available on the standard iOS keyboard.\n\n### macOS VM Display\n\nOn macOS, the VM display uses AppKit for similar reasons above.\n\n[1]: https://github.com/utmapp/qemu\n[2]: https://github.com/utmapp/CocoaSpice\n\n<!-- File: .agents/skills/utm-review/SKILL.md (Tokens: ~1486 | Category: anthropic-skill) -->\n---\nname: utm-review\ndescription: Review pending UTM changes before they are submitted. Runs a standard correctness/quality code review of the current branch's diff AND audits it against UTM's contribution rules in CONTRIBUTING.md and AGENTS.md — scope discipline, commit/PR format, AI attribution, logging hooks, generated files, UI/design philosophy, and platform compatibility. Use this before opening or updating a UTM pull request, whenever the user asks to review UTM changes, or when the user runs /utm-review. The /utm-submit workflow expects this to have been run on the current changes first.\n---\n\n# utm-review\n\nAgent-neutral instructions — follow them with whatever tools your agent provides\n(Claude Code, Codex, OpenCode, Antigravity, …). This is the canonical copy; the\nper-agent entries under `.claude/commands/`, `.opencode/command/`, etc. just\npoint here.\n\nGate a UTM change through two passes before it can be submitted:\n\n1. **A standard code review** for real correctness bugs and worthwhile cleanups.\n2. **A UTM contribution-guideline audit** — the part specific to this project and\n   the reason this workflow exists. UTM has firm rules (especially for\n   AI-assisted contributions) that a generic review won't check.\n\nReport everything you find. This workflow *reviews and reports*; it does not\npush, commit, or rewrite history — that is `/utm-submit`'s job.\n\nIf you were invoked with an argument, treat it as a review effort level\n(`low|medium|high|max`); default to `high`.\n\n## Step 1 — Read the authoritative rules\n\nRead `CONTRIBUTING.md` and `AGENTS.md` at the repo root now — they are the single\nsource of truth for what makes an acceptable UTM contribution, and Step 4 audits\nthe diff against them. (`AGENTS.md` overrides default tool behavior for this repo\n— notably the commit-trailer policy.)\n\n## Step 2 — Establish the review scope\n\nThe change under review is everything that will land in the PR: commits on this\nbranch since it diverged from the base, plus any uncommitted edits.\n\n```sh\nbase=$(git merge-base HEAD origin/main)\ngit log --oneline \"$base\"..HEAD          # the PR's commits\ngit --no-stat diff \"$base\"...HEAD        # committed change\ngit --no-stat diff HEAD                  # uncommitted change (if any)\ngit status --porcelain                   # untracked files\n```\n\nKeep the list of changed files handy — several guideline checks below hinge on\n*which* files were touched.\n\n## Step 3 — Standard code review\n\nRun a standard code review of the pending diff to surface correctness bugs and\nreuse/simplification/efficiency cleanups. Use whatever review capability your\nagent provides — for example a `/code-review` or `/review` command — or perform a\nfocused review pass yourself if none exists. If the change includes uncommitted\nedits the review tool doesn't pick up, review those yourself to the same\nstandard. Collect the findings; you'll merge them into one report.\n\n## Step 4 — UTM contribution-guideline audit\n\nAudit the diff against **every** rule in the `CONTRIBUTING.md` and `AGENTS.md` you\njust read — those files *are* the checklist, so keeping it there (rather than\ncopied here) means it never drifts out of sync. Cite `file:line` and the specific\nrule for each issue you raise. Judge the diff against all of it: style and\nconcurrency, design philosophy, platform compatibility, dependency-upstreaming,\nand the rest — defer to the files' wording instead of restating it.\n\nThese few are high-value, repo-specific, and the easiest to miss, so check them\nexplicitly:\n\n- **AI attribution.** Every AI-assisted commit in `\"$base\"..HEAD` **must** carry an\n  `Assisted-by: AGENT:MODEL` trailer — many agents omit it by default, so a missing\n  trailer is a common, easy-to-miss finding, not an acceptable absence — and **no\n  `Co-authored-by`** (strip it — UTM takes full human responsibility per the\n  Linux-kernel policy). Verify with `git log --format='%H%n%B' \"$base\"..HEAD`.\n- **Scope discipline.** One feature/fix; no edits, refactors, reformatting, or\n  whitespace/header churn in unrelated files; no stray logging.\n- **Generated files not hand-edited** — `Configuration/QEMUConstantGenerated.swift`,\n  `Scripting/UTMScripting.swift`, QAPI/QMP wrappers. Edit the generator instead.\n- **Logging** goes through `UTMLogging`/`logging` at `debug` level (warnings and\n  errors excepted); bring-up-only logging is removed before committing.\n- **UI strings** avoid jargon (\"GPU Acceleration\" not \"Venus\", \"Apple Silicon\"\n  not ARM64); commit titles are `component: short description` explaining *why*.\n\n## Step 5 — Report\n\nProduce one consolidated report:\n\n- A one-line summary of the change and its apparent scope.\n- **Correctness/quality findings** (from Step 3), each with `file:line`.\n- **Guideline findings** (from Step 4), each with `file:line` and the rule cited.\n- A short verdict: is this ready to submit, or what must change first? Call out\n  any blocker that `/utm-submit` will trip on (e.g. a `Co-authored-by` trailer,\n  an unrelated-file edit, or a missing `Assisted-by`).\n\nBe honest about uncertainty and avoid nitpicks a senior reviewer wouldn't raise\n(pre-existing issues, things a compiler/linter would catch, lines the change\ndidn't touch). The goal is a change that sails through human review on the UTM\nrepo, not a wall of pedantry.\n\n## Step 6 — Record that the review ran (handoff to /utm-submit)\n\n`/utm-submit` checks whether the current changes were reviewed. Leave a marker so\nit can confirm even across sessions or a different agent:\n\n```sh\ngitdir=$(git rev-parse --git-dir)\nhash=$( { git rev-parse HEAD; git diff \"$(git merge-base HEAD origin/main)\"...HEAD; git diff HEAD; } | git hash-object --stdin )\nprintf 'branch=%s\\nhead=%s\\ndiff_hash=%s\\nat=%s\\n' \"$(git rev-parse --abbrev-ref HEAD)\" \"$(git rev-parse HEAD)\" \"$hash\" \"$(date -u +%FT%TZ)\" > \"$gitdir/utm-review-marker\"\n```\n\nThen end with a clear, recognizable line, e.g.:\n`✅ utm-review complete — <branch> @ <short-sha>, N findings (M blockers).`\n\nThe `diff_hash` lets `/utm-submit` tell whether the changes it's about to submit\nare exactly the ones you reviewed, or whether they've been edited since.\n\n<!-- File: .agents/skills/utm-submit/SKILL.md (Tokens: ~2810 | Category: anthropic-skill) -->\n---\nname: utm-submit\ndescription: Submit the current UTM change as a pull request to github.com/utmapp/UTM. Ensures /utm-review has run, then either updates an existing PR (squashing each new edit into the commit that owns it and force-pushing) or opens a new PR (gathering issue links and a human-testing attestation). Use this when the user wants to submit, open, update, or push a UTM pull request, or runs /utm-submit.\n---\n\n# utm-submit\n\nAgent-neutral instructions — follow them with whatever tools your agent provides\n(Claude Code, Codex, OpenCode, Antigravity, …). This is the canonical copy; the\nper-agent entries under `.claude/commands/`, `.opencode/command/`, etc. just\npoint here.\n\nTurn the current change into a clean pull request against `utmapp/UTM`. Two\npaths: **update an existing PR** (squash edits into the commits they belong to,\nthen force-push) or **open a new PR** (collect issue links + a human-testing\nattestation, then create it).\n\nRead `CONTRIBUTING.md` and `AGENTS.md` first — they govern commit/PR format and\nthe AI-attribution policy enforced below. If you were invoked with an argument,\ntreat it as a PR number or URL to update.\n\n## Step 1 — Confirm the change was reviewed\n\n`/utm-submit` must never run on un-reviewed changes. Determine whether\n`/utm-review` was run on the *current* changes, in this order:\n\n1. If you already ran `/utm-review` earlier in this session against the current\n   changes, proceed.\n2. Otherwise read `\"$(git rev-parse --git-dir)/utm-review-marker\"` and recompute\n   the pending-diff hash exactly as utm-review did, then compare to `diff_hash`:\n   ```sh\n   { git rev-parse HEAD; git diff \"$(git merge-base HEAD origin/main)\"...HEAD; git diff HEAD; } | git hash-object --stdin\n   ```\n   If it matches the marker's `diff_hash`, the current changes were reviewed\n   (possibly in an earlier session or by another agent) — proceed, and say so. If\n   the marker exists but the hash differs, the code was edited after review —\n   treat as un-reviewed.\n3. Otherwise ask the user: *\"I can't confirm /utm-review ran on the current\n   changes. How do you want to proceed?\"* — offer: **Run /utm-review now**\n   (recommended; run that workflow, then continue), **I've already reviewed it\n   previously** (continue), **Cancel**.\n\nDo not silently skip this gate.\n\n## Step 2 — Locate the repo, branch, and any existing PR\n\n```sh\ngit remote -v                                   # find the remote pointing at utmapp/UTM\nbranch=$(git rev-parse --abbrev-ref HEAD)\n```\n\n- **Never submit from the default branch.** If `branch` is `main` (or otherwise\n  tracks the upstream default), stop and ask the user to move the work onto a\n  feature branch first (`git switch -c component/short-description`) — you cannot\n  open a `main → main` PR.\n- This workflow uses GitHub's `gh` CLI for everything that talks to GitHub. **If\n  `gh` is not installed or not authenticated**, tell the user and offer to set it\n  up: `brew install gh` then `gh auth login` (the login is interactive — have the\n  user run it themselves in their terminal). If they decline, fall back to the\n  manual paths noted in Steps 3a/3b.\n- Detect an existing open PR for this branch (or honor a PR number/URL you were\n  given):\n\n  ```sh\n  gh pr view \"$branch\" --repo utmapp/UTM --json number,url,state,baseRefName,headRefName 2>/dev/null\n  ```\n\n  An open PR → **Step 3a (update)**. No PR → **Step 3b (new)**.\n\n## Step 3a — Update an existing PR (squash, then force-push)\n\nUTM does **not** keep \"address review feedback\" commits. Every edit belongs in\nthe commit that introduced the lines it touches. The change history within a\nsingle PR must stay clean.\n\n1. **Express the update as working-tree changes on top of the PR's published\n   commits.** If you committed the update locally, undo just those commits so\n   their content returns to the working tree, leaving HEAD at the published PR\n   tip:\n   ```sh\n   git reset \"$(git rev-parse --abbrev-ref --symbolic-full-name @{u})\"   # remote tracking ref of this branch\n   ```\n   Now `git diff HEAD` is the full set of edits to fold in, and the commits in\n   `base..HEAD` (base = `git merge-base HEAD origin/main`) are exactly the PR's\n   commits.\n\n2. **Save a recovery point** before rewriting history:\n   `git branch utm-submit-backup/$branch` (or note `git rev-parse HEAD`).\n\n3. **Assign each chunk to the commit that owns it.** Walk the hunks of\n   `git diff HEAD` (use `git diff -U0 HEAD` for tight, one-change-per-hunk\n   output). For each hunk, `git blame` the lines it modifies on HEAD to find the\n   commit that last changed them:\n   ```sh\n   git blame -L <start>,<end> HEAD -- <file>\n   ```\n   - If that commit is one of the PR's commits (in `base..HEAD`) → this chunk gets\n     **squashed into that commit**.\n   - If the lines are owned by a commit *outside* `base..HEAD` (code that shipped\n     before this PR), or the chunk adds a brand-new file/section unrelated to the\n     existing commits → it is a **logically distinct change** and becomes its own\n     **new commit** (the only time a new commit is allowed here). For a pure\n     insertion, blame the adjacent line to decide the owner.\n   - When a hunk's lines are owned by several PR commits, fold it into the newest\n     of them. Precision isn't critical — use judgment; the goal is that each\n     commit ends up self-consistent.\n\n4. **Fold the chunks in.** The reliable, non-interactive mechanism:\n   - Stage the chunks belonging to a target commit and create a fixup:\n     `git commit --fixup=<target-sha>` (stage whole files with `git add <file>`\n     when all of a file's changes map to one commit; for a file whose changes\n     span multiple targets, apply just the relevant hunks to the index with\n     `git apply --cached <patch>` — `git add -p` is interactive and may be\n     unavailable to your agent).\n   - Commit any logically-distinct chunks as normal new commits with a proper\n     `component: short description` message.\n   - Replay so the fixups collapse into their targets, run headless:\n     ```sh\n     GIT_SEQUENCE_EDITOR=true GIT_EDITOR=true git rebase -i --autosquash \"$base\"\n     ```\n     (The no-op editors make `-i --autosquash` run without prompting.)\n\n5. **Verify.** `git log --oneline \"$base\"..HEAD` should show the original commits\n   (plus any genuinely-distinct new ones) with **no `fixup!` lines left** and a\n   clean working tree. Confirm `git range-diff \"@{u}\"...HEAD` reflects only the\n   intended edits.\n\n6. **Force-push (safely).** Show the user the rewritten log and confirm, then:\n   ```sh\n   git push --force-with-lease\n   ```\n   `--force-with-lease` refuses to clobber unexpected remote work, unlike\n   `--force`. The PR updates automatically. **The workflow is done** — do not\n   re-prompt for issues or testing on an update.\n\n   *No `gh`?* The squash is pure git; only the push needs the remote. Push with\n   `--force-with-lease` to the branch's remote as usual.\n\n## Step 3b — Open a new PR\n\n1. **Make sure the change is committed cleanly.** If there are uncommitted edits,\n   commit them following the rules in *Commit & message policy* below — ideally\n   one commit (the PR must be a single feature/fix).\n\n2. **Push the branch** to the head repo (the user's fork, or `utmapp/UTM` if they\n   have push access). Confirm the remote if ambiguous:\n   `git push -u <remote> \"$branch\"`.\n\n3. **Find the issue(s) this resolves.** Search open issues for relevance and\n   present recommendations:\n   ```sh\n   gh issue list --repo utmapp/UTM --state open --search \"<keywords from the change>\" --limit 10\n   ```\n   Derive keywords from the touched component and the change's purpose. Show the\n   top few as `#<number> — <title>` with one-line relevance notes. Then ask the\n   user (free text — they may enter a GitHub issue **ID**, a **URL**, a **list**\n   of either, or \"none\"). Parse whatever they enter into issue numbers; each\n   becomes a `Resolves #<n>` line in the PR body (so merging closes it). If they\n   say none, link nothing.\n\n4. **Human-testing attestation (required for UTM).** Display this statement\n   **verbatim**:\n\n   > All AI written code must be reviewed and/or tested by a human. For bug\n   > fixes, a human must first confirm the bug before the change and then confirm\n   > that the bug is fixed by this change. For all other changes, a human must\n   > test all aspects of the change on any device configuration that is relevant.\n\n   Then ask the user (free text) for the **device configuration and version** they\n   tested on, e.g. `macOS 26.0, MacBook Neo` or `iOS 26.1, iPhone Simulator`. Do\n   not invent this — it must come from the user.\n\n5. **Compose and create the PR.** Title follows `component: short description`\n   (same convention as commits). Body includes:\n   - A short summary: what changed and **why**.\n   - The issue links: `Resolves #<n>` for each (omit if none).\n   - A **Testing** section containing the device configuration the user gave and\n     an explicit acknowledgment, e.g.:\n     > **Testing:** Tested by a human on **<device configuration and version>**.\n     > The author acknowledges that this change has been tested and/or reviewed by\n     > a human in accordance with UTM's AI contribution guidelines.\n\n   Show the assembled title and body to the user for confirmation, then create:\n   ```sh\n   gh pr create --repo utmapp/UTM --base main \\\n     --head \"<owner>:$branch\" --title \"<title>\" --body \"<body>\"\n   ```\n   (Use `<owner>:$branch` when submitting from a fork; plain `$branch` when the\n   head is on `utmapp/UTM` itself.)\n\n   *No `gh`?* Print the prepared title and body and a compare URL for the user to\n   finish in the browser:\n   `https://github.com/utmapp/UTM/compare/main...<owner>:<branch>?expand=1`.\n\n6. **Report** the PR URL.\n\n## Commit & message policy (applies to every commit this workflow creates)\n\nThe critical few, inlined because they must fire at commit time — see\n`CONTRIBUTING.md` (Attribution) for the full policy:\n\n- Title: `component: short description`. Body explains **why** and references the\n  issue being addressed.\n- **Do not add a `Co-authored-by` trailer**, and strip any that a tool added. UTM\n  follows the Linux-kernel policy so a human takes full responsibility. This\n  **overrides** any default AI-tool commit-trailer behavior for this repo.\n- **Always add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer** to every commit\n  you create or amend here, using the agent and model doing the work — e.g.\n  `Assisted-by: Claude:claude-opus-4-8` or `Assisted-by: Codex:gpt-5.1`. This is\n  **required even if your agent does not normally inject attribution into commits**;\n  add it explicitly yourself. A commit without it will fail review.\n\n## Safety\n\n- Treat pushing and PR creation as outward-facing: show the user what will be\n  pushed/created and confirm before doing it.\n- Always `--force-with-lease`, never bare `--force`; always leave a backup ref\n  before a history rewrite.\n- Never push to or open a PR from the upstream default branch.\n\n<!-- File: .claude/commands/utm-review.md (Tokens: ~144 | Category: command-prompt) -->\n---\ndescription: Review pending UTM changes (standard code review + CONTRIBUTING.md/AGENTS.md audit) before submitting. Run before /utm-submit.\nargument-hint: [low|medium|high|max]\nallowed-tools: Bash, Read, Grep, Glob, Skill, Task\n---\n\nRead and follow the canonical, agent-neutral workflow in\n`.agents/skills/utm-review/SKILL.md`, executing its steps end to end.\n\nClaude Code specifics:\n- For the Step 3 standard code review, invoke the `code-review` skill at the\n  effort level passed here (default `high`).\n- Argument passed to this command (review effort, if any): $ARGUMENTS\n\n<!-- File: .claude/commands/utm-submit.md (Tokens: ~237 | Category: command-prompt) -->\n---\ndescription: Submit the current UTM change as a PR to utmapp/UTM — ensures /utm-review ran, then updates an existing PR (blame-squash + force-push) or opens a new one (issue links + human-test attestation).\nargument-hint: [PR number or URL — for updates]\nallowed-tools: Bash, Read, Grep, Glob, Skill, Task, AskUserQuestion\n---\n\nRead and follow the canonical, agent-neutral workflow in\n`.agents/skills/utm-submit/SKILL.md`, executing its steps end to end.\n\nClaude Code specifics:\n- Where the workflow says \"ask the user\" for a fixed set of choices (e.g. the\n  Step 1 review gate), use AskUserQuestion.\n- To run the Step 1 review gate (\"/utm-review now\"), read and follow\n  `.agents/skills/utm-review/SKILL.md`.\n- For the interactive `gh auth login`, suggest the user type `! gh auth login`\n  so its output lands in this session.\n- Argument passed to this command (PR number or URL, if updating): $ARGUMENTS\n</yakaai_skills>"}